Skip to content

Commit

Permalink
Merge pull request #22 from jhale1805/dev
Browse files Browse the repository at this point in the history
Improve date handling
  • Loading branch information
thehale authored Oct 22, 2021
2 parents bcbf221 + da9a8e9 commit efd02e8
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 51 deletions.
34 changes: 18 additions & 16 deletions src/github_projects_burndown_chart/chart/burndown.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,40 +3,42 @@

from config import config
from gh.project import Project
from util.dates import parse_to_local, parse_to_utc


class BurndownChart:

def __init__(self, project: Project):
# Initialize important dates
self.start_date = datetime.strptime(
config['settings']['sprint_start_date'],
'%Y-%m-%d')
self.end_date = datetime.strptime(
config['settings']['sprint_end_date'],
'%Y-%m-%d')
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.project: Project = project

def render(self):
outstanding_points_by_day = self.project.outstanding_points_by_day(
self.start_date,
self.end_date)
outstanding_points_by_day = self.project.outstanding_points_by_date(
self.start_date_utc,
self.end_date_utc)
# 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())

# 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/(len(y)-1)),
color="green",
linestyle=(0, (5, 5)))
slope=-(self.project.total_points/(len(y)-1)),
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
plt.xticks(x, outstanding_points_by_day.keys())
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)

# Set titles and labels
Expand Down
88 changes: 53 additions & 35 deletions src/github_projects_burndown_chart/gh/project.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
from datetime import datetime, timedelta
from typing import Dict
from dateutil.parser import isoparse

from config import config
from util.dates import TODAY_UTC


class Project:
Expand All @@ -17,33 +20,52 @@ def __parse_columns(self, project_data):
def total_points(self):
return sum([column.get_total_points() for column in self.columns])

def points_completed_by_date(self, start_date, end_date):
points_completed_by_date = {
str(date)[:10] : 0
for date in [
start_date + timedelta(days=x)
for x in range(0, (end_date - start_date).days + 1)
]
}
for column in self.columns:
for card in column.cards:
if card.closedAt:
date_str = str(card.closedAt)[:10]
points_completed_by_date[date_str] += card.points
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_day(self, start_date, end_date):
outstanding_points_by_day = {}
points_completed = 0
points_completed_by_date = self.points_completed_by_date(start_date, end_date)
current_date = datetime.now()
for date in points_completed_by_date:
points_completed += points_completed_by_date[date]
if datetime.strptime(date, '%Y-%m-%d') < current_date:
outstanding_points_by_day[date] = self.total_points - points_completed
else:
outstanding_points_by_day[date] = None
return outstanding_points_by_day
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
}


class Column:
Expand All @@ -69,17 +91,13 @@ def __init__(self, card_data):
def __parse_createdAt(self, card_data):
createdAt = None
if card_data.get('createdAt'):
createdAt = datetime.strptime(
card_data['createdAt'][:10],
'%Y-%m-%d')
createdAt = isoparse(card_data['createdAt'])
return createdAt

def __parse_closedAt(self, card_data):
closedAt = None
if card_data.get('closedAt'):
closedAt = datetime.strptime(
card_data['closedAt'][:10],
'%Y-%m-%d')
closedAt = isoparse(card_data['closedAt'])
return closedAt

def __parse_points(self, card_data):
Expand All @@ -89,7 +107,7 @@ def __parse_points(self, card_data):
card_points = 1
else:
card_labels = card_data.get('labels', {"nodes": []})['nodes']
for label in card_labels:
if points_label in label['name']:
card_points += int(label['name'][len(points_label):])
return card_points
card_points = sum([int(label['name'][len(points_label):])
for label in card_labels
if points_label in label['name']])
return card_points
Empty file.
21 changes: 21 additions & 0 deletions src/github_projects_burndown_chart/util/dates.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from datetime import datetime, timedelta, timezone
from dateutil import parser

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.
"""
raw_datetime = parser.parse(date_string) + UTC_OFFSET
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'))

0 comments on commit efd02e8

Please sign in to comment.