Merge lp:~james-w/launchpad-work-items-tracker/date-based into lp:~linaro-automation/launchpad-work-items-tracker/linaro

Proposed by James Westby
Status: Superseded
Proposed branch: lp:~james-w/launchpad-work-items-tracker/date-based
Merge into: lp:~linaro-automation/launchpad-work-items-tracker/linaro
Diff against target: 526 lines (+176/-73)
6 files modified
burndown-chart (+16/-7)
generate-all (+12/-2)
html-report (+12/-24)
report_tools.py (+84/-30)
templates/base.html (+20/-0)
templates/milestone_list.html (+32/-10)
To merge this branch: bzr merge lp:~james-w/launchpad-work-items-tracker/date-based
Reviewer Review Type Date Requested Status
Michael Hudson-Doyle (community) Abstain
Review via email: mp+52895@code.launchpad.net

This proposal has been superseded by a proposal from 2011-03-17.

Description of the change

Hi,

This branch adds a new way of viewing milestones, by target
date rather than by milestone.

This means that you can have a page that shows info for all
milestones that land on the same day.

This is desirable, as the monthly milestones we will be moving
to may be named differently in different projects, but engineers
will want to see everything that they have to do for a particular
day on one page.

Thanks,

James

To post a comment you must log in.
Revision history for this message
Michael Hudson-Doyle (mwhudson) wrote :

I don't think I understand the code well enough to say whether these changes make sense, I'm afraid. Happy to talk through the changes on mumble.

The sheer number of places that have to be changed to accept a date argument suggests that the layering in this code isn't really right, but I don't think that's a surprise to learn somehow :-)

review: Abstain
283. By James Westby

Rework to use polymorpism for milestone due dates.

284. By James Westby

Changes from review comments. Thanks Michael.

285. By James Westby

Comments from review. Thanks Michael.

Unmerged revisions

279. By James Westby

Set the right status for each bug task workitem.

278. By James Westby

Allow workitems from projects other than Ubuntu.

277. By James Westby

Correct a copy-paste error that stopped the new workitem list pages from being generated.

276. By James Westby

