diff --git a/apps/worker/src/processors/eoc-push.ts b/apps/worker/src/processors/eoc-push.ts index 7e7881a..504d123 100644 --- a/apps/worker/src/processors/eoc-push.ts +++ b/apps/worker/src/processors/eoc-push.ts @@ -1,4 +1,5 @@ import type { Redis } from "ioredis"; +import BRAND from "../../../../brand.config.json" with { type: "json" }; import { CACHE_KEY } from "./ops-summary.js"; export interface EocPushResult { @@ -29,9 +30,40 @@ interface EocFeedback { [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: - * 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. */ function mapPayloadForEoc(parsed: Record): Record { @@ -49,6 +81,33 @@ function mapPayloadForEoc(parsed: Record): Record 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; } diff --git a/apps/worker/test/eoc-push.test.ts b/apps/worker/test/eoc-push.test.ts index 524121e..43c1fcc 100644 --- a/apps/worker/test/eoc-push.test.ts +++ b/apps/worker/test/eoc-push.test.ts @@ -82,4 +82,57 @@ describe("pushOpsSummaryToEoc", () => { expect(result.ok).toBe(false); 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; + } + }); });