convert ee template list, host group list to tables

This commit is contained in:
Keith J. Grant
2021-06-11 10:31:47 -07:00
committed by Shane McDonald
parent 0b4a296181
commit c6fa85036e
7 changed files with 164 additions and 153 deletions

View File

@@ -8,7 +8,10 @@ import { ExecutionEnvironmentsAPI } from '../../../api';
import { getQSConfig, parseQueryString } from '../../../util/qs'; import { getQSConfig, parseQueryString } from '../../../util/qs';
import useRequest from '../../../util/useRequest'; import useRequest from '../../../util/useRequest';
import DatalistToolbar from '../../../components/DataListToolbar'; import DatalistToolbar from '../../../components/DataListToolbar';
import PaginatedDataList from '../../../components/PaginatedDataList'; import PaginatedTable, {
HeaderCell,
HeaderRow,
} from '../../../components/PaginatedTable';
import ExecutionEnvironmentTemplateListItem from './ExecutionEnvironmentTemplateListItem'; import ExecutionEnvironmentTemplateListItem from './ExecutionEnvironmentTemplateListItem';
@@ -74,7 +77,7 @@ function ExecutionEnvironmentTemplateList({ executionEnvironment }) {
return ( return (
<> <>
<Card> <Card>
<PaginatedDataList <PaginatedTable
contentError={contentError} contentError={contentError}
hasContentLoading={isLoading} hasContentLoading={isLoading}
items={templates} items={templates}
@@ -106,24 +109,16 @@ function ExecutionEnvironmentTemplateList({ executionEnvironment }) {
key: 'modified_by__username__icontains', key: 'modified_by__username__icontains',
}, },
]} ]}
toolbarSortColumns={[
{
name: t`Name`,
key: 'name',
},
{
name: t`Created`,
key: 'created',
},
{
name: t`Modified`,
key: 'modified',
},
]}
renderToolbar={props => ( renderToolbar={props => (
<DatalistToolbar {...props} qsConfig={QS_CONFIG} /> <DatalistToolbar {...props} qsConfig={QS_CONFIG} />
)} )}
renderItem={template => ( headerRow={
<HeaderRow qsConfig={QS_CONFIG} isSelectable={false}>
<HeaderCell sortKey="name">{t`Name`}</HeaderCell>
<HeaderCell>{t`Type`}</HeaderCell>
</HeaderRow>
}
renderRow={template => (
<ExecutionEnvironmentTemplateListItem <ExecutionEnvironmentTemplateListItem
key={template.id} key={template.id}
template={template} template={template}

View File

@@ -1,38 +1,20 @@
import React from 'react'; import React from 'react';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { import { Tr, Td } from '@patternfly/react-table';
DataListItem,
DataListItemRow,
DataListItemCells,
} from '@patternfly/react-core';
import DataListCell from '../../../components/DataListCell';
function ExecutionEnvironmentTemplateListItem({ template, detailUrl }) { function ExecutionEnvironmentTemplateListItem({ template, detailUrl }) {
return ( return (
<DataListItem <Tr id={`template-row-${template.id}`}>
key={template.id} <Td dataLabel={t`Name`}>
aria-labelledby={`check-action-${template.id}`} <Link to={`${detailUrl}`}>{template.name}</Link>
id={`${template.id}`} </Td>
> <Td dataLabel={t`Type`}>
<DataListItemRow> {template.type === 'job_template'
<DataListItemCells ? t`Job Template`
dataListCells={[ : t`Workflow Job Template`}
<DataListCell key="name" aria-label={t`Name`}> </Td>
<Link to={`${detailUrl}`}> </Tr>
<b>{template.name}</b>
</Link>
</DataListCell>,
<DataListCell key="template-type" aria-label={t`Template type`}>
{template.type === 'job_template'
? t`Job Template`
: t`Workflow Job Template`}
</DataListCell>,
]}
/>
</DataListItemRow>
</DataListItem>
); );
} }

View File

@@ -16,33 +16,50 @@ describe('<ExecutionEnvironmentTemplateListItem/>', () => {
test('should mount successfully', async () => { test('should mount successfully', async () => {
await act(async () => { await act(async () => {
wrapper = mountWithContexts( wrapper = mountWithContexts(
<ExecutionEnvironmentTemplateListItem <table>
template={template} <tbody>
detailUrl={`/templates/${template.type}/${template.id}/details`} <ExecutionEnvironmentTemplateListItem
/> template={template}
detailUrl={`/templates/${template.type}/${template.id}/details`}
/>
</tbody>
</table>
); );
}); });
expect(wrapper.find('ExecutionEnvironmentTemplateListItem').length).toBe(1); expect(wrapper.find('ExecutionEnvironmentTemplateListItem').length).toBe(1);
expect(wrapper.find('DataListCell[aria-label="Name"]').text()).toBe(
template.name
);
expect( expect(
wrapper.find('DataListCell[aria-label="Template type"]').text() wrapper
.find('Td')
.at(0)
.text()
).toBe(template.name);
expect(
wrapper
.find('Td')
.at(1)
.text()
).toBe('Job Template'); ).toBe('Job Template');
}); });
test('should distinguish template types', async () => { test('should distinguish template types', async () => {
await act(async () => { await act(async () => {
wrapper = mountWithContexts( wrapper = mountWithContexts(
<ExecutionEnvironmentTemplateListItem <table>
template={{ ...template, type: 'workflow_job_template' }} <tbody>
detailUrl={`/templates/${template.type}/${template.id}/details`} <ExecutionEnvironmentTemplateListItem
/> template={{ ...template, type: 'workflow_job_template' }}
detailUrl={`/templates/${template.type}/${template.id}/details`}
/>
</tbody>
</table>
); );
}); });
expect(wrapper.find('ExecutionEnvironmentTemplateListItem').length).toBe(1); expect(wrapper.find('ExecutionEnvironmentTemplateListItem').length).toBe(1);
expect( expect(
wrapper.find('DataListCell[aria-label="Template type"]').text() wrapper
.find('Td')
.at(1)
.text()
).toBe('Workflow Job Template'); ).toBe('Workflow Job Template');
}); });
}); });

View File

@@ -3,64 +3,50 @@ import { bool, func, number, oneOfType, string } from 'prop-types';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import { import { Button } from '@patternfly/react-core';
Button, import { Tr, Td } from '@patternfly/react-table';
DataListAction,
DataListCheck,
DataListItem,
DataListItemCells,
DataListItemRow,
Tooltip,
} from '@patternfly/react-core';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { PencilAltIcon } from '@patternfly/react-icons'; import { PencilAltIcon } from '@patternfly/react-icons';
import DataListCell from '../../../components/DataListCell'; import { ActionsTd, ActionItem } from '../../../components/PaginatedTable';
import { Group } from '../../../types'; import { Group } from '../../../types';
function HostGroupItem({ group, inventoryId, isSelected, onSelect }) { function HostGroupItem({ group, inventoryId, isSelected, onSelect, rowIndex }) {
const labelId = `check-action-${group.id}`; const labelId = `check-action-${group.id}`;
const detailUrl = `/inventories/inventory/${inventoryId}/groups/${group.id}/details`; const detailUrl = `/inventories/inventory/${inventoryId}/groups/${group.id}/details`;
const editUrl = `/inventories/inventory/${inventoryId}/groups/${group.id}/edit`; const editUrl = `/inventories/inventory/${inventoryId}/groups/${group.id}/edit`;
return ( return (
<DataListItem key={group.id} aria-labelledby={labelId} id={`${group.id}`}> <Tr id={`group-row-${group.id}`}>
<DataListItemRow> <Td
<DataListCheck select={{
aria-labelledby={labelId} rowIndex,
id={`select-group-${group.id}`} isSelected,
checked={isSelected} onSelect,
onChange={onSelect} }}
/> dataLabel={t`Selected`}
<DataListItemCells />
dataListCells={[ <Td dataLabel={t`Name`}>
<DataListCell key="name"> {' '}
<Link to={`${detailUrl}`} id={labelId}> <Link to={`${detailUrl}`} id={labelId}>
<b>{group.name}</b> <b>{group.name}</b>
</Link> </Link>
</DataListCell>, </Td>
]} <ActionsTd dataLabel={t`Actions`}>
/> <ActionItem
<DataListAction visible={group.summary_fields.user_capabilities.edit}
aria-label={t`actions`} tooltip={t`Edit Group`}
aria-labelledby={labelId}
id={labelId}
> >
{group.summary_fields.user_capabilities.edit && ( <Button
<Tooltip content={t`Edit Group`} position="top"> ouiaId={`${group.id}-edit-button`}
<Button variant="plain"
ouiaId={`${group.id}-edit-button`} component={Link}
variant="plain" to={editUrl}
component={Link} >
to={editUrl} <PencilAltIcon />
> </Button>
<PencilAltIcon /> </ActionItem>
</Button> </ActionsTd>
</Tooltip> </Tr>
)}
</DataListAction>
</DataListItemRow>
</DataListItem>
); );
} }

View File

@@ -18,12 +18,16 @@ describe('<HostGroupItem />', () => {
beforeEach(() => { beforeEach(() => {
wrapper = mountWithContexts( wrapper = mountWithContexts(
<HostGroupItem <table>
group={mockGroup} <tbody>
inventoryId={1} <HostGroupItem
isSelected={false} group={mockGroup}
onSelect={() => {}} inventoryId={1}
/> isSelected={false}
onSelect={() => {}}
/>
</tbody>
</table>
); );
}); });
@@ -40,12 +44,16 @@ describe('<HostGroupItem />', () => {
copyMockGroup.summary_fields.user_capabilities.edit = false; copyMockGroup.summary_fields.user_capabilities.edit = false;
wrapper = mountWithContexts( wrapper = mountWithContexts(
<HostGroupItem <table>
group={copyMockGroup} <tbody>
inventoryId={1} <HostGroupItem
isSelected={false} group={copyMockGroup}
onSelect={() => {}} inventoryId={1}
/> isSelected={false}
onSelect={() => {}}
/>
</tbody>
</table>
); );
expect(wrapper.find('PencilAltIcon').exists()).toBeFalsy(); expect(wrapper.find('PencilAltIcon').exists()).toBeFalsy();
}); });

View File

@@ -11,9 +11,11 @@ import useSelected from '../../../util/useSelected';
import { HostsAPI, InventoriesAPI } from '../../../api'; import { HostsAPI, InventoriesAPI } from '../../../api';
import AlertModal from '../../../components/AlertModal'; import AlertModal from '../../../components/AlertModal';
import ErrorDetail from '../../../components/ErrorDetail'; import ErrorDetail from '../../../components/ErrorDetail';
import PaginatedDataList, { import PaginatedTable, {
ToolbarAddButton, HeaderCell,
} from '../../../components/PaginatedDataList'; HeaderRow,
} from '../../../components/PaginatedTable';
import { ToolbarAddButton } from '../../../components/PaginatedDataList';
import AssociateModal from '../../../components/AssociateModal'; import AssociateModal from '../../../components/AssociateModal';
import DisassociateButton from '../../../components/DisassociateButton'; import DisassociateButton from '../../../components/DisassociateButton';
import DataListToolbar from '../../../components/DataListToolbar'; import DataListToolbar from '../../../components/DataListToolbar';
@@ -82,9 +84,13 @@ function HostGroupsList({ host }) {
fetchGroups(); fetchGroups();
}, [fetchGroups]); }, [fetchGroups]);
const { selected, isAllSelected, handleSelect, setSelected } = useSelected( const {
groups selected,
); isAllSelected,
handleSelect,
clearSelected,
selectAll,
} = useSelected(groups);
const { const {
isLoading: isDisassociateLoading, isLoading: isDisassociateLoading,
@@ -105,7 +111,7 @@ function HostGroupsList({ host }) {
const handleDisassociate = async () => { const handleDisassociate = async () => {
await disassociateHosts(); await disassociateHosts();
setSelected([]); clearSelected();
}; };
const fetchGroupsToAssociate = useCallback( const fetchGroupsToAssociate = useCallback(
@@ -146,13 +152,13 @@ function HostGroupsList({ host }) {
return ( return (
<> <>
<PaginatedDataList <PaginatedTable
contentError={contentError} contentError={contentError}
hasContentLoading={isLoading || isDisassociateLoading} hasContentLoading={isLoading || isDisassociateLoading}
items={groups} items={groups}
itemCount={itemCount} itemCount={itemCount}
qsConfig={QS_CONFIG} qsConfig={QS_CONFIG}
onRowClick={handleSelect} clearSelected={clearSelected}
toolbarSearchColumns={[ toolbarSearchColumns={[
{ {
name: t`Name`, name: t`Name`,
@@ -168,15 +174,15 @@ function HostGroupsList({ host }) {
key: 'modified_by__username__icontains', key: 'modified_by__username__icontains',
}, },
]} ]}
toolbarSortColumns={[
{
name: t`Name`,
key: 'name',
},
]}
toolbarSearchableKeys={searchableKeys} toolbarSearchableKeys={searchableKeys}
toolbarRelatedSearchableKeys={relatedSearchableKeys} toolbarRelatedSearchableKeys={relatedSearchableKeys}
renderItem={item => ( headerRow={
<HeaderRow qsConfig={QS_CONFIG}>
<HeaderCell sortKey="name">{t`Name`}</HeaderCell>
<HeaderCell>{t`Actions`}</HeaderCell>
</HeaderRow>
}
renderRow={(item, index) => (
<HostGroupItem <HostGroupItem
key={item.id} key={item.id}
group={item} group={item}
@@ -184,6 +190,7 @@ function HostGroupsList({ host }) {
inventoryId={item.summary_fields.inventory.id} inventoryId={item.summary_fields.inventory.id}
isSelected={selected.some(row => row.id === item.id)} isSelected={selected.some(row => row.id === item.id)}
onSelect={() => handleSelect(item)} onSelect={() => handleSelect(item)}
rowIndex={index}
/> />
)} )}
renderToolbar={props => ( renderToolbar={props => (
@@ -191,9 +198,7 @@ function HostGroupsList({ host }) {
{...props} {...props}
showSelectAll showSelectAll
isAllSelected={isAllSelected} isAllSelected={isAllSelected}
onSelectAll={isSelected => onSelectAll={selectAll}
setSelected(isSelected ? [...groups] : [])
}
qsConfig={QS_CONFIG} qsConfig={QS_CONFIG}
additionalControls={[ additionalControls={[
...(canAdd ...(canAdd

View File

@@ -122,46 +122,63 @@ describe('<HostGroupsList />', () => {
test('should check and uncheck the row item', async () => { test('should check and uncheck the row item', async () => {
expect( expect(
wrapper.find('DataListCheck[id="select-group-1"]').props().checked wrapper
.find('.pf-c-table__check')
.first()
.find('input')
.props().checked
).toBe(false); ).toBe(false);
await act(async () => { await act(async () => {
wrapper.find('DataListCheck[id="select-group-1"]').invoke('onChange')( wrapper
true .find('.pf-c-table__check')
); .first()
.find('input')
.invoke('onChange')(true);
}); });
wrapper.update(); wrapper.update();
expect( expect(
wrapper.find('DataListCheck[id="select-group-1"]').props().checked wrapper
.find('.pf-c-table__check')
.first()
.find('input')
.props().checked
).toBe(true); ).toBe(true);
await act(async () => { await act(async () => {
wrapper.find('DataListCheck[id="select-group-1"]').invoke('onChange')( wrapper
false .find('.pf-c-table__check')
); .first()
.find('input')
.invoke('onChange')(false);
}); });
wrapper.update(); wrapper.update();
expect( expect(
wrapper.find('DataListCheck[id="select-group-1"]').props().checked wrapper
.find('.pf-c-table__check')
.first()
.find('input')
.props().checked
).toBe(false); ).toBe(false);
}); });
test('should check all row items when select all is checked', async () => { test('should check all row items when select all is checked', async () => {
wrapper.find('DataListCheck').forEach(el => { expect.assertions(9);
wrapper.find('.pf-c-table__check input').forEach(el => {
expect(el.props().checked).toBe(false); expect(el.props().checked).toBe(false);
}); });
await act(async () => { await act(async () => {
wrapper.find('Checkbox#select-all').invoke('onChange')(true); wrapper.find('Checkbox#select-all').invoke('onChange')(true);
}); });
wrapper.update(); wrapper.update();
wrapper.find('DataListCheck').forEach(el => { wrapper.find('.pf-c-table__check input').forEach(el => {
expect(el.props().checked).toBe(true); expect(el.props().checked).toBe(true);
}); });
await act(async () => { await act(async () => {
wrapper.find('Checkbox#select-all').invoke('onChange')(false); wrapper.find('Checkbox#select-all').invoke('onChange')(false);
}); });
wrapper.update(); wrapper.update();
wrapper.find('DataListCheck').forEach(el => { wrapper.find('.pf-c-table__check input').forEach(el => {
expect(el.props().checked).toBe(false); expect(el.props().checked).toBe(false);
}); });
}); });
@@ -236,17 +253,18 @@ describe('<HostGroupsList />', () => {
}); });
test('expected api calls are made for multi-disassociation', async () => { test('expected api calls are made for multi-disassociation', async () => {
expect.assertions(11);
expect(HostsAPI.disassociateGroup).toHaveBeenCalledTimes(0); expect(HostsAPI.disassociateGroup).toHaveBeenCalledTimes(0);
expect(HostsAPI.readAllGroups).toHaveBeenCalledTimes(1); expect(HostsAPI.readAllGroups).toHaveBeenCalledTimes(1);
expect(wrapper.find('DataListCheck').length).toBe(3);
wrapper.find('DataListCheck').forEach(el => { wrapper.find('.pf-c-table__check input').forEach(el => {
expect(el.props().checked).toBe(false); expect(el.props().checked).toBe(false);
}); });
await act(async () => { await act(async () => {
wrapper.find('Checkbox#select-all').invoke('onChange')(true); wrapper.find('Checkbox#select-all').invoke('onChange')(true);
}); });
wrapper.update(); wrapper.update();
wrapper.find('DataListCheck').forEach(el => { wrapper.find('.pf-c-table__check input').forEach(el => {
expect(el.props().checked).toBe(true); expect(el.props().checked).toBe(true);
}); });
wrapper.find('button[aria-label="Disassociate"]').simulate('click'); wrapper.find('button[aria-label="Disassociate"]').simulate('click');