swarm-ui: the moved tabs' redirects come from one table, with tests
The four tab moves each hand-wrote a redirect route. They are one table now, MOVED_ROUTES, and movedHref says where an old address goes with its query kept; /builds?run= still lands on the build's card. The builds page's own redirect helper is gone.
This commit is contained in:
parent
f55f0973eb
commit
c28ca7aab5
5 changed files with 58 additions and 35 deletions
|
|
@ -1,7 +1,7 @@
|
||||||
// Root shell component — routing only. Each route's content is its own
|
// Root shell component — routing only. Each route's content is its own
|
||||||
// page component under `./pages/`; this file just maps paths to them.
|
// page component under `./pages/`; this file just maps paths to them.
|
||||||
import { Redirect, Route, Router, Switch, useSearch } from "wouter-preact";
|
import { Redirect, Route, Router, Switch, useSearch } from "wouter-preact";
|
||||||
import { jobsHref } from "./pages/builds/builds-types.js";
|
import { MOVED_ROUTES, movedHref } from "./shell/routes.js";
|
||||||
import { Shell } from "./shell/Shell.js";
|
import { Shell } from "./shell/Shell.js";
|
||||||
import { useGuardedLocation } from "./shell/useGuardedLocation.js";
|
import { useGuardedLocation } from "./shell/useGuardedLocation.js";
|
||||||
import { AgentsPage } from "./pages/agents/AgentsPage.js";
|
import { AgentsPage } from "./pages/agents/AgentsPage.js";
|
||||||
|
|
@ -16,11 +16,10 @@ import { ReportsPage } from "./pages/reports/ReportsPage.js";
|
||||||
import { StartPage } from "./pages/StartPage.js";
|
import { StartPage } from "./pages/StartPage.js";
|
||||||
import { Panel } from "./ui/panel/Panel.js";
|
import { Panel } from "./ui/panel/Panel.js";
|
||||||
|
|
||||||
// The builds page moved onto the jobs tab. Links made before that, such as the
|
// An address from before the tabs were folded together: replace it with the
|
||||||
// Details link on a swarm-builder commit status, land here and go on to the
|
// tab that holds the page now, query kept.
|
||||||
// same build's card, query kept.
|
function MovedTo({ from }: { from: string }) {
|
||||||
function BuildsMoved() {
|
return <Redirect to={movedHref(from, useSearch()) ?? "/"} replace />;
|
||||||
return <Redirect to={jobsHref(useSearch())} replace />;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function NotFound() {
|
function NotFound() {
|
||||||
|
|
@ -38,9 +37,11 @@ export function App() {
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route path="/" component={StartPage} />
|
<Route path="/" component={StartPage} />
|
||||||
<Route path="/reports" component={ReportsPage} />
|
<Route path="/reports" component={ReportsPage} />
|
||||||
<Route path="/alerts">
|
{Object.keys(MOVED_ROUTES).map((from) => (
|
||||||
<Redirect to="/reports" replace />
|
<Route key={from} path={from}>
|
||||||
</Route>
|
<MovedTo from={from} />
|
||||||
|
</Route>
|
||||||
|
))}
|
||||||
<Route path="/agents" component={AgentsPage} />
|
<Route path="/agents" component={AgentsPage} />
|
||||||
<Route path="/agents/:name/terminal" component={AgentTerminalPage} />
|
<Route path="/agents/:name/terminal" component={AgentTerminalPage} />
|
||||||
<Route
|
<Route
|
||||||
|
|
@ -48,22 +49,12 @@ export function App() {
|
||||||
component={AgentTerminalPage}
|
component={AgentTerminalPage}
|
||||||
/>
|
/>
|
||||||
<Route path="/jobs" component={JobsPage} />
|
<Route path="/jobs" component={JobsPage} />
|
||||||
<Route path="/schedules">
|
|
||||||
<Redirect to="/messages" replace />
|
|
||||||
</Route>
|
|
||||||
<Route path="/builds" component={BuildsMoved} />
|
|
||||||
<Route path="/messages" component={MessagesPage} />
|
<Route path="/messages" component={MessagesPage} />
|
||||||
<Route path="/notifications" component={NotificationsPage} />
|
<Route path="/notifications" component={NotificationsPage} />
|
||||||
<Route path={NEW_FLOW_PATH}>
|
<Route path={NEW_FLOW_PATH}>
|
||||||
<FlowEditorPage params={{ flowId: "" }} isNew />
|
<FlowEditorPage params={{ flowId: "" }} isNew />
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="/notifications/:flowId" component={FlowEditorPage} />
|
<Route path="/notifications/:flowId" component={FlowEditorPage} />
|
||||||
<Route path="/issues">
|
|
||||||
<Redirect to="/reports" replace />
|
|
||||||
</Route>
|
|
||||||
<Route path="/review">
|
|
||||||
<Redirect to="/jobs" replace />
|
|
||||||
</Route>
|
|
||||||
<Route path="/components" component={ComponentsPage} />
|
<Route path="/components" component={ComponentsPage} />
|
||||||
<Route component={NotFound} />
|
<Route component={NotFound} />
|
||||||
</Switch>
|
</Switch>
|
||||||
|
|
|
||||||
|
|
@ -37,11 +37,6 @@ export const OUTCOME_TONE: Record<BuildOutcome, BadgeTone> = {
|
||||||
error: "warning",
|
error: "warning",
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Where an old `/builds` link goes: the jobs page, keeping its query. */
|
|
||||||
export function jobsHref(search: string): string {
|
|
||||||
return search === "" ? "/jobs" : `/jobs?${search}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** The run a `?run=<id>` query names, or `null` when it names none. */
|
/** The run a `?run=<id>` query names, or `null` when it names none. */
|
||||||
export function wantedRun(search: string): string | null {
|
export function wantedRun(search: string): string | null {
|
||||||
const id = new URLSearchParams(search).get("run");
|
const id = new URLSearchParams(search).get("run");
|
||||||
|
|
|
||||||
|
|
@ -27,3 +27,22 @@ export function visibleNavItems<T extends { href: string }>(
|
||||||
if (!hideDeveloper) return items;
|
if (!hideDeveloper) return items;
|
||||||
return items.filter((item) => !DEVELOPER_ROUTES.includes(item.href));
|
return items.filter((item) => !DEVELOPER_ROUTES.includes(item.href));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tabs that were folded into another one. Each old address redirects to its
|
||||||
|
// new tab, so a bookmark or a link made before the move still lands. The
|
||||||
|
// builds page's `?run=` query rides along, which a swarm-builder commit
|
||||||
|
// status link relies on.
|
||||||
|
export const MOVED_ROUTES: Readonly<Record<string, string>> = {
|
||||||
|
"/alerts": "/reports",
|
||||||
|
"/issues": "/reports",
|
||||||
|
"/builds": "/jobs",
|
||||||
|
"/review": "/jobs",
|
||||||
|
"/schedules": "/messages",
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Where an old address goes, keeping its query; `null` for any other path. */
|
||||||
|
export function movedHref(path: string, search: string): string | null {
|
||||||
|
const to = MOVED_ROUTES[path];
|
||||||
|
if (to === undefined) return null;
|
||||||
|
return search === "" ? to : `${to}?${search}`;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,7 @@
|
||||||
// stripping, so the `.ts` module is imported as it is.
|
// stripping, so the `.ts` module is imported as it is.
|
||||||
import assert from "node:assert/strict";
|
import assert from "node:assert/strict";
|
||||||
import { test } from "node:test";
|
import { test } from "node:test";
|
||||||
import {
|
import { bodyText, wantedRun } from "../src/pages/builds/builds-types.ts";
|
||||||
bodyText,
|
|
||||||
jobsHref,
|
|
||||||
wantedRun,
|
|
||||||
} from "../src/pages/builds/builds-types.ts";
|
|
||||||
|
|
||||||
const base = {
|
const base = {
|
||||||
id: "atlas-abc-1",
|
id: "atlas-abc-1",
|
||||||
|
|
@ -53,8 +49,3 @@ test("a run with no text for its outcome shows no body", () => {
|
||||||
assert.equal(bodyText({ ...base, outcome: "failed" }), null);
|
assert.equal(bodyText({ ...base, outcome: "failed" }), null);
|
||||||
assert.equal(bodyText({ ...base, outcome: "built", error_tail: "x" }), null);
|
assert.equal(bodyText({ ...base, outcome: "built", error_tail: "x" }), null);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("an old builds link goes to the jobs page and keeps its query", () => {
|
|
||||||
assert.equal(jobsHref("run=atlas-abc-1"), "/jobs?run=atlas-abc-1");
|
|
||||||
assert.equal(jobsHref(""), "/jobs");
|
|
||||||
});
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,12 @@
|
||||||
// The shell's per-route layout: which paths are full-bleed. Run by `npm test`.
|
// The shell's per-route layout: which paths are full-bleed. Run by `npm test`.
|
||||||
import assert from "node:assert/strict";
|
import assert from "node:assert/strict";
|
||||||
import { test } from "node:test";
|
import { test } from "node:test";
|
||||||
import { isFullBleedRoute, visibleNavItems } from "../src/shell/routes.ts";
|
import {
|
||||||
|
MOVED_ROUTES,
|
||||||
|
isFullBleedRoute,
|
||||||
|
movedHref,
|
||||||
|
visibleNavItems,
|
||||||
|
} from "../src/shell/routes.ts";
|
||||||
|
|
||||||
test("the start page is full-bleed", () => {
|
test("the start page is full-bleed", () => {
|
||||||
assert.equal(isFullBleedRoute("/"), true);
|
assert.equal(isFullBleedRoute("/"), true);
|
||||||
|
|
@ -42,3 +47,25 @@ test("hide developer details takes the components tab out of the nav", () => {
|
||||||
test("with it off the nav is whole again", () => {
|
test("with it off the nav is whole again", () => {
|
||||||
assert.deepEqual(visibleNavItems(NAV, false), NAV);
|
assert.deepEqual(visibleNavItems(NAV, false), NAV);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("an old address goes to the tab that holds its page, query kept", () => {
|
||||||
|
assert.equal(movedHref("/alerts", ""), "/reports");
|
||||||
|
assert.equal(movedHref("/issues", ""), "/reports");
|
||||||
|
assert.equal(movedHref("/review", ""), "/jobs");
|
||||||
|
assert.equal(movedHref("/schedules", ""), "/messages");
|
||||||
|
assert.equal(
|
||||||
|
movedHref("/builds", "run=atlas-abc-1"),
|
||||||
|
"/jobs?run=atlas-abc-1",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("a current tab is not moved", () => {
|
||||||
|
assert.equal(movedHref("/jobs", ""), null);
|
||||||
|
assert.equal(movedHref("/agents", "x=1"), null);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("no old address leads to another old address", () => {
|
||||||
|
for (const to of Object.values(MOVED_ROUTES)) {
|
||||||
|
assert.equal(to in MOVED_ROUTES, false, to);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue