def filter_stream(self, req, method, filename, stream, data): chrome = Chrome(self.env) if req.path_info.startswith('/milestone') \ and req.args.get('action') in ['edit', 'new'] \ and 'max_level' not in data: milestone = data.get('milestone') levels = IttecoMilestoneAdminPanel(self.env).milestone_levels mydata ={'structured_milestones':StructuredMilestone.select(self.env), 'max_level': levels and len(levels)-1 or 0, 'milestone_name' : milestone and milestone.parent or None, 'field_name' : 'parent'} stream |=Transformer('//*[@id="edit"]/fieldset').append( chrome.render_template(req, 'itteco_milestones_dd.html', mydata, fragment=True)) if 'ticket' in data: tkt = data['ticket'] mydata ={ 'structured_milestones':StructuredMilestone.select(self.env), 'milestone_name': data['ticket']['milestone'], 'field_name' : 'field_milestone', 'hide_completed' : not ( tkt.exists and 'TICKET_ADMIN' in req.perm(tkt.resource)) } req.chrome.setdefault('ctxtnav',[]).insert( -1, tag.a( _('Go To Whiteboard'), href=req.href.whiteboard('team_tasks', data['ticket']['milestone'] or 'none') ) ) stream |=Transformer('//*[@id="field-milestone"]').replace( chrome.render_template(req, 'itteco_milestones_dd.html', mydata, fragment=True)) return stream
def create(self, req, attributes): """ Create a structure milestone object.""" name = attributes.get('summary') try: if StructuredMilestone(self.env, name).exists: raise TracError('Milestone with name %s already exists' % name) except ResourceNotFound: pass milestone = StructuredMilestone(self.env) milestone.name = name req.perm.require('MILESTONE_CREATE', Resource(milestone.resource.realm)) milestone.description = attributes.get('description') milestone.ticket.populate(attributes) def set_date(name, attr_name = None): val = attributes.get(name) if val is None: return val = val and parse_date(val, tzinfo=req.tz) or None if attr_name is not None: setattr(milestone, attr_name, val) milestone.ticket[name] = val and str(to_timestamp(val)) set_date('duedate', 'due') set_date('completedate', 'completed') set_date('started') milestone.ticket.values['reporter'] = attributes.get('author') or get_reporter_id(req) milestone.insert() return self._milestone_as_dict(milestone)
def _resolve_milestone(self, name, include_kids, show_completed): def _flatten_and_get_names(mil, include_kids, show_completed): names= [] if mil: mil = isinstance(mil, StructuredMilestone) and [mil,] or mil for m in mil: if show_completed or not m.completed: names.append(m.name) if include_kids: names.extend(_flatten_and_get_names(m.kids, include_kids, show_completed)) return names if name=='nearest': db = self.env.get_db_cnx() cursor = db.cursor() cursor.execute( 'SELECT name FROM milestone WHERE due>%s ORDER BY due LIMIT 1', \ (to_timestamp(datetime.now(utc)),)) row = cursor.fetchone() name=row and row[0] or 'none' elif name=='not_completed_milestones': return _flatten_and_get_names(StructuredMilestone.select(self.env, False), \ include_kids, show_completed) if name=='none': return '' try: mil = StructuredMilestone(self.env, name) names = _flatten_and_get_names(mil, include_kids, show_completed) if not names: names = mil.name return names except ResourceNotFound: return ''
def update(self, req, name, comment, attributes=None): """ Updates a structure milestone object.""" milestone = StructuredMilestone(self.env, name) if not milestone.exists: raise TracError('Milestone with name %s does not exist' % name) req.perm.require('MILESTONE_MODIFY', Resource(milestone.resource.realm)) if attributes is not None: milestone.name = attributes.get('summary') milestone.description = attributes.get('description') milestone.ticket.populate(attributes) def set_date(name, attr_name = None): val = attributes.get(name) if val is None: return val = val and parse_date(val, tzinfo=req.tz) or None if attr_name is not None: setattr(milestone, attr_name, val) milestone.ticket[name] = val and str(to_timestamp(val)) set_date('duedate', 'due') set_date('completedate', 'completed') set_date('started') milestone.save_changes(get_reporter_id(req, 'author'), comment) return self._milestone_as_dict(milestone)
def delete(self, req, name): """ Deletes structure milestone object.""" milestone = StructuredMilestone(self.env, name) if not milestone.exists: raise TracError('Milestone with name %s does not exist' % name) req.perm.require('MILESTONE_DELETE', Resource(milestone.resource.realm)) milestone.delete() return {'name': milestone.name, 'description': milestone.description}
def _render_editor(self, req, db, milestone): data = { 'milestone': milestone, 'ticket': milestone.ticket, 'datefields': self.date_fields, 'date_hint': get_date_format_hint(), 'datetime_hint': get_datetime_format_hint(), 'milestone_groups': [], 'jump_to': req.args.get('jump_to') or referer_module(req) } if milestone.exists: req.perm(milestone.resource).require('MILESTONE_VIEW') milestones = [ m for m in StructuredMilestone.select(self.env, db=db) if m.name != milestone.name and 'MILESTONE_VIEW' in req.perm(m.resource) ] data['milestone_groups'] = group_milestones( milestones, 'TICKET_ADMIN' in req.perm) else: req.perm(milestone.resource).require('MILESTONE_CREATE') TicketModule(self.env)._insert_ticket_data( req, milestone.ticket, data, get_reporter_id(req, 'author'), {}) self._add_tickets_report_data(milestone, req, data) context = Context.from_request(req, milestone.resource) data['attachments'] = AttachmentModule( self.env).attachment_data(context) return 'itteco_milestone_edit.html', data, None
def get(self, req, name): """ Get a structure milestone object.""" milestone = StructuredMilestone(self.env, name) req.perm.require('MILESTONE_VIEW', Resource(milestone.resource.realm)) if not milestone.exists: raise TracError('Milestone with name %s does not exist' % name) return self._milestone_as_dict(milestone)
def _render_editor(self, req, db, milestone): data = { 'milestone': milestone, 'ticket': milestone.ticket, 'datefields' : self.date_fields, 'date_hint': get_date_format_hint(), 'datetime_hint': get_datetime_format_hint(), 'milestone_groups': [], 'jump_to' : req.args.get('jump_to') or referer_module(req) } if milestone.exists: req.perm(milestone.resource).require('MILESTONE_VIEW') milestones = [m for m in StructuredMilestone.select(self.env, db=db) if m.name != milestone.name and 'MILESTONE_VIEW' in req.perm(m.resource)] data['milestone_groups'] = group_milestones(milestones, 'TICKET_ADMIN' in req.perm) else: req.perm(milestone.resource).require('MILESTONE_CREATE') TicketModule(self.env)._insert_ticket_data(req, milestone.ticket, data, get_reporter_id(req, 'author'), {}) self._add_tickets_report_data(milestone, req, data) context = Context.from_request(req, milestone.resource) data['attachments']=AttachmentModule(self.env).attachment_data(context) return 'itteco_milestone_edit.html', data, None
def create(self, req, attributes): """ Create a structure milestone object.""" name = attributes.get('summary') try: if StructuredMilestone(self.env, name).exists: raise TracError('Milestone with name %s already exists' % name) except ResourceNotFound: pass milestone = StructuredMilestone(self.env) milestone.name = name req.perm.require('MILESTONE_CREATE', Resource(milestone.resource.realm)) milestone.description = attributes.get('description') milestone.ticket.populate(attributes) def set_date(name, attr_name=None): val = attributes.get(name) if val is None: return val = val and parse_date(val, tzinfo=req.tz) or None if attr_name is not None: setattr(milestone, attr_name, val) milestone.ticket[name] = val and str(to_timestamp(val)) set_date('duedate', 'due') set_date('completedate', 'completed') set_date('started') milestone.ticket.values['reporter'] = attributes.get( 'author') or get_reporter_id(req) milestone.insert() return self._milestone_as_dict(milestone)
def filter_stream(self, req, method, filename, stream, data): chrome = Chrome(self.env) if req.path_info.startswith('/milestone') \ and req.args.get('action') in ['edit', 'new'] \ and 'max_level' not in data: milestone = data.get('milestone') levels = IttecoMilestoneAdminPanel(self.env).milestone_levels mydata = { 'structured_milestones': StructuredMilestone.select(self.env), 'max_level': levels and len(levels) - 1 or 0, 'milestone_name': milestone and milestone.parent or None, 'field_name': 'parent' } stream |= Transformer('//*[@id="edit"]/fieldset').append( chrome.render_template(req, 'itteco_milestones_dd.html', mydata, fragment=True)) if 'ticket' in data: tkt = data['ticket'] mydata = { 'structured_milestones': StructuredMilestone.select(self.env), 'milestone_name': data['ticket']['milestone'], 'field_name': 'field_milestone', 'hide_completed': not (tkt.exists and 'TICKET_ADMIN' in req.perm(tkt.resource)) } req.chrome.setdefault('ctxtnav', []).insert( -1, tag.a(_('Go To Whiteboard'), href=req.href.whiteboard( 'team_tasks', data['ticket']['milestone'] or 'none'))) stream |= Transformer('//*[@id="field-milestone"]').replace( chrome.render_template(req, 'itteco_milestones_dd.html', mydata, fragment=True)) return stream
def process_request(self, req): req.perm('ticket').require('TICKET_VIEW') board_type = req.args.get('board_type', 'team_tasks') milestone = req.args.get('milestone') if board_type == 'chart_settings': return self._chart_settings(milestone) else: board_type = _get_req_param(req, 'board_type', 'team_tasks') if board_type != req.args.get('board_type'): #boardtype was not implicitly selected, let's restore previos state req.redirect(req.href.whiteboard(board_type, milestone)) add_stylesheet(req, 'common/css/roadmap.css') add_stylesheet(req, 'itteco/css/common.css') add_jscript( req, [ 'stuff/ui/ui.core.js', 'stuff/ui/ui.draggable.js', 'stuff/ui/ui.droppable.js', 'stuff/ui/ui.resizable.js', 'stuff/ui/plugins/jquery.colorbox.js', 'stuff/plugins/jquery.rpc.js', 'custom_select.js', 'whiteboard2.js' ], IttecoEvnSetup(self.env).debug ) show_closed_milestones = req.args.get('show_closed_milestones', False) scope_item, work_item = self._get_wbitems_config(board_type) structured_milestones = StructuredMilestone.select(self.env, show_closed_milestones) if board_type == 'burndown': structured_milestones, _ignore = self._get_milestones_by_level(structured_milestones, 'Sprint', True) data ={ 'structured_milestones' : structured_milestones, 'current_board_type' : board_type, 'milestone' : milestone, 'milestone_levels': IttecoEvnSetup(self.env).milestone_levels, 'stats_config': self._get_stats_config(), 'show_closed_milestones': show_closed_milestones, 'wbconfig' : { 'rpcurl' : req.href.login("xmlrpc"), 'baseurl' : req.href(), 'workitem' : work_item, 'scopeitem': scope_item, 'groups': self.ticket_groups, 'transitions': self.transitions }, 'team' : self.team_members_provider and self.team_members_provider.get_team_members() or [], 'ticket_types' : work_item['types'] or [] } return 'itteco_whiteboard2.html', data, 'text/html'
def _get_job_done(self, mil_names, tkt_type=None, db=None): started_at = to_timestamp(datetime(tzinfo=localtz, \ *(StructuredMilestone(self.env, mil_names[0]).started.timetuple()[:3]))) db = db or self.env.get_db_cnx() cursor = db.cursor() base_sql = None params = list(mil_names) group_by = " GROUP BY t.id, t.type" final_statuses = IttecoEvnSetup(self.env).final_statuses status_params = ("%s," * len(final_statuses))[:-1] params = final_statuses + final_statuses + params if self.count_burndown_on == 'quantity': base_sql = """SELECT MAX(c.time), t.id, t.type, 1 FROM ticket t LEFT JOIN milestone m ON m.name=t.milestone LEFT OUTER JOIN ticket_change c ON t.id=c.ticket AND c.field='status' AND c.newvalue IN (%s) WHERE IN (%s) AND m.name IN (%s)""" % \ ( status_params, status_params, ("%s,"*len(mil_names))[:-1] ) else: base_sql = "SELECT MAX(c.time), t.id, t.type, "+db.cast(db.concat('0','tc.value'),'int')+ \ """FROM ticket t LEFT JOIN milestone m ON m.name=t.milestone LEFT JOIN ticket_custom tc ON t.id=tc.ticket AND tc.name=%%s LEFT OUTER JOIN ticket_change c ON t.id=c.ticket AND c.field='status' AND c.newvalue IN (%s) WHERE t.status IN (%s) AND m.name IN (%s)""" % \ ( status_params, status_params, ("%s,"*len(mil_names))[:-1] ) params = [self.count_burndown_on] + params group_by += ", tc.value" if tkt_type: if isinstance(tkt_type, basestring): base_sql += " AND t.type=%s" params += [tkt_type] else: base_sql += " AND t.type IN (%s)" % ("%s," * len(tkt_type))[:-1] params += list(tkt_type) cursor.execute(base_sql + group_by + " ORDER BY 1", params) data = [(to_datetime((dt < started_at) and started_at or dt), ttype, sum or 0) for dt, tkt_id, ttype, sum in cursor] return data
def update(self, req, name, comment, attributes=None): """ Updates a structure milestone object.""" milestone = StructuredMilestone(self.env, name) if not milestone.exists: raise TracError('Milestone with name %s does not exist' % name) req.perm.require('MILESTONE_MODIFY', Resource(milestone.resource.realm)) if attributes is not None: milestone.name = attributes.get('summary') milestone.description = attributes.get('description') milestone.ticket.populate(attributes) def set_date(name, attr_name=None): val = attributes.get(name) if val is None: return val = val and parse_date(val, tzinfo=req.tz) or None if attr_name is not None: setattr(milestone, attr_name, val) milestone.ticket[name] = val and str(to_timestamp(val)) set_date('duedate', 'due') set_date('completedate', 'completed') set_date('started') milestone.save_changes(get_reporter_id(req, 'author'), comment) return self._milestone_as_dict(milestone)
def milestone(self, req): mil_id = req.args.get('obj_id') if mil_id: req.perm.require('MILESTONE_MODIFY', Resource('milestone', mil_id)) else: req.perm.require('MILESTONE_CREATE') milestone = StructuredMilestone(self.env, mil_id) descriptor = WhiteboardModule(self.env).get_new_ticket_descriptor( [type.name for type in Type.select(self.env)], milestone.ticket.id) data = { 'structured_milestones': StructuredMilestone.select(self.env), 'new_ticket_descriptor': descriptor, 'milestone': milestone, 'action_controls': self._get_action_controls(req, descriptor['ticket']), } return 'itteco_milestone_quick_edit.html', data, 'text/html'
def _comment_milestone(self, req, mil_id): milestone = StructuredMilestone(self.env, mil_id) if not milestone.exists: raise ResourceNotFound('Milestone %s does not exist.' % mil_id, 'Invalid Milestone Name') req.perm.require('MILESTONE_MODIFY', milestone.resource) changes = TicketModule(self.env).rendered_changelog_entries( req, milestone.ticket) return 'itteco_milestone_comment.html', { 'milestone': milestone, 'changes': changes }, 'text/html'
def ticket(self, req): tkt_id = req.args.get("obj_id") if tkt_id: req.perm.require("TICKET_MODIFY", Resource("ticket", tkt_id)) else: req.perm.require("TICKET_CREATE") descriptor = WhiteboardModule(self.env).get_new_ticket_descriptor( [type.name for type in Type.select(self.env)], tkt_id ) data = { "structured_milestones": StructuredMilestone.select(self.env), "resolutions": [], # val.name for val in Resolution.select(self.env)], "new_ticket_descriptor": descriptor, "action_controls": self._get_action_controls(req, descriptor["ticket"]), } return "itteco_ticket_edit.html", data, "text/html"
def milestone(self, req): mil_id = req.args.get("obj_id") if mil_id: req.perm.require("MILESTONE_MODIFY", Resource("milestone", mil_id)) else: req.perm.require("MILESTONE_CREATE") milestone = StructuredMilestone(self.env, mil_id) descriptor = WhiteboardModule(self.env).get_new_ticket_descriptor( [type.name for type in Type.select(self.env)], milestone.ticket.id ) data = { "structured_milestones": StructuredMilestone.select(self.env), "new_ticket_descriptor": descriptor, "milestone": milestone, "action_controls": self._get_action_controls(req, descriptor["ticket"]), } return "itteco_milestone_quick_edit.html", data, "text/html"
def _render_link(self, context, name, label, extra=''): try: milestone = StructuredMilestone(self.env, name) except TracError: milestone = None # Note: the above should really not be needed, `Milestone.exists` # should simply be false if the milestone doesn't exist in the db # (related to #4130) href = context.href.milestone(name) if milestone and milestone.exists: if 'MILESTONE_VIEW' in context.perm(milestone.resource): closed = milestone.is_completed and 'closed ' or '' return tag.a(label, class_='%smilestone' % closed, href=href + extra) elif 'MILESTONE_CREATE' in context.perm('milestone', name): return tag.a(label, class_='missing milestone', href=href + extra, rel='nofollow') return tag.a(label, class_='missing milestone')
def ticket(self, req): tkt_id = req.args.get('obj_id') if tkt_id: req.perm.require('TICKET_MODIFY', Resource('ticket', tkt_id)) else: req.perm.require('TICKET_CREATE') descriptor = WhiteboardModule(self.env).get_new_ticket_descriptor( [type.name for type in Type.select(self.env)], tkt_id) data = { 'structured_milestones': StructuredMilestone.select(self.env), 'resolutions': [], #val.name for val in Resolution.select(self.env)], 'new_ticket_descriptor': descriptor, 'action_controls': self._get_action_controls(req, descriptor['ticket']), } return 'itteco_ticket_edit.html', data, 'text/html'
def process_request(self, req): milestone_id = req.args.get('id') req.perm('milestone', milestone_id).require('MILESTONE_VIEW') add_link(req, 'up', req.href.roadmap(), _('Roadmap')) db = self.env.get_db_cnx() # TODO: db can be removed milestone = StructuredMilestone(self.env, milestone_id, db) action = req.args.get('action', 'view') if req.method == 'POST': if req.args.has_key('cancel'): req.redirect(req.href.roadmap()) elif action == 'delete': self._do_delete(req, db, milestone) return self._do_save(req, db, milestone) elif action in ('new', 'edit', 'view'): return self._render_editor(req, db, milestone) elif action == 'delete': return self._render_confirm(req, db, milestone) req.redirect(req.href.roadmap())
def query_stories(self, req, context): level = context.get('level') all_milestones = StructuredMilestone.select(self.env, True) mils, mils_dict = self._get_milestones_by_level(all_milestones, level, context.get('show_completed')) milestones = [mil.name for mil in mils] +[''] fields = [ 'summary', 'description', 'owner', self.scope_element_weight_field, self.work_element_weight_field ] def milestone_as_dict(milestone): res = dict([(f, milestone.ticket[f]) for f in fields]) res.update( { 'id': milestone.name, 'references': [] } ) return res empty_scope_element = {'id': '', 'summary': 'Backlog (no milestone)','references': []} roots = [empty_scope_element] + [milestone_as_dict(m) for m in mils] milestone_by_name = dict([(m['id'], m) for m in roots]) scope_tkt_types = set([t for t in IttecoEvnSetup(self.env).scope_element]) tickets, ticket_ids = self._get_tickets_graph(req, milestones, (scope_tkt_types,)) self.env.log.debug('roots ="%s"' % (roots,)) for ticket in tickets: root = milestone_by_name.get(ticket['milestone'],empty_scope_element) root['references'].append(ticket) return roots
def process_request(self, req): milestone_realm = Resource('milestone') req.perm.require('MILESTONE_VIEW') showall = req.args.get('show') == 'all' db = self.env.get_db_cnx() milestones = [m for m in StructuredMilestone.select(self.env, True, db) if 'MILESTONE_VIEW' in req.perm(m.resource)] requested_fmt = req.args.get('format') if requested_fmt == 'ics': self.render_ics(req, db, milestones) return max_level = len(IttecoEvnSetup(self.env).milestone_levels) max_level = max_level and max_level-1 or 0; current_level = int(req.args.get('mil_type', max_level)) if current_level==-1: #show all milestones regardless to the level milestones = sum([_get_milestone_with_all_kids(mil) for mil in milestones], []) else: #filter by level i =0 while i<current_level: next_level_mils = [] for m in milestones: next_level_mils.extend(m.kids) milestones = next_level_mils i+=1 calc_on = req.args.get('calc_on') ticket_group = req.args.get('ticket_group', 'all') selected_types = None if ticket_group=='scope_element': selected_types = IttecoEvnSetup(self.env).scope_element elif ticket_group=='work_element': selected_types = IttecoEvnSetup(self.env).work_element else: ticket_group = 'all' selected_type_names = [tkt_type.name for tkt_type in Type.select(self.env) if selected_types is None or tkt_type.value in selected_types] stats = [] milestones = [mil for mil in milestones if showall or not mil.is_completed] for milestone in milestones: tickets = get_tickets_for_structured_milestone( self.env, db, milestone.name, calc_on, selected_type_names) tickets = apply_ticket_permissions(self.env, req, tickets) stat = SelectionTicketGroupStatsProvider(self.env).get_ticket_group_stats(tickets, calc_on) stats.append( milestone_stats_data( req, stat, [m.name for m in _get_milestone_with_all_kids(milestone)])) if requested_fmt=='json': self._render_milestones_stats_as_json(req, milestones, stats) return # FIXME should use the 'webcal:' scheme, probably username = None if req.authname and req.authname != 'anonymous': username = req.authname icshref = req.href.roadmap(show=req.args.get('show'), user=username, format='ics') add_link(req, 'alternate', icshref, _('iCalendar'), 'text/calendar', 'ics') visibility = [{'index':idx, 'label': label, 'active': idx==current_level} for idx, label in enumerate(IttecoEvnSetup(self.env).milestone_levels)] ticket_groups = [{'index':value, 'label': name, 'active': value==ticket_group} for value, name in self._ticket_groups] calculate_on = self.get_statistics_source(req.args.get('calc_on')) data = { 'milestones': milestones, 'milestone_stats': stats, 'mil_types': visibility, 'ticket_groups': ticket_groups, 'calc_on': calculate_on, 'queries': [], 'showall': showall, } self.env.log.debug('data:%s' % data) return 'itteco_roadmap.html', data, None
def _do_save(self, req, db, milestone): if milestone.exists: req.perm(milestone.resource).require('MILESTONE_MODIFY') else: req.perm(milestone.resource).require('MILESTONE_CREATE') ticket_module = TicketModule(self.env) ticket_module._populate(req, milestone.ticket, False) if not milestone.exists: reporter_id = get_reporter_id(req, 'author') milestone.ticket.values['reporter'] = reporter_id action = req.args.get('action', 'leave') field_changes, problems = ticket_module.get_ticket_changes( req, milestone.ticket, action) if problems: for problem in problems: add_warning(req, problem) add_warning( req, tag( tag.p('Please review your configuration, ' 'probably starting with'), tag.pre('[trac]\nworkflow = ...\n'), tag.p('in your ', tag.tt('trac.ini'), '.'))) ticket_module._apply_ticket_changes(milestone.ticket, field_changes) old_name = milestone.name new_name = milestone.ticket['summary'] milestone.name = new_name milestone.description = milestone.ticket['description'] due = req.args.get('duedate', '') milestone.due = due and parse_date(due, tzinfo=req.tz) or None milestone.ticket['duedate'] = milestone.due and str( to_timestamp(milestone.due)) or None completed = req.args.get('completedate', '') retarget_to = req.args.get('target') # Instead of raising one single error, check all the constraints and # let the user fix them by going back to edit mode showing the warnings warnings = [] def warn(msg): add_warning(req, msg) warnings.append(msg) # -- check the name if new_name: if new_name != old_name: # check that the milestone doesn't already exists # FIXME: the whole .exists business needs to be clarified # (#4130) and should behave like a WikiPage does in # this respect. try: other_milestone = StructuredMilestone( self.env, new_name, db) warn( _( 'Milestone "%(name)s" already exists, please ' 'choose another name', name=new_name)) except ResourceNotFound: pass else: warn(_('You must provide a name for the milestone.')) # -- check completed date if action in MilestoneSystem(self.env).starting_action: milestone.ticket['started'] = str(to_timestamp(datetime.now(utc))) if action in MilestoneSystem(self.env).completing_action: milestone.completed = datetime.now(utc) if warnings: return self._render_editor(req, db, milestone) # -- actually save changes if milestone.exists: cnum = req.args.get('cnum') replyto = req.args.get('replyto') internal_cnum = cnum if cnum and replyto: # record parent.child relationship internal_cnum = '%s.%s' % (replyto, cnum) now = datetime.now(utc) milestone.save_changes(get_reporter_id(req, 'author'), req.args.get('comment'), when=now, cnum=internal_cnum) # eventually retarget opened tickets associated with the milestone if 'retarget' in req.args and completed: cursor = db.cursor() cursor.execute( "UPDATE ticket SET milestone=%s WHERE " "milestone=%s and status != 'closed'", (retarget_to, old_name)) self.env.log.info('Tickets associated with milestone %s ' 'retargeted to %s' % (old_name, retarget_to)) else: milestone.insert() db.commit() add_notice(req, _('Your changes have been saved.')) jump_to = req.args.get('jump_to', 'roadmap') if jump_to == 'roadmap': req.redirect(req.href.roadmap()) elif jump_to == 'whiteboard': req.redirect( req.href.whiteboard('team_tasks') + '#' + milestone.name) else: req.redirect(req.href.milestone(milestone.name))
def _chart_settings(self, milestone): burndown_info = self.burndown_info_provider.metrics(milestone) mils =[] def flatten(mil): mils.append(mil) for kid in mil.kids: flatten(kid) flatten(StructuredMilestone(self.env, milestone)) fmt_date = lambda x: format_datetime(x, '%Y-%m-%dT%H:%M:%S') cvs_data = graphs = events = None if burndown_info: metrics, graphs = burndown_info def get_color(tkt_type): tkt_cfg = self.ticket_type_config if tkt_cfg: cfg = tkt_cfg.get(tkt_type) if cfg: return cfg.get('max_color') graphs = [{'name': graph, 'color': get_color(graph)} for graph in graphs] burndown_cvs_data = burnup_cvs_data = [] keys = ['burndown', 'approximation', 'ideal'] milestone_dates= dict([(mil.completed or mil.due, mil) for mil in mils ]) events =[] prev_burndown = metrics[0]['burndown'] prev_burnup = 0 def genitems(metric): yield fmt_date(metric['datetime']) for key in keys: yield str(metric.get(key,'')) for metric in metrics: ts = metric['datetime'] line = ",".join(genitems(metric)) burnup_sum = 0 burnup = metric.get('burnup',[]) for item in burnup: burnup_sum -= item line +=','+str(-1*item) if burnup: line +=','+str(burnup_sum) burndown_cvs_data.append(line) if ts in milestone_dates: mil = milestone_dates[ts] if mil.is_completed: del milestone_dates[ts] burndown = metric['burndown'] events.append({'datetime': fmt_date(mil.completed), 'extended': True, 'text': '"%s" completed\nBurndown delta %d\nBurnup delta %d.' \ % (mil.name, prev_burndown-burndown, prev_burnup-burnup_sum) , 'url': self.env.abs_href('milestone',mil.name)}) burndown_delta =0 prev_burnup = burnup_sum prev_burndown = burndown events.extend([{'datetime': fmt_date(mil.due), 'text': '"%s" is planned to be completed.' % mil.name , 'url': self.env.abs_href('milestone',mil.name)} for mil in milestone_dates.itervalues()]) cvs_data = "<![CDATA["+"\n".join(burndown_cvs_data)+"]]>" data = {'data': cvs_data, 'graphs': graphs, 'events': events} return 'iiteco_chart_settings.xml', data, 'text/xml'
def metrics(self, milestone): mil = StructuredMilestone(self.env, milestone) if not mil.is_started: return None scope = self._milestone_scope(mil) scope_types = IttecoEvnSetup(self.env).scope_element types = self._work_types() one_day_delta = timedelta(1) metrics = [{ 'datetime': mil.started, 'burndown': scope, 'burnup': [0] * len(types) }] def clone_item(item, new_time): item = deepcopy(item) item['datetime'] = new_time metrics.append(item) return item for ts, ttype, sum in self._burnup_info(mil): last = metrics[-1] if ts != last['datetime']: if self.fill_idle_days: time_delta = ts - last['datetime'] if time_delta.days > 1: last = clone_item(last, ts - one_day_delta) last = clone_item(last, ts) if ttype in self.initial_plan_element: #this ticket type should influence burndown last['burndown'] -= sum elif ttype not in scope_types: #burnup by ticket type turned around axis X, in order to look like burndown idx = types.index(ttype) last['burnup'][idx] += sum calculated_end_date = None if len(metrics) > 1: # do we have any progress? start = metrics[0] end = metrics[-1] if start['burndown'] != end['burndown']: # do we have any progress to perform approximations? start_ts = to_timestamp(start['datetime']) end_ts = to_timestamp(end['datetime']) calc_ts = start_ts+ \ start['burndown']*(end_ts - start_ts)/ \ (start['burndown']-end['burndown']) end['approximation'] = end['burndown'] calculated_end_date = to_datetime(calc_ts) if mil.due: #if milestone is fully planned, add line of ideal progress if mil.due > metrics[-1]['datetime']: metrics.append({'datetime': mil.due}) metrics[0]['ideal'] = scope metrics[-1]['ideal'] = 0 if calculated_end_date: if calculated_end_date > metrics[-1]['datetime']: metrics.append({ 'datetime': calculated_end_date, 'approximation': 0 }) else: #let's find a correct place on timeline for calculated end date for i in xrange(0, len(metrics)): metric = metrics[i] if metric['datetime'] >= calculated_end_date: if metric['datetime'] > calculated_end_date: metric = {'datetime': calculated_end_date} metrics.insert(i, metric) metric['approximation'] = 0 break return metrics, types
def process_request(self, req): milestone_realm = Resource('milestone') req.perm.require('MILESTONE_VIEW') showall = req.args.get('show') == 'all' db = self.env.get_db_cnx() milestones = [ m for m in StructuredMilestone.select(self.env, True, db) if 'MILESTONE_VIEW' in req.perm(m.resource) ] requested_fmt = req.args.get('format') if requested_fmt == 'ics': self.render_ics(req, db, milestones) return max_level = len(IttecoEvnSetup(self.env).milestone_levels) max_level = max_level and max_level - 1 or 0 current_level = int(req.args.get('mil_type', max_level)) if current_level == -1: #show all milestones regardless to the level milestones = sum( [_get_milestone_with_all_kids(mil) for mil in milestones], []) else: #filter by level i = 0 while i < current_level: next_level_mils = [] for m in milestones: next_level_mils.extend(m.kids) milestones = next_level_mils i += 1 calc_on = req.args.get('calc_on') ticket_group = req.args.get('ticket_group', 'all') selected_types = None if ticket_group == 'scope_element': selected_types = IttecoEvnSetup(self.env).scope_element elif ticket_group == 'work_element': selected_types = IttecoEvnSetup(self.env).work_element else: ticket_group = 'all' selected_type_names = [ tkt_type.name for tkt_type in Type.select(self.env) if selected_types is None or tkt_type.value in selected_types ] stats = [] milestones = [ mil for mil in milestones if showall or not mil.is_completed ] for milestone in milestones: tickets = get_tickets_for_structured_milestone( self.env, db, milestone.name, calc_on, selected_type_names) tickets = apply_ticket_permissions(self.env, req, tickets) stat = SelectionTicketGroupStatsProvider( self.env).get_ticket_group_stats(tickets, calc_on) stats.append( milestone_stats_data( req, stat, [m.name for m in _get_milestone_with_all_kids(milestone)])) if requested_fmt == 'json': self._render_milestones_stats_as_json(req, milestones, stats) return # FIXME should use the 'webcal:' scheme, probably username = None if req.authname and req.authname != 'anonymous': username = req.authname icshref = req.href.roadmap(show=req.args.get('show'), user=username, format='ics') add_link(req, 'alternate', icshref, _('iCalendar'), 'text/calendar', 'ics') visibility = [{ 'index': idx, 'label': label, 'active': idx == current_level } for idx, label in enumerate( IttecoEvnSetup(self.env).milestone_levels)] ticket_groups = [{ 'index': value, 'label': name, 'active': value == ticket_group } for value, name in self._ticket_groups] calculate_on = self.get_statistics_source(req.args.get('calc_on')) data = { 'milestones': milestones, 'milestone_stats': stats, 'mil_types': visibility, 'ticket_groups': ticket_groups, 'calc_on': calculate_on, 'queries': [], 'showall': showall, } self.env.log.debug('data:%s' % data) return 'itteco_roadmap.html', data, None
def _render_admin_panel(self, req, cat, page, milestone): req.perm.require('TICKET_ADMIN') add_stylesheet(req, 'itteco/css/common.css') add_jscript( req, [ 'stuff/ui/ui.core.js', 'stuff/ui/ui.resizable.js', 'custom_select.js' ], IttecoEvnSetup(self.env).debug ) # Detail view? if milestone: mil = StructuredMilestone(self.env, milestone) if req.method == 'POST': if req.args.get('save'): mil.name = req.args.get('name') mil.due = mil.completed = None due = req.args.get('duedate', '') if due: mil.due = parse_date(due, req.tz) if req.args.get('completed', False): completed = req.args.get('completeddate', '') mil.completed = parse_date(completed, req.tz) if mil.completed > datetime.now(utc): raise TracError(_('Completion date may not be in ' 'the future'), _('Invalid Completion Date')) mil.description = req.args.get('description', '') mil.parent = req.args.get('parent', None) if mil.parent and mil.parent==mil.name: raise TracError(_('Milestone cannot be parent for itself,Please, give it another thought.'), _('Something is wrong with Parent Milestone. Will you check it please?')) if mil.parent and not StructuredMilestone(self.env, mil.parent).exists: raise TracError(_('Milestone should have a valid parent. It does not look like this is the case.'), _('Something is wrong with Parent Milestone. Will you check it please?')) mil.update() req.redirect(req.href.admin(cat, page)) elif req.args.get('cancel'): req.redirect(req.href.admin(cat, page)) add_script(req, 'common/js/wikitoolbar.js') data = {'view': 'detail', 'milestone': mil} else: if req.method == 'POST': # Add Milestone if req.args.get('add') and req.args.get('name'): name = req.args.get('name') try: StructuredMilestone(self.env, name) except ResourceNotFound: mil = StructuredMilestone(self.env) mil.name = name if req.args.get('duedate'): mil.due = parse_date(req.args.get('duedate'), req.tz) mil.parent = req.args.get('parent', None) if mil.parent and not StructuredMilestone(self.env, mil.parent).exists: raise TracError(_('Milestone should have a valid parent. It does not look like this is the case'), _('Something is wrong with Parent Milestone. Will you check it please?')) mil.insert() req.redirect(req.href.admin(cat, page)) else: raise TracError(_('Sorry, milestone %s already exists.') % name) # Remove milestone elif req.args.get('remove'): sel = req.args.get('sel') if not sel: raise TracError(_('Please, select the milestone.')) if not isinstance(sel, list): sel = [sel] db = self.env.get_db_cnx() for name in sel: mil = StructuredMilestone(self.env, name, db=db) mil.delete(db=db) db.commit() req.redirect(req.href.admin(cat, page)) # Set default milestone elif req.args.get('apply'): if req.args.get('default'): name = req.args.get('default') self.config.set('ticket', 'default_milestone', name) self.config.save() req.redirect(req.href.admin(cat, page)) data = { 'view': 'list', 'default': self.config.get('ticket', 'default_milestone'), } # Get ticket count db = self.env.get_db_cnx() cursor = db.cursor() milestones = [] structured_milestones = StructuredMilestone.select(self.env) mil_names = self._get_mil_names(structured_milestones) cursor.execute("SELECT milestone, COUNT(*) FROM ticket " "WHERE milestone IN (%s) GROUP BY milestone" % ("%s,"*len(mil_names))[:-1], mil_names) mil_tkt_quantity = {} for mil, cnt in cursor: mil_tkt_quantity[mil]=cnt data.update({ 'date_hint': get_date_format_hint(), 'milestones': [(mil, 0) for mil in structured_milestones],# we recover this anyway 'structured_milestones': structured_milestones, 'milestone_tickets_quantity': mil_tkt_quantity, 'max_milestone_level': self.milestone_levels and len(self.milestone_levels)-1 or 0, 'datetime_hint': get_datetime_format_hint() }) return 'itteco_admin_milestones.html', data