Merge pull request #10134 from AlexSCorey/10132-RelatedGroupsCnverttoTables

converts inventory groups related groups and related hosts to tables

SUMMARY
This addresses #10132
ISSUE TYPE

Feature Pull Request

COMPONENT NAME

UI

ADDITIONAL INFORMATION

Reviewed-by: Jake McDermott <yo@jakemcdermott.me>
This commit is contained in:
softwarefactory-project-zuul[bot] 2021-05-17 19:07:57 +00:00 committed by GitHub
commit 0e74f51aa3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
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 DataListToolbar from '../../../components/DataListToolbar';
import ErrorDetail from '../../../components/ErrorDetail';
import PaginatedDataList from '../../../components/PaginatedDataList';
import PaginatedTable, {
HeaderCell,
HeaderRow,
} from '../../../components/PaginatedTable';
import AssociateModal from '../../../components/AssociateModal';
import DisassociateButton from '../../../components/DisassociateButton';
import AdHocCommands from '../../../components/AdHocCommands/AdHocCommands';
@ -172,7 +175,7 @@ function InventoryGroupHostList() {
);
return (
<>
<PaginatedDataList
<PaginatedTable
contentError={contentError}
hasContentLoading={
isLoading || isDisassociateLoading || isAdHocLaunchLoading
@ -203,6 +206,13 @@ function InventoryGroupHostList() {
key: 'name',
},
]}
headerRow={
<HeaderRow qsConfig={QS_CONFIG}>
<HeaderCell sortKey="name">{t`Name`}</HeaderCell>
<HeaderCell>{t`Activity`}</HeaderCell>
<HeaderCell>{t`Actions`}</HeaderCell>
</HeaderRow>
}
toolbarSearchableKeys={searchableKeys}
toolbarRelatedSearchableKeys={relatedSearchableKeys}
renderToolbar={props => (
@ -235,14 +245,15 @@ function InventoryGroupHostList() {
]}
/>
)}
renderItem={o => (
renderRow={(host, index) => (
<InventoryGroupHostListItem
key={o.id}
host={o}
detailUrl={`/inventories/inventory/${inventoryId}/hosts/${o.id}/details`}
editUrl={`/inventories/inventory/${inventoryId}/hosts/${o.id}/edit`}
isSelected={selected.some(row => row.id === o.id)}
onSelect={() => handleSelect(o)}
key={host.id}
rowIndex={index}
host={host}
detailUrl={`/inventories/inventory/${inventoryId}/hosts/${host.id}/details`}
editUrl={`/inventories/inventory/${inventoryId}/hosts/${host.id}/edit`}
isSelected={selected.some(row => row.id === host.id)}
onSelect={() => handleSelect(host)}
/>
)}
emptyStateControls={canAdd && addButton}

View File

@ -58,21 +58,21 @@ describe('<InventoryGroupHostList />', () => {
test('should check and uncheck the row item', async () => {
expect(
wrapper.find('DataListCheck[id="select-host-2"]').props().checked
wrapper.find('input[aria-label="Select row 2"]').props().checked
).toBe(false);
await act(async () => {
wrapper.find('DataListCheck[id="select-host-2"]').invoke('onChange')();
wrapper.find('input[aria-label="Select row 2"]').invoke('onChange')();
});
wrapper.update();
expect(
wrapper.find('DataListCheck[id="select-host-2"]').props().checked
wrapper.find('input[aria-label="Select row 2"]').props().checked
).toBe(true);
await act(async () => {
wrapper.find('DataListCheck[id="select-host-2"]').invoke('onChange')();
wrapper.find('input[aria-label="Select row 2"]').invoke('onChange')();
});
wrapper.update();
expect(
wrapper.find('DataListCheck[id="select-host-2"]').props().checked
wrapper.find('input[aria-label="Select row 2"]').props().checked
).toBe(false);
});

View File

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

View File

@ -17,6 +17,7 @@ describe('<InventoryGroupHostListItem />', () => {
host={mockHost}
isSelected={false}
onSelect={() => {}}
rowIndex={0}
/>
);
});
@ -26,12 +27,9 @@ describe('<InventoryGroupHostListItem />', () => {
});
test('should display expected row item content', () => {
expect(
wrapper
.find('DataListCell')
.first()
.text()
).toBe('.host-000001.group-00000.dummy');
expect(wrapper.find('b').text()).toContain(
'.host-000001.group-00000.dummy'
);
expect(wrapper.find('Sparkline').length).toBe(1);
expect(wrapper.find('HostToggle').length).toBe(1);
});
@ -50,6 +48,7 @@ describe('<InventoryGroupHostListItem />', () => {
host={mockHost}
isSelected={false}
onSelect={() => {}}
rowIndex={0}
/>
);
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 DataListToolbar from '../../../components/DataListToolbar';
import PaginatedDataList from '../../../components/PaginatedDataList';
import PaginatedTable, {
HeaderCell,
HeaderRow,
} from '../../../components/PaginatedTable';
import InventoryGroupRelatedGroupListItem from './InventoryRelatedGroupListItem';
import AddDropDownButton from '../../../components/AddDropDownButton';
import AdHocCommands from '../../../components/AdHocCommands/AdHocCommands';
@ -131,6 +134,7 @@ function InventoryRelatedGroupList() {
const addButton = (
<AddDropDownButton
key="add"
ouiaId="add-existing-group-button"
dropdownItems={[
<DropdownItem
key={addExistingGroup}
@ -140,6 +144,7 @@ function InventoryRelatedGroupList() {
{addExistingGroup}
</DropdownItem>,
<DropdownItem
ouiaId="add-new-group-button"
component={Link}
to={`${addFormUrl}`}
key={addNewGroup}
@ -153,7 +158,7 @@ function InventoryRelatedGroupList() {
return (
<>
<PaginatedDataList
<PaginatedTable
contentError={contentError}
hasContentLoading={isLoading || isAdHocLaunchLoading}
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
key={o.id}
group={o}
detailUrl={`/inventories/inventory/${inventoryId}/groups/${o.id}/details`}
editUrl={`/inventories/inventory/${inventoryId}/groups/${o.id}/edit`}
isSelected={selected.some(row => row.id === o.id)}
onSelect={() => handleSelect(o)}
key={group.id}
rowIndex={index}
group={group}
detailUrl={`/inventories/inventory/${inventoryId}/groups/${group.id}/details`}
editUrl={`/inventories/inventory/${inventoryId}/groups/${group.id}/edit`}
isSelected={selected.some(row => row.id === group.id)}
onSelect={() => handleSelect(group)}
/>
)}
emptyStateControls={canAdd && addButton}

View File

@ -110,21 +110,21 @@ describe('<InventoryRelatedGroupList />', () => {
test('should check and uncheck the row item', async () => {
expect(
wrapper.find('DataListCheck[id="select-group-2"]').props().checked
wrapper.find('input[aria-label="Select row 0"]').props().checked
).toBe(false);
await act(async () => {
wrapper.find('DataListCheck[id="select-group-2"]').invoke('onChange')();
wrapper.find('input[aria-label="Select row 0"]').invoke('onChange')();
});
wrapper.update();
expect(
wrapper.find('DataListCheck[id="select-group-2"]').props().checked
wrapper.find('input[aria-label="Select row 0"]').props().checked
).toBe(true);
await act(async () => {
wrapper.find('DataListCheck[id="select-group-2"]').invoke('onChange')();
wrapper.find('input[aria-label="Select row 0"]').invoke('onChange')();
});
wrapper.update();
expect(
wrapper.find('DataListCheck[id="select-group-2"]').props().checked
wrapper.find('input[aria-label="Select row 0"]').props().checked
).toBe(false);
});

View File

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

View File

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