convert InstanceList to table

This commit is contained in:
Keith J. Grant 2021-06-07 15:06:12 -07:00
parent d3b20e6585
commit 8078daa733
3 changed files with 161 additions and 174 deletions

View File

@ -5,9 +5,11 @@ import { useLocation, useParams } from 'react-router-dom';
import 'styled-components/macro';
import DataListToolbar from '../../../components/DataListToolbar';
import PaginatedDataList, {
ToolbarAddButton,
} from '../../../components/PaginatedDataList';
import PaginatedTable, {
HeaderRow,
HeaderCell,
} from '../../../components/PaginatedTable';
import { ToolbarAddButton } from '../../../components/PaginatedDataList';
import DisassociateButton from '../../../components/DisassociateButton';
import AssociateModal from '../../../components/AssociateModal';
import AlertModal from '../../../components/AlertModal';
@ -73,9 +75,13 @@ function InstanceList() {
}
);
const { selected, isAllSelected, handleSelect, setSelected } = useSelected(
instances
);
const {
selected,
isAllSelected,
handleSelect,
clearSelected,
selectAll,
} = useSelected(instances);
useEffect(() => {
fetchInstances();
@ -116,7 +122,7 @@ function InstanceList() {
const handleDisassociate = async () => {
await disassociateInstances();
setSelected([]);
clearSelected();
};
const { error, dismissError } = useDismissableError(
@ -140,14 +146,14 @@ function InstanceList() {
return (
<>
<PaginatedDataList
<PaginatedTable
contentError={contentError}
hasContentLoading={isLoading || isDisassociateLoading}
items={instances}
itemCount={count}
pluralizedItemName={t`Instances`}
qsConfig={QS_CONFIG}
onRowClick={handleSelect}
clearSelected={clearSelected}
toolbarSearchableKeys={searchableKeys}
toolbarRelatedSearchableKeys={relatedSearchableKeys}
toolbarSearchColumns={[
@ -168,9 +174,7 @@ function InstanceList() {
{...props}
showSelectAll
isAllSelected={isAllSelected}
onSelectAll={isSelected =>
setSelected(isSelected ? [...instances] : [])
}
onSelectAll={selectAll}
qsConfig={QS_CONFIG}
additionalControls={[
...(canAdd
@ -200,7 +204,18 @@ function InstanceList() {
}
/>
)}
renderItem={instance => (
headerRow={
<HeaderRow qsConfig={QS_CONFIG}>
<HeaderCell sortKey="name">{t`Name`}</HeaderCell>
<HeaderCell>{t`Type`}</HeaderCell>
<HeaderCell>{t`Running Jobs`}</HeaderCell>
<HeaderCell>{t`Total Jobs`}</HeaderCell>
<HeaderCell>{t`Capacity Adjustment`}</HeaderCell>
<HeaderCell>{t`Used Capacity`}</HeaderCell>
<HeaderCell>{t`Actions`}</HeaderCell>
</HeaderRow>
}
renderRow={(instance, index) => (
<InstanceListItem
key={instance.id}
value={instance.hostname}
@ -208,6 +223,7 @@ function InstanceList() {
onSelect={() => handleSelect(instance)}
isSelected={selected.some(row => row.id === instance.id)}
fetchInstances={fetchInstances}
rowIndex={index}
/>
)}
/>

View File

@ -4,19 +4,14 @@ import { t, Plural } from '@lingui/macro';
import styled from 'styled-components';
import 'styled-components/macro';
import {
Badge as PFBadge,
Progress,
ProgressMeasureLocation,
ProgressSize,
DataListAction as PFDataListAction,
DataListCheck,
DataListItem as PFDataListItem,
DataListItemRow as PFDataListItemRow,
DataListItemCells as PFDataListItemCells,
Slider,
} from '@patternfly/react-core';
import { Tr, Td } from '@patternfly/react-table';
import _DataListCell from '../../../components/DataListCell';
import { ActionsTd, ActionItem } from '../../../components/PaginatedTable';
import InstanceToggle from '../../../components/InstanceToggle';
import { Instance } from '../../../types';
import useRequest, { useDismissableError } from '../../../util/useRequest';
@ -26,44 +21,10 @@ import { useConfig } from '../../../contexts/Config';
import AlertModal from '../../../components/AlertModal';
import ErrorDetail from '../../../components/ErrorDetail';
const DataListItem = styled(PFDataListItem)`
display: flex;
flex-direction: column;
justify-content: center;
`;
const DataListItemRow = styled(PFDataListItemRow)`
align-items: center;
`;
const DataListItemCells = styled(PFDataListItemCells)`
align-items: center;
`;
const DataListAction = styled(PFDataListAction)`
align-items: center;
`;
const Unavailable = styled.span`
color: var(--pf-global--danger-color--200);
`;
const DataListCell = styled(_DataListCell)`
white-space: nowrap;
align-items: center;
`;
const Badge = styled(PFBadge)`
margin-left: 8px;
`;
const ListGroup = styled.span`
margin-left: 12px;
&:first-of-type {
margin-left: 0;
}
`;
const SliderHolder = styled.div`
display: flex;
align-items: center;
@ -86,7 +47,13 @@ function computeForks(memCapacity, cpuCapacity, selectedCapacityAdjustment) {
);
}
function InstanceListItem({ instance, isSelected, onSelect, fetchInstances }) {
function InstanceListItem({
instance,
isSelected,
onSelect,
fetchInstances,
rowIndex,
}) {
const { me = {} } = useConfig();
const [forks, setForks] = useState(
computeForks(
@ -137,92 +104,64 @@ function InstanceListItem({ instance, isSelected, onSelect, fetchInstances }) {
return (
<>
<DataListItem
aria-labelledby={labelId}
id={`${instance.id}`}
key={instance.id}
>
<DataListItemRow>
<DataListCheck
aria-labelledby={labelId}
checked={isSelected}
id={`instances-${instance.id}`}
onChange={onSelect}
/>
<DataListItemCells
dataListCells={[
<DataListCell key="name" aria-label={t`instance host name`}>
<b>{instance.hostname}</b>
</DataListCell>,
<DataListCell key="type" aria-label={t`instance type`}>
<b css="margin-right: 24px">{t`Type`}</b>
<span id={labelId}>
{instance.managed_by_policy ? t`Auto` : t`Manual`}
</span>
</DataListCell>,
<DataListCell
key="related-field-counts"
aria-label={t`instance counts`}
width={3}
>
<ListGroup>
<b>{t`Running jobs`}</b>
<Badge isRead>{instance.jobs_running}</Badge>
</ListGroup>
<ListGroup>
<b>{t`Total jobs`}</b>
<Badge isRead>{instance.jobs_total}</Badge>
</ListGroup>
</DataListCell>,
<DataListCell
key="capacity-adjustment"
aria-label={t`capacity adjustment`}
width={4}
>
<SliderHolder data-cy="slider-holder">
<div data-cy="cpu-capacity">{t`CPU ${instance.cpu_capacity}`}</div>
<SliderForks data-cy="slider-forks">
<div data-cy="number-forks">
<Plural value={forks} one="# fork" other="# forks" />
</div>
<Slider
areCustomStepsContinuous
max={1}
min={0}
step={0.1}
value={instance.capacity_adjustment}
onChange={handleChangeValue}
isDisabled={!me?.is_superuser || !instance.enabled}
data-cy="slider"
/>
</SliderForks>
<div data-cy="mem-capacity">{t`RAM ${instance.mem_capacity}`}</div>
</SliderHolder>
</DataListCell>,
<DataListCell
key="capacity"
aria-label={t`instance group used capacity`}
>
{usedCapacity(instance)}
</DataListCell>,
]}
/>
<DataListAction
aria-label={t`actions`}
aria-labelledby={labelId}
id={labelId}
>
<Tr id={`instance-row-${instance.id}`}>
<Td
select={{
rowIndex,
isSelected,
onSelect,
disable: false,
}}
dataLabel={t`Selected`}
/>
<Td id={labelId} dataLabel={t`Name`}>
{instance.hostname}
</Td>
<Td dataLabel={t`Type`}>
{instance.managed_by_policy ? t`Auto` : t`Manual`}
</Td>
<Td dataLabel={t`Running Jobs`}>{instance.jobs_running}</Td>
<Td dataLabel={t`Total Jobs`}>{instance.jobs_total}</Td>
<Td dataLabel={t`Capacity Adjustment`}>
<SliderHolder data-cy="slider-holder">
<div data-cy="cpu-capacity">{t`CPU ${instance.cpu_capacity}`}</div>
<SliderForks data-cy="slider-forks">
<div data-cy="number-forks">
<Plural value={forks} one="# fork" other="# forks" />
</div>
<Slider
areCustomStepsContinuous
max={1}
min={0}
step={0.1}
value={instance.capacity_adjustment}
onChange={handleChangeValue}
isDisabled={!me?.is_superuser || !instance.enabled}
data-cy="slider"
/>
</SliderForks>
<div data-cy="mem-capacity">{t`RAM ${instance.mem_capacity}`}</div>
</SliderHolder>
</Td>
<Td
dataLabel={t`Instance group used capacity`}
css="--pf-c-table--cell--MinWidth: 175px;"
>
{usedCapacity(instance)}
</Td>
<ActionsTd
dataLabel={t`Actions`}
css="--pf-c-table--cell--Width: 125px"
>
<ActionItem visible>
<InstanceToggle
css="display: inline-flex;"
fetchInstances={fetchInstances}
instance={instance}
/>
</DataListAction>
</DataListItemRow>
</DataListItem>
</ActionItem>
</ActionsTd>
</Tr>
{updateError && (
<AlertModal
variant="error"

View File

@ -49,12 +49,16 @@ describe('<InstanceListItem/>', () => {
test('should mount successfully', async () => {
await act(async () => {
wrapper = mountWithContexts(
<InstanceListItem
instance={instance[0]}
isSelected={false}
onSelect={() => {}}
fetchInstances={() => {}}
/>
<table>
<tbody>
<InstanceListItem
instance={instance[0]}
isSelected={false}
onSelect={() => {}}
fetchInstances={() => {}}
/>
</tbody>
</table>
);
});
expect(wrapper.find('InstanceListItem').length).toBe(1);
@ -63,12 +67,16 @@ describe('<InstanceListItem/>', () => {
test('should calculate number of forks when slide changes', async () => {
await act(async () => {
wrapper = mountWithContexts(
<InstanceListItem
instance={instance[0]}
isSelected={false}
onSelect={() => {}}
fetchInstances={() => {}}
/>
<table>
<tbody>
<InstanceListItem
instance={instance[0]}
isSelected={false}
onSelect={() => {}}
fetchInstances={() => {}}
/>
</tbody>
</table>
);
});
expect(wrapper.find('InstanceListItem').length).toBe(1);
@ -105,30 +113,41 @@ describe('<InstanceListItem/>', () => {
test('should render the proper data instance', async () => {
await act(async () => {
wrapper = mountWithContexts(
<InstanceListItem
instance={instance[0]}
isSelected={false}
onSelect={() => {}}
fetchInstances={() => {}}
/>
<table>
<tbody>
<InstanceListItem
instance={instance[0]}
isSelected={false}
onSelect={() => {}}
fetchInstances={() => {}}
/>
</tbody>
</table>
);
});
expect(
wrapper.find('PFDataListCell[aria-label="instance host name"]').text()
wrapper
.find('Td')
.at(1)
.text()
).toBe('awx');
expect(wrapper.find('Progress').prop('value')).toBe(40);
expect(
wrapper.find('PFDataListCell[aria-label="instance type"]').text()
).toBe('TypeAuto');
expect(wrapper.find('input#instances-1').prop('checked')).toBe(false);
wrapper
.find('Td')
.at(2)
.text()
).toBe('Auto');
expect(
wrapper
.find('PFDataListCell[aria-label="capacity adjustment"]')
.find('Td')
.at(5)
.containsMatchingElement(<div>CPU 24</div>)
);
expect(
wrapper
.find('PFDataListCell[aria-label="capacity adjustment"]')
.find('Td')
.at(5)
.containsMatchingElement(<div>RAM 24</div>)
);
expect(wrapper.find('InstanceListItem__SliderForks').text()).toContain(
@ -136,18 +155,27 @@ describe('<InstanceListItem/>', () => {
);
});
test('should be checked', async () => {
test('should render checkbox', async () => {
const onSelect = jest.fn();
await act(async () => {
wrapper = mountWithContexts(
<InstanceListItem
instance={instance[0]}
isSelected
onSelect={() => {}}
fetchInstances={() => {}}
/>
<table>
<tbody>
<InstanceListItem
instance={instance[0]}
onSelect={onSelect}
fetchInstances={() => {}}
/>
</tbody>
</table>
);
});
expect(wrapper.find('input#instances-1').prop('checked')).toBe(true);
expect(
wrapper
.find('Td')
.first()
.prop('select').onSelect
).toEqual(onSelect);
});
test('should display instance toggle', () => {
@ -176,12 +204,16 @@ describe('<InstanceListItem/>', () => {
);
await act(async () => {
wrapper = mountWithContexts(
<InstanceListItem
instance={instance[0]}
isSelected={false}
onSelect={() => {}}
fetchInstances={() => {}}
/>,
<table>
<tbody>
<InstanceListItem
instance={instance[0]}
isSelected={false}
onSelect={() => {}}
fetchInstances={() => {}}
/>
</tbody>
</table>,
{ context: { network: { handleHttpError: () => {} } } }
);
});