Merge lp:~salgado/launchpad-work-items-tracker/blueprints-over-time into lp:~linaro-automation/launchpad-work-items-tracker/linaro
- blueprints-over-time
- Merge into linaro
Proposed by
Guilherme Salgado
Status: | Merged |
---|---|
Merged at revision: | 316 |
Proposed branch: | lp:~salgado/launchpad-work-items-tracker/blueprints-over-time |
Merge into: | lp:~linaro-automation/launchpad-work-items-tracker/linaro |
Diff against target: |
793 lines (+478/-33) 15 files modified
collect (+2/-0) collect_roadmap (+9/-0) html-report (+1/-0) lpworkitems/collect_roadmap.py (+26/-0) lpworkitems/database.py (+17/-3) lpworkitems/factory.py (+32/-1) lpworkitems/models.py (+26/-5) lpworkitems/models_roadmap.py (+15/-0) lpworkitems/tests/test_collect.py (+0/-1) lpworkitems/tests/test_collect_roadmap.py (+23/-5) lpworkitems/tests/test_factory.py (+1/-1) lpworkitems/tests/test_models.py (+17/-2) report_tools.py (+46/-15) roadmap-bp-chart (+254/-0) templates/roadmap_lane.html (+9/-0) |
To merge this branch: | bzr merge lp:~salgado/launchpad-work-items-tracker/blueprints-over-time |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Mattias Backman (community) | Approve | ||
Review via email: mp+85921@code.launchpad.net |
Commit message
Description of the change
This branch adds a new table which stores the number of blueprints per (day, status, lane). This is then used on the roadmap page to show a graph of blueprints per status/day for the quarter.
To post a comment you must log in.
Revision history for this message
Mattias Backman (mabac) wrote : | # |
- 326. By Guilherme Salgado
-
merge linaro branch
- 327. By Guilherme Salgado
-
Improve a docstring
- 328. By Guilherme Salgado
-
Rework the tests and fix blueprints_
over_time( ) - 329. By Guilherme Salgado
-
Fix blueprints_
over_time - 330. By Guilherme Salgado
-
Remove some commented out code
- 331. By Guilherme Salgado
-
Re-add a test assertion I accidentally removed
- 332. By Guilherme Salgado
-
Remove an unnecessary import
Revision history for this message
Mattias Backman (mabac) wrote : | # |
No more staging artifacts that I can find.
review:
Approve
- 333. By Guilherme Salgado
-
Create a new function in collect_roadmap which takes care of deleting and recreating the blueprint status per day
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'collect' | |||
2 | --- collect 2011-12-07 09:03:27 +0000 | |||
3 | +++ collect 2011-12-15 18:49:25 +0000 | |||
4 | @@ -755,6 +755,8 @@ | |||
5 | 755 | 755 | ||
6 | 756 | # reset status for current day | 756 | # reset status for current day |
7 | 757 | collector.clear_todays_workitems() | 757 | collector.clear_todays_workitems() |
8 | 758 | # We can delete all blueprints while keeping work items for previous days | ||
9 | 759 | # because there's no foreign key reference from WorkItem to Blueprint. | ||
10 | 758 | collector.clear_blueprints() | 760 | collector.clear_blueprints() |
11 | 759 | collector.clear_metas() | 761 | collector.clear_metas() |
12 | 760 | collector.clear_complexitys() | 762 | collector.clear_complexitys() |
13 | 761 | 763 | ||
14 | === modified file 'collect_roadmap' | |||
15 | --- collect_roadmap 2011-12-15 12:59:35 +0000 | |||
16 | +++ collect_roadmap 2011-12-15 18:49:25 +0000 | |||
17 | @@ -245,6 +245,13 @@ | |||
18 | 245 | logger.setLevel(logging.DEBUG) | 245 | logger.setLevel(logging.DEBUG) |
19 | 246 | 246 | ||
20 | 247 | 247 | ||
21 | 248 | def update_todays_blueprint_daily_count_per_state(collector): | ||
22 | 249 | """Clear today's entries and create them again to reflect the current | ||
23 | 250 | state of blueprints.""" | ||
24 | 251 | collector.clear_todays_blueprint_daily_count_per_state() | ||
25 | 252 | collector.store_roadmap_bp_count_per_state() | ||
26 | 253 | |||
27 | 254 | |||
28 | 248 | def main(): | 255 | def main(): |
29 | 249 | report_tools.fix_stdouterr() | 256 | report_tools.fix_stdouterr() |
30 | 250 | 257 | ||
31 | @@ -274,6 +281,8 @@ | |||
32 | 274 | 281 | ||
33 | 275 | kanban_import(collector, cfg, opts.board, opts.api_token) | 282 | kanban_import(collector, cfg, opts.board, opts.api_token) |
34 | 276 | 283 | ||
35 | 284 | update_todays_blueprint_daily_count_per_state(collector) | ||
36 | 285 | |||
37 | 277 | store.commit() | 286 | store.commit() |
38 | 278 | 287 | ||
39 | 279 | os.unlink(lock_path) | 288 | os.unlink(lock_path) |
40 | 280 | 289 | ||
41 | === modified file 'html-report' | |||
42 | --- html-report 2011-12-07 09:03:27 +0000 | |||
43 | +++ html-report 2011-12-15 18:49:25 +0000 | |||
44 | @@ -495,6 +495,7 @@ | |||
45 | 495 | data.update(dict(page_type="roadmap_lane")) | 495 | data.update(dict(page_type="roadmap_lane")) |
46 | 496 | data.update(dict(lane_title=title)) | 496 | data.update(dict(lane_title=title)) |
47 | 497 | data.update(dict(lanes=lanes)) | 497 | data.update(dict(lanes=lanes)) |
48 | 498 | data.update(dict(chart_url=opts.chart_url)) | ||
49 | 498 | print report_tools.fill_template( | 499 | print report_tools.fill_template( |
50 | 499 | "roadmap_lane.html", data, theme=opts.theme) | 500 | "roadmap_lane.html", data, theme=opts.theme) |
51 | 500 | 501 | ||
52 | 501 | 502 | ||
53 | === modified file 'lpworkitems/collect_roadmap.py' | |||
54 | --- lpworkitems/collect_roadmap.py 2011-12-13 09:24:21 +0000 | |||
55 | +++ lpworkitems/collect_roadmap.py 2011-12-15 18:49:25 +0000 | |||
56 | @@ -1,3 +1,5 @@ | |||
57 | 1 | import datetime | ||
58 | 2 | |||
59 | 1 | from lpworkitems import models_roadmap | 3 | from lpworkitems import models_roadmap |
60 | 2 | from utils import unicode_or_None | 4 | from utils import unicode_or_None |
61 | 3 | 5 | ||
62 | @@ -24,6 +26,30 @@ | |||
63 | 24 | def store_card(self, card): | 26 | def store_card(self, card): |
64 | 25 | self.store.add(card) | 27 | self.store.add(card) |
65 | 26 | 28 | ||
66 | 29 | def clear_todays_blueprint_daily_count_per_state(self): | ||
67 | 30 | self._clear_all( | ||
68 | 31 | models_roadmap.BlueprintDailyCountPerState, | ||
69 | 32 | models_roadmap.BlueprintDailyCountPerState.day == datetime.date.today()) | ||
70 | 33 | |||
71 | 34 | def store_roadmap_bp_count_per_state(self): | ||
72 | 35 | query = """ | ||
73 | 36 | SELECT implementation, lane_id, count(*) | ||
74 | 37 | FROM specs | ||
75 | 38 | JOIN meta on spec = specs.name | ||
76 | 39 | JOIN card on roadmap_id = value | ||
77 | 40 | WHERE key = 'Roadmap id' | ||
78 | 41 | GROUP BY implementation, lane_id | ||
79 | 42 | """ | ||
80 | 43 | day = datetime.date.today() | ||
81 | 44 | result = self.store.execute(query) | ||
82 | 45 | for status, lane_id, count in result: | ||
83 | 46 | obj = models_roadmap.BlueprintDailyCountPerState() | ||
84 | 47 | obj.day = day | ||
85 | 48 | obj.status = status | ||
86 | 49 | obj.lane_id = lane_id | ||
87 | 50 | obj.count = count | ||
88 | 51 | self.store.add(obj) | ||
89 | 52 | |||
90 | 27 | 53 | ||
91 | 28 | def get_json_item(data, item_name): | 54 | def get_json_item(data, item_name): |
92 | 29 | item = data[item_name] | 55 | item = data[item_name] |
93 | 30 | 56 | ||
94 | === modified file 'lpworkitems/database.py' | |||
95 | --- lpworkitems/database.py 2011-11-03 15:48:13 +0000 | |||
96 | +++ lpworkitems/database.py 2011-12-15 18:49:25 +0000 | |||
97 | @@ -7,13 +7,13 @@ | |||
98 | 7 | store.execute('''CREATE TABLE version ( | 7 | store.execute('''CREATE TABLE version ( |
99 | 8 | db_layout_ref INT NOT NULL | 8 | db_layout_ref INT NOT NULL |
100 | 9 | )''') | 9 | )''') |
102 | 10 | store.execute('''INSERT INTO version VALUES (14)''') | 10 | store.execute('''INSERT INTO version VALUES (15)''') |
103 | 11 | 11 | ||
104 | 12 | store.execute('''CREATE TABLE specs ( | 12 | store.execute('''CREATE TABLE specs ( |
105 | 13 | name VARCHAR(255) PRIMARY KEY, | 13 | name VARCHAR(255) PRIMARY KEY, |
106 | 14 | url VARCHAR(1000) NOT NULL, | 14 | url VARCHAR(1000) NOT NULL, |
107 | 15 | priority CHAR(20), | 15 | priority CHAR(20), |
109 | 16 | implementation CHAR(30), | 16 | implementation CHAR(30) NOT NULL, |
110 | 17 | assignee CHAR(50), | 17 | assignee CHAR(50), |
111 | 18 | team CHAR(50), | 18 | team CHAR(50), |
112 | 19 | status VARCHAR(5000) NOT NULL, | 19 | status VARCHAR(5000) NOT NULL, |
113 | @@ -25,6 +25,13 @@ | |||
114 | 25 | roadmap_notes VARCHAR(5000) | 25 | roadmap_notes VARCHAR(5000) |
115 | 26 | )''') | 26 | )''') |
116 | 27 | 27 | ||
117 | 28 | store.execute('''CREATE TABLE spec_daily_count_per_state ( | ||
118 | 29 | status VARCHAR(5000) NOT NULL, | ||
119 | 30 | day DATE NOT NULL, | ||
120 | 31 | lane_id REFERENCES lane(lane_id), | ||
121 | 32 | count INT NOT NULL | ||
122 | 33 | )''') | ||
123 | 34 | |||
124 | 28 | store.execute('''CREATE TABLE work_items ( | 35 | store.execute('''CREATE TABLE work_items ( |
125 | 29 | description VARCHAR(1000) NOT NULL, | 36 | description VARCHAR(1000) NOT NULL, |
126 | 30 | spec VARCHAR(255) REFERENCES specs(name), | 37 | spec VARCHAR(255) REFERENCES specs(name), |
127 | @@ -234,7 +241,14 @@ | |||
128 | 234 | store.execute('ALTER TABLE card ADD COLUMN url VARCHAR(200)') | 241 | store.execute('ALTER TABLE card ADD COLUMN url VARCHAR(200)') |
129 | 235 | store.execute('ALTER TABLE card ADD COLUMN is_healthy BOOLEAN') | 242 | store.execute('ALTER TABLE card ADD COLUMN is_healthy BOOLEAN') |
130 | 236 | store.execute('UPDATE version SET db_layout_ref = 14') | 243 | store.execute('UPDATE version SET db_layout_ref = 14') |
132 | 237 | 244 | if ver == 14: | |
133 | 245 | store.execute('''CREATE TABLE spec_daily_count_per_state ( | ||
134 | 246 | status VARCHAR(5000) NOT NULL, | ||
135 | 247 | day DATE NOT NULL, | ||
136 | 248 | lane_id REFERENCES lane(lane_id), | ||
137 | 249 | count INT NOT NULL | ||
138 | 250 | )''') | ||
139 | 251 | store.execute('UPDATE version SET db_layout_ref = 15') | ||
140 | 238 | 252 | ||
141 | 239 | def get_store(dbpath): | 253 | def get_store(dbpath): |
142 | 240 | '''Open/initialize database. | 254 | '''Open/initialize database. |
143 | 241 | 255 | ||
144 | === modified file 'lpworkitems/factory.py' | |||
145 | --- lpworkitems/factory.py 2011-06-14 22:00:21 +0000 | |||
146 | +++ lpworkitems/factory.py 2011-12-15 18:49:25 +0000 | |||
147 | @@ -11,6 +11,7 @@ | |||
148 | 11 | TeamStructure, | 11 | TeamStructure, |
149 | 12 | Workitem, | 12 | Workitem, |
150 | 13 | ) | 13 | ) |
151 | 14 | from lpworkitems.models_roadmap import BlueprintDailyCountPerState, Card | ||
152 | 14 | 15 | ||
153 | 15 | 16 | ||
154 | 16 | class Factory(object): | 17 | class Factory(object): |
155 | @@ -63,6 +64,8 @@ | |||
156 | 63 | url = self.getUniqueUnicode(prefix=name+"_url") | 64 | url = self.getUniqueUnicode(prefix=name+"_url") |
157 | 64 | if status is None: | 65 | if status is None: |
158 | 65 | status = self.getUniqueUnicode(prefix=name+"_status") | 66 | status = self.getUniqueUnicode(prefix=name+"_status") |
159 | 67 | if implementation is None: | ||
160 | 68 | implementation = u'Unknown' | ||
161 | 66 | blueprint.name = name | 69 | blueprint.name = name |
162 | 67 | blueprint.url = url | 70 | blueprint.url = url |
163 | 68 | blueprint.status = status | 71 | blueprint.status = status |
164 | @@ -109,8 +112,11 @@ | |||
165 | 109 | self.store.add(workitem) | 112 | self.store.add(workitem) |
166 | 110 | return workitem | 113 | return workitem |
167 | 111 | 114 | ||
169 | 112 | def make_meta(self, store=True): | 115 | def make_meta(self, key=None, value=None, blueprint=None, store=True): |
170 | 113 | meta = Meta() | 116 | meta = Meta() |
171 | 117 | meta.key = key | ||
172 | 118 | meta.value = value | ||
173 | 119 | meta.blueprint = blueprint | ||
174 | 114 | if store: | 120 | if store: |
175 | 115 | self.store.add(meta) | 121 | self.store.add(meta) |
176 | 116 | return meta | 122 | return meta |
177 | @@ -155,3 +161,28 @@ | |||
178 | 155 | if store: | 161 | if store: |
179 | 156 | self.store.add(person) | 162 | self.store.add(person) |
180 | 157 | return person | 163 | return person |
181 | 164 | |||
182 | 165 | def make_blueprint_daily_count_per_state(self, status=None, count=1, | ||
183 | 166 | day=None, store=True): | ||
184 | 167 | if status is None: | ||
185 | 168 | status = self.getUniqueUnicode() | ||
186 | 169 | if day is None: | ||
187 | 170 | day = datetime.date.today() | ||
188 | 171 | obj = BlueprintDailyCountPerState() | ||
189 | 172 | obj.day = day | ||
190 | 173 | obj.status = status | ||
191 | 174 | obj.count = count | ||
192 | 175 | obj.lane_id = 1 | ||
193 | 176 | if store: | ||
194 | 177 | self.store.add(obj) | ||
195 | 178 | return obj | ||
196 | 179 | |||
197 | 180 | def make_card(self, store=True): | ||
198 | 181 | name = self.getUniqueUnicode() | ||
199 | 182 | card_id = self.getUniqueInteger() | ||
200 | 183 | lane_id = self.getUniqueInteger() | ||
201 | 184 | roadmap_id = self.getUniqueUnicode() | ||
202 | 185 | card = Card(name, card_id, lane_id, roadmap_id) | ||
203 | 186 | if store: | ||
204 | 187 | self.store.add(card) | ||
205 | 188 | return card | ||
206 | 158 | 189 | ||
207 | === modified file 'lpworkitems/models.py' | |||
208 | --- lpworkitems/models.py 2011-12-07 09:03:27 +0000 | |||
209 | +++ lpworkitems/models.py 2011-12-15 18:49:25 +0000 | |||
210 | @@ -2,7 +2,15 @@ | |||
211 | 2 | import re | 2 | import re |
212 | 3 | from utils import unicode_or_None | 3 | from utils import unicode_or_None |
213 | 4 | 4 | ||
215 | 5 | from storm.locals import Date, Reference, ReferenceSet, Unicode | 5 | from storm.locals import Date, Int, Reference, ReferenceSet, Unicode |
216 | 6 | |||
217 | 7 | ROADMAP_STATUSES_MAP = { | ||
218 | 8 | u'Completed': [u'Implemented'], | ||
219 | 9 | u'Blocked': [u'Needs Infrastructure', u'Blocked', u'Deferred'], | ||
220 | 10 | u'In Progress': [u'Deployment', u'Needs Code Review', | ||
221 | 11 | u'Beta Available', u'Good progress', | ||
222 | 12 | u'Slow progress', u'Started'], | ||
223 | 13 | u'Planned': [u'Unknown', u'Not started', u'Informational']} | ||
224 | 6 | 14 | ||
225 | 7 | 15 | ||
226 | 8 | def fill_blueprint_info_from_launchpad(model_bp, lp_bp): | 16 | def fill_blueprint_info_from_launchpad(model_bp, lp_bp): |
227 | @@ -48,6 +56,14 @@ | |||
228 | 48 | project = Unicode() | 56 | project = Unicode() |
229 | 49 | 57 | ||
230 | 50 | 58 | ||
231 | 59 | def get_roadmap_status_for_bp_implementation_status(implementation): | ||
232 | 60 | for key in ROADMAP_STATUSES_MAP: | ||
233 | 61 | if implementation in ROADMAP_STATUSES_MAP[key]: | ||
234 | 62 | return key | ||
235 | 63 | # XXX: Is None the appropriate return value here? | ||
236 | 64 | return None | ||
237 | 65 | |||
238 | 66 | |||
239 | 51 | class Blueprint(object): | 67 | class Blueprint(object): |
240 | 52 | 68 | ||
241 | 53 | __storm_table__ = "specs" | 69 | __storm_table__ = "specs" |
242 | @@ -81,6 +97,15 @@ | |||
243 | 81 | lp_bp.whiteboard, "Roadmap\s+Notes") | 97 | lp_bp.whiteboard, "Roadmap\s+Notes") |
244 | 82 | return model_bp | 98 | return model_bp |
245 | 83 | 99 | ||
246 | 100 | @property | ||
247 | 101 | def roadmap_status(self): | ||
248 | 102 | return get_roadmap_status_for_bp_implementation_status( | ||
249 | 103 | self.implementation) | ||
250 | 104 | |||
251 | 105 | |||
252 | 106 | def current_date(): | ||
253 | 107 | return datetime.date.today() | ||
254 | 108 | |||
255 | 84 | 109 | ||
256 | 85 | class Person(object): | 110 | class Person(object): |
257 | 86 | 111 | ||
258 | @@ -108,10 +133,6 @@ | |||
259 | 108 | superteam_name = Unicode(name="team") | 133 | superteam_name = Unicode(name="team") |
260 | 109 | 134 | ||
261 | 110 | 135 | ||
262 | 111 | def current_date(): | ||
263 | 112 | return datetime.date.today() | ||
264 | 113 | |||
265 | 114 | |||
266 | 115 | class Meta(object): | 136 | class Meta(object): |
267 | 116 | 137 | ||
268 | 117 | __storm_table__ = "meta" | 138 | __storm_table__ = "meta" |
269 | 118 | 139 | ||
270 | === modified file 'lpworkitems/models_roadmap.py' | |||
271 | --- lpworkitems/models_roadmap.py 2011-11-03 15:48:13 +0000 | |||
272 | +++ lpworkitems/models_roadmap.py 2011-12-15 18:49:25 +0000 | |||
273 | @@ -4,6 +4,8 @@ | |||
274 | 4 | 4 | ||
275 | 5 | from storm.locals import Date, Reference, ReferenceSet, Unicode, Int, Bool | 5 | from storm.locals import Date, Reference, ReferenceSet, Unicode, Int, Bool |
276 | 6 | 6 | ||
277 | 7 | from lpworkitems import models | ||
278 | 8 | |||
279 | 7 | 9 | ||
280 | 8 | class Card(object): | 10 | class Card(object): |
281 | 9 | 11 | ||
282 | @@ -43,3 +45,16 @@ | |||
283 | 43 | def __init__(self, name, lane_id): | 45 | def __init__(self, name, lane_id): |
284 | 44 | self.lane_id = lane_id | 46 | self.lane_id = lane_id |
285 | 45 | self.name = name | 47 | self.name = name |
286 | 48 | |||
287 | 49 | |||
288 | 50 | def current_date(): | ||
289 | 51 | return datetime.date.today() | ||
290 | 52 | |||
291 | 53 | |||
292 | 54 | class BlueprintDailyCountPerState(object): | ||
293 | 55 | __storm_table__ = 'spec_daily_count_per_state' | ||
294 | 56 | __storm_primary__ = 'status', 'day' | ||
295 | 57 | day = Date(default_factory=current_date) | ||
296 | 58 | status = Unicode() | ||
297 | 59 | lane_id = Int() | ||
298 | 60 | count = Int() | ||
299 | 46 | 61 | ||
300 | === modified file 'lpworkitems/tests/test_collect.py' | |||
301 | --- lpworkitems/tests/test_collect.py 2011-06-14 22:00:21 +0000 | |||
302 | +++ lpworkitems/tests/test_collect.py 2011-12-15 18:49:25 +0000 | |||
303 | @@ -184,7 +184,6 @@ | |||
304 | 184 | self.store.find( | 184 | self.store.find( |
305 | 185 | Milestone, Milestone.name == name).one()) | 185 | Milestone, Milestone.name == name).one()) |
306 | 186 | 186 | ||
307 | 187 | |||
308 | 188 | def test_store_blueprint_stores_blueprint(self): | 187 | def test_store_blueprint_stores_blueprint(self): |
309 | 189 | blueprint = self.factory.make_blueprint(store=False) | 188 | blueprint = self.factory.make_blueprint(store=False) |
310 | 190 | ret = self.collector.store_blueprint(blueprint) | 189 | ret = self.collector.store_blueprint(blueprint) |
311 | 191 | 190 | ||
312 | === modified file 'lpworkitems/tests/test_collect_roadmap.py' | |||
313 | --- lpworkitems/tests/test_collect_roadmap.py 2011-12-13 09:24:21 +0000 | |||
314 | +++ lpworkitems/tests/test_collect_roadmap.py 2011-12-15 18:49:25 +0000 | |||
315 | @@ -1,15 +1,13 @@ | |||
316 | 1 | import datetime | ||
317 | 2 | |||
318 | 1 | from lpworkitems.collect_roadmap import ( | 3 | from lpworkitems.collect_roadmap import ( |
319 | 2 | CollectorStore, | 4 | CollectorStore, |
320 | 3 | get_json_item, | 5 | get_json_item, |
321 | 4 | lookup_kanban_priority, | ||
322 | 5 | ) | 6 | ) |
323 | 7 | from lpworkitems.models_roadmap import BlueprintDailyCountPerState | ||
324 | 6 | from lpworkitems.error_collector import ( | 8 | from lpworkitems.error_collector import ( |
325 | 7 | ErrorCollector, | 9 | ErrorCollector, |
326 | 8 | ) | 10 | ) |
327 | 9 | from lpworkitems.models_roadmap import ( | ||
328 | 10 | Card, | ||
329 | 11 | Lane, | ||
330 | 12 | ) | ||
331 | 13 | from lpworkitems.testing import TestCaseWithFakeLaunchpad | 11 | from lpworkitems.testing import TestCaseWithFakeLaunchpad |
332 | 14 | 12 | ||
333 | 15 | 13 | ||
334 | @@ -26,6 +24,26 @@ | |||
335 | 26 | fn() | 24 | fn() |
336 | 27 | self.assertEqual(0, self.store.find(cls).count()) | 25 | self.assertEqual(0, self.store.find(cls).count()) |
337 | 28 | 26 | ||
338 | 27 | def test_clear_todays_blueprint_daily_count_per_state(self): | ||
339 | 28 | self.factory.make_blueprint_daily_count_per_state( | ||
340 | 29 | day=datetime.date.today()) | ||
341 | 30 | self.assertClears( | ||
342 | 31 | BlueprintDailyCountPerState, | ||
343 | 32 | self.collector.clear_todays_blueprint_daily_count_per_state) | ||
344 | 33 | |||
345 | 34 | def test_store_roadmap_bp_count_per_state(self): | ||
346 | 35 | bp = self.factory.make_blueprint() | ||
347 | 36 | card = self.factory.make_card() | ||
348 | 37 | meta = self.factory.make_meta( | ||
349 | 38 | key=u'Roadmap id', value=card.roadmap_id, blueprint=bp) | ||
350 | 39 | self.collector.store_roadmap_bp_count_per_state() | ||
351 | 40 | self.assertEqual( | ||
352 | 41 | 1, self.store.find(BlueprintDailyCountPerState).count()) | ||
353 | 42 | entry = self.store.find(BlueprintDailyCountPerState).one() | ||
354 | 43 | self.assertEqual(1, entry.count) | ||
355 | 44 | self.assertEqual(card.lane_id, entry.lane_id) | ||
356 | 45 | self.assertEqual(bp.implementation, entry.status) | ||
357 | 46 | |||
358 | 29 | # XXX Add tests for the roadmap classes. | 47 | # XXX Add tests for the roadmap classes. |
359 | 30 | 48 | ||
360 | 31 | 49 | ||
361 | 32 | 50 | ||
362 | === modified file 'lpworkitems/tests/test_factory.py' | |||
363 | --- lpworkitems/tests/test_factory.py 2011-06-04 18:48:23 +0000 | |||
364 | +++ lpworkitems/tests/test_factory.py 2011-12-15 18:49:25 +0000 | |||
365 | @@ -162,7 +162,7 @@ | |||
366 | 162 | implementation = u"Implemented" | 162 | implementation = u"Implemented" |
367 | 163 | self.assert_with_and_without( | 163 | self.assert_with_and_without( |
368 | 164 | self.factory.make_blueprint, "implementation", implementation, | 164 | self.factory.make_blueprint, "implementation", implementation, |
370 | 165 | Equals(None)) | 165 | Equals("Unknown")) |
371 | 166 | 166 | ||
372 | 167 | def test_uses_assignee_name(self): | 167 | def test_uses_assignee_name(self): |
373 | 168 | assignee_name = self.factory.getUniqueUnicode( | 168 | assignee_name = self.factory.getUniqueUnicode( |
374 | 169 | 169 | ||
375 | === modified file 'lpworkitems/tests/test_models.py' | |||
376 | --- lpworkitems/tests/test_models.py 2011-12-06 15:20:43 +0000 | |||
377 | +++ lpworkitems/tests/test_models.py 2011-12-15 18:49:25 +0000 | |||
378 | @@ -6,8 +6,11 @@ | |||
379 | 6 | extract_last_path_segment_from_url, | 6 | extract_last_path_segment_from_url, |
380 | 7 | extract_user_name_from_url, | 7 | extract_user_name_from_url, |
381 | 8 | get_whiteboard_section, | 8 | get_whiteboard_section, |
384 | 9 | ) | 9 | ROADMAP_STATUSES_MAP, |
385 | 10 | from lpworkitems.testing import TestCaseWithFakeLaunchpad | 10 | ) |
386 | 11 | from lpworkitems.testing import ( | ||
387 | 12 | TestCaseWithFakeLaunchpad, | ||
388 | 13 | ) | ||
389 | 11 | 14 | ||
390 | 12 | 15 | ||
391 | 13 | class GetWhiteboardSectionTests(TestCase): | 16 | class GetWhiteboardSectionTests(TestCase): |
392 | @@ -42,6 +45,18 @@ | |||
393 | 42 | 45 | ||
394 | 43 | class BlueprintTests(TestCaseWithFakeLaunchpad): | 46 | class BlueprintTests(TestCaseWithFakeLaunchpad): |
395 | 44 | 47 | ||
396 | 48 | def test_roadmap_status(self): | ||
397 | 49 | roadmap_status = "Completed" | ||
398 | 50 | bp_implementation = ROADMAP_STATUSES_MAP[roadmap_status][0] | ||
399 | 51 | bp_status = self.factory.make_blueprint( | ||
400 | 52 | implementation=bp_implementation) | ||
401 | 53 | self.assertEqual(roadmap_status, bp_status.roadmap_status) | ||
402 | 54 | |||
403 | 55 | def test_roadmap_status_unknown_status(self): | ||
404 | 56 | blueprint = self.factory.make_blueprint( | ||
405 | 57 | implementation=u"Not Expected") | ||
406 | 58 | self.assertEqual(None, blueprint.roadmap_status) | ||
407 | 59 | |||
408 | 45 | def test_from_launchpad_sets_name(self): | 60 | def test_from_launchpad_sets_name(self): |
409 | 46 | name = self.factory.getUniqueUnicode(prefix="lpblueprint") | 61 | name = self.factory.getUniqueUnicode(prefix="lpblueprint") |
410 | 47 | lp_bp = self.lp.make_blueprint(name=name) | 62 | lp_bp = self.lp.make_blueprint(name=name) |
411 | 48 | 63 | ||
412 | === modified file 'report_tools.py' | |||
413 | --- report_tools.py 2011-12-12 07:35:35 +0000 | |||
414 | +++ report_tools.py 2011-12-15 18:49:25 +0000 | |||
415 | @@ -14,7 +14,8 @@ | |||
416 | 14 | Card, | 14 | Card, |
417 | 15 | ) | 15 | ) |
418 | 16 | from lpworkitems.models import ( | 16 | from lpworkitems.models import ( |
420 | 17 | Meta, | 17 | Meta, ROADMAP_STATUSES_MAP, |
421 | 18 | get_roadmap_status_for_bp_implementation_status, | ||
422 | 18 | ) | 19 | ) |
423 | 19 | 20 | ||
424 | 20 | valid_states = [u'inprogress', u'blocked', u'todo', u'done', u'postponed'] | 21 | valid_states = [u'inprogress', u'blocked', u'todo', u'done', u'postponed'] |
425 | @@ -206,12 +207,17 @@ | |||
426 | 206 | def roadmap_pages(my_path, database, basename, config, lane, root=None): | 207 | def roadmap_pages(my_path, database, basename, config, lane, root=None): |
427 | 207 | cfg = load_config(config) | 208 | cfg = load_config(config) |
428 | 208 | fh = open(basename + '.html', 'w') | 209 | fh = open(basename + '.html', 'w') |
429 | 210 | # XXX fix this. the intention is to have the chart in the same dir as the | ||
430 | 211 | # html. This is not essential but makes linking in roadmap_lane.html easier | ||
431 | 212 | chart_name = '/'.join(basename.split('/')[:-1]) + '/current_quarter.svg' | ||
432 | 209 | try: | 213 | try: |
433 | 210 | args = [os.path.join(my_path, 'html-report'), '-d', database] | 214 | args = [os.path.join(my_path, 'html-report'), '-d', database] |
434 | 211 | args += ['--report-type', 'roadmap_page'] | 215 | args += ['--report-type', 'roadmap_page'] |
435 | 212 | args += ['--lane', lane.name] | 216 | args += ['--lane', lane.name] |
436 | 213 | if root: | 217 | if root: |
437 | 214 | args += ['--root', root] | 218 | args += ['--root', root] |
438 | 219 | if lane.is_current: | ||
439 | 220 | args += ['--chart', chart_name] | ||
440 | 215 | report_args(args, theme=get_theme(cfg)) | 221 | report_args(args, theme=get_theme(cfg)) |
441 | 216 | proc = Popen(args, stdout=fh) | 222 | proc = Popen(args, stdout=fh) |
442 | 217 | print basename + '.html' | 223 | print basename + '.html' |
443 | @@ -219,6 +225,13 @@ | |||
444 | 219 | finally: | 225 | finally: |
445 | 220 | fh.close() | 226 | fh.close() |
446 | 221 | 227 | ||
447 | 228 | if lane.is_current: | ||
448 | 229 | args = [os.path.join(my_path, 'roadmap-bp-chart'), '-d', database, | ||
449 | 230 | '-o', chart_name] | ||
450 | 231 | proc = Popen(args) | ||
451 | 232 | print chart_name | ||
452 | 233 | proc.wait() | ||
453 | 234 | |||
454 | 222 | 235 | ||
455 | 223 | def roadmap_cards(my_path, database, basename, config, card, root=None): | 236 | def roadmap_cards(my_path, database, basename, config, card, root=None): |
456 | 224 | cfg = load_config(config) | 237 | cfg = load_config(config) |
457 | @@ -341,6 +354,34 @@ | |||
458 | 341 | return escape(html, True) | 354 | return escape(html, True) |
459 | 342 | 355 | ||
460 | 343 | 356 | ||
461 | 357 | def blueprints_over_time(store): | ||
462 | 358 | '''Calculate blueprint development over time for the current lane. | ||
463 | 359 | |||
464 | 360 | We do not need to care about teams or groups since this is intended for the | ||
465 | 361 | roadmap overview. | ||
466 | 362 | |||
467 | 363 | Return date -> state -> count mapping. states are | ||
468 | 364 | {planned,inprogress,completed,blocked}. | ||
469 | 365 | ''' | ||
470 | 366 | data = {} | ||
471 | 367 | result = store.execute(""" | ||
472 | 368 | SELECT status, day, count | ||
473 | 369 | FROM spec_daily_count_per_state | ||
474 | 370 | JOIN lane on lane.lane_id = spec_daily_count_per_state.lane_id | ||
475 | 371 | WHERE lane.is_current = 1 | ||
476 | 372 | """) | ||
477 | 373 | for status, day, count in result: | ||
478 | 374 | roadmap_status = get_roadmap_status_for_bp_implementation_status( | ||
479 | 375 | status) | ||
480 | 376 | assert roadmap_status is not None | ||
481 | 377 | if day not in data: | ||
482 | 378 | data[day] = {} | ||
483 | 379 | if roadmap_status not in data[day]: | ||
484 | 380 | data[day][roadmap_status] = 0 | ||
485 | 381 | data[day][roadmap_status] += count | ||
486 | 382 | return data | ||
487 | 383 | |||
488 | 384 | |||
489 | 344 | def workitems_over_time(store, team=None, group=None, milestone_collection=None): | 385 | def workitems_over_time(store, team=None, group=None, milestone_collection=None): |
490 | 345 | '''Calculate work item development over time. | 386 | '''Calculate work item development over time. |
491 | 346 | 387 | ||
492 | @@ -978,21 +1019,11 @@ | |||
493 | 978 | 1019 | ||
494 | 979 | def card_blueprints_by_status(store, roadmap_id): | 1020 | def card_blueprints_by_status(store, roadmap_id): |
495 | 980 | blueprints = card_blueprints(store, roadmap_id) | 1021 | blueprints = card_blueprints(store, roadmap_id) |
506 | 981 | statuses = {'Completed': ['Implemented'], | 1022 | bp_by_status = {} |
507 | 982 | 'Blocked': ['Needs Infrastructure', 'Blocked', 'Deferred'], | 1023 | for key in ROADMAP_STATUSES_MAP: |
508 | 983 | 'In Progress': ['Deployment', 'Needs Code Review', | 1024 | bp_by_status[key] = [] |
499 | 984 | 'Beta Available', 'Good progress', | ||
500 | 985 | 'Slow progress', 'Started'], | ||
501 | 986 | 'Planned': ['Unknown', 'Not started', 'Informational']} | ||
502 | 987 | bp_by_status = {'In Progress': [], | ||
503 | 988 | 'Blocked': [], | ||
504 | 989 | 'Planned': [], | ||
505 | 990 | 'Completed': []} | ||
509 | 991 | for bp in blueprints: | 1025 | for bp in blueprints: |
514 | 992 | for status in statuses.iterkeys(): | 1026 | bp_by_status[bp.roadmap_status].append(bp) |
511 | 993 | if bp.implementation in statuses[status]: | ||
512 | 994 | bp_by_status[status].append(bp) | ||
513 | 995 | break | ||
515 | 996 | return bp_by_status | 1027 | return bp_by_status |
516 | 997 | 1028 | ||
517 | 998 | 1029 | ||
518 | 999 | 1030 | ||
519 | === added file 'roadmap-bp-chart' | |||
520 | --- roadmap-bp-chart 1970-01-01 00:00:00 +0000 | |||
521 | +++ roadmap-bp-chart 2011-12-15 18:49:25 +0000 | |||
522 | @@ -0,0 +1,254 @@ | |||
523 | 1 | #!/usr/bin/python | ||
524 | 2 | # | ||
525 | 3 | # Create a blueprint tracking chart from a blueprint database. | ||
526 | 4 | # | ||
527 | 5 | # Copyright (C) 2010, 2011 Canonical Ltd. | ||
528 | 6 | # License: GPL-3 | ||
529 | 7 | |||
530 | 8 | import optparse, datetime, sys | ||
531 | 9 | import report_tools | ||
532 | 10 | |||
533 | 11 | from pychart import * | ||
534 | 12 | |||
535 | 13 | def date_to_ordinal(s): | ||
536 | 14 | '''Turn yyyy-mm-dd strings to ordinals''' | ||
537 | 15 | return report_tools.date_to_python(s).toordinal() | ||
538 | 16 | |||
539 | 17 | |||
540 | 18 | def ordinal_to_date(ordinal): | ||
541 | 19 | '''Turn an ordinal date into a string''' | ||
542 | 20 | d = datetime.date.fromordinal(int(ordinal)) | ||
543 | 21 | return d.strftime('%Y-%m-%d') | ||
544 | 22 | |||
545 | 23 | def format_date(ordinal): | ||
546 | 24 | d = datetime.date.fromordinal(int(ordinal)) | ||
547 | 25 | return '/a60{}' + d.strftime('%b %d, %y') | ||
548 | 26 | |||
549 | 27 | def do_chart(data, start_date, end_date, trend_start, title, filename, only_weekdays, inverted): | ||
550 | 28 | #set up default values | ||
551 | 29 | format = 'svg' | ||
552 | 30 | height = 450 | ||
553 | 31 | width = 1000 | ||
554 | 32 | legend_x = 700 | ||
555 | 33 | legend_y = 200 | ||
556 | 34 | title_x = 300 | ||
557 | 35 | title_y = 350 | ||
558 | 36 | |||
559 | 37 | if inverted: | ||
560 | 38 | legend_x=200 | ||
561 | 39 | |||
562 | 40 | # Tell pychart to use colors | ||
563 | 41 | theme.use_color = True | ||
564 | 42 | theme.default_font_size = 12 | ||
565 | 43 | theme.reinitialize() | ||
566 | 44 | |||
567 | 45 | # turn into pychart data model and calculate maximum number of WIs | ||
568 | 46 | max_items = 1 # start at 1 to avoid zero div | ||
569 | 47 | lastactive = 0 | ||
570 | 48 | pcdata = [] | ||
571 | 49 | |||
572 | 50 | for date in xrange(date_to_ordinal(start_date), date_to_ordinal(end_date)+1): | ||
573 | 51 | if (not only_weekdays or datetime.date.fromordinal(date).weekday() < 5): | ||
574 | 52 | end_date = ordinal_to_date(date) | ||
575 | 53 | i = data.get(ordinal_to_date(date), {}) | ||
576 | 54 | count = i.get('Completed', 0) + i.get('Planned', 0) + i.get('Blocked', 0) + i.get('In Progress', 0) | ||
577 | 55 | if max_items < count: | ||
578 | 56 | max_items = count | ||
579 | 57 | pcdata.append((date, i.get('Planned', 0),0, | ||
580 | 58 | i.get('Blocked', 0),0, | ||
581 | 59 | i.get('In Progress', 0),0, | ||
582 | 60 | i.get('Completed',0),0, count)) | ||
583 | 61 | if count > 0: | ||
584 | 62 | lastactive = len(pcdata) - 1 | ||
585 | 63 | |||
586 | 64 | # add some extra space to look nicer | ||
587 | 65 | max_items = int(max_items * 1.05) | ||
588 | 66 | |||
589 | 67 | x_interval = len(pcdata)/20 | ||
590 | 68 | if max_items > 500: | ||
591 | 69 | y_interval = max_items/200*10 | ||
592 | 70 | elif max_items < 20: | ||
593 | 71 | y_interval = 1 | ||
594 | 72 | else: | ||
595 | 73 | y_interval = max_items/20 | ||
596 | 74 | |||
597 | 75 | # create the chart object | ||
598 | 76 | chart_object.set_defaults(area.T, size=(width, height), | ||
599 | 77 | y_range=(0, None), x_coord=category_coord.T(pcdata, 0)) | ||
600 | 78 | |||
601 | 79 | # tell the chart object it will use a bar chart, and will | ||
602 | 80 | # use the data list for it's model | ||
603 | 81 | chart_object.set_defaults(bar_plot.T, data=pcdata) | ||
604 | 82 | |||
605 | 83 | # create the chart area | ||
606 | 84 | # tell it to start at coords 0,0 | ||
607 | 85 | # tell it the labels, and the tics, etc.. | ||
608 | 86 | # HACK: to prevent 0 div | ||
609 | 87 | if max_items == 0: | ||
610 | 88 | max_items = 1 | ||
611 | 89 | ar = area.T(legend=legend.T(loc=(legend_x,legend_y)), loc=(0,0), | ||
612 | 90 | x_axis=axis.X(label='Date', tic_interval=x_interval,format=format_date), | ||
613 | 91 | y_axis=axis.Y(label='Blueprints', tic_interval=y_interval), | ||
614 | 92 | y_range=(0, max_items)) | ||
615 | 93 | |||
616 | 94 | #initialize the blar_plot fill styles | ||
617 | 95 | bar_plot.fill_styles.reset() | ||
618 | 96 | |||
619 | 97 | # create each set of data to plot | ||
620 | 98 | # note that index zero is the label col | ||
621 | 99 | # for each column of data, tell it what to use for the legend and | ||
622 | 100 | # what color to make the bar, no lines, and | ||
623 | 101 | # what plot to stack on | ||
624 | 102 | |||
625 | 103 | tlabel = '' | ||
626 | 104 | |||
627 | 105 | if inverted: | ||
628 | 106 | plot1 = bar_plot.T(label='Completed' + tlabel, hcol=7) | ||
629 | 107 | plot1.fill_style = fill_style.Plain(bgcolor=color.seagreen) | ||
630 | 108 | |||
631 | 109 | plot3 = bar_plot.T(label='In Progress' + tlabel, hcol=5, stack_on = plot1) | ||
632 | 110 | plot3.fill_style = fill_style.Plain(bgcolor=color.gray65) | ||
633 | 111 | |||
634 | 112 | plot5 = bar_plot.T(label='Blocked' + tlabel, hcol=3, stack_on = plot3) | ||
635 | 113 | plot5.fill_style = fill_style.Plain(bgcolor=color.red1) | ||
636 | 114 | |||
637 | 115 | plot7 = bar_plot.T(label='Planned' + tlabel, hcol=1, stack_on = plot5) | ||
638 | 116 | plot7.fill_style = fill_style.Plain(bgcolor=color.darkorange1) | ||
639 | 117 | else: | ||
640 | 118 | plot1 = bar_plot.T(label='Planned' + tlabel, hcol=1) | ||
641 | 119 | plot1.fill_style = fill_style.Plain(bgcolor=color.darkorange1) | ||
642 | 120 | |||
643 | 121 | plot3 = bar_plot.T(label='Blocked' + tlabel, hcol=3, stack_on = plot1) | ||
644 | 122 | plot3.fill_style = fill_style.Plain(bgcolor=color.red1) | ||
645 | 123 | |||
646 | 124 | plot5 = bar_plot.T(label='In Progress' + tlabel, hcol=5, stack_on = plot3) | ||
647 | 125 | plot5.fill_style = fill_style.Plain(bgcolor=color.gray65) | ||
648 | 126 | |||
649 | 127 | plot7 = bar_plot.T(label='Completed' + tlabel, hcol=7, stack_on = plot5) | ||
650 | 128 | plot7.fill_style = fill_style.Plain(bgcolor=color.seagreen) | ||
651 | 129 | |||
652 | 130 | |||
653 | 131 | plot1.line_style = None | ||
654 | 132 | plot3.line_style = None | ||
655 | 133 | plot5.line_style = None | ||
656 | 134 | plot7.line_style = None | ||
657 | 135 | |||
658 | 136 | plot11 = bar_plot.T(label='total', hcol=9) | ||
659 | 137 | plot11.fill_style = None | ||
660 | 138 | plot11.line_style = line_style.gray30 | ||
661 | 139 | |||
662 | 140 | # create the canvas with the specified filename and file format | ||
663 | 141 | can = canvas.init(filename,format) | ||
664 | 142 | |||
665 | 143 | # add the data to the area and draw it | ||
666 | 144 | ar.add_plot(plot1, plot3, plot5, plot7) | ||
667 | 145 | ar.draw() | ||
668 | 146 | |||
669 | 147 | # title | ||
670 | 148 | tb = text_box.T(loc=(title_x, title_y), text=title, line_style=None) | ||
671 | 149 | tb.fill_style = None | ||
672 | 150 | tb.draw() | ||
673 | 151 | |||
674 | 152 | # | ||
675 | 153 | # main | ||
676 | 154 | # | ||
677 | 155 | |||
678 | 156 | # argv parsing | ||
679 | 157 | optparser = optparse.OptionParser() | ||
680 | 158 | optparser.add_option('-d', '--database', | ||
681 | 159 | help='Path to database', dest='database', metavar='PATH') | ||
682 | 160 | optparser.add_option('-t', '--team', | ||
683 | 161 | help='Restrict report to a particular team', dest='team') | ||
684 | 162 | optparser.add_option('-m', '--milestone', | ||
685 | 163 | help='Restrict report to a particular milestone', dest='milestone') | ||
686 | 164 | optparser.add_option('-o', '--output', | ||
687 | 165 | help='Output file', dest='output') | ||
688 | 166 | optparser.add_option('--trend-start', type='int', | ||
689 | 167 | help='Explicitly set start of trend line', dest='trendstart') | ||
690 | 168 | optparser.add_option('-u', '--user', | ||
691 | 169 | help='Run for this user', dest='user') | ||
692 | 170 | optparser.add_option('--only-weekdays', action='store_true', | ||
693 | 171 | help='Skip Saturdays and Sundays in the resulting graph', dest='only_weekdays') | ||
694 | 172 | optparser.add_option('--inverted', action='store_true', | ||
695 | 173 | help='Generate an inverted burndown chart', dest='inverted') | ||
696 | 174 | optparser.add_option('-s', '--start-date', | ||
697 | 175 | help='Explicitly set the start date of the burndown data', dest='start_date') | ||
698 | 176 | optparser.add_option('-e', '--end-date', | ||
699 | 177 | help='Explicitly set the end date of the burndown data', dest='end_date') | ||
700 | 178 | optparser.add_option('--no-foreign', action='store_true', default=False, | ||
701 | 179 | help='Do not show foreign totals separate', dest='noforeign') | ||
702 | 180 | optparser.add_option('--group', | ||
703 | 181 | help='Run for this group', dest='group') | ||
704 | 182 | optparser.add_option('--date', | ||
705 | 183 | help='Run for this date', dest='date') | ||
706 | 184 | |||
707 | 185 | (opts, args) = optparser.parse_args() | ||
708 | 186 | if not opts.database: | ||
709 | 187 | optparser.error('No database given') | ||
710 | 188 | if not opts.output: | ||
711 | 189 | optparser.error('No output file given') | ||
712 | 190 | |||
713 | 191 | if opts.user and opts.team: | ||
714 | 192 | optparser.error('team and user options are mutually exclusive') | ||
715 | 193 | if opts.user and opts.group: | ||
716 | 194 | optparser.error('user and group options are mutually exclusive') | ||
717 | 195 | if opts.team and opts.group: | ||
718 | 196 | optparser.error('team and group options are mutually exclusive') | ||
719 | 197 | if opts.milestone and opts.date: | ||
720 | 198 | optparser.error('milestone and date options are mutually exclusive') | ||
721 | 199 | |||
722 | 200 | # The typing allows polymorphic behavior | ||
723 | 201 | if opts.user: | ||
724 | 202 | opts.team = report_tools.user_string(opts.user) | ||
725 | 203 | elif opts.team: | ||
726 | 204 | opts.team = report_tools.team_string(opts.team) | ||
727 | 205 | |||
728 | 206 | store = report_tools.get_store(opts.database) | ||
729 | 207 | |||
730 | 208 | milestone_collection = None | ||
731 | 209 | if opts.milestone: | ||
732 | 210 | milestone_collection = report_tools.get_milestone(store, opts.milestone) | ||
733 | 211 | elif opts.date: | ||
734 | 212 | milestone_collection = report_tools.MilestoneGroup( | ||
735 | 213 | report_tools.date_to_python(opts.date)) | ||
736 | 214 | |||
737 | 215 | |||
738 | 216 | # get date -> state -> count mapping | ||
739 | 217 | data = report_tools.blueprints_over_time(store) | ||
740 | 218 | |||
741 | 219 | if len(data) == 0: | ||
742 | 220 | print 'WARNING: no blueprints, not generating chart (team: %s, group: %s, due date: %s)' % ( | ||
743 | 221 | opts.team or 'all', opts.group or 'none', milestone_collection and milestone_collection.display_name or 'none') | ||
744 | 222 | sys.exit(0) | ||
745 | 223 | |||
746 | 224 | # calculate start/end date if no dates are given | ||
747 | 225 | if opts.start_date is None: | ||
748 | 226 | start_date = sorted(data.keys())[0] | ||
749 | 227 | else: | ||
750 | 228 | start_date=opts.start_date | ||
751 | 229 | |||
752 | 230 | if opts.end_date is None: | ||
753 | 231 | if milestone_collection is not None: | ||
754 | 232 | end_date = milestone_collection.due_date_str | ||
755 | 233 | else: | ||
756 | 234 | end_date=report_tools.milestone_due_date(store) | ||
757 | 235 | else: | ||
758 | 236 | end_date=opts.end_date | ||
759 | 237 | |||
760 | 238 | if not start_date or not end_date or date_to_ordinal(start_date) > date_to_ordinal(end_date): | ||
761 | 239 | print 'WARNING: empty date range, not generating chart (team: %s, group: %s, due date: %s)' % ( | ||
762 | 240 | opts.team or 'all', opts.group or 'none', milestone_collection and milestone_collection.display_name or 'none') | ||
763 | 241 | sys.exit(0) | ||
764 | 242 | |||
765 | 243 | # title | ||
766 | 244 | if opts.team: | ||
767 | 245 | title = '/20' + opts.team | ||
768 | 246 | elif opts.group: | ||
769 | 247 | title = "/20" + opts.group | ||
770 | 248 | else: | ||
771 | 249 | title = '/20all teams' | ||
772 | 250 | |||
773 | 251 | if milestone_collection is not None: | ||
774 | 252 | title += ' (%s)' % milestone_collection.name | ||
775 | 253 | |||
776 | 254 | do_chart(data, start_date, end_date, opts.trendstart, title, opts.output, opts.only_weekdays, opts.inverted) | ||
777 | 0 | 255 | ||
778 | === modified file 'templates/roadmap_lane.html' | |||
779 | --- templates/roadmap_lane.html 2011-12-12 09:17:20 +0000 | |||
780 | +++ templates/roadmap_lane.html 2011-12-15 18:49:25 +0000 | |||
781 | @@ -41,3 +41,12 @@ | |||
782 | 41 | % endfor | 41 | % endfor |
783 | 42 | </table> | 42 | </table> |
784 | 43 | 43 | ||
785 | 44 | % if chart_url != 'burndown.svg': | ||
786 | 45 | <!-- The cli option defaults to burndown.svg! :( --> | ||
787 | 46 | <div class="overview_graph"> | ||
788 | 47 | <h3>Blueprint progress</h3><p><a href="current_quarter.svg">(enlarge)</a></p> | ||
789 | 48 | <object | ||
790 | 49 | height="500" width="833" | ||
791 | 50 | data="current_quarter.svg" type="image/svg+xml">Blueprint progress</object> | ||
792 | 51 | </div> | ||
793 | 52 | % endif |
On Thu, Dec 15, 2011 at 6:58 PM, Guilherme Salgado infrastructure) /code.launchpad .net/~salgado/ launchpad- work-items- tracker/ blueprints- over-time/ +merge/ 85921 /code.launchpad .net/~salgado/ launchpad- work-items- tracker/ blueprints- over-time/ +merge/ 85921 args.append( "--debug" ) args.append( "--mail" )
<email address hidden> wrote:
> Guilherme Salgado has proposed merging lp:~salgado/launchpad-work-items-tracker/blueprints-over-time into lp:~linaro-infrastructure/launchpad-work-items-tracker/linaro.
>
> Requested reviews:
> Linaro Infrastructure (linaro-
>
> For more details, see:
> https:/
>
> This branch adds a new table which stores the number of blueprints per (day, status, lane). This is then used on the roadmap page to show a graph of blueprints per status/day for the quarter.
> --
> https:/
> Your team Linaro Infrastructure is requested to review the proposed merge of lp:~salgado/launchpad-work-items-tracker/blueprints-over-time into lp:~linaro-infrastructure/launchpad-work-items-tracker/linaro.
>
> === modified file 'all-projects'
> --- all-projects 2011-12-07 09:03:27 +0000
> +++ all-projects 2011-12-15 17:57:13 +0000
> @@ -117,8 +117,6 @@
>
> if opts.debug:
> extra_collect_
> - else:
> - extra_collect_
This should only be removed on staging. It's just so we don't email
about errors while testing.
> args): write(" collect failed for %s" % project_name) clear_todays_ workitems( ) clear_blueprint s() clear_metas( ) clear_complexit ys()
> if not collect(source_dir, db_file, config_file, extra_collect_
> sys.stderr.
>
> === modified file 'collect'
> --- collect 2011-12-07 09:03:27 +0000
> +++ collect 2011-12-15 17:57:13 +0000
> @@ -6,6 +6,7 @@
> # Copyright (C) 2010, 2011 Canonical Ltd.
> # License: GPL-3
>
> +import datetime
> import urllib, re, sys, optparse, smtplib, pwd, os, urlparse
> import logging
> from email.mime.text import MIMEText
> @@ -755,6 +756,8 @@
>
> # reset status for current day
> collector.
> + # We can delete all blueprints while keeping work items for previous days
> + # because there's no foreign key reference from WorkItem to Blueprint.
> collector.
> collector.
> collector.
>
> === modified file 'collect_roadmap'
> --- collect_roadmap 2011-12-15 12:59:35 +0000
> +++ collect_roadmap 2011-12-15 17:57:13 +0000
> @@ -51,9 +51,15 @@
> except urllib2.HTTPError, e:
> print "HTTP error for url '%s': %d" % (url, e.code)
> except urllib2.URLError, e:
> +<<<<<<< TREE
> print "Network error for url '%s': %s" % (url, e.reason.args[1])
> except ValueError, e:
> print "Data error for url '%s': %s" % (url, e.message)
> +=======
> + print "Network error: %s" % e.reason.args[1] opts.database) store, '', error_collector) clear_todays_ blueprint_ daily_count_ per_state( ) clear_lanes( ) clear_cards( ) import( collector, cfg, opt...
> + except ValueError, e:
> + print "Data error: %s" % e.message
> +>>>>>>> MERGE-SOURCE
>
> return data
>
> @@ -269,11 +275,14 @@
> store = get_store(
> collector = CollectorStore(
>
> + collector.
> collector.
> collector.
>
> kanban_