Compare commits
No commits in common. "243d71686dc836f6dcd50536eaa1e93908c25d6f" and "9aa30b07db04ca457969996dfb4879b948edaa40" have entirely different histories.
243d71686d
...
9aa30b07db
2 changed files with 10 additions and 180 deletions
|
|
@ -3,7 +3,7 @@ import {
|
||||||
DASHBOARD_SCHEMA_VERSION,
|
DASHBOARD_SCHEMA_VERSION,
|
||||||
type DashboardDocument,
|
type DashboardDocument,
|
||||||
} from "@dimensionlab/dashboard-model";
|
} from "@dimensionlab/dashboard-model";
|
||||||
import { resolveDashboardDatasources, resolveDashboardTile } from ".";
|
import { resolveDashboardDatasources } from ".";
|
||||||
|
|
||||||
describe("dashboard datasource resolution", () => {
|
describe("dashboard datasource resolution", () => {
|
||||||
test("hydrates telemetry, service health, weather, and summary data from live adapters", async () => {
|
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(resolved).not.toBe(testDocument);
|
||||||
expect(testDocument.telemetry[0].value.value).toBe(1);
|
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<Response>((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();
|
const testDocument = testDashboard();
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,6 @@ export type DashboardTileResolution =
|
||||||
|
|
||||||
export interface DatasourceResolutionOptions {
|
export interface DatasourceResolutionOptions {
|
||||||
fetch?: DatasourceFetch;
|
fetch?: DatasourceFetch;
|
||||||
now?: () => number;
|
|
||||||
prometheusBaseUrl?: string;
|
prometheusBaseUrl?: string;
|
||||||
prometheusRangeSeconds?: number;
|
prometheusRangeSeconds?: number;
|
||||||
prometheusStepSeconds?: number;
|
prometheusStepSeconds?: number;
|
||||||
|
|
@ -114,7 +113,9 @@ export async function resolveDashboardTile(
|
||||||
const item = module.id === "runtime-health-summary"
|
const item = module.id === "runtime-health-summary"
|
||||||
? runtimeHealthSummary(
|
? runtimeHealthSummary(
|
||||||
module,
|
module,
|
||||||
await serviceGroupsSnapshot(document, context),
|
await Promise.all(
|
||||||
|
document.serviceGroups.map((group) => resolveServiceGroup(group, context)),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
: await resolveModule(module, context);
|
: await resolveModule(module, context);
|
||||||
|
|
||||||
|
|
@ -131,7 +132,7 @@ export async function resolveDashboardTile(
|
||||||
item: await resolveStatusTile(
|
item: await resolveStatusTile(
|
||||||
statusItem,
|
statusItem,
|
||||||
document.metadata.refreshIntervalSeconds,
|
document.metadata.refreshIntervalSeconds,
|
||||||
document,
|
document.serviceGroups,
|
||||||
context,
|
context,
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
@ -139,27 +140,14 @@ export async function resolveDashboardTile(
|
||||||
|
|
||||||
interface DatasourceContext {
|
interface DatasourceContext {
|
||||||
fetch: DatasourceFetch;
|
fetch: DatasourceFetch;
|
||||||
fetchIdentity: number;
|
|
||||||
now: () => number;
|
|
||||||
prometheusBaseUrl: string;
|
prometheusBaseUrl: string;
|
||||||
prometheusRangeSeconds: number;
|
prometheusRangeSeconds: number;
|
||||||
prometheusStepSeconds: number;
|
prometheusStepSeconds: number;
|
||||||
requestTimeoutMs: number;
|
requestTimeoutMs: number;
|
||||||
serviceGroupsSnapshot?: Promise<ServiceGroup[]>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type DatasourceFetch = (input: string, init?: RequestInit) => Promise<Response>;
|
type DatasourceFetch = (input: string, init?: RequestInit) => Promise<Response>;
|
||||||
|
|
||||||
interface ServiceGroupsSnapshotEntry {
|
|
||||||
expiresAt: number;
|
|
||||||
snapshot: Promise<ServiceGroup[]>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const serviceGroupsSnapshotTtlMs = 30_000;
|
|
||||||
const serviceGroupsSnapshotCache = new Map<string, ServiceGroupsSnapshotEntry>();
|
|
||||||
const datasourceFetchIdentities = new WeakMap<DatasourceFetch, number>();
|
|
||||||
let nextDatasourceFetchIdentity = 1;
|
|
||||||
|
|
||||||
interface PrometheusVectorResult {
|
interface PrometheusVectorResult {
|
||||||
metric?: Record<string, string>;
|
metric?: Record<string, string>;
|
||||||
value?: [number, string];
|
value?: [number, string];
|
||||||
|
|
@ -171,12 +159,8 @@ interface PrometheusMatrixResult {
|
||||||
}
|
}
|
||||||
|
|
||||||
function datasourceContext(options: DatasourceResolutionOptions): DatasourceContext {
|
function datasourceContext(options: DatasourceResolutionOptions): DatasourceContext {
|
||||||
const fetch = options.fetch || globalThis.fetch;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
fetch,
|
fetch: options.fetch || globalThis.fetch,
|
||||||
fetchIdentity: datasourceFetchIdentity(fetch),
|
|
||||||
now: options.now || Date.now,
|
|
||||||
prometheusBaseUrl:
|
prometheusBaseUrl:
|
||||||
options.prometheusBaseUrl ||
|
options.prometheusBaseUrl ||
|
||||||
process.env.PROMETHEUS_BASE_URL ||
|
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<ServiceGroup[]> {
|
|
||||||
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(
|
async function resolveTelemetryCard(
|
||||||
card: TelemetryCard,
|
card: TelemetryCard,
|
||||||
context: DatasourceContext,
|
context: DatasourceContext,
|
||||||
|
|
@ -563,11 +485,13 @@ function resolveStatusItem(
|
||||||
async function resolveStatusTile(
|
async function resolveStatusTile(
|
||||||
item: StatusItem,
|
item: StatusItem,
|
||||||
refreshIntervalSeconds: number | undefined,
|
refreshIntervalSeconds: number | undefined,
|
||||||
document: DashboardDocument,
|
serviceGroups: ServiceGroup[],
|
||||||
context: DatasourceContext,
|
context: DatasourceContext,
|
||||||
): Promise<StatusItem> {
|
): Promise<StatusItem> {
|
||||||
if (item.id === "system-status") {
|
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);
|
const health = serviceHealthSummary(resolvedGroups);
|
||||||
return {
|
return {
|
||||||
...structuredClone(item),
|
...structuredClone(item),
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue