Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions cli/src/types/supabase.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1527,6 +1527,7 @@ export type Database = {
versions_created: number
apps_with_cli_onboarding_builds_24h: number
apps_with_manual_builds_24h: number
apps_with_preview: number
apps_active: number | null
average_ltv: number
build_avg_seconds_day_android: number
Expand Down Expand Up @@ -1620,6 +1621,7 @@ export type Database = {
versions_created?: number
apps_with_cli_onboarding_builds_24h?: number
apps_with_manual_builds_24h?: number
apps_with_preview?: number
apps_active?: number | null
average_ltv?: number
build_avg_seconds_day_android?: number
Expand Down Expand Up @@ -1713,6 +1715,7 @@ export type Database = {
versions_created?: number
apps_with_cli_onboarding_builds_24h?: number
apps_with_manual_builds_24h?: number
apps_with_preview?: number
apps_active?: number | null
average_ltv?: number
build_avg_seconds_day_android?: number
Expand Down
2 changes: 2 additions & 0 deletions messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,8 @@
"admin-app-build-onboarding-total-apps-created": "Apps created",
"admin-apps-created-by-day": "Apps Created by Day",
"admin-apps-created-by-day-series": "Apps created",
"admin-apps-with-preview": "Apps with Preview QR Enabled",
"admin-apps-with-preview-series": "Apps with preview QR",
"admin-versions-uploaded-by-day": "Versions Uploaded by Day",
"admin-versions-uploaded-by-day-series": "Versions uploaded",
"admin-credits": "Admin Credits",
Expand Down
29 changes: 29 additions & 0 deletions src/pages/admin/dashboard/users.vue
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ const globalStatsTrendData = ref<Array<{
apps_created: number
versions_created: number
demo_apps_created: number
apps_with_preview: number
devices_last_month: number
trial_extended_orgs: number
trial_extended_subscribed_orgs: number
Expand Down Expand Up @@ -706,6 +707,22 @@ const versionsCreatedTrendSeries = computed(() => {
]
})

const appsWithPreviewTrendSeries = computed(() => {
if (globalStatsTrendData.value.length === 0)
return []

return [
{
label: t('admin-apps-with-preview-series'),
data: globalStatsTrendData.value.map(item => ({
date: item.date,
value: item.apps_with_preview ?? 0,
})),
color: '#119eff',
},
]
})

const trialExtensionTrendSeries = computed(() => {
if (globalStatsTrendData.value.length === 0)
return []
Expand Down Expand Up @@ -1582,6 +1599,18 @@ displayStore.defaultBack = '/dashboard'
:is-loading="isLoadingGlobalStatsTrend"
/>
</ChartCard>

<!-- Apps with Preview QR Enabled -->
<ChartCard
:title="t('admin-apps-with-preview')"
:is-loading="isLoadingGlobalStatsTrend"
:has-data="appsWithPreviewTrendSeries.length > 0"
>
<AdminMultiLineChart
:series="appsWithPreviewTrendSeries"
:is-loading="isLoadingGlobalStatsTrend"
/>
</ChartCard>
</div>
</div>
</div>
Expand Down
3 changes: 3 additions & 0 deletions src/types/supabase.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1567,6 +1567,7 @@ export type Database = {
apps_created: number
apps_with_cli_onboarding_builds_24h: number
apps_with_manual_builds_24h: number
apps_with_preview: number
average_ltv: number
build_avg_seconds_day_android: number
build_avg_seconds_day_ios: number
Expand Down Expand Up @@ -1676,6 +1677,7 @@ export type Database = {
apps_created?: number
apps_with_cli_onboarding_builds_24h?: number
apps_with_manual_builds_24h?: number
apps_with_preview?: number
average_ltv?: number
build_avg_seconds_day_android?: number
build_avg_seconds_day_ios?: number
Expand Down Expand Up @@ -1785,6 +1787,7 @@ export type Database = {
apps_created?: number
apps_with_cli_onboarding_builds_24h?: number
apps_with_manual_builds_24h?: number
apps_with_preview?: number
average_ltv?: number
build_avg_seconds_day_android?: number
build_avg_seconds_day_ios?: number
Expand Down
5 changes: 4 additions & 1 deletion supabase/functions/_backend/plugin_runtime/utils/pg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1097,7 +1097,7 @@ export function requestInfosChannelPostgres(
const MANIFEST_ROWS_CACHE_PATH = '/.manifest-rows-v1'
const MANIFEST_ROWS_CACHE_TTL_SECONDS = 60

type ManifestRow = { file_name: string, file_hash: string, s3_path: string }
interface ManifestRow { file_name: string, file_hash: string, s3_path: string }

export async function requestManifestEntriesPostgres(
c: Context,
Expand Down Expand Up @@ -1909,6 +1909,7 @@ export interface AdminGlobalStatsTrend {
apps_with_manual_builds_24h: number
app_build_onboarding_finalized: boolean
apps_active: number
apps_with_preview: number
users: number
users_active: number
paying: number
Expand Down Expand Up @@ -2036,6 +2037,7 @@ export async function getAdminGlobalStatsTrend(
COALESCE(NULLIF(to_jsonb(gs) ->> 'apps_with_manual_builds_24h', '')::int, 0)::int AS apps_with_manual_builds_24h,
(onboarding_next.date_id IS NOT NULL)::boolean AS app_build_onboarding_finalized,
gs.apps_active::int AS apps_active,
COALESCE(NULLIF(to_jsonb(gs) ->> 'apps_with_preview', '')::int, 0)::int AS apps_with_preview,
gs.users::int AS users,
gs.users_active::int AS users_active,
gs.paying::int AS paying,
Expand Down Expand Up @@ -2192,6 +2194,7 @@ export async function getAdminGlobalStatsTrend(
apps_with_manual_builds_24h: Number(row.apps_with_manual_builds_24h) || 0,
app_build_onboarding_finalized: row.app_build_onboarding_finalized === true || row.app_build_onboarding_finalized === 'true',
apps_active: Number(row.apps_active) || 0,
apps_with_preview: Number(row.apps_with_preview) || 0,
users: Number(row.users) || 0,
users_active: Number(row.users_active) || 0,
paying: Number(row.paying) || 0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1538,6 +1538,7 @@ export type Database = {
versions_created: number
apps_with_cli_onboarding_builds_24h: number
apps_with_manual_builds_24h: number
apps_with_preview: number
average_ltv: number
build_avg_seconds_day_android: number
build_avg_seconds_day_ios: number
Expand Down Expand Up @@ -1637,6 +1638,7 @@ export type Database = {
versions_created?: number
apps_with_cli_onboarding_builds_24h?: number
apps_with_manual_builds_24h?: number
apps_with_preview?: number
average_ltv?: number
build_avg_seconds_day_android?: number
build_avg_seconds_day_ios?: number
Expand Down Expand Up @@ -1736,6 +1738,7 @@ export type Database = {
versions_created?: number
apps_with_cli_onboarding_builds_24h?: number
apps_with_manual_builds_24h?: number
apps_with_preview?: number
average_ltv?: number
build_avg_seconds_day_android?: number
build_avg_seconds_day_ios?: number
Expand Down
35 changes: 30 additions & 5 deletions supabase/functions/_backend/triggers/logsnag_insights.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@
import { BRES, middlewareAPISecret, quickError } from '../utils/hono.ts'
import { cloudlog, cloudlogErr } from '../utils/logging.ts'
import { logsnagInsights } from '../utils/logsnag.ts'
import { closeClient, getDrizzleClient, getPgClient } from '../utils/pg.ts'
import { readGlobalNotificationStatsCF } from '../utils/nativeNotifications.ts'
import { closeClient, getDrizzleClient, getPgClient } from '../utils/pg.ts'
import { countAllApps, countAllUpdates, countAllUpdatesExternal, getUpdateStats } from '../utils/stats.ts'
import { supabaseAdmin } from '../utils/supabase.ts'
import { sendEventToTracking } from '../utils/tracking.ts'
import { backgroundTask } from '../utils/utils.ts'

const DAY_IN_MS = 24 * 60 * 60 * 1000

interface PlanTotal { [key: string]: number }
Expand All @@ -42,11 +43,11 @@
apps_with_cli_onboarding_builds_24h: number
apps_with_manual_builds_24h: number
}
type AppBuildOnboardingMetricRow = {
interface AppBuildOnboardingMetricRow {
created_at: string | Date | null

Check warning on line 47 in supabase/functions/_backend/triggers/logsnag_insights.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Replace this union type with a type alias.

See more on https://sonarcloud.io/project/issues?id=Cap-go_capgo&issues=AZ_gRYzEXLQn_jWPPgfa&open=AZ_gRYzEXLQn_jWPPgfa&pullRequest=2937
created_from_onboarding: boolean | null
onboarding_completed_at: string | Date | null
build_count: number | string | null

Check warning on line 50 in supabase/functions/_backend/triggers/logsnag_insights.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Replace this union type with a type alias.

See more on https://sonarcloud.io/project/issues?id=Cap-go_capgo&issues=AZ_gRYzEXLQn_jWPPgfb&open=AZ_gRYzEXLQn_jWPPgfb&pullRequest=2937
}

function parseMetricDate(value: string | Date | null): number | null {
Expand Down Expand Up @@ -1421,7 +1422,7 @@
const dayEndIso = window.prevDayEnd.toISOString()

try {
const result = await drizzleClient.execute<AppBuildOnboardingMetricRow>(sql`

Check failure on line 1425 in supabase/functions/_backend/triggers/logsnag_insights.ts

View workflow job for this annotation

GitHub Actions / Lint and typecheck

Type 'AppBuildOnboardingMetricRow' does not satisfy the constraint 'Record<string, unknown>'.

Check failure on line 1425 in supabase/functions/_backend/triggers/logsnag_insights.ts

View workflow job for this annotation

GitHub Actions / Lint and typecheck

Type 'AppBuildOnboardingMetricRow' does not satisfy the constraint 'Record<string, unknown>'.
WITH created_apps AS (
SELECT app_id, created_at, created_from_onboarding, onboarding_completed_at
FROM public.apps
Expand Down Expand Up @@ -1489,6 +1490,29 @@
}
}

async function countAppsWithPreview(c: Context, snapshotEnd: Date): Promise<number> {
const pgClient = getPgClient(c, false)
const drizzleClient = getDrizzleClient(pgClient)

try {
const result = await drizzleClient.execute<{ count: number }>(sql`
SELECT COUNT(*)::int AS count
FROM public.apps AS apps
WHERE apps.allow_preview = true
AND apps.created_at < ${snapshotEnd}
`)

return Number(result.rows[0]?.count) || 0
}
catch (error) {
cloudlogErr({ requestId: c.get('requestId'), message: 'countAppsWithPreview error', error })
return 0
Comment on lines +1507 to +1509

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.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Propagate count failures instead of recording zero.

Lines [1507-1509] convert every database error into a valid-looking 0. The core shard can then persist that value as the snapshot, making a failed query indistinguishable from zero preview-enabled apps. Log the error and rethrow it so shard failure handling can retry the snapshot.

Suggested error propagation
   catch (error) {
     cloudlogErr({ requestId: c.get('requestId'), message: 'countAppsWithPreview error', error })
-    return 0
+    throw error
   }
📝 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
catch (error) {
cloudlogErr({ requestId: c.get('requestId'), message: 'countAppsWithPreview error', error })
return 0
catch (error) {
cloudlogErr({ requestId: c.get('requestId'), message: 'countAppsWithPreview error', error })
throw error
🤖 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/triggers/logsnag_insights.ts` around lines 1507 -
1509, Update the countAppsWithPreview error handler to keep logging the database
error, then rethrow it instead of returning 0. Preserve the successful count
path so shard failure handling can detect the failure and retry the snapshot.

}
finally {
closeClient(c, pgClient)
}
}

async function getTrialExtensionStats(c: Context, window: CurrentDayWindow): Promise<TrialExtensionStats> {
const pgClient = getPgClient(c, false)
const drizzleClient = getDrizzleClient(pgClient)
Expand Down Expand Up @@ -2602,6 +2626,7 @@
const finalizedAppBuildOnboardingWindow = getCompletedAppBuildOnboardingWindow(window)
const [
apps,
apps_with_preview,
updates,
updates_external,
users,
Expand All @@ -2613,6 +2638,7 @@
finalizedAppBuildOnboardingMetrics,
] = await Promise.all([
countAllApps(c, window.prevDayEnd),
countAppsWithPreview(c, window.prevDayEnd),
countAllUpdates(c, window.prevDayEnd),
countAllUpdatesExternal(c, window.prevDayEnd),
countRegisteredUsersForSnapshot(c, window.prevDayEnd),
Expand Down Expand Up @@ -2653,6 +2679,7 @@
await updateGlobalStatsSnapshot(c, window.prevDayDateId, {
apps,
apps_active: actives.apps,
apps_with_preview,
above_plan_with_credits,
above_plan_without_credits,
need_upgrade,
Expand Down Expand Up @@ -2680,7 +2707,7 @@
users_active: actives.users,
})

cloudlog({ requestId: c.get('requestId'), message: 'Updated global stats core shard', dateId: window.prevDayDateId, finalizedAppBuildOnboardingDateId: finalizedAppBuildOnboardingWindow.prevDayDateId, apps, updates, users, orgs })
cloudlog({ requestId: c.get('requestId'), message: 'Updated global stats core shard', dateId: window.prevDayDateId, finalizedAppBuildOnboardingDateId: finalizedAppBuildOnboardingWindow.prevDayDateId, apps, apps_with_preview, updates, users, orgs })
}

async function getRegistersToday(c: Context, createdAfterIso: string, createdBeforeIso: string): Promise<number> {
Expand Down Expand Up @@ -3163,7 +3190,6 @@
return `${(count * 100 / total).toFixed(0)}% - ${count}`
}


interface NativeNotificationGlobalStats {
apps: number
providers: number
Expand Down Expand Up @@ -3569,7 +3595,6 @@
case 'native_notifications':
await runNativeNotificationsGlobalStatsShard(c, window)
await markGlobalStatsShardComplete(c, dateId, shard)
return
}
}

Expand Down
17 changes: 10 additions & 7 deletions supabase/functions/_backend/utils/pg.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { SQL } from 'drizzle-orm'
import type { Context } from 'hono'
import type { AdminOnboardingActivationCohort } from './onboardingFunnel.ts'
import { and, eq, isNotNull, isNull, or, sql } from 'drizzle-orm'
import { drizzle } from 'drizzle-orm/node-postgres'
import { alias } from 'drizzle-orm/pg-core'
Expand All @@ -8,14 +9,13 @@ import { getRuntimeKey } from 'hono/adapter'
import { Pool } from 'pg'
import { backgroundTask, existInEnv, getEnv } from '../utils/utils.ts'
import { CacheHelper } from './cache.ts'
import { getAdminOnboardingTelemetry } from './cloudflare.ts'
import { getAdminOnboardingActivationMetrics } from './onboardingFunnel.ts'
import type { AdminOnboardingActivationCohort } from './onboardingFunnel.ts'
import { getChannelSelfOverride, isChannelSelfStoreEnabled } from './channelSelfStore.ts'
import { getAdminOnboardingTelemetry } from './cloudflare.ts'
import { DISPOSABLE_EMAIL_DOMAINS, PERSONAL_EMAIL_DOMAINS } from './emailClassification.ts'
import { getClientDbRegionSB } from './geolocation.ts'
import { REQUIRED_GLOBAL_STATS_SHARDS } from './global_stats.ts'
import { cloudlog, cloudlogErr } from './logging.ts'
import { getAdminOnboardingActivationMetrics } from './onboardingFunnel.ts'
import * as schema from './postgres_schema.ts'
import { withOptionalManifestSelect } from './queryHelpers.ts'
import { getRolloutDecision } from './rollout.ts'
Expand Down Expand Up @@ -1102,10 +1102,10 @@ export async function getAppOwnerPostgres(
}
}

export type AppBlockProviderInfraRequestsLookup =
| { status: 'found', blockProviderInfraRequests: boolean }
| { status: 'missing' }
| { status: 'error' }
export type AppBlockProviderInfraRequestsLookup
= | { status: 'found', blockProviderInfraRequests: boolean }
| { status: 'missing' }
| { status: 'error' }

export async function getAppBlockProviderInfraRequestsPostgres(
c: Context,
Expand Down Expand Up @@ -1538,6 +1538,7 @@ export interface AdminGlobalStatsTrend {
apps_with_manual_builds_24h: number
app_build_onboarding_finalized: boolean
apps_active: number
apps_with_preview: number
users: number
users_active: number
paying: number
Expand Down Expand Up @@ -1675,6 +1676,7 @@ export async function getAdminGlobalStatsTrend(
COALESCE(NULLIF(to_jsonb(gs) ->> 'apps_with_manual_builds_24h', '')::int, 0)::int AS apps_with_manual_builds_24h,
(onboarding_next.date_id IS NOT NULL)::boolean AS app_build_onboarding_finalized,
gs.apps_active::int AS apps_active,
COALESCE(NULLIF(to_jsonb(gs) ->> 'apps_with_preview', '')::int, 0)::int AS apps_with_preview,
gs.users::int AS users,
gs.users_active::int AS users_active,
gs.paying::int AS paying,
Expand Down Expand Up @@ -1841,6 +1843,7 @@ export async function getAdminGlobalStatsTrend(
apps_with_manual_builds_24h: Number(row.apps_with_manual_builds_24h) || 0,
app_build_onboarding_finalized: row.app_build_onboarding_finalized === true || row.app_build_onboarding_finalized === 'true',
apps_active: Number(row.apps_active) || 0,
apps_with_preview: Number(row.apps_with_preview) || 0,
users: Number(row.users) || 0,
users_active: Number(row.users_active) || 0,
paying: Number(row.paying) || 0,
Expand Down
3 changes: 3 additions & 0 deletions supabase/functions/_backend/utils/supabase.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1567,6 +1567,7 @@ export type Database = {
apps_created: number
apps_with_cli_onboarding_builds_24h: number
apps_with_manual_builds_24h: number
apps_with_preview: number
average_ltv: number
build_avg_seconds_day_android: number
build_avg_seconds_day_ios: number
Expand Down Expand Up @@ -1676,6 +1677,7 @@ export type Database = {
apps_created?: number
apps_with_cli_onboarding_builds_24h?: number
apps_with_manual_builds_24h?: number
apps_with_preview?: number
average_ltv?: number
build_avg_seconds_day_android?: number
build_avg_seconds_day_ios?: number
Expand Down Expand Up @@ -1785,6 +1787,7 @@ export type Database = {
apps_created?: number
apps_with_cli_onboarding_builds_24h?: number
apps_with_manual_builds_24h?: number
apps_with_preview?: number
average_ltv?: number
build_avg_seconds_day_android?: number
build_avg_seconds_day_ios?: number
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
ALTER TABLE public.global_stats
ADD COLUMN IF NOT EXISTS apps_with_preview bigint NOT NULL DEFAULT 0;
Comment on lines +1 to +2

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.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Prevent false historical values.

DEFAULT 0 writes 0 into every existing global_stats row. The trend will report false zeros for dates before August 8, 2026, unless downstream queries exclude those rows.

Backfill from a source that records historical allow_preview state, or start the trend at the first real snapshot. Do not derive historical values from the current apps.allow_preview state.

🤖 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/migrations/20260808071749_apps_with_preview_global_stats.sql` around
lines 1 - 2, Update the migration adding apps_with_preview so existing
global_stats rows are not populated with misleading zero values. Backfill
historical values from a source containing the recorded allow_preview state, or
constrain the trend to begin at the first valid snapshot; do not derive
historical values from the current apps.allow_preview state.


COMMENT ON COLUMN public.global_stats.apps_with_preview
IS 'Number of apps with preview QR enabled (allow_preview = true) at snapshot day end.';

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 | 🟡 Minor | ⚡ Quick win

Fix the SQL comment line length.

SQLFluff reports that Line 5 is 90 characters, exceeding the 80-character limit. Shorten or split the comment without changing its meaning.

Proposed fix
-  IS 'Number of apps with preview QR enabled (allow_preview = true) at snapshot day end.';
+  IS 'Apps with preview QR enabled at snapshot day end (allow_preview = true).';
📝 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
IS 'Number of apps with preview QR enabled (allow_preview = true) at snapshot day end.';
IS 'Apps with preview QR enabled at snapshot day end (allow_preview = true).';
🧰 Tools
🪛 SQLFluff (4.2.2)

[error] 5-5: Line is too long (90 > 80).

(LT05)

🤖 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/migrations/20260808071749_apps_with_preview_global_stats.sql` at
line 5, Shorten or split the SQL comment attached to the preview-apps statistic
so each line is no longer than 80 characters, while preserving its meaning about
apps with allow_preview enabled at snapshot day end.

Source: Linters/SAST tools

12 changes: 12 additions & 0 deletions tests/logsnag-insights-revenue.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -680,6 +680,18 @@ describe('logsnag revenue metric helpers', () => {
expect(coreSnapshotQuery).not.toContain('si.plan_usage > 100')
expect(coreSnapshotQuery).not.toContain('o.has_usage_credits')
})

it.concurrent('snapshots apps with preview QR enabled in the core global stats shard', () => {
const source = readFileSync(new URL('../supabase/functions/_backend/triggers/logsnag_insights.ts', import.meta.url), 'utf8')
const countFn = source.match(/async function countAppsWithPreview[\s\S]*?async function getTrialExtensionStats/)?.[0] ?? ''
const coreShard = source.match(/async function runCoreGlobalStatsShard[\s\S]*?async function getRegistersToday/)?.[0] ?? ''

expect(countFn).toContain('apps.allow_preview = true')
expect(countFn).toContain('apps.created_at <')
expect(countFn).toContain('snapshotEnd')
expect(coreShard).toContain('countAppsWithPreview(c, window.prevDayEnd)')
expect(coreShard).toContain('apps_with_preview,')
})
Comment on lines +683 to +694

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.

🎯 Functional Correctness | 🔵 Trivial | 🏗️ Heavy lift

Test the metric behavior instead of matching source text.

Lines [685-694] only inspect implementation strings. They do not execute countAppsWithPreview, verify the snapshotEnd boundary with data, or confirm the value passed to updateGlobalStatsSnapshot. A broken query that always returns zero can still pass this test. Add a behavioral test with mocked database clients or an existing database-backed fixture.

🤖 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/logsnag-insights-revenue.unit.test.ts` around lines 683 - 694, Replace
the source-string assertions in the “snapshots apps with preview QR enabled”
test with a behavioral test that executes the relevant stats flow using mocked
database clients or an existing database fixture. Seed records covering
preview-enabled apps before and at the snapshot boundary, verify
countAppsWithPreview returns only records before snapshotEnd, and assert
runCoreGlobalStatsShard passes that value as apps_with_preview to
updateGlobalStatsSnapshot.

it.concurrent('normalizes logsnag insights retry payload counts', () => {
expect(logsnagInsightsTestUtils.normalizeLogsnagInsightsRetryCount('2')).toBe(2)
expect(logsnagInsightsTestUtils.normalizeLogsnagInsightsRetryCount(2.8)).toBe(2)
Expand Down
Loading