Merge lp:~clint-fewbar/launchpad-work-items-tracker/server-team-mods into lp:launchpad-work-items-tracker
- server-team-mods
- Merge into trunk
Status: | Superseded |
---|---|
Proposed branch: | lp:~clint-fewbar/launchpad-work-items-tracker/server-team-mods |
Merge into: | lp:launchpad-work-items-tracker |
Diff against target: |
587 lines (+207/-101) 6 files modified
burndown-chart (+38/-16) collect (+14/-1) generate-all (+11/-4) html-report (+22/-10) json-report (+11/-0) report_tools.py (+111/-70) |
To merge this branch: | bzr merge lp:~clint-fewbar/launchpad-work-items-tracker/server-team-mods |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Developers of work-items-tracker | Pending | ||
Review via email: mp+32613@code.launchpad.net |
This proposal has been superseded by a proposal from 2010-08-18.
Commit message
Description of the change
- reordering of statuses so inprogress appears between todo and done in html report
- only generate milestones due after today, unless told otherwise.
- 212. By Clint Byrum
-
generate milestones due today or later, rather than only after today
- 213. By Clint Byrum
-
only show user direct assigned items on personal page
- 214. By Clint Byrum
-
- adding blocked status
- where possible, reducing number of queries run by using group by instead
- adding index for assignee+milestone (used for several of the user mode queries) - 215. By Clint Byrum
-
need to create indexes on db initialization as well
- 216. By Clint Byrum
-
merging with trunk
- 217. By Clint Byrum
-
fixing user page svg generation
- 218. By Clint Byrum
-
merging with trunk
- 219. By Clint Byrum
-
Fixing failure during milestone-only html-report (forgot to remove w.status clause and make args an explicit tuple)
- 220. By Clint Byrum
-
Re-showing data points accidentally hidden in burndown chart
- 221. By Clint Byrum
-
fixing broken trend lines
- 222. By Clint Byrum
-
- changing link for users to their wi tracker page on team pages
- fixing all teams burndown charts to look nicer (no more foreign/team distinction) - 223. By Clint Byrum
-
- fixing burnup chart trend line
- adding all users in server team to burnup_chart_teams, and enabling adding users to burnup
Unmerged revisions
Preview Diff
1 | === modified file 'burndown-chart' |
2 | --- burndown-chart 2010-07-29 22:11:16 +0000 |
3 | +++ burndown-chart 2010-08-18 21:15:52 +0000 |
4 | @@ -43,10 +43,11 @@ |
5 | |
6 | for date in xrange(date_to_ordinal(start_date), date_to_ordinal(end_date)+1): |
7 | i = data.get(ordinal_to_date(date), {}) |
8 | - count = i.get('done', 0) + i.get('todo', 0) + i.get('inprogress', 0) + i.get('postponed', 0) |
9 | + count = i.get('done', 0) + i.get('todo', 0) + i.get('blocked', 0) + i.get('inprogress', 0) + i.get('postponed', 0) |
10 | if max_items < count: |
11 | max_items = count |
12 | pcdata.append((date, i.get('todo_teamonly', 0), i.get('todo', 0), |
13 | + i.get('blocked_teamonly', 0), i.get('blocked', 0), |
14 | i.get('inprogress_teamonly', 0), i.get('inprogress', 0), |
15 | i.get('done_teamonly', 0), i.get('done', 0), |
16 | i.get('postponed_teamonly', 0), i.get('postponed', 0), count)) |
17 | @@ -71,6 +72,9 @@ |
18 | # create the chart area |
19 | # tell it to start at coords 0,0 |
20 | # tell it the labels, and the tics, etc.. |
21 | + # HACK: to prevent 0 div |
22 | + if max_items == 0: |
23 | + max_items = 1 |
24 | ar = area.T(legend=legend.T(loc=(legend_x,legend_y)), loc=(0,0), |
25 | x_axis=axis.X(label='Date', tic_interval=x_interval,format=format_date), |
26 | y_axis=axis.Y(label='Work Items', tic_interval=y_interval), |
27 | @@ -91,30 +95,37 @@ |
28 | plot2.fill_style = fill_style.Plain(bgcolor=color.coral1) |
29 | plot2.line_style = None |
30 | |
31 | - plot3 = bar_plot.T(label='inprogress (team)', hcol=3, stack_on = plot2) |
32 | - plot3.fill_style = fill_style.Plain(bgcolor=color.orange2) |
33 | + plot3 = bar_plot.T(label='blocked (team)', hcol=3, stack_on = plot2) |
34 | + plot3.fill_style = fill_style.Plain(bgcolor=color.darkred) |
35 | plot3.line_style = None |
36 | - plot4 = bar_plot.T(label='inprogress (foreign)', hcol=4, stack_on = plot2) |
37 | - plot4.fill_style = fill_style.Plain(bgcolor=color.orange1) |
38 | + plot4 = bar_plot.T(label='blocked (foreign)', hcol=4, stack_on = plot2) |
39 | + plot4.fill_style = fill_style.Plain(bgcolor=color.indianred) |
40 | plot4.line_style = None |
41 | |
42 | - plot5 = bar_plot.T(label='done (team)', hcol=5, stack_on = plot4) |
43 | - plot5.fill_style = fill_style.green |
44 | + plot5 = bar_plot.T(label='inprogress (team)', hcol=5, stack_on = plot4) |
45 | + plot5.fill_style = fill_style.Plain(bgcolor=color.orange2) |
46 | plot5.line_style = None |
47 | - plot6 = bar_plot.T(label='done (foreign)', hcol=6, stack_on = plot4) |
48 | - plot6.fill_style = fill_style.Plain(bgcolor=color.olivedrab1) |
49 | + plot6 = bar_plot.T(label='inprogress (foreign)', hcol=6, stack_on = plot4) |
50 | + plot6.fill_style = fill_style.Plain(bgcolor=color.orange1) |
51 | plot6.line_style = None |
52 | |
53 | - plot7 = bar_plot.T(label="postponed (team)", hcol=7, stack_on = plot6) |
54 | - plot7.fill_style = fill_style.Plain(bgcolor=color.yellow2) |
55 | + plot7 = bar_plot.T(label='done (team)', hcol=7, stack_on = plot6) |
56 | + plot7.fill_style = fill_style.green |
57 | plot7.line_style = None |
58 | - plot8 = bar_plot.T(label="postponed (foreign) ", hcol=8, stack_on = plot6) |
59 | - plot8.fill_style = fill_style.Plain(bgcolor=color.yellow1) |
60 | + plot8 = bar_plot.T(label='done (foreign)', hcol=8, stack_on = plot6) |
61 | + plot8.fill_style = fill_style.Plain(bgcolor=color.olivedrab1) |
62 | plot8.line_style = None |
63 | |
64 | - plot9 = bar_plot.T(label='total', hcol=9) |
65 | - plot9.fill_style = None |
66 | - plot9.line_style = line_style.gray30 |
67 | + plot9 = bar_plot.T(label="postponed (team)", hcol=9, stack_on = plot8) |
68 | + plot9.fill_style = fill_style.Plain(bgcolor=color.yellow2) |
69 | + plot9.line_style = None |
70 | + plot10 = bar_plot.T(label="postponed (foreign) ", hcol=10, stack_on = plot8) |
71 | + plot10.fill_style = fill_style.Plain(bgcolor=color.yellow1) |
72 | + plot10.line_style = None |
73 | + |
74 | + plot11 = bar_plot.T(label='total', hcol=9) |
75 | + plot10.fill_style = None |
76 | + plot10.line_style = line_style.gray30 |
77 | |
78 | # create the canvas with the specified filename and file format |
79 | can = canvas.init(filename,format) |
80 | @@ -156,6 +167,8 @@ |
81 | help='Output file', dest='output') |
82 | optparser.add_option('--trend-start', type='int', |
83 | help='Explicitly set start of trend line', dest='trendstart') |
84 | +optparser.add_option('-u', '--user', |
85 | + help='Run for this user', dest='user') |
86 | |
87 | (opts, args) = optparser.parse_args() |
88 | if not opts.database: |
89 | @@ -163,6 +176,15 @@ |
90 | if not opts.output: |
91 | optparser.error('No output file given') |
92 | |
93 | +if opts.user and opts.team: |
94 | + optparser.error('team and user options are mutually exclusive') |
95 | + |
96 | +# The typing allows polymorphic behavior |
97 | +if opts.user: |
98 | + opts.team = report_tools.user_string(opts.user) |
99 | +elif opts.team: |
100 | + opts.team = report_tools.team_string(opts.team) |
101 | + |
102 | # get date -> state -> count mapping |
103 | db = report_tools.get_db(opts.database) |
104 | data = report_tools.workitems_over_time(db, opts.team, opts.milestone) |
105 | |
106 | === modified file 'collect' |
107 | --- collect 2010-07-29 22:11:16 +0000 |
108 | +++ collect 2010-08-18 21:15:52 +0000 |
109 | @@ -618,7 +618,7 @@ |
110 | else: |
111 | status_search = fields[2:] |
112 | for f in status_search: |
113 | - if 'DONE' in f or 'POSTPONED' in f or 'TODO' in f or 'INPROGRESS' in f: |
114 | + if 'DONE' in f or 'POSTPONED' in f or 'TODO' in f or 'INPROGRESS' in f or 'BLOCKED' in f: |
115 | ff = f.split() |
116 | if len(ff) == 2: |
117 | assignee = ff[1] |
118 | @@ -628,6 +628,9 @@ |
119 | elif 'POSTPONED' in f: |
120 | istatus = 'postponed' |
121 | break |
122 | + elif 'BLOCKED' in f: |
123 | + istatus = 'blocked' |
124 | + break |
125 | elif 'INPROGRESS' in f and allow_inprogress: |
126 | istatus = 'inprogress' |
127 | break |
128 | @@ -807,6 +810,13 @@ |
129 | db.commit() |
130 | ver = 5 |
131 | |
132 | + if ver == 5: |
133 | + dbg('Upgrading DB to layout version 6') |
134 | + create_v6_indexes(cur) |
135 | + cur.execute('UPDATE version SET db_layout_ref = 6') |
136 | + db.commit() |
137 | + ver = 6 |
138 | + |
139 | return db |
140 | |
141 | def create_v5_indexes(cur): |
142 | @@ -814,6 +824,9 @@ |
143 | cur.execute('''CREATE INDEX work_items_date_idx ON work_items (date)''') |
144 | cur.execute('''CREATE INDEX work_items_status_idx ON work_items (status)''') |
145 | |
146 | +def create_v6_indexes(cur): |
147 | + cur.execute('''CREATE INDEX work_items_assignee_milestone_idx on work_items(assignee,milestone)''') |
148 | + |
149 | ######################################################################## |
150 | # |
151 | # Program operations and main |
152 | |
153 | === modified file 'generate-all' |
154 | --- generate-all 2010-07-28 16:21:04 +0000 |
155 | +++ generate-all 2010-08-18 21:15:52 +0000 |
156 | @@ -20,6 +20,9 @@ |
157 | dest='config', metavar='PATH') |
158 | optparser.add_option('-o', '--output', metavar='DIRECTORY', |
159 | help='Output directory', dest='output_dir') |
160 | +optparser.add_option('-a', '--all-milestones', |
161 | + help='Regnerate all milestones, even those that were due before today', |
162 | + dest='all_milestones', default=False, action='store_true') |
163 | |
164 | (opts, args) = optparser.parse_args() |
165 | if not opts.database: |
166 | @@ -38,8 +41,12 @@ |
167 | |
168 | # get milestones and teams |
169 | cur = db.cursor() |
170 | -cur.execute('SELECT name FROM milestones') |
171 | +if opts.all_milestones: |
172 | + cur.execute('SELECT name FROM milestones') |
173 | +else: |
174 | + cur.execute("SELECT name FROM milestones WHERE due_date >= date('now')") |
175 | milestones = [i[0] for i in cur] |
176 | +print 'regenerating milestones: %s' % ','.join(milestones) |
177 | cur.execute('SELECT DISTINCT team FROM teams') |
178 | teams = [i[0] for i in cur] |
179 | cur.execute('SELECT DISTINCT name FROM teams') |
180 | @@ -61,17 +68,17 @@ |
181 | print basename + '.html' |
182 | f = open(basename + '.html', 'w') |
183 | subprocess.call([os.path.join(my_path, 'html-report'), '-d', opts.database, |
184 | - '-t', u, '-m', m, '--chart', '%s.svg' % target], stdout=f) |
185 | + '-u', u, '-m', m, '--chart', '%s.svg' % target], stdout=f) |
186 | f.close() |
187 | |
188 | print basename + '.json' |
189 | f = open(basename + '.json', 'w') |
190 | subprocess.call([os.path.join(my_path, 'json-report'), '-d', opts.database, |
191 | - '-t', u, '-m', m, '-c', opts.config], stdout=f) |
192 | + '-u', u, '-m', m, '-c', opts.config or ''], stdout=f) |
193 | f.close() |
194 | |
195 | print basename + '.svg' |
196 | - argv = [os.path.join(my_path, 'burndown-chart'), '-d', opts.database, '-t', |
197 | + argv = [os.path.join(my_path, 'burndown-chart'), '-d', opts.database, '-u', |
198 | u, '-m', m, '-o', basename + '.svg'] |
199 | if u in trend_starts: |
200 | argv += ['--trend-start', str(trend_starts[t])] |
201 | |
202 | === modified file 'html-report' |
203 | --- html-report 2010-07-29 22:11:16 +0000 |
204 | +++ html-report 2010-08-18 21:15:52 +0000 |
205 | @@ -27,7 +27,7 @@ |
206 | <p>(Click header to sort)</p> |
207 | <table id="byspecification"> |
208 | <thead> |
209 | - <tr><th>Specification</th><th>Complexity</th> <th>todo</th><th>inprogress</th><th>postponed</th><th>done</th> <th>Completion</th> <th>Priority</th> <th>Status</th></tr> |
210 | + <tr><th>Specification</th><th>Complexity</th> <th>todo</th><th>blocked</th><th>inprogress</th><th>postponed</th><th>done</th> <th>Completion</th> <th>Priority</th> <th>Status</th></tr> |
211 | </thead> |
212 | ''' |
213 | data = report_tools.blueprint_completion(db, team, milestone) |
214 | @@ -35,16 +35,16 @@ |
215 | completion = [] |
216 | for (bp, i) in data.iteritems(): |
217 | completion.append((bp, |
218 | - int(float(i['postponed']+i['done'])/(i['todo']+i['done']+i['postponed']+i['inprogress'])*100 + 0.5))) |
219 | + int(float(i['postponed']+i['done'])/(i['todo']+i['blocked']+i['done']+i['postponed']+i['inprogress'])*100 + 0.5))) |
220 | |
221 | completion.sort(key=lambda k: k[1]*100+report_tools.priority_value(data[k[0]]['priority']), reverse=True) |
222 | |
223 | for (bp, percent) in completion: |
224 | - print ' <tr><td>%s</td> <td>%s</td><td>%i</td><td>%i</td><td>%i</td><td>%i</td> <td>%i%%<br/>' \ |
225 | + print ' <tr><td>%s</td> <td>%s</td><td>%i</td><td>%i</td><td>%i</td><td>%i</td><td>%i</td> <td>%i%%<br/>' \ |
226 | '<span style="font-size: 70%%">%s</span></td> <td>%s</td> <td>%s</td></tr>' % ( |
227 | '<a href="%s">%s</a>' % (data[bp]['url'], escape_html(bp)), |
228 | data[bp]['complexity'] or '', |
229 | - data[bp]['todo'], data[bp]['inprogress'], data[bp]['postponed'], data[bp]['done'], |
230 | + data[bp]['todo'], data[bp]['blocked'], data[bp]['inprogress'], data[bp]['postponed'], data[bp]['done'], |
231 | percent, |
232 | data[bp]['implementation'], |
233 | format_priority(data[bp]['priority']), |
234 | @@ -58,24 +58,24 @@ |
235 | <p>(Click header to sort)</p> |
236 | <table id="byassignee"> |
237 | <thead> |
238 | - <tr><th>Assignee</th> <th>Complexity</th><th>todo</th><th>inprogress</th><th>postponed</th><th>done</th> <th>Completion</th></tr> |
239 | + <tr><th>Assignee</th> <th>Complexity</th><th>todo</th><th>blocked</th><th>inprogress</th><th>postponed</th><th>done</th> <th>Completion</th></tr> |
240 | </thead> |
241 | ''' |
242 | data = report_tools.assignee_completion(db, team, milestone) |
243 | |
244 | completion = [] |
245 | for a, i in data.iteritems(): |
246 | - (todo, inprogress, postponed, done) = (len(i['todo']), len(i['inprogress']), len(i['postponed']), len(i['done'])) |
247 | + (todo, blocked, inprogress, postponed, done) = (len(i['todo']), len(i['blocked']), len(i['inprogress']), len(i['postponed']), len(i['done'])) |
248 | completion.append((a, |
249 | - int(float(postponed+done) / (todo+inprogress+done+postponed)*100 + 0.5))) |
250 | + int(float(postponed+done) / (todo+blocked+inprogress+done+postponed)*100 + 0.5))) |
251 | |
252 | completion.sort(key=lambda k: k[0], reverse=False) |
253 | |
254 | for (a, percent) in completion: |
255 | a_html = escape_url(a or team or 'nobody') |
256 | url = '%s/~%s/+specs?role=assignee' % (report_tools.blueprints_base_url, a_html) |
257 | - print ' <tr><td><a href="%s">%s</a></td> <td>%s</td><td>%i</td><td>%i</td><td>%i</td><td>%i</td> <td>%i%%</td></tr>' % ( |
258 | - url, a_html, data[a]['complexity'] or '', len(data[a]['todo']), len(data[a]['inprogress']), len(data[a]['postponed']), |
259 | + print ' <tr><td><a href="%s">%s</a></td> <td>%s</td><td>%i</td><td>%i</td><td>%i</td><td>%i</td><td>%i</td> <td>%i%%</td></tr>' % ( |
260 | + url, a_html, data[a]['complexity'] or '', len(data[a]['todo']), len(data[a]['blocked']), len(data[a]['inprogress']), len(data[a]['postponed']), |
261 | len(data[a]['done']), percent) |
262 | print '</table>' |
263 | |
264 | @@ -101,12 +101,14 @@ |
265 | |
266 | for a, i in sorted(data.iteritems(), key=lambda k: k[0]): |
267 | todo_len = len(i['todo']) |
268 | + blocked_len = len(i['blocked']) |
269 | postponed_len = len(i['postponed']) |
270 | done_len = len(i['done']) |
271 | inprogress_len = len(i['inprogress']) |
272 | a_html = escape_url(a or team or 'nobody') |
273 | a_url = '%s/~%s/+specs?role=assignee' % (report_tools.blueprints_base_url, a_html) |
274 | rows = {'todo': '<td rowspan="%s">todo</td>' % todo_len, |
275 | + 'blocked': '<td rowspan="%s">blocked</td>' % blocked_len, |
276 | 'postponed': '<td rowspan="%s">postponed</td>' % postponed_len, |
277 | 'done': '<td rowspan="%s">done</td>' % done_len, |
278 | 'inprogress': '<td rowspan="%s">inprogress</td>' % inprogress_len} |
279 | @@ -118,7 +120,7 @@ |
280 | print ' <tr>', |
281 | if not printed_assignee: |
282 | print '<td rowspan="%s"><a href="%s" name="%s">%s</a></td> ' % ( |
283 | - todo_len+postponed_len+done_len+inprogress_len, a_url, escape_url(a or team or 'nobody'), |
284 | + todo_len+blocked_len+postponed_len+done_len+inprogress_len, a_url, escape_url(a or team or 'nobody'), |
285 | a_html) |
286 | printed_assignee = True |
287 | if not printed_status: |
288 | @@ -260,10 +262,20 @@ |
289 | help='Restrict report to a particular milestone', dest='milestone') |
290 | optparser.add_option('--chart', default='burndown.svg', |
291 | help='(Relative) URL to burndown chart', dest='chart_url') |
292 | + optparser.add_option('-u', '--user', |
293 | + help='Run for this user', dest='user') |
294 | |
295 | (opts, args) = optparser.parse_args() |
296 | if not opts.database: |
297 | optparser.error('No database given') |
298 | + if opts.user and opts.team: |
299 | + optparser.error('team and user options are mutually exclusive') |
300 | + |
301 | + # The typing allows polymorphic behavior |
302 | + if opts.user: |
303 | + opts.team = report_tools.user_string(opts.user) |
304 | + elif opts.team: |
305 | + opts.team = report_tools.team_string(opts.team) |
306 | |
307 | db = report_tools.get_db(opts.database) |
308 | |
309 | |
310 | === modified file 'json-report' |
311 | --- json-report 2010-04-06 13:48:48 +0000 |
312 | +++ json-report 2010-08-18 21:15:52 +0000 |
313 | @@ -25,12 +25,23 @@ |
314 | optparser.add_option('-c', '--config', |
315 | help='Path to configuration file (if given, this can read adjusted trend lines start values)', |
316 | dest='config', metavar='PATH') |
317 | + optparser.add_option('-u', '--user', |
318 | + help='Run for this user', dest='user') |
319 | |
320 | (opts, args) = optparser.parse_args() |
321 | if not opts.database: |
322 | optparser.error('No database given') |
323 | db = report_tools.get_db(opts.database) |
324 | |
325 | + if opts.user and opts.team: |
326 | + optparser.error('team and user options are mutually exclusive') |
327 | + |
328 | + # The typing allows polymorphic behavior |
329 | + if opts.user: |
330 | + opts.team = report_tools.user_string(opts.user) |
331 | + elif opts.team: |
332 | + opts.team = report_tools.team_string(opts.team) |
333 | + |
334 | if opts.config: |
335 | config = report_tools.load_config(opts.config) |
336 | else: |
337 | |
338 | === modified file 'report_tools.py' |
339 | --- report_tools.py 2010-07-29 22:11:16 +0000 |
340 | +++ report_tools.py 2010-08-18 21:15:52 +0000 |
341 | @@ -6,9 +6,15 @@ |
342 | import sqlite3 as dbapi2 |
343 | from cgi import escape |
344 | |
345 | -valid_states = ['todo', 'done', 'postponed', 'inprogress'] |
346 | +valid_states = ['inprogress','blocked','todo','done', 'postponed'] |
347 | blueprints_base_url = 'https://blueprints.launchpad.net' |
348 | |
349 | +class user_string(str): |
350 | + pass |
351 | + |
352 | +class team_string(str): |
353 | + pass |
354 | + |
355 | def escape_sql(text): |
356 | return text.replace("'", "''") |
357 | |
358 | @@ -84,35 +90,49 @@ |
359 | |
360 | # include both direct team assignments, as well as assignmets to |
361 | # members of that team |
362 | - for s in valid_states: |
363 | - cur = db.cursor() |
364 | - if team: |
365 | - # WIs which are assigned to team members |
366 | - cur.execute('SELECT date, COUNT(DISTINCT w.description) ' |
367 | + cur = db.cursor() |
368 | + if team: |
369 | + # WIs which are assigned to team members |
370 | + if isinstance(team, user_string): |
371 | + cur.execute('SELECT status, date, COUNT(DISTINCT w.description) ' |
372 | + 'FROM work_items w ' |
373 | + 'WHERE assignee = ? %s ' |
374 | + 'GROUP BY status, date' % ms_sql, [team]) |
375 | + else: |
376 | + cur.execute('SELECT status, date, COUNT(DISTINCT w.description) ' |
377 | 'FROM work_items w LEFT JOIN teams ' |
378 | 'ON w.assignee = teams.name ' |
379 | - 'WHERE status = ? AND (team = ? OR assignee = ?) %s' |
380 | - 'GROUP BY date' % ms_sql, (s, team, team)) |
381 | - |
382 | - for (date, num) in cur: |
383 | - data.setdefault(date, {})[s + '_teamonly'] = num |
384 | - |
385 | - # all WIs which belong to that team's specs |
386 | - cur.execute('SELECT date, COUNT(DISTINCT w.description) ' |
387 | + 'WHERE (team = ? OR assignee = ?) %s' |
388 | + 'GROUP BY status, date' % ms_sql, (team, team)) |
389 | + |
390 | + for (s, date, num) in cur: |
391 | + data.setdefault(date, {})[s + '_teamonly'] = num |
392 | + |
393 | + # all WIs which belong to that team's specs |
394 | + # Only do this for teams, users only want their |
395 | + # assigned work items |
396 | + if isinstance(team, user_string): |
397 | + cur.execute('SELECT w.status, date, COUNT(DISTINCT w.description) ' |
398 | + 'FROM work_items w, specs s ON w.spec = s.name ' |
399 | + 'WHERE (w.assignee = ? OR (s.assignee = ? and w.assignee is null)) %s' |
400 | + 'GROUP BY w.status, date' % ms_sql, (team, team)) |
401 | + else: |
402 | + cur.execute('SELECT w.status, date, COUNT(DISTINCT w.description) ' |
403 | 'FROM work_items w, specs s ON w.spec = s.name ' |
404 | ' LEFT JOIN teams ON (s.assignee = teams.name OR w.assignee = teams.name) ' |
405 | - 'WHERE w.status = ? AND (teams.team = ? OR w.assignee = ? OR s.assignee = ?) %s' |
406 | - 'GROUP BY date' % ms_sql, (s, team, team, team)) |
407 | - |
408 | - for (date, num) in cur: |
409 | - data.setdefault(date, {})[s] = num |
410 | - else: |
411 | - # all WIs for this milestone |
412 | - cur.execute('SELECT date, count(*) FROM work_items w ' |
413 | - 'WHERE status=? %s GROUP BY date' % ms_sql, (s,)) |
414 | - |
415 | - for (date, num) in cur: |
416 | - data.setdefault(date, {})[s] = num |
417 | + 'WHERE (teams.team = ? OR w.assignee = ? OR s.assignee = ?) %s' |
418 | + 'GROUP BY w.status, date' % ms_sql, (team, team, team)) |
419 | + |
420 | + for (s, date, num) in cur: |
421 | + data.setdefault(date, {})[s] = num |
422 | + |
423 | + else: |
424 | + # all WIs for this milestone |
425 | + cur.execute('SELECT status, date, count(*) FROM work_items w ' |
426 | + 'WHERE 1=1 %s GROUP BY status, date' % ms_sql) |
427 | + |
428 | + for (date, num) in cur: |
429 | + data.setdefault(date, {})[s] = num |
430 | return data |
431 | |
432 | def blueprint_completion(db, team=None, milestone=None): |
433 | @@ -134,39 +154,46 @@ |
434 | cur.execute('SELECT max(date) FROM work_items') |
435 | (last_date,) = cur.fetchone() |
436 | |
437 | - for s in valid_states: |
438 | - cur = db.cursor() |
439 | - if team: |
440 | - # include both direct team assignments, as well as assignmnets to |
441 | - # members of that team |
442 | - cur.execute('SELECT s.name, COUNT(DISTINCT w.description), s.status, s.priority, s.implementation, s.url, s.roadmap_notes ' |
443 | + cur = db.cursor() |
444 | + if team: |
445 | + # include both direct team assignments, as well as assignmnets to |
446 | + # members of that team |
447 | + if isinstance(team, user_string): |
448 | + cur.execute('SELECT w.status, s.name, COUNT(DISTINCT w.description), s.status, s.priority, s.implementation, s.url, s.roadmap_notes ' |
449 | + 'FROM work_items w, specs s ON w.spec = s.name ' |
450 | + 'WHERE w.date = ? AND' |
451 | + ' (w.assignee = ? or (s.assignee = ? and w.assignee is null)) %s ' |
452 | + 'GROUP BY w.status,w.spec' % ms_sql, |
453 | + (last_date, team, team)) |
454 | + else: |
455 | + cur.execute('SELECT w.status, s.name, COUNT(DISTINCT w.description), s.status, s.priority, s.implementation, s.url, s.roadmap_notes ' |
456 | 'FROM work_items w, specs s ON w.spec = s.name ' |
457 | ' LEFT JOIN teams ON (s.assignee = teams.name OR w.assignee = teams.name)' |
458 | - 'WHERE w.status = ? AND w.date = ? AND' |
459 | + 'WHERE w.date = ? AND' |
460 | ' (teams.team = ? OR w.assignee = ? or s.assignee = ?) %s ' |
461 | - 'GROUP BY w.spec' % ms_sql, |
462 | - (s, last_date, team, team, team)) |
463 | - else: |
464 | - cur.execute('SELECT s.name, COUNT(DISTINCT w.description), s.status, s.priority, s.implementation, s.url, s.roadmap_notes ' |
465 | - 'FROM work_items w, specs s ' |
466 | - 'ON w.spec = s.name ' |
467 | - 'WHERE w.status = ? AND w.date = ? %s ' |
468 | - 'GROUP BY w.spec' % ms_sql, |
469 | - (s, last_date)) |
470 | + 'GROUP BY w.status, w.spec' % ms_sql, |
471 | + (last_date, team, team, team)) |
472 | + else: |
473 | + cur.execute('SELECT w.status, s.name, COUNT(DISTINCT w.description), s.status, s.priority, s.implementation, s.url, s.roadmap_notes ' |
474 | + 'FROM work_items w, specs s ' |
475 | + 'ON w.spec = s.name ' |
476 | + 'WHERE w.status = ? AND w.date = ? %s ' |
477 | + 'GROUP BY w.status, w.spec' % ms_sql, |
478 | + (last_date)) |
479 | |
480 | - for (bp, num, status, priority, impl, url, roadmap_notes) in cur: |
481 | - info = data.setdefault(bp, {'todo': 0, 'done': 0, 'postponed': 0, 'inprogress': 0}) |
482 | - info[s] = num |
483 | - info['status'] = status or '' |
484 | - info['priority'] = priority |
485 | - info['implementation'] = impl |
486 | - info['url'] = url |
487 | - info['roadmap_notes'] = roadmap_notes |
488 | - |
489 | - cur2 = db.cursor(); |
490 | - cur2.execute('SELECT SUM(points) FROM complexity as w WHERE spec = ? AND date = ? %s' % ms_sql, (bp, last_date)) |
491 | - for points in cur2: |
492 | - info['complexity'] = points[0] |
493 | + for (s, bp, num, status, priority, impl, url, roadmap_notes) in cur: |
494 | + info = data.setdefault(bp, {'todo': 0, 'blocked': 0, 'done': 0, 'postponed': 0, 'inprogress': 0}) |
495 | + info[s] = num |
496 | + info['status'] = status or '' |
497 | + info['priority'] = priority |
498 | + info['implementation'] = impl |
499 | + info['url'] = url |
500 | + info['roadmap_notes'] = roadmap_notes |
501 | + |
502 | + cur2 = db.cursor(); |
503 | + cur2.execute('SELECT SUM(points) FROM complexity as w WHERE spec = ? AND date = ? %s' % ms_sql, (bp, last_date)) |
504 | + for points in cur2: |
505 | + info['complexity'] = points[0] |
506 | |
507 | return data |
508 | |
509 | @@ -175,7 +202,7 @@ |
510 | '''Determine current blueprint tasks completion. |
511 | |
512 | Return blueprint -> info mapping, with info being a map with these |
513 | - keys: todo, inprogress, done, postponed, status, priority, implementation, url, tasks. |
514 | + keys: todo, blocked, inprogress, done, postponed, status, priority, implementation, url, tasks. |
515 | ''' |
516 | default = blueprint_completion(db, team=team) |
517 | data = {} |
518 | @@ -196,12 +223,19 @@ |
519 | if team: |
520 | # include both direct team assignments, as well as assignmnets to |
521 | # members of that team |
522 | - cur.execute('SELECT DISTINCT w.spec, w.description, w.assignee, w.milestone, w.status ' |
523 | - 'FROM work_items w, specs s ON w.spec = s.name ' |
524 | - ' LEFT JOIN teams ON (s.assignee = teams.name OR w.assignee = teams.name) ' |
525 | - 'WHERE w.status = ? AND w.date = ? AND' |
526 | - ' (teams.team = ? OR w.assignee = ? or s.assignee = ?) %s ' % ms_sql, |
527 | - (s, last_date, team, team, team)) |
528 | + if isinstance(team, user_string): |
529 | + cur.execute('SELECT DISTINCT w.spec, w.description, w.assignee, w.milestone, w.status ' |
530 | + 'FROM work_items w, specs s ON w.spec = s.name ' |
531 | + 'WHERE w.status = ? AND w.date = ? AND' |
532 | + ' (w.assignee = ? or (s.assignee = ? and w.assignee is null)) %s ' % ms_sql, |
533 | + (s, last_date, team, team)) |
534 | + else: |
535 | + cur.execute('SELECT DISTINCT w.spec, w.description, w.assignee, w.milestone, w.status ' |
536 | + 'FROM work_items w, specs s ON w.spec = s.name ' |
537 | + ' LEFT JOIN teams ON (s.assignee = teams.name OR w.assignee = teams.name) ' |
538 | + 'WHERE w.status = ? AND w.date = ? AND' |
539 | + ' (teams.team = ? OR w.assignee = ? or s.assignee = ?) %s ' % ms_sql, |
540 | + (s, last_date, team, team, team)) |
541 | else: |
542 | cur.execute('SELECT DISTINCT w.spec, w.description, w.assignee, w.milestone, w.status ' |
543 | 'FROM work_items w, specs s ' |
544 | @@ -227,7 +261,7 @@ |
545 | '''Determine current by-assignee completion. |
546 | |
547 | Return assignee -> info mapping with info being a map with these |
548 | - keys: todo, inprogress, done, postponed. Each of those values is a list of [blueprint, |
549 | + keys: todo, blocked, inprogress, done, postponed. Each of those values is a list of [blueprint, |
550 | workitem, priority, spec_url]. |
551 | ''' |
552 | data = {} |
553 | @@ -248,12 +282,19 @@ |
554 | if team: |
555 | # include both direct team assignments, as well as assignmnets to |
556 | # members of that team |
557 | - cur.execute('SELECT DISTINCT w.assignee, w.spec, w.description, s.priority, s.url ' |
558 | - 'FROM work_items w, specs s ON w.spec = s.name ' |
559 | - ' LEFT JOIN teams ON (s.assignee = teams.name OR w.assignee = teams.name) ' |
560 | - 'WHERE w.status = ? AND w.date = ? AND' |
561 | - ' (teams.team = ? OR w.assignee = ? or s.assignee = ?) %s ' % ms_sql, |
562 | - (s, last_date, team, team, team)) |
563 | + if isinstance(team, user_string): |
564 | + cur.execute('SELECT DISTINCT w.assignee, w.spec, w.description, s.priority, s.url ' |
565 | + 'FROM work_items w, specs s ON w.spec = s.name ' |
566 | + 'WHERE w.status = ? AND w.date = ? AND' |
567 | + ' (w.assignee = ? or (s.assignee = ? and w.assignee is null)) %s ' % ms_sql, |
568 | + (s, last_date, team, team)) |
569 | + else: |
570 | + cur.execute('SELECT DISTINCT w.assignee, w.spec, w.description, s.priority, s.url ' |
571 | + 'FROM work_items w, specs s ON w.spec = s.name ' |
572 | + ' LEFT JOIN teams ON (s.assignee = teams.name OR w.assignee = teams.name) ' |
573 | + 'WHERE w.status = ? AND w.date = ? AND' |
574 | + ' (teams.team = ? OR w.assignee = ? or s.assignee = ?) %s ' % ms_sql, |
575 | + (s, last_date, team, team, team)) |
576 | else: |
577 | cur.execute('SELECT DISTINCT w.assignee, w.spec, w.description, s.priority, s.url ' |
578 | 'FROM work_items w, specs s ' |
579 | @@ -261,7 +302,7 @@ |
580 | 'WHERE w.status = ? AND w.date = ? %s ' % ms_sql, |
581 | (s, last_date)) |
582 | for (a, bp, description, priority, url) in cur: |
583 | - info = data.setdefault(a, {'todo': [], 'done': [], 'postponed': [], 'inprogress': []}) |
584 | + info = data.setdefault(a, {'todo': [], 'blocked': [], 'done': [], 'postponed': [], 'inprogress': []}) |
585 | info[s].append([bp, description, priority, url]) |
586 | |
587 | cur2 = db.cursor(); |