Example #1
0
 def validate_ticket(self, req, ticket):
     if req.args.get('action') == 'resolve' and req.args.get(
             'action_resolve_resolve_resolution') == 'fixed':
         links = TicketLinks(self.env, ticket)
         for i in links.blocked_by:
             if Ticket(self.env, i)['status'] != 'closed':
                 yield None, 'Ticket #%s is blocking this ticket' % i
Example #2
0
    def _build_graph(self, req, tkt_id):
        links = TicketLinks(self.env, tkt_id)

        g = graphviz.Graph()

        node_default = g['node']
        node_default['style'] = 'filled'

        edge_default = g['edge']
        edge_default['style'] = ''

        # Force this to the top of the graph
        g[tkt_id]

        links = sorted(links.walk(), key=lambda link: link.tkt.id)
        for link in links:
            tkt = link.tkt
            node = g[tkt.id]
            node['label'] = u'#%s' % tkt.id
            node['fillcolor'] = tkt['status'] == 'closed' and 'green' or 'red'
            node['URL'] = req.href.ticket(tkt.id)
            node['alt'] = u'Ticket #%s' % tkt.id
            node['tooltip'] = tkt['summary']

            for n in link.blocking:
                node > g[n]

        return g
Example #3
0
 def _prepare_links(self, tkt, db):
     links = TicketLinks(self.env, tkt, db)
     links.blocking = set(
         int(n) for n in self.NUMBERS_RE.findall(tkt['blocking'] or ''))
     links.blocked_by = set(
         int(n) for n in self.NUMBERS_RE.findall(tkt['blockedby'] or ''))
     return links
Example #4
0
    def post_process_request(self, req, template, data, content_type):
        if req.path_info.startswith('/ticket/'):
            # In case of an invalid ticket, the data is invalid
            if not data:
                return template, data, content_type
            tkt = data['ticket']
            links = TicketLinks(self.env, tkt)

            for i in links.blocked_by:
                if Ticket(self.env, i)['status'] != 'closed':
                    add_script(req, 'mastertickets/disable_resolve.js')
                    break

            # Add link to depgraph if needed
            if links:
                add_ctxtnav(req, 'Depgraph',
                            req.href.depgraph('ticket', tkt.id))

            for change in data.get('changes', {}):
                if not change.has_key('fields'):
                    continue
                for field, field_data in change['fields'].iteritems():
                    if field in self.fields:
                        if field_data['new'].strip():
                            new = set(
                                [int(n) for n in field_data['new'].split(',')])
                        else:
                            new = set()
                        if field_data['old'].strip():
                            old = set(
                                [int(n) for n in field_data['old'].split(',')])
                        else:
                            old = set()
                        add = new - old
                        sub = old - new
                        elms = tag()
                        if add:
                            elms.append(
                                tag.em(u', '.join(
                                    [unicode(n) for n in sorted(add)])))
                            elms.append(u' added')
                        if add and sub:
                            elms.append(u'; ')
                        if sub:
                            elms.append(
                                tag.em(u', '.join(
                                    [unicode(n) for n in sorted(sub)])))
                            elms.append(u' removed')
                        field_data['rendered'] = elms

        #add a link to generate a dependency graph for all the tickets in the milestone
        if req.path_info.startswith('/milestone/'):
            if not data:
                return template, data, content_type
            milestone = data['milestone']
            add_ctxtnav(req, 'Depgraph',
                        req.href.depgraph('milestone', milestone.name))

        return template, data, content_type
Example #5
0
 def ticket_deleted(self, tkt):
     db = self.env.get_db_cnx()
     
     links = TicketLinks(self.env, tkt, db)
     links.blocking = set()
     links.blocked_by = set()
     links.save('trac', 'Ticket #%s deleted'%tkt.id, when=None, db=db)
     
     db.commit()
