def _get_wbitems_config(self, board_type): milestoneitem = { 'realm' : 'milestone', 'types' : [milestone_ticket_type], 'weight': self.work_element_weight_field, 'weightlabel' : 'CP' } scopeitem = { 'realm' : 'ticket', 'types' : [t for t in IttecoEvnSetup(self.env).scope_element], 'weight': self.scope_element_weight_field, 'weightlabel' : 'BV' } def read_options(fname): options = [ f['options'] \ for f in TicketSystem(self.env).get_ticket_fields() \ if f['name']==fname ] if options: return options[0] workitem = { 'realm' : 'ticket', 'types' : [t for t in IttecoEvnSetup(self.env).work_element], 'weight': self.work_element_weight_field, 'weightlabel' : 'CP', 'options' : read_options(self.work_element_weight_field) } if board_type=='team_tasks': return (scopeitem, workitem) else: return (milestoneitem, scopeitem)
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 setUp(self): self.env = EnvironmentStub(enable=['trac.*', 'itteco.*']) self.env.config.set('trac', 'permission_policies', 'CalendarSystem, DefaultPermissionPolicy') self.itteco_env = IttecoEvnSetup(self.env) self.itteco_env.upgrade_environment(self.env.get_db_cnx()) self.calendar_system = CalendarSystem(self.env) self.perm_system = perm.PermissionSystem(self.env) self.perm = perm.PermissionCache(self.env, 'testuser')
class CalendarSystemTestCase(unittest.TestCase): def setUp(self): self.env = EnvironmentStub(enable=['trac.*', 'itteco.*']) self.env.config.set('trac', 'permission_policies', 'CalendarSystem, DefaultPermissionPolicy') self.itteco_env = IttecoEvnSetup(self.env) self.itteco_env.upgrade_environment(self.env.get_db_cnx()) self.calendar_system = CalendarSystem(self.env) self.perm_system = perm.PermissionSystem(self.env) self.perm = perm.PermissionCache(self.env, 'testuser') def tearDown(self): self.env.reset_db() def _create_calendar(self, name, username, type=CalendarType.Private): calendar = Calendar(self.env, None, username) calendar.theme = 1 calendar.type = type id = calendar.insert() return calendar def test_permission_policies(self): self.perm_system.grant_permission('testuser', 'CALENDAR_CREATE') self.perm_system.grant_permission('testuser', 'CALENDAR_MODIFY') self.perm_system.grant_permission('testuser', 'CALENDAR_VIEW') self.perm_system.grant_permission('testuser', 'CALENDAR_DELETE') self.assertTrue('CALENDAR_CREATE' in self.perm) calendar = self._create_calendar('cal1', 'testuser') c = calendar.resource self.assertTrue('CALENDAR_MODIFY' in self.perm(c)) self.assertTrue('CALENDAR_VIEW' in self.perm(c)) self.assertTrue('CALENDAR_DELETE' in self.perm(c)) calendar2 = self._create_calendar('cal2', 'user1') c2 = calendar2.resource self.assertTrue('CALENDAR_VIEW' not in self.perm(c2), \ 'There should be no access to private calendar of another user.') calendar3 = self._create_calendar('cal3', 'user1', CalendarType.Shared) c3 = calendar3.resource self.assertTrue('CALENDAR_VIEW' in self.perm(c3), \ 'There should view access to shared calendars.')
def query_tasks(self, req, context): milestone_name = self._resolve_milestone(context.get('milestone'), context.get('show_sub_mils'), context.get('show_completed')) all_tkt_types = set([ticket_type.name for ticket_type in Type.select(self.env)]) scope_tkt_types = set([t for t in IttecoEvnSetup(self.env).scope_element]) workitem_tkt_types = all_tkt_types - scope_tkt_types \ - set([t for t in IttecoEvnSetup(self.env).excluded_element]) self.env.log.debug('workitem_tkt_types ="%s"' % (workitem_tkt_types,)) roots, ticket_ids = self._get_tickets_graph(req, milestone_name, (scope_tkt_types , workitem_tkt_types)) self.env.log.debug('roots ="%s"' % (roots,)) empty_scope_element = {'summary': 'Not assigned to any story'} not_assigned_work_items, _ignore = self._get_tickets_graph(req, milestone_name, (workitem_tkt_types,)) for ticket in not_assigned_work_items: if ticket['id'] not in ticket_ids: empty_scope_element.setdefault('references', []).append(ticket) roots.append(empty_scope_element) return roots
def _get_ticket_config(self): ticket_config = self.env.config['itteco-whiteboard-tickets-config'] if self._old_ticket_config!=ticket_config: default_fields = ticket_config.getlist('default_fields') show_workflow = ticket_config.getbool('show_workflow') allowed_tkt_types = [ type.name for type in Type.select(self.env)] _ticket_type_config = {} for option in ticket_config: try: tkt_type, prop = option.split('.',1) if tkt_type and ( tkt_type in allowed_tkt_types or \ tkt_type[0]=='$'): _ticket_type_config.setdefault( tkt_type, { 'fields' : default_fields, 'workflow' : show_workflow } )[prop] = ticket_config.get(option) except ValueError : pass scope_types = IttecoEvnSetup(self.env).scope_element scope_element_field_name = self.scope_element_weight_field work_types = IttecoEvnSetup(self.env).work_element work_element_field_name = self.work_element_weight_field for type in allowed_tkt_types: if type not in _ticket_type_config: _ticket_type_config[type]={'fields':default_fields, 'workflow' : show_workflow} for type in _ticket_type_config.iterkeys(): _ticket_type_config[type]['weight_field_name'] = \ type in scope_types and scope_element_field_name or work_element_field_name _ticket_type_config[type]['fields']=self._get_ticket_fields( _ticket_type_config[type].get('fields'), []) self._ticket_type_config = _ticket_type_config self._old_ticket_config=ticket_config return self._ticket_type_config
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 setUp(self): self.env = EnvironmentStub(enable=['trac.*', 'itteco.*']) self.env.config.set('trac', 'permission_policies', 'CalendarSystem, DefaultPermissionPolicy') self.itteco_env= IttecoEvnSetup(self.env) self.itteco_env.upgrade_environment(self.env.get_db_cnx()) #self.calendar_system = CalendarSystem(self.env) self.rpc = CalendarRPC(self.env) self.perm_system = perm.PermissionSystem(self.env) self.req = Mock() self.req.perm = perm.PermissionCache(self.env, 'testuser') self._createTestData();
class CalendarRPCTestCase(unittest.TestCase): def setUp(self): self.env = EnvironmentStub(enable=['trac.*', 'itteco.*']) self.env.config.set('trac', 'permission_policies', 'CalendarSystem, DefaultPermissionPolicy') self.itteco_env= IttecoEvnSetup(self.env) self.itteco_env.upgrade_environment(self.env.get_db_cnx()) #self.calendar_system = CalendarSystem(self.env) self.rpc = CalendarRPC(self.env) self.perm_system = perm.PermissionSystem(self.env) self.req = Mock() self.req.perm = perm.PermissionCache(self.env, 'testuser') self._createTestData(); def tearDown(self): self.env.reset_db() def _createTestData(self): self._create_calendar('cal1', 'testuser') self._create_calendar('cal2', 'testuser') self._create_calendar('cal3', 'user1') self._create_calendar('cal4', 'user1', CalendarType.Shared) def _create_calendar(self, name, username, type= CalendarType.Private): calendar = Calendar(self.env, None, username) calendar.name = name calendar.theme = 1 calendar.type = type id = calendar.insert() return calendar def test_calendars_rpc(self): self.assertEqual('calendar', self.rpc.xmlrpc_namespace()) self.assertEqual(['cal1','cal2','cal4'], [c.name for c in self.rpc.query(self.req)])
def delete(self, retarget_to=None, author=None, db=None): handle_commit = False if not db: db = self.env.get_db_cnx() handle_commit = True self.milestone.delete(retarget_to, author, db) self.ticket.delete(db) if handle_commit: db.commit() listeners = IttecoEvnSetup(self.env).change_listeners for listener in listeners: listener.milestone_deleted(self)
def _burnup_info(self, milestone): mils = [milestone.name] + [m.name for m in milestone.kids] tkt_types = None if self.count_burndown_on != 'quantity': all_types = Type.select(self.env) tkt_types = [ type.name for type in all_types if type.name not in IttecoEvnSetup(self.env).scope_element ] else: #the only way to count progress on the tickets quantity is to have # the same set of tickets for scope and for progress calculations tkt_types = self._work_types() return self._get_job_done(mils, tkt_types)
class CalendarRPCTestCase(unittest.TestCase): def setUp(self): self.env = EnvironmentStub(enable=['trac.*', 'itteco.*']) self.env.config.set('trac', 'permission_policies', 'CalendarSystem, DefaultPermissionPolicy') self.itteco_env = IttecoEvnSetup(self.env) self.itteco_env.upgrade_environment(self.env.get_db_cnx()) #self.calendar_system = CalendarSystem(self.env) self.rpc = CalendarRPC(self.env) self.perm_system = perm.PermissionSystem(self.env) self.req = Mock() self.req.perm = perm.PermissionCache(self.env, 'testuser') self._createTestData() def tearDown(self): self.env.reset_db() def _createTestData(self): self._create_calendar('cal1', 'testuser') self._create_calendar('cal2', 'testuser') self._create_calendar('cal3', 'user1') self._create_calendar('cal4', 'user1', CalendarType.Shared) def _create_calendar(self, name, username, type=CalendarType.Private): calendar = Calendar(self.env, None, username) calendar.name = name calendar.theme = 1 calendar.type = type id = calendar.insert() return calendar def test_calendars_rpc(self): self.assertEqual('calendar', self.rpc.xmlrpc_namespace()) self.assertEqual(['cal1', 'cal2', 'cal4'], [c.name for c in self.rpc.query(self.req)])
def insert(self, db=None): assert self.name, 'Cannot create milestone with no name' handle_commit = False if not db: db = self.env.get_db_cnx() handle_commit = True self.milestone.insert(db) self.ticket.insert(db=db) if handle_commit: db.commit() listeners = IttecoEvnSetup(self.env).change_listeners for listener in listeners: listener.milestone_created(self)
def _milestone_scope(self, milestone): """ Returns numeric representation of the scope for given milestone """ mils = [milestone.name] tkt_types = None if self.count_burndown_on == 'quantity': mils += [m.name for m in milestone.kids] tkt_types = self._work_types() else: all_types = Type.select(self.env) tkt_types = [ type.name for type in all_types if type.name in IttecoEvnSetup(self.env).scope_element ] return self._calculate_scope(mils, tkt_types)
def filter_stream(self, req, method, filename, stream, data): if req.path_info.startswith('/report/'): link_builder = req.href.chrome debug = IttecoEvnSetup(self.env).debug def script_tag(path=None, content=None): kw = {'type': "text/javascript"} if path: kw['src'] = link_builder(map_script(path, debug)) return tag.script(content, **kw) stream |= Transformer("//head").prepend( tag.link(type="text/css", rel="stylesheet", href=link_builder("itteco/css/report.css")) ).append( tag( script_tag('stuff/plugins/jquery.rpc.js'), script_tag('stuff/ui/plugins/jquery.jeditable.js'), script_tag("editable_report.js"), script_tag( content="$(document).ready(function(){" + "$('#main').editableReport(" + "{" + "rpcurl: '" + req.href('login', 'xmlrpc') + "'," + "fields: " + self.fields_dict(data.get('header_groups', [])) + "})" + "});"))) try: stream |= Transformer("//head").prepend( tag( # TODO fix scripts base path tag.link(type="text/css", rel="stylesheet", href=link_builder("itteco/css/common.css")), tag.link(type="text/css", rel="stylesheet", href=link_builder("itteco/css/report.css")), #tag.link(type="text/css", rel="stylesheet", href=link_builder("itteco/css/colorbox/colorbox.css")) )).append( tag(script_tag("stuff/ui/ui.core.js"), script_tag("stuff/ui/ui.draggable.js"), script_tag("stuff/ui/ui.droppable.js"), script_tag("stuff/ui/ui.resizable.js"), script_tag('stuff/ui/plugins/jquery.colorbox.js'), script_tag('custom_select.js'))) except ValueError, e: #we do not fail the report it self, may be it works in read only mode self.env.log.debug('Report decoration failed: %s' % e)
def process_request(self, req): if req.method == 'POST' and ('preview' not in req.args) and req.args.get('id'): id = int(req.args.get('id')) ticket = Ticket(self.env, id) if ticket.exists: def get_ids(req, attr_name): ids = req.args.get(attr_name, []) return isinstance(ids, basestring) and (ids, ) or ids links = TicketLinks(self.env, ticket) links.outgoing_links = [ int(id) for id in get_ids(req, 'ticket_links') ] links.wiki_links = get_ids(req, 'wiki_links') links.save() template, data, content_type = req.args[ 'original_handler'].process_request( RedirectInterceptor(req, self._get_jump_to_url)) if template == 'ticket.html': add_jscript(req, ['stuff/plugins/jquery.rpc.js', 'references.js'], IttecoEvnSetup(self.env).debug) tkt = data['ticket'] links = TicketLinks(self.env, tkt) data['filters'] = self._get_search_filters(req) data['ticket_links'] = { 'incoming': { 'title': 'Referred by:', 'blockid': 'inblock', 'removable': False, 'links': self._ids_to_tickets(links.incoming_links) }, 'outgoing': { 'title': 'Refers to:', 'blockid': 'outblock', 'removable': True, 'links': self._ids_to_tickets(links.outgoing_links) }, 'wiki': links.wiki_links } data['jump_to'] = req.args.get('jump_to') or referer_module(req) return 'itteco_ticket.html', data, content_type return template, data, content_type
def save_changes(self, author, comment, when=None, db=None, cnum=''): assert self.name, 'Cannot update milestone with no name' handle_commit = False if not db: db = self.env.get_db_cnx() handle_commit = True old_values = self.ticket._old self.milestone.update(db) self.ticket.save_changes(author, comment, when, db, cnum) if handle_commit: db.commit() old_values['name'] = self.name listeners = IttecoEvnSetup(self.env).change_listeners for listener in listeners: listener.milestone_changed(self, old_values)
def post_process_request(self, req, template, data, content_type): self.env.log.debug( 'post_process_request req=%s, pathinfo=%s, args=%s' % (req, req.path_info, req.args)) if req.path_info.startswith('/ticket/') \ or req.path_info.startswith('/newticket') \ or req.path_info.startswith('/milestone') \ or req.path_info.startswith('/roadmap'): #add_stylesheet(req, 'itteco/css/common.css') add_jscript(req, [ 'stuff/ui/ui.core.js', 'stuff/ui/ui.resizable.js', 'stuff/ui/ui.draggable.js', 'stuff/ui/ui.droppable.js', 'custom_select.js' ], IttecoEvnSetup(self.env).debug) return template, data, content_type
def my_active_tickets(self, req): """ Returns all none closed tickets assigned to the current user.""" user = req.authname def ticket_as_dict(id, summary): return { 'ticketId': id, 'summary': summary } final_statuses = [status for status in IttecoEvnSetup(self.env).final_statuses] db = self.env.get_db_cnx() cursor = db.cursor() cursor.execute(""" SELECT id as ticketId, summary FROM ticket WHERE owner = %%s AND status NOT IN (%s)""" % ("%s," * len(final_statuses))[:-1], [user,]+final_statuses) return [ticket_as_dict(tktId, summary) for tktId, summary in cursor]
def _render_ui(self, req): #add_stylesheet(req, 'itteco/css/colorbox/colorbox.css') add_stylesheet(req, 'itteco/css/common.css') add_stylesheet(req, 'itteco/css/calendar.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/ui.datepicker.js', 'stuff/ui/ui.slider.js', 'stuff/ui/plugins/fullcalendar.js', 'stuff/ui/plugins/jquery.colorbox.js', 'stuff/ui/plugins/timepicker.js', 'stuff/plugins/jquery.rpc.js', 'calendar.js' ], IttecoEvnSetup(self.env).debug) icshref = req.href.calendar(format='ics') add_link(req, 'alternate', icshref, _('iCalendar'), 'text/calendar', 'ics') return 'itteco_calendar_view.html', {}, None
def _add_tickets_report_data(self, milestone, req, data): tickets_report = MilestoneSystem(self.env).tickets_report if tickets_report: req.args['MILESTONE'] = milestone.name req.args['id'] = tickets_report report_data = IttecoReportModule(self.env).get_report_data( req, tickets_report) self.env.log.debug('report_data="%s"' % (report_data, )) data.update(report_data) debug = IttecoEvnSetup(self.env).debug add_jscript(req, 'stuff/plugins/jquery.rpc.js', debug) add_jscript(req, 'stuff/ui/plugins/jquery.jeditable.js', debug) add_jscript(req, 'stuff/ui/ui.core.js', debug) add_jscript(req, 'stuff/ui/ui.draggable.js', debug) add_jscript(req, 'stuff/ui/ui.droppable.js', debug) add_jscript(req, 'editable_report.js', debug) add_jscript(req, 'report.js', debug) #add_stylesheet(req, 'itteco/css/report.css') data['fields_config'] = IttecoReportModule(self.env).fields_dict( data.get('header_groups', [])) data['render_report'] = tickets_report or None
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 _get_level_label(self, idx): label = 'undefined' levels = IttecoEvnSetup(self.env).milestone_levels if levels and idx < len(levels): label = levels[idx] return label
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
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 _work_types(self): """ Returns ticket types that are taken into consideration while counting milestone progress """ return list(set(IttecoEvnSetup(self.env).work_element) - \ set(self.initial_plan_element))