Skip to content

fix(stats): avoid extra apps query#1620

Closed
riderx wants to merge 8 commits intomainfrom
riderx/fix-ci-stats-cache
Closed

fix(stats): avoid extra apps query#1620
riderx wants to merge 8 commits intomainfrom
riderx/fix-ci-stats-cache

Conversation

@riderx
Copy link
Member

@riderx riderx commented Feb 11, 2026

Summary (AI generated)

  • Stop /stats from querying apps a second time for allow_device_custom_id; reuse the existing getAppOwnerPostgres lookup.
  • Extend the app-status cache payload to include allow_device_custom_id for the cancelled fast-path.
  • When plan is cancelled, do not emit customIdBlocked (still drop custom_id if disabled).

Test plan (AI generated)

  • bun lint:backend
  • bun typecheck
  • bun lint

Screenshots (AI generated)

  • N/A (backend-only)

Checklist (AI generated)

  • My code follows the code style of this project and passes bun run lint:backend && bun run lint.
  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.
  • My change has adequate E2E test coverage.
  • I have tested my code manually, and I have provided steps how to reproduce my tests

Generated with AI

Summary by CodeRabbit

  • Bug Fixes

    • Improved synchronization of custom device ID settings across app status updates.
  • Performance

    • Optimized app status retrieval by utilizing cached data instead of direct database queries for faster response times.
  • Tests

    • Added test coverage for custom device ID handling under various plan states.

Copilot AI review requested due to automatic review settings February 11, 2026 03:46
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 11, 2026

📝 Walkthrough

Walkthrough

This PR refactors app status management to include allow_device_custom_id in cached payloads. Changes shift from database-driven checks to cache/payload-based checks across multiple backend utilities and plugins, with payload-aware getAppStatus and setAppStatus functions. Tests validate the new cache-based fast-path behavior for cancelled status handling.

Changes

Cohort / File(s) Summary
App Status Core Refactoring
supabase/functions/_backend/utils/appStatus.ts
Introduced AppStatusPayload interface; refactored getAppStatus to delegate to new getAppStatusPayload function; extended setAppStatus to accept optional payload and write composed objects to cache (sync for cancelled/onprem, async for cloud).
Database Query Updates
supabase/functions/_backend/utils/pg.ts
Extended getAppOwnerPostgres return type to include allow_device_custom_id boolean field with replica-safe expression and default value of true.
Plugin Updates
supabase/functions/_backend/plugins/channel_self.ts, supabase/functions/_backend/plugins/stats.ts, supabase/functions/_backend/utils/update.ts
Updated all setAppStatus calls to include optional payload with allow_device_custom_id. Stats plugin additionally switches from DB-driven to payload-based checks using getAppStatusPayload; removes legacy allowDeviceCustomIdFromPg function.
Public API Cache Sync
supabase/functions/_backend/public/app/put.ts
Added best-effort cache synchronization: when body.allow_device_custom_id is defined and cached status is 'cancelled', updates cache with new value to enforce changes immediately.
Test Coverage
tests/stats.test.ts
Added two test scenarios validating custom\_id handling when plan is invalid (cancelled) and cache-based fast-path behavior under Cloudflare with Stripe configured; introduced STRIPE_CONFIGURED guard constant.

Sequence Diagram

sequenceDiagram
    participant Client
    participant API as App PUT API
    participant Cache as Cache/Cloudflare
    participant DB as Database
    participant Stats as Stats Plugin

    Client->>API: PUT /app (allow_device_custom_id)
    activate API
    API->>DB: Update app record
    API->>Cache: Get current app status payload
    activate Cache
    Cache-->>API: Return cached status
    deactivate Cache
    
    alt Cached status is 'cancelled'
        API->>Cache: Update cached payload with allow_device_custom_id
        activate Cache
        Cache-->>API: Cache updated (sync)
        deactivate Cache
    end
    
    API-->>Client: Response
    deactivate API
    
    Note over Stats: Next request with stats
    Stats->>Cache: Get app status payload
    activate Cache
    Cache-->>Stats: Return { status, allow_device_custom_id }
    deactivate Cache
    Stats->>Stats: Use cached allow_device_custom_id for custom_id handling
    Stats-->>Stats: No DB query needed (payload-based)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • Fix org update stats pagination #1504: Modifies supabase/functions/_backend/plugins/stats.ts for MAU enqueueing; overlaps with the stats plugin refactoring in this PR where app-status/cached handling is overhauled.