Example #6
0
    def ticket_changed(self, tkt, comment, author, old_values):
        db = self.env.get_db_cnx()

        links = TicketLinks(self.env, tkt, db)
        links.blocking = set(self.NUMBERS_RE.findall(tkt['blocking'] or ''))
        links.blocked_by = set(self.NUMBERS_RE.findall(tkt['blockedby'] or ''))
        links.save(author, comment, tkt.time_changed, db)

        db.commit()
Example #7
0
    def post_process_request(self, req, template, data, content_type):
        if req.path_info.startswith('/ticket/'):
            tkt = data['ticket']
            links = TicketLinks(self.env, tkt)

            for i in links.blocked_by:
                if Ticket(self.env, i)['status'] != 'closed':
                    add_script(req, 'mastertickets/disable_resolve.js')
                    break

            data['mastertickets'] = {
                'field_values': {
                    'blocking': linkify_ids(self.env, req, links.blocking),
                    'blockedby': linkify_ids(self.env, req, links.blocked_by),
                },
            }

            # Add link to depgraph if needed
            if links:
                add_ctxtnav(req, 'Depgraph', req.href.depgraph(tkt.id))

            for change in data.get('changes', []):
                for field, field_data in change['fields'].iteritems():
                    if field in self.fields:
                        if field_data['new'].strip():
                            new = set(
                                [int(n) for n in field_data['new'].split(',')])
                        else:
                            new = set()
                        if field_data['old'].strip():
                            old = set(
                                [int(n) for n in field_data['old'].split(',')])
                        else:
                            old = set()
                        add = new - old
                        sub = old - new
                        elms = tag()
                        if add:
                            elms.append(
                                tag.em(u', '.join(
                                    [unicode(n) for n in sorted(add)])))
                            elms.append(u' added')
                        if add and sub:
                            elms.append(u'; ')
                        if sub:
                            elms.append(
                                tag.em(u', '.join(
                                    [unicode(n) for n in sorted(sub)])))
                            elms.append(u' removed')
                        field_data['rendered'] = elms

        return template, data, content_type
Example #8
0
    def process_request(self, req):
        path_info = req.path_info[10:]

        if not path_info:
            raise TracError('No ticket specified')

        tkt_id = path_info.split('/', 1)[0]
        g = self._build_graph(req, tkt_id)
        if '/' in path_info or 'format' in req.args:

            format = req.args.get('format')
            if format == 'text':
                req.send(str(g), 'text/plain')
            elif format == 'debug':
                import pprint
                req.send(pprint.pformat(TicketLinks(self.env, tkt_id)),
                         'text/plain')
            elif format is not None:
                req.send(g.render(self.dot_path, format), 'text/plain')

            if self.use_gs:
                ps = g.render(self.dot_path, 'ps2')
                gs = subprocess.Popen([
                    self.gs_path, '-q', '-dTextAlphaBits=4',
                    '-dGraphicsAlphaBits=4', '-sDEVICE=png16m', '-o',
                    '%stdout%', '-'
                ],
                                      stdin=subprocess.PIPE,
                                      stdout=subprocess.PIPE,
                                      stderr=subprocess.PIPE)
                img, err = gs.communicate(ps)
                if err:
                    self.log.debug('MasterTickets: Error from gs: %s', err)
            else:
                img = g.render(self.dot_path)
            req.send(img, 'image/png')
        else:
            data = {}

            tkt = Ticket(self.env, tkt_id)
            data['tkt'] = tkt
            data['graph'] = g
            data['graph_render'] = partial(g.render, self.dot_path)
            data['use_gs'] = self.use_gs

            add_ctxtnav(req, 'Back to Ticket #%s' % tkt.id,
                        req.href.ticket(tkt_id))
            return 'depgraph.html', data, None
