milestone 32: task-shaped configuration, file mode as a rendering, config status api
Gates / frontend (push) Successful in 1m22s
Gates / test (push) Successful in 1m54s
Gates / test-aarch64 (push) Successful in 7m57s
Gates / package (push) Successful in 5m29s
Gates / container (push) Successful in 10s
CI / gates (push) Successful in 32m51s
Gates / frontend (push) Successful in 1m22s
Gates / test (push) Successful in 1m54s
Gates / test-aarch64 (push) Successful in 7m57s
Gates / package (push) Successful in 5m29s
Gates / container (push) Successful in 10s
CI / gates (push) Successful in 32m51s
This commit is contained in:
@@ -100,7 +100,9 @@ test("an open episode shows its facts, its copy and the error the server sent",
|
||||
expect(screen.getByText(EVENT_COPY["blocklist.refresh"].impact)).toBeTruthy();
|
||||
expect(screen.getByText(EVENT_COPY["blocklist.refresh"].remediation)).toBeTruthy();
|
||||
expect(screen.getByText("download failed: ConnectionTimedOut")).toBeTruthy();
|
||||
expect(screen.getByRole("link", { name: "Go to Blocklists" }).getAttribute("href")).toBe("/blocklists");
|
||||
expect(screen.getByRole("link", { name: "Go to Blocklist sources" }).getAttribute("href")).toBe(
|
||||
"/configuration/protection?tab=sources",
|
||||
);
|
||||
});
|
||||
|
||||
test("a resolved episode states how long it lasted, not how long it has run", async () => {
|
||||
|
||||
@@ -207,9 +207,27 @@ export default function DiagnosticDetailPage() {
|
||||
|
||||
{copy.link !== undefined && (
|
||||
<p {...stylex.props(styles.links)}>
|
||||
<Link to={copy.link.to} {...stylex.props(styles.link, shared.focusRing)}>
|
||||
Go to {copy.link.label}
|
||||
</Link>
|
||||
{copy.link.to === "/configuration/protection" ? (
|
||||
<Link
|
||||
to="/configuration/protection"
|
||||
search={{ tab: "sources" }}
|
||||
{...stylex.props(styles.link, shared.focusRing)}
|
||||
>
|
||||
Go to {copy.link.label}
|
||||
</Link>
|
||||
) : copy.link.to === "/configuration/resolution" ? (
|
||||
<Link
|
||||
to="/configuration/resolution"
|
||||
search={{ tab: "upstreams" }}
|
||||
{...stylex.props(styles.link, shared.focusRing)}
|
||||
>
|
||||
Go to {copy.link.label}
|
||||
</Link>
|
||||
) : (
|
||||
<Link to={copy.link.to} {...stylex.props(styles.link, shared.focusRing)}>
|
||||
Go to {copy.link.label}
|
||||
</Link>
|
||||
)}
|
||||
</p>
|
||||
)}
|
||||
|
||||
|
||||
@@ -99,7 +99,7 @@ test("the five conditions are stated in words, healthy ones without a way out",
|
||||
expect(within(strip()).queryByRole("link")).toBeNull();
|
||||
});
|
||||
|
||||
test("protection unavailable sends the reader to Blocklists, upstreams to Upstreams", async () => {
|
||||
test("protection unavailable sends the reader to the sources, upstreams to the pool", async () => {
|
||||
healthBody = health({
|
||||
protection: { state: "unavailable", until: null },
|
||||
upstreams: { state: "unavailable", available: 0, total: 2 },
|
||||
@@ -107,10 +107,12 @@ test("protection unavailable sends the reader to Blocklists, upstreams to Upstre
|
||||
renderDiagnostics();
|
||||
await waitFor(() => expect(strip()).toBeTruthy());
|
||||
|
||||
expect(within(fact("Protection")).getByRole("link", { name: "Blocklists" }).getAttribute("href")).toBe(
|
||||
"/blocklists",
|
||||
expect(within(fact("Protection")).getByRole("link", { name: "Blocklist sources" }).getAttribute("href")).toBe(
|
||||
"/configuration/protection?tab=sources",
|
||||
);
|
||||
expect(within(fact("Upstreams")).getByRole("link", { name: "Upstreams" }).getAttribute("href")).toBe(
|
||||
"/configuration/resolution?tab=upstreams",
|
||||
);
|
||||
expect(within(fact("Upstreams")).getByRole("link", { name: "Upstreams" }).getAttribute("href")).toBe("/upstreams");
|
||||
});
|
||||
|
||||
test("a losing query log narrows this page to the disk, a failed writer to the query log", async () => {
|
||||
@@ -159,7 +161,8 @@ test("the strip says it is loading before the first reading, never empty conditi
|
||||
renderDiagnostics();
|
||||
|
||||
expect(await screen.findByText("Loading status…")).toBeTruthy();
|
||||
expect(screen.queryByText("Protection")).toBeNull();
|
||||
// Scoped to the page: "Protection" is also a nav destination now.
|
||||
expect(within(document.querySelector("main") as HTMLElement).queryByText("Protection")).toBeNull();
|
||||
|
||||
release();
|
||||
await waitFor(() => expect(strip()).toBeTruthy());
|
||||
|
||||
@@ -124,8 +124,25 @@ function FactLinkAnchor({ link }: { link: FactLink }) {
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
// The tab belongs to the destination, not to the fact: a source that stopped
|
||||
// loading is read on Protection's Sources tab, an upstream on Resolution's.
|
||||
if (link.to === "/configuration/protection") {
|
||||
return (
|
||||
<Link
|
||||
to="/configuration/protection"
|
||||
search={{ tab: "sources" }}
|
||||
{...stylex.props(styles.link, shared.focusRing)}
|
||||
>
|
||||
{link.label}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Link to={link.to} {...stylex.props(styles.link, shared.focusRing)}>
|
||||
<Link
|
||||
to="/configuration/resolution"
|
||||
search={{ tab: "upstreams" }}
|
||||
{...stylex.props(styles.link, shared.focusRing)}
|
||||
>
|
||||
{link.label}
|
||||
</Link>
|
||||
);
|
||||
|
||||
@@ -8,14 +8,16 @@
|
||||
* `DIAGNOSTIC_CODES` to prove it at runtime too. A sixteenth code added to the
|
||||
* enum fails `tsc` here before it can reach the page as a bare dotted string.
|
||||
*
|
||||
* `link` points at the configuration surface that governs the failure. Those
|
||||
* are today's routes; the navigation restructure re-points them.
|
||||
* `link` points at the configuration surface that governs the failure. The
|
||||
* path alone decides which tab of that surface the detail page opens, so the
|
||||
* copy never carries a tab of its own to drift from it.
|
||||
*/
|
||||
|
||||
import { DIAGNOSTIC_CODES, type DiagnosticCode } from "@/lib/types";
|
||||
|
||||
/** The literal paths keep `link.to` assignable to a typed router `Link`. */
|
||||
export type CopyLinkPath = "/settings" | "/blocklists" | "/upstreams" | "/clients";
|
||||
export type CopyLinkPath =
|
||||
"/configuration/system" | "/configuration/protection" | "/configuration/resolution" | "/clients";
|
||||
|
||||
export interface EventCopy {
|
||||
title: string;
|
||||
@@ -39,9 +41,9 @@ export function copyFor(code: DiagnosticCode): EventCopy {
|
||||
);
|
||||
}
|
||||
|
||||
const SETTINGS = { to: "/settings", label: "Settings" } as const;
|
||||
const BLOCKLISTS = { to: "/blocklists", label: "Blocklists" } as const;
|
||||
const UPSTREAMS = { to: "/upstreams", label: "Upstreams" } as const;
|
||||
const SETTINGS = { to: "/configuration/system", label: "System" } as const;
|
||||
const BLOCKLISTS = { to: "/configuration/protection", label: "Blocklist sources" } as const;
|
||||
const UPSTREAMS = { to: "/configuration/resolution", label: "Upstreams" } as const;
|
||||
const CLIENTS = { to: "/clients", label: "Clients" } as const;
|
||||
|
||||
/**
|
||||
|
||||
@@ -28,7 +28,7 @@ test("protection: active, paused indefinitely, paused until a time, unavailable"
|
||||
expect(timed.value).toBe("Paused until 14:05");
|
||||
const gone = factsBy({ protection: { state: "unavailable", until: null } })["protection"];
|
||||
expect(gone.value).toBe("Unavailable");
|
||||
expect(gone.link).toEqual({ kind: "route", to: "/blocklists", label: "Blocklists" });
|
||||
expect(gone.link).toEqual({ kind: "route", to: "/configuration/protection", label: "Blocklist sources" });
|
||||
});
|
||||
|
||||
test("a pause never reads as a fault, and never carries a way out", () => {
|
||||
@@ -44,7 +44,7 @@ test("upstreams count the enabled pool, and only an empty one links out", () =>
|
||||
expect(ok.link).toBeUndefined();
|
||||
const none = factsBy({ upstreams: { state: "unavailable", available: 0, total: 3 } })["upstreams"];
|
||||
expect(none.value).toBe("None reachable");
|
||||
expect(none.link).toEqual({ kind: "route", to: "/upstreams", label: "Upstreams" });
|
||||
expect(none.link).toEqual({ kind: "route", to: "/configuration/resolution", label: "Upstreams" });
|
||||
});
|
||||
|
||||
test("query history: losing blames the disk gate, a failed writer blames the query log", () => {
|
||||
|
||||
@@ -20,7 +20,7 @@ export type FactTone = "ok" | "notice" | "warn" | "danger";
|
||||
* that can fix the condition.
|
||||
*/
|
||||
export type FactLink =
|
||||
| { kind: "route"; to: "/blocklists" | "/upstreams"; label: string }
|
||||
| { kind: "route"; to: "/configuration/protection" | "/configuration/resolution"; label: string }
|
||||
| { kind: "filter"; component: string; label: string };
|
||||
|
||||
export interface HealthFact {
|
||||
@@ -50,7 +50,7 @@ function protectionFact(protection: Health["protection"], locale?: string, timeZ
|
||||
tone: "danger",
|
||||
value: "Unavailable",
|
||||
detail: "No filter snapshot is published, so queries are not being filtered.",
|
||||
link: { kind: "route", to: "/blocklists", label: "Blocklists" },
|
||||
link: { kind: "route", to: "/configuration/protection", label: "Blocklist sources" },
|
||||
};
|
||||
}
|
||||
if (protection.state === "paused") {
|
||||
@@ -105,7 +105,7 @@ export function healthFacts(health: Health, locale?: string, timeZone?: string):
|
||||
value: upstreams.state === "unavailable" ? "None reachable" : "Available",
|
||||
detail: `${upstreams.available} of ${upstreams.total} enabled`,
|
||||
...(upstreams.state === "unavailable"
|
||||
? { link: { kind: "route", to: "/upstreams", label: "Upstreams" } as FactLink }
|
||||
? { link: { kind: "route", to: "/configuration/resolution", label: "Upstreams" } as FactLink }
|
||||
: {}),
|
||||
},
|
||||
queryHistoryFact(health.query_history, locale, timeZone),
|
||||
|
||||
Reference in New Issue
Block a user