mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-26 09:52:11 +03:00
* feat(dashboard): add reorder-by-availability button to provider connections Adds a "Reorder" action to the provider detail Connections toolbar that sorts a provider's connections so available ones float to the top and unavailable ones sink to the bottom, then persists the new order via the existing per-connection priority PUT endpoint (same pattern already used by handleSwapPriority). Availability is computed with OmniRoute's own resilience model rather than upstream's `modelLock_*` convention: a connection counts as available when its effective status (testStatus, adjusted for the lazy connection-cooldown window via rateLimitedUntil) is active/success — mirroring the exact logic ConnectionRow already uses for its status badge, so the button and the row badges never disagree. The sort is a stable Array.prototype.sort, so connections keep their relative order within each availability group. New pure helpers (sortConnectionsByAvailability, isConnectionAvailable, getConnectionEffectiveStatus) live in connectionRowHelpers.ts and are covered by a dedicated unit test, including the cooldown-lazy-recovery edge case. i18n keys added to all 43 locales. Co-authored-by: Fazril Syaveral Hillaby <fazriloke18@gmail.com> Inspired-by: https://github.com/decolua/9router/pull/2558 * chore(changelog): fragment for #7211 * fix(dashboard): extract reorder-by-availability into its own hook (file-size ratchet) The reorder-by-availability feature pushed useProviderConnections.ts to 974 lines, past its frozen file-size cap (954). Extract the handler + its state into a dedicated useReorderByAvailability hook, following the same pattern already used for useModelVisibilityHandlers/useModelImportHandlers — no behavior change, same tests still cover the sort logic in connectionRowHelpers.ts. * fix(dashboard): type the reorder hook's notifier explicitly (dashboard-typecheck TS2339) ReturnType<typeof useNotificationStore> resolves to unknown under the dashboard-scoped tsconfig gate (#7203), so notify.error tripped TS2339. The hook only needs error(), so declare that minimal surface directly. --------- Co-authored-by: Fazril Syaveral Hillaby <fazriloke18@gmail.com>
88 lines
3.1 KiB
TypeScript
88 lines
3.1 KiB
TypeScript
import { test } from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import {
|
|
sortConnectionsByAvailability,
|
|
isConnectionAvailable,
|
|
getConnectionEffectiveStatus,
|
|
} from "../../src/app/(dashboard)/dashboard/providers/[id]/components/connectionRowHelpers";
|
|
|
|
// Reorder-by-availability — upstream 9router PR #2558 ported to OmniRoute's
|
|
// resilience model (rateLimitedUntil cooldown + testStatus), not the
|
|
// upstream `modelLock_*` field convention. See CLAUDE.md "Resilience Runtime
|
|
// State" → Connection Cooldown.
|
|
|
|
test("sortConnectionsByAvailability moves available connections to the top", () => {
|
|
const connections = [
|
|
{ id: "a", testStatus: "error" },
|
|
{ id: "b", testStatus: "active" },
|
|
{ id: "c", testStatus: "success" },
|
|
{ id: "d", testStatus: "expired" },
|
|
];
|
|
|
|
const sorted = sortConnectionsByAvailability(connections);
|
|
|
|
assert.deepEqual(
|
|
sorted.map((c) => c.id),
|
|
["b", "c", "a", "d"]
|
|
);
|
|
});
|
|
|
|
test("sortConnectionsByAvailability is a stable sort (preserves relative order within each group)", () => {
|
|
const connections = [
|
|
{ id: "1", testStatus: "error" },
|
|
{ id: "2", testStatus: "active" },
|
|
{ id: "3", testStatus: "error" },
|
|
{ id: "4", testStatus: "success" },
|
|
{ id: "5", testStatus: "unknown" },
|
|
];
|
|
|
|
const sorted = sortConnectionsByAvailability(connections);
|
|
|
|
// Available group (2, 4) keeps its original relative order, then the
|
|
// unavailable group (1, 3, 5) keeps its original relative order.
|
|
assert.deepEqual(
|
|
sorted.map((c) => c.id),
|
|
["2", "4", "1", "3", "5"]
|
|
);
|
|
});
|
|
|
|
test("sortConnectionsByAvailability does not mutate the input array", () => {
|
|
const connections = [{ id: "a", testStatus: "error" }, { id: "b", testStatus: "active" }];
|
|
const original = [...connections];
|
|
|
|
sortConnectionsByAvailability(connections);
|
|
|
|
assert.deepEqual(connections, original);
|
|
});
|
|
|
|
test("a testStatus: 'unavailable' connection past its cooldown counts as available (lazy recovery)", () => {
|
|
const pastCooldown = new Date(Date.now() - 60_000).toISOString();
|
|
const connection = { testStatus: "unavailable", rateLimitedUntil: pastCooldown };
|
|
|
|
assert.equal(getConnectionEffectiveStatus(connection), "active");
|
|
assert.equal(isConnectionAvailable(connection), true);
|
|
});
|
|
|
|
test("a testStatus: 'unavailable' connection still within cooldown stays unavailable", () => {
|
|
const futureCooldown = new Date(Date.now() + 60_000).toISOString();
|
|
const connection = { testStatus: "unavailable", rateLimitedUntil: futureCooldown };
|
|
|
|
assert.equal(getConnectionEffectiveStatus(connection), "unavailable");
|
|
assert.equal(isConnectionAvailable(connection), false);
|
|
});
|
|
|
|
test("sortConnectionsByAvailability treats an active cooldown as unavailable even ahead of a hard error", () => {
|
|
const futureCooldown = new Date(Date.now() + 60_000).toISOString();
|
|
const connections = [
|
|
{ id: "cooling", testStatus: "unavailable", rateLimitedUntil: futureCooldown },
|
|
{ id: "recovered", testStatus: "active" },
|
|
];
|
|
|
|
const sorted = sortConnectionsByAvailability(connections);
|
|
|
|
assert.deepEqual(
|
|
sorted.map((c) => c.id),
|
|
["recovered", "cooling"]
|
|
);
|
|
});
|