mirror of
https://github.com/home-assistant/frontend.git
synced 2026-10-23 22:24:24 +00:00
* Link trace steps to the script or automation run they started A step that runs a script or triggers an automation already records the started run as child_id in its trace, but the step details only dumped it as raw YAML. Show a link to that run's trace instead, named after the script or automation. A link to another run of the same item, like a script that starts itself, only changes the query string, so the trace pages now pick up a changed run_id themselves. The logbook's trace link and the new link share one URL helper, which encodes the item id. * Keep the shown trace run in the URL A link to another run of the same script or automation adds a history entry, but the trace page only followed in-app navigation, so browser back and forward left the URL and the shown run apart. Write the shown run into the current history entry, follow popstate within the same trace page, and let only the latest trace list request update the page. Entries without a run_id are ignored, since closing a dialog with back lands on one with the same URL. * Let only the latest trace request update the trace page Going back to the shown run while a linked run was still loading left the older request in charge: it then showed the linked run and wrote it into the history entry the user went back to. Remember which run the latest trace list request is for and drop it when the URL returns to the shown run. Loading the same run twice could also let the older response win, so trace requests are numbered as well. * Let a URL change replace the first trace list request While a trace page was loading its first trace list, a link or browser history step to another run of the same script or automation was ignored, so that first request won and wrote its run into the newer history entry. Handle such a change right away, so it replaces the pending request. * Let a run picked on the trace page win over a pending link While a link to another run of the same script or automation was loading, a run picked with the trace picker or the arrows was overwritten once the link's trace list arrived, and Refresh dropped the linked run while the URL kept it. A picked run now cancels the pending link, and Refresh keeps the run the link asked for. * Move the trace run navigation into a shared controller Both trace pages carried the same logic to follow the URL, number their requests and write the shown run back into the URL. Move it into a TraceRunController with unit tests, build the trace page path in one place, and split the child trace name from its link. No change in behaviour. * Link trace timeline steps to the run they started The step details link a step that ran a script or triggered an automation to the run it started. Show the same link under that step in the trace timeline, and share the name lookup and the link between both views. * Compare trace paths decoded The route decodes the item id, so a trace page can also be opened by a link that encodes it differently, like the logbook's links, which do not encode it. Compare the decoded paths, so that the URL follows the shown run there too. --------- Co-authored-by: Xyolyp <[email protected]>
164 lines
5.5 KiB
TypeScript
164 lines
5.5 KiB
TypeScript
import type { ReactiveControllerHost } from "lit";
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
import { TraceRunController } from "../../src/data/trace-run-controller";
|
|
|
|
// The trace pages rely on these rules to keep the shown run, the URL and the
|
|
// browser history in step while links, history steps, picks and responses
|
|
// overlap. Each case is a race that once showed the wrong run.
|
|
|
|
const PATH = "/config/script/trace/my_script";
|
|
|
|
const createHost = (): ReactiveControllerHost => ({
|
|
addController: vi.fn(),
|
|
removeController: vi.fn(),
|
|
requestUpdate: vi.fn(),
|
|
updateComplete: Promise.resolve(true),
|
|
});
|
|
|
|
const navigate = (url: string) => {
|
|
history.pushState(null, "", url);
|
|
window.dispatchEvent(new CustomEvent("location-changed"));
|
|
};
|
|
|
|
const historyStep = (url: string) => {
|
|
history.replaceState(null, "", url);
|
|
window.dispatchEvent(new PopStateEvent("popstate"));
|
|
};
|
|
|
|
describe("TraceRunController", () => {
|
|
let shownRunId: string | undefined;
|
|
let loadedRuns: string[];
|
|
let controller: TraceRunController;
|
|
|
|
beforeEach(() => {
|
|
history.replaceState(null, "", `${PATH}?run_id=a`);
|
|
shownRunId = "a";
|
|
loadedRuns = [];
|
|
controller = new TraceRunController(createHost(), {
|
|
tracePath: () => PATH,
|
|
shownRunId: () => shownRunId,
|
|
loadRun: (runId) => loadedRuns.push(runId),
|
|
});
|
|
controller.hostConnected();
|
|
});
|
|
|
|
afterEach(() => {
|
|
controller.hostDisconnected();
|
|
});
|
|
|
|
it("loads another run of the same page named by a link", () => {
|
|
navigate(`${PATH}?run_id=b`);
|
|
expect(loadedRuns).toEqual(["b"]);
|
|
});
|
|
|
|
it("follows browser back and forward like a link", () => {
|
|
historyStep(`${PATH}?run_id=b`);
|
|
expect(loadedRuns).toEqual(["b"]);
|
|
});
|
|
|
|
it("ignores other pages, entries without a run and the shown run", () => {
|
|
navigate("/config/script/trace/other_script?run_id=b");
|
|
navigate(PATH);
|
|
navigate(`${PATH}?run_id=a`);
|
|
expect(loadedRuns).toEqual([]);
|
|
});
|
|
|
|
it("does not load a run that is already on its way", () => {
|
|
controller.startListRequest("b");
|
|
navigate(`${PATH}?run_id=b`);
|
|
expect(loadedRuns).toEqual([]);
|
|
});
|
|
|
|
it("drops a pending link when the URL returns to the shown run", () => {
|
|
const request = controller.startListRequest("b");
|
|
historyStep(`${PATH}?run_id=a`);
|
|
expect(controller.isLatestListRequest(request)).toBe(false);
|
|
expect(controller.requestedRunId).toBeUndefined();
|
|
expect(loadedRuns).toEqual([]);
|
|
});
|
|
|
|
it("lets only the latest trace list request through", () => {
|
|
const first = controller.startListRequest();
|
|
const second = controller.startListRequest("b");
|
|
expect(controller.isLatestListRequest(first)).toBe(false);
|
|
expect(controller.isLatestListRequest(second)).toBe(true);
|
|
controller.endListRequest();
|
|
expect(controller.requestedRunId).toBeUndefined();
|
|
});
|
|
|
|
it("lets only the latest trace request through", () => {
|
|
const first = controller.startTraceRequest();
|
|
const second = controller.startTraceRequest();
|
|
expect(controller.isLatestTraceRequest(first)).toBe(false);
|
|
expect(controller.isLatestTraceRequest(second)).toBe(true);
|
|
});
|
|
|
|
it("cancels a pending link when a run is picked, but not a refresh", () => {
|
|
const link = controller.startListRequest("b");
|
|
controller.cancelLinkRequest();
|
|
expect(controller.isLatestListRequest(link)).toBe(false);
|
|
|
|
const refresh = controller.startListRequest();
|
|
controller.cancelLinkRequest();
|
|
expect(controller.isLatestListRequest(refresh)).toBe(true);
|
|
});
|
|
|
|
it("writes the shown run into the current history entry", () => {
|
|
history.replaceState(null, "", `${PATH}?run_id=a&more-info-entity-id=x`);
|
|
const entries = history.length;
|
|
controller.writeRunIdToUrl("c");
|
|
const params = new URLSearchParams(location.search);
|
|
expect(params.get("run_id")).toBe("c");
|
|
expect(params.get("more-info-entity-id")).toBe("x");
|
|
expect(history.length).toBe(entries);
|
|
});
|
|
|
|
it("leaves the URL of another page alone", () => {
|
|
history.replaceState(null, "", "/config/script/dashboard");
|
|
controller.writeRunIdToUrl("c");
|
|
expect(location.pathname + location.search).toBe(
|
|
"/config/script/dashboard"
|
|
);
|
|
});
|
|
|
|
it("stops following the URL once the page is gone", () => {
|
|
controller.hostDisconnected();
|
|
navigate(`${PATH}?run_id=b`);
|
|
expect(loadedRuns).toEqual([]);
|
|
});
|
|
|
|
describe("with an item id that needs encoding", () => {
|
|
// The automation id "kitchen:lights", which the route decodes from any of
|
|
// its encodings.
|
|
let automation: TraceRunController;
|
|
|
|
beforeEach(() => {
|
|
automation = new TraceRunController(createHost(), {
|
|
tracePath: () => "/config/automation/trace/kitchen%3Alights",
|
|
shownRunId: () => "a",
|
|
loadRun: (runId) => loadedRuns.push(runId),
|
|
});
|
|
automation.hostConnected();
|
|
});
|
|
|
|
afterEach(() => {
|
|
automation.hostDisconnected();
|
|
});
|
|
|
|
it("follows links that encode the id differently or not at all", () => {
|
|
navigate("/config/automation/trace/kitchen:lights?run_id=b");
|
|
navigate("/config/automation/trace/kitchen%3alights?run_id=c");
|
|
automation.writeRunIdToUrl("d");
|
|
expect(loadedRuns).toEqual(["b", "c"]);
|
|
expect(location.pathname + location.search).toBe(
|
|
"/config/automation/trace/kitchen%3alights?run_id=d"
|
|
);
|
|
});
|
|
|
|
it("ignores a path that cannot be decoded", () => {
|
|
navigate("/config/automation/trace/kitchen%3?run_id=b");
|
|
expect(loadedRuns).toEqual([]);
|
|
});
|
|
});
|
|
});
|