clear list selection on navigate (first batch)

This commit is contained in:
Keith J. Grant
2021-05-25 16:48:38 -07:00
parent fef24355ab
commit 143a4a61b3
15 changed files with 182 additions and 194 deletions

View File

@@ -37,7 +37,6 @@ function DataListToolbar({
onExpand, onExpand,
onSelectAll, onSelectAll,
additionalControls, additionalControls,
qsConfig, qsConfig,
pagination, pagination,
}) { }) {

View File

@@ -1,4 +1,4 @@
import React, { useState, useEffect, useCallback } from 'react'; import React, { useEffect, useCallback } from 'react';
import { useLocation } from 'react-router-dom'; import { useLocation } from 'react-router-dom';
import { t, Plural } from '@lingui/macro'; import { t, Plural } from '@lingui/macro';
@@ -13,7 +13,7 @@ import useRequest, {
useDismissableError, useDismissableError,
} from '../../util/useRequest'; } from '../../util/useRequest';
import { useConfig } from '../../contexts/Config'; import { useConfig } from '../../contexts/Config';
import useSelected from '../../util/useSelected';
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';
@@ -35,8 +35,6 @@ function JobList({ defaultParams, showTypeColumn = false }) {
); );
const { me } = useConfig(); const { me } = useConfig();
const [selected, setSelected] = useState([]);
const location = useLocation(); const location = useLocation();
const { const {
result: { results, count, relatedSearchableKeys, searchableKeys }, result: { results, count, relatedSearchableKeys, searchableKeys },
@@ -88,7 +86,14 @@ function JobList({ defaultParams, showTypeColumn = false }) {
const jobs = useWsJobs(results, fetchJobsById, qsConfig); const jobs = useWsJobs(results, fetchJobsById, qsConfig);
const isAllSelected = selected.length === jobs.length && selected.length > 0; const {
selected,
isAllSelected,
handleSelect,
setSelected,
selectAll,
clearSelected,
} = useSelected(jobs);
const { const {
error: cancelJobsError, error: cancelJobsError,
@@ -143,18 +148,6 @@ function JobList({ defaultParams, showTypeColumn = false }) {
setSelected([]); 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));
}
};
const cannotDeleteItems = selected.filter(job => isJobRunning(job.status)); const cannotDeleteItems = selected.filter(job => isJobRunning(job.status));
return ( return (
@@ -226,6 +219,7 @@ function JobList({ defaultParams, showTypeColumn = false }) {
<HeaderCell>{t`Actions`}</HeaderCell> <HeaderCell>{t`Actions`}</HeaderCell>
</HeaderRow> </HeaderRow>
} }
clearSelected={clearSelected}
toolbarSearchableKeys={searchableKeys} toolbarSearchableKeys={searchableKeys}
toolbarRelatedSearchableKeys={relatedSearchableKeys} toolbarRelatedSearchableKeys={relatedSearchableKeys}
renderToolbar={props => ( renderToolbar={props => (
@@ -233,7 +227,7 @@ function JobList({ defaultParams, showTypeColumn = false }) {
{...props} {...props}
showSelectAll showSelectAll
isAllSelected={isAllSelected} isAllSelected={isAllSelected}
onSelectAll={handleSelectAll} onSelectAll={selectAll}
qsConfig={qsConfig} qsConfig={qsConfig}
additionalControls={[ additionalControls={[
<ToolbarDeleteButton <ToolbarDeleteButton

View File

@@ -1,5 +1,5 @@
import 'styled-components/macro'; import 'styled-components/macro';
import React, { Fragment } from 'react'; import React, { useEffect } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { TableComposable, Tbody } from '@patternfly/react-table'; import { TableComposable, Tbody } from '@patternfly/react-table';
@@ -33,10 +33,16 @@ function PaginatedTable({
showPageSizeOptions, showPageSizeOptions,
renderToolbar, renderToolbar,
emptyContentMessage, emptyContentMessage,
clearSelected,
ouiaId, ouiaId,
}) { }) {
const { search, pathname } = useLocation(); const { search, pathname } = useLocation();
const history = useHistory(); const history = useHistory();
const location = useLocation();
useEffect(() => {
clearSelected();
}, [location.search, clearSelected]);
const pushHistoryState = qs => { const pushHistoryState = qs => {
history.push(qs ? `${pathname}?${qs}` : pathname); history.push(qs ? `${pathname}?${qs}` : pathname);
@@ -127,7 +133,7 @@ function PaginatedTable({
); );
return ( return (
<Fragment> <>
<ListHeader <ListHeader
itemCount={itemCount} itemCount={itemCount}
renderToolbar={renderToolbar} renderToolbar={renderToolbar}
@@ -159,7 +165,7 @@ function PaginatedTable({
onPerPageSelect={handleSetPageSize} onPerPageSelect={handleSetPageSize}
/> />
) : null} ) : null}
</Fragment> </>
); );
} }
@@ -182,6 +188,7 @@ PaginatedTable.propTypes = {
renderToolbar: PropTypes.func, renderToolbar: PropTypes.func,
hasContentLoading: PropTypes.bool, hasContentLoading: PropTypes.bool,
contentError: PropTypes.shape(), contentError: PropTypes.shape(),
clearSelected: PropTypes.func,
ouiaId: PropTypes.string, ouiaId: PropTypes.string,
}; };
@@ -195,6 +202,7 @@ PaginatedTable.defaultProps = {
showPageSizeOptions: true, showPageSizeOptions: true,
renderToolbar: props => <DataListToolbar {...props} />, renderToolbar: props => <DataListToolbar {...props} />,
ouiaId: null, ouiaId: null,
clearSelected: () => {},
}; };
export { PaginatedTable as _PaginatedTable }; export { PaginatedTable as _PaginatedTable };

View File

@@ -1,4 +1,4 @@
import React, { useState, useEffect, useCallback } from 'react'; import React, { useEffect, useCallback } from 'react';
import { useLocation } from 'react-router-dom'; import { useLocation } from 'react-router-dom';
import { bool, func } from 'prop-types'; import { bool, func } from 'prop-types';
@@ -10,6 +10,7 @@ import PaginatedTable, { HeaderRow, HeaderCell } from '../../PaginatedTable';
import DataListToolbar from '../../DataListToolbar'; import DataListToolbar from '../../DataListToolbar';
import { ToolbarAddButton, ToolbarDeleteButton } from '../../PaginatedDataList'; import { ToolbarAddButton, ToolbarDeleteButton } from '../../PaginatedDataList';
import useRequest, { useDeleteItems } from '../../../util/useRequest'; import useRequest, { useDeleteItems } from '../../../util/useRequest';
import useSelected from '../../../util/useSelected';
import { getQSConfig, parseQueryString } from '../../../util/qs'; import { getQSConfig, parseQueryString } from '../../../util/qs';
import ScheduleListItem from './ScheduleListItem'; import ScheduleListItem from './ScheduleListItem';
@@ -27,8 +28,6 @@ function ScheduleList({
launchConfig, launchConfig,
surveyConfig, surveyConfig,
}) { }) {
const [selected, setSelected] = useState([]);
const location = useLocation(); const location = useLocation();
const { const {
@@ -76,8 +75,13 @@ function ScheduleList({
fetchSchedules(); fetchSchedules();
}, [fetchSchedules]); }, [fetchSchedules]);
const isAllSelected = const {
selected.length === schedules.length && selected.length > 0; selected,
isAllSelected,
handleSelect,
selectAll,
clearSelected,
} = useSelected(schedules);
const { const {
isLoading: isDeleteLoading, 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 () => { const handleDelete = async () => {
await deleteJobs(); await deleteJobs();
setSelected([]); clearSelected();
}; };
const canAdd = const canAdd =
@@ -183,6 +175,7 @@ function ScheduleList({
isMissingSurvey={isTemplate && hasMissingSurveyValue(item)} isMissingSurvey={isTemplate && hasMissingSurveyValue(item)}
/> />
)} )}
clearSelected={clearSelected}
toolbarSearchColumns={[ toolbarSearchColumns={[
{ {
name: t`Name`, name: t`Name`,
@@ -209,7 +202,7 @@ function ScheduleList({
{...props} {...props}
showSelectAll showSelectAll
isAllSelected={isAllSelected} isAllSelected={isAllSelected}
onSelectAll={handleSelectAll} onSelectAll={selectAll}
qsConfig={QS_CONFIG} qsConfig={QS_CONFIG}
additionalControls={[ additionalControls={[
...(canAdd ...(canAdd

View File

@@ -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 { useLocation, Link } from 'react-router-dom';
import { t, Plural } from '@lingui/macro'; import { t, Plural } from '@lingui/macro';
import { Card, DropdownItem } from '@patternfly/react-core'; import { Card, DropdownItem } from '@patternfly/react-core';
@@ -13,6 +13,7 @@ import ErrorDetail from '../ErrorDetail';
import { ToolbarDeleteButton } from '../PaginatedDataList'; import { ToolbarDeleteButton } from '../PaginatedDataList';
import PaginatedTable, { HeaderRow, HeaderCell } from '../PaginatedTable'; import PaginatedTable, { HeaderRow, HeaderCell } from '../PaginatedTable';
import useRequest, { useDeleteItems } from '../../util/useRequest'; import useRequest, { useDeleteItems } from '../../util/useRequest';
import useSelected from '../../util/useSelected';
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';
@@ -35,8 +36,6 @@ function TemplateList({ defaultParams }) {
); );
const location = useLocation(); const location = useLocation();
const [selected, setSelected] = useState([]);
const { const {
result: { result: {
results, results,
@@ -87,8 +86,14 @@ function TemplateList({ defaultParams }) {
const templates = useWsTemplates(results); const templates = useWsTemplates(results);
const isAllSelected = const {
selected.length === templates.length && selected.length > 0; selected,
isAllSelected,
handleSelect,
selectAll,
clearSelected,
} = useSelected(templates);
const { const {
isLoading: isDeleteLoading, isLoading: isDeleteLoading,
deleteItems: deleteTemplates, deleteItems: deleteTemplates,
@@ -117,19 +122,7 @@ function TemplateList({ defaultParams }) {
const handleTemplateDelete = async () => { const handleTemplateDelete = async () => {
await deleteTemplates(); await deleteTemplates();
setSelected([]); clearSelected();
};
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));
}
}; };
const canAddJT = const canAddJT =
@@ -179,7 +172,7 @@ function TemplateList({ defaultParams }) {
); );
return ( return (
<Fragment> <>
<Card> <Card>
<PaginatedTable <PaginatedTable
contentError={contentError} contentError={contentError}
@@ -188,7 +181,7 @@ function TemplateList({ defaultParams }) {
itemCount={count} itemCount={count}
pluralizedItemName={t`Templates`} pluralizedItemName={t`Templates`}
qsConfig={qsConfig} qsConfig={qsConfig}
onRowClick={handleSelect} clearSelected={clearSelected}
toolbarSearchColumns={[ toolbarSearchColumns={[
{ {
name: t`Name`, name: t`Name`,
@@ -235,7 +228,7 @@ function TemplateList({ defaultParams }) {
{...props} {...props}
showSelectAll showSelectAll
isAllSelected={isAllSelected} isAllSelected={isAllSelected}
onSelectAll={handleSelectAll} onSelectAll={selectAll}
qsConfig={qsConfig} qsConfig={qsConfig}
additionalControls={[ additionalControls={[
...(canAddJT || canAddWFJT ? [addButton] : []), ...(canAddJT || canAddWFJT ? [addButton] : []),
@@ -281,7 +274,7 @@ function TemplateList({ defaultParams }) {
{t`Failed to delete one or more templates.`} {t`Failed to delete one or more templates.`}
<ErrorDetail error={deletionError} /> <ErrorDetail error={deletionError} />
</AlertModal> </AlertModal>
</Fragment> </>
); );
} }

View File

@@ -76,9 +76,14 @@ function CredentialList() {
fetchCredentials(); fetchCredentials();
}, [fetchCredentials]); }, [fetchCredentials]);
const { selected, isAllSelected, handleSelect, setSelected } = useSelected( const {
credentials selected,
); isAllSelected,
handleSelect,
setSelected,
selectAll,
clearSelected,
} = useSelected(credentials);
const { const {
isLoading: isDeleteLoading, isLoading: isDeleteLoading,
@@ -115,7 +120,7 @@ function CredentialList() {
items={credentials} items={credentials}
itemCount={credentialCount} itemCount={credentialCount}
qsConfig={QS_CONFIG} qsConfig={QS_CONFIG}
onRowClick={handleSelect} clearSelected={clearSelected}
toolbarSearchableKeys={searchableKeys} toolbarSearchableKeys={searchableKeys}
toolbarRelatedSearchableKeys={relatedSearchableKeys} toolbarRelatedSearchableKeys={relatedSearchableKeys}
toolbarSearchColumns={[ toolbarSearchColumns={[
@@ -160,9 +165,7 @@ function CredentialList() {
{...props} {...props}
showSelectAll showSelectAll
isAllSelected={isAllSelected} isAllSelected={isAllSelected}
onSelectAll={isSelected => onSelectAll={selectAll}
setSelected(isSelected ? [...credentials] : [])
}
qsConfig={QS_CONFIG} qsConfig={QS_CONFIG}
additionalControls={[ additionalControls={[
...(canAdd ...(canAdd

View File

@@ -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 { useHistory, useLocation, useRouteMatch } from 'react-router-dom';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
@@ -17,6 +17,7 @@ import PaginatedTable, {
HeaderCell, HeaderCell,
} from '../../../components/PaginatedTable'; } from '../../../components/PaginatedTable';
import useRequest, { useDeleteItems } from '../../../util/useRequest'; import useRequest, { useDeleteItems } from '../../../util/useRequest';
import useSelected from '../../../util/useSelected';
import { import {
encodeQueryString, encodeQueryString,
getQSConfig, getQSConfig,
@@ -36,7 +37,6 @@ function HostList() {
const history = useHistory(); const history = useHistory();
const location = useLocation(); const location = useLocation();
const match = useRouteMatch(); const match = useRouteMatch();
const [selected, setSelected] = useState([]);
const parsedQueryStrings = parseQueryString(QS_CONFIG, location.search); const parsedQueryStrings = parseQueryString(QS_CONFIG, location.search);
const nonDefaultSearchParams = {}; const nonDefaultSearchParams = {};
@@ -86,7 +86,14 @@ function HostList() {
fetchHosts(); fetchHosts();
}, [fetchHosts]); }, [fetchHosts]);
const isAllSelected = selected.length === hosts.length && selected.length > 0; const {
selected,
isAllSelected,
handleSelect,
selectAll,
clearSelected,
} = useSelected(hosts);
const { const {
isLoading: isDeleteLoading, isLoading: isDeleteLoading,
deleteItems: deleteHosts, deleteItems: deleteHosts,
@@ -105,19 +112,7 @@ function HostList() {
const handleHostDelete = async () => { const handleHostDelete = async () => {
await deleteHosts(); await deleteHosts();
setSelected([]); clearSelected();
};
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));
}
}; };
const handleSmartInventoryClick = () => { const handleSmartInventoryClick = () => {
@@ -141,7 +136,7 @@ function HostList() {
itemCount={count} itemCount={count}
pluralizedItemName={t`Hosts`} pluralizedItemName={t`Hosts`}
qsConfig={QS_CONFIG} qsConfig={QS_CONFIG}
onRowClick={handleSelect} clearSelected={clearSelected}
toolbarSearchColumns={[ toolbarSearchColumns={[
{ {
name: t`Name`, name: t`Name`,
@@ -175,7 +170,7 @@ function HostList() {
{...props} {...props}
showSelectAll showSelectAll
isAllSelected={isAllSelected} isAllSelected={isAllSelected}
onSelectAll={handleSelectAll} onSelectAll={selectAll}
qsConfig={QS_CONFIG} qsConfig={QS_CONFIG}
additionalControls={[ additionalControls={[
...(canAdd ...(canAdd

View File

@@ -15,6 +15,7 @@ import {
ToolbarAddButton, ToolbarAddButton,
ToolbarDeleteButton, ToolbarDeleteButton,
} from '../../../components/PaginatedDataList'; } from '../../../components/PaginatedDataList';
import useSelected from '../../../util/useSelected';
import AdHocCommands from '../../../components/AdHocCommands/AdHocCommands'; import AdHocCommands from '../../../components/AdHocCommands/AdHocCommands';
import InventoryHostItem from './InventoryHostItem'; import InventoryHostItem from './InventoryHostItem';
@@ -25,7 +26,6 @@ const QS_CONFIG = getQSConfig('host', {
}); });
function InventoryHostList() { function InventoryHostList() {
const [selected, setSelected] = useState([]);
const [isAdHocLaunchLoading, setIsAdHocLaunchLoading] = useState(false); const [isAdHocLaunchLoading, setIsAdHocLaunchLoading] = useState(false);
const { id } = useParams(); const { id } = useParams();
const { search } = useLocation(); const { search } = useLocation();
@@ -74,17 +74,14 @@ function InventoryHostList() {
fetchData(); fetchData();
}, [fetchData]); }, [fetchData]);
const handleSelectAll = isSelected => { const {
setSelected(isSelected ? [...hosts] : []); 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 { const {
isLoading: isDeleteLoading, isLoading: isDeleteLoading,
deleteItems: deleteHosts, deleteItems: deleteHosts,
@@ -99,12 +96,11 @@ function InventoryHostList() {
const handleDeleteHosts = async () => { const handleDeleteHosts = async () => {
await deleteHosts(); await deleteHosts();
setSelected([]); clearSelected();
}; };
const canAdd = const canAdd =
actions && Object.prototype.hasOwnProperty.call(actions, 'POST'); actions && Object.prototype.hasOwnProperty.call(actions, 'POST');
const isAllSelected = selected.length > 0 && selected.length === hosts.length;
return ( return (
<> <>
@@ -115,6 +111,7 @@ function InventoryHostList() {
itemCount={hostCount} itemCount={hostCount}
pluralizedItemName={t`Hosts`} pluralizedItemName={t`Hosts`}
qsConfig={QS_CONFIG} qsConfig={QS_CONFIG}
clearSelected={clearSelected}
toolbarSearchableKeys={searchableKeys} toolbarSearchableKeys={searchableKeys}
toolbarRelatedSearchableKeys={relatedSearchableKeys} toolbarRelatedSearchableKeys={relatedSearchableKeys}
headerRow={ headerRow={
@@ -128,7 +125,7 @@ function InventoryHostList() {
{...props} {...props}
showSelectAll showSelectAll
isAllSelected={isAllSelected} isAllSelected={isAllSelected}
onSelectAll={handleSelectAll} onSelectAll={selectAll}
qsConfig={QS_CONFIG} qsConfig={QS_CONFIG}
additionalControls={[ additionalControls={[
...(canAdd ...(canAdd

View File

@@ -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 { useLocation, useRouteMatch, Link } from 'react-router-dom';
import { t, Plural } from '@lingui/macro'; import { t, Plural } from '@lingui/macro';
import { Card, PageSection, DropdownItem } from '@patternfly/react-core'; import { Card, PageSection, DropdownItem } from '@patternfly/react-core';
import { InventoriesAPI } from '../../../api'; import { InventoriesAPI } from '../../../api';
import useRequest, { useDeleteItems } from '../../../util/useRequest'; import useRequest, { useDeleteItems } from '../../../util/useRequest';
import useSelected from '../../../util/useSelected';
import AlertModal from '../../../components/AlertModal'; import AlertModal from '../../../components/AlertModal';
import DatalistToolbar from '../../../components/DataListToolbar'; import DatalistToolbar from '../../../components/DataListToolbar';
import ErrorDetail from '../../../components/ErrorDetail'; import ErrorDetail from '../../../components/ErrorDetail';
@@ -27,7 +28,6 @@ const QS_CONFIG = getQSConfig('inventory', {
function InventoryList() { function InventoryList() {
const location = useLocation(); const location = useLocation();
const match = useRouteMatch(); const match = useRouteMatch();
const [selected, setSelected] = useState([]);
const { const {
result: { result: {
@@ -89,8 +89,14 @@ function InventoryList() {
QS_CONFIG QS_CONFIG
); );
const isAllSelected = const {
selected.length === inventories.length && selected.length > 0; selected,
isAllSelected,
handleSelect,
selectAll,
clearSelected,
} = useSelected(inventories);
const { const {
isLoading: isDeleteLoading, isLoading: isDeleteLoading,
deleteItems: deleteInventories, deleteItems: deleteInventories,
@@ -107,26 +113,12 @@ function InventoryList() {
const handleInventoryDelete = async () => { const handleInventoryDelete = async () => {
await deleteInventories(); await deleteInventories();
setSelected([]); clearSelected();
}; };
const hasContentLoading = isDeleteLoading || isLoading; const hasContentLoading = isDeleteLoading || isLoading;
const canAdd = actions && actions.POST; 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( const deleteDetailsRequests = relatedResourceDeleteRequests.inventory(
selected[0] selected[0]
); );
@@ -197,6 +189,7 @@ function InventoryList() {
]} ]}
toolbarSearchableKeys={searchableKeys} toolbarSearchableKeys={searchableKeys}
toolbarRelatedSearchableKeys={relatedSearchableKeys} toolbarRelatedSearchableKeys={relatedSearchableKeys}
clearSelected={clearSelected}
headerRow={ headerRow={
<HeaderRow qsConfig={QS_CONFIG}> <HeaderRow qsConfig={QS_CONFIG}>
<HeaderCell sortKey="name">{t`Name`}</HeaderCell> <HeaderCell sortKey="name">{t`Name`}</HeaderCell>
@@ -211,7 +204,7 @@ function InventoryList() {
{...props} {...props}
showSelectAll showSelectAll
isAllSelected={isAllSelected} isAllSelected={isAllSelected}
onSelectAll={handleSelectAll} onSelectAll={selectAll}
qsConfig={QS_CONFIG} qsConfig={QS_CONFIG}
additionalControls={[ additionalControls={[
...(canAdd ? [addButton] : []), ...(canAdd ? [addButton] : []),
@@ -251,7 +244,11 @@ function InventoryList() {
? `${match.url}/smart_inventory/${inventory.id}/details` ? `${match.url}/smart_inventory/${inventory.id}/details`
: `${match.url}/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)} isSelected={selected.some(row => row.id === inventory.id)}
/> />
)} )}

View File

@@ -306,7 +306,7 @@ function NotificationTemplateDetail({ template, defaultMessages }) {
label={t`HTTP Headers`} label={t`HTTP Headers`}
value={JSON.stringify(configuration.headers)} value={JSON.stringify(configuration.headers)}
mode="json" mode="json"
rows="6" rows={6}
dataCy="nt-detail-webhook-headers" dataCy="nt-detail-webhook-headers"
/> />
</> </>

View File

@@ -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 { useLocation, useRouteMatch } from 'react-router-dom';
import { t, Plural } from '@lingui/macro'; import { t, Plural } from '@lingui/macro';
import { Card, PageSection } from '@patternfly/react-core'; import { Card, PageSection } from '@patternfly/react-core';
@@ -17,6 +17,7 @@ import PaginatedTable, {
HeaderCell, HeaderCell,
} from '../../../components/PaginatedTable'; } from '../../../components/PaginatedTable';
import { getQSConfig, parseQueryString } from '../../../util/qs'; import { getQSConfig, parseQueryString } from '../../../util/qs';
import useSelected from '../../../util/useSelected';
import OrganizationListItem from './OrganizationListItem'; import OrganizationListItem from './OrganizationListItem';
import { relatedResourceDeleteRequests } from '../../../util/getRelatedResourceDeleteDetails'; import { relatedResourceDeleteRequests } from '../../../util/getRelatedResourceDeleteDetails';
@@ -30,8 +31,6 @@ function OrganizationsList() {
const location = useLocation(); const location = useLocation();
const match = useRouteMatch(); const match = useRouteMatch();
const [selected, setSelected] = useState([]);
const addUrl = `${match.url}/add`; const addUrl = `${match.url}/add`;
const { const {
@@ -77,8 +76,14 @@ function OrganizationsList() {
fetchOrganizations(); fetchOrganizations();
}, [fetchOrganizations]); }, [fetchOrganizations]);
const isAllSelected = const {
selected.length === organizations.length && selected.length > 0; selected,
isAllSelected,
handleSelect,
selectAll,
clearSelected,
} = useSelected(organizations);
const { const {
isLoading: isDeleteLoading, isLoading: isDeleteLoading,
deleteItems: deleteOrganizations, deleteItems: deleteOrganizations,
@@ -99,23 +104,12 @@ function OrganizationsList() {
const handleOrgDelete = async () => { const handleOrgDelete = async () => {
await deleteOrganizations(); await deleteOrganizations();
setSelected([]); clearSelected();
}; };
const hasContentLoading = isDeleteLoading || isOrgsLoading; const hasContentLoading = isDeleteLoading || isOrgsLoading;
const canAdd = actions && actions.POST; 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( const deleteDetailsRequests = relatedResourceDeleteRequests.organization(
selected[0] selected[0]
); );
@@ -131,6 +125,7 @@ function OrganizationsList() {
itemCount={organizationCount} itemCount={organizationCount}
pluralizedItemName={t`Organizations`} pluralizedItemName={t`Organizations`}
qsConfig={QS_CONFIG} qsConfig={QS_CONFIG}
clearSelected={clearSelected}
toolbarSearchColumns={[ toolbarSearchColumns={[
{ {
name: t`Name`, name: t`Name`,
@@ -165,7 +160,7 @@ function OrganizationsList() {
{...props} {...props}
showSelectAll showSelectAll
isAllSelected={isAllSelected} isAllSelected={isAllSelected}
onSelectAll={handleSelectAll} onSelectAll={selectAll}
qsConfig={QS_CONFIG} qsConfig={QS_CONFIG}
additionalControls={[ additionalControls={[
...(canAdd ...(canAdd

View File

@@ -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 { useLocation, useRouteMatch } from 'react-router-dom';
import { t, Plural } from '@lingui/macro'; import { t, Plural } from '@lingui/macro';
import { Card, PageSection } from '@patternfly/react-core'; import { Card, PageSection } from '@patternfly/react-core';
@@ -17,6 +17,7 @@ import PaginatedTable, {
HeaderCell, HeaderCell,
} from '../../../components/PaginatedTable'; } from '../../../components/PaginatedTable';
import useWsProjects from './useWsProjects'; import useWsProjects from './useWsProjects';
import useSelected from '../../../util/useSelected';
import { relatedResourceDeleteRequests } from '../../../util/getRelatedResourceDeleteDetails'; import { relatedResourceDeleteRequests } from '../../../util/getRelatedResourceDeleteDetails';
import { getQSConfig, parseQueryString } from '../../../util/qs'; import { getQSConfig, parseQueryString } from '../../../util/qs';
@@ -31,7 +32,6 @@ const QS_CONFIG = getQSConfig('project', {
function ProjectList() { function ProjectList() {
const location = useLocation(); const location = useLocation();
const match = useRouteMatch(); const match = useRouteMatch();
const [selected, setSelected] = useState([]);
const { const {
result: { result: {
@@ -78,8 +78,15 @@ function ProjectList() {
const projects = useWsProjects(results); const projects = useWsProjects(results);
const isAllSelected = const {
selected.length === projects.length && selected.length > 0; selected,
isAllSelected,
handleSelect,
setSelected,
selectAll,
clearSelected,
} = useSelected(projects);
const { const {
isLoading: isDeleteLoading, isLoading: isDeleteLoading,
deleteItems: deleteProjects, deleteItems: deleteProjects,
@@ -104,24 +111,12 @@ function ProjectList() {
const hasContentLoading = isDeleteLoading || isLoading; const hasContentLoading = isDeleteLoading || isLoading;
const canAdd = actions && actions.POST; 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( const deleteDetailsRequests = relatedResourceDeleteRequests.project(
selected[0] selected[0]
); );
return ( return (
<Fragment> <>
<PageSection> <PageSection>
<Card> <Card>
<PaginatedTable <PaginatedTable
@@ -131,7 +126,7 @@ function ProjectList() {
itemCount={itemCount} itemCount={itemCount}
pluralizedItemName={t`Projects`} pluralizedItemName={t`Projects`}
qsConfig={QS_CONFIG} qsConfig={QS_CONFIG}
onRowClick={handleSelect} clearSelected={clearSelected}
toolbarSearchColumns={[ toolbarSearchColumns={[
{ {
name: t`Name`, name: t`Name`,
@@ -182,7 +177,7 @@ function ProjectList() {
{...props} {...props}
showSelectAll showSelectAll
isAllSelected={isAllSelected} isAllSelected={isAllSelected}
onSelectAll={handleSelectAll} onSelectAll={selectAll}
qsConfig={QS_CONFIG} qsConfig={QS_CONFIG}
additionalControls={[ additionalControls={[
...(canAdd ...(canAdd
@@ -239,7 +234,7 @@ function ProjectList() {
{t`Failed to delete one or more projects.`} {t`Failed to delete one or more projects.`}
<ErrorDetail error={deletionError} /> <ErrorDetail error={deletionError} />
</AlertModal> </AlertModal>
</Fragment> </>
); );
} }

View File

@@ -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 { useLocation, useRouteMatch } from 'react-router-dom';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
@@ -17,6 +17,7 @@ import {
ToolbarAddButton, ToolbarAddButton,
ToolbarDeleteButton, ToolbarDeleteButton,
} from '../../../components/PaginatedDataList'; } from '../../../components/PaginatedDataList';
import useSelected from '../../../util/useSelected';
import { getQSConfig, parseQueryString } from '../../../util/qs'; import { getQSConfig, parseQueryString } from '../../../util/qs';
import TeamListItem from './TeamListItem'; import TeamListItem from './TeamListItem';
@@ -30,7 +31,6 @@ const QS_CONFIG = getQSConfig('team', {
function TeamList() { function TeamList() {
const location = useLocation(); const location = useLocation();
const match = useRouteMatch(); const match = useRouteMatch();
const [selected, setSelected] = useState([]);
const { const {
result: { result: {
@@ -75,7 +75,14 @@ function TeamList() {
fetchTeams(); fetchTeams();
}, [fetchTeams]); }, [fetchTeams]);
const isAllSelected = selected.length === teams.length && selected.length > 0; const {
selected,
isAllSelected,
handleSelect,
selectAll,
clearSelected,
} = useSelected(teams);
const { const {
isLoading: isDeleteLoading, isLoading: isDeleteLoading,
deleteItems: deleteTeams, deleteItems: deleteTeams,
@@ -94,26 +101,14 @@ function TeamList() {
const handleTeamDelete = async () => { const handleTeamDelete = async () => {
await deleteTeams(); await deleteTeams();
setSelected([]); clearSelected();
}; };
const hasContentLoading = isDeleteLoading || isLoading; const hasContentLoading = isDeleteLoading || isLoading;
const canAdd = actions && actions.POST; 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 ( return (
<Fragment> <>
<PageSection> <PageSection>
<Card> <Card>
<PaginatedTable <PaginatedTable
@@ -123,7 +118,7 @@ function TeamList() {
itemCount={itemCount} itemCount={itemCount}
pluralizedItemName={t`Teams`} pluralizedItemName={t`Teams`}
qsConfig={QS_CONFIG} qsConfig={QS_CONFIG}
onRowClick={handleSelect} clearSelected={clearSelected}
toolbarSearchColumns={[ toolbarSearchColumns={[
{ {
name: t`Name`, name: t`Name`,
@@ -161,7 +156,7 @@ function TeamList() {
{...props} {...props}
showSelectAll showSelectAll
isAllSelected={isAllSelected} isAllSelected={isAllSelected}
onSelectAll={handleSelectAll} onSelectAll={selectAll}
qsConfig={QS_CONFIG} qsConfig={QS_CONFIG}
additionalControls={[ additionalControls={[
...(canAdd ...(canAdd
@@ -208,7 +203,7 @@ function TeamList() {
{t`Failed to delete one or more teams.`} {t`Failed to delete one or more teams.`}
<ErrorDetail error={deletionError} /> <ErrorDetail error={deletionError} />
</AlertModal> </AlertModal>
</Fragment> </>
); );
} }

View File

@@ -19,6 +19,7 @@ import { ToolbarAddButton } from '../../../components/PaginatedDataList';
import SurveyListItem from './SurveyListItem'; import SurveyListItem from './SurveyListItem';
import SurveyToolbar from './SurveyToolbar'; import SurveyToolbar from './SurveyToolbar';
import SurveyPreviewModal from './SurveyPreviewModal'; import SurveyPreviewModal from './SurveyPreviewModal';
import useSelected from '../../../util/useSelected';
const Button = styled(_Button)` const Button = styled(_Button)`
margin: 20px; margin: 20px;
@@ -36,15 +37,16 @@ function SurveyList({
const match = useRouteMatch(); const match = useRouteMatch();
const questions = survey?.spec || []; const questions = survey?.spec || [];
const [selected, setSelected] = useState([]);
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
const [isPreviewModalOpen, setIsPreviewModalOpen] = useState(false); const [isPreviewModalOpen, setIsPreviewModalOpen] = useState(false);
const isAllSelected =
selected.length === questions?.length && selected.length > 0;
const handleSelectAll = isSelected => { const {
setSelected(isSelected ? [...questions] : []); selected,
}; isAllSelected,
setSelected,
selectAll,
clearSelected,
} = useSelected(questions);
const handleSelect = item => { const handleSelect = item => {
if (selected.some(q => q.variable === item.variable)) { if (selected.some(q => q.variable === item.variable)) {
@@ -61,7 +63,7 @@ function SurveyList({
await updateSurvey(questions.filter(q => !selected.includes(q))); await updateSurvey(questions.filter(q => !selected.includes(q)));
} }
setIsDeleteModalOpen(false); setIsDeleteModalOpen(false);
setSelected([]); clearSelected();
}; };
const moveUp = question => { const moveUp = question => {
@@ -91,7 +93,7 @@ function SurveyList({
isOpen={isDeleteModalOpen} isOpen={isDeleteModalOpen}
onClose={() => { onClose={() => {
setIsDeleteModalOpen(false); setIsDeleteModalOpen(false);
setSelected([]); clearSelected();
}} }}
actions={[ actions={[
<Button <Button
@@ -110,7 +112,7 @@ function SurveyList({
aria-label={t`cancel delete`} aria-label={t`cancel delete`}
onClick={() => { onClick={() => {
setIsDeleteModalOpen(false); setIsDeleteModalOpen(false);
setSelected([]); clearSelected();
}} }}
> >
{t`Cancel`} {t`Cancel`}
@@ -181,7 +183,7 @@ function SurveyList({
<> <>
<SurveyToolbar <SurveyToolbar
isAllSelected={isAllSelected} isAllSelected={isAllSelected}
onSelectAll={handleSelectAll} onSelectAll={selectAll}
surveyEnabled={surveyEnabled} surveyEnabled={surveyEnabled}
onToggleSurvey={toggleSurvey} onToggleSurvey={toggleSurvey}
isDeleteDisabled={selected?.length === 0} isDeleteDisabled={selected?.length === 0}

View File

@@ -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 * 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 * isAllSelected: boolean that indicates if all items are selected
* handleSelect: function that adds and removes items from selected list * handleSelect: function that adds and removes items from selected list
* setSelected: setter function * 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 isAllSelected = selected.length > 0 && selected.length === list.length;
const handleSelect = row => { const handleSelect = row => {
if (!row.id) {
throw new Error(`Selected row does not have an id`);
}
if (selected.some(s => s.id === row.id)) { if (selected.some(s => s.id === row.id)) {
setSelected(prevState => [...prevState.filter(i => i.id !== row.id)]); setSelected(prevState => [...prevState.filter(i => i.id !== row.id)]);
} else { } 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,
};
} }