fix(eoc-push): interpolera {brand} + EOC-tone i pushad wall
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
import type { Redis } from "ioredis";
|
import type { Redis } from "ioredis";
|
||||||
|
import BRAND from "../../../../brand.config.json" with { type: "json" };
|
||||||
import { CACHE_KEY } from "./ops-summary.js";
|
import { CACHE_KEY } from "./ops-summary.js";
|
||||||
|
|
||||||
export interface EocPushResult {
|
export interface EocPushResult {
|
||||||
@@ -29,9 +30,40 @@ interface EocFeedback {
|
|||||||
[key: string]: unknown;
|
[key: string]: unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface EocTile {
|
||||||
|
label: string;
|
||||||
|
value: string;
|
||||||
|
tone?: string;
|
||||||
|
[key: string]: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EocBoard {
|
||||||
|
title: string;
|
||||||
|
tiles: EocTile[];
|
||||||
|
[key: string]: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EocWall {
|
||||||
|
boards: EocBoard[];
|
||||||
|
[key: string]: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EocQueue {
|
||||||
|
workers_ok?: boolean;
|
||||||
|
[key: string]: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EocSafety {
|
||||||
|
allergen_invariant_brott?: number;
|
||||||
|
[key: string]: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build a payload that matches EOC's authoritative contract:
|
* Build a payload that matches EOC's authoritative contract:
|
||||||
* feedback.senaste[].created_at must be sent as feedback.senaste[].skapad.
|
* - feedback.senaste[].created_at must be sent as feedback.senaste[].skapad.
|
||||||
|
* - wall.boards[].title interpolates {brand} with the brand name.
|
||||||
|
* - wall tile tones use EOC vocabulary (good|warn|crit|"").
|
||||||
|
* - Workers and Allergen-brott tiles can escalate to "crit" from flat fields.
|
||||||
* All other keys are forwarded unchanged.
|
* All other keys are forwarded unchanged.
|
||||||
*/
|
*/
|
||||||
function mapPayloadForEoc(parsed: Record<string, unknown>): Record<string, unknown> {
|
function mapPayloadForEoc(parsed: Record<string, unknown>): Record<string, unknown> {
|
||||||
@@ -49,6 +81,33 @@ function mapPayloadForEoc(parsed: Record<string, unknown>): Record<string, unkno
|
|||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const wall = mapped.wall as EocWall | undefined;
|
||||||
|
if (wall && Array.isArray(wall.boards)) {
|
||||||
|
const jobb = mapped.jobb as EocQueue | undefined;
|
||||||
|
const sakerhet = mapped.sakerhet as EocSafety | undefined;
|
||||||
|
const workersDown = jobb?.workers_ok === false;
|
||||||
|
const allergenBreach = (sakerhet?.allergen_invariant_brott ?? 0) > 0;
|
||||||
|
|
||||||
|
mapped.wall = {
|
||||||
|
...wall,
|
||||||
|
boards: wall.boards.map((board) => ({
|
||||||
|
...board,
|
||||||
|
title: board.title.replace(/\{brand\}/g, BRAND.name),
|
||||||
|
tiles: board.tiles.map((tile) => {
|
||||||
|
let tone = tile.tone === "ok" ? "good" : tile.tone;
|
||||||
|
if (tile.label === "Workers" && workersDown) {
|
||||||
|
tone = "crit";
|
||||||
|
}
|
||||||
|
if (tile.label === "Allergen-brott" && allergenBreach) {
|
||||||
|
tone = "crit";
|
||||||
|
}
|
||||||
|
return { ...tile, tone };
|
||||||
|
}),
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return mapped;
|
return mapped;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -82,4 +82,57 @@ describe("pushOpsSummaryToEoc", () => {
|
|||||||
expect(result.ok).toBe(false);
|
expect(result.ok).toBe(false);
|
||||||
expect(result.error).toMatch(/cache miss/);
|
expect(result.error).toMatch(/cache miss/);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("transforms wall titles and tones for EOC", async () => {
|
||||||
|
process.env.EOC_URL = "https://eoc.example.com";
|
||||||
|
process.env.EOC_PUSH_TOKEN = "tok";
|
||||||
|
await redis.set(
|
||||||
|
"ops:summary:cache",
|
||||||
|
JSON.stringify({
|
||||||
|
as_of: new Date().toISOString(),
|
||||||
|
app: { app: "cibello" },
|
||||||
|
jobb: { workers_ok: false },
|
||||||
|
sakerhet: { allergen_invariant_brott: 1 },
|
||||||
|
wall: {
|
||||||
|
boards: [
|
||||||
|
{
|
||||||
|
title: "{brand} Overview",
|
||||||
|
tiles: [
|
||||||
|
{ label: "MRR", value: "0 kr", tone: "ok" },
|
||||||
|
{ label: "Workers", value: "NERE", tone: "warn" },
|
||||||
|
{ label: "Allergen-brott", value: "1", tone: "warn" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
"EX",
|
||||||
|
60,
|
||||||
|
);
|
||||||
|
|
||||||
|
const originalFetch = globalThis.fetch;
|
||||||
|
const fetchMock = vi.fn().mockResolvedValue(new Response(null, { status: 204 }));
|
||||||
|
globalThis.fetch = fetchMock as unknown as typeof fetch;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await pushOpsSummaryToEoc(redis);
|
||||||
|
expect(result.ok).toBe(true);
|
||||||
|
|
||||||
|
const init = fetchMock.mock.calls[0][1] as RequestInit;
|
||||||
|
const body = JSON.parse(init.body as string);
|
||||||
|
const board = body.wall.boards[0];
|
||||||
|
expect(board.title).toBe("Cibello Overview");
|
||||||
|
expect(board.title).not.toMatch(/\{brand\}/);
|
||||||
|
|
||||||
|
const tones = board.tiles.map((t: { tone?: string }) => t.tone);
|
||||||
|
for (const tone of tones) {
|
||||||
|
expect(["good", "warn", "crit", ""]).toContain(tone);
|
||||||
|
}
|
||||||
|
expect(board.tiles.find((t: { label: string }) => t.label === "MRR")?.tone).toBe("good");
|
||||||
|
expect(board.tiles.find((t: { label: string }) => t.label === "Workers")?.tone).toBe("crit");
|
||||||
|
expect(board.tiles.find((t: { label: string }) => t.label === "Allergen-brott")?.tone).toBe("crit");
|
||||||
|
} finally {
|
||||||
|
globalThis.fetch = originalFetch;
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user