Skip to content

Commit dcfd440

Browse files
authored
Merge pull request #431 from brokoli777/update-time-in-draft
fix: Update time in draft to account for edge cases and update tests
2 parents c0b6fb2 + aff7091 commit dcfd440

File tree

3 files changed

+104
-21
lines changed

3 files changed

+104
-21
lines changed

Diff for: issue_metrics.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -125,8 +125,7 @@ def get_per_issue_metrics(
125125
ready_for_review_at = get_time_to_ready_for_review(issue, pull_request)
126126
if env_vars.draft_pr_tracking:
127127
issue_with_metrics.time_in_draft = measure_time_in_draft(
128-
issue=issue,
129-
ready_for_review_at=ready_for_review_at,
128+
issue=issue
130129
)
131130

132131
if env_vars.hide_time_to_first_response is False:

Diff for: test_time_in_draft.py

+83-8
Original file line numberDiff line numberDiff line change
@@ -18,37 +18,112 @@ def setUp(self):
1818
Setup common test data and mocks.
1919
"""
2020
self.issue = MagicMock()
21-
self.issue.issue.created_at = datetime(2021, 1, 1, tzinfo=pytz.utc)
2221
self.issue.issue.state = "open"
2322

2423
def test_time_in_draft_with_ready_for_review(self):
2524
"""
26-
Test measure_time_in_draft when ready_for_review_at is provided.
25+
Test measure_time_in_draft with one draft and review interval.
2726
"""
28-
ready_for_review_at = datetime(2021, 1, 3, tzinfo=pytz.utc)
29-
result = measure_time_in_draft(self.issue, ready_for_review_at)
27+
self.issue.events.return_value = [
28+
MagicMock(
29+
event="converted_to_draft",
30+
created_at=datetime(2021, 1, 1, tzinfo=pytz.utc),
31+
),
32+
MagicMock(
33+
event="ready_for_review",
34+
created_at=datetime(2021, 1, 3, tzinfo=pytz.utc),
35+
),
36+
]
37+
result = measure_time_in_draft(self.issue)
3038
expected = timedelta(days=2)
3139
self.assertEqual(result, expected, "The time in draft should be 2 days.")
3240

3341
def test_time_in_draft_without_ready_for_review(self):
3442
"""
3543
Test measure_time_in_draft when ready_for_review_at is not provided and issue is still open.
3644
"""
45+
self.issue.events.return_value = [
46+
MagicMock(
47+
event="converted_to_draft",
48+
created_at=datetime(2021, 1, 1, tzinfo=pytz.utc),
49+
),
50+
]
3751
now = datetime(2021, 1, 4, tzinfo=pytz.utc)
3852
with unittest.mock.patch("time_in_draft.datetime") as mock_datetime:
3953
mock_datetime.now.return_value = now
40-
result = measure_time_in_draft(self.issue, None)
54+
result = measure_time_in_draft(self.issue)
4155
expected = timedelta(days=3)
4256
self.assertEqual(result, expected, "The time in draft should be 3 days.")
4357

58+
def test_time_in_draft_multiple_intervals(self):
59+
"""
60+
Test measure_time_in_draft with multiple draft intervals.
61+
"""
62+
self.issue.events.return_value = [
63+
MagicMock(
64+
event="converted_to_draft",
65+
created_at=datetime(2021, 1, 1, tzinfo=pytz.utc),
66+
),
67+
MagicMock(
68+
event="ready_for_review",
69+
created_at=datetime(2021, 1, 3, tzinfo=pytz.utc),
70+
),
71+
MagicMock(
72+
event="converted_to_draft",
73+
created_at=datetime(2021, 1, 5, tzinfo=pytz.utc),
74+
),
75+
MagicMock(
76+
event="ready_for_review",
77+
created_at=datetime(2021, 1, 7, tzinfo=pytz.utc),
78+
),
79+
]
80+
result = measure_time_in_draft(self.issue)
81+
expected = timedelta(days=4)
82+
self.assertEqual(result, expected, "The total time in draft should be 4 days.")
83+
84+
def test_time_in_draft_ongoing_draft(self):
85+
"""
86+
Test measure_time_in_draft with an ongoing draft interval.
87+
"""
88+
self.issue.events.return_value = [
89+
MagicMock(
90+
event="converted_to_draft",
91+
created_at=datetime(2021, 1, 1, tzinfo=pytz.utc),
92+
),
93+
]
94+
with unittest.mock.patch("time_in_draft.datetime") as mock_datetime:
95+
mock_datetime.now.return_value = datetime(2021, 1, 4, tzinfo=pytz.utc)
96+
result = measure_time_in_draft(self.issue)
97+
expected = timedelta(days=3)
98+
self.assertEqual(
99+
result, expected, "The ongoing draft time should be 3 days."
100+
)
101+
102+
def test_time_in_draft_no_draft_events(self):
103+
"""
104+
Test measure_time_in_draft with no draft-related events.
105+
"""
106+
self.issue.events.return_value = []
107+
result = measure_time_in_draft(self.issue)
108+
self.assertIsNone(
109+
result, "The result should be None when there are no draft events."
110+
)
111+
44112
def test_time_in_draft_without_ready_for_review_and_closed(self):
45113
"""
46-
Test measure_time_in_draft when ready_for_review_at is not provided and issue is closed.
114+
Test measure_time_in_draft for a closed issue with an ongoing draft and ready_for_review_at is not provided.
47115
"""
116+
self.issue.events.return_value = [
117+
MagicMock(
118+
event="converted_to_draft",
119+
created_at=datetime(2021, 1, 1, tzinfo=pytz.utc),
120+
),
121+
]
48122
self.issue.issue.state = "closed"
49-
result = measure_time_in_draft(self.issue, None)
123+
result = measure_time_in_draft(self.issue)
50124
self.assertIsNone(
51-
result, "The result should be None when draft was never used."
125+
result,
126+
"The result should be None for a closed issue with an ongoing draft.",
52127
)
53128

54129

Diff for: time_in_draft.py

+20-11
Original file line numberDiff line numberDiff line change
@@ -13,23 +13,32 @@
1313

1414
def measure_time_in_draft(
1515
issue: github3.issues.Issue,
16-
ready_for_review_at: Union[datetime, None],
17-
) -> Union[datetime, None]:
18-
"""If a pull request has had time in the draft state, return the amount of time it was in draft.
16+
) -> Union[timedelta, None]:
17+
"""If a pull request has had time in the draft state, return the cumulative amount of time it was in draft.
1918
2019
args:
2120
issue (github3.issues.Issue): A GitHub issue which has been pre-qualified as a pull request.
22-
ready_for_review_at (datetime | None): The time the pull request was marked as
23-
ready for review.
2421
2522
returns:
26-
Union[datetime, None]: The time the pull request was in draft state.
23+
Union[timedelta, None]: Total time the pull request has spent in draft state.
2724
"""
28-
if ready_for_review_at:
29-
return ready_for_review_at - issue.issue.created_at
30-
if issue.issue.state == "open":
31-
return datetime.now(pytz.utc) - issue.issue.created_at
32-
return None
25+
events = issue.events()
26+
draft_start = None
27+
total_draft_time = timedelta(0)
28+
29+
for event in events:
30+
if event.event == "converted_to_draft":
31+
draft_start = event.created_at
32+
elif event.event == "ready_for_review" and draft_start:
33+
# Calculate draft time for this interval
34+
total_draft_time += event.created_at - draft_start
35+
draft_start = None
36+
37+
# If the PR is currently in draft state, calculate the time in draft up to now
38+
if draft_start and issue.issue.state == "open":
39+
total_draft_time += datetime.now(pytz.utc) - draft_start
40+
41+
return total_draft_time if total_draft_time > timedelta(0) else None
3342

3443

3544
def get_stats_time_in_draft(

0 commit comments

Comments
 (0)