fix(web): ignore malformed dashboard tile snapshots
This commit is contained in:
parent
4ce42fb52b
commit
88c448258d
3 changed files with 112 additions and 1 deletions
|
|
@ -45,6 +45,11 @@ type DashboardTileResponse =
|
|||
state: "not_found";
|
||||
tile: DashboardTileReference;
|
||||
message: string;
|
||||
}
|
||||
| {
|
||||
state: "disabled";
|
||||
tile: DashboardTileReference;
|
||||
message: string;
|
||||
};
|
||||
|
||||
type DashboardTileHydrationResult = "aborted" | "failed" | "ready";
|
||||
|
|
|
|||
|
|
@ -164,6 +164,15 @@ describe("dashboard refresh lifecycle", () => {
|
|||
},
|
||||
schemaVersion: "dashboard.v1",
|
||||
});
|
||||
store.saveTile({
|
||||
currentRevisionId: "revision-a",
|
||||
response: {
|
||||
state: "disabled",
|
||||
tile: { kind: "module", id: "disabled-module" },
|
||||
message: "Disabled",
|
||||
},
|
||||
schemaVersion: "dashboard.v1",
|
||||
});
|
||||
|
||||
now = 61_000;
|
||||
|
||||
|
|
@ -193,6 +202,53 @@ describe("dashboard refresh lifecycle", () => {
|
|||
schemaVersion: "dashboard.v1",
|
||||
})).toEqual([]);
|
||||
});
|
||||
|
||||
test("ignores malformed stored tile snapshots", () => {
|
||||
const storage = createMemoryStorage();
|
||||
storage.setItem(
|
||||
"dimensionlab.dashboard.tiles.v1",
|
||||
JSON.stringify({
|
||||
currentRevisionId: "revision-a",
|
||||
schemaVersion: "dashboard.v1",
|
||||
tiles: [
|
||||
{},
|
||||
{
|
||||
item: { id: "infra-ram", detail: "live" },
|
||||
savedAt: 1_000,
|
||||
tile: { kind: "telemetry", id: "infra-ram" },
|
||||
},
|
||||
{
|
||||
item: { id: "broken-detail", detail: 42 },
|
||||
savedAt: 1_000,
|
||||
tile: { kind: "telemetry", id: "broken-detail" },
|
||||
},
|
||||
],
|
||||
version: 1,
|
||||
}),
|
||||
);
|
||||
|
||||
const store = createDashboardTileSnapshotStore(storage, {
|
||||
now: () => 16_000,
|
||||
});
|
||||
|
||||
expect(store.restore({
|
||||
currentRevisionId: "revision-a",
|
||||
schemaVersion: "dashboard.v1",
|
||||
})).toEqual([
|
||||
{
|
||||
ageMs: 15_000,
|
||||
response: {
|
||||
state: "ready",
|
||||
tile: { kind: "telemetry", id: "infra-ram" },
|
||||
item: {
|
||||
id: "infra-ram",
|
||||
detail: "live - stale 15s",
|
||||
severity: "stale",
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
async function waitFor(predicate: () => boolean) {
|
||||
|
|
|
|||
|
|
@ -231,7 +231,12 @@ export function createDashboardTileSnapshotStore(
|
|||
return null;
|
||||
}
|
||||
|
||||
return payload as DashboardTileSnapshotPayload;
|
||||
return {
|
||||
currentRevisionId: payload.currentRevisionId,
|
||||
schemaVersion: payload.schemaVersion,
|
||||
tiles: payload.tiles.filter(isDashboardTileSnapshotRecord),
|
||||
version: 1,
|
||||
};
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
|
|
@ -332,6 +337,51 @@ function dashboardTileSnapshotKey(tile: DashboardTileReference): string {
|
|||
return JSON.stringify(tile);
|
||||
}
|
||||
|
||||
function isDashboardTileSnapshotRecord(
|
||||
value: unknown,
|
||||
): value is DashboardTileSnapshotRecord {
|
||||
if (!isSnapshotRecord(value)) return false;
|
||||
|
||||
return (
|
||||
typeof value.savedAt === "number" &&
|
||||
Number.isFinite(value.savedAt) &&
|
||||
isDashboardTileReference(value.tile) &&
|
||||
isDashboardTileSnapshotItem(value.item)
|
||||
);
|
||||
}
|
||||
|
||||
function isDashboardTileReference(
|
||||
value: unknown,
|
||||
): value is DashboardTileReference {
|
||||
if (!isSnapshotRecord(value) || typeof value.kind !== "string") return false;
|
||||
|
||||
switch (value.kind) {
|
||||
case "telemetry":
|
||||
case "module":
|
||||
return typeof value.id === "string";
|
||||
case "service":
|
||||
return typeof value.groupId === "string" && typeof value.id === "string";
|
||||
case "status":
|
||||
return typeof value.stripId === "string" && typeof value.id === "string";
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function isDashboardTileSnapshotItem(
|
||||
value: unknown,
|
||||
): value is DashboardTileSnapshotItem {
|
||||
return (
|
||||
isSnapshotRecord(value) &&
|
||||
typeof value.id === "string" &&
|
||||
(value.detail === undefined || typeof value.detail === "string")
|
||||
);
|
||||
}
|
||||
|
||||
function isSnapshotRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === "object" && value !== null;
|
||||
}
|
||||
|
||||
function staleDashboardTileDetail(
|
||||
detail: string | undefined,
|
||||
ageMs: number,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue