Skip to content

Commit

Permalink
Adding custom hook for selection on list component
Browse files Browse the repository at this point in the history
  • Loading branch information
gnehapk committed Jun 17, 2020
1 parent c882a73 commit 2ba07dc
Show file tree
Hide file tree
Showing 9 changed files with 120 additions and 137 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,8 @@ export const CreateOCSServiceForm = withHandlePromise<
params: { appName, ns },
},
} = props;
const [selectedNodes, setSelectedNodes] = React.useState<NodeKind[]>(null);
const [visibleRows, setVisibleRows] = React.useState<NodeKind[]>(null);
const [selectedNodes, setSelectedNodes] = React.useState<NodeKind[]>([]);
const [visibleRows, setVisibleRows] = React.useState<NodeKind[]>([]);
const [osdSize, setOSDSize] = React.useState(defaultRequestSize.NON_BAREMETAL);
const [storageClass, setStorageClass] = React.useState<StorageClassResourceKind>(null);
const dispatch = useDispatch();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@ import {
import { humanizeCpuCores, ResourceLink, pluralize } from '@console/internal/components/utils/';
import { NodeKind } from '@console/internal/module/k8s';
import { Table } from '@console/internal/components/factory';
import { IRow, OnSelect } from '@patternfly/react-table';
import { IRow } from '@patternfly/react-table';
import { hasOCSTaint, hasTaints, getConvertedUnits } from '../../utils/install';
import { cephStorageLabel } from '../../selectors';

import './ocs-install.scss';
import { useSelectList } from '@console/shared/src/hooks/select-list';

const tableColumnClasses = [
classNames('col-md-1', 'col-sm-1', 'col-xs-1'),
Expand Down Expand Up @@ -117,37 +119,11 @@ const getRows: GetRows = ({ componentProps, customData }) => {
const NodeTable: React.FC<NodeTableProps> = (props) => {
const { selectedNodes, setSelectedNodes, visibleRows } = props.customData;

const onSelect: OnSelect = (_event, isSelected, rowIndex, rowData) => {
const selectedUIDs = selectedNodes?.map((node) => node.metadata.uid) ?? [];
const visibleUIDs = visibleRows?.map((row) => row.metadata.uid);
if (rowIndex === -1) {
if (isSelected) {
const uniqueUIDs = _.uniq([...visibleUIDs, ...selectedUIDs]);
setSelectedNodes(
_.uniqBy(
[...visibleRows, ...selectedNodes].filter((node) =>
uniqueUIDs.includes(node.metadata.uid),
),
(n) => n.metadata.uid,
),
);
} else {
setSelectedNodes(
_.uniqBy(
selectedNodes.filter((node) => !visibleUIDs.includes(node.metadata.uid)),
(n) => n.metadata.uid,
),
);
}
} else {
const uniqueUIDs = _.xor(selectedUIDs, [rowData.props.id]);
const data = _.uniqBy(
[...visibleRows, ...selectedNodes].filter((node) => uniqueUIDs.includes(node.metadata.uid)),
(n) => n.metadata.uid,
);
setSelectedNodes(data);
}
};
const { onSelect, selectedRows } = useSelectList(visibleRows);

React.useEffect(() => {
setSelectedNodes(selectedRows);
}, [selectedRows]);

return (
<>
Expand Down
1 change: 1 addition & 0 deletions frontend/packages/console-shared/src/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ export * from './fullscreen';
export * from './scroll';
export * from './plugins-overview-tab-section';
export * from './debounce';
export * from './select-list';
47 changes: 47 additions & 0 deletions frontend/packages/console-shared/src/hooks/select-list.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import * as React from 'react';
import * as _ from 'lodash';
import { K8sResourceKind } from '@console/internal/module/k8s';
import {
IRowData, // eslint-disable-line no-unused-vars
} from '@patternfly/react-table';

export const useSelectList = (visibleRows: K8sResourceKind[]) => {
const [selectedRows, setSelectedRows] = React.useState<K8sResourceKind[]>([]);

const onSelect = (_event: React.MouseEvent, isSelected: boolean, rowIndex: number, rowData: IRowData) => {
const selectedUIDs = selectedRows?.map((node) => node.metadata.uid) ?? [];
const visibleUIDs = visibleRows?.map((row) => row.metadata.uid);
if (rowIndex === -1) {
if (isSelected) {
const uniqueUIDs = _.uniq([...visibleUIDs, ...selectedUIDs]);
setSelectedRows(
_.uniqBy(
[...visibleRows, ...selectedRows].filter((node) =>
uniqueUIDs.includes(node.metadata.uid),
),
(n) => n.metadata.uid,
),
);
} else {
setSelectedRows(
_.uniqBy(
selectedRows.filter((node) => !visibleUIDs.includes(node.metadata.uid)),
(n) => n.metadata.uid,
),
);
}
} else {
const uniqueUIDs = _.xor(selectedUIDs, [rowData?.props?.id]);
const data = _.uniqBy(
[...visibleRows, ...selectedRows].filter((node) => uniqueUIDs.includes(node.metadata.uid)),
(n) => n.metadata.uid,
);
setSelectedRows(data);
}
};
return {
onSelect,
selectedRows
};
};

Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,13 @@ import {
} from '@console/internal/components/utils';
import { history } from '@console/internal/components/utils/router';
import { ListPage } from '@console/internal/components/factory';
import { k8sCreate, referenceFor } from '@console/internal/module/k8s';
import { k8sCreate, referenceFor, NodeKind } from '@console/internal/module/k8s';
import { NodeModel } from '@console/internal/models';
import { ClusterServiceVersionModel } from '@console/operator-lifecycle-manager';
import { LocalVolumeSetModel } from '../../models';
import { NodesSelectionList } from './nodes-selection-list';
import {
RowUIDMap,
LocalVolumeSetKind,
DeviceType,
DiskType,
DeviceMechanicalProperty,
} from './types';
import { getSelectedNodeUIDs } from './utils';
import { LocalVolumeSetKind, DeviceType, DiskType, DeviceMechanicalProperty } from './types';
import { getName } from '@console/shared';
import './create-local-volume-set.scss';

const volumeModeDropdownItems = {
Expand All @@ -56,8 +50,8 @@ const CreateLocalVolumeSet: React.FC = withHandlePromise<CreateLocalVolumeSetPro
const [volumeType, setVolumeType] = React.useState<DiskType>(DiskType.SSD);
const [volumeMode, setVolumeMode] = React.useState(volumeModeDropdownItems.Block);
const [maxVolumeLimit, setMaxVolumeLimit] = React.useState('');
const [rows, setRows] = React.useState<RowUIDMap>({});
const [allSelected, setAllSelected] = React.useState<boolean>(null);
const [selectedNodes, setSelectedNodes] = React.useState<NodeKind[]>([]);
const [visibleRows, setVisibleRows] = React.useState<NodeKind[]>([]);

const { ns, appName } = match.params;
const modelName = LocalVolumeSetModel.label;
Expand Down Expand Up @@ -85,13 +79,12 @@ const CreateLocalVolumeSet: React.FC = withHandlePromise<CreateLocalVolumeSetPro
};

if (showNodesList) {
const selectedNodesUID = getSelectedNodeUIDs(rows);
const selectedNodes = selectedNodesUID.map((uid) => rows[uid].props.data.metadata.name);
const selectedNodesNames = selectedNodes.map((node) => getName(node));
requestData.spec.nodeSelector = {
nodeSelectorTerms: [
{
matchExpressions: [
{ key: 'kubernetes.io/hostname', operator: 'In', values: [...selectedNodes] },
{ key: 'kubernetes.io/hostname', operator: 'In', values: [...selectedNodesNames] },
],
},
],
Expand Down Expand Up @@ -170,7 +163,7 @@ const CreateLocalVolumeSet: React.FC = withHandlePromise<CreateLocalVolumeSetPro
</FormGroup>
{showNodesList && (
<ListPage
customData={{ rows, setRows, allSelected, setAllSelected }}
customData={{ selectedNodes, setSelectedNodes, visibleRows, setVisibleRows }}
showTitle={false}
kind={NodeModel.kind}
ListComponent={NodesSelectionList}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,20 @@ import * as React from 'react';
import * as _ from 'lodash';
import { Text, pluralize } from '@patternfly/react-core';
import * as classNames from 'classnames';
import { sortable, OnSelect } from '@patternfly/react-table';
import { sortable } from '@patternfly/react-table';
import { Table } from '@console/internal/components/factory';
import {
ResourceLink,
humanizeBinaryBytes,
humanizeCpuCores,
convertToBaseValue,
} from '@console/internal/components/utils';
import { NodeKind } from '@console/internal/module/k8s';
import { getUID, getName, getNodeCPUCapacity, getNodeAllocatableMemory } from '@console/shared';
import { NodeTableRow, RowUIDMap } from './types';
import { getSelectedNodeUIDs } from './utils';
import { getName, getNodeCPUCapacity, getNodeAllocatableMemory } from '@console/shared';
import { GetRows } from './types';
import './node-selection-list.scss';
import { useSelectList } from '@console/shared/src/hooks/select-list';
import { IRow } from '@patternfly/react-table';

const tableColumnClasses = [
classNames('pf-u-w-30-on-sm'),
Expand Down Expand Up @@ -50,87 +52,57 @@ const setTableHeader = () => {
];
};

const stateShouldUpdate = (rowUIDMap: RowUIDMap, rows: RowUIDMap): boolean => {
/* On initial render rows will be empty */
if (_.isEmpty(rows)) return true;
return Object.keys(rowUIDMap).some((uid) => rows?.[uid]?.selected !== rowUIDMap?.[uid]?.selected);
};
const getSelected = (selected: NodeKind[], nodeUID: string) =>
selected.map((node) => node.metadata.uid).includes(nodeUID);

const createNodeUIDMap = (nodes: NodeKind[]): NodeUIDMap =>
nodes.reduce((nodeUIDMap: NodeUIDMap, node: NodeKind) => {
const uid = getUID(node);
nodeUIDMap[uid] = node;
return nodeUIDMap;
}, {});
const getRows: GetRows = ({ componentProps, customData }) => {
const { data: filteredData } = componentProps;
const { selectedNodes, setVisibleRows, visibleRows } = customData;

const createRowUIDMap = (nodeUIDMap: NodeUIDMap, rows: RowUIDMap): RowUIDMap =>
Object.keys(nodeUIDMap).reduce((rowUIDMap, uid: string) => {
const node = nodeUIDMap[uid];
const nodeName = getName(node);
const nodeLocation = node.metadata.labels?.['failure-domain.beta.kubernetes.io/zone'] ?? '-';
const nodeCpuCapacity = getNodeCPUCapacity(node);
const nodeAllocatableMemory = getNodeAllocatableMemory(node);
const rows = filteredData.map((node: NodeKind) => {
const cpuSpec: string = getNodeCPUCapacity(node);
const memSpec: string = getNodeAllocatableMemory(node);
const nodeTaints = node.spec?.taints?.length ?? 0;
const cells = [
const cells: IRow['cells'] = [
{
title: <ResourceLink kind="Node" name={nodeName} title={uid} />,
title: <ResourceLink kind="Node" name={getName(node)} title={node.metadata.uid} />,
},
{
title: nodeCpuCapacity || '-',
title: `${humanizeCpuCores(cpuSpec).string || '-'}`,
},
{
title: humanizeBinaryBytes(convertToBaseValue(nodeAllocatableMemory)).string || '-',
title: humanizeBinaryBytes(convertToBaseValue(memSpec)).string ?? '-',
},
{
title: nodeLocation || '-',
title: node.metadata.labels?.['failure-domain.beta.kubernetes.io/zone'] ?? '-',
},
{
title: pluralize(nodeTaints, 'taint'),
},
];
rowUIDMap[uid] = {
return {
cells,
selected: rows?.[uid]?.selected ?? false,
selected: getSelected(selectedNodes, node.metadata.uid),
props: {
data: nodeUIDMap[uid],
uid,
id: node.metadata.uid,
},
};
return rowUIDMap;
}, {});

const setTableRows: SetTableRows = ({ componentProps, customData }) => {
const { data: filteredData } = componentProps;
const { rows, setRows, allSelected, setAllSelected } = customData;

const nodeUIDMap = createNodeUIDMap(filteredData);
const rowUIDMap = createRowUIDMap(nodeUIDMap, rows);
const tableRows = Object.values(rowUIDMap);
});

if (allSelected !== null) {
/* Selecting and deselecting visible table rows */
Object.keys(rowUIDMap).forEach((uid) => (rowUIDMap[uid].selected = allSelected));
setRows({ ...rows, ...rowUIDMap });
setAllSelected(null);
} else if (!_.isEmpty(rowUIDMap) && stateShouldUpdate(rowUIDMap, rows)) {
setRows({ ...rows, ...rowUIDMap });
if (!_.isEqual(filteredData, visibleRows)) {
setVisibleRows(filteredData);
}
return tableRows;
return rows;
};

export const NodesSelectionList: React.FC<NodesSelectionListProps> = (props) => {
const { rows, setRows, setAllSelected } = props.customData;
const { selectedNodes, setSelectedNodes, visibleRows } = props.customData;

const onSelectTableRows: OnSelect = (_event, isSelected, rowId, rowData) => {
const updatedRows: RowUIDMap = { ...rows };
if (rowId === -1) {
setAllSelected(isSelected);
} else {
const { uid } = rowData.props;
updatedRows[uid].selected = isSelected;
setRows({ ...updatedRows });
}
};
const { onSelect, selectedRows } = useSelectList(visibleRows);

React.useEffect(() => {
setSelectedNodes(selectedRows);
}, [selectedRows]);

return (
<>
Expand All @@ -140,14 +112,14 @@ export const NodesSelectionList: React.FC<NodesSelectionListProps> = (props) =>
aria-label="Select nodes for creating volume filter"
data-test-id="create-lvs-form-node-selection-table"
Header={setTableHeader}
Rows={setTableRows}
onSelect={onSelectTableRows}
Rows={getRows}
onSelect={onSelect}
customData={props.customData}
virtualize={false}
/>
</div>
<Text data-test-id="create-lvs-form-selected-nodes" component="h6">
{pluralize(getSelectedNodeUIDs(rows).length, 'node')} selected
{`${pluralize(selectedNodes?.length || 0, 'node')} selected`}
</Text>
</>
);
Expand All @@ -156,18 +128,9 @@ export const NodesSelectionList: React.FC<NodesSelectionListProps> = (props) =>
type NodesSelectionListProps = {
data: NodeKind[];
customData: {
allSelected: boolean;
rows: RowUIDMap;
setAllSelected: React.Dispatch<React.SetStateAction<boolean>>;
setRows: React.Dispatch<React.SetStateAction<RowUIDMap>>;
selectedNodes: NodeKind[];
setSelectedNodes: React.Dispatch<React.SetStateAction<NodeKind[]>>;
visibleRows: NodeKind[];
setVisibleRows: React.Dispatch<React.SetStateAction<NodeKind[]>>;
};
};

type SetTableRows = (props: {
componentProps: { data: NodeKind[] };
customData: NodesSelectionListProps['customData'];
}) => NodeTableRow[];

type NodeUIDMap = {
[key: string]: NodeKind;
};
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,10 @@ import { NodeKind, K8sResourceCommon } from '@console/internal/module/k8s';

export type NodeTableRow = {
cells: IRow['cells'];
selected: IRow['selected'];
props: {
data: NodeKind;
uid: string;
id: string;
};
};

export type RowUIDMap = {
[key: string]: NodeTableRow;
selected: boolean;
};

export enum DiskType {
Expand Down Expand Up @@ -49,3 +44,15 @@ export type LocalVolumeSetKind = K8sResourceCommon & {
maxDeviceCount?: number;
};
};

export type GetRows = ({
componentProps,
customData,
}: {
componentProps: { data: NodeKind[] };
customData: {
selectedNodes: NodeKind[];
visibleRows: NodeKind[];
setVisibleRows: React.Dispatch<React.SetStateAction<NodeKind[]>>;
};
}) => NodeTableRow[];

This file was deleted.

0 comments on commit 2ba07dc

Please sign in to comment.