Skip to content

feat(observe): version breakdown filters by platform and channel - #2950

Open
riderx wants to merge 2 commits into
mainfrom
cursor/native-observe-version-filters-02f2
Open

feat(observe): version breakdown filters by platform and channel#2950
riderx wants to merge 2 commits into
mainfrom
cursor/native-observe-version-filters-02f2

Conversation

@riderx

@riderx riderx commented Aug 8, 2026

Copy link
Copy Markdown
Member

Summary (AI generated)

  • Add version_group to /private/native_observe_stats (version, version_platform, version_platform_channel)
  • Aggregate Version breakdown rows by platform and optional default_channel (Postgres join + Cloudflare DEVICE_INFO enrichment)
  • Add a group selector on /app/:app/observe/native Version breakdown with platform/channel columns
  • Pack readNativeObserveStatsSB / readNativeObserveStatsCF args into one options object (Sonar typescript: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 android or 1.0.0 android production on 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
  • ESLint on touched files
  • Open /app/<app>/observe/native
  • Default grouping still shows version-only rows
  • Switch to Version + platform and confirm platform column + split device counts
  • Switch to Version + platform + channel and confirm channel column (uses device default_channel)
  • Sonar new issues for S107 cleared after options-object refactor

Generated with AI

Open in Web Open in Cursor 

View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

Review in cubic

Summary by CodeRabbit

  • New Features

    • Added grouping options for native app version statistics by version, platform, or platform and channel.
    • Added an accessible selector for switching between grouping modes.
    • Updated version tables with platform and channel details, dynamic descriptions, and adjusted layouts.
  • Bug Fixes

    • Improved device metadata handling for platform and channel breakdowns, including unknown channel values.
  • Tests

    • Added coverage for all supported version-grouping options and their resulting breakdowns.

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

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

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

Changes

Native observe version grouping

Layer / File(s) Summary
Version-group aggregation and response contracts
supabase/functions/_backend/private/native_observe_stats.ts, tests/native-observe-stats.unit.test.ts
The backend validates grouping modes, aggregates version/platform/channel dimensions, applies grouping-specific limits, and returns the selected grouping. Unit tests cover normalization and grouped rows.
Cloudflare device metadata enrichment
supabase/functions/_backend/utils/cloudflare.ts, supabase/functions/_backend/private/native_observe_stats.ts
Cloudflare Analytics Engine data now resolves platform and channel metadata for device IDs. The Cloudflare aggregation path uses the enriched samples for dimension-aware grouping.
Dashboard grouping control and display
src/components/dashboard/VersionGroupSelector.vue, src/pages/app/[app].observe.native.vue, messages/en.json
The dashboard adds a localized grouping selector, refetches statistics when the grouping changes, and conditionally renders platform and channel columns with matching help text and row keys.

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
Loading

Suggested labels: codex

Suggested reviewers: wcaleniewolny

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adding platform and channel filters to the native observe version breakdown.
Description check ✅ Passed The description explains the change, motivation, impact, and test plan; it also lists completed automated checks and remaining manual checks.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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

@codspeed-hq

codspeed-hq Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 43 untouched benchmarks
⏩ 2 skipped benchmarks1


Comparing cursor/native-observe-version-filters-02f2 (8061857) with main (ab0f4b3)

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.

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>
@cursor
cursor Bot temporarily deployed to deepsec-pr August 8, 2026 19:27 Inactive
@sonarqubecloud

sonarqubecloud Bot commented Aug 8, 2026

Copy link
Copy Markdown

@riderx
riderx marked this pull request as ready for review August 8, 2026 19:35
@cursor

cursor Bot commented Aug 8, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

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

@coderabbitai coderabbitai Bot added the codex label Aug 8, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

📥 Commits

Reviewing files that changed from the base of the PR and between fd9a223 and 8061857.

📒 Files selected for processing (6)
  • messages/en.json
  • src/components/dashboard/VersionGroupSelector.vue
  • src/pages/app/[app].observe.native.vue
  • supabase/functions/_backend/private/native_observe_stats.ts
  • supabase/functions/_backend/utils/cloudflare.ts
  • tests/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)

Comment on lines +794 to +816
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',
}
})
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

Comment on lines +165 to +202
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)
})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

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

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, the native-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) {

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: 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(

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: 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>

Comment thread messages/en.json
"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",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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>

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants