11from datetime import datetime , timedelta
2+ from typing import Dict
3+ from dateutil .parser import isoparse
24
35from config import config
6+ from util .dates import TODAY_UTC
47
58
69class Project :
@@ -17,33 +20,52 @@ def __parse_columns(self, project_data):
1720 def total_points (self ):
1821 return sum ([column .get_total_points () for column in self .columns ])
1922
20- def points_completed_by_date (self , start_date , end_date ):
21- points_completed_by_date = {
22- str (date )[:10 ] : 0
23- for date in [
24- start_date + timedelta (days = x )
25- for x in range (0 , (end_date - start_date ).days + 1 )
26- ]
27- }
28- for column in self .columns :
29- for card in column .cards :
30- if card .closedAt :
31- date_str = str (card .closedAt )[:10 ]
32- points_completed_by_date [date_str ] += card .points
23+ def points_completed_by_date (self , start_date : datetime , end_date : datetime ) -> Dict [datetime , int ]:
24+ """Computes the number of points completed by date.
25+ Basically the data behind a burnup chart for the given date range.
26+
27+ Args:
28+ start_date (datetime): The start date of the chart in UTC.
29+ end_date (datetime): The end date of the chart in UTC.
30+
31+ Returns:
32+ Dict[datetime, int]: A dictionary of date and points completed.
33+ """
34+ points_completed_by_date = {}
35+
36+ cards = [card for column in self .columns for card in column .cards ]
37+ completed_cards = [card for card in cards if card .closedAt is not None ]
38+ sprint_dates = [start_date + timedelta (days = x )
39+ # The +1 includes the end_date in the list
40+ for x in range (0 , (end_date - start_date ).days + 1 )]
41+ for date in sprint_dates :
42+ # Get the issues completed before midnight on the given date.
43+ date_23_59 = date + timedelta (hours = 23 , minutes = 59 )
44+ cards_done_by_date = [card for card in completed_cards
45+ if card .closedAt <= date_23_59 ]
46+ points_completed_by_date [date ] = sum ([card .points for card
47+ in cards_done_by_date ])
3348 return points_completed_by_date
3449
35- def outstanding_points_by_day (self , start_date , end_date ):
36- outstanding_points_by_day = {}
37- points_completed = 0
38- points_completed_by_date = self .points_completed_by_date (start_date , end_date )
39- current_date = datetime .now ()
40- for date in points_completed_by_date :
41- points_completed += points_completed_by_date [date ]
42- if datetime .strptime (date , '%Y-%m-%d' ) < current_date :
43- outstanding_points_by_day [date ] = self .total_points - points_completed
44- else :
45- outstanding_points_by_day [date ] = None
46- return outstanding_points_by_day
50+ def outstanding_points_by_date (self , start_date : datetime , end_date : datetime ) -> Dict [datetime , int ]:
51+ """Computes the number of points remaining to be completed by date.
52+ Basically the data behind a burndown chart for the given date range.
53+
54+ Args:
55+ start_date (datetime): The start date of the chart in UTC.
56+ end_date (datetime): The end date of the chart in UTC.
57+
58+ Returns:
59+ Dict[datetime, int]: A dictionary of date and points remaining.
60+ """
61+ points_completed_by_date = self .points_completed_by_date (
62+ start_date , end_date )
63+ today_23_59 = TODAY_UTC + timedelta (hours = 23 , minutes = 59 )
64+ return {
65+ date : self .total_points - points_completed_by_date [date ]
66+ if date <= today_23_59 else None
67+ for date in points_completed_by_date
68+ }
4769
4870
4971class Column :
@@ -69,17 +91,13 @@ def __init__(self, card_data):
6991 def __parse_createdAt (self , card_data ):
7092 createdAt = None
7193 if card_data .get ('createdAt' ):
72- createdAt = datetime .strptime (
73- card_data ['createdAt' ][:10 ],
74- '%Y-%m-%d' )
94+ createdAt = isoparse (card_data ['createdAt' ])
7595 return createdAt
7696
7797 def __parse_closedAt (self , card_data ):
7898 closedAt = None
7999 if card_data .get ('closedAt' ):
80- closedAt = datetime .strptime (
81- card_data ['closedAt' ][:10 ],
82- '%Y-%m-%d' )
100+ closedAt = isoparse (card_data ['closedAt' ])
83101 return closedAt
84102
85103 def __parse_points (self , card_data ):
@@ -89,7 +107,7 @@ def __parse_points(self, card_data):
89107 card_points = 1
90108 else :
91109 card_labels = card_data .get ('labels' , {"nodes" : []})['nodes' ]
92- for label in card_labels :
93- if points_label in label [ 'name' ]:
94- card_points += int ( label ['name' ][ len ( points_label ): ])
95- return card_points
110+ card_points = sum ([ int ( label [ 'name' ][ len ( points_label ):])
111+ for label in card_labels
112+ if points_label in label ['name' ]])
113+ return card_points
0 commit comments