converts inventory groups related groups and related hosts to tables

This commit is contained in:
Alex Corey
2021-05-06 16:30:51 -04:00
parent 2d81143c98
commit 8fe437380d
8 changed files with 142 additions and 160 deletions

View File

@@ -14,7 +14,10 @@ import useSelected from '../../../util/useSelected';
import AlertModal from '../../../components/AlertModal'; import AlertModal from '../../../components/AlertModal';
import DataListToolbar from '../../../components/DataListToolbar'; import DataListToolbar from '../../../components/DataListToolbar';
import ErrorDetail from '../../../components/ErrorDetail'; import ErrorDetail from '../../../components/ErrorDetail';
import PaginatedDataList from '../../../components/PaginatedDataList'; import PaginatedTable, {
HeaderCell,
HeaderRow,
} from '../../../components/PaginatedTable';
import AssociateModal from '../../../components/AssociateModal'; import AssociateModal from '../../../components/AssociateModal';
import DisassociateButton from '../../../components/DisassociateButton'; import DisassociateButton from '../../../components/DisassociateButton';
import AdHocCommands from '../../../components/AdHocCommands/AdHocCommands'; import AdHocCommands from '../../../components/AdHocCommands/AdHocCommands';
@@ -172,7 +175,7 @@ function InventoryGroupHostList() {
); );
return ( return (
<> <>
<PaginatedDataList <PaginatedTable
contentError={contentError} contentError={contentError}
hasContentLoading={ hasContentLoading={
isLoading || isDisassociateLoading || isAdHocLaunchLoading isLoading || isDisassociateLoading || isAdHocLaunchLoading
@@ -203,6 +206,13 @@ function InventoryGroupHostList() {
key: 'name', key: 'name',
}, },
]} ]}
headerRow={
<HeaderRow qsConfig={QS_CONFIG}>
<HeaderCell sortKey="name">{t`Name`}</HeaderCell>
<HeaderCell>{t`Activity`}</HeaderCell>
<HeaderCell>{t`Actions`}</HeaderCell>
</HeaderRow>
}
toolbarSearchableKeys={searchableKeys} toolbarSearchableKeys={searchableKeys}
toolbarRelatedSearchableKeys={relatedSearchableKeys} toolbarRelatedSearchableKeys={relatedSearchableKeys}
renderToolbar={props => ( renderToolbar={props => (
@@ -235,14 +245,15 @@ function InventoryGroupHostList() {
]} ]}
/> />
)} )}
renderItem={o => ( renderRow={(host, index) => (
<InventoryGroupHostListItem <InventoryGroupHostListItem
key={o.id} key={host.id}
host={o} rowIndex={index}
detailUrl={`/inventories/inventory/${inventoryId}/hosts/${o.id}/details`} host={host}
editUrl={`/inventories/inventory/${inventoryId}/hosts/${o.id}/edit`} detailUrl={`/inventories/inventory/${inventoryId}/hosts/${host.id}/details`}
isSelected={selected.some(row => row.id === o.id)} editUrl={`/inventories/inventory/${inventoryId}/hosts/${host.id}/edit`}
onSelect={() => handleSelect(o)} isSelected={selected.some(row => row.id === host.id)}
onSelect={() => handleSelect(host)}
/> />
)} )}
emptyStateControls={canAdd && addButton} emptyStateControls={canAdd && addButton}

View File

@@ -58,21 +58,21 @@ describe('<InventoryGroupHostList />', () => {
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-host-2"]').props().checked wrapper.find('input[aria-label="Select row 2"]').props().checked
).toBe(false); ).toBe(false);
await act(async () => { await act(async () => {
wrapper.find('DataListCheck[id="select-host-2"]').invoke('onChange')(); wrapper.find('input[aria-label="Select row 2"]').invoke('onChange')();
}); });
wrapper.update(); wrapper.update();
expect( expect(
wrapper.find('DataListCheck[id="select-host-2"]').props().checked wrapper.find('input[aria-label="Select row 2"]').props().checked
).toBe(true); ).toBe(true);
await act(async () => { await act(async () => {
wrapper.find('DataListCheck[id="select-host-2"]').invoke('onChange')(); wrapper.find('input[aria-label="Select row 2"]').invoke('onChange')();
}); });
wrapper.update(); wrapper.update();
expect( expect(
wrapper.find('DataListCheck[id="select-host-2"]').props().checked wrapper.find('input[aria-label="Select row 2"]').props().checked
).toBe(false); ).toBe(false);
}); });

View File

