From 193465cce974f94cd8d7bd779b40fb25f1eb9f9c Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza Date: Sun, 12 Jul 2026 15:52:30 -0300 Subject: [PATCH] fix(dashboard): hide disabled provider connections from combo builder The combos page's fetchData() only filtered available connections by testStatus ("active"/"success"), so a connection the user had explicitly disabled (isActive: false) could still show up in the combo builder if it carried a stale testStatus from before it was disabled. Add filterActiveConnections() in src/shared/utils/connectionStatus.ts and apply it ahead of the existing testStatus filter. Co-authored-by: itolstov Inspired-by: https://github.com/decolua/9router/pull/2526 --- src/app/(dashboard)/dashboard/combos/page.tsx | 6 ++- src/shared/utils/connectionStatus.ts | 26 ++++++++++ ...nnection-status-filter-active-2526.test.ts | 47 +++++++++++++++++++ 3 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 src/shared/utils/connectionStatus.ts create mode 100644 tests/unit/connection-status-filter-active-2526.test.ts diff --git a/src/app/(dashboard)/dashboard/combos/page.tsx b/src/app/(dashboard)/dashboard/combos/page.tsx index a91ac0c7f3..33516b851a 100644 --- a/src/app/(dashboard)/dashboard/combos/page.tsx +++ b/src/app/(dashboard)/dashboard/combos/page.tsx @@ -13,6 +13,7 @@ import Modal from "@/shared/components/Modal"; import Toggle from "@/shared/components/Toggle"; import Tooltip from "@/shared/components/Tooltip"; import { useCopyToClipboard } from "@/shared/hooks/useCopyToClipboard"; +import { filterActiveConnections } from "@/shared/utils/connectionStatus"; import { FieldLabelWithHelp, WeightTotalBar } from "./parts"; import { ResponseValidationEditor, type ResponseValidationValue } from "./ResponseValidationEditor"; import ReasoningTokenBufferToggle from "./ReasoningTokenBufferToggle"; @@ -768,7 +769,10 @@ export default function CombosPage() { if (combosRes.ok) setCombos((combosData.combos || []).filter((c) => !c.isHidden)); if (providersRes.ok) { - const active = (providersData.connections || []).filter( + // Exclude connections the user has explicitly disabled (isActive === false) + // before applying the test-status filter — a disabled connection can still + // carry a stale "active"/"success" testStatus from before it was disabled. + const active = filterActiveConnections(providersData.connections || []).filter( (c) => c.testStatus === "active" || c.testStatus === "success" ); setActiveProviders(active); diff --git a/src/shared/utils/connectionStatus.ts b/src/shared/utils/connectionStatus.ts new file mode 100644 index 0000000000..0d871ae130 --- /dev/null +++ b/src/shared/utils/connectionStatus.ts @@ -0,0 +1,26 @@ +/** + * Shared helpers for filtering/classifying provider connections by their + * active/disabled state, independent of their last test result. + * + * A connection can have `isActive: false` (explicitly disabled by the user) + * while still carrying a stale `testStatus` of "active"/"success" from + * before it was disabled — callers that only filter on `testStatus` will + * incorrectly keep serving disabled connections. + */ + +export interface ConnectionActiveFlag { + isActive?: boolean; + [key: string]: unknown; +} + +/** + * Filters out connections that have been explicitly disabled + * (`isActive === false`). Connections without an `isActive` field are + * treated as active for backward compatibility. + */ +export function filterActiveConnections( + connections: T[] | null | undefined +): T[] { + if (!Array.isArray(connections)) return []; + return connections.filter((connection) => connection?.isActive !== false); +} diff --git a/tests/unit/connection-status-filter-active-2526.test.ts b/tests/unit/connection-status-filter-active-2526.test.ts new file mode 100644 index 0000000000..24d3d05b9d --- /dev/null +++ b/tests/unit/connection-status-filter-active-2526.test.ts @@ -0,0 +1,47 @@ +import { test } from "node:test"; +import assert from "node:assert/strict"; + +import { filterActiveConnections } from "@/shared/utils/connectionStatus"; + +// Ported from decolua/9router#2526 — the combos builder listed provider +// connections the user had explicitly disabled, because the page only +// filtered on the connection's last `testStatus` and ignored `isActive`. +// A disabled connection can still carry a stale "active"/"success" +// testStatus from before it was disabled. + +test("filterActiveConnections excludes explicitly disabled connections", () => { + const active = { id: "active", isActive: true }; + const legacyActive = { id: "legacy" }; // no isActive field -> treated as active + const disabled = { id: "disabled", isActive: false }; + + assert.deepEqual(filterActiveConnections([active, disabled, legacyActive]), [ + active, + legacyActive, + ]); +}); + +test("filterActiveConnections returns an empty list for invalid input", () => { + assert.deepEqual(filterActiveConnections(undefined), []); + assert.deepEqual(filterActiveConnections(null), []); +}); + +test("combos page fetchData filter mirrors filterActiveConnections + testStatus gate", () => { + // Regression for the exact bug: a disabled connection with a stale + // "active" testStatus must NOT survive the combined filter used in + // src/app/(dashboard)/dashboard/combos/page.tsx fetchData(). + const connections = [ + { id: "healthy", isActive: true, testStatus: "active" }, + { id: "disabled-but-stale-status", isActive: false, testStatus: "active" }, + { id: "disabled-success-status", isActive: false, testStatus: "success" }, + { id: "enabled-not-tested", isActive: true, testStatus: "untested" }, + ]; + + const result = filterActiveConnections(connections).filter( + (c) => c.testStatus === "active" || c.testStatus === "success" + ); + + assert.deepEqual( + result.map((c) => c.id), + ["healthy"] + ); +});