mirror of
https://github.com/ansible/awx.git
synced 2026-01-22 23:18:03 -03:30
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:
commit
0e74f51aa3
@ -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}
|
||||
|
||||
@ -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);
|
||||
});
|
||||
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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);
|
||||
});
|
||||
|
||||
|
||||
@ -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,
|
||||
};
|
||||
|
||||
@ -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();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user