source: peerreviewplugin/tags/0.12/3.1/codereview/peerReviewPerform.py

Last change on this file was 17260, checked in by Cinc-th, 5 years ago

PeerReviewPlugin: add debug output for mime type used for generating preview.

File size: 14.9 KB
Line 
1#
2# Copyright (C) 2005-2006 Team5
3# All rights reserved.
4#
5# This software is licensed as described in the file COPYING.txt, which
6# you should have received as part of this distribution.
7#
8# Author: Team5
9#
10
11# Code Review plugin
12# This class handles the display for the perform code review page
13# The file contents are taken from the respository and converted to
14# an HTML friendly format.  The line annotator customizes the
15# repository browser's line number to indicate what lines are being
16# reviewed and if there are any comments on a particular line.
17
18from genshi.core import QName
19from genshi.filters.transform import Transformer
20from pkg_resources import get_distribution, parse_version
21from trac.core import *
22from trac.mimeview import *
23from trac.mimeview.api import IHTMLPreviewAnnotator
24from trac.util import format_date
25from trac.util.html import html as tag
26from trac.web.chrome import INavigationContributor, ITemplateStreamFilter, Chrome, \
27                            add_link, add_stylesheet, add_script, add_script_data
28from trac.web.main import IRequestHandler
29from trac.versioncontrol.web_ui.util import *
30from trac.versioncontrol.api import RepositoryManager
31from trac.versioncontrol.diff import diff_blocks, get_diff_options
32from model import Comment, PeerReviewModel, ReviewFileModel
33from peerReviewMain import add_ctxt_nav_items
34from repo import file_data_from_repo
35from util import not_allowed_to_comment, review_is_finished, review_is_locked
36
37
38class PeerReviewPerform(Component):
39    """Perform a code review.
40
41    [[BR]]
42    Trac 0.12 comes with a very ancient version of jQuery. This plugin replaces that version with 1.11.2 on the
43    fly. Similar to Trac 1.0 you may specify your own jQuery in your config file.
44
45    {{{#!ini
46    [trac]
47    jquery_location = https://path/to/jquery.js
48    }}}
49    If not set the bundled version will be used.
50
51    The same can be done for the jQuery UI package and the theme to use.
52    {{{#!ini
53    [trac]
54    jquery_ui_location = https://path/to/jquery-ui.js
55    jquery_ui_theme_location = https://path/to/jquery-ui-theme.css
56    }}}
57    jQuery-ui 1.11.4 is bundled with this plugin.
58    """
59    implements(INavigationContributor, IRequestHandler, IHTMLPreviewAnnotator, ITemplateStreamFilter)
60
61    imagePath = ''
62    trac_version = get_distribution('trac').version
63    legacy_trac = parse_version(trac_version) < parse_version('1.0.0')  # True if Trac V0.12.x
64
65    # ITextAnnotator methods
66    def get_annotation_type(self):
67        return 'performCodeReview', 'Line', 'Line numbers'
68
69    def get_annotation_data(self, context):
70        r_file = context.get_hint('reviewfile')
71        authname = context.get_hint('authname')
72        perm = context.get_hint('perm')
73        review = PeerReviewModel(self.env, r_file['review_id'])
74
75        # Is it allowed to comment on the file?
76        if review_is_finished(self.env.config, review):
77            is_locked = True
78        else:
79            is_locked = review_is_locked(self.env.config, review, authname)
80
81        # Don't let users comment who are not part of this review
82        if not_allowed_to_comment(self.env, review, perm, authname):
83            is_locked = True
84
85        data = [[c.line_num for c in Comment.select_by_file_id(self.env, r_file['file_id'])],
86                review, is_locked]
87        return data
88
89    #line annotator for Perform Code Review page
90    #if line has a comment, places an icon to indicate comment
91    #if line is not in the rage of reviewed lines, it makes
92    #the color a light gray
93    def annotate_row(self, context, row, lineno, line, data):
94        r_file = context.get_hint('reviewfile')
95        if (lineno <= int(r_file['line_end']) and lineno >= int(r_file['line_start'])) or int(r_file['line_start']) == 0:
96            # If there is a comment on this line
97            lines = data[0]
98            # review = data[1]
99            if lineno in lines:
100                return row.append(tag.th(id='L%s' % lineno)(tag.a(tag.img(src='%s' % self.imagePath) + ' ' + str(lineno),
101                                                                  href='javascript:getComments(%s, %s)' %
102                                                                       (lineno, r_file['file_id']))))
103            if not data[2]:
104                return row.append(tag.th(id='L%s' % lineno)(tag.a(lineno, href='javascript:addComment(%s, %s, -1)'
105                                                                           % (lineno, r_file['file_id']))))
106            else:
107                return row.append(tag.th(str(lineno), id='L%s' % lineno))
108
109        #color line numbers outside range light gray
110        return row.append(tag.th(id='L%s' % lineno)(tag.font(lineno, color='#CCCCCC')))
111
112    # ITemplateStreamFilter
113
114    def filter_stream(self, req, method, filename, stream, data):
115        def repl_jquery(name, event):
116            """ Replace Trac jquery.js with jquery.js coming with plugin. """
117            attrs = event[1][1]
118            #match=re.match(self.PATH_REGEX, req.path_info)
119            #if match and attrs.get(name) and attrs.get(name).endswith("common/js/jquery.js"):
120            if attrs.get(name):
121                if attrs.get(name).endswith("common/js/jquery.js"):
122                    jquery = self.env.config.get('trac', 'jquery_location')
123                    if jquery:
124                        attrs -= name
125                        attrs |= [(QName(name), jquery)]
126                    else:
127                        return attrs.get(name).replace("common/js/jquery.js", 'hw/js/jquery-1.11.2.min.js')
128                elif attrs.get(name) and attrs.get(name).endswith("common/js/keyboard_nav.js"):
129                    #keyboard_nav.js uses function live() which was removed with jQuery 1.9. Use a fixed script here
130                    return attrs.get(name) .replace("common/js/keyboard_nav.js", 'req/js/keyboard_nav.js')
131            return attrs.get(name) #.replace('#trac-add-comment', '?minview')
132
133        # Replace jQuery with a more recent version when using Trac 0.12
134        if self.legacy_trac:
135            stream = stream | Transformer('//head/script').attr('src', repl_jquery)
136        return stream
137
138    # INavigationContributor methods
139
140    def get_active_navigation_item(self, req):
141        return 'peerReviewMain'
142
143    def get_navigation_items(self, req):
144        return []
145
146    # IRequestHandler methods
147    def match_request(self, req):
148        return req.path_info == '/peerReviewPerform'
149
150    def process_request(self, req):
151        req.perm.require('CODE_REVIEW_DEV')
152
153        #get the fileID from the request arguments
154        fileid = req.args.get('IDFile')
155        if not fileid:
156            raise TracError("No file ID given - unable to load page.", "File ID Error")
157
158        #make the thumbtac image global so the line annotator has access to it
159        self.imagePath = 'chrome/hw/images/thumbtac11x11.gif'
160
161        data = {'file_id': fileid}
162
163        r_file = ReviewFileModel(self.env, fileid)  # This will replace rfile
164        review = PeerReviewModel(self.env, r_file['review_id'])
165        review.date = format_date(review['created'])
166        data['review_file'] = r_file
167        data['review'] = review
168
169        repos = RepositoryManager(self.env).get_repository(r_file['repo'])
170        if not repos:
171            raise TracError("Unable to acquire subversion repository.",
172                            "Subversion Repository Error")
173
174        # The following may raise an exception if revision can't be found
175        rev = r_file['changerevision']
176        if rev:
177            rev = repos.normalize_rev(rev)
178        rev_or_latest = rev or repos.youngest_rev
179        node = get_existing_node(self.env, repos, r_file['path'], rev_or_latest)
180
181        # Data for parent review if any
182        if review['parent_id'] != 0:
183            par_review = PeerReviewModel(self.env, review['parent_id'])  # Raises 'ResourceNotFound' on error
184            par_review.date = format_date(par_review['created'])
185            par_file = ReviewFileModel(self.env, get_parent_file_id(self.env, r_file, review['parent_id']))
186            lines = [c.line_num for c in Comment.select_by_file_id(self.env, par_file['file_id'])]
187            par_file.comments = list(set(lines))  # remove duplicates
188            par_revision = par_file['revision']
189            if par_revision:
190                par_revision = repos.normalize_rev(par_revision)
191            rev_or_latest = par_revision or repos.youngest_rev
192            par_node = get_existing_node(self.env, repos, par_file['path'], rev_or_latest)
193        else:
194            par_review = None
195            par_file = None  # TODO: there may be some error handling missing for this. Create a dummy here?
196        data['par_file'] = par_file
197        data['parent_review'] = par_review
198
199        # Wether to show the full file in the browser.
200        if int(r_file['line_start']) == 0:
201            data['fullrange'] = True
202        else:
203            data['fullrange'] = False
204
205        # Generate HTML preview - this code take from Trac - refer to their documentation
206        mime_type = node.content_type
207        self.env.log.debug("mime_type taken from node.content_type: %s" % (mime_type,))
208        if not mime_type or mime_type == 'application/octet-stream':
209            mime_type = get_mimetype(node.name) or mime_type or 'text/plain'
210
211        ctpos = mime_type.find('charset=')
212        if ctpos >= 0:
213            charset = mime_type[ctpos + 8:]
214        else:
215            charset = None
216
217        mimeview = Mimeview(self.env)
218        rev = None  # Is this correct? Seems to work with the call 'rev=rev or node.rev' further down
219        content = node.get_content().read(mimeview.max_preview_size)  # We get the raw data without keyword substitution
220        if not is_binary(content):
221            if mime_type != 'text/plain':
222                plain_href = self.env.href.peerReviewBrowser(node.path, rev=rev or node.rev, format='txt')
223                add_link(req, 'alternate', plain_href, 'Plain Text', 'text/plain')
224
225        if par_review:
226            # A followup review with diff viewer
227            create_diff_data(req, data, node, par_node)
228        else:
229            context = Context.from_request(req, 'source', node.path, node.created_rev)
230            context.set_hints(reviewfile=r_file)
231            context.set_hints(authname=req.authname)
232            context.set_hints(perm=req.perm)
233
234            self.env.log.debug("Creating preview data for %s with mime_type = %s" % (node.created_path, mime_type))
235            preview_data = mimeview.preview_data(context, content, len(content),
236                                                 mime_type, node.created_path,
237                                                 None,
238                                                 annotations=['performCodeReview'])
239            data['preview'] = preview_data
240            # TODO: use in template 'preview.rendered' instead similar to preview_file.html
241            data['file_rendered'] = preview_data['rendered']
242
243        # A finished review can't be changed anymore except by a manager
244        data['is_finished'] = review_is_finished(self.env.config, review)
245        # A user can't chnage his voting for a reviewed review
246        data['review_locked'] = review_is_locked(self.env.config, review, req.authname)
247        data['not_allowed'] = not_allowed_to_comment(self.env, review, req.perm, req.authname)
248
249        scr_data = {'peer_comments': sorted(list(set([c.line_num for c in
250                                               Comment.select_by_file_id(self.env, r_file['file_id'])]))),
251                    'peer_file_id': fileid,
252                    'peer_review_id': r_file['review_id'],
253                    'auto_preview_timeout': self.env.config.get('trac', 'auto_preview_timeout', '2.0'),
254                    'form_token': req.form_token,
255                    'peer_diff_style': data['style'] if 'style' in data else 'no_diff'}
256        if par_review:
257            scr_data['peer_parent_file_id'] = par_file['file_id']
258            scr_data['peer_parent_comments'] = sorted(list(set([c.line_num for c in
259                                                         Comment.select_by_file_id(self.env, par_file['file_id'])])))
260        else:
261            scr_data['peer_parent_file_id'] = 0  # Mark that we don't have a parent
262            scr_data['peer_parent_comments'] = []
263
264        # For comment dialogs when using Trac 0.12. Otherwise use jQuery coming with Trac
265        if self.legacy_trac:
266            add_script(req, self.env.config.get('trac', 'jquery_ui_location') or
267                            'hw/js/jquery-ui-1.11.4.min.js')
268            add_stylesheet(req, self.env.config.get('trac', 'jquery_ui_theme_location') or
269                           'hw/css/jquery-ui-1.11.4.min.css')
270        else:
271            Chrome(self.env).add_jquery_ui(req)
272
273        add_stylesheet(req, 'common/css/code.css')
274        add_stylesheet(req, 'common/css/diff.css')
275        add_stylesheet(req, 'hw/css/peerreview.css')
276        add_script_data(req, scr_data)
277        add_script(req, 'common/js/auto_preview.js')
278        add_script(req, "hw/js/peer_review_perform.js")
279        add_ctxt_nav_items(req)
280
281        return 'peerReviewPerform.html', data, None
282
283
284def get_parent_file_id(env, r_file, par_review_id):
285
286    fid = u"%s%s%s" % (r_file['path'], r_file['line_start'], r_file['line_end'])
287
288    rfiles = ReviewFileModel.select_by_review(env, par_review_id)
289    for f in rfiles:
290        tmp = u"%s%s%s" % (f['path'], f['line_start'], f['line_end'])
291        if tmp == fid:
292            return f['file_id']
293    return 0
294
295
296def create_diff_data(req, data, node, par_node):
297    style, options, diff_data = get_diff_options(req)
298
299    old = file_data_from_repo(par_node)
300    new = file_data_from_repo(node)
301
302    if old == new:
303        data['nochanges'] = True
304
305    if diff_data['options']['contextall']:
306        context = None
307    else:
308        context = diff_data['options']['contextlines']
309
310    diff = diff_blocks(old, new, context=context,
311                       ignore_blank_lines=diff_data['options']['ignoreblanklines'],
312                       ignore_case=diff_data['options']['ignorecase'],
313                       ignore_space_changes=diff_data['options']['ignorewhitespace'])
314
315    review = data['review']
316    par_review = data['parent_review']
317    changes = []
318    info = {# 'title': '',
319            # 'comments': 'Ein Kommentar',
320            'diffs': diff,
321            'new': {'path': node.path, 'rev': "%s (Review #%s)" % (node.rev, review['review_id']), 'shortrev': node.rev},
322            'old': {'path': par_node.path, 'rev': "%s (Review #%s)" % (par_node.rev, par_review['review_id']),
323                    'shortrev': par_node.rev},
324            'props': []}
325    changes.append(info)
326    data['changes'] = changes
327
328    data['diff'] = diff_data  # {'style': 'inline', 'options': []},
329    data['longcol'] = 'Revision',
330    data['shortcol'] = 'r'
331    data['style'] = style
Note: See TracBrowser for help on using the repository browser.