Skip to content

Commit db2770c

Browse files
committed
feat(ops): usage dashboard UI with charts and tables
- Add hourly/daily time buckets and buildUsageTimeSeriesJson - Render /ops/usage with Chart.js (CDN): line + doughnut - Tables: actors by type, by service, search breakdown - GET /ops/usage/api/timeseries for JSON; collapsible raw payload Made-with: Cursor
1 parent 88a1433 commit db2770c

2 files changed

Lines changed: 384 additions & 20 deletions

File tree

src/__tests__/usage-db.test.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { describe, expect, it, beforeEach, afterEach } from "vitest";
22
import { mkdtempSync, rmSync } from "node:fs";
33
import { join } from "node:path";
44
import { tmpdir } from "node:os";
5-
import { buildUsageSummaryJson } from "../usage/dashboard.js";
5+
import { buildUsageSummaryJson, buildUsageTimeSeriesJson } from "../usage/dashboard.js";
66
import { recordUsageEvent, resetUsageDbForTests } from "../usage/db.js";
77

88
describe("usage SQLite", () => {
@@ -61,4 +61,29 @@ describe("usage SQLite", () => {
6161
expect(mcp?.c).toBe(1);
6262
expect(search?.c).toBe(2);
6363
});
64+
65+
it("time series sums to total events in window", () => {
66+
recordUsageEvent({
67+
service: "search",
68+
eventType: "search_query",
69+
actorType: "anonymous_ip",
70+
actorKey: "a",
71+
tier: "free",
72+
});
73+
recordUsageEvent({
74+
service: "landing",
75+
eventType: "pixel",
76+
actorType: "anonymous_ip",
77+
actorKey: "b",
78+
});
79+
80+
const series = buildUsageTimeSeriesJson(24) as {
81+
enabled?: boolean;
82+
points: { t: number; c: number }[];
83+
};
84+
expect(series.enabled).toBe(true);
85+
const sumBuckets = series.points.reduce((acc, p) => acc + p.c, 0);
86+
const summary = buildUsageSummaryJson(24) as { totalEvents: number };
87+
expect(sumBuckets).toBe(summary.totalEvents);
88+
});
6489
});

0 commit comments

Comments
 (0)