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

Proposed by Clint Byrum
Status: Merged
Merged at revision: 206
Proposed branch: lp:~clint-fewbar/launchpad-work-items-tracker/server-team-mods
Merge into: lp:launchpad-work-items-tracker
Diff against target: 374 lines (+107/-35)
6 files modified
burndown-chart (+20/-12)
collect (+26/-3)
config/maverick.cfg (+1/-0)
generate-all (+33/-0)
html-report (+13/-11)
report_tools.py (+14/-9)
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+31336@code.launchpad.net

Description of the change

Hello tracker hackers.. this branch adds these features:

* Adds indexes making report and chart generation 5 - 6 times faster
* generate-all produces a burndown and report for every single user in the u directory
* inprogress status is now recorded for teams marked as 'inprogress_teams' in config, which currently is only 'canonical-server'

To post a comment you must log in.

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-13 08:55:43 +0000
+++ burndown-chart 2010-07-29 22:19:40 +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('postponed', 0)46 count = i.get('done', 0) + i.get('todo', 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('inprogress_teamonly', 0), i.get('inprogress', 0),
50 i.get('done_teamonly', 0), i.get('done', 0),51 i.get('done_teamonly', 0), i.get('done', 0),
51 i.get('postponed_teamonly', 0), i.get('postponed', 0), count))52 i.get('postponed_teamonly', 0), i.get('postponed', 0), count))
5253
@@ -90,23 +91,30 @@
90 plot2.fill_style = fill_style.Plain(bgcolor=color.coral1)91 plot2.fill_style = fill_style.Plain(bgcolor=color.coral1)
91 plot2.line_style = None92 plot2.line_style = None
9293
93 plot3 = bar_plot.T(label='done (team)', hcol=3, stack_on = plot2)94 plot3 = bar_plot.T(label='inprogress (team)', hcol=3, stack_on = plot2)
94 plot3.fill_style = fill_style.green95 plot3.fill_style = fill_style.Plain(bgcolor=color.orange2)
95 plot3.line_style = None96 plot3.line_style = None
96 plot4 = bar_plot.T(label='done (foreign)', hcol=4, stack_on = plot2)97 plot4 = bar_plot.T(label='inprogress (foreign)', hcol=4, stack_on = plot2)
97 plot4.fill_style = fill_style.Plain(bgcolor=color.olivedrab1)98 plot4.fill_style = fill_style.Plain(bgcolor=color.orange1)
98 plot4.line_style = None99 plot4.line_style = None
99100
100 plot5 = bar_plot.T(label="postponed (team)", hcol=5, stack_on = plot4)101 plot5 = bar_plot.T(label='done (team)', hcol=5, stack_on = plot4)
101 plot5.fill_style = fill_style.Plain(bgcolor=color.yellow2)102 plot5.fill_style = fill_style.green
102 plot5.line_style = None103 plot5.line_style = None
103 plot6 = bar_plot.T(label="postponed (foreign) ", hcol=6, stack_on = plot4)104 plot6 = bar_plot.T(label='done (foreign)', hcol=6, stack_on = plot4)
104 plot6.fill_style = fill_style.Plain(bgcolor=color.yellow1)105 plot6.fill_style = fill_style.Plain(bgcolor=color.olivedrab1)
105 plot6.line_style = None106 plot6.line_style = None
106107
107 plot7 = bar_plot.T(label='total', hcol=7)108 plot7 = bar_plot.T(label="postponed (team)", hcol=7, stack_on = plot6)
108 plot7.fill_style = None109 plot7.fill_style = fill_style.Plain(bgcolor=color.yellow2)
109 plot7.line_style = line_style.gray30110 plot7.line_style = None
111 plot8 = bar_plot.T(label="postponed (foreign) ", hcol=8, stack_on = plot6)
112 plot8.fill_style = fill_style.Plain(bgcolor=color.yellow1)
113 plot8.line_style = None
114
115 plot9 = bar_plot.T(label='total', hcol=9)
116 plot9.fill_style = None
117 plot9.line_style = line_style.gray30
110118
111 # create the canvas with the specified filename and file format119 # create the canvas with the specified filename and file format
112 can = canvas.init(filename,format)120 can = canvas.init(filename,format)
113121
=== modified file 'collect'
--- collect 2010-07-22 12:08:04 +0000
+++ collect 2010-07-29 22:19:40 +0000
@@ -359,7 +359,7 @@
359 return None359 return None
360360
361def lp_import_blueprint_workitems(lp, db, bp_name, bp_url, contents, release,361def lp_import_blueprint_workitems(lp, db, bp_name, bp_url, contents, release,
362 allow_inprogress = False):362 inprogress_teams):
363 '''Collect work items from a Launchpad blueprint.363 '''Collect work items from a Launchpad blueprint.
364364
365 This includes work items from the whiteboard as well as linked bugs.365 This includes work items from the whiteboard as well as linked bugs.
@@ -386,6 +386,16 @@
386 dbg('lp_import_blueprint_workitems(): processing %s (spec milestone: %s, spec assignee: %s, spec implementation: %s)' % (386 dbg('lp_import_blueprint_workitems(): processing %s (spec milestone: %s, spec assignee: %s, spec implementation: %s)' % (
387 bp_name, spec_milestone, spec_assignee, spec_implementation))387 bp_name, spec_milestone, spec_assignee, spec_implementation))
388388
389 cur.execute('SELECT team FROM teams WHERE name = ?', (spec_assignee,))
390 assignee_teams = [t[0] for t in cur]
391
392 allow_inprogress = False
393 for t in assignee_teams:
394 if t in inprogress_teams:
395 dbg(' %s allows in progress due to membership in team %s' % (spec_assignee, t))
396 allow_inprogress = True
397 break
398
389 cur.execute('SELECT name FROM milestones')399 cur.execute('SELECT name FROM milestones')
390 valid_milestones = [m[0] for m in cur]400 valid_milestones = [m[0] for m in cur]
391401
@@ -537,7 +547,7 @@
537 dbg('lp_import(): downloading %s from %s' % (bp, url))547 dbg('lp_import(): downloading %s from %s' % (bp, url))
538 contents = urllib.urlopen(url).read().decode('UTF-8')548 contents = urllib.urlopen(url).read().decode('UTF-8')
539 if lp_import_blueprint(db, bp, url, contents):549 if lp_import_blueprint(db, bp, url, contents):
540 lp_import_blueprint_workitems(lp, db, bp, url, contents, cfg.get('release'))550 lp_import_blueprint_workitems(lp, db, bp, url, contents, cfg.get('release'), cfg.get('inprogress_teams'))
541551
542########################################################################552########################################################################
543#553#
@@ -690,7 +700,7 @@
690 cur.execute('''CREATE TABLE version (700 cur.execute('''CREATE TABLE version (
691 db_layout_ref INT NOT NULL701 db_layout_ref INT NOT NULL
692 )''')702 )''')
693 cur.execute('''INSERT INTO version VALUES (4)''')703 cur.execute('''INSERT INTO version VALUES (5)''')
694704
695 cur.execute('''CREATE TABLE specs (705 cur.execute('''CREATE TABLE specs (
696 name VARCHAR(255) PRIMARY KEY,706 name VARCHAR(255) PRIMARY KEY,
@@ -742,6 +752,8 @@
742 date TIMESTAMP NOT NULL752 date TIMESTAMP NOT NULL
743 )''') 753 )''')
744754
755 create_v5_indexes(cur)
756
745 db.commit()757 db.commit()
746 else:758 else:
747 # upgrade DB layout759 # upgrade DB layout
@@ -788,9 +800,20 @@
788 dbg('DB upgrade finished')800 dbg('DB upgrade finished')
789 ver = 4801 ver = 4
790802
803 if ver == 4:
804 dbg('Upgrading DB to layout version 5')
805 create_v5_indexes(cur)
806 cur.execute('UPDATE version SET db_layout_ref = 5')
807 db.commit()
808 ver = 5
791809
792 return db810 return db
793811
812def create_v5_indexes(cur):
813 cur.execute('''CREATE INDEX teams_name_idx on teams(name)''')
814 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)''')
816
794########################################################################817########################################################################
795#818#
796# Program operations and main819# Program operations and main
797820
=== modified file 'config/maverick.cfg'
--- config/maverick.cfg 2010-07-20 15:32:46 +0000
+++ config/maverick.cfg 2010-07-29 22:19:40 +0000
@@ -75,3 +75,4 @@
7575
76recursive_teams = ['arm-ubuntu']76recursive_teams = ['arm-ubuntu']
7777
78inprogress_teams = ['canonical-server']
7879
=== modified file 'generate-all'
--- generate-all 2010-04-06 13:48:48 +0000
+++ generate-all 2010-07-29 22:19:40 +0000
@@ -42,9 +42,41 @@
42milestones = [i[0] for i in cur]42milestones = [i[0] for i in cur]
43cur.execute('SELECT DISTINCT team FROM teams')43cur.execute('SELECT DISTINCT team FROM teams')
44teams = [i[0] for i in cur]44teams = [i[0] for i in cur]
45cur.execute('SELECT DISTINCT name FROM teams')
46users = [i[0] for i in cur]
4547
46my_path = os.path.dirname(sys.argv[0])48my_path = os.path.dirname(sys.argv[0])
4749
50usersubdir = os.path.join(opts.output_dir, 'u')
51try:
52 os.mkdir(usersubdir)
53except OSError:
54 None
55
56for u in users:
57 for m in milestones:
58 # entire cycle report for user
59 target = u + '-' + m
60 basename = os.path.join(usersubdir, target)
61 print basename + '.html'
62 f = open(basename + '.html', 'w')
63 subprocess.call([os.path.join(my_path, 'html-report'), '-d', opts.database,
64 '-t', u, '-m', m, '--chart', '%s.svg' % target], stdout=f)
65 f.close()
66
67 print basename + '.json'
68 f = open(basename + '.json', 'w')
69 subprocess.call([os.path.join(my_path, 'json-report'), '-d', opts.database,
70 '-t', u, '-m', m, '-c', opts.config], stdout=f)
71 f.close()
72
73 print basename + '.svg'
74 argv = [os.path.join(my_path, 'burndown-chart'), '-d', opts.database, '-t',
75 u, '-m', m, '-o', basename + '.svg']
76 if u in trend_starts:
77 argv += ['--trend-start', str(trend_starts[t])]
78 subprocess.call(argv)
79
48# per team/milestone reports80# per team/milestone reports
49for t in teams:81for t in teams:
50 for m in milestones:82 for m in milestones:
@@ -68,6 +100,7 @@
68 argv += ['--trend-start', str(trend_starts[(t, m)])]100 argv += ['--trend-start', str(trend_starts[(t, m)])]
69 subprocess.call(argv)101 subprocess.call(argv)
70102
103
71 # entire cycle report for team104 # entire cycle report for team
72 basename = os.path.join(opts.output_dir, t)105 basename = os.path.join(opts.output_dir, t)
73 print basename + '.html'106 print basename + '.html'
74107
=== modified file 'html-report'
--- html-report 2010-07-08 07:37:26 +0000
+++ html-report 2010-07-29 22:19:40 +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>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>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'])*100 + 0.5)))38 int(float(i['postponed']+i['done'])/(i['todo']+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%%<br/>' \43 print ' <tr><td>%s</td> <td>%s</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]['postponed'], data[bp]['done'],47 data[bp]['todo'], 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>postponed</th><th>done</th> <th>Completion</th></tr>61 <tr><th>Assignee</th> <th>Complexity</th><th>todo</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, postponed, done) = (len(i['todo']), len(i['postponed']), len(i['done']))68 (todo, inprogress, postponed, done) = (len(i['todo']), len(i['inprogress']), len(i['postponed']), len(i['done']))
69 completion.append((a,69 completion.append((a,
70 int(float(postponed+done) / (todo+done+postponed)*100 + 0.5)))70 int(float(postponed+done) / (todo+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></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></tr>' % (
78 url, a_html, data[a]['complexity'] or '', len(data[a]['todo']), len(data[a]['postponed']),78 url, a_html, data[a]['complexity'] or '', len(data[a]['todo']), 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
@@ -103,11 +103,13 @@
103 todo_len = len(i['todo'])103 todo_len = len(i['todo'])
104 postponed_len = len(i['postponed'])104 postponed_len = len(i['postponed'])
105 done_len = len(i['done'])105 done_len = len(i['done'])
106 inprogress_len = len(i['inprogress'])
106 a_html = escape_url(a or team or 'nobody')107 a_html = escape_url(a or team or 'nobody')
107 a_url = '%s/~%s/+specs?role=assignee' % (report_tools.blueprints_base_url, a_html)108 a_url = '%s/~%s/+specs?role=assignee' % (report_tools.blueprints_base_url, a_html)
108 rows = {'todo': '<td rowspan="%s">todo</td>' % todo_len,109 rows = {'todo': '<td rowspan="%s">todo</td>' % todo_len,
109 'postponed': '<td rowspan="%s">postponed</td>' % postponed_len,110 'postponed': '<td rowspan="%s">postponed</td>' % postponed_len,
110 'done': '<td rowspan="%s">done</td>' % done_len}111 'done': '<td rowspan="%s">done</td>' % done_len,
112 'inprogress': '<td rowspan="%s">inprogress</td>' % inprogress_len}
111 printed_assignee = False113 printed_assignee = False
112 for status in report_tools.valid_states:114 for status in report_tools.valid_states:
113 printed_status = False115 printed_status = False
@@ -116,7 +118,7 @@
116 print ' <tr>',118 print ' <tr>',
117 if not printed_assignee:119 if not printed_assignee:
118 print '<td rowspan="%s"><a href="%s" name="%s">%s</a></td> ' % (120 print '<td rowspan="%s"><a href="%s" name="%s">%s</a></td> ' % (
119 todo_len+postponed_len+done_len, a_url, escape_url(a or team or 'nobody'),121 todo_len+postponed_len+done_len+inprogress_len, a_url, escape_url(a or team or 'nobody'),
120 a_html)122 a_html)
121 printed_assignee = True123 printed_assignee = True
122 if not printed_status:124 if not printed_status:
123125
=== modified file 'report_tools.py'
--- report_tools.py 2010-07-08 00:38:33 +0000
+++ report_tools.py 2010-07-29 22:19:40 +0000
@@ -6,7 +6,7 @@
6import sqlite3 as dbapi26import sqlite3 as dbapi2
7from cgi import escape7from cgi import escape
88
9valid_states = ['todo', 'done', 'postponed']9valid_states = ['todo', 'done', 'postponed', 'inprogress']
10blueprints_base_url = 'https://blueprints.launchpad.net'10blueprints_base_url = 'https://blueprints.launchpad.net'
1111
12def escape_sql(text):12def escape_sql(text):
@@ -59,7 +59,7 @@
59 given team.59 given team.
6060
61 Return date -> state -> count mapping. states are61 Return date -> state -> count mapping. states are
62 {todo,done,postponed}{,_teamonly}.62 {todo,inprogress,done,postponed}{,_teamonly}.
63 '''63 '''
64 data = {}64 data = {}
65 if milestone:65 if milestone:
@@ -119,7 +119,7 @@
119 '''Determine current blueprint completion.119 '''Determine current blueprint completion.
120120
121 Return blueprint -> info mapping, with info being a map with these121 Return blueprint -> info mapping, with info being a map with these
122 keys: todo, done, postponed, status, priority, implementation, url.122 keys: todo, inprogress, done, postponed, status, priority, implementation, url.
123 '''123 '''
124 data = {}124 data = {}
125 if milestone == '':125 if milestone == '':
@@ -155,7 +155,7 @@
155 (s, last_date))155 (s, last_date))
156156
157 for (bp, num, status, priority, impl, url, roadmap_notes) in cur:157 for (bp, num, status, priority, impl, url, roadmap_notes) in cur:
158 info = data.setdefault(bp, {'todo': 0, 'done': 0, 'postponed': 0})158 info = data.setdefault(bp, {'todo': 0, 'done': 0, 'postponed': 0, 'inprogress': 0})
159 info[s] = num159 info[s] = num
160 info['status'] = status or ''160 info['status'] = status or ''
161 info['priority'] = priority161 info['priority'] = priority
@@ -175,7 +175,7 @@
175 '''Determine current blueprint tasks completion.175 '''Determine current blueprint tasks completion.
176176
177 Return blueprint -> info mapping, with info being a map with these177 Return blueprint -> info mapping, with info being a map with these
178 keys: todo, done, postponed, status, priority, implementation, url, tasks.178 keys: todo, inprogress, done, postponed, status, priority, implementation, url, tasks.
179 '''179 '''
180 default = blueprint_completion(db, team=team)180 default = blueprint_completion(db, team=team)
181 data = {}181 data = {}
@@ -227,7 +227,7 @@
227 '''Determine current by-assignee completion.227 '''Determine current by-assignee completion.
228228
229 Return assignee -> info mapping with info being a map with these229 Return assignee -> info mapping with info being a map with these
230 keys: todo, done, postponed. Each of those values is a list of [blueprint,230 keys: todo, inprogress, done, postponed. Each of those values is a list of [blueprint,
231 workitem, priority, spec_url].231 workitem, priority, spec_url].
232 '''232 '''
233 data = {}233 data = {}
@@ -261,7 +261,7 @@
261 'WHERE w.status = ? AND w.date = ? %s ' % ms_sql,261 'WHERE w.status = ? AND w.date = ? %s ' % ms_sql,
262 (s, last_date))262 (s, last_date))
263 for (a, bp, description, priority, url) in cur:263 for (a, bp, description, priority, url) in cur:
264 info = data.setdefault(a, {'todo': [], 'done': [], 'postponed': []})264 info = data.setdefault(a, {'todo': [], 'done': [], 'postponed': [], 'inprogress': []})
265 info[s].append([bp, description, priority, url])265 info[s].append([bp, description, priority, url])
266266
267 cur2 = db.cursor();267 cur2 = db.cursor();
@@ -288,7 +288,12 @@
288288
289 ### limit by a team 'like'? (see config)289 ### limit by a team 'like'? (see config)
290 if team:290 if team:
291 team_sql = "AND name like '%%%s%%' " % config.get('teams')[team]291 lookup = ''
292 try:
293 lookup = config.get('teams')[team]
294 except KeyError:
295 lookup = team
296 team_sql = "AND name like '%%%s%%' " % lookup
292 else:297 else:
293 team_sql = ""298 team_sql = ""
294299
@@ -345,7 +350,7 @@
345 ### use the todo/done/postponed numbers from blueprint_completion350 ### use the todo/done/postponed numbers from blueprint_completion
346 csd = completion_dict.get( spec_row["name"], None )351 csd = completion_dict.get( spec_row["name"], None )
347 if csd:352 if csd:
348 for k in ("todo","done","postponed"):353 for k in valid_states:
349 spec_dict["completion"][k] = csd[k]354 spec_dict["completion"][k] = csd[k]
350355
351356

Subscribers

People subscribed via source and target branches

to all changes: