From 42e2a9b707bb88937f33f351f4bf9cfcc7e995c7 Mon Sep 17 00:00:00 2001 From: saltbo Date: Wed, 22 Jul 2026 01:18:24 -0400 Subject: [PATCH] fix(stats): show exact partial-day snapshots --- server/adapters/repos/admin-stats.ts | 26 ++++++------------ server/http/admin-stats.integration.test.ts | 29 ++++++++++++++++++--- 2 files changed, 34 insertions(+), 21 deletions(-) diff --git a/server/adapters/repos/admin-stats.ts b/server/adapters/repos/admin-stats.ts index c5b57692..c8364ea6 100644 --- a/server/adapters/repos/admin-stats.ts +++ b/server/adapters/repos/admin-stats.ts @@ -76,7 +76,6 @@ async function getOverviewStatistics( topUsage, storageDataQuality, counterDays, - snapshotDays, ] = await Promise.all([ getUserInventory(reader), getActiveUserSnapshot(reader), @@ -90,7 +89,6 @@ async function getOverviewStatistics( getTopPersonalUsage(db, now), getStorageDataQuality(reader), reader.completeDayKeys('counters'), - reader.completeDayKeys('snapshots'), ]) const dates = createDateBuckets(effective) const activeByDate = new Map(activeByDay.map((row) => [row.date, row])) @@ -115,8 +113,8 @@ async function getOverviewStatistics( }, trend: dates.map((date) => ({ date, - totalUsers: snapshotDays.has(date) ? (totalUsersByDay.get(date) ?? null) : null, - activeUsers: snapshotDays.has(date) ? (activeByDate.get(date)?.mau ?? null) : null, + totalUsers: totalUsersByDay.get(date) ?? null, + activeUsers: activeByDate.get(date)?.mau ?? null, newUsers: counterDays.has(date) ? (newUsersByDay.get(date) ?? 0) : null, })), topUsage: exactUsage ? topUsage : [], @@ -130,7 +128,7 @@ async function getOverviewStatistics( changes?.exact !== false return { date, - usedBytes: exactLedger && snapshotDays.has(date) ? (storageUsedByDay.get(date) ?? null) : null, + usedBytes: exactLedger ? (storageUsedByDay.get(date) ?? null) : null, writtenBytes: exactChanges ? (changes?.writtenBytes ?? 0) : null, releasedBytes: exactChanges ? (changes?.releasedBytes ?? 0) : null, } @@ -274,7 +272,6 @@ async function getDashboardOverviewStats( trafficLedgerComplete, previousTrafficLedgerComplete, counterDays, - snapshotDays, trafficFirstCompleteDay, ] = await Promise.all([ getUserInventory(reader), @@ -295,7 +292,6 @@ async function getDashboardOverviewStats( trafficLedgerCoversRange(db, effective), trafficLedgerCoversRange(db, previous), reader.completeDayKeys('counters'), - reader.completeDayKeys('snapshots'), trafficLedgerFirstCompleteDay(db), ]) const [trendNewUsers, activeByDay, storageUsedByDay, uploadByDay, downloadByDay, missingBytesByDay] = @@ -312,8 +308,8 @@ async function getDashboardOverviewStats( return { date, newUsers: counterDays.has(date) ? (trendNewUsers.get(date) ?? 0) : null, - activeUsers: snapshotDays.has(date) ? (activeByDay.get(date) ?? null) : null, - storageUsedBytes: snapshotDays.has(date) ? (storageUsedByDay.get(date) ?? null) : null, + activeUsers: activeByDay.get(date) ?? null, + storageUsedBytes: storageUsedByDay.get(date) ?? null, uploadBytes: counterDays.has(date) && !missingBytes?.upload ? (uploadByDay.get(date) ?? 0) : null, downloadBytes: counterDays.has(date) && @@ -483,7 +479,6 @@ async function getDashboardGrowthStats( snapshotCoverage, comparisonSnapshotCoverage, counterDays, - snapshotDays, ] = await Promise.all([ getUserInventory(reader), getSignupTotal(reader), @@ -498,13 +493,12 @@ async function getDashboardGrowthStats( reader.coverage('snapshots'), previousReader.coverage('snapshots'), reader.completeDayKeys('counters'), - reader.completeDayKeys('snapshots'), ]) const newUsersByDay = await getSignupsByDay(reader) const userScaleTrend = createDateBuckets(effective).map((date) => ({ date, newUsers: counterDays.has(date) ? (newUsersByDay.get(date) ?? 0) : null, - totalUsers: snapshotDays.has(date) ? (totalsByDay.get(date) ?? null) : null, + totalUsers: totalsByDay.get(date) ?? null, })) const currentCountersComplete = completeCoverage(coverage) const previousCountersComplete = completeCoverage(comparisonCoverage) @@ -530,9 +524,7 @@ async function getDashboardGrowthStats( silentUserRate: nullablePercent(users?.silent ?? null, users?.total ?? null), }, userScaleTrend, - activeUserTrend: activeByDay.map((row) => - snapshotDays.has(row.date) ? row : { ...row, dau: null, wau: null, mau: null }, - ), + activeUserTrend: activeByDay, userStatus: users ? percentRows([ { name: 'normal', value: users.normal }, @@ -578,7 +570,6 @@ async function getDashboardStorageStats( comparisonSnapshotCoverage, missingBytesByDay, counterDays, - snapshotDays, ] = await Promise.all([ getQuotaTotals(reader), getStorageInventory(reader), @@ -603,14 +594,13 @@ async function getDashboardStorageStats( previousReader.coverage('snapshots'), getMissingTransferBytesByDay(reader), reader.completeDayKeys('counters'), - reader.completeDayKeys('snapshots'), ]) const exactUsage = storageDataQuality.usageDriftSpaces === null || storageDataQuality.usageDriftSpaces === 0 const exactLedger = storageDataQuality.ledgerDriftSpaces === null || storageDataQuality.ledgerDriftSpaces === 0 const storageTrend = createDateBuckets(effective).map((date) => { return { date, - usedBytes: exactLedger && snapshotDays.has(date) ? (storageUsedByDay.get(date) ?? null) : null, + usedBytes: exactLedger ? (storageUsedByDay.get(date) ?? null) : null, newBytes: counterDays.has(date) && !missingBytesByDay.get(date)?.upload ? (uploadsByDay.get(date) ?? 0) : null, newFiles: counterDays.has(date) ? (uploadFilesByDay.get(date) ?? 0) : null, } diff --git a/server/http/admin-stats.integration.test.ts b/server/http/admin-stats.integration.test.ts index 9bf6e361..454e63dc 100644 --- a/server/http/admin-stats.integration.test.ts +++ b/server/http/admin-stats.integration.test.ts @@ -252,6 +252,18 @@ describe('site stats routes', () => { ('partial-upload', ${at}, '', 'transfer.upload', '', '', 1, 128, 0, '{"version":3,"scope":"counters","quality":"exact"}', ${at + 3_600_000}), ('partial-share-save', ${at}, '', 'share.saved', '', '', 1, 0, 0, + '{"version":3,"scope":"counters","quality":"exact"}', ${at + 3_600_000}), + ('partial-user-inventory', ${at}, '', 'user.inventory', '', '', 42, 0, 0, + '{"version":3,"scope":"snapshots","quality":"exact"}', ${at + 3_600_000}), + ('partial-active-base', ${at}, '', 'user.active_snapshot', '', '', 7, 0, 7, + '{"version":3,"scope":"snapshots","quality":"exact"}', ${at + 3_600_000}), + ('partial-active-dau', ${at}, '', 'user.active_snapshot', 'window', 'dau', 3, 0, 3, + '{"version":3,"scope":"snapshots","quality":"exact"}', ${at + 3_600_000}), + ('partial-active-wau', ${at}, '', 'user.active_snapshot', 'window', 'wau', 5, 0, 5, + '{"version":3,"scope":"snapshots","quality":"exact"}', ${at + 3_600_000}), + ('partial-active-mau', ${at}, '', 'user.active_snapshot', 'window', 'mau', 7, 0, 7, + '{"version":3,"scope":"snapshots","quality":"exact"}', ${at + 3_600_000}), + ('partial-storage-balance', ${at}, '', 'storage.ledger_balance', '', '', 0, 4096, 0, '{"version":3,"scope":"counters","quality":"exact"}', ${at + 3_600_000}) `) const query = 'from=2026-07-01T10%3A00%3A00.000Z&to=2026-07-01T11%3A59%3A59.999Z&timeZone=UTC' @@ -262,10 +274,16 @@ describe('site stats routes', () => { app.request(`/api/site/stats/traffic?${query}`, { headers }), app.request(`/api/site/stats/sharing?${query}`, { headers }), ]) + const overview = await createAdminStatsRepo(db).getOverviewStatistics(new Date(at + 3 * 3_600_000), { + from: new Date(at), + to: new Date(at + 2 * 3_600_000 - 1), + timeZone: 'UTC', + }) const growth = (await growthRes.json()) as { coverage: { status: string } summary: { newUsers: { value: number | null } } - userScaleTrend: Array<{ newUsers: number | null }> + userScaleTrend: Array<{ newUsers: number | null; totalUsers: number | null }> + activeUserTrend: Array<{ dau: number | null; wau: number | null; mau: number | null }> registrationSources: unknown[] } const storage = (await storageRes.json()) as { @@ -285,11 +303,16 @@ describe('site stats routes', () => { expect([growthRes.status, storageRes.status, trafficRes.status, sharingRes.status]).toEqual([200, 200, 200, 200]) expect(growth.coverage.status).toBe('partial') expect(growth.summary.newUsers.value).toBeNull() - expect(growth.userScaleTrend).toEqual([{ date: '2026-07-01', newUsers: null, totalUsers: null }]) + expect(growth.userScaleTrend).toEqual([{ date: '2026-07-01', newUsers: null, totalUsers: 42 }]) + expect(growth.activeUserTrend).toEqual([{ date: '2026-07-01', dau: 3, wau: 5, mau: 7 }]) expect(growth.registrationSources).toEqual([]) + expect(overview.users.trend).toEqual([{ date: '2026-07-01', totalUsers: 42, activeUsers: 7, newUsers: null }]) + expect(overview.storageTrend).toEqual([ + { date: '2026-07-01', usedBytes: 4096, writtenBytes: null, releasedBytes: null }, + ]) expect(storage.summary.newBytes.value).toBeNull() expect(storage.summary.newFiles.value).toBeNull() - expect(storage.storageTrend).toEqual([{ date: '2026-07-01', usedBytes: null, newBytes: null, newFiles: null }]) + expect(storage.storageTrend).toEqual([{ date: '2026-07-01', usedBytes: 4096, newBytes: null, newFiles: null }]) expect(traffic.summary.totalBytes.value).toBeNull() expect(traffic.summary.requestCount.value).toBeNull() expect(traffic.trafficTrend).toEqual([