diff --git a/apps/web/src/lib/server/datasources/dashboard-datasources.test.ts b/apps/web/src/lib/server/datasources/dashboard-datasources.test.ts index d24b48b..61acdd3 100644 --- a/apps/web/src/lib/server/datasources/dashboard-datasources.test.ts +++ b/apps/web/src/lib/server/datasources/dashboard-datasources.test.ts @@ -3,7 +3,7 @@ import { DASHBOARD_SCHEMA_VERSION, type DashboardDocument, } from "@dimensionlab/dashboard-model"; -import { resolveDashboardDatasources, resolveDashboardTile } from "."; +import { resolveDashboardDatasources } from "."; describe("dashboard datasource resolution", () => { test("hydrates telemetry, service health, weather, and summary data from live adapters", async () => { @@ -124,100 +124,6 @@ describe("dashboard datasource resolution", () => { expect(resolved).not.toBe(testDocument); expect(testDocument.telemetry[0].value.value).toBe(1); }); - - test("shares service health snapshots across aggregate tile hydration", async () => { - let resolveFetch: ((response: Response) => void) | undefined; - const fetch = vi.fn((input: RequestInfo | URL) => { - const url = String(input); - if (url !== "https://service.example/health") { - throw new Error(`Unhandled test request: ${url}`); - } - - return new Promise((resolve) => { - resolveFetch = resolve; - }); - }); - const document = testDashboard(); - - const moduleTile = resolveDashboardTile( - document, - { kind: "module", id: "runtime-health-summary" }, - { fetch }, - ); - const statusTile = resolveDashboardTile( - document, - { kind: "status", stripId: "footer", id: "system-status" }, - { fetch }, - ); - - await Promise.resolve(); - expect(fetch).toHaveBeenCalledTimes(1); - - resolveFetch?.(jsonResponse({ status: "UP", ping: 42 })); - expect(await moduleTile).toMatchObject({ - state: "ready", - item: { - id: "runtime-health-summary", - severity: "ok", - value: "all systems operational", - }, - }); - expect(await statusTile).toMatchObject({ - state: "ready", - item: { - id: "system-status", - severity: "ok", - value: "All systems operational", - }, - }); - }); - - test("isolates service health snapshots by datasource fetch context", async () => { - const firstFetch = vi.fn(async () => - jsonResponse({ - status: "UP", - ping: 42, - }) - ); - const secondFetch = vi.fn(async () => - jsonResponse({ - status: "DOWN", - ping: 0, - }) - ); - const document = testDashboard(); - document.serviceGroups[0].services[0] = { - ...document.serviceGroups[0].services[0], - id: "api-isolated", - datasource: { - type: "external", - adapter: "http-status", - reference: "GET https://service.example/isolated", - }, - }; - - await resolveDashboardTile( - document, - { kind: "status", stripId: "footer", id: "system-status" }, - { fetch: firstFetch }, - ); - const second = await resolveDashboardTile( - document, - { kind: "status", stripId: "footer", id: "system-status" }, - { fetch: secondFetch }, - ); - - expect(firstFetch).toHaveBeenCalledTimes(1); - expect(secondFetch).toHaveBeenCalledTimes(1); - expect(second).toMatchObject({ - state: "ready", - item: { - id: "system-status", - severity: "danger", - value: "1 service down", - }, - }); - }); }); const testDocument = testDashboard(); diff --git a/apps/web/src/lib/server/datasources/index.ts b/apps/web/src/lib/server/datasources/index.ts index 62df13a..ee24bcd 100644 --- a/apps/web/src/lib/server/datasources/index.ts +++ b/apps/web/src/lib/server/datasources/index.ts @@ -41,7 +41,6 @@ export type DashboardTileResolution = export interface DatasourceResolutionOptions { fetch?: DatasourceFetch; - now?: () => number; prometheusBaseUrl?: string; prometheusRangeSeconds?: number; prometheusStepSeconds?: number; @@ -114,7 +113,9 @@ export async function resolveDashboardTile( const item = module.id === "runtime-health-summary" ? runtimeHealthSummary( module, - await serviceGroupsSnapshot(document, context), + await Promise.all( + document.serviceGroups.map((group) => resolveServiceGroup(group, context)), + ), ) : await resolveModule(module, context); @@ -131,7 +132,7 @@ export async function resolveDashboardTile( item: await resolveStatusTile( statusItem, document.metadata.refreshIntervalSeconds, - document, + document.serviceGroups, context, ), }; @@ -139,27 +140,14 @@ export async function resolveDashboardTile( interface DatasourceContext { fetch: DatasourceFetch; - fetchIdentity: number; - now: () => number; prometheusBaseUrl: string; prometheusRangeSeconds: number; prometheusStepSeconds: number; requestTimeoutMs: number; - serviceGroupsSnapshot?: Promise; } type DatasourceFetch = (input: string, init?: RequestInit) => Promise; -interface ServiceGroupsSnapshotEntry { - expiresAt: number; - snapshot: Promise; -} - -const serviceGroupsSnapshotTtlMs = 30_000; -const serviceGroupsSnapshotCache = new Map(); -const datasourceFetchIdentities = new WeakMap(); -let nextDatasourceFetchIdentity = 1; - interface PrometheusVectorResult { metric?: Record; value?: [number, string]; @@ -171,12 +159,8 @@ interface PrometheusMatrixResult { } function datasourceContext(options: DatasourceResolutionOptions): DatasourceContext { - const fetch = options.fetch || globalThis.fetch; - return { - fetch, - fetchIdentity: datasourceFetchIdentity(fetch), - now: options.now || Date.now, + fetch: options.fetch || globalThis.fetch, prometheusBaseUrl: options.prometheusBaseUrl || process.env.PROMETHEUS_BASE_URL || @@ -187,68 +171,6 @@ function datasourceContext(options: DatasourceResolutionOptions): DatasourceCont }; } -function datasourceFetchIdentity(fetch: DatasourceFetch): number { - const existing = datasourceFetchIdentities.get(fetch); - if (existing) return existing; - - const next = nextDatasourceFetchIdentity; - nextDatasourceFetchIdentity += 1; - datasourceFetchIdentities.set(fetch, next); - return next; -} - -function serviceGroupsSnapshot( - document: DashboardDocument, - context: DatasourceContext, -): Promise { - if (context.serviceGroupsSnapshot) return context.serviceGroupsSnapshot; - - const key = serviceGroupsSnapshotKey(document, context); - const now = context.now(); - const cached = serviceGroupsSnapshotCache.get(key); - if (cached && cached.expiresAt > now) { - context.serviceGroupsSnapshot = cached.snapshot; - return cached.snapshot; - } - - const snapshot = Promise.all( - document.serviceGroups.map((group) => resolveServiceGroup(group, context)), - ); - context.serviceGroupsSnapshot = snapshot; - serviceGroupsSnapshotCache.set(key, { - expiresAt: now + serviceGroupsSnapshotTtlMs, - snapshot, - }); - snapshot.catch(() => { - if (serviceGroupsSnapshotCache.get(key)?.snapshot === snapshot) { - serviceGroupsSnapshotCache.delete(key); - } - }); - return snapshot; -} - -function serviceGroupsSnapshotKey( - document: DashboardDocument, - context: DatasourceContext, -): string { - return JSON.stringify( - { - fetchIdentity: context.fetchIdentity, - prometheusBaseUrl: context.prometheusBaseUrl, - prometheusRangeSeconds: context.prometheusRangeSeconds, - prometheusStepSeconds: context.prometheusStepSeconds, - requestTimeoutMs: context.requestTimeoutMs, - serviceGroups: document.serviceGroups.map((group) => ({ - id: group.id, - services: group.services.map((service) => ({ - datasource: service.datasource, - id: service.id, - })), - })), - }, - ); -} - async function resolveTelemetryCard( card: TelemetryCard, context: DatasourceContext, @@ -563,11 +485,13 @@ function resolveStatusItem( async function resolveStatusTile( item: StatusItem, refreshIntervalSeconds: number | undefined, - document: DashboardDocument, + serviceGroups: ServiceGroup[], context: DatasourceContext, ): Promise { if (item.id === "system-status") { - const resolvedGroups = await serviceGroupsSnapshot(document, context); + const resolvedGroups = await Promise.all( + serviceGroups.map((group) => resolveServiceGroup(group, context)), + ); const health = serviceHealthSummary(resolvedGroups); return { ...structuredClone(item),