From 0701d50099084fc37ac4985b8de0e4ee4e964a55 Mon Sep 17 00:00:00 2001 From: Nick De Villiers Date: Fri, 5 Jan 2024 14:14:52 +0000 Subject: [PATCH] feat(devices): use MainToolbar in DeviceListHeader MAASENG-2515 (#5254) --- package.json | 2 +- .../views/DeviceList/DeviceList.test.tsx | 11 +--- .../devices/views/DeviceList/DeviceList.tsx | 3 +- .../DeviceListControls.test.tsx | 21 ------- .../DeviceListControls/DeviceListControls.tsx | 42 ------------- .../DeviceList/DeviceListControls/index.ts | 1 - .../DeviceFilterAccordion.test.tsx | 0 .../DeviceFilterAccordion.tsx | 0 .../DeviceFilterAccordion/index.ts | 0 .../DeviceListHeader.test.tsx | 35 +++++++++-- .../DeviceListHeader/DeviceListHeader.tsx | 61 +++++++++++++------ src/scss/_vanilla-overrides.scss | 8 +++ yarn.lock | 8 +-- 13 files changed, 91 insertions(+), 101 deletions(-) delete mode 100644 src/app/devices/views/DeviceList/DeviceListControls/DeviceListControls.test.tsx delete mode 100644 src/app/devices/views/DeviceList/DeviceListControls/DeviceListControls.tsx delete mode 100644 src/app/devices/views/DeviceList/DeviceListControls/index.ts rename src/app/devices/views/DeviceList/{DeviceListControls => DeviceListHeader}/DeviceFilterAccordion/DeviceFilterAccordion.test.tsx (100%) rename src/app/devices/views/DeviceList/{DeviceListControls => DeviceListHeader}/DeviceFilterAccordion/DeviceFilterAccordion.tsx (100%) rename src/app/devices/views/DeviceList/{DeviceListControls => DeviceListHeader}/DeviceFilterAccordion/index.ts (100%) diff --git a/package.json b/package.json index 67ea73e5db..db691519c7 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "build-storybook": "storybook build" }, "dependencies": { - "@canonical/maas-react-components": "1.14.0", + "@canonical/maas-react-components": "1.15.0", "@canonical/macaroon-bakery": "1.3.2", "@canonical/react-components": "0.46.0", "@reduxjs/toolkit": "1.9.3", diff --git a/src/app/devices/views/DeviceList/DeviceList.test.tsx b/src/app/devices/views/DeviceList/DeviceList.test.tsx index eef5558355..dc42d67bb7 100644 --- a/src/app/devices/views/DeviceList/DeviceList.test.tsx +++ b/src/app/devices/views/DeviceList/DeviceList.test.tsx @@ -23,9 +23,7 @@ describe("DeviceList", () => { route: "/devices?q=test+search", state, }); - expect(screen.getByRole("searchbox", { name: "Search" })).toHaveValue( - "test search" - ); + expect(screen.getByRole("searchbox")).toHaveValue("test search"); }); it("changes the URL when the search text changes", async () => { @@ -42,11 +40,8 @@ describe("DeviceList", () => { , { route: "/machines?q=test+search", state } ); - await userEvent.clear(screen.getByRole("searchbox", { name: "Search" })); - await userEvent.type( - screen.getByRole("searchbox", { name: "Search" }), - "hostname:foo" - ); + await userEvent.clear(screen.getByRole("searchbox")); + await userEvent.type(screen.getByRole("searchbox"), "hostname:foo"); await waitFor(() => { expect(search).toBe("?hostname=foo"); diff --git a/src/app/devices/views/DeviceList/DeviceList.tsx b/src/app/devices/views/DeviceList/DeviceList.tsx index 88f4478212..feedb4ed9c 100644 --- a/src/app/devices/views/DeviceList/DeviceList.tsx +++ b/src/app/devices/views/DeviceList/DeviceList.tsx @@ -4,7 +4,6 @@ import { useDispatch, useSelector } from "react-redux"; import { useLocation } from "react-router"; import { useNavigate } from "react-router-dom-v5-compat"; -import DeviceListControls from "./DeviceListControls"; import DeviceListHeader from "./DeviceListHeader"; import DeviceListTable from "./DeviceListTable"; @@ -53,6 +52,7 @@ const DeviceList = (): JSX.Element => { @@ -68,7 +68,6 @@ const DeviceList = (): JSX.Element => { } sidePanelTitle={getSidePanelTitle("Devices", sidePanelContent)} > - { - let state: RootState = rootStateFactory(); - - it("changes the search text when the filters change", () => { - const { rerender } = renderWithBrowserRouter( - , - { route: "/machines?q=test+search", state } - ); - expect(screen.getByRole("searchbox")).toHaveValue(""); - - rerender(); - - expect(screen.getByRole("searchbox")).toHaveValue("free-text"); - }); -}); diff --git a/src/app/devices/views/DeviceList/DeviceListControls/DeviceListControls.tsx b/src/app/devices/views/DeviceList/DeviceListControls/DeviceListControls.tsx deleted file mode 100644 index a6215f3ba0..0000000000 --- a/src/app/devices/views/DeviceList/DeviceListControls/DeviceListControls.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import { useEffect, useState } from "react"; - -import { Col, Row } from "@canonical/react-components"; - -import DeviceFilterAccordion from "./DeviceFilterAccordion"; - -import DebounceSearchBox from "app/base/components/DebounceSearchBox"; -import type { SetSearchFilter } from "app/base/types"; - -type Props = { - filter: string; - setFilter: SetSearchFilter; -}; - -const DeviceListControls = ({ filter, setFilter }: Props): JSX.Element => { - const [searchText, setSearchText] = useState(filter); - - useEffect(() => { - // If the filters change then update the search input text. - setSearchText(filter); - }, [filter]); - - return ( - - - - - - setFilter(debouncedText)} - searchText={searchText} - setSearchText={setSearchText} - /> - - - ); -}; - -export default DeviceListControls; diff --git a/src/app/devices/views/DeviceList/DeviceListControls/index.ts b/src/app/devices/views/DeviceList/DeviceListControls/index.ts deleted file mode 100644 index bc273122ff..0000000000 --- a/src/app/devices/views/DeviceList/DeviceListControls/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from "./DeviceListControls"; diff --git a/src/app/devices/views/DeviceList/DeviceListControls/DeviceFilterAccordion/DeviceFilterAccordion.test.tsx b/src/app/devices/views/DeviceList/DeviceListHeader/DeviceFilterAccordion/DeviceFilterAccordion.test.tsx similarity index 100% rename from src/app/devices/views/DeviceList/DeviceListControls/DeviceFilterAccordion/DeviceFilterAccordion.test.tsx rename to src/app/devices/views/DeviceList/DeviceListHeader/DeviceFilterAccordion/DeviceFilterAccordion.test.tsx diff --git a/src/app/devices/views/DeviceList/DeviceListControls/DeviceFilterAccordion/DeviceFilterAccordion.tsx b/src/app/devices/views/DeviceList/DeviceListHeader/DeviceFilterAccordion/DeviceFilterAccordion.tsx similarity index 100% rename from src/app/devices/views/DeviceList/DeviceListControls/DeviceFilterAccordion/DeviceFilterAccordion.tsx rename to src/app/devices/views/DeviceList/DeviceListHeader/DeviceFilterAccordion/DeviceFilterAccordion.tsx diff --git a/src/app/devices/views/DeviceList/DeviceListControls/DeviceFilterAccordion/index.ts b/src/app/devices/views/DeviceList/DeviceListHeader/DeviceFilterAccordion/index.ts similarity index 100% rename from src/app/devices/views/DeviceList/DeviceListControls/DeviceFilterAccordion/index.ts rename to src/app/devices/views/DeviceList/DeviceListHeader/DeviceFilterAccordion/index.ts diff --git a/src/app/devices/views/DeviceList/DeviceListHeader/DeviceListHeader.test.tsx b/src/app/devices/views/DeviceList/DeviceListHeader/DeviceListHeader.test.tsx index a007f15809..b228b9b505 100644 --- a/src/app/devices/views/DeviceList/DeviceListHeader/DeviceListHeader.test.tsx +++ b/src/app/devices/views/DeviceList/DeviceListHeader/DeviceListHeader.test.tsx @@ -30,6 +30,7 @@ describe("DeviceListHeader", () => { state.device.loaded = false; renderWithBrowserRouter( , @@ -42,26 +43,26 @@ describe("DeviceListHeader", () => { state.device.loaded = true; renderWithBrowserRouter( , { state } ); - expect(screen.getByTestId("section-header-subtitle")).toHaveTextContent( - "2 devices available" - ); + expect(screen.getByText("2 devices available")).toBeInTheDocument(); }); it("disables the add device button if any devices are selected", () => { state.device.selected = ["abc123"]; renderWithBrowserRouter( , { state } ); - expect(screen.getByTestId("add-device-button")).toBeDisabled(); + expect(screen.getByRole("button", { name: "Add device" })).toBeDisabled(); }); it("can open the add device form", async () => { @@ -69,15 +70,39 @@ describe("DeviceListHeader", () => { renderWithBrowserRouter( , { state } ); - await userEvent.click(screen.getByTestId("add-device-button")); + await userEvent.click(screen.getByRole("button", { name: "Add device" })); expect(setSidePanelContent).toHaveBeenCalledWith({ view: DeviceSidePanelViews.ADD_DEVICE, }); }); + + it("changes the search text when the filters change", () => { + const { rerender } = renderWithBrowserRouter( + , + { state } + ); + + expect(screen.getByRole("searchbox")).toHaveValue(""); + + rerender( + + ); + + expect(screen.getByRole("searchbox")).toHaveValue("free-text"); + }); }); diff --git a/src/app/devices/views/DeviceList/DeviceListHeader/DeviceListHeader.tsx b/src/app/devices/views/DeviceList/DeviceListHeader/DeviceListHeader.tsx index c63ab8560f..0f62cf0b2d 100644 --- a/src/app/devices/views/DeviceList/DeviceListHeader/DeviceListHeader.tsx +++ b/src/app/devices/views/DeviceList/DeviceListHeader/DeviceListHeader.tsx @@ -1,30 +1,67 @@ -import { Button } from "@canonical/react-components"; +import { useEffect, useState } from "react"; + +import { MainToolbar } from "@canonical/maas-react-components"; +import { Button, Col, Spinner } from "@canonical/react-components"; import { useSelector } from "react-redux"; +import DeviceFilterAccordion from "./DeviceFilterAccordion"; + +import DebounceSearchBox from "app/base/components/DebounceSearchBox"; import ModelListSubtitle from "app/base/components/ModelListSubtitle"; import NodeActionMenu from "app/base/components/NodeActionMenu"; -import SectionHeader from "app/base/components/SectionHeader"; import type { SetSearchFilter } from "app/base/types"; import { DeviceSidePanelViews } from "app/devices/constants"; import type { DeviceSetSidePanelContent } from "app/devices/types"; import deviceSelectors from "app/store/device/selectors"; type Props = { + searchFilter: string; setSidePanelContent: DeviceSetSidePanelContent; setSearchFilter: SetSearchFilter; }; const DeviceListHeader = ({ + searchFilter, setSidePanelContent, setSearchFilter, }: Props): JSX.Element => { const devices = useSelector(deviceSelectors.all); const devicesLoaded = useSelector(deviceSelectors.loaded); const selectedDevices = useSelector(deviceSelectors.selected); + const [searchText, setSearchText] = useState(searchFilter); + + useEffect(() => { + // If the filters change then update the search input text. + setSearchText(searchFilter); + }, [searchFilter]); return ( - + + Devices + + {devicesLoaded ? ( + setSearchFilter("in:(Selected)")} + modelName="device" + selected={selectedDevices.length} + /> + ) : ( + + )} + + + + + setSearchFilter(debouncedText)} + searchText={searchText} + setSearchText={setSearchText} + /> , + 0} @@ -48,19 +85,9 @@ const DeviceListHeader = ({ } }} showCount - />, - ]} - subtitle={ - setSearchFilter("in:(Selected)")} - modelName="device" - selected={selectedDevices.length} /> - } - subtitleLoading={!devicesLoaded} - title="Devices" - /> + + ); }; diff --git a/src/scss/_vanilla-overrides.scss b/src/scss/_vanilla-overrides.scss index 2cfe67151d..09d5af2231 100644 --- a/src/scss/_vanilla-overrides.scss +++ b/src/scss/_vanilla-overrides.scss @@ -35,3 +35,11 @@ .p-tooltip__message { z-index: $side-navigation-z-index + 1; } + +// 05-01-2024 Nick: Using MainToolbar without ContentSection means no padding at top of page. +// Will be fixed once all main pages use MainToolbar and ContentSection is implemented +// https://warthogs.atlassian.net/browse/MAASENG-2440 +// https://github.com/canonical/maas-ui/pull/5244 +.main-toolbar { + padding-top: $spv--medium; +} diff --git a/yarn.lock b/yarn.lock index 6220f89da2..092b17734b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2907,10 +2907,10 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== -"@canonical/maas-react-components@1.14.0": - version "1.14.0" - resolved "https://registry.yarnpkg.com/@canonical/maas-react-components/-/maas-react-components-1.14.0.tgz#3347a28cc968ed8a13a5e501ec6ebde11a0274c5" - integrity sha512-ApSpDchiCn8EJ597Er/yMeAzc4+iuphU0eXCmmZVcMFCSl2iQqJ6NncA1eCRPgBN7QQs++wqHJ5ry0VPHup0vA== +"@canonical/maas-react-components@1.15.0": + version "1.15.0" + resolved "https://registry.yarnpkg.com/@canonical/maas-react-components/-/maas-react-components-1.15.0.tgz#c14ec75eeb2bfe2757e45fb3bebf67689865e4df" + integrity sha512-0KQ0CXi6D2PURVUerRR/0CAitp3Qf7olUIJwXZnrePlLyas9zmnZ2HyVydlS/z5YptaHExqh/EQT9AGln/3T+A== "@canonical/macaroon-bakery@1.3.2": version "1.3.2"