52
by Martin Pitt
html-report: First version with "status by specification" |
1 |
#!/usr/bin/python
|
258.1.2
by Martin Pitt
html-report: Add textual stats over time |
2 |
# -*- coding: utf-8
|
52
by Martin Pitt
html-report: First version with "status by specification" |
3 |
#
|
4 |
# Create HTML reports from a work item database.
|
|
281.1.1
by Martin Pitt
add license file and copyright headers |
5 |
#
|
6 |
# Copyright (C) 2010, 2011 Canonical Ltd.
|
|
7 |
# License: GPL-3
|
|
52
by Martin Pitt
html-report: First version with "status by specification" |
8 |
|
144
by Colin Watson
fix pyflakes unhappiness (trailing whitespace, unused imports) |
9 |
import optparse |
52
by Martin Pitt
html-report: First version with "status by specification" |
10 |
|
249.2.23
by James Westby
Remove unused import. |
11 |
from report_tools import escape_url |
52
by Martin Pitt
html-report: First version with "status by specification" |
12 |
import report_tools |
306.1.15
by Mattias Backman
Move health checks to a separate module and record them with a decorator. |
13 |
from roadmap_health import ( |
14 |
card_health_checks, |
|
15 |
)
|
|
314.1.3
by Mattias Backman
Clean up by moving status summing to report_tools. |
16 |
from lpworkitems.models import ( |
17 |
ROADMAP_STATUSES_MAP, |
|
319.1.4
by Mattias Backman
Sort blueprints in card view; first by status in reversed order, then by milestone. |
18 |
ROADMAP_ORDERED_STATUSES, |
314.1.3
by Mattias Backman
Clean up by moving status summing to report_tools. |
19 |
)
|
254.1.2
by James Westby
Use a template to generate the report. |
20 |
|
314.1.8
by Mattias Backman
Restore a blank line. |
21 |
|
254.1.2
by James Westby
Use a template to generate the report. |
22 |
class WorkitemTarget(object): |
23 |
||
24 |
def __init__(self, todo=0, blocked=0, inprogress=0, postponed=0, |
|
25 |
done=0): |
|
26 |
self.todo = todo |
|
27 |
self.blocked = blocked |
|
28 |
self.inprogress = inprogress |
|
29 |
self.postponed = postponed |
|
30 |
self.done = done |
|
31 |
||
32 |
@property
|
|
33 |
def percent_complete(self): |
|
34 |
completed = self.postponed + self.done |
|
240.1.27
by James Westby
Report on how many workitems have to be fixed. |
35 |
if self.total < 1: |
249.2.19
by James Westby
Avoid division by zero. |
36 |
return 0 |
240.1.27
by James Westby
Report on how many workitems have to be fixed. |
37 |
return int((float(completed)/self.total)*100 + 0.5) |
38 |
||
39 |
@property
|
|
40 |
def total(self): |
|
41 |
total = self.postponed + self.done + self.todo + self.blocked + self.inprogress |
|
42 |
return total |
|
254.1.2
by James Westby
Use a template to generate the report. |
43 |
|
44 |
||
45 |
class Blueprint(WorkitemTarget): |
|
46 |
||
47 |
def __init__(self, name, url, complexity=0, |
|
48 |
todo=0, blocked=0, inprogress=0, postponed=0, |
|
49 |
done=0, implementation=None, priority=None, |
|
50 |
status=None): |
|
51 |
super(Blueprint, self).__init__( |
|
52 |
todo=todo, blocked=blocked, inprogress=inprogress, |
|
53 |
postponed=postponed, done=done) |
|
54 |
self.name = name |
|
55 |
self.url = url |
|
56 |
self.complexity = complexity |
|
57 |
self.implementation = implementation |
|
58 |
self.priority = priority |
|
59 |
self.status = status |
|
60 |
||
240.9.1
by James Westby
Add pages listing the workitems in each status. |
61 |
|
254.1.2
by James Westby
Use a template to generate the report. |
62 |
class Assignee(WorkitemTarget): |
63 |
||
64 |
def __init__(self, name, url, complexity=0, todo_wis=[], |
|
65 |
blocked_wis=[], inprogress_wis=[], postponed_wis=[], |
|
66 |
done_wis=[]): |
|
67 |
super(Assignee, self).__init__( |
|
68 |
todo=len(todo_wis), blocked=len(blocked_wis), |
|
69 |
inprogress=len(inprogress_wis), postponed=len(postponed_wis), |
|
70 |
done=len(done_wis)) |
|
71 |
self.name = name |
|
72 |
self.url = url |
|
73 |
self.complexity = complexity |
|
74 |
self.todo_wis = todo_wis |
|
75 |
self.blocked_wis = blocked_wis |
|
76 |
self.inprogress_wis = inprogress_wis |
|
77 |
self.postponed_wis = postponed_wis |
|
78 |
self.done_wis = done_wis |
|
79 |
||
249.2.5
by James Westby
Add a "thermometer" to the pages. |
80 |
|
249.2.12
by James Westby
Add an overview page. |
81 |
class BlueprintGroup(Blueprint): |
82 |
||
83 |
def __init__(self, name, url, area, complexity=0, todo=0, blocked=0, |
|
84 |
inprogress=0, postponed=0, done=0, implementation=None, |
|
85 |
priority=None, status=None): |
|
86 |
super(BlueprintGroup, self).__init__(name, url, |
|
87 |
complexity=complexity, todo=todo, blocked=blocked, |
|
88 |
inprogress=inprogress, postponed=postponed, |
|
89 |
done=done, implementation=implementation, priority=priority, |
|
90 |
status=status) |
|
91 |
self.area = area |
|
249.2.25
by James Westby
Add a report for each spec_group that shows progress towards it. |
92 |
self.specs = [] |
249.2.12
by James Westby
Add an overview page. |
93 |
|
94 |
||
95 |
class Area(object): |
|
96 |
||
97 |
def __init__(self, name): |
|
98 |
self.name = name |
|
99 |
self.groups = [] |
|
100 |
||
101 |
def has_priority(self): |
|
102 |
return any([g.priority for g in self.groups]) |
|
103 |
||
104 |
||
240.9.1
by James Westby
Add pages listing the workitems in each status. |
105 |
class Workitem(object): |
240.9.2
by James Westby
Improvements from the review. Thanks Guilherme. |
106 |
"""A single workitem"""
|
240.9.1
by James Westby
Add pages listing the workitems in each status. |
107 |
|
108 |
def __init__(self, description, status, blueprint, assignee=None): |
|
109 |
self.description = description |
|
110 |
self.status = status |
|
111 |
self.blueprint = blueprint |
|
240.1.63
by James Westby
Avoid a crash for workitems without Assignee. |
112 |
self.assignee = assignee |
240.9.1
by James Westby
Add pages listing the workitems in each status. |
113 |
|
114 |
||
240.11.6
by James Westby
Comments from review. Thanks Michael. |
115 |
def spec_group_completion(db, team, milestone_collection=None): |
116 |
data = report_tools.spec_group_completion(db, team, milestone_collection=milestone_collection) |
|
249.2.6
by James Westby
Add a table showing progress towards TRs. |
117 |
if not data: |
249.2.40
by James Westby
Avoid Undefined when no TRs. |
118 |
return dict(areas=[], group_completion_series=[], groups=[]) |
249.2.37
by James Westby
Add TRs to burndown reports. |
119 |
groups = [] |
249.2.8
by James Westby
Split the TRs by "area". |
120 |
by_area = {} |
249.2.12
by James Westby
Add an overview page. |
121 |
for bp_name, i in data.items(): |
122 |
area_name = i['area'] |
|
249.2.62
by James Westby
Hardcode ad == advanced development. |
123 |
if area_name == "ad": |
124 |
area_name = "advanced development" |
|
249.2.12
by James Westby
Add an overview page. |
125 |
area_name = " ".join([a.capitalize() for a in area_name.split()]) |
126 |
if area_name not in by_area: |
|
127 |
area = Area(area_name) |
|
128 |
by_area[area_name] = area |
|
129 |
else: |
|
130 |
area = by_area[area_name] |
|
131 |
group = BlueprintGroup(bp_name, i['url'], area, |
|
132 |
todo=i['todo'], blocked=i['blocked'], |
|
133 |
inprogress=i['inprogress'], |
|
134 |
postponed=i['postponed'], done=i['done'], |
|
135 |
implementation=i['implementation'], |
|
136 |
priority=i['priority'], status=i['status']) |
|
249.2.37
by James Westby
Add TRs to burndown reports. |
137 |
groups.append(group) |
249.2.12
by James Westby
Add an overview page. |
138 |
area.groups.append(group) |
139 |
return dict(areas=sorted(by_area.values(), key=lambda x: x.name), |
|
249.2.37
by James Westby
Add TRs to burndown reports. |
140 |
groups=sorted(groups, key=lambda x: report_tools.priority_value(x.priority), reverse=True)) |
249.2.10
by James Westby
Add a graph of TR completion. |
141 |
|
249.2.6
by James Westby
Add a table showing progress towards TRs. |
142 |
|
240.11.6
by James Westby
Comments from review. Thanks Michael. |
143 |
def spec_completion(db, team, milestone_collection=None): |
144 |
data = report_tools.blueprint_completion(db, team, milestone_collection=milestone_collection) |
|
249
by Martin Pitt
html-report: avoid showing empty complexity/priority columns |
145 |
|
254.1.2
by James Westby
Use a template to generate the report. |
146 |
blueprints = [] |
249.2.11
by James Westby
Merge the use of templates. |
147 |
all_workitems = WorkitemTarget() |
240.1.20
by Jamie Bennett
Remove the display of 'Unknown' Blueprints and separate Essential and High out. Leave in 'Unknown' data collection in case we want to use it in another way |
148 |
essential_workitems = WorkitemTarget() |
249.2.95
by James Westby
Put progress bars for each priority on the overview page. |
149 |
high_workitems = WorkitemTarget() |
150 |
medium_workitems = WorkitemTarget() |
|
151 |
low_workitems = WorkitemTarget() |
|
152 |
unknown_workitems = WorkitemTarget() |
|
249.2.11
by James Westby
Merge the use of templates. |
153 |
for bp_name, i in data.items(): |
154 |
bp = Blueprint(bp_name, i['url'], complexity=i['complexity'], |
|
254.1.2
by James Westby
Use a template to generate the report. |
155 |
todo=i['todo'], blocked=i['blocked'], |
156 |
inprogress=i['inprogress'], |
|
157 |
postponed=i['postponed'], done=i['done'], |
|
158 |
implementation=i['implementation'], |
|
249.2.11
by James Westby
Merge the use of templates. |
159 |
priority=i['priority'], status=i['status']) |
249.2.95
by James Westby
Put progress bars for each priority on the overview page. |
160 |
def add_totals(container): |
161 |
container.todo += bp.todo |
|
162 |
container.blocked += bp.blocked |
|
163 |
container.inprogress += bp.inprogress |
|
164 |
container.postponed += bp.postponed |
|
165 |
container.done += bp.done |
|
166 |
add_totals(all_workitems) |
|
167 |
by_importance_workitems = unknown_workitems |
|
240.1.20
by Jamie Bennett
Remove the display of 'Unknown' Blueprints and separate Essential and High out. Leave in 'Unknown' data collection in case we want to use it in another way |
168 |
if i['priority'] == "Essential": |
169 |
by_importance_workitems = essential_workitems |
|
170 |
elif i['priority'] == "High": |
|
249.2.95
by James Westby
Put progress bars for each priority on the overview page. |
171 |
by_importance_workitems = high_workitems |
172 |
elif i['priority'] == "Medium": |
|
173 |
by_importance_workitems = medium_workitems |
|
174 |
elif i['priority'] == "Low": |
|
175 |
by_importance_workitems = low_workitems |
|
176 |
add_totals(by_importance_workitems) |
|
249.2.11
by James Westby
Merge the use of templates. |
177 |
blueprints.append(bp) |
254.1.2
by James Westby
Use a template to generate the report. |
178 |
|
179 |
# avoid showing empty columns
|
|
180 |
specs_have_status = any([bp.status for bp in blueprints]) |
|
181 |
specs_have_complexity = any([bp.complexity > 0 for bp in blueprints]) |
|
182 |
||
183 |
blueprints.sort( |
|
184 |
key=lambda bp: bp.percent_complete*100+report_tools.priority_value(bp.priority), |
|
185 |
reverse=True) |
|
186 |
||
187 |
return dict( |
|
188 |
specs_have_status=specs_have_status, |
|
189 |
specs_have_complexity=specs_have_complexity, |
|
249.2.11
by James Westby
Merge the use of templates. |
190 |
blueprints=blueprints, |
249.2.95
by James Westby
Put progress bars for each priority on the overview page. |
191 |
all_workitems=all_workitems, |
240.1.20
by Jamie Bennett
Remove the display of 'Unknown' Blueprints and separate Essential and High out. Leave in 'Unknown' data collection in case we want to use it in another way |
192 |
essential_workitems=essential_workitems, |
249.2.95
by James Westby
Put progress bars for each priority on the overview page. |
193 |
high_workitems=high_workitems, |
194 |
medium_workitems=medium_workitems, |
|
195 |
low_workitems=low_workitems, |
|
196 |
unknown_workitems=unknown_workitems, |
|
197 |
)
|
|
254.1.2
by James Westby
Use a template to generate the report. |
198 |
|
240.11.6
by James Westby
Comments from review. Thanks Michael. |
199 |
def get_assignee_completion(db, team=None, group=None, milestone_collection=None): |
200 |
data = report_tools.assignee_completion(db, team=team, group=group, milestone_collection=milestone_collection) |
|
52
by Martin Pitt
html-report: First version with "status by specification" |
201 |
|
54
by Martin Pitt
html-report: Add workitem details |
202 |
def _sort(a, b): |
203 |
# Takes [blueprint, workitem, priority, url] in a and b.
|
|
204 |
# first by priority
|
|
52
by Martin Pitt
html-report: First version with "status by specification" |
205 |
relative = cmp(report_tools.priority_value(b[2]), report_tools.priority_value(a[2])) |
206 |
if relative != 0: |
|
207 |
return relative |
|
54
by Martin Pitt
html-report: Add workitem details |
208 |
# then by blueprint
|
52
by Martin Pitt
html-report: First version with "status by specification" |
209 |
return cmp(a[0], b[0]) |
210 |
||
254.1.2
by James Westby
Use a template to generate the report. |
211 |
assignees = [] |
212 |
for name, info in data.items(): |
|
213 |
name = name or team or 'nobody' |
|
214 |
name_html = escape_url(name or team or 'nobody') |
|
240.11.6
by James Westby
Comments from review. Thanks Michael. |
215 |
if not milestone_collection or not milestone_collection.single_milestone or isinstance(team, report_tools.user_string): |
254.1.2
by James Westby
Use a template to generate the report. |
216 |
url = '%s/~%s/+specs?role=assignee' % (report_tools.blueprints_base_url, name_html) |
217 |
else: |
|
240.11.6
by James Westby
Comments from review. Thanks Michael. |
218 |
target = name_html + '-' + milestone_collection.name |
254.1.2
by James Westby
Use a template to generate the report. |
219 |
url = 'u/%s.html' % target |
220 |
assignees.append( |
|
221 |
Assignee(name, url, complexity=info['complexity'], |
|
222 |
todo_wis=sorted(info['todo'], cmp=_sort), |
|
223 |
blocked_wis=info['blocked'], |
|
224 |
inprogress_wis=info['inprogress'], |
|
225 |
postponed_wis=info['postponed'], |
|
226 |
done_wis=info['done'])) |
|
227 |
return assignees |
|
228 |
||
229 |
||
240.11.6
by James Westby
Comments from review. Thanks Michael. |
230 |
def by_assignee(db, team=None, group=None, milestone_collection=None): |
240.11.4
by James Westby
Rework to use polymorpism for milestone due dates. |
231 |
assignees = get_assignee_completion( |
240.11.6
by James Westby
Comments from review. Thanks Michael. |
232 |
db, team=team, group=group, milestone_collection=milestone_collection) |
254.1.2
by James Westby
Use a template to generate the report. |
233 |
assignees.sort(key=lambda a: a.name, reverse=False) |
234 |
||
235 |
# avoid showing empty columns
|
|
236 |
assignees_have_complexity = any([a.complexity > 0 for a in assignees]) |
|
237 |
||
238 |
# avoid showing empty columns
|
|
239 |
workitems_have_priority = False |
|
240 |
for assignee in assignees: |
|
54
by Martin Pitt
html-report: Add workitem details |
241 |
for status in report_tools.valid_states: |
254.1.2
by James Westby
Use a template to generate the report. |
242 |
for wi in getattr(assignee, status+"_wis", []): |
243 |
if wi[2]: |
|
244 |
workitems_have_priority = True |
|
245 |
break
|
|
246 |
if workitems_have_priority: |
|
247 |
break
|
|
248 |
||
249 |
return dict( |
|
250 |
assignees_have_complexity=assignees_have_complexity, |
|
251 |
assignees=assignees, |
|
252 |
workitems_have_priority=workitems_have_priority, |
|
253 |
valid_states=report_tools.valid_states) |
|
254 |
||
52
by Martin Pitt
html-report: First version with "status by specification" |
255 |
|
249.2.14
by James Westby
Add member and milestone links to overview. |
256 |
def member_completion(db, team, milestone): |
240.1.62
by James Westby
Show all teams if there is no primary_team. |
257 |
if team is not None: |
258 |
members = report_tools.subteams(db, team) |
|
259 |
else: |
|
260 |
members = report_tools.all_teams(db) |
|
249.2.14
by James Westby
Add member and milestone links to overview. |
261 |
return dict(members=sorted(members)) |
262 |
||
263 |
||
240.1.27
by James Westby
Report on how many workitems have to be fixed. |
264 |
def blueprint_count(db, team, milestone): |
265 |
return dict(blueprint_count=report_tools.blueprint_count(db, team=team, milestone=milestone)) |
|
266 |
||
267 |
||
249.2.25
by James Westby
Add a report for each spec_group that shows progress towards it. |
268 |
def get_group(db, group_name): |
269 |
info = report_tools.spec_group_info(db, group_name) |
|
249.2.65
by James Westby
Don't error if the group has no workitems. |
270 |
if info is None: |
271 |
# Means either no group, or no workitems.
|
|
272 |
# We know that it likely means no workitems, but we should
|
|
273 |
# do two queries to allow us to represent that better
|
|
274 |
return BlueprintGroup(group_name, None, None) |
|
249.2.25
by James Westby
Add a report for each spec_group that shows progress towards it. |
275 |
area = Area(info['area']) |
276 |
group = BlueprintGroup(group_name, info['url'], area, |
|
277 |
todo=info['todo'], blocked=info['blocked'], |
|
278 |
inprogress=info['inprogress'], postponed=info['postponed'], |
|
279 |
done=info['done'], implementation=info['implementation'], |
|
280 |
priority=info['priority'], status=info['status']) |
|
281 |
for spec_name, spec_info in info['specs'].items(): |
|
282 |
spec = Blueprint(spec_name, spec_info['url'], |
|
283 |
complexity=0, |
|
284 |
todo=spec_info['todo'], |
|
285 |
blocked=spec_info['blocked'], |
|
286 |
inprogress=spec_info['inprogress'], |
|
287 |
postponed=spec_info['postponed'], |
|
288 |
done=spec_info['done'], |
|
289 |
implementation=spec_info['implementation'], |
|
290 |
priority=spec_info['priority'], |
|
291 |
status=spec_info['status']) |
|
292 |
group.specs.append(spec) |
|
293 |
return group |
|
294 |
||
295 |
||
240.11.9
by James Westby
Fix another caller of assignee completion to use milestone_collection. |
296 |
def workitems_in_status(db, status, team=None, milestone_collection=None): |
240.9.2
by James Westby
Improvements from the review. Thanks Guilherme. |
297 |
"""Get a list of the workitems in a particular status.
|
298 |
||
299 |
The list can be filtered by assigned user/team, and/or by milestone if desired.
|
|
300 |
"""
|
|
240.9.1
by James Westby
Add pages listing the workitems in each status. |
301 |
data = report_tools.assignee_completion( |
240.11.9
by James Westby
Fix another caller of assignee completion to use milestone_collection. |
302 |
db, team=team, milestone_collection=milestone_collection) |
240.9.1
by James Westby
Add pages listing the workitems in each status. |
303 |
workitems = [] |
304 |
for assignee in data: |
|
305 |
if status in data[assignee]: |
|
306 |
for wi_info in data[assignee][status]: |
|
307 |
bp_name, description, priority, url = wi_info |
|
308 |
blueprint = Blueprint(bp_name, url, priority=priority) |
|
309 |
workitems.append( |
|
310 |
Workitem(description, status, blueprint, |
|
311 |
assignee=assignee)) |
|
312 |
return workitems |
|
313 |
||
314 |
||
249.2.74
by James Westby
Add a person list like the teams list. |
315 |
def list_people(db, team=None): |
316 |
team_info = report_tools.team_information(db, team=team) |
|
317 |
if team is None: |
|
318 |
people = set() |
|
319 |
for new_people in team_info.values(): |
|
320 |
for new_person in new_people: |
|
321 |
people.add(new_person) |
|
322 |
else: |
|
323 |
people = set(team_info.get(team, [])) |
|
324 |
return sorted(list(people)) |
|
325 |
||
326 |
||
249.2.12
by James Westby
Add an overview page. |
327 |
class ReportWriter(object): |
328 |
||
249.2.89
by James Westby
Display real names where possible. |
329 |
def template_data(self, db, opts): |
249.2.45
by James Westby
ALlow specifying the root url to allow the pages to link. |
330 |
data = {} |
331 |
if opts.root: |
|
332 |
root = opts.root |
|
240.7.1
by James Westby
Ensure that --root has a trailing slash. |
333 |
if not root.endswith("/"): |
334 |
root += "/" |
|
249.2.45
by James Westby
ALlow specifying the root url to allow the pages to link. |
335 |
else: |
336 |
root = "/" |
|
337 |
data["root"] = root |
|
249.2.89
by James Westby
Display real names where possible. |
338 |
data["real_names"] = report_tools.real_names(db) |
285.1.14
by Mattias Backman
Add code for generating pages. |
339 |
data.update(report_tools.days_left(db, milestone=opts.milestone)) |
249.2.45
by James Westby
ALlow specifying the root url to allow the pages to link. |
340 |
return data |
341 |
||
240.11.6
by James Westby
Comments from review. Thanks Michael. |
342 |
def get_milestone_collection(self, db, opts): |
343 |
milestone_collection = None |
|
240.11.4
by James Westby
Rework to use polymorpism for milestone due dates. |
344 |
if opts.milestone: |
240.11.6
by James Westby
Comments from review. Thanks Michael. |
345 |
milestone_collection = report_tools.get_milestone(db, opts.milestone) |
240.11.4
by James Westby
Rework to use polymorpism for milestone due dates. |
346 |
elif opts.date: |
240.11.7
by James Westby
Ensure to create MilesstoneGroups with python dates. |
347 |
milestone_collection = report_tools.MilestoneGroup( |
348 |
report_tools.date_to_python(opts.date)) |
|
240.11.6
by James Westby
Comments from review. Thanks Michael. |
349 |
return milestone_collection |
240.11.4
by James Westby
Rework to use polymorpism for milestone due dates. |
350 |
|
249.2.25
by James Westby
Add a report for each spec_group that shows progress towards it. |
351 |
def burndown(self, db, opts): |
249.2.12
by James Westby
Add an overview page. |
352 |
'''Print work item status as HTML.'''
|
353 |
||
249.2.25
by James Westby
Add a report for each spec_group that shows progress towards it. |
354 |
if not opts.title: |
355 |
if opts.team: |
|
356 |
title = opts.team |
|
249.2.12
by James Westby
Add an overview page. |
357 |
else: |
358 |
title = 'all teams' |
|
249.2.25
by James Westby
Add a report for each spec_group that shows progress towards it. |
359 |
else: |
360 |
title = opts.title |
|
361 |
||
240.11.6
by James Westby
Comments from review. Thanks Michael. |
362 |
milestone_collection = self.get_milestone_collection(db, opts) |
363 |
if milestone_collection is not None: |
|
364 |
title += ' (%s)' % milestone_collection.name |
|
249.2.25
by James Westby
Add a report for each spec_group that shows progress towards it. |
365 |
|
249.2.89
by James Westby
Display real names where possible. |
366 |
data = self.template_data(db, opts) |
249.2.45
by James Westby
ALlow specifying the root url to allow the pages to link. |
367 |
data.update(dict(team_name=title, chart_url=opts.chart_url)) |
240.11.4
by James Westby
Rework to use polymorpism for milestone due dates. |
368 |
data.update( |
240.11.6
by James Westby
Comments from review. Thanks Michael. |
369 |
spec_group_completion(db, opts.team, milestone_collection=milestone_collection)) |
370 |
data.update(spec_completion(db, opts.team, milestone_collection=milestone_collection)) |
|
371 |
data.update(by_assignee(db, team=opts.team, milestone_collection=milestone_collection)) |
|
240.1.78
by James Westby
Merge the Ubuntu branch. |
372 |
data.update(time_stats(db, team=opts.team, milestone_collection=milestone_collection)) |
240.11.6
by James Westby
Comments from review. Thanks Michael. |
373 |
if milestone_collection is not None: |
240.6.1
by James Westby
Highlight the active "tab" at the top of the page. |
374 |
data.update(dict(page_type="milestones")) |
375 |
elif isinstance(opts.team, report_tools.user_string): |
|
376 |
data.update(dict(page_type="people")) |
|
377 |
else: |
|
378 |
data.update(dict(page_type="teams")) |
|
240.1.68
by James Westby
Correctly use the templates for the chosen theme. |
379 |
print report_tools.fill_template( |
380 |
"burndown.html", data, theme=opts.theme) |
|
249.2.12
by James Westby
Add an overview page. |
381 |
|
249.2.25
by James Westby
Add a report for each spec_group that shows progress towards it. |
382 |
def overview(self, db, opts): |
383 |
if not opts.title: |
|
384 |
if opts.team: |
|
385 |
title = opts.team |
|
249.2.12
by James Westby
Add an overview page. |
386 |
else: |
387 |
title = 'all teams' |
|
249.2.25
by James Westby
Add a report for each spec_group that shows progress towards it. |
388 |
else: |
389 |
title = opts.title |
|
249.2.12
by James Westby
Add an overview page. |
390 |
|
240.11.6
by James Westby
Comments from review. Thanks Michael. |
391 |
milestone_collection = self.get_milestone_collection(db, opts) |
240.11.4
by James Westby
Rework to use polymorpism for milestone due dates. |
392 |
|
240.11.6
by James Westby
Comments from review. Thanks Michael. |
393 |
if milestone_collection is not None: |
394 |
title += ' (%s)' % milestone_collection.name |
|
249.2.45
by James Westby
ALlow specifying the root url to allow the pages to link. |
395 |
|
249.2.89
by James Westby
Display real names where possible. |
396 |
data = self.template_data(db, opts) |
249.2.45
by James Westby
ALlow specifying the root url to allow the pages to link. |
397 |
data.update(dict(team_name=title, chart_url=opts.chart_url)) |
240.11.6
by James Westby
Comments from review. Thanks Michael. |
398 |
data.update(spec_group_completion(db, None, milestone_collection=milestone_collection)) |
399 |
data.update(spec_completion(db, opts.team, milestone_collection=milestone_collection)) |
|
240.1.27
by James Westby
Report on how many workitems have to be fixed. |
400 |
data.update(blueprint_count(db, opts.team, opts.milestone)) |
240.6.1
by James Westby
Highlight the active "tab" at the top of the page. |
401 |
data.update(dict(page_type="overview")) |
240.1.68
by James Westby
Correctly use the templates for the chosen theme. |
402 |
print report_tools.fill_template( |
403 |
"overview.html", data, theme=opts.theme) |
|
52
by Martin Pitt
html-report: First version with "status by specification" |
404 |
|
249.2.25
by James Westby
Add a report for each spec_group that shows progress towards it. |
405 |
def group_overview(self, db, opts): |
406 |
if not opts.group: |
|
407 |
raise AssertionError( |
|
408 |
"Must specify group for group_overview report.") |
|
249.2.45
by James Westby
ALlow specifying the root url to allow the pages to link. |
409 |
|
249.2.89
by James Westby
Display real names where possible. |
410 |
data = self.template_data(db, opts) |
249.2.45
by James Westby
ALlow specifying the root url to allow the pages to link. |
411 |
data.update(dict(chart_url=opts.chart_url)) |
249.2.25
by James Westby
Add a report for each spec_group that shows progress towards it. |
412 |
data["group"] = get_group(db, opts.group) |
249.2.43
by James Westby
Add assignee and workitem tables to TR reports. |
413 |
data.update(by_assignee(db, group=opts.group)) |
240.6.1
by James Westby
Highlight the active "tab" at the top of the page. |
414 |
data.update(dict(page_type="overview")) |
240.1.68
by James Westby
Correctly use the templates for the chosen theme. |
415 |
print report_tools.fill_template( |
416 |
"group_overview.html", data, theme=opts.theme) |
|
249.2.25
by James Westby
Add a report for each spec_group that shows progress towards it. |
417 |
|
249.2.67
by James Westby
Generate a team list. |
418 |
def team_list(self, db, opts): |
249.2.89
by James Westby
Display real names where possible. |
419 |
data = self.template_data(db, opts) |
249.2.67
by James Westby
Generate a team list. |
420 |
data.update(member_completion(db, opts.team, opts.milestone)) |
240.6.1
by James Westby
Highlight the active "tab" at the top of the page. |
421 |
data.update(dict(page_type="teams")) |
240.1.68
by James Westby
Correctly use the templates for the chosen theme. |
422 |
print report_tools.fill_template( |
423 |
"team_list.html", data, theme=opts.theme) |
|
249.2.67
by James Westby
Generate a team list. |
424 |
|
249.2.74
by James Westby
Add a person list like the teams list. |
425 |
def person_list(self, db, opts): |
249.2.89
by James Westby
Display real names where possible. |
426 |
data = self.template_data(db, opts) |
249.2.74
by James Westby
Add a person list like the teams list. |
427 |
data.update(people=list_people(db, team=opts.team)) |
240.6.1
by James Westby
Highlight the active "tab" at the top of the page. |
428 |
data.update(dict(page_type="people")) |
240.1.68
by James Westby
Correctly use the templates for the chosen theme. |
429 |
print report_tools.fill_template( |
430 |
"person_list.html", data, theme=opts.theme) |
|
249.2.74
by James Westby
Add a person list like the teams list. |
431 |
|
249.2.68
by James Westby
Add a milestones page. |
432 |
def milestone_list(self, db, opts): |
249.2.89
by James Westby
Display real names where possible. |
433 |
data = self.template_data(db, opts) |
240.11.1
by James Westby
Improve the milestone page by showing the milestones on a timeline. |
434 |
data.update( |
435 |
milestone_groups=report_tools.milestone_groups( |
|
436 |
db, team=opts.team)) |
|
240.6.1
by James Westby
Highlight the active "tab" at the top of the page. |
437 |
data.update(dict(page_type="milestones")) |
240.1.68
by James Westby
Correctly use the templates for the chosen theme. |
438 |
print report_tools.fill_template( |
439 |
"milestone_list.html", data, theme=opts.theme) |
|
249.2.68
by James Westby
Add a milestones page. |
440 |
|
249.2.69
by James Westby
Add about page. |
441 |
def about(self, db, opts): |
249.2.89
by James Westby
Display real names where possible. |
442 |
data = self.template_data(db, opts) |
240.6.1
by James Westby
Highlight the active "tab" at the top of the page. |
443 |
data.update(dict(page_type="about")) |
240.1.68
by James Westby
Correctly use the templates for the chosen theme. |
444 |
print report_tools.fill_template( |
445 |
"about.html", data, theme=opts.theme) |
|
249.2.69
by James Westby
Add about page. |
446 |
|
240.9.1
by James Westby
Add pages listing the workitems in each status. |
447 |
def workitem_list(self, db, opts): |
240.9.2
by James Westby
Improvements from the review. Thanks Guilherme. |
448 |
"""Generate a page listing workitems in a particular status."""
|
240.9.1
by James Westby
Add pages listing the workitems in each status. |
449 |
assert opts.status is not None, ( |
450 |
"Must pass --status for workitem_list report-type") |
|
240.11.9
by James Westby
Fix another caller of assignee completion to use milestone_collection. |
451 |
milestone_collection = self.get_milestone_collection(db, opts) |
240.9.1
by James Westby
Add pages listing the workitems in each status. |
452 |
workitems = workitems_in_status( |
240.11.9
by James Westby
Fix another caller of assignee completion to use milestone_collection. |
453 |
db, opts.status, team=opts.team, |
454 |
milestone_collection=milestone_collection) |
|
240.9.1
by James Westby
Add pages listing the workitems in each status. |
455 |
data = self.template_data(db, opts) |
456 |
data.update(dict(status=opts.status)) |
|
457 |
data.update(dict(workitems=workitems)) |
|
458 |
data.update(dict(page_type="overview")) |
|
240.1.68
by James Westby
Correctly use the templates for the chosen theme. |
459 |
print report_tools.fill_template( |
460 |
"workitem_list.html", data, theme=opts.theme) |
|
240.9.1
by James Westby
Add pages listing the workitems in each status. |
461 |
|
285.1.14
by Mattias Backman
Add code for generating pages. |
462 |
def roadmap_page(self, store, opts): |
463 |
if opts.lane is None: |
|
464 |
print "<h1>Error, no lane specified.</h1>" |
|
465 |
if not opts.title: |
|
466 |
title = opts.lane |
|
467 |
else: |
|
468 |
title = opts.title |
|
469 |
||
470 |
data = self.template_data(store, opts) |
|
306.1.9
by Mattias Backman
Add health checks. |
471 |
lane = report_tools.lane(store, opts.lane) |
301.2.5
by Mattias Backman
Put a lane selector on top of the Lane views. |
472 |
lanes = report_tools.lanes(store) |
285.1.14
by Mattias Backman
Add code for generating pages. |
473 |
statuses = [] |
326.1.1
by Mattias Backman
Add a blueprint progress bar to the roadmap lane views. |
474 |
bp_status_totals = {'Completed': 0, 'Total': 0, 'Percentage': 0} |
285.1.14
by Mattias Backman
Add code for generating pages. |
475 |
for status, cards in report_tools.statuses(store, lane): |
306.1.1
by Mattias Backman
Various fixes at Connect. |
476 |
cards_with_bps = [] |
303.2.2
by Mattias Backman
Display bp count on lane cards. |
477 |
for card in cards: |
306.1.15
by Mattias Backman
Move health checks to a separate module and record them with a decorator. |
478 |
report_tools.check_card_health(store, card_health_checks, card) |
314.1.3
by Mattias Backman
Clean up by moving status summing to report_tools. |
479 |
blueprint_status_counts = report_tools.card_bp_status_counts( |
480 |
store, card.roadmap_id) |
|
481 |
total = sum(blueprint_status_counts.values()) |
|
326.1.1
by Mattias Backman
Add a blueprint progress bar to the roadmap lane views. |
482 |
bp_percentages = dict.fromkeys(ROADMAP_ORDERED_STATUSES, 0) |
483 |
bp_status_totals['Completed'] += \ |
|
484 |
blueprint_status_counts['Completed'] |
|
314.1.3
by Mattias Backman
Clean up by moving status summing to report_tools. |
485 |
for key in ROADMAP_STATUSES_MAP: |
326.1.1
by Mattias Backman
Add a blueprint progress bar to the roadmap lane views. |
486 |
bp_status_totals['Total'] += blueprint_status_counts[key] |
314.1.3
by Mattias Backman
Clean up by moving status summing to report_tools. |
487 |
if total > 0: |
314.1.4
by Mattias Backman
Fix trunking of percentages. |
488 |
bp_percentages[key] = ( |
489 |
100.0 * blueprint_status_counts[key] / total) |
|
314.1.3
by Mattias Backman
Clean up by moving status summing to report_tools. |
490 |
|
491 |
cards_with_bps.append({'card': card, |
|
492 |
'bp_statuses': blueprint_status_counts, |
|
493 |
'bp_percentages': bp_percentages}) |
|
306.1.1
by Mattias Backman
Various fixes at Connect. |
494 |
statuses.append(dict(name=status, cards=cards_with_bps)) |
326.1.1
by Mattias Backman
Add a blueprint progress bar to the roadmap lane views. |
495 |
if bp_status_totals['Total'] > 0: |
496 |
bp_status_totals['Percentage'] = (100 * bp_status_totals['Completed'] / |
|
497 |
bp_status_totals['Total']) |
|
314.1.3
by Mattias Backman
Clean up by moving status summing to report_tools. |
498 |
|
285.1.14
by Mattias Backman
Add code for generating pages. |
499 |
data.update(dict(statuses=statuses)) |
326.1.1
by Mattias Backman
Add a blueprint progress bar to the roadmap lane views. |
500 |
data.update(dict(bp_status_totals=bp_status_totals)) |
319.1.4
by Mattias Backman
Sort blueprints in card view; first by status in reversed order, then by milestone. |
501 |
data.update(dict(status_order=ROADMAP_ORDERED_STATUSES)) |
285.1.14
by Mattias Backman
Add code for generating pages. |
502 |
data.update(dict(page_type="roadmap_lane")) |
301.2.5
by Mattias Backman
Put a lane selector on top of the Lane views. |
503 |
data.update(dict(lane_title=title)) |
504 |
data.update(dict(lanes=lanes)) |
|
309.7.5
by Mattias Backman
Add blueprint progress chart to bottom of lane report. |
505 |
data.update(dict(chart_url=opts.chart_url)) |
285.1.14
by Mattias Backman
Add code for generating pages. |
506 |
print report_tools.fill_template( |
507 |
"roadmap_lane.html", data, theme=opts.theme) |
|
508 |
||
509 |
||
299.1.2
by Mattias Backman
Generate card pages with list of blueprints. |
510 |
def roadmap_card(self, store, opts): |
511 |
if opts.card is None: |
|
512 |
print "<h1>Error, no card specified.</h1>" |
|
513 |
||
514 |
data = self.template_data(store, opts) |
|
515 |
card = report_tools.card(store, int(opts.card)).one() |
|
306.1.15
by Mattias Backman
Move health checks to a separate module and record them with a decorator. |
516 |
health_checks = report_tools.check_card_health(store, card_health_checks, card) |
306.1.9
by Mattias Backman
Add health checks. |
517 |
lane = report_tools.lane(store, None, id=card.lane_id) |
299.1.2
by Mattias Backman
Generate card pages with list of blueprints. |
518 |
|
519 |
if not opts.title: |
|
520 |
title = card.name |
|
521 |
else: |
|
522 |
title = opts.title |
|
523 |
||
303.2.1
by Mattias Backman
Display blueprints per high level status. |
524 |
blueprints = report_tools.card_blueprints_by_status(store, card.roadmap_id) |
326.1.2
by Mattias Backman
Use the new progress bar on the card view too. |
525 |
bp_status_totals = {'Completed': 0, 'Total': 0, 'Percentage': 0} |
526 |
bp_status_totals['Total'] = (len(blueprints['Planned']) + |
|
527 |
len(blueprints['Blocked']) + |
|
528 |
len(blueprints['In Progress']) + |
|
529 |
len(blueprints['Completed'])) |
|
530 |
bp_status_totals['Completed'] = len(blueprints['Completed']) |
|
531 |
if bp_status_totals['Total'] > 0: |
|
532 |
bp_status_totals['Percentage'] = (100 * bp_status_totals['Completed'] / |
|
533 |
bp_status_totals['Total']) |
|
534 |
||
535 |
card_has_blueprints = bp_status_totals['Total'] > 0 |
|
299.1.2
by Mattias Backman
Generate card pages with list of blueprints. |
536 |
|
319.1.4
by Mattias Backman
Sort blueprints in card view; first by status in reversed order, then by milestone. |
537 |
status_order = ROADMAP_ORDERED_STATUSES[:] |
538 |
status_order.reverse() |
|
539 |
||
299.1.2
by Mattias Backman
Generate card pages with list of blueprints. |
540 |
data.update(dict(page_type="roadmap_card")) |
301.1.2
by Mattias Backman
Use new fields in the card view. |
541 |
data.update(dict(card_title=title)) |
542 |
data.update(dict(card=card)) |
|
306.1.9
by Mattias Backman
Add health checks. |
543 |
data.update(dict(health_checks=health_checks)) |
299.1.2
by Mattias Backman
Generate card pages with list of blueprints. |
544 |
data.update(dict(lane=lane.name)) |
319.1.4
by Mattias Backman
Sort blueprints in card view; first by status in reversed order, then by milestone. |
545 |
data.update(dict(status_order=status_order)) |
299.1.2
by Mattias Backman
Generate card pages with list of blueprints. |
546 |
data.update(dict(blueprints=blueprints)) |
326.1.2
by Mattias Backman
Use the new progress bar on the card view too. |
547 |
data.update(dict(bp_status_totals=bp_status_totals)) |
306.2.2
by Mattias Backman
Determine if card has blueprints outside of html template. |
548 |
data.update(dict(card_has_blueprints=card_has_blueprints)) |
299.1.2
by Mattias Backman
Generate card pages with list of blueprints. |
549 |
|
550 |
print report_tools.fill_template( |
|
551 |
"roadmap_card.html", data, theme=opts.theme) |
|
552 |
||
553 |
||
263.1.4
by James Westby
Merge templates branch. |
554 |
class WorkitemsOnDate(object): |
555 |
||
556 |
def __init__(self, date, done, delta_done, todo, delta_todo): |
|
557 |
self.date = date |
|
558 |
self.done = done |
|
559 |
self.delta_done = delta_done |
|
560 |
self.todo = todo |
|
561 |
self.delta_todo = delta_todo |
|
562 |
||
52
by Martin Pitt
html-report: First version with "status by specification" |
563 |
|
240.1.82
by James Westby
Fix the function signature. |
564 |
def time_stats(db, team=None, milestone_collection=None): |
240.1.78
by James Westby
Merge the Ubuntu branch. |
565 |
data = report_tools.workitems_over_time( |
240.1.81
by James Westby
Fix the calling convention |
566 |
db, team=team, milestone_collection=milestone_collection) |
258.1.2
by Martin Pitt
html-report: Add textual stats over time |
567 |
|
568 |
prev_done = None |
|
569 |
prev_todo = None |
|
263.1.4
by James Westby
Merge templates branch. |
570 |
|
571 |
workitems_by_day = [] |
|
260
by Martin Pitt
html-report: sort dates |
572 |
for (date, states) in sorted(data.iteritems(), key=lambda k: k[0]): |
262
by Martin Pitt
time-stats: not all states exist always |
573 |
todo = states.get('todo', 0) + states.get('inprogress', 0) |
574 |
done = states.get('done', 0) + states.get('postponed', 0) |
|
258.1.2
by Martin Pitt
html-report: Add textual stats over time |
575 |
if prev_done: |
576 |
delta_done = done - prev_done |
|
577 |
if delta_done < 0: |
|
578 |
delta_done = ' (' + str(delta_done) + ')' |
|
579 |
else: |
|
580 |
delta_done = ' (+' + str(delta_done) + ')' |
|
581 |
else: |
|
582 |
delta_done = '' |
|
583 |
if prev_todo: |
|
261
by Martin Pitt
fix time-stats todo column |
584 |
delta_todo = todo - prev_todo |
258.1.2
by Martin Pitt
html-report: Add textual stats over time |
585 |
if delta_todo < 0: |
586 |
delta_todo = ' (' + str(delta_todo) + ')' |
|
587 |
else: |
|
588 |
delta_todo = ' (+' + str(delta_todo) + ')' |
|
589 |
else: |
|
590 |
delta_todo = '' |
|
591 |
prev_todo = todo |
|
592 |
prev_done = done |
|
263.1.4
by James Westby
Merge templates branch. |
593 |
workitems_by_day.append(WorkitemsOnDate(date, done, delta_done, |
594 |
todo, delta_todo)) |
|
595 |
return dict(workitems_by_day=workitems_by_day) |
|
52
by Martin Pitt
html-report: First version with "status by specification" |
596 |
|
240.1.78
by James Westby
Merge the Ubuntu branch. |
597 |
|
52
by Martin Pitt
html-report: First version with "status by specification" |
598 |
#
|
599 |
# main
|
|
600 |
#
|
|
601 |
||
602 |
if __name__ == '__main__': |
|
603 |
report_tools.fix_stdouterr() |
|
604 |
||
605 |
# argv parsing
|
|
606 |
optparser = optparse.OptionParser() |
|
607 |
optparser.add_option('-d', '--database', |
|
608 |
help='Path to database', dest='database', metavar='PATH') |
|
609 |
optparser.add_option('-t', '--team', |
|
610 |
help='Restrict report to a particular team', dest='team') |
|
611 |
optparser.add_option('-m', '--milestone', |
|
612 |
help='Restrict report to a particular milestone', dest='milestone') |
|
108
by Martin Pitt
html-report: Fix default chart name |
613 |
optparser.add_option('--chart', default='burndown.svg', |
59
by Martin Pitt
html-report: add --chart option |
614 |
help='(Relative) URL to burndown chart', dest='chart_url') |
205.1.8
by Clint Byrum
only show user direct assigned items on personal page |
615 |
optparser.add_option('-u', '--user', |
616 |
help='Run for this user', dest='user') |
|
245
by Martin Pitt
html-report: add --title option |
617 |
optparser.add_option('--title', |
618 |
help='Set custom title') |
|
249.2.12
by James Westby
Add an overview page. |
619 |
optparser.add_option('--report-type', default="burndown", |
620 |
dest="report_type") |
|
249.2.25
by James Westby
Add a report for each spec_group that shows progress towards it. |
621 |
optparser.add_option('--group', dest="group", |
622 |
help="Select the group for a group_overview report.") |
|
249.2.45
by James Westby
ALlow specifying the root url to allow the pages to link. |
623 |
optparser.add_option('--root', dest="root", |
624 |
help="Root URL for the charts") |
|
240.9.1
by James Westby
Add pages listing the workitems in each status. |
625 |
optparser.add_option('--status', dest="status", |
626 |
help="Workitem status to consider for workitem_list report-type") |
|
240.11.2
by James Westby
Add a set of pages for date-based milestone views. |
627 |
optparser.add_option('--date', dest="date", |
628 |
help="Include all milestones targetted to this date.") |
|
240.1.65
by James Westby
Add theme support. |
629 |
optparser.add_option('--theme', dest="theme", |
630 |
help="The theme to use.", default="linaro") |
|
285.1.14
by Mattias Backman
Add code for generating pages. |
631 |
optparser.add_option('--lane', |
632 |
help='Roadmap lane', dest='lane') |
|
299.1.2
by Mattias Backman
Generate card pages with list of blueprints. |
633 |
optparser.add_option('--card', |
634 |
help='Roadmap card', dest='card') |
|
52
by Martin Pitt
html-report: First version with "status by specification" |
635 |
|
636 |
(opts, args) = optparser.parse_args() |
|
637 |
if not opts.database: |
|
638 |
optparser.error('No database given') |
|
205.1.8
by Clint Byrum
only show user direct assigned items on personal page |
639 |
if opts.user and opts.team: |
640 |
optparser.error('team and user options are mutually exclusive') |
|
240.11.2
by James Westby
Add a set of pages for date-based milestone views. |
641 |
if opts.milestone and opts.date: |
642 |
optparser.error('milestone and date options are mutually exclusive') |
|
205.1.8
by Clint Byrum
only show user direct assigned items on personal page |
643 |
|
644 |
# The typing allows polymorphic behavior
|
|
645 |
if opts.user: |
|
646 |
opts.team = report_tools.user_string(opts.user) |
|
647 |
elif opts.team: |
|
648 |
opts.team = report_tools.team_string(opts.team) |
|
52
by Martin Pitt
html-report: First version with "status by specification" |
649 |
|
281.2.1
by Mattias Backman
Change all SQL queries to go through the Storm API instead of SQLite directly. |
650 |
store = report_tools.get_store(opts.database) |
52
by Martin Pitt
html-report: First version with "status by specification" |
651 |
|
249.2.12
by James Westby
Add an overview page. |
652 |
writer = ReportWriter() |
653 |
method = getattr(writer, opts.report_type, None) |
|
654 |
if method is None: |
|
655 |
optparser.error('Unknown report type: %s' % opts.report_type) |
|
281.2.1
by Mattias Backman
Change all SQL queries to go through the Storm API instead of SQLite directly. |
656 |
method(store, opts) |