From a3e39177b0239873c179529366c26008ded66e6e Mon Sep 17 00:00:00 2001 From: Nick De Villiers Date: Thu, 20 Oct 2022 11:42:27 +0100 Subject: [PATCH] feat(vault): Display warning icon next to controller nav link if not all region controllers are configured with Vault (#4504) - Add warning icon next to controller nav link if not all region controllers are configured with Vault - Add test to check if icon is displayed or not --- .../base/components/Header/Header.test.tsx | 34 ++++++++++ src/app/base/components/Header/Header.tsx | 62 ++++++++++++++++--- src/app/base/components/Header/_index.scss | 4 ++ 3 files changed, 92 insertions(+), 8 deletions(-) diff --git a/src/app/base/components/Header/Header.test.tsx b/src/app/base/components/Header/Header.test.tsx index e1693f5a14..fe723f4180 100644 --- a/src/app/base/components/Header/Header.test.tsx +++ b/src/app/base/components/Header/Header.test.tsx @@ -16,6 +16,8 @@ import { authState as authStateFactory, config as configFactory, configState as configStateFactory, + controller as controllerFactory, + controllerState as controllerStateFactory, rootState as rootStateFactory, user as userFactory, userState as userStateFactory, @@ -39,6 +41,9 @@ beforeEach(() => { ], loaded: true, }), + controller: controllerStateFactory({ + items: [controllerFactory()], + }), user: userStateFactory({ auth: authStateFactory({ user: userFactory({ @@ -218,6 +223,35 @@ it("highlights sub-urls", () => { expect(currentMenuItem).toHaveTextContent("Machines"); }); +it("displays a warning icon next to controllers if vault is not fully configured", () => { + state.controller.items = [ + controllerFactory({ vault_configured: true }), + controllerFactory({ vault_configured: false }), + ]; + renderWithBrowserRouter(
, { route: "/", wrapperProps: { state } }); + + const controllerLink = screen.getByRole("link", { + name: "warning Controllers", + }); + const warningIcon = within(controllerLink).getByTestId("warning-icon"); + expect(warningIcon).toHaveClass( + "p-navigation--item-icon p-icon--security-warning-grey" + ); +}); + +it("does not display a warning icon next to controllers if vault is fully configured", () => { + state.controller.items = [ + controllerFactory({ vault_configured: true }), + controllerFactory({ vault_configured: true }), + ]; + renderWithBrowserRouter(
, { route: "/", wrapperProps: { state } }); + + const controllerLink = screen.getByRole("link", { name: "Controllers" }); + expect( + within(controllerLink).queryByTestId("warning-icon") + ).not.toBeInTheDocument(); +}); + it("links from the logo to the dashboard for admins", () => { state.user.auth.user = userFactory({ is_superuser: true }); renderWithBrowserRouter(
, { diff --git a/src/app/base/components/Header/Header.tsx b/src/app/base/components/Header/Header.tsx index 436eae5472..c2e6d6e2e5 100644 --- a/src/app/base/components/Header/Header.tsx +++ b/src/app/base/components/Header/Header.tsx @@ -3,6 +3,7 @@ import { useEffect, useContext } from "react"; import type { NavLink } from "@canonical/react-components"; import { + Icon, isNavigationButton, Theme, Navigation, @@ -26,6 +27,9 @@ import ThemePreviewContext from "app/base/theme-preview-context"; import urls from "app/base/urls"; import authSelectors from "app/store/auth/selectors"; import configSelectors from "app/store/config/selectors"; +import { actions as controllerActions } from "app/store/controller"; +import controllerSelectors from "app/store/controller/selectors"; +import type { RootState } from "app/store/root/types"; import { actions as statusActions } from "app/store/status"; type NavItem = { @@ -137,11 +141,17 @@ const isSelected = (path: string, link: NavItem) => { ); }; -const generateItems = ( - links: NavItem[], - path: string, - forHardwareMenu: boolean -) => { +const generateItems = ({ + links, + path, + forHardwareMenu, + vaultIncomplete, +}: { + links: NavItem[]; + path: string; + forHardwareMenu: boolean; + vaultIncomplete: boolean; +}) => { if (forHardwareMenu) { // Only include the items for the hardware menu. links = links.filter((link) => link.inHardwareMenu); @@ -155,7 +165,21 @@ const generateItems = ( }), isSelected: isSelected(path, link), key: link.url, - label: link.label, + label: + link.label === "Controllers" && vaultIncomplete ? ( // check if vault is set up on all controllers + <> + + {link.label} + {/** Display a warning icon if setup is incomplete */} + + ) : ( + link.label + ), url: link.url, })); }; @@ -203,6 +227,18 @@ export const Header = (): JSX.Element => { setTheme(maasTheme ? maasTheme : "default"); }, [location, maasTheme, setTheme]); + useEffect(() => { + dispatch(controllerActions.fetch()); + }, [dispatch]); + + const { unconfiguredControllers, configuredControllers } = useSelector( + (state: RootState) => + controllerSelectors.getVaultConfiguredControllers(state) + ); + + const vaultIncomplete = + unconfiguredControllers.length >= 1 && configuredControllers.length >= 1; + // Hide the navigation items when the user is not authenticated or hasn't been // through the intro process. const showLinks = isAuthenticated && completedIntro && completedUserIntro; @@ -233,10 +269,20 @@ export const Header = (): JSX.Element => { ? [ { className: "p-navigation__hardware-menu", - items: generateItems(links, path, true), + items: generateItems({ + links: links, + path: path, + forHardwareMenu: true, + vaultIncomplete: vaultIncomplete, + }), label: "Hardware", }, - ...generateItems(links, path, false), + ...generateItems({ + links: links, + path: path, + forHardwareMenu: false, + vaultIncomplete: vaultIncomplete, + }), ] : null } diff --git a/src/app/base/components/Header/_index.scss b/src/app/base/components/Header/_index.scss index 2bac5dafc4..b00feffc36 100644 --- a/src/app/base/components/Header/_index.scss +++ b/src/app/base/components/Header/_index.scss @@ -35,4 +35,8 @@ .p-navigation--red { background-color: #A71B33 !important; } + + .p-navigation--item-icon { + margin-right: $sph--small; + } } \ No newline at end of file