From e3703d962d36b93f1462cc79943d496bc62b8e2b Mon Sep 17 00:00:00 2001 From: goulustis Date: Wed, 4 Mar 2026 14:19:54 +0800 Subject: [PATCH] frontend --- frontend/src/activeConfigSelection.test.ts | 79 ++++++++++++++++++++++ frontend/src/activeConfigSelection.ts | 45 ++++++++++++ 2 files changed, 124 insertions(+) create mode 100644 frontend/src/activeConfigSelection.test.ts create mode 100644 frontend/src/activeConfigSelection.ts diff --git a/frontend/src/activeConfigSelection.test.ts b/frontend/src/activeConfigSelection.test.ts new file mode 100644 index 0000000..14d7ec5 --- /dev/null +++ b/frontend/src/activeConfigSelection.test.ts @@ -0,0 +1,79 @@ +import { describe, expect, it } from "vitest"; + +import { chooseActiveConfigItem, chooseDisplayItemsByPipeline } from "./activeConfigSelection"; +import type { GraphConfigListItem } from "./types"; + +const mk = (patch: Partial): GraphConfigListItem => ({ + graph_id: "routing", + pipeline_id: "agent-a", + prompt_set_id: "set-1", + name: "default", + description: "", + is_active: false, + tool_keys: [], + api_key: "", + created_at: null, + updated_at: null, + ...patch, +}); + +describe("chooseActiveConfigItem", () => { + it("prefers active item over newer inactive items", () => { + const items = [ + mk({ + pipeline_id: "agent-a", + prompt_set_id: "old-active", + is_active: true, + updated_at: "2025-01-01T00:00:00Z", + }), + mk({ + pipeline_id: "agent-a", + prompt_set_id: "new-inactive", + is_active: false, + updated_at: "2025-03-01T00:00:00Z", + }), + ]; + const selected = chooseActiveConfigItem(items, "agent-a"); + expect(selected?.prompt_set_id).toBe("old-active"); + }); + + it("falls back to latest updated_at when no active item exists", () => { + const items = [ + mk({ + pipeline_id: "agent-b", + prompt_set_id: "set-1", + updated_at: "2025-01-01T00:00:00Z", + }), + mk({ + pipeline_id: "agent-b", + prompt_set_id: "set-2", + updated_at: "2025-02-01T00:00:00Z", + }), + ]; + const selected = chooseActiveConfigItem(items, "agent-b"); + expect(selected?.prompt_set_id).toBe("set-2"); + }); +}); + +describe("chooseDisplayItemsByPipeline", () => { + it("returns one selected item per pipeline_id", () => { + const items = [ + mk({ pipeline_id: "agent-b", prompt_set_id: "set-1", updated_at: "2025-01-01T00:00:00Z" }), + mk({ + pipeline_id: "agent-b", + prompt_set_id: "set-2", + is_active: true, + updated_at: "2025-02-01T00:00:00Z", + }), + mk({ + pipeline_id: "agent-a", + prompt_set_id: "set-3", + updated_at: "2025-03-01T00:00:00Z", + }), + ]; + const selected = chooseDisplayItemsByPipeline(items); + expect(selected.map((x) => x.pipeline_id)).toEqual(["agent-a", "agent-b"]); + expect(selected.find((x) => x.pipeline_id === "agent-b")?.prompt_set_id).toBe("set-2"); + }); +}); + diff --git a/frontend/src/activeConfigSelection.ts b/frontend/src/activeConfigSelection.ts new file mode 100644 index 0000000..0aa4543 --- /dev/null +++ b/frontend/src/activeConfigSelection.ts @@ -0,0 +1,45 @@ +import type { GraphConfigListItem } from "./types"; + +function toTimestamp(value?: string | null): number { + if (!value) { + return 0; + } + const parsed = Date.parse(value); + return Number.isNaN(parsed) ? 0 : parsed; +} + +export function chooseActiveConfigItem( + items: GraphConfigListItem[], + pipelineId: string +): GraphConfigListItem | null { + const candidates = items.filter((item) => item.pipeline_id === pipelineId); + if (candidates.length === 0) { + return null; + } + const active = candidates.find((item) => item.is_active); + if (active) { + return active; + } + return [...candidates].sort((a, b) => toTimestamp(b.updated_at) - toTimestamp(a.updated_at))[0]; +} + +export function chooseDisplayItemsByPipeline( + items: GraphConfigListItem[] +): GraphConfigListItem[] { + const byPipeline = new Map(); + for (const item of items) { + const list = byPipeline.get(item.pipeline_id) || []; + list.push(item); + byPipeline.set(item.pipeline_id, list); + } + + const out: GraphConfigListItem[] = []; + for (const [pipelineId, list] of byPipeline.entries()) { + const selected = chooseActiveConfigItem(list, pipelineId); + if (selected) { + out.push(selected); + } + } + return out.sort((a, b) => a.pipeline_id.localeCompare(b.pipeline_id)); +} +