Example #9
0
    def validate_ticket(self, req, ticket):
        db = self.env.get_db_cnx()
        cursor = db.cursor()

        tid = ticket.id
        links = self._prepare_links(ticket, db)

        if req.args.get('action') == 'resolve' and req.args.get(
                'action_resolve_resolve_resolution') == 'fixed':
            for i in links.blocked_by:
                if Ticket(self.env, i)['status'] != 'closed':
                    yield None, 'Ticket #%s is blocking this ticket' % i

        # Check that ticket does not have itself as a blocker
        if tid in links.blocking | links.blocked_by:
            yield 'blocked_by', 'This ticket is blocking itself'
            return

        # Check that there aren't any blocked_by in blocking or their parents
        blocking = links.blocking.copy()
        while len(blocking) > 0:
            if len(links.blocked_by & blocking) > 0:
                yield 'blocked_by', 'This ticket has circular dependencies'
                return
            new_blocking = set()
            for link in blocking:
                tmp_tkt = Ticket(self.env, link)
                new_blocking |= TicketLinks(self.env, tmp_tkt, db).blocking
            blocking = new_blocking

        for field in ('blocking', 'blockedby'):
            try:
                ids = self.NUMBERS_RE.findall(ticket[field] or '')
                for tid in ids[:]:
                    cursor.execute('SELECT id FROM ticket WHERE id=%s',
                                   (tid, ))
                    row = cursor.fetchone()
                    if row is None:
                        ids.remove(tid)
                ticket[field] = ', '.join(sorted(ids, key=lambda x: int(x)))
            except Exception, e:
                self.log.debug('MasterTickets: Error parsing %s "%s": %s',
                               field, ticket[field], e)
                yield field, 'Not a valid list of ticket IDs'
Example #10
0
    def process_request(self, req):
        realm = req.args['realm']
        id_ = req.args['id']

        if not which(self.dot_path):
            raise TracError(
                _("Path to dot executable is invalid: %(path)s",
                  path=self.dot_path))

        # Urls to generate the depgraph for a ticket is /depgraph/ticketnum
        # Urls to generate the depgraph for a milestone is
        # /depgraph/milestone/milestone_name

        # List of tickets to generate the depgraph.
        if realm == 'milestone':
            # We need to query the list of tickets in the milestone
            query = Query(self.env, constraints={'milestone': [id_]}, max=0)
            tkt_ids = [fields['id'] for fields in query.execute(req)]
        else:
            tid = as_int(id_, None)
            if tid is None:
                raise TracError(
                    tag_("%(id)s is not a valid ticket id.", id=html.tt(id_)))
            tkt_ids = [tid]

        # The summary argument defines whether we place the ticket id or
        # its summary in the node's label
        label_summary = 0
        if 'summary' in req.args:
            label_summary = int(req.args.get('summary'))

        g = self._build_graph(req, tkt_ids, label_summary=label_summary)
        if req.path_info.endswith('/depgraph.png') or 'format' in req.args:
            format_ = req.args.get('format')
            if format_ == 'text':
                # In case g.__str__ returns unicode, convert it in ascii
                req.send(
                    to_unicode(g).encode('ascii', 'replace'), 'text/plain')
            elif format_ == 'debug':
                import pprint

                req.send(
                    pprint.pformat(
                        [TicketLinks(self.env, tkt_id) for tkt_id in tkt_ids]),
                    'text/plain')
            elif format_ is not None:
                if format_ in self.acceptable_formats:
                    req.send(g.render(self.dot_path, format_), 'text/plain')
                else:
                    raise TracError(
                        _("The %(format)s format is not allowed.",
                          format=format_))

            if self.use_gs:
                ps = g.render(self.dot_path, 'ps2')
                gs = subprocess.Popen([
                    self.gs_path, '-q', '-dTextAlphaBits=4',
                    '-dGraphicsAlphaBits=4', '-sDEVICE=png16m',
                    '-sOutputFile=%stdout%', '-'
                ],
                                      stdin=subprocess.PIPE,
                                      stdout=subprocess.PIPE,
                                      stderr=subprocess.PIPE)
                img, err = gs.communicate(ps)
                if err:
                    self.log.debug('MasterTickets: Error from gs: %s', err)
            else:
                img = g.render(self.dot_path)
            req.send(img, 'image/png')
        else:
            data = {}

            # Add a context link to enable/disable labels in nodes.
            if label_summary:
                add_ctxtnav(req, 'Without labels',
                            req.href(req.path_info, summary=0))
            else:
                add_ctxtnav(req, 'With labels',
                            req.href(req.path_info, summary=1))

            if realm == 'milestone':
                add_ctxtnav(req, 'Back to Milestone: %s' % id_,
                            req.href.milestone(id_))
                data['milestone'] = id_
            else:
                data['ticket'] = id_
                add_ctxtnav(req, 'Back to Ticket #%s' % id_,
                            req.href.ticket(id_))
            data['graph'] = g
            data['graph_render'] = functools.partial(g.render, self.dot_path)
            data['use_gs'] = self.use_gs

            return 'depgraph.html', data, None
