Skip to content
Open
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
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions messages/en.context.json
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@
"admin-app-build-onboarding-total-apps-created": "Used in platform admin dashboard. Role: UI label about \"admin app build onboarding total apps created\". Translate for UI; keep Capgo product names, code, and placeholders unchanged.",
"admin-apps-created-by-day": "Used in Capgo web console areas: pages/admin/dashboard. Role: UI label. Translate for UI; keep Capgo product names, code, and placeholders unchanged.",
"admin-apps-created-by-day-series": "Used in Capgo web console areas: pages/admin/dashboard. Role: UI label. Translate for UI; keep Capgo product names, code, and placeholders unchanged.",
"admin-apps-with-store-url-series": "Used in Capgo web console areas: pages/admin/dashboard. Role: UI label. Translate for UI; keep Capgo product names, code, and placeholders unchanged.",
"admin-apps-with-preview": "Used in Capgo web console areas: pages/admin/dashboard. Role: UI label. Translate for UI; keep Capgo product names, code, and placeholders unchanged.",
"admin-apps-with-preview-series": "Used in Capgo web console areas: pages/admin/dashboard. Role: UI label. Translate for UI; keep Capgo product names, code, and placeholders unchanged.",
"admin-credits": "Used in Capgo web console areas: pages/admin/dashboard. Role: UI label. Translate for UI; keep Capgo product names, code, and placeholders unchanged.",
Expand Down
1 change: 1 addition & 0 deletions messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@
"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-store-url-series": "Apps with store link",
"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",
Expand Down
9 changes: 9 additions & 0 deletions src/pages/admin/dashboard/users.vue
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ const globalStatsTrendData = ref<Array<{
versions_created: number
demo_apps_created: number
apps_with_preview: number
apps_with_store_url: number
devices_last_month: number
trial_extended_orgs: number
trial_extended_subscribed_orgs: number
Expand Down Expand Up @@ -707,6 +708,14 @@ const appsCreatedTrendSeries = computed(() => {
})),
color: '#2563eb',
},
{
label: t('admin-apps-with-store-url-series'),
data: globalStatsTrendData.value.map(item => ({
date: item.date,
value: item.apps_with_store_url ?? 0,
})),
color: '#f59e0b',
},
]
})

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 @@ -1613,6 +1613,7 @@ export type Database = {
apps_with_cli_onboarding_builds_24h: number
apps_with_manual_builds_24h: number
apps_with_preview: number
apps_with_store_url: number
average_ltv: number
build_avg_seconds_day_android: number
build_avg_seconds_day_ios: number
Expand Down Expand Up @@ -1724,6 +1725,7 @@ export type Database = {
apps_with_cli_onboarding_builds_24h?: number
apps_with_manual_builds_24h?: number
apps_with_preview?: number
apps_with_store_url?: number
average_ltv?: number
build_avg_seconds_day_android?: number
build_avg_seconds_day_ios?: number
Expand Down Expand Up @@ -1835,6 +1837,7 @@ export type Database = {
apps_with_cli_onboarding_builds_24h?: number
apps_with_manual_builds_24h?: number
apps_with_preview?: number
apps_with_store_url?: number
average_ltv?: number
build_avg_seconds_day_android?: number
build_avg_seconds_day_ios?: number
Expand Down
3 changes: 3 additions & 0 deletions supabase/functions/_backend/plugin_runtime/utils/pg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1911,6 +1911,7 @@ export interface AdminGlobalStatsTrend {
app_build_onboarding_finalized: boolean
apps_active: number
apps_with_preview: number
apps_with_store_url: number
users: number
users_active: number
paying: number
Expand Down Expand Up @@ -2039,6 +2040,7 @@ export async function getAdminGlobalStatsTrend(
(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,
COALESCE(NULLIF(to_jsonb(gs) ->> 'apps_with_store_url', '')::int, 0)::int AS apps_with_store_url,
gs.users::int AS users,
gs.users_active::int AS users_active,
gs.paying::int AS paying,
Expand Down Expand Up @@ -2196,6 +2198,7 @@ export async function getAdminGlobalStatsTrend(
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,
apps_with_store_url: Number(row.apps_with_store_url) || 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 @@ -1545,6 +1545,7 @@ export type Database = {
apps_with_cli_onboarding_builds_24h: number
apps_with_manual_builds_24h: number
apps_with_preview: number
apps_with_store_url: number
average_ltv: number
build_avg_seconds_day_android: number
build_avg_seconds_day_ios: number
Expand Down Expand Up @@ -1646,6 +1647,7 @@ export type Database = {
apps_with_cli_onboarding_builds_24h?: number
apps_with_manual_builds_24h?: number
apps_with_preview?: number
apps_with_store_url?: number
average_ltv?: number
build_avg_seconds_day_android?: number
build_avg_seconds_day_ios?: number
Expand Down Expand Up @@ -1747,6 +1749,7 @@ export type Database = {
apps_with_cli_onboarding_builds_24h?: number
apps_with_manual_builds_24h?: number
apps_with_preview?: number
apps_with_store_url?: number
average_ltv?: number
build_avg_seconds_day_android?: number
build_avg_seconds_day_ios?: number
Expand Down
54 changes: 49 additions & 5 deletions supabase/functions/_backend/triggers/logsnag_insights.ts
Original file line number Diff line number Diff line change
Expand Up @@ -452,11 +452,13 @@
orgs?: number
// Present in repo migrations before prod deploy; keep writable while generated types lag.
apps_with_preview?: number
apps_with_store_url?: number
plan_credits?: number
}
type GlobalStatsSnapshotRow = GlobalStatsRow & {
orgs?: number | null
apps_with_preview?: number | null
apps_with_store_url?: number | null
plan_credits?: number | null
}

Expand Down Expand Up @@ -940,6 +942,10 @@
return isMissingSchemaColumnError(error, ['apps_with_preview'])
}

function isMissingAppsWithStoreUrlColumnError(error: unknown): boolean {
return isMissingSchemaColumnError(error, ['apps_with_store_url'])
}

async function calculateRevenue(c: Context, referenceDate?: Date): Promise<PlanRevenue> {
const pgClient = getPgClient(c, false)
const drizzleClient = getDrizzleClient(pgClient)
Expand Down Expand Up @@ -1577,6 +1583,32 @@
}
}

async function countAppsWithStoreUrl(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.created_at < ${snapshotEnd}
AND (
apps.ios_store_url ~ '[^[:space:]]'
OR apps.android_store_url ~ '[^[:space:]]'
)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
`)

return Number(result.rows[0]?.count) || 0
}
catch (error) {
cloudlogErr({ requestId: c.get('requestId'), message: 'countAppsWithStoreUrl error', error })
return 0
}
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 @@ -1652,30 +1684,39 @@
}
}

async function updateGlobalStatsSnapshot(c: Context, dateId: string, patch: GlobalStatsSnapshotPatch): Promise<void> {

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

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this function to reduce its Cognitive Complexity from 18 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=Cap-go_capgo&issues=AaAU36V1mNuBD7IhhqUK&open=AaAU36V1mNuBD7IhhqUK&pullRequest=3116
await ensureGlobalStatsSnapshotRow(c, dateId)

const { orgs, apps_with_preview, ...globalStatsPatch } = patch
const { orgs, apps_with_preview, apps_with_store_url, ...globalStatsPatch } = patch
const updatePayload = {
...globalStatsPatch,
...(apps_with_preview === undefined ? {} : { apps_with_preview }),
...(apps_with_store_url === undefined ? {} : { apps_with_store_url }),
} as GlobalStatsUpdate
const { error } = await supabaseAdmin(c)
.from('global_stats')
.update(updatePayload)
.eq('date_id', dateId)

if (error) {
if (apps_with_preview !== undefined && isMissingAppsWithPreviewColumnError(error)) {
const missingPreview = apps_with_preview !== undefined && isMissingAppsWithPreviewColumnError(error)
const missingStoreUrl = apps_with_store_url !== undefined && isMissingAppsWithStoreUrlColumnError(error)
if (missingPreview || missingStoreUrl) {
cloudlog({
requestId: c.get('requestId'),
message: 'global_stats.apps_with_preview missing; retrying snapshot update without it',
message: 'global_stats optional app metric column missing; retrying snapshot update without it',
dateId,
missingPreview,
missingStoreUrl,
error,
})
const { error: legacyError } = await supabaseAdmin(c)
.from('global_stats')
.update(globalStatsPatch as GlobalStatsUpdate)
.update({
...globalStatsPatch,
...(missingPreview || apps_with_preview === undefined ? {} : { apps_with_preview }),
...(missingStoreUrl || apps_with_store_url === undefined ? {} : { apps_with_store_url }),
} as GlobalStatsUpdate)
.eq('date_id', dateId)
if (legacyError)
throw legacyError
Expand Down Expand Up @@ -2737,6 +2778,7 @@
const [
apps,
apps_with_preview,
apps_with_store_url,
updates,
updates_external,
users,
Expand All @@ -2749,6 +2791,7 @@
] = await Promise.all([
countAllApps(c, window.prevDayEnd),
countAppsWithPreview(c, window.prevDayEnd),
countAppsWithStoreUrl(c, window.prevDayEnd),
countAllUpdates(c, window.prevDayEnd),
countAllUpdatesExternal(c, window.prevDayEnd),
countRegisteredUsersForSnapshot(c, window.prevDayEnd),
Expand Down Expand Up @@ -2790,6 +2833,7 @@
apps,
apps_active: actives.apps,
apps_with_preview,
apps_with_store_url,
above_plan_with_credits,
above_plan_without_credits,
need_upgrade,
Expand Down Expand Up @@ -2818,7 +2862,7 @@
users_active: actives.users,
})

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

async function getRegistersToday(c: Context, createdAfterIso: string, createdBeforeIso: string): Promise<number> {
Expand Down
3 changes: 3 additions & 0 deletions supabase/functions/_backend/utils/pg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1540,6 +1540,7 @@ export interface AdminGlobalStatsTrend {
app_build_onboarding_finalized: boolean
apps_active: number
apps_with_preview: number
apps_with_store_url: number
users: number
users_active: number
paying: number
Expand Down Expand Up @@ -1679,6 +1680,7 @@ export async function getAdminGlobalStatsTrend(
(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,
COALESCE(NULLIF(to_jsonb(gs) ->> 'apps_with_store_url', '')::int, 0)::int AS apps_with_store_url,
gs.users::int AS users,
gs.users_active::int AS users_active,
gs.paying::int AS paying,
Expand Down Expand Up @@ -1847,6 +1849,7 @@ export async function getAdminGlobalStatsTrend(
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,
apps_with_store_url: Number(row.apps_with_store_url) || 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 @@ -1613,6 +1613,7 @@ export type Database = {
apps_with_cli_onboarding_builds_24h: number
apps_with_manual_builds_24h: number
apps_with_preview: number
apps_with_store_url: number
average_ltv: number
build_avg_seconds_day_android: number
build_avg_seconds_day_ios: number
Expand Down Expand Up @@ -1724,6 +1725,7 @@ export type Database = {
apps_with_cli_onboarding_builds_24h?: number
apps_with_manual_builds_24h?: number
apps_with_preview?: number
apps_with_store_url?: number
average_ltv?: number
build_avg_seconds_day_android?: number
build_avg_seconds_day_ios?: number
Expand Down Expand Up @@ -1835,6 +1837,7 @@ export type Database = {
apps_with_cli_onboarding_builds_24h?: number
apps_with_manual_builds_24h?: number
apps_with_preview?: number
apps_with_store_url?: 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
Comment thread
riderx marked this conversation as resolved.
ADD COLUMN IF NOT EXISTS apps_with_store_url bigint NOT NULL DEFAULT 0;

COMMENT ON COLUMN public.global_stats.apps_with_store_url
IS 'Number of apps with at least one App Store or Google Play link at snapshot day end.';
5 changes: 5 additions & 0 deletions tests/admin-stats.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ beforeAll(async () => {
date_id: GLOBAL_STATS_TREND_DATES[0],
apps: 10,
apps_created: 2,
apps_with_store_url: 4,
versions_created: 5,
apps_with_cli_onboarding_builds_24h: 1,
apps_with_manual_builds_24h: 0,
Expand Down Expand Up @@ -211,6 +212,7 @@ beforeAll(async () => {
date_id: GLOBAL_STATS_TREND_DATES[1],
apps: 11,
apps_created: 3,
apps_with_store_url: 6,
versions_created: 8,
apps_with_cli_onboarding_builds_24h: 2,
apps_with_manual_builds_24h: 1,
Expand Down Expand Up @@ -256,6 +258,7 @@ beforeAll(async () => {
date_id: GLOBAL_STATS_TREND_DATES[2],
apps: 12,
apps_created: 0,
apps_with_store_url: 0,
versions_created: 0,
apps_with_cli_onboarding_builds_24h: 0,
apps_with_manual_builds_24h: 0,
Expand Down Expand Up @@ -1005,6 +1008,7 @@ describe('/private/admin_stats', () => {
date: string
apps: number
apps_created: number
apps_with_store_url: number
versions_created: number
apps_with_cli_onboarding_builds_24h: number
apps_with_manual_builds_24h: number
Expand Down Expand Up @@ -1034,6 +1038,7 @@ describe('/private/admin_stats', () => {
expect(latest).toBeTruthy()
expect(latest?.apps).toBe(11)
expect(latest?.apps_created).toBe(3)
expect(latest?.apps_with_store_url).toBe(6)
expect(latest?.versions_created).toBe(8)
expect(latest?.apps_with_cli_onboarding_builds_24h).toBe(2)
expect(latest?.app_build_onboarding_finalized).toBe(true)
Expand Down
16 changes: 16 additions & 0 deletions tests/logsnag-insights-revenue.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -804,6 +804,22 @@ describe('logsnag revenue metric helpers', () => {
expect(source).toContain('apps_with_preview?: number')
expect(source).toContain('isMissingAppsWithPreviewColumnError')
})

it.concurrent('snapshots apps with at least one store URL 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 countAppsWithStoreUrl[\s\S]*?async function getTrialExtensionStats/)?.[0] ?? ''
const coreShard = source.match(/async function runCoreGlobalStatsShard[\s\S]*?async function getRegistersToday/)?.[0] ?? ''

expect(countFn).toContain('apps.ios_store_url')
expect(countFn).toContain('apps.android_store_url')
expect(countFn).toContain('[^[:space:]]')
expect(countFn).toContain('apps.created_at <')
expect(countFn).toContain('snapshotEnd')
expect(coreShard).toContain('countAppsWithStoreUrl(c, window.prevDayEnd)')
expect(coreShard).toContain('apps_with_store_url,')
expect(source).toContain('apps_with_store_url?: number')
expect(source).toContain('isMissingAppsWithStoreUrlColumnError')
})
it.concurrent('normalizes logsnag insights retry payload counts', () => {
expect(logsnagInsightsTestUtils.normalizeLogsnagInsightsRetryCount('2')).toBe(2)
expect(logsnagInsightsTestUtils.normalizeLogsnagInsightsRetryCount(2.8)).toBe(2)
Expand Down
Loading