Migrate EE list to tables

Migrate EE list to tables.

See:https://github.com/ansible/awx/issues/7884
This commit is contained in:
nixocio
2021-02-08 16:35:28 -05:00
committed by Shane McDonald
parent 26f7a2ba81
commit d6b5990cd2
4 changed files with 135 additions and 101 deletions

View File

@@ -15,6 +15,7 @@ const executionEnvironments = {
data: { data: {
results: [ results: [
{ {
name: 'Foo',
id: 1, id: 1,
image: 'https://registry.com/r/image/manifest', image: 'https://registry.com/r/image/manifest',
organization: null, organization: null,
@@ -23,6 +24,7 @@ const executionEnvironments = {
summary_fields: { user_capabilities: { edit: true, delete: true } }, summary_fields: { user_capabilities: { edit: true, delete: true } },
}, },
{ {
name: 'Bar',
id: 2, id: 2,
image: 'https://registry.com/r/image2/manifest', image: 'https://registry.com/r/image2/manifest',
organization: null, organization: null,
@@ -38,6 +40,14 @@ const executionEnvironments = {
const options = { data: { actions: { POST: true } } }; const options = { data: { actions: { POST: true } } };
describe('<ExecutionEnvironmentList/>', () => { describe('<ExecutionEnvironmentList/>', () => {
beforeEach(() => {
ExecutionEnvironmentsAPI.read.mockResolvedValue(executionEnvironments);
ExecutionEnvironmentsAPI.readOptions.mockResolvedValue(options);
});
afterEach(() => {
jest.clearAllMocks();
});
let wrapper; let wrapper;
test('should mount successfully', async () => { test('should mount successfully', async () => {
@@ -52,9 +62,6 @@ describe('<ExecutionEnvironmentList/>', () => {
}); });
test('should have data fetched and render 2 rows', async () => { test('should have data fetched and render 2 rows', async () => {
ExecutionEnvironmentsAPI.read.mockResolvedValue(executionEnvironments);
ExecutionEnvironmentsAPI.readOptions.mockResolvedValue(options);
await act(async () => { await act(async () => {
wrapper = mountWithContexts(<ExecutionEnvironmentList />); wrapper = mountWithContexts(<ExecutionEnvironmentList />);
}); });
@@ -69,10 +76,7 @@ describe('<ExecutionEnvironmentList/>', () => {
expect(ExecutionEnvironmentsAPI.readOptions).toBeCalled(); expect(ExecutionEnvironmentsAPI.readOptions).toBeCalled();
}); });
test('should delete item successfully', async () => { test('should delete items successfully', async () => {
ExecutionEnvironmentsAPI.read.mockResolvedValue(executionEnvironments);
ExecutionEnvironmentsAPI.readOptions.mockResolvedValue(options);
await act(async () => { await act(async () => {
wrapper = mountWithContexts(<ExecutionEnvironmentList />); wrapper = mountWithContexts(<ExecutionEnvironmentList />);
}); });
@@ -82,27 +86,25 @@ describe('<ExecutionEnvironmentList/>', () => {
el => el.length > 0 el => el.length > 0
); );
wrapper
.find('input#select-execution-environment-1')
.simulate('change', executionEnvironments.data.results[0]);
wrapper.update();
expect(
wrapper.find('input#select-execution-environment-1').prop('checked')
).toBe(true);
await act(async () => { await act(async () => {
wrapper.find('Button[aria-label="Delete"]').prop('onClick')(); wrapper
.find('ExecutionEnvironmentListItem')
.at(0)
.invoke('onSelect')();
}); });
wrapper.update(); wrapper.update();
await act(async () => { await act(async () => {
wrapper.find('Button[aria-label="confirm delete"]').prop('onClick')(); wrapper
.find('ExecutionEnvironmentListItem')
.at(1)
.invoke('onSelect')();
});
wrapper.update();
await act(async () => {
wrapper.find('ToolbarDeleteButton').invoke('onDelete')();
}); });
expect(ExecutionEnvironmentsAPI.destroy).toBeCalledWith( expect(ExecutionEnvironmentsAPI.destroy).toHaveBeenCalledTimes(2);
executionEnvironments.data.results[0].id
);
}); });
test('should render deletion error modal', async () => { test('should render deletion error modal', async () => {
@@ -117,19 +119,24 @@ describe('<ExecutionEnvironmentList/>', () => {
}, },
}) })
); );
ExecutionEnvironmentsAPI.read.mockResolvedValue(executionEnvironments);
ExecutionEnvironmentsAPI.readOptions.mockResolvedValue(options);
await act(async () => { await act(async () => {
wrapper = mountWithContexts(<ExecutionEnvironmentList />); wrapper = mountWithContexts(<ExecutionEnvironmentList />);
}); });
waitForElement(wrapper, 'ExecutionEnvironmentList', el => el.length > 0); waitForElement(wrapper, 'ExecutionEnvironmentList', el => el.length > 0);
wrapper wrapper
.find('input#select-execution-environment-1') .find('ExecutionEnvironmentListItem')
.at(0)
.find('input')
.simulate('change', 'a'); .simulate('change', 'a');
wrapper.update(); wrapper.update();
expect( expect(
wrapper.find('input#select-execution-environment-1').prop('checked') wrapper
.find('ExecutionEnvironmentListItem')
.at(0)
.find('input')
.prop('checked')
).toBe(true); ).toBe(true);
await act(async () => await act(async () =>
@@ -156,7 +163,6 @@ describe('<ExecutionEnvironmentList/>', () => {
}, },
}) })
); );
ExecutionEnvironmentsAPI.readOptions.mockResolvedValue(options);
await act(async () => { await act(async () => {
wrapper = mountWithContexts(<ExecutionEnvironmentList />); wrapper = mountWithContexts(<ExecutionEnvironmentList />);
}); });

View File

@@ -8,10 +8,14 @@ import { ExecutionEnvironmentsAPI } from '../../../api';
import { getQSConfig, parseQueryString } from '../../../util/qs'; import { getQSConfig, parseQueryString } from '../../../util/qs';
import useRequest, { useDeleteItems } from '../../../util/useRequest'; import useRequest, { useDeleteItems } from '../../../util/useRequest';
import useSelected from '../../../util/useSelected'; import useSelected from '../../../util/useSelected';
import PaginatedDataList, { import {
ToolbarDeleteButton, ToolbarDeleteButton,
ToolbarAddButton, ToolbarAddButton,
} from '../../../components/PaginatedDataList'; } from '../../../components/PaginatedDataList';
import PaginatedTable, {
HeaderRow,
HeaderCell,
} from '../../../components/PaginatedTable';
import ErrorDetail from '../../../components/ErrorDetail'; import ErrorDetail from '../../../components/ErrorDetail';
import AlertModal from '../../../components/AlertModal'; import AlertModal from '../../../components/AlertModal';
import DatalistToolbar from '../../../components/DataListToolbar'; import DatalistToolbar from '../../../components/DataListToolbar';
@@ -21,7 +25,7 @@ import ExecutionEnvironmentsListItem from './ExecutionEnvironmentListItem';
const QS_CONFIG = getQSConfig('execution_environments', { const QS_CONFIG = getQSConfig('execution_environments', {
page: 1, page: 1,
page_size: 20, page_size: 20,
order_by: 'image', order_by: 'name',
}); });
function ExecutionEnvironmentList({ i18n }) { function ExecutionEnvironmentList({ i18n }) {
@@ -106,7 +110,7 @@ function ExecutionEnvironmentList({ i18n }) {
<> <>
<PageSection> <PageSection>
<Card> <Card>
<PaginatedDataList <PaginatedTable
contentError={contentError} contentError={contentError}
hasContentLoading={isLoading || deleteLoading} hasContentLoading={isLoading || deleteLoading}
items={executionEnvironments} items={executionEnvironments}
@@ -141,6 +145,13 @@ function ExecutionEnvironmentList({ i18n }) {
key: 'description', key: 'description',
}, },
]} ]}
headerRow={
<HeaderRow qsConfig={QS_CONFIG}>
<HeaderCell sortKey="name">{i18n._(t`Name`)}</HeaderCell>
<HeaderCell>{i18n._(t`Image`)}</HeaderCell>
<HeaderCell>{i18n._(t`Organization`)}</HeaderCell>
</HeaderRow>
}
renderToolbar={props => ( renderToolbar={props => (
<DatalistToolbar <DatalistToolbar
{...props} {...props}
@@ -168,9 +179,10 @@ function ExecutionEnvironmentList({ i18n }) {
]} ]}
/> />
)} )}
renderItem={executionEnvironment => ( renderRow={(executionEnvironment, index) => (
<ExecutionEnvironmentsListItem <ExecutionEnvironmentsListItem
key={executionEnvironment.id} key={executionEnvironment.id}
rowIndex={index}
executionEnvironment={executionEnvironment} executionEnvironment={executionEnvironment}
detailUrl={`${match.url}/${executionEnvironment.id}/details`} detailUrl={`${match.url}/${executionEnvironment.id}/details`}
onSelect={() => handleSelect(executionEnvironment)} onSelect={() => handleSelect(executionEnvironment)}

View File

@@ -3,18 +3,11 @@ import { string, bool, func } from 'prop-types';
import { withI18n } from '@lingui/react'; import { withI18n } from '@lingui/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 { Button } from '@patternfly/react-core';
Button, import { Tr, Td } from '@patternfly/react-table';
DataListAction,
DataListCheck,
DataListItem,
DataListItemRow,
DataListItemCells,
Tooltip,
} from '@patternfly/react-core';
import { PencilAltIcon } from '@patternfly/react-icons'; import { PencilAltIcon } from '@patternfly/react-icons';
import DataListCell from '../../../components/DataListCell'; import { ActionsTd, ActionItem } from '../../../components/PaginatedTable';
import { ExecutionEnvironment } from '../../../types'; import { ExecutionEnvironment } from '../../../types';
function ExecutionEnvironmentListItem({ function ExecutionEnvironmentListItem({
@@ -23,55 +16,56 @@ function ExecutionEnvironmentListItem({
isSelected, isSelected,
onSelect, onSelect,
i18n, i18n,
rowIndex,
}) { }) {
const labelId = `check-action-${executionEnvironment.id}`; const labelId = `check-action-${executionEnvironment.id}`;
return ( return (
<DataListItem <Tr id={`ee-row-${executionEnvironment.id}`}>
key={executionEnvironment.id} <Td
aria-labelledby={labelId} select={{
id={`${executionEnvironment.id} `} rowIndex,
> isSelected,
<DataListItemRow> onSelect,
<DataListCheck disable: false,
id={`select-execution-environment-${executionEnvironment.id}`} }}
checked={isSelected} dataLabel={i18n._(t`Selected`)}
onChange={onSelect} />
aria-labelledby={labelId} <Td id={labelId} dataLabel={i18n._(t`Name`)}>
/> <Link to={`${detailUrl}`}>
<DataListItemCells <b>{executionEnvironment.name}</b>
dataListCells={[ </Link>
<DataListCell </Td>
key="image" <Td id={labelId} dataLabel={i18n._(t`Image`)}>
aria-label={i18n._(t`execution environment image`)} {executionEnvironment.image}
> </Td>
<Link to={`${detailUrl}`}> <Td id={labelId} dataLabel={i18n._(t`Organization`)}>
<b>{executionEnvironment.image}</b> {executionEnvironment.organization ? (
</Link> <Link
</DataListCell>, to={`/organizations/${executionEnvironment?.summary_fields?.organization?.id}/details`}
]}
/>
<DataListAction
aria-label={i18n._(t`actions`)}
aria-labelledby={labelId}
id={labelId}
>
<Tooltip
content={i18n._(t`Edit execution environment`)}
position="top"
> >
<Button <b>{executionEnvironment?.summary_fields?.organization?.name}</b>
aria-label={i18n._(t`Edit execution environment`)} </Link>
variant="plain" ) : (
component={Link} i18n._(t`Globally Available`)
to={`/execution_environments/${executionEnvironment.id}/edit`} )}
> </Td>
<PencilAltIcon /> <ActionsTd dataLabel={i18n._(t`Actions`)}>
</Button> <ActionItem
</Tooltip> visible={executionEnvironment.summary_fields.user_capabilities.edit}
</DataListAction> tooltip={i18n._(t`Edit Execution Environment`)}
</DataListItemRow> >
</DataListItem> <Button
aria-label={i18n._(t`Edit Execution Environment`)}
variant="plain"
component={Link}
to={`/execution_environments/${executionEnvironment.id}/edit`}
>
<PencilAltIcon />
</Button>
</ActionItem>
</ActionsTd>
</Tr>
); );
} }

View File

@@ -8,21 +8,27 @@ import ExecutionEnvironmentListItem from './ExecutionEnvironmentListItem';
describe('<ExecutionEnvironmentListItem/>', () => { describe('<ExecutionEnvironmentListItem/>', () => {
let wrapper; let wrapper;
const executionEnvironment = { const executionEnvironment = {
name: 'Foo',
id: 1, id: 1,
image: 'https://registry.com/r/image/manifest', image: 'https://registry.com/r/image/manifest',
organization: null, organization: null,
credential: null, credential: null,
summary_fields: { user_capabilities: { edit: true } },
}; };
test('should mount successfully', async () => { test('should mount successfully', async () => {
await act(async () => { await act(async () => {
wrapper = mountWithContexts( wrapper = mountWithContexts(
<ExecutionEnvironmentListItem <table>
executionEnvironment={executionEnvironment} <tbody>
detailUrl="execution_environments/1/details" <ExecutionEnvironmentListItem
isSelected={false} executionEnvironment={executionEnvironment}
onSelect={() => {}} detailUrl="execution_environments/1/details"
/> isSelected={false}
onSelect={() => {}}
/>
</tbody>
</table>
); );
}); });
expect(wrapper.find('ExecutionEnvironmentListItem').length).toBe(1); expect(wrapper.find('ExecutionEnvironmentListItem').length).toBe(1);
@@ -31,22 +37,38 @@ describe('<ExecutionEnvironmentListItem/>', () => {
test('should render the proper data', async () => { test('should render the proper data', async () => {
await act(async () => { await act(async () => {
wrapper = mountWithContexts( wrapper = mountWithContexts(
<ExecutionEnvironmentListItem <table>
executionEnvironment={executionEnvironment} <tbody>
detailUrl="execution_environments/1/details" <ExecutionEnvironmentListItem
isSelected={false} executionEnvironment={executionEnvironment}
onSelect={() => {}} detailUrl="execution_environments/1/details"
/> isSelected={false}
onSelect={() => {}}
/>
</tbody>
</table>
); );
}); });
expect( expect(
wrapper wrapper
.find('DataListCell[aria-label="execution environment image"]') .find('Td')
.at(1)
.text()
).toBe(executionEnvironment.name);
expect(
wrapper
.find('Td')
.at(2)
.text() .text()
).toBe(executionEnvironment.image); ).toBe(executionEnvironment.image);
expect(wrapper.find('PencilAltIcon').length).toBe(1);
expect( expect(
wrapper.find('input#select-execution-environment-1').prop('checked') wrapper
).toBe(false); .find('Td')
.at(3)
.text()
).toBe('Globally Available');
expect(wrapper.find('PencilAltIcon').exists()).toBeTruthy();
}); });
}); });