source: peerreviewplugin/trunk/codereview/browser.py

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

PeerReviewPlugin: use /peerreviewfile/xx instead of /peerreviewperform?IDFile=xx as url for review files.

File size: 10.7 KB
Line 
1# -*- coding: utf-8 -*-
2
3from .model import ReviewFileModel
4from .peerReviewCommentCallback import writeJSONResponse, writeResponse
5from .peerReviewPerform import CommentAnnotator
6from .repo import hash_from_file_node
7from string import Template
8from trac.core import Component, implements
9from trac.util.html import tag
10from trac.mimeview.api import IHTMLPreviewAnnotator
11from trac.util.translation import _
12from trac.versioncontrol import RepositoryManager, ResourceNotFound
13from trac.versioncontrol.web_ui.util import get_existing_node
14from trac.web.api import IRequestFilter, IRequestHandler
15from trac.web.chrome import add_ctxtnav, add_script, add_script_data, add_stylesheet
16
17__author__ = 'Cinc'
18
19
20# Not used
21def files_with_comments(env, path, rev):
22    """Return a dict with file_id as key and a comment id list as value."""
23    with env.sb_query as db:
24        cursor = db.cursor()
25        cursor.execute("""SELECT f.file_id,
26        f.revision, f.changerevision, f.review_id , c.comment_id, c.line_num
27        FROM peerreviewfile AS f
28        JOIN peerreviewcomment as c ON c.file_id = f.file_id
29        WHERE f.path = %s
30        AND f.changerevision = %s
31        """, (path, rev))
32
33        for row in cursor:
34            env.log.info('### %s', row)
35
36# Not used
37def select_by_path(env, path):
38    """Returns a generator."""
39    rf = ReviewFileModel(env)
40    rf.clear_props()
41    rf['path'] = path
42    return rf.list_matching_objects()
43
44
45class PeerReviewBrowser(Component):
46    """Show information about file review status in Trac source code browser.
47
48    The file review status is only shown when displaying a source file.
49
50    '''Note''': This plugin may be disabled without side effects.
51    """
52    implements(IHTMLPreviewAnnotator, IRequestFilter, IRequestHandler)
53
54    # IRequestFilter methods
55
56    def pre_process_request(self, req, handler):
57        """Always returns the request handler, even if unchanged."""
58        return handler
59
60    def post_process_request(self, req, template, data, content_type):
61        """Do any post-processing the request might need;
62        `data` may be updated in place.
63
64        Always returns a tuple of (template, data, content_type), even if
65        unchanged.
66
67        Note that `template`, `data`, `content_type` will be `None` if:
68         - called when processing an error page
69         - the default request handler did not return any result
70        """
71        # Note that data is already filled with information about the source file, repo and what not
72        path = req.args.get('path')
73        rev = req.args.get('rev')
74
75        def is_file_with_comments(env, path, rev):
76            """Return a dict with file_id as key and a comment id list as value."""
77            with env.db_query as db:
78                cursor = db.cursor()
79                cursor.execute("""SELECT COUNT(f.file_id)
80                FROM peerreviewfile AS f
81                JOIN peerreviewcomment as c ON c.file_id = f.file_id
82                WHERE f.path = %s
83                AND f.changerevision = %s
84                """, (path, rev))
85                return cursor.fetchone()[0] != 0
86
87        #  We only handle the browser
88        split_path = req.path_info.split('/')
89        if path and req.path_info.startswith('/browser/') and data:
90            add_stylesheet(req, 'hw/css/peerreview.css')
91            add_script_data(req,
92                            {'peer_repo': data.get('reponame', ''),
93                             'peer_rev': data.get('created_rev', ''),
94                             'peer_is_head': 0 if rev else 1,
95                             'peer_path': path,
96                             'peer_status_url': req.href.peerreviewstatus(),
97                             # Index page has no data['dir'] if only the repoindex page is shown and
98                             # no default repo is defined
99                             'peer_is_dir': data.get('dir', None) != None or len(split_path) == 2,
100                             'tacUrl': req.href.chrome('/hw/images/thumbtac11x11.gif')})
101            add_script(req, "hw/js/peer_trac_browser.js")
102            # add_script(req, "hw/js/peer_review_perform.js")
103
104        # Deactivate code comments in browser view for now
105        if path and req.path_info.startswith('/browser_/'):
106            if is_file_with_comments(self.env, '/' + data['path'], data.get('created_rev')):
107                add_ctxtnav(req, _("Code Comments"), req.href(req.path_info, annotate='prcomment', rev=rev),
108                            title=_("Show Code Comments"))
109            else:
110                add_ctxtnav(req, tag.span(_("Code Comments"), class_="missing"))
111        return template, data, content_type
112
113    # IRequestHandler methods
114
115    def _create_status_tbl(self, req):
116        tr_tmpl = """<tr${bg_color}>
117        <td><a href="${file_href}">${file_id}</a></td>
118        <td><a href="${review_href}">${review_id}</a></td>
119        <td>${chg_rev}</td><td>${hash}</td><td>${status}</td>
120        </tr>
121        """
122        tbl_tmpl = """<h3>%s</h3>
123        <table class="listing peer-status-tbl">
124        <thead>
125            <tr>
126                <th>File ID</th><th>Review</th><th>Change Revision</th><th>Hash</th><th>Status</th>
127            </tr>
128        </thead>
129        <tbody>
130            %%s
131        </tbody>
132        </table>
133        """ % (_('Status Codereview'),)
134        not_head_tmpl = "<h3>%s</h3><p>%s</p>" %\
135                        (_('Status Codereview'), _('Codereview status is only available for HEAD revision.'))
136
137        if req.args.get('peer_is_dir') == 'true':
138            return ''
139
140        repo = req.args.get('peer_repo')
141        path = req.args.get('peer_path', '')
142
143        if repo:
144            # path starts with slash and reponame, like /reponame/my/path/to/file.txt
145            # All path information in the database is with leading '/'.
146            path = path[len(repo) + 1:]
147        rev = req.args.get('peer_rev')
148
149        # if req.args.getint('peer_is_head') == 0:
150        #     return not_head_tmpl
151
152        res = '<div id="peer-msg" class="system-message warning">%s</div>' %\
153              _('No review for this file revision yet.')
154        trows = ''
155        with self.env.db_query as db:
156            for row in db("SELECT review_id, changerevision, hash, status, file_id FROM peerreviewfile "
157                          "WHERE path = %s AND repo = %s AND review_id != 0 "
158                          "ORDER BY review_id", (path, repo)):
159                #  Colorize row with current file revision. Last review wins...
160                if row[1] == rev and row[3] == 'approved':
161                    bg = ' style="background-color: #dfd"'
162                    res = '<div id="peer-msg" class="system-message notice">' \
163                          '%s</div>' % _('File is <strong>approved</strong>.')
164                elif row[1] == rev and row[3] == 'disapproved':
165                    bg = ' style="background-color: #ffb"'
166                    res = '<div id="peer-msg" class="system-message warning">%s' \
167                          '</div>' % _('File is not <strong>approved</strong>.')
168                else:
169                    bg = ''
170
171                data = {'review_id': row[0],
172                        'chg_rev': row[1],
173                        'hash': row[2],
174                        'status': row[3],
175                        'file_id': row[4],
176                        'file_href': req.href.peerreviewfile(row[4]),
177                        'review_href': req.href.peerreviewview(row[0]),
178                        'bg_color': bg}
179                trows += Template(tr_tmpl).safe_substitute(data)
180        if trows:
181            return tbl_tmpl % trows  #  + res
182        else:
183            return "<h3>%s</h3>" % _('Status Codereview') + res
184
185    def match_request(self, req):
186        return req.path_info == '/peerreviewstatus'
187
188    def process_request(self, req):
189        tr = """<tr><td colspan="2"><strong>{label}</strong>&nbsp;{hash}</td></tr>"""
190        data = {'statushtml': self._create_status_tbl(req),
191                'hashhtml': tr.format(label="Hash:", hash=self.get_hash_for_file(req))}
192        writeJSONResponse(req, data)
193
194    def get_hash_for_file(self, req):
195        """Return the hash for the currently viewed file.
196
197        :param req: Request object. The arg dict holds file information like path, revision, ...
198        :return file hash string or an empty string. The hash is in hex
199        """
200        if req.args.get('peer_dir') == 'true':
201            return None
202
203        reponame = req.args.get('peer_repo')
204        path = req.args.get('peer_path', '')
205        if reponame:
206            # path starts with slash and reponame, like /reponame/my/path/to/file.txt
207            # All path information in the database is with leading '/'.
208            path = path[len(reponame) + 1:]
209        rev = req.args.get('peer_rev')
210        node = None
211        repos = RepositoryManager(self.env).get_repository(reponame)
212
213        try:
214            node = get_existing_node(req, repos, path, rev)
215        except ResourceNotFound:
216            # The file may be a SVN copy. If so the revision is the one from the
217            # source file, the path is the current (not source) path.
218            # Search the history. This probably breaks when several copies are made.
219            src_path, src_rev, chnge = range(3)
220            for item in repos.get_path_history(path, limit=1):  # this is a generator
221                if item[chnge] in ('copy', 'move'):
222                    node = get_existing_node(req, repos, item[src_path], item[src_rev])
223                    break
224        if node:
225            return hash_from_file_node(node)
226        else:
227            return ''
228
229    # IHTMLPreviewAnnotator methods
230
231    def get_annotation_type(self):
232        # Disable annotator in browser view for now
233        return 'prcomment_', 'Comment', 'Review Coment'
234
235    def get_annotation_data(self, context):
236
237
238        self.log.info(context)
239        self.log.info('parent: %s %s', context.parent, context.parent.resource)
240        self.log.info('%s %s', context.resource, context.resource.realm)
241        self.log.info('parent: %s %s', context.resource.parent, context.resource.parent.realm)
242        self.log.info(context.resource.id)
243
244        return CommentAnnotator(self.env, context, 'chrome/hw/images/thumbtac11x11.gif', 'prcomment')
245
246    def annotate_row(self, context, row, lineno, line, comment_annotator):
247        """line annotator for Perform Code Review page.
248
249        If line has a comment, places an icon to indicate comment.
250        """
251        # self.log.info(lineno)
252        #comment_col = tag.th(style='color: red', class_='prcomment')
253        #row.append(comment_col)
254        comment_annotator.annotate_browser(row, lineno)
Note: See TracBrowser for help on using the repository browser.