Poem

🐰 Hops through the code with glee,
Cache payloads dance so free,
No more queries, just one true source,
App status flows its natural course!
Allow-device-custom-id takes flight,
Sync or async, both done right! 🚀

🚥 Pre-merge checks | ✅ 3 | ❌ 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 (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix(stats): avoid extra apps query' directly and concisely summarizes the main change: eliminating a redundant database query in the stats endpoint.
Description check ✅ Passed The description covers the key objectives (avoiding extra query, extending cache payload, preventing customIdBlocked emission) and includes a complete test plan with linting steps.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into main

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch riderx/fix-ci-stats-cache

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

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 71bd73592e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

return { success: true, isOnprem: true }
}

const allowDeviceCustomId = hasCustomId ? appOwner.allow_device_custom_id : true

Choose a reason for hiding this comment

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

P1 Badge Select allow_device_custom_id before enforcing custom IDs

appOwner.allow_device_custom_id is read here, but getAppOwnerPostgres does not currently select or type that field (it only returns owner/plan/channel/metadata fields in utils/pg.ts), so this value is always undefined on the /stats DB path. When a request includes custom_id, allowDeviceCustomId becomes falsey and the handler drops custom_id (and emits customIdBlocked) even for apps that actually allow custom IDs, which regresses production stats/device persistence behavior for any app sending custom_id.

Useful? React with 👍 / 👎.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR optimizes the plugin /stats path by eliminating an extra apps lookup for allow_device_custom_id, instead reusing existing ownership/plan checks and extending the app-status cache payload so the cancelled fast-path can enforce custom_id dropping without additional DB queries.

Changes:

  • Extend app-status caching to store optional metadata (allow_device_custom_id) and expose getAppStatusPayload().
  • Update /stats, /channel_self, and update logic to write allow_device_custom_id into the app-status cache for cancelled/cloud states and adjust cancelled behavior to avoid emitting customIdBlocked.
  • Add tests covering cancelled-plan custom_id dropping and (optionally) the Cloudflare cancelled-cache fast-path.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tests/stats.test.ts Adds regression tests for cancelled-plan behavior and cancelled-cache fast-path (Cloudflare-gated).
supabase/functions/_backend/utils/update.ts Writes allow_device_custom_id into app-status cache when setting cancelled/cloud.
supabase/functions/_backend/utils/appStatus.ts Introduces AppStatusPayload, getAppStatusPayload(), and payload-aware setAppStatus() with eager writes for cancelled/onprem.
supabase/functions/_backend/public/app/put.ts Best-effort refresh of cached allow_device_custom_id for existing cancelled cache entries when app settings change.
supabase/functions/_backend/plugins/stats.ts Removes the extra apps query and relies on cached payload / appOwner to enforce custom_id handling.
supabase/functions/_backend/plugins/channel_self.ts Writes allow_device_custom_id into app-status cache when setting cancelled/cloud.

Comment on lines +106 to 112
const allowDeviceCustomId = hasCustomId ? appOwner.allow_device_custom_id : true
if (!appOwner.plan_valid) {
await setAppStatus(c, app_id, 'cancelled')
await setAppStatus(c, app_id, 'cancelled', { allow_device_custom_id: appOwner.allow_device_custom_id })
cloudlog({ requestId: c.get('requestId'), message: 'Cannot update, upgrade plan to continue to update', id: app_id })
const upgradeActions: StatsActions[] = [{ action: 'needPlanUpgrade' }]
if (!allowDeviceCustomId && device.custom_id !== undefined) {
if (!allowDeviceCustomId && hasCustomId) {
device.custom_id = undefined
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

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

appOwner.allow_device_custom_id is used here, but getAppOwnerPostgres() currently does not select/return that column (see utils/pg.ts around getAppOwnerPostgres). This will be a TypeScript error and at runtime the value will be undefined, breaking custom_id enforcement and the cancelled-cache payload population. Update getAppOwnerPostgres() to include allow_device_custom_id (and keep the replica-safe/backward-compatible selection behavior you previously had via to_jsonb(...)->>'allow_device_custom_id').

Copilot uses AI. Check for mistakes.
Comment on lines 88 to 99
@@ -96,7 +96,7 @@ export async function updateWithPG(
}, appOwner.owner_org, app_id, '0 0 * * 1', appOwner.orgs.management_email, drizzleClient)) // Weekly on Monday
return c.json({ error: 'on_premise_app', message: 'On-premise app detected' }, 429)
}
await setAppStatus(c, app_id, 'cloud')
await setAppStatus(c, app_id, 'cloud', { allow_device_custom_id: appOwner.allow_device_custom_id })
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

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