@@ -1,38 +1,22 @@
import 'styled-components/macro'; import 'styled-components/macro';
import React from 'react'; import React from 'react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { string, bool, func } from 'prop-types'; import { string, bool, func, number } from 'prop-types';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import { Button, Tooltip } from '@patternfly/react-core';
import {
Button,
DataListAction as _DataListAction,
DataListCheck,
DataListItem,
DataListItemCells,
DataListItemRow,
Tooltip,
} from '@patternfly/react-core';
import { PencilAltIcon } from '@patternfly/react-icons'; import { PencilAltIcon } from '@patternfly/react-icons';
import styled from 'styled-components';
import DataListCell from '../../../components/DataListCell';
import { Td, Tr } from '@patternfly/react-table';
import { ActionItem, ActionsTd } from '../../../components/PaginatedTable';
import HostToggle from '../../../components/HostToggle'; import HostToggle from '../../../components/HostToggle';
import Sparkline from '../../../components/Sparkline'; import Sparkline from '../../../components/Sparkline';
import { Host } from '../../../types'; import { Host } from '../../../types';
const DataListAction = styled(_DataListAction)`
align-items: center;
display: grid;
grid-gap: 24px;
grid-template-columns: min-content 40px;
`;
function InventoryGroupHostListItem({ function InventoryGroupHostListItem({
detailUrl, detailUrl,
editUrl, editUrl,
host, host,
rowIndex,
isSelected, isSelected,
onSelect, onSelect,
}) { }) {
@@ -44,55 +28,55 @@ function InventoryGroupHostListItem({
const labelId = `check-action-${host.id}`; const labelId = `check-action-${host.id}`;
return ( return (
<DataListItem key={host.id} aria-labelledby={labelId} id={`${host.id}`}> <Tr id={host.id} arialabelledby={labelId}>
<DataListItemRow> <Td
<DataListCheck select={{
id={`select-host-${host.id}`} rowIndex,
checked={isSelected} isSelected,
onChange={onSelect} onSelect,
aria-labelledby={labelId} }}
/> dataLabel={t`Selected`}
<DataListItemCells />
dataListCells={[ <Td id={labelId}>
<DataListCell key="name"> <Link to={`${detailUrl}`}>
<Link to={`${detailUrl}`}> <b>{host.name}</b>
<b>{host.name}</b> </Link>
</Link> </Td>
</DataListCell>, <Td dataLabel={t`Activity`}>
<DataListCell key="recentJobs"> <Sparkline jobs={recentPlaybookJobs} />
<Sparkline jobs={recentPlaybookJobs} /> </Td>
</DataListCell>, <ActionsTd dataLabel={t`Actions`} gridColumns="auto 40px">
]} <ActionItem
/> visible={host.summary_fields.user_capabilities?.edit}
<DataListAction tooltip={t`Toggle host`}
aria-label={t`actions`}
aria-labelledby={labelId}
id={labelId}
> >
<HostToggle css="grid-column: 1" host={host} /> <HostToggle host={host} />
{host.summary_fields.user_capabilities?.edit && ( </ActionItem>
<Tooltip content={t`Edit Host`} position="top"> <ActionItem
<Button tooltip={t`Edit Host`}
ouiaId={`${host.id}-edit-button`} visible={host.summary_fields.user_capabilities?.edit}
aria-label={t`Edit Host`} >
css="grid-column: 2" <Tooltip content={t`Edit Host`} position="top">
variant="plain" <Button
component={Link} ouiaId={`${host.id}-edit-button`}
to={`${editUrl}`} aria-label={t`Edit Host`}
> variant="plain"
<PencilAltIcon /> component={Link}
</Button> to={`${editUrl}`}
</Tooltip> >
)} <PencilAltIcon />
</DataListAction> </Button>
</DataListItemRow> </Tooltip>
</DataListItem> </ActionItem>
</ActionsTd>
</Tr>
); );
} }
InventoryGroupHostListItem.propTypes = { InventoryGroupHostListItem.propTypes = {
detailUrl: string.isRequired, detailUrl: string.isRequired,
editUrl: string.isRequired, editUrl: string.isRequired,
rowIndex: number.isRequired,
host: Host.isRequired, host: Host.isRequired,
isSelected: bool.isRequired, isSelected: bool.isRequired,
onSelect: func.isRequired, onSelect: func.isRequired,

View File

@@ -17,6 +17,7 @@ describe('<InventoryGroupHostListItem />', () => {
host={mockHost} host={mockHost}
isSelected={false} isSelected={false}
onSelect={() => {}} onSelect={() => {}}
rowIndex={0}
/> />
); );
}); });
@@ -26,12 +27,9 @@ describe('<InventoryGroupHostListItem />', () => {
}); });
test('should display expected row item content', () => { test('should display expected row item content', () => {
expect( expect(wrapper.find('b').text()).toContain(
wrapper '.host-000001.group-00000.dummy'
.find('DataListCell') );
.first()
.text()
).toBe('.host-000001.group-00000.dummy');
expect(wrapper.find('Sparkline').length).toBe(1); expect(wrapper.find('Sparkline').length).toBe(1);
expect(wrapper.find('HostToggle').length).toBe(1); expect(wrapper.find('HostToggle').length).toBe(1);
}); });
@@ -50,6 +48,7 @@ describe('<InventoryGroupHostListItem />', () => {
host={mockHost} host={mockHost}
isSelected={false} isSelected={false}
onSelect={() => {}} onSelect={() => {}}
rowIndex={0}
/> />
); );
expect(wrapper.find('PencilAltIcon').exists()).toBeFalsy(); expect(wrapper.find('PencilAltIcon').exists()).toBeFalsy();

