diff --git a/src/PresentationalComponents/PatchSetWrapper/PatchSetWrapper.js b/src/PresentationalComponents/PatchSetWrapper/PatchSetWrapper.js index a72525c5..532c5a8a 100644 --- a/src/PresentationalComponents/PatchSetWrapper/PatchSetWrapper.js +++ b/src/PresentationalComponents/PatchSetWrapper/PatchSetWrapper.js @@ -5,17 +5,19 @@ import PatchSetWizard from '../../SmartComponents/PatchSetWizard/PatchSetWizard' import UnassignSystemsModal from '../../SmartComponents/Modals/UnassignSystemsModal'; import AssignSystemsModal from '../../SmartComponents/Modals/AssignSystemsModal'; -const PatchSetWrapper = ({ patchSetState, setPatchSetState }) => { +const PatchSetWrapper = ({ patchSetState, setPatchSetState, totalItems }) => { return (<> {(patchSetState.isUnassignSystemsModalOpen) && } {(patchSetState.isPatchSetWizardOpen) && } @@ -24,6 +26,7 @@ const PatchSetWrapper = ({ patchSetState, setPatchSetState }) => { PatchSetWrapper.propTypes = { patchSetState: propTypes.object, - setPatchSetState: propTypes.func + setPatchSetState: propTypes.func, + totalItems: propTypes.number }; export default PatchSetWrapper; diff --git a/src/PresentationalComponents/PatchSetWrapper/PatchSetWrapper.test.js b/src/PresentationalComponents/PatchSetWrapper/PatchSetWrapper.test.js index 2f55ca9e..0cddd0d4 100644 --- a/src/PresentationalComponents/PatchSetWrapper/PatchSetWrapper.test.js +++ b/src/PresentationalComponents/PatchSetWrapper/PatchSetWrapper.test.js @@ -5,6 +5,7 @@ import { mountWithRouterAndProviderAndIntl } from '../../../config/rtlwrapper'; jest.mock('../../SmartComponents/PatchSetWizard/PatchSetWizard', () => () =>
); jest.mock('../../SmartComponents/Modals/UnassignSystemsModal', () => () =>
); +jest.mock('../../SmartComponents/Modals/AssignSystemsModal', () => () =>
); const mockState = {}; @@ -28,7 +29,8 @@ const testProps = { isPatchSetWizardOpen: true, systemsIDs: ['system-1', 'system-2'] }, - setPatchSetState: jest.fn() + setPatchSetState: jest.fn(), + totalItems: 101 }; describe('PatchSetWrapper', () => { it('should display PatchSetWizard when isPatchSetWizardOpen prop is true', () => { diff --git a/src/SmartComponents/Advisories/Advisories.js b/src/SmartComponents/Advisories/Advisories.js index 6d27e2de..6144d2fb 100644 --- a/src/SmartComponents/Advisories/Advisories.js +++ b/src/SmartComponents/Advisories/Advisories.js @@ -100,7 +100,8 @@ const Advisories = () => { { endpoint: ID_API_ENDPOINTS.advisories, queryParams, - selectionDispatcher: selectAdvisoryRow + selectionDispatcher: selectAdvisoryRow, + totalItems: metadata?.total_items } ); diff --git a/src/SmartComponents/Advisories/Advisories.test.js b/src/SmartComponents/Advisories/Advisories.test.js index f9c42a1d..9f865766 100644 --- a/src/SmartComponents/Advisories/Advisories.test.js +++ b/src/SmartComponents/Advisories/Advisories.test.js @@ -24,7 +24,7 @@ jest.mock('../../Utilities/api', () => ({ exportAdvisoriesCSV: jest.fn(() => Promise.resolve({ success: true }).catch((err) => console.log(err))), fetchSystems: jest.fn(() => Promise.resolve({ data: { id: 'testId' } }).catch((err) => console.log(err))), fetchViewAdvisoriesSystems: jest.fn(() => Promise.resolve({ success: true }).catch((err) => console.log(err))), - fetchIDs: jest.fn(() => Promise.resolve({ ids: [] }).catch((err) => console.log(err))), + fetchIDs: jest.fn(() => Promise.resolve({ data: [{ id: 'test_id-1' }] }).catch((err) => console.log(err))), fetchApplicableAdvisoriesApi: jest.fn(() => Promise.resolve({ success: true }).catch((err) => console.log(err))) })); @@ -52,7 +52,7 @@ const mockState = { ...storeListDefaults, metadata: { limit: 25, offset: 0, - total_items: 10 + total_items: 101 } }; diff --git a/src/SmartComponents/AdvisorySystems/AdvisorySystem.test.js b/src/SmartComponents/AdvisorySystems/AdvisorySystem.test.js index cf7c415e..fd8fbe58 100644 --- a/src/SmartComponents/AdvisorySystems/AdvisorySystem.test.js +++ b/src/SmartComponents/AdvisorySystems/AdvisorySystem.test.js @@ -11,7 +11,7 @@ initMocks(); jest.mock('../../Utilities/api', () => ({ ...jest.requireActual('../../Utilities/api'), - fetchIDs: jest.fn(() => Promise.resolve({ ids: [] }).catch((err) => console.log(err))) + fetchIDs: jest.fn(() => Promise.resolve({ data: [{ id: 'test_id-1' }] }).catch((err) => console.log(err))) })); jest.mock( @@ -30,7 +30,7 @@ const mockState = { selectedRows: { 'test-system-1': true }, error: {}, status: 'resolved', - total: 2 + total: 101 }, AdvisorySystemsStore: { queryParams: {} diff --git a/src/SmartComponents/AdvisorySystems/AdvisorySystems.js b/src/SmartComponents/AdvisorySystems/AdvisorySystems.js index 0052b406..95709551 100644 --- a/src/SmartComponents/AdvisorySystems/AdvisorySystems.js +++ b/src/SmartComponents/AdvisorySystems/AdvisorySystems.js @@ -102,7 +102,8 @@ const AdvisorySystems = ({ advisoryName }) => { { endpoint: ID_API_ENDPOINTS.advisorySystems(advisoryName), queryParams, - selectionDispatcher: systemSelectAction + selectionDispatcher: systemSelectAction, + totalItems } ); diff --git a/src/SmartComponents/Modals/AssignSystemsModal.js b/src/SmartComponents/Modals/AssignSystemsModal.js index 9893a85e..9d8f24a1 100644 --- a/src/SmartComponents/Modals/AssignSystemsModal.js +++ b/src/SmartComponents/Modals/AssignSystemsModal.js @@ -10,8 +10,10 @@ import { addNotification } from '@redhat-cloud-services/frontend-components-noti import { patchSetAssignSystemsNotifications } from '../PatchSet/PatchSetAssets'; import { filterSelectedActiveSystemIDs } from '../../Utilities/Helpers'; import { filterSatelliteManagedSystems } from './Helpers'; +import { useFetchBatched } from '../../Utilities/hooks'; +import isEmpty from 'lodash/isEmpty'; -const AssignSystemsModal = ({ patchSetState = {}, setPatchSetState, intl }) => { +const AssignSystemsModal = ({ patchSetState = {}, setPatchSetState, intl, totalItems }) => { const dispatch = useDispatch(); const { systemsIDs, isAssignSystemsModalOpen } = patchSetState; @@ -20,6 +22,7 @@ const AssignSystemsModal = ({ patchSetState = {}, setPatchSetState, intl }) => { const [systemsNotManagedBySatellite, setSystemsNotManagedBySatellite] = useState([]); const [systemsLoading, setSystemsLoading] = useState(true); + const { fetchBatched } = useFetchBatched(); const closeModal = () => { setPatchSetState({ @@ -66,10 +69,13 @@ const AssignSystemsModal = ({ patchSetState = {}, setPatchSetState, intl }) => { }; useEffect(() => { - if (systemsIDs) { + if (systemsIDs && !isEmpty(systemsIDs)) { setSystemsLoading(true); - - filterSatelliteManagedSystems(Object.keys(systemsIDs)).then(result => { + filterSatelliteManagedSystems( + Object.keys(systemsIDs), + fetchBatched, + totalItems + ).then(result => { setSystemsNotManagedBySatellite(result); setSystemsLoading(false); }); @@ -144,7 +150,8 @@ const AssignSystemsModal = ({ patchSetState = {}, setPatchSetState, intl }) => { AssignSystemsModal.propTypes = { intl: propTypes.any, setPatchSetState: propTypes.func, - patchSetState: propTypes.object + patchSetState: propTypes.object, + totalItems: propTypes.number }; export default injectIntl(AssignSystemsModal); diff --git a/src/SmartComponents/Modals/Helpers.js b/src/SmartComponents/Modals/Helpers.js index 8b1ea864..127c1b62 100644 --- a/src/SmartComponents/Modals/Helpers.js +++ b/src/SmartComponents/Modals/Helpers.js @@ -2,28 +2,37 @@ import React from 'react'; import { GridItem } from '@patternfly/react-core'; import messages from '../../Messages'; -import { fetchIDs, fetchSystems } from '../../Utilities/api'; +import { fetchIDs } from '../../Utilities/api'; -export const filterSystemsWithoutSets = (systemsIDs) => { - return fetchSystems({ - limit: -1, 'filter[baseline_name]': 'neq:', - filter: { stale: [true, false] } - }).then((allSystemsWithPatchSet) => { - return systemsIDs.filter(systemID => - allSystemsWithPatchSet?.data?.some(system => system.id === systemID) +const filterChosenSystems = (urlFilter, systemsIDs, fetchBatched, totalItems) => { + return fetchBatched( + (filter) => fetchIDs( + '/ids/systems', + filter + ), + { + ...urlFilter, + filter: { stale: [true, false] } + }, + totalItems, + 100 + ).then((systemsNotManagedBySatellite) => { + const aggregatedResult = systemsNotManagedBySatellite.flatMap(({ data }) => data); + return systemsIDs.filter(systemID =>{ + return aggregatedResult?.some(system => system.id === systemID); + } ); }); }; -export const filterSatelliteManagedSystems = (systemsIDs) => { - return fetchIDs('/ids/systems', { - limit: -1, 'filter[satellite_managed]': 'false', - filter: { stale: [true, false] } - }).then((systemsNotManagedBySatellite) => { - return systemsIDs.filter(systemID => - systemsNotManagedBySatellite?.data?.some(system => system.id === systemID) - ); - }); +export const filterSystemsWithoutSets = (systemsIDs, fetchBatched, totalItems) => { + const urlFilter = { 'filter[baseline_name]': 'neq:' }; + return filterChosenSystems(urlFilter, systemsIDs, fetchBatched, totalItems); +}; + +export const filterSatelliteManagedSystems = (systemsIDs, fetchBatched, totalItems) => { + const urlFilter = { 'filter[satellite_managed]': 'false' }; + return filterChosenSystems(urlFilter, systemsIDs, fetchBatched, totalItems); }; export const renderUnassignModalMessages = (bodyMessage, systemsCount, intl) => ( diff --git a/src/SmartComponents/Modals/UnassignSystemsModal.js b/src/SmartComponents/Modals/UnassignSystemsModal.js index 95695730..600e8927 100644 --- a/src/SmartComponents/Modals/UnassignSystemsModal.js +++ b/src/SmartComponents/Modals/UnassignSystemsModal.js @@ -6,11 +6,13 @@ import { injectIntl } from 'react-intl'; import messages from '../../Messages'; import { useUnassignSystemsHook } from './useUnassignSystemsHook'; import { renderUnassignModalMessages, filterSystemsWithoutSets } from './Helpers'; +import { useFetchBatched } from '../../Utilities/hooks'; -const UnassignSystemsModal = ({ unassignSystemsModalState = {}, setUnassignSystemsModalOpen, intl }) => { +const UnassignSystemsModal = ({ unassignSystemsModalState = {}, setUnassignSystemsModalOpen, intl, totalItems }) => { const { systemsIDs, isUnassignSystemsModalOpen } = unassignSystemsModalState; const [systemsWithPatchSet, setSystemWithPatchSet] = useState([]); const [systemsLoading, setSystemsLoading] = useState(true); + const { fetchBatched } = useFetchBatched(); const handleModalToggle = (shouldRefresh) => { setUnassignSystemsModalOpen({ @@ -29,7 +31,12 @@ const UnassignSystemsModal = ({ unassignSystemsModalState = {}, setUnassignSyste useEffect(() => { setSystemsLoading(true); - filterSystemsWithoutSets(systemsIDs).then(result => { + filterSystemsWithoutSets( + systemsIDs, + fetchBatched, + totalItems + ) + .then(result => { setSystemWithPatchSet(result); setSystemsLoading(false); }); @@ -82,6 +89,7 @@ const UnassignSystemsModal = ({ unassignSystemsModalState = {}, setUnassignSyste UnassignSystemsModal.propTypes = { intl: propTypes.any, setUnassignSystemsModalOpen: propTypes.func, - unassignSystemsModalState: propTypes.object + unassignSystemsModalState: propTypes.object, + totalItems: propTypes.number }; export default injectIntl(UnassignSystemsModal); diff --git a/src/SmartComponents/Modals/UnassignSystemsModal.test.js b/src/SmartComponents/Modals/UnassignSystemsModal.test.js index 1d5a76b8..693aac09 100644 --- a/src/SmartComponents/Modals/UnassignSystemsModal.test.js +++ b/src/SmartComponents/Modals/UnassignSystemsModal.test.js @@ -1,6 +1,6 @@ import { addNotification } from '@redhat-cloud-services/frontend-components-notifications/redux'; import UnassignSystemsModal from './UnassignSystemsModal'; -import { unassignSystemFromPatchSet, fetchSystems } from '../../Utilities/api'; +import { unassignSystemFromPatchSet } from '../../Utilities/api'; import { initMocks } from '../../Utilities/unitTestingUtilities'; import { patchSetUnassignSystemsNotifications } from '../PatchSet/PatchSetAssets'; import { render, waitFor, screen } from '@testing-library/react'; @@ -12,7 +12,7 @@ initMocks(); jest.mock('../../Utilities/api', () => ({ ...jest.requireActual('../../Utilities/api'), unassignSystemFromPatchSet: jest.fn(), - fetchSystems: jest.fn() + fetchIDs: jest.fn(() => Promise.resolve({ data: [{ id: 'test_1' }] })) })); jest.mock('react-redux', () => ({ @@ -23,8 +23,6 @@ jest.mock('@redhat-cloud-services/frontend-components-notifications/redux', () = addNotification: jest.fn(() => {}) })); -fetchSystems.mockResolvedValue({ data: [{ id: 'test_1' }] }); - let unassignSystemsModalState = { isUnassignSystemsModalOpen: true, systemsIDs: ['test_1', 'test_2', 'test_3'] diff --git a/src/SmartComponents/PackageSystems/PackageSystems.js b/src/SmartComponents/PackageSystems/PackageSystems.js index 7c1a6b07..4ba3ac2b 100644 --- a/src/SmartComponents/PackageSystems/PackageSystems.js +++ b/src/SmartComponents/PackageSystems/PackageSystems.js @@ -100,7 +100,8 @@ const PackageSystems = ({ packageName }) => { queryParams, selectionDispatcher: systemSelectAction, constructFilename, - apiResponseTransformer: filterRemediatablePackageSystems + apiResponseTransformer: filterRemediatablePackageSystems, + totalItems } ); diff --git a/src/SmartComponents/PackageSystems/PackageSystems.test.js b/src/SmartComponents/PackageSystems/PackageSystems.test.js index 09b8630b..f9ed8ce3 100644 --- a/src/SmartComponents/PackageSystems/PackageSystems.test.js +++ b/src/SmartComponents/PackageSystems/PackageSystems.test.js @@ -23,12 +23,11 @@ jest.mock('../../Utilities/api', () => ({ fetchPackageVersions: jest.fn(() => Promise.resolve({ success: true }).catch((err) => console.log(err))), fetchIDs: jest.fn(() => Promise.resolve({ data: [{ - attributes: { - advisory_type: 2, - description: 'The tzdata penhancements.', - public_date: '2020-10-19T15:02:38Z', - synopsis: 'tzdata enhancement update' - }, + advisory_type: 2, + description: 'The tzdata penhancements.', + public_date: '2020-10-19T15:02:38Z', + synopsis: 'tzdata enhancement update', + updatable: true, id: 'RHBA-2020:4282', type: 'advisory' }] @@ -47,7 +46,7 @@ const mockState = { selectedRows: { 'test-system-1': 'packageEvra' }, error: {}, status: 'resolved', - total: 2 + total: 101 }, PackageSystemsStore: { queryParams: {} diff --git a/src/SmartComponents/PatchSet/PatchSet.js b/src/SmartComponents/PatchSet/PatchSet.js index d18ff445..2ce399f9 100644 --- a/src/SmartComponents/PatchSet/PatchSet.js +++ b/src/SmartComponents/PatchSet/PatchSet.js @@ -106,7 +106,8 @@ const PatchSet = () => { { endpoint: ID_API_ENDPOINTS.templates, queryParams, - selectionDispatcher: selectPatchSetRow + selectionDispatcher: selectPatchSetRow, + totalItems: metadata.total_items } ); diff --git a/src/SmartComponents/PatchSetDetail/PatchSetDetail.js b/src/SmartComponents/PatchSetDetail/PatchSetDetail.js index 05a463c3..8d95fc37 100644 --- a/src/SmartComponents/PatchSetDetail/PatchSetDetail.js +++ b/src/SmartComponents/PatchSetDetail/PatchSetDetail.js @@ -140,7 +140,8 @@ const PatchSetDetail = () => { { endpoint: ID_API_ENDPOINTS.templateSystems(patchSetId), queryParams, - selectionDispatcher: systemSelectAction + selectionDispatcher: systemSelectAction, + totalItems } ); diff --git a/src/SmartComponents/PatchSetWizard/steps/ReviewSystems.js b/src/SmartComponents/PatchSetWizard/steps/ReviewSystems.js index 1369f8e8..49b2ae1b 100644 --- a/src/SmartComponents/PatchSetWizard/steps/ReviewSystems.js +++ b/src/SmartComponents/PatchSetWizard/steps/ReviewSystems.js @@ -124,7 +124,8 @@ export const ReviewSystems = ({ systemsIDs = [], ...props }) => { satellite_managed: false } }, - customSelector: selectRows + customSelector: selectRows, + totalItems: metadata.total_items } ); return ( diff --git a/src/SmartComponents/SystemAdvisories/SystemAdvisories.js b/src/SmartComponents/SystemAdvisories/SystemAdvisories.js index 2d7ac79a..a294e3b4 100644 --- a/src/SmartComponents/SystemAdvisories/SystemAdvisories.js +++ b/src/SmartComponents/SystemAdvisories/SystemAdvisories.js @@ -93,7 +93,8 @@ const SystemAdvisories = ({ handleNoSystemData, inventoryId, shouldRefresh }) => endpoint: ID_API_ENDPOINTS.systemAdvisories(inventoryId), queryParams, selectionDispatcher: selectSystemAdvisoryRow, - constructFilename + constructFilename, + totalItems: metadata?.total_items } ); diff --git a/src/SmartComponents/SystemAdvisories/SystemAdvisories.test.js b/src/SmartComponents/SystemAdvisories/SystemAdvisories.test.js index 42f99f9c..be17668b 100644 --- a/src/SmartComponents/SystemAdvisories/SystemAdvisories.test.js +++ b/src/SmartComponents/SystemAdvisories/SystemAdvisories.test.js @@ -29,7 +29,7 @@ const mockState = { metadata: { limit: 25, offset: 0, - total_items: 10 + total_items: 101 }, expandedRows: {}, selectedRows: {}, diff --git a/src/SmartComponents/SystemPackages/SystemPackages.js b/src/SmartComponents/SystemPackages/SystemPackages.js index aff18361..4004cc68 100644 --- a/src/SmartComponents/SystemPackages/SystemPackages.js +++ b/src/SmartComponents/SystemPackages/SystemPackages.js @@ -78,7 +78,8 @@ const SystemPackages = ({ handleNoSystemData, inventoryId, shouldRefresh }) => { queryParams, selectionDispatcher: selectSystemPackagesRow, constructFilename, - transformKey + transformKey, + totalItems: metadata?.total_items } ); diff --git a/src/SmartComponents/SystemPackages/SystemPackages.test.js b/src/SmartComponents/SystemPackages/SystemPackages.test.js index f9b88960..1941ea4e 100644 --- a/src/SmartComponents/SystemPackages/SystemPackages.test.js +++ b/src/SmartComponents/SystemPackages/SystemPackages.test.js @@ -53,7 +53,7 @@ const mockState = { metadata: { limit: 25, offset: 0, - total_items: 10 + total_items: 101 } }; diff --git a/src/SmartComponents/Systems/SystemsListAssets.js b/src/SmartComponents/Systems/SystemsListAssets.js index 15d17dd4..413e9274 100644 --- a/src/SmartComponents/Systems/SystemsListAssets.js +++ b/src/SmartComponents/Systems/SystemsListAssets.js @@ -179,9 +179,9 @@ export const useActivateRemediationModal = (setRemediationIssues, setRemediation ); fetchBatched( - (__, pagination) => fetchApplicableSystemAdvisoriesApi({ ...filter, ...pagination }), - totalCount, - filter + (filterWithPagination) => fetchApplicableSystemAdvisoriesApi(filterWithPagination), + filter, + totalCount ).then(response => { const advisories = response.flatMap(({ data }) => data); const remediationIssues = remediationProvider( diff --git a/src/SmartComponents/Systems/SystemsMainContent.js b/src/SmartComponents/Systems/SystemsMainContent.js index 74214d1d..dac67ac8 100644 --- a/src/SmartComponents/Systems/SystemsMainContent.js +++ b/src/SmartComponents/Systems/SystemsMainContent.js @@ -58,7 +58,11 @@ const SystemsMainContent = () => { return ( - + {isRemediationOpen && (input) => { diff --git a/src/Utilities/TestingUtilities.js b/src/Utilities/TestingUtilities.js index cb0ffb24..4e2e9fcb 100644 --- a/src/Utilities/TestingUtilities.js +++ b/src/Utilities/TestingUtilities.js @@ -78,12 +78,21 @@ export const testBulkSelection = (fetchAllCallback, fetchAllUrl, spyOnAction, se it('should fetch all the data using limit=-1', async () => { await user.click(screen.getByLabelText('Select')); - await user.click(screen.getByText('Select all (10)')); - - expect(fetchAllCallback).toHaveBeenCalledWith( - fetchAllUrl, - expect.objectContaining({ limit: -1, offset: 0 }) + await user.click(screen.getByText('Select all (101)')); + await waitFor( + () => { + expect(fetchAllCallback).toHaveBeenCalledTimes(2); + expect(fetchAllCallback).toHaveBeenCalledWith( + fetchAllUrl, + expect.objectContaining({ limit: 100, offset: 0 }) + ); + expect(fetchAllCallback).toHaveBeenCalledWith( + fetchAllUrl, + expect.objectContaining({ limit: 100, offset: 100 }) + ); + } ); + }); it('should unselect rows', async () => { diff --git a/src/Utilities/hooks/useFetchBatched.js b/src/Utilities/hooks/useFetchBatched.js index 63fd1fa8..87679d2d 100644 --- a/src/Utilities/hooks/useFetchBatched.js +++ b/src/Utilities/hooks/useFetchBatched.js @@ -5,14 +5,25 @@ export const useFetchBatched = () => { return { isLoading, - fetchBatched: (fetchFunction, total, filter, batchSize = 50) => { + fetchBatched: async (fetchFunction, filter, total, batchSize = 50) => { + if (!total) { + total = await fetchFunction({ limit: 1 }).then( + response => response?.meta?.total_items || 0 + ); + } + const pages = Math.ceil(total / batchSize) || 1; const results = resolve( [...new Array(pages)].map( // eslint-disable-next-line camelcase - (_, pageIdx) => () => - fetchFunction(filter, { offset: pageIdx + 1, limit: batchSize }) + (_, pageIdx) => () => { + return fetchFunction({ + ...filter, + offset: pageIdx * batchSize, + limit: batchSize + }); + } ) ); diff --git a/src/Utilities/hooks/useOnSelect.js b/src/Utilities/hooks/useOnSelect.js index 0c1f9c72..d2c1533a 100644 --- a/src/Utilities/hooks/useOnSelect.js +++ b/src/Utilities/hooks/useOnSelect.js @@ -3,6 +3,8 @@ import { useDispatch } from 'react-redux'; import { fetchIDs } from '../api'; import { toggleAllSelectedAction } from '../../store/Actions/Actions'; import { isObject } from '../Helpers'; +import { useFetchBatched } from './useFetchBatched'; +import isArray from 'lodash/isArray'; export const ID_API_ENDPOINTS = { advisories: '/ids/advisories', @@ -14,23 +16,43 @@ export const ID_API_ENDPOINTS = { systemPackages: (systemID) => `/systems/${systemID}/packages`, templateSystems: (templateId) => `/ids/baselines/${templateId}/systems` }; +const isArrayWithData = (dataStructure) => { + return isArray(dataStructure) && dataStructure.length; +}; const useFetchAllIDs = ( endpoint, - apiResponseTransformer -) => - useCallback((queryParams) => - fetchIDs(endpoint, { ...queryParams, limit: -1 }) - .then(response => - apiResponseTransformer ? apiResponseTransformer(response) : response - ), - [] - ); + apiResponseTransformer, + totalItems +) => { + const { fetchBatched } = useFetchBatched(); + return useCallback(async (queryParams) => { + const response = await fetchBatched( + (filter) => fetchIDs(endpoint, filter), + queryParams, + totalItems, + 100 + ); + + const aggregatedResponse = response.reduce((accumulator = {}, currentValue) => { + Object.keys(accumulator).forEach(key => { + if (isArrayWithData(currentValue[key])) { + accumulator[key] = accumulator[key].concat(currentValue[key]); + } + }); + + return accumulator; + }, { data: [], ids: [] }); + + return apiResponseTransformer ? apiResponseTransformer(aggregatedResponse) : aggregatedResponse; + }, + [totalItems, endpoint, fetchBatched]); +}; const useCreateSelectedRow = (transformKey, constructFilename) => useCallback((rows, toSelect = []) => { const { ids, data } = rows; - const shouldUseOnlyIDs = Array.isArray(ids); + const shouldUseOnlyIDs = !isArrayWithData(data); const items = shouldUseOnlyIDs ? ids : data; items.forEach((item) => { @@ -79,9 +101,8 @@ const createSelectors = ( }; const selectAll = (fetchIDs, queryParams) => { - queryParams.offset = 0; return fetchIDs(queryParams).then(response => { - if (Array.isArray(response.data)) { + if (isArrayWithData(response.data)) { let rowsToSelect = response.data.filter(row => row.status !== 'Applicable'); dispatchSelection(createSelectedRow({ data: rowsToSelect })); } else { @@ -103,11 +124,12 @@ export const useOnSelect = (rawData, selectedRows, config) => { transformKey, apiResponseTransformer, //TODO: get rid of this custom selector - customSelector + customSelector, + totalItems } = config; const dispatch = useDispatch(); - const fetchIDs = useFetchAllIDs(endpoint, apiResponseTransformer); + const fetchIDs = useFetchAllIDs(endpoint, apiResponseTransformer, totalItems); const createSelectedRow = useCreateSelectedRow(transformKey, constructFilename); const toggleAllSystemsSelected = (flagState) => { diff --git a/src/Utilities/hooks/useOnSelect.test.js b/src/Utilities/hooks/useOnSelect.test.js index ae42e3ae..3b737460 100644 --- a/src/Utilities/hooks/useOnSelect.test.js +++ b/src/Utilities/hooks/useOnSelect.test.js @@ -1,4 +1,5 @@ import { act, renderHook } from '@testing-library/react-hooks'; +import { waitFor } from '@testing-library/react'; import { fetchIDs } from '../api'; import { useOnSelect } from './useOnSelect'; @@ -8,9 +9,11 @@ jest.mock('react-redux', () => ({ })); jest.mock('../api', () => ({ ...jest.requireActual('../api'), - fetchIDs: jest.fn(() => Promise.resolve({ - data: [{ id: 'db-item' }] - })) + fetchIDs: jest.fn(() => { + return Promise.resolve({ + data: [{ id: 'db-item' }] + }); + }) })); const rows = [ @@ -127,16 +130,21 @@ describe('useOnSelect', () => { ]); }); - it('Should select all items from db', () => { + it('Should select all items from db', async () => { + config.totalItems = 102; const { result } = renderHook(() => useOnSelect(rows, {}, config) ); - act(() => { - result.current('all', {}); - }); + result.current('all', {}); - expect(fetchIDs).toHaveBeenCalledWith('/some/api/endpoint', { limit: -1, offset: 0, search: 'test-search' }); + await waitFor( + () => { + expect(fetchIDs).toHaveBeenCalledTimes(2); + expect(fetchIDs).toHaveBeenCalledWith('/some/api/endpoint', { limit: 100, offset: 0, search: 'test-search' }); + expect(fetchIDs).toHaveBeenCalledWith('/some/api/endpoint', { limit: 100, offset: 100, search: 'test-search' }); + } + ); }); it('Should skip invalid rows while selection', () => { diff --git a/src/Utilities/hooks/usePatchSetState.js b/src/Utilities/hooks/usePatchSetState.js index 0251b3b5..b730c0fb 100644 --- a/src/Utilities/hooks/usePatchSetState.js +++ b/src/Utilities/hooks/usePatchSetState.js @@ -14,7 +14,7 @@ export const usePatchSetState = (selectedRows) => { isUnassignSystemsModalOpen: false, isAssignSystemsModalOpen: false, shouldRefresh: false, - systemsIDs: [] + systemsIDs: {} }); const openPatchSetAssignWizard = (systemID) => {