• R/O
  • SSH
  • HTTPS

traclight: コミット


コミットメタ情報

リビジョン121 (tree)
日時2010-12-13 21:33:29
作者okamototk

ログメッセージ

fixed burndownchart is broken with multi component and fixed term.

変更サマリ

差分

--- branches/tl3_0/plugins/svn/scrumburndownplugin/burndown/burndown.py (revision 120)
+++ branches/tl3_0/plugins/svn/scrumburndownplugin/burndown/burndown.py (revision 121)
@@ -1,399 +1,396 @@
1-# Burndown plugin
2-# Copyright (C) 2006 Sam Bloomquist <spooninator@hotmail.com>
3-# Copyright (C) 2006-2008 Daan van Etten <daan@stuq.nl>
4-# All rights reserved.
5-
6-# This software may at some point consist of voluntary contributions made by
7-# many individuals. For the exact contribution history, see the revision
8-# history and logs, available at http://projects.edgewall.com/trac/.
9-#
10-# Author: Sam Bloomquist <spooninator@hotmail.com>
11-# Author: Daan van Etten <daan@stuq.nl>
12-
13-import time
14-import datetime
15-import sys
16-
17-import dbhelper
18-
19-from trac import __version__ as tracversion_runtime
20-
21-from datetime import datetime, date
22-
23-from trac.core import *
24-from trac.config import BoolOption
25-from trac.env import IEnvironmentSetupParticipant
26-from trac.perm import IPermissionRequestor
27-from trac.web.chrome import INavigationContributor, ITemplateProvider, add_stylesheet, add_script
28-from trac.web.main import IRequestHandler
29-from trac.util import escape, Markup, format_date
30-from trac.ticket import ITicketChangeListener
31-from trac.ticket import model
32-from trac.util.datefmt import to_datetime
33-
34-class BurndownComponent(Component):
35- implements(IEnvironmentSetupParticipant, INavigationContributor,
36- IRequestHandler, ITemplateProvider, IPermissionRequestor, ITicketChangeListener)
37-
38- tracversion=tracversion_runtime[:4]
39-
40- #---------------------------------------------------------------------------
41- # IEnvironmentSetupParticipant methods
42- #---------------------------------------------------------------------------
43- def environment_created(self):
44- """Called when a new Trac environment is created."""
45- if self.environment_needs_upgrade(None):
46- self.upgrade_environment(None)
47-
48- def environment_needs_upgrade(self, db):
49- if not db:
50- db = self.env.get_db_cnx()
51-
52- needsUpgrade = True
53-
54- # See if the burndown table exists, if not, we need an upgrade
55- if dbhelper.table_exists(db, "burndown"):
56- needsUpgrade = False
57- if dbhelper.table_field_exists(db, "burndown", "week"):
58- needsUpgrade = True
59-
60- if dbhelper.table_field_exists(db, "milestone", "started"):
61- needsUpgrade = False
62-
63- return needsUpgrade
64-
65- def upgrade_environment(self, db):
66- db = self.env.get_db_cnx()
67-
68- needsCreate = True
69- needsUpgrade_milestone = True
70- needsUpgrade_burndown = False
71-
72- if dbhelper.table_exists(db, "burndown"):
73- needsCreate = False
74- if dbhelper.table_field_exists(db, "burndown", "week"):
75- needsUpgrade_burndown = True
76-
77- if dbhelper.table_field_exists(db, "milestone", "started"):
78- needsUpgrade_milestone = False
79-
80- if needsCreate:
81- print >> sys.stderr, 'Attempting to create the burndown table'
82- dbhelper.create_burndown_table(db, self.env)
83-
84- if needsUpgrade_milestone:
85- print >> sys.stderr, 'Attempting to modify the milestone table'
86- dbhelper.upgrade_milestone_table(db, self.env)
87-
88- if needsUpgrade_burndown:
89- print >> sys.stderr, 'Attempting to modify the burndown table'
90- dbhelper.upgrade_burndown_table(db, self.env)
91-
92- db.commit()
93-
94- #---------------------------------------------------------------------------
95- # ITicketChangeListener methods
96- #---------------------------------------------------------------------------
97-
98- def ticket_created(self, ticket):
99- self.log.debug('burndown plugin - ticket_created')
100- self.update_burndown_data()
101-
102- def ticket_changed(self, ticket, comment, author, old_values):
103- self.log.debug('burndown plugin - ticket_changed')
104- self.update_burndown_data()
105-
106- def ticket_deleted(self, ticket):
107- self.log.debug('burndown plugin - ticket_modified')
108- self.update_burndown_data()
109-
110- #---------------------------------------------------------------------------
111- # INavigationContributor methods
112- #---------------------------------------------------------------------------
113- def get_active_navigation_item(self, req):
114- return "burndown"
115-
116- def get_navigation_items(self, req):
117- if req.perm.has_permission("BURNDOWN_VIEW"):
118- if self.tracversion=="0.10":
119- yield 'mainnav', 'burndown', Markup('<a href="%s">Burndown</a>') % req.href.burndown()
120- else:
121- yield 'mainnav', 'burndown', Markup('<a href="%s">Burndown</a>' % req.href.burndown())
122-
123- #---------------------------------------------------------------------------
124- # IPermissionRequestor methods
125- #---------------------------------------------------------------------------
126- def get_permission_actions(self):
127- return ["BURNDOWN_VIEW", "BURNDOWN_ADMIN"]
128-
129- #---------------------------------------------------------------------------
130- # IRequestHandler methods
131- #---------------------------------------------------------------------------
132- def match_request(self, req):
133- return req.path_info == '/burndown'
134-
135- def process_request(self, req):
136- req.perm.assert_permission('BURNDOWN_VIEW')
137-
138- db = self.env.get_db_cnx()
139-
140- milestones = dbhelper.get_milestones(db)
141- components = dbhelper.get_components(db)
142-
143- selected_milestone = None
144- if (len(milestones)>0):
145- selected_milestone = dbhelper.get_current_milestone(db, req.args.get('selected_milestone', ""))
146-
147- selected_component = req.args.get('selected_component', 'All Components')
148-
149- empty_db_for_testing = req.args.get('empty_db_for_testing', 'false')
150- if empty_db_for_testing == "true":
151- req.perm.assert_permission('TRAC_ADMIN')
152- dbhelper.empty_db_for_testing(db)
153-
154- # expose display data to the templates
155- data = {}
156- data['milestones'] = req.hdf['milestones'] = milestones
157- data['components'] = req.hdf['components'] = components
158- data['selected_milestone'] = req.hdf['selected_milestone'] = selected_milestone
159- data['selected_component'] = req.hdf['selected_component'] = selected_component
160- data['draw_graph'] = req.hdf['draw_graph'] = False
161- data['start'] = req.hdf['start'] = False
162-
163- if req.perm.has_permission("BURNDOWN_ADMIN"):
164- data['start'] = req.hdf['start'] = True # show the start and complete milestone buttons to admins
165-
166- if req.args.has_key('start'):
167- self.start_milestone(db, selected_milestone['name'])
168-
169- data['draw_graph'] = req.hdf['draw_graph'] = True
170- self.update_burndown_data()
171-
172- data['burndown_data'] = req.hdf['burndown_data'] = []
173- burndown_data, estimated_data = self.get_burndown_data(db, selected_milestone, components, selected_component)
174- data['burndown_data'] = req.hdf['burndown_data'] = burndown_data
175- data['estimated_data'] = req.hdf['estimated_data'] = estimated_data
176-
177- add_stylesheet(req, 'hw/css/burndown.css')
178-
179- self.update_burndown_data()
180-
181- if self.tracversion=="0.10":
182- add_script(req, 'hw/js/line.js')
183- add_script(req, 'hw/js/wz_jsgraphics.js')
184- return 'burndown.cs', None
185- else:
186- data['library'] = ''
187- if data['library'] == 'flot':
188- add_script(req, 'hw/js/jquery.flot.js')
189- else:
190- add_script(req, 'hw/js/line.js')
191- add_script(req, 'hw/js/wz_jsgraphics.js')
192-
193- return 'burndown.html', data, None
194-
195-
196- def get_burndown_data(self, db, selected_milestone, components, selected_component):
197- cursor = db.cursor()
198- cursor.execute("SELECT due FROM milestone where name='%s'" % selected_milestone['name']);
199- milestone_due = cursor.fetchone()[0];
200- self.log.info("milestone_due:")
201- self.log.info(milestone_due)
202- t = to_datetime(milestone_due)
203- due_milestone = datetime(t.year,t.month,t.day)
204-
205- delta = None
206- component_data = {} # this will be a dictionary of lists of tuples -- e.g. component_data = {'componentName':[(id, hours_remaining), (id, hours_remaining), (id, hours_remaining)]}
207- for comp in components:
208- if selected_component == 'All Components' or comp['name'] == selected_component:
209- self.log.debug("comp = %s", comp['name'])
210- self.log.debug("selected_component = %s", sselected_component)
211- sqlBurndown = "SELECT id, hours_remaining ,date"\
212- "FROM burndown "\
213- "WHERE milestone_name = %s AND component_name = %s "\
214- "ORDER BY id"
215- cursor.execute(sqlBurndown, (selected_milestone['name'], comp['name']))
216- component_data[comp['name']] = cursor.fetchall()
217- self.log.debug(component_data[comp['name']])
218-
219- if len(components)==0:
220- comp = {'name':'-'}
221- if selected_component == 'All Components' or comp['name'] == selected_component:
222- self.log.debug("comp = %s", comp['name'])
223- self.log.debug("selected_component = %s", selected_component)
224- sqlBurndown = "SELECT id, hours_remaining, date "\
225- "FROM burndown "\
226- "WHERE milestone_name = %s AND component_name = %s "\
227- "ORDER BY id"
228- cursor.execute(sqlBurndown, (selected_milestone['name'], comp['name']))
229- component_data[comp['name']] = cursor.fetchall()
230- self.log.debug(component_data[comp['name']])
231-
232- if len(component_data) > 0 and component_data[component_data.keys()[0]]:
233- burndown_length = len(component_data[component_data.keys()[0]])
234- else:
235- burndown_length = 0
236- burndown_data = []
237- if selected_component == 'All Components':
238- for time_unit in range (0, burndown_length):
239- sumHours = 0
240- for comp in components:
241- plotdata.append(comp['date'])
242- self.log.debug('component: %s', [comp['name']]);
243- self.log.debug('time_unit: %s', [time_unit]);
244- if (component_data[comp['name']] and len(component_data[comp['name']]) > time_unit):
245- self.log.debug('hours: %s', component_data[comp['name']][time_unit][1]);
246- sumHours += component_data[comp['name']][time_unit][1]
247- if len(components)==0:
248- comp={'name':'-'}
249- self.log.debug('component: %s', [comp['name']]);
250- self.log.debug('time_unit: %s', [time_unit]);
251- if (component_data[comp['name']] and len(component_data[comp['name']]) > time_unit):
252- self.log.debug('hours: %s', component_data[comp['name']][time_unit][1]);
253- sumHours += component_data[comp['name']][time_unit][1]
254-
255- burndown_data.append((time_unit+1, sumHours))
256-
257- else:
258- for time_unit in range (0, len(component_data[selected_component])):
259- plotdata.append(component_data[selected_component]['date'])
260- burndown_data.append((time_unit+1, component_data[selected_component][time_unit][1]))
261- start_milestone = None
262- estimated_data = []
263- if len(burndown_data)!=0:
264- if len(components)==0:
265- t = time.strptime(component_data['-'][0][2],'%Y/%m/%d')
266- start_milestone = datetime(t[0] ,t[1] ,t[2])
267- else:
268- t = time.strptime(component_data[component_data.keys()[0]][0][2],'%Y/%m/%d')
269- start_milestone = datetime(t[0] ,t[1] ,t[2])
270- initial_estimated = burndown_data[0][1]
271-
272- term = (due_milestone - start_milestone).days
273-
274- for time_unit in range (0,term):
275- estimated_data.append((time_unit+1 ,initial_estimated*(1-float(time_unit+1)/term) ))
276-
277- return (burndown_data, estimated_data)
278-
279- def start_milestone(self, db, milestone):
280- startdate = dbhelper.get_startdate_for_milestone(db, milestone)
281-
282- if startdate != None:
283- raise TracError("Milestone %s was already started." % milestone)
284-
285- dbhelper.set_startdate_for_milestone(db, milestone, int(time.time()))
286-
287- #---------------------------------------------------------------------------
288- # ITemplateProvider methods
289- #---------------------------------------------------------------------------
290- def get_templates_dirs(self):
291- from pkg_resources import resource_filename
292- return [resource_filename(__name__, 'templates')]
293-
294- def get_htdocs_dirs(self):
295- from pkg_resources import resource_filename
296- return [('hw', resource_filename(__name__, 'htdocs'))]
297-
298- #------------------------------------------------------------------------
299- # update_burndown_data
300- # - add up the hours remaining for the open tickets for each open milestone and put the sums into the burndown table
301- #------------------------------------------------------------------------
302- def update_burndown_data(self):
303- db = self.env.get_db_cnx()
304- cursor = db.cursor()
305-
306- # today's date
307- today = format_date(int(time.time()))
308-
309- milestones = dbhelper.get_milestones(db)
310- components = dbhelper.get_components(db)
311- for mile in milestones:
312- if mile['started'] and not mile['completed']: # milestone started, but not completed
313- for comp in components:
314- sqlSelect = "SELECT est.value AS estimate, ts.value AS spent "\
315- "FROM ticket t "\
316- " LEFT OUTER JOIN ticket_custom est ON (t.id = est.ticket AND est.name = 'estimatedhours') "\
317- " LEFT OUTER JOIN ticket_custom ts ON (t.id = ts.ticket AND ts.name = 'totalhours') "\
318- "WHERE t.component = %s AND t.milestone = %s"\
319- " AND status IN ('new', 'assigned', 'reopened', 'accepted') "
320- cursor.execute(sqlSelect, [comp['name'], mile['name']])
321-
322- rows = cursor.fetchall()
323- hours = 0
324- estimate = 0
325- spent = 0
326- if rows:
327- for estimate, spent in rows:
328- if not estimate:
329- estimate = 0
330- if not spent:
331- spent = 0
332-
333- if (float(estimate) - float(spent)) > 0:
334- hours += float(estimate) - float(spent)
335-
336- cursor.execute("SELECT id FROM burndown WHERE date = %s AND milestone_name = %s"\
337- "AND component_name = %s", [today, mile['name'], comp['name']])
338-
339- row = cursor.fetchone()
340-
341- try:
342- if row:
343- cursor.execute("UPDATE burndown SET hours_remaining = %s WHERE date = %s AND milestone_name = %s"\
344- "AND component_name = %s", [hours, today, mile['name'], comp['name']])
345- else:
346- cursor.execute("INSERT INTO burndown(component_name, milestone_name, date, hours_remaining) "\
347- " VALUES(%s,%s,%s,%s)", [comp['name'], mile['name'], today, hours])
348- except Exception, inst:
349- self.log.debug(type(inst)) # the exception instance
350- self.log.debug(inst.args) # arguments stored in .args
351- self.log.debug(inst) # __str__ allows args to printed directly
352- cursor.connection.rollback()
353- else:
354- db.commit()
355- if len(components)==0:
356- comp = {'name':'-'}
357- sqlSelect = "SELECT est.value AS estimate, ts.value AS spent "\
358- "FROM ticket t "\
359- " LEFT OUTER JOIN ticket_custom est ON (t.id = est.ticket AND est.name = 'estimatedhours') "\
360- " LEFT OUTER JOIN ticket_custom ts ON (t.id = ts.ticket AND ts.name = 'totalhours') "\
361- "WHERE t.milestone = %s"\
362- " AND status IN ('new', 'assigned', 'reopened', 'accepted') "
363- cursor.execute(sqlSelect, [mile['name']])
364-
365- rows = cursor.fetchall()
366- hours = 0
367- estimate = 0
368- spent = 0
369- if rows:
370- for estimate, spent in rows:
371- if not estimate:
372- estimate = 0
373- if not spent:
374- spent = 0
375-
376- if (float(estimate) - float(spent)) > 0:
377- hours += float(estimate) - float(spent)
378-
379- cursor.execute("SELECT id FROM burndown WHERE date = %s AND milestone_name = %s"\
380- "AND component_name = %s", [today, mile['name'], comp['name']])
381-
382- row = cursor.fetchone()
383-
384- try:
385- if row:
386- cursor.execute("UPDATE burndown SET hours_remaining = %s WHERE date = %s AND milestone_name = %s"\
387- "AND component_name = %s", [hours, today, mile['name'], comp['name']])
388- else:
389- cursor.execute("INSERT INTO burndown(component_name, milestone_name, date, hours_remaining) "\
390- " VALUES(%s,%s,%s,%s)", [comp['name'], mile['name'], today, hours])
391- except Exception, inst:
392- self.log.debug(type(inst)) # the exception instance
393- self.log.debug(inst.args) # arguments stored in .args
394- self.log.debug(inst) # __str__ allows args to printed directly
395- cursor.connection.rollback()
396- else:
397- db.commit()
398-
399-
1+# Burndown plugin
2+# Copyright (C) 2006 Sam Bloomquist <spooninator@hotmail.com>
3+# Copyright (C) 2006-2008 Daan van Etten <daan@stuq.nl>
4+# All rights reserved.
5+
6+# This software may at some point consist of voluntary contributions made by
7+# many individuals. For the exact contribution history, see the revision
8+# history and logs, available at http://projects.edgewall.com/trac/.
9+#
10+# Author: Sam Bloomquist <spooninator@hotmail.com>
11+# Author: Daan van Etten <daan@stuq.nl>
12+
13+import time
14+import datetime
15+import sys
16+
17+import dbhelper
18+
19+from trac import __version__ as tracversion_runtime
20+
21+from datetime import datetime, date
22+
23+from trac.core import *
24+from trac.config import BoolOption
25+from trac.env import IEnvironmentSetupParticipant
26+from trac.perm import IPermissionRequestor
27+from trac.web.chrome import INavigationContributor, ITemplateProvider, add_stylesheet, add_script
28+from trac.web.main import IRequestHandler
29+from trac.util import escape, Markup, format_date
30+from trac.ticket import ITicketChangeListener
31+from trac.ticket import model
32+from trac.util.datefmt import to_datetime
33+
34+class BurndownComponent(Component):
35+ implements(IEnvironmentSetupParticipant, INavigationContributor,
36+ IRequestHandler, ITemplateProvider, IPermissionRequestor, ITicketChangeListener)
37+
38+ tracversion=tracversion_runtime[:4]
39+
40+ #---------------------------------------------------------------------------
41+ # IEnvironmentSetupParticipant methods
42+ #---------------------------------------------------------------------------
43+ def environment_created(self):
44+ """Called when a new Trac environment is created."""
45+ if self.environment_needs_upgrade(None):
46+ self.upgrade_environment(None)
47+
48+ def environment_needs_upgrade(self, db):
49+ if not db:
50+ db = self.env.get_db_cnx()
51+
52+ needsUpgrade = True
53+
54+ # See if the burndown table exists, if not, we need an upgrade
55+ if dbhelper.table_exists(db, "burndown"):
56+ needsUpgrade = False
57+ if dbhelper.table_field_exists(db, "burndown", "week"):
58+ needsUpgrade = True
59+
60+ if dbhelper.table_field_exists(db, "milestone", "started"):
61+ needsUpgrade = False
62+
63+ return needsUpgrade
64+
65+ def upgrade_environment(self, db):
66+ db = self.env.get_db_cnx()
67+
68+ needsCreate = True
69+ needsUpgrade_milestone = True
70+ needsUpgrade_burndown = False
71+
72+ if dbhelper.table_exists(db, "burndown"):
73+ needsCreate = False
74+ if dbhelper.table_field_exists(db, "burndown", "week"):
75+ needsUpgrade_burndown = True
76+
77+ if dbhelper.table_field_exists(db, "milestone", "started"):
78+ needsUpgrade_milestone = False
79+
80+ if needsCreate:
81+ print >> sys.stderr, 'Attempting to create the burndown table'
82+ dbhelper.create_burndown_table(db, self.env)
83+
84+ if needsUpgrade_milestone:
85+ print >> sys.stderr, 'Attempting to modify the milestone table'
86+ dbhelper.upgrade_milestone_table(db, self.env)
87+
88+ if needsUpgrade_burndown:
89+ print >> sys.stderr, 'Attempting to modify the burndown table'
90+ dbhelper.upgrade_burndown_table(db, self.env)
91+
92+ db.commit()
93+
94+ #---------------------------------------------------------------------------
95+ # ITicketChangeListener methods
96+ #---------------------------------------------------------------------------
97+
98+ def ticket_created(self, ticket):
99+ self.log.debug('burndown plugin - ticket_created')
100+ self.update_burndown_data()
101+
102+ def ticket_changed(self, ticket, comment, author, old_values):
103+ self.log.debug('burndown plugin - ticket_changed')
104+ self.update_burndown_data()
105+
106+ def ticket_deleted(self, ticket):
107+ self.log.debug('burndown plugin - ticket_modified')
108+ self.update_burndown_data()
109+
110+ #---------------------------------------------------------------------------
111+ # INavigationContributor methods
112+ #---------------------------------------------------------------------------
113+ def get_active_navigation_item(self, req):
114+ return "burndown"
115+
116+ def get_navigation_items(self, req):
117+ if req.perm.has_permission("BURNDOWN_VIEW"):
118+ if self.tracversion=="0.10":
119+ yield 'mainnav', 'burndown', Markup('<a href="%s">Burndown</a>') % req.href.burndown()
120+ else:
121+ yield 'mainnav', 'burndown', Markup('<a href="%s">Burndown</a>' % req.href.burndown())
122+
123+ #---------------------------------------------------------------------------
124+ # IPermissionRequestor methods
125+ #---------------------------------------------------------------------------
126+ def get_permission_actions(self):
127+ return ["BURNDOWN_VIEW", "BURNDOWN_ADMIN"]
128+
129+ #---------------------------------------------------------------------------
130+ # IRequestHandler methods
131+ #---------------------------------------------------------------------------
132+ def match_request(self, req):
133+ return req.path_info == '/burndown'
134+
135+ def process_request(self, req):
136+ req.perm.assert_permission('BURNDOWN_VIEW')
137+
138+ db = self.env.get_db_cnx()
139+
140+ milestones = dbhelper.get_milestones(db)
141+ components = dbhelper.get_components(db)
142+
143+ selected_milestone = None
144+ if (len(milestones)>0):
145+ selected_milestone = dbhelper.get_current_milestone(db, req.args.get('selected_milestone', ""))
146+
147+ selected_component = req.args.get('selected_component', 'All Components')
148+
149+ empty_db_for_testing = req.args.get('empty_db_for_testing', 'false')
150+ if empty_db_for_testing == "true":
151+ req.perm.assert_permission('TRAC_ADMIN')
152+ dbhelper.empty_db_for_testing(db)
153+
154+ # expose display data to the templates
155+ data = {}
156+ data['milestones'] = req.hdf['milestones'] = milestones
157+ data['components'] = req.hdf['components'] = components
158+ data['selected_milestone'] = req.hdf['selected_milestone'] = selected_milestone
159+ data['selected_component'] = req.hdf['selected_component'] = selected_component
160+ data['draw_graph'] = req.hdf['draw_graph'] = False
161+ data['start'] = req.hdf['start'] = False
162+
163+ if req.perm.has_permission("BURNDOWN_ADMIN"):
164+ data['start'] = req.hdf['start'] = True # show the start and complete milestone buttons to admins
165+
166+ if req.args.has_key('start'):
167+ self.start_milestone(db, selected_milestone['name'])
168+
169+ data['draw_graph'] = req.hdf['draw_graph'] = True
170+ self.update_burndown_data()
171+
172+ data['burndown_data'] = req.hdf['burndown_data'] = []
173+ burndown_data, estimated_data = self.get_burndown_data(db, selected_milestone, components, selected_component)
174+ data['burndown_data'] = req.hdf['burndown_data'] = burndown_data
175+ data['estimated_data'] = req.hdf['estimated_data'] = estimated_data
176+
177+ add_stylesheet(req, 'hw/css/burndown.css')
178+
179+ self.update_burndown_data()
180+
181+ if self.tracversion=="0.10":
182+ add_script(req, 'hw/js/line.js')
183+ add_script(req, 'hw/js/wz_jsgraphics.js')
184+ return 'burndown.cs', None
185+ else:
186+ data['library'] = ''
187+ if data['library'] == 'flot':
188+ add_script(req, 'hw/js/jquery.flot.js')
189+ else:
190+ add_script(req, 'hw/js/line.js')
191+ add_script(req, 'hw/js/wz_jsgraphics.js')
192+
193+ return 'burndown.html', data, None
194+
195+
196+ def get_burndown_data(self, db, selected_milestone, components, selected_component):
197+ cursor = db.cursor()
198+ cursor.execute("SELECT due FROM milestone where name='%s'" % selected_milestone['name']);
199+ milestone_due = cursor.fetchone()[0];
200+ t = to_datetime(milestone_due)
201+ due_milestone = datetime(t.year,t.month,t.day)
202+
203+ delta = None
204+ component_data = {} # this will be a dictionary of lists of tuples -- e.g. component_data = {'componentName':[(id, hours_remaining), (id, hours_remaining), (id, hours_remaining)]}
205+ for comp in components:
206+ if selected_component == 'All Components' or comp['name'] == selected_component:
207+ sqlBurndown = "SELECT id, hours_remaining ,date "\
208+ "FROM burndown "\
209+ "WHERE milestone_name = '%s' AND component_name = '%s' "\
210+ "ORDER BY id" % (selected_milestone['name'], comp['name'])
211+ cursor.execute(sqlBurndown)
212+ component_data[comp['name']] = cursor.fetchall()
213+ self.log.debug(component_data[comp['name']])
214+
215+ if len(components)==0:
216+ comp = {'name':'-'}
217+ if selected_component == 'All Components' or comp['name'] == selected_component:
218+ self.log.debug("comp = %s", comp['name'])
219+ self.log.debug("selected_component = %s", selected_component)
220+ sqlBurndown = "SELECT id, hours_remaining, date "\
221+ "FROM burndown "\
222+ "WHERE milestone_name = %s AND component_name = %s "\
223+ "ORDER BY id"
224+ cursor.execute(sqlBurndown, (selected_milestone['name'], comp['name']))
225+ component_data[comp['name']] = cursor.fetchall()
226+ self.log.debug(component_data[comp['name']])
227+
228+ if len(component_data) > 0 and component_data[component_data.keys()[0]]:
229+ burndown_length = len(component_data[component_data.keys()[0]])
230+ else:
231+ burndown_length = 0
232+ burndown_data = []
233+ plotdata = []
234+ if selected_component == 'All Components':
235+ for time_unit in range (0, burndown_length):
236+ sumHours = 0
237+ for comp in components:
238+ plotdata.append(component_data[comp['name']][time_unit][2])
239+ self.log.debug('component: %s', [comp['name']]);
240+ self.log.debug('time_unit: %s', [time_unit]);
241+ if (component_data[comp['name']] and len(component_data[comp['name']]) > time_unit):
242+ self.log.debug('hours: %s', component_data[comp['name']][time_unit][1]);
243+ sumHours += component_data[comp['name']][time_unit][1]
244+ if len(components)==0:
245+ comp={'name':'-'}
246+ self.log.debug('component: %s', [comp['name']]);
247+ self.log.debug('time_unit: %s', [time_unit]);
248+ if (component_data[comp['name']] and len(component_data[comp['name']]) > time_unit):
249+ self.log.debug('hours: %s', component_data[comp['name']][time_unit][1]);
250+ sumHours += component_data[comp['name']][time_unit][1]
251+
252+ burndown_data.append((time_unit+1, sumHours))
253+
254+ else:
255+ for time_unit in range (0, len(component_data[selected_component])):
256+ plotdata.append(component_data[selected_component]['date'])
257+ burndown_data.append((time_unit+1, component_data[selected_component][time_unit][1]))
258+ start_milestone = None
259+ estimated_data = []
260+ if len(burndown_data)!=0:
261+ if len(components)==0:
262+ t = time.strptime(component_data['-'][0][2],'%Y/%m/%d')
263+ start_milestone = datetime(t[0] ,t[1] ,t[2])
264+ else:
265+ t = time.strptime(component_data[component_data.keys()[0]][0][2],'%Y/%m/%d')
266+ start_milestone = datetime(t[0] ,t[1] ,t[2])
267+ initial_estimated = burndown_data[0][1]
268+
269+ term = (due_milestone - start_milestone).days + 1
270+
271+ for time_unit in range (0,term):
272+ estimated_data.append((time_unit+1 ,initial_estimated*(1-float(time_unit+1)/term) ))
273+
274+ return (burndown_data, estimated_data)
275+
276+ def start_milestone(self, db, milestone):
277+ startdate = dbhelper.get_startdate_for_milestone(db, milestone)
278+
279+ if startdate != None:
280+ raise TracError("Milestone %s was already started." % milestone)
281+
282+ dbhelper.set_startdate_for_milestone(db, milestone, int(time.time()))
283+
284+ #---------------------------------------------------------------------------
285+ # ITemplateProvider methods
286+ #---------------------------------------------------------------------------
287+ def get_templates_dirs(self):
288+ from pkg_resources import resource_filename
289+ return [resource_filename(__name__, 'templates')]
290+
291+ def get_htdocs_dirs(self):
292+ from pkg_resources import resource_filename
293+ return [('hw', resource_filename(__name__, 'htdocs'))]
294+
295+ #------------------------------------------------------------------------
296+ # update_burndown_data
297+ # - add up the hours remaining for the open tickets for each open milestone and put the sums into the burndown table
298+ #------------------------------------------------------------------------
299+ def update_burndown_data(self):
300+ db = self.env.get_db_cnx()
301+ cursor = db.cursor()
302+
303+ # today's date
304+ today = format_date(int(time.time()))
305+
306+ milestones = dbhelper.get_milestones(db)
307+ components = dbhelper.get_components(db)
308+ for mile in milestones:
309+ if mile['started'] and not mile['completed']: # milestone started, but not completed
310+ for comp in components:
311+ sqlSelect = "SELECT est.value AS estimate, ts.value AS spent "\
312+ "FROM ticket t "\
313+ " LEFT OUTER JOIN ticket_custom est ON (t.id = est.ticket AND est.name = 'estimatedhours') "\
314+ " LEFT OUTER JOIN ticket_custom ts ON (t.id = ts.ticket AND ts.name = 'totalhours') "\
315+ "WHERE t.component = %s AND t.milestone = %s"\
316+ " AND status IN ('new', 'assigned', 'reopened', 'accepted') "
317+ cursor.execute(sqlSelect, [comp['name'], mile['name']])
318+
319+ rows = cursor.fetchall()
320+ hours = 0
321+ estimate = 0
322+ spent = 0
323+ if rows:
324+ for estimate, spent in rows:
325+ if not estimate:
326+ estimate = 0
327+ if not spent:
328+ spent = 0
329+
330+ if (float(estimate) - float(spent)) > 0:
331+ hours += float(estimate) - float(spent)
332+
333+ cursor.execute("SELECT id FROM burndown WHERE date = %s AND milestone_name = %s"\
334+ "AND component_name = %s", [today, mile['name'], comp['name']])
335+
336+ row = cursor.fetchone()
337+
338+ try:
339+ if row:
340+ cursor.execute("UPDATE burndown SET hours_remaining = %s WHERE date = %s AND milestone_name = %s"\
341+ "AND component_name = %s", [hours, today, mile['name'], comp['name']])
342+ else:
343+ cursor.execute("INSERT INTO burndown(component_name, milestone_name, date, hours_remaining) "\
344+ " VALUES(%s,%s,%s,%s)", [comp['name'], mile['name'], today, hours])
345+ except Exception, inst:
346+ self.log.debug(type(inst)) # the exception instance
347+ self.log.debug(inst.args) # arguments stored in .args
348+ self.log.debug(inst) # __str__ allows args to printed directly
349+ cursor.connection.rollback()
350+ else:
351+ db.commit()
352+ if len(components)==0:
353+ comp = {'name':'-'}
354+ sqlSelect = "SELECT est.value AS estimate, ts.value AS spent "\
355+ "FROM ticket t "\
356+ " LEFT OUTER JOIN ticket_custom est ON (t.id = est.ticket AND est.name = 'estimatedhours') "\
357+ " LEFT OUTER JOIN ticket_custom ts ON (t.id = ts.ticket AND ts.name = 'totalhours') "\
358+ "WHERE t.milestone = %s"\
359+ " AND status IN ('new', 'assigned', 'reopened', 'accepted') "
360+ cursor.execute(sqlSelect, [mile['name']])
361+
362+ rows = cursor.fetchall()
363+ hours = 0
364+ estimate = 0
365+ spent = 0
366+ if rows:
367+ for estimate, spent in rows:
368+ if not estimate:
369+ estimate = 0
370+ if not spent:
371+ spent = 0
372+
373+ if (float(estimate) - float(spent)) > 0:
374+ hours += float(estimate) - float(spent)
375+
376+ cursor.execute("SELECT id FROM burndown WHERE date = %s AND milestone_name = %s"\
377+ "AND component_name = %s", [today, mile['name'], comp['name']])
378+
379+ row = cursor.fetchone()
380+
381+ try:
382+ if row:
383+ cursor.execute("UPDATE burndown SET hours_remaining = %s WHERE date = %s AND milestone_name = %s"\
384+ "AND component_name = %s", [hours, today, mile['name'], comp['name']])
385+ else:
386+ cursor.execute("INSERT INTO burndown(component_name, milestone_name, date, hours_remaining) "\
387+ " VALUES(%s,%s,%s,%s)", [comp['name'], mile['name'], today, hours])
388+ except Exception, inst:
389+ self.log.debug(type(inst)) # the exception instance
390+ self.log.debug(inst.args) # arguments stored in .args
391+ self.log.debug(inst) # __str__ allows args to printed directly
392+ cursor.connection.rollback()
393+ else:
394+ db.commit()
395+
396+
旧リポジトリブラウザで表示