Skip to content

Commit

Permalink
web: Improvements in tables
Browse files Browse the repository at this point in the history
- Unify columns.
- Activate type checking.
- Refactoring code.
  • Loading branch information
joseivanlopez committed Apr 26, 2024
1 parent dd8ce25 commit 5420b85
Show file tree
Hide file tree
Showing 15 changed files with 601 additions and 390 deletions.
43 changes: 29 additions & 14 deletions web/src/components/core/ExpandableSelector.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@
import React, { useState } from "react";
import { Table, Thead, Tr, Th, Tbody, Td, ExpandableRowContent, RowSelectVariant } from "@patternfly/react-table";

/**
* @typedef {import("@patternfly/react-table").TableProps} TableProps
* @typedef {import("react").RefAttributes<HTMLTableElement>} HTMLTableProps
*/

/**
* An object for sharing data across nested maps
*
Expand Down Expand Up @@ -93,23 +98,26 @@ const sanitizeSelection = (selection, allowMultiple) => {
};

/**
* Build a expandable table with selectable items
* Build a expandable table with selectable items.
* @component
*
* @note It only accepts one nesting level.
*
* @param {object} props
* @param {ExpandableSelectorColumn[]} props.columns - Collection of objects defining columns.
* @param {boolean} [props.isMultiple=false] - Whether multiple selection is allowed.
* @param {object[]} props.items - Collection of items to be rendered.
* @param {string} [props.itemIdKey="id"] - The key for retrieving the item id.
* @param {(item: object) => Array<object>} [props.itemChildren=() => []] - Lookup method to retrieve children from given item.
* @param {(item: object) => boolean} [props.itemSelectable=() => true] - Whether an item will be selectable or not.
* @param {(item: object) => (string|undefined)} [props.itemClassNames=() => ""] - Callback that allows adding additional CSS class names to item row.
* @param {object[]} [props.itemsSelected=[]] - Collection of selected items.
* @param {string[]} [props.initialExpandedKeys=[]] - Ids of initially expanded items.
* @param {(selection: Array<object>) => void} [props.onSelectionChange=noop] - Callback to be triggered when selection changes.
* @param {object} [props.tableProps] - Props for {@link https://www.patternfly.org/components/table/#table PF/Table}.
* @typedef {object} ExpandableSelectorBaseProps
* @property {ExpandableSelectorColumn[]} [columns=[]] - Collection of objects defining columns.
* @property {boolean} [isMultiple=false] - Whether multiple selection is allowed.
* @property {object[]} [items=[]] - Collection of items to be rendered.
* @property {string} [itemIdKey="id"] - The key for retrieving the item id.
* @property {(item: object) => Array<object>} [itemChildren=() => []] - Lookup method to retrieve children from given item.
* @property {(item: object) => boolean} [itemSelectable=() => true] - Whether an item will be selectable or not.
* @property {(item: object) => (string|undefined)} [itemClassNames=() => ""] - Callback that allows adding additional CSS class names to item row.
* @property {object[]} [itemsSelected=[]] - Collection of selected items.
* @property {string[]} [initialExpandedKeys=[]] - Ids of initially expanded items.
* @property {(selection: Array<object>) => void} [onSelectionChange=noop] - Callback to be triggered when selection changes.
*
* @typedef {ExpandableSelectorBaseProps & TableProps & HTMLTableProps} ExpandableSelectorProps
*
* @param {ExpandableSelectorProps} props
*/
export default function ExpandableSelector({
columns = [],
Expand All @@ -126,7 +134,14 @@ export default function ExpandableSelector({
}) {
const [expandedItemsKeys, setExpandedItemsKeys] = useState(initialExpandedKeys);
const selection = sanitizeSelection(itemsSelected, isMultiple);
const isItemSelected = (item) => selection.includes(item);
const isItemSelected = (item) => {
const selected = selection.find((selectionItem) => {
return Object.hasOwn(selectionItem, itemIdKey) &&
selectionItem[itemIdKey] === item[itemIdKey];
});

return selected !== undefined || selection.includes(item);
};
const isItemExpanded = (key) => expandedItemsKeys.includes(key);
const toggleExpanded = (key) => {
if (isItemExpanded(key)) {
Expand Down
10 changes: 5 additions & 5 deletions web/src/components/core/TreeTable.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ import { Table, Thead, Tr, Th, Tbody, Td, TreeRowWrapper } from '@patternfly/rea

/**
* @typedef {object} TreeTableColumn
* @property {string} title
* @property {(any) => React.ReactNode} content
* @property {string} name
* @property {(object) => React.ReactNode} value
* @property {string} [classNames]
*/

Expand Down Expand Up @@ -82,14 +82,14 @@ export default function TreeTable({
const renderColumns = (item, treeRow) => {
return columns.map((c, cIdx) => {
const props = {
dataLabel: c.title,
dataLabel: c.name,
className: c.classNames
};

if (cIdx === 0) props.treeRow = treeRow;

return (
<Td key={cIdx} {...props}>{c.content(item)}</Td>
<Td key={cIdx} {...props}>{c.value(item)}</Td>
);
});
};
Expand Down Expand Up @@ -138,7 +138,7 @@ export default function TreeTable({
>
<Thead>
<Tr>
{ columns.map((c, i) => <Th key={i} className={c.classNames}>{c.title}</Th>) }
{ columns.map((c, i) => <Th key={i} className={c.classNames}>{c.name}</Th>) }
</Tr>
</Thead>
<Tbody>
Expand Down
27 changes: 14 additions & 13 deletions web/src/components/storage/DeviceSelectionDialog.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import { _ } from "~/i18n";
import { deviceChildren } from "~/components/storage/utils";
import { ControlledPanels as Panels, Popup } from "~/components/core";
import { DeviceSelectorTable } from "~/components/storage";
import { noop } from "~/utils";
import { compact, noop } from "~/utils";

/**
* @typedef {import ("~/client/storage").ProposalTarget} ProposalTarget
Expand All @@ -46,20 +46,21 @@ const OPTIONS_NAME = "selection-mode";
* Renders a dialog that allows the user to select a target device for installation.
* @component
*
* @param {object} props
* @param {ProposalTarget} props.target
* @param {StorageDevice|undefined} props.targetDevice
* @param {StorageDevice[]} props.targetPVDevices
* @param {StorageDevice[]} props.devices - The actions to perform in the system.
* @param {boolean} [props.isOpen=false] - Whether the dialog is visible or not.
* @param {() => void} [props.onCancel=noop]
* @param {(target: Target) => void} [props.onAccept=noop]
* @typedef {object} DeviceSelectionDialogProps
* @property {ProposalTarget} target
* @property {StorageDevice|undefined} targetDevice
* @property {StorageDevice[]} targetPVDevices
* @property {StorageDevice[]} devices - The actions to perform in the system.
* @property {boolean} [isOpen=false] - Whether the dialog is visible or not.
* @property {() => void} [onCancel=noop]
* @property {(target: TargetConfig) => void} [onAccept=noop]
*
* @typedef {object} Target
* @typedef {object} TargetConfig
* @property {string} target
* @property {StorageDevice|undefined} targetDevice
* @property {StorageDevice[]} targetPVDevices
*
* @param {DeviceSelectionDialogProps} props
*/
export default function DeviceSelectionDialog({
target: defaultTarget,
Expand Down Expand Up @@ -149,7 +150,7 @@ devices.").split(/[[\]]/);
<DeviceSelectorTable
aria-label={_("Device selector for target disk")}
devices={devices}
selected={[targetDevice]}
selectedDevices={compact([targetDevice])}
itemChildren={deviceChildren}
itemSelectable={isDeviceSelectable}
onSelectionChange={selectTargetDevice}
Expand All @@ -166,7 +167,7 @@ devices.").split(/[[\]]/);
aria-label={_("Device selector for new LVM volume group")}
isMultiple
devices={devices}
selected={targetPVDevices}
selectedDevices={targetPVDevices}
itemChildren={deviceChildren}
itemSelectable={isDeviceSelectable}
onSelectionChange={setTargetPVDevices}
Expand Down
13 changes: 13 additions & 0 deletions web/src/components/storage/DeviceSelectionDialog.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ import { screen, within } from "@testing-library/react";
import { plainRender } from "~/test-utils";
import { DeviceSelectionDialog } from "~/components/storage";

/**
* @typedef {import ("~/client/storage").StorageDevice} StorageDevice
* @typedef {import("./DeviceSelectionDialog").DeviceSelectionDialogProps} DeviceSelectionDialogProps
*/

/** @type {StorageDevice} */
const sda = {
sid: 59,
isDrive: true,
Expand All @@ -40,13 +46,15 @@ const sda = {
sdCard: true,
active: true,
name: "/dev/sda",
description: "",
size: 1024,
recoverableSize: 0,
systems : [],
udevIds: ["ata-Micron_1100_SATA_512GB_12563", "scsi-0ATA_Micron_1100_SATA_512GB"],
udevPaths: ["pci-0000:00-12", "pci-0000:00-12-ata"],
};

/** @type {StorageDevice} */
const sdb = {
sid: 62,
isDrive: true,
Expand All @@ -61,13 +69,15 @@ const sdb = {
sdCard: false,
active: true,
name: "/dev/sdb",
description: "",
size: 2048,
recoverableSize: 0,
systems : [],
udevIds: [],
udevPaths: ["pci-0000:00-19"]
};

/** @type {StorageDevice} */
const sdc = {
sid: 63,
isDrive: true,
Expand All @@ -82,13 +92,15 @@ const sdc = {
sdCard: false,
active: true,
name: "/dev/sdc",
description: "",
size: 2048,
recoverableSize: 0,
systems : [],
udevIds: [],
udevPaths: ["pci-0000:00-19"]
};

/** @type {DeviceSelectionDialogProps} */
let props;

const expectSelector = (selector) => {
Expand Down Expand Up @@ -124,6 +136,7 @@ describe("DeviceSelectionDialog", () => {
props = {
isOpen: true,
target: "DISK",
targetDevice: undefined,
targetPVDevices: [],
devices: [sda, sdb, sdc],
onCancel: jest.fn(),
Expand Down
Loading

0 comments on commit 5420b85

Please sign in to comment.