mirror of
https://github.com/ansible/awx.git
synced 2026-01-20 06:01:25 -03:30
Merge pull request #10329 from keithjgrant/6853-select-all-quirks
Clear list selections on pagination/search/sort SUMMARY Updates nearly every list* so that URL param changes (pagination, search, or sort) clear the selection. This prevents the list of selected items in state from including items that may no longer appear on screen — preventing the user from accidentally deleting or otherwise altering an item they may not realize they still have selected. This also updates the useSelected hook to provide selectAll and clearSelected functions. Any lists that weren't yet already using this hook have been updated to do so. Addresses #6853 and #7509 ISSUE TYPE Bugfix Pull Request COMPONENT NAME UI ADDITIONAL INFORMATION *Lists that do not include this change are modals where the user is expected to paginate through screens and make several selections along the way (e.g. Multi Credential select modal), and lists that still use PaginatedDataList and are yet to be converted to PaginatedTable Note: I originally wanted to make the clearSelected prop on PaginatedTable required, so any lists that don't have this fix applied would fail loudly. Unfortunately that wasn't possible, as there were a few lists that should not have this behavior, so I had to leave it as an optional prop. Reviewed-by: Alex Corey <Alex.swansboro@gmail.com> Reviewed-by: Jake McDermott <yo@jakemcdermott.me>
This commit is contained in:
commit
168c022d3e
@ -37,7 +37,6 @@ function DataListToolbar({
|
||||
onExpand,
|
||||
onSelectAll,
|
||||
additionalControls,
|
||||
|
||||
qsConfig,
|
||||
pagination,
|
||||
}) {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import React, { useEffect, useCallback } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { t, Plural } from '@lingui/macro';
|
||||
|
||||
@ -13,7 +13,7 @@ import useRequest, {
|
||||
useDismissableError,
|
||||
} from '../../util/useRequest';
|
||||
import { useConfig } from '../../contexts/Config';
|
||||
|
||||
import useSelected from '../../util/useSelected';
|
||||
import { isJobRunning, getJobModel } from '../../util/jobs';
|
||||
import { getQSConfig, parseQueryString } from '../../util/qs';
|
||||
import JobListItem from './JobListItem';
|
||||
@ -35,8 +35,6 @@ function JobList({ defaultParams, showTypeColumn = false }) {
|
||||
);
|
||||
|
||||
const { me } = useConfig();
|
||||
|
||||
const [selected, setSelected] = useState([]);
|
||||
const location = useLocation();
|
||||
const {
|
||||
result: { results, count, relatedSearchableKeys, searchableKeys },
|
||||
@ -88,7 +86,13 @@ function JobList({ defaultParams, showTypeColumn = false }) {
|
||||
|
||||
const jobs = useWsJobs(results, fetchJobsById, qsConfig);
|
||||
|
||||
const isAllSelected = selected.length === jobs.length && selected.length > 0;
|
||||
const {
|
||||
selected,
|
||||
isAllSelected,
|
||||
handleSelect,
|
||||
selectAll,
|
||||
clearSelected,
|
||||
} = useSelected(jobs);
|
||||
|
||||
const {
|
||||
error: cancelJobsError,
|
||||
@ -135,24 +139,12 @@ function JobList({ defaultParams, showTypeColumn = false }) {
|
||||
|
||||
const handleJobCancel = async () => {
|
||||
await cancelJobs();
|
||||
setSelected([]);
|
||||
clearSelected();
|
||||
};
|
||||
|
||||
const handleJobDelete = async () => {
|
||||
await deleteJobs();
|
||||
setSelected([]);
|
||||
};
|
||||
|
||||
const handleSelectAll = isSelected => {
|
||||
setSelected(isSelected ? [...jobs] : []);
|
||||
};
|
||||
|
||||
const handleSelect = item => {
|
||||
if (selected.some(s => s.id === item.id)) {
|
||||
setSelected(selected.filter(s => s.id !== item.id));
|
||||
} else {
|
||||
setSelected(selected.concat(item));
|
||||
}
|
||||
clearSelected();
|
||||
};
|
||||
|
||||
const cannotDeleteItems = selected.filter(job => isJobRunning(job.status));
|
||||
@ -226,6 +218,7 @@ function JobList({ defaultParams, showTypeColumn = false }) {
|
||||
<HeaderCell>{t`Actions`}</HeaderCell>
|
||||
</HeaderRow>
|
||||
}
|
||||
clearSelected={clearSelected}
|
||||
toolbarSearchableKeys={searchableKeys}
|
||||
toolbarRelatedSearchableKeys={relatedSearchableKeys}
|
||||
renderToolbar={props => (
|
||||
@ -233,7 +226,7 @@ function JobList({ defaultParams, showTypeColumn = false }) {
|
||||
{...props}
|
||||
showSelectAll
|
||||
isAllSelected={isAllSelected}
|
||||
onSelectAll={handleSelectAll}
|
||||
onSelectAll={selectAll}
|
||||
qsConfig={qsConfig}
|
||||
additionalControls={[
|
||||
<ToolbarDeleteButton
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import 'styled-components/macro';
|
||||
import React, { Fragment } from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { TableComposable, Tbody } from '@patternfly/react-table';
|
||||
|
||||
@ -33,10 +33,16 @@ function PaginatedTable({
|
||||
showPageSizeOptions,
|
||||
renderToolbar,
|
||||
emptyContentMessage,
|
||||
clearSelected,
|
||||
ouiaId,
|
||||
}) {
|
||||
const { search, pathname } = useLocation();
|
||||
const history = useHistory();
|
||||
const location = useLocation();
|
||||
|
||||
useEffect(() => {
|
||||
clearSelected();
|
||||
}, [location.search, clearSelected]);
|
||||
|
||||
const pushHistoryState = qs => {
|
||||
history.push(qs ? `${pathname}?${qs}` : pathname);
|
||||
@ -127,7 +133,7 @@ function PaginatedTable({
|
||||
);
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<>
|
||||
<ListHeader
|
||||
itemCount={itemCount}
|
||||
renderToolbar={renderToolbar}
|
||||
@ -159,7 +165,7 @@ function PaginatedTable({
|
||||
onPerPageSelect={handleSetPageSize}
|
||||
/>
|
||||
) : null}
|
||||
</Fragment>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@ -182,6 +188,7 @@ PaginatedTable.propTypes = {
|
||||
renderToolbar: PropTypes.func,
|
||||
hasContentLoading: PropTypes.bool,
|
||||
contentError: PropTypes.shape(),
|
||||
clearSelected: PropTypes.func,
|
||||
ouiaId: PropTypes.string,
|
||||
};
|
||||
|
||||
@ -195,6 +202,7 @@ PaginatedTable.defaultProps = {
|
||||
showPageSizeOptions: true,
|
||||
renderToolbar: props => <DataListToolbar {...props} />,
|
||||
ouiaId: null,
|
||||
clearSelected: () => {},
|
||||
};
|
||||
|
||||
export { PaginatedTable as _PaginatedTable };
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import React, { useEffect, useCallback } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { bool, func } from 'prop-types';
|
||||
|
||||
@ -10,6 +10,7 @@ import PaginatedTable, { HeaderRow, HeaderCell } from '../../PaginatedTable';
|
||||
import DataListToolbar from '../../DataListToolbar';
|
||||
import { ToolbarAddButton, ToolbarDeleteButton } from '../../PaginatedDataList';
|
||||
import useRequest, { useDeleteItems } from '../../../util/useRequest';
|
||||
import useSelected from '../../../util/useSelected';
|
||||
import { getQSConfig, parseQueryString } from '../../../util/qs';
|
||||
import ScheduleListItem from './ScheduleListItem';
|
||||
|
||||
@ -27,8 +28,6 @@ function ScheduleList({
|
||||
launchConfig,
|
||||
surveyConfig,
|
||||
}) {
|
||||
const [selected, setSelected] = useState([]);
|
||||
|
||||
const location = useLocation();
|
||||
|
||||
const {
|
||||
@ -76,8 +75,13 @@ function ScheduleList({
|
||||
fetchSchedules();
|
||||
}, [fetchSchedules]);
|
||||
|
||||
const isAllSelected =
|
||||
selected.length === schedules.length && selected.length > 0;
|
||||
const {
|
||||
selected,
|
||||
isAllSelected,
|
||||
handleSelect,
|
||||
selectAll,
|
||||
clearSelected,
|
||||
} = useSelected(schedules);
|
||||
|
||||
const {
|
||||
isLoading: isDeleteLoading,
|
||||
@ -95,21 +99,9 @@ function ScheduleList({
|
||||
}
|
||||
);
|
||||
|
||||
const handleSelectAll = isSelected => {
|
||||
setSelected(isSelected ? [...schedules] : []);
|
||||
};
|
||||
|
||||
const handleSelect = row => {
|
||||
if (selected.some(s => s.id === row.id)) {
|
||||
setSelected(selected.filter(s => s.id !== row.id));
|
||||
} else {
|
||||
setSelected(selected.concat(row));
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = async () => {
|
||||
await deleteJobs();
|
||||
setSelected([]);
|
||||
clearSelected();
|
||||
};
|
||||
|
||||
const canAdd =
|
||||
@ -183,6 +175,7 @@ function ScheduleList({
|
||||
isMissingSurvey={isTemplate && hasMissingSurveyValue(item)}
|
||||
/>
|
||||
)}
|
||||
clearSelected={clearSelected}
|
||||
toolbarSearchColumns={[
|
||||
{
|
||||
name: t`Name`,
|
||||
@ -209,7 +202,7 @@ function ScheduleList({
|
||||
{...props}
|
||||
showSelectAll
|
||||
isAllSelected={isAllSelected}
|
||||
onSelectAll={handleSelectAll}
|
||||
onSelectAll={selectAll}
|
||||
qsConfig={QS_CONFIG}
|
||||
additionalControls={[
|
||||
...(canAdd
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React, { Fragment, useEffect, useState, useCallback } from 'react';
|
||||
import React, { useEffect, useCallback } from 'react';
|
||||
import { useLocation, Link } from 'react-router-dom';
|
||||
import { t, Plural } from '@lingui/macro';
|
||||
import { Card, DropdownItem } from '@patternfly/react-core';
|
||||
@ -13,6 +13,7 @@ import ErrorDetail from '../ErrorDetail';
|
||||
import { ToolbarDeleteButton } from '../PaginatedDataList';
|
||||
import PaginatedTable, { HeaderRow, HeaderCell } from '../PaginatedTable';
|
||||
import useRequest, { useDeleteItems } from '../../util/useRequest';
|
||||
import useSelected from '../../util/useSelected';
|
||||
import { getQSConfig, parseQueryString } from '../../util/qs';
|
||||
import useWsTemplates from '../../util/useWsTemplates';
|
||||
import AddDropDownButton from '../AddDropDownButton';
|
||||
@ -35,8 +36,6 @@ function TemplateList({ defaultParams }) {
|
||||
);
|
||||
|
||||
const location = useLocation();
|
||||
const [selected, setSelected] = useState([]);
|
||||
|
||||
const {
|
||||
result: {
|
||||
results,
|
||||
@ -87,8 +86,14 @@ function TemplateList({ defaultParams }) {
|
||||
|
||||
const templates = useWsTemplates(results);
|
||||
|
||||
const isAllSelected =
|
||||
selected.length === templates.length && selected.length > 0;
|
||||
const {
|
||||
selected,
|
||||
isAllSelected,
|
||||
handleSelect,
|
||||
selectAll,
|
||||
clearSelected,
|
||||
} = useSelected(templates);
|
||||
|
||||
const {
|
||||
isLoading: isDeleteLoading,
|
||||
deleteItems: deleteTemplates,
|
||||
@ -117,19 +122,7 @@ function TemplateList({ defaultParams }) {
|
||||
|
||||
const handleTemplateDelete = async () => {
|
||||
await deleteTemplates();
|
||||
setSelected([]);
|
||||
};
|
||||
|
||||
const handleSelectAll = isSelected => {
|
||||
setSelected(isSelected ? [...templates] : []);
|
||||
};
|
||||
|
||||
const handleSelect = template => {
|
||||
if (selected.some(s => s.id === template.id)) {
|
||||
setSelected(selected.filter(s => s.id !== template.id));
|
||||
} else {
|
||||
setSelected(selected.concat(template));
|
||||
}
|
||||
clearSelected();
|
||||
};
|
||||
|
||||
const canAddJT =
|
||||
@ -179,7 +172,7 @@ function TemplateList({ defaultParams }) {
|
||||
);
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<>
|
||||
<Card>
|
||||
<PaginatedTable
|
||||
contentError={contentError}
|
||||
@ -188,7 +181,7 @@ function TemplateList({ defaultParams }) {
|
||||
itemCount={count}
|
||||
pluralizedItemName={t`Templates`}
|
||||
qsConfig={qsConfig}
|
||||
onRowClick={handleSelect}
|
||||
clearSelected={clearSelected}
|
||||
toolbarSearchColumns={[
|
||||
{
|
||||
name: t`Name`,
|
||||
@ -235,7 +228,7 @@ function TemplateList({ defaultParams }) {
|
||||
{...props}
|
||||
showSelectAll
|
||||
isAllSelected={isAllSelected}
|
||||
onSelectAll={handleSelectAll}
|
||||
onSelectAll={selectAll}
|
||||
qsConfig={qsConfig}
|
||||
additionalControls={[
|
||||
...(canAddJT || canAddWFJT ? [addButton] : []),
|
||||
@ -281,7 +274,7 @@ function TemplateList({ defaultParams }) {
|
||||
{t`Failed to delete one or more templates.`}
|
||||
<ErrorDetail error={deletionError} />
|
||||
</AlertModal>
|
||||
</Fragment>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -77,9 +77,13 @@ function ApplicationsList() {
|
||||
fetchApplications();
|
||||
}, [fetchApplications]);
|
||||
|
||||
const { selected, isAllSelected, handleSelect, setSelected } = useSelected(
|
||||
applications
|
||||
);
|
||||
const {
|
||||
selected,
|
||||
isAllSelected,
|
||||
handleSelect,
|
||||
clearSelected,
|
||||
selectAll,
|
||||
} = useSelected(applications);
|
||||
|
||||
const {
|
||||
isLoading: deleteLoading,
|
||||
@ -99,7 +103,7 @@ function ApplicationsList() {
|
||||
|
||||
const handleDeleteApplications = async () => {
|
||||
await deleteApplications();
|
||||
setSelected([]);
|
||||
clearSelected();
|
||||
};
|
||||
|
||||
const canAdd = actions && actions.POST;
|
||||
@ -115,7 +119,7 @@ function ApplicationsList() {
|
||||
itemCount={itemCount}
|
||||
pluralizedItemName={t`Applications`}
|
||||
qsConfig={QS_CONFIG}
|
||||
onRowClick={handleSelect}
|
||||
clearSelected={clearSelected}
|
||||
toolbarSearchColumns={[
|
||||
{
|
||||
name: t`Name`,
|
||||
@ -134,9 +138,7 @@ function ApplicationsList() {
|
||||
{...props}
|
||||
showSelectAll
|
||||
isAllSelected={isAllSelected}
|
||||
onSelectAll={isSelected =>
|
||||
setSelected(isSelected ? [...applications] : [])
|
||||
}
|
||||
onSelectAll={selectAll}
|
||||
qsConfig={QS_CONFIG}
|
||||
additionalControls={[
|
||||
...(canAdd
|
||||
|
||||
@ -76,9 +76,14 @@ function CredentialList() {
|
||||
fetchCredentials();
|
||||
}, [fetchCredentials]);
|
||||
|
||||
const { selected, isAllSelected, handleSelect, setSelected } = useSelected(
|
||||
credentials
|
||||
);
|
||||
const {
|
||||
selected,
|
||||
isAllSelected,
|
||||
handleSelect,
|
||||
setSelected,
|
||||
selectAll,
|
||||
clearSelected,
|
||||
} = useSelected(credentials);
|
||||
|
||||
const {
|
||||
isLoading: isDeleteLoading,
|
||||
@ -115,7 +120,7 @@ function CredentialList() {
|
||||
items={credentials}
|
||||
itemCount={credentialCount}
|
||||
qsConfig={QS_CONFIG}
|
||||
onRowClick={handleSelect}
|
||||
clearSelected={clearSelected}
|
||||
toolbarSearchableKeys={searchableKeys}
|
||||
toolbarRelatedSearchableKeys={relatedSearchableKeys}
|
||||
toolbarSearchColumns={[
|
||||
@ -160,9 +165,7 @@ function CredentialList() {
|
||||
{...props}
|
||||
showSelectAll
|
||||
isAllSelected={isAllSelected}
|
||||
onSelectAll={isSelected =>
|
||||
setSelected(isSelected ? [...credentials] : [])
|
||||
}
|
||||
onSelectAll={selectAll}
|
||||
qsConfig={QS_CONFIG}
|
||||
additionalControls={[
|
||||
...(canAdd
|
||||
|
||||
@ -77,9 +77,13 @@ function CredentialTypeList() {
|
||||
fetchCredentialTypes();
|
||||
}, [fetchCredentialTypes]);
|
||||
|
||||
const { selected, isAllSelected, handleSelect, setSelected } = useSelected(
|
||||
credentialTypes
|
||||
);
|
||||
const {
|
||||
selected,
|
||||
isAllSelected,
|
||||
handleSelect,
|
||||
clearSelected,
|
||||
selectAll,
|
||||
} = useSelected(credentialTypes);
|
||||
|
||||
const {
|
||||
isLoading: deleteLoading,
|
||||
@ -101,7 +105,7 @@ function CredentialTypeList() {
|
||||
|
||||
const handleDelete = async () => {
|
||||
await deleteCredentialTypes();
|
||||
setSelected([]);
|
||||
clearSelected();
|
||||
};
|
||||
|
||||
const canAdd = actions && actions.POST;
|
||||
@ -121,7 +125,7 @@ function CredentialTypeList() {
|
||||
itemCount={credentialTypesCount}
|
||||
pluralizedItemName={t`Credential Types`}
|
||||
qsConfig={QS_CONFIG}
|
||||
onRowClick={handleSelect}
|
||||
clearSelected={clearSelected}
|
||||
toolbarSearchColumns={[
|
||||
{
|
||||
name: t`Name`,
|
||||
@ -148,9 +152,7 @@ function CredentialTypeList() {
|
||||
{...props}
|
||||
showSelectAll
|
||||
isAllSelected={isAllSelected}
|
||||
onSelectAll={isSelected =>
|
||||
setSelected(isSelected ? [...credentialTypes] : [])
|
||||
}
|
||||
onSelectAll={selectAll}
|
||||
qsConfig={QS_CONFIG}
|
||||
additionalControls={[
|
||||
...(canAdd
|
||||
|
||||
@ -76,9 +76,13 @@ function ExecutionEnvironmentList() {
|
||||
fetchExecutionEnvironments();
|
||||
}, [fetchExecutionEnvironments]);
|
||||
|
||||
const { selected, isAllSelected, handleSelect, setSelected } = useSelected(
|
||||
executionEnvironments
|
||||
);
|
||||
const {
|
||||
selected,
|
||||
isAllSelected,
|
||||
handleSelect,
|
||||
clearSelected,
|
||||
selectAll,
|
||||
} = useSelected(executionEnvironments);
|
||||
|
||||
const {
|
||||
isLoading: deleteLoading,
|
||||
@ -100,7 +104,7 @@ function ExecutionEnvironmentList() {
|
||||
|
||||
const handleDelete = async () => {
|
||||
await deleteExecutionEnvironments();
|
||||
setSelected([]);
|
||||
clearSelected();
|
||||
};
|
||||
|
||||
const canAdd = actions && actions.POST;
|
||||
@ -119,7 +123,7 @@ function ExecutionEnvironmentList() {
|
||||
itemCount={executionEnvironmentsCount}
|
||||
pluralizedItemName={t`Execution Environments`}
|
||||
qsConfig={QS_CONFIG}
|
||||
onRowClick={handleSelect}
|
||||
clearSelected={clearSelected}
|
||||
toolbarSearchableKeys={searchableKeys}
|
||||
toolbarRelatedSearchableKeys={relatedSearchableKeys}
|
||||
toolbarSearchColumns={[
|
||||
@ -164,9 +168,7 @@ function ExecutionEnvironmentList() {
|
||||
{...props}
|
||||
showSelectAll
|
||||
isAllSelected={isAllSelected}
|
||||
onSelectAll={isSelected =>
|
||||
setSelected(isSelected ? [...executionEnvironments] : [])
|
||||
}
|
||||
onSelectAll={selectAll}
|
||||
qsConfig={QS_CONFIG}
|
||||
additionalControls={[
|
||||
...(canAdd
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import React, { useEffect, useCallback } from 'react';
|
||||
import { useHistory, useLocation, useRouteMatch } from 'react-router-dom';
|
||||
|
||||
import { t } from '@lingui/macro';
|
||||
@ -17,6 +17,7 @@ import PaginatedTable, {
|
||||
HeaderCell,
|
||||
} from '../../../components/PaginatedTable';
|
||||
import useRequest, { useDeleteItems } from '../../../util/useRequest';
|
||||
import useSelected from '../../../util/useSelected';
|
||||
import {
|
||||
encodeQueryString,
|
||||
getQSConfig,
|
||||
@ -36,7 +37,6 @@ function HostList() {
|
||||
const history = useHistory();
|
||||
const location = useLocation();
|
||||
const match = useRouteMatch();
|
||||
const [selected, setSelected] = useState([]);
|
||||
const parsedQueryStrings = parseQueryString(QS_CONFIG, location.search);
|
||||
const nonDefaultSearchParams = {};
|
||||
|
||||
@ -86,7 +86,14 @@ function HostList() {
|
||||
fetchHosts();
|
||||
}, [fetchHosts]);
|
||||
|
||||
const isAllSelected = selected.length === hosts.length && selected.length > 0;
|
||||
const {
|
||||
selected,
|
||||
isAllSelected,
|
||||
handleSelect,
|
||||
selectAll,
|
||||
clearSelected,
|
||||
} = useSelected(hosts);
|
||||
|
||||
const {
|
||||
isLoading: isDeleteLoading,
|
||||
deleteItems: deleteHosts,
|
||||
@ -105,19 +112,7 @@ function HostList() {
|
||||
|
||||
const handleHostDelete = async () => {
|
||||
await deleteHosts();
|
||||
setSelected([]);
|
||||
};
|
||||
|
||||
const handleSelectAll = isSelected => {
|
||||
setSelected(isSelected ? [...hosts] : []);
|
||||
};
|
||||
|
||||
const handleSelect = host => {
|
||||
if (selected.some(h => h.id === host.id)) {
|
||||
setSelected(selected.filter(h => h.id !== host.id));
|
||||
} else {
|
||||
setSelected(selected.concat(host));
|
||||
}
|
||||
clearSelected();
|
||||
};
|
||||
|
||||
const handleSmartInventoryClick = () => {
|
||||
@ -141,7 +136,7 @@ function HostList() {
|
||||
itemCount={count}
|
||||
pluralizedItemName={t`Hosts`}
|
||||
qsConfig={QS_CONFIG}
|
||||
onRowClick={handleSelect}
|
||||
clearSelected={clearSelected}
|
||||
toolbarSearchColumns={[
|
||||
{
|
||||
name: t`Name`,
|
||||
@ -175,7 +170,7 @@ function HostList() {
|
||||
{...props}
|
||||
showSelectAll
|
||||
isAllSelected={isAllSelected}
|
||||
onSelectAll={handleSelectAll}
|
||||
onSelectAll={selectAll}
|
||||
qsConfig={QS_CONFIG}
|
||||
additionalControls={[
|
||||
...(canAdd
|
||||
|
||||
@ -92,9 +92,13 @@ function InstanceGroupList() {
|
||||
fetchInstanceGroups();
|
||||
}, [fetchInstanceGroups]);
|
||||
|
||||
const { selected, isAllSelected, handleSelect, setSelected } = useSelected(
|
||||
instanceGroups
|
||||
);
|
||||
const {
|
||||
selected,
|
||||
isAllSelected,
|
||||
handleSelect,
|
||||
clearSelected,
|
||||
selectAll,
|
||||
} = useSelected(instanceGroups);
|
||||
|
||||
const modifiedSelected = modifyInstanceGroups(selected);
|
||||
|
||||
@ -118,7 +122,7 @@ function InstanceGroupList() {
|
||||
|
||||
const handleDelete = async () => {
|
||||
await deleteInstanceGroups();
|
||||
setSelected([]);
|
||||
clearSelected();
|
||||
};
|
||||
|
||||
const canAdd = actions && actions.POST;
|
||||
@ -201,7 +205,7 @@ function InstanceGroupList() {
|
||||
itemCount={instanceGroupsCount}
|
||||
pluralizedItemName={pluralizedItemName}
|
||||
qsConfig={QS_CONFIG}
|
||||
onRowClick={handleSelect}
|
||||
clearSelected={clearSelected}
|
||||
toolbarSearchableKeys={searchableKeys}
|
||||
toolbarRelatedSearchableKeys={relatedSearchableKeys}
|
||||
renderToolbar={props => (
|
||||
@ -209,9 +213,7 @@ function InstanceGroupList() {
|
||||
{...props}
|
||||
showSelectAll
|
||||
isAllSelected={isAllSelected}
|
||||
onSelectAll={isSelected =>
|
||||
setSelected(isSelected ? [...instanceGroups] : [])
|
||||
}
|
||||
onSelectAll={selectAll}
|
||||
qsConfig={QS_CONFIG}
|
||||
additionalControls={[
|
||||
...(canAdd ? [addButton] : []),
|
||||
|
||||
@ -77,9 +77,13 @@ function InventoryGroupsList() {
|
||||
fetchData();
|
||||
}, [fetchData]);
|
||||
|
||||
const { selected, isAllSelected, handleSelect, setSelected } = useSelected(
|
||||
groups
|
||||
);
|
||||
const {
|
||||
selected,
|
||||
isAllSelected,
|
||||
handleSelect,
|
||||
clearSelected,
|
||||
selectAll,
|
||||
} = useSelected(groups);
|
||||
|
||||
const renderTooltip = () => {
|
||||
const itemsUnableToDelete = selected
|
||||
@ -111,7 +115,7 @@ function InventoryGroupsList() {
|
||||
items={groups}
|
||||
itemCount={groupCount}
|
||||
qsConfig={QS_CONFIG}
|
||||
onRowClick={handleSelect}
|
||||
clearSelected={clearSelected}
|
||||
toolbarSearchColumns={[
|
||||
{
|
||||
name: t`Name`,
|
||||
@ -159,9 +163,7 @@ function InventoryGroupsList() {
|
||||
{...props}
|
||||
showSelectAll
|
||||
isAllSelected={isAllSelected}
|
||||
onSelectAll={isSelected =>
|
||||
setSelected(isSelected ? [...groups] : [])
|
||||
}
|
||||
onSelectAll={selectAll}
|
||||
qsConfig={QS_CONFIG}
|
||||
additionalControls={[
|
||||
...(canAdd
|
||||
@ -185,7 +187,7 @@ function InventoryGroupsList() {
|
||||
}
|
||||
onAfterDelete={() => {
|
||||
fetchData();
|
||||
setSelected([]);
|
||||
clearSelected();
|
||||
}}
|
||||
/>
|
||||
</Tooltip>,
|
||||
|
||||
@ -84,9 +84,13 @@ function InventoryHostGroupsList() {
|
||||
fetchGroups();
|
||||
}, [fetchGroups]);
|
||||
|
||||
const { selected, isAllSelected, handleSelect, setSelected } = useSelected(
|
||||
groups
|
||||
);
|
||||
const {
|
||||
selected,
|
||||
isAllSelected,
|
||||
handleSelect,
|
||||
clearSelected,
|
||||
selectAll,
|
||||
} = useSelected(groups);
|
||||
|
||||
const {
|
||||
isLoading: isDisassociateLoading,
|
||||
@ -107,7 +111,7 @@ function InventoryHostGroupsList() {
|
||||
|
||||
const handleDisassociate = async () => {
|
||||
await disassociateHosts();
|
||||
setSelected([]);
|
||||
clearSelected();
|
||||
};
|
||||
|
||||
const fetchGroupsToAssociate = useCallback(
|
||||
@ -156,7 +160,7 @@ function InventoryHostGroupsList() {
|
||||
items={groups}
|
||||
itemCount={itemCount}
|
||||
qsConfig={QS_CONFIG}
|
||||
onRowClick={handleSelect}
|
||||
clearSelected={clearSelected}
|
||||
toolbarSearchColumns={[
|
||||
{
|
||||
name: t`Name`,
|
||||
@ -195,9 +199,7 @@ function InventoryHostGroupsList() {
|
||||
{...props}
|
||||
showSelectAll
|
||||
isAllSelected={isAllSelected}
|
||||
onSelectAll={isSelected =>
|
||||
setSelected(isSelected ? [...groups] : [])
|
||||
}
|
||||
onSelectAll={selectAll}
|
||||
qsConfig={QS_CONFIG}
|
||||
additionalControls={[
|
||||
...(canAdd
|
||||
|
||||
@ -15,6 +15,7 @@ import {
|
||||
ToolbarAddButton,
|
||||
ToolbarDeleteButton,
|
||||
} from '../../../components/PaginatedDataList';
|
||||
import useSelected from '../../../util/useSelected';
|
||||
import AdHocCommands from '../../../components/AdHocCommands/AdHocCommands';
|
||||
import InventoryHostItem from './InventoryHostItem';
|
||||
|
||||
@ -25,7 +26,6 @@ const QS_CONFIG = getQSConfig('host', {
|
||||
});
|
||||
|
||||
function InventoryHostList() {
|
||||
const [selected, setSelected] = useState([]);
|
||||
const [isAdHocLaunchLoading, setIsAdHocLaunchLoading] = useState(false);
|
||||
const { id } = useParams();
|
||||
const { search } = useLocation();
|
||||
@ -74,17 +74,14 @@ function InventoryHostList() {
|
||||
fetchData();
|
||||
}, [fetchData]);
|
||||
|
||||
const handleSelectAll = isSelected => {
|
||||
setSelected(isSelected ? [...hosts] : []);
|
||||
};
|
||||
const {
|
||||
selected,
|
||||
isAllSelected,
|
||||
handleSelect,
|
||||
selectAll,
|
||||
clearSelected,
|
||||
} = useSelected(hosts);
|
||||
|
||||
const handleSelect = row => {
|
||||
if (selected.some(s => s.id === row.id)) {
|
||||
setSelected(selected.filter(s => s.id !== row.id));
|
||||
} else {
|
||||
setSelected(selected.concat(row));
|
||||
}
|
||||
};
|
||||
const {
|
||||
isLoading: isDeleteLoading,
|
||||
deleteItems: deleteHosts,
|
||||
@ -99,12 +96,11 @@ function InventoryHostList() {
|
||||
|
||||
const handleDeleteHosts = async () => {
|
||||
await deleteHosts();
|
||||
setSelected([]);
|
||||
clearSelected();
|
||||
};
|
||||
|
||||
const canAdd =
|
||||
actions && Object.prototype.hasOwnProperty.call(actions, 'POST');
|
||||
const isAllSelected = selected.length > 0 && selected.length === hosts.length;
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -115,6 +111,7 @@ function InventoryHostList() {
|
||||
itemCount={hostCount}
|
||||
pluralizedItemName={t`Hosts`}
|
||||
qsConfig={QS_CONFIG}
|
||||
clearSelected={clearSelected}
|
||||
toolbarSearchableKeys={searchableKeys}
|
||||
toolbarRelatedSearchableKeys={relatedSearchableKeys}
|
||||
headerRow={
|
||||
@ -128,7 +125,7 @@ function InventoryHostList() {
|
||||
{...props}
|
||||
showSelectAll
|
||||
isAllSelected={isAllSelected}
|
||||
onSelectAll={handleSelectAll}
|
||||
onSelectAll={selectAll}
|
||||
qsConfig={QS_CONFIG}
|
||||
additionalControls={[
|
||||
...(canAdd
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
import React, { useState, useCallback, useEffect } from 'react';
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import { useLocation, useRouteMatch, Link } from 'react-router-dom';
|
||||
import { t, Plural } from '@lingui/macro';
|
||||
import { Card, PageSection, DropdownItem } from '@patternfly/react-core';
|
||||
import { InventoriesAPI } from '../../../api';
|
||||
import useRequest, { useDeleteItems } from '../../../util/useRequest';
|
||||
import useSelected from '../../../util/useSelected';
|
||||
import AlertModal from '../../../components/AlertModal';
|
||||
import DatalistToolbar from '../../../components/DataListToolbar';
|
||||
import ErrorDetail from '../../../components/ErrorDetail';
|
||||
@ -27,7 +28,6 @@ const QS_CONFIG = getQSConfig('inventory', {
|
||||
function InventoryList() {
|
||||
const location = useLocation();
|
||||
const match = useRouteMatch();
|
||||
const [selected, setSelected] = useState([]);
|
||||
|
||||
const {
|
||||
result: {
|
||||
@ -89,8 +89,14 @@ function InventoryList() {
|
||||
QS_CONFIG
|
||||
);
|
||||
|
||||
const isAllSelected =
|
||||
selected.length === inventories.length && selected.length > 0;
|
||||
const {
|
||||
selected,
|
||||
isAllSelected,
|
||||
handleSelect,
|
||||
selectAll,
|
||||
clearSelected,
|
||||
} = useSelected(inventories);
|
||||
|
||||
const {
|
||||
isLoading: isDeleteLoading,
|
||||
deleteItems: deleteInventories,
|
||||
@ -107,26 +113,12 @@ function InventoryList() {
|
||||
|
||||
const handleInventoryDelete = async () => {
|
||||
await deleteInventories();
|
||||
setSelected([]);
|
||||
clearSelected();
|
||||
};
|
||||
|
||||
const hasContentLoading = isDeleteLoading || isLoading;
|
||||
const canAdd = actions && actions.POST;
|
||||
|
||||
const handleSelectAll = isSelected => {
|
||||
setSelected(isSelected ? [...inventories] : []);
|
||||
};
|
||||
|
||||
const handleSelect = row => {
|
||||
if (!row.pending_deletion) {
|
||||
if (selected.some(s => s.id === row.id)) {
|
||||
setSelected(selected.filter(s => s.id !== row.id));
|
||||
} else {
|
||||
setSelected(selected.concat(row));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const deleteDetailsRequests = relatedResourceDeleteRequests.inventory(
|
||||
selected[0]
|
||||
);
|
||||
@ -197,6 +189,7 @@ function InventoryList() {
|
||||
]}
|
||||
toolbarSearchableKeys={searchableKeys}
|
||||
toolbarRelatedSearchableKeys={relatedSearchableKeys}
|
||||
clearSelected={clearSelected}
|
||||
headerRow={
|
||||
<HeaderRow qsConfig={QS_CONFIG}>
|
||||
<HeaderCell sortKey="name">{t`Name`}</HeaderCell>
|
||||
@ -211,7 +204,7 @@ function InventoryList() {
|
||||
{...props}
|
||||
showSelectAll
|
||||
isAllSelected={isAllSelected}
|
||||
onSelectAll={handleSelectAll}
|
||||
onSelectAll={selectAll}
|
||||
qsConfig={QS_CONFIG}
|
||||
additionalControls={[
|
||||
...(canAdd ? [addButton] : []),
|
||||
@ -251,7 +244,11 @@ function InventoryList() {
|
||||
? `${match.url}/smart_inventory/${inventory.id}/details`
|
||||
: `${match.url}/inventory/${inventory.id}/details`
|
||||
}
|
||||
onSelect={() => handleSelect(inventory)}
|
||||
onSelect={() => {
|
||||
if (!inventory.pending_deletion) {
|
||||
handleSelect(inventory);
|
||||
}
|
||||
}}
|
||||
isSelected={selected.some(row => row.id === inventory.id)}
|
||||
/>
|
||||
)}
|
||||
|
||||
@ -83,9 +83,13 @@ function InventorySourceList() {
|
||||
fetchSources();
|
||||
}, [fetchSources]);
|
||||
|
||||
const { selected, isAllSelected, handleSelect, setSelected } = useSelected(
|
||||
sources
|
||||
);
|
||||
const {
|
||||
selected,
|
||||
isAllSelected,
|
||||
handleSelect,
|
||||
clearSelected,
|
||||
selectAll,
|
||||
} = useSelected(sources);
|
||||
|
||||
const {
|
||||
isLoading: isDeleteLoading,
|
||||
@ -140,7 +144,7 @@ function InventorySourceList() {
|
||||
if (!deleteRelatedResourcesError) {
|
||||
await handleDeleteSources();
|
||||
}
|
||||
setSelected([]);
|
||||
clearSelected();
|
||||
};
|
||||
const canAdd =
|
||||
sourceChoicesOptions &&
|
||||
@ -164,14 +168,13 @@ function InventorySourceList() {
|
||||
itemCount={sourceCount}
|
||||
pluralizedItemName={t`Inventory Sources`}
|
||||
qsConfig={QS_CONFIG}
|
||||
clearSelected={clearSelected}
|
||||
renderToolbar={props => (
|
||||
<DatalistToolbar
|
||||
{...props}
|
||||
showSelectAll
|
||||
isAllSelected={isAllSelected}
|
||||
onSelectAll={isSelected =>
|
||||
setSelected(isSelected ? [...sources] : [])
|
||||
}
|
||||
onSelectAll={selectAll}
|
||||
qsConfig={QS_CONFIG}
|
||||
additionalControls={[
|
||||
...(canAdd
|
||||
|
||||
@ -306,7 +306,7 @@ function NotificationTemplateDetail({ template, defaultMessages }) {
|
||||
label={t`HTTP Headers`}
|
||||
value={JSON.stringify(configuration.headers)}
|
||||
mode="json"
|
||||
rows="6"
|
||||
rows={6}
|
||||
dataCy="nt-detail-webhook-headers"
|
||||
/>
|
||||
</>
|
||||
|
||||
@ -82,9 +82,13 @@ function NotificationTemplatesList() {
|
||||
fetchTemplates();
|
||||
}, [fetchTemplates]);
|
||||
|
||||
const { selected, isAllSelected, handleSelect, setSelected } = useSelected(
|
||||
templates
|
||||
);
|
||||
const {
|
||||
selected,
|
||||
isAllSelected,
|
||||
handleSelect,
|
||||
clearSelected,
|
||||
selectAll,
|
||||
} = useSelected(templates);
|
||||
|
||||
const {
|
||||
isLoading: isDeleteLoading,
|
||||
@ -106,7 +110,7 @@ function NotificationTemplatesList() {
|
||||
|
||||
const handleDelete = async () => {
|
||||
await deleteTemplates();
|
||||
setSelected([]);
|
||||
clearSelected();
|
||||
};
|
||||
|
||||
const addTestToast = useCallback(notification => {
|
||||
@ -133,7 +137,7 @@ function NotificationTemplatesList() {
|
||||
itemCount={count}
|
||||
pluralizedItemName={t`Notification Templates`}
|
||||
qsConfig={QS_CONFIG}
|
||||
onRowClick={handleSelect}
|
||||
clearSelected={clearSelected}
|
||||
toolbarSearchColumns={[
|
||||
{
|
||||
name: t`Name`,
|
||||
@ -176,7 +180,7 @@ function NotificationTemplatesList() {
|
||||
{...props}
|
||||
showSelectAll
|
||||
isAllSelected={isAllSelected}
|
||||
onSelectAll={set => setSelected(set ? [...templates] : [])}
|
||||
onSelectAll={selectAll}
|
||||
qsConfig={QS_CONFIG}
|
||||
additionalControls={[
|
||||
...(canAdd
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import React, { useEffect, useCallback } from 'react';
|
||||
import { useLocation, useRouteMatch } from 'react-router-dom';
|
||||
import { t, Plural } from '@lingui/macro';
|
||||
import { Card, PageSection } from '@patternfly/react-core';
|
||||
@ -17,6 +17,7 @@ import PaginatedTable, {
|
||||
HeaderCell,
|
||||
} from '../../../components/PaginatedTable';
|
||||
import { getQSConfig, parseQueryString } from '../../../util/qs';
|
||||
import useSelected from '../../../util/useSelected';
|
||||
import OrganizationListItem from './OrganizationListItem';
|
||||
import { relatedResourceDeleteRequests } from '../../../util/getRelatedResourceDeleteDetails';
|
||||
|
||||
@ -30,8 +31,6 @@ function OrganizationsList() {
|
||||
const location = useLocation();
|
||||
const match = useRouteMatch();
|
||||
|
||||
const [selected, setSelected] = useState([]);
|
||||
|
||||
const addUrl = `${match.url}/add`;
|
||||
|
||||
const {
|
||||
@ -77,8 +76,14 @@ function OrganizationsList() {
|
||||
fetchOrganizations();
|
||||
}, [fetchOrganizations]);
|
||||
|
||||
const isAllSelected =
|
||||
selected.length === organizations.length && selected.length > 0;
|
||||
const {
|
||||
selected,
|
||||
isAllSelected,
|
||||
handleSelect,
|
||||
selectAll,
|
||||
clearSelected,
|
||||
} = useSelected(organizations);
|
||||
|
||||
const {
|
||||
isLoading: isDeleteLoading,
|
||||
deleteItems: deleteOrganizations,
|
||||
@ -99,23 +104,12 @@ function OrganizationsList() {
|
||||
|
||||
const handleOrgDelete = async () => {
|
||||
await deleteOrganizations();
|
||||
setSelected([]);
|
||||
clearSelected();
|
||||
};
|
||||
|
||||
const hasContentLoading = isDeleteLoading || isOrgsLoading;
|
||||
const canAdd = actions && actions.POST;
|
||||
|
||||
const handleSelectAll = isSelected => {
|
||||
setSelected(isSelected ? [...organizations] : []);
|
||||
};
|
||||
|
||||
const handleSelect = row => {
|
||||
if (selected.some(s => s.id === row.id)) {
|
||||
setSelected(selected.filter(s => s.id !== row.id));
|
||||
} else {
|
||||
setSelected(selected.concat(row));
|
||||
}
|
||||
};
|
||||
const deleteDetailsRequests = relatedResourceDeleteRequests.organization(
|
||||
selected[0]
|
||||
);
|
||||
@ -131,6 +125,7 @@ function OrganizationsList() {
|
||||
itemCount={organizationCount}
|
||||
pluralizedItemName={t`Organizations`}
|
||||
qsConfig={QS_CONFIG}
|
||||
clearSelected={clearSelected}
|
||||
toolbarSearchColumns={[
|
||||
{
|
||||
name: t`Name`,
|
||||
@ -165,7 +160,7 @@ function OrganizationsList() {
|
||||
{...props}
|
||||
showSelectAll
|
||||
isAllSelected={isAllSelected}
|
||||
onSelectAll={handleSelectAll}
|
||||
onSelectAll={selectAll}
|
||||
qsConfig={QS_CONFIG}
|
||||
additionalControls={[
|
||||
...(canAdd
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React, { Fragment, useState, useEffect, useCallback } from 'react';
|
||||
import React, { useEffect, useCallback } from 'react';
|
||||
import { useLocation, useRouteMatch } from 'react-router-dom';
|
||||
import { t, Plural } from '@lingui/macro';
|
||||
import { Card, PageSection } from '@patternfly/react-core';
|
||||
@ -17,6 +17,7 @@ import PaginatedTable, {
|
||||
HeaderCell,
|
||||
} from '../../../components/PaginatedTable';
|
||||
import useWsProjects from './useWsProjects';
|
||||
import useSelected from '../../../util/useSelected';
|
||||
import { relatedResourceDeleteRequests } from '../../../util/getRelatedResourceDeleteDetails';
|
||||
import { getQSConfig, parseQueryString } from '../../../util/qs';
|
||||
|
||||
@ -31,7 +32,6 @@ const QS_CONFIG = getQSConfig('project', {
|
||||
function ProjectList() {
|
||||
const location = useLocation();
|
||||
const match = useRouteMatch();
|
||||
const [selected, setSelected] = useState([]);
|
||||
|
||||
const {
|
||||
result: {
|
||||
@ -78,8 +78,15 @@ function ProjectList() {
|
||||
|
||||
const projects = useWsProjects(results);
|
||||
|
||||
const isAllSelected =
|
||||
selected.length === projects.length && selected.length > 0;
|
||||
const {
|
||||
selected,
|
||||
isAllSelected,
|
||||
handleSelect,
|
||||
setSelected,
|
||||
selectAll,
|
||||
clearSelected,
|
||||
} = useSelected(projects);
|
||||
|
||||
const {
|
||||
isLoading: isDeleteLoading,
|
||||
deleteItems: deleteProjects,
|
||||
@ -104,24 +111,12 @@ function ProjectList() {
|
||||
const hasContentLoading = isDeleteLoading || isLoading;
|
||||
const canAdd = actions && actions.POST;
|
||||
|
||||
const handleSelectAll = isSelected => {
|
||||
setSelected(isSelected ? [...projects] : []);
|
||||
};
|
||||
|
||||
const handleSelect = row => {
|
||||
if (selected.some(s => s.id === row.id)) {
|
||||
setSelected(selected.filter(s => s.id !== row.id));
|
||||
} else {
|
||||
setSelected(selected.concat(row));
|
||||
}
|
||||
};
|
||||
|
||||
const deleteDetailsRequests = relatedResourceDeleteRequests.project(
|
||||
selected[0]
|
||||
);
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<>
|
||||
<PageSection>
|
||||
<Card>
|
||||
<PaginatedTable
|
||||
@ -131,7 +126,7 @@ function ProjectList() {
|
||||
itemCount={itemCount}
|
||||
pluralizedItemName={t`Projects`}
|
||||
qsConfig={QS_CONFIG}
|
||||
onRowClick={handleSelect}
|
||||
clearSelected={clearSelected}
|
||||
toolbarSearchColumns={[
|
||||
{
|
||||
name: t`Name`,
|
||||
@ -182,7 +177,7 @@ function ProjectList() {
|
||||
{...props}
|
||||
showSelectAll
|
||||
isAllSelected={isAllSelected}
|
||||
onSelectAll={handleSelectAll}
|
||||
onSelectAll={selectAll}
|
||||
qsConfig={QS_CONFIG}
|
||||
additionalControls={[
|
||||
...(canAdd
|
||||
@ -239,7 +234,7 @@ function ProjectList() {
|
||||
{t`Failed to delete one or more projects.`}
|
||||
<ErrorDetail error={deletionError} />
|
||||
</AlertModal>
|
||||
</Fragment>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -52,7 +52,7 @@ function SubscriptionModal({
|
||||
[]
|
||||
);
|
||||
|
||||
const { selected, handleSelect } = useSelected(subscriptions);
|
||||
const { selected, setSelected } = useSelected(subscriptions);
|
||||
|
||||
function handleConfirm() {
|
||||
const [subscription] = selected;
|
||||
@ -64,6 +64,14 @@ function SubscriptionModal({
|
||||
fetchSubscriptions();
|
||||
}, [fetchSubscriptions]);
|
||||
|
||||
const handleSelect = item => {
|
||||
if (selected.some(s => s.pool_id === item.pool_id)) {
|
||||
setSelected(selected.filter(s => s.pool_id !== item.pool_id));
|
||||
} else {
|
||||
setSelected(selected.concat(item));
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedSubscription?.pool_id) {
|
||||
handleSelect({ pool_id: selectedSubscription.pool_id });
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React, { Fragment, useState, useEffect, useCallback } from 'react';
|
||||
import React, { useEffect, useCallback } from 'react';
|
||||
import { useLocation, useRouteMatch } from 'react-router-dom';
|
||||
|
||||
import { t } from '@lingui/macro';
|
||||
@ -17,6 +17,7 @@ import {
|
||||
ToolbarAddButton,
|
||||
ToolbarDeleteButton,
|
||||
} from '../../../components/PaginatedDataList';
|
||||
import useSelected from '../../../util/useSelected';
|
||||
import { getQSConfig, parseQueryString } from '../../../util/qs';
|
||||
|
||||
import TeamListItem from './TeamListItem';
|
||||
@ -30,7 +31,6 @@ const QS_CONFIG = getQSConfig('team', {
|
||||
function TeamList() {
|
||||
const location = useLocation();
|
||||
const match = useRouteMatch();
|
||||
const [selected, setSelected] = useState([]);
|
||||
|
||||
const {
|
||||
result: {
|
||||
@ -75,7 +75,14 @@ function TeamList() {
|
||||
fetchTeams();
|
||||
}, [fetchTeams]);
|
||||
|
||||
const isAllSelected = selected.length === teams.length && selected.length > 0;
|
||||
const {
|
||||
selected,
|
||||
isAllSelected,
|
||||
handleSelect,
|
||||
selectAll,
|
||||
clearSelected,
|
||||
} = useSelected(teams);
|
||||
|
||||
const {
|
||||
isLoading: isDeleteLoading,
|
||||
deleteItems: deleteTeams,
|
||||
@ -94,26 +101,14 @@ function TeamList() {
|
||||
|
||||
const handleTeamDelete = async () => {
|
||||
await deleteTeams();
|
||||
setSelected([]);
|
||||
clearSelected();
|
||||
};
|
||||
|
||||
const hasContentLoading = isDeleteLoading || isLoading;
|
||||
const canAdd = actions && actions.POST;
|
||||
|
||||
const handleSelectAll = isSelected => {
|
||||
setSelected(isSelected ? [...teams] : []);
|
||||
};
|
||||
|
||||
const handleSelect = row => {
|
||||
if (selected.some(s => s.id === row.id)) {
|
||||
setSelected(selected.filter(s => s.id !== row.id));
|
||||
} else {
|
||||
setSelected(selected.concat(row));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<>
|
||||
<PageSection>
|
||||
<Card>
|
||||
<PaginatedTable
|
||||
@ -123,7 +118,7 @@ function TeamList() {
|
||||
itemCount={itemCount}
|
||||
pluralizedItemName={t`Teams`}
|
||||
qsConfig={QS_CONFIG}
|
||||
onRowClick={handleSelect}
|
||||
clearSelected={clearSelected}
|
||||
toolbarSearchColumns={[
|
||||
{
|
||||
name: t`Name`,
|
||||
@ -161,7 +156,7 @@ function TeamList() {
|
||||
{...props}
|
||||
showSelectAll
|
||||
isAllSelected={isAllSelected}
|
||||
onSelectAll={handleSelectAll}
|
||||
onSelectAll={selectAll}
|
||||
qsConfig={QS_CONFIG}
|
||||
additionalControls={[
|
||||
...(canAdd
|
||||
@ -208,7 +203,7 @@ function TeamList() {
|
||||
{t`Failed to delete one or more teams.`}
|
||||
<ErrorDetail error={deletionError} />
|
||||
</AlertModal>
|
||||
</Fragment>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -19,6 +19,7 @@ import { ToolbarAddButton } from '../../../components/PaginatedDataList';
|
||||
import SurveyListItem from './SurveyListItem';
|
||||
import SurveyToolbar from './SurveyToolbar';
|
||||
import SurveyPreviewModal from './SurveyPreviewModal';
|
||||
import useSelected from '../../../util/useSelected';
|
||||
|
||||
const Button = styled(_Button)`
|
||||
margin: 20px;
|
||||
@ -36,15 +37,16 @@ function SurveyList({
|
||||
const match = useRouteMatch();
|
||||
|
||||
const questions = survey?.spec || [];
|
||||
const [selected, setSelected] = useState([]);
|
||||
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
||||
const [isPreviewModalOpen, setIsPreviewModalOpen] = useState(false);
|
||||
const isAllSelected =
|
||||
selected.length === questions?.length && selected.length > 0;
|
||||
|
||||
const handleSelectAll = isSelected => {
|
||||
setSelected(isSelected ? [...questions] : []);
|
||||
};
|
||||
const {
|
||||
selected,
|
||||
isAllSelected,
|
||||
setSelected,
|
||||
selectAll,
|
||||
clearSelected,
|
||||
} = useSelected(questions);
|
||||
|
||||
const handleSelect = item => {
|
||||
if (selected.some(q => q.variable === item.variable)) {
|
||||
@ -61,7 +63,7 @@ function SurveyList({
|
||||
await updateSurvey(questions.filter(q => !selected.includes(q)));
|
||||
}
|
||||
setIsDeleteModalOpen(false);
|
||||
setSelected([]);
|
||||
clearSelected();
|
||||
};
|
||||
|
||||
const moveUp = question => {
|
||||
@ -91,7 +93,7 @@ function SurveyList({
|
||||
isOpen={isDeleteModalOpen}
|
||||
onClose={() => {
|
||||
setIsDeleteModalOpen(false);
|
||||
setSelected([]);
|
||||
clearSelected();
|
||||
}}
|
||||
actions={[
|
||||
<Button
|
||||
@ -110,7 +112,7 @@ function SurveyList({
|
||||
aria-label={t`cancel delete`}
|
||||
onClick={() => {
|
||||
setIsDeleteModalOpen(false);
|
||||
setSelected([]);
|
||||
clearSelected();
|
||||
}}
|
||||
>
|
||||
{t`Cancel`}
|
||||
@ -181,7 +183,7 @@ function SurveyList({
|
||||
<>
|
||||
<SurveyToolbar
|
||||
isAllSelected={isAllSelected}
|
||||
onSelectAll={handleSelectAll}
|
||||
onSelectAll={selectAll}
|
||||
surveyEnabled={surveyEnabled}
|
||||
onToggleSurvey={toggleSurvey}
|
||||
isDeleteDisabled={selected?.length === 0}
|
||||
|
||||
@ -73,9 +73,13 @@ function UserList() {
|
||||
fetchUsers();
|
||||
}, [fetchUsers]);
|
||||
|
||||
const { selected, isAllSelected, handleSelect, setSelected } = useSelected(
|
||||
users
|
||||
);
|
||||
const {
|
||||
selected,
|
||||
isAllSelected,
|
||||
handleSelect,
|
||||
clearSelected,
|
||||
selectAll,
|
||||
} = useSelected(users);
|
||||
|
||||
const {
|
||||
isLoading: isDeleteLoading,
|
||||
@ -95,7 +99,7 @@ function UserList() {
|
||||
|
||||
const handleUserDelete = async () => {
|
||||
await deleteUsers();
|
||||
setSelected([]);
|
||||
clearSelected();
|
||||
};
|
||||
|
||||
const hasContentLoading = isDeleteLoading || isLoading;
|
||||
@ -112,7 +116,7 @@ function UserList() {
|
||||
itemCount={itemCount}
|
||||
pluralizedItemName={t`Users`}
|
||||
qsConfig={QS_CONFIG}
|
||||
onRowClick={handleSelect}
|
||||
clearSelected={clearSelected}
|
||||
toolbarSearchColumns={[
|
||||
{
|
||||
name: t`Username`,
|
||||
@ -135,9 +139,7 @@ function UserList() {
|
||||
{...props}
|
||||
showSelectAll
|
||||
isAllSelected={isAllSelected}
|
||||
onSelectAll={isSelected =>
|
||||
setSelected(isSelected ? [...users] : [])
|
||||
}
|
||||
onSelectAll={selectAll}
|
||||
qsConfig={QS_CONFIG}
|
||||
additionalControls={[
|
||||
...(canAdd
|
||||
|
||||
@ -85,9 +85,13 @@ function UserTeamList() {
|
||||
fetchTeams();
|
||||
}, [fetchTeams]);
|
||||
|
||||
const { selected, isAllSelected, handleSelect, setSelected } = useSelected(
|
||||
teams
|
||||
);
|
||||
const {
|
||||
selected,
|
||||
isAllSelected,
|
||||
handleSelect,
|
||||
clearSelected,
|
||||
selectAll,
|
||||
} = useSelected(teams);
|
||||
|
||||
const disassociateUserRoles = team => {
|
||||
return [
|
||||
@ -141,7 +145,7 @@ function UserTeamList() {
|
||||
|
||||
const handleDisassociate = async () => {
|
||||
await disassociateTeams();
|
||||
setSelected([]);
|
||||
clearSelected();
|
||||
};
|
||||
|
||||
const { error, dismissError } = useDismissableError(
|
||||
@ -176,7 +180,7 @@ function UserTeamList() {
|
||||
itemCount={count}
|
||||
pluralizedItemName={t`Teams`}
|
||||
qsConfig={QS_CONFIG}
|
||||
onRowClick={handleSelect}
|
||||
clearSelected={clearSelected}
|
||||
headerRow={
|
||||
<HeaderRow qsConfig={QS_CONFIG}>
|
||||
<HeaderCell sortKey="name">{t`Name`}</HeaderCell>
|
||||
@ -200,9 +204,7 @@ function UserTeamList() {
|
||||
{...props}
|
||||
showSelectAll
|
||||
isAllSelected={isAllSelected}
|
||||
onSelectAll={isSelected =>
|
||||
setSelected(isSelected ? [...teams] : [])
|
||||
}
|
||||
onSelectAll={selectAll}
|
||||
qsConfig={QS_CONFIG}
|
||||
additionalControls={[
|
||||
...(canAdd
|
||||
|
||||
@ -84,9 +84,13 @@ function WorkflowApprovalsList() {
|
||||
QS_CONFIG
|
||||
);
|
||||
|
||||
const { selected, isAllSelected, handleSelect, setSelected } = useSelected(
|
||||
workflowApprovals
|
||||
);
|
||||
const {
|
||||
selected,
|
||||
isAllSelected,
|
||||
handleSelect,
|
||||
clearSelected,
|
||||
selectAll,
|
||||
} = useSelected(workflowApprovals);
|
||||
|
||||
const {
|
||||
isLoading: isDeleteLoading,
|
||||
@ -108,7 +112,7 @@ function WorkflowApprovalsList() {
|
||||
|
||||
const handleDelete = async () => {
|
||||
await deleteWorkflowApprovals();
|
||||
setSelected([]);
|
||||
clearSelected();
|
||||
};
|
||||
|
||||
const {
|
||||
@ -126,7 +130,7 @@ function WorkflowApprovalsList() {
|
||||
|
||||
const handleApprove = async () => {
|
||||
await approveWorkflowApprovals();
|
||||
setSelected([]);
|
||||
clearSelected();
|
||||
};
|
||||
|
||||
const {
|
||||
@ -144,7 +148,7 @@ function WorkflowApprovalsList() {
|
||||
|
||||
const handleDeny = async () => {
|
||||
await denyWorkflowApprovals();
|
||||
setSelected([]);
|
||||
clearSelected();
|
||||
};
|
||||
|
||||
const {
|
||||
@ -168,7 +172,7 @@ function WorkflowApprovalsList() {
|
||||
itemCount={count}
|
||||
pluralizedItemName={t`Workflow Approvals`}
|
||||
qsConfig={QS_CONFIG}
|
||||
onRowClick={handleSelect}
|
||||
clearSelected={clearSelected}
|
||||
toolbarSearchColumns={[
|
||||
{
|
||||
name: t`Name`,
|
||||
@ -187,9 +191,7 @@ function WorkflowApprovalsList() {
|
||||
{...props}
|
||||
showSelectAll
|
||||
isAllSelected={isAllSelected}
|
||||
onSelectAll={set =>
|
||||
setSelected(set ? [...workflowApprovals] : [])
|
||||
}
|
||||
onSelectAll={selectAll}
|
||||
qsConfig={QS_CONFIG}
|
||||
additionalControls={[
|
||||
<WorkflowApprovalListApproveButton
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { useState } from 'react';
|
||||
import { useState, useCallback } from 'react';
|
||||
|
||||
/**
|
||||
* useSelected hook provides a way to read and update a selected list
|
||||
@ -8,6 +8,7 @@ import { useState } from 'react';
|
||||
* isAllSelected: boolean that indicates if all items are selected
|
||||
* handleSelect: function that adds and removes items from selected list
|
||||
* setSelected: setter function
|
||||
* clearSelected: de-select all items
|
||||
* }
|
||||
*/
|
||||
|
||||
@ -16,6 +17,9 @@ export default function useSelected(list = []) {
|
||||
const isAllSelected = selected.length > 0 && selected.length === list.length;
|
||||
|
||||
const handleSelect = row => {
|
||||
if (!row.id) {
|
||||
throw new Error(`Selected row does not have an id`);
|
||||
}
|
||||
if (selected.some(s => s.id === row.id)) {
|
||||
setSelected(prevState => [...prevState.filter(i => i.id !== row.id)]);
|
||||
} else {
|
||||
@ -23,5 +27,23 @@ export default function useSelected(list = []) {
|
||||
}
|
||||
};
|
||||
|
||||
return { selected, isAllSelected, handleSelect, setSelected };
|
||||
const selectAll = useCallback(
|
||||
isSelected => {
|
||||
setSelected(isSelected ? [...list] : []);
|
||||
},
|
||||
[list]
|
||||
);
|
||||
|
||||
const clearSelected = useCallback(() => {
|
||||
setSelected([]);
|
||||
}, []);
|
||||
|
||||
return {
|
||||
selected,
|
||||
isAllSelected,
|
||||
handleSelect,
|
||||
setSelected,
|
||||
selectAll,
|
||||
clearSelected,
|
||||
};
|
||||
}
|
||||
|
||||
@ -19,6 +19,8 @@ describe('useSelected hook', () => {
|
||||
let isAllSelected;
|
||||
let handleSelect;
|
||||
let setSelected;
|
||||
let selectAll;
|
||||
let clearSelected;
|
||||
|
||||
test('should return expected initial values', () => {
|
||||
testHook(() => {
|
||||
@ -72,4 +74,51 @@ describe('useSelected hook', () => {
|
||||
expect(selected).toEqual([]);
|
||||
expect(isAllSelected).toEqual(false);
|
||||
});
|
||||
|
||||
test('should return selectAll', () => {
|
||||
testHook(() => {
|
||||
({
|
||||
selected,
|
||||
isAllSelected,
|
||||
handleSelect,
|
||||
setSelected,
|
||||
selectAll,
|
||||
} = useSelected(array));
|
||||
});
|
||||
|
||||
act(() => {
|
||||
selectAll(true);
|
||||
});
|
||||
expect(isAllSelected).toEqual(true);
|
||||
expect(selected).toEqual(array);
|
||||
|
||||
act(() => {
|
||||
selectAll(false);
|
||||
});
|
||||
expect(isAllSelected).toEqual(false);
|
||||
expect(selected).toEqual([]);
|
||||
});
|
||||
|
||||
test('should return clearSelected', () => {
|
||||
testHook(() => {
|
||||
({
|
||||
selected,
|
||||
isAllSelected,
|
||||
handleSelect,
|
||||
setSelected,
|
||||
selectAll,
|
||||
clearSelected,
|
||||
} = useSelected(array));
|
||||
});
|
||||
|
||||
act(() => {
|
||||
selectAll(true);
|
||||
});
|
||||
|
||||
act(() => {
|
||||
clearSelected();
|
||||
});
|
||||
expect(isAllSelected).toEqual(false);
|
||||
expect(selected).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user