View File

@@ -10,7 +10,10 @@ import { getQSConfig, parseQueryString, mergeParams } from '../../../util/qs';
import useSelected from '../../../util/useSelected'; import useSelected from '../../../util/useSelected';
import DataListToolbar from '../../../components/DataListToolbar'; import DataListToolbar from '../../../components/DataListToolbar';
import PaginatedDataList from '../../../components/PaginatedDataList'; import PaginatedTable, {
HeaderCell,
HeaderRow,
} from '../../../components/PaginatedTable';
import InventoryGroupRelatedGroupListItem from './InventoryRelatedGroupListItem'; import InventoryGroupRelatedGroupListItem from './InventoryRelatedGroupListItem';
import AddDropDownButton from '../../../components/AddDropDownButton'; import AddDropDownButton from '../../../components/AddDropDownButton';
import AdHocCommands from '../../../components/AdHocCommands/AdHocCommands'; import AdHocCommands from '../../../components/AdHocCommands/AdHocCommands';
@@ -131,6 +134,7 @@ function InventoryRelatedGroupList() {
const addButton = ( const addButton = (
<AddDropDownButton <AddDropDownButton
key="add" key="add"
ouiaId="add-existing-group-button"
dropdownItems={[ dropdownItems={[
<DropdownItem <DropdownItem
key={addExistingGroup} key={addExistingGroup}
@@ -140,6 +144,7 @@ function InventoryRelatedGroupList() {
{addExistingGroup} {addExistingGroup}
</DropdownItem>, </DropdownItem>,
<DropdownItem <DropdownItem
ouiaId="add-new-group-button"
component={Link} component={Link}
to={`${addFormUrl}`} to={`${addFormUrl}`}
key={addNewGroup} key={addNewGroup}
@@ -153,7 +158,7 @@ function InventoryRelatedGroupList() {
return ( return (
<> <>
<PaginatedDataList <PaginatedTable
contentError={contentError} contentError={contentError}
hasContentLoading={isLoading || isAdHocLaunchLoading} hasContentLoading={isLoading || isAdHocLaunchLoading}
items={groups} items={groups}
@@ -209,14 +214,21 @@ function InventoryRelatedGroupList() {
]} ]}
/> />
)} )}
renderItem={o => ( headerRow={
<HeaderRow qsConfig={QS_CONFIG}>
<HeaderCell sortKey="name">{t`Name`}</HeaderCell>
<HeaderCell>{t`Actions`}</HeaderCell>
</HeaderRow>
}
renderRow={(group, index) => (
<InventoryGroupRelatedGroupListItem <InventoryGroupRelatedGroupListItem
key={o.id} key={group.id}
group={o} rowIndex={index}
detailUrl={`/inventories/inventory/${inventoryId}/groups/${o.id}/details`} group={group}
editUrl={`/inventories/inventory/${inventoryId}/groups/${o.id}/edit`} detailUrl={`/inventories/inventory/${inventoryId}/groups/${group.id}/details`}
isSelected={selected.some(row => row.id === o.id)} editUrl={`/inventories/inventory/${inventoryId}/groups/${group.id}/edit`}
onSelect={() => handleSelect(o)} isSelected={selected.some(row => row.id === group.id)}
onSelect={() => handleSelect(group)}
/> />
)} )}
emptyStateControls={canAdd && addButton} emptyStateControls={canAdd && addButton}

View File

@@ -110,21 +110,21 @@ describe('<InventoryRelatedGroupList />', () => {
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-2"]').props().checked wrapper.find('input[aria-label="Select row 0"]').props().checked
).toBe(false); ).toBe(false);
await act(async () => { await act(async () => {
wrapper.find('DataListCheck[id="select-group-2"]').invoke('onChange')(); wrapper.find('input[aria-label="Select row 0"]').invoke('onChange')();
}); });
wrapper.update(); wrapper.update();
expect( expect(
wrapper.find('DataListCheck[id="select-group-2"]').props().checked wrapper.find('input[aria-label="Select row 0"]').props().checked
).toBe(true); ).toBe(true);
await act(async () => { await act(async () => {
wrapper.find('DataListCheck[id="select-group-2"]').invoke('onChange')(); wrapper.find('input[aria-label="Select row 0"]').invoke('onChange')();
}); });
wrapper.update(); wrapper.update();
expect( expect(
wrapper.find('DataListCheck[id="select-group-2"]').props().checked wrapper.find('input[aria-label="Select row 0"]').props().checked
).toBe(false); ).toBe(false);
}); });

