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 29, 2020
1 parent c882a73 commit f83ef18
Show file tree
Hide file tree
Showing 10 changed files with 264 additions and 170 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
} from '@console/internal/module/k8s';
import { ListPage } from '@console/internal/components/factory';
import { NodeModel } from '@console/internal/models';
import { hasLabel, getName } from '@console/shared';
import { getName, hasLabel } from '@console/shared';
import {
withHandlePromise,
HandlePromiseProps,
Expand All @@ -38,6 +38,7 @@ import { cephStorageLabel } from '../../selectors';
import NodeTable from './node-list';
import { PVsAvailableCapacity } from './pvs-available-capacity';
import { OCS_FLAG, OCS_CONVERGED_FLAG } from '../../features';

import './ocs-install.scss';

const makeLabelNodesRequest = (selectedNodes: NodeKind[]): Promise<NodeKind>[] => {
Expand Down Expand Up @@ -96,16 +97,15 @@ export const CreateOCSServiceForm = withHandlePromise<
params: { appName, ns },
},
} = props;
const [selectedNodes, setSelectedNodes] = React.useState<NodeKind[]>(null);
const [visibleRows, setVisibleRows] = React.useState<NodeKind[]>(null);
const [osdSize, setOSDSize] = React.useState(defaultRequestSize.NON_BAREMETAL);
const [storageClass, setStorageClass] = React.useState<StorageClassResourceKind>(null);
const dispatch = useDispatch();
const [nodes, setNodes] = React.useState<NodeKind[]>([]);

