def _delete_change(self, req, id, ts, field): """Delete the change to a field on the specified ticket at the specified timestamp.""" ticket = Ticket(self.env, id) db = self.env.get_db_cnx() cursor = db.cursor() dt = to_datetime(int(ts)) changelog = ticket.get_changelog(dt) if changelog: if field == 'change': # Iterate over all the fields that have changed for change in changelog: self._delete_change(req, id, ts, change[2]) elif field == 'attachment': # Delete the attachment cursor.execute(""" DELETE FROM attachment WHERE type = 'ticket' AND id = %s AND time = %s""", (id, ts)) else: # Revert the field to its old value if it's the newest change to that field exists_newer = [True for change in ticket.get_changelog() if to_timestamp(change[0]) > int(ts) and field == change[2]] if field != 'comment' and not exists_newer: oldval = [change[3] for change in changelog if change[2] == field] if oldval: custom_fields = [f['name'] for f in ticket.fields if f.get('custom')] if field in custom_fields: cursor.execute(""" UPDATE ticket_custom SET value=%s WHERE ticket=%s AND name=%s""", (oldval[0], id, field)) else: cursor.execute(""" UPDATE ticket SET %s=%%s WHERE id=%%s""" % field, (oldval[0], id)) # Delete the ticket change cursor.execute(""" DELETE FROM ticket_change WHERE ticket=%s AND time=%s AND field=%s """, (id, ts, field)) else: raise TracError(""" Ticket change with timestamp %s (datetime: %s) not found in ticket #%s changelog. """ % (ts, dt, id)) db.commit() msg = "Change to field \"%s\" of ticket #%s at %s has been deleted." \ % (field, id, dt) add_notice(req, msg) self.log.debug("TicketDelete: " + msg)
def get_changelog( self , ticketid): t = Ticket(self.macroenv.tracenv, ticketid) try: return( t.get_changelog() ) except: self.macroenv.tracenv.log.warning("get_changelog failed on ticket %s", ticketid) return [] # no changelogs
def get_changelog( self , ticketid): t = Ticket(self.macroenv.tracenv, ticketid) try: return( t.get_changelog() ) except: self.macroenv.tracenv.log.warn("get_changelog failed on ticket %s", ticketid) return [] # no changelogs
def test_changelog_with_attachment(self): """Verify ordering of attachments and comments in the changelog.""" tkt_id = self._insert_ticket("Test", reporter="joe", component="foo") ticket = Ticket(self.env, tkt_id) t1 = datetime(2001, 1, 1, 1, 1, 1, 0, utc) ticket.save_changes("jane", "Testing", t1) t2 = datetime(2001, 1, 1, 1, 1, 2, 0, utc) db = self.env.get_db_cnx() cursor = db.cursor() cursor.execute( "INSERT INTO attachment (type,id,filename,size,time," " description,author,ipnr) " "VALUES ('ticket',%s,'file.txt',1234,%s," " 'My file','mark','')", (str(tkt_id), to_utimestamp(t2)), ) db.commit() t3 = datetime(2001, 1, 1, 1, 1, 3, 0, utc) ticket.save_changes("jim", "Other", t3) log = ticket.get_changelog() self.assertEqual(4, len(log)) self.assertEqual((t1, "jane", "comment", "1", "Testing", True), log[0]) self.assertEqual( [(t2, "mark", "attachment", "", "file.txt", False), (t2, "mark", "comment", "", "My file", False)], sorted(log[1:3]), ) self.assertEqual((t3, "jim", "comment", "2", "Other", True), log[3])
def test_prop_whitespace_change_is_not_saved(self): ticket = Ticket(self.env) ticket.populate({'summary': 'ticket summary'}) ticket.insert() ticket['summary'] = ' ticket summary ' ticket.save_changes() self.assertEqual(0, len(ticket.get_changelog()))
def test_changelog_with_reverted_change(self): tkt_id = self._insert_ticket("Test", reporter="joe", component="foo") ticket = Ticket(self.env, tkt_id) ticket["component"] = "bar" ticket["component"] = "foo" now = datetime(2001, 1, 1, 1, 1, 1, 0, utc) ticket.save_changes("jane", "Testing", now) self.assertEqual([(now, "jane", "comment", "1", "Testing", True)], list(ticket.get_changelog()))
def test_can_save_ticket_without_explicit_username(self): ticket = Ticket(self.env) ticket.insert() ticket['summary'] = 'another summary' ticket.save_changes() for change in ticket.get_changelog(): self.assertIsNone(change[1])
def test_changelog_with_reverted_change(self): tkt_id = self._insert_ticket('Test', reporter='joe', component='foo') ticket = Ticket(self.env, tkt_id) ticket['component'] = 'bar' ticket['component'] = 'foo' now = datetime(2001, 1, 1, 1, 1, 1, 0, utc) ticket.save_changes('jane', 'Testing', now) self.assertEqual([(now, 'jane', 'comment', '1', 'Testing', True)], list(ticket.get_changelog()))
def test_can_save_ticket_without_explicit_username(self): ticket = Ticket(self.env) ticket.insert() ticket['summary'] = 'another summary' ticket.save_changes() for change in ticket.get_changelog(): self.assertEqual(None, change[1])
def test_can_save_ticket_without_explicit_comment(self): ticket = Ticket(self.env) ticket.insert() ticket['summary'] = 'another summary' ticket.save_changes('foo') changes = ticket.get_changelog() comment_change = [c for c in changes if c[2] == 'comment'][0] self.assertEqual('1', comment_change[3]) self.assertEqual('', comment_change[4])
def test_can_save_ticket_without_explicit_comment(self): ticket = Ticket(self.env) ticket.insert() ticket["summary"] = "another summary" ticket.save_changes("foo") changes = ticket.get_changelog() comment_change = [c for c in changes if c[2] == "comment"][0] self.assertEqual("1", comment_change[3]) self.assertEqual("", comment_change[4])
def testComplexNotification(self): env = Environment(ENV) ticket1 = Ticket(env) ticket1['summary'] = 'Fix please.' ticket1['status'] = 'new' ticket1_id = ticket1.insert() ticket2 = Ticket(env) ticket2['summary'] = 'This one too, thanks.' ticket2['status'] = 'new' ticket2_id = ticket2.insert() ticket1 = Ticket(env, ticket1_id) self.assertEqual(ticket1['status'], 'new') self.assertEqual(ticket1['resolution'], '') ticket2 = Ticket(env, ticket2_id) self.assertEqual(ticket2['status'], 'new') self.assertEqual(ticket2['resolution'], '') message1 = "Fix #%d: you're welcome." % ticket1_id self.makeGitCommit(ALTGIT, 'newfile', 'with some new content', message1) message2 = "See #%d: you bet." % ticket2_id self.makeGitCommit(ALTGIT, 'newfile', 'with improved content', message2) self.openGitHubHook(2, 'alt') ticket1 = Ticket(env, ticket1_id) self.assertEqual(ticket1['status'], 'closed') self.assertEqual(ticket1['resolution'], 'fixed') changelog1 = ticket1.get_changelog() self.assertEqual(len(changelog1), 4) self.assertEqual(changelog1[0][2], 'comment') self.assertIn("you're welcome", changelog1[0][4]) ticket2 = Ticket(env, ticket2_id) self.assertEqual(ticket2['status'], 'new') self.assertEqual(ticket2['resolution'], '') changelog2 = ticket2.get_changelog() self.assertEqual(len(changelog2), 1) self.assertEqual(changelog2[0][2], 'comment') self.assertIn("you bet", changelog2[0][4])
def test_process_commit(self): commits = [] time = self.now for data in COMMITS: c = GitHubCommit(self.env, git_url=GIT_URL, **data) c.time = time c.save() commits.append(c) time = time + 60 # parse commit for the first time (with a clone) self.ticker_updater.process_commit(commits[0]) tkt1 = Ticket(self.env, 1) change_log = tkt1.get_changelog() eq_(1, len(change_log)) eq_('Damien Lebrun <*****@*****.**>', change_log[0][1]) eq_( 'Commit %(id)s:\n\n%(message)s\n\nSource: [%(url)s]' % COMMITS[0], change_log[0][4]) # parse same commit (still clone), # should not update the ticket self.ticker_updater.process_commit(commits[1]) tkt1 = Ticket(self.env, 1) change_log = tkt1.get_changelog() eq_(1, len(change_log)) # parse the commit from the main git repository # Should update the repository self.ticker_updater.process_commit(commits[2]) tkt1 = Ticket(self.env, 1) change_log = tkt1.get_changelog() eq_(3, len(change_log)) eq_('Damien Lebrun <*****@*****.**>', change_log[1][1]) eq_( 'Commit %(id)s:\n\n%(message)s\n\nSource: [%(url)s]' % COMMITS[2], change_log[1][4]) eq_('Damien Lebrun <*****@*****.**>', change_log[2][1]) eq_('resolution',change_log[2][2]) eq_('fixed',change_log[2][4])
def test_subsecond_change(self): """Perform two ticket changes within a second.""" tkt_id = self._insert_ticket('Test', reporter='joe', component='foo') ticket = Ticket(self.env, tkt_id) t1 = datetime(2001, 1, 1, 1, 1, 1, 123456, utc) ticket.save_changes('jane', 'Testing', t1) t2 = datetime(2001, 1, 1, 1, 1, 1, 123789, utc) ticket.save_changes('jim', 'Other', t2) log = ticket.get_changelog() self.assertEqual(2, len(log)) self.assertEqual((t1, 'jane', 'comment', '1', 'Testing', True), log[0]) self.assertEqual((t2, 'jim', 'comment', '2', 'Other', True), log[1])
def test_subsecond_change(self): """Perform two ticket changes within a second.""" tkt_id = self._insert_ticket("Test", reporter="joe", component="foo") ticket = Ticket(self.env, tkt_id) t1 = datetime(2001, 1, 1, 1, 1, 1, 123456, utc) ticket.save_changes("jane", "Testing", t1) t2 = datetime(2001, 1, 1, 1, 1, 1, 123789, utc) ticket.save_changes("jim", "Other", t2) log = ticket.get_changelog() self.assertEqual(2, len(log)) self.assertEqual((t1, "jane", "comment", "1", "Testing", True), log[0]) self.assertEqual((t2, "jim", "comment", "2", "Other", True), log[1])
def test_changelog(self): tkt_id = self._insert_ticket('Test', reporter='joe', component='foo', milestone='bar') ticket = Ticket(self.env, tkt_id) ticket['component'] = 'bar' ticket['milestone'] = 'foo' now = datetime(2001, 1, 1, 1, 1, 1, 0, utc) ticket.save_changes('jane', 'Testing', now) changelog = sorted(ticket.get_changelog()) self.assertEqual([(now, 'jane', 'comment', '1', 'Testing', True), (now, 'jane', 'component', 'foo', 'bar', True), (now, 'jane', 'milestone', 'bar', 'foo', True)], changelog)
def _delete_change(self, id, ts, field=None): """Delete the change on the given ticket at the given timestamp.""" db = self.env.get_db_cnx() cursor = db.cursor() ticket = Ticket(self.env,id) if field: if field == 'attachment': pass # Better handling still pending else: custom_fields = [f['name'] for f in ticket.fields if f.get('custom')] if field != "comment" and not [1 for time, author, field2, oldval, newval, _ in ticket.get_changelog() if time > ts and field == field2]: oldval = [old for _, _, field2, old, _, _ in ticket.get_changelog(ts) if field2 == field][0] if field in custom_fields: cursor.execute("UPDATE ticket_custom SET value=%s WHERE ticket=%s AND name=%s", (oldval, id, field)) else: cursor.execute("UPDATE ticket SET %s=%%s WHERE id=%%s" % field, (oldval, id)) cursor.execute("DELETE FROM ticket_change WHERE ticket = %s AND time = %s AND field = %s", (id, ts, field)) else: for _, _, field, _, _, _ in ticket.get_changelog(ts): self._delete_change(id, ts, field) db.commit()
def test_changelog_with_reverted_change(self): tkt_id = self._insert_ticket('Test', reporter='joe', component='foo') ticket = Ticket(self.env, tkt_id) ticket['component'] = 'bar' ticket['component'] = 'foo' ticket.save_changes('jane', 'Testing', when=42) for t, author, field, old, new, permanent in ticket.get_changelog(): self.assertEqual((42, 'jane', True), (t, author, permanent)) if field == 'comment': self.assertEqual(('', 'Testing'), (old, new)) else: self.fail('Unexpected change (%s)' % ((t, author, field, old, new),))
def test_changelog_with_reverted_change(self): tkt_id = self._insert_ticket('Test', reporter='joe', component='foo') ticket = Ticket(self.env, tkt_id) ticket['component'] = 'bar' ticket['component'] = 'foo' ticket.save_changes('jane', 'Testing', when=42) for t, author, field, old, new in ticket.get_changelog(): self.assertEqual((42, 'jane'), (t, author)) if field == 'comment': self.assertEqual(('', 'Testing'), (old, new)) else: self.fail('Unexpected change (%s)' % ((t, author, field, old, new), ))
def _delete_change(self, id, ts, field=None): """Delete the change on the given ticket at the given timestamp.""" db = self.env.get_db_cnx() cursor = db.cursor() ticket = Ticket(self.env, id) if field: if field == 'attachment': pass # Better handling still pending else: custom_fields = [ f['name'] for f in ticket.fields if f.get('custom') ] if field != "comment" and not [ 1 for time, author, field2, oldval, newval, _ in ticket.get_changelog() if time > ts and field == field2 ]: oldval = [ old for _, _, field2, old, _, _ in ticket.get_changelog(ts) if field2 == field ][0] if field in custom_fields: cursor.execute( "UPDATE ticket_custom SET value=%s WHERE ticket=%s AND name=%s", (oldval, id, field)) else: cursor.execute( "UPDATE ticket SET %s=%%s WHERE id=%%s" % field, (oldval, id)) cursor.execute( "DELETE FROM ticket_change WHERE ticket = %s AND time = %s AND field = %s", (id, ts, field)) else: for _, _, field, _, _, _ in ticket.get_changelog(ts): self._delete_change(id, ts, field) db.commit()
def test_changelog(self): tkt_id = self._insert_ticket("Test", reporter="joe", component="foo", milestone="bar") ticket = Ticket(self.env, tkt_id) ticket["component"] = "bar" ticket["milestone"] = "foo" now = datetime(2001, 1, 1, 1, 1, 1, 0, utc) ticket.save_changes("jane", "Testing", now) changelog = sorted(ticket.get_changelog()) self.assertEqual( [ (now, "jane", "comment", "1", "Testing", True), (now, "jane", "component", "foo", "bar", True), (now, "jane", "milestone", "bar", "foo", True), ], changelog, )
def test_action_with_state_change(self): """Actions can have change status.""" self.env.config.set('ticket-workflow', 'embiggen', '* -> big') first_ticket_id = self._insert_ticket('Test 1', reporter='joe', status='small') second_ticket_id = self._insert_ticket('Test 2', reporter='joe') selected_tickets = [first_ticket_id, second_ticket_id] batch = BatchModifyModule(self.env) batch._save_ticket_changes(self.req, selected_tickets, {}, '', 'embiggen') ticket = Ticket(self.env, int(first_ticket_id)) changes = ticket.get_changelog() self.assertFieldChanged(first_ticket_id, 'status', 'big') self.assertFieldChanged(second_ticket_id, 'status', 'big')
def _populate_action_comments(self): t = Ticket(self.env, self.testrun) history = t.get_changelog() pat = re.compile(r"(\b(ta_id)=([0-9]+))") for change in history: # we get a list of change attributes [...,...,"user","comment",...] # (datetime.datetime(2012, 6, 25, 7, 19, 7, 742580, tzinfo=<FixedOffset "UTC" 0:00:00>), # u'testadmin', # u'comment', # u'2', # u'\npassed with comment "Check menu and toolbar" in # [http://localhorst:8000/trac/TestManager/general/testcase/23 TestCase #23] # for [wiki:Testcases/UC012?revision=None]\n\ntoo slow\n', 1) match = pat.search(change[4]) # if the ta_id is found, we have an association if match and int(match.group(3)) == self.id: self._comments.append({"user": change[1], "text": change[4]})
def test_changelog(self): tkt_id = self._insert_ticket('Test', reporter='joe', component='foo', milestone='bar') ticket = Ticket(self.env, tkt_id) ticket['component'] = 'bar' ticket['milestone'] = 'foo' now = datetime(2001, 1, 1, 1, 1, 1, 0, utc) ticket.save_changes('jane', 'Testing', now) for t, author, field, old, new, permanent in ticket.get_changelog(): self.assertEqual((now, 'jane', True), (t, author, permanent)) if field == 'component': self.assertEqual(('foo', 'bar'), (old, new)) elif field == 'milestone': self.assertEqual(('bar', 'foo'), (old, new)) elif field == 'comment': self.assertEqual(('', 'Testing'), (old, new)) else: self.fail('Unexpected change (%s)' % ((t, author, field, old, new),))
def test_changelog_with_attachment(self): """Verify ordering of attachments and comments in the changelog.""" tkt_id = self._insert_ticket('Test', reporter='joe', component='foo') ticket = Ticket(self.env, tkt_id) t1 = datetime(2001, 1, 1, 1, 1, 1, 0, utc) ticket.save_changes('jane', 'Testing', t1) t2 = datetime(2001, 1, 1, 1, 1, 2, 0, utc) self.env.db_transaction(""" INSERT INTO attachment (type, id, filename, size, time, description, author, ipnr) VALUES ('ticket',%s,'file.txt',1234,%s, 'My file','mark','') """, (str(tkt_id), to_utimestamp(t2))) t3 = datetime(2001, 1, 1, 1, 1, 3, 0, utc) ticket.save_changes('jim', 'Other', t3) log = ticket.get_changelog() self.assertEqual(4, len(log)) self.assertEqual((t1, 'jane', 'comment', '1', 'Testing', True), log[0]) self.assertEqual([(t2, 'mark', 'attachment', '', 'file.txt', False), (t2, 'mark', 'comment', '', 'My file', False)], sorted(log[1:3])) self.assertEqual((t3, 'jim', 'comment', '2', 'Other', True), log[3])
def test_action_with_side_effects(self): """Actions can have operations with side effects.""" self.env.config.set('ticket-workflow', 'buckify', '* -> *') self.env.config.set('ticket-workflow', 'buckify.operations', 'set_owner') self.req.args = {} self.req.args['action_buckify_reassign_owner'] = 'buck' first_ticket_id = self._insert_ticket('Test 1', reporter='joe', owner='foo') second_ticket_id = self._insert_ticket('Test 2', reporter='joe') selected_tickets = [first_ticket_id, second_ticket_id] batch = BatchModifyModule(self.env) batch._save_ticket_changes(self.req, selected_tickets, {}, '', 'buckify') ticket = Ticket(self.env, int(first_ticket_id)) changes = ticket.get_changelog() self.assertFieldChanged(first_ticket_id, 'owner', 'buck') self.assertFieldChanged(second_ticket_id, 'owner', 'buck')
def testNotification(self): ticket = Ticket(self.env) ticket['summary'] = 'I need a commit!' ticket['status'] = 'new' ticket_id = ticket.insert() ticket = Ticket(self.env, ticket_id) self.assertEqual(ticket['status'], 'new') self.assertEqual(ticket['resolution'], '') message = "Fix #%d: here you go." % ticket_id self.makeGitCommit(GIT, 'newfile', 'with some new content', message) self.openGitHubHook() ticket = Ticket(self.env, ticket_id) self.assertEqual(ticket['status'], 'closed') self.assertEqual(ticket['resolution'], 'fixed') changelog = ticket.get_changelog() self.assertEqual(len(changelog), 4) self.assertEqual(changelog[0][2], 'comment') self.assertIn("here you go", changelog[0][4])
def background_cron(self): self.env.log.info('TracTaskCare _cron()') t = Timer(self.cron_period, self.background_cron) t.start() headers = { 'Content-Type': 'application/json', self.auth_x_httpheader_key: self.auth_x_httpheader_value, self.auth_httpheader_key: self.auth_httpheader_value, } url = self.resource_getalltickets res = requests.get(url, headers=headers) payload = res.json()['payload'] for taskcare_ticket in payload: if taskcare_ticket['status'] == 'Solved': continue if taskcare_ticket[ self.filter_column_key] != self.filter_column_value: continue # taskTitle = 'TKT-xxxxx' with self.env.db_query as db: sql = 'SELECT ticket FROM ticket_custom WHERE name = %s AND value = %s;' rows = db(sql, (self.taskcare_column, taskcare_ticket['taskTitle'])) if 0 == len(rows): # Create a new ticket and sync ticket = Ticket(self.env) ticket['description'] = taskcare_ticket['Description'] ticket['summary'] = taskcare_ticket['subject'] ticket[self.taskcare_column] = taskcare_ticket['taskTitle'] ticket.insert() else: ticket = Ticket(self.env, tkt_id=int(rows[0])) trac_comments = ticket.get_changelog() taskcare_comments = taskcare_ticket['taskComments']
def test_create_ticket(self): # Multiple test in one method, this sucks # 1. Creating ticket ticket = Ticket(self.env) ticket['reporter'] = 'santa' ticket['summary'] = 'Foo' ticket['foo'] = 'This is a custom field' self.assertEqual('santa', ticket['reporter']) self.assertEqual('Foo', ticket['summary']) self.assertEqual('This is a custom field', ticket['foo']) ticket.insert() # Retrieving ticket ticket2 = Ticket(self.env, 1) self.assertEqual(1, ticket2.id) self.assertEqual('santa', ticket2['reporter']) self.assertEqual('Foo', ticket2['summary']) self.assertEqual('This is a custom field', ticket2['foo']) # Modifying ticket ticket2['summary'] = 'Bar' ticket2['foo'] = 'New value' ticket2.save_changes('santa', 'this is my comment') # Retrieving ticket ticket3 = Ticket(self.env, 1) self.assertEqual(1, ticket3.id) self.assertEqual(ticket3['reporter'], 'santa') self.assertEqual(ticket3['summary'], 'Bar') self.assertEqual(ticket3['foo'], 'New value') # Testing get_changelog() log = ticket3.get_changelog() self.assertEqual(len(log), 3) ok_vals = ['foo', 'summary', 'comment'] self.failUnless(log[0][2] in ok_vals) self.failUnless(log[1][2] in ok_vals) self.failUnless(log[2][2] in ok_vals)
def assertCommentAdded(self, ticket_id, comment): ticket = Ticket(self.env, int(ticket_id)) changes = ticket.get_changelog() comment_change = [c for c in changes if c[2] == 'comment'][0] self.assertEqual(comment_change[2], comment)
def _draw_ticket(self, req, params): # Draw image and generate data for image map for ticket view start, end, days = self._get_dates(params) members = self._settings.members resources = self._settings.resources cursor = self.db.cursor() # ids are required for mapping members to their colours midx = {} for i, m in enumerate(members): midx[m] = i # group tickets according to the owner mem_tickets, owned_tickets = {}, 0 for mem in members: statement = '''select id from ticket where owner = "%s" and not (status = "closed" and changetime <= %s) and not time > %s'''\ % (mem, self._to_time(start), self._to_time(end)) cursor.execute(statement) mem_tickets[mem] = [t[0] for t in cursor] owned_tickets += len(mem_tickets[mem]) if owned_tickets == 0: params['msg'] = 'There are no tickets to visualise in the selected time period' return # ticket size is relative to number of tickets and "screen size" ticket_width = self._bounded(_TICKET_MIN, _SCREEN_SPACE / owned_tickets, _TICKET_MAX) date_offset, bar_gap, name_offset = 80, 20, 30 # determine width of graph page_width = date_offset + (owned_tickets * ticket_width) + (len(members) * bar_gap) # determine height of graph page_height = name_offset + (days * _SQUARE_SIZE) img = Image.new("RGBA", (page_width, page_height), (255, 255, 255, 255)) draw = ImageDraw.Draw(img) draw = self._write_dates((0, page_height), start, days, draw) # draw individual tickets map = [] memx = date_offset colh = days * _SQUARE_SIZE for i, mem in enumerate(members): x, y = memx, page_height colw = len(mem_tickets[mem]) * ticket_width draw.rectangle([x, y, x + colw - 1, y - colh + 1], fill=(229, 229, 229, 255)) for j, tid in enumerate(mem_tickets[mem]): ticket = Ticket(self.env, tid) tcreated = ticket.time_created.date() starty = page_height - self._date_y(start, tcreated) x = memx + (j * ticket_width) # determine the colour index for the reported circle reporter = ticket['reporter'] if reporter not in midx: continue #reporter not configured in Narcissus settings, so don't show ticket cidx = midx[reporter] dots = [(starty, cidx)] # create the ticket lines: last change starts as ticket open, # line starts as thin, colour starts as black draw_from = draw_to = tcreated width = change_width = ticket_width / 4 status = ticket['status'] for dtime, _, field, _, newvalue, _ in ticket.get_changelog(): dtime = dtime.date() if dtime <= end: dot = None ttype = field if ttype == 'status': ttype = newvalue if ttype == 'assigned': draw_to = dtime change_width = ticket_width / 2 status = 'assigned' elif ttype == 'resolution': draw_to = dtime dot = midx[ticket['reporter']] + 1 status = 'closed' elif ttype == 'reopened': draw_to = dtime change_width = ticket_width / 4 dot = midx[ticket['reporter']] + 1 status = 'new' if draw_to > draw_from: draw_days = (draw_to - draw_from).days y = page_height - self._date_y(start, draw_from) - (_SQUARE_SIZE / 2) draw.line([x + (ticket_width / 2), y, x + (ticket_width / 2), y - draw_days * _SQUARE_SIZE], fill=(128, 128, 128, 255), width=width) if dot: y = page_height - self._date_y(start, draw_to) dots.append((y, dot - 1)) draw_from = draw_to width = change_width if status != 'closed' or draw_to > end: draw_days = (end - draw_from).days y = page_height - self._date_y(start, draw_from) - (_SQUARE_SIZE / 2) draw.line([x + (ticket_width / 2), y, x + (ticket_width / 2), y - (draw_days * _SQUARE_SIZE + (_SQUARE_SIZE / 2))], fill=(128, 128, 128, 255), width=width) for dot in dots: y, i = dot[0] - (_SQUARE_SIZE / 2), dot[1] draw.ellipse([x, y - ticket_width, x + ticket_width, y], fill=(_RED[i%len(_RED)], _GREEN[i%len(_GREEN)], _BLUE[i%len(_BLUE)], 255)) # default length if there haven't been any changes to the ticket yet last_change = end if ticket['status'] == 'closed': for dtime, _, _, _, _, _ in ticket.get_changelog(): dtime = dtime.date() if dtime <= end: last_change = dtime ticket_length = (last_change - tcreated).days + 1 # add info for the template to render an image map idx = 'ticket_%s' % (tid) href = '%s/details?tid=%s' % (req.base_url, tid) item = {} item['href'] = href item['x1'] = x item['y1'] = starty - (ticket_length * _SQUARE_SIZE) item['x2'] = x + ticket_width item['y2'] = starty map.append(item) # print member name x, y = memx + 10, name_offset * 0.4 draw.text((x, y), mem, fill=(0, 0, 0, 255), font=self._ttf) # increment the horizontal point for the next member memx += colw + bar_gap params['mapItems'] = map params['vis'] = self._cache_image(req, params, img, 'vis')
def _update_data(self, req): # Update narcissus_data table with info to be visualised db = self.env.get_db_cnx() cursor = db.cursor() cursor.execute('select max(dtime) from narcissus_data') start_rev = 1 last_update = cursor.fetchone()[0] if not last_update: # no data yet, update data from the beginning cursor.execute('select min(time) from wiki') last_update = cursor.fetchone()[0] else: # update data from this change cursor.execute('select eid from narcissus_data where resource = "svn"') for row in cursor: if int(row[0]) > start_rev: start_rev = int(row[0]) start_rev += 1 last_update = datetime.date.fromtimestamp(last_update) members = self._settings.members # populate table with wiki activity cursor.execute('''select name, min(version), count(version), min(time) from wiki where time > %f group by name order by min(time)''' % self._to_time(last_update)) for page, start_version, versions, _ in cursor: versions += start_version # page has been added if start_version == 1: new_page = WikiPage(self.env, page, 1) add = len(new_page.text.splitlines()) if (new_page.author in members) and (new_page.time.date() > last_update): self._insert_data(new_page.author, new_page.time, new_page.name, 'wiki', 'add', add) start_version += 1 for i in xrange(start_version, versions): # page has been edited old_page, edit_page = WikiPage(self.env, page, i - 1), WikiPage(self.env, page, i) if (edit_page.author in members) and (edit_page.time.date() > last_update): changes = self._my_diff(old_page.text, edit_page.text) edit = self._edit_newlines(changes) if edit: self._insert_data(edit_page.author, edit_page.time, edit_page.name, 'wiki', 'edit', edit) # populate table with ticket activity credits = self._settings.credits cursor.execute('select id from ticket') for row in cursor: ticket = Ticket(self.env, row[0]) # ticket has been opened if ticket['reporter'] in members and ticket.time_created.date() > last_update: self._insert_data(ticket['reporter'], ticket.time_created, ticket.id, 'ticket', 'open', 1) # FIXME for dtime, author, field, _, newvalue, _ in ticket.get_changelog(): if author in members and dtime.date() > last_update: if field == 'comment': # ticket has received comment self._insert_data(author, dtime, ticket.id, 'ticket', 'comment', credits['comment']) elif newvalue == 'assigned': # ticket has been accepted self._insert_data(author, dtime, ticket.id, 'ticket', 'accept', credits['accept']) elif field == 'resolution': # ticket has been closed, reopened closures only count as a change reopened = False for inner_time, _, _, _, _, _ in ticket.get_changelog(): if inner_time > dtime and field == 'resolution': reopened = True if reopened: self._insert_data(author, dtime, ticket.id, 'ticket', 'update', credits['update']) else: if ticket['owner'] in members: self._insert_data(ticket['owner'], dtime, ticket.id, 'ticket', 'close', credits['close']) if author != ticket['owner']: self._insert_data(author, dtime, ticket.id, 'ticket', 'proxy close', credits['update']) else: # ticket has been changed self._insert_data(author, dtime, ticket.id, 'ticket', 'update', 1) # FIXME # populate table with svn activity repos = self.env.get_repository() youngest_rev = str(repos.youngest_rev or 0).split(':')[0] try: youngest_rev = int(youngest_rev) + 1 except: youngest_rev = 0 for rev in xrange(start_rev, int(youngest_rev) + 1): cs = repos.get_changeset(rev) add, edit = 0, 0 for path, _, change, base_path, base_rev in cs.get_changes(): if change == 'add': add += self._svn_add_newlines(repos, path, rev) elif change == 'edit': diff_args = {'old_path': base_path, 'old_rev': base_rev, 'new_path': path or '/', 'new_rev': rev } changes = self._my_svn_diff(repos, diff_args) if changes: edit += self._edit_newlines(changes) valid_user = cs.author in members if not valid_user: for u, n, e in self.env.get_known_users(): if e != None and e in cs.author: valid_user = True if add and valid_user: self._insert_data(cs.author, cs.date, rev, 'svn', 'add', add) if edit and valid_user: self._insert_data(cs.author, cs.date, rev, 'svn', 'edit', edit) db.commit()
def _draw_ticket(self, req, params): # Draw image and generate data for image map for ticket view start, end, days = self._get_dates(params) members = self._settings.members resources = self._settings.resources cursor = self.db.cursor() # ids are required for mapping members to their colours midx = {} for i, m in enumerate(members): midx[m] = i # group tickets according to the owner mem_tickets, owned_tickets = {}, 0 for mem in members: statement = '''select id from ticket where owner = "%s" and not (status = "closed" and changetime <= %s) and not time > %s'''\ % (mem, start, end) cursor.execute(statement) mem_tickets[mem] = [t[0] for t in cursor] owned_tickets += len(mem_tickets[mem]) if owned_tickets == 0: params[ 'msg'] = 'There are no tickets to visualise in the selected time period' return # ticket size is relative to number of tickets and "screen size" ticket_width = self._bounded(_TICKET_MIN, _SCREEN_SPACE / owned_tickets, _TICKET_MAX) date_offset, bar_gap, name_offset = 80, 20, 30 # determine width of graph page_width = date_offset + (owned_tickets * ticket_width) + (len(members) * bar_gap) # determine height of graph page_height = name_offset + (days * _SQUARE_SIZE) img = Image.new("RGBA", (page_width, page_height), (255, 255, 255, 255)) draw = ImageDraw.Draw(img) draw = self._write_dates((0, page_height), start, days, draw) # draw individual tickets map = [] memx = date_offset colh = days * _SQUARE_SIZE for i, mem in enumerate(members): x, y = memx, page_height colw = len(mem_tickets[mem]) * ticket_width draw.rectangle([x, y, x + colw - 1, y - colh + 1], fill=(229, 229, 229, 255)) for j, tid in enumerate(mem_tickets[mem]): ticket = Ticket(self.env, tid) tcreated = self._to_microseconds(ticket.time_created.date()) starty = page_height - self._date_y(start, tcreated) x = memx + (j * ticket_width) # determine the colour index for the reported circle reporter = ticket['reporter'] if reporter not in midx: continue #reporter not configured in Narcissus settings, so don't show ticket cidx = midx[reporter] dots = [(starty, cidx)] # create the ticket lines: last change starts as ticket open, # line starts as thin, colour starts as black draw_from = draw_to = tcreated width = change_width = ticket_width / 4 status = ticket['status'] for dtime, _, field, _, newvalue, _ in ticket.get_changelog(): dtime = self._to_microseconds(dtime) if dtime <= end: dot = None ttype = field if ttype == 'status': ttype = newvalue if ttype == 'assigned': draw_to = dtime change_width = ticket_width / 2 status = 'assigned' elif ttype == 'resolution': draw_to = dtime dot = midx[ticket['reporter']] + 1 status = 'closed' elif ttype == 'reopened': draw_to = dtime change_width = ticket_width / 4 dot = midx[ticket['reporter']] + 1 status = 'new' if draw_to > draw_from: draw_days = self._date_days(draw_to - draw_from) y = page_height - self._date_y( start, draw_from) - (_SQUARE_SIZE / 2) draw.line([ x + (ticket_width / 2), y, x + (ticket_width / 2), y - draw_days * _SQUARE_SIZE ], fill=(128, 128, 128, 255), width=width) if dot: y = page_height - self._date_y(start, draw_to) dots.append((y, dot - 1)) draw_from = draw_to width = change_width if status != 'closed' or draw_to > end: draw_days = self._date_days(end - draw_from) y = page_height - self._date_y( start, draw_from) - (_SQUARE_SIZE / 2) draw.line([ x + (ticket_width / 2), y, x + (ticket_width / 2), y - (draw_days * _SQUARE_SIZE + (_SQUARE_SIZE / 2)) ], fill=(128, 128, 128, 255), width=width) for dot in dots: y, i = dot[0] - (_SQUARE_SIZE / 2), dot[1] draw.ellipse([x, y - ticket_width, x + ticket_width, y], fill=(_RED[i % len(_RED)], _GREEN[i % len(_GREEN)], _BLUE[i % len(_BLUE)], 255)) # default length if there haven't been any changes to the ticket yet last_change = end if ticket['status'] == 'closed': for dtime, _, _, _, _, _ in ticket.get_changelog(): dtime = self._to_microseconds(dtime) if dtime <= end: last_change = dtime ticket_length = self._date_days(last_change - tcreated) + 1 # add info for the template to render an image map idx = 'ticket_%s' % (tid) href = '%s/details?tid=%s' % (req.base_url, tid) item = {} item['href'] = href item['x1'] = x item['y1'] = starty - (ticket_length * _SQUARE_SIZE) item['x2'] = x + ticket_width item['y2'] = starty map.append(item) # print member name x, y = memx + 10, name_offset * 0.4 draw.text((x, y), mem, fill=(0, 0, 0, 255), font=self._ttf) # increment the horizontal point for the next member memx += colw + bar_gap params['mapItems'] = map params['vis'] = self._cache_image(req, params, img, 'vis')
def _update_data(self, req): # Update narcissus_data table with info to be visualised db = self.env.get_db_cnx() cursor = db.cursor() cursor.execute('select max(dtime) from narcissus_data') start_rev = 1 last_update = cursor.fetchone()[0] if not last_update: # no data yet, update data from the beginning cursor.execute('select min(time) from wiki') last_update = cursor.fetchone()[0] else: # update data from this change cursor.execute( 'select eid from narcissus_data where resource = "svn"') for row in cursor: if int(row[0]) > start_rev: start_rev = int(row[0]) start_rev += 1 members = self._settings.members # populate table with wiki activity cursor.execute( '''select name, min(version), count(version), min(time) from wiki where time > %f group by name order by min(time)''' % last_update) for page, start_version, versions, _ in cursor: versions += start_version # page has been added if start_version == 1: new_page = WikiPage(self.env, page, 1) add = len(new_page.text.splitlines()) if (new_page.author in members) and (self._to_microseconds( new_page.time) > last_update): self._insert_data(new_page.author, new_page.time, new_page.name, 'wiki', 'add', add) start_version += 1 for i in xrange(start_version, versions): # page has been edited old_page, edit_page = WikiPage(self.env, page, i - 1), WikiPage( self.env, page, i) if (edit_page.author in members) and (self._to_microseconds( edit_page.time) > last_update): changes = self._my_diff(old_page.text, edit_page.text) edit = self._edit_newlines(changes) if edit: self._insert_data(edit_page.author, edit_page.time, edit_page.name, 'wiki', 'edit', edit) # populate table with ticket activity credits = self._settings.credits cursor.execute('select id from ticket') for row in cursor: ticket = Ticket(self.env, row[0]) # ticket has been opened if ticket['reporter'] in members and self._to_microseconds( ticket.time_created) > last_update: self._insert_data(ticket['reporter'], ticket.time_created, ticket.id, 'ticket', 'open', 1) # FIXME for dtime, author, field, _, newvalue, _ in ticket.get_changelog(): if author in members and self._to_microseconds( dtime) > last_update: if field == 'comment': # ticket has received comment self._insert_data(author, dtime, ticket.id, 'ticket', 'comment', credits['comment']) elif newvalue == 'assigned': # ticket has been accepted self._insert_data(author, dtime, ticket.id, 'ticket', 'accept', credits['accept']) elif field == 'resolution': # ticket has been closed, reopened closures only count as a change reopened = False for inner_time, _, _, _, _, _ in ticket.get_changelog( ): if inner_time > dtime and field == 'resolution': reopened = True if reopened: self._insert_data(author, dtime, ticket.id, 'ticket', 'update', credits['update']) else: if ticket['owner'] in members: self._insert_data(ticket['owner'], dtime, ticket.id, 'ticket', 'close', credits['close']) if author != ticket['owner']: self._insert_data(author, dtime, ticket.id, 'ticket', 'proxy close', credits['update']) else: # ticket has been changed self._insert_data(author, dtime, ticket.id, 'ticket', 'update', 1) # FIXME # populate table with svn activity repos = self.env.get_repository() youngest_rev = str(repos.youngest_rev or 0).split(':')[0] try: youngest_rev = int(youngest_rev) + 1 except: youngest_rev = 0 for rev in xrange(start_rev, youngest_rev): cs = repos.get_changeset(rev) add, edit = 0, 0 for path, _, change, base_path, base_rev in cs.get_changes(): if change == 'add': add += self._svn_add_newlines(repos, path, rev) elif change == 'edit': diff_args = { 'old_path': base_path, 'old_rev': base_rev, 'new_path': path or '/', 'new_rev': rev } changes = self._my_svn_diff(repos, diff_args) if changes: edit += self._edit_newlines(changes) valid_user = cs.author in members if not valid_user: for u, n, e in self.env.get_known_users(): if e != None and e in cs.author: valid_user = True if add and valid_user: self._insert_data(cs.author, cs.date, rev, 'svn', 'add', add) if edit and valid_user: self._insert_data(cs.author, cs.date, rev, 'svn', 'edit', edit) db.commit()
def assertFieldChanged(self, ticket_id, field, new_value): ticket = Ticket(self.env, int(ticket_id)) changes = ticket.get_changelog() field_change = [c for c in changes if c[2] == field][0] self.assertEqual(field_change[4], new_value)
def get_changelog(self): t = Ticket(self.env, id) return (t.get_changelog())
def get_changelog( self ): t = Ticket(self.env, id) return( t.get_changelog() )
def get_ticket_notification_recipients(env, config, tktid, prev_cc=None, modtime=None): """Returns notifications recipients. :since 1.0.2: the `config` parameter is no longer used. :since 1.0.2: the `prev_cc` parameter is deprecated. :since 1.1.3: deprecated and will be removed in 1.3.1. """ section = env.config['notification'] always_notify_reporter = section.getbool('always_notify_reporter') always_notify_owner = section.getbool('always_notify_owner') always_notify_updater = section.getbool('always_notify_updater') cc_recipients = set(prev_cc or []) to_recipients = set() tkt = Ticket(env, tktid) # CC field is stored as comma-separated string. Parse to list. to_list = lambda cc: cc.replace(',', ' ').split() # Backward compatibility if not modtime: modtime = tkt['changetime'] # Harvest email addresses from the author field of ticket_change(s) if always_notify_updater: for author, ticket in env.db_query( """ SELECT DISTINCT author, ticket FROM ticket_change WHERE ticket=%s """, (tktid, )): to_recipients.add(author) # Harvest previous owner and cc list author = None for changelog in tkt.get_changelog(modtime): author, field, old = changelog[1:4] if field == 'owner' and always_notify_owner: to_recipients.add(old) elif field == 'cc': cc_recipients.update(to_list(old)) # Harvest email addresses from the cc, reporter, and owner fields updater = author or tkt['reporter'] if tkt['cc']: cc_recipients.update(to_list(tkt['cc'])) if always_notify_reporter: to_recipients.add(tkt['reporter']) if always_notify_owner: to_recipients.add(tkt['owner']) if always_notify_updater and updater: to_recipients.add(updater) # Suppress the updater from the recipients if necessary if not always_notify_updater: filter_out = True if always_notify_reporter and updater == tkt['reporter']: filter_out = False if always_notify_owner and updater == tkt['owner']: filter_out = False if filter_out: to_recipients.discard(updater) return list(to_recipients), list(cc_recipients), \ tkt['reporter'], tkt['owner']
def test_comment_with_whitespace_only_is_not_saved(self): ticket = Ticket(self.env) ticket.insert() ticket.save_changes(comment='\n \n ') self.assertEqual(0, len(ticket.get_changelog()))
def get_list(self, realm, wl, req, fields=None): db = self.env.get_db_cnx() cursor = db.cursor() context = Context.from_request(req) locale = getattr(req, 'locale', LC_TIME) ticketlist = [] extradict = {} if not fields: fields = set(self.default_fields['ticket']) else: fields = set(fields) if 'changetime' in fields: max_changetime = datetime(1970, 1, 1, tzinfo=utc) min_changetime = datetime.now(utc) if 'time' in fields: max_time = datetime(1970, 1, 1, tzinfo=utc) min_time = datetime.now(utc) for sid, last_visit in wl.get_watched_resources( 'ticket', req.authname): ticketdict = {} try: ticket = Ticket(self.env, sid, db) exists = ticket.exists except: exists = False if not exists: ticketdict['deleted'] = True if 'id' in fields: ticketdict['id'] = sid ticketdict['ID'] = '#' + sid if 'author' in fields: ticketdict['author'] = '?' if 'changetime' in fields: ticketdict['changedsincelastvisit'] = 1 ticketdict['changetime'] = '?' ticketdict['ichangetime'] = 0 if 'time' in fields: ticketdict['time'] = '?' ticketdict['itime'] = 0 if 'comment' in fields: ticketdict['comment'] = tag.strong(t_("deleted"), class_='deleted') if 'notify' in fields: ticketdict['notify'] = wl.is_notify(req, 'ticket', sid) if 'description' in fields: ticketdict['description'] = '' if 'owner' in fields: ticketdict['owner'] = '' if 'reporter' in fields: ticketdict['reporter'] = '' ticketlist.append(ticketdict) continue render_elt = lambda x: x if not (Chrome(self.env).show_email_addresses or \ 'EMAIL_VIEW' in req.perm(ticket.resource)): render_elt = obfuscate_email_address # Copy all requested fields from ticket if fields: for f in fields: ticketdict[f] = ticket.values.get(f, u'') else: ticketdict = ticket.values.copy() changetime = ticket.time_changed if wl.options['attachment_changes']: for attachment in Attachment.select(self.env, 'ticket', sid, db): if attachment.date > changetime: changetime = attachment.date if 'attachment' in fields: attachments = [] for attachment in Attachment.select(self.env, 'ticket', sid, db): wikitext = u'[attachment:"' + u':'.join([ attachment.filename, 'ticket', sid ]) + u'" ' + attachment.filename + u']' attachments.extend([ tag(', '), format_to_oneliner(self.env, context, wikitext, shorten=False) ]) if attachments: attachments.reverse() attachments.pop() ticketdict['attachment'] = moreless(attachments, 5) # Changes are special. Comment, commentnum and last author are included in them. if 'changes' in fields or 'author' in fields or 'comment' in fields or 'commentnum' in fields: changes = [] # If there are now changes the reporter is the last author author = ticket.values['reporter'] commentnum = u"0" comment = u"" want_changes = 'changes' in fields for date, cauthor, field, oldvalue, newvalue, permanent in ticket.get_changelog( changetime, db): author = cauthor if field == 'comment': if 'commentnum' in fields: ticketdict['commentnum'] = to_unicode(oldvalue) if 'comment' in fields: comment = to_unicode(newvalue) comment = moreless(comment, 200) ticketdict['comment'] = comment if not want_changes: break else: if want_changes: label = self.fields['ticket'].get(field, u'') if label: changes.extend([ tag( tag.strong(label), ' ', render_property_diff( self.env, req, ticket, field, oldvalue, newvalue)), tag('; ') ]) if want_changes: # Remove the last tag('; '): if changes: changes.pop() changes = moreless(changes, 5) ticketdict['changes'] = tag(changes) if 'id' in fields: ticketdict['id'] = sid ticketdict['ID'] = format_to_oneliner(self.env, context, '#' + sid, shorten=True) if 'cc' in fields: if render_elt == obfuscate_email_address: ticketdict['cc'] = ', '.join( [render_elt(c) for c in ticketdict['cc'].split(', ')]) if 'author' in fields: ticketdict['author'] = render_elt(author) if 'changetime' in fields: ichangetime = to_timestamp(changetime) ticketdict.update( changetime=format_datetime(changetime, locale=locale, tzinfo=req.tz), ichangetime=ichangetime, changedsincelastvisit=(last_visit < ichangetime and 1 or 0), changetime_delta=pretty_timedelta(changetime), changetime_link=req.href.timeline( precision='seconds', from_=trac_format_datetime(changetime, 'iso8601', tzinfo=req.tz))) if changetime > max_changetime: max_changetime = changetime if changetime < min_changetime: min_changetime = changetime if 'time' in fields: time = ticket.time_created ticketdict.update(time=format_datetime(time, locale=locale, tzinfo=req.tz), itime=to_timestamp(time), time_delta=pretty_timedelta(time), time_link=req.href.timeline( precision='seconds', from_=trac_format_datetime( time, 'iso8601', tzinfo=req.tz))) if time > max_time: max_time = time if time < min_time: min_time = time if 'description' in fields: description = ticket.values['description'] description = moreless(description, 200) ticketdict['description'] = description if 'notify' in fields: ticketdict['notify'] = wl.is_notify(req, 'ticket', sid) if 'owner' in fields: ticketdict['owner'] = render_elt(ticket.values['owner']) if 'reporter' in fields: ticketdict['reporter'] = render_elt(ticket.values['reporter']) if 'tags' in fields and self.tagsystem: tags = [] for t in self.tagsystem.get_tags(req, Resource('ticket', sid)): tags.extend( [tag.a(t, href=req.href('tags', q=t)), tag(', ')]) if tags: tags.pop() ticketdict['tags'] = moreless(tags, 10) ticketlist.append(ticketdict) if 'changetime' in fields: extradict['max_changetime'] = format_datetime(max_changetime, locale=locale, tzinfo=req.tz) extradict['min_changetime'] = format_datetime(min_changetime, locale=locale, tzinfo=req.tz) if 'time' in fields: extradict['max_time'] = format_datetime(max_time, locale=locale, tzinfo=req.tz) extradict['min_time'] = format_datetime(min_time, locale=locale, tzinfo=req.tz) return ticketlist, extradict
if ':' in source_obj: ticket_num, source_obj = source_obj.split(':', 1) if not Ticket.id_is_valid(ticket_num): return system_message("%s is not a valid ticket id" % ticket_num) try: ticket = Ticket(self.env, ticket_num) if 'TICKET_VIEW' not in formatter.perm(ticket.resource): return '' except ResourceNotFound: return system_message("Ticket %s does not exist" % ticket_num) if ':' in source_obj: source_format, comment_num = source_obj.split(':', 1) if source_format == 'comment': changelog = ticket.get_changelog() out = [] if changelog: for (ts, author, field, oldval, newval, permanent) in changelog: if field == 'comment' and \ oldval == comment_num: dest_format = 'text/x-trac-wiki' ctxt = Context.from_request( formatter.req, 'ticket', ticket_num) out = newval break if not out: return system_message( "Comment %s does not exist for Ticket %s" % (comment_num, ticket_num))
def get_ticket_notification_recipients(env, config, tktid, prev_cc=None, modtime=None): """Returns notifications recipients. :since 1.0.2: the `config` parameter is no longer used. :since 1.0.2: the `prev_cc` parameter is deprecated. :since 1.1.3: deprecated and will be removed in 1.3.1. """ section = env.config['notification'] always_notify_reporter = section.getbool('always_notify_reporter') always_notify_owner = section.getbool('always_notify_owner') always_notify_updater = section.getbool('always_notify_updater') cc_recipients = set(prev_cc or []) to_recipients = set() tkt = Ticket(env, tktid) # CC field is stored as comma-separated string. Parse to list. to_list = lambda cc: cc.replace(',', ' ').split() # Backward compatibility if not modtime: modtime = tkt['changetime'] # Harvest email addresses from the author field of ticket_change(s) if always_notify_updater: for author, ticket in env.db_query(""" SELECT DISTINCT author, ticket FROM ticket_change WHERE ticket=%s """, (tktid, )): to_recipients.add(author) # Harvest previous owner and cc list author = None for changelog in tkt.get_changelog(modtime): author, field, old = changelog[1:4] if field == 'owner' and always_notify_owner: to_recipients.add(old) elif field == 'cc': cc_recipients.update(to_list(old)) # Harvest email addresses from the cc, reporter, and owner fields updater = author or tkt['reporter'] if tkt['cc']: cc_recipients.update(to_list(tkt['cc'])) if always_notify_reporter: to_recipients.add(tkt['reporter']) if always_notify_owner: to_recipients.add(tkt['owner']) if always_notify_updater and updater: to_recipients.add(updater) # Suppress the updater from the recipients if necessary if not always_notify_updater: filter_out = True if always_notify_reporter and updater == tkt['reporter']: filter_out = False if always_notify_owner and updater == tkt['owner']: filter_out = False if filter_out: to_recipients.discard(updater) return list(to_recipients), list(cc_recipients), \ tkt['reporter'], tkt['owner']
out, ctxt, dest_format = self._get_source(formatter, source_obj, dest_format) elif source_format == "ticket": if ":" in source_obj: ticket_num, source_obj = source_obj.split(":", 1) if not Ticket.id_is_valid(ticket_num): return system_message("%s is not a valid ticket id" % ticket_num) try: ticket = Ticket(self.env, ticket_num) if not "TICKET_VIEW" in formatter.perm(ticket.resource): return "" except ResourceNotFound, e: return system_message("Ticket %s does not exist" % ticket_num) if ":" in source_obj: source_format, comment_num = source_obj.split(":", 1) if source_format == "comment": changelog = ticket.get_changelog() out = [] if changelog: for (ts, author, field, oldval, newval, permanent) in changelog: if field == "comment" and oldval == comment_num: dest_format = "text/x-trac-wiki" ctxt = Context.from_request(formatter.req, "ticket", ticket_num) out = newval break if not out: return system_message("Comment %s does not exist for Ticket %s" % (comment_num, ticket_num)) else: system_message("Unsupported ticket field %s" % source_format) else: return system_message("Ticket field must be specified") else: