diff --git a/web/package/agama-web-ui.changes b/web/package/agama-web-ui.changes index f59e102936..c02d818006 100644 --- a/web/package/agama-web-ui.changes +++ b/web/package/agama-web-ui.changes @@ -1,3 +1,10 @@ +------------------------------------------------------------------- +Mon Nov 25 11:12:36 UTC 2024 - David Diaz + +- Unify Install and "warning" header buttons. +- Move installation issues to a drawer shown when Install button + is clicked (gh#agama-project#agama#1778). + ------------------------------------------------------------------- Fri Nov 15 16:48:44 UTC 2024 - Ladislav Slezák diff --git a/web/src/assets/styles/app.scss b/web/src/assets/styles/app.scss index 04d35448a9..c1ca3baa64 100644 --- a/web/src/assets/styles/app.scss +++ b/web/src/assets/styles/app.scss @@ -62,7 +62,38 @@ button.remove-link:hover { right: 0; bottom: 0; left: 0; - z-index: 999; } } } + +.agama-install-button { + padding: var(--pf-v5-global--spacer--sm) var(--pf-v5-global--spacer--md); + font-size: var(--fs-large); +} + +.agama-issues-mark { + background: white; + width: 24px; + height: 24px; + border-radius: 24px; + top: -7px; + right: -7px; + position: absolute; + display: flex; + align-content: center; + justify-content: center; +} + +.agama-issues-drawer-body { + padding: var(--pf-v5-global--spacer--lg); + + h4 a { + text-decoration: underline; + font-weight: var(--fw-bold); + } + + ul li.pf-m-info, + ul li.pf-m-warning { + --pf-v5-c-notification-drawer__list-item--before--BackgroundColor: none; + } +} diff --git a/web/src/assets/styles/patternfly-overrides.scss b/web/src/assets/styles/patternfly-overrides.scss index 2c6ab76be7..6cafb3e554 100644 --- a/web/src/assets/styles/patternfly-overrides.scss +++ b/web/src/assets/styles/patternfly-overrides.scss @@ -347,3 +347,12 @@ inline-size: 250px; } } + +// A temporary workaround to fix "stacking contexts" problems with scroll and +// sticky page sections in Agama layout. It will not be needed when migrating to +// latest PF6 release, since the root of the problem has been addressed there by +// removing the DrawerContentBody from the Page component. See +// https://github.com/patternfly/patternfly/pull/7130 and related links +.pf-v5-c-drawer__body { + display: contents; +} diff --git a/web/src/components/core/InstallButton.test.tsx b/web/src/components/core/InstallButton.test.tsx index 965660de11..452b58f5e9 100644 --- a/web/src/components/core/InstallButton.test.tsx +++ b/web/src/components/core/InstallButton.test.tsx @@ -67,9 +67,23 @@ describe("InstallButton", () => { ); }); - it("renders nothing", () => { + it("renders additional information to warn users about found problems", () => { const { container } = installerRender(); - expect(container).toBeEmptyDOMElement(); + const button = screen.getByRole("button", { name: /Install/ }); + // An exlamation icon as visual mark + const icon = container.querySelector("svg"); + expect(icon).toHaveAttribute("data-icon-name", "exclamation"); + // An aria-label for users using an screen reader + within(button).getByLabelText(/Not possible with the current setup/); + }); + + it("triggers the onClickWithIssues callback without rendering the confirmation dialog", async () => { + const onClickWithIssuesFn = jest.fn(); + const { user } = installerRender(); + const button = screen.getByRole("button", { name: /Install/ }); + await user.click(button); + expect(onClickWithIssuesFn).toHaveBeenCalled(); + await waitFor(() => expect(screen.queryByRole("dialog")).not.toBeInTheDocument()); }); }); @@ -78,16 +92,21 @@ describe("InstallButton", () => { mockIssuesList = new IssuesList([], [], [], []); }); - it("renders an Install button", () => { - installerRender(); - screen.getByRole("button", { name: "Install" }); + it("renders the button without any additional information", () => { + const { container } = installerRender(); + const button = screen.getByRole("button", { name: "Install" }); + // Renders nothing else + const icon = container.querySelector("svg"); + expect(icon).toBeNull(); + expect(within(button).queryByLabelText(/Not possible with the current setup/)).toBeNull(); }); - it("renders a confirmation dialog when clicked", async () => { - const { user } = installerRender(); + it("renders a confirmation dialog when clicked without triggering the onClickWithIssues callback", async () => { + const onClickWithIssuesFn = jest.fn(); + const { user } = installerRender(); const button = await screen.findByRole("button", { name: "Install" }); await user.click(button); - + expect(onClickWithIssuesFn).not.toHaveBeenCalled(); screen.getByRole("dialog", { name: "Confirm Installation" }); }); diff --git a/web/src/components/core/InstallButton.tsx b/web/src/components/core/InstallButton.tsx index 5d483d0360..7978a63c13 100644 --- a/web/src/components/core/InstallButton.tsx +++ b/web/src/components/core/InstallButton.tsx @@ -21,15 +21,14 @@ */ import React, { useState } from "react"; - import { Button, ButtonProps, Stack } from "@patternfly/react-core"; - import { Popup } from "~/components/core"; -import { _ } from "~/i18n"; import { startInstallation } from "~/api/manager"; import { useAllIssues } from "~/queries/issues"; import { useLocation } from "react-router-dom"; import { PRODUCT, ROOT } from "~/routes/paths"; +import { _ } from "~/i18n"; +import { Icon } from "../layout"; /** * List of paths where the InstallButton must not be shown. @@ -81,14 +80,17 @@ according to the provided installation settings.", * When clicked, it will ask for a confirmation before triggering the request * for starting the installation. */ -const InstallButton = (props: Omit) => { +const InstallButton = ( + props: Omit & { onClickWithIssues?: () => void }, +) => { const issues = useAllIssues(); const [isOpen, setIsOpen] = useState(false); const location = useLocation(); + const hasIssues = !issues.isEmpty; - if (!issues.isEmpty) return; if (EXCLUDED_FROM.includes(location.pathname)) return; + const { onClickWithIssues, ...buttonProps } = props; const open = async () => setIsOpen(true); const close = () => setIsOpen(false); const onAccept = () => { @@ -96,11 +98,26 @@ const InstallButton = (props: Omit) => { startInstallation(); }; + // TRANSLATORS: The install button label + const buttonText = _("Install"); + // TRANSLATORS: Accessible text included with the install button when there are issues + const withIssuesAriaLabel = _("Not possible with the current setup. Click to know more."); + return ( <> - {isOpen && } diff --git a/web/src/components/core/IssuesDrawer.test.tsx b/web/src/components/core/IssuesDrawer.test.tsx new file mode 100644 index 0000000000..ea720364cf --- /dev/null +++ b/web/src/components/core/IssuesDrawer.test.tsx @@ -0,0 +1,130 @@ +/* + * Copyright (c) [2024] SUSE LLC + * + * All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, contact SUSE LLC. + * + * To contact SUSE LLC about this file by physical or electronic mail, you may + * find current contact information at www.suse.com. + */ + +import React from "react"; +import { screen, within } from "@testing-library/react"; +import { installerRender } from "~/test-utils"; +import { InstallationPhase } from "~/types/status"; +import { IssuesList } from "~/types/issues"; +import IssuesDrawer from "./IssuesDrawer"; + +let phase = InstallationPhase.Config; +let mockIssuesList: IssuesList; +const onCloseFn = jest.fn(); + +jest.mock("~/queries/issues", () => ({ + ...jest.requireActual("~/queries/issues"), + useAllIssues: () => mockIssuesList, +})); + +jest.mock("~/queries/status", () => ({ + useInstallerStatus: () => ({ + phase, + }), +})); + +const itRendersNothing = () => + it("renders nothing", () => { + const { container } = installerRender(); + expect(container).toBeEmptyDOMElement(); + }); + +describe("IssuesDrawer", () => { + describe("when there are no installation issues", () => { + beforeEach(() => { + mockIssuesList = new IssuesList([], [], [], []); + }); + + itRendersNothing(); + }); + + describe("when there are installation issues", () => { + beforeEach(() => { + mockIssuesList = new IssuesList( + [], + [ + { + description: "Software Fake Issue", + source: 0, + severity: 0, + details: "Software Fake Issue details", + }, + ], + [ + { + description: "Storage Fake Issue 1", + source: 0, + severity: 0, + details: "Storage Fake Issue 1 details", + }, + { + description: "Storage Fake Issue 2", + source: 0, + severity: 0, + details: "Storage Fake Issue 2 details", + }, + ], + [ + { + description: "Users Fake Issue", + source: 0, + severity: 0, + details: "Users Fake Issue details", + }, + ], + ); + }); + + it("renders the drawer with categorized issues linking to their scope", async () => { + const { user } = installerRender(); + + const softwareIssues = screen.getByRole("region", { name: "Software" }); + const storageIssues = screen.getByRole("region", { name: "Storage" }); + const usersIssues = screen.getByRole("region", { name: "Users" }); + + const softwareLink = within(softwareIssues).getByRole("link", { name: "Software" }); + expect(softwareLink).toHaveAttribute("href", "/software"); + within(softwareIssues).getByText("Software Fake Issue"); + + const storageLink = within(storageIssues).getByRole("link", { name: "Storage" }); + expect(storageLink).toHaveAttribute("href", "/storage"); + within(storageIssues).getByText("Storage Fake Issue 1"); + within(storageIssues).getByText("Storage Fake Issue 2"); + + const usersLink = within(usersIssues).getByRole("link", { name: "Users" }); + expect(usersLink).toHaveAttribute("href", "/users"); + within(usersIssues).getByText("Users Fake Issue"); + + const closeButton = screen.getByRole("button", { name: "Close" }); + await user.click(closeButton); + expect(onCloseFn).toHaveBeenCalled(); + }); + + describe("at install phase", () => { + beforeEach(() => { + phase = InstallationPhase.Install; + }); + + itRendersNothing(); + }); + }); +}); diff --git a/web/src/components/core/IssuesDrawer.tsx b/web/src/components/core/IssuesDrawer.tsx new file mode 100644 index 0000000000..94e94c923b --- /dev/null +++ b/web/src/components/core/IssuesDrawer.tsx @@ -0,0 +1,104 @@ +/* + * Copyright (c) [2024] SUSE LLC + * + * All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, contact SUSE LLC. + * + * To contact SUSE LLC about this file by physical or electronic mail, you may + * find current contact information at www.suse.com. + */ + +import React, { forwardRef } from "react"; +import { + HelperText, + HelperTextItem, + NotificationDrawer, + NotificationDrawerBody, + NotificationDrawerHeader, + Stack, +} from "@patternfly/react-core"; +import Link from "~/components/core/Link"; +import { useAllIssues } from "~/queries/issues"; +import { useInstallerStatus } from "~/queries/status"; +import { IssueSeverity } from "~/types/issues"; +import { InstallationPhase } from "~/types/status"; +import { _ } from "~/i18n"; + +/** + * Drawer for displaying installation issues + */ +const IssuesDrawer = forwardRef(({ onClose }: { onClose: () => void }, ref) => { + const issues = useAllIssues(); + const { phase } = useInstallerStatus({ suspense: true }); + const { issues: issuesByScope } = issues; + + // FIXME: share below headers with navigation menu + const scopeHeaders = { + users: _("Users"), + storage: _("Storage"), + software: _("Software"), + }; + + if (issues.isEmpty || phase === InstallationPhase.Install) return; + + return ( + + + + +

+ {_( + "Current settings do not meet the requirements for installing the product. You can click on each section's name to access the relevant page and adjust the settings as needed.", + )} +

+ {Object.entries(issuesByScope).map(([scope, issues], idx) => { + if (issues.length === 0) return null; + const ariaLabelId = `${scope}-issues-section`; + + return ( +
+ +

+ + {scopeHeaders[scope]} + +

+
    + {issues.map((issue, subIdx) => { + const variant = issue.severity === IssueSeverity.Error ? "warning" : "info"; + + return ( +
  • + + {/** @ts-expect-error TS complain about variant, let's fix it after PF6 migration */} + + {issue.description} + + +
  • + ); + })} +
+
+
+ ); + })} +
+
+
+ ); +}); + +export default IssuesDrawer; diff --git a/web/src/components/core/IssuesLink.test.tsx b/web/src/components/core/IssuesLink.test.tsx deleted file mode 100644 index 5416526fb6..0000000000 --- a/web/src/components/core/IssuesLink.test.tsx +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (c) [2024] SUSE LLC - * - * All Rights Reserved. - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation; either version 2 of the License, or (at your option) - * any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, contact SUSE LLC. - * - * To contact SUSE LLC about this file by physical or electronic mail, you may - * find current contact information at www.suse.com. - */ - -import React from "react"; -import { screen } from "@testing-library/react"; -import { installerRender, mockRoutes } from "~/test-utils"; -import { IssuesLink } from "~/components/core"; -import { IssuesList } from "~/types/issues"; -import { PRODUCT as PATHS } from "~/routes/paths"; - -const mockStartInstallationFn = jest.fn(); -let mockIssuesList: IssuesList; - -jest.mock("~/api/manager", () => ({ - ...jest.requireActual("~/api/manager"), - startInstallation: () => mockStartInstallationFn(), -})); - -jest.mock("~/queries/issues", () => ({ - ...jest.requireActual("~/queries/issues"), - useAllIssues: () => mockIssuesList, -})); - -describe("when there are installation issues", () => { - beforeEach(() => { - mockIssuesList = new IssuesList( - [ - { - description: "Fake Issue", - source: 0, - severity: 0, - details: "Fake Issue details", - }, - ], - [], - [], - [], - ); - }); - - it("renders the issues link", () => { - installerRender(); - screen.getByRole("link", { name: "Installation issues" }); - }); - - describe("but installer is rendering the product selection", () => { - beforeEach(() => { - mockRoutes(PATHS.changeProduct); - }); - - it("renders nothing", () => { - const { container } = installerRender(); - expect(container).toBeEmptyDOMElement(); - }); - }); - - describe("but installer is configuring the product", () => { - beforeEach(() => { - mockRoutes(PATHS.progress); - }); - - it("renders nothing", () => { - const { container } = installerRender(); - expect(container).toBeEmptyDOMElement(); - }); - }); -}); - -describe("when there are no installation issues", () => { - beforeEach(() => { - mockIssuesList = new IssuesList([], [], [], []); - }); - - it("renders nothing", () => { - const { container } = installerRender(); - expect(container).toBeEmptyDOMElement(); - }); -}); diff --git a/web/src/components/core/IssuesLink.tsx b/web/src/components/core/IssuesLink.tsx deleted file mode 100644 index 4aaf88de53..0000000000 --- a/web/src/components/core/IssuesLink.tsx +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (c) [2024] SUSE LLC - * - * All Rights Reserved. - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation; either version 2 of the License, or (at your option) - * any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, contact SUSE LLC. - * - * To contact SUSE LLC about this file by physical or electronic mail, you may - * find current contact information at www.suse.com. - */ - -import React from "react"; -import { useLocation } from "react-router-dom"; -import { useAllIssues } from "~/queries/issues"; -import Link, { LinkProps } from "~/components/core/Link"; -import { Icon } from "../layout"; -import { Tooltip } from "@patternfly/react-core"; -import { PRODUCT, ROOT } from "~/routes/paths"; -import { _ } from "~/i18n"; - -/** - * Installation issues link - * - * As a counterpart of the InstallButton, it shows a button with a warning icon - * when the installation is not possible because there are installation issues. - */ -const IssuesLink = (props: Omit) => { - const issues = useAllIssues(); - const location = useLocation(); - - if (issues.isEmpty) return; - // Do not show the button if the user is about to change the product or the - // installer is configuring a product. - if ([PRODUCT.changeProduct, PRODUCT.progress].includes(location.pathname)) return; - - return ( - - - - - - ); -}; - -export default IssuesLink; diff --git a/web/src/components/core/index.js b/web/src/components/core/index.js index 781be69513..7ce2bff886 100644 --- a/web/src/components/core/index.js +++ b/web/src/components/core/index.js @@ -32,7 +32,6 @@ export { default as EmailInput } from "./EmailInput"; export { default as InstallationFinished } from "./InstallationFinished"; export { default as InstallationProgress } from "./InstallationProgress"; export { default as InstallButton } from "./InstallButton"; -export { default as IssuesLink } from "./IssuesLink"; export { default as IssuesHint } from "./IssuesHint"; export { default as SectionSkeleton } from "./SectionSkeleton"; export { default as ListSearch } from "./ListSearch"; @@ -52,4 +51,5 @@ export { default as TreeTable } from "./TreeTable"; export { default as Link } from "./Link"; export { default as EmptyState } from "./EmptyState"; export { default as InstallerOptions } from "./InstallerOptions"; +export { default as IssuesDrawer } from "./IssuesDrawer"; export { default as Drawer } from "./Drawer"; diff --git a/web/src/components/layout/Header.test.tsx b/web/src/components/layout/Header.test.tsx index cd2e5c7c8e..1ae19803fc 100644 --- a/web/src/components/layout/Header.test.tsx +++ b/web/src/components/layout/Header.test.tsx @@ -42,6 +42,7 @@ let phase: InstallationPhase; let isBusy: boolean; jest.mock("~/components/core/InstallerOptions", () => () =>
Installer Options Mock
); +jest.mock("~/components/core/InstallButton", () => () =>
Install Button Mock
); jest.mock("~/queries/software", () => ({ useProduct: () => ({ @@ -57,11 +58,6 @@ jest.mock("~/queries/status", () => ({ }), })); -jest.mock("~/queries/issues", () => ({ - ...jest.requireActual("~/queries/issues"), - useAllIssues: () => ({ isEmpty: true }), -})); - const doesNotRenderInstallerL10nOptions = () => it("does not render the installer localization options", async () => { const { user } = installerRender(
); @@ -86,6 +82,11 @@ describe("Header", () => { expect(screen.queryByRole("heading", { name: tumbleweed.name, level: 1 })).toBeNull(); }); + it("mounts the Install button", () => { + installerRender(
); + screen.getByText("Install Button Mock"); + }); + it("renders an options dropdown", async () => { const { user } = installerRender(
); expect(screen.queryByRole("menu")).toBeNull(); diff --git a/web/src/components/layout/Header.tsx b/web/src/components/layout/Header.tsx index fdd229c31a..a4cbfa9b02 100644 --- a/web/src/components/layout/Header.tsx +++ b/web/src/components/layout/Header.tsx @@ -45,9 +45,9 @@ import { useProduct } from "~/queries/software"; import { _ } from "~/i18n"; import { InstallationPhase } from "~/types/status"; import { useInstallerStatus } from "~/queries/status"; -import { InstallButton, InstallerOptions, IssuesLink } from "~/components/core"; +import { InstallButton, InstallerOptions } from "~/components/core"; import { useLocation } from "react-router-dom"; -import { ROOT as PATHS } from "~/routes/paths"; +import { ROOT } from "~/routes/paths"; export type HeaderProps = { /** Whether the application sidebar should be mounted or not */ @@ -58,6 +58,8 @@ export type HeaderProps = { showInstallerOptions?: boolean; /** The background color for the top bar */ background?: MastheadProps["backgroundColor"]; + /** Callback to be triggered for toggling the IssuesDrawer visibility */ + toggleIssuesDrawer?: () => void; }; const OptionsDropdown = ({ showInstallerOptions }) => { @@ -88,7 +90,7 @@ const OptionsDropdown = ({ showInstallerOptions }) => { )} > - + {_("Download logs")} {showInstallerOptions && ( @@ -120,6 +122,7 @@ export default function Header({ showSidebarToggle = true, showProductName = true, background = "dark", + toggleIssuesDrawer, }: HeaderProps): React.ReactNode { const location = useLocation(); const { selectedProduct } = useProduct(); @@ -150,9 +153,8 @@ export default function Header({ - - - + + diff --git a/web/src/components/layout/Icon.tsx b/web/src/components/layout/Icon.tsx index 6612381b44..cbdc3ce110 100644 --- a/web/src/components/layout/Icon.tsx +++ b/web/src/components/layout/Icon.tsx @@ -38,6 +38,7 @@ import Downloading from "@icons/downloading.svg?component"; import Edit from "@icons/edit.svg?component"; import EditSquare from "@icons/edit_square.svg?component"; import Error from "@icons/error.svg?component"; +import Exclamation from "@icons/exclamation.svg?component"; import ExpandAll from "@icons/expand_all.svg?component"; import ExpandCircleDown from "@icons/expand_circle_down.svg?component"; import Feedback from "@icons/feedback.svg?component"; @@ -102,6 +103,7 @@ const icons = { edit: Edit, edit_square: EditSquare, error: Error, + exclamation: Exclamation, expand_all: ExpandAll, expand_circle_down: ExpandCircleDown, feedback: Feedback, diff --git a/web/src/components/layout/Layout.tsx b/web/src/components/layout/Layout.tsx index 16c1b4e28b..a90f83cb1c 100644 --- a/web/src/components/layout/Layout.tsx +++ b/web/src/components/layout/Layout.tsx @@ -20,11 +20,12 @@ * find current contact information at www.suse.com. */ -import React, { Suspense } from "react"; +import React, { Suspense, useState } from "react"; import { Outlet } from "react-router-dom"; import { Page } from "@patternfly/react-core"; import Header, { HeaderProps } from "~/components/layout/Header"; import { Loading, Sidebar } from "~/components/layout"; +import { IssuesDrawer } from "~/components/core"; type LayoutProps = React.PropsWithChildren<{ mountHeader?: boolean; @@ -35,6 +36,8 @@ type LayoutProps = React.PropsWithChildren<{ /** * Component for laying out the application content inside a PF/Page that might * or might not mount a header and a sidebar depending on the given props. + * + * FIXME: move the focus to the notification drawer when it is open */ const Layout = ({ mountHeader = true, @@ -42,11 +45,25 @@ const Layout = ({ headerOptions = {}, children, }: LayoutProps) => { + const [issuesDrawerVisible, setIssuesDrawerVisible] = useState(false); + const closeIssuesDrawer = () => setIssuesDrawerVisible(false); + const toggleIssuesDrawer = () => setIssuesDrawerVisible(!issuesDrawerVisible); + return ( } + header={ + mountHeader && ( +
+ ) + } sidebar={mountSidebar && } + notificationDrawer={} + isNotificationDrawerExpanded={issuesDrawerVisible} > }>{children || } diff --git a/web/src/components/overview/OverviewPage.test.tsx b/web/src/components/overview/OverviewPage.test.tsx index c025225506..2cef9ae6b6 100644 --- a/web/src/components/overview/OverviewPage.test.tsx +++ b/web/src/components/overview/OverviewPage.test.tsx @@ -22,81 +22,18 @@ import React from "react"; import { screen } from "@testing-library/react"; -import { installerRender } from "~/test-utils"; +import { plainRender } from "~/test-utils"; import { OverviewPage } from "~/components/overview"; -import { IssuesList } from "~/types/issues"; -import { Product } from "~/types/software"; - -const tumbleweed: Product = { - id: "Tumbleweed", - name: "openSUSE Tumbleweed", - icon: "tumbleweed.svg", - description: "Tumbleweed description...", -}; - -let mockIssuesList: IssuesList = new IssuesList([], [], [], []); - -jest.mock("~/queries/software", () => ({ - ...jest.requireActual("~/queries/software"), - useProduct: () => ({ selectedProduct: tumbleweed }), - useProductChanges: () => jest.fn(), -})); - -jest.mock("~/queries/issues", () => ({ - ...jest.requireActual("~/queries/issues"), - useIssuesChanges: () => jest.fn().mockResolvedValue(mockIssuesList), - useAllIssues: () => mockIssuesList, -})); jest.mock("~/components/overview/L10nSection", () => () =>
Localization Section
); jest.mock("~/components/overview/StorageSection", () => () =>
Storage Section
); jest.mock("~/components/overview/SoftwareSection", () => () =>
Software Section
); -jest.mock("~/components/core/InstallButton", () => () =>
Install Button
); describe("when a product is selected", () => { - it("renders the overview page content and the Install button", async () => { - installerRender(); - screen.findByText("Localization Section"); - screen.findByText("Storage Section"); - screen.findByText("Software Section"); - screen.findByText("Install Button"); - }); - - it("renders found issues, if any", () => {}); -}); - -describe("when there are issues", () => { - beforeEach(() => { - mockIssuesList = new IssuesList( - [ - { - description: "Fake Issue", - details: "Fake Issue details", - source: 0, - severity: 1, - }, - ], - [], - [], - [], - ); - }); - - it("renders the issues section", () => { - installerRender(); - screen.findByText("Installation blocking issues"); - screen.findByText("Fake Issue"); - screen.findByText("Fake Issue details"); - }); -}); - -describe("when there are no issues", () => { - beforeEach(() => { - mockIssuesList = new IssuesList([], [], [], []); - }); - - it("does not render the issues section", () => { - installerRender(); - expect(screen.queryByText("Installation blocking issues")).toBeNull(); + it("renders the overview page content", async () => { + plainRender(); + await screen.findByText("Localization Section"); + await screen.findByText("Storage Section"); + await screen.findByText("Software Section"); }); }); diff --git a/web/src/components/overview/OverviewPage.tsx b/web/src/components/overview/OverviewPage.tsx index f72e598b25..ecbe0c5951 100644 --- a/web/src/components/overview/OverviewPage.tsx +++ b/web/src/components/overview/OverviewPage.tsx @@ -21,76 +21,12 @@ */ import React from "react"; -import { - Grid, - GridItem, - Hint, - HintBody, - NotificationDrawer, - NotificationDrawerBody, - NotificationDrawerList, - NotificationDrawerListItem, - NotificationDrawerListItemBody, - NotificationDrawerListItemHeader, - Stack, -} from "@patternfly/react-core"; -import { Link } from "react-router-dom"; +import { Grid, GridItem, Hint, HintBody, Stack } from "@patternfly/react-core"; import { Page } from "~/components/core"; import L10nSection from "./L10nSection"; import StorageSection from "./StorageSection"; import SoftwareSection from "./SoftwareSection"; import { _ } from "~/i18n"; -import { useAllIssues } from "~/queries/issues"; -import { IssuesList as IssuesListType, IssueSeverity } from "~/types/issues"; - -const IssuesList = ({ issues }: { issues: IssuesListType }) => { - const scopeHeaders = { - users: _("Users"), - storage: _("Storage"), - software: _("Software"), - }; - - const { issues: issuesByScope } = issues; - const list = []; - Object.entries(issuesByScope).forEach(([scope, issues], idx) => { - issues.forEach((issue, subIdx) => { - const variant = issue.severity === IssueSeverity.Error ? "warning" : "info"; - - const link = ( - - - - {issue.description} - - - ); - list.push(link); - }); - }); - - return ( - - - {list} - - - ); -}; - -const IssuesSection = ({ issues }: { issues: IssuesListType }) => { - return ( - - - - ); -}; const OverviewSection = () => ( ( ); export default function OverviewPage() { - const issues = useAllIssues(); - return ( @@ -123,14 +57,9 @@ export default function OverviewPage() { - + - {!issues.isEmpty && ( - - - - )}