Skip to content

fix: resolve missing lead time breakdown when review events are absen…#697

Open
THEAbhishekjoshi wants to merge 2 commits into
middlewarehq:mainfrom
THEAbhishekjoshi:fix-lead-time-breakdown
Open

fix: resolve missing lead time breakdown when review events are absen…#697
THEAbhishekjoshi wants to merge 2 commits into
middlewarehq:mainfrom
THEAbhishekjoshi:fix-lead-time-breakdown

Conversation

@THEAbhishekjoshi
Copy link
Copy Markdown

@THEAbhishekjoshi THEAbhishekjoshi commented May 21, 2026

Summary

Fixes #696
Fixes incorrect Lead Time breakdown calculations when PRs are merged without review and/or approval events.

Previously, if a PR skipped parts of the review chain, portions of the elapsed lifecycle time were not attributed to any breakdown component, causing the breakdown totals to not reconcile with the overall lead/cycle time.


Problem

The existing implementation assumes the following review chain always exists:

Ready for Review → Review → Approval → Merge

However, some PRs may be:

  • merged without reviews
  • reviewed but merged without approval

In those cases:

  • first_review_time
  • rework_time
  • merge_time

were set to -1 / None, causing parts of the elapsed time to disappear from the Lead Time breakdown.

Example:

Scenario Missing Time
No review + no approval ready → merge
Review exists, no approval review → merge

As a result, the total cycle time remained correct, but the breakdown components no longer summed to the total.


Solution

Implemented fallback timing behavior so elapsed time is attributed to the next logical stage when review events are missing.

Fallback behavior:

Scenario Response Time Rework Time Merge Time
Review + Approval ready → review review → approval approval → merge
Review, no Approval ready → review review → merge 0
No Review, no Approval ready → merge 0 0

This ensures:

  • no elapsed lifecycle time is lost
  • breakdown components reconcile with total lead/cycle time
  • analytics remain meaningful even for unreviewed PRs

Summary by CodeRabbit

  • Bug Fixes
    • Improved pull request metrics by excluding bot-created events from analytics calculations.
    • Reworked rework and merge time logic to return meaningful durations (including zero for merged PRs without reviews) instead of placeholder values when review data is missing.
    • Fixed first-review time to use a reliable fallback timestamp so it reports an actual duration when review timestamps are absent.

Review Change Stack

@CLAassistant
Copy link
Copy Markdown

CLAassistant commented May 21, 2026

CLA assistant check
All committers have signed the CLA.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 21, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: bf4b585c-d8f0-418d-9cc7-054f2c44549f

📥 Commits

Reviewing files that changed from the base of the PR and between bd0edb3 and 956be63.

📒 Files selected for processing (2)
  • backend/analytics_server/mhq/service/code/sync/etl_code_analytics.py
  • backend/analytics_server/tests/service/code/sync/test_etl_code_analytics.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/analytics_server/mhq/service/code/sync/etl_code_analytics.py

Walkthrough

The PR passes bot-filtered events into get_pr_performance and introduces first_response_end_time (first_review.created_at or pr.state_changed_at) to compute first_response_time, rework_time, and merge_time, replacing -1 sentinels and setting merge_time to 0 for merged but unreviewed PRs. Tests updated accordingly.

Changes

Lead Time Breakdown Components

Layer / File(s) Summary
Bot event filtering at call site
backend/analytics_server/mhq/service/code/sync/etl_code_analytics.py
create_pr_metrics filters out bot-created PR events into non_bot_pr_events and passes the filtered list to get_pr_performance.
First response / rework / first-review time calculation
backend/analytics_server/mhq/service/code/sync/etl_code_analytics.py
get_pr_performance introduces first_response_end_time (from first_review.created_at or pr.state_changed_at) and uses it to compute rework_time when there are no approved reviews; first_review_time is derived from this anchor instead of returning -1.
Merge time sentinel logic update
backend/analytics_server/mhq/service/code/sync/etl_code_analytics.py
get_pr_performance sets merge_time = 0 for merged PRs without approved reviews while keeping -1 for unmerged PRs.
Tests: fallback and sentinel expectations
backend/analytics_server/tests/service/code/sync/test_etl_code_analytics.py
Tests added/updated to assert first_review_time fallback for merged PRs without reviews, merge_time = 0 for merged-but-unreviewed PRs, computed rework_time for non-approved PRs based on CHANGES_REQUESTED events, and 0.0 rework_time for merged PRs without reviews.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I sat and tallied hours missed,
Where silent reviews had slipped.
Anchors fallen in their stead,
Now every second finds a bed.
Hoppity metrics—whole again!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: resolving missing lead time breakdown calculations when review events are absent, directly addressing issue #696.
Description check ✅ Passed The PR description is comprehensive and well-structured, covering the problem, solution, and fallback behavior mapping. It includes the linked issue reference and detailed context for the changes.
Linked Issues check ✅ Passed The code changes directly implement the suggested fix from issue #696 by adding fallback timing behavior to attribute elapsed time to next logical stage when review events are missing [#696].
Out of Scope Changes check ✅ Passed All changes are scoped to updating get_pr_performance logic and corresponding test cases to implement the fallback timing behavior specified in issue #696. No unrelated modifications are present.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
backend/analytics_server/mhq/service/code/sync/etl_code_analytics.py (1)

