Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Update time in draft to account for edge cases and update tests #431

Merged
merged 4 commits into from
Dec 1, 2024
Merged
Show file tree
Hide file tree
Changes from 2 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
3 changes: 1 addition & 2 deletions issue_metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,7 @@ def get_per_issue_metrics(
ready_for_review_at = get_time_to_ready_for_review(issue, pull_request)
if env_vars.draft_pr_tracking:
issue_with_metrics.time_in_draft = measure_time_in_draft(
issue=issue,
ready_for_review_at=ready_for_review_at,
issue=issue
)

if env_vars.hide_time_to_first_response is False:
Expand Down
61 changes: 51 additions & 10 deletions test_time_in_draft.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,38 +18,79 @@ def setUp(self):
Setup common test data and mocks.
"""
self.issue = MagicMock()
self.issue.issue.created_at = datetime(2021, 1, 1, tzinfo=pytz.utc)
self.issue.issue.state = "open"

def test_time_in_draft_with_ready_for_review(self):
"""
Test measure_time_in_draft when ready_for_review_at is provided.
Test measure_time_in_draft with one draft and review interval.
"""
ready_for_review_at = datetime(2021, 1, 3, tzinfo=pytz.utc)
result = measure_time_in_draft(self.issue, ready_for_review_at)
self.issue.events.return_value = [
MagicMock(event="converted_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)),
]
result = measure_time_in_draft(self.issue)
expected = timedelta(days=2)
self.assertEqual(result, expected, "The time in draft should be 2 days.")

def test_time_in_draft_without_ready_for_review(self):
"""
Test measure_time_in_draft when ready_for_review_at is not provided and issue is still open.
"""
self.issue.events.return_value = [
MagicMock(event="converted_to_draft", created_at=datetime(2021, 1, 1, tzinfo=pytz.utc)),
]
now = datetime(2021, 1, 4, tzinfo=pytz.utc)
with unittest.mock.patch("time_in_draft.datetime") as mock_datetime:
mock_datetime.now.return_value = now
result = measure_time_in_draft(self.issue, None)
result = measure_time_in_draft(self.issue)
expected = timedelta(days=3)
self.assertEqual(result, expected, "The time in draft should be 3 days.")

def test_time_in_draft_multiple_intervals(self):
"""
Test measure_time_in_draft with multiple draft intervals.
"""
self.issue.events.return_value = [
MagicMock(event="converted_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", created_at=datetime(2021, 1, 5, tzinfo=pytz.utc)),
MagicMock(event="ready_for_review", created_at=datetime(2021, 1, 7, tzinfo=pytz.utc)),
]
result = measure_time_in_draft(self.issue)
expected = timedelta(days=4)
self.assertEqual(result, expected, "The total time in draft should be 4 days.")

def test_time_in_draft_ongoing_draft(self):
"""
Test measure_time_in_draft with an ongoing draft interval.
"""
self.issue.events.return_value = [
MagicMock(event="converted_to_draft", created_at=datetime(2021, 1, 1, tzinfo=pytz.utc)),
]
with unittest.mock.patch("time_in_draft.datetime") as mock_datetime:
mock_datetime.now.return_value = datetime(2021, 1, 4, tzinfo=pytz.utc)
result = measure_time_in_draft(self.issue)
expected = timedelta(days=3)
self.assertEqual(result, expected, "The ongoing draft time should be 3 days.")

def test_time_in_draft_no_draft_events(self):
"""
Test measure_time_in_draft with no draft-related events.
"""
self.issue.events.return_value = []
result = measure_time_in_draft(self.issue)
self.assertIsNone(result, "The result should be None when there are no draft events.")

def test_time_in_draft_without_ready_for_review_and_closed(self):
"""
Test measure_time_in_draft when ready_for_review_at is not provided and issue is closed.
Test measure_time_in_draft for a closed issue with an ongoing draft and ready_for_review_at is not provided.
"""
self.issue.events.return_value = [
MagicMock(event="converted_to_draft", created_at=datetime(2021, 1, 1, tzinfo=pytz.utc)),
]
self.issue.issue.state = "closed"
result = measure_time_in_draft(self.issue, None)
self.assertIsNone(
result, "The result should be None when draft was never used."
)
result = measure_time_in_draft(self.issue)
self.assertIsNone(result, "The result should be None for a closed issue with an ongoing draft.")


class TestGetStatsTimeInDraft(unittest.TestCase):
Expand Down
31 changes: 20 additions & 11 deletions time_in_draft.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,23 +13,32 @@

def measure_time_in_draft(
issue: github3.issues.Issue,
ready_for_review_at: Union[datetime, None],
) -> Union[datetime, None]:
"""If a pull request has had time in the draft state, return the amount of time it was in draft.
) -> Union[timedelta, None]:
"""If a pull request has had time in the draft state, return the cumulative amount of time it was in draft.

args:
issue (github3.issues.Issue): A GitHub issue which has been pre-qualified as a pull request.
ready_for_review_at (datetime | None): The time the pull request was marked as
ready for review.

returns:
Union[datetime, None]: The time the pull request was in draft state.
Union[timedelta, None]: Total time the pull request has spent in draft state.
"""
if ready_for_review_at:
return ready_for_review_at - issue.issue.created_at
if issue.issue.state == "open":
return datetime.now(pytz.utc) - issue.issue.created_at
return None
events = issue.events()
draft_start = None
total_draft_time = timedelta(0)

for event in events:
if event.event == "converted_to_draft":
draft_start = event.created_at
elif event.event == "ready_for_review" and draft_start:
# Calculate draft time for this interval
total_draft_time += event.created_at - draft_start
draft_start = None

# If the PR is currently in draft state, calculate the time in draft up to now
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


def get_stats_time_in_draft(
Expand Down