31d4ba67fa
* perf(stats): split stats endpoint per-section and rewrite GetGlobalStats Stats page used to wait on a single endpoint that ran 7 SQL queries in parallel and rendered nothing until the slowest one returned. Cold first-paint was the bottleneck users actually hit. - Backend: split /api/v1/workspaces/:id/stats into 7 per-section endpoints (global, tasks, daily-activity, completed-activity, agent-usage, repositories, git). Each handler runs one repo call and returns its DTO. - Backend: rewrote GetGlobalStats from 9 independent correlated subqueries into 4 CTEs (task / session / turn / message). Each underlying table is now scanned at most once per request instead of 1-3 times. - Frontend: replaced the monolithic fetchStats + single PanelState with useStatsSections hook (7 parallel fetches, per-section SectionStatus). Each panel renders skeleton -> data -> error independently; light queries (overview, agent usage, git) paint immediately, heavy ones stream in. - Tests: added TestGetGlobalStats_AggregatesAcrossTables to lock the CTE-based query against regressions for turns, user/tool messages, and total duration. * fix(stats): address PR review feedback - Restore cache: "no-store" on section fetches; Gin emits no Cache-Control headers so the browser could otherwise serve stale stats on revisit (Greptile P1, Cubic). - Stop blanking the Overview panel when /git fails: render OverviewCards as soon as `global` is ready and treat `git` as optional, falling back to the Averages card. Updates GitOrAveragesCard / OverviewCards types (Claude). - Fix TaskStatsHasMore off-by-one at exactly taskStatsLimit rows: probe taskStatsLimit+1, slice back to the page size, only flag hasMore on a confirmed extra row (Claude, Cubic). - Rename anyError → firstError with a comment documenting the deterministic insertion-order iteration; clearer signal that callers should use it as "any section failed?" and surface per-section errors in their own panels (Claude). --------- Co-authored-by: Kandev Agent <agent@kandev.dev>
85 lines
2.1 KiB
TypeScript
85 lines
2.1 KiB
TypeScript
import { fetchJson, type ApiRequestOptions } from "../client";
|
|
import type {
|
|
AgentUsageDTO,
|
|
CompletedTaskActivityDTO,
|
|
DailyActivityDTO,
|
|
GitStatsDTO,
|
|
GlobalStatsDTO,
|
|
RepositoryStatsDTO,
|
|
TaskStatsDTO,
|
|
} from "@/lib/types/http";
|
|
|
|
export type StatsRange = "week" | "month" | "all";
|
|
|
|
export type TaskStatsResponse = {
|
|
task_stats: TaskStatsDTO[];
|
|
task_stats_has_more: boolean;
|
|
};
|
|
|
|
function rangeQuery(range?: StatsRange): string {
|
|
return range ? `?range=${encodeURIComponent(range)}` : "";
|
|
}
|
|
|
|
function statsUrl(workspaceId: string, section: string, range?: StatsRange): string {
|
|
return `/api/v1/workspaces/${workspaceId}/stats/${section}${rangeQuery(range)}`;
|
|
}
|
|
|
|
export function fetchGlobalStats(
|
|
workspaceId: string,
|
|
options?: ApiRequestOptions,
|
|
range?: StatsRange,
|
|
) {
|
|
return fetchJson<GlobalStatsDTO>(statsUrl(workspaceId, "global", range), options);
|
|
}
|
|
|
|
export function fetchTaskStats(
|
|
workspaceId: string,
|
|
options?: ApiRequestOptions,
|
|
range?: StatsRange,
|
|
) {
|
|
return fetchJson<TaskStatsResponse>(statsUrl(workspaceId, "tasks", range), options);
|
|
}
|
|
|
|
export function fetchDailyActivity(
|
|
workspaceId: string,
|
|
options?: ApiRequestOptions,
|
|
range?: StatsRange,
|
|
) {
|
|
return fetchJson<DailyActivityDTO[]>(statsUrl(workspaceId, "daily-activity", range), options);
|
|
}
|
|
|
|
export function fetchCompletedActivity(
|
|
workspaceId: string,
|
|
options?: ApiRequestOptions,
|
|
range?: StatsRange,
|
|
) {
|
|
return fetchJson<CompletedTaskActivityDTO[]>(
|
|
statsUrl(workspaceId, "completed-activity", range),
|
|
options,
|
|
);
|
|
}
|
|
|
|
export function fetchAgentUsage(
|
|
workspaceId: string,
|
|
options?: ApiRequestOptions,
|
|
range?: StatsRange,
|
|
) {
|
|
return fetchJson<AgentUsageDTO[]>(statsUrl(workspaceId, "agent-usage", range), options);
|
|
}
|
|
|
|
export function fetchRepositoryStats(
|
|
workspaceId: string,
|
|
options?: ApiRequestOptions,
|
|
range?: StatsRange,
|
|
) {
|
|
return fetchJson<RepositoryStatsDTO[]>(statsUrl(workspaceId, "repositories", range), options);
|
|
}
|
|
|
|
export function fetchGitStats(
|
|
workspaceId: string,
|
|
options?: ApiRequestOptions,
|
|
range?: StatsRange,
|
|
) {
|
|
return fetchJson<GitStatsDTO>(statsUrl(workspaceId, "git", range), options);
|
|
}
|