const submit = (event: React.MouseEvent<HTMLButtonElement>) => {
event.preventDefault();
// eslint-disable-next-line promise/catch-or-return
handlePromise(makeOCSRequest(selectedNodes, storageClass, osdSize)).then(() => {
handlePromise(makeOCSRequest(nodes, storageClass, osdSize)).then(() => {
dispatch(setFlag(OCS_CONVERGED_FLAG, true));
dispatch(setFlag(OCS_FLAG, true));
history.push(
Expand Down Expand Up @@ -153,7 +153,9 @@ export const CreateOCSServiceForm = withHandlePromise<
kind={NodeModel.kind}
showTitle={false}
ListComponent={NodeTable}
customData={{ selectedNodes, setSelectedNodes, visibleRows, setVisibleRows }}
customData={{
onRowSelected: setNodes,
}}
/>
</FormGroup>
<FormGroup
Expand Down Expand Up @@ -200,7 +202,7 @@ export const CreateOCSServiceForm = withHandlePromise<
type="button"
variant="primary"
onClick={submit}
isDisabled={(selectedNodes?.length ?? 0) < minSelectedNode}
isDisabled={(nodes?.length ?? 0) < minSelectedNode}
>
Create
</Button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@ import {
getNodeAllocatableMemory,
hasLabel,
} from '@console/shared';
import { useSelectList } from '@console/shared/src/hooks/select-list';
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';

const tableColumnClasses = [
Expand Down Expand Up @@ -50,25 +52,28 @@ const getColumns = () => {
];
};

const getSelected = (selected: NodeKind[], nodeUID: string) =>
selected.map((node) => node.metadata.uid).includes(nodeUID);
const isSelected = (selected: Set<string>, nodeUID: string): boolean => selected.has(nodeUID);

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

const getRows: GetRows = ({ componentProps, customData }) => {
const getRows: GetRows = (
{ componentProps },
visibleRows,
setVisibleRows,
selectedNodes,
setSelectedNodes,
) => {
const { data } = componentProps;
const { selectedNodes, setSelectedNodes, setVisibleRows, visibleRows } = customData;

const filteredData = data.filter((node: NodeKind) => hasOCSTaint(node) || !hasTaints(node));

Expand All @@ -95,59 +100,36 @@ const getRows: GetRows = ({ componentProps, customData }) => {
];
return {
cells,
selected: _.isArray(selectedNodes)
? getSelected(selectedNodes, node.metadata.uid)
selected: selectedNodes
? isSelected(selectedNodes, node.metadata.uid)
: hasLabel(node, cephStorageLabel),
props: {
id: node.metadata.uid,
},
};
});

if (!_.isEqual(filteredData, visibleRows)) {
setVisibleRows(filteredData);
if (!selectedNodes && filteredData.length) {
const uids = new Set(filteredData.map((n) => n.metadata.uid));

if (!_.isEqual(uids, visibleRows)) {
setVisibleRows(uids);
if (!selectedNodes?.size && filteredData.length) {
const preSelected = filteredData.filter((row) => hasLabel(row, cephStorageLabel));
setSelectedNodes(preSelected);
}
}

return rows;
};

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 [visibleRows, setVisibleRows] = React.useState<Set<string>>();

const {
onSelect,
selectedRows: selectedNodes,
updateSelectedRows: setSelectedNodes,
} = useSelectList<NodeKind>(props.data, visibleRows, props.customData.onRowSelected);

return (
<>
Expand All @@ -156,14 +138,16 @@ const NodeTable: React.FC<NodeTableProps> = (props) => {
aria-label="Node Table"
data-test-id="select-nodes-table"
{...props}
Rows={getRows}
Rows={(rowProps) =>
getRows(rowProps, visibleRows, setVisibleRows, selectedNodes, setSelectedNodes)
}
Header={getColumns}
virtualize={false}
onSelect={onSelect}
/>
</div>
<p className="control-label help-block" data-test-id="nodes-selected">
{`${pluralize(selectedNodes?.length || 0, 'node')} selected`}
{pluralize(selectedNodes?.size, 'node')} selected
</p>
</>
);
Expand All @@ -174,10 +158,7 @@ export default NodeTable;
type NodeTableProps = {
data: NodeKind[];
customData: {
selectedNodes: NodeKind[];
setSelectedNodes: React.Dispatch<React.SetStateAction<NodeKind[]>>;
visibleRows: NodeKind[];
setVisibleRows: React.Dispatch<React.SetStateAction<NodeKind[]>>;
onRowSelected: (nodes: NodeKind[]) => void;
};
filters: { name: string; label: { all: string[] } };
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
export const data = [
{
metadata: {
name: 'node-1.internal',
uid: '1',
labels: {
'failure-domain.beta.kubernetes.io/zone': 'us-east-1a',
'kubernetes.io/hostname': 'node-1',
'node-role.kubernetes.io/worker': '',
},
status: {
capacity: {
cpu: '16',
},
allocatable: {
memory: '1000Ki',
},
},
},
},
{
metadata: {
name: 'node-2.internal',
uid: '2',
labels: {
'failure-domain.beta.kubernetes.io/zone': 'us-east-1b',
'kubernetes.io/hostname': 'node-2',
'node-role.kubernetes.io/worker': '',
},
status: {
capacity: {
cpu: '16',
},
allocatable: {
memory: '1000Ki',
},
},
},
},
{
metadata: {
name: 'node-3.internal',
uid: '3',
labels: {
'failure-domain.beta.kubernetes.io/zone': 'us-east-1c',
'kubernetes.io/hostname': 'node-3',
'node-role.kubernetes.io/worker': '',
},
status: {
capacity: {
cpu: '16',
},
allocatable: {
memory: '1000Ki',
},
},
},
},
];

export const visibleRows = new Set(['1', '2', '3']);

export const onRowSelected = jest.fn();
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { act } from 'react-dom/test-utils';
import { testHook } from '../../test-utils/hooks-utils';
import { useSelectList } from '../select-list';
import { data, visibleRows, onRowSelected } from '../__mocks__/select-list-data';

describe('useSelectList', () => {
let onSelect;
let selectedRows;
let updateSelectedRows;

beforeEach(() => {
testHook(() => {
({ onSelect, selectedRows, updateSelectedRows } = useSelectList(
data,
visibleRows,
onRowSelected,
));
});
});

it('onSelect should update selectedRows properly', () => {
act(() => {
onSelect({}, true, -1);
});
expect(selectedRows).toEqual(visibleRows);
act(() => {
onSelect({}, false, 1, { props: { id: '2' } });
});
expect(selectedRows).toEqual(new Set(['1', '3']));
act(() => {
onSelect({}, false, -1);
});
expect(selectedRows).toEqual(new Set());
});

it('updateSelectedRows should update selectedRows properly and call onRowSelected', () => {
act(() => {
updateSelectedRows(data);
});
expect(selectedRows).toEqual(visibleRows);
expect(onRowSelected).toHaveBeenCalled();
});
});
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 { OnSelect } from '@patternfly/react-table';
import { K8sResourceCommon } from '@console/internal/module/k8s';

export const useSelectList = <R extends K8sResourceCommon>(
data: R[],
visibleRows: Set<string>,
onRowSelected: (rows: R[]) => void,
): {
onSelect: OnSelect;
selectedRows: Set<string>;
updateSelectedRows: (rows: R[]) => void;
} => {
const [selectedRows, setSelectedRows] = React.useState<Set<string>>(new Set());

const onSelect = React.useCallback(
(_event, isSelected, rowIndex, rowData) => {
const uniqueUIDs: Set<string> = selectedRows ? new Set([...selectedRows]) : new Set<string>();

if (rowIndex === -1) {
isSelected
? visibleRows.forEach((uid) => uniqueUIDs.add(uid))
: visibleRows.forEach((uid) => uniqueUIDs.delete(uid));
} else {
isSelected ? uniqueUIDs.add(rowData?.props?.id) : uniqueUIDs.delete(rowData?.props?.id);
}

setSelectedRows(uniqueUIDs);
onRowSelected(data.filter((row) => uniqueUIDs.has(row.metadata.uid)));
},
[data, onRowSelected, selectedRows, visibleRows],
);

const updateSelectedRows = React.useCallback(
(rows: R[]) => {
onRowSelected(rows);
setSelectedRows(new Set(rows.map((row) => row.metadata.uid)));
},
[onRowSelected],
);

return {
onSelect,
selectedRows,
updateSelectedRows,
};
};

0 comments on commit f83ef18

Please sign in to comment.