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 'styled-components/macro';
import DataListToolbar from '../../../components/DataListToolbar'; import DataListToolbar from '../../../components/DataListToolbar';
import PaginatedDataList, { import PaginatedTable, {
ToolbarAddButton, HeaderRow,
} from '../../../components/PaginatedDataList'; HeaderCell,
} from '../../../components/PaginatedTable';
import { ToolbarAddButton } from '../../../components/PaginatedDataList';
import DisassociateButton from '../../../components/DisassociateButton'; import DisassociateButton from '../../../components/DisassociateButton';
import AssociateModal from '../../../components/AssociateModal'; import AssociateModal from '../../../components/AssociateModal';
import AlertModal from '../../../components/AlertModal'; import AlertModal from '../../../components/AlertModal';
@@ -73,9 +75,13 @@ function InstanceList() {
} }
); );
const { selected, isAllSelected, handleSelect, setSelected } = useSelected( const {
instances selected,
); isAllSelected,
handleSelect,
clearSelected,
selectAll,
} = useSelected(instances);
useEffect(() => { useEffect(() => {
fetchInstances(); fetchInstances();
@@ -116,7 +122,7 @@ function InstanceList() {
const handleDisassociate = async () => { const handleDisassociate = async () => {
await disassociateInstances(); await disassociateInstances();
setSelected([]); clearSelected();
}; };
const { error, dismissError } = useDismissableError( const { error, dismissError } = useDismissableError(
@@ -140,14 +146,14 @@ function InstanceList() {
return ( return (
<> <>
<PaginatedDataList <PaginatedTable
contentError={contentError} contentError={contentError}
hasContentLoading={isLoading || isDisassociateLoading} hasContentLoading={isLoading || isDisassociateLoading}
items={instances} items={instances}
itemCount={count} itemCount={count}
pluralizedItemName={t`Instances`} pluralizedItemName={t`Instances`}
qsConfig={QS_CONFIG} qsConfig={QS_CONFIG}
onRowClick={handleSelect} clearSelected={clearSelected}
toolbarSearchableKeys={searchableKeys} toolbarSearchableKeys={searchableKeys}
toolbarRelatedSearchableKeys={relatedSearchableKeys} toolbarRelatedSearchableKeys={relatedSearchableKeys}
toolbarSearchColumns={[ toolbarSearchColumns={[
@@ -168,9 +174,7 @@ function InstanceList() {
{...props} {...props}
showSelectAll showSelectAll
isAllSelected={isAllSelected} isAllSelected={isAllSelected}
onSelectAll={isSelected => onSelectAll={selectAll}
setSelected(isSelected ? [...instances] : [])
}
qsConfig={QS_CONFIG} qsConfig={QS_CONFIG}
additionalControls={[ additionalControls={[
...(canAdd ...(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 <InstanceListItem
key={instance.id} key={instance.id}
value={instance.hostname} value={instance.hostname}
@@ -208,6 +223,7 @@ function InstanceList() {
onSelect={() => handleSelect(instance)} onSelect={() => handleSelect(instance)}
isSelected={selected.some(row => row.id === instance.id)} isSelected={selected.some(row => row.id === instance.id)}
fetchInstances={fetchInstances} fetchInstances={fetchInstances}
rowIndex={index}
/> />
)} )}
/> />

View File

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

View File

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