From bfb9a91e4987ff0f79d6245b4f2be0c9e71a0557 Mon Sep 17 00:00:00 2001 From: vince Date: Fri, 19 Jun 2026 03:52:20 +0200 Subject: [PATCH] fix(dashboard): address observability review findings --- src/lib/model/fixtures/dimensionlab.ts | 13 ++-- src/lib/server/dashboard.test.ts | 79 +++++++++++++++++++++ src/lib/server/dashboard.ts | 34 ++++++++- src/lib/ui/components/DashboardFrame.svelte | 6 +- src/lib/ui/components/ServicePanel.svelte | 2 +- src/lib/ui/model-renderer.test.ts | 3 + src/lib/ui/model-renderer.ts | 1 + src/lib/ui/types.ts | 1 + src/routes/+page.server.ts | 5 +- src/routes/+page.svelte | 27 +++++++ tests/e2e/dashboard.spec.ts | 27 +++++++ 11 files changed, 188 insertions(+), 10 deletions(-) diff --git a/src/lib/model/fixtures/dimensionlab.ts b/src/lib/model/fixtures/dimensionlab.ts index 6c06e78..c2b4808 100644 --- a/src/lib/model/fixtures/dimensionlab.ts +++ b/src/lib/model/fixtures/dimensionlab.ts @@ -548,7 +548,7 @@ export const dimensionLabDashboardFixture: DashboardDocument = { datasource: uptimeMonitor(27), detail: "fallback - exporter health from Prometheus", }), - ]), + ], "grid"), ], statusStrips: [ { @@ -635,12 +635,17 @@ function service(seed: ServiceSeed): ServiceEntry { }; } -function group(id: string, title: string, services: ServiceEntry[]): ServiceGroup { +function group( + id: string, + title: string, + services: ServiceEntry[], + layout: ServiceGroup["layout"] = "list", +): ServiceGroup { return { id, - title, - layout: "list", + layout, services, + title, }; } diff --git a/src/lib/server/dashboard.test.ts b/src/lib/server/dashboard.test.ts index 06c45b8..b15fa6c 100644 --- a/src/lib/server/dashboard.test.ts +++ b/src/lib/server/dashboard.test.ts @@ -47,6 +47,68 @@ describe("dashboard runtime loader", () => { expect(store.listRevisions()).toHaveLength(1); }); + test("refreshes an existing initial seed when the bundled seed changes", async () => { + const store = await createTestStore(); + const oldSeed = olderDimensionLabSeed(); + store.seedDashboardIfEmpty(oldSeed, { + actor: "initial-seed", + message: "load initial dashboard document", + }); + + const runtime = loadDashboardRuntime(store, { + refreshSeedDocument: true, + seedDocument: dimensionLabDashboardFixture, + seedIfEmpty: true, + }); + + expect(runtime.state).toBe("ready"); + if (runtime.state !== "ready") throw new Error("expected ready dashboard"); + expect(runtime.document.statusStrips[0]?.items.map((item) => item.id)).toContain( + "auto-refresh", + ); + expect(runtime.document.serviceGroups.flatMap((group) => group.services).map((service) => service.id)).toContain( + "prompt-registry", + ); + expect(store.listRevisions()).toHaveLength(2); + expect(store.getActiveDashboard()?.revision.actor).toBe("initial-seed"); + }); + + test("does not refresh a dashboard after a user-authored revision", async () => { + const store = await createTestStore(); + const oldSeed = olderDimensionLabSeed(); + store.seedDashboardIfEmpty(oldSeed, { + actor: "initial-seed", + message: "load initial dashboard document", + }); + store.commitDashboard( + { + ...oldSeed, + metadata: { + ...oldSeed.metadata, + title: "Custom Dashboard", + }, + }, + { + actor: "agent", + message: "customize dashboard", + }, + ); + + const runtime = loadDashboardRuntime(store, { + refreshSeedDocument: true, + seedDocument: dimensionLabDashboardFixture, + seedIfEmpty: true, + }); + + expect(runtime.state).toBe("ready"); + if (runtime.state !== "ready") throw new Error("expected ready dashboard"); + expect(runtime.document.metadata.title).toBe("Custom Dashboard"); + expect(runtime.document.statusStrips[0]?.items.map((item) => item.id)).not.toContain( + "auto-refresh", + ); + expect(store.listRevisions()).toHaveLength(2); + }); + test("returns empty state when no dashboard is active and seeding is disabled", async () => { const store = await createTestStore(); @@ -69,3 +131,20 @@ async function createTestStore() { return store; } + +function olderDimensionLabSeed() { + const document = structuredClone(dimensionLabDashboardFixture); + document.statusStrips = document.statusStrips.map((strip) => ({ + ...strip, + items: strip.items.filter((item) => item.id !== "auto-refresh"), + })); + document.serviceGroups = document.serviceGroups.map((group) => + group.id === "ai-automation" + ? { + ...group, + services: group.services.filter((service) => service.id !== "prompt-registry"), + } + : group, + ); + return document; +} diff --git a/src/lib/server/dashboard.ts b/src/lib/server/dashboard.ts index d2bfc39..7821ee0 100644 --- a/src/lib/server/dashboard.ts +++ b/src/lib/server/dashboard.ts @@ -43,6 +43,7 @@ export interface DashboardRuntimeInvalid { } export interface DashboardRuntimeOptions { + refreshSeedDocument?: boolean; seedIfEmpty?: boolean; seedDocument?: DashboardDocument; } @@ -54,8 +55,20 @@ export function loadDashboardRuntime( const dashboardStore = store || createDashboardStore(); try { + const seedDocument = options.seedDocument || dimensionLabDashboardFixture; const active = dashboardStore.getActiveDashboard(); if (active) { + if ( + options.refreshSeedDocument && + shouldRefreshSeedDashboard(active, seedDocument) + ) { + const refreshed = dashboardStore.commitDashboard(seedDocument, { + actor: "initial-seed", + message: "refresh bundled dashboard document", + }); + return readyRuntimeState(refreshed.document, refreshed.id); + } + return readyRuntimeState(active.document, active.currentRevisionId); } @@ -69,7 +82,7 @@ export function loadDashboardRuntime( } const seeded = dashboardStore.seedDashboardIfEmpty( - options.seedDocument || dimensionLabDashboardFixture, + seedDocument, { actor: "initial-seed", message: "load initial dashboard document", @@ -113,3 +126,22 @@ function invalidRuntimeState(errors: string[]): DashboardRuntimeInvalid { errors, }; } + +function shouldRefreshSeedDashboard( + active: { document: DashboardDocument; revision: { actor: string } }, + seedDocument: DashboardDocument, +): boolean { + if (active.revision.actor !== "initial-seed") return false; + if (!isBundledDimensionLabSeed(active.document, seedDocument)) return false; + return JSON.stringify(active.document) !== JSON.stringify(seedDocument); +} + +function isBundledDimensionLabSeed( + document: DashboardDocument, + seedDocument: DashboardDocument, +): boolean { + return ( + document.metadata.title === seedDocument.metadata.title && + document.metadata.description === seedDocument.metadata.description + ); +} diff --git a/src/lib/ui/components/DashboardFrame.svelte b/src/lib/ui/components/DashboardFrame.svelte index 4ed42e1..5f1f0e4 100644 --- a/src/lib/ui/components/DashboardFrame.svelte +++ b/src/lib/ui/components/DashboardFrame.svelte @@ -113,12 +113,12 @@ overflow: hidden; } - .dashboard-frame__panels :global(.service-panel[data-model-id="runtime-health"]) { + .dashboard-frame__panels :global(.service-panel[data-layout="grid"]) { grid-column: 1 / -1; } .dashboard-frame__panels - :global(.service-panel[data-model-id="runtime-health"] .panel__body) { + :global(.service-panel[data-layout="grid"] .panel__body) { grid-template-columns: repeat(4, minmax(0, 1fr)); } @@ -147,7 +147,7 @@ } .dashboard-frame__panels - :global(.service-panel[data-model-id="runtime-health"] .panel__body) { + :global(.service-panel[data-layout="grid"] .panel__body) { grid-template-columns: 1fr; } } diff --git a/src/lib/ui/components/ServicePanel.svelte b/src/lib/ui/components/ServicePanel.svelte index 0b997d7..864e09a 100644 --- a/src/lib/ui/components/ServicePanel.svelte +++ b/src/lib/ui/components/ServicePanel.svelte @@ -7,7 +7,7 @@ let { group }: { group: UiServiceGroup } = $props(); -
+
{#if group.summary?.length} diff --git a/src/lib/ui/model-renderer.test.ts b/src/lib/ui/model-renderer.test.ts index c660676..62942ee 100644 --- a/src/lib/ui/model-renderer.test.ts +++ b/src/lib/ui/model-renderer.test.ts @@ -25,6 +25,9 @@ describe("dashboard model renderer", () => { expect(dashboard.serviceGroups.map((group) => group.id)).toEqual( dimensionLabDashboardFixture.layout.serviceGroups, ); + expect(dashboard.serviceGroups.find((group) => group.id === "runtime-health")?.layout).toBe( + "grid", + ); expect(dashboard.modules.map((module) => module.id)).toEqual([ "weather-amsterdam", "runtime-health-summary", diff --git a/src/lib/ui/model-renderer.ts b/src/lib/ui/model-renderer.ts index 96e58f3..d5f3647 100644 --- a/src/lib/ui/model-renderer.ts +++ b/src/lib/ui/model-renderer.ts @@ -58,6 +58,7 @@ function telemetryToUi(card: TelemetryCard): UiTelemetryCard { function serviceGroupToUi(group: ServiceGroup): UiServiceGroup { return { id: group.id, + layout: group.layout, title: group.title, services: group.services.map(serviceToUi), }; diff --git a/src/lib/ui/types.ts b/src/lib/ui/types.ts index 55d8aee..792a24b 100644 --- a/src/lib/ui/types.ts +++ b/src/lib/ui/types.ts @@ -52,6 +52,7 @@ export interface UiServiceRow { export interface UiServiceGroup { id: string; + layout?: "grid" | "list"; title: string; services: UiServiceRow[]; summary?: UiStatusItem[]; diff --git a/src/routes/+page.server.ts b/src/routes/+page.server.ts index e2bd766..36e913d 100644 --- a/src/routes/+page.server.ts +++ b/src/routes/+page.server.ts @@ -2,7 +2,10 @@ import { loadDashboardRuntime } from "$lib/server/dashboard"; import { resolveDashboardDatasources } from "$lib/server/datasources"; export async function load() { - const dashboard = loadDashboardRuntime(undefined, { seedIfEmpty: true }); + const dashboard = loadDashboardRuntime(undefined, { + refreshSeedDocument: true, + seedIfEmpty: true, + }); if (dashboard.state !== "ready") { return { dashboard }; diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index b4c4f11..29e61c9 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -1,9 +1,12 @@ diff --git a/tests/e2e/dashboard.spec.ts b/tests/e2e/dashboard.spec.ts index 367bc55..c0d65f6 100644 --- a/tests/e2e/dashboard.spec.ts +++ b/tests/e2e/dashboard.spec.ts @@ -64,10 +64,33 @@ test.describe("dashboard page QA gate", () => { const metrics = await page.evaluate(() => { const footer = document.querySelector("[data-model-id='footer-status']"); const runtime = document.querySelector("[data-model-id='runtime-health']"); + const visibleItems = [ + ...document.querySelectorAll(".telemetry-card"), + ...document.querySelectorAll(".service-row"), + ...document.querySelectorAll(".footer-cell"), + ].map((element) => { + const rect = element.getBoundingClientRect(); + return { + bottom: rect.bottom, + height: rect.height, + id: element.getAttribute("data-model-id"), + top: rect.top, + width: rect.width, + }; + }); return { + clippedItems: visibleItems.filter((item) => + item.top < 0 || + item.bottom > window.innerHeight || + item.width <= 0 || + item.height <= 0 + ), + footerCellCount: document.querySelectorAll(".footer-cell").length, scrollWidth: document.documentElement.scrollWidth, scrollHeight: document.documentElement.scrollHeight, + serviceRowCount: document.querySelectorAll(".service-row").length, + telemetryCardCount: document.querySelectorAll(".telemetry-card").length, footerBottom: footer?.getBoundingClientRect().bottom ?? 0, runtimeBottom: runtime?.getBoundingClientRect().bottom ?? 0, }; @@ -77,6 +100,10 @@ test.describe("dashboard page QA gate", () => { expect(metrics.scrollHeight).toBeLessThanOrEqual(956); expect(metrics.runtimeBottom).toBeLessThanOrEqual(956); expect(metrics.footerBottom).toBeLessThanOrEqual(956); + expect(metrics.telemetryCardCount).toBe(16); + expect(metrics.serviceRowCount).toBe(28); + expect(metrics.footerCellCount).toBe(5); + expect(metrics.clippedItems).toEqual([]); }); test("keeps the first screen usable on mobile", async ({ page }, testInfo) => {