mirror of
https://github.com/saltbo/zpan.git
synced 2026-09-19 01:51:11 +08:00
fix(admin): count recent users from live data
This commit is contained in:
@@ -310,6 +310,29 @@ describe('admin hourly stats rollup', () => {
|
||||
).rejects.toThrow('stats_bucket_must_align_to_utc_hour')
|
||||
})
|
||||
|
||||
it('counts recent registered users live even when signup rollups are unavailable', async () => {
|
||||
const { app, db } = await createTestApp()
|
||||
await adminHeaders(app)
|
||||
const now = new Date('2026-07-20T18:30:00.000Z')
|
||||
const recentCreatedAt = new Date('2026-07-15T09:00:00.000Z').getTime()
|
||||
const oldCreatedAt = new Date('2026-07-13T23:59:59.999Z').getTime()
|
||||
const [{ id: seededUserId }] = await db.all<{ id: string }>(sql`SELECT id FROM user LIMIT 1`)
|
||||
await db.run(sql`UPDATE user SET created_at = ${oldCreatedAt}`)
|
||||
await db.run(sql`UPDATE user SET created_at = ${recentCreatedAt} WHERE id = ${seededUserId}`)
|
||||
await db.run(sql`
|
||||
INSERT INTO user (id, name, email, email_verified, created_at, updated_at)
|
||||
VALUES ('old-overview-user', 'Old User', 'old-overview@example.com', 1, ${oldCreatedAt}, ${oldCreatedAt})
|
||||
`)
|
||||
|
||||
const overview = await createAdminStatsRepo(db).getOverviewStatistics(now, {
|
||||
from: new Date('2026-06-21T00:00:00.000Z'),
|
||||
to: new Date('2026-07-20T17:59:59.999Z'),
|
||||
timeZone: 'UTC',
|
||||
})
|
||||
|
||||
expect(overview.users.new7Days).toBe(1)
|
||||
})
|
||||
|
||||
it('rolls exact storage writes and releases into the overview trend', async () => {
|
||||
const { app, db } = await createTestApp()
|
||||
await adminHeaders(app)
|
||||
|
||||
@@ -13,7 +13,7 @@ import type {
|
||||
AdminTopShare,
|
||||
AdminTransferDataQuality,
|
||||
} from '@shared/types'
|
||||
import { and, eq, gte, inArray, lte } from 'drizzle-orm'
|
||||
import { and, eq, gte, inArray, lte, sql } from 'drizzle-orm'
|
||||
import { member, organization, user } from '../../db/auth-schema'
|
||||
import { matters, shares, statsRollupsHourly } from '../../db/schema'
|
||||
import {
|
||||
@@ -55,6 +55,7 @@ async function getOverviewStatistics(
|
||||
const [
|
||||
inventory,
|
||||
active,
|
||||
recentRegisteredUsers,
|
||||
newUsersByDay,
|
||||
totalUsersByDay,
|
||||
activeByDay,
|
||||
@@ -66,6 +67,7 @@ async function getOverviewStatistics(
|
||||
] = await Promise.all([
|
||||
getUserInventory(reader),
|
||||
getActiveUserSnapshot(reader),
|
||||
getRecentRegisteredUserCount(db, now),
|
||||
getSignupsByDay(reader),
|
||||
getUserTotalsByDay(reader),
|
||||
getRollingActiveUserTrend(reader, effective),
|
||||
@@ -80,7 +82,6 @@ async function getOverviewStatistics(
|
||||
const dau = active?.dau ?? null
|
||||
const wau = active?.wau ?? null
|
||||
const mau = active?.mau ?? null
|
||||
const recentSignups = dates.slice(-7).map((date) => (newUsersByDay.has(date) ? (newUsersByDay.get(date) ?? null) : 0))
|
||||
const exactUsage = storageDataQuality.usageDriftSpaces === null || storageDataQuality.usageDriftSpaces === 0
|
||||
const exactLedger = storageDataQuality.ledgerDriftSpaces === null || storageDataQuality.ledgerDriftSpaces === 0
|
||||
const storageChangesExactFrom = fullStorageChangeDayFrom(storageLedgerOpening)
|
||||
@@ -89,9 +90,7 @@ async function getOverviewStatistics(
|
||||
users: {
|
||||
total: inventory?.total ?? null,
|
||||
active30Days: mau,
|
||||
new7Days: recentSignups.some((value) => value === null)
|
||||
? null
|
||||
: recentSignups.reduce<number>((total, value) => total + (value ?? 0), 0),
|
||||
new7Days: recentRegisteredUsers,
|
||||
activity: {
|
||||
today: dau,
|
||||
last7Days: dau === null || wau === null ? null : Math.max(0, wau - dau),
|
||||
@@ -120,6 +119,15 @@ async function getOverviewStatistics(
|
||||
}
|
||||
}
|
||||
|
||||
async function getRecentRegisteredUserCount(db: Database, now: Date): Promise<number> {
|
||||
const from = utcDateStart(addCalendarDays(dayKey(now), -6))
|
||||
const [row] = await db
|
||||
.select({ count: sql<number>`COUNT(*)` })
|
||||
.from(user)
|
||||
.where(and(gte(user.createdAt, from), lte(user.createdAt, now)))
|
||||
return Number(row.count)
|
||||
}
|
||||
|
||||
async function getTopPersonalUsage(
|
||||
db: Database,
|
||||
reader: AdminStatsHourlyReader,
|
||||
|
||||
Reference in New Issue
Block a user