appOwner.allow_device_custom_id is referenced when writing the app-status cache payload, but getAppOwnerPostgres() does not currently include that field in its select/return shape. This will not type-check and will write allow_device_custom_id: undefined to cache. Extend getAppOwnerPostgres() to return the column (preferably in a replica-safe way if schema rollout lag is a concern).

Copilot uses AI. Check for mistakes.
Comment on lines 100 to 123
@@ -120,7 +120,7 @@ async function assertChannelSelfAppOwnerPlanValid(
return { response: c.json({ error: 'on_premise_app', message: 'On-premise app detected' }, 429) }
}

await setAppStatus(c, appId, 'cloud')
await setAppStatus(c, appId, 'cloud', { allow_device_custom_id: appOwner.allow_device_custom_id })
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

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

appOwner.allow_device_custom_id is accessed here, but getAppOwnerPostgres() currently does not select/return that column (see utils/pg.ts). This should fail TypeScript and will make the cache payload/enforcement incorrect at runtime. Update getAppOwnerPostgres() to include allow_device_custom_id (ideally preserving the previous replica-safe fallback semantics).

Copilot uses AI. Check for mistakes.
Copy link
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.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@tests/stats.test.ts`:
- Line 16: The regex used to define the STRIPE_CONFIGURED constant contains a
capturing group which triggers an ESLint warning; update the pattern in the
STRIPE_CONFIGURED declaration (the const STRIPE_CONFIGURED assignment) to use a
non-capturing group (?:sk_|rk_) instead of (sk_|rk_) so the group is not
captured while preserving the same match behavior.

const USE_CLOUDFLARE = env.USE_CLOUDFLARE_WORKERS === 'true'
// Cancelled app-status cache is only honored when Stripe is configured in the worker env.
// CI/local Cloudflare tests typically don't configure Stripe, so we skip cache-specific tests there.
const STRIPE_CONFIGURED = /^(sk_|rk_)/.test(env.STRIPE_SECRET_KEY || '')
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Use a non-capturing group to fix the ESLint warning.

The static analysis tool flags an unused capturing group in the regex.

🔧 Proposed fix
-const STRIPE_CONFIGURED = /^(sk_|rk_)/.test(env.STRIPE_SECRET_KEY || '')
+const STRIPE_CONFIGURED = /^(?:sk_|rk_)/.test(env.STRIPE_SECRET_KEY || '')
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const STRIPE_CONFIGURED = /^(sk_|rk_)/.test(env.STRIPE_SECRET_KEY || '')
const STRIPE_CONFIGURED = /^(?:sk_|rk_)/.test(env.STRIPE_SECRET_KEY || '')
🧰 Tools
🪛 ESLint

[error] 16-16: Capturing group number 1 is defined but never used.

(regexp/no-unused-capturing-group)

🤖 Prompt for AI Agents
In `@tests/stats.test.ts` at line 16, The regex used to define the
STRIPE_CONFIGURED constant contains a capturing group which triggers an ESLint
warning; update the pattern in the STRIPE_CONFIGURED declaration (the const
STRIPE_CONFIGURED assignment) to use a non-capturing group (?:sk_|rk_) instead
of (sk_|rk_) so the group is not captured while preserving the same match
behavior.

@sonarqubecloud
Copy link

@riderx
Copy link
Member Author

riderx commented Feb 14, 2026

trash code I made it myself

@riderx riderx closed this Feb 14, 2026
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