def _group_sprints_by_milestone(self, req, milestones_to_show): """ Returns a dict which holds the sprints grouped by milestone (key: milestone, value: list of tuples (sprint, progress bar data)). The dict contains only sprints for milestones in milestones_to_show. """ # save sprints in dictionary, key is the associated milestone provider = DefaultTicketGroupStatsProvider(self.env) milestone_names = [milestone.name for milestone in milestones_to_show] sprints = {} sp_controller = SprintController(self.env) list_sprints = SprintController.ListSprintsCommand(self.env, criteria={'milestone': 'in %s' % \ milestone_names}) for s in sp_controller.process_command(list_sprints): # milestone_name = sprint.milestone # if (not milestone_name) or (milestone_name not in milestone_names): # continue milestone_name = s.milestone if milestone_name not in sprints: sprints[milestone_name] = [] sprint_stats = self.build_sprint_story_statistics(req, s.name, provider=provider) sprint_data = (s, sprint_stats) sprints[milestone_name].append(sprint_data) return sprints
def actual_burndown(self, filter_by_component=None): command = SprintController.GetActualBurndownCommand( self.env, sprint=self.sprint.name, filter_by_component=filter_by_component, remaining_field=BurndownDataConstants.REMAINING_TIME) return SprintController(self.env).process_command(command)
def _sprint(self, req, sprint_name): try: get_sprint = SprintController.GetSprintCommand(self.env, sprint=sprint_name) get_sprint.native = True return SprintController(self.env).process_command(get_sprint) except ICommand.NotValidError, e: self.error_response(req, {}, [exception_to_unicode(e)])
def _do_delete(self, req, name): """Deletes and retarget tickets to a new sprint if specified""" self._do_retarget(req, name) cmd_delete = SprintController.DeleteSprintCommand(self.env, sprint=name) self.controller.process_command(cmd_delete) req.redirect(req.href.roadmap())
def _do_retarget(self, req, name): """Perform a retarget if selected in the request""" retarget = req.args.get('retarget') if retarget: cmd_retarget = SprintController.RetargetTicketsCommand( self.env, sprint=name, retarget=retarget, author=req.authname) self.controller.process_command(cmd_retarget)
def _do_save(self, req, name): """Saves the sprint with the given name""" params = self._prepare_params(req, sprint=name) cmd_save = SprintController.SaveSprintCommand(self.env, **params) self.controller.process_command(cmd_save) sprint, old_sprint = params['sprint'], params['old_sprint'] if sprint and old_sprint and sprint != old_sprint: name = sprint return self.redirect(req, SprintDisplayView(self.env), name)
def _build_story_statistics(self, req, stats_provider, sprint_name): """ Assemble statistics for all stories in the given sprint_name so that the progress bar can be displayed. """ sprint_stats = dict() if not sprint_name: return sprint_stats cmd_get_stories = SprintController.ListTicketsHavingPropertiesCommand(self.env, sprint=sprint_name, properties=[Key.STORY_POINTS]) stories = self.controller.process_command(cmd_get_stories) type_dict = dict() closed_stories = list() inprogress_stories = list() for story in stories: if story[Key.STATUS] == Status.CLOSED: closed_stories.append(story) elif story[Key.STATUS] != Status.NEW: inprogress_stories.append(story) type_dict[story[Key.TYPE]] = True type_names = type_dict.keys() number_open_stories = len(stories) - (len(closed_stories) + \ len(inprogress_stories)) try: stat = TicketGroupStats('User Stories status', 'stories') stat.add_interval(_('Completed'), len(closed_stories), qry_args={Key.STATUS: Status.CLOSED}, css_class='closed', overall_completion=True) stat.add_interval(_('In Progress'), len(inprogress_stories), qry_args={Key.STATUS: Status.ACCEPTED}, css_class='inprogress') stat.add_interval(_('Open'), number_open_stories, qry_args={Key.STATUS: [Status.NEW, Status.REOPENED]}, css_class='open') stat.refresh_calcs() sprint_stats = self._build_sprint_stats_data(req, stat, sprint_name, type_names=type_names) except Exception, e: # The DB is closed? And we don't break for statistics error(stats_provider, "ERROR: %s" % to_unicode(e))
def _load_sprint(self, sprint_name, native=False): sprint = None error_message = None if sprint_name == None: error_message = 'No sprint specified' else: try: # Charts are a layer below the Sprint so they must not import this # module globally to avoid recursive imports from agilo.scrum.sprint import SprintController cmd = SprintController.GetSprintCommand cmd_get_sprint = cmd(self.env, sprint=sprint_name) cmd_get_sprint.native = native sprint = SprintController( self.env).process_command(cmd_get_sprint) except Exception, e: error_message = 'Sprint %s does not exist' % unicode(e) sprint = None
def _prepare_links(self, req, name, milestone): """Prepares the links for the navigation bar""" list_sprints = SprintController.ListSprintsCommand( self.env, criteria={'milestone': name}) sprints = self.controller.process_command(list_sprints) for i, s in enumerate(sprints): s = s['sprint'] if s['name'] == name: if i > 0: add_link(req, 'prev', self.href(sprints[i - 1]['sprint']['name'])) if i < len(sprints) - 1: add_link(req, 'next', self.href(sprints[i + 1]['sprint']['name'])) break # add back link to Milestone if milestone: add_link(req, 'up', req.href.milestone(milestone), 'Milestone') prevnext_nav(req, 'Sprint', 'Milestone')
def prepare_data(self, req, args, data=None): """Returns the sprint visualization data""" if not args: raise TracError("You should provide at least a sprint name: ", args) # Assuming there is a sprint name name = args.get('name') if data is None: data = {} # avoid circular import from agilo.scrum.backlog.web_ui import NewBacklogView data.update({ 'may_edit_sprint': Action.SPRINT_EDIT in req.perm, # show a deletion confirmation if user wanted to delete 'delete_confirmation': 'delete' in req.args, 'close_confirmation': 'close' in req.args, 'edit_form_action': req.href(SprintEditView.url, name, 'edit'), 'confirm_form_action': req.href(SprintConfirmView.url, name, 'confirm'), 'sprint_backlog_link': req.href(NewBacklogView.url, Key.SPRINT_BACKLOG, name) }) list_sprints = \ SprintController.ListSprintsCommand(self.env, criteria={'name': '!=%s' % name}) data['sprints'] = [s.name for s in \ self.controller.process_command(list_sprints)] # Generate the date converter function for this timezone convert_date = view.generate_converter(req.tz) get_sprint = SprintController.GetSprintCommand(self.env, sprint=name) sprint = self.controller.process_command(get_sprint) # update the sprint dates sprint.start = convert_date(sprint.start) sprint.end = convert_date(sprint.end) # update the data with the Sprint info data.update({'sprint': sprint}) # now get the sprint ticket statistics cmd_stats = SprintController.GetTicketsStatisticsCommand(self.env, sprint=name, totals=True) nr_new, nr_planned, nr_closed = self.controller.process_command( cmd_stats) data['planned_tickets'] = nr_new data['in_progres_tickets'] = nr_planned data['open_tickets'] = nr_new + nr_planned data['closed_tickets'] = nr_closed rm = AgiloRoadmapModule(self.env) data['sprint_stats'] = rm.build_sprint_story_statistics(req, name) sprint_dates = { 'start': sprint.start, 'end': sprint.end, 'duration': sprint.duration } if sprint.is_closed: data['date_info'] = "Ended the %(end)s. Duration %(duration)s days (started the %(start)s)" % \ sprint_dates elif sprint.is_currently_running: data['date_info'] = "Started the %(start)s, ends on the %(end)s." % \ sprint_dates else: data['date_info'] = "Starts the %(start)s. Duration %(duration)s days, ends on the %(end)s" % \ sprint_dates return data
def _do_create(self, req, name): """Creates a sprint getting data from the post request""" params = self._prepare_params(req, name=name) cmd_create = SprintController.CreateSprintCommand(self.env, **params) self.controller.process_command(cmd_create) return self.redirect(req, SprintDisplayView(self.env), name)
def _get_sprint(self, req, name): cmd_get = SprintController.GetSprintCommand(self.env, sprint=name) return self.controller.process_command( cmd_get, date_converter=view.generate_converter(req.tz))
def __init__(self): """Initialize a Sprint Controller""" self.controller = SprintController(self.env)
class AgiloRoadmapModule(RoadmapModule): implements(IRequestHandler) def __init__(self): """Initialize a Sprint Controller""" self.controller = SprintController(self.env) # -------------------------------------------------------------------------- # Felix Schwarz, 11.10.2008: Copied from trac.ticket.roadmap (0.11), we # initially needed our own copy of that because we want to filter the ticket # by sprint name and not by milestone name. Now we want to filter by type, too. def _build_sprint_stats_data(self, req, stat, sprint_name, grouped_by='component', group=None, type_names=None): all_type_names = AgiloConfig(self.env).get_available_types() def query_href(extra_args): args = {'sprint': sprint_name, grouped_by: group, 'group': 'status'} # custom addition to show only certain types if type_names != None: if len(type_names) == 1: args[Key.TYPE] = type_names[0] elif len(type_names) > 1: uninteresting_types = set(all_type_names).difference(set(type_names)) args[Key.TYPE] = ['!' + name for name in uninteresting_types] # end of custom addition args.update(extra_args) return req.href.query(args) return {'stats': stat, 'stats_href': query_href(stat.qry_args), 'interval_hrefs': [query_href(interval['qry_args']) for interval in stat.intervals]} # -------------------------------------------------------------------------- def _build_story_statistics(self, req, stats_provider, sprint_name): """ Assemble statistics for all stories in the given sprint_name so that the progress bar can be displayed. """ sprint_stats = dict() if not sprint_name: return sprint_stats cmd_get_stories = SprintController.ListTicketsHavingPropertiesCommand(self.env, sprint=sprint_name, properties=[Key.STORY_POINTS]) stories = self.controller.process_command(cmd_get_stories) type_dict = dict() closed_stories = list() inprogress_stories = list() for story in stories: if story[Key.STATUS] == Status.CLOSED: closed_stories.append(story) elif story[Key.STATUS] != Status.NEW: inprogress_stories.append(story) type_dict[story[Key.TYPE]] = True type_names = type_dict.keys() number_open_stories = len(stories) - (len(closed_stories) + \ len(inprogress_stories)) try: stat = TicketGroupStats('User Stories status', 'stories') stat.add_interval(_('Completed'), len(closed_stories), qry_args={Key.STATUS: Status.CLOSED}, css_class='closed', overall_completion=True) stat.add_interval(_('In Progress'), len(inprogress_stories), qry_args={Key.STATUS: Status.ACCEPTED}, css_class='inprogress') stat.add_interval(_('Open'), number_open_stories, qry_args={Key.STATUS: [Status.NEW, Status.REOPENED]}, css_class='open') stat.refresh_calcs() sprint_stats = self._build_sprint_stats_data(req, stat, sprint_name, type_names=type_names) except Exception, e: # The DB is closed? And we don't break for statistics error(stats_provider, "ERROR: %s" % to_unicode(e)) return sprint_stats