source: peerreviewplugin/trunk/codereview/timeline.py

Last change on this file was 18283, checked in by Cinc-th, 2 years ago

PeerReviewPlugin: improve presentation of reviews in timeline. Respect timeline->changeset_show_files setting from trac.ini when rendering review files in timeline. Render user name instead of login name on timeline for reviewers and review creators.

File size: 6.0 KB
Line 
1# -*- coding: utf-8 -*-
2#
3# Copyright (C) 2016-2021 Cinc
4# All rights reserved.
5#
6# This software is licensed as described in the file COPYING.txt, which
7# you should have received as part of this distribution.
8#
9# Author: Cinc
10#
11from .model import PeerReviewModel, PeerReviewerModel, ReviewFileModel
12from trac.core import Component, implements
13from trac.wiki.formatter import format_to_html
14from trac.resource import Resource, get_resource_url
15from trac.timeline.api import ITimelineEventProvider
16from trac.util.datefmt import from_utimestamp, to_utimestamp
17from trac.util.html import html as tag
18from trac.util.text import to_unicode
19from trac.util.translation import _
20from trac.web.chrome import add_stylesheet, Chrome
21
22
23class PeerReviewTimeline(Component):
24    """Provide code review events for the timeline.
25
26    [[BR]]
27    You need permission {{{CODE_REVIEW_VIEW}}} to see code review events.
28
29    '''Note:''' It is safe to disable this plugin when no timeline for code reviews should be shown.
30    """
31
32    implements(ITimelineEventProvider)
33
34    # ITimelineEventProvider methods
35
36    def get_timeline_filters(self, req):
37        if 'CODE_REVIEW_VIEW' in req.perm:
38            yield ('peerreview', _('Code Reviews'))
39
40    def get_timeline_events(self, req, start, stop, filters):
41        if 'peerreview' in filters:
42            ts_start = to_utimestamp(start)
43            ts_stop = to_utimestamp(stop)
44
45            coderev_resource = Resource('peerreview')
46            author_short = Chrome(self.env).authorinfo_short
47
48            add_stylesheet(req, 'hw/css/peerreview.css')
49
50            def reviewers_for_review(rev_id):
51                rm = PeerReviewerModel(self.env)
52                rm.clear_props()
53                rm['review_id'] = rev_id
54                reviewers = [author_short(reviewer['reviewer']) for reviewer in rm.list_matching_objects()]
55                return reviewers
56
57            def get_files_for_review_id(review_id):
58                """Get all files belonging to the given review id. Provide the number of comments if asked for."""
59                rfm = ReviewFileModel(self.env)
60                rfm.clear_props()
61                rfm['review_id'] = review_id
62                rev_files = list(rfm.list_matching_objects())
63                return rev_files
64
65            with self.env.db_query as db:
66                reviews = {}
67                for rid, t, author, field, oldvalue, newvalue \
68                        in db("""
69                                        SELECT pc.review_id, pc.time, pc.author,
70                                               pc.field, pc.oldvalue, pc.newvalue
71                                        FROM peerreview_change AS pc
72                                        WHERE pc.time>=%s AND pc.time<=%s
73                                        ORDER BY pc.time, pc.review_id
74                                        """, (ts_start, ts_stop)):
75                    if not (oldvalue or newvalue):
76                        # ignore empty change corresponding to custom field
77                        # created (None -> '') or deleted ('' -> None)
78                        continue
79                    if field == 'status':
80                        try:
81                            codereview, reviewers_list, coderev_page, files = reviews[rid]
82                        except KeyError:
83                            reviews[rid] = [PeerReviewModel(self.env, rid),
84                                            reviewers_for_review(rid),
85                                            coderev_resource(id=rid),
86                                            get_files_for_review_id(rid)
87                                            ]
88                            codereview, reviewers_list, coderev_page, files = reviews[rid]
89                        yield('peerreview', from_utimestamp(t), codereview['owner'],
90                              (coderev_page, codereview['name'], codereview['notes'],
91                               reviewers_list, oldvalue, newvalue, files))
92
93
94    def render_timeline_event(self, context, field, event):
95        codereview_page, name, notes, reviewersList, oldstatus, newstatus, files = event[3]
96        num_files = self.config.getint('timeline', 'changeset_show_files', 0)
97
98        if field == 'url':
99            return get_resource_url(self.env, codereview_page, context.href)
100        if field == 'title':
101            return tag(_('Code review '), tag.em(name), " (%s)" % codereview_page.id,
102                       _(": Status changed from '%s' to '%s'" % (oldstatus, newstatus))
103                       )
104
105        def filelist():
106            def create_li(f):
107                return tag.li(tag.div(),
108                                 tag.a('%s @ %s' % (f['path'], f['changerevision']),
109                                       href='peerreviewfile/%s' % f['file_id']
110                                       )
111                                )
112
113            ul = tag.ul(class_="rfiles")
114
115            if num_files == 0:
116                # don't show
117                return None
118            elif num_files == -1:
119                # unlimited
120                for rfile in files:
121                    ul.append(create_li(rfile))
122            else:
123                for rfile in files[:num_files]:
124                    ul.append(create_li(rfile))
125                if num_files < len(files):
126                    ul.append(tag.li(u'…'))
127            return ul
128
129        if field == 'description':
130            return tag(_('Assigned to: '),
131                       tag.ul([tag.li(reviewer) for reviewer in reviewersList],
132                              class_='userlist'),
133                       tag.div(_('Additional notes:')) if notes else None,
134                       tag.div(
135                           format_to_html(self.env, context, notes),
136                           class_='notes'
137                       ) if notes else None,
138                       tag.div(_('Files:')) if num_files else None,
139                       filelist()
140                       )
Note: See TracBrowser for help on using the repository browser.