Example #11
0
    def process_request(self, req):
        path_info = req.path_info[10:]

        if not path_info:
            raise TracError('No ticket specified')

        #list of tickets to generate the depgraph for
        tkt_ids = []
        milestone = None
        split_path = path_info.split('/', 2)

        #Urls to generate the depgraph for a ticket is /depgraph/ticketnum
        #Urls to generate the depgraph for a milestone is /depgraph/milestone/milestone_name
        if split_path[0] == 'milestone':
            #we need to query the list of tickets in the milestone
            milestone = split_path[1]
            query = Query(self.env,
                          constraints={'milestone': [milestone]},
                          max=0)
            tkt_ids = [fields['id'] for fields in query.execute()]
        else:
            #the list is a single ticket
            tkt_ids = [int(split_path[0])]

        #the summary argument defines whether we place the ticket id or
        #it's summary in the node's label
        label_summary = 0
        if 'summary' in req.args:
            label_summary = int(req.args.get('summary'))

        g = self._build_graph(req, tkt_ids, label_summary=label_summary)
        if path_info.endswith('/depgraph.png') or 'format' in req.args:
            format = req.args.get('format')
            if format == 'text':
                #in case g.__str__ returns unicode, we need to convert it in ascii
                req.send(
                    to_unicode(g).encode('ascii', 'replace'), 'text/plain')
            elif format == 'debug':
                import pprint
                req.send(
                    pprint.pformat(
                        [TicketLinks(self.env, tkt_id) for tkt_id in tkt_ids]),
                    'text/plain')
            elif format is not None:
                req.send(g.render(self.dot_path, format), 'text/plain')

            if self.use_gs:
                ps = g.render(self.dot_path, 'ps2')
                gs = subprocess.Popen([
                    self.gs_path, '-q', '-dTextAlphaBits=4',
                    '-dGraphicsAlphaBits=4', '-sDEVICE=png16m',
                    '-sOutputFile=%stdout%', '-'
                ],
                                      stdin=subprocess.PIPE,
                                      stdout=subprocess.PIPE,
                                      stderr=subprocess.PIPE)
                img, err = gs.communicate(ps)
                if err:
                    self.log.debug('MasterTickets: Error from gs: %s', err)
            else:
                img = g.render(self.dot_path)
            req.send(img, 'image/png')
        else:
            data = {}

            #add a context link to enable/disable labels in nodes
            if label_summary:
                add_ctxtnav(req, 'Without labels',
                            req.href(req.path_info, summary=0))
            else:
                add_ctxtnav(req, 'With labels',
                            req.href(req.path_info, summary=1))

            if milestone is None:
                tkt = Ticket(self.env, tkt_ids[0])
                data['tkt'] = tkt
                add_ctxtnav(req, 'Back to Ticket #%s' % tkt.id,
                            req.href.ticket(tkt.id))
            else:
                add_ctxtnav(req, 'Back to Milestone %s' % milestone,
                            req.href.milestone(milestone))
            data['milestone'] = milestone
            data['graph'] = g
            data['graph_render'] = partial(g.render, self.dot_path)
            data['use_gs'] = self.use_gs

            return 'depgraph.html', data, None