98-108: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Guard against negative rework_time for parity with merge_time.

The merge_time branch on line 119 already clamps negatives to -1 to prevent garbage when events arrive out of expected order (e.g., approval recorded after merge, clock skew, post-close reviews). The new rework_time = (pr.state_changed_at - first_response_end_time).total_seconds() path on lines 99–101 has no such guard, so a first_review.created_at later than pr.state_changed_at would yield a negative rework duration that flows through to pr.rework_time (line 33–35 only filters -1, not negatives).

🛡️ Suggested guard
         if not approved_reviews:
-            rework_time = (
-                pr.state_changed_at - first_response_end_time
-            ).total_seconds()
+            rework_time = (
+                pr.state_changed_at - first_response_end_time
+            ).total_seconds()
+            # Prevent garbage state when review is recorded after state change
+            rework_time = -1 if rework_time < 0 else rework_time
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/analytics_server/mhq/service/code/sync/etl_code_analytics.py` around
lines 98 - 108, The computed rework_time in etl_code_analytics.py can be
negative when using the branch that sets rework_time = (pr.state_changed_at -
first_response_end_time).total_seconds(); update that branch to clamp negative
values the same way merge_time does (set rework_time to -1 when the computed
value is < 0) so pr.rework_time never receives a raw negative duration. Locate
the variables/expressions rework_time, pr.state_changed_at,
first_response_end_time, first_review and approved_reviews and apply the guard
there to match the merge_time behavior.
🧹 Nitpick comments (1)
backend/analytics_server/mhq/service/code/sync/etl_code_analytics.py (1)

131-138: 💤 Low value

Dead fallback branch in first_review_time.

first_response_end_time is now first_review.created_at if first_review else pr.state_changed_at (lines 92–96). Since create_pr_metrics returns early for PullRequestState.OPEN (line 24–25), pr.state_changed_at is always set when this code runs, making first_response_end_time unconditionally truthy. The if first_response_end_time else -1 ternary on lines 136–137 is therefore unreachable and can be simplified.

♻️ Suggested simplification
         return PRPerformance(
             first_review_time=(
-                (
-                    first_response_end_time - pull_request_ready_for_review_time
-                ).total_seconds()
-                if first_response_end_time
-                else -1
+                first_response_end_time - pull_request_ready_for_review_time
+            ).total_seconds(),
-            ),
             rework_time=rework_time,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/analytics_server/mhq/service/code/sync/etl_code_analytics.py` around
lines 131 - 138, The fallback branch in PRPerformance.first_review_time is dead
because first_response_end_time is always truthy; simplify the expression by
removing the ternary and unconditionally computing (first_response_end_time -
pull_request_ready_for_review_time).total_seconds() when constructing
PRPerformance in create_pr_metrics (use the existing symbols
first_response_end_time and pull_request_ready_for_review_time and the
PRPerformance constructor) so the unreachable "else -1" branch is eliminated.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@backend/analytics_server/mhq/service/code/sync/etl_code_analytics.py`:
- Around line 98-108: The computed rework_time in etl_code_analytics.py can be
negative when using the branch that sets rework_time = (pr.state_changed_at -
first_response_end_time).total_seconds(); update that branch to clamp negative
values the same way merge_time does (set rework_time to -1 when the computed
value is < 0) so pr.rework_time never receives a raw negative duration. Locate
the variables/expressions rework_time, pr.state_changed_at,
first_response_end_time, first_review and approved_reviews and apply the guard
there to match the merge_time behavior.

---

Nitpick comments:
In `@backend/analytics_server/mhq/service/code/sync/etl_code_analytics.py`:
- Around line 131-138: The fallback branch in PRPerformance.first_review_time is
dead because first_response_end_time is always truthy; simplify the expression
by removing the ternary and unconditionally computing (first_response_end_time -
pull_request_ready_for_review_time).total_seconds() when constructing
PRPerformance in create_pr_metrics (use the existing symbols
first_response_end_time and pull_request_ready_for_review_time and the
PRPerformance constructor) so the unreachable "else -1" branch is eliminated.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: f10b82b8-8ce9-4f3e-8c61-2ece778f78ee

📥 Commits

Reviewing files that changed from the base of the PR and between 5c3f32d and bd0edb3.

📒 Files selected for processing (1)
  • backend/analytics_server/mhq/service/code/sync/etl_code_analytics.py

dhruvagarwal
dhruvagarwal previously approved these changes May 27, 2026
Copy link
Copy Markdown
Member

@dhruvagarwal dhruvagarwal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@THEAbhishekjoshi can you please review the linter and breaking unit tests?

@dhruvagarwal dhruvagarwal self-requested a review May 27, 2026 17:51
@THEAbhishekjoshi
Copy link
Copy Markdown
Author

@dhruvagarwal i'm on it

@THEAbhishekjoshi
Copy link
Copy Markdown
Author

hey @dhruvagarwal I've pushed the backend formatting fixes (black and trailing whitespace) and the corrected unit tests

could you please approve the workflow runs to let the tests execute and re-review the changes when you have a moment :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Lead Time breakdown missing time when review events are absent

3 participants