Skip to content

Commit

Permalink
fix: add table empty state for all tables on MAAS-UI (#5189)
Browse files Browse the repository at this point in the history
  • Loading branch information
Jay-Topher authored Oct 12, 2023
1 parent 8239ba3 commit 5044f68
Show file tree
Hide file tree
Showing 56 changed files with 473 additions and 69 deletions.
6 changes: 2 additions & 4 deletions src/app/base/components/DHCPTable/DHCPTable.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { MemoryRouter } from "react-router-dom";
import { CompatRouter } from "react-router-dom-v5-compat";
import configureStore from "redux-mock-store";

import DHCPTable, { Labels, TestIds } from "./DHCPTable";
import DHCPTable, { TestIds } from "./DHCPTable";

import { Labels as FormLabels } from "app/base/components/DhcpForm";
import { MachineMeta } from "app/store/machine/types";
Expand Down Expand Up @@ -69,9 +69,7 @@ describe("DHCPTable", () => {
</Provider>
);

expect(
screen.getByRole("alert", { name: Labels.LoadingData })
).toBeInTheDocument();
expect(screen.getByText("Loading...")).toBeInTheDocument();
});

it("shows snippets for a machine", () => {
Expand Down
15 changes: 6 additions & 9 deletions src/app/base/components/DHCPTable/DHCPTable.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useState } from "react";

import { List, MainTable, Spinner } from "@canonical/react-components";
import { List, MainTable } from "@canonical/react-components";
import { useSelector } from "react-redux";
import { Link } from "react-router-dom-v5-compat";

Expand All @@ -21,7 +21,7 @@ import { getIpRangeDisplayName } from "app/store/iprange/utils";
import type { RootState } from "app/store/root/types";
import type { Subnet } from "app/store/subnet/types";
import type { Node } from "app/store/types/node";
import { isId } from "app/utils";
import { generateEmptyStateMsg, getTableStatus, isId } from "app/utils";

type BaseProps = {
className?: string;
Expand Down Expand Up @@ -149,6 +149,7 @@ const DHCPTable = ({
);

useFetchActions([dhcpsnippetActions.fetch]);
const tableStatus = getTableStatus({ isLoading: dhcpsnippetLoading });

return (
<TitledSection className={className} title={Labels.SectionTitle}>
Expand All @@ -158,13 +159,9 @@ const DHCPTable = ({
className="dhcp-snippets-table p-table-expanding--light"
defaultSort="name"
defaultSortDirection="descending"
emptyStateMsg={
dhcpsnippetLoading ? (
<Spinner aria-label={Labels.LoadingData} text="Loading..." />
) : (
`No DHCP snippets applied to this ${modelName}.`
)
}
emptyStateMsg={generateEmptyStateMsg(tableStatus, {
default: `No DHCP snippets applied to this ${modelName}.`,
})}
expanding
headers={[
{
Expand Down
11 changes: 11 additions & 0 deletions src/app/base/components/SSHKeyList/SSHKeyList.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -191,4 +191,15 @@ describe("SSHKeyList", () => {
);
expect(actions.some((action) => action.type === "message/add")).toBe(true);
});

it("displays a message if there are no SSH keys", () => {
state.sshkey.items = [];
const store = mockStore(state);
renderWithBrowserRouter(<SSHKeyList />, {
route: "/account/prefs/ssh-keys",
store,
});

expect(screen.getByText("No SSH keys available.")).toBeInTheDocument();
});
});
1 change: 1 addition & 0 deletions src/app/base/components/SSHKeyList/SSHKeyList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ const SSHKeyList = ({ sidebar = true, ...tableProps }: Props): JSX.Element => {
)}
<SettingsTable
aria-label="SSH keys"
emptyStateMsg="No SSH keys available."
headers={[
{
content: "Source",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,12 @@ describe("EventLogsTable", () => {
});
expect(screen.getByLabelText(Label.Title)).toBeInTheDocument();
});

it("displays a message if there is no data", () => {
renderWithMockStore(<EventLogsTable events={[]} />, {
state,
});

expect(screen.getByText("No event logs available.")).toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ const EventLogsTable = ({ events }: Props): JSX.Element => {
<MainTable
aria-label={Label.Title}
className="event-logs-table"
emptyStateMsg="No event logs available."
headers={[
{
content: "Time",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -263,4 +263,13 @@ describe("NodeTestsTable", () => {
"Unsuppress",
]);
});

it("displays a message when there is no script result", () => {
renderWithBrowserRouter(
<NodeTestsTable node={machine} scriptResults={[]} />,
{ route: "/machine/abc123", state }
);

expect(screen.getByText("No results available.")).toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ const NodeTestsTable = ({ node, scriptResults }: Props): JSX.Element => {
<MainTable
aria-label="Test results"
className="node-tests-table p-table-expanding--light"
emptyStateMsg="No results available."
expanding
headers={[
...(showSuppressCol
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ const CacheSetsTable = ({ canEditStorage, node }: Props): JSX.Element => {
return (
<MainTable
className="p-table-expanding--light"
emptyStateMsg="No cache set available."
expanding
headers={isMachine ? headers : headers.slice(0, -1)}
responsive
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,26 @@ describe("NetworkTable", () => {
expect(within(ipPrimary).getByText("1.2.3.99")).toBeInTheDocument();
});

it("displays a message when empty", () => {
state.subnet.items = [];
state.fabric.items = [];
state.vlan.items = [];
state.machine.items = [machine];

renderWithBrowserRouter(
<NetworkTable
expanded={null}
node={machine}
selected={[]}
setExpanded={jest.fn()}
setSelected={jest.fn()}
/>,
{ state }
);

expect(screen.getByText(Label.EmptyList)).toBeInTheDocument();
});

it("can display an interface that is an alias", () => {
const fabric = fabricFactory({ name: "fabric-name" });
state.fabric.items = [fabric];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ export enum Label {
Actions = "Actions",
ActionsMenu = "Interface actions",
DHCP = "DHCP",
EmptyList = "No interfaces availaable",
Fabric = "Fabric",
IP = "IP Address",
MAC = "MAC",
Expand Down Expand Up @@ -494,6 +495,7 @@ const NetworkTable = ({
})}
defaultSort="name"
defaultSortDirection="descending"
emptyStateMsg={Label.EmptyList}
expanding
headers={[
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import ControllerListTable from "./ControllerListTable";
import ControllerListTable, { Label } from "./ControllerListTable";

import urls from "app/base/urls";
import type { Controller } from "app/store/controller/types";
Expand Down Expand Up @@ -378,4 +378,17 @@ describe("ControllerListTable", () => {
).not.toBeInTheDocument();
});
});

it("displays message for empty state", () => {
renderWithBrowserRouter(
<ControllerListTable
controllers={[]}
onSelectedChange={jest.fn()}
selectedIDs={[]}
/>,
{ state }
);

expect(screen.getByText(Label.EmptyList)).toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { MainTable, Spinner, Link } from "@canonical/react-components";
import { MainTable, Link } from "@canonical/react-components";
import { useSelector } from "react-redux";

import StatusColumn from "./StatusColumn";
Expand All @@ -22,7 +22,12 @@ import { actions as generalActions } from "app/store/general";
import { vaultEnabled as vaultEnabledSelectors } from "app/store/general/selectors";
import type { RootState } from "app/store/root/types";
import { NodeType } from "app/store/types/node";
import { generateCheckboxHandlers, isComparable } from "app/utils";
import {
generateEmptyStateMsg,
generateCheckboxHandlers,
isComparable,
getTableStatus,
} from "app/utils";
import type { CheckboxHandlers } from "app/utils/generateCheckboxHandlers";

type Props = {
Expand All @@ -35,6 +40,11 @@ type Props = {

type SortKey = keyof Controller | "version";

export enum Label {
NoResults = "No controllers match the search criteria.",
EmptyList = "No controllers available.",
}

const getSortValue = (sortKey: SortKey, controller: Controller) => {
switch (sortKey) {
case "version":
Expand Down Expand Up @@ -233,18 +243,16 @@ const ControllerListTable = ({
const vaultEnabled = useSelector(vaultEnabledSelectors.get);

useFetchActions([generalActions.fetchVaultEnabled, controllerActions.fetch]);
const tableStatus = getTableStatus({ isLoading: loading, hasFilter });

return (
<MainTable
aria-label="controllers list"
className="controller-list-table"
emptyStateMsg={
loading ? (
<Spinner text="Loading..." />
) : hasFilter ? (
"No controllers match the search criteria."
) : null
}
emptyStateMsg={generateEmptyStateMsg(tableStatus, {
default: Label.EmptyList,
filtered: Label.NoResults,
})}
headers={[
{
className: "fqdn-col",
Expand Down
20 changes: 12 additions & 8 deletions src/app/dashboard/views/DiscoveriesList/DiscoveriesList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {
ContextualMenu,
MainTable,
Row,
Spinner,
} from "@canonical/react-components";
import classNames from "classnames";
import { useSelector, useDispatch } from "react-redux";
Expand All @@ -27,13 +26,16 @@ import discoverySelectors from "app/store/discovery/selectors";
import type { Discovery } from "app/store/discovery/types";
import { DiscoveryMeta } from "app/store/discovery/types";
import type { RootState } from "app/store/root/types";
import { generateEmptyStateMsg, getTableStatus } from "app/utils";

export enum Labels {
DiscoveriesList = "Discoveries list",
Loading = "Loading...",
NoNewDiscoveries = "No new discoveries.",
AddDiscovery = "Add discovery...",
DeleteDiscovery = "Delete discovery...",
NoResults = "No discoveries match the search criteria.",
EmptyList = "No discoveries available.",
}

enum ExpandedType {
Expand Down Expand Up @@ -225,6 +227,11 @@ const DiscoveriesList = (): JSX.Element => {
},
];

const tableStatus = getTableStatus({
isLoading: loading,
hasFilter: !!searchString,
});

return (
<div aria-label={Labels.DiscoveriesList}>
<Row>
Expand All @@ -248,13 +255,10 @@ const DiscoveriesList = (): JSX.Element => {
data-testid="discoveries-table"
defaultSort="lastSeen"
defaultSortDirection="ascending"
emptyStateMsg={
loading ? (
<Spinner text="Loading..." />
) : (
"No discoveries match the search criteria."
)
}
emptyStateMsg={generateEmptyStateMsg(tableStatus, {
default: Labels.EmptyList,
filtered: Labels.NoResults,
})}
expanding
headers={headers}
rows={generateRows(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -204,4 +204,23 @@ describe("DeviceNetworkTable", () => {
const rows = screen.getAllByRole("row");
expect(rows[1]).toHaveClass("is-active");
});

it("displays an empty table description", () => {
state.device.items = [
deviceDetailsFactory({
interfaces: [],
system_id: "abc123",
}),
];
const store = mockStore(state);
renderWithBrowserRouter(
<DeviceNetworkTable
expanded={null}
setExpanded={jest.fn()}
systemId="abc123"
/>,
{ store }
);
expect(screen.getByText("No interfaces available.")).toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,7 @@ const DeviceNetworkTable = ({
className="p-table-expanding--light device-network-table"
defaultSort="name"
defaultSortDirection="descending"
emptyStateMsg="No interfaces available."
expanding
headers={[
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import DeviceListTable from "./DeviceListTable";
import DeviceListTable, { Labels } from "./DeviceListTable";

import urls from "app/base/urls";
import type { Device } from "app/store/device/types";
Expand Down Expand Up @@ -294,4 +294,18 @@ describe("DeviceListTable", () => {
expect(onSelectedChange).toHaveBeenCalledWith([]);
});
});

it("displays a message when empty", () => {
const onSelectedChange = jest.fn();
renderWithBrowserRouter(
<DeviceListTable
devices={[]}
onSelectedChange={onSelectedChange}
selectedIDs={[]}
/>,
{ state }
);

expect(screen.getByText(Labels.EmptyList)).toBeInTheDocument();
});
});
Loading

0 comments on commit 5044f68

Please sign in to comment.