Skip to content

fix(dashboard): read update delivery latency from Analytics Engine - #2770

Merged
riderx merged 2 commits into
mainfrom
fix/update-delivery-stats-cf-dual-path
Jul 28, 2026
Merged

fix(dashboard): read update delivery latency from Analytics Engine#2770
riderx merged 2 commits into
mainfrom
fix/update-delivery-stats-cf-dual-path

Conversation

@riderx

@riderx riderx commented Jul 28, 2026

Copy link
Copy Markdown
Member

Summary (AI generated)

  • Route POST /private/update_delivery_stats through the same CF/Postgres dual path as other private stats (APP_LOG → Analytics Engine, otherwise Postgres)
  • Add CF timing-event query helpers and reuse start/complete pairing + duration_ms metadata parsing in JS (AE SQL cannot JOIN)
  • Extend unit coverage for pairing/aggregation and register the new AE SQL query in fixtures

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)

  • Unit tests for delivery pairing/percentiles pass
  • With APP_LOG unset (local Supabase), endpoint still returns Postgres-backed stats
  • In Cloudflare/prod, app/org scopes populate from app_log download start/complete events (or duration_ms metadata)
  • Platform scope remains admin-only and metadata-only
  • Empty org (no apps) returns zero samples without scanning all apps

Generated with AI

Made with Cursor

Review in cubic

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>
@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 32 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: d658c456-7038-44d6-b9d4-84cb5f0dd6ad

📥 Commits

Reviewing files that changed from the base of the PR and between 266d395 and 571dfda.

📒 Files selected for processing (4)
  • supabase/functions/_backend/private/update_delivery_stats.ts
  • supabase/functions/_backend/utils/cloudflare.ts
  • tests/helpers/collectAnalyticsEngineSqlFixtures.ts
  • tests/update-delivery-stats.unit.test.ts

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

@codspeed-hq

codspeed-hq Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 43 untouched benchmarks
⏩ 2 skipped benchmarks1


Comparing fix/update-delivery-stats-cf-dual-path (571dfda) with main (266d395)

Open in CodSpeed

Footnotes

  1. 2 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@riderx
riderx marked this pull request as ready for review July 28, 2026 08:15
Comment thread supabase/functions/_backend/utils/cloudflare.ts
@cursor
cursor Bot requested review from Dalanir and WcaleNieWolny July 28, 2026 08:18

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

Open in Web View Automation 

Sent by Cursor Approval Agent: Pull Request Approver External

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

Open in Web View Automation 

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>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ 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
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 571dfda. Configure here.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

Open in Web View Automation 

Sent by Cursor Approval Agent: Pull Request Approver External

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Risk: medium. Left a non-blocking comment — Cursor Bugbot found 1 unresolved medium issue (out-of-range duration_ms metadata still falls back to start/complete pairing, diverging from Postgres). Not approving; reviewers were already assigned.

Open in Web View Automation 

Sent by Cursor Approval Agent: Pull Request Approver

actions: params.actions,
app_ids: params.appIds,
})
events.push(...chunk)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 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.

Fix in Cursor Fix in Web

Reviewed by Cursor Security Reviewer for commit 571dfda. Configure here.

@sonarqubecloud

Copy link
Copy Markdown

@riderx
riderx merged commit 6a2a63b into main Jul 28, 2026
55 of 75 checks passed

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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')

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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, {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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>

@riderx
riderx deleted the fix/update-delivery-stats-cf-dual-path branch July 28, 2026 08:41
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.

1 participant