feat: unify user analytics and optimize overview aggregation

Merge user accounts and usage reporting into one page with a combined ranking and account table, shared precise time ranges, and simpler range labels.

Parse overview metadata once through a schema-only view migration and disable JIT locally for bucket rebuilds. Preserve automatic backfills.

Add redacted OAuth refresh diagnostics, bucket failure context, and regression coverage. Resolve strict Clippy warnings.
This commit is contained in:
elky
2026-10-05 00:28:31 +08:00
parent 2075cd95de
commit cb7b9c9ecd
27 changed files with 1255 additions and 587 deletions
@@ -113,4 +113,13 @@ describe('adminApi analytics cache options', () => {
})
})
it('refreshes both leaderboards for the same precise period as user accounts', async () => {
const range = { from: '2026-09-10T15:20:42Z', to: '2026-09-10T16:20:42Z', timezone: 'Asia/Shanghai' }
await adminApi.getLeaderboardUsers(range, { skipCache: true })
await adminApi.getLeaderboardUserGroups(range, { skipCache: true })
expect(getMock).toHaveBeenCalledWith('/api/admin/stats/leaderboard/users', { params: range })
expect(getMock).toHaveBeenCalledWith('/api/admin/stats/leaderboard/user-groups', { params: range })
for (const call of cachedRequestMock.mock.calls as unknown[][]) expect(call[2]).toBe(0)
})
})
+10 -4
View File
@@ -1288,6 +1288,8 @@ export const adminApi = {
// Stats / Leaderboards
async getLeaderboardUsers(params?: {
from?: string
to?: string
start_date?: string
end_date?: string
preset?: string
@@ -1302,7 +1304,7 @@ export const adminApi = {
include_inactive?: boolean
exclude_admin?: boolean
user_group_id?: string
}): Promise<LeaderboardResponse> {
}, options?: AdminAnalyticsRequestOptions): Promise<LeaderboardResponse> {
const cacheKey = buildCacheKey('admin:stats:leaderboard:users', params)
return cachedRequest(
cacheKey,
@@ -1312,11 +1314,13 @@ export const adminApi = {
})
return response.data
},
20 * 1000
options?.skipCache ? 0 : 20 * 1000
)
},
async getLeaderboardUserGroups(params?: {
from?: string
to?: string
start_date?: string
end_date?: string
preset?: string
@@ -1330,7 +1334,7 @@ export const adminApi = {
model?: string
include_inactive?: boolean
exclude_admin?: boolean
}): Promise<LeaderboardResponse> {
}, options?: AdminAnalyticsRequestOptions): Promise<LeaderboardResponse> {
const cacheKey = buildCacheKey('admin:stats:leaderboard:user-groups', params)
return cachedRequest(
cacheKey,
@@ -1341,7 +1345,7 @@ export const adminApi = {
)
return response.data
},
20 * 1000
options?.skipCache ? 0 : 20 * 1000
)
},
@@ -1621,6 +1625,8 @@ export const adminApi = {
async getTimeSeries(
params?: {
from?: string
to?: string
start_date?: string
end_date?: string
preset?: string