fix(activity): an old audit/log link opens its tab, times read relative, one name for admin actions
Seen in the browser, not in the page's own tests: the navigation provider rewrites a retired page id to `activity` before the page's chunk reads the URL, so `?page=audit` landed on the Runs tab. The retired-page map now carries the params that open the successor's part (`at`), and the provider writes them as it rewrites the page — on load and on Back alike. A list time is relative with the exact second on hover (the DESIGN copy rule); a run moved over from the audit log, whose start and end are one instant, shows no length instead of an invented "1 sec". The audit log is called "admin actions" everywhere it is named (the clear button and its confirm, the Configuration group, the cleanup job, which also prunes the run history its description left out).
This commit is contained in:
@@ -45,13 +45,6 @@ describe("activityUrlState", () => {
|
||||
expect(state).toEqual(DEFAULT_ACTIVITY_STATE);
|
||||
});
|
||||
|
||||
it("opens the tab of the page a link still names", () => {
|
||||
expect(activityParamsToState(new URLSearchParams("page=audit")).tab).toBe("admin");
|
||||
expect(activityParamsToState(new URLSearchParams("page=logs")).tab).toBe("logs");
|
||||
// A tab the link names wins over the retired page's.
|
||||
expect(activityParamsToState(new URLSearchParams("page=logs&at=quota")).tab).toBe("quota");
|
||||
});
|
||||
|
||||
it("clears its own keys and the server log's on a page change", () => {
|
||||
expect(ACTIVITY_KEYS).toEqual(expect.arrayContaining(["at", "aw", "au", "aq", "rjob", "lreq"]));
|
||||
});
|
||||
|
||||
@@ -54,10 +54,6 @@ export const DEFAULT_ACTIVITY_STATE: ActivityUrlState = {
|
||||
const OWN_KEYS = ["at", "aw", "au", "aq", "rjob", "rst", "aact"] as const;
|
||||
export const ACTIVITY_KEYS = [...OWN_KEYS, ...LOGS_KEYS] as const;
|
||||
|
||||
// The pages this one replaced: a link or a bookmark to one opens its tab here (urlState's RETIRED
|
||||
// sends the page id itself here).
|
||||
const RETIRED_TAB: Readonly<Record<string, ActivityTab>> = { audit: "admin", logs: "logs" };
|
||||
|
||||
// The same shapes the API validates: a hand-edited link degrades to "no filter" here instead of to
|
||||
// a 422 on every request the page makes.
|
||||
const USER_RE = /^(none|\d{1,10})$/;
|
||||
@@ -71,10 +67,8 @@ const oneOf = <T extends string>(value: string | null, list: readonly T[]): T |
|
||||
|
||||
export function activityParamsToState(params: URLSearchParams): ActivityUrlState {
|
||||
return {
|
||||
tab:
|
||||
oneOf(params.get("at"), ACTIVITY_TABS) ??
|
||||
RETIRED_TAB[params.get("page") ?? ""] ??
|
||||
DEFAULT_ACTIVITY_STATE.tab,
|
||||
// A link to a page this one replaced arrives with its tab already set (urlState's RETIRED).
|
||||
tab: oneOf(params.get("at"), ACTIVITY_TABS) ?? DEFAULT_ACTIVITY_STATE.tab,
|
||||
window: oneOf(params.get("aw"), ACTIVITY_WINDOWS) ?? DEFAULT_ACTIVITY_STATE.window,
|
||||
user: pick(params.get("au"), USER_RE, 10),
|
||||
q: (params.get("aq") ?? "").slice(0, Q_MAX),
|
||||
|
||||
@@ -141,22 +141,30 @@ function isPage(p: string | null | undefined): p is Page {
|
||||
return !!p && (PAGES as readonly string[]).includes(p);
|
||||
}
|
||||
|
||||
// A page this build no longer has, and the page that took its place. A bookmark, the remembered last
|
||||
// page and an old history entry can still name one; all three resolve through `resolvePage`, so a
|
||||
// retired page lands where its content went instead of on the feed.
|
||||
const RETIRED: Readonly<Record<string, Page>> = {
|
||||
// A page this build no longer has, the page that took its place, and the params that open its part
|
||||
// of that page. A bookmark, the remembered last page and an old history entry can still name one; all
|
||||
// three resolve through `resolvePage`, so a retired page lands where its content went instead of on
|
||||
// the feed, and the navigation provider writes `params` into the address bar as it rewrites the page.
|
||||
const RETIRED: Readonly<Record<string, { page: Page; params?: Record<string, string> }>> = {
|
||||
// The download queue became the tray; what it downloaded lives in Files.
|
||||
downloads: "files",
|
||||
// The audit log and the server log became tabs of the activity page (activityUrlState opens the
|
||||
// right tab from the old page id).
|
||||
audit: "activity",
|
||||
logs: "activity",
|
||||
downloads: { page: "files" },
|
||||
// The audit log and the server log became tabs of the activity page (its `at` key).
|
||||
audit: { page: "activity", params: { at: "admin" } },
|
||||
logs: { page: "activity", params: { at: "logs" } },
|
||||
};
|
||||
|
||||
const retired = (p: string | null | undefined) =>
|
||||
p && Object.prototype.hasOwnProperty.call(RETIRED, p) ? RETIRED[p]! : null;
|
||||
|
||||
/** A page id as stored or linked: a live page, a retired page's successor, else null. */
|
||||
export function resolvePage(p: string | null | undefined): Page | null {
|
||||
if (isPage(p)) return p;
|
||||
return p && Object.prototype.hasOwnProperty.call(RETIRED, p) ? RETIRED[p]! : null;
|
||||
return retired(p)?.page ?? null;
|
||||
}
|
||||
|
||||
/** The params that open a retired page's part of its successor (none for a live page). */
|
||||
export function retiredParams(p: string | null | undefined): Record<string, string> {
|
||||
return isPage(p) ? {} : (retired(p)?.params ?? {});
|
||||
}
|
||||
|
||||
export function readPage(): Page {
|
||||
|
||||
Reference in New Issue
Block a user