Generate pages for each workitem status.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'burndown-chart'
--- burndown-chart 2011-02-16 23:59:52 +0000
+++ burndown-chart 2011-03-10 18:14:55 +0000
@@ -247,6 +247,8 @@
247 help='Do not show foreign totals separate', dest='noforeign')247 help='Do not show foreign totals separate', dest='noforeign')
248optparser.add_option('--group',248optparser.add_option('--group',
249 help='Run for this group', dest='group')249 help='Run for this group', dest='group')
250optparser.add_option('--date',
251 help='Run for this date', dest='date')
250252
251(opts, args) = optparser.parse_args()253(opts, args) = optparser.parse_args()
252if not opts.database:254if not opts.database:
@@ -260,6 +262,8 @@
260 optparser.error('user and group options are mutually exclusive')262 optparser.error('user and group options are mutually exclusive')
261if opts.team and opts.group:263if opts.team and opts.group:
262 optparser.error('team and group options are mutually exclusive')264 optparser.error('team and group options are mutually exclusive')
265if opts.milestone and opts.date:
266 optparser.error('milestone and date options are mutually exclusive')
263267
264# The typing allows polymorphic behavior268# The typing allows polymorphic behavior
265if opts.user:269if opts.user:
@@ -269,11 +273,11 @@
269273
270# get date -> state -> count mapping274# get date -> state -> count mapping
271db = report_tools.get_db(opts.database)275db = report_tools.get_db(opts.database)
272data = report_tools.workitems_over_time(db, team=opts.team, milestone=opts.milestone, group=opts.group)276data = report_tools.workitems_over_time(db, team=opts.team, milestone=opts.milestone, group=opts.group, date=opts.date)
273277
274if len(data) == 0:278if len(data) == 0:
275 print 'WARNING: no work items, not generating chart (team: %s, milestone: %s, group: %s)' % (279 print 'WARNING: no work items, not generating chart (team: %s, milestone: %s, group: %s, date: %s)' % (
276 opts.team or 'all', opts.milestone or 'none', opts.group or 'none')280 opts.team or 'all', opts.milestone or 'none', opts.group or 'none', opts.date or 'none')
277 sys.exit(0)281 sys.exit(0)
278282
279# calculate start/end date if no dates are given283# calculate start/end date if no dates are given
@@ -283,14 +287,17 @@
283 start_date=opts.start_date287 start_date=opts.start_date
284288
285if opts.end_date is None:289if opts.end_date is None:
286 cur = db.cursor()290 if opts.date:
287 end_date=report_tools.milestone_due_date(db, milestone=opts.milestone)291 end_date = opts.date
292 else:
293 cur = db.cursor()
294 end_date=report_tools.milestone_due_date(db, milestone=opts.milestone)
288else:295else:
289 end_date=opts.end_date296 end_date=opts.end_date
290297
291if not start_date or not end_date or date_to_ordinal(start_date) > date_to_ordinal(end_date):298if not start_date or not end_date or date_to_ordinal(start_date) > date_to_ordinal(end_date):
292 print 'WARNING: empty date range, not generating chart (team: %s, milestone: %s)' % (299 print 'WARNING: empty date range, not generating chart (team: %s, milestone: %s, group: %s, date: %s)' % (
293 opts.team or 'all', opts.milestone or 'none')300 opts.team or 'all', opts.milestone or 'none', opts.group or 'none', opts.date or 'none')
294 sys.exit(0)301 sys.exit(0)
295302
296# title303# title
@@ -307,6 +314,8 @@
307314
308if opts.milestone:315if opts.milestone:
309 title += ' (%s)' % opts.milestone316 title += ' (%s)' % opts.milestone
317elif opts.date:
318 title += ' (%s)' % opts.date
310319
311do_chart(data, start_date, end_date, opts.trendstart, title, opts.output, opts.only_weekdays, opts.inverted, noforeign)320do_chart(data, start_date, end_date, opts.trendstart, title, opts.output, opts.only_weekdays, opts.inverted, noforeign)
312321
313322
=== modified file 'generate-all'
--- generate-all 2011-02-26 00:57:42 +0000
+++ generate-all 2011-03-10 18:14:55 +0000
@@ -34,11 +34,15 @@
34 optparser.error('output directory does not exist')34 optparser.error('output directory does not exist')
3535
36if opts.config:36if opts.config:
37 trend_starts = report_tools.load_config(opts.config).get('trend_start', {})37 cfg = report_tools.load_config(opts.config)
38 burnup_chart_teams = report_tools.load_config(opts.config).get('burnup_chart_teams', [])38 trend_starts = cfg.get('trend_start', {})
39 burnup_chart_teams = cfg.get('burnup_chart_teams', [])
40 primary_team = cfg.get("primary_team", None)
39else:41else:
42 cfg = None
40 trend_starts = {}43 trend_starts = {}
41 burnup_chart_teams = []44 burnup_chart_teams = []
45 primary_team = None
4246
43lock_path = opts.database + ".generate_lock"47lock_path = opts.database + ".generate_lock"
44lock_f = open(lock_path, "wb")48lock_f = open(lock_path, "wb")
@@ -104,6 +108,12 @@
104 else:108 else:
105 report_tools.run_reports(my_path, opts.database, basename, opts.config, milestone=m, team=None, root=opts.root)109 report_tools.run_reports(my_path, opts.database, basename, opts.config, milestone=m, team=None, root=opts.root)
106110
111# date-based milestone view
112for mg in report_tools.milestone_groups(db, team=primary_team):
113 due_date_str = mg.due_date_str
114 basename = os.path.join(opts.output_dir, due_date_str)
115 report_tools.run_reports(my_path, opts.database, basename, opts.config, team=primary_team, root=opts.root, date=due_date_str)
116
107# groups117# groups
108for g in groups:118for g in groups:
109 basename = os.path.join(groupssubdir, g)119 basename = os.path.join(groupssubdir, g)
110120
=== modified file 'html-report'
--- html-report 2011-02-28 15:59:50 +0000
+++ html-report 2011-03-10 18:14:55 +0000
@@ -105,8 +105,8 @@
105 return self._assignee or self.blueprint.assignee105 return self._assignee or self.blueprint.assignee
106106
107107
108def spec_group_completion(db, team, milestone):108def spec_group_completion(db, team, milestone=None, date=None):
109 data = report_tools.spec_group_completion(db, team, milestone)109 data = report_tools.spec_group_completion(db, team, milestone=milestone, date=date)
110 if not data:110 if not data:
111 return dict(areas=[], group_completion_series=[], groups=[])111 return dict(areas=[], group_completion_series=[], groups=[])
112 groups = []112 groups = []
@@ -250,25 +250,6 @@
250 return dict(members=sorted(members))250 return dict(members=sorted(members))
251251
252252
253class Milestone(object):
254
255 def __init__(self, name, link, project):
256 self.name = name
257 self.link = link
258 self.project = project
259
260
261def milestones_info(db, team, milestone):
262 if team:
263 template = team + "-%s.html"
264 else:
265 template = "%s.html"
266 milestones = []
267 for milestone, project in report_tools.milestone_list(db).items():
268 milestones.append(Milestone(milestone, template % milestone, project))
269 return dict(milestones=milestones)
270
271
272def blueprint_count(db, team, milestone):253def blueprint_count(db, team, milestone):
273 return dict(blueprint_count=report_tools.blueprint_count(db, team=team, milestone=milestone))254 return dict(blueprint_count=report_tools.blueprint_count(db, team=team, milestone=milestone))
274255
@@ -360,13 +341,15 @@
360341
361 if opts.milestone:342 if opts.milestone:
362 title += ' (%s)' % opts.milestone343 title += ' (%s)' % opts.milestone
344 elif opts.date:
345 title += ' (%s)' % opts.date
363346
364 data = self.template_data(db, opts)347 data = self.template_data(db, opts)
365 data.update(dict(team_name=title, chart_url=opts.chart_url))348 data.update(dict(team_name=title, chart_url=opts.chart_url))
366 data.update(spec_group_completion(db, opts.team, opts.milestone))349 data.update(spec_group_completion(db, opts.team, opts.milestone))
367 data.update(spec_completion(db, opts.team, opts.milestone))350 data.update(spec_completion(db, opts.team, opts.milestone))
368 data.update(by_assignee(db, opts.team, opts.milestone))351 data.update(by_assignee(db, opts.team, opts.milestone))
369 if opts.milestone:352 if opts.milestone or opts.date:
370 data.update(dict(page_type="milestones"))353 data.update(dict(page_type="milestones"))
371 elif isinstance(opts.team, report_tools.user_string):354 elif isinstance(opts.team, report_tools.user_string):
372 data.update(dict(page_type="people"))355 data.update(dict(page_type="people"))
@@ -390,7 +373,6 @@
390 data.update(dict(team_name=title, chart_url=opts.chart_url))373 data.update(dict(team_name=title, chart_url=opts.chart_url))
391 data.update(spec_group_completion(db, None, opts.milestone))374 data.update(spec_group_completion(db, None, opts.milestone))
392 data.update(spec_completion(db, opts.team, opts.milestone))375 data.update(spec_completion(db, opts.team, opts.milestone))
393 data.update(milestones_info(db, opts.team, opts.milestone))
394 data.update(blueprint_count(db, opts.team, opts.milestone))376 data.update(blueprint_count(db, opts.team, opts.milestone))
395 data.update(dict(page_type="overview"))377 data.update(dict(page_type="overview"))
396 print report_tools.fill_template("overview.html", data)378 print report_tools.fill_template("overview.html", data)
@@ -423,7 +405,9 @@
423405
424 def milestone_list(self, db, opts):406 def milestone_list(self, db, opts):
425 data = self.template_data(db, opts)407 data = self.template_data(db, opts)
426 data.update(milestones_info(db, opts.team, opts.milestone))408 data.update(
409 milestone_groups=report_tools.milestone_groups(
410 db, team=opts.team))
427 data.update(dict(page_type="milestones"))411 data.update(dict(page_type="milestones"))
428 print report_tools.fill_template("milestone_list.html", data)412 print report_tools.fill_template("milestone_list.html", data)
429413
@@ -474,12 +458,16 @@
474 help="Root URL for the charts")458 help="Root URL for the charts")
475 optparser.add_option('--status', dest="status",459 optparser.add_option('--status', dest="status",
476 help="Workitem status to consider for workitem_list report-type")460 help="Workitem status to consider for workitem_list report-type")
461 optparser.add_option('--date', dest="date",
462 help="Include all milestones targetted to this date.")
477463
478 (opts, args) = optparser.parse_args()464 (opts, args) = optparser.parse_args()
479 if not opts.database:465 if not opts.database:
480 optparser.error('No database given')466 optparser.error('No database given')
481 if opts.user and opts.team:467 if opts.user and opts.team:
482 optparser.error('team and user options are mutually exclusive')468 optparser.error('team and user options are mutually exclusive')
469 if opts.milestone and opts.date:
470 optparser.error('milestone and date options are mutually exclusive')
483471
484 # The typing allows polymorphic behavior472 # The typing allows polymorphic behavior
485 if opts.user:473 if opts.user:
486474
=== modified file 'report_tools.py'
--- report_tools.py 2011-02-28 18:18:24 +0000
+++ report_tools.py 2011-03-10 18:14:55 +0000
@@ -24,13 +24,15 @@
24def escape_sql(text):24def escape_sql(text):
25 return text.replace("'", "''")25 return text.replace("'", "''")
2626
27def report_args(args, team=None, user=None, milestone=None):27def report_args(args, team=None, user=None, milestone=None, date=None):
28 if milestone:28 if milestone:
29 args.extend(['-m',milestone])29 args.extend(['-m',milestone])
30 if team:30 if team:
31 args.extend(['-t',team])31 args.extend(['-t',team])
32 if user:32 if user:
33 args.extend(['-u',user])33 args.extend(['-u',user])
34 if date:
35 args.extend(['--date', date])
3436
35def status_overview(my_path, database, basename, config, root=None):37def status_overview(my_path, database, basename, config, root=None):
36 cfg = load_config(config)38 cfg = load_config(config)
@@ -166,13 +168,17 @@
166168
167169
168def run_reports(my_path, database, basename, config, milestone=None, team=None,170def run_reports(my_path, database, basename, config, milestone=None, team=None,
169 user=None, trend_starts=None, trend_override=None, burnup=False, root=None):171 user=None, trend_starts=None, trend_override=None, burnup=False, root=None, date=None):
170172
171 ident = (team or user)173 ident = (team or user)
172 if milestone and ident:174 if milestone and ident:
173 chartname = ident + '-' + milestone175 chartname = ident + '-' + milestone
174 elif milestone:176 elif milestone:
175 chartname = 'all-' + milestone177 chartname = 'all-' + milestone
178 elif date and ident:
179 chartname = "%s-%s" % (date, milestone)
180 elif date:
181 chartname = 'all-' + date
176 elif ident:182 elif ident:
177 chartname = ident183 chartname = ident
178 else:184 else:
@@ -186,16 +192,17 @@
186 args += ['--title', cfg['title']]192 args += ['--title', cfg['title']]
187 if root:193 if root:
188 args += ['--root', root]194 args += ['--root', root]
189 report_args(args, team, user, milestone)195 report_args(args, team=team, user=user, milestone=milestone, date=date)
190 hreport = Popen(args, stdout=hf)196 hreport = Popen(args, stdout=hf)
191197
192 jf = open(basename + '.json', 'w')198 if not date:
193 args = [os.path.join(my_path, 'json-report'), '-d', database, '-c', config]199 jf = open(basename + '.json', 'w')
194 report_args(args, team, user, milestone)200 args = [os.path.join(my_path, 'json-report'), '-d', database, '-c', config]
195 jreport = Popen(args, stdout=jf)201 report_args(args, team=team, user=user, milestone=milestone)
202 jreport = Popen(args, stdout=jf)
196203
197 args = [os.path.join(my_path, 'burndown-chart'), '-d', database, '-o', basename + '.svg']204 args = [os.path.join(my_path, 'burndown-chart'), '-d', database, '-o', basename + '.svg']
198 report_args(args, team, user, milestone)205 report_args(args, team=team, user=user, milestone=milestone, date=date)
199206
200 if trend_override:207 if trend_override:
201 args.extend(['--trend-start', trend_override])208 args.extend(['--trend-start', trend_override])
@@ -203,7 +210,7 @@
203 if milestone and ident:210 if milestone and ident:
204 if (ident, milestone) in trend_starts:211 if (ident, milestone) in trend_starts:
205 args.extend(['--trend-start', str(trend_starts[(ident, milestone)])])212 args.extend(['--trend-start', str(trend_starts[(ident, milestone)])])
206 elif ident:213 elif ident and not date:
207 if ident in trend_starts:214 if ident in trend_starts:
208 args.extend(['--trend-start', str(trend_starts[ident])])215 args.extend(['--trend-start', str(trend_starts[ident])])
209 if burnup:216 if burnup:
@@ -259,7 +266,7 @@
259 return escape(html, True)266 return escape(html, True)
260267
261268
262def get_milestone_start_sql(db, milestone):269def get_milestone_start_sql(db, milestone=None, date=None):
263 if milestone:270 if milestone:
264 # get milestone date range271 # get milestone date range
265 cur = db.cursor()272 cur = db.cursor()
@@ -279,12 +286,21 @@
279 else:286 else:
280 ms_sql = "AND w.milestone='%s' AND date <= '%s'" % (287 ms_sql = "AND w.milestone='%s' AND date <= '%s'" % (
281 escape_sql(milestone), date_end)288 escape_sql(milestone), date_end)
289 elif date:
290 cur = db.cursor()
291 cur.execute('SELECT MAX(due_date), MAX(due_date) < date(CURRENT_TIMESTAMP) FROM milestones WHERE due_date < ?', (escape_sql(date), ))
292 (date_start, in_milestone) = cur.fetchone()
293 if in_milestone:
294 ms_sql = "AND date >= '%s' AND date <= '%s'" % (date_start, escape_sql(date))
295 else:
296 ms_sql = "AND date <= '%s'" % escape_sql(date)
297 ms_sql += "AND w.milestone IN (SELECT m.name from milestones m WHERE m.due_date = '%s')" % escape_sql(date)
282 else:298 else:
283 ms_sql = ''299 ms_sql = ''
284 return ms_sql300 return ms_sql
285301
286302
287def workitems_over_time(db, team=None, milestone=None, group=None):303def workitems_over_time(db, team=None, milestone=None, group=None, date=None):
288 '''Calculate work item development over time.304 '''Calculate work item development over time.
289305
290 If team is given, this filters out blueprints for that particular team. If306 If team is given, this filters out blueprints for that particular team. If
@@ -296,7 +312,7 @@
296 {todo,inprogress,done,postponed}{,_teamonly}.312 {todo,inprogress,done,postponed}{,_teamonly}.
297 '''313 '''
298 data = {}314 data = {}
299 ms_sql = get_milestone_start_sql(db, milestone)315 ms_sql = get_milestone_start_sql(db, milestone=milestone, date=date)
300 # include both direct team assignments, as well as assignmets to316 # include both direct team assignments, as well as assignmets to
301 # members of that team317 # members of that team
302 cur = db.cursor()318 cur = db.cursor()
@@ -361,14 +377,20 @@
361 data.setdefault(date, {})[s] = num377 data.setdefault(date, {})[s] = num
362 return data378 return data
363379
364def spec_group_completion(db, team=None, milestone=None):380def milestone_select_sql(milestone=None, date=None):
365 data = {}381 if not milestone and not date:
366 if milestone == '':
367 ms_sql = "AND w.milestone IS NULL"382 ms_sql = "AND w.milestone IS NULL"
368 elif milestone:383 elif milestone:
369 ms_sql = "AND w.milestone='%s'" % escape_sql(milestone)384 ms_sql = "AND w.milestone='%s'" % escape_sql(milestone)
385 elif date:
386 ms_sql = "AND w.milestone IN (SELECT m.name from milestones m WHERE m.due_date = '%s'" % date
370 else:387 else:
371 ms_sql = ''388 ms_sql = ''
389 return ms_sql
390
391def spec_group_completion(db, team=None, milestone=None, date=None):
392 data = {}
393 ms_sql = milestone_select_sql(milestone=milestone, date=date)
372394
373 # last date395 # last date
374 cur = db.cursor()396 cur = db.cursor()
@@ -451,19 +473,14 @@
451 return info473 return info
452474
453475
454def blueprint_completion(db, team=None, milestone=None):476def blueprint_completion(db, team=None, milestone=None, date=None):
455 '''Determine current blueprint completion.477 '''Determine current blueprint completion.
456478
457 Return blueprint -> info mapping, with info being a map with these479 Return blueprint -> info mapping, with info being a map with these
458 keys: todo, inprogress, done, postponed, status, priority, implementation, url.480 keys: todo, inprogress, done, postponed, status, priority, implementation, url.
459 '''481 '''
460 data = {}482 data = {}
461 if milestone == '':483 ms_sql = milestone_select_sql(milestone=milestone, date=date)
462 ms_sql = "AND w.milestone IS NULL"
463 elif milestone:
464 ms_sql = "AND w.milestone='%s'" % escape_sql(milestone)
465 else:
466 ms_sql = ''
467484
468 # last date485 # last date
469 cur = db.cursor()486 cur = db.cursor()
@@ -651,7 +668,7 @@
651668
652669
653def milestone_start_date(db, milestone=None):670def milestone_start_date(db, milestone=None):
654 ms_sql = get_milestone_start_sql(db, milestone)671 ms_sql = get_milestone_start_sql(db, milestone=milestone)
655 cur = db.cursor()672 cur = db.cursor()
656 cur.execute('SELECT MIN(date) FROM work_items w '673 cur.execute('SELECT MIN(date) FROM work_items w '
657 'WHERE 1=1 %s GROUP BY date' % ms_sql)674 'WHERE 1=1 %s GROUP BY date' % ms_sql)
@@ -673,12 +690,13 @@
673 cycle_percent_elapsed = duration_elapsed(690 cycle_percent_elapsed = duration_elapsed(
674 start_date, due_date, current_date)691 start_date, due_date, current_date)
675 return dict(692 return dict(
693 current_date=current_date,
676 days_left=weekdays_between(current_date, due_date),694 days_left=weekdays_between(current_date, due_date),
677 cycle_percent_elapsed=cycle_percent_elapsed,695 cycle_percent_elapsed=cycle_percent_elapsed,
678 )696 )
679697
680698
681def assignee_completion(db, team=None, milestone=None, group=None):699def assignee_completion(db, team=None, milestone=None, group=None, date=None):
682 '''Determine current by-assignee completion.700 '''Determine current by-assignee completion.
683701
684 Return assignee -> info mapping with info being a map with these702 Return assignee -> info mapping with info being a map with these
@@ -686,12 +704,7 @@
686 workitem, priority, spec_url].704 workitem, priority, spec_url].
687 '''705 '''
688 data = {}706 data = {}
689 if milestone == '':707 ms_sql = milestone_select_sql(milestone=milestone, date=date)
690 ms_sql = "AND w.milestone IS NULL"
691 elif milestone:
692 ms_sql = "AND w.milestone='%s'" % milestone
693 else:
694 ms_sql = ''
695708
696 # last date709 # last date
697 cur = db.cursor()710 cur = db.cursor()
@@ -904,6 +917,47 @@
904 return milestones917 return milestones
905918
906919
920class Milestone(object):
921
922 def __init__(self, name, link, project, due_date):
923 self.name = name
924 self.link = link
925 self.project = project
926 self.due_date = due_date
927
928
929class MilestoneGroup(object):
930
931 def __init__(self, due_date):
932 self.due_date = due_date
933 self.milestones = []
934
935 @property
936 def due_date_str(self):
937 return self.due_date.strftime("%Y-%m-%d")
938
939
940def milestone_groups(db, team=None):
941 if team:
942 template = team + "-%s.html"
943 else:
944 template = "%s.html"
945 groups = []
946 cur = db.cursor()
947 cur.execute('SELECT m.name, m.project, m.due_date '
948 'FROM milestones m ORDER BY m.due_date, m.name, m.project')
949 for name, project, due_date_str in cur:
950 due_date = date_to_python(due_date_str)
951 if len(groups) < 1 or due_date != groups[-1].due_date:
952 group = MilestoneGroup(due_date)
953 groups.append(group)
954 else:
955 group = groups[-1]
956 group.milestones.append(
957 Milestone(name, template % name, project, due_date))
958 return groups
959
960
907def real_names(db):961def real_names(db):
908 cur = db.cursor()962 cur = db.cursor()
909 cur.execute('SELECT name, display_name FROM real_names')963 cur.execute('SELECT name, display_name FROM real_names')
910964
=== modified file 'templates/base.html'
--- templates/base.html 2011-02-28 15:59:50 +0000
+++ templates/base.html 2011-03-10 18:14:55 +0000
@@ -12,6 +12,26 @@
12 border-width: 1px; padding-right: 10px; }12 border-width: 1px; padding-right: 10px; }
13 table#byworkitem td { vertical-align: top; }13 table#byworkitem td { vertical-align: top; }
1414
15 table#milestones {
16 border-style: none;
17 }
18
19 table#milestones td.date {
20 border-left: 3px solid #98C13D;
21 }
22
23 table#milestones td {
24 vertical-align: bottom;
25 border-style: none;
26 border-bottom: none;
27 padding-left: 0px;
28 margin-left: 0px;
29 }
30
31 table#milestones td span.connector{
32 color: #98C13D;
33 }
34
15 td.priority_Undefined, th.priority_Undefined { color: gray; }35 td.priority_Undefined, th.priority_Undefined { color: gray; }
16 td.priority_Low, th.priority_Low { color: blue; }36 td.priority_Low, th.priority_Low { color: blue; }
17 td.priority_Medium, th.priority_Medium { color: #f60; }37 td.priority_Medium, th.priority_Medium { color: #f60; }
1838
=== modified file 'templates/milestone_list.html'
--- templates/milestone_list.html 2011-02-06 20:10:39 +0000
+++ templates/milestone_list.html 2011-03-10 18:14:55 +0000
@@ -7,16 +7,38 @@
77
8<h1>Milestones</h1>8<h1>Milestones</h1>
99
10<p>These are all of the milestones during the current cycle. Click on any one of them to see10<p>Here is a timeline of all the milestones during the current cycle. Click on any one of them to see
11the tasks that are assigned to be completed by that milestone.</p>11the tasks that are assigned to be completed by that milestone.
12The next milestone is highlighted by the marker on the left of the timeline.</p>
1213
13% if milestones:14% if milestone_groups:
14<ul>15<table id="milestones">
15% for milestone in milestones:16<% last_group = None %>
16<li><a href="${base.url(milestone.link)}">${milestone.name} (${milestone.project})</a></li>17% for group in milestone_groups:
17% endfor18<tr
18</ul>19## Make the height of the row proportional to the duration between this milestone
20## and the previous. The height will not shrink below the contents though, so
21## there is no worry about very short durations.
22% if last_group is not None:
23height="${(group.due_date.toordinal() - last_group.due_date.toordinal())*2}px"
24% endif
25
26>
27 <td class="current_date_marker">
28## Point at the first milestone that is after the current date
29% if group.due_date >= current_date and (last_group is None or last_group.due_date < current_date):
30&gt;
31% endif
32 </td>
33 <td class="date"><span class="connector">-</span> <a href="${base.url(group.due_date_str+".html")}">${group.due_date}</a></td>
34 <td class="milestones">
35% for milestone in group.milestones:
36<a href="${base.url(milestone.link)}">${milestone.name} (${milestone.project})</a>
37% endfor
38 </td>
39<% last_group = group %>
40% endfor
41</table>
19% else:42% else:
20<p>No teams!</p>43<p>No milestones!</p>
21% endif44% endif
22

Subscribers

People subscribed via source and target branches

to all changes: