source: pullrequestsplugin/tags/pullrequestsplugin-1.1/pullrequests/web_ui.py

Last change on this file was 17366, checked in by lucid, 4 years ago

PullRequestsPlugin: Show warning if PR not found.
(fix #13556)

File size: 9.2 KB
Line 
1# -*- coding: utf-8 -*-
2
3import re
4
5from datetime import datetime
6
7from genshi.builder import tag
8
9from trac.admin import *
10from trac.config import ListOption
11from trac.core import *
12from trac.resource import Resource
13from trac.ticket.api import ITicketManipulator
14from trac.util import get_reporter_id
15from trac.util.datefmt import format_datetime, utc
16from trac.util.presentation import Paginator
17from trac.web.api import IRequestFilter
18from trac.web.chrome import Chrome, add_link, add_script, add_stylesheet, add_notice, add_warning, web_context
19from trac.wiki.formatter import format_to_oneliner
20from trac.wiki.macros import WikiMacroBase
21from trac.wiki.api import parse_args
22
23from pullrequests.model import PullRequest
24
25
26class PullRequestsModule(Component):
27
28    implements(IAdminPanelProvider, IRequestFilter, ITicketManipulator)
29
30    create_commands = ListOption('pullrequests', 'create_commands', 'open')
31    update_commands = ListOption('pullrequests', 'update_commands', 'reviewed, closed')
32
33    # IAdminPanelProvider methods
34
35    def get_admin_panels(self, req):
36        if 'PULL_REQUEST' in req.perm:
37            yield ('pr', "Pull Requests", 'list', "List")
38
39    def render_admin_panel(self, req, category, panel, path_info):
40        if panel == 'list':
41            req.perm.require('PULL_REQUEST')
42            return self.render_pr_list_panel(req, category, panel, path_info)
43
44    def render_pr_list_panel(self, req, category, panel, path_info):
45        def format_datetime_utc(t):
46            return format_datetime(t, tzinfo=utc, locale=getattr(req, 'lc_time', None))
47
48        def format_wikilink(pr):
49            resource = Resource('ticket', pr.ticket)
50            context = web_context(req, resource)
51            return format_to_oneliner(self.env, context, pr.wikilink)
52
53        # Detail view?
54        if path_info:
55            id = path_info
56            pr = PullRequest.select_by_id(self.env, id)
57            if not pr:
58                raise TracError("Pull request does not exist!")
59            if req.method == 'POST':
60                if req.args.get('save'):
61                    pr.status = req.args.get('status')
62                    pr.add_reviewer(req.authname)
63
64                    PullRequest.update_status_and_reviewers(self.env, pr)
65                    add_notice(req, 'Your changes have been saved.')
66                    req.redirect(req.href.admin(category, panel))
67                elif req.args.get('cancel'):
68                    req.redirect(req.href.admin(category, panel))
69
70            Chrome(self.env).add_wiki_toolbars(req)
71            data = {'view': 'detail',
72                    'pr': pr,
73                    'statuses': self.create_commands + self.update_commands,
74                    'format_datetime_utc': format_datetime_utc,
75            }
76
77        else:
78            #Pagination
79            page = int(req.args.get('page', 1))
80            max_per_page = int(req.args.get('max', 10))
81
82            prs = PullRequest.select_all_paginated(self.env, page, max_per_page)
83            total_count = PullRequest.count_all(self.env)
84
85            paginator = Paginator(prs, page - 1, max_per_page, total_count)
86            if paginator.has_next_page:
87                next_href = req.href.admin(category, panel, max=max_per_page, page=page + 1)
88                add_link(req, 'next', next_href, 'Next Page')
89            if paginator.has_previous_page:
90                prev_href = req.href.admin(category, panel, max=max_per_page, page=page - 1)
91                add_link(req, 'prev', prev_href, 'Previous Page')
92   
93            pagedata = []
94            shown_pages = paginator.get_shown_pages(21)
95            for page in shown_pages:
96                pagedata.append([req.href.admin(category, panel, max=max_per_page, page=page), None,
97                                str(page), 'Page %d' % (page,)])
98            paginator.shown_pages = [dict(zip(['href', 'class', 'string', 'title'], p)) for p in pagedata]
99            paginator.current_page = {'href': None, 'class': 'current',
100                                    'string': str(paginator.page + 1),
101                                    'title':None}
102            data = {'view': 'list',
103                    'paginator': paginator,
104                    'max_per_page': max_per_page,
105                    'prs': prs,
106                    'format_datetime_utc': format_datetime_utc,
107                    'format_wikilink': format_wikilink,
108            }
109
110        return 'pullrequests.html', data, None
111
112    # IRequestFilter methods
113
114    def pre_process_request(self, req, handler):
115        return handler
116
117    def post_process_request(self, req, template, data, content_type):
118        path = req.path_info
119        if path.startswith('/ticket/'):
120            if data and 'ticket' in data and 'fields' in data:
121                self._append_pr_links(req, data)
122        return template, data, content_type
123
124    def _append_pr_links(self, req, data):
125        rendered = ''
126        ticket = data['ticket']
127        items = PullRequest.select(self.env, ticket=str(ticket.id))
128        if items:
129            ticket.values['PRs'] = True # Activates field
130            results = []
131            for pr in reversed(items):
132                label = 'PR:%s (%s)' % (pr.id, pr.status)
133                href = req.href.ticket(pr.ticket) + '#comment:%s' % (pr.comment,)
134                link = tag.a(label, href=href)
135                results.append(link)
136            rendered = tag.span(*[e for pair in zip(results, [' '] * len(results)) for e in pair][:-1])
137        data['fields'].append({
138            'name': 'PRs',
139            'rendered': rendered,
140            'type': 'textarea', # Full row
141        })
142
143    # ITicketManipulator methods
144
145    command_pr_url = r'(?P<command>[A-Za-z]+) PR: (?P<wikilink>\S+)'
146    command_pr_id = r'(?P<command>[A-Za-z]+) PR:(?P<id>[0-9]+)'
147
148    command_pr_url_re = re.compile(command_pr_url)
149    command_pr_id_re = re.compile(command_pr_id)   
150
151    def prepare_ticket(self, req, ticket, fields, actions):
152        pass
153
154    def validate_ticket(self, req, ticket):
155        if 'preview' in req.args:
156            # During preview: Do nothing
157            return []
158
159        if 'PULL_REQUEST' in req.perm:
160            comment = req.args.get('comment')
161            author = get_reporter_id(req, 'author')
162            if comment:
163                req.args['comment'] = self._handle_comment(req, ticket, comment, author)
164        return []
165
166    def _handle_comment(self, req, ticket, comment, author):
167        def create_pr_and_inline_id(m):
168            command = m.group('command')
169            wikilink = m.group('wikilink')
170            if command in self.create_commands:
171                id = None
172                status = command
173                reviewers = ''
174                opened = modified = datetime.now(utc)
175                comment_number = (ticket.get_comment_number(ticket['changetime']) or 0) + 1
176                pr = PullRequest(id, status, author, reviewers, opened, modified, ticket.id, comment_number, wikilink)
177                PullRequest.add(self.env, pr)
178                add_notice(req, 'A new pull request has been created.')
179
180                return u'%s PR:%s %s' % (command, pr.id, wikilink)
181            else:
182                return m.group(0)
183            return u'[%s %s]' % (('/hours/%s' % ticket.id), match.group())
184        comment = self.command_pr_url_re.sub(create_pr_and_inline_id, comment)
185
186        for m in self.command_pr_id_re.finditer(comment):
187            command = m.group('command')
188            id = m.group('id')
189            if command in self.update_commands:
190                pr = PullRequest.select_by_id(self.env, id)
191                if pr is None:
192                    add_warning(req, 'Pull request %s was not found.' % (id,))
193                    continue
194                pr.status = command
195                pr.add_reviewer(author)
196                PullRequest.update_status_and_reviewers(self.env, pr)
197                add_notice(req, 'Pull request %s has been updated.' % (id,))
198
199        return comment
200
201
202class PRQueryMacro(WikiMacroBase):
203    """List all matching pull requests.
204   
205    Example:
206    {{{
207        [[PRQuery(status=reviewed,author=joe)]]
208    }}}
209    """
210
211    def expand_macro(self, formatter, name, content):
212        args, kw = parse_args(content)
213        status = kw.get('status')
214        author = kw.get('author')
215        items = PullRequest.select(self.env, status=status, author=author)
216
217        rows = [tag.tr(
218                    tag.td(pr.id),
219                    tag.td(pr.status),
220                    tag.td(tag.a('#%s' % (pr.ticket,), href=formatter.href.ticket(pr.ticket) + '#comment:%s' % (pr.comment,))),
221                    tag.td(format_to_oneliner(self.env, formatter.context, pr.wikilink)),
222                    tag.td(pr.author),
223                    tag.td(pr.reviewers),
224                    class_='odd' if idx % 2 else 'even')
225                for idx, pr in enumerate(items)]
226        if not rows:
227            rows = [tag.tr(tag.td('No pull requests found', colspan=6, class_='even'))]
228
229        return tag.table(
230            tag.thead(
231                tag.tr(
232                    tag.th('PR'),
233                    tag.th('Status'),
234                    tag.th('Ticket'),
235                    tag.th('Changes'),
236                    tag.th('Author'),
237                    tag.th('Reviewers'),
238                    class_='trac-columns')),
239            tag.tbody(rows),
240            class_='listing')
Note: See TracBrowser for help on using the repository browser.