Skip to content

Commit 6fa9041

Browse files
CopilotzkoppertCopilot
authored
fix: time_in_draft calculation for pull requests initially created as drafts (#587)
* Initial plan * Fix time_in_draft calculation for PRs initially created as drafts Co-authored-by: zkoppert <[email protected]> * perf: reduce duplicate loops Co-authored-by: Copilot <[email protected]> Signed-off-by: Zack Koppert <[email protected]> --------- Signed-off-by: Zack Koppert <[email protected]> Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: zkoppert <[email protected]> Co-authored-by: Zack Koppert <[email protected]> Co-authored-by: Copilot <[email protected]>
1 parent 4e16ea9 commit 6fa9041

File tree

3 files changed

+103
-1
lines changed

3 files changed

+103
-1
lines changed

issue_metrics.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ def get_per_issue_metrics(
146146
)
147147
if env_vars.draft_pr_tracking:
148148
issue_with_metrics.time_in_draft = measure_time_in_draft(
149-
issue=issue
149+
issue=issue, pull_request=pull_request
150150
)
151151
except TypeError as e:
152152
print(

test_time_in_draft.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,58 @@ def test_time_in_draft_without_ready_for_review_and_closed(self):
128128
"The result should be None for a closed issue with an ongoing draft.",
129129
)
130130

131+
def test_time_in_draft_initially_created_as_draft(self):
132+
"""
133+
Test measure_time_in_draft with a PR initially created as draft.
134+
"""
135+
# Set up issue created_at time
136+
self.issue.issue.created_at = "2021-01-01T00:00:00Z"
137+
138+
# Mock events with only ready_for_review (no converted_to_draft)
139+
self.issue.issue.events.return_value = [
140+
MagicMock(
141+
event="ready_for_review",
142+
created_at=datetime(2021, 1, 3, tzinfo=pytz.utc),
143+
),
144+
]
145+
146+
# Mock pull request object
147+
mock_pull_request = MagicMock()
148+
149+
result = measure_time_in_draft(self.issue, mock_pull_request)
150+
expected = timedelta(days=2)
151+
self.assertEqual(
152+
result,
153+
expected,
154+
"The time in draft should be 2 days for initially draft PR.",
155+
)
156+
157+
def test_time_in_draft_initially_created_as_draft_still_open(self):
158+
"""
159+
Test measure_time_in_draft with a PR initially created as draft and still in draft.
160+
"""
161+
# Set up issue created_at time
162+
self.issue.issue.created_at = "2021-01-01T00:00:00Z"
163+
164+
# Mock events with no ready_for_review events (still draft)
165+
self.issue.issue.events.return_value = []
166+
167+
# Mock pull request object indicating it's currently draft
168+
mock_pull_request = MagicMock()
169+
mock_pull_request.draft = True
170+
171+
with unittest.mock.patch("time_in_draft.datetime") as mock_datetime:
172+
# Keep the real datetime class but only mock the now() method
173+
mock_datetime.fromisoformat = datetime.fromisoformat
174+
mock_datetime.now.return_value = datetime(2021, 1, 4, tzinfo=pytz.utc)
175+
result = measure_time_in_draft(self.issue, mock_pull_request)
176+
expected = timedelta(days=3)
177+
self.assertEqual(
178+
result,
179+
expected,
180+
"The time in draft should be 3 days for initially draft PR still in draft.",
181+
)
182+
131183
def test_time_in_draft_with_attribute_error_scenario(self):
132184
"""
133185
Test measure_time_in_draft to ensure it doesn't raise AttributeError when called

time_in_draft.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,13 @@
1313

1414
def measure_time_in_draft(
1515
issue: github3.issues.Issue,
16+
pull_request: Union[github3.pulls.PullRequest, None] = None,
1617
) -> Union[timedelta, None]:
1718
"""If a pull request has had time in the draft state, return the cumulative amount of time it was in draft.
1819
1920
args:
2021
issue (github3.issues.Issue): A GitHub issue which has been pre-qualified as a pull request.
22+
pull_request (github3.pulls.PullRequest, optional): The pull request object.
2123
2224
returns:
2325
Union[timedelta, None]: Total time the pull request has spent in draft state.
@@ -26,6 +28,54 @@ def measure_time_in_draft(
2628
draft_start = None
2729
total_draft_time = timedelta(0)
2830

31+
# Check if PR was initially created as draft
32+
pr_created_at = None
33+
34+
try:
35+
if pull_request is None:
36+
pull_request = issue.issue.pull_request()
37+
38+
pr_created_at = datetime.fromisoformat(
39+
issue.issue.created_at.replace("Z", "+00:00")
40+
)
41+
42+
# Look for ready_for_review events to determine if PR was initially draft
43+
ready_for_review_events = []
44+
converted_to_draft_events = []
45+
for event in events:
46+
if event.event == "ready_for_review":
47+
ready_for_review_events.append(event)
48+
elif event.event == "converted_to_draft":
49+
converted_to_draft_events.append(event)
50+
51+
# If there are ready_for_review events, check if PR was initially draft
52+
if ready_for_review_events:
53+
first_ready_event = min(ready_for_review_events, key=lambda x: x.created_at)
54+
prior_draft_events = [
55+
e
56+
for e in converted_to_draft_events
57+
if e.created_at < first_ready_event.created_at
58+
]
59+
60+
if not prior_draft_events:
61+
# PR was initially created as draft, calculate time from creation to first ready_for_review
62+
total_draft_time += first_ready_event.created_at - pr_created_at
63+
64+
# If there are no ready_for_review events but the PR is currently draft, it might be initially draft and still open
65+
elif not ready_for_review_events and not converted_to_draft_events:
66+
# Check if PR is currently draft and open
67+
if (
68+
hasattr(pull_request, "draft")
69+
and pull_request.draft
70+
and issue.issue.state == "open"
71+
):
72+
# PR was initially created as draft and is still draft
73+
draft_start = pr_created_at
74+
75+
except (AttributeError, ValueError, TypeError):
76+
# If we can't get PR info, fall back to original logic
77+
pass
78+
2979
for event in events:
3080
if event.event == "converted_to_draft":
3181
draft_start = event.created_at

0 commit comments

Comments
 (0)