Merge lp:~salgado/launchpad-work-items-tracker/blueprints-over-time into lp:~linaro-automation/launchpad-work-items-tracker/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
Reviewer Review Type Date Requested Status
Mattias Backman (community) Approve
Review via email: mp+85921@code.launchpad.net

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 :
Download full text (31.5 KiB)

On Thu, Dec 15, 2011 at 6:58 PM, Guilherme Salgado
<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-infrastructure)
>
> For more details, see:
> https://code.launchpad.net/~salgado/launchpad-work-items-tracker/blueprints-over-time/+merge/85921
>
> 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://code.launchpad.net/~salgado/launchpad-work-items-tracker/blueprints-over-time/+merge/85921
> 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_args.append("--debug")
> -        else:
> -            extra_collect_args.append("--mail")

This should only be removed on staging. It's just so we don't email
about errors while testing.

>
>         if not collect(source_dir, db_file, config_file, extra_collect_args):
>             sys.stderr.write("collect failed for %s" % project_name)
>
> === 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.clear_todays_workitems()
> +    # We can delete all blueprints while keeping work items for previous days
> +    # because there's no foreign key reference from WorkItem to Blueprint.
>     collector.clear_blueprints()
>     collector.clear_metas()
>     collector.clear_complexitys()
>
> === 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]
> +    except ValueError, e:
> +        print "Data error: %s" % e.message
> +>>>>>>> MERGE-SOURCE
>
>     return data
>
> @@ -269,11 +275,14 @@
>     store = get_store(opts.database)
>     collector = CollectorStore(store, '', error_collector)
>
> +    collector.clear_todays_blueprint_daily_count_per_state()
>     collector.clear_lanes()
>     collector.clear_cards()
>
>     kanban_import(collector, cfg, opt...

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
=== modified file 'collect'
--- collect 2011-12-07 09:03:27 +0000
+++ collect 2011-12-15 18:49:25 +0000
@@ -755,6 +755,8 @@
755755
756 # reset status for current day756 # reset status for current day
757 collector.clear_todays_workitems()757 collector.clear_todays_workitems()
758 # We can delete all blueprints while keeping work items for previous days
759 # because there's no foreign key reference from WorkItem to Blueprint.
758 collector.clear_blueprints()760 collector.clear_blueprints()
759 collector.clear_metas()761 collector.clear_metas()
760 collector.clear_complexitys()762 collector.clear_complexitys()
761763
=== modified file 'collect_roadmap'
--- collect_roadmap 2011-12-15 12:59:35 +0000
+++ collect_roadmap 2011-12-15 18:49:25 +0000
@@ -245,6 +245,13 @@
245 logger.setLevel(logging.DEBUG)245 logger.setLevel(logging.DEBUG)
246246
247247
248def update_todays_blueprint_daily_count_per_state(collector):
249 """Clear today's entries and create them again to reflect the current
250 state of blueprints."""
251 collector.clear_todays_blueprint_daily_count_per_state()
252 collector.store_roadmap_bp_count_per_state()
253
254
248def main():255def main():
249 report_tools.fix_stdouterr()256 report_tools.fix_stdouterr()
250257
@@ -274,6 +281,8 @@
274281
275 kanban_import(collector, cfg, opts.board, opts.api_token)282 kanban_import(collector, cfg, opts.board, opts.api_token)
276283
284 update_todays_blueprint_daily_count_per_state(collector)
285
277 store.commit()286 store.commit()
278287
279 os.unlink(lock_path)288 os.unlink(lock_path)
280289
=== modified file 'html-report'
--- html-report 2011-12-07 09:03:27 +0000
+++ html-report 2011-12-15 18:49:25 +0000
@@ -495,6 +495,7 @@
495 data.update(dict(page_type="roadmap_lane"))495 data.update(dict(page_type="roadmap_lane"))
496 data.update(dict(lane_title=title))496 data.update(dict(lane_title=title))
497 data.update(dict(lanes=lanes))497 data.update(dict(lanes=lanes))
498 data.update(dict(chart_url=opts.chart_url))
498 print report_tools.fill_template(499 print report_tools.fill_template(
499 "roadmap_lane.html", data, theme=opts.theme)500 "roadmap_lane.html", data, theme=opts.theme)
500501
501502
=== modified file 'lpworkitems/collect_roadmap.py'
--- lpworkitems/collect_roadmap.py 2011-12-13 09:24:21 +0000
+++ lpworkitems/collect_roadmap.py 2011-12-15 18:49:25 +0000
@@ -1,3 +1,5 @@
1import datetime
2
1from lpworkitems import models_roadmap3from lpworkitems import models_roadmap
2from utils import unicode_or_None4from utils import unicode_or_None
35
@@ -24,6 +26,30 @@
24 def store_card(self, card):26 def store_card(self, card):
25 self.store.add(card)27 self.store.add(card)
2628
29 def clear_todays_blueprint_daily_count_per_state(self):
30 self._clear_all(
31 models_roadmap.BlueprintDailyCountPerState,
32 models_roadmap.BlueprintDailyCountPerState.day == datetime.date.today())
33
34 def store_roadmap_bp_count_per_state(self):
35 query = """
36 SELECT implementation, lane_id, count(*)
37 FROM specs
38 JOIN meta on spec = specs.name
39 JOIN card on roadmap_id = value
40 WHERE key = 'Roadmap id'
41 GROUP BY implementation, lane_id
42 """
43 day = datetime.date.today()
44 result = self.store.execute(query)
45 for status, lane_id, count in result:
46 obj = models_roadmap.BlueprintDailyCountPerState()
47 obj.day = day
48 obj.status = status
49 obj.lane_id = lane_id
50 obj.count = count
51 self.store.add(obj)
52
2753
28def get_json_item(data, item_name):54def get_json_item(data, item_name):
29 item = data[item_name]55 item = data[item_name]
3056
=== modified file 'lpworkitems/database.py'
--- lpworkitems/database.py 2011-11-03 15:48:13 +0000
+++ lpworkitems/database.py 2011-12-15 18:49:25 +0000
@@ -7,13 +7,13 @@
7 store.execute('''CREATE TABLE version (7 store.execute('''CREATE TABLE version (
8 db_layout_ref INT NOT NULL8 db_layout_ref INT NOT NULL
9 )''')9 )''')
10 store.execute('''INSERT INTO version VALUES (14)''')10 store.execute('''INSERT INTO version VALUES (15)''')
1111
12 store.execute('''CREATE TABLE specs (12 store.execute('''CREATE TABLE specs (
13 name VARCHAR(255) PRIMARY KEY,13 name VARCHAR(255) PRIMARY KEY,
14 url VARCHAR(1000) NOT NULL,14 url VARCHAR(1000) NOT NULL,
15 priority CHAR(20),15 priority CHAR(20),
16 implementation CHAR(30),16 implementation CHAR(30) NOT NULL,
17 assignee CHAR(50),17 assignee CHAR(50),
18 team CHAR(50),18 team CHAR(50),
19 status VARCHAR(5000) NOT NULL,19 status VARCHAR(5000) NOT NULL,
@@ -25,6 +25,13 @@
25 roadmap_notes VARCHAR(5000)25 roadmap_notes VARCHAR(5000)
26 )''')26 )''')
2727
28 store.execute('''CREATE TABLE spec_daily_count_per_state (
29 status VARCHAR(5000) NOT NULL,
30 day DATE NOT NULL,
31 lane_id REFERENCES lane(lane_id),
32 count INT NOT NULL
33 )''')
34
28 store.execute('''CREATE TABLE work_items (35 store.execute('''CREATE TABLE work_items (
29 description VARCHAR(1000) NOT NULL,36 description VARCHAR(1000) NOT NULL,
30 spec VARCHAR(255) REFERENCES specs(name),37 spec VARCHAR(255) REFERENCES specs(name),
@@ -234,7 +241,14 @@
234 store.execute('ALTER TABLE card ADD COLUMN url VARCHAR(200)')241 store.execute('ALTER TABLE card ADD COLUMN url VARCHAR(200)')
235 store.execute('ALTER TABLE card ADD COLUMN is_healthy BOOLEAN')242 store.execute('ALTER TABLE card ADD COLUMN is_healthy BOOLEAN')
236 store.execute('UPDATE version SET db_layout_ref = 14')243 store.execute('UPDATE version SET db_layout_ref = 14')
237244 if ver == 14:
245 store.execute('''CREATE TABLE spec_daily_count_per_state (
246 status VARCHAR(5000) NOT NULL,
247 day DATE NOT NULL,
248 lane_id REFERENCES lane(lane_id),
249 count INT NOT NULL
250 )''')
251 store.execute('UPDATE version SET db_layout_ref = 15')
238252
239def get_store(dbpath):253def get_store(dbpath):
240 '''Open/initialize database.254 '''Open/initialize database.
241255
=== modified file 'lpworkitems/factory.py'
--- lpworkitems/factory.py 2011-06-14 22:00:21 +0000
+++ lpworkitems/factory.py 2011-12-15 18:49:25 +0000
@@ -11,6 +11,7 @@
11 TeamStructure,11 TeamStructure,
12 Workitem,12 Workitem,
13 )13 )
14from lpworkitems.models_roadmap import BlueprintDailyCountPerState, Card
1415
1516
16class Factory(object):17class Factory(object):
@@ -63,6 +64,8 @@
63 url = self.getUniqueUnicode(prefix=name+"_url")64 url = self.getUniqueUnicode(prefix=name+"_url")
64 if status is None:65 if status is None:
65 status = self.getUniqueUnicode(prefix=name+"_status")66 status = self.getUniqueUnicode(prefix=name+"_status")
67 if implementation is None:
68 implementation = u'Unknown'
66 blueprint.name = name69 blueprint.name = name
67 blueprint.url = url70 blueprint.url = url
68 blueprint.status = status71 blueprint.status = status
@@ -109,8 +112,11 @@
109 self.store.add(workitem)112 self.store.add(workitem)
110 return workitem113 return workitem
111114
112 def make_meta(self, store=True):115 def make_meta(self, key=None, value=None, blueprint=None, store=True):
113 meta = Meta()116 meta = Meta()
117 meta.key = key
118 meta.value = value
119 meta.blueprint = blueprint
114 if store:120 if store:
115 self.store.add(meta)121 self.store.add(meta)
116 return meta122 return meta
@@ -155,3 +161,28 @@
155 if store:161 if store:
156 self.store.add(person)162 self.store.add(person)
157 return person163 return person
164
165 def make_blueprint_daily_count_per_state(self, status=None, count=1,
166 day=None, store=True):
167 if status is None:
168 status = self.getUniqueUnicode()
169 if day is None:
170 day = datetime.date.today()
171 obj = BlueprintDailyCountPerState()
172 obj.day = day
173 obj.status = status
174 obj.count = count
175 obj.lane_id = 1
176 if store:
177 self.store.add(obj)
178 return obj
179
180 def make_card(self, store=True):
181 name = self.getUniqueUnicode()
182 card_id = self.getUniqueInteger()
183 lane_id = self.getUniqueInteger()
184 roadmap_id = self.getUniqueUnicode()
185 card = Card(name, card_id, lane_id, roadmap_id)
186 if store:
187 self.store.add(card)
188 return card
158189
=== modified file 'lpworkitems/models.py'
--- lpworkitems/models.py 2011-12-07 09:03:27 +0000
+++ lpworkitems/models.py 2011-12-15 18:49:25 +0000
@@ -2,7 +2,15 @@
2import re2import re
3from utils import unicode_or_None3from utils import unicode_or_None
44
5from storm.locals import Date, Reference, ReferenceSet, Unicode5from storm.locals import Date, Int, Reference, ReferenceSet, Unicode
6
7ROADMAP_STATUSES_MAP = {
8 u'Completed': [u'Implemented'],
9 u'Blocked': [u'Needs Infrastructure', u'Blocked', u'Deferred'],
10 u'In Progress': [u'Deployment', u'Needs Code Review',
11 u'Beta Available', u'Good progress',
12 u'Slow progress', u'Started'],
13 u'Planned': [u'Unknown', u'Not started', u'Informational']}
614
715
8def fill_blueprint_info_from_launchpad(model_bp, lp_bp):16def fill_blueprint_info_from_launchpad(model_bp, lp_bp):
@@ -48,6 +56,14 @@
48 project = Unicode()56 project = Unicode()
4957
5058
59def get_roadmap_status_for_bp_implementation_status(implementation):
60 for key in ROADMAP_STATUSES_MAP:
61 if implementation in ROADMAP_STATUSES_MAP[key]:
62 return key
63 # XXX: Is None the appropriate return value here?
64 return None
65
66
51class Blueprint(object):67class Blueprint(object):
5268
53 __storm_table__ = "specs"69 __storm_table__ = "specs"
@@ -81,6 +97,15 @@
81 lp_bp.whiteboard, "Roadmap\s+Notes")97 lp_bp.whiteboard, "Roadmap\s+Notes")
82 return model_bp98 return model_bp
8399
100 @property
101 def roadmap_status(self):
102 return get_roadmap_status_for_bp_implementation_status(
103 self.implementation)
104
105
106def current_date():
107 return datetime.date.today()
108
84109
85class Person(object):110class Person(object):
86111
@@ -108,10 +133,6 @@
108 superteam_name = Unicode(name="team")133 superteam_name = Unicode(name="team")
109134
110135
111def current_date():
112 return datetime.date.today()
113
114
115class Meta(object):136class Meta(object):
116137
117 __storm_table__ = "meta"138 __storm_table__ = "meta"
118139
=== modified file 'lpworkitems/models_roadmap.py'
--- lpworkitems/models_roadmap.py 2011-11-03 15:48:13 +0000
+++ lpworkitems/models_roadmap.py 2011-12-15 18:49:25 +0000
@@ -4,6 +4,8 @@
44
5from storm.locals import Date, Reference, ReferenceSet, Unicode, Int, Bool5from storm.locals import Date, Reference, ReferenceSet, Unicode, Int, Bool
66
7from lpworkitems import models
8
79
8class Card(object):10class Card(object):
911
@@ -43,3 +45,16 @@
43 def __init__(self, name, lane_id):45 def __init__(self, name, lane_id):
44 self.lane_id = lane_id46 self.lane_id = lane_id
45 self.name = name47 self.name = name
48
49
50def current_date():
51 return datetime.date.today()
52
53
54class BlueprintDailyCountPerState(object):
55 __storm_table__ = 'spec_daily_count_per_state'
56 __storm_primary__ = 'status', 'day'
57 day = Date(default_factory=current_date)
58 status = Unicode()
59 lane_id = Int()
60 count = Int()
4661
=== modified file 'lpworkitems/tests/test_collect.py'
--- lpworkitems/tests/test_collect.py 2011-06-14 22:00:21 +0000
+++ lpworkitems/tests/test_collect.py 2011-12-15 18:49:25 +0000
@@ -184,7 +184,6 @@
184 self.store.find(184 self.store.find(
185 Milestone, Milestone.name == name).one())185 Milestone, Milestone.name == name).one())
186186
187
188 def test_store_blueprint_stores_blueprint(self):187 def test_store_blueprint_stores_blueprint(self):
189 blueprint = self.factory.make_blueprint(store=False)188 blueprint = self.factory.make_blueprint(store=False)
190 ret = self.collector.store_blueprint(blueprint)189 ret = self.collector.store_blueprint(blueprint)
191190
=== modified file 'lpworkitems/tests/test_collect_roadmap.py'
--- lpworkitems/tests/test_collect_roadmap.py 2011-12-13 09:24:21 +0000
+++ lpworkitems/tests/test_collect_roadmap.py 2011-12-15 18:49:25 +0000
@@ -1,15 +1,13 @@
1import datetime
2
1from lpworkitems.collect_roadmap import (3from lpworkitems.collect_roadmap import (
2 CollectorStore,4 CollectorStore,
3 get_json_item,5 get_json_item,
4 lookup_kanban_priority,
5 )6 )
7from lpworkitems.models_roadmap import BlueprintDailyCountPerState
6from lpworkitems.error_collector import (8from lpworkitems.error_collector import (
7 ErrorCollector,9 ErrorCollector,
8 )10 )
9from lpworkitems.models_roadmap import (
10 Card,
11 Lane,
12 )
13from lpworkitems.testing import TestCaseWithFakeLaunchpad11from lpworkitems.testing import TestCaseWithFakeLaunchpad
1412
1513
@@ -26,6 +24,26 @@
26 fn()24 fn()
27 self.assertEqual(0, self.store.find(cls).count())25 self.assertEqual(0, self.store.find(cls).count())
2826
27 def test_clear_todays_blueprint_daily_count_per_state(self):
28 self.factory.make_blueprint_daily_count_per_state(
29 day=datetime.date.today())
30 self.assertClears(
31 BlueprintDailyCountPerState,
32 self.collector.clear_todays_blueprint_daily_count_per_state)
33
34 def test_store_roadmap_bp_count_per_state(self):
35 bp = self.factory.make_blueprint()
36 card = self.factory.make_card()
37 meta = self.factory.make_meta(
38 key=u'Roadmap id', value=card.roadmap_id, blueprint=bp)
39 self.collector.store_roadmap_bp_count_per_state()
40 self.assertEqual(
41 1, self.store.find(BlueprintDailyCountPerState).count())
42 entry = self.store.find(BlueprintDailyCountPerState).one()
43 self.assertEqual(1, entry.count)
44 self.assertEqual(card.lane_id, entry.lane_id)
45 self.assertEqual(bp.implementation, entry.status)
46
29 # XXX Add tests for the roadmap classes.47 # XXX Add tests for the roadmap classes.
3048
3149
3250
=== modified file 'lpworkitems/tests/test_factory.py'
--- lpworkitems/tests/test_factory.py 2011-06-04 18:48:23 +0000
+++ lpworkitems/tests/test_factory.py 2011-12-15 18:49:25 +0000
@@ -162,7 +162,7 @@
162 implementation = u"Implemented"162 implementation = u"Implemented"
163 self.assert_with_and_without(163 self.assert_with_and_without(
164 self.factory.make_blueprint, "implementation", implementation,164 self.factory.make_blueprint, "implementation", implementation,
165 Equals(None))165 Equals("Unknown"))
166166
167 def test_uses_assignee_name(self):167 def test_uses_assignee_name(self):
168 assignee_name = self.factory.getUniqueUnicode(168 assignee_name = self.factory.getUniqueUnicode(
169169
=== modified file 'lpworkitems/tests/test_models.py'
--- lpworkitems/tests/test_models.py 2011-12-06 15:20:43 +0000
+++ lpworkitems/tests/test_models.py 2011-12-15 18:49:25 +0000
@@ -6,8 +6,11 @@
6 extract_last_path_segment_from_url,6 extract_last_path_segment_from_url,
7 extract_user_name_from_url,7 extract_user_name_from_url,
8 get_whiteboard_section,8 get_whiteboard_section,
9 )9 ROADMAP_STATUSES_MAP,
10from lpworkitems.testing import TestCaseWithFakeLaunchpad10 )
11from lpworkitems.testing import (
12 TestCaseWithFakeLaunchpad,
13 )
1114
1215
13class GetWhiteboardSectionTests(TestCase):16class GetWhiteboardSectionTests(TestCase):
@@ -42,6 +45,18 @@
4245
43class BlueprintTests(TestCaseWithFakeLaunchpad):46class BlueprintTests(TestCaseWithFakeLaunchpad):
4447
48 def test_roadmap_status(self):
49 roadmap_status = "Completed"
50 bp_implementation = ROADMAP_STATUSES_MAP[roadmap_status][0]
51 bp_status = self.factory.make_blueprint(
52 implementation=bp_implementation)
53 self.assertEqual(roadmap_status, bp_status.roadmap_status)
54
55 def test_roadmap_status_unknown_status(self):
56 blueprint = self.factory.make_blueprint(
57 implementation=u"Not Expected")
58 self.assertEqual(None, blueprint.roadmap_status)
59
45 def test_from_launchpad_sets_name(self):60 def test_from_launchpad_sets_name(self):
46 name = self.factory.getUniqueUnicode(prefix="lpblueprint")61 name = self.factory.getUniqueUnicode(prefix="lpblueprint")
47 lp_bp = self.lp.make_blueprint(name=name)62 lp_bp = self.lp.make_blueprint(name=name)
4863
=== modified file 'report_tools.py'
--- report_tools.py 2011-12-12 07:35:35 +0000
+++ report_tools.py 2011-12-15 18:49:25 +0000
@@ -14,7 +14,8 @@
14 Card,14 Card,
15)15)
16from lpworkitems.models import (16from lpworkitems.models import (
17 Meta,17 Meta, ROADMAP_STATUSES_MAP,
18 get_roadmap_status_for_bp_implementation_status,
18)19)
1920
20valid_states = [u'inprogress', u'blocked', u'todo', u'done', u'postponed']21valid_states = [u'inprogress', u'blocked', u'todo', u'done', u'postponed']
@@ -206,12 +207,17 @@
206def roadmap_pages(my_path, database, basename, config, lane, root=None):207def roadmap_pages(my_path, database, basename, config, lane, root=None):
207 cfg = load_config(config)208 cfg = load_config(config)
208 fh = open(basename + '.html', 'w')209 fh = open(basename + '.html', 'w')
210 # XXX fix this. the intention is to have the chart in the same dir as the
211 # html. This is not essential but makes linking in roadmap_lane.html easier
212 chart_name = '/'.join(basename.split('/')[:-1]) + '/current_quarter.svg'
209 try:213 try:
210 args = [os.path.join(my_path, 'html-report'), '-d', database]214 args = [os.path.join(my_path, 'html-report'), '-d', database]
211 args += ['--report-type', 'roadmap_page']215 args += ['--report-type', 'roadmap_page']
212 args += ['--lane', lane.name]216 args += ['--lane', lane.name]
213 if root:217 if root:
214 args += ['--root', root]218 args += ['--root', root]
219 if lane.is_current:
220 args += ['--chart', chart_name]
215 report_args(args, theme=get_theme(cfg))221 report_args(args, theme=get_theme(cfg))
216 proc = Popen(args, stdout=fh)222 proc = Popen(args, stdout=fh)
217 print basename + '.html'223 print basename + '.html'
@@ -219,6 +225,13 @@
219 finally:225 finally:
220 fh.close()226 fh.close()
221227
228 if lane.is_current:
229 args = [os.path.join(my_path, 'roadmap-bp-chart'), '-d', database,
230 '-o', chart_name]
231 proc = Popen(args)
232 print chart_name
233 proc.wait()
234
222235
223def roadmap_cards(my_path, database, basename, config, card, root=None):236def roadmap_cards(my_path, database, basename, config, card, root=None):
224 cfg = load_config(config)237 cfg = load_config(config)
@@ -341,6 +354,34 @@
341 return escape(html, True)354 return escape(html, True)
342355
343356
357def blueprints_over_time(store):
358 '''Calculate blueprint development over time for the current lane.
359
360 We do not need to care about teams or groups since this is intended for the
361 roadmap overview.
362
363 Return date -> state -> count mapping. states are
364 {planned,inprogress,completed,blocked}.
365 '''
366 data = {}
367 result = store.execute("""
368 SELECT status, day, count
369 FROM spec_daily_count_per_state
370 JOIN lane on lane.lane_id = spec_daily_count_per_state.lane_id
371 WHERE lane.is_current = 1
372 """)
373 for status, day, count in result:
374 roadmap_status = get_roadmap_status_for_bp_implementation_status(
375 status)
376 assert roadmap_status is not None
377 if day not in data:
378 data[day] = {}
379 if roadmap_status not in data[day]:
380 data[day][roadmap_status] = 0
381 data[day][roadmap_status] += count
382 return data
383
384
344def workitems_over_time(store, team=None, group=None, milestone_collection=None):385def workitems_over_time(store, team=None, group=None, milestone_collection=None):
345 '''Calculate work item development over time.386 '''Calculate work item development over time.
346387
@@ -978,21 +1019,11 @@
9781019
979def card_blueprints_by_status(store, roadmap_id):1020def card_blueprints_by_status(store, roadmap_id):
980 blueprints = card_blueprints(store, roadmap_id)1021 blueprints = card_blueprints(store, roadmap_id)
981 statuses = {'Completed': ['Implemented'],1022 bp_by_status = {}
982 'Blocked': ['Needs Infrastructure', 'Blocked', 'Deferred'],1023 for key in ROADMAP_STATUSES_MAP:
983 'In Progress': ['Deployment', 'Needs Code Review',1024 bp_by_status[key] = []
984 'Beta Available', 'Good progress',
985 'Slow progress', 'Started'],
986 'Planned': ['Unknown', 'Not started', 'Informational']}
987 bp_by_status = {'In Progress': [],
988 'Blocked': [],
989 'Planned': [],
990 'Completed': []}
991 for bp in blueprints:1025 for bp in blueprints:
992 for status in statuses.iterkeys():1026 bp_by_status[bp.roadmap_status].append(bp)
993 if bp.implementation in statuses[status]:
994 bp_by_status[status].append(bp)
995 break
996 return bp_by_status1027 return bp_by_status
9971028
9981029
9991030
=== added file 'roadmap-bp-chart'
--- roadmap-bp-chart 1970-01-01 00:00:00 +0000
+++ roadmap-bp-chart 2011-12-15 18:49:25 +0000
@@ -0,0 +1,254 @@
1#!/usr/bin/python
2#
3# Create a blueprint tracking chart from a blueprint database.
4#
5# Copyright (C) 2010, 2011 Canonical Ltd.
6# License: GPL-3
7
8import optparse, datetime, sys
9import report_tools
10
11from pychart import *
12
13def date_to_ordinal(s):
14 '''Turn yyyy-mm-dd strings to ordinals'''
15 return report_tools.date_to_python(s).toordinal()
16
17
18def ordinal_to_date(ordinal):
19 '''Turn an ordinal date into a string'''
20 d = datetime.date.fromordinal(int(ordinal))
21 return d.strftime('%Y-%m-%d')
22
23def format_date(ordinal):
24 d = datetime.date.fromordinal(int(ordinal))
25 return '/a60{}' + d.strftime('%b %d, %y')
26
27def do_chart(data, start_date, end_date, trend_start, title, filename, only_weekdays, inverted):
28 #set up default values
29 format = 'svg'
30 height = 450
31 width = 1000
32 legend_x = 700
33 legend_y = 200
34 title_x = 300
35 title_y = 350
36
37 if inverted:
38 legend_x=200
39
40 # Tell pychart to use colors
41 theme.use_color = True
42 theme.default_font_size = 12
43 theme.reinitialize()
44
45 # turn into pychart data model and calculate maximum number of WIs
46 max_items = 1 # start at 1 to avoid zero div
47 lastactive = 0
48 pcdata = []
49
50 for date in xrange(date_to_ordinal(start_date), date_to_ordinal(end_date)+1):
51 if (not only_weekdays or datetime.date.fromordinal(date).weekday() < 5):
52 end_date = ordinal_to_date(date)
53 i = data.get(ordinal_to_date(date), {})
54 count = i.get('Completed', 0) + i.get('Planned', 0) + i.get('Blocked', 0) + i.get('In Progress', 0)
55 if max_items < count:
56 max_items = count
57 pcdata.append((date, i.get('Planned', 0),0,
58 i.get('Blocked', 0),0,
59 i.get('In Progress', 0),0,
60 i.get('Completed',0),0, count))
61 if count > 0:
62 lastactive = len(pcdata) - 1
63
64 # add some extra space to look nicer
65 max_items = int(max_items * 1.05)
66
67 x_interval = len(pcdata)/20
68 if max_items > 500:
69 y_interval = max_items/200*10
70 elif max_items < 20:
71 y_interval = 1
72 else:
73 y_interval = max_items/20
74
75 # create the chart object
76 chart_object.set_defaults(area.T, size=(width, height),
77 y_range=(0, None), x_coord=category_coord.T(pcdata, 0))
78
79 # tell the chart object it will use a bar chart, and will
80 # use the data list for it's model
81 chart_object.set_defaults(bar_plot.T, data=pcdata)
82
83 # create the chart area
84 # tell it to start at coords 0,0
85 # tell it the labels, and the tics, etc..
86 # HACK: to prevent 0 div
87 if max_items == 0:
88 max_items = 1
89 ar = area.T(legend=legend.T(loc=(legend_x,legend_y)), loc=(0,0),
90 x_axis=axis.X(label='Date', tic_interval=x_interval,format=format_date),
91 y_axis=axis.Y(label='Blueprints', tic_interval=y_interval),
92 y_range=(0, max_items))
93
94 #initialize the blar_plot fill styles
95 bar_plot.fill_styles.reset()
96
97 # create each set of data to plot
98 # note that index zero is the label col
99 # for each column of data, tell it what to use for the legend and
100 # what color to make the bar, no lines, and
101 # what plot to stack on
102
103 tlabel = ''
104
105 if inverted:
106 plot1 = bar_plot.T(label='Completed' + tlabel, hcol=7)
107 plot1.fill_style = fill_style.Plain(bgcolor=color.seagreen)
108
109 plot3 = bar_plot.T(label='In Progress' + tlabel, hcol=5, stack_on = plot1)
110 plot3.fill_style = fill_style.Plain(bgcolor=color.gray65)
111
112 plot5 = bar_plot.T(label='Blocked' + tlabel, hcol=3, stack_on = plot3)
113 plot5.fill_style = fill_style.Plain(bgcolor=color.red1)
114
115 plot7 = bar_plot.T(label='Planned' + tlabel, hcol=1, stack_on = plot5)
116 plot7.fill_style = fill_style.Plain(bgcolor=color.darkorange1)
117 else:
118 plot1 = bar_plot.T(label='Planned' + tlabel, hcol=1)
119 plot1.fill_style = fill_style.Plain(bgcolor=color.darkorange1)
120
121 plot3 = bar_plot.T(label='Blocked' + tlabel, hcol=3, stack_on = plot1)
122 plot3.fill_style = fill_style.Plain(bgcolor=color.red1)
123
124 plot5 = bar_plot.T(label='In Progress' + tlabel, hcol=5, stack_on = plot3)
125 plot5.fill_style = fill_style.Plain(bgcolor=color.gray65)
126
127 plot7 = bar_plot.T(label='Completed' + tlabel, hcol=7, stack_on = plot5)
128 plot7.fill_style = fill_style.Plain(bgcolor=color.seagreen)
129
130
131 plot1.line_style = None
132 plot3.line_style = None
133 plot5.line_style = None
134 plot7.line_style = None
135
136 plot11 = bar_plot.T(label='total', hcol=9)
137 plot11.fill_style = None
138 plot11.line_style = line_style.gray30
139
140 # create the canvas with the specified filename and file format
141 can = canvas.init(filename,format)
142
143 # add the data to the area and draw it
144 ar.add_plot(plot1, plot3, plot5, plot7)
145 ar.draw()
146
147 # title
148 tb = text_box.T(loc=(title_x, title_y), text=title, line_style=None)
149 tb.fill_style = None
150 tb.draw()
151
152#
153# main
154#
155
156# argv parsing
157optparser = optparse.OptionParser()
158optparser.add_option('-d', '--database',
159 help='Path to database', dest='database', metavar='PATH')
160optparser.add_option('-t', '--team',
161 help='Restrict report to a particular team', dest='team')
162optparser.add_option('-m', '--milestone',
163 help='Restrict report to a particular milestone', dest='milestone')
164optparser.add_option('-o', '--output',
165 help='Output file', dest='output')
166optparser.add_option('--trend-start', type='int',
167 help='Explicitly set start of trend line', dest='trendstart')
168optparser.add_option('-u', '--user',
169 help='Run for this user', dest='user')
170optparser.add_option('--only-weekdays', action='store_true',
171 help='Skip Saturdays and Sundays in the resulting graph', dest='only_weekdays')
172optparser.add_option('--inverted', action='store_true',
173 help='Generate an inverted burndown chart', dest='inverted')
174optparser.add_option('-s', '--start-date',
175 help='Explicitly set the start date of the burndown data', dest='start_date')
176optparser.add_option('-e', '--end-date',
177 help='Explicitly set the end date of the burndown data', dest='end_date')
178optparser.add_option('--no-foreign', action='store_true', default=False,
179 help='Do not show foreign totals separate', dest='noforeign')
180optparser.add_option('--group',
181 help='Run for this group', dest='group')
182optparser.add_option('--date',
183 help='Run for this date', dest='date')
184
185(opts, args) = optparser.parse_args()
186if not opts.database:
187 optparser.error('No database given')
188if not opts.output:
189 optparser.error('No output file given')
190
191if opts.user and opts.team:
192 optparser.error('team and user options are mutually exclusive')
193if opts.user and opts.group:
194 optparser.error('user and group options are mutually exclusive')
195if opts.team and opts.group:
196 optparser.error('team and group options are mutually exclusive')
197if opts.milestone and opts.date:
198 optparser.error('milestone and date options are mutually exclusive')
199
200# The typing allows polymorphic behavior
201if opts.user:
202 opts.team = report_tools.user_string(opts.user)
203elif opts.team:
204 opts.team = report_tools.team_string(opts.team)
205
206store = report_tools.get_store(opts.database)
207
208milestone_collection = None
209if opts.milestone:
210 milestone_collection = report_tools.get_milestone(store, opts.milestone)
211elif opts.date:
212 milestone_collection = report_tools.MilestoneGroup(
213 report_tools.date_to_python(opts.date))
214
215
216# get date -> state -> count mapping
217data = report_tools.blueprints_over_time(store)
218
219if len(data) == 0:
220 print 'WARNING: no blueprints, not generating chart (team: %s, group: %s, due date: %s)' % (
221 opts.team or 'all', opts.group or 'none', milestone_collection and milestone_collection.display_name or 'none')
222 sys.exit(0)
223
224# calculate start/end date if no dates are given
225if opts.start_date is None:
226 start_date = sorted(data.keys())[0]
227else:
228 start_date=opts.start_date
229
230if opts.end_date is None:
231 if milestone_collection is not None:
232 end_date = milestone_collection.due_date_str
233 else:
234 end_date=report_tools.milestone_due_date(store)
235else:
236 end_date=opts.end_date
237
238if not start_date or not end_date or date_to_ordinal(start_date) > date_to_ordinal(end_date):
239 print 'WARNING: empty date range, not generating chart (team: %s, group: %s, due date: %s)' % (
240 opts.team or 'all', opts.group or 'none', milestone_collection and milestone_collection.display_name or 'none')
241 sys.exit(0)
242
243# title
244if opts.team:
245 title = '/20' + opts.team
246elif opts.group:
247 title = "/20" + opts.group
248else:
249 title = '/20all teams'
250
251if milestone_collection is not None:
252 title += ' (%s)' % milestone_collection.name
253
254do_chart(data, start_date, end_date, opts.trendstart, title, opts.output, opts.only_weekdays, opts.inverted)
0255
=== modified file 'templates/roadmap_lane.html'
--- templates/roadmap_lane.html 2011-12-12 09:17:20 +0000
+++ templates/roadmap_lane.html 2011-12-15 18:49:25 +0000
@@ -41,3 +41,12 @@
41% endfor41% endfor
42</table>42</table>
4343
44% if chart_url != 'burndown.svg':
45<!-- The cli option defaults to burndown.svg! :( -->
46<div class="overview_graph">
47<h3>Blueprint progress</h3><p><a href="current_quarter.svg">(enlarge)</a></p>
48<object
49 height="500" width="833"
50 data="current_quarter.svg" type="image/svg+xml">Blueprint progress</object>
51</div>
52% endif

Subscribers

People subscribed via source and target branches