diff --git a/web/package/agama-web-ui.changes b/web/package/agama-web-ui.changes index 4bb4b07420..bbfb9dec5e 100644 --- a/web/package/agama-web-ui.changes +++ b/web/package/agama-web-ui.changes @@ -1,3 +1,8 @@ +------------------------------------------------------------------- +Wed Oct 23 16:26:29 UTC 2024 - David Diaz + +- Fix the link to download the logs (gh#agama-project/agama#1694). + ------------------------------------------------------------------- Wed Oct 23 15:26:29 UTC 2024 - Imobach Gonzalez Sosa diff --git a/web/src/components/core/LogsButton.test.tsx b/web/src/components/core/LogsButton.test.tsx index b1b7d5101a..37827da2ee 100644 --- a/web/src/components/core/LogsButton.test.tsx +++ b/web/src/components/core/LogsButton.test.tsx @@ -25,8 +25,6 @@ import { screen } from "@testing-library/react"; import { installerRender } from "~/test-utils"; import { LogsButton } from "~/components/core"; -const originalCreateElement = document.createElement; - const executor = jest.fn(); const fetchLogsFn = jest.fn(); @@ -50,102 +48,8 @@ afterAll(() => { }); describe("LogsButton", () => { - it("renders a button for downloading logs", () => { + it("renders a link for downloading logs", () => { installerRender(); - screen.getByRole("button", { name: "Download logs" }); - }); - - describe("when user clicks on it", () => { - it("inits download logs process", async () => { - const { user } = installerRender(); - const button = screen.getByRole("button", { name: "Download logs" }); - await user.click(button); - expect(fetchLogsFn).toHaveBeenCalled(); - }); - - it("changes button text, puts it as disabled, and displays an informative alert", async () => { - const { user } = installerRender(); - - const button = screen.getByRole("button", { name: "Download logs" }); - expect(button).not.toHaveAttribute("disabled"); - - await user.click(button); - - expect(button.innerHTML).not.toContain("Download logs"); - expect(button.innerHTML).toContain("Collecting logs..."); - expect(button).toHaveAttribute("disabled"); - - const info = screen.queryByRole("heading", { name: /.*logs download as soon as.*/i }); - const warning = screen.queryByRole("heading", { name: /.*went wrong*/i }); - - expect(info).toBeInTheDocument(); - expect(warning).not.toBeInTheDocument(); - }); - - describe("and logs are collected successfully", () => { - beforeEach(() => { - fetchLogsFn.mockResolvedValue({ - blob: jest.fn().mockResolvedValue(new Blob(["testing"])), - }); - }); - - it("triggers the download", async () => { - const { user } = installerRender(); - - // Ugly mocking needed here. - // Improvements are wanted and welcome. - // NOTE: document.createElement cannot mocked in beforeAll because it breaks all testsuite - // since its used internally by jsdom. Simply spying it is not enough because we want to - // mock only the call to the HTMLAnchorElement creation that happens when user clicks on the - // "Download logs". - // @ts-expect-error - document.originalCreateElement = originalCreateElement; - - const anchorMock = document.createElement("a"); - anchorMock.setAttribute = jest.fn(); - anchorMock.click = jest.fn(); - - jest.spyOn(document, "createElement").mockImplementation((tag) => { - // @ts-expect-error - return tag === "a" ? anchorMock : document.originalCreateElement(tag); - }); - - // Now, let's simulate the "Download logs" user click - const button = screen.getByRole("button", { name: "Download logs" }); - await user.click(button); - - // And test what we're looking for - expect(document.createElement).toHaveBeenCalledWith("a"); - expect(anchorMock).toHaveAttribute("href", "fake-blob-url"); - expect(anchorMock).toHaveAttribute( - "download", - expect.stringMatching(/agama-installation-logs/), - ); - expect(anchorMock.click).toHaveBeenCalled(); - - // Be polite and restore document.createElement function, - // although it should be done by the call to jest.restoreAllMocks() - // in the afterAll block - document.createElement = originalCreateElement; - }); - }); - - describe("but the process fails", () => { - beforeEach(() => { - fetchLogsFn.mockRejectedValue("Sorry, something went wrong"); - }); - - it("displays a warning alert along with the Download logs button", async () => { - const { user } = installerRender(); - - const button = screen.getByRole("button", { name: "Download logs" }); - expect(button).not.toHaveAttribute("disabled"); - - await user.click(button); - - expect(button.innerHTML).toContain("Download logs"); - screen.getByRole("heading", { name: /.*went wrong.*try again.*/i }); - }); - }); + screen.getByRole("link", { name: "Download logs" }); }); }); diff --git a/web/src/components/core/LogsButton.tsx b/web/src/components/core/LogsButton.tsx index e096c14ef9..0428c0da84 100644 --- a/web/src/components/core/LogsButton.tsx +++ b/web/src/components/core/LogsButton.tsx @@ -20,111 +20,17 @@ * find current contact information at www.suse.com. */ -import React, { useState } from "react"; -import { Alert, Button, ButtonProps } from "@patternfly/react-core"; -import { Popup } from "~/components/core"; +import React from "react"; import { _ } from "~/i18n"; -import { useCancellablePromise } from "~/utils"; -import { fetchLogs } from "~/api/manager"; - -const FILENAME = "agama-installation-logs.tar.gz"; /** * Button for collecting and downloading Agama/YaST logs */ -const LogsButton = (props: ButtonProps) => { - const { cancellablePromise } = useCancellablePromise(); - const [error, setError] = useState(null); - const [isCollecting, setIsCollecting] = useState(false); - - /** - * Helper function for triggering the download automatically - * - * @note Based on the article "Programmatic file downloads in the browser" found at - * https://blog.logrocket.com/programmatic-file-downloads-in-the-browser-9a5186298d5c - * - * @param {string} url - the file location to download from - */ - const autoDownload = (url: string) => { - const a = document.createElement("a"); - a.href = url; - a.download = FILENAME; - - // Click handler that releases the object URL after the element has been clicked - // This is required to let the browser know not to keep the reference to the file any longer - // See https://developer.mozilla.org/en-US/docs/Web/API/URL/revokeObjectURL - const clickHandler = () => { - setTimeout(() => { - URL.revokeObjectURL(url); - a.removeEventListener("click", clickHandler); - }, 150); - }; - - // Add the click event listener on the anchor element - a.addEventListener("click", clickHandler, false); - - // Programmatically trigger a click on the anchor element - // Needed for make the download to happen automatically without attaching the anchor element to - // the DOM - a.click(); - }; - - const collectAndDownload = () => { - setError(null); - setIsCollecting(true); - cancellablePromise(fetchLogs().then((response) => response.blob())) - .then(URL.createObjectURL) - .then(autoDownload) - .catch((error) => { - console.error(error); - setError(true); - }) - .finally(() => setIsCollecting(false)); - }; - - const close = () => setError(false); - +const LogsButton = () => { return ( - <> - - - - {isCollecting && ( - - )} - - {error && ( - - )} - - - {_("Close")} - - - - + + {_("Download logs")} + ); };