diff --git a/awx/ui_next/src/components/DataListToolbar/DataListToolbar.jsx b/awx/ui_next/src/components/DataListToolbar/DataListToolbar.jsx index fceaa60ed3..c87d8cbc92 100644 --- a/awx/ui_next/src/components/DataListToolbar/DataListToolbar.jsx +++ b/awx/ui_next/src/components/DataListToolbar/DataListToolbar.jsx @@ -37,7 +37,6 @@ function DataListToolbar({ onExpand, onSelectAll, additionalControls, - qsConfig, pagination, }) { diff --git a/awx/ui_next/src/components/JobList/JobList.jsx b/awx/ui_next/src/components/JobList/JobList.jsx index eaaa26095c..c2d113b829 100644 --- a/awx/ui_next/src/components/JobList/JobList.jsx +++ b/awx/ui_next/src/components/JobList/JobList.jsx @@ -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 }) { {t`Actions`} } + 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={[ { + clearSelected(); + }, [location.search, clearSelected]); const pushHistoryState = qs => { history.push(qs ? `${pathname}?${qs}` : pathname); @@ -127,7 +133,7 @@ function PaginatedTable({ ); return ( - + <> ) : null} - + ); } @@ -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 => , ouiaId: null, + clearSelected: () => {}, }; export { PaginatedTable as _PaginatedTable }; diff --git a/awx/ui_next/src/components/Schedule/ScheduleList/ScheduleList.jsx b/awx/ui_next/src/components/Schedule/ScheduleList/ScheduleList.jsx index 60592f9e99..4a1a28e509 100644 --- a/awx/ui_next/src/components/Schedule/ScheduleList/ScheduleList.jsx +++ b/awx/ui_next/src/components/Schedule/ScheduleList/ScheduleList.jsx @@ -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 diff --git a/awx/ui_next/src/components/TemplateList/TemplateList.jsx b/awx/ui_next/src/components/TemplateList/TemplateList.jsx index 9ab0905306..69a0e6b7f6 100644 --- a/awx/ui_next/src/components/TemplateList/TemplateList.jsx +++ b/awx/ui_next/src/components/TemplateList/TemplateList.jsx @@ -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 ( - + <> - + ); } diff --git a/awx/ui_next/src/screens/Application/ApplicationsList/ApplicationsList.jsx b/awx/ui_next/src/screens/Application/ApplicationsList/ApplicationsList.jsx index 23af1fb071..e9cb51e8f7 100644 --- a/awx/ui_next/src/screens/Application/ApplicationsList/ApplicationsList.jsx +++ b/awx/ui_next/src/screens/Application/ApplicationsList/ApplicationsList.jsx @@ -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 diff --git a/awx/ui_next/src/screens/Credential/CredentialList/CredentialList.jsx b/awx/ui_next/src/screens/Credential/CredentialList/CredentialList.jsx index 6f80e18ceb..bd78478cdd 100644 --- a/awx/ui_next/src/screens/Credential/CredentialList/CredentialList.jsx +++ b/awx/ui_next/src/screens/Credential/CredentialList/CredentialList.jsx @@ -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 diff --git a/awx/ui_next/src/screens/CredentialType/CredentialTypeList/CredentialTypeList.jsx b/awx/ui_next/src/screens/CredentialType/CredentialTypeList/CredentialTypeList.jsx index bf8e371e31..4b93b28438 100644 --- a/awx/ui_next/src/screens/CredentialType/CredentialTypeList/CredentialTypeList.jsx +++ b/awx/ui_next/src/screens/CredentialType/CredentialTypeList/CredentialTypeList.jsx @@ -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 diff --git a/awx/ui_next/src/screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.jsx b/awx/ui_next/src/screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.jsx index 534a683038..6db687b031 100644 --- a/awx/ui_next/src/screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.jsx +++ b/awx/ui_next/src/screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.jsx @@ -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 diff --git a/awx/ui_next/src/screens/Host/HostList/HostList.jsx b/awx/ui_next/src/screens/Host/HostList/HostList.jsx index 5c2bbd8670..f9ed610f8a 100644 --- a/awx/ui_next/src/screens/Host/HostList/HostList.jsx +++ b/awx/ui_next/src/screens/Host/HostList/HostList.jsx @@ -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 diff --git a/awx/ui_next/src/screens/InstanceGroup/InstanceGroupList/InstanceGroupList.jsx b/awx/ui_next/src/screens/InstanceGroup/InstanceGroupList/InstanceGroupList.jsx index f125fd2003..776a9e9afb 100644 --- a/awx/ui_next/src/screens/InstanceGroup/InstanceGroupList/InstanceGroupList.jsx +++ b/awx/ui_next/src/screens/InstanceGroup/InstanceGroupList/InstanceGroupList.jsx @@ -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] : []), diff --git a/awx/ui_next/src/screens/Inventory/InventoryGroups/InventoryGroupsList.jsx b/awx/ui_next/src/screens/Inventory/InventoryGroups/InventoryGroupsList.jsx index ec9d562453..1c680f9050 100644 --- a/awx/ui_next/src/screens/Inventory/InventoryGroups/InventoryGroupsList.jsx +++ b/awx/ui_next/src/screens/Inventory/InventoryGroups/InventoryGroupsList.jsx @@ -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(); }} /> , diff --git a/awx/ui_next/src/screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.jsx b/awx/ui_next/src/screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.jsx index 49868207c5..e493fb2197 100644 --- a/awx/ui_next/src/screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.jsx +++ b/awx/ui_next/src/screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.jsx @@ -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 diff --git a/awx/ui_next/src/screens/Inventory/InventoryHosts/InventoryHostList.jsx b/awx/ui_next/src/screens/Inventory/InventoryHosts/InventoryHostList.jsx index 9b8bd79873..44a52af3b2 100644 --- a/awx/ui_next/src/screens/Inventory/InventoryHosts/InventoryHostList.jsx +++ b/awx/ui_next/src/screens/Inventory/InventoryHosts/InventoryHostList.jsx @@ -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 diff --git a/awx/ui_next/src/screens/Inventory/InventoryList/InventoryList.jsx b/awx/ui_next/src/screens/Inventory/InventoryList/InventoryList.jsx index 6b674fe973..1ca1b7511a 100644 --- a/awx/ui_next/src/screens/Inventory/InventoryList/InventoryList.jsx +++ b/awx/ui_next/src/screens/Inventory/InventoryList/InventoryList.jsx @@ -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={ {t`Name`} @@ -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)} /> )} diff --git a/awx/ui_next/src/screens/Inventory/InventorySources/InventorySourceList.jsx b/awx/ui_next/src/screens/Inventory/InventorySources/InventorySourceList.jsx index 5147773c86..14c22a06b5 100644 --- a/awx/ui_next/src/screens/Inventory/InventorySources/InventorySourceList.jsx +++ b/awx/ui_next/src/screens/Inventory/InventorySources/InventorySourceList.jsx @@ -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 => ( - setSelected(isSelected ? [...sources] : []) - } + onSelectAll={selectAll} qsConfig={QS_CONFIG} additionalControls={[ ...(canAdd diff --git a/awx/ui_next/src/screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.jsx b/awx/ui_next/src/screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.jsx index 554660b15b..236ec0b656 100644 --- a/awx/ui_next/src/screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.jsx +++ b/awx/ui_next/src/screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.jsx @@ -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" /> diff --git a/awx/ui_next/src/screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.jsx b/awx/ui_next/src/screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.jsx index 849290a166..a927ab77ca 100644 --- a/awx/ui_next/src/screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.jsx +++ b/awx/ui_next/src/screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.jsx @@ -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 diff --git a/awx/ui_next/src/screens/Organization/OrganizationList/OrganizationList.jsx b/awx/ui_next/src/screens/Organization/OrganizationList/OrganizationList.jsx index 4b5907dad9..1217c6e0b7 100644 --- a/awx/ui_next/src/screens/Organization/OrganizationList/OrganizationList.jsx +++ b/awx/ui_next/src/screens/Organization/OrganizationList/OrganizationList.jsx @@ -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 diff --git a/awx/ui_next/src/screens/Project/ProjectList/ProjectList.jsx b/awx/ui_next/src/screens/Project/ProjectList/ProjectList.jsx index eae938776d..66b9dff1f9 100644 --- a/awx/ui_next/src/screens/Project/ProjectList/ProjectList.jsx +++ b/awx/ui_next/src/screens/Project/ProjectList/ProjectList.jsx @@ -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 ( - + <> - + ); } diff --git a/awx/ui_next/src/screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.jsx b/awx/ui_next/src/screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.jsx index 9f15164b86..04a9b964af 100644 --- a/awx/ui_next/src/screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.jsx +++ b/awx/ui_next/src/screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.jsx @@ -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 }); diff --git a/awx/ui_next/src/screens/Team/TeamList/TeamList.jsx b/awx/ui_next/src/screens/Team/TeamList/TeamList.jsx index 6bfbe89005..36c43a1525 100644 --- a/awx/ui_next/src/screens/Team/TeamList/TeamList.jsx +++ b/awx/ui_next/src/screens/Team/TeamList/TeamList.jsx @@ -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 ( - + <> - + ); } diff --git a/awx/ui_next/src/screens/Template/Survey/SurveyList.jsx b/awx/ui_next/src/screens/Template/Survey/SurveyList.jsx index 55a6e9a68d..ff86fa7b7d 100644 --- a/awx/ui_next/src/screens/Template/Survey/SurveyList.jsx +++ b/awx/ui_next/src/screens/Template/Survey/SurveyList.jsx @@ -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={[