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 | 43 | 43 | ||
6 | 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): |
7 | 45 | i = data.get(ordinal_to_date(date), {}) | 45 | i = data.get(ordinal_to_date(date), {}) |
9 | 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) |
10 | 47 | if max_items < count: | 47 | if max_items < count: |
11 | 48 | max_items = count | 48 | max_items = count |
12 | 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), |
13 | 50 | i.get('blocked_teamonly', 0), i.get('blocked', 0), | ||
14 | 50 | i.get('inprogress_teamonly', 0), i.get('inprogress', 0), | 51 | i.get('inprogress_teamonly', 0), i.get('inprogress', 0), |
15 | 51 | i.get('done_teamonly', 0), i.get('done', 0), | 52 | i.get('done_teamonly', 0), i.get('done', 0), |
16 | 52 | i.get('postponed_teamonly', 0), i.get('postponed', 0), count)) | 53 | i.get('postponed_teamonly', 0), i.get('postponed', 0), count)) |
17 | @@ -71,6 +72,9 @@ | |||
18 | 71 | # create the chart area | 72 | # create the chart area |
19 | 72 | # tell it to start at coords 0,0 | 73 | # tell it to start at coords 0,0 |
20 | 73 | # tell it the labels, and the tics, etc.. | 74 | # tell it the labels, and the tics, etc.. |
21 | 75 | # HACK: to prevent 0 div | ||
22 | 76 | if max_items == 0: | ||
23 | 77 | max_items = 1 | ||
24 | 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), |
25 | 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), |
26 | 76 | y_axis=axis.Y(label='Work Items', tic_interval=y_interval), | 80 | y_axis=axis.Y(label='Work Items', tic_interval=y_interval), |
27 | @@ -91,30 +95,37 @@ | |||
28 | 91 | plot2.fill_style = fill_style.Plain(bgcolor=color.coral1) | 95 | plot2.fill_style = fill_style.Plain(bgcolor=color.coral1) |
29 | 92 | plot2.line_style = None | 96 | plot2.line_style = None |
30 | 93 | 97 | ||
33 | 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) |
34 | 95 | plot3.fill_style = fill_style.Plain(bgcolor=color.orange2) | 99 | plot3.fill_style = fill_style.Plain(bgcolor=color.darkred) |
35 | 96 | plot3.line_style = None | 100 | plot3.line_style = None |
38 | 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) |
39 | 98 | plot4.fill_style = fill_style.Plain(bgcolor=color.orange1) | 102 | plot4.fill_style = fill_style.Plain(bgcolor=color.indianred) |
40 | 99 | plot4.line_style = None | 103 | plot4.line_style = None |
41 | 100 | 104 | ||
44 | 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) |
45 | 102 | plot5.fill_style = fill_style.green | 106 | plot5.fill_style = fill_style.Plain(bgcolor=color.orange2) |
46 | 103 | plot5.line_style = None | 107 | plot5.line_style = None |
49 | 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) |
50 | 105 | plot6.fill_style = fill_style.Plain(bgcolor=color.olivedrab1) | 109 | plot6.fill_style = fill_style.Plain(bgcolor=color.orange1) |
51 | 106 | plot6.line_style = None | 110 | plot6.line_style = None |
52 | 107 | 111 | ||
55 | 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) |
56 | 109 | plot7.fill_style = fill_style.Plain(bgcolor=color.yellow2) | 113 | plot7.fill_style = fill_style.green |
57 | 110 | plot7.line_style = None | 114 | plot7.line_style = None |
60 | 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) |
61 | 112 | plot8.fill_style = fill_style.Plain(bgcolor=color.yellow1) | 116 | plot8.fill_style = fill_style.Plain(bgcolor=color.olivedrab1) |
62 | 113 | plot8.line_style = None | 117 | plot8.line_style = None |
63 | 114 | 118 | ||
67 | 115 | plot9 = bar_plot.T(label='total', hcol=9) | 119 | plot9 = bar_plot.T(label="postponed (team)", hcol=9, stack_on = plot8) |
68 | 116 | plot9.fill_style = None | 120 | plot9.fill_style = fill_style.Plain(bgcolor=color.yellow2) |
69 | 117 | plot9.line_style = line_style.gray30 | 121 | plot9.line_style = None |
70 | 122 | plot10 = bar_plot.T(label="postponed (foreign) ", hcol=10, stack_on = plot8) | ||
71 | 123 | plot10.fill_style = fill_style.Plain(bgcolor=color.yellow1) | ||
72 | 124 | plot10.line_style = None | ||
73 | 125 | |||
74 | 126 | plot11 = bar_plot.T(label='total', hcol=9) | ||
75 | 127 | plot10.fill_style = None | ||
76 | 128 | plot10.line_style = line_style.gray30 | ||
77 | 118 | 129 | ||
78 | 119 | # create the canvas with the specified filename and file format | 130 | # create the canvas with the specified filename and file format |
79 | 120 | can = canvas.init(filename,format) | 131 | can = canvas.init(filename,format) |
80 | @@ -156,6 +167,8 @@ | |||
81 | 156 | help='Output file', dest='output') | 167 | help='Output file', dest='output') |
82 | 157 | optparser.add_option('--trend-start', type='int', | 168 | optparser.add_option('--trend-start', type='int', |
83 | 158 | help='Explicitly set start of trend line', dest='trendstart') | 169 | help='Explicitly set start of trend line', dest='trendstart') |
84 | 170 | optparser.add_option('-u', '--user', | ||
85 | 171 | help='Run for this user', dest='user') | ||
86 | 159 | 172 | ||
87 | 160 | (opts, args) = optparser.parse_args() | 173 | (opts, args) = optparser.parse_args() |
88 | 161 | if not opts.database: | 174 | if not opts.database: |
89 | @@ -163,6 +176,15 @@ | |||
90 | 163 | if not opts.output: | 176 | if not opts.output: |
91 | 164 | optparser.error('No output file given') | 177 | optparser.error('No output file given') |
92 | 165 | 178 | ||
93 | 179 | if opts.user and opts.team: | ||
94 | 180 | optparser.error('team and user options are mutually exclusive') | ||
95 | 181 | |||
96 | 182 | # The typing allows polymorphic behavior | ||
97 | 183 | if opts.user: | ||
98 | 184 | opts.team = report_tools.user_string(opts.user) | ||
99 | 185 | elif opts.team: | ||
100 | 186 | opts.team = report_tools.team_string(opts.team) | ||
101 | 187 | |||
102 | 166 | # get date -> state -> count mapping | 188 | # get date -> state -> count mapping |
103 | 167 | db = report_tools.get_db(opts.database) | 189 | db = report_tools.get_db(opts.database) |
104 | 168 | data = report_tools.workitems_over_time(db, opts.team, opts.milestone) | 190 | data = report_tools.workitems_over_time(db, opts.team, opts.milestone) |
105 | 169 | 191 | ||
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 | 618 | else: | 618 | else: |
111 | 619 | status_search = fields[2:] | 619 | status_search = fields[2:] |
112 | 620 | for f in status_search: | 620 | for f in status_search: |
114 | 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: |
115 | 622 | ff = f.split() | 622 | ff = f.split() |
116 | 623 | if len(ff) == 2: | 623 | if len(ff) == 2: |
117 | 624 | assignee = ff[1] | 624 | assignee = ff[1] |
118 | @@ -628,6 +628,9 @@ | |||
119 | 628 | elif 'POSTPONED' in f: | 628 | elif 'POSTPONED' in f: |
120 | 629 | istatus = 'postponed' | 629 | istatus = 'postponed' |
121 | 630 | break | 630 | break |
122 | 631 | elif 'BLOCKED' in f: | ||
123 | 632 | istatus = 'blocked' | ||
124 | 633 | break | ||
125 | 631 | elif 'INPROGRESS' in f and allow_inprogress: | 634 | elif 'INPROGRESS' in f and allow_inprogress: |
126 | 632 | istatus = 'inprogress' | 635 | istatus = 'inprogress' |
127 | 633 | break | 636 | break |
128 | @@ -807,6 +810,13 @@ | |||
129 | 807 | db.commit() | 810 | db.commit() |
130 | 808 | ver = 5 | 811 | ver = 5 |
131 | 809 | 812 | ||
132 | 813 | if ver == 5: | ||
133 | 814 | dbg('Upgrading DB to layout version 6') | ||
134 | 815 | create_v6_indexes(cur) | ||
135 | 816 | cur.execute('UPDATE version SET db_layout_ref = 6') | ||
136 | 817 | db.commit() | ||
137 | 818 | ver = 6 | ||
138 | 819 | |||
139 | 810 | return db | 820 | return db |
140 | 811 | 821 | ||
141 | 812 | def create_v5_indexes(cur): | 822 | def create_v5_indexes(cur): |
142 | @@ -814,6 +824,9 @@ | |||
143 | 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)''') |
144 | 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)''') |
145 | 816 | 826 | ||
146 | 827 | def create_v6_indexes(cur): | ||
147 | 828 | cur.execute('''CREATE INDEX work_items_assignee_milestone_idx on work_items(assignee,milestone)''') | ||
148 | 829 | |||
149 | 817 | ######################################################################## | 830 | ######################################################################## |
150 | 818 | # | 831 | # |
151 | 819 | # Program operations and main | 832 | # Program operations and main |
152 | 820 | 833 | ||
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 | 20 | dest='config', metavar='PATH') | 20 | dest='config', metavar='PATH') |
158 | 21 | optparser.add_option('-o', '--output', metavar='DIRECTORY', | 21 | optparser.add_option('-o', '--output', metavar='DIRECTORY', |
159 | 22 | help='Output directory', dest='output_dir') | 22 | help='Output directory', dest='output_dir') |
160 | 23 | optparser.add_option('-a', '--all-milestones', | ||
161 | 24 | help='Regnerate all milestones, even those that were due before today', | ||
162 | 25 | dest='all_milestones', default=False, action='store_true') | ||
163 | 23 | 26 | ||
164 | 24 | (opts, args) = optparser.parse_args() | 27 | (opts, args) = optparser.parse_args() |
165 | 25 | if not opts.database: | 28 | if not opts.database: |
166 | @@ -38,8 +41,12 @@ | |||
167 | 38 | 41 | ||
168 | 39 | # get milestones and teams | 42 | # get milestones and teams |
169 | 40 | cur = db.cursor() | 43 | cur = db.cursor() |
171 | 41 | cur.execute('SELECT name FROM milestones') | 44 | if opts.all_milestones: |
172 | 45 | cur.execute('SELECT name FROM milestones') | ||
173 | 46 | else: | ||
174 | 47 | cur.execute("SELECT name FROM milestones WHERE due_date >= date('now')") | ||
175 | 42 | milestones = [i[0] for i in cur] | 48 | milestones = [i[0] for i in cur] |
176 | 49 | print 'regenerating milestones: %s' % ','.join(milestones) | ||
177 | 43 | cur.execute('SELECT DISTINCT team FROM teams') | 50 | cur.execute('SELECT DISTINCT team FROM teams') |
178 | 44 | teams = [i[0] for i in cur] | 51 | teams = [i[0] for i in cur] |
179 | 45 | cur.execute('SELECT DISTINCT name FROM teams') | 52 | cur.execute('SELECT DISTINCT name FROM teams') |
180 | @@ -61,17 +68,17 @@ | |||
181 | 61 | print basename + '.html' | 68 | print basename + '.html' |
182 | 62 | f = open(basename + '.html', 'w') | 69 | f = open(basename + '.html', 'w') |
183 | 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, |
185 | 64 | '-t', u, '-m', m, '--chart', '%s.svg' % target], stdout=f) | 71 | '-u', u, '-m', m, '--chart', '%s.svg' % target], stdout=f) |
186 | 65 | f.close() | 72 | f.close() |
187 | 66 | 73 | ||
188 | 67 | print basename + '.json' | 74 | print basename + '.json' |
189 | 68 | f = open(basename + '.json', 'w') | 75 | f = open(basename + '.json', 'w') |
190 | 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, |
192 | 70 | '-t', u, '-m', m, '-c', opts.config], stdout=f) | 77 | '-u', u, '-m', m, '-c', opts.config or ''], stdout=f) |
193 | 71 | f.close() | 78 | f.close() |
194 | 72 | 79 | ||
195 | 73 | print basename + '.svg' | 80 | print basename + '.svg' |
197 | 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', |
198 | 75 | u, '-m', m, '-o', basename + '.svg'] | 82 | u, '-m', m, '-o', basename + '.svg'] |
199 | 76 | if u in trend_starts: | 83 | if u in trend_starts: |
200 | 77 | argv += ['--trend-start', str(trend_starts[t])] | 84 | argv += ['--trend-start', str(trend_starts[t])] |
201 | 78 | 85 | ||
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 | 27 | <p>(Click header to sort)</p> | 27 | <p>(Click header to sort)</p> |
207 | 28 | <table id="byspecification"> | 28 | <table id="byspecification"> |
208 | 29 | <thead> | 29 | <thead> |
210 | 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> |
211 | 31 | </thead> | 31 | </thead> |
212 | 32 | ''' | 32 | ''' |
213 | 33 | data = report_tools.blueprint_completion(db, team, milestone) | 33 | data = report_tools.blueprint_completion(db, team, milestone) |
214 | @@ -35,16 +35,16 @@ | |||
215 | 35 | completion = [] | 35 | completion = [] |
216 | 36 | for (bp, i) in data.iteritems(): | 36 | for (bp, i) in data.iteritems(): |
217 | 37 | completion.append((bp, | 37 | completion.append((bp, |
219 | 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))) |
220 | 39 | 39 | ||
221 | 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) |
222 | 41 | 41 | ||
223 | 42 | for (bp, percent) in completion: | 42 | for (bp, percent) in completion: |
225 | 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/>' \ |
226 | 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>' % ( |
227 | 45 | '<a href="%s">%s</a>' % (data[bp]['url'], escape_html(bp)), | 45 | '<a href="%s">%s</a>' % (data[bp]['url'], escape_html(bp)), |
228 | 46 | data[bp]['complexity'] or '', | 46 | data[bp]['complexity'] or '', |
230 | 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'], |
231 | 48 | percent, | 48 | percent, |
232 | 49 | data[bp]['implementation'], | 49 | data[bp]['implementation'], |
233 | 50 | format_priority(data[bp]['priority']), | 50 | format_priority(data[bp]['priority']), |
234 | @@ -58,24 +58,24 @@ | |||
235 | 58 | <p>(Click header to sort)</p> | 58 | <p>(Click header to sort)</p> |
236 | 59 | <table id="byassignee"> | 59 | <table id="byassignee"> |
237 | 60 | <thead> | 60 | <thead> |
239 | 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> |
240 | 62 | </thead> | 62 | </thead> |
241 | 63 | ''' | 63 | ''' |
242 | 64 | data = report_tools.assignee_completion(db, team, milestone) | 64 | data = report_tools.assignee_completion(db, team, milestone) |
243 | 65 | 65 | ||
244 | 66 | completion = [] | 66 | completion = [] |
245 | 67 | for a, i in data.iteritems(): | 67 | for a, i in data.iteritems(): |
247 | 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'])) |
248 | 69 | completion.append((a, | 69 | completion.append((a, |
250 | 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))) |
251 | 71 | 71 | ||
252 | 72 | completion.sort(key=lambda k: k[0], reverse=False) | 72 | completion.sort(key=lambda k: k[0], reverse=False) |
253 | 73 | 73 | ||
254 | 74 | for (a, percent) in completion: | 74 | for (a, percent) in completion: |
255 | 75 | a_html = escape_url(a or team or 'nobody') | 75 | a_html = escape_url(a or team or 'nobody') |
256 | 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) |
259 | 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>' % ( |
260 | 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']), |
261 | 79 | len(data[a]['done']), percent) | 79 | len(data[a]['done']), percent) |
262 | 80 | print '</table>' | 80 | print '</table>' |
263 | 81 | 81 | ||
264 | @@ -101,12 +101,14 @@ | |||
265 | 101 | 101 | ||
266 | 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]): |
267 | 103 | todo_len = len(i['todo']) | 103 | todo_len = len(i['todo']) |
268 | 104 | blocked_len = len(i['blocked']) | ||
269 | 104 | postponed_len = len(i['postponed']) | 105 | postponed_len = len(i['postponed']) |
270 | 105 | done_len = len(i['done']) | 106 | done_len = len(i['done']) |
271 | 106 | inprogress_len = len(i['inprogress']) | 107 | inprogress_len = len(i['inprogress']) |
272 | 107 | a_html = escape_url(a or team or 'nobody') | 108 | a_html = escape_url(a or team or 'nobody') |
273 | 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) |
274 | 109 | rows = {'todo': '<td rowspan="%s">todo</td>' % todo_len, | 110 | rows = {'todo': '<td rowspan="%s">todo</td>' % todo_len, |
275 | 111 | 'blocked': '<td rowspan="%s">blocked</td>' % blocked_len, | ||
276 | 110 | 'postponed': '<td rowspan="%s">postponed</td>' % postponed_len, | 112 | 'postponed': '<td rowspan="%s">postponed</td>' % postponed_len, |
277 | 111 | 'done': '<td rowspan="%s">done</td>' % done_len, | 113 | 'done': '<td rowspan="%s">done</td>' % done_len, |
278 | 112 | 'inprogress': '<td rowspan="%s">inprogress</td>' % inprogress_len} | 114 | 'inprogress': '<td rowspan="%s">inprogress</td>' % inprogress_len} |
279 | @@ -118,7 +120,7 @@ | |||
280 | 118 | print ' <tr>', | 120 | print ' <tr>', |
281 | 119 | if not printed_assignee: | 121 | if not printed_assignee: |
282 | 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> ' % ( |
284 | 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'), |
285 | 122 | a_html) | 124 | a_html) |
286 | 123 | printed_assignee = True | 125 | printed_assignee = True |
287 | 124 | if not printed_status: | 126 | if not printed_status: |
288 | @@ -260,10 +262,20 @@ | |||
289 | 260 | help='Restrict report to a particular milestone', dest='milestone') | 262 | help='Restrict report to a particular milestone', dest='milestone') |
290 | 261 | optparser.add_option('--chart', default='burndown.svg', | 263 | optparser.add_option('--chart', default='burndown.svg', |
291 | 262 | help='(Relative) URL to burndown chart', dest='chart_url') | 264 | help='(Relative) URL to burndown chart', dest='chart_url') |
292 | 265 | optparser.add_option('-u', '--user', | ||
293 | 266 | help='Run for this user', dest='user') | ||
294 | 263 | 267 | ||
295 | 264 | (opts, args) = optparser.parse_args() | 268 | (opts, args) = optparser.parse_args() |
296 | 265 | if not opts.database: | 269 | if not opts.database: |
297 | 266 | optparser.error('No database given') | 270 | optparser.error('No database given') |
298 | 271 | if opts.user and opts.team: | ||
299 | 272 | optparser.error('team and user options are mutually exclusive') | ||
300 | 273 | |||
301 | 274 | # The typing allows polymorphic behavior | ||
302 | 275 | if opts.user: | ||
303 | 276 | opts.team = report_tools.user_string(opts.user) | ||
304 | 277 | elif opts.team: | ||
305 | 278 | opts.team = report_tools.team_string(opts.team) | ||
306 | 267 | 279 | ||
307 | 268 | db = report_tools.get_db(opts.database) | 280 | db = report_tools.get_db(opts.database) |
308 | 269 | 281 | ||
309 | 270 | 282 | ||
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 | 25 | optparser.add_option('-c', '--config', | 25 | optparser.add_option('-c', '--config', |
315 | 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)', |
316 | 27 | dest='config', metavar='PATH') | 27 | dest='config', metavar='PATH') |
317 | 28 | optparser.add_option('-u', '--user', | ||
318 | 29 | help='Run for this user', dest='user') | ||
319 | 28 | 30 | ||
320 | 29 | (opts, args) = optparser.parse_args() | 31 | (opts, args) = optparser.parse_args() |
321 | 30 | if not opts.database: | 32 | if not opts.database: |
322 | 31 | optparser.error('No database given') | 33 | optparser.error('No database given') |
323 | 32 | db = report_tools.get_db(opts.database) | 34 | db = report_tools.get_db(opts.database) |
324 | 33 | 35 | ||
325 | 36 | if opts.user and opts.team: | ||
326 | 37 | optparser.error('team and user options are mutually exclusive') | ||
327 | 38 | |||
328 | 39 | # The typing allows polymorphic behavior | ||
329 | 40 | if opts.user: | ||
330 | 41 | opts.team = report_tools.user_string(opts.user) | ||
331 | 42 | elif opts.team: | ||
332 | 43 | opts.team = report_tools.team_string(opts.team) | ||
333 | 44 | |||
334 | 34 | if opts.config: | 45 | if opts.config: |
335 | 35 | config = report_tools.load_config(opts.config) | 46 | config = report_tools.load_config(opts.config) |
336 | 36 | else: | 47 | else: |
337 | 37 | 48 | ||
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 | 6 | import sqlite3 as dbapi2 | 6 | import sqlite3 as dbapi2 |
343 | 7 | from cgi import escape | 7 | from cgi import escape |
344 | 8 | 8 | ||
346 | 9 | valid_states = ['todo', 'done', 'postponed', 'inprogress'] | 9 | valid_states = ['inprogress','blocked','todo','done', 'postponed'] |
347 | 10 | blueprints_base_url = 'https://blueprints.launchpad.net' | 10 | blueprints_base_url = 'https://blueprints.launchpad.net' |
348 | 11 | 11 | ||
349 | 12 | class user_string(str): | ||
350 | 13 | pass | ||
351 | 14 | |||
352 | 15 | class team_string(str): | ||
353 | 16 | pass | ||
354 | 17 | |||
355 | 12 | def escape_sql(text): | 18 | def escape_sql(text): |
356 | 13 | return text.replace("'", "''") | 19 | return text.replace("'", "''") |
357 | 14 | 20 | ||
358 | @@ -84,35 +90,49 @@ | |||
359 | 84 | 90 | ||
360 | 85 | # include both direct team assignments, as well as assignmets to | 91 | # include both direct team assignments, as well as assignmets to |
361 | 86 | # members of that team | 92 | # members of that team |
367 | 87 | for s in valid_states: | 93 | cur = db.cursor() |
368 | 88 | cur = db.cursor() | 94 | if team: |
369 | 89 | if team: | 95 | # WIs which are assigned to team members |
370 | 90 | # WIs which are assigned to team members | 96 | if isinstance(team, user_string): |
371 | 91 | cur.execute('SELECT date, COUNT(DISTINCT w.description) ' | 97 | cur.execute('SELECT status, date, COUNT(DISTINCT w.description) ' |
372 | 98 | 'FROM work_items w ' | ||
373 | 99 | 'WHERE assignee = ? %s ' | ||
374 | 100 | 'GROUP BY status, date' % ms_sql, [team]) | ||
375 | 101 | else: | ||
376 | 102 | cur.execute('SELECT status, date, COUNT(DISTINCT w.description) ' | ||
377 | 92 | 'FROM work_items w LEFT JOIN teams ' | 103 | 'FROM work_items w LEFT JOIN teams ' |
378 | 93 | 'ON w.assignee = teams.name ' | 104 | 'ON w.assignee = teams.name ' |
387 | 94 | 'WHERE status = ? AND (team = ? OR assignee = ?) %s' | 105 | 'WHERE (team = ? OR assignee = ?) %s' |
388 | 95 | 'GROUP BY date' % ms_sql, (s, team, team)) | 106 | 'GROUP BY status, date' % ms_sql, (team, team)) |
389 | 96 | 107 | ||
390 | 97 | for (date, num) in cur: | 108 | for (s, date, num) in cur: |
391 | 98 | data.setdefault(date, {})[s + '_teamonly'] = num | 109 | data.setdefault(date, {})[s + '_teamonly'] = num |
392 | 99 | 110 | ||
393 | 100 | # all WIs which belong to that team's specs | 111 | # all WIs which belong to that team's specs |
394 | 101 | cur.execute('SELECT date, COUNT(DISTINCT w.description) ' | 112 | # Only do this for teams, users only want their |
395 | 113 | # assigned work items | ||
396 | 114 | if isinstance(team, user_string): | ||
397 | 115 | cur.execute('SELECT w.status, date, COUNT(DISTINCT w.description) ' | ||
398 | 116 | 'FROM work_items w, specs s ON w.spec = s.name ' | ||
399 | 117 | 'WHERE (w.assignee = ? OR (s.assignee = ? and w.assignee is null)) %s' | ||
400 | 118 | 'GROUP BY w.status, date' % ms_sql, (team, team)) | ||
401 | 119 | else: | ||
402 | 120 | cur.execute('SELECT w.status, date, COUNT(DISTINCT w.description) ' | ||
403 | 102 | 'FROM work_items w, specs s ON w.spec = s.name ' | 121 | 'FROM work_items w, specs s ON w.spec = s.name ' |
404 | 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) ' |
417 | 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' |
418 | 105 | 'GROUP BY date' % ms_sql, (s, team, team, team)) | 124 | 'GROUP BY w.status, date' % ms_sql, (team, team, team)) |
419 | 106 | 125 | ||
420 | 107 | for (date, num) in cur: | 126 | for (s, date, num) in cur: |
421 | 108 | data.setdefault(date, {})[s] = num | 127 | data.setdefault(date, {})[s] = num |
422 | 109 | else: | 128 | |
423 | 110 | # all WIs for this milestone | 129 | else: |
424 | 111 | cur.execute('SELECT date, count(*) FROM work_items w ' | 130 | # all WIs for this milestone |
425 | 112 | 'WHERE status=? %s GROUP BY date' % ms_sql, (s,)) | 131 | cur.execute('SELECT status, date, count(*) FROM work_items w ' |
426 | 113 | 132 | 'WHERE 1=1 %s GROUP BY status, date' % ms_sql) | |
427 | 114 | for (date, num) in cur: | 133 | |
428 | 115 | data.setdefault(date, {})[s] = num | 134 | for (date, num) in cur: |
429 | 135 | data.setdefault(date, {})[s] = num | ||
430 | 116 | return data | 136 | return data |
431 | 117 | 137 | ||
432 | 118 | def blueprint_completion(db, team=None, milestone=None): | 138 | def blueprint_completion(db, team=None, milestone=None): |
433 | @@ -134,39 +154,46 @@ | |||
434 | 134 | cur.execute('SELECT max(date) FROM work_items') | 154 | cur.execute('SELECT max(date) FROM work_items') |
435 | 135 | (last_date,) = cur.fetchone() | 155 | (last_date,) = cur.fetchone() |
436 | 136 | 156 | ||
443 | 137 | for s in valid_states: | 157 | cur = db.cursor() |
444 | 138 | cur = db.cursor() | 158 | if team: |
445 | 139 | if team: | 159 | # include both direct team assignments, as well as assignmnets to |
446 | 140 | # include both direct team assignments, as well as assignmnets to | 160 | # members of that team |
447 | 141 | # members of that team | 161 | if isinstance(team, user_string): |
448 | 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 ' |
449 | 163 | 'FROM work_items w, specs s ON w.spec = s.name ' | ||
450 | 164 | 'WHERE w.date = ? AND' | ||
451 | 165 | ' (w.assignee = ? or (s.assignee = ? and w.assignee is null)) %s ' | ||
452 | 166 | 'GROUP BY w.status,w.spec' % ms_sql, | ||
453 | 167 | (last_date, team, team)) | ||
454 | 168 | else: | ||
455 | 169 | cur.execute('SELECT w.status, s.name, COUNT(DISTINCT w.description), s.status, s.priority, s.implementation, s.url, s.roadmap_notes ' | ||
456 | 143 | 'FROM work_items w, specs s ON w.spec = s.name ' | 170 | 'FROM work_items w, specs s ON w.spec = s.name ' |
457 | 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)' |
459 | 145 | 'WHERE w.status = ? AND w.date = ? AND' | 172 | 'WHERE w.date = ? AND' |
460 | 146 | ' (teams.team = ? OR w.assignee = ? or s.assignee = ?) %s ' | 173 | ' (teams.team = ? OR w.assignee = ? or s.assignee = ?) %s ' |
470 | 147 | 'GROUP BY w.spec' % ms_sql, | 174 | 'GROUP BY w.status, w.spec' % ms_sql, |
471 | 148 | (s, last_date, team, team, team)) | 175 | (last_date, team, team, team)) |
472 | 149 | else: | 176 | else: |
473 | 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 ' |
474 | 151 | 'FROM work_items w, specs s ' | 178 | 'FROM work_items w, specs s ' |
475 | 152 | 'ON w.spec = s.name ' | 179 | 'ON w.spec = s.name ' |
476 | 153 | 'WHERE w.status = ? AND w.date = ? %s ' | 180 | 'WHERE w.status = ? AND w.date = ? %s ' |
477 | 154 | 'GROUP BY w.spec' % ms_sql, | 181 | 'GROUP BY w.status, w.spec' % ms_sql, |
478 | 155 | (s, last_date)) | 182 | (last_date)) |
479 | 156 | 183 | ||
493 | 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: |
494 | 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}) |
495 | 159 | info[s] = num | 186 | info[s] = num |
496 | 160 | info['status'] = status or '' | 187 | info['status'] = status or '' |
497 | 161 | info['priority'] = priority | 188 | info['priority'] = priority |
498 | 162 | info['implementation'] = impl | 189 | info['implementation'] = impl |
499 | 163 | info['url'] = url | 190 | info['url'] = url |
500 | 164 | info['roadmap_notes'] = roadmap_notes | 191 | info['roadmap_notes'] = roadmap_notes |
501 | 165 | 192 | ||
502 | 166 | cur2 = db.cursor(); | 193 | cur2 = db.cursor(); |
503 | 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)) |
504 | 168 | for points in cur2: | 195 | for points in cur2: |
505 | 169 | info['complexity'] = points[0] | 196 | info['complexity'] = points[0] |
506 | 170 | 197 | ||
507 | 171 | return data | 198 | return data |
508 | 172 | 199 | ||
509 | @@ -175,7 +202,7 @@ | |||
510 | 175 | '''Determine current blueprint tasks completion. | 202 | '''Determine current blueprint tasks completion. |
511 | 176 | 203 | ||
512 | 177 | Return blueprint -> info mapping, with info being a map with these | 204 | Return blueprint -> info mapping, with info being a map with these |
514 | 178 | keys: todo, inprogress, done, postponed, status, priority, implementation, url, tasks. | 205 | keys: todo, blocked, inprogress, done, postponed, status, priority, implementation, url, tasks. |
515 | 179 | ''' | 206 | ''' |
516 | 180 | default = blueprint_completion(db, team=team) | 207 | default = blueprint_completion(db, team=team) |
517 | 181 | data = {} | 208 | data = {} |
518 | @@ -196,12 +223,19 @@ | |||
519 | 196 | if team: | 223 | if team: |
520 | 197 | # include both direct team assignments, as well as assignmnets to | 224 | # include both direct team assignments, as well as assignmnets to |
521 | 198 | # members of that team | 225 | # members of that team |
528 | 199 | cur.execute('SELECT DISTINCT w.spec, w.description, w.assignee, w.milestone, w.status ' | 226 | if isinstance(team, user_string): |
529 | 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 ' |
530 | 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 ' |
531 | 202 | 'WHERE w.status = ? AND w.date = ? AND' | 229 | 'WHERE w.status = ? AND w.date = ? AND' |
532 | 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, |
533 | 204 | (s, last_date, team, team, team)) | 231 | (s, last_date, team, team)) |
534 | 232 | else: | ||
535 | 233 | cur.execute('SELECT DISTINCT w.spec, w.description, w.assignee, w.milestone, w.status ' | ||
536 | 234 | 'FROM work_items w, specs s ON w.spec = s.name ' | ||
537 | 235 | ' LEFT JOIN teams ON (s.assignee = teams.name OR w.assignee = teams.name) ' | ||
538 | 236 | 'WHERE w.status = ? AND w.date = ? AND' | ||
539 | 237 | ' (teams.team = ? OR w.assignee = ? or s.assignee = ?) %s ' % ms_sql, | ||
540 | 238 | (s, last_date, team, team, team)) | ||
541 | 205 | else: | 239 | else: |
542 | 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 ' |
543 | 207 | 'FROM work_items w, specs s ' | 241 | 'FROM work_items w, specs s ' |
544 | @@ -227,7 +261,7 @@ | |||
545 | 227 | '''Determine current by-assignee completion. | 261 | '''Determine current by-assignee completion. |
546 | 228 | 262 | ||
547 | 229 | Return assignee -> info mapping with info being a map with these | 263 | Return assignee -> info mapping with info being a map with these |
549 | 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, |
550 | 231 | workitem, priority, spec_url]. | 265 | workitem, priority, spec_url]. |
551 | 232 | ''' | 266 | ''' |
552 | 233 | data = {} | 267 | data = {} |
553 | @@ -248,12 +282,19 @@ | |||
554 | 248 | if team: | 282 | if team: |
555 | 249 | # include both direct team assignments, as well as assignmnets to | 283 | # include both direct team assignments, as well as assignmnets to |
556 | 250 | # members of that team | 284 | # members of that team |
563 | 251 | cur.execute('SELECT DISTINCT w.assignee, w.spec, w.description, s.priority, s.url ' | 285 | if isinstance(team, user_string): |
564 | 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 ' |
565 | 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 ' |
566 | 254 | 'WHERE w.status = ? AND w.date = ? AND' | 288 | 'WHERE w.status = ? AND w.date = ? AND' |
567 | 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, |
568 | 256 | (s, last_date, team, team, team)) | 290 | (s, last_date, team, team)) |
569 | 291 | else: | ||
570 | 292 | cur.execute('SELECT DISTINCT w.assignee, w.spec, w.description, s.priority, s.url ' | ||
571 | 293 | 'FROM work_items w, specs s ON w.spec = s.name ' | ||
572 | 294 | ' LEFT JOIN teams ON (s.assignee = teams.name OR w.assignee = teams.name) ' | ||
573 | 295 | 'WHERE w.status = ? AND w.date = ? AND' | ||
574 | 296 | ' (teams.team = ? OR w.assignee = ? or s.assignee = ?) %s ' % ms_sql, | ||
575 | 297 | (s, last_date, team, team, team)) | ||
576 | 257 | else: | 298 | else: |
577 | 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 ' |
578 | 259 | 'FROM work_items w, specs s ' | 300 | 'FROM work_items w, specs s ' |
579 | @@ -261,7 +302,7 @@ | |||
580 | 261 | 'WHERE w.status = ? AND w.date = ? %s ' % ms_sql, | 302 | 'WHERE w.status = ? AND w.date = ? %s ' % ms_sql, |
581 | 262 | (s, last_date)) | 303 | (s, last_date)) |
582 | 263 | for (a, bp, description, priority, url) in cur: | 304 | for (a, bp, description, priority, url) in cur: |
584 | 264 | info = data.setdefault(a, {'todo': [], 'done': [], 'postponed': [], 'inprogress': []}) | 305 | info = data.setdefault(a, {'todo': [], 'blocked': [], 'done': [], 'postponed': [], 'inprogress': []}) |
585 | 265 | info[s].append([bp, description, priority, url]) | 306 | info[s].append([bp, description, priority, url]) |
586 | 266 | 307 | ||
587 | 267 | cur2 = db.cursor(); | 308 | cur2 = db.cursor(); |