Ticket #5572: milestone_groups-r5762.diff
| File milestone_groups-r5762.diff, 9.2 KB (added by cboos, 17 months ago) |
|---|
-
trac/ticket/roadmap.py
63 63 self.done_percent = 0 64 64 self.done_count = 0 65 65 66 def add_interval(self, title, count, qry_args, css_class, countsToProg=0): 66 def add_interval(self, title, count, qry_args, css_class, 67 overall_completion=None, countsToProg=0): 67 68 """Adds a division to this stats' group's progress bar. 68 69 69 70 `title` is the display name (eg 'closed', 'spent effort') of this … … 72 73 `qry_args` is a dict of extra params that will yield the subset of 73 74 tickets in this interval on a query. 74 75 `css_class` is the css class that will be used to display the division. 75 `countsToProg` can be set to true to make this interval count towards 76 overall completion of this group of tickets. 76 `overall_completion` can be set to true to make this interval count 77 towards overall completion of this group of tickets. 78 79 (Warning: `countsToProg` argument will be removed in 0.12, use 80 `overall_completion` instead) 77 81 """ 82 if overall_completion is None: 83 overall_completion = countsToProg 78 84 self.intervals.append({ 79 85 'title': title, 80 86 'count': count, 81 87 'qry_args': qry_args, 82 88 'css_class': css_class, 83 89 'percent': None, 84 'countsToProg': countsToProg 90 'countsToProg': overall_completion, 91 'overall_completion': overall_completion, 85 92 }) 86 93 self.count = self.count + count 87 94 … … 95 102 interval['percent'] = round(float(interval['count'] / 96 103 float(self.count) * 100)) 97 104 total_percent = total_percent + interval['percent'] 98 if interval[' countsToProg']:105 if interval['overall_completion']: 99 106 self.done_percent += interval['percent'] 100 107 self.done_count += interval['count'] 101 108 102 109 if self.done_count and total_percent != 100: 103 fudge_int = [i for i in self.intervals if i['countsToProg']][0] 110 fudge_int = [i for i in self.intervals 111 if i['overall_completion']][0] 104 112 fudge_amt = 100 - total_percent 105 113 fudge_int['percent'] += fudge_amt 106 114 self.done_percent += fudge_amt 107 115 116 108 117 class DefaultTicketGroupStatsProvider(Component): 118 """Configurable ticket group statistics provider. 119 120 Example configuration (which is also the default): 121 122 [milestone-groups] 123 closed = closed # a list of accepted status 124 closed.order = 0 # sequence number in the progress bar 125 closed.args = group=resolution # optional extra param for the query 126 closed.overall_completion = true # count for overall completion 127 128 active = !closed # '!' for a list of rejected status 129 active.order = 1 130 active.css = open # css class for this interval 131 """ 132 109 133 implements(ITicketGroupStatsProvider) 110 134 135 def _get_ticket_groups(self): 136 if 'milestone-groups' in self.config: 137 groups = {} 138 order = 0 139 for option, value in self.config.options('milestone-groups'): 140 if '.' in option: 141 name, qualifier = option.split('.', 1) 142 group = groups.get(name) 143 if group: 144 group[qualifier] = value 145 else: 146 groups[option] = {'name': option, 'status': value, 147 'order': order} 148 order += 1 149 return [group for group in sorted(groups.values(), 150 key=lambda g: int(g['order']))] 151 else: 152 return [{'name': 'closed', 'status': 'closed', 153 'args': 'group=resolution', 'overall_completion': 'true'}, 154 {'name': 'active', 'status': '!closed', 'css': 'open'}] 155 111 156 def get_ticket_group_stats(self, ticket_ids): 112 157 total_cnt = len(ticket_ids) 158 status_cnt = {} 159 for s in TicketSystem(self.env).get_all_status(): 160 status_cnt[s] = 0 113 161 if total_cnt: 162 active_cnt = ticket_cnt = 0 114 163 cursor = self.env.get_db_cnx().cursor() 115 str_ids = [str(x) for x in sorted(ticket_ids)] 116 active_cnt = cursor.execute("SELECT count(1) FROM ticket " 117 "WHERE status <> 'closed' AND id IN " 118 "(%s)" % ",".join(str_ids)) 119 active_cnt = 0 164 cursor.execute("SELECT count(*) FROM ticket") 120 165 for cnt, in cursor: 121 active_cnt = cnt 122 else: 123 active_cnt = 0 166 ticket_cnt = cnt 167 if ticket_ids > ticket_cnt / 4: 168 # then it's probably faster to get the status for all tickets 169 ids = set(ticket_ids) 170 cursor.execute("SELECT id, status FROM ticket") 171 for id, s in cursor: 172 if id in ids: 173 status_cnt[s] = status_cnt.get(s, 0) + 1 174 else: 175 str_ids = [str(x) for x in sorted(ticket_ids)] 176 cursor.execute("SELECT status, count(status) FROM ticket " 177 "WHERE id IN (%s) GROUP BY status" % 178 ",".join(str_ids)) 179 for s, cnt in cursor: 180 status_cnt[s] = cnt 124 181 125 closed_cnt = total_cnt - active_cnt126 127 182 stat = TicketGroupStats('ticket status', 'ticket') 128 stat.add_interval('closed', closed_cnt, 129 {'status': 'closed', 'group': 'resolution'}, 130 'closed', True) 131 stat.add_interval('active', active_cnt, 132 {'status': ['new', 'assigned', 'reopened']}, 133 'open', False) 183 for group in self._get_ticket_groups(): 184 group_cnt = 0 185 accepted = [s.strip() for s in 186 group['status'].replace('!', '').split(',')] 187 rejected = [] 188 if '!' in group['status']: 189 accepted, rejected = rejected, accepted 190 query_args = {} 191 for s, cnt in status_cnt.iteritems(): 192 if s in accepted or (rejected and s not in rejected): 193 group_cnt += cnt 194 query_args.setdefault('status', []).append(s) 195 for arg in [kv for kv in group.get('args', '').split(',') 196 if '=' in kv]: 197 k, v = [a.strip() for a in arg.split('=', 1)] 198 query_args[k] = v 199 stat.add_interval(group['name'], group_cnt, query_args, 200 group.get('css', group['name']), 201 group.get('overall_completion', False)) 134 202 stat.refresh_calcs() 135 203 return stat 136 204 … … 631 699 632 700 for idx, gstat in enumerate(group_stats): 633 701 gs_dict = milestone_groups[idx] 634 gs_dict['percent_of_max_total'] = (float(gstat.count) / 635 float(max_count) * 100) 702 percent = 1.0 703 if max_count: 704 percent = float(gstat.count) / float(max_count) * 100 705 gs_dict['percent_of_max_total'] = percent 636 706 637 707 return 'milestone_view.html', data, None 638 708 -
trac/ticket/workflows/basic-workflow.ini
21 21 reopen = closed -> reopened 22 22 reopen.permissions = TICKET_CREATE 23 23 reopen.operations = del_resolution 24 25 [milestone-groups] 26 closed = closed 27 closed.order = 0 28 closed.args = group=resolution 29 closed.overall_completion = true 30 31 active = assigned,accepted 32 active.order = 1 33 active.css = open 34 35 new = new,reopened 36 new.order = 2 -
trac/htdocs/css/roadmap.css
19 19 text-decoration: none 20 20 } 21 21 table.progress td { background: #fff; padding: 0 } 22 table.progress td.new { background: #f5f5b5 } 22 23 table.progress td.closed { background: #bae0ba } 23 24 table.progress td :hover { background: none } 24 25 p.percent { font-size: 10px; line-height: 2.4em; margin: 0.9em 0 0 } -
trac/templates/macros.html
237 237 <dd><a href="${interval_hrefs[idx]}">${interval.count}</a></dd> 238 238 </py:for> 239 239 <py:if test="stats_href"> 240 <dt> Total ${stats.unit}s:</dt>240 <dt>/ Total ${stats.unit}s:</dt> 241 241 <dd><a href="${stats_href}">${sum([x.count for x in stats.intervals], 0)}</a></dd> 242 242 </py:if> 243 243 </dl>
