def expand_macro(self, formatter, name, content): req = formatter.req # Analyze the arguments list, that should be in content args = content.split(',') if len(args) == 0: raise TracError("No argument provided for %s." % self.__class__.__name__) # The first must be the type of the chart chart_type = self._get_chart_type(args[0]) # The second the Sprint sprint_name = self._get_sprint(args[1]) width, height = self._get_dimensions(args[2:]) filter_by = None if len(args) >= 4: filter_by = args[3] chart_params = dict(sprint_name=sprint_name, width=width, height=height, filter_by=filter_by) debug(self.env, "Params: %s" % chart_params) chart = ChartGenerator(self.env).get_chartwidget( chart_type, **chart_params) chart.prepare_rendering(req) return html.DIV(chart.display())
def ticket_changed(self, ticket, comment, author, old_values): ticket = AgiloTicket.as_agilo_ticket(ticket) generator = ChartGenerator(self.env) # AT: this will only work if the task has been explicitly planned for # the sprint, otherwise it won't update. The sprint change is good for # task containers. # if ticket[Key.SPRINT]: if ticket[Key.SPRINT] or ticket.is_writeable_field(Key.REMAINING_TIME): sprint = self._get_sprint_name(ticket) generator.invalidate_cache(sprint_name=sprint) if old_values.get(Key.SPRINT) and old_values[Key.SPRINT] != ticket[Key.SPRINT]: generator.invalidate_cache(sprint_name=old_values[Key.SPRINT])
def _add_charts_to_template_data(self, req, current_sprint_name, data): if current_sprint_name is None: # If we don't have at least one running sprint, we can not display # any chart. data['burndown'] = FakeWidget(self.env, '') data['points'] = FakeWidget(self.env, '') data['pie'] = FakeWidget(self.env, '') data['ticket_stats'] = FakeWidget(self.env, '') return cached_data = dict() if current_sprint_name not in [None, '']: backlog = self._get_sprint_backlog(sprint_name=current_sprint_name) cached_data = dict(tickets=backlog) # Hour Burndown Chart chart_generator = ChartGenerator(self.env) get_widget = chart_generator.get_chartwidget widget = get_widget(ChartType.BURNDOWN, width=680, height=350, sprint_name=current_sprint_name, cached_data=cached_data) widget.prepare_rendering(req) data['burndown'] = widget # Story Point Burndown Chart chart_generator = ChartGenerator(self.env) get_widget = chart_generator.get_chartwidget widget = get_widget(ChartType.POINT_BURNDOWN, width=680, height=350, sprint_name=current_sprint_name, cached_data=cached_data) widget.prepare_rendering(req) data['points'] = widget widget = get_widget(ChartType.SPRINT_RESOURCES_STATS, width=300, height=300, sprint_name=current_sprint_name, cached_data=cached_data) widget.prepare_rendering(req) data['pie'] = widget widget = get_widget(ChartType.SPRINT_TICKET_STATS, width=300, height=300, sprint_name=current_sprint_name) widget.prepare_rendering(req) data['ticket_stats'] = widget
def testTeamMetricsChartContainsAllMetricsDataForMultipleSeries(self): self.env.compmgr.enabled[MetricsChartGenerator] = True today = now() self._add_metrics(self.sprint, **{Key.VELOCITY: 10}) start_sprint2 = today - timedelta(days=30) sprint2 = self.teh.create_sprint(name='Sprint 2', start=start_sprint2, duration=20, team=self.sprint.team) self._add_metrics(sprint2, **{Key.ESTIMATED_VELOCITY: 7}) start_sprint3 = today - 2 * timedelta(days=30) sprint3 = self.teh.create_sprint(name='Sprint 3', start=start_sprint3, duration=20, team=self.sprint.team) self._add_metrics(sprint3, **{Key.VELOCITY: 5}) self._add_metrics(sprint3, **{Key.ESTIMATED_VELOCITY: 9}) widget = ChartGenerator(self.env).get_chartwidget(ChartType.TEAM_METRICS, team_name=self.sprint.team.name, metric_names=[Key.ESTIMATED_VELOCITY, Key.VELOCITY]) self.assert_equals(['Sprint 3', 'Sprint 2', self.sprint.name], widget.data['sprint_names']) metrics = widget.data['metrics'] self.assert_equals(2, len(metrics)) velocity_label, velocity_data = metrics[0] self.assert_equals(get_label(Key.ESTIMATED_VELOCITY), velocity_label) self.assert_equals([(0, 9), (1, 7)], velocity_data) velocity_label, velocity_data = metrics[1] self.assert_equals(get_label(Key.VELOCITY), velocity_label) self.assert_equals([(0, 5), (2, 10)], velocity_data)
def detail_save_view(self, req, cat, page, name): team_member = req.args.get('team_member') or req.args.get('new_team_member') if team_member: # save from team member view return self.member_save(req, cat, page, name) if name=='unassigned': team = None else: team = self.tm.get(name=name) if not team: return req.redirect(req.href.admin(cat, page)) if req.args.get('delete'): for member_name in req.args.getlist('delete'): member = self.tmm.get(name=member_name) if team: member.team = None self.tmm.save(member) else: self.tmm.delete(member) # removing members will reduce the capacity, hence the ideal # burndown must be recalculated ChartGenerator(self.env).invalidate_cache() return req.redirect(req.href.admin(cat, page, name)) team.description = req.args.get('description') self.tm.save(team) return req.redirect(req.href.admin(cat, page))
def member_save(self, req, cat, page, name): if req.args.get('createUser_cancel'): self.redirect_to_team_view_page(req, cat, page, name) team = self.tm.get(name=name) member_name = req.args.get('team_member') or req.args.get('new_team_member') team_member = self._get_or_create_team_member_without_saving(req, member_name, team) if self.show_confirmation_user_creation(req, team_member.name): data = {'view': 'create_user_confirm', 'user_name': team_member.name, 'team_name': team.name, 'member_description' : team_member.description} return 'agilo_admin_team.html', data query_params = {} if self.perform_user_creation(req): self.create_user_and_grant_permissions(req, team_member) query_params = dict(team_member=team_member.name) else: try: team_member.capacity = [float(req.args.get('ts_%d' % i) or '0') for i in range(7)] except ValueError: # TODO: Enhance error handling add_warning(req, 'Could not parse time value') self.redirect_to_team_view_page(req, cat, page, name) self.tmm.save(team_member) # capacity may have changed - hence the ideal burndown is different ChartGenerator(self.env).invalidate_cache() self.redirect_to_team_view_page(req, cat, page, name, **query_params)
def _get_and_prepare_burndown_chart_if_neccessary(self, req, backlog): if not backlog.is_sprint_backlog(): return {} sprint = backlog.sprint() get_widget = ChartGenerator(self.env).get_chartwidget widget = get_widget(ChartType.BURNDOWN, sprint_name=sprint.name) widget.prepare_rendering(req) return dict(chart=widget)
def testSprintTicketStatsChartUsesAliases(self): self.env.compmgr.enabled[SprintTicketStatsChartGenerator] = True self.teh.create_ticket(Type.USER_STORY, {Key.SPRINT: self.sprint.name}) get_widget = ChartGenerator(self.env).get_chartwidget widget = get_widget(ChartType.SPRINT_TICKET_STATS, sprint_name=self.sprint.name) chart_labels = set([item[1] for item in widget.data['labels']]) self.assert_equals(set(['User Story', 'Task']), chart_labels)
def _get_charts(self, req, team, available_metrics): chart_widgets = [] grouped_metrics = self._get_metric_groups(available_metrics) get_widget = ChartGenerator(self.env).get_chartwidget for metric_names in grouped_metrics: widget = get_widget(ChartType.TEAM_METRICS, team_name=team.name, metric_names=metric_names) widget.prepare_rendering(req) chart_widgets.append(widget) return chart_widgets
def get_widget(self, sprint_name, use_cache=False, filter_by=None, tz=localtz): get_widget = ChartGenerator(self.env).get_chartwidget widget = get_widget(ChartType.BURNDOWN, sprint_name=sprint_name, use_cache=use_cache, filter_by=filter_by) widget.prepare_rendering(self.teh.mock_request(tz=tz)) return widget
def expand_macro(self, formatter, name, content): req = formatter.req # Analyze the arguments list, that should be in content args = content.split(',') if len(args) == 0: raise TracError("No argument provided for %s." % self.__class__.__name__) # The first must be the type of the chart chart_type = self._get_chart_type(args[0]) # The second the Sprint sprint_name = self._get_sprint(args[1]) width, height = self._get_dimensions(args[2:]) filter_by = None if len(args) >= 4: filter_by = args[3] chart_params = dict(sprint_name=sprint_name, width=width, height=height, filter_by=filter_by) debug(self.env, "Params: %s" % chart_params) chart = ChartGenerator(self.env).get_chartwidget(chart_type, **chart_params) chart.prepare_rendering(req) return html.DIV(chart.display())
def ticket_changed(self, ticket, comment, author, old_values): ticket = AgiloTicket.as_agilo_ticket(ticket) generator = ChartGenerator(self.env) # AT: this will only work if the task has been explicitly planned for # the sprint, otherwise it won't update. The sprint change is good for # task containers. #if ticket[Key.SPRINT]: if ticket[Key.SPRINT] or ticket.is_writeable_field(Key.REMAINING_TIME): sprint = self._get_sprint_name(ticket) generator.invalidate_cache(sprint_name=sprint) if old_values.get( Key.SPRINT) and old_values[Key.SPRINT] != ticket[Key.SPRINT]: generator.invalidate_cache(sprint_name=old_values[Key.SPRINT])
def testSprintTicketStatsChartShowsCorrectTotal(self): self.env.compmgr.enabled[SprintTicketStatsChartGenerator] = True self.teh.create_ticket(Type.USER_STORY, {Key.SPRINT: self.sprint.name}) get_widget = ChartGenerator(self.env).get_chartwidget widget = get_widget(ChartType.SPRINT_TICKET_STATS, sprint_name=self.sprint.name) self.assert_equals(2, len(widget.data['closed'])) task_alias = AgiloConfig(self.env).ALIASES.get(Type.TASK) self.assert_equals((1, task_alias), widget.data['labels'][1]) task_data = zip(widget.data['closed'], widget.data['planned'], widget.data['total'])[1] closed, planned, total = [value for (t, value) in task_data] # previously we assumed that closed+planned == total which is wrong, so # detect this situation here... self.assert_not_equals(closed + planned, total) self.assert_equals(closed + planned + 2, total)
def ticket_deleted(self, ticket): ticket = AgiloTicket.as_agilo_ticket(ticket) if ticket[Key.SPRINT] or ticket.is_writeable_field(Key.REMAINING_TIME): sprint = self._get_sprint_name(ticket) generator = ChartGenerator(self.env) generator.invalidate_cache(sprint_name=sprint)