diff --git a/README.md b/README.md index f8bdd32..0301dcf 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,7 @@ This allows the `.gitignore` to exclude your `config.json` from being accidental | `sprint_end_date` | The last day of the sprint formatted as `YYYY-MM-DD`.

Must be entered here since GitHub Project Boards don't have an assigned start/end date.

Example: `2021-10-21` | | `chart_end_date` | (OPTIONAL) The last day to show on the burndown chart formatted as `YYYY-MM-DD`.

Used to change the end date of the chart without affecting the slope of the ideal burndown line (e.g. to show tasks that were completed after the official end of a sprint).

Example: `2021-10-24` | | `points_label` | (OPTIONAL) The prefix for issue labels containing the point value of the issue. Removing this prefix must leave just an integer. If set to `null`, the burndown chart will count open issues instead of points.

Example: `Points: ` (with the space) | +| `calculators` | (OPTIONAL) A list of the calculator(s) to use to calculate the point burndown lines to show on the burndown chart. (DEFAULT: [`closed`])

_OPTIONS:_ `closed`, `assigned`, `created`, `taiga`

Example: [`taiga`, `closed`, `assigned`] | #### Organization Projects All settings are the same as for the [Repository Projects](#repository-projects), except `repo_owner` and `repo_name` are replaced with `organization_name` as shown below. diff --git a/docs/images/example_burndown_chart.png b/docs/images/example_burndown_chart.png index c4d15764811c3daf2865cceba59f0ca872336726..452a31fac144923f12e31c2e6bd726ce62f1deef 100644 GIT binary patch delta 36452 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 PF6*2Unl{JxE(` delta 36452 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­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 diff --git a/src/github_projects_burndown_chart/chart/burndown.py b/src/github_projects_burndown_chart/chart/burndown.py index cb0a778..37988a0 100644 --- a/src/github_projects_burndown_chart/chart/burndown.py +++ b/src/github_projects_burndown_chart/chart/burndown.py @@ -1,58 +1,83 @@ +from dataclasses import dataclass, field from datetime import datetime +from typing import Any, Dict, Iterable import matplotlib.pyplot as plt import os -from config import config -from gh.project import Project -from util.dates import parse_to_local, parse_to_utc +from util.dates import parse_to_local, date_range -class BurndownChart: +@dataclass +class BurndownChartDataSeries: + name: str + data: Iterable[Dict[datetime, int]] + format: Dict[str, Any] + + +def default_ideal_trendline_format() -> Dict[str, Any]: + return dict( + color="grey", + linestyle=(0, (5, 5)) + ) + + +@dataclass +class BurndownChartData: + sprint_name: str + utc_chart_start: datetime + utc_chart_end: datetime + utc_sprint_start: datetime + utc_sprint_end: datetime + total_points: int + series: Iterable[BurndownChartDataSeries] + points_label: str = "Outstanding Points" + ideal_trendline_format: Dict[str, Any] = field( + default_factory=default_ideal_trendline_format) + - def __init__(self, project: Project): - self.start_date_utc: datetime = parse_to_utc( - config['settings']['sprint_start_date']) - self.end_date_utc: datetime = parse_to_utc( - config['settings']['sprint_end_date']) - self.chart_end_date_utc: datetime = parse_to_utc( - config['settings']['chart_end_date']) \ - if config['settings'].get('chart_end_date') else None +class BurndownChart: - self.project: Project = project + def __init__(self, data: BurndownChartData): + self.data: BurndownChartData = data def __prepare_chart(self): - end_date = self.chart_end_date_utc if self.chart_end_date_utc else self.end_date_utc - outstanding_points_by_day = self.project.outstanding_points_by_date( - self.start_date_utc, - end_date) - # Load date dict for priority values with x being range of how many days are in sprint - x = list(range(len(outstanding_points_by_day.keys()))) - y = list(outstanding_points_by_day.values()) - sprint_days = (self.end_date_utc - self.start_date_utc).days - - # Plot point values for sprint along xaxis=range yaxis=points over time - plt.plot(x, y) - plt.axline((x[0], self.project.total_points), - slope=-(self.project.total_points/(sprint_days)), - color="green", - linestyle=(0, (5, 5))) - - # Set sprint beginning - plt.ylim(ymin=0) - plt.xlim(xmin=x[0], xmax=x[-1]) - - # Replace xaxis range for date matching to range value - date_labels = [str(parse_to_local(date))[:10] - for date in outstanding_points_by_day.keys()] - plt.xticks(x, date_labels) - plt.xticks(rotation=90) + # Plot the data + chart_dates = date_range( + self.data.utc_chart_start, self.data.utc_chart_end) + for series in self.data.series: + series_dates = [chart_dates.index(date) + for date in series.data.keys()] + series_points = list(series.data.values()) + plt.plot( + series_dates, + series_points, + label=series.name, + **series.format + ) + plt.legend() - # Set titles and labels - plt.title(f"{self.project.name}: Burndown Chart") - points_label = config['settings']['points_label'] - plt.ylabel(f"Outstanding {'Points' if points_label else 'Issues'}") + # Configure title and labels + plt.title(f"{self.data.sprint_name}: Burndown Chart") + plt.ylabel(self.data.points_label) plt.xlabel("Date") + # Configure axes limits + plt.ylim(ymin=0, ymax=self.data.total_points * 1.1) + plt.xlim(xmin=chart_dates.index(self.data.utc_chart_start), + xmax=chart_dates.index(self.data.utc_chart_end)) + + # Configure x-axis tick marks + date_labels = [str(parse_to_local(date))[:10] for date in chart_dates] + plt.xticks(range(len(chart_dates)), date_labels) + plt.xticks(rotation=90) + + # Plot the ideal trendline + sprint_days = (self.data.utc_sprint_end - + self.data.utc_sprint_start).days + plt.axline((chart_dates.index(self.data.utc_sprint_start), self.data.total_points), + slope=-(self.data.total_points/(sprint_days)), + **self.data.ideal_trendline_format) + def generate_chart(self, path): self.__prepare_chart() if not os.path.exists(path): diff --git a/src/github_projects_burndown_chart/config/__init__.py b/src/github_projects_burndown_chart/config/__init__.py index 0188b86..ad04ea7 100644 --- a/src/github_projects_burndown_chart/config/__init__.py +++ b/src/github_projects_burndown_chart/config/__init__.py @@ -1,7 +1,10 @@ +from datetime import datetime import json import os import logging +from util.dates import parse_to_utc + # Set up logging __logger = logging.getLogger(__name__) __ch = logging.StreamHandler() @@ -39,6 +42,15 @@ class Config: self.project_type = project_type self.project_name = project_name + def utc_sprint_start(self) -> datetime: + return self.__get_date('sprint_start_date') + + def utc_sprint_end(self) -> datetime: + return self.__get_date('sprint_end_date') + + def utc_chart_end(self) -> datetime: + return self.__get_date('chart_end_date') + def __getitem__(self, key: str): if not hasattr(self, 'project_type'): raise AttributeError('No project has been set.') @@ -46,6 +58,10 @@ class Config: raise AttributeError('No project has been set.') return self.raw_config[self.project_type][self.project_name][key] + def __get_date(self, name: str) -> datetime: + date = self['settings'].get(name) + return parse_to_utc(date) if date else None + config = Config(__config) diff --git a/src/github_projects_burndown_chart/gh/api_wrapper.py b/src/github_projects_burndown_chart/gh/api_wrapper.py index 61a53f6..8bd5797 100644 --- a/src/github_projects_burndown_chart/gh/api_wrapper.py +++ b/src/github_projects_burndown_chart/gh/api_wrapper.py @@ -1,6 +1,10 @@ import logging +import os import requests -from requests.api import head +from datetime import date +import hashlib +import json +import tempfile from config import config, secrets from .project import Project @@ -29,13 +33,25 @@ def get_organization_project() -> dict: def gh_api_query(query: str, variables: dict) -> dict: + response = __get_from_cache(query, variables) + if not response: + response = __get_from_api(query, variables) + __cache_response(query, variables, response) + return response + + +def prepare_payload(query, variables): + return {'query': query, 'variables': variables} + + +def __get_from_api(query, variables): headers = {'Authorization': 'bearer %s' % secrets['github_token']} \ if 'github_token' in secrets else {} response = requests.post( 'https://api.github.com/graphql', headers=headers, - json={'query': query, 'variables': variables}).json() + json=prepare_payload(query, variables)).json() # Gracefully report failures due to bad credentials if response.get('message') and response['message'] == 'Bad credentials': @@ -53,3 +69,26 @@ def gh_api_query(query: str, variables: dict) -> dict: __logger.critical(response['errors']) exit(1) return response + + +def __get_from_cache(query, variables): + temp_path = __temp_path(query, variables) + if os.path.exists(temp_path): + with open(temp_path, 'r') as f: + return json.load(f) + return None + + +def __cache_response(query, variables, response): + temp_path = __temp_path(query, variables) + with open(temp_path, 'w') as f: + json.dump(response, f) + + +def __temp_path(query, variables): + temp_dir = tempfile.gettempdir() + payload = prepare_payload(query, variables) + payload.update({'today': str(date.today())}) + filename = f"{hashlib.sha256(json.dumps(payload).encode('utf-8')).hexdigest()}.json" + temp_path = os.path.join(temp_dir, filename) + return temp_path diff --git a/src/github_projects_burndown_chart/gh/project.py b/src/github_projects_burndown_chart/gh/project.py index 5e003b7..7f93ea2 100644 --- a/src/github_projects_burndown_chart/gh/project.py +++ b/src/github_projects_burndown_chart/gh/project.py @@ -1,9 +1,7 @@ -from datetime import datetime, timedelta -from typing import Dict +from datetime import datetime from dateutil.parser import isoparse from config import config -from util.dates import TODAY_UTC class Project: @@ -20,52 +18,9 @@ class Project: def total_points(self): return sum([column.get_total_points() for column in self.columns]) - def points_completed_by_date(self, start_date: datetime, end_date: datetime) -> Dict[datetime, int]: - """Computes the number of points completed by date. - Basically the data behind a burnup chart for the given date range. - - Args: - start_date (datetime): The start date of the chart in UTC. - end_date (datetime): The end date of the chart in UTC. - - Returns: - Dict[datetime, int]: A dictionary of date and points completed. - """ - points_completed_by_date = {} - - cards = [card for column in self.columns for card in column.cards] - completed_cards = [card for card in cards if card.closedAt is not None] - sprint_dates = [start_date + timedelta(days=x) - # The +1 includes the end_date in the list - for x in range(0, (end_date - start_date).days + 1)] - for date in sprint_dates: - # Get the issues completed before midnight on the given date. - date_23_59 = date + timedelta(hours=23, minutes=59) - cards_done_by_date = [card for card in completed_cards - if card.closedAt <= date_23_59] - points_completed_by_date[date] = sum([card.points for card - in cards_done_by_date]) - return points_completed_by_date - - def outstanding_points_by_date(self, start_date: datetime, end_date: datetime) -> Dict[datetime, int]: - """Computes the number of points remaining to be completed by date. - Basically the data behind a burndown chart for the given date range. - - Args: - start_date (datetime): The start date of the chart in UTC. - end_date (datetime): The end date of the chart in UTC. - - Returns: - Dict[datetime, int]: A dictionary of date and points remaining. - """ - points_completed_by_date = self.points_completed_by_date( - start_date, end_date) - today_23_59 = TODAY_UTC + timedelta(hours=23, minutes=59) - return { - date: self.total_points - points_completed_by_date[date] - if date <= today_23_59 else None - for date in points_completed_by_date - } + @property + def cards(self): + return [card for column in self.columns for card in column.cards] class Column: @@ -84,23 +39,31 @@ class Column: class Card: def __init__(self, card_data): card_data = card_data['content'] if card_data['content'] else card_data - self.createdAt = self.__parse_createdAt(card_data) - self.closedAt = self.__parse_closedAt(card_data) + self.created: datetime = self.__parse_createdAt(card_data) + self.assigned: datetime = self.__parse_assignedAt(card_data) + self.closed: datetime = self.__parse_closedAt(card_data) self.points = self.__parse_points(card_data) - def __parse_createdAt(self, card_data): + def __parse_assignedAt(self, card_data) -> datetime: + assignedAt = None + assignedDates = card_data.get('timelineItems', {}).get('nodes', []) + if assignedDates: + assignedAt = isoparse(assignedDates[0]['createdAt']) + return assignedAt + + def __parse_createdAt(self, card_data) -> datetime: createdAt = None if card_data.get('createdAt'): createdAt = isoparse(card_data['createdAt']) return createdAt - def __parse_closedAt(self, card_data): + def __parse_closedAt(self, card_data) -> datetime: closedAt = None if card_data.get('closedAt'): closedAt = isoparse(card_data['closedAt']) return closedAt - def __parse_points(self, card_data): + def __parse_points(self, card_data) -> int: card_points = 0 points_label = config['settings']['points_label'] if not points_label: diff --git a/src/github_projects_burndown_chart/gh/queries/OrganizationProject.graphql b/src/github_projects_burndown_chart/gh/queries/OrganizationProject.graphql index 1bbd376..1752616 100644 --- a/src/github_projects_burndown_chart/gh/queries/OrganizationProject.graphql +++ b/src/github_projects_burndown_chart/gh/queries/OrganizationProject.graphql @@ -13,6 +13,14 @@ query OrganizationProject($organization_name: String!, $project_number: Int!, $c content { ... on Issue { title + timelineItems(first: 20, itemTypes: [ASSIGNED_EVENT]) { + nodes { + __typename + ... on AssignedEvent { + createdAt + } + } + } createdAt closedAt labels(first: $labels_per_issue_count) { diff --git a/src/github_projects_burndown_chart/gh/queries/RepositoryProject.graphql b/src/github_projects_burndown_chart/gh/queries/RepositoryProject.graphql index 93a16b0..0b73bb2 100644 --- a/src/github_projects_burndown_chart/gh/queries/RepositoryProject.graphql +++ b/src/github_projects_burndown_chart/gh/queries/RepositoryProject.graphql @@ -1,4 +1,3 @@ -# Heavily inspired by https://github.com/radekstepan/burnchart/issues/129#issuecomment-394469442 query RepositoryProject($repo_owner: String!, $repo_name: String!, $project_number: Int!, $column_count: Int!, $max_cards_per_column_count: Int!, $labels_per_issue_count: Int!) { repository(owner: $repo_owner, name: $repo_name) { project(number: $project_number) { @@ -15,6 +14,14 @@ query RepositoryProject($repo_owner: String!, $repo_name: String!, $project_numb content { ... on Issue { title + timelineItems(first: 20, itemTypes: [ASSIGNED_EVENT]) { + nodes { + __typename + ... on AssignedEvent { + createdAt + } + } + } createdAt closedAt labels(first: $labels_per_issue_count) { @@ -30,4 +37,4 @@ query RepositoryProject($repo_owner: String!, $repo_name: String!, $project_numb } } } -} +} \ No newline at end of file diff --git a/src/github_projects_burndown_chart/main.py b/src/github_projects_burndown_chart/main.py index 2c27f8d..3b9143c 100644 --- a/src/github_projects_burndown_chart/main.py +++ b/src/github_projects_burndown_chart/main.py @@ -1,33 +1,65 @@ import argparse -from chart.burndown import BurndownChart +from chart.burndown import * from config import config from discord import webhook from gh.api_wrapper import get_organization_project, get_repository_project from gh.project import Project +from util import calculators, colors +from util.stats import * +from util.calculators import * -if __name__ == '__main__': - # Parse the command line arguments + +def parse_cli_args(): parser = argparse.ArgumentParser( description='Generate a burndown chart for a GitHub project.') - parser.add_argument("project_type", - choices=['repository', 'organization'], + parser.add_argument("project_type", choices=['repository', 'organization'], help="The type of project to generate a burndown chart for. Can be either 'organization' or 'repository'.") - parser.add_argument("project_name", help="The name of the project as it appears in the config.json") - parser.add_argument("--discord", action='store_true', help="If present, posts the burndown chart to the configured webhook") - args = parser.parse_args() + parser.add_argument("project_name", + help="The name of the project as it appears in the config.json") + parser.add_argument("--discord", action='store_true', + help="If present, posts the burndown chart to the configured webhook") + return parser.parse_args() - # Point the config to the correct project - config.set_project(args.project_type, args.project_name) - - # Pull the corresponding project from GitHub + +def download_project_data(args): if args.project_type == 'repository': project: Project = get_repository_project() elif args.project_type == 'organization': project: Project = get_organization_project() - + return project + + +def prepare_chart_data(stats: ProjectStats): + color = colors() + data = BurndownChartData( + sprint_name=stats.project.name, + utc_chart_start=config.utc_sprint_start(), + utc_chart_end=config.utc_chart_end() or config.utc_sprint_end(), + utc_sprint_start=config.utc_sprint_start(), + utc_sprint_end=config.utc_sprint_end(), + total_points=stats.total_points, + series=[ + BurndownChartDataSeries( + name=pts_type, + data=stats.remaining_points_by_date( + calculators(stats.project)[pts_type]), + format=dict(color=next(color)) + ) for pts_type in config['settings'].get('calculators', ['closed']) + ], + points_label=f"Outstanding {'Points' if config['settings']['points_label'] else 'Issues'}" + ) + return data + + +if __name__ == '__main__': + args = parse_cli_args() + config.set_project(args.project_type, args.project_name) + project = download_project_data(args) + stats = ProjectStats(project, config.utc_sprint_start(), + config.utc_chart_end() or config.utc_sprint_end()) # Generate the burndown chart - burndown_chart = BurndownChart(project) + burndown_chart = BurndownChart(prepare_chart_data(stats)) if args.discord: chart_path = "./tmp/chart.png" burndown_chart.generate_chart(chart_path) diff --git a/src/github_projects_burndown_chart/util/__init__.py b/src/github_projects_burndown_chart/util/__init__.py index e69de29..019d9f3 100644 --- a/src/github_projects_burndown_chart/util/__init__.py +++ b/src/github_projects_burndown_chart/util/__init__.py @@ -0,0 +1,19 @@ +from gh.project import Project +from util.calculators import * + + +def calculators(project: Project): + return { + 'created': CreatedPointsCalculator(project.cards), + 'assigned': AssignedPointsCalculator(project.cards), + 'closed': ClosedPointsCalculator(project.cards), + 'taiga': TaigaPointsCalculator(project.cards) + } + +def colors(): + count = -1 + colors = ['blue', 'green', 'gold', 'red', 'orange', + 'purple', 'pink', 'brown', 'black', 'grey'] + while True: + count += 1 + yield colors[count % len(colors)] \ No newline at end of file diff --git a/src/github_projects_burndown_chart/util/calculators.py b/src/github_projects_burndown_chart/util/calculators.py new file mode 100644 index 0000000..738743a --- /dev/null +++ b/src/github_projects_burndown_chart/util/calculators.py @@ -0,0 +1,52 @@ +from datetime import datetime +from typing import List + +from gh.project import Card + + +class PointsCalculator: + + def __init__(self, cards: List[Card]): + self.cards: List[Card] = cards + + def points_as_of(self, date: datetime): + raise NotImplementedError() + + +class ClosedPointsCalculator(PointsCalculator): + + def points_as_of(self, date: datetime): + return sum(card.points for card in self.cards + if isinstance(card.closed, datetime) + and card.closed <= date) + + +class AssignedPointsCalculator(PointsCalculator): + + def points_as_of(self, date: datetime): + return sum(card.points for card in self.cards + if isinstance(card.assigned, datetime) + and card.assigned <= date) + + +class CreatedPointsCalculator(PointsCalculator): + + def points_as_of(self, date: datetime): + return sum(card.points for card in self.cards + if isinstance(card.created, datetime) + and card.created <= date) + + +class TaigaPointsCalculator(PointsCalculator): + + def points_as_of(self, date: datetime): + closed_by_date = [card for card in self.cards + if isinstance(getattr(card, 'closed'), datetime) + and getattr(card, 'closed') <= date] + points = sum(card.points for card in closed_by_date) + assigned_by_date = [card for card in self.cards + if isinstance(getattr(card, 'assigned'), datetime) + and getattr(card, 'assigned') <= date + and card not in closed_by_date] + points += sum(card.points / 2 for card in assigned_by_date) + return points diff --git a/src/github_projects_burndown_chart/util/dates.py b/src/github_projects_burndown_chart/util/dates.py index 5f0a68e..d61e7ca 100644 --- a/src/github_projects_burndown_chart/util/dates.py +++ b/src/github_projects_burndown_chart/util/dates.py @@ -1,9 +1,11 @@ from datetime import datetime, timedelta, timezone from dateutil import parser +from typing import List UTC_OFFSET: timedelta = datetime.utcnow() - datetime.now() """Local time + UTC_OFFSET = UTC Time""" + def parse_to_utc(date_string: str) -> datetime: """ Parse a date string to UTC time. @@ -12,10 +14,18 @@ def parse_to_utc(date_string: str) -> datetime: datetime_utc = raw_datetime.replace(tzinfo=timezone.utc) return datetime_utc + def parse_to_local(datetime_utc: datetime) -> datetime: """ Parse a datetime object to local time. """ return datetime_utc.astimezone() -TODAY_UTC: datetime = parse_to_utc(datetime.today().strftime('%Y-%m-%d')) \ No newline at end of file + +def date_range(start_date: datetime, end_date: datetime) -> List[datetime]: + # The +1 includes the end_date in the list + num_days = (end_date - start_date).days + 1 + return [start_date + timedelta(days=x) for x in range(0, num_days)] + + +TODAY_UTC: datetime = parse_to_utc(datetime.today().strftime('%Y-%m-%d')) diff --git a/src/github_projects_burndown_chart/util/stats.py b/src/github_projects_burndown_chart/util/stats.py new file mode 100644 index 0000000..0feb0a8 --- /dev/null +++ b/src/github_projects_burndown_chart/util/stats.py @@ -0,0 +1,36 @@ +from datetime import datetime, timedelta +from typing import Dict, Iterable +from gh.project import * +from util.dates import TODAY_UTC, date_range +from util.calculators import * + + +class ProjectStats(): + + def __init__(self, project: Project, start_date: datetime, end_date: datetime): + self.start_date: datetime = start_date + self.end_date: datetime = end_date + self.project: Project = project + + @property + def total_points(self) -> int: + return self.project.total_points + + def points_by_date(self, calculator: PointsCalculator) -> Dict[datetime, int]: + points = {} + sprint_dates: Iterable[datetime] = date_range( + self.start_date, self.end_date) + for date in sprint_dates: + # Get the issues completed before midnight on the given date. + date_23_59 = date + timedelta(hours=23, minutes=59) + points[date] = calculator.points_as_of(date_23_59) + return points + + def remaining_points_by_date(self, calculator: PointsCalculator) -> Dict[datetime, int]: + points_by_date = self.points_by_date(calculator) + today_23_59 = TODAY_UTC + timedelta(hours=23, minutes=59) + return { + date: self.total_points - points_by_date[date] + if date <= today_23_59 else None + for date in points_by_date + }