Files
multica/packages/core/dashboard/queries.ts
2026-07-13 13:30:45 +08:00

148 lines
4.2 KiB
TypeScript

import { keepPreviousData, queryOptions } from "@tanstack/react-query";
import { api } from "../api";
export const dashboardKeys = {
all: (wsId: string) => ["dashboard", wsId] as const,
daily: (
wsId: string,
days: number,
projectId: string | null,
tz: string,
) => [...dashboardKeys.all(wsId), "daily", days, projectId, tz] as const,
byAgent: (
wsId: string,
days: number,
projectId: string | null,
tz: string,
) => [...dashboardKeys.all(wsId), "by-agent", days, projectId, tz] as const,
agentRuntime: (
wsId: string,
days: number,
projectId: string | null,
tz: string,
) => [...dashboardKeys.all(wsId), "agent-runtime", days, projectId, tz] as const,
runTimeDaily: (
wsId: string,
days: number,
projectId: string | null,
tz: string,
) => [...dashboardKeys.all(wsId), "runtime-daily", days, projectId, tz] as const,
};
// 5-min rollup cadence on the server, 60s background refetch on the client.
const STALE_TIME = 60 * 1000;
// Range changes should keep the previous result mounted so KPI cards and
// charts transition in place instead of falling back to a full-page skeleton.
// Scope changes are deliberately excluded: carrying data across workspaces,
// projects, report kinds, or timezones would briefly display the wrong data.
function isSameDashboardScope(
previousKey: readonly unknown[] | undefined,
nextKey: readonly unknown[],
): boolean {
if (!previousKey || previousKey.length !== nextKey.length) return false;
return previousKey.every(
(part, index) => index === 3 || Object.is(part, nextKey[index]),
);
}
// `tz` participates in every dashboard key so a Preferences change
// repoints the cache. All four series — token rollups and the
// atq.completed_at-based run-time series — slice their day boundary in
// the viewer's tz, so the four dashboard tabs always agree.
export function dashboardUsageDailyOptions(
wsId: string,
days: number,
projectId: string | null,
tz: string,
) {
const queryKey = dashboardKeys.daily(wsId, days, projectId, tz);
return queryOptions({
queryKey,
queryFn: () =>
api.getDashboardUsageDaily({
days,
project_id: projectId ?? undefined,
tz,
}),
enabled: !!wsId,
staleTime: STALE_TIME,
placeholderData: (previousData, previousQuery) =>
isSameDashboardScope(previousQuery?.queryKey, queryKey)
? keepPreviousData(previousData)
: undefined,
});
}
export function dashboardUsageByAgentOptions(
wsId: string,
days: number,
projectId: string | null,
tz: string,
) {
const queryKey = dashboardKeys.byAgent(wsId, days, projectId, tz);
return queryOptions({
queryKey,
queryFn: () =>
api.getDashboardUsageByAgent({
days,
project_id: projectId ?? undefined,
tz,
}),
enabled: !!wsId,
staleTime: STALE_TIME,
placeholderData: (previousData, previousQuery) =>
isSameDashboardScope(previousQuery?.queryKey, queryKey)
? keepPreviousData(previousData)
: undefined,
});
}
export function dashboardAgentRunTimeOptions(
wsId: string,
days: number,
projectId: string | null,
tz: string,
) {
const queryKey = dashboardKeys.agentRuntime(wsId, days, projectId, tz);
return queryOptions({
queryKey,
queryFn: () =>
api.getDashboardAgentRunTime({
days,
project_id: projectId ?? undefined,
tz,
}),
enabled: !!wsId,
staleTime: STALE_TIME,
placeholderData: (previousData, previousQuery) =>
isSameDashboardScope(previousQuery?.queryKey, queryKey)
? keepPreviousData(previousData)
: undefined,
});
}
export function dashboardRunTimeDailyOptions(
wsId: string,
days: number,
projectId: string | null,
tz: string,
) {
const queryKey = dashboardKeys.runTimeDaily(wsId, days, projectId, tz);
return queryOptions({
queryKey,
queryFn: () =>
api.getDashboardRunTimeDaily({
days,
project_id: projectId ?? undefined,
tz,
}),
enabled: !!wsId,
staleTime: STALE_TIME,
placeholderData: (previousData, previousQuery) =>
isSameDashboardScope(previousQuery?.queryKey, queryKey)
? keepPreviousData(previousData)
: undefined,
});
}