From f26aa16da3ce954239c102edf9a4a4e2adddfe27 Mon Sep 17 00:00:00 2001 From: Milan Soni <123074437+Iammilansoni@users.noreply.github.com> Date: Sun, 5 Jul 2026 14:16:17 +0530 Subject: [PATCH] feat(rankings): add 'Configured Only' filter to Free Provider Rankings page (#6245) feat(rankings): add 'Configured Only' filter to Free Provider Rankings (#6245, closes #6150). 9/9 test green. Thanks @Iammilansoni. Integrated into release/v3.8.45. --- CHANGELOG.md | 4 + .../dashboard/free-provider-rankings/page.tsx | 76 +++++++++++++-- src/i18n/messages/en.json | 6 +- ...rovider-rankings-configured-filter.test.ts | 93 +++++++++++++++++++ 4 files changed, 172 insertions(+), 7 deletions(-) create mode 100644 tests/unit/free-provider-rankings-configured-filter.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index bfa214eb1c..a4fec7f6e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,10 @@ - **refactor(dashboard):** extract the onboarding-wizard "Open provider details" link target into a pure, unit-tested `buildProviderDetailsHref(connection)` helper. The wizard already routes by `connection.id` (the node UUID) rather than the provider category slug (#6144/#6145); this hardens that behavior behind a tested helper that guards a missing id/connection. Regression guard: `tests/unit/provider-onboarding-href.test.ts`. ([#6166](https://github.com/diegosouzapw/OmniRoute/pull/6166) — thanks @KooshaPari) +### ✨ New Features + +- **feat(rankings):** add a **'Configured Only'** filter to the Free Provider Rankings page, so the table can be narrowed to just the providers you have configured connections for (with an empty-state hint when none are configured). New `en.json` keys and a pure filter helper covered by `tests/unit/free-provider-rankings-configured-filter.test.ts`. ([#6245](https://github.com/diegosouzapw/OmniRoute/pull/6245), closes [#6150](https://github.com/diegosouzapw/OmniRoute/issues/6150) — thanks @Iammilansoni) + --- ## [3.8.43] — 2026-07-02 diff --git a/src/app/(dashboard)/dashboard/free-provider-rankings/page.tsx b/src/app/(dashboard)/dashboard/free-provider-rankings/page.tsx index a112f299d5..31a6614076 100644 --- a/src/app/(dashboard)/dashboard/free-provider-rankings/page.tsx +++ b/src/app/(dashboard)/dashboard/free-provider-rankings/page.tsx @@ -60,6 +60,8 @@ export default function FreeProviderRankingsPage() { const [loading, setLoading] = useState(true); const [error, setError] = useState(""); const [filter, setFilter] = useState(""); + const [configuredOnly, setConfiguredOnly] = useState(false); + const [configuredProviderIds, setConfiguredProviderIds] = useState>(new Set()); const fetchRankings = useCallback( async (category?: string) => { @@ -86,6 +88,28 @@ export default function FreeProviderRankingsPage() { fetchRankings(filter || undefined); }, [filter, fetchRankings]); + useEffect(() => { + let active = true; + fetch("/api/providers") + .then((res) => (res.ok ? res.json() : { connections: [] })) + .then((data) => { + if (!active) return; + const ids = new Set(); + for (const conn of data.connections || []) { + if (conn?.provider) ids.add(conn.provider); + } + setConfiguredProviderIds(ids); + }) + .catch(() => {}); + return () => { + active = false; + }; + }, []); + + const displayedRankings = configuredOnly + ? rankings.filter((r) => configuredProviderIds.has(r.id)) + : rankings; + return (
{/* Header */} @@ -111,6 +135,30 @@ export default function FreeProviderRankingsPage() { {t(opt.labelKey)} ))} +
+ + +
{error &&
{error}
} @@ -122,9 +170,9 @@ export default function FreeProviderRankingsPage() { ) : ( <> {/* Top 3 Podium */} - {rankings.length >= 3 && ( + {displayedRankings.length >= 3 && (
- {rankings.slice(0, 3).map((provider, idx) => ( + {displayedRankings.slice(0, 3).map((provider, idx) => (
0 && ( + {displayedRankings.length > 0 && (
@@ -181,10 +229,11 @@ export default function FreeProviderRankingsPage() { + - {rankings.map((provider, idx) => ( + {displayedRankings.map((provider, idx) => ( + ))} @@ -237,9 +297,13 @@ export default function FreeProviderRankingsPage() { )} - {rankings.length === 0 && !error && ( + {displayedRankings.length === 0 && !error && ( -
{t("emptyState")}
+
+ {configuredOnly && rankings.length > 0 + ? t("noConfiguredProviders") + : t("emptyState")} +
)} diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index 75b632a949..8a34193da8 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -9030,7 +9030,11 @@ "colScore": "Score", "colAvgScore": "Avg Score", "colModels": "Models", - "colType": "Type" + "colType": "Type", + "configuredOnly": "Configured Only", + "configuredOnlyHint": "Show only providers with active connections", + "noConfiguredProviders": "No configured providers found. Add a provider connection first.", + "colConfigured": "Status" }, "discovery": { "title": "Provider Discovery", diff --git a/tests/unit/free-provider-rankings-configured-filter.test.ts b/tests/unit/free-provider-rankings-configured-filter.test.ts new file mode 100644 index 0000000000..86b16552de --- /dev/null +++ b/tests/unit/free-provider-rankings-configured-filter.test.ts @@ -0,0 +1,93 @@ +/** + * Unit tests for the "Configured Only" filter on the Free Provider Rankings page. + * + * Phase 1 of #6150 — verifies the toggle state, filtering logic, status column, + * cleanup flag, and i18n keys exist in the source code. + */ + +import test from "node:test"; +import assert from "node:assert/strict"; +import { readFileSync } from "node:fs"; +import { join } from "node:path"; + +const root = join(import.meta.dirname, "../.."); +const read = (p: string) => readFileSync(join(root, p), "utf8"); +const pageSrc = read("src/app/(dashboard)/dashboard/free-provider-rankings/page.tsx"); +const en = JSON.parse(read("src/i18n/messages/en.json")); + +test("page declares configuredOnly state", () => { + assert.ok(pageSrc.includes("useState(false)"), "configuredOnly defaults to false"); + assert.ok(pageSrc.includes("setConfiguredOnly"), "setConfiguredOnly setter exists"); +}); + +test("page declares configuredProviderIds state", () => { + assert.ok(pageSrc.includes("configuredProviderIds"), "configuredProviderIds state exists"); + assert.ok(pageSrc.includes("Set"), "configuredProviderIds is typed as Set"); +}); + +test("page fetches /api/providers on mount", () => { + assert.ok(pageSrc.includes('fetch("/api/providers")'), "fetches /api/providers"); + assert.ok(pageSrc.includes("conn?.provider"), "uses optional chaining for conn.provider"); +}); + +test("useEffect has cleanup flag to prevent stale state updates", () => { + assert.ok(pageSrc.includes("let active = true"), "declares cleanup flag"); + assert.ok(pageSrc.includes("if (!active) return"), "guards state update with active flag"); + assert.ok(pageSrc.includes("active = false"), "cleanup function sets active to false"); +}); + +test("displayedRankings filters by configuredProviderIds when toggle is on", () => { + assert.ok(pageSrc.includes("displayedRankings"), "displayedRankings derived variable exists"); + assert.ok( + pageSrc.includes("configuredProviderIds.has(r.id)"), + "filters rankings by configuredProviderIds.has(r.id)" + ); + assert.ok( + pageSrc.includes("configuredOnly\n ? rankings.filter"), + "conditional: when configuredOnly is true, filters rankings" + ); +}); + +test("toggle switch has accessible attributes", () => { + assert.ok(pageSrc.includes('role="switch"'), "toggle has role=switch"); + assert.ok( + pageSrc.includes("aria-checked={configuredOnly}"), + "toggle has aria-checked bound to configuredOnly" + ); + assert.ok( + pageSrc.includes('htmlFor="configured-only-toggle"'), + "label is linked to toggle via htmlFor" + ); +}); + +test("table has a 'Configured' status column", () => { + assert.ok(pageSrc.includes('t("colConfigured")'), "table header includes colConfigured key"); + assert.ok( + pageSrc.includes("configuredProviderIds.has(provider.id)"), + "status column checks configuredProviderIds" + ); +}); + +test("empty state shows noConfiguredProviders when toggle is on", () => { + assert.ok( + pageSrc.includes('t("noConfiguredProviders")'), + "empty state uses noConfiguredProviders i18n key" + ); + assert.ok( + pageSrc.includes("configuredOnly && rankings.length > 0"), + "shows noConfiguredProviders only when toggle is on and data exists" + ); +}); + +test("i18n: en.json has all required filter keys", () => { + const keys = en.freeProviderRankingsPage; + assert.ok(keys, "freeProviderRankingsPage namespace exists in en.json"); + assert.equal(typeof keys.configuredOnly, "string", "configuredOnly is a string"); + assert.equal(typeof keys.configuredOnlyHint, "string", "configuredOnlyHint is a string"); + assert.equal(typeof keys.noConfiguredProviders, "string", "noConfiguredProviders is a string"); + assert.equal(typeof keys.colConfigured, "string", "colConfigured is a string"); + assert.ok(keys.configuredOnly.length > 0, "configuredOnly is non-empty"); + assert.ok(keys.configuredOnlyHint.length > 0, "configuredOnlyHint is non-empty"); + assert.ok(keys.noConfiguredProviders.length > 0, "noConfiguredProviders is non-empty"); + assert.ok(keys.colConfigured.length > 0, "colConfigured is non-empty"); +});
{t("colAvgScore")} {t("colModels")} {t("colType")}{t("colConfigured")}
{idx + 1} @@ -229,6 +278,17 @@ export default function FreeProviderRankingsPage() { {provider.category.toUpperCase()} + {configuredProviderIds.has(provider.id) ? ( + + ✓ + + ) : ( + + — + + )} +