From ef28c98520d4c2714f0cb0c6111ac67d882ed351 Mon Sep 17 00:00:00 2001 From: Peter Makowski Date: Wed, 12 Jul 2023 10:32:53 +0200 Subject: [PATCH] refactor: MachineListTable - extract tableModels to own file --- .../MachineListTable/MachineListTable.tsx | 491 +----------------- .../MachineListTable/tableModels.tsx | 433 +++++++++++++++ .../MachineList/MachineListTable/types.ts | 63 +++ 3 files changed, 503 insertions(+), 484 deletions(-) create mode 100644 src/app/machines/views/MachineList/MachineListTable/tableModels.tsx create mode 100644 src/app/machines/views/MachineList/MachineListTable/types.ts diff --git a/src/app/machines/views/MachineList/MachineListTable/MachineListTable.tsx b/src/app/machines/views/MachineList/MachineListTable/MachineListTable.tsx index 56412f787d4..77e14049b07 100644 --- a/src/app/machines/views/MachineList/MachineListTable/MachineListTable.tsx +++ b/src/app/machines/views/MachineList/MachineListTable/MachineListTable.tsx @@ -1,47 +1,26 @@ -import type { ReactNode } from "react"; import { useMemo, memo, useCallback, useEffect, useState } from "react"; -import type { ValueOf } from "@canonical/react-components"; -import { Button, MainTable } from "@canonical/react-components"; -import type { - MainTableCell, - MainTableRow, -} from "@canonical/react-components/dist/components/MainTable/MainTable"; +import { MainTable } from "@canonical/react-components"; import classNames from "classnames"; import { useDispatch } from "react-redux"; import AllCheckbox from "./AllCheckbox"; -import CoresColumn from "./CoresColumn"; -import DisksColumn from "./DisksColumn"; -import FabricColumn from "./FabricColumn"; -import GroupCheckbox from "./GroupCheckbox"; import MachineListDisplayCount from "./MachineListDisplayCount"; -import MachineListGroupCount from "./MachineListGroupCount"; import MachineListPagination from "./MachineListPagination"; import MachineListSelectedCount from "./MachineListSelectedCount/MachineListSelectedCount"; -import NameColumn from "./NameColumn"; -import OwnerColumn from "./OwnerColumn"; import PageSizeSelect from "./PageSizeSelect"; -import PoolColumn from "./PoolColumn"; -import PowerColumn from "./PowerColumn"; -import RamColumn from "./RamColumn"; -import StatusColumn from "./StatusColumn"; -import StorageColumn from "./StorageColumn"; -import ZoneColumn from "./ZoneColumn"; +import { + filterColumns, + generateSkeletonRows, + generateGroupRows, +} from "./tableModels"; +import type { Props } from "./types"; -import DoubleRow from "app/base/components/DoubleRow"; -import Placeholder from "app/base/components/Placeholder"; import TableHeader from "app/base/components/TableHeader"; import { useSendAnalytics } from "app/base/hooks"; import { SortDirection } from "app/base/types"; import { columnLabels, columns, MachineColumns } from "app/machines/constants"; -import type { GetMachineMenuToggleHandler } from "app/machines/types"; import { actions as generalActions } from "app/store/general"; -import type { - FetchFilters, - Machine, - MachineStateListGroup, -} from "app/store/machine/types"; import { FetchGroupKey } from "app/store/machine/types"; import { FilterMachines } from "app/store/machine/utils"; import { useMachineSelectedCount } from "app/store/machine/utils/hooks"; @@ -51,467 +30,11 @@ import { actions as userActions } from "app/store/user"; import { actions as zoneActions } from "app/store/zone"; export enum Label { - HideGroup = "Hide", Loading = "Loading machines", Machines = "Machines", NoResults = "No machines match the search criteria.", - ShowGroup = "Show", } -type Props = { - callId?: string | null; - currentPage: number; - filter?: string; - grouping?: FetchGroupKey | null; - groups: MachineStateListGroup[] | null; - hiddenColumns?: string[]; - hiddenGroups?: (string | null)[]; - machineCount: number | null; - totalPages: number | null; - machines: Machine[]; - machinesLoading?: boolean | null; - pageSize: number; - setCurrentPage: (currentPage: number) => void; - setHiddenGroups?: (hiddenGroups: (string | null)[]) => void; - setPageSize?: (pageSize: number) => void; - showActions?: boolean; - sortDirection: ValueOf; - sortKey: FetchGroupKey | null; - setSortDirection: (sortDirection: ValueOf) => void; - setSortKey: (sortKey: FetchGroupKey | null) => void; -}; - -type TableColumn = MainTableCell & { key: string }; - -type GenerateRowParams = { - callId?: string | null; - groupValue: MachineStateListGroup["value"]; - hiddenColumns: NonNullable; - machines: Machine[]; - getToggleHandler: GetMachineMenuToggleHandler; - showActions: Props["showActions"]; - showMAC: boolean; - showFullName: boolean; -}; - -type RowContent = { - [MachineColumns.FQDN]: ReactNode; - [MachineColumns.POWER]: ReactNode; - [MachineColumns.STATUS]: ReactNode; - [MachineColumns.OWNER]: ReactNode; - [MachineColumns.POOL]: ReactNode; - [MachineColumns.ZONE]: ReactNode; - [MachineColumns.FABRIC]: ReactNode; - [MachineColumns.CPU]: ReactNode; - [MachineColumns.MEMORY]: ReactNode; - [MachineColumns.DISKS]: ReactNode; - [MachineColumns.STORAGE]: ReactNode; -}; - -/** - * Filters columns by hiddenColumns. - * - * If showActions is true, the "fqdn" column will not be filtered as action checkboxes - * share the "fqdn" column. - * @param {Array} columns - headers or rows - * @param {string[]} hiddenColumns - columns to hide, e.g. ["zone"] - * @param {bool} showActions - whether actions and associated checkboxes are displayed - */ -const filterColumns = ( - columns: TableColumn[], - hiddenColumns: NonNullable, - showActions: Props["showActions"] -) => { - if (hiddenColumns.length === 0) { - return columns; - } - return columns.filter( - (column) => - !hiddenColumns.includes(column.key) || - (column.key === "fqdn" && showActions) - ); -}; - -const generateRow = ({ - key, - content, - hiddenColumns, - showActions, - classes, -}: { - key: string | number; - content: RowContent; - hiddenColumns: NonNullable; - showActions: GenerateRowParams["showActions"]; - classes?: string; -}) => { - const columns = [ - { - "aria-label": columnLabels[MachineColumns.FQDN], - key: MachineColumns.FQDN, - className: "fqdn-col", - content: content[MachineColumns.FQDN], - }, - { - "aria-label": columnLabels[MachineColumns.POWER], - key: MachineColumns.POWER, - className: "power-col", - content: content[MachineColumns.POWER], - }, - { - "aria-label": columnLabels[MachineColumns.STATUS], - key: MachineColumns.STATUS, - className: "status-col", - content: content[MachineColumns.STATUS], - }, - { - "aria-label": columnLabels[MachineColumns.OWNER], - key: MachineColumns.OWNER, - className: "owner-col", - content: content[MachineColumns.OWNER], - }, - { - "aria-label": columnLabels[MachineColumns.POOL], - key: MachineColumns.POOL, - className: "pool-col", - content: content[MachineColumns.POOL], - }, - { - "aria-label": columnLabels[MachineColumns.ZONE], - key: MachineColumns.ZONE, - className: "zone-col", - content: content[MachineColumns.ZONE], - }, - { - "aria-label": columnLabels[MachineColumns.FABRIC], - key: MachineColumns.FABRIC, - className: "fabric-col", - content: content[MachineColumns.FABRIC], - }, - { - "aria-label": columnLabels[MachineColumns.CPU], - key: MachineColumns.CPU, - className: "cores-col", - content: content[MachineColumns.CPU], - }, - { - "aria-label": columnLabels[MachineColumns.MEMORY], - key: MachineColumns.MEMORY, - className: "ram-col", - content: content[MachineColumns.MEMORY], - }, - { - "aria-label": columnLabels[MachineColumns.DISKS], - key: MachineColumns.DISKS, - className: "disks-col", - content: content[MachineColumns.DISKS], - }, - { - "aria-label": columnLabels[MachineColumns.STORAGE], - key: MachineColumns.STORAGE, - className: "storage-col", - content: content[MachineColumns.STORAGE], - }, - ]; - - return { - key, - className: classNames( - "machine-list__machine", - { - "truncated-border": showActions, - }, - classes - ), - columns: filterColumns(columns, hiddenColumns, showActions), - }; -}; - -const generateSkeletonRows = ( - hiddenColumns: NonNullable, - showActions: GenerateRowParams["showActions"] -) => { - return Array.from(Array(5)).map((_, i) => { - const content = { - [MachineColumns.FQDN]: ( - xxxxxxxxx.xxxx} - secondary={xxx.xxx.xx.x} - /> - ), - [MachineColumns.POWER]: ( - Xxxxxxxxxxx} - secondary={Xxxxxxx} - /> - ), - [MachineColumns.STATUS]: ( - XXXXX XXXXX} /> - ), - [MachineColumns.OWNER]: ( - Xxxx} - secondary={XXXX, XXX} - /> - ), - [MachineColumns.POOL]: ( - Xxxxx} /> - ), - [MachineColumns.ZONE]: ( - Xxxxxxx} - secondary={Xxxxxxx} - /> - ), - [MachineColumns.FABRIC]: ( - Xxxxxxx-X} - secondary={Xxxxx} - /> - ), - [MachineColumns.CPU]: ( - XX} - primaryClassName="u-align--right" - secondary={xxxXX} - secondaryClassName="u-align--right" - /> - ), - [MachineColumns.MEMORY]: ( - XX xxx} - primaryClassName="u-align--right" - /> - ), - [MachineColumns.DISKS]: ( - XX} - primaryClassName="u-align--right" - /> - ), - [MachineColumns.STORAGE]: ( - X.XX} - primaryClassName="u-align--right" - /> - ), - }; - return generateRow({ - key: i, - content, - hiddenColumns, - showActions, - classes: "machine-list__machine--inactive", - }); - }); -}; -const generateRows = ({ - callId, - groupValue, - hiddenColumns, - machines, - getToggleHandler, - showActions, - showMAC, - showFullName, -}: GenerateRowParams) => { - const getMenuHandler: GetMachineMenuToggleHandler = (...args) => - showActions ? getToggleHandler(...args) : () => undefined; - - return machines.map((row) => { - const content = { - [MachineColumns.FQDN]: ( - - ), - [MachineColumns.POWER]: ( - - ), - [MachineColumns.STATUS]: ( - - ), - [MachineColumns.OWNER]: ( - - ), - [MachineColumns.POOL]: ( - - ), - [MachineColumns.ZONE]: ( - - ), - [MachineColumns.FABRIC]: ( - - ), - [MachineColumns.CPU]: ( - - ), - [MachineColumns.MEMORY]: ( - - ), - [MachineColumns.DISKS]: ( - - ), - [MachineColumns.STORAGE]: ( - - ), - }; - return generateRow({ - key: row.system_id, - content, - hiddenColumns, - showActions, - }); - }); -}; - -const generateGroupRows = ({ - callId, - grouping, - groups, - hiddenGroups, - machines, - setHiddenGroups, - showActions, - hiddenColumns, - filter, - ...rowProps -}: { - callId?: string | null; - grouping?: FetchGroupKey | null; - groups: MachineStateListGroup[] | null; - hiddenGroups: NonNullable; - setHiddenGroups: Props["setHiddenGroups"]; - filter: FetchFilters | null; -} & Omit) => { - let rows: MainTableRow[] = []; - - groups?.forEach((group) => { - const { collapsed, count, items: machineIDs, name, value } = group; - // When the table is set to ungrouped then there are no group headers. - if (grouping) { - rows.push({ - key: `${name}-group`, - "aria-label": `${name} machines group`, - className: "machine-list__group", - columns: [ - { - colSpan: columns.length - hiddenColumns.length, - content: ( - <> - - ) : ( - {name} - ) - } - secondary={ - - } - secondaryClassName={ - showActions ? "u-nudge--secondary-row u-align--left" : null - } - /> -
- -
- - ), - }, - ], - }); - } - // Get the machines in this group using the list of machine ids provided by the group. - const visibleMachines = collapsed - ? [] - : machineIDs.reduce((groupMachines, systemId) => { - const machine = machines.find( - ({ system_id }) => system_id === systemId - ); - if (machine) { - groupMachines.push(machine); - } - return groupMachines; - }, []); - rows = rows.concat( - generateRows({ - ...rowProps, - callId, - groupValue: group.value, - machines: visibleMachines, - showActions, - hiddenColumns, - }) - ); - }); - return rows; -}; - export const MachineListTable = ({ callId, currentPage, diff --git a/src/app/machines/views/MachineList/MachineListTable/tableModels.tsx b/src/app/machines/views/MachineList/MachineListTable/tableModels.tsx new file mode 100644 index 00000000000..4cadf90adff --- /dev/null +++ b/src/app/machines/views/MachineList/MachineListTable/tableModels.tsx @@ -0,0 +1,433 @@ +import { Button } from "@canonical/react-components"; +import type { MainTableRow } from "@canonical/react-components/dist/components/MainTable/MainTable"; +import classNames from "classnames"; +import pluralize from "pluralize"; + +import CoresColumn from "./CoresColumn"; +import DisksColumn from "./DisksColumn"; +import FabricColumn from "./FabricColumn"; +import GroupCheckbox from "./GroupCheckbox"; +import NameColumn from "./NameColumn"; +import OwnerColumn from "./OwnerColumn"; +import PoolColumn from "./PoolColumn"; +import PowerColumn from "./PowerColumn"; +import RamColumn from "./RamColumn"; +import StatusColumn from "./StatusColumn"; +import StorageColumn from "./StorageColumn"; +import ZoneColumn from "./ZoneColumn"; +import type { + Props, + TableColumn, + GenerateRowParams, + RowContent, +} from "./types"; + +import DoubleRow from "app/base/components/DoubleRow"; +import Placeholder from "app/base/components/Placeholder"; +import { columnLabels, columns, MachineColumns } from "app/machines/constants"; +import type { GetMachineMenuToggleHandler } from "app/machines/types"; +import type { + Machine, + MachineStateListGroup, + FetchGroupKey, +} from "app/store/machine/types"; + +/** + * Filters columns by hiddenColumns. + * + * If showActions is true, the "fqdn" column will not be filtered as action checkboxes + * share the "fqdn" column. + * @param {Array} columns - headers or rows + * @param {string[]} hiddenColumns - columns to hide, e.g. ["zone"] + * @param {bool} showActions - whether actions and associated checkboxes are displayed + */ +export const filterColumns = ( + columns: TableColumn[], + hiddenColumns: NonNullable, + showActions: Props["showActions"] +): TableColumn[] => { + if (hiddenColumns.length === 0) { + return columns; + } + return columns.filter( + (column) => + !hiddenColumns.includes(column.key) || + (column.key === "fqdn" && showActions) + ); +}; + +export const generateRow = ({ + key, + content, + hiddenColumns, + showActions, + classes, +}: { + key: string | number; + content: RowContent; + hiddenColumns: NonNullable; + showActions: GenerateRowParams["showActions"]; + classes?: string; +}) => { + const columns = [ + { + "aria-label": columnLabels[MachineColumns.FQDN], + key: MachineColumns.FQDN, + className: "fqdn-col", + content: content[MachineColumns.FQDN], + }, + { + "aria-label": columnLabels[MachineColumns.POWER], + key: MachineColumns.POWER, + className: "power-col", + content: content[MachineColumns.POWER], + }, + { + "aria-label": columnLabels[MachineColumns.STATUS], + key: MachineColumns.STATUS, + className: "status-col", + content: content[MachineColumns.STATUS], + }, + { + "aria-label": columnLabels[MachineColumns.OWNER], + key: MachineColumns.OWNER, + className: "owner-col", + content: content[MachineColumns.OWNER], + }, + { + "aria-label": columnLabels[MachineColumns.POOL], + key: MachineColumns.POOL, + className: "pool-col", + content: content[MachineColumns.POOL], + }, + { + "aria-label": columnLabels[MachineColumns.ZONE], + key: MachineColumns.ZONE, + className: "zone-col", + content: content[MachineColumns.ZONE], + }, + { + "aria-label": columnLabels[MachineColumns.FABRIC], + key: MachineColumns.FABRIC, + className: "fabric-col", + content: content[MachineColumns.FABRIC], + }, + { + "aria-label": columnLabels[MachineColumns.CPU], + key: MachineColumns.CPU, + className: "cores-col", + content: content[MachineColumns.CPU], + }, + { + "aria-label": columnLabels[MachineColumns.MEMORY], + key: MachineColumns.MEMORY, + className: "ram-col", + content: content[MachineColumns.MEMORY], + }, + { + "aria-label": columnLabels[MachineColumns.DISKS], + key: MachineColumns.DISKS, + className: "disks-col", + content: content[MachineColumns.DISKS], + }, + { + "aria-label": columnLabels[MachineColumns.STORAGE], + key: MachineColumns.STORAGE, + className: "storage-col", + content: content[MachineColumns.STORAGE], + }, + ]; + + return { + key, + className: classNames( + "machine-list__machine", + { + "truncated-border": showActions, + }, + classes + ), + columns: filterColumns(columns, hiddenColumns, showActions), + }; +}; + +export const generateSkeletonRows = ( + hiddenColumns: NonNullable, + showActions: GenerateRowParams["showActions"] +) => { + return Array.from(Array(5)).map((_, i) => { + const content = { + [MachineColumns.FQDN]: ( + xxxxxxxxx.xxxx} + secondary={xxx.xxx.xx.x} + /> + ), + [MachineColumns.POWER]: ( + Xxxxxxxxxxx} + secondary={Xxxxxxx} + /> + ), + [MachineColumns.STATUS]: ( + XXXXX XXXXX} /> + ), + [MachineColumns.OWNER]: ( + Xxxx} + secondary={XXXX, XXX} + /> + ), + [MachineColumns.POOL]: ( + Xxxxx} /> + ), + [MachineColumns.ZONE]: ( + Xxxxxxx} + secondary={Xxxxxxx} + /> + ), + [MachineColumns.FABRIC]: ( + Xxxxxxx-X} + secondary={Xxxxx} + /> + ), + [MachineColumns.CPU]: ( + XX} + primaryClassName="u-align--right" + secondary={xxxXX} + secondaryClassName="u-align--right" + /> + ), + [MachineColumns.MEMORY]: ( + XX xxx} + primaryClassName="u-align--right" + /> + ), + [MachineColumns.DISKS]: ( + XX} + primaryClassName="u-align--right" + /> + ), + [MachineColumns.STORAGE]: ( + X.XX} + primaryClassName="u-align--right" + /> + ), + }; + return generateRow({ + key: i, + content, + hiddenColumns, + showActions, + classes: "machine-list__machine--inactive", + }); + }); +}; + +export const generateRows = ({ + callId, + groupValue, + hiddenColumns, + machines, + getToggleHandler, + showActions, + showMAC, + showFullName, +}: GenerateRowParams) => { + const getMenuHandler: GetMachineMenuToggleHandler = (...args) => + showActions ? getToggleHandler(...args) : () => undefined; + + return machines.map((row) => { + const content = { + [MachineColumns.FQDN]: ( + + ), + [MachineColumns.POWER]: ( + + ), + [MachineColumns.STATUS]: ( + + ), + [MachineColumns.OWNER]: ( + + ), + [MachineColumns.POOL]: ( + + ), + [MachineColumns.ZONE]: ( + + ), + [MachineColumns.FABRIC]: ( + + ), + [MachineColumns.CPU]: ( + + ), + [MachineColumns.MEMORY]: ( + + ), + [MachineColumns.DISKS]: ( + + ), + [MachineColumns.STORAGE]: ( + + ), + }; + return generateRow({ + key: row.system_id, + content, + hiddenColumns, + showActions, + }); + }); +}; + +export enum Label { + HideGroup = "Hide", + ShowGroup = "Show", +} + +export const generateGroupRows = ({ + callId, + grouping, + groups, + hiddenGroups, + machines, + setHiddenGroups, + showActions, + hiddenColumns, + ...rowProps +}: { + callId?: string | null; + grouping?: FetchGroupKey | null; + groups: MachineStateListGroup[] | null; + hiddenGroups: NonNullable; + setHiddenGroups: Props["setHiddenGroups"]; +} & Omit) => { + let rows: MainTableRow[] = []; + + groups?.forEach((group) => { + const { collapsed, count, items: machineIDs, name, value } = group; + // When the table is set to ungrouped then there are no group headers. + if (grouping) { + rows.push({ + "aria-label": `${name} machines group`, + className: "machine-list__group", + columns: [ + { + colSpan: columns.length - hiddenColumns.length, + content: ( + <> + + ) : ( + {name} + ) + } + secondary={pluralize("machine", count, true)} + secondaryClassName={ + showActions ? "u-nudge--secondary-row u-align--left" : null + } + /> +
+ +
+ + ), + }, + ], + }); + } + // Get the machines in this group using the list of machine ids provided by the group. + const visibleMachines = collapsed + ? [] + : machineIDs.reduce((groupMachines, systemId) => { + const machine = machines.find( + ({ system_id }) => system_id === systemId + ); + if (machine) { + groupMachines.push(machine); + } + return groupMachines; + }, []); + rows = rows.concat( + generateRows({ + ...rowProps, + callId, + groupValue: group.value, + machines: visibleMachines, + showActions, + hiddenColumns, + }) + ); + }); + return rows; +}; diff --git a/src/app/machines/views/MachineList/MachineListTable/types.ts b/src/app/machines/views/MachineList/MachineListTable/types.ts new file mode 100644 index 00000000000..55e46f31259 --- /dev/null +++ b/src/app/machines/views/MachineList/MachineListTable/types.ts @@ -0,0 +1,63 @@ +import type { ReactNode } from "react"; + +import type { ValueOf } from "@canonical/react-components"; +import type { MainTableCell } from "@canonical/react-components/dist/components/MainTable/MainTable"; + +import type { SortDirection } from "app/base/types"; +import type { MachineColumns } from "app/machines/constants"; +import type { GetMachineMenuToggleHandler } from "app/machines/types"; +import type { + Machine, + MachineStateListGroup, + FetchGroupKey, +} from "app/store/machine/types"; + +export type Props = { + callId?: string | null; + currentPage: number; + filter?: string; + grouping?: FetchGroupKey | null; + groups: MachineStateListGroup[] | null; + hiddenColumns?: string[]; + hiddenGroups?: (string | null)[]; + machineCount: number | null; + machines: Machine[]; + machinesLoading?: boolean | null; + pageSize: number; + totalPages: number; + setCurrentPage: (currentPage: number) => void; + setHiddenGroups?: (hiddenGroups: (string | null)[]) => void; + setPageSize?: (pageSize: number) => void; + showActions?: boolean; + sortDirection: ValueOf; + sortKey: FetchGroupKey | null; + setSortDirection: (sortDirection: ValueOf) => void; + setSortKey: (sortKey: FetchGroupKey | null) => void; +}; + +export type TableColumn = MainTableCell & { key: string }; + +export type GenerateRowParams = { + callId?: string | null; + groupValue: MachineStateListGroup["value"]; + hiddenColumns: NonNullable; + machines: Machine[]; + getToggleHandler: GetMachineMenuToggleHandler; + showActions: Props["showActions"]; + showMAC: boolean; + showFullName: boolean; +}; + +export type RowContent = { + [MachineColumns.FQDN]: ReactNode; + [MachineColumns.POWER]: ReactNode; + [MachineColumns.STATUS]: ReactNode; + [MachineColumns.OWNER]: ReactNode; + [MachineColumns.POOL]: ReactNode; + [MachineColumns.ZONE]: ReactNode; + [MachineColumns.FABRIC]: ReactNode; + [MachineColumns.CPU]: ReactNode; + [MachineColumns.MEMORY]: ReactNode; + [MachineColumns.DISKS]: ReactNode; + [MachineColumns.STORAGE]: ReactNode; +};