source: mergebot/trunk/mergebot/web_ui.py @ 16

Last change on this file since 16 was 16, checked in by retracile, 15 years ago

Mergebot: Codebase as released with permission from CommProve?, plus cleanups to remove traces of that environment.

File size: 11.2 KB
Line 
1#!/usr/bin/env python
2
3import random
4
5from trac.core import *
6from trac import util
7from trac.ticket.query import Query
8from trac.ticket.model import Ticket
9from trac.config import Option
10from trac.perm import IPermissionRequestor
11from trac.web.main import IRequestHandler
12from trac.web.chrome import INavigationContributor, ITemplateProvider
13
14from MergeActor import MergeActor
15from BranchActor import BranchActor
16from RebranchActor import RebranchActor
17from CheckMergeActor import CheckMergeActor
18
19class MergeBotModule(Component):
20    implements(INavigationContributor, IPermissionRequestor, IRequestHandler, ITemplateProvider)
21
22    # INavigationContributor
23    # so it shows up in the main nav bar
24    def get_active_navigation_item(self, req):
25        return 'mergebot'
26    def get_navigation_items(self, req):
27        """Generator that yields the MergeBot tab, but only if the user has
28        MERGEBOT_VIEW privs."""
29        if req.perm.has_permission("MERGEBOT_VIEW"):
30            label = util.Markup('<a href="%s">MergeBot</a>' % \
31                req.href.mergebot())
32            yield ('mainnav', 'mergebot', label)
33
34    # IPermissionRequestor methods
35    # So we can control access to this functionality
36    def get_permission_actions(self):
37        """Returns a permission structure."""
38        actions = ["MERGEBOT_VIEW", "MERGEBOT_BRANCH", "MERGEBOT_MERGE_TICKET",
39            "MERGEBOT_MERGE_RELEASE"]
40        # MERGEBOT_ADMIN implies all of the above permissions
41        allactions = actions + [
42            ("MERGEBOT_ADMIN", actions),
43            ("MERGEBOT_BRANCH", ["MERGEBOT_VIEW"]),
44            ("MERGEBOT_MERGE_TICKET", ["MERGEBOT_VIEW"]),
45            ("MERGEBOT_MERGE_RELEASE", ["MERGEBOT_VIEW"])
46            ]
47        return allactions
48
49    # IRequestHandler
50    def match_request(self, req):
51        """Returns true, if the given request path is handled by this module"""
52        # For now, we don't recognize any arguments...
53        return req.path_info == "/mergebot" or req.path_info.startswith("/mergebot/")
54
55    def _get_ticket_info(self, ticketid):
56        # grab the ticket info we care about
57        fields = ['summary', 'component', 'version', 'status']
58        info = {}
59        ticket = Ticket(self.env, ticketid)
60        for field in fields:
61            info[field] = ticket[field]
62        info['href'] = self.env.href.ticket(ticketid)
63        self.log.debug("id=%s, info=%r" % (ticketid, info))
64        return info
65
66    def process_request(self, req):
67        """This is the function called when a user requests a mergebot page."""
68        req.perm.assert_permission("MERGEBOT_VIEW")
69
70        # 2nd redirect back to the real mergebot page To address POST
71        if req.path_info == "/mergebot/redir":
72            req.redirect(req.href.mergebot())
73
74        debugs = []
75        req.hdf["title"] = "MergeBot"
76
77        if req.method == "POST": # The user hit a button
78            #debugs += [
79            #    "POST",
80            #    "Branching ticket %s" % req.args,
81            #]
82
83            ticketnum = req.args['ticket']
84            component = req.args['component']
85            version = req.args['version']
86            requestor = req.authname or "anonymous"
87            ticket = Ticket(self.env, int(ticketnum))
88            # FIXME: check for 'todo' key?
89            # If the request is not valid, just ignore it.
90            actor = None
91            if req.args['action'] == "Branch":
92                req.perm.assert_permission("MERGEBOT_BRANCH")
93                if self._is_branchable(ticket):
94                    actor = BranchActor(self.env)
95            elif req.args['action'] == "Rebranch":
96                req.perm.assert_permission("MERGEBOT_BRANCH")
97                if self._is_rebranchable(ticket):
98                    actor = RebranchActor(self.env)
99            elif req.args['action'] == "CheckMerge":
100                req.perm.assert_permission("MERGEBOT_VIEW")
101                if self._is_checkmergeable(ticket):
102                    actor = CheckMergeActor(self.env)
103            elif req.args['action'] == "Merge":
104                if version.startswith("#"):
105                    req.perm.assert_permission("MERGEBOT_MERGE_TICKET")
106                else:
107                    req.perm.assert_permission("MERGEBOT_MERGE_RELEASE")
108                if self._is_mergeable(ticket):
109                    actor = MergeActor(self.env)
110            if actor:
111                actor.AddTask([ticketnum, component, version, requestor])
112                try:
113                    actor.Run() # Starts processing deamon.
114                except Exception, e:
115                    self.log.exception(e)
116            # First half of a double-redirect to make a refresh not re-send the
117            # POST data.
118            req.redirect(req.href.mergebot("redir"))
119
120        # We want to fill out the information for the page unconditionally.
121
122        # I need to get a list of tickets.  For non-admins, restrict the list
123        # to the tickets owned by that user.
124        querystring = "status=new|assigned|reopened&version!="
125        if not req.perm.has_permission("MERGEBOT_ADMIN"):
126            querystring += "&owner=%s" % (req.authname,)
127        query = Query.from_string(self.env, querystring, order="id")
128        columns = query.get_columns()
129        for name in ("component", "version", "mergebotstate"):
130            if name not in columns:
131                columns.append(name)
132        #debugs.append("query.fields = %s" % str(query.fields))
133        db = self.env.get_db_cnx()
134        tickets = query.execute(req, db)
135        #debugs += map(str, tickets)
136        req.hdf['mergebot.ticketcount'] = str(len(tickets))
137
138        # Make the tickets indexable by ticket id number:
139        ticketinfo = {}
140        for ticket in tickets:
141            ticketinfo[ticket['id']] = ticket
142        #debugs.append(str(ticketinfo))
143
144        availableTickets = tickets[:]
145        queued_tickets = []
146        # We currently have 4 queues, "branch", "rebranch", "checkmerge", and
147        # "merge"
148        queues = [
149            ("branch", BranchActor),
150            ("rebranch", RebranchActor),
151            ("checkmerge", CheckMergeActor),
152            ("merge", MergeActor)
153        ]
154        for queuename, actor in queues:
155            status, queue = actor(self.env).GetStatus()
156            req.hdf["mergebot.queue.%s" % (queuename, )] = queuename
157            if status:
158                # status[0] is ticketnum
159                req.hdf["mergebot.queue.%s.current" % (queuename)] = status[0]
160                req.hdf["mergebot.queue.%s.current.requestor" % (queuename)] = status[3]
161                ticketnum = int(status[0])
162                queued_tickets.append(ticketnum)
163                ticket = self._get_ticket_info(ticketnum)
164                for field, value in ticket.items():
165                    req.hdf["mergebot.queue.%s.current.%s" % (queuename, field)] = value
166            else:
167                req.hdf["mergebot.queue.%s.current" % (queuename)] = ""
168                req.hdf["mergebot.queue.%s.current.requestor" % (queuename)] = ""
169
170            for i in range(len(queue)):
171                ticketnum = int(queue[i][0])
172                queued_tickets.append(ticketnum)
173                req.hdf["mergebot.queue.%s.queue.%d" % (queuename, i)] = str(ticketnum)
174                req.hdf["mergebot.queue.%s.queue.%d.requestor" % (queuename, i)] = queue[i][3]
175                ticket = self._get_ticket_info(ticketnum)
176                for field, value in ticket.items():
177                    req.hdf["mergebot.queue.%s.queue.%d.%s" % (queuename, i, field)] = value
178                    #debugs.append("%s queue %d, ticket #%d, %s = %s" % (queuename, i, ticketnum, field, value))
179
180        # Provide the list of tickets at the bottom of the page, along with
181        # flags for which buttons should be enabled for each ticket.
182        for ticket in availableTickets:
183            ticketnum = ticket['id']
184            if ticketnum in queued_tickets:
185                # Don't allow more actions to be taken on a ticket that is
186                # currently queued for something.  In the future, we may want
187                # to support a 'Cancel' button, though.
188                continue
189            req.hdf["mergebot.notqueued.%d" % (ticketnum)] = str(ticketnum)
190            for field, value in ticket.items():
191                req.hdf["mergebot.notqueued.%d.%s" % (ticketnum, field)] = value
192            # Select what actions this user may make on this ticket based on
193            # its current state.
194            # MERGE
195            req.hdf["mergebot.notqueued.%d.actions.merge" % (ticketnum)] = 0
196            if req.perm.has_permission("MERGEBOT_MERGE_RELEASE") and \
197                not ticket['version'].startswith("#"):
198                req.hdf["mergebot.notqueued.%d.actions.merge" % (ticketnum)] = self._is_mergeable(ticket)
199            if req.perm.has_permission("MERGEBOT_MERGE_TICKET") and \
200                ticket['version'].startswith("#"):
201                req.hdf["mergebot.notqueued.%d.actions.merge" % (ticketnum)] = self._is_mergeable(ticket)
202            # CHECK-MERGE
203            if req.perm.has_permission("MERGEBOT_VIEW"):
204                req.hdf["mergebot.notqueued.%d.actions.checkmerge" % (ticketnum)] = self._is_checkmergeable(ticket)
205            else:
206                req.hdf["mergebot.notqueued.%d.actions.checkmerge" % (ticketnum)] = 0
207            # BRANCH, REBRANCH
208            if req.perm.has_permission("MERGEBOT_BRANCH"):
209                req.hdf["mergebot.notqueued.%d.actions.branch" % (ticketnum)] = self._is_branchable(ticket)
210                req.hdf["mergebot.notqueued.%d.actions.rebranch" % (ticketnum)] = self._is_rebranchable(ticket)
211            else:
212                req.hdf["mergebot.notqueued.%d.actions.branch" % (ticketnum)] = 0
213                req.hdf["mergebot.notqueued.%d.actions.rebranch" % (ticketnum)] = 0
214
215        # Add debugs:
216        req.hdf["mergebot.debug"] = len(debugs)
217        for i in range(len(debugs)):
218            req.hdf["mergebot.debug.%d" % (i)] = debugs[i]
219
220        return "mergebot.cs", None
221
222    def _is_branchable(self, ticket):
223        try:
224            state = ticket['mergebotstate']
225        except KeyError:
226            state = ""
227        return state == "" or state == "merged"
228    def _is_rebranchable(self, ticket):
229        # TODO: we should be able to tell if trunk (or version) has had commits
230        # since we branched, and only mark it as rebranchable if there have
231        # been.
232        try:
233            state = ticket['mergebotstate']
234        except KeyError:
235            state = ""
236        return state in ["branched", "conflicts"]
237    def _is_mergeable(self, ticket):
238        try:
239            state = ticket['mergebotstate']
240        except KeyError:
241            state = ""
242        return state == "branched"
243    def _is_checkmergeable(self, ticket):
244        try:
245            state = ticket['mergebotstate']
246        except KeyError:
247            state = ""
248        return state == "branched" or state == "conflicts"
249
250    # ITemplateProvider
251    def get_htdocs_dirs(self):
252        return []
253    def get_templates_dirs(self):
254        # It appears that everyone does this import here instead of at the top
255        # level... I'm not sure I understand why...
256        from pkg_resources import resource_filename
257        return [resource_filename(__name__, 'templates')]
258
259# vim:foldmethod=indent foldcolumn=8
260# vim:softtabstop=4 shiftwidth=4 tabstop=4 expandtab
Note: See TracBrowser for help on using the repository browser.