Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file modified issue_metrics.py
100644 → 100755
Empty file.
54 changes: 44 additions & 10 deletions test_time_in_draft.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def test_time_in_draft_with_ready_for_review(self):
"""
self.issue.issue.events.return_value = [
MagicMock(
event="converted_to_draft",
event="convert_to_draft",
created_at=datetime(2021, 1, 1, tzinfo=pytz.utc),
),
MagicMock(
Expand All @@ -46,7 +46,7 @@ def test_time_in_draft_without_ready_for_review(self):
"""
self.issue.issue.events.return_value = [
MagicMock(
event="converted_to_draft",
event="convert_to_draft",
created_at=datetime(2021, 1, 1, tzinfo=pytz.utc),
),
]
Expand All @@ -63,15 +63,15 @@ def test_time_in_draft_multiple_intervals(self):
"""
self.issue.issue.events.return_value = [
MagicMock(
event="converted_to_draft",
event="convert_to_draft",
created_at=datetime(2021, 1, 1, tzinfo=pytz.utc),
),
MagicMock(
event="ready_for_review",
created_at=datetime(2021, 1, 3, tzinfo=pytz.utc),
),
MagicMock(
event="converted_to_draft",
event="convert_to_draft",
created_at=datetime(2021, 1, 5, tzinfo=pytz.utc),
),
MagicMock(
Expand All @@ -89,7 +89,7 @@ def test_time_in_draft_ongoing_draft(self):
"""
self.issue.issue.events.return_value = [
MagicMock(
event="converted_to_draft",
event="convert_to_draft",
created_at=datetime(2021, 1, 1, tzinfo=pytz.utc),
),
]
Expand Down Expand Up @@ -117,7 +117,7 @@ def test_time_in_draft_without_ready_for_review_and_closed(self):
"""
self.issue.issue.events.return_value = [
MagicMock(
event="converted_to_draft",
event="convert_to_draft",
created_at=datetime(2021, 1, 1, tzinfo=pytz.utc),
),
]
Expand All @@ -133,9 +133,9 @@ def test_time_in_draft_initially_created_as_draft(self):
Test measure_time_in_draft with a PR initially created as draft.
"""
# Set up issue created_at time
self.issue.issue.created_at = "2021-01-01T00:00:00Z"
self.issue.issue.created_at = datetime(2021, 1, 1, tzinfo=pytz.utc)

# Mock events with only ready_for_review (no converted_to_draft)
# Mock events with only ready_for_review (no convert_to_draft)
self.issue.issue.events.return_value = [
MagicMock(
event="ready_for_review",
Expand All @@ -159,7 +159,7 @@ def test_time_in_draft_initially_created_as_draft_still_open(self):
Test measure_time_in_draft with a PR initially created as draft and still in draft.
"""
# Set up issue created_at time
self.issue.issue.created_at = "2021-01-01T00:00:00Z"
self.issue.issue.created_at = datetime(2021, 1, 1, tzinfo=pytz.utc)

# Mock events with no ready_for_review events (still draft)
self.issue.issue.events.return_value = []
Expand Down Expand Up @@ -192,7 +192,7 @@ def test_time_in_draft_with_attribute_error_scenario(self):
issue_search_result.issue.state = "open"
issue_search_result.issue.events.return_value = [
MagicMock(
event="converted_to_draft",
event="convert_to_draft",
created_at=datetime(2021, 1, 1, tzinfo=pytz.utc),
),
]
Expand All @@ -204,6 +204,40 @@ def test_time_in_draft_with_attribute_error_scenario(self):
expected = timedelta(days=3)
self.assertEqual(result, expected, "The time in draft should be 3 days.")

def test_time_in_draft_with_iterator_events(self):
"""
Test measure_time_in_draft with events() returning an iterator instead of a list.
This test ensures the function works correctly when events() returns an iterator
(as it does in the real GitHub API), which can only be consumed once.
"""
# Set up issue created_at time
self.issue.issue.created_at = datetime(2021, 1, 1, tzinfo=pytz.utc)

# Create an iterator of events (simulating real GitHub API behavior)
def events_iterator():
return iter(
[
MagicMock(
event="convert_to_draft",
created_at=datetime(2021, 1, 1, tzinfo=pytz.utc),
),
MagicMock(
event="ready_for_review",
created_at=datetime(2021, 1, 3, tzinfo=pytz.utc),
),
]
)

self.issue.issue.events = events_iterator

result = measure_time_in_draft(self.issue)
expected = timedelta(days=2)
self.assertEqual(
result,
expected,
"The time in draft should be 2 days when events() returns an iterator.",
)


class TestGetStatsTimeInDraft(unittest.TestCase):
"""
Expand Down
25 changes: 14 additions & 11 deletions time_in_draft.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def measure_time_in_draft(
returns:
Union[timedelta, None]: Total time the pull request has spent in draft state.
"""
events = issue.issue.events()
events = list(issue.issue.events())
draft_start = None
total_draft_time = timedelta(0)

Expand All @@ -35,25 +35,23 @@ def measure_time_in_draft(
if pull_request is None:
pull_request = issue.issue.pull_request()

pr_created_at = datetime.fromisoformat(
issue.issue.created_at.replace("Z", "+00:00")
)
pr_created_at = issue.issue.created_at

# Look for ready_for_review events to determine if PR was initially draft
ready_for_review_events = []
converted_to_draft_events = []
convert_to_draft_events = []
for event in events:
if event.event == "ready_for_review":
ready_for_review_events.append(event)
elif event.event == "converted_to_draft":
converted_to_draft_events.append(event)
elif event.event == "convert_to_draft":
convert_to_draft_events.append(event)

# If there are ready_for_review events, check if PR was initially draft
if ready_for_review_events:
first_ready_event = min(ready_for_review_events, key=lambda x: x.created_at)
prior_draft_events = [
e
for e in converted_to_draft_events
for e in convert_to_draft_events
if e.created_at < first_ready_event.created_at
]

Expand All @@ -62,7 +60,7 @@ def measure_time_in_draft(
total_draft_time += first_ready_event.created_at - pr_created_at

# If there are no ready_for_review events but the PR is currently draft, it might be initially draft and still open
elif not ready_for_review_events and not converted_to_draft_events:
elif not ready_for_review_events and not convert_to_draft_events:
# Check if PR is currently draft and open
if (
hasattr(pull_request, "draft")
Expand All @@ -77,7 +75,7 @@ def measure_time_in_draft(
pass

for event in events:
if event.event == "converted_to_draft":
if event.event == "convert_to_draft":
draft_start = event.created_at
elif event.event == "ready_for_review" and draft_start:
# Calculate draft time for this interval
Expand All @@ -88,7 +86,12 @@ def measure_time_in_draft(
if draft_start and issue.issue.state == "open":
total_draft_time += datetime.now(pytz.utc) - draft_start

return total_draft_time if total_draft_time > timedelta(0) else None
# Round to the nearest second
return (
timedelta(seconds=round(total_draft_time.total_seconds()))
if total_draft_time > timedelta(0)
else None
)


def get_stats_time_in_draft(
Expand Down
Loading