fix(dashboard): read update delivery latency from Analytics Engine - #2770
Conversation
Prod plugin stats live in APP_LOG, not Postgres. Use the same CF/SB dual path as other private stats so Time to deliver an update can show data. Co-authored-by: Cursor <cursoragent@cursor.com>
|
Warning Review limit reachedYou’ve reached a temporary PR review limit under our Fair Usage Limits Policy. Next review available in: 32 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (4)
Comment |
Merging this PR will not alter performance
Comparing Footnotes
|
There was a problem hiding this comment.
Stale comment
Risk: medium. Left a non-blocking comment; not approving because Cursor Bugbot has an unresolved medium finding (Analytics Engine 50k-row truncation) and this private stats dual-path change is above the low-risk approval threshold. Reviewers were assigned.
Sent by Cursor Approval Agent: Pull Request Approver External
There was a problem hiding this comment.
Stale comment
Risk: medium. Left a non-blocking comment — Cursor Bugbot completed with 1 unresolved medium finding (Analytics Engine 50k-row cap can truncate delivery events and skew percentiles). Not approving; assigned a reviewer for human attention.
Sent by Cursor Approval Agent: Pull Request Approver
Avoid skewing percentiles when APP_LOG returns only the newest 50k rows for a multi-day window on busy apps. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 571dfda. Configure here.
| if (matchedStart !== null) | ||
| durationMs = endMs - matchedStart | ||
| } | ||
| } |
There was a problem hiding this comment.
Out-of-range metadata still pairs
Medium Severity
In the Analytics Engine path, when duration_ms metadata parses but fails the max-duration check, buildDeliveriesFromEvents falls back to start/complete pairing. The Postgres query keeps the metadata value in COALESCE and drops the row when it exceeds the cap, so prod and local can disagree on samples and percentiles for the same events.
Reviewed by Cursor Bugbot for commit 571dfda. Configure here.
There was a problem hiding this comment.
Risk: medium. Left a non-blocking comment; not approving because Cursor Bugbot completed as skipped on this synchronize and this private stats dual-path change is above the low-risk approval threshold. Human review is already requested (2 reviewers assigned); no additional reviewers added.
Sent by Cursor Approval Agent: Pull Request Approver External
| actions: params.actions, | ||
| app_ids: params.appIds, | ||
| }) | ||
| events.push(...chunk) |
There was a problem hiding this comment.
🔒 Agentic Security Review
Severity: MEDIUM
The Cloudflare analytics path appends every per-day chunk into a single in-memory events array and later performs full sorting/aggregation. Because this endpoint accepts windows up to 365 days, an authenticated caller can trigger large query fan-out and heavy memory/CPU work in one request.
Impact: Repeated large-window requests can create application-layer availability pressure (latency spikes/timeouts) on shared backend runtime resources.
Reviewed by Cursor Security Reviewer for commit 571dfda. Configure here.
|
There was a problem hiding this comment.
2 issues found across 2 files (changes from recent commits).
Confidence score: 3/5
- In
supabase/functions/_backend/private/update_delivery_stats.ts, the new chunking can start at midnight instead of the intended two-hour pairing lookback, and combined with the 50,000-row cap this can undercount first-day delivery samples when the prior day is busy, leading to inaccurate app/org stats — restore the lookback window for each chunk and ensure pagination/limits can’t drop carryover rows. - In
supabase/functions/_backend/private/update_delivery_stats.ts, a 365-day latency request now runs up to 366 Analytics Engine queries sequentially, so latency and failure risk grow linearly with date range and can cause slow or brittle responses for large periods — add bounded parallelism or switch to a query strategy that reduces per-day fan-out.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="supabase/functions/_backend/private/update_delivery_stats.ts">
<violation number="1" location="supabase/functions/_backend/private/update_delivery_stats.ts:559">
P2: App/org delivery samples on the first day can be undercounted when the preceding day is busy: the newly added chunk starts at midnight instead of the requested two-hour pairing lookback, and the 50,000-row limit can discard the latest start events. Starting the first chunk at `params.queryStart` (then using UTC-midnight boundaries for subsequent chunks) would preserve the required lookback without consuming the cap on unrelated events.</violation>
<violation number="2" location="supabase/functions/_backend/private/update_delivery_stats.ts:565">
P2: A 365-day delivery-latency request now performs up to 366 Analytics Engine queries sequentially, making response time and failure exposure grow linearly with the selected period. Bounded parallelism or a query strategy with fewer windows would keep the endpoint responsive for the maximum supported range.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| // AE SQL has a hard row cap and no JOIN. Fetch UTC day windows so busy apps | ||
| // keep coverage across the whole period instead of only the newest 50k rows. | ||
| const events: UpdateDeliveryTimingEventCF[] = [] | ||
| let cursor = params.queryStart.utc().startOf('day') |
There was a problem hiding this comment.
P2: App/org delivery samples on the first day can be undercounted when the preceding day is busy: the newly added chunk starts at midnight instead of the requested two-hour pairing lookback, and the 50,000-row limit can discard the latest start events. Starting the first chunk at params.queryStart (then using UTC-midnight boundaries for subsequent chunks) would preserve the required lookback without consuming the cap on unrelated events.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At supabase/functions/_backend/private/update_delivery_stats.ts, line 559:
<comment>App/org delivery samples on the first day can be undercounted when the preceding day is busy: the newly added chunk starts at midnight instead of the requested two-hour pairing lookback, and the 50,000-row limit can discard the latest start events. Starting the first chunk at `params.queryStart` (then using UTC-midnight boundaries for subsequent chunks) would preserve the required lookback without consuming the cap on unrelated events.</comment>
<file context>
@@ -544,6 +544,37 @@ async function readUpdateDeliveryStatsSB(
+ // AE SQL has a hard row cap and no JOIN. Fetch UTC day windows so busy apps
+ // keep coverage across the whole period instead of only the newest 50k rows.
+ const events: UpdateDeliveryTimingEventCF[] = []
+ let cursor = params.queryStart.utc().startOf('day')
+ const end = params.endExclusive.utc()
+
</file context>
| while (cursor.isBefore(end)) { | ||
| const next = cursor.add(1, 'day') | ||
| const chunkEnd = next.isBefore(end) ? next : end | ||
| const chunk = await readUpdateDeliveryTimingEventsCF(c, { |
There was a problem hiding this comment.
P2: A 365-day delivery-latency request now performs up to 366 Analytics Engine queries sequentially, making response time and failure exposure grow linearly with the selected period. Bounded parallelism or a query strategy with fewer windows would keep the endpoint responsive for the maximum supported range.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At supabase/functions/_backend/private/update_delivery_stats.ts, line 565:
<comment>A 365-day delivery-latency request now performs up to 366 Analytics Engine queries sequentially, making response time and failure exposure grow linearly with the selected period. Bounded parallelism or a query strategy with fewer windows would keep the endpoint responsive for the maximum supported range.</comment>
<file context>
@@ -544,6 +544,37 @@ async function readUpdateDeliveryStatsSB(
+ while (cursor.isBefore(end)) {
+ const next = cursor.add(1, 'day')
+ const chunkEnd = next.isBefore(end) ? next : end
+ const chunk = await readUpdateDeliveryTimingEventsCF(c, {
+ start_date: cursor.toISOString(),
+ end_date: chunkEnd.toISOString(),
</file context>







Summary (AI generated)
POST /private/update_delivery_statsthrough the same CF/Postgres dual path as other private stats (APP_LOG→ Analytics Engine, otherwise Postgres)duration_msmetadata parsing in JS (AE SQL cannot JOIN)Motivation (AI generated)
"Time to deliver an update" stayed empty in production because the endpoint only queried
public.stats, while prod plugin download actions are written to Cloudflare Analytics Engine (APP_LOG). Local/demo Postgres still works; prod never had download timing rows to percentile.Business Impact (AI generated)
Restores a customer-facing delivery latency chart in the Capgo console so app/org/admin users can see real OTA download latency instead of a permanent empty state.
Test Plan (AI generated)
APP_LOGunset (local Supabase), endpoint still returns Postgres-backed statsapp_logdownload start/complete events (orduration_msmetadata)Generated with AI
Made with Cursor