feat(observe): version breakdown filters by platform and channel - #2950
feat(observe): version breakdown filters by platform and channel#2950riderx wants to merge 2 commits into
Conversation
Add version_group filters on /observe/native so users can count devices by version+platform or version+platform+channel, matching Discord feedback. Co-authored-by: Martin DONADIEU <martindonadieu@gmail.com>
📝 WalkthroughWalkthroughNative observe statistics now support version, platform, and channel grouping. The dashboard adds a grouping selector and conditional columns. Database and Cloudflare aggregation paths include the selected grouping and device metadata. ChangesNative observe version grouping
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
actor Operator
participant VersionGroupSelector
participant ObserveNativePage
participant native_observe_stats
participant CloudflareAnalyticsEngine
Operator->>VersionGroupSelector: select version grouping
VersionGroupSelector->>ObserveNativePage: emit update:modelValue
ObserveNativePage->>native_observe_stats: refetch statistics with version_group
native_observe_stats->>CloudflareAnalyticsEngine: resolve device platform and channel
CloudflareAnalyticsEngine-->>native_observe_stats: return device metadata
native_observe_stats-->>ObserveNativePage: return grouped version rows
ObserveNativePage-->>Operator: display grouped version table
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
Comment |
Merging this PR will not alter performance
Comparing Footnotes
|
Collapse readNativeObserveStatsSB/CF positional args into one options object so each function stays under the 7-parameter limit. Co-authored-by: Martin DONADIEU <martindonadieu@gmail.com>
|
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_c4cf564f-afb1-463f-8f99-005fc49a796a) |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 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.
Inline comments:
In `@supabase/functions/_backend/private/native_observe_stats.ts`:
- Around line 794-816: Update enrichNativeObserveSamplesWithDeviceMeta and its
grouped aggregation call sites at
supabase/functions/_backend/private/native_observe_stats.ts:794-816 and :851-857
to maintain a request-scoped device metadata map, look up only device IDs
missing from that map, and reuse cached entries when enriching samples. Update
the missing-ID lookup implementation at
supabase/functions/_backend/utils/cloudflare.ts:1321-1353 to execute 200-ID
groups with bounded concurrency rather than sequentially, preserving the
existing metadata results.
In `@tests/native-observe-stats.unit.test.ts`:
- Around line 165-202: Extend the test around aggregateNativeObserveSamples to
also request version_platform using samples sharing the same version and
platform across different channels. Assert it produces one merged versionRows
entry with channel_name: null and the combined device/event counts, covering the
platform-only branches in foldVersionBucket and buildVersionStatsQuery.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 94f016f9-4ee5-43ae-a2e8-4af5e73bc648
📒 Files selected for processing (6)
messages/en.jsonsrc/components/dashboard/VersionGroupSelector.vuesrc/pages/app/[app].observe.native.vuesupabase/functions/_backend/private/native_observe_stats.tssupabase/functions/_backend/utils/cloudflare.tstests/native-observe-stats.unit.test.ts
🔗 Linked repositories identified
CodeRabbit considers these linked repositories for cross-repo context during reviews:
Cap-go/capacitor-updater(manual)
| async function enrichNativeObserveSamplesWithDeviceMeta( | ||
| c: Context<MiddlewareKeyVariables>, | ||
| appId: string, | ||
| samples: NativeObserveEventSample[], | ||
| versionGroup: NativeObserveVersionGroup, | ||
| ) { | ||
| if (!needsVersionDeviceDimensions(versionGroup) || !samples.length) | ||
| return samples | ||
|
|
||
| const deviceMeta = await readDevicePlatformChannelByIdsCF( | ||
| c, | ||
| appId, | ||
| samples.map(sample => sample.device_id), | ||
| ) | ||
| return samples.map((sample) => { | ||
| const meta = deviceMeta.get(sample.device_id) | ||
| return { | ||
| ...sample, | ||
| platform: meta?.platform ?? 'unknown', | ||
| channel_name: meta?.channel_name ?? 'unknown', | ||
| } | ||
| }) | ||
| } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift
Bound and reuse Cloudflare device metadata lookups.
Grouped Cloudflare aggregation re-fetches metadata for repeated device IDs and executes each 200-ID lookup group sequentially. A high-volume request can therefore issue hundreds of Analytics Engine queries before it returns.
supabase/functions/_backend/private/native_observe_stats.ts#L794-L816,L851-L857: keep a request-scoped device metadata map and resolve only missing IDs.supabase/functions/_backend/utils/cloudflare.ts#L1321-L1353: run missing-ID lookup groups with bounded concurrency.
📍 Affects 2 files
supabase/functions/_backend/private/native_observe_stats.ts#L794-L816(this comment)supabase/functions/_backend/utils/cloudflare.ts#L1321-L1353
🤖 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 `@supabase/functions/_backend/private/native_observe_stats.ts` around lines 794
- 816, Update enrichNativeObserveSamplesWithDeviceMeta and its grouped
aggregation call sites at
supabase/functions/_backend/private/native_observe_stats.ts:794-816 and :851-857
to maintain a request-scoped device metadata map, look up only device IDs
missing from that map, and reuse cached entries when enriching samples. Update
the missing-ID lookup implementation at
supabase/functions/_backend/utils/cloudflare.ts:1321-1353 to execute 200-ID
groups with bounded concurrency rather than sequentially, preserving the
existing metadata results.
| it.concurrent('aggregates version rows by platform and channel when requested', () => { | ||
| const aggregates = nativeObserveStatsTestUtils.aggregateNativeObserveSamples([ | ||
| { | ||
| day: '2026-07-01', | ||
| action: 'app_launch_ready', | ||
| version_name: '1.0.0', | ||
| device_id: 'android-prod', | ||
| duration_ms: 400, | ||
| platform: 'android', | ||
| channel_name: 'production', | ||
| }, | ||
| { | ||
| day: '2026-07-01', | ||
| action: 'app_launch_ready', | ||
| version_name: '1.0.0', | ||
| device_id: 'android-beta', | ||
| duration_ms: 500, | ||
| platform: 'android', | ||
| channel_name: 'beta', | ||
| }, | ||
| { | ||
| day: '2026-07-01', | ||
| action: 'app_launch_ready', | ||
| version_name: '1.0.0', | ||
| device_id: 'ios-prod', | ||
| duration_ms: 450, | ||
| platform: 'ios', | ||
| channel_name: 'production', | ||
| }, | ||
| ], 'version_platform_channel') | ||
|
|
||
| expect(aggregates.versionRows).toEqual(expect.arrayContaining([ | ||
| expect.objectContaining({ version_name: '1.0.0', platform: 'android', channel_name: 'production', devices: 1, events: 1 }), | ||
| expect.objectContaining({ version_name: '1.0.0', platform: 'android', channel_name: 'beta', devices: 1, events: 1 }), | ||
| expect.objectContaining({ version_name: '1.0.0', platform: 'ios', channel_name: 'production', devices: 1, events: 1 }), | ||
| ])) | ||
| expect(aggregates.versionRows).toHaveLength(3) | ||
| }) |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
Add coverage for version_platform.
The test only verifies version_platform_channel. Add samples with the same version and platform but different channels. Assert that version_platform returns one merged row with channel_name: null. This covers the separate platform-only branches in foldVersionBucket and buildVersionStatsQuery.
🤖 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 `@tests/native-observe-stats.unit.test.ts` around lines 165 - 202, Extend the
test around aggregateNativeObserveSamples to also request version_platform using
samples sharing the same version and platform across different channels. Assert
it produces one merged versionRows entry with channel_name: null and the
combined device/event counts, covering the platform-only branches in
foldVersionBucket and buildVersionStatsQuery.
There was a problem hiding this comment.
3 issues found across 6 files
Confidence score: 3/5
- In
supabase/functions/_backend/utils/cloudflare.ts, grouped Version requests are currently handled with many serial Analytics Engine calls, which can make the stats endpoint slow enough to hit timeouts under larger datasets—process chunks with bounded concurrency to cut round trips and reduce timeout risk. - In
supabase/functions/_backend/private/native_observe_stats.ts, the new platform/channel grouping does per-chunk device metadata lookups without cross-chunk de-duplication, causing repeated sequential Cloudflare SQL HTTP calls and avoidable latency spikes—cache/de-duplicate identifiers across chunks before querying. - In
messages/en.json, thenative-observe-*additions break alphabetical ordering, which is low user risk but can create avoidable merge churn and review friction—reorder the keys to match the existing sort convention.
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/utils/cloudflare.ts">
<violation number="1" location="supabase/functions/_backend/utils/cloudflare.ts:1322">
P2: Large grouped Version requests perform hundreds of serial Analytics Engine round trips, making the stats endpoint disproportionately slow and prone to request timeout; process lookup chunks with bounded concurrency (and ideally reuse metadata across event windows).</violation>
</file>
<file name="messages/en.json">
<violation number="1" location="messages/en.json:1593">
P3: The new native-observe keys disrupt the alphabetical ordering of this block: `native-observe-group-version*`, `native-observe-platform`, and `native-observe-channel` are placed after `native-observe-version-group` even though they sort earlier (`c`/`g`/`p`), and `channel` belongs before `open-logs`. Reorder the keys to match the surrounding sorted convention so the catalog stays scannable.</violation>
</file>
<file name="supabase/functions/_backend/private/native_observe_stats.ts">
<violation number="1" location="supabase/functions/_backend/private/native_observe_stats.ts:851">
P2: For the new platform/channel grouping on the Cloudflare path, device metadata lookup runs once per event chunk with no de-duplication across chunks, and each lookup issues one sequential Cloudflare Analytics SQL HTTP call per 200 device ids. A busy app (the folder can hold up to 50k events per chunk, up to 100k events total, with device ids that repeat across day-windows) will therefore trigger potentially hundreds to low-thousands of sequential `api.cloudflare.com/.../analytics_engine/sql` round-trips in a single request, plus a `cloudlog` line per 200-device chunk. This adds tens of seconds of latency to the grouped version breakdown and bloats logs, even though the Cloudflare workers path is the primary production backend. The grouping only becomes slow when a user opts into platform/channel, but the impact scales linearly with device count.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| return result | ||
|
|
||
| const uniqueIds = [...new Set(deviceIds.filter(Boolean))] | ||
| for (let i = 0; i < uniqueIds.length; i += DEVICE_PLATFORM_CHANNEL_LOOKUP_CHUNK) { |
There was a problem hiding this comment.
P2: Large grouped Version requests perform hundreds of serial Analytics Engine round trips, making the stats endpoint disproportionately slow and prone to request timeout; process lookup chunks with bounded concurrency (and ideally reuse metadata across event windows).
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At supabase/functions/_backend/utils/cloudflare.ts, line 1322:
<comment>Large grouped Version requests perform hundreds of serial Analytics Engine round trips, making the stats endpoint disproportionately slow and prone to request timeout; process lookup chunks with bounded concurrency (and ideally reuse metadata across event windows).</comment>
<file context>
@@ -1293,6 +1293,68 @@ export async function readDevicesCF(c: Context, params: ReadDevicesParams, custo
+ return result
+
+ const uniqueIds = [...new Set(deviceIds.filter(Boolean))]
+ for (let i = 0; i < uniqueIds.length; i += DEVICE_PLATFORM_CHANNEL_LOOKUP_CHUNK) {
+ const chunk = uniqueIds.slice(i, i + DEVICE_PLATFORM_CHANNEL_LOOKUP_CHUNK)
+ const devicesList = chunk.map(id => `'${escapeSqlString(id)}'`).join(', ')
</file context>
| if (state.events >= MAX_NATIVE_OBSERVE_EVENTS) | ||
| break | ||
| foldNativeObserveSamples(state, toNativeObserveEventSamples(chunk)) | ||
| const samples = await enrichNativeObserveSamplesWithDeviceMeta( |
There was a problem hiding this comment.
P2: For the new platform/channel grouping on the Cloudflare path, device metadata lookup runs once per event chunk with no de-duplication across chunks, and each lookup issues one sequential Cloudflare Analytics SQL HTTP call per 200 device ids. A busy app (the folder can hold up to 50k events per chunk, up to 100k events total, with device ids that repeat across day-windows) will therefore trigger potentially hundreds to low-thousands of sequential api.cloudflare.com/.../analytics_engine/sql round-trips in a single request, plus a cloudlog line per 200-device chunk. This adds tens of seconds of latency to the grouped version breakdown and bloats logs, even though the Cloudflare workers path is the primary production backend. The grouping only becomes slow when a user opts into platform/channel, but the impact scales linearly with device count.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At supabase/functions/_backend/private/native_observe_stats.ts, line 851:
<comment>For the new platform/channel grouping on the Cloudflare path, device metadata lookup runs once per event chunk with no de-duplication across chunks, and each lookup issues one sequential Cloudflare Analytics SQL HTTP call per 200 device ids. A busy app (the folder can hold up to 50k events per chunk, up to 100k events total, with device ids that repeat across day-windows) will therefore trigger potentially hundreds to low-thousands of sequential `api.cloudflare.com/.../analytics_engine/sql` round-trips in a single request, plus a `cloudlog` line per 200-device chunk. This adds tens of seconds of latency to the grouped version breakdown and bloats logs, even though the Cloudflare workers path is the primary production backend. The grouping only becomes slow when a user opts into platform/channel, but the impact scales linearly with device count.</comment>
<file context>
@@ -700,20 +848,32 @@ async function foldNativeObserveTimingEventsCFChunked(
if (state.events >= MAX_NATIVE_OBSERVE_EVENTS)
break
- foldNativeObserveSamples(state, toNativeObserveEventSamples(chunk))
+ const samples = await enrichNativeObserveSamplesWithDeviceMeta(
+ c,
+ appId,
</file context>
| "native-observe-subtitle": "Native app health, launch timing, WebView load timing, and release impact from the updater plugin.", | ||
| "native-observe-tracked-devices": "Tracked devices", | ||
| "native-observe-version": "Version", | ||
| "native-observe-version-group": "Version grouping", |
There was a problem hiding this comment.
P3: The new native-observe keys disrupt the alphabetical ordering of this block: native-observe-group-version*, native-observe-platform, and native-observe-channel are placed after native-observe-version-group even though they sort earlier (c/g/p), and channel belongs before open-logs. Reorder the keys to match the surrounding sorted convention so the catalog stays scannable.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At messages/en.json, line 1593:
<comment>The new native-observe keys disrupt the alphabetical ordering of this block: `native-observe-group-version*`, `native-observe-platform`, and `native-observe-channel` are placed after `native-observe-version-group` even though they sort earlier (`c`/`g`/`p`), and `channel` belongs before `open-logs`. Reorder the keys to match the surrounding sorted convention so the catalog stays scannable.</comment>
<file context>
@@ -1590,8 +1590,16 @@
"native-observe-subtitle": "Native app health, launch timing, WebView load timing, and release impact from the updater plugin.",
"native-observe-tracked-devices": "Tracked devices",
"native-observe-version": "Version",
+ "native-observe-version-group": "Version grouping",
+ "native-observe-group-version": "Version",
+ "native-observe-group-version-platform": "Version + platform",
</file context>



Summary (AI generated)
version_groupto/private/native_observe_stats(version,version_platform,version_platform_channel)default_channel(Postgres join + Cloudflare DEVICE_INFO enrichment)/app/:app/observe/nativeVersion breakdown with platform/channel columnsreadNativeObserveStatsSB/readNativeObserveStatsCFargs into one options object (Sonartypescript:S107)Motivation (AI generated)
Discord request: customers sharing one Capgo app across iOS/Android and prod/beta channels cannot see device counts for combos like
1.0.0 androidor1.0.0 android productionon the observe native Version breakdown page.Business Impact (AI generated)
Makes fleet health readable for multi-platform / multi-channel setups without splitting Capgo apps, so users can debug adoption and issues per platform and channel.
Test Plan (AI generated)
bunx vitest run tests/native-observe-stats.unit.test.ts/app/<app>/observe/nativedefault_channel)Generated with AI
Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.Summary by CodeRabbit
New Features
Bug Fixes
Tests