From d4d2e68a1fb4de1cf2fd7d8b65ec385b22e7272b Mon Sep 17 00:00:00 2001 From: "Bob.Hou" Date: Mon, 7 Sep 2026 07:56:49 -0400 Subject: [PATCH] fix(dashboard): pass nodeMap into Runtime QuotaGroup (#12868) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Validado numa worktree combinada com as 16 PRs desta leva sobre `release/v3.8.51`: typecheck:core limpo, check-file-size e check-changelog-integrity OK, complexity 2788/3218 e cognitive 1261/1437 (ambos sob a baseline), ESLint 0 erros nos 152 arquivos alterados, 771 testes unitários focados, 49 de integração e a suíte vitest:ui completa (2149) verdes. O `nodeMap` lido do closure de `RuntimePageClient` por uma função de nível de módulo é uma bomba-relógio silenciosa: só explode quando um monitor entra em error/exhausted/alerting, e o teste existente só alimentava listas vazias. Tirar o arquivo do exclude do vitest vale tanto quanto o fix — confirmei aqui que `tests/unit/ui/runtime-page-client.test.tsx` agora roda na `test:vitest:ui` e passa. A anotação sobre o "内部服务器错误" ser o catálogo RSC da página, e não o crash, poupou o próximo a caçar fantasma. --- .../fixes/12868-runtime-quotagroup-nodemap.md | 1 + config/quality/file-size-baseline.json | 5 +- .../dashboard/runtime/RuntimePageClient.tsx | 29 +++++++-- tests/unit/ui/runtime-page-client.test.tsx | 60 +++++++++++++++++++ vitest.config.ts | 1 - 5 files changed, 89 insertions(+), 7 deletions(-) create mode 100644 changelog.d/fixes/12868-runtime-quotagroup-nodemap.md diff --git a/changelog.d/fixes/12868-runtime-quotagroup-nodemap.md b/changelog.d/fixes/12868-runtime-quotagroup-nodemap.md new file mode 100644 index 0000000000..3311c08f04 --- /dev/null +++ b/changelog.d/fixes/12868-runtime-quotagroup-nodemap.md @@ -0,0 +1 @@ +- **fix(dashboard):** pass `nodeMap` into Runtime `QuotaGroup` so a quota monitor in error/exhausted/alerting no longer throws `ReferenceError: nodeMap is not defined`. ([#12868](https://github.com/diegosouzapw/OmniRoute/pull/12868)) diff --git a/config/quality/file-size-baseline.json b/config/quality/file-size-baseline.json index 4bed8fd2e2..cde18d7f02 100644 --- a/config/quality/file-size-baseline.json +++ b/config/quality/file-size-baseline.json @@ -1,4 +1,5 @@ { + "_rebaseline_2026_09_06_runtime_quotagroup_nodemap": "Own growth: src/app/(dashboard)/dashboard/runtime/RuntimePageClient.tsx 1201->1222 (+21, check-file-size split-newline). QuotaGroup is a module-level sibling and was reading nodeMap from RuntimePageClient's closure; that identifier is not in scope, so a quota monitor with status error/exhausted/alerting throws ReferenceError. Fix threads nodeMap as a prop (3 call sites + parameter + ProviderNodeEntry import). Prettier wraps the long import and the three QuotaGroup JSX tags. Covered by tests/unit/ui/runtime-page-client.test.tsx (empty monitors stay green; error+exhausted fixtures mount QuotaGroup).", "_rebaseline_2026_09_03_reset_aware_model_family": "Own growth: open-sse/services/combo.ts 4036->4041 (+5). buildAutoCandidates now keys the reset-aware quota cache by getQuotaFetchScope and spreads requestedModel onto the connection so Gemini windows stay off a Claude-empty Antigravity account. Irreducible wiring at the existing fetchResetAwareQuotaWithCache call site; the family helper itself lives in antigravityQuotaFamily.ts. Covered by tests/unit/reset-aware-request-scope-12600.test.ts.", "_rebaseline_2026_09_03_overloaded_not_provider_breaker": "fix/overloaded-not-provider-breaker own growth: open-sse/services/combo.ts 4036->4075 (check-file-size split-newline, +39). Circuit-open pre-skip now records the breaker retryAfter and, when every target was skipped that way, waits the short reset via resolveCircuitOpenWaitDecision (new leaf in comboCooldownRetry.ts) instead of crystallizing ALL_TARGETS_SKIPPED in ~43ms. skippedForCircuitOpen / earliestCircuitOpenRetryMs reset each setTry so a later iteration cannot inherit a stale retryAfter. Irreducible at the existing ALL_TARGETS_SKIPPED chokepoint (same pattern as #7301/#8213 cooldown-wait). Predicate itself lives in circuitBreaker.ts / comboPredicates.ts / chatPredicates.ts, all under cap. Covered by tests/unit/overloaded-not-provider-breaker.test.ts + combo-cooldown-retry.test.ts.", "_rebaseline_2026_09_03_12649_free_tier_reaudit_gateways": "PR #12649 (fix/free-tier-quota-reaudit) own growth: src/shared/constants/providers/apikey/gateways.ts 1459->1462 (+3 = the nara authHint rewritten for the re-audited 7M/day plan now wraps to two lines, plus the Prettier reflow of two pre-existing >100-col authHint lines (oneminai, freebuff) that lint-staged enforces on any touch of the file; additive text at the existing registry chokepoint, same god-file no-split rationale as prior gateways.ts rebaselines: #11786 seekai, #10987 logfare, #10531 freebuff). Covered by tests/unit/free-tier-reaudit-2026-09.test.ts and tests/unit/free-providers-batch-2026-07.test.ts.", @@ -439,7 +440,7 @@ "src/app/(dashboard)/dashboard/endpoint/EndpointPageClient.tsx": 2491, "src/app/(dashboard)/dashboard/providers/[id]/components/modals/EditConnectionModal.tsx": 1631, "src/app/(dashboard)/dashboard/providers/page.tsx": 2025, - "src/app/(dashboard)/dashboard/runtime/RuntimePageClient.tsx": 1201, + "src/app/(dashboard)/dashboard/runtime/RuntimePageClient.tsx": 1222, "src/app/(dashboard)/dashboard/settings/components/ProxyRegistryManager.tsx": 1475, "src/app/(dashboard)/dashboard/settings/components/ResilienceTab.tsx": 1271, "src/app/(dashboard)/dashboard/settings/components/RoutingTab.tsx": 1607, @@ -575,7 +576,7 @@ "src/app/(dashboard)/dashboard/health/page.tsx": "1165", "src/app/(dashboard)/dashboard/providers/[id]/components/modals/EditConnectionModal.tsx": "1324", "src/app/(dashboard)/dashboard/providers/page.tsx": "1944", - "src/app/(dashboard)/dashboard/runtime/RuntimePageClient.tsx": "1201", + "src/app/(dashboard)/dashboard/runtime/RuntimePageClient.tsx": "1222", "src/app/(dashboard)/dashboard/settings/components/PricingTab.tsx": "1019", "src/app/(dashboard)/dashboard/settings/components/ProxyRegistryManager.tsx": "1470", "src/app/(dashboard)/dashboard/settings/components/ResilienceTab.tsx": "1123", diff --git a/src/app/(dashboard)/dashboard/runtime/RuntimePageClient.tsx b/src/app/(dashboard)/dashboard/runtime/RuntimePageClient.tsx index 300d93e2e3..a8547124e7 100644 --- a/src/app/(dashboard)/dashboard/runtime/RuntimePageClient.tsx +++ b/src/app/(dashboard)/dashboard/runtime/RuntimePageClient.tsx @@ -6,7 +6,11 @@ import Link from "next/link"; import Card from "@/shared/components/Card"; import ProviderIcon from "@/shared/components/ProviderIcon"; import ModelCooldownsCard from "./components/ModelCooldownsCard"; -import { useProviderNodeMap, resolveProviderName } from "@/lib/display/useProviderNodeMap"; +import { + useProviderNodeMap, + resolveProviderName, + type ProviderNodeEntry, +} from "@/lib/display/useProviderNodeMap"; type KnownBreakerState = "CLOSED" | "OPEN" | "HALF_OPEN" | "DEGRADED"; type BreakerState = KnownBreakerState | (string & {}); @@ -1002,13 +1006,28 @@ export default function RuntimePageClient() { return (
{exhausted.length > 0 && ( - + )} {alerting.length > 0 && ( - + )} {errors.length > 0 && ( - + )}
); @@ -1148,10 +1167,12 @@ function QuotaGroup({ tone, label, items, + nodeMap, }: { tone: "red" | "amber" | "orange"; label: string; items: QuotaMonitor[]; + nodeMap: Map; }) { const t = useTranslations("runtime"); const toneMap = { diff --git a/tests/unit/ui/runtime-page-client.test.tsx b/tests/unit/ui/runtime-page-client.test.tsx index 9c159ec7c2..c05273381b 100644 --- a/tests/unit/ui/runtime-page-client.test.tsx +++ b/tests/unit/ui/runtime-page-client.test.tsx @@ -133,6 +133,9 @@ const runtimePageClientMessages: Record = { openQuota: "Open Quota", allQuotasHealthy: "All quotas healthy", moreSuffix: "+{count} more", + statusExhausted: "EXHAUSTED", + statusAlerting: "ALERTING", + statusError: "ERROR", }; function runtimePageClientTranslate(key: string, values?: Record): string { @@ -196,6 +199,63 @@ describe("RuntimePageClient", () => { await waitForText("future-provider"); await waitForText("DEG"); await waitForText("UNK"); + await waitForText("All quotas healthy"); expect(document.body.textContent).not.toContain("Internal Server Error"); }); + + it("renders error and exhausted quota monitors without a nodeMap ReferenceError", async () => { + fetchMock.mockImplementation((input: RequestInfo | URL) => { + const path = getRequestPath(input); + if (path === "/api/monitoring/health") { + return Promise.resolve( + jsonResponse({ + providerBreakers: [], + lockouts: {}, + sessions: { activeCount: 0, stickyBoundCount: 0, byApiKey: {}, top: [] }, + quotaMonitor: { + active: 2, + alerting: 0, + exhausted: 1, + errors: 1, + monitors: [ + { + accountId: "acct-error", + provider: "agy", + window: "5h", + status: "error", + remainingPercent: 3, + }, + { + accountId: "acct-exhausted", + provider: "volcengine-coding-plan", + window: "5h", + status: "exhausted", + remainingPercent: 0, + }, + ], + }, + }) + ); + } + if (path === "/api/providers/client") { + return Promise.resolve(jsonResponse({ connections: [] })); + } + if (path === "/api/provider-nodes") { + return Promise.resolve(jsonResponse({ nodes: [] })); + } + return Promise.resolve(jsonResponse({})); + }); + + await renderRuntimePage(); + + await waitForText("Runtime"); + await waitForText("acct-error"); + await waitForText("acct-exhausted"); + await waitForText("ERROR"); + await waitForText("EXHAUSTED"); + expect(document.body.textContent).toContain("agy"); + expect(document.body.textContent).toContain("volcengine-coding-plan"); + expect(document.body.textContent).not.toContain("Internal Server Error"); + expect(document.body.textContent).not.toContain("All quotas healthy"); + }); }); diff --git a/vitest.config.ts b/vitest.config.ts index 06fe4a94ed..c5a89d6007 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -58,7 +58,6 @@ export default defineConfig({ "tests/unit/ui/livePage.test.tsx", // #8618 — pre-existing failure; remove this exclusion when fixed "tests/unit/ui/diffPane.test.tsx", // #8618 — pre-existing failure; remove this exclusion when fixed "tests/unit/ui/CliCodePage.test.tsx", // #8618 — pre-existing failure; remove this exclusion when fixed - "tests/unit/ui/runtime-page-client.test.tsx", // #8618 — pre-existing failure; remove this exclusion when fixed "tests/unit/ui/engineConfigPage.test.tsx", // #8618 — pre-existing failure; remove this exclusion when fixed "open-sse/services/autoCombo/__tests__/autoCombo.test.ts", // #8618 — pre-existing failure; remove this exclusion when fixed "tests/unit/ui/agent-card-risk-modal.test.tsx", // #8618 — pre-existing failure; remove this exclusion when fixed