View File

@@ -1,81 +1,59 @@
import 'styled-components/macro'; import 'styled-components/macro';
import React from 'react'; import React from 'react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { string, bool, func } from 'prop-types'; import { string, bool, func, number } from 'prop-types';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import { import { Td, Tr } from '@patternfly/react-table';
Button, import { Button } from '@patternfly/react-core';
DataListAction as _DataListAction,
DataListCheck,
DataListItem,
DataListItemCells,
DataListItemRow,
Tooltip,
} from '@patternfly/react-core';
import { PencilAltIcon } from '@patternfly/react-icons'; import { PencilAltIcon } from '@patternfly/react-icons';
import styled from 'styled-components';
import DataListCell from '../../../components/DataListCell';
import { Group } from '../../../types'; import { Group } from '../../../types';
import { ActionItem, ActionsTd } from '../../../components/PaginatedTable';
const DataListAction = styled(_DataListAction)`
align-items: center;
display: grid;
grid-gap: 24px;
grid-template-columns: min-content 40px;
`;
function InventoryRelatedGroupListItem({ function InventoryRelatedGroupListItem({
detailUrl, detailUrl,
editUrl, editUrl,
group, group,
rowIndex,
isSelected, isSelected,
onSelect, onSelect,
}) { }) {
const labelId = `check-action-${group.id}`; const labelId = `check-action-${group.id}`;
return ( return (
<DataListItem key={group.id} aria-labelledby={labelId} id={`${group.id}`}> <Tr id={group.id} aria-labelledby={labelId}>
<DataListItemRow> <Td
<DataListCheck select={{
id={`select-group-${group.id}`} rowIndex,
checked={isSelected} isSelected,
onChange={onSelect} onSelect,
aria-labelledby={labelId} }}
/> dataLabel={t`Selected`}
<DataListItemCells />
dataListCells={[ <Td id={labelId}>
<DataListCell key="name"> <Link to={`${detailUrl}`}>
<Link to={`${detailUrl}`}> <b>{group.name}</b>
<b>{group.name}</b> </Link>
</Link> </Td>
</DataListCell>, <ActionsTd dataLabel={t`Actions`}>
]} <ActionItem
/> tooltip={t`Edit Group`}
<DataListAction visible={group.summary_fields.user_capabilities?.edit}
aria-label={t`actions`}
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 aria-label={t`Edit Group`}
ouiaId={`${group.id}-edit-button`} variant="plain"
aria-label={t`Edit Group`} component={Link}
css="grid-column: 2" to={`${editUrl}`}
variant="plain" >
component={Link} <PencilAltIcon />
to={`${editUrl}`} </Button>
> </ActionItem>
<PencilAltIcon /> </ActionsTd>
</Button> </Tr>
</Tooltip>
)}
</DataListAction>
</DataListItemRow>
</DataListItem>
); );
} }
@@ -83,6 +61,7 @@ InventoryRelatedGroupListItem.propTypes = {
detailUrl: string.isRequired, detailUrl: string.isRequired,
editUrl: string.isRequired, editUrl: string.isRequired,
group: Group.isRequired, group: Group.isRequired,
rowIndex: number.isRequired,
isSelected: bool.isRequired, isSelected: bool.isRequired,
onSelect: func.isRequired, onSelect: func.isRequired,
}; };

View File

@@ -17,6 +17,7 @@ describe('<InventoryRelatedGroupListItem />', () => {
group={mockGroup} group={mockGroup}
isSelected={false} isSelected={false}
onSelect={() => {}} onSelect={() => {}}
rowIndex={0}
/> />
); );
}); });
@@ -26,12 +27,7 @@ describe('<InventoryRelatedGroupListItem />', () => {
}); });
test('should display expected row item content', () => { test('should display expected row item content', () => {
expect( expect(wrapper.find('b').text()).toContain('Group 2 Inventory 0');
wrapper
.find('DataListCell')
.first()
.text()
).toBe(' Group 2 Inventory 0');
}); });
test('edit button shown to users with edit capabilities', () => { test('edit button shown to users with edit capabilities', () => {
@@ -46,6 +42,7 @@ describe('<InventoryRelatedGroupListItem />', () => {
group={mockRelatedGroups.results[2]} group={mockRelatedGroups.results[2]}
isSelected={false} isSelected={false}
onSelect={() => {}} onSelect={() => {}}
rowIndex={0}
/> />
); );
expect(wrapper.find('PencilAltIcon').exists()).toBeFalsy(); expect(wrapper.find('PencilAltIcon').exists()).toBeFalsy();