Merge lp:~clint-fewbar/launchpad-work-items-tracker/server-team-mods into lp:launchpad-work-items-tracker

Proposed by Clint Byrum
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
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.

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.

To post a comment you must log in.
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

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'burndown-chart'
--- burndown-chart 2010-07-29 22:11:16 +0000
+++ burndown-chart 2010-08-18 21:15:52 +0000
@@ -43,10 +43,11 @@
4343
44 for date in xrange(date_to_ordinal(start_date), date_to_ordinal(end_date)+1):44 for date in xrange(date_to_ordinal(start_date), date_to_ordinal(end_date)+1):
45 i = data.get(ordinal_to_date(date), {})45 i = data.get(ordinal_to_date(date), {})
46 count = i.get('done', 0) + i.get('todo', 0) + i.get('inprogress', 0) + i.get('postponed', 0)46 count = i.get('done', 0) + i.get('todo', 0) + i.get('blocked', 0) + i.get('inprogress', 0) + i.get('postponed', 0)
47 if max_items < count:47 if max_items < count:
48 max_items = count48 max_items = count
49 pcdata.append((date, i.get('todo_teamonly', 0), i.get('todo', 0),49 pcdata.append((date, i.get('todo_teamonly', 0), i.get('todo', 0),
50 i.get('blocked_teamonly', 0), i.get('blocked', 0),
50 i.get('inprogress_teamonly', 0), i.get('inprogress', 0),51 i.get('inprogress_teamonly', 0), i.get('inprogress', 0),
51 i.get('done_teamonly', 0), i.get('done', 0),52 i.get('done_teamonly', 0), i.get('done', 0),
52 i.get('postponed_teamonly', 0), i.get('postponed', 0), count))53 i.get('postponed_teamonly', 0), i.get('postponed', 0), count))
@@ -71,6 +72,9 @@
71 # create the chart area72 # create the chart area
72 # tell it to start at coords 0,073 # tell it to start at coords 0,0
73 # tell it the labels, and the tics, etc..74 # tell it the labels, and the tics, etc..
75 # HACK: to prevent 0 div
76 if max_items == 0:
77 max_items = 1
74 ar = area.T(legend=legend.T(loc=(legend_x,legend_y)), loc=(0,0),78 ar = area.T(legend=legend.T(loc=(legend_x,legend_y)), loc=(0,0),
75 x_axis=axis.X(label='Date', tic_interval=x_interval,format=format_date),79 x_axis=axis.X(label='Date', tic_interval=x_interval,format=format_date),
76 y_axis=axis.Y(label='Work Items', tic_interval=y_interval),80 y_axis=axis.Y(label='Work Items', tic_interval=y_interval),
@@ -91,30 +95,37 @@
91 plot2.fill_style = fill_style.Plain(bgcolor=color.coral1)95 plot2.fill_style = fill_style.Plain(bgcolor=color.coral1)
92 plot2.line_style = None96 plot2.line_style = None
9397
94 plot3 = bar_plot.T(label='inprogress (team)', hcol=3, stack_on = plot2)98 plot3 = bar_plot.T(label='blocked (team)', hcol=3, stack_on = plot2)
95 plot3.fill_style = fill_style.Plain(bgcolor=color.orange2)99 plot3.fill_style = fill_style.Plain(bgcolor=color.darkred)
96 plot3.line_style = None100 plot3.line_style = None
97 plot4 = bar_plot.T(label='inprogress (foreign)', hcol=4, stack_on = plot2)101 plot4 = bar_plot.T(label='blocked (foreign)', hcol=4, stack_on = plot2)
98 plot4.fill_style = fill_style.Plain(bgcolor=color.orange1)102 plot4.fill_style = fill_style.Plain(bgcolor=color.indianred)
99 plot4.line_style = None103 plot4.line_style = None
100104
101 plot5 = bar_plot.T(label='done (team)', hcol=5, stack_on = plot4)105 plot5 = bar_plot.T(label='inprogress (team)', hcol=5, stack_on = plot4)
102 plot5.fill_style = fill_style.green106 plot5.fill_style = fill_style.Plain(bgcolor=color.orange2)
103 plot5.line_style = None107 plot5.line_style = None
104 plot6 = bar_plot.T(label='done (foreign)', hcol=6, stack_on = plot4)108 plot6 = bar_plot.T(label='inprogress (foreign)', hcol=6, stack_on = plot4)
105 plot6.fill_style = fill_style.Plain(bgcolor=color.olivedrab1)109 plot6.fill_style = fill_style.Plain(bgcolor=color.orange1)
106 plot6.line_style = None110 plot6.line_style = None
107111
108 plot7 = bar_plot.T(label="postponed (team)", hcol=7, stack_on = plot6)112 plot7 = bar_plot.T(label='done (team)', hcol=7, stack_on = plot6)
109 plot7.fill_style = fill_style.Plain(bgcolor=color.yellow2)113 plot7.fill_style = fill_style.green
110 plot7.line_style = None114 plot7.line_style = None
111 plot8 = bar_plot.T(label="postponed (foreign) ", hcol=8, stack_on = plot6)115 plot8 = bar_plot.T(label='done (foreign)', hcol=8, stack_on = plot6)
112 plot8.fill_style = fill_style.Plain(bgcolor=color.yellow1)116 plot8.fill_style = fill_style.Plain(bgcolor=color.olivedrab1)
113 plot8.line_style = None117 plot8.line_style = None
114118
115 plot9 = bar_plot.T(label='total', hcol=9)119 plot9 = bar_plot.T(label="postponed (team)", hcol=9, stack_on = plot8)
116 plot9.fill_style = None120 plot9.fill_style = fill_style.Plain(bgcolor=color.yellow2)
117 plot9.line_style = line_style.gray30121 plot9.line_style = None
122 plot10 = bar_plot.T(label="postponed (foreign) ", hcol=10, stack_on = plot8)
123 plot10.fill_style = fill_style.Plain(bgcolor=color.yellow1)
124 plot10.line_style = None
125
126 plot11 = bar_plot.T(label='total', hcol=9)
127 plot10.fill_style = None
128 plot10.line_style = line_style.gray30
118129
119 # create the canvas with the specified filename and file format130 # create the canvas with the specified filename and file format
120 can = canvas.init(filename,format)131 can = canvas.init(filename,format)
@@ -156,6 +167,8 @@
156 help='Output file', dest='output')167 help='Output file', dest='output')
157optparser.add_option('--trend-start', type='int',168optparser.add_option('--trend-start', type='int',
158 help='Explicitly set start of trend line', dest='trendstart')169 help='Explicitly set start of trend line', dest='trendstart')
170optparser.add_option('-u', '--user',
171 help='Run for this user', dest='user')
159172
160(opts, args) = optparser.parse_args()173(opts, args) = optparser.parse_args()
161if not opts.database:174if not opts.database:
@@ -163,6 +176,15 @@
163if not opts.output:176if not opts.output:
164 optparser.error('No output file given')177 optparser.error('No output file given')
165178
179if opts.user and opts.team:
180 optparser.error('team and user options are mutually exclusive')
181
182# The typing allows polymorphic behavior
183if opts.user:
184 opts.team = report_tools.user_string(opts.user)
185elif opts.team:
186 opts.team = report_tools.team_string(opts.team)
187
166# get date -> state -> count mapping188# get date -> state -> count mapping
167db = report_tools.get_db(opts.database)189db = report_tools.get_db(opts.database)
168data = report_tools.workitems_over_time(db, opts.team, opts.milestone)190data = report_tools.workitems_over_time(db, opts.team, opts.milestone)
169191
=== modified file 'collect'
--- collect 2010-07-29 22:11:16 +0000
+++ collect 2010-08-18 21:15:52 +0000
@@ -618,7 +618,7 @@
618 else:618 else:
619 status_search = fields[2:]619 status_search = fields[2:]
620 for f in status_search:620 for f in status_search:
621 if 'DONE' in f or 'POSTPONED' in f or 'TODO' in f or 'INPROGRESS' in f:621 if 'DONE' in f or 'POSTPONED' in f or 'TODO' in f or 'INPROGRESS' in f or 'BLOCKED' in f:
622 ff = f.split()622 ff = f.split()
623 if len(ff) == 2:623 if len(ff) == 2:
624 assignee = ff[1]624 assignee = ff[1]
@@ -628,6 +628,9 @@
628 elif 'POSTPONED' in f:628 elif 'POSTPONED' in f:
629 istatus = 'postponed'629 istatus = 'postponed'
630 break630 break
631 elif 'BLOCKED' in f:
632 istatus = 'blocked'
633 break
631 elif 'INPROGRESS' in f and allow_inprogress:634 elif 'INPROGRESS' in f and allow_inprogress:
632 istatus = 'inprogress'635 istatus = 'inprogress'
633 break636 break
@@ -807,6 +810,13 @@
807 db.commit()810 db.commit()
808 ver = 5811 ver = 5
809812
813 if ver == 5:
814 dbg('Upgrading DB to layout version 6')
815 create_v6_indexes(cur)
816 cur.execute('UPDATE version SET db_layout_ref = 6')
817 db.commit()
818 ver = 6
819
810 return db820 return db
811821
812def create_v5_indexes(cur):822def create_v5_indexes(cur):
@@ -814,6 +824,9 @@
814 cur.execute('''CREATE INDEX work_items_date_idx ON work_items (date)''')824 cur.execute('''CREATE INDEX work_items_date_idx ON work_items (date)''')
815 cur.execute('''CREATE INDEX work_items_status_idx ON work_items (status)''')825 cur.execute('''CREATE INDEX work_items_status_idx ON work_items (status)''')
816826
827def create_v6_indexes(cur):
828 cur.execute('''CREATE INDEX work_items_assignee_milestone_idx on work_items(assignee,milestone)''')
829
817########################################################################830########################################################################
818#831#
819# Program operations and main832# Program operations and main
820833
=== modified file 'generate-all'
--- generate-all 2010-07-28 16:21:04 +0000
+++ generate-all 2010-08-18 21:15:52 +0000
@@ -20,6 +20,9 @@
20 dest='config', metavar='PATH')20 dest='config', metavar='PATH')
21optparser.add_option('-o', '--output', metavar='DIRECTORY',21optparser.add_option('-o', '--output', metavar='DIRECTORY',
22 help='Output directory', dest='output_dir')22 help='Output directory', dest='output_dir')
23optparser.add_option('-a', '--all-milestones',
24 help='Regnerate all milestones, even those that were due before today',
25 dest='all_milestones', default=False, action='store_true')
2326
24(opts, args) = optparser.parse_args()27(opts, args) = optparser.parse_args()
25if not opts.database:28if not opts.database:
@@ -38,8 +41,12 @@
3841
39# get milestones and teams42# get milestones and teams
40cur = db.cursor()43cur = db.cursor()
41cur.execute('SELECT name FROM milestones')44if opts.all_milestones:
45 cur.execute('SELECT name FROM milestones')
46else:
47 cur.execute("SELECT name FROM milestones WHERE due_date >= date('now')")
42milestones = [i[0] for i in cur]48milestones = [i[0] for i in cur]
49print 'regenerating milestones: %s' % ','.join(milestones)
43cur.execute('SELECT DISTINCT team FROM teams')50cur.execute('SELECT DISTINCT team FROM teams')
44teams = [i[0] for i in cur]51teams = [i[0] for i in cur]
45cur.execute('SELECT DISTINCT name FROM teams')52cur.execute('SELECT DISTINCT name FROM teams')
@@ -61,17 +68,17 @@
61 print basename + '.html'68 print basename + '.html'
62 f = open(basename + '.html', 'w')69 f = open(basename + '.html', 'w')
63 subprocess.call([os.path.join(my_path, 'html-report'), '-d', opts.database,70 subprocess.call([os.path.join(my_path, 'html-report'), '-d', opts.database,
64 '-t', u, '-m', m, '--chart', '%s.svg' % target], stdout=f)71 '-u', u, '-m', m, '--chart', '%s.svg' % target], stdout=f)
65 f.close()72 f.close()
6673
67 print basename + '.json'74 print basename + '.json'
68 f = open(basename + '.json', 'w')75 f = open(basename + '.json', 'w')
69 subprocess.call([os.path.join(my_path, 'json-report'), '-d', opts.database,76 subprocess.call([os.path.join(my_path, 'json-report'), '-d', opts.database,
70 '-t', u, '-m', m, '-c', opts.config], stdout=f)77 '-u', u, '-m', m, '-c', opts.config or ''], stdout=f)
71 f.close()78 f.close()
7279
73 print basename + '.svg'80 print basename + '.svg'
74 argv = [os.path.join(my_path, 'burndown-chart'), '-d', opts.database, '-t',81 argv = [os.path.join(my_path, 'burndown-chart'), '-d', opts.database, '-u',
75 u, '-m', m, '-o', basename + '.svg']82 u, '-m', m, '-o', basename + '.svg']
76 if u in trend_starts:83 if u in trend_starts:
77 argv += ['--trend-start', str(trend_starts[t])]84 argv += ['--trend-start', str(trend_starts[t])]
7885
=== modified file 'html-report'
--- html-report 2010-07-29 22:11:16 +0000
+++ html-report 2010-08-18 21:15:52 +0000
@@ -27,7 +27,7 @@
27<p>(Click header to sort)</p>27<p>(Click header to sort)</p>
28<table id="byspecification">28<table id="byspecification">
29 <thead>29 <thead>
30 <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>30 <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>
31 </thead>31 </thead>
32'''32'''
33 data = report_tools.blueprint_completion(db, team, milestone)33 data = report_tools.blueprint_completion(db, team, milestone)
@@ -35,16 +35,16 @@
35 completion = []35 completion = []
36 for (bp, i) in data.iteritems():36 for (bp, i) in data.iteritems():
37 completion.append((bp,37 completion.append((bp,
38 int(float(i['postponed']+i['done'])/(i['todo']+i['done']+i['postponed']+i['inprogress'])*100 + 0.5)))38 int(float(i['postponed']+i['done'])/(i['todo']+i['blocked']+i['done']+i['postponed']+i['inprogress'])*100 + 0.5)))
3939
40 completion.sort(key=lambda k: k[1]*100+report_tools.priority_value(data[k[0]]['priority']), reverse=True)40 completion.sort(key=lambda k: k[1]*100+report_tools.priority_value(data[k[0]]['priority']), reverse=True)
4141
42 for (bp, percent) in completion:42 for (bp, percent) in completion:
43 print ' <tr><td>%s</td> <td>%s</td><td>%i</td><td>%i</td><td>%i</td><td>%i</td> <td>%i%%<br/>' \43 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/>' \
44 '<span style="font-size: 70%%">%s</span></td> <td>%s</td> <td>%s</td></tr>' % (44 '<span style="font-size: 70%%">%s</span></td> <td>%s</td> <td>%s</td></tr>' % (
45 '<a href="%s">%s</a>' % (data[bp]['url'], escape_html(bp)),45 '<a href="%s">%s</a>' % (data[bp]['url'], escape_html(bp)),
46 data[bp]['complexity'] or '',46 data[bp]['complexity'] or '',
47 data[bp]['todo'], data[bp]['inprogress'], data[bp]['postponed'], data[bp]['done'],47 data[bp]['todo'], data[bp]['blocked'], data[bp]['inprogress'], data[bp]['postponed'], data[bp]['done'],
48 percent,48 percent,
49 data[bp]['implementation'],49 data[bp]['implementation'],
50 format_priority(data[bp]['priority']),50 format_priority(data[bp]['priority']),
@@ -58,24 +58,24 @@
58<p>(Click header to sort)</p>58<p>(Click header to sort)</p>
59<table id="byassignee">59<table id="byassignee">
60 <thead>60 <thead>
61 <tr><th>Assignee</th> <th>Complexity</th><th>todo</th><th>inprogress</th><th>postponed</th><th>done</th> <th>Completion</th></tr>61 <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>
62 </thead>62 </thead>
63'''63'''
64 data = report_tools.assignee_completion(db, team, milestone)64 data = report_tools.assignee_completion(db, team, milestone)
6565
66 completion = []66 completion = []
67 for a, i in data.iteritems():67 for a, i in data.iteritems():
68 (todo, inprogress, postponed, done) = (len(i['todo']), len(i['inprogress']), len(i['postponed']), len(i['done']))68 (todo, blocked, inprogress, postponed, done) = (len(i['todo']), len(i['blocked']), len(i['inprogress']), len(i['postponed']), len(i['done']))
69 completion.append((a,69 completion.append((a,
70 int(float(postponed+done) / (todo+inprogress+done+postponed)*100 + 0.5)))70 int(float(postponed+done) / (todo+blocked+inprogress+done+postponed)*100 + 0.5)))
7171
72 completion.sort(key=lambda k: k[0], reverse=False)72 completion.sort(key=lambda k: k[0], reverse=False)
7373
74 for (a, percent) in completion:74 for (a, percent) in completion:
75 a_html = escape_url(a or team or 'nobody')75 a_html = escape_url(a or team or 'nobody')
76 url = '%s/~%s/+specs?role=assignee' % (report_tools.blueprints_base_url, a_html)76 url = '%s/~%s/+specs?role=assignee' % (report_tools.blueprints_base_url, a_html)
77 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>' % (77 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>' % (
78 url, a_html, data[a]['complexity'] or '', len(data[a]['todo']), len(data[a]['inprogress']), len(data[a]['postponed']),78 url, a_html, data[a]['complexity'] or '', len(data[a]['todo']), len(data[a]['blocked']), len(data[a]['inprogress']), len(data[a]['postponed']),
79 len(data[a]['done']), percent)79 len(data[a]['done']), percent)
80 print '</table>'80 print '</table>'
8181
@@ -101,12 +101,14 @@
101101
102 for a, i in sorted(data.iteritems(), key=lambda k: k[0]):102 for a, i in sorted(data.iteritems(), key=lambda k: k[0]):
103 todo_len = len(i['todo'])103 todo_len = len(i['todo'])
104 blocked_len = len(i['blocked'])
104 postponed_len = len(i['postponed'])105 postponed_len = len(i['postponed'])
105 done_len = len(i['done'])106 done_len = len(i['done'])
106 inprogress_len = len(i['inprogress'])107 inprogress_len = len(i['inprogress'])
107 a_html = escape_url(a or team or 'nobody')108 a_html = escape_url(a or team or 'nobody')
108 a_url = '%s/~%s/+specs?role=assignee' % (report_tools.blueprints_base_url, a_html)109 a_url = '%s/~%s/+specs?role=assignee' % (report_tools.blueprints_base_url, a_html)
109 rows = {'todo': '<td rowspan="%s">todo</td>' % todo_len,110 rows = {'todo': '<td rowspan="%s">todo</td>' % todo_len,
111 'blocked': '<td rowspan="%s">blocked</td>' % blocked_len,
110 'postponed': '<td rowspan="%s">postponed</td>' % postponed_len,112 'postponed': '<td rowspan="%s">postponed</td>' % postponed_len,
111 'done': '<td rowspan="%s">done</td>' % done_len,113 'done': '<td rowspan="%s">done</td>' % done_len,
112 'inprogress': '<td rowspan="%s">inprogress</td>' % inprogress_len}114 'inprogress': '<td rowspan="%s">inprogress</td>' % inprogress_len}
@@ -118,7 +120,7 @@
118 print ' <tr>',120 print ' <tr>',
119 if not printed_assignee:121 if not printed_assignee:
120 print '<td rowspan="%s"><a href="%s" name="%s">%s</a></td> ' % (122 print '<td rowspan="%s"><a href="%s" name="%s">%s</a></td> ' % (
121 todo_len+postponed_len+done_len+inprogress_len, a_url, escape_url(a or team or 'nobody'),123 todo_len+blocked_len+postponed_len+done_len+inprogress_len, a_url, escape_url(a or team or 'nobody'),
122 a_html)124 a_html)
123 printed_assignee = True125 printed_assignee = True
124 if not printed_status:126 if not printed_status:
@@ -260,10 +262,20 @@
260 help='Restrict report to a particular milestone', dest='milestone')262 help='Restrict report to a particular milestone', dest='milestone')
261 optparser.add_option('--chart', default='burndown.svg',263 optparser.add_option('--chart', default='burndown.svg',
262 help='(Relative) URL to burndown chart', dest='chart_url')264 help='(Relative) URL to burndown chart', dest='chart_url')
265 optparser.add_option('-u', '--user',
266 help='Run for this user', dest='user')
263267
264 (opts, args) = optparser.parse_args()268 (opts, args) = optparser.parse_args()
265 if not opts.database:269 if not opts.database:
266 optparser.error('No database given')270 optparser.error('No database given')
271 if opts.user and opts.team:
272 optparser.error('team and user options are mutually exclusive')
273
274 # The typing allows polymorphic behavior
275 if opts.user:
276 opts.team = report_tools.user_string(opts.user)
277 elif opts.team:
278 opts.team = report_tools.team_string(opts.team)
267279
268 db = report_tools.get_db(opts.database)280 db = report_tools.get_db(opts.database)
269281
270282
=== modified file 'json-report'
--- json-report 2010-04-06 13:48:48 +0000
+++ json-report 2010-08-18 21:15:52 +0000
@@ -25,12 +25,23 @@
25 optparser.add_option('-c', '--config',25 optparser.add_option('-c', '--config',
26 help='Path to configuration file (if given, this can read adjusted trend lines start values)',26 help='Path to configuration file (if given, this can read adjusted trend lines start values)',
27 dest='config', metavar='PATH')27 dest='config', metavar='PATH')
28 optparser.add_option('-u', '--user',
29 help='Run for this user', dest='user')
2830
29 (opts, args) = optparser.parse_args()31 (opts, args) = optparser.parse_args()
30 if not opts.database:32 if not opts.database:
31 optparser.error('No database given')33 optparser.error('No database given')
32 db = report_tools.get_db(opts.database)34 db = report_tools.get_db(opts.database)
3335
36 if opts.user and opts.team:
37 optparser.error('team and user options are mutually exclusive')
38
39 # The typing allows polymorphic behavior
40 if opts.user:
41 opts.team = report_tools.user_string(opts.user)
42 elif opts.team:
43 opts.team = report_tools.team_string(opts.team)
44
34 if opts.config:45 if opts.config:
35 config = report_tools.load_config(opts.config)46 config = report_tools.load_config(opts.config)
36 else:47 else:
3748
=== modified file 'report_tools.py'
--- report_tools.py 2010-07-29 22:11:16 +0000
+++ report_tools.py 2010-08-18 21:15:52 +0000
@@ -6,9 +6,15 @@
6import sqlite3 as dbapi26import sqlite3 as dbapi2
7from cgi import escape7from cgi import escape
88
9valid_states = ['todo', 'done', 'postponed', 'inprogress']9valid_states = ['inprogress','blocked','todo','done', 'postponed']
10blueprints_base_url = 'https://blueprints.launchpad.net'10blueprints_base_url = 'https://blueprints.launchpad.net'
1111
12class user_string(str):
13 pass
14
15class team_string(str):
16 pass
17
12def escape_sql(text):18def escape_sql(text):
13 return text.replace("'", "''")19 return text.replace("'", "''")
1420
@@ -84,35 +90,49 @@
8490
85 # include both direct team assignments, as well as assignmets to91 # include both direct team assignments, as well as assignmets to
86 # members of that team92 # members of that team
87 for s in valid_states:93 cur = db.cursor()
88 cur = db.cursor()94 if team:
89 if team:95 # WIs which are assigned to team members
90 # WIs which are assigned to team members96 if isinstance(team, user_string):
91 cur.execute('SELECT date, COUNT(DISTINCT w.description) '97 cur.execute('SELECT status, date, COUNT(DISTINCT w.description) '
98 'FROM work_items w '
99 'WHERE assignee = ? %s '
100 'GROUP BY status, date' % ms_sql, [team])
101 else:
102 cur.execute('SELECT status, date, COUNT(DISTINCT w.description) '
92 'FROM work_items w LEFT JOIN teams '103 'FROM work_items w LEFT JOIN teams '
93 'ON w.assignee = teams.name '104 'ON w.assignee = teams.name '
94 'WHERE status = ? AND (team = ? OR assignee = ?) %s'105 'WHERE (team = ? OR assignee = ?) %s'
95 'GROUP BY date' % ms_sql, (s, team, team))106 'GROUP BY status, date' % ms_sql, (team, team))
96107
97 for (date, num) in cur:108 for (s, date, num) in cur:
98 data.setdefault(date, {})[s + '_teamonly'] = num109 data.setdefault(date, {})[s + '_teamonly'] = num
99110
100 # all WIs which belong to that team's specs111 # all WIs which belong to that team's specs
101 cur.execute('SELECT date, COUNT(DISTINCT w.description) '112 # Only do this for teams, users only want their
113 # assigned work items
114 if isinstance(team, user_string):
115 cur.execute('SELECT w.status, date, COUNT(DISTINCT w.description) '
116 'FROM work_items w, specs s ON w.spec = s.name '
117 'WHERE (w.assignee = ? OR (s.assignee = ? and w.assignee is null)) %s'
118 'GROUP BY w.status, date' % ms_sql, (team, team))
119 else:
120 cur.execute('SELECT w.status, date, COUNT(DISTINCT w.description) '
102 'FROM work_items w, specs s ON w.spec = s.name '121 'FROM work_items w, specs s ON w.spec = s.name '
103 ' LEFT JOIN teams ON (s.assignee = teams.name OR w.assignee = teams.name) '122 ' LEFT JOIN teams ON (s.assignee = teams.name OR w.assignee = teams.name) '
104 'WHERE w.status = ? AND (teams.team = ? OR w.assignee = ? OR s.assignee = ?) %s'123 'WHERE (teams.team = ? OR w.assignee = ? OR s.assignee = ?) %s'
105 'GROUP BY date' % ms_sql, (s, team, team, team))124 'GROUP BY w.status, date' % ms_sql, (team, team, team))
106125
107 for (date, num) in cur:126 for (s, date, num) in cur:
108 data.setdefault(date, {})[s] = num127 data.setdefault(date, {})[s] = num
109 else:128
110 # all WIs for this milestone129 else:
111 cur.execute('SELECT date, count(*) FROM work_items w '130 # all WIs for this milestone
112 'WHERE status=? %s GROUP BY date' % ms_sql, (s,))131 cur.execute('SELECT status, date, count(*) FROM work_items w '
113132 'WHERE 1=1 %s GROUP BY status, date' % ms_sql)
114 for (date, num) in cur:133
115 data.setdefault(date, {})[s] = num134 for (date, num) in cur:
135 data.setdefault(date, {})[s] = num
116 return data136 return data
117137
118def blueprint_completion(db, team=None, milestone=None):138def blueprint_completion(db, team=None, milestone=None):
@@ -134,39 +154,46 @@
134 cur.execute('SELECT max(date) FROM work_items')154 cur.execute('SELECT max(date) FROM work_items')
135 (last_date,) = cur.fetchone()155 (last_date,) = cur.fetchone()
136156
137 for s in valid_states:157 cur = db.cursor()
138 cur = db.cursor()158 if team:
139 if team:159 # include both direct team assignments, as well as assignmnets to
140 # include both direct team assignments, as well as assignmnets to160 # members of that team
141 # members of that team161 if isinstance(team, user_string):
142 cur.execute('SELECT s.name, COUNT(DISTINCT w.description), s.status, s.priority, s.implementation, s.url, s.roadmap_notes '162 cur.execute('SELECT w.status, s.name, COUNT(DISTINCT w.description), s.status, s.priority, s.implementation, s.url, s.roadmap_notes '
163 'FROM work_items w, specs s ON w.spec = s.name '
164 'WHERE w.date = ? AND'
165 ' (w.assignee = ? or (s.assignee = ? and w.assignee is null)) %s '
166 'GROUP BY w.status,w.spec' % ms_sql,
167 (last_date, team, team))
168 else:
169 cur.execute('SELECT w.status, s.name, COUNT(DISTINCT w.description), s.status, s.priority, s.implementation, s.url, s.roadmap_notes '
143 'FROM work_items w, specs s ON w.spec = s.name '170 'FROM work_items w, specs s ON w.spec = s.name '
144 ' LEFT JOIN teams ON (s.assignee = teams.name OR w.assignee = teams.name)'171 ' LEFT JOIN teams ON (s.assignee = teams.name OR w.assignee = teams.name)'
145 'WHERE w.status = ? AND w.date = ? AND'172 'WHERE w.date = ? AND'
146 ' (teams.team = ? OR w.assignee = ? or s.assignee = ?) %s '173 ' (teams.team = ? OR w.assignee = ? or s.assignee = ?) %s '
147 'GROUP BY w.spec' % ms_sql,174 'GROUP BY w.status, w.spec' % ms_sql,
148 (s, last_date, team, team, team))175 (last_date, team, team, team))
149 else:176 else:
150 cur.execute('SELECT s.name, COUNT(DISTINCT w.description), s.status, s.priority, s.implementation, s.url, s.roadmap_notes '177 cur.execute('SELECT w.status, s.name, COUNT(DISTINCT w.description), s.status, s.priority, s.implementation, s.url, s.roadmap_notes '
151 'FROM work_items w, specs s '178 'FROM work_items w, specs s '
152 'ON w.spec = s.name '179 'ON w.spec = s.name '
153 'WHERE w.status = ? AND w.date = ? %s '180 'WHERE w.status = ? AND w.date = ? %s '
154 'GROUP BY w.spec' % ms_sql,181 'GROUP BY w.status, w.spec' % ms_sql,
155 (s, last_date))182 (last_date))
156183
157 for (bp, num, status, priority, impl, url, roadmap_notes) in cur:184 for (s, bp, num, status, priority, impl, url, roadmap_notes) in cur:
158 info = data.setdefault(bp, {'todo': 0, 'done': 0, 'postponed': 0, 'inprogress': 0})185 info = data.setdefault(bp, {'todo': 0, 'blocked': 0, 'done': 0, 'postponed': 0, 'inprogress': 0})
159 info[s] = num186 info[s] = num
160 info['status'] = status or ''187 info['status'] = status or ''
161 info['priority'] = priority188 info['priority'] = priority
162 info['implementation'] = impl189 info['implementation'] = impl
163 info['url'] = url190 info['url'] = url
164 info['roadmap_notes'] = roadmap_notes191 info['roadmap_notes'] = roadmap_notes
165 192
166 cur2 = db.cursor();193 cur2 = db.cursor();
167 cur2.execute('SELECT SUM(points) FROM complexity as w WHERE spec = ? AND date = ? %s' % ms_sql, (bp, last_date))194 cur2.execute('SELECT SUM(points) FROM complexity as w WHERE spec = ? AND date = ? %s' % ms_sql, (bp, last_date))
168 for points in cur2:195 for points in cur2:
169 info['complexity'] = points[0] 196 info['complexity'] = points[0]
170197
171 return data198 return data
172199
@@ -175,7 +202,7 @@
175 '''Determine current blueprint tasks completion.202 '''Determine current blueprint tasks completion.
176203
177 Return blueprint -> info mapping, with info being a map with these204 Return blueprint -> info mapping, with info being a map with these
178 keys: todo, inprogress, done, postponed, status, priority, implementation, url, tasks.205 keys: todo, blocked, inprogress, done, postponed, status, priority, implementation, url, tasks.
179 '''206 '''
180 default = blueprint_completion(db, team=team)207 default = blueprint_completion(db, team=team)
181 data = {}208 data = {}
@@ -196,12 +223,19 @@
196 if team:223 if team:
197 # include both direct team assignments, as well as assignmnets to224 # include both direct team assignments, as well as assignmnets to
198 # members of that team225 # members of that team
199 cur.execute('SELECT DISTINCT w.spec, w.description, w.assignee, w.milestone, w.status '226 if isinstance(team, user_string):
200 'FROM work_items w, specs s ON w.spec = s.name '227 cur.execute('SELECT DISTINCT w.spec, w.description, w.assignee, w.milestone, w.status '
201 ' LEFT JOIN teams ON (s.assignee = teams.name OR w.assignee = teams.name) '228 'FROM work_items w, specs s ON w.spec = s.name '
202 'WHERE w.status = ? AND w.date = ? AND'229 'WHERE w.status = ? AND w.date = ? AND'
203 ' (teams.team = ? OR w.assignee = ? or s.assignee = ?) %s ' % ms_sql,230 ' (w.assignee = ? or (s.assignee = ? and w.assignee is null)) %s ' % ms_sql,
204 (s, last_date, team, team, team))231 (s, last_date, team, team))
232 else:
233 cur.execute('SELECT DISTINCT w.spec, w.description, w.assignee, w.milestone, w.status '
234 'FROM work_items w, specs s ON w.spec = s.name '
235 ' LEFT JOIN teams ON (s.assignee = teams.name OR w.assignee = teams.name) '
236 'WHERE w.status = ? AND w.date = ? AND'
237 ' (teams.team = ? OR w.assignee = ? or s.assignee = ?) %s ' % ms_sql,
238 (s, last_date, team, team, team))
205 else:239 else:
206 cur.execute('SELECT DISTINCT w.spec, w.description, w.assignee, w.milestone, w.status '240 cur.execute('SELECT DISTINCT w.spec, w.description, w.assignee, w.milestone, w.status '
207 'FROM work_items w, specs s '241 'FROM work_items w, specs s '
@@ -227,7 +261,7 @@
227 '''Determine current by-assignee completion.261 '''Determine current by-assignee completion.
228262
229 Return assignee -> info mapping with info being a map with these263 Return assignee -> info mapping with info being a map with these
230 keys: todo, inprogress, done, postponed. Each of those values is a list of [blueprint,264 keys: todo, blocked, inprogress, done, postponed. Each of those values is a list of [blueprint,
231 workitem, priority, spec_url].265 workitem, priority, spec_url].
232 '''266 '''
233 data = {}267 data = {}
@@ -248,12 +282,19 @@
248 if team:282 if team:
249 # include both direct team assignments, as well as assignmnets to283 # include both direct team assignments, as well as assignmnets to
250 # members of that team284 # members of that team
251 cur.execute('SELECT DISTINCT w.assignee, w.spec, w.description, s.priority, s.url '285 if isinstance(team, user_string):
252 'FROM work_items w, specs s ON w.spec = s.name '286 cur.execute('SELECT DISTINCT w.assignee, w.spec, w.description, s.priority, s.url '
253 ' LEFT JOIN teams ON (s.assignee = teams.name OR w.assignee = teams.name) '287 'FROM work_items w, specs s ON w.spec = s.name '
254 'WHERE w.status = ? AND w.date = ? AND'288 'WHERE w.status = ? AND w.date = ? AND'
255 ' (teams.team = ? OR w.assignee = ? or s.assignee = ?) %s ' % ms_sql,289 ' (w.assignee = ? or (s.assignee = ? and w.assignee is null)) %s ' % ms_sql,
256 (s, last_date, team, team, team))290 (s, last_date, team, team))
291 else:
292 cur.execute('SELECT DISTINCT w.assignee, w.spec, w.description, s.priority, s.url '
293 'FROM work_items w, specs s ON w.spec = s.name '
294 ' LEFT JOIN teams ON (s.assignee = teams.name OR w.assignee = teams.name) '
295 'WHERE w.status = ? AND w.date = ? AND'
296 ' (teams.team = ? OR w.assignee = ? or s.assignee = ?) %s ' % ms_sql,
297 (s, last_date, team, team, team))
257 else:298 else:
258 cur.execute('SELECT DISTINCT w.assignee, w.spec, w.description, s.priority, s.url '299 cur.execute('SELECT DISTINCT w.assignee, w.spec, w.description, s.priority, s.url '
259 'FROM work_items w, specs s '300 'FROM work_items w, specs s '
@@ -261,7 +302,7 @@
261 'WHERE w.status = ? AND w.date = ? %s ' % ms_sql,302 'WHERE w.status = ? AND w.date = ? %s ' % ms_sql,
262 (s, last_date))303 (s, last_date))
263 for (a, bp, description, priority, url) in cur:304 for (a, bp, description, priority, url) in cur:
264 info = data.setdefault(a, {'todo': [], 'done': [], 'postponed': [], 'inprogress': []})305 info = data.setdefault(a, {'todo': [], 'blocked': [], 'done': [], 'postponed': [], 'inprogress': []})
265 info[s].append([bp, description, priority, url])306 info[s].append([bp, description, priority, url])
266307
267 cur2 = db.cursor();308 cur2 = db.cursor();

Subscribers

People subscribed via source and target branches

to all changes: