Christopher Lenz wrote:
> Am 11.09.2007 um 10:25 schrieb Christian Boos:
>   
>> I'd like to reach a decision for ticket #5572 (Roadmap Progress bar
>> errors when using custom Workflow). Again a bug report that turned  
>> into
>> a new feature ;-)
>>     
> [snip]
>   
>> Opinions?
>>     
>
> I agree with you in general, but maybe we can still simplify the  
> change a bit.
>
> Specifically, do we really need the wildcard and negation support for  
> specifying the list of statuses? People won't have 50 different  
> statuses (and if they do, they deserve some punishment ;) ), and you  
> really don't need to go in and edit the grouping configuration all  
> the time. So I think that using just a plain list of status names  
> would be sufficient, and simplify the code (and documentation). That  
> would also remove the reliance on the order of the options if I'm not  
> mistaken.
>
>   

Negation support was there for being able to specify things like 
"anything but closed".
A wildcard is useful for specifying a catch-all group, useful when you 
upgraded the workflow a few times and you are unsure about what are the 
remaining states.
However the first use case could probably be subsumed by the second and 
we'd only need one catch-all group after all.
So yes, I think the patch can be simplified further in this area.
The .order is still needed I think, in order to have control over the 
sequence of the groups within the progress bar.

> Some other minor nits:
>
> * Naming of the "all_status" and "remaining_status" variables: I'd  
> prefer the more common plural of "statuses" here :)
>
> * The .css option: I think "css_class" would be more intuitive/explicit
>
> * The .args option: Not sure about this one... first, the name should  
> probably be "query_args" to be more descriptive. And that you  
> separate options with a comma is not really intuitive, as it looks  
> like a query string; I would've first tried using "&" there :P But  
> that's something that can be fixed by a slight change to the  
> documentation.
>   

All good points. I'm surprised Eli didn't bug me for the first one, 
considering how I harassed him about the same thing on the workflow 
branch ;-)

I've uploaded an updated patch on the ticket, which applies cleanly on 
r6000/trunk:
http://trac.edgewall.org/attachment/ticket/5572/milestone_groups-r6000.diff

However, for ease of review I'm also attaching a diff  to this mail 
showing the requested changes (i.e. that one would apply on top of the 
previous patch).
This would nearly have deserved a branch, right? :-)

-- Christian


--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups "Trac 
Development" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to [EMAIL PROTECTED]
For more options, visit this group at 
http://groups.google.com/group/trac-dev?hl=en
-~----------~----~----~----~------~----~------~--~---

diff -r 7547da304fd3 -r 0647ea041f94 trac/ticket/roadmap.py
--- a/trac/ticket/roadmap.py	Tue Sep 11 16:26:24 2007 +0200
+++ b/trac/ticket/roadmap.py	Tue Sep 11 17:11:41 2007 +0200
@@ -138,14 +138,14 @@ class DefaultTicketGroupStatsProvider(Co
     # Qualifiers for the above group (the group must have been defined first):
     
     closed.order = 0                     # sequence number in the progress bar
-    closed.args = group=resolution       # optional extra param for the query
+    closed.query_args = group=resolution # optional extra param for the query
     closed.overall_completion = true     # count for overall completion
 
     # Definition of an 'active' group:
 
-    active = *
+    active = *                           # one catch-all group is allowed
     active.order = 1
-    active.css = open                    # CSS class for this interval
+    active.css_class = open              # CSS class for this interval
 
     # The CSS class can be one of: new (yellow), open (no color) or
     # closed (green). New styles can easily be added using the following
@@ -157,13 +157,13 @@ class DefaultTicketGroupStatsProvider(Co
 
     default_milestone_groups =  [
         {'name': 'closed', 'status': 'closed',
-         'args': 'group=resolution', 'overall_completion': 'true'},
-        {'name': 'active', 'status': '*', 'css': 'open'}
+         'query_args': 'group=resolution', 'overall_completion': 'true'},
+        {'name': 'active', 'status': '*', 'css_class': 'open'}
         ]
 
     def _get_ticket_groups(self):
-        """Returns a dict describing the ticket groups used in milestone
-        progress bars.
+        """Returns a list of dict describing the ticket groups
+        in the expected order of appearance in the milestone progress bars.
         """
         if 'milestone-groups' in self.config:
             groups = {}
@@ -183,9 +183,9 @@ class DefaultTicketGroupStatsProvider(Co
 
     def get_ticket_group_stats(self, ticket_ids):
         total_cnt = len(ticket_ids)
-        all_status = set(TicketSystem(self.env).get_all_status())
+        all_statuses = set(TicketSystem(self.env).get_all_status())
         status_cnt = {}
-        for s in all_status:
+        for s in all_statuses:
             status_cnt[s] = 0
         if total_cnt:
             cursor = self.env.get_db_cnx().cursor()
@@ -197,42 +197,50 @@ class DefaultTicketGroupStatsProvider(Co
                 status_cnt[s] = cnt
 
         stat = TicketGroupStats('ticket status', 'ticket')
-        remaining_status = set(all_status)
-        for group in self._get_ticket_groups():
-            group_cnt = 0
+        remaining_statuses = set(all_statuses)
+        groups =  self._get_ticket_groups()
+        catch_all_group = None
+        # we need to go through the groups twice, so that the catch up group
+        # doesn't need to be the last one in the sequence
+        for group in groups:
             status_str = group['status'].strip()
-            invert = False
             if status_str == '*':
-                group_status = remaining_status
-                remaining_status = set()
+                if catch_all_group:
+                    raise TracError(_(
+                        "'%(group1)s' and '%(group2)s' milestone groups "
+                        "both are declared to be \"catch-all\" groups. "
+                        "Please check your configuration.",
+                        group1=group['name'], group2=catch_all_group['name']))
+                catch_all_group = group
             else:
-                if status_str.startswith('!'):
-                    status_str = status_str[1:]
-                    invert = True
-                group_status = set([s.strip() for s in status_str.split(',')])\
-                               & all_status
-                if invert:
-                    remaining_status = group_status # see XOR trick below
-                elif group_status - remaining_status:
+                group_statuses = set([s.strip()
+                                      for s in status_str.split(',')]) \
+                                      & all_statuses
+                if group_statuses - remaining_statuses:
                     raise TracError(_(
                         "'%(groupname)s' milestone group reused status "
                         "'%(status)s' already taken by other groups. "
                         "Please check your configuration.",
                         groupname=group['name'],
-                        status=', '.join(group_status - remaining_status)))
+                        status=', '.join(group_statuses - remaining_statuses)))
                 else:
-                    remaining_status -= group_status
+                    remaining_statuses -= group_statuses
+                group['statuses'] = group_statuses
+        if catch_all_group:
+            catch_all_group['statuses'] = remaining_statuses
+        for group in groups:
+            group_cnt = 0
             query_args = {}
             for s, cnt in status_cnt.iteritems():
-                if (s in group_status) ^ invert:
+                if s in group['statuses']:
                     group_cnt += cnt
                     query_args.setdefault('status', []).append(s)
-            for arg in [kv for kv in group.get('args', '').split(',')
+            for arg in [kv for kv in group.get('query_args', '').split(',')
                         if '=' in kv]:
                 k, v = [a.strip() for a in arg.split('=', 1)]
                 query_args[k] = v
             stat.add_interval(group['name'], group_cnt, query_args,
-                              group.get('css', group['name']),
+                              group.get('css_class', group['name']),
                               bool(group.get('overall_completion')))
         stat.refresh_calcs()
         return stat

Reply via email to