Skip to content

Commit

Permalink
Merge pull request #431 from brokoli777/update-time-in-draft
Browse files Browse the repository at this point in the history
fix: Update time in draft to account for edge cases and update tests
  • Loading branch information
zkoppert authored Dec 1, 2024
2 parents c0b6fb2 + aff7091 commit dcfd440
Show file tree
Hide file tree
Showing 3 changed files with 104 additions and 21 deletions.
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
91 changes: 83 additions & 8 deletions test_time_in_draft.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,37 +18,112 @@ 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)
result = measure_time_in_draft(self.issue)
self.assertIsNone(
result, "The result should be None when draft was never used."
result,
"The result should be None for a closed issue with an ongoing draft.",
)


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

0 comments on commit dcfd440

Please sign in to comment.