def find_blockers(self, ticket, field, blockers): remote_tktsys = RemoteTicketSystem(self.env) links = remote_tktsys.parse_links(ticket[field]) for remote_name, link in links: linked_ticket = RemoteTicket(self.env, remote_name, link) if linked_ticket['status'] != 'closed': blockers.append((remote_name, link)) else: self.find_blockers(linked_ticket, field, blockers) return blockers
def validate_links_exist(self, ticket, end): remote_tktsys = RemoteTicketSystem(self.env) links = remote_tktsys.parse_links(ticket[end]) bad_links = [] for remote_name, link in links: try: tkt = RemoteTicket(self.env, remote_name, link) except ResourceNotFound: bad_links.append((remote_name, link)) if bad_links: return ("Remote tickets linked in '%s' could not be found: [%s]" % (end, ', '.join('%s:#%s' % t for t in bad_links)))
def _refresh_ticket(self): rts = RemoteTicketSystem(self.env) remote_trac = rts.get_remote_trac(self.remote_name)['url'] xmlrpc_addr = Href(remote_trac).rpc() server = xmlrpclib.ServerProxy(xmlrpc_addr) try: tkt_vals = server.ticket.get(self.id) except xmlrpclib.ProtocolError, e: msg = ("Could not contact remote Trac '%s' at %s. " "Received error %s, %s") log = ("XML-RPC ProtocolError contacting Trac %s at %s, " "errcode=%s, errmsg='%s'") args = (self.remote_name, xmlrpc_addr, e.errcode, e.errmsg) self.env.log.warn(log, *args) raise ResourceNotFound(msg % args, "Uncontactable server")
def _fetch_ticket(self): rts = RemoteTicketSystem(self.env) db = self.env.get_read_db() cursor = db.cursor() # Try to retrieve remote ticket from cache cursor.execute( '''SELECT %s FROM remote_tickets WHERE remote_name=%%s and id=%%s ''' % (', '.join(self.table_fields)), (self.remote_name, self.id)) row = cursor.fetchone() # Remote ticket not in cache if not row: self._refresh_ticket() self._cachetime = from_utimestamp(row[self.cachetime_pos]) ttl = timedelta(seconds=int(rts.cache_ttl) // 1000, microseconds=int(rts.cache_ttl) % 1000 * 1000) # Cached remote ticket is too old if self._cachetime < datetime.now(utc) - ttl: self._refresh_ticket() # Cached ticket is valid, populate instance for name, value in zip(self.remote_fields, row): if name in self.time_fields: self.values[name] = from_utimestamp(value) elif value is None: self.values[name] = empty else: self.values[name] = value
def _do_ticket(self, req, template, data, content_type): if 'ticket' in data and 'linked_tickets' in data: ticket = data['ticket'] context = Context.from_request(req, ticket.resource) # Add name:#n links to link fields of Ticket instance when # flowing from storage to browser if req.method == 'GET': RemoteLinksProvider(self.env).augment_ticket(ticket) # Rerender link fields for field in data['fields']: if field['type'] == 'link': name = field['name'] field['rendered'] = format_to_oneliner(self.env, context, ticket[name]) # Add RemoteTicket objects for linked issues table, and pass list # of rejects that could not be retrieved linked_tickets, linked_rejects = self._remote_tickets(ticket, context) data['linked_tickets'].extend(linked_tickets) data['linked_rejects'].extend(linked_rejects) # Provide list of remote sites if newlinked form options are present if 'newlinked_options' in data: remote_sites = RemoteTicketSystem(self.env).get_remote_tracs() data['remote_sites'] = remote_sites return (template, data, content_type)
def find_cycle(self, ticket, field, path): tkt_ref = '%s:#%s' % (getattr(ticket, 'remote_name', ''), ticket.id) if tkt_ref in path: path.append(tkt_ref) return path path.append(tkt_ref) remote_tktsys = RemoteTicketSystem(self.env) links = remote_tktsys.parse_links(ticket[field]) for remote_name, link in links: linked_ticket = RemoteTicket(self.env, remote_name, link) cycle = self.find_cycle(linked_ticket, field, copy(path)) if cycle != None: return cycle return None
def validate_ticket(self, req, ticket): action = req.args.get('action') ticket_system = TicketSystem(self.env) links_provider = LinksProvider(self.env) remote_tktsys = RemoteTicketSystem(self.env) for end in ticket_system.link_ends_map: check = self.validate_links_exist(ticket, end) if check: yield None, check continue validator_name = links_provider.get_validator(end) if validator_name == 'no_cycle': validator = self.validate_no_cycle elif (validator_name == 'parent_child' and end == links_provider.PARENT_END): validator = self.validate_parent else: validator = self.validate_any check = validator(ticket, end) if check: yield None, check if action == 'resolve': blockers = self.find_blockers(ticket, end, []) if blockers: blockers_str = ', '.join('%s:#%s' % rlink for rlink in unique(blockers)) msg = ("Cannot resolve this ticket because it is " "blocked by '%s' tickets [%s]" % (end, blockers_str)) yield None, msg
def _remote_tickets(self, ticket, context): link_fields = [f for f in ticket.fields if f['type'] == 'link'] rts = RemoteTicketSystem(self.env) linked_tickets = [] linked_rejects = [] for field in link_fields: for link_name, link in rts.parse_links(ticket[field['name']]): tkt_fmt = format_to_oneliner(self.env, context, '%s:#%s' % (link_name, link)) try: tkt = RemoteTicket(self.env, link_name, link) linked_tickets.append((field['label'], tkt_fmt, tkt)) except ResourceNotFound: linked_rejects.append((field['label'], tkt_fmt)) return linked_tickets, linked_rejects
def validate_parent(self, ticket, end): cycle_validation = self.validate_no_cycle(ticket, end) if cycle_validation: return cycle_validation links = RemoteTicketSystem(self.env).parse_links(ticket[end]) if len(links) > 1 and end == LinksProvider.PARENT_END: parents_str = ', '.join('%s:#%s' % (remote_name, tkt_id) for (remote_name, tkt_id) in links) return ("Multiple links in '%s': %s:#%s -> [%s]" % (LinksProvider(self.env).render_end(end), ticket.remote_name, ticket.id, parents_str))
def _do_newticket(self, req, template, data, content_type): link_remote_val = req.args.get('linked_remote_val', '') pattern = RemoteTicketSystem(self.env).REMOTES_RE lrv_match = pattern.match(link_remote_val) link_end = req.args.get('linked_end', '') ends_map = TicketSystem(self.env).link_ends_map if ('ticket' in data and lrv_match and link_end in ends_map): ticket = data['ticket'] remote_name = lrv_match.group(1) remote_id = lrv_match.group(2) remote_ticket = RemoteTicket(self.env, remote_name, remote_id, refresh=True) link_fields = [f for f in ticket.fields if f['name'] == link_end] copy_field_names = link_fields[0]['copy_fields'] ticket[link_end] = link_remote_val for fname in copy_field_names: ticket[fname] = remote_ticket[fname] data['remote_ticket'] = remote_ticket return (template, data, content_type)
def pre_process_request(self, req, handler): # If linked_val request argument matches the URL of a known # remote site then: # - Parse it, storing the result in linked_remote_val # - Remove the linked_val argument, so trac doesn't also process it if 'linked_val' in req.args: linked_val = req.args['linked_val'] patt = re.compile(r'(.+)/ticket/(\d+)') for name, site in RemoteTicketSystem(self.env)._intertracs.items(): m = patt.match(linked_val) if m: remote_base, remote_tkt_id = m.groups() if remote_base == site['url'].rstrip('/'): req.args['linked_remote_val'] = '%s:#%s' \ % (name, remote_tkt_id) del req.args['linked_val'] break return handler
def ticket_changed(self, ticket, comment, author, old_values): link_fields = [f['name'] for f in ticket.fields if f.get('link')] ticket_system = TicketSystem(self.env) links_provider = LinksProvider(self.env) remote_tktsys = RemoteTicketSystem(self.env) # We go behind trac's back to augment the ticket with remote links # As a result trac doesn't provide a correct old_values so fetch # our own orig_old_vals = old_values if old_values is None: old_values = {} else: self._augment_values(ticket.id, old_values) @self.env.with_transaction() def do_changed(db): cursor = db.cursor() for end in link_fields: # Determine links added or removed in this change by taking the # set difference of new and old values new_rtkts = set(remote_tktsys.parse_links(ticket[end])) old_rtkts = set(remote_tktsys.parse_links(old_values.get(end))) links_added = new_rtkts - old_rtkts links_removed = old_rtkts - new_rtkts links_changed = old_rtkts ^ new_rtkts # Additons and removals other_end = ticket_system.link_ends_map[end] # Add link records for remote links created in this change records = [('', ticket.id, end, rname, rid) for rname, rid in links_added] if other_end: records += [(rname, rid, other_end, '', ticket.id) for rname, rid in links_added] cursor.executemany( ''' INSERT INTO remote_ticket_links (source_name, source, type, destination_name, destination) VALUES (%s, %s, %s, %s, %s)''', records) # Remove link records for remote links removed in this change records = [('', ticket.id, end, rname, rid) for rname, rid in links_removed] if other_end: records += [(rname, rid, other_end, '', ticket.id) for rname, rid in links_added] cursor.executemany( ''' DELETE FROM remote_ticket_links WHERE source_name=%s AND source=%s AND type=%s AND destination_name=%s AND destination=%s''', records) # Record change history in ticket_change # Again we're going behind trac's back, so take care not to # obliterate existing records: # - If the field (end) has changed local links, as well as # changed remote links then update the record # - If the only change was to remote links then there is no # ticket_change record to update, so insert one if links_changed and orig_old_vals is not None: when_ts = to_utimestamp(ticket['changetime']) cursor.execute( ''' UPDATE ticket_change SET oldvalue=%s, newvalue=%s WHERE ticket=%s AND time=%s AND author=%s AND field=%s ''', (old_values[end], ticket[end], ticket.id, when_ts, author, end)) # Check that a row was updated, if so if cursor.rowcount >= 1: continue cursor.execute( ''' INSERT INTO ticket_change (ticket, time, author, field, oldvalue, newvalue) VALUES (%s, %s, %s, %s, %s, %s) ''', (ticket.id, when_ts, author, end, old_values[end], ticket[end]))