mirror of
https://github.com/ansible/awx.git
synced 2026-03-03 09:48:51 -03:30
Merge pull request #10623 from AlexSCorey/4208-ExpandWholeList
Adds functionality for expand all SUMMARY Closes #4208. Adds basically the same work as useSelected, in a useExpanded hook. ISSUE TYPE Feature Pull Request COMPONENT NAME UI AWX VERSION ADDITIONAL INFORMATION Reviewed-by: Keith Grant <keithjgrant@gmail.com> Reviewed-by: Sarah Akus <sakus@redhat.com>
This commit is contained in:
@@ -4,6 +4,7 @@ import styled from 'styled-components';
|
|||||||
|
|
||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import {
|
import {
|
||||||
|
Button,
|
||||||
Checkbox,
|
Checkbox,
|
||||||
Toolbar,
|
Toolbar,
|
||||||
ToolbarContent as PFToolbarContent,
|
ToolbarContent as PFToolbarContent,
|
||||||
@@ -14,7 +15,11 @@ import {
|
|||||||
DropdownPosition,
|
DropdownPosition,
|
||||||
KebabToggle,
|
KebabToggle,
|
||||||
} from '@patternfly/react-core';
|
} from '@patternfly/react-core';
|
||||||
import { SearchIcon } from '@patternfly/react-icons';
|
import {
|
||||||
|
AngleDownIcon,
|
||||||
|
AngleRightIcon,
|
||||||
|
SearchIcon,
|
||||||
|
} from '@patternfly/react-icons';
|
||||||
import ExpandCollapse from '../ExpandCollapse';
|
import ExpandCollapse from '../ExpandCollapse';
|
||||||
import Search from '../Search';
|
import Search from '../Search';
|
||||||
import Sort from '../Sort';
|
import Sort from '../Sort';
|
||||||
@@ -28,14 +33,16 @@ const ToolbarContent = styled(PFToolbarContent)`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
function DataListToolbar({
|
function DataListToolbar({
|
||||||
|
isAllExpanded,
|
||||||
|
onExpandAll,
|
||||||
itemCount,
|
itemCount,
|
||||||
clearAllFilters,
|
clearAllFilters,
|
||||||
searchColumns,
|
searchColumns,
|
||||||
searchableKeys,
|
searchableKeys,
|
||||||
relatedSearchableKeys,
|
relatedSearchableKeys,
|
||||||
sortColumns,
|
sortColumns,
|
||||||
showSelectAll,
|
|
||||||
isAllSelected,
|
isAllSelected,
|
||||||
|
onSelectAll,
|
||||||
isCompact,
|
isCompact,
|
||||||
onSort,
|
onSort,
|
||||||
onSearch,
|
onSearch,
|
||||||
@@ -43,7 +50,6 @@ function DataListToolbar({
|
|||||||
onRemove,
|
onRemove,
|
||||||
onCompact,
|
onCompact,
|
||||||
onExpand,
|
onExpand,
|
||||||
onSelectAll,
|
|
||||||
additionalControls,
|
additionalControls,
|
||||||
qsConfig,
|
qsConfig,
|
||||||
pagination,
|
pagination,
|
||||||
@@ -70,7 +76,6 @@ function DataListToolbar({
|
|||||||
setIsKebabOpen(false);
|
setIsKebabOpen(false);
|
||||||
}
|
}
|
||||||
}, [isKebabModalOpen]);
|
}, [isKebabModalOpen]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Toolbar
|
<Toolbar
|
||||||
id={`${qsConfig.namespace}-list-toolbar`}
|
id={`${qsConfig.namespace}-list-toolbar`}
|
||||||
@@ -79,7 +84,27 @@ function DataListToolbar({
|
|||||||
clearFiltersButtonText={t`Clear all filters`}
|
clearFiltersButtonText={t`Clear all filters`}
|
||||||
>
|
>
|
||||||
<ToolbarContent>
|
<ToolbarContent>
|
||||||
{showSelectAll && (
|
{onExpandAll && (
|
||||||
|
<ToolbarGroup>
|
||||||
|
<ToolbarItem>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
onExpandAll(!isAllExpanded);
|
||||||
|
}}
|
||||||
|
aria-label={t`Expand all rows`}
|
||||||
|
ouiaId="expand-all-rows"
|
||||||
|
variant="plain"
|
||||||
|
>
|
||||||
|
{isAllExpanded ? (
|
||||||
|
<AngleDownIcon aria-label={t`Is expanded`} />
|
||||||
|
) : (
|
||||||
|
<AngleRightIcon aria-label={t`Is not expanded`} />
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</ToolbarItem>
|
||||||
|
</ToolbarGroup>
|
||||||
|
)}
|
||||||
|
{onSelectAll && (
|
||||||
<ToolbarGroup>
|
<ToolbarGroup>
|
||||||
<ToolbarItem>
|
<ToolbarItem>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
@@ -178,7 +203,6 @@ DataListToolbar.propTypes = {
|
|||||||
searchableKeys: PropTypes.arrayOf(PropTypes.string),
|
searchableKeys: PropTypes.arrayOf(PropTypes.string),
|
||||||
relatedSearchableKeys: PropTypes.arrayOf(PropTypes.string),
|
relatedSearchableKeys: PropTypes.arrayOf(PropTypes.string),
|
||||||
sortColumns: SortColumns,
|
sortColumns: SortColumns,
|
||||||
showSelectAll: PropTypes.bool,
|
|
||||||
isAllSelected: PropTypes.bool,
|
isAllSelected: PropTypes.bool,
|
||||||
isCompact: PropTypes.bool,
|
isCompact: PropTypes.bool,
|
||||||
onCompact: PropTypes.func,
|
onCompact: PropTypes.func,
|
||||||
@@ -198,7 +222,6 @@ DataListToolbar.defaultProps = {
|
|||||||
relatedSearchableKeys: [],
|
relatedSearchableKeys: [],
|
||||||
sortColumns: null,
|
sortColumns: null,
|
||||||
clearAllFilters: null,
|
clearAllFilters: null,
|
||||||
showSelectAll: false,
|
|
||||||
isAllSelected: false,
|
isAllSelected: false,
|
||||||
isCompact: false,
|
isCompact: false,
|
||||||
onCompact: null,
|
onCompact: null,
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ describe('<DataListToolbar />', () => {
|
|||||||
const onReplaceSearch = jest.fn();
|
const onReplaceSearch = jest.fn();
|
||||||
const onSort = jest.fn();
|
const onSort = jest.fn();
|
||||||
const onSelectAll = jest.fn();
|
const onSelectAll = jest.fn();
|
||||||
|
const onExpandAll = jest.fn();
|
||||||
|
|
||||||
test('it triggers the expected callbacks', () => {
|
test('it triggers the expected callbacks', () => {
|
||||||
const searchColumns = [
|
const searchColumns = [
|
||||||
@@ -285,4 +286,61 @@ describe('<DataListToolbar />', () => {
|
|||||||
1
|
1
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should handle expanded rows', async () => {
|
||||||
|
const searchColumns = [
|
||||||
|
{ name: 'Name', key: 'name__icontains', isDefault: true },
|
||||||
|
];
|
||||||
|
const sortColumns = [{ name: 'Name', key: 'name' }];
|
||||||
|
|
||||||
|
const newtoolbar = mountWithContexts(
|
||||||
|
<DataListToolbar
|
||||||
|
qsConfig={QS_CONFIG}
|
||||||
|
isAllSelected={false}
|
||||||
|
searchColumns={searchColumns}
|
||||||
|
sortColumns={sortColumns}
|
||||||
|
onSearch={onSearch}
|
||||||
|
onReplaceSearch={onReplaceSearch}
|
||||||
|
onSort={onSort}
|
||||||
|
onSelectAll={onSelectAll}
|
||||||
|
showSelectAll
|
||||||
|
showExpandAll
|
||||||
|
isAllExpanded={false}
|
||||||
|
onExpandAll={onExpandAll}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
await act(async () =>
|
||||||
|
newtoolbar.find('Button[aria-label="Expand all rows"]').prop('onClick')()
|
||||||
|
);
|
||||||
|
expect(newtoolbar.find('AngleRightIcon')).toHaveLength(1);
|
||||||
|
expect(newtoolbar.find('AngleDownIcon')).toHaveLength(0);
|
||||||
|
expect(onExpandAll).toBeCalledWith(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should render angle down icon', async () => {
|
||||||
|
const searchColumns = [
|
||||||
|
{ name: 'Name', key: 'name__icontains', isDefault: true },
|
||||||
|
];
|
||||||
|
const sortColumns = [{ name: 'Name', key: 'name' }];
|
||||||
|
|
||||||
|
const newtoolbar = mountWithContexts(
|
||||||
|
<DataListToolbar
|
||||||
|
qsConfig={QS_CONFIG}
|
||||||
|
isAllSelected={false}
|
||||||
|
searchColumns={searchColumns}
|
||||||
|
sortColumns={sortColumns}
|
||||||
|
onSearch={onSearch}
|
||||||
|
onReplaceSearch={onReplaceSearch}
|
||||||
|
onSort={onSort}
|
||||||
|
onSelectAll={onSelectAll}
|
||||||
|
showSelectAll
|
||||||
|
showExpandAll
|
||||||
|
isAllExpanded
|
||||||
|
onExpandAll={onExpandAll}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(newtoolbar.find('AngleDownIcon')).toHaveLength(1);
|
||||||
|
expect(newtoolbar.find('AngleRightIcon')).toHaveLength(0);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import useRequest, {
|
|||||||
} from '../../util/useRequest';
|
} from '../../util/useRequest';
|
||||||
import { useConfig } from '../../contexts/Config';
|
import { useConfig } from '../../contexts/Config';
|
||||||
import useSelected from '../../util/useSelected';
|
import useSelected from '../../util/useSelected';
|
||||||
|
import useExpanded from '../../util/useExpanded';
|
||||||
import { isJobRunning, getJobModel } from '../../util/jobs';
|
import { isJobRunning, getJobModel } from '../../util/jobs';
|
||||||
import { getQSConfig, parseQueryString } from '../../util/qs';
|
import { getQSConfig, parseQueryString } from '../../util/qs';
|
||||||
import JobListItem from './JobListItem';
|
import JobListItem from './JobListItem';
|
||||||
@@ -97,6 +98,10 @@ function JobList({ defaultParams, showTypeColumn = false }) {
|
|||||||
clearSelected,
|
clearSelected,
|
||||||
} = useSelected(jobs);
|
} = useSelected(jobs);
|
||||||
|
|
||||||
|
const { expanded, isAllExpanded, handleExpand, expandAll } = useExpanded(
|
||||||
|
jobs
|
||||||
|
);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
error: cancelJobsError,
|
error: cancelJobsError,
|
||||||
isLoading: isCancelLoading,
|
isLoading: isCancelLoading,
|
||||||
@@ -227,7 +232,8 @@ function JobList({ defaultParams, showTypeColumn = false }) {
|
|||||||
renderToolbar={props => (
|
renderToolbar={props => (
|
||||||
<DatalistToolbar
|
<DatalistToolbar
|
||||||
{...props}
|
{...props}
|
||||||
showSelectAll
|
isAllExpanded={isAllExpanded}
|
||||||
|
onExpandAll={expandAll}
|
||||||
isAllSelected={isAllSelected}
|
isAllSelected={isAllSelected}
|
||||||
onSelectAll={selectAll}
|
onSelectAll={selectAll}
|
||||||
qsConfig={qsConfig}
|
qsConfig={qsConfig}
|
||||||
@@ -264,6 +270,8 @@ function JobList({ defaultParams, showTypeColumn = false }) {
|
|||||||
<JobListItem
|
<JobListItem
|
||||||
key={job.id}
|
key={job.id}
|
||||||
job={job}
|
job={job}
|
||||||
|
isExpanded={expanded.some(row => row.id === job.id)}
|
||||||
|
onExpand={() => handleExpand(job)}
|
||||||
isSuperUser={me?.is_superuser}
|
isSuperUser={me?.is_superuser}
|
||||||
showTypeColumn={showTypeColumn}
|
showTypeColumn={showTypeColumn}
|
||||||
onSelect={() => handleSelect(job)}
|
onSelect={() => handleSelect(job)}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState } from 'react';
|
import React from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
@@ -20,6 +20,8 @@ import JobCancelButton from '../JobCancelButton';
|
|||||||
|
|
||||||
const Dash = styled.span``;
|
const Dash = styled.span``;
|
||||||
function JobListItem({
|
function JobListItem({
|
||||||
|
isExpanded,
|
||||||
|
onExpand,
|
||||||
job,
|
job,
|
||||||
rowIndex,
|
rowIndex,
|
||||||
isSelected,
|
isSelected,
|
||||||
@@ -28,7 +30,6 @@ function JobListItem({
|
|||||||
isSuperUser = false,
|
isSuperUser = false,
|
||||||
}) {
|
}) {
|
||||||
const labelId = `check-action-${job.id}`;
|
const labelId = `check-action-${job.id}`;
|
||||||
const [isExpanded, setIsExpanded] = useState(false);
|
|
||||||
|
|
||||||
const jobTypes = {
|
const jobTypes = {
|
||||||
project_update: t`Source Control Update`,
|
project_update: t`Source Control Update`,
|
||||||
@@ -63,7 +64,7 @@ function JobListItem({
|
|||||||
expand={{
|
expand={{
|
||||||
rowIndex: job.id,
|
rowIndex: job.id,
|
||||||
isExpanded,
|
isExpanded,
|
||||||
onToggle: () => setIsExpanded(!isExpanded),
|
onToggle: onExpand,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Td
|
<Td
|
||||||
|
|||||||
@@ -204,7 +204,6 @@ function ScheduleList({
|
|||||||
renderToolbar={props => (
|
renderToolbar={props => (
|
||||||
<DataListToolbar
|
<DataListToolbar
|
||||||
{...props}
|
{...props}
|
||||||
showSelectAll
|
|
||||||
isAllSelected={isAllSelected}
|
isAllSelected={isAllSelected}
|
||||||
onSelectAll={selectAll}
|
onSelectAll={selectAll}
|
||||||
qsConfig={QS_CONFIG}
|
qsConfig={QS_CONFIG}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import PaginatedTable, {
|
|||||||
} from '../PaginatedTable';
|
} from '../PaginatedTable';
|
||||||
import useRequest, { useDeleteItems } from '../../util/useRequest';
|
import useRequest, { useDeleteItems } from '../../util/useRequest';
|
||||||
import useSelected from '../../util/useSelected';
|
import useSelected from '../../util/useSelected';
|
||||||
|
import useExpanded from '../../util/useExpanded';
|
||||||
import { getQSConfig, parseQueryString } from '../../util/qs';
|
import { getQSConfig, parseQueryString } from '../../util/qs';
|
||||||
import useWsTemplates from '../../util/useWsTemplates';
|
import useWsTemplates from '../../util/useWsTemplates';
|
||||||
import AddDropDownButton from '../AddDropDownButton';
|
import AddDropDownButton from '../AddDropDownButton';
|
||||||
@@ -97,6 +98,10 @@ function TemplateList({ defaultParams }) {
|
|||||||
clearSelected,
|
clearSelected,
|
||||||
} = useSelected(templates);
|
} = useSelected(templates);
|
||||||
|
|
||||||
|
const { expanded, isAllExpanded, handleExpand, expandAll } = useExpanded(
|
||||||
|
templates
|
||||||
|
);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isLoading: isDeleteLoading,
|
isLoading: isDeleteLoading,
|
||||||
deleteItems: deleteTemplates,
|
deleteItems: deleteTemplates,
|
||||||
@@ -229,9 +234,10 @@ function TemplateList({ defaultParams }) {
|
|||||||
renderToolbar={props => (
|
renderToolbar={props => (
|
||||||
<DatalistToolbar
|
<DatalistToolbar
|
||||||
{...props}
|
{...props}
|
||||||
showSelectAll
|
|
||||||
isAllSelected={isAllSelected}
|
isAllSelected={isAllSelected}
|
||||||
onSelectAll={selectAll}
|
onSelectAll={selectAll}
|
||||||
|
isAllExpanded={isAllExpanded}
|
||||||
|
onExpandAll={expandAll}
|
||||||
qsConfig={qsConfig}
|
qsConfig={qsConfig}
|
||||||
additionalControls={[
|
additionalControls={[
|
||||||
...(canAddJT || canAddWFJT ? [addButton] : []),
|
...(canAddJT || canAddWFJT ? [addButton] : []),
|
||||||
@@ -259,6 +265,8 @@ function TemplateList({ defaultParams }) {
|
|||||||
template={template}
|
template={template}
|
||||||
detailUrl={`/templates/${template.type}/${template.id}`}
|
detailUrl={`/templates/${template.type}/${template.id}`}
|
||||||
onSelect={() => handleSelect(template)}
|
onSelect={() => handleSelect(template)}
|
||||||
|
isExpanded={expanded.some(row => row.id === template.id)}
|
||||||
|
onExpand={() => handleExpand(template)}
|
||||||
isSelected={selected.some(row => row.id === template.id)}
|
isSelected={selected.some(row => row.id === template.id)}
|
||||||
fetchTemplates={fetchTemplates}
|
fetchTemplates={fetchTemplates}
|
||||||
rowIndex={index}
|
rowIndex={index}
|
||||||
|
|||||||
@@ -34,6 +34,8 @@ const ExclamationTriangleIconWarning = styled(ExclamationTriangleIcon)`
|
|||||||
ExclamationTriangleIconWarning.displayName = 'ExclamationTriangleIconWarning';
|
ExclamationTriangleIconWarning.displayName = 'ExclamationTriangleIconWarning';
|
||||||
|
|
||||||
function TemplateListItem({
|
function TemplateListItem({
|
||||||
|
isExpanded,
|
||||||
|
onExpand,
|
||||||
template,
|
template,
|
||||||
isSelected,
|
isSelected,
|
||||||
onSelect,
|
onSelect,
|
||||||
@@ -42,7 +44,6 @@ function TemplateListItem({
|
|||||||
rowIndex,
|
rowIndex,
|
||||||
}) {
|
}) {
|
||||||
const config = useConfig();
|
const config = useConfig();
|
||||||
const [isExpanded, setIsExpanded] = useState(false);
|
|
||||||
const [isDisabled, setIsDisabled] = useState(false);
|
const [isDisabled, setIsDisabled] = useState(false);
|
||||||
const labelId = `check-action-${template.id}`;
|
const labelId = `check-action-${template.id}`;
|
||||||
|
|
||||||
@@ -119,7 +120,7 @@ function TemplateListItem({
|
|||||||
expand={{
|
expand={{
|
||||||
rowIndex,
|
rowIndex,
|
||||||
isExpanded,
|
isExpanded,
|
||||||
onToggle: () => setIsExpanded(!isExpanded),
|
onToggle: onExpand,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Td
|
<Td
|
||||||
|
|||||||
@@ -381,6 +381,7 @@ describe('<TemplateListItem />', () => {
|
|||||||
<tbody>
|
<tbody>
|
||||||
<TemplateListItem
|
<TemplateListItem
|
||||||
isSelected={false}
|
isSelected={false}
|
||||||
|
isExpanded
|
||||||
detailUrl="/templates/job_template/1/details"
|
detailUrl="/templates/job_template/1/details"
|
||||||
template={{
|
template={{
|
||||||
...mockJobTemplateData,
|
...mockJobTemplateData,
|
||||||
@@ -390,15 +391,7 @@ describe('<TemplateListItem />', () => {
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
);
|
);
|
||||||
expect(
|
|
||||||
wrapper
|
|
||||||
.find('Tr')
|
|
||||||
.last()
|
|
||||||
.prop('isExpanded')
|
|
||||||
).toBe(false);
|
|
||||||
await act(async () =>
|
|
||||||
wrapper.find('button[aria-label="Details"]').simulate('click')
|
|
||||||
);
|
|
||||||
wrapper.update();
|
wrapper.update();
|
||||||
expect(
|
expect(
|
||||||
wrapper
|
wrapper
|
||||||
|
|||||||
@@ -121,7 +121,6 @@ function ApplicationTokenList() {
|
|||||||
renderToolbar={props => (
|
renderToolbar={props => (
|
||||||
<DatalistToolbar
|
<DatalistToolbar
|
||||||
{...props}
|
{...props}
|
||||||
showSelectAll
|
|
||||||
isAllSelected={isAllSelected}
|
isAllSelected={isAllSelected}
|
||||||
onSelectAll={selectAll}
|
onSelectAll={selectAll}
|
||||||
qsConfig={QS_CONFIG}
|
qsConfig={QS_CONFIG}
|
||||||
|
|||||||
@@ -134,7 +134,6 @@ function ApplicationsList() {
|
|||||||
renderToolbar={props => (
|
renderToolbar={props => (
|
||||||
<DatalistToolbar
|
<DatalistToolbar
|
||||||
{...props}
|
{...props}
|
||||||
showSelectAll
|
|
||||||
isAllSelected={isAllSelected}
|
isAllSelected={isAllSelected}
|
||||||
onSelectAll={selectAll}
|
onSelectAll={selectAll}
|
||||||
qsConfig={QS_CONFIG}
|
qsConfig={QS_CONFIG}
|
||||||
|
|||||||
@@ -161,7 +161,6 @@ function CredentialList() {
|
|||||||
renderToolbar={props => (
|
renderToolbar={props => (
|
||||||
<DataListToolbar
|
<DataListToolbar
|
||||||
{...props}
|
{...props}
|
||||||
showSelectAll
|
|
||||||
isAllSelected={isAllSelected}
|
isAllSelected={isAllSelected}
|
||||||
onSelectAll={selectAll}
|
onSelectAll={selectAll}
|
||||||
qsConfig={QS_CONFIG}
|
qsConfig={QS_CONFIG}
|
||||||
|
|||||||
@@ -148,7 +148,6 @@ function CredentialTypeList() {
|
|||||||
renderToolbar={props => (
|
renderToolbar={props => (
|
||||||
<DatalistToolbar
|
<DatalistToolbar
|
||||||
{...props}
|
{...props}
|
||||||
showSelectAll
|
|
||||||
isAllSelected={isAllSelected}
|
isAllSelected={isAllSelected}
|
||||||
onSelectAll={selectAll}
|
onSelectAll={selectAll}
|
||||||
qsConfig={QS_CONFIG}
|
qsConfig={QS_CONFIG}
|
||||||
|
|||||||
@@ -164,7 +164,6 @@ function ExecutionEnvironmentList() {
|
|||||||
renderToolbar={props => (
|
renderToolbar={props => (
|
||||||
<DatalistToolbar
|
<DatalistToolbar
|
||||||
{...props}
|
{...props}
|
||||||
showSelectAll
|
|
||||||
isAllSelected={isAllSelected}
|
isAllSelected={isAllSelected}
|
||||||
onSelectAll={selectAll}
|
onSelectAll={selectAll}
|
||||||
qsConfig={QS_CONFIG}
|
qsConfig={QS_CONFIG}
|
||||||
|
|||||||
@@ -196,7 +196,6 @@ function HostGroupsList({ host }) {
|
|||||||
renderToolbar={props => (
|
renderToolbar={props => (
|
||||||
<DataListToolbar
|
<DataListToolbar
|
||||||
{...props}
|
{...props}
|
||||||
showSelectAll
|
|
||||||
isAllSelected={isAllSelected}
|
isAllSelected={isAllSelected}
|
||||||
onSelectAll={selectAll}
|
onSelectAll={selectAll}
|
||||||
qsConfig={QS_CONFIG}
|
qsConfig={QS_CONFIG}
|
||||||
|
|||||||
@@ -171,7 +171,6 @@ function HostList() {
|
|||||||
renderToolbar={props => (
|
renderToolbar={props => (
|
||||||
<DataListToolbar
|
<DataListToolbar
|
||||||
{...props}
|
{...props}
|
||||||
showSelectAll
|
|
||||||
isAllSelected={isAllSelected}
|
isAllSelected={isAllSelected}
|
||||||
onSelectAll={selectAll}
|
onSelectAll={selectAll}
|
||||||
qsConfig={QS_CONFIG}
|
qsConfig={QS_CONFIG}
|
||||||
|
|||||||
@@ -258,7 +258,6 @@ function InstanceGroupList({
|
|||||||
renderToolbar={props => (
|
renderToolbar={props => (
|
||||||
<DatalistToolbar
|
<DatalistToolbar
|
||||||
{...props}
|
{...props}
|
||||||
showSelectAll
|
|
||||||
isAllSelected={isAllSelected}
|
isAllSelected={isAllSelected}
|
||||||
onSelectAll={selectAll}
|
onSelectAll={selectAll}
|
||||||
qsConfig={QS_CONFIG}
|
qsConfig={QS_CONFIG}
|
||||||
|
|||||||
@@ -174,7 +174,6 @@ function InstanceList() {
|
|||||||
renderToolbar={props => (
|
renderToolbar={props => (
|
||||||
<DataListToolbar
|
<DataListToolbar
|
||||||
{...props}
|
{...props}
|
||||||
showSelectAll
|
|
||||||
isAllSelected={isAllSelected}
|
isAllSelected={isAllSelected}
|
||||||
onSelectAll={selectAll}
|
onSelectAll={selectAll}
|
||||||
qsConfig={QS_CONFIG}
|
qsConfig={QS_CONFIG}
|
||||||
|
|||||||
@@ -218,7 +218,6 @@ function InventoryGroupHostList() {
|
|||||||
renderToolbar={props => (
|
renderToolbar={props => (
|
||||||
<DataListToolbar
|
<DataListToolbar
|
||||||
{...props}
|
{...props}
|
||||||
showSelectAll
|
|
||||||
isAllSelected={isAllSelected}
|
isAllSelected={isAllSelected}
|
||||||
onSelectAll={isSelected =>
|
onSelectAll={isSelected =>
|
||||||
setSelected(isSelected ? [...hosts] : [])
|
setSelected(isSelected ? [...hosts] : [])
|
||||||
|
|||||||
@@ -159,7 +159,6 @@ function InventoryGroupsList() {
|
|||||||
renderToolbar={props => (
|
renderToolbar={props => (
|
||||||
<DataListToolbar
|
<DataListToolbar
|
||||||
{...props}
|
{...props}
|
||||||
showSelectAll
|
|
||||||
isAllSelected={isAllSelected}
|
isAllSelected={isAllSelected}
|
||||||
onSelectAll={selectAll}
|
onSelectAll={selectAll}
|
||||||
qsConfig={QS_CONFIG}
|
qsConfig={QS_CONFIG}
|
||||||
|
|||||||
@@ -197,7 +197,6 @@ function InventoryHostGroupsList() {
|
|||||||
renderToolbar={props => (
|
renderToolbar={props => (
|
||||||
<DataListToolbar
|
<DataListToolbar
|
||||||
{...props}
|
{...props}
|
||||||
showSelectAll
|
|
||||||
isAllSelected={isAllSelected}
|
isAllSelected={isAllSelected}
|
||||||
onSelectAll={selectAll}
|
onSelectAll={selectAll}
|
||||||
qsConfig={QS_CONFIG}
|
qsConfig={QS_CONFIG}
|
||||||
|
|||||||
@@ -140,7 +140,6 @@ function InventoryHostList() {
|
|||||||
renderToolbar={props => (
|
renderToolbar={props => (
|
||||||
<DataListToolbar
|
<DataListToolbar
|
||||||
{...props}
|
{...props}
|
||||||
showSelectAll
|
|
||||||
isAllSelected={isAllSelected}
|
isAllSelected={isAllSelected}
|
||||||
onSelectAll={selectAll}
|
onSelectAll={selectAll}
|
||||||
qsConfig={QS_CONFIG}
|
qsConfig={QS_CONFIG}
|
||||||
|
|||||||
@@ -214,7 +214,6 @@ function InventoryList() {
|
|||||||
renderToolbar={props => (
|
renderToolbar={props => (
|
||||||
<DatalistToolbar
|
<DatalistToolbar
|
||||||
{...props}
|
{...props}
|
||||||
showSelectAll
|
|
||||||
isAllSelected={isAllSelected}
|
isAllSelected={isAllSelected}
|
||||||
onSelectAll={selectAll}
|
onSelectAll={selectAll}
|
||||||
qsConfig={QS_CONFIG}
|
qsConfig={QS_CONFIG}
|
||||||
|
|||||||
@@ -192,7 +192,6 @@ function InventoryRelatedGroupList() {
|
|||||||
renderToolbar={props => (
|
renderToolbar={props => (
|
||||||
<DataListToolbar
|
<DataListToolbar
|
||||||
{...props}
|
{...props}
|
||||||
showSelectAll
|
|
||||||
isAllSelected={isAllSelected}
|
isAllSelected={isAllSelected}
|
||||||
onSelectAll={isSelected =>
|
onSelectAll={isSelected =>
|
||||||
setSelected(isSelected ? [...groups] : [])
|
setSelected(isSelected ? [...groups] : [])
|
||||||
|
|||||||
@@ -169,7 +169,6 @@ function InventorySourceList() {
|
|||||||
renderToolbar={props => (
|
renderToolbar={props => (
|
||||||
<DatalistToolbar
|
<DatalistToolbar
|
||||||
{...props}
|
{...props}
|
||||||
showSelectAll
|
|
||||||
isAllSelected={isAllSelected}
|
isAllSelected={isAllSelected}
|
||||||
onSelectAll={selectAll}
|
onSelectAll={selectAll}
|
||||||
qsConfig={QS_CONFIG}
|
qsConfig={QS_CONFIG}
|
||||||
|
|||||||
@@ -87,7 +87,6 @@ function SmartInventoryHostList({ inventory }) {
|
|||||||
renderToolbar={props => (
|
renderToolbar={props => (
|
||||||
<DataListToolbar
|
<DataListToolbar
|
||||||
{...props}
|
{...props}
|
||||||
showSelectAll
|
|
||||||
isAllSelected={isAllSelected}
|
isAllSelected={isAllSelected}
|
||||||
onSelectAll={selectAll}
|
onSelectAll={selectAll}
|
||||||
qsConfig={QS_CONFIG}
|
qsConfig={QS_CONFIG}
|
||||||
|
|||||||
@@ -91,11 +91,7 @@ function ManagementJobList() {
|
|||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
renderToolbar={props => (
|
renderToolbar={props => (
|
||||||
<DatalistToolbar
|
<DatalistToolbar {...props} qsConfig={QS_CONFIG} />
|
||||||
{...props}
|
|
||||||
showSelectAll={false}
|
|
||||||
qsConfig={QS_CONFIG}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
headerRow={
|
headerRow={
|
||||||
<HeaderRow qsConfig={QS_CONFIG}>
|
<HeaderRow qsConfig={QS_CONFIG}>
|
||||||
|
|||||||
@@ -176,7 +176,6 @@ function NotificationTemplatesList() {
|
|||||||
renderToolbar={props => (
|
renderToolbar={props => (
|
||||||
<DataListToolbar
|
<DataListToolbar
|
||||||
{...props}
|
{...props}
|
||||||
showSelectAll
|
|
||||||
isAllSelected={isAllSelected}
|
isAllSelected={isAllSelected}
|
||||||
onSelectAll={selectAll}
|
onSelectAll={selectAll}
|
||||||
qsConfig={QS_CONFIG}
|
qsConfig={QS_CONFIG}
|
||||||
|
|||||||
@@ -156,7 +156,6 @@ function OrganizationsList() {
|
|||||||
renderToolbar={props => (
|
renderToolbar={props => (
|
||||||
<DataListToolbar
|
<DataListToolbar
|
||||||
{...props}
|
{...props}
|
||||||
showSelectAll
|
|
||||||
isAllSelected={isAllSelected}
|
isAllSelected={isAllSelected}
|
||||||
onSelectAll={selectAll}
|
onSelectAll={selectAll}
|
||||||
qsConfig={QS_CONFIG}
|
qsConfig={QS_CONFIG}
|
||||||
|
|||||||
@@ -141,7 +141,6 @@ function ProjectJobTemplatesList() {
|
|||||||
renderToolbar={props => (
|
renderToolbar={props => (
|
||||||
<DatalistToolbar
|
<DatalistToolbar
|
||||||
{...props}
|
{...props}
|
||||||
showSelectAll
|
|
||||||
isAllSelected={isAllSelected}
|
isAllSelected={isAllSelected}
|
||||||
onSelectAll={selectAll}
|
onSelectAll={selectAll}
|
||||||
qsConfig={QS_CONFIG}
|
qsConfig={QS_CONFIG}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import PaginatedTable, {
|
|||||||
} from '../../../components/PaginatedTable';
|
} from '../../../components/PaginatedTable';
|
||||||
import useWsProjects from './useWsProjects';
|
import useWsProjects from './useWsProjects';
|
||||||
import useSelected from '../../../util/useSelected';
|
import useSelected from '../../../util/useSelected';
|
||||||
|
import useExpanded from '../../../util/useExpanded';
|
||||||
import { relatedResourceDeleteRequests } from '../../../util/getRelatedResourceDeleteDetails';
|
import { relatedResourceDeleteRequests } from '../../../util/getRelatedResourceDeleteDetails';
|
||||||
import { getQSConfig, parseQueryString } from '../../../util/qs';
|
import { getQSConfig, parseQueryString } from '../../../util/qs';
|
||||||
|
|
||||||
@@ -103,6 +104,10 @@ function ProjectList() {
|
|||||||
clearSelected,
|
clearSelected,
|
||||||
} = useSelected(projects);
|
} = useSelected(projects);
|
||||||
|
|
||||||
|
const { expanded, isAllExpanded, handleExpand, expandAll } = useExpanded(
|
||||||
|
projects
|
||||||
|
);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isLoading: isDeleteLoading,
|
isLoading: isDeleteLoading,
|
||||||
deleteItems: deleteProjects,
|
deleteItems: deleteProjects,
|
||||||
@@ -212,7 +217,8 @@ function ProjectList() {
|
|||||||
renderToolbar={props => (
|
renderToolbar={props => (
|
||||||
<DataListToolbar
|
<DataListToolbar
|
||||||
{...props}
|
{...props}
|
||||||
showSelectAll
|
isAllExpanded={isAllExpanded}
|
||||||
|
onExpandAll={expandAll}
|
||||||
isAllSelected={isAllSelected}
|
isAllSelected={isAllSelected}
|
||||||
onSelectAll={selectAll}
|
onSelectAll={selectAll}
|
||||||
qsConfig={QS_CONFIG}
|
qsConfig={QS_CONFIG}
|
||||||
@@ -244,6 +250,8 @@ function ProjectList() {
|
|||||||
)}
|
)}
|
||||||
renderRow={(project, index) => (
|
renderRow={(project, index) => (
|
||||||
<ProjectListItem
|
<ProjectListItem
|
||||||
|
isExpanded={expanded.some(row => row.id === project.id)}
|
||||||
|
onExpand={() => handleExpand(project)}
|
||||||
fetchProjects={fetchProjects}
|
fetchProjects={fetchProjects}
|
||||||
key={project.id}
|
key={project.id}
|
||||||
project={project}
|
project={project}
|
||||||
|
|||||||
@@ -38,6 +38,8 @@ const ExclamationTriangleIcon = styled(PFExclamationTriangleIcon)`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
function ProjectListItem({
|
function ProjectListItem({
|
||||||
|
isExpanded,
|
||||||
|
onExpand,
|
||||||
project,
|
project,
|
||||||
isSelected,
|
isSelected,
|
||||||
onSelect,
|
onSelect,
|
||||||
@@ -46,7 +48,6 @@ function ProjectListItem({
|
|||||||
rowIndex,
|
rowIndex,
|
||||||
onRefreshRow,
|
onRefreshRow,
|
||||||
}) {
|
}) {
|
||||||
const [isExpanded, setIsExpanded] = useState(false);
|
|
||||||
const [isDisabled, setIsDisabled] = useState(false);
|
const [isDisabled, setIsDisabled] = useState(false);
|
||||||
ProjectListItem.propTypes = {
|
ProjectListItem.propTypes = {
|
||||||
project: Project.isRequired,
|
project: Project.isRequired,
|
||||||
@@ -165,7 +166,7 @@ function ProjectListItem({
|
|||||||
expand={{
|
expand={{
|
||||||
rowIndex,
|
rowIndex,
|
||||||
isExpanded,
|
isExpanded,
|
||||||
onToggle: () => setIsExpanded(!isExpanded),
|
onToggle: onExpand,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Td
|
<Td
|
||||||
|
|||||||
@@ -393,6 +393,7 @@ describe('<ProjectsListItem />', () => {
|
|||||||
<tbody>
|
<tbody>
|
||||||
<ProjectsListItem
|
<ProjectsListItem
|
||||||
rowIndex={1}
|
rowIndex={1}
|
||||||
|
isExpanded
|
||||||
isSelected={false}
|
isSelected={false}
|
||||||
detailUrl="/project/1"
|
detailUrl="/project/1"
|
||||||
onSelect={() => {}}
|
onSelect={() => {}}
|
||||||
@@ -431,16 +432,7 @@ describe('<ProjectsListItem />', () => {
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
);
|
);
|
||||||
expect(
|
|
||||||
wrapper
|
|
||||||
.find('Tr')
|
|
||||||
.last()
|
|
||||||
.prop('isExpanded')
|
|
||||||
).toBe(false);
|
|
||||||
await act(async () =>
|
|
||||||
wrapper.find('button[aria-label="Details"]').simulate('click')
|
|
||||||
);
|
|
||||||
wrapper.update();
|
|
||||||
expect(
|
expect(
|
||||||
wrapper
|
wrapper
|
||||||
.find('Tr')
|
.find('Tr')
|
||||||
|
|||||||
@@ -152,7 +152,6 @@ function TeamList() {
|
|||||||
renderToolbar={props => (
|
renderToolbar={props => (
|
||||||
<DataListToolbar
|
<DataListToolbar
|
||||||
{...props}
|
{...props}
|
||||||
showSelectAll
|
|
||||||
isAllSelected={isAllSelected}
|
isAllSelected={isAllSelected}
|
||||||
onSelectAll={selectAll}
|
onSelectAll={selectAll}
|
||||||
qsConfig={QS_CONFIG}
|
qsConfig={QS_CONFIG}
|
||||||
|
|||||||
@@ -135,7 +135,6 @@ function UserList() {
|
|||||||
renderToolbar={props => (
|
renderToolbar={props => (
|
||||||
<DataListToolbar
|
<DataListToolbar
|
||||||
{...props}
|
{...props}
|
||||||
showSelectAll
|
|
||||||
isAllSelected={isAllSelected}
|
isAllSelected={isAllSelected}
|
||||||
onSelectAll={selectAll}
|
onSelectAll={selectAll}
|
||||||
qsConfig={QS_CONFIG}
|
qsConfig={QS_CONFIG}
|
||||||
|
|||||||
@@ -202,7 +202,6 @@ function UserTeamList() {
|
|||||||
renderToolbar={props => (
|
renderToolbar={props => (
|
||||||
<DataListToolbar
|
<DataListToolbar
|
||||||
{...props}
|
{...props}
|
||||||
showSelectAll
|
|
||||||
isAllSelected={isAllSelected}
|
isAllSelected={isAllSelected}
|
||||||
onSelectAll={selectAll}
|
onSelectAll={selectAll}
|
||||||
qsConfig={QS_CONFIG}
|
qsConfig={QS_CONFIG}
|
||||||
|
|||||||
@@ -150,7 +150,6 @@ function UserTokenList() {
|
|||||||
renderToolbar={props => (
|
renderToolbar={props => (
|
||||||
<DataListToolbar
|
<DataListToolbar
|
||||||
{...props}
|
{...props}
|
||||||
showSelectAll
|
|
||||||
isAllSelected={isAllSelected}
|
isAllSelected={isAllSelected}
|
||||||
qsConfig={QS_CONFIG}
|
qsConfig={QS_CONFIG}
|
||||||
onSelectAll={selectAll}
|
onSelectAll={selectAll}
|
||||||
|
|||||||
@@ -189,7 +189,6 @@ function WorkflowApprovalsList() {
|
|||||||
renderToolbar={props => (
|
renderToolbar={props => (
|
||||||
<DataListToolbar
|
<DataListToolbar
|
||||||
{...props}
|
{...props}
|
||||||
showSelectAll
|
|
||||||
isAllSelected={isAllSelected}
|
isAllSelected={isAllSelected}
|
||||||
onSelectAll={selectAll}
|
onSelectAll={selectAll}
|
||||||
qsConfig={QS_CONFIG}
|
qsConfig={QS_CONFIG}
|
||||||
|
|||||||
32
awx/ui_next/src/util/useExpanded.jsx
Normal file
32
awx/ui_next/src/util/useExpanded.jsx
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import { useState, useCallback } from 'react';
|
||||||
|
|
||||||
|
export default function useExpanded(list = []) {
|
||||||
|
const [expanded, setExpanded] = useState([]);
|
||||||
|
const isAllExpanded = expanded.length > 0 && expanded.length === list.length;
|
||||||
|
|
||||||
|
const handleExpand = row => {
|
||||||
|
if (!row.id) {
|
||||||
|
throw new Error(`Selected row does not have an id`);
|
||||||
|
}
|
||||||
|
if (expanded.some(s => s.id === row.id)) {
|
||||||
|
setExpanded(prevState => [...prevState.filter(i => i.id !== row.id)]);
|
||||||
|
} else {
|
||||||
|
setExpanded(prevState => [...prevState, row]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const expandAll = useCallback(
|
||||||
|
isExpanded => {
|
||||||
|
setExpanded(isExpanded ? [...list] : []);
|
||||||
|
},
|
||||||
|
[list]
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
expanded,
|
||||||
|
isAllExpanded,
|
||||||
|
handleExpand,
|
||||||
|
setExpanded,
|
||||||
|
expandAll,
|
||||||
|
};
|
||||||
|
}
|
||||||
116
awx/ui_next/src/util/useExpanded.test.jsx
Normal file
116
awx/ui_next/src/util/useExpanded.test.jsx
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { act } from 'react-dom/test-utils';
|
||||||
|
import { mount } from 'enzyme';
|
||||||
|
import useExpanded from './useExpanded';
|
||||||
|
|
||||||
|
const array = [{ id: '1' }, { id: '2' }, { id: '3' }];
|
||||||
|
|
||||||
|
const TestHook = ({ callback }) => {
|
||||||
|
callback();
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const testHook = callback => {
|
||||||
|
mount(<TestHook callback={callback} />);
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('useSelected hook', () => {
|
||||||
|
let expanded;
|
||||||
|
let isAllExpanded;
|
||||||
|
let handleExpand;
|
||||||
|
let setExpanded;
|
||||||
|
let expandAll;
|
||||||
|
|
||||||
|
test('should return expected initial values', () => {
|
||||||
|
testHook(() => {
|
||||||
|
({
|
||||||
|
expanded,
|
||||||
|
isAllExpanded,
|
||||||
|
handleExpand,
|
||||||
|
setExpanded,
|
||||||
|
expandAll,
|
||||||
|
} = useExpanded());
|
||||||
|
});
|
||||||
|
expect(expanded).toEqual([]);
|
||||||
|
expect(isAllExpanded).toEqual(false);
|
||||||
|
expect(handleExpand).toBeInstanceOf(Function);
|
||||||
|
expect(setExpanded).toBeInstanceOf(Function);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('handleSelect should update and filter selected items', () => {
|
||||||
|
testHook(() => {
|
||||||
|
({
|
||||||
|
expanded,
|
||||||
|
isAllExpanded,
|
||||||
|
handleExpand,
|
||||||
|
setExpanded,
|
||||||
|
expandAll,
|
||||||
|
} = useExpanded());
|
||||||
|
});
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
handleExpand(array[0]);
|
||||||
|
});
|
||||||
|
expect(expanded).toEqual([array[0]]);
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
handleExpand(array[0]);
|
||||||
|
});
|
||||||
|
expect(expanded).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should return expected isAllSelected value', () => {
|
||||||
|
testHook(() => {
|
||||||
|
({
|
||||||
|
expanded,
|
||||||
|
isAllExpanded,
|
||||||
|
handleExpand,
|
||||||
|
setExpanded,
|
||||||
|
expandAll,
|
||||||
|
} = useExpanded(array));
|
||||||
|
});
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
handleExpand(array[0]);
|
||||||
|
});
|
||||||
|
expect(expanded).toEqual([array[0]]);
|
||||||
|
expect(isAllExpanded).toEqual(false);
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
handleExpand(array[1]);
|
||||||
|
handleExpand(array[2]);
|
||||||
|
});
|
||||||
|
expect(expanded).toEqual(array);
|
||||||
|
expect(isAllExpanded).toEqual(true);
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
setExpanded([]);
|
||||||
|
});
|
||||||
|
expect(expanded).toEqual([]);
|
||||||
|
expect(isAllExpanded).toEqual(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should return selectAll', () => {
|
||||||
|
testHook(() => {
|
||||||
|
({
|
||||||
|
expanded,
|
||||||
|
isAllExpanded,
|
||||||
|
handleExpand,
|
||||||
|
setExpanded,
|
||||||
|
expandAll,
|
||||||
|
} = useExpanded(array));
|
||||||
|
});
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
expandAll(true);
|
||||||
|
});
|
||||||
|
expect(isAllExpanded).toEqual(true);
|
||||||
|
expect(expanded).toEqual(array);
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
expandAll(false);
|
||||||
|
});
|
||||||
|
expect(isAllExpanded).toEqual(false);
|
||||||
|
expect(expanded).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user