def start_date_validator(form, field): states = States() if states.index(form.state.data) >= states.index(states.start): if field.data is None: raise ValidationError("Start date required since the card's state is %s" % form.state.data) else: field.errors = []
def report_detailed_flow(group="all", months=3, cards_only=False): end = kardboard.util.now() months_ranges = month_ranges(end, months) start_day = make_start_date(date=months_ranges[0][0]) end_day = make_end_date(date=end) if cards_only: only_arg = ('state_card_counts', 'date', 'group') else: only_arg = ('state_counts', 'date', 'group') reports = FlowReport.objects.filter( date__gte=start_day, date__lte=end_day, group=group).only(*only_arg) if not reports: abort(404) chart = {} chart['categories'] = [] series = [] for state in States(): seri = {'name': state, 'data': []} series.append(seri) done_starting_point = 0 for report in reports: chart['categories'].append(report.date.strftime("%m/%d")) for seri in series: if cards_only: daily_seri_data = report.state_card_counts.get(seri['name'], 0) else: daily_seri_data = report.state_counts.get(seri['name'], 0) if seri['name'] == "Done": if len(seri['data']) == 0: done_starting_point = daily_seri_data daily_seri_data = 0 else: daily_seri_data = daily_seri_data - done_starting_point seri['data'].append(daily_seri_data) chart['series'] = series start_date = reports.order_by('date').first().date reports.order_by('-date') context = { 'title': "Detailed Cumulative Flow", 'reports': reports, 'months': months, 'cards_only': cards_only, 'chart': chart, 'start_date': start_date, 'updated_at': reports[0].updated_at, 'states': States(), 'version': VERSION, } return render_template('report-detailed-flow.html', **context)
def start_date_validator(form, field): states = States() if states.index(form.state.data) >= states.index(states.start): if field.data is None: raise ValidationError("Start date required since the card's state is %s" % form.state.data) elif field.data is None and form.backlog_date.data is not None and form.done_date.data is not None: raise ValidationError("Card with a backlog date and done date must have a start date") else: field.errors = []
def start_date_validator(form, field): states = States() if states.index(form.state.data) >= states.index(states.start): if field.data is None: raise ValidationError( "Start date required since the card's state is %s" % form.state.data) elif (field.data is None and form.backlog_date.data is not None and form.done_date.data is not None): raise ValidationError( 'Card with a backlog date and done date must have a start date') else: field.errors = []
def funnel(state_slug): states = States() try: state = states.find_by_slug(state_slug) funnel = Funnel(state, app.config.get('FUNNEL_VIEWS', {})[state]) except KeyError: abort(404) cards = funnel.ordered_cards() funnel_auth = False if kardboard.auth.is_authenticated() is True: funnel_auth = funnel.is_authorized(session.get('username', '')) if request.method == "POST": if kardboard.auth.is_authenticated() is False or funnel_auth is False: abort(403) start = time.time() card_key_list = request.form.getlist('card[]') counter = 1 for card_key in card_key_list: Kard.objects(key=card_key.strip()).only('priority').update_one( set__priority=counter) counter += 1 elapsed = (time.time() - start) return jsonify(message="Reordered %s cards in %.2fs" % (counter, elapsed)) title = "%s: All boards" % state funnel_markers = funnel.markers() context = { 'title': title.replace("Backlog", "Ready: Elabo").replace("OTIS", "TIE"), 'state': state, 'state_slug': state_slug, 'cards': cards, 'times_in_state': funnel.times_in_state(), 'funnel_throughput': funnel.throughput, 'funnel_markers': funnel_markers, 'funnel_auth': funnel_auth, 'updated_at': datetime.datetime.now(), 'version': VERSION, 'authenticated': kardboard.auth.is_authenticated(), } return render_template('funnel.html', **context)
def funnel(state_slug): states = States() try: state = states.find_by_slug(state_slug) funnel = Funnel(state, app.config.get('FUNNEL_VIEWS', {})[state]) except KeyError: abort(404) cards = funnel.ordered_cards() funnel_auth = False if kardboard.auth.is_authenticated() is True: funnel_auth = funnel.is_authorized(session.get('username', '')) if request.method == "POST": if kardboard.auth.is_authenticated() is False or funnel_auth is False: abort(403) start = time.time() card_key_list = request.form.getlist('card[]') counter = 1 for card_key in card_key_list: Kard.objects( key=card_key.strip() ).only('priority').update_one(set__priority=counter) counter += 1 elapsed = (time.time() - start) return jsonify(message="Reordered %s cards in %.2fs" % (counter, elapsed)) title = "%s: All boards" % state funnel_markers = funnel.markers() context = { 'title': title.replace("Backlog", "Ready: Elabo").replace("OTIS", "TIE"), 'state': state, 'state_slug': state_slug, 'cards': cards, 'times_in_state': funnel.times_in_state(), 'funnel_throughput': funnel.throughput, 'funnel_markers': funnel_markers, 'funnel_auth': funnel_auth, 'updated_at': datetime.datetime.now(), 'version': VERSION, 'authenticated': kardboard.auth.is_authenticated(), } return render_template('funnel.html', **context)
def funnel(state_slug): states = States() try: state = states.find_by_slug(state_slug) funnel = Funnel(state, app.config.get("FUNNEL_VIEWS", {})[state]) except KeyError: abort(404) cards = funnel.ordered_cards() funnel_auth = False if kardboard.auth.is_authenticated() is True: funnel_auth = funnel.is_authorized(session.get("username", "")) if request.method == "POST": if kardboard.auth.is_authenticated() is False or funnel_auth is False: abort(403) start = time.time() card_key_list = request.form.getlist("card[]") counter = 1 for card_key in card_key_list: Kard.objects(key=card_key.strip()).only("priority").update_one(set__priority=counter) counter += 1 elapsed = time.time() - start return jsonify(message="Reordered %s cards in %.2fs" % (counter, elapsed)) title = "%s: All boards" % state funnel_markers = funnel.markers() context = { "title": title, "state": state, "state_slug": state_slug, "cards": cards, "times_in_state": funnel.times_in_state(), "funnel_throughput": funnel.throughput, "funnel_markers": funnel_markers, "funnel_auth": funnel_auth, "updated_at": datetime.datetime.now(), "version": VERSION, "authenticated": kardboard.auth.is_authenticated(), } return render_template("funnel.html", **context)
def state(): date = datetime.datetime.now() date = make_end_date(date=date) states = States() board = DisplayBoard(backlog_limit=0) # defaults to all teams, 7 days of done board.cards # force the card calculation board.rows title = app.config.get('SITE_NAME') metrics = [] teams = teams_service.setup_teams( app.config ) context = { 'title': title, 'board': board, 'states': states, 'metrics': metrics, 'wip_limits': {}, 'date': date, 'teams': teams, 'updated_at': datetime.datetime.now(), 'version': VERSION, } return render_template('state.html', **context)
def main(): states = States() backlogged_states = [] for k in Kard.backlogged(): if k.state not in states: if k.state not in backlogged_states: backlogged_states.append(k.state) k.state = states.backlog k.save() start_states = [] for k in Kard.in_progress(): if k.state not in states: if k.state not in start_states: start_states.append(k.state) k.state = states.start k.save() print "Valid states" print states print "Backlogged states that went away" print backlogged_states print "Start states that went away" print start_states
def jira_add_team_cards(team, filter_id): from kardboard.tickethelpers import JIRAHelper from kardboard.models import States from kardboard.app import app statsd_conn = app.statsd.get_client('tasks.jira_add_team_cards') counter = statsd_conn.get_client(class_=statsd.Counter) total_timer = statsd_conn.get_client(class_=statsd.Timer) total_timer.start() logger = jira_add_team_cards.get_logger() logger.info("JIRA BACKLOG SYNC %s: %s" % (team, filter_id)) states = States() helper = JIRAHelper(app.config, None) issues = helper.service.getIssuesFromFilter(helper.auth, filter_id) for issue in issues: if Kard.objects.filter(key=issue.key): # Card exists, pass pass else: logger.info("JIRA BACKLOGGING %s: %s" % (team, issue.key)) defaults = { 'key': issue.key, 'title': issue.summary, 'backlog_date': datetime.datetime.now(), 'team': team, 'state': states.backlog, } c = Kard(**defaults) c.ticket_system.actually_update(issue) c.save() counter += 1 total_timer.stop()
def report_assignee(group="all"): states = States() states_of_interest = [s for s in states if s not in (states.backlog, states.done)] # ReportGroup of WIP rg = ReportGroup(group, Kard.objects.filter(state__in=states_of_interest)) distro = {} for k in rg.queryset.all(): assignee = k._assignee or "Unassigned" distro.setdefault(assignee, 0) distro[assignee] += 1 total = 0 total = float(sum(distro.values())) distro = distro.items() distro.sort(key=lambda x: x[1]) distro.reverse() percentages = [(x[0], (x[1] / total)) for x in distro] percentages = [(x[0], round(x[1], 2)) for x in percentages] chart = {} chart['data'] = percentages context = { 'data': distro, 'chart': chart, 'total': total, 'title': "Assignee breakdown", 'updated_at': datetime.datetime.now(), 'version': VERSION, } return render_template('report-assignee.html', **context)
def done_date_validator(form, field): states = States() if form.state.data == states.done: if field.data is None: raise ValidationError( "Done date required since the card's state is %s" % form.state.data) else: field.errors = []
def test_wip(self): from kardboard.models import States states = States() with mock.patch('kardboard.services.teams.Kard') as mock_Kard: self.service.wip() mock_Kard.objects.filter.assert_called_with( team=self.team, done_date=None, state__in=states.in_progress, )
def test_start_and_done_date_togetherness(self): from kardboard.models import States states = States() self.required_data['state'] = unicode(states.done) self.required_data['start_date'] = '' self.required_data['done_date'] = u'06/12/2011' f = self.Form(self._post_data()) f.validate() self.assertEqual(2, len(f.start_date.errors))
def test_done_date_requiredness(self): """ If the state is set to DONE_STATE then a done_date is required. """ from kardboard.models import States states = States() self.required_data['state'] = unicode(states.done) f = self.Form(self._post_data()) f.validate() self.assertEqual(2, len(f.done_date.errors))
def setUp(self): super(DisplayBoardTests, self).setUp() self.config[ 'TICKET_HELPER'] = 'kardboard.tickethelpers.TestTicketHelper' self.Kard = self._get_card_class() self.start_date = self._date('start', ) from kardboard.models import States self.states = States() self.teams = self.config.get('CARD_TEAMS') self._set_up_cards()
def team_backlog(team_slug=None): if request.method == "POST": if kardboard.auth.is_authenticated() is False: abort(403) start = time.time() card_key_list = request.form.getlist('card[]') counter = 1 for card_key in card_key_list: Kard.objects(key=card_key.strip()).only('priority').update_one( set__priority=counter) counter += 1 elapsed = (time.time() - start) return jsonify(message="Reordered %s cards in %.2fs" % (counter, elapsed)) teams = _get_teams() team = _find_team_by_slug(team_slug, teams) weeks = 4 backlog = Kard.objects.filter( team=team.name, state=States().backlog, ).exclude('_ticket_system_data').order_by('priority') backlog_marker_data, backlog_markers = _team_backlog_markers( team, backlog, weeks) backlog_without_order = [k for k in backlog if k.priority is None] backlog_with_order = [k for k in backlog if k.priority is not None] backlog_with_order.sort(key=lambda k: k.priority) backlog = backlog_with_order + backlog_without_order title = "%s" % team.name context = { 'title': title, 'team_slug': team_slug, 'team': team, 'backlog': backlog, 'backlog_markers': backlog_markers, 'backlog_marker_data': backlog_marker_data, 'updated_at': datetime.datetime.now(), 'teams': teams, 'version': VERSION, 'authenticated': kardboard.auth.is_authenticated(), } return render_template('team-backlog.html', **context)
def _init_card_form(*args, **kwargs): states = States() new = kwargs.get('new', False) if new: del kwargs['new'] klass = get_card_form(new=new) f = klass(*args, **kwargs) if states: f.state.choices = states.for_forms teams = app.config.get('CARD_TEAMS') if teams: f.team.choices = _make_choice_field_ready(teams) return f
def _init_card_form(*args, **kwargs): states = States() new = kwargs.get('new', False) if new: del kwargs['new'] klass = get_card_form(new=new) f = klass(*args, **kwargs) if states: f.state.choices = states.for_forms teams = teams_service.setup_teams(app.config) if teams: f.team.choices = _make_choice_field_ready(teams.names) return f
def state(): date = datetime.datetime.now() date = make_end_date(date=date) states = States() board = DisplayBoard() # defaults to all teams, 7 days of done board.cards # force the card calculation board.rows title = app.config.get('SITE_NAME') wip_cards = [k for k in board.cards if k.state in states.in_progress] done_this_week = [k for k in board.cards if k.state == states.done] blocked = [k for k in wip_cards if k.blocked] metrics = [ { 'WIP': len(wip_cards) }, { 'Blocked': len(blocked) }, { 'Done this week': len(done_this_week) }, ] context = { 'title': title, 'board': board, 'states': states, 'metrics': metrics, 'date': date, 'updated_at': datetime.datetime.now(), 'version': VERSION, } return render_template('team.html', **context)
def team(team_slug=None): from kardboard.services.boards import TeamBoard teams = _get_teams() team = _find_team_by_slug(team_slug, teams) try: wip_limit_config = app.config['WIP_LIMITS'][team_slug] except KeyError: wip_limit_config = {} conwip = wip_limit_config.get('conwip', None) wip_limits = WIPLimits( name=team_slug, conwip=conwip, columns=wip_limit_config, ) weeks = 4 exclude_classes = _get_excluded_classes() team_stats = teams_service.TeamStats(team.name, exclude_classes) weekly_throughput = team_stats.weekly_throughput_ave(weeks) hit_sla = team_stats.hit_sla(weeks) hit_sla_delta = team_stats.hit_sla(weeks, weeks_offset=weeks) hit_sla, hit_sla_delta = zero_if_none(hit_sla), zero_if_none(hit_sla_delta) hit_sla_delta = hit_sla - hit_sla_delta total_throughput = teams_service.TeamStats(team.name).throughput(weeks) total_throughput_delta = teams_service.TeamStats(team.name).throughput( weeks, weeks_offset=weeks) total_throughput, total_throughput_delta = zero_if_none( total_throughput), zero_if_none(total_throughput_delta) total_throughput_delta = total_throughput - total_throughput_delta cycle_time = team_stats.percentile(.8, weeks) cycle_time_delta = team_stats.percentile(.8, weeks, weeks_offset=weeks) cycle_time, cycle_time_delta = zero_if_none(cycle_time), zero_if_none( cycle_time_delta) cycle_time_delta = cycle_time - cycle_time_delta metrics = [ { 'name': 'Throughput', 'value': total_throughput, 'delta': total_throughput_delta, }, { 'name': 'Hit SLA', 'value': hit_sla, 'delta': hit_sla_delta, }, { 'name': 'Cycle time', 'value': cycle_time, 'delta': cycle_time_delta, }, ] board = TeamBoard(team.name, States(), wip_limits) backlog_limit = weekly_throughput * 4 or 30 cards = Kard.objects.for_team_board( team=team.name, backlog_limit=backlog_limit, done_days=7, ) board.add_cards(cards) backlog_marker_data, backlog_markers = _team_backlog_markers( team, board.columns[0]['cards'], weeks, ) report_config = ( { 'slug': 'assignee', 'name': "Assignee" }, { 'slug': 'cycle/distribution/all', 'name': "Cycle time", 'months': 1 }, { 'slug': 'service-class', 'name': 'Service class' }, { 'slug': 'blocked', 'name': 'Blocked', 'months': 3 }, { 'slug': 'done', 'name': 'Done', 'months': 1 }, { 'slug': 'leaderboard', 'name': 'Leaderboard', 'months': 3 }, { 'slug': 'flow/detail', 'name': "Cumulative Flow", 'months': 2 }, ) context = { 'title': "%s cards" % team.name, 'metrics': metrics, 'wip_limits': wip_limits, 'team': team, 'teams': teams, 'board': board, 'round': round, 'backlog_markers': backlog_markers, 'backlog_marker_data': backlog_marker_data, 'weekly_throughput': weekly_throughput, 'report_config': report_config, 'updated_at': datetime.datetime.now(), 'version': VERSION, 'authenticated': kardboard.auth.is_authenticated(), } return render_template('team.html', **context)
def team(team_slug=None): date = datetime.datetime.now() date = make_end_date(date=date) teams = app.config.get('CARD_TEAMS', []) states = States() team_mapping = {} for team in teams: team_mapping[slugify(team)] = team target_team = None if team_slug: target_team = team_mapping.get(team_slug, None) if not team: abort(404) board = DisplayBoard(teams=[ target_team, ]) wip_cards = [k for k in board.cards if k.state in states.in_progress] done_this_week = [k for k in board.cards if k.state == states.done] three_months_ago = date - relativedelta.relativedelta(months=3) done_past_three_months = Kard.objects.filter( team=target_team, done_date__exists=True, done_date__gte=three_months_ago) std_dev = standard_deviation( [k.cycle_time for k in done_past_three_months]) ave_cycle_time = Kard.objects.filter(team=target_team).moving_cycle_time( year=date.year, month=date.month, day=date.day) confidence_cycle = round(ave_cycle_time + std_dev + std_dev) try: confidence_cycle = int(confidence_cycle) except ValueError: pass metrics = [ { 'WIP': len(wip_cards) }, { 'Ave. Cycle Time': ave_cycle_time }, { '95% confidence level': confidence_cycle }, { 'Done this week': len(done_this_week) }, ] title = "%s cards" % target_team report_config = ({ 'slug': 'assignee', 'name': 'Assignee breakdown' }, { 'slug': 'cycle/distribution', 'name': 'Cycle time' }, { 'slug': 'throughput', 'name': 'Throughput' }, { 'slug': 'leaderboard', 'name': 'Leaderboard' }, { 'slug': 'done', 'name': 'Done' }) context = { 'title': title, 'team_slug': team_slug, 'target_team': target_team, 'metrics': metrics, 'report_config': report_config, 'board': board, 'date': date, 'updated_at': datetime.datetime.now(), 'version': VERSION, } return render_template('team.html', **context)
def setUp(self): from kardboard.models import States super(FlowReportTestCase, self).setUp() self.states = States() self._set_up_cards()
def team(team_slug=None): from kardboard.services.boards import TeamBoard teams = _get_teams() team = _find_team_by_slug(team_slug, teams) try: wip_limit_config = app.config['WIP_LIMITS'][team_slug] except KeyError: wip_limit_config = {} conwip = wip_limit_config.get('conwip', None) wip_limits = WIPLimits( name=team_slug, conwip=conwip, columns=wip_limit_config, ) weeks = 4 exclude_classes = _get_excluded_classes() team_stats = teams_service.TeamStats(team.name, exclude_classes) weekly_throughput = team_stats.weekly_throughput_ave(weeks) metrics = [ {'WIP': team_stats.wip_count()}, {'Weekly throughput': team_stats.weekly_throughput_ave(weeks)}, ] ave = team_stats.average(weeks) if ave: metrics.append({'Cycle time ave.': team_stats.average(weeks)}) stdev = team_stats.standard_deviation(weeks) if stdev: metrics.append({'Standard deviation': stdev},) board = TeamBoard(team.name, States(), wip_limits) backlog_limit = weekly_throughput * 4 or 30 cards = Kard.objects.for_team_board( team=team.name, backlog_limit=backlog_limit, done_days=7, ) board.add_cards(cards) backlog_marker_data, backlog_markers = _team_backlog_markers( team, board.columns[0]['cards'], weeks, ) metrics.append( {'80% confidence': backlog_marker_data['confidence_80']}, ) report_config = ( {'slug': 'cycle/distribution/all', 'name': "Cycle time"}, {'slug': 'flow/detail', 'name': "Cumulative Flow"}, {'slug': 'done', 'name': 'Done'}, {'slug': 'service-class', 'name': 'Service class'}, ) context = { 'title': "%s cards" % team.name, 'metrics': metrics, 'wip_limits': wip_limits, 'team': team, 'teams': teams, 'board': board, 'backlog_markers': backlog_markers, 'backlog_marker_data': backlog_marker_data, 'weekly_throughput': weekly_throughput, 'report_config': report_config, 'updated_at': datetime.datetime.now(), 'version': VERSION, 'authenticated': kardboard.auth.is_authenticated(), } return render_template('team.html', **context)