diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/useDeleteMultipleRecordsAction.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/useDeleteMultipleRecordsAction.tsx index 5e517ace1..7e2c5a405 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/useDeleteMultipleRecordsAction.tsx +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/useDeleteMultipleRecordsAction.tsx @@ -13,13 +13,14 @@ import { useFavorites } from '@/favorites/hooks/useFavorites'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { DELETE_MAX_COUNT } from '@/object-record/constants/DeleteMaxCount'; import { useDeleteManyRecords } from '@/object-record/hooks/useDeleteManyRecords'; -import { useFetchAllRecordIds } from '@/object-record/hooks/useFetchAllRecordIds'; import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable'; import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal'; import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useCallback, useContext, useState } from 'react'; import { IconTrash, isDefined } from 'twenty-ui'; +import { useLazyFetchAllRecords } from '@/object-record/hooks/useLazyFetchAllRecords'; +import { DEFAULT_QUERY_PAGE_SIZE } from '@/object-record/constants/DefaultQueryPageSize'; export const useDeleteMultipleRecordsAction = ({ objectMetadataItem, @@ -60,15 +61,18 @@ export const useDeleteMultipleRecordsAction = ({ objectMetadataItem, ); - const { fetchAllRecordIds } = useFetchAllRecordIds({ + const { fetchAllRecords: fetchAllRecordIds } = useLazyFetchAllRecords({ objectNameSingular: objectMetadataItem.nameSingular, filter: graphqlFilter, + limit: DEFAULT_QUERY_PAGE_SIZE, + recordGqlFields: { id: true }, }); const { closeRightDrawer } = useRightDrawer(); const handleDeleteClick = useCallback(async () => { - const recordIdsToDelete = await fetchAllRecordIds(); + const recordsToDelete = await fetchAllRecordIds(); + const recordIdsToDelete = recordsToDelete.map((record) => record.id); resetTableRowSelection(); diff --git a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useFetchAllRecordIds.test.tsx b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useFetchAllRecordIds.test.tsx deleted file mode 100644 index 80b57d7dc..000000000 --- a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useFetchAllRecordIds.test.tsx +++ /dev/null @@ -1,96 +0,0 @@ -import { act, renderHook } from '@testing-library/react'; -import { useEffect } from 'react'; -import { useRecoilState } from 'recoil'; - -import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; -import { - mockPageSize, - peopleMockWithIdsOnly, - query, - responseFirstRequest, - responseSecondRequest, - responseThirdRequest, - variablesFirstRequest, - variablesSecondRequest, - variablesThirdRequest, -} from '@/object-record/hooks/__mocks__/useFetchAllRecordIds'; -import { useFetchAllRecordIds } from '@/object-record/hooks/useFetchAllRecordIds'; -import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; -import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; - -const mocks = [ - { - delay: 100, - request: { - query, - variables: variablesFirstRequest, - }, - result: jest.fn(() => ({ - data: responseFirstRequest, - })), - }, - { - delay: 100, - request: { - query, - variables: variablesSecondRequest, - }, - result: jest.fn(() => ({ - data: responseSecondRequest, - })), - }, - { - delay: 100, - request: { - query, - variables: variablesThirdRequest, - }, - result: jest.fn(() => ({ - data: responseThirdRequest, - })), - }, -]; - -const Wrapper = getJestMetadataAndApolloMocksWrapper({ - apolloMocks: mocks, -}); - -describe('useFetchAllRecordIds', () => { - it('fetches all record ids with fetch more synchronous loop', async () => { - const { result } = renderHook( - () => { - const [, setObjectMetadataItems] = useRecoilState( - objectMetadataItemsState, - ); - - useEffect(() => { - setObjectMetadataItems(generatedMockObjectMetadataItems); - }, [setObjectMetadataItems]); - - return useFetchAllRecordIds({ - objectNameSingular: 'person', - pageSize: mockPageSize, - }); - }, - { - wrapper: Wrapper, - }, - ); - - const { fetchAllRecordIds } = result.current; - - let recordIds: string[] = []; - - await act(async () => { - recordIds = await fetchAllRecordIds(); - }); - - expect(mocks[0].result).toHaveBeenCalled(); - expect(mocks[1].result).toHaveBeenCalled(); - expect(mocks[2].result).toHaveBeenCalled(); - - expect(recordIds).toEqual( - peopleMockWithIdsOnly.edges.map((edge) => edge.node.id).slice(0, 6), - ); - }); -}); diff --git a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useLazyFetchAllRecords.test.tsx b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useLazyFetchAllRecords.test.tsx new file mode 100644 index 000000000..c7d28f10b --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useLazyFetchAllRecords.test.tsx @@ -0,0 +1,173 @@ +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; +import { act, renderHook, waitFor } from '@testing-library/react'; +import { expect } from '@storybook/test'; +import { useLazyFetchAllRecords } from '@/object-record/hooks/useLazyFetchAllRecords'; +import { MockedResponse } from '@apollo/client/testing'; +import gql from 'graphql-tag'; +import { PERSON_FRAGMENT_WITH_DEPTH_ZERO_RELATIONS } from '@/object-record/hooks/__mocks__/personFragments'; +import { getJestMetadataAndApolloMocksAndActionMenuWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksAndContextStoreWrapper'; + +const defaultResponseData = { + pageInfo: { + hasNextPage: false, + hasPreviousPage: false, + startCursor: '', + endCursor: '', + }, + totalCount: 2, +}; + +const mockPerson = { + __typename: 'Person', + updatedAt: '2021-08-03T19:20:06.000Z', + whatsapp: { + primaryPhoneNumber: '+1', + primaryPhoneCountryCode: '234-567-890', + additionalPhones: [], + }, + linkedinLink: { + primaryLinkUrl: 'https://www.linkedin.com', + primaryLinkLabel: 'linkedin', + secondaryLinks: ['https://www.linkedin.com'], + }, + name: { + firstName: 'firstName', + lastName: 'lastName', + }, + emails: { + primaryEmail: 'email', + additionalEmails: [], + }, + position: 'position', + createdBy: { + source: 'source', + workspaceMemberId: '1', + name: 'name', + }, + avatarUrl: 'avatarUrl', + jobTitle: 'jobTitle', + xLink: { + primaryLinkUrl: 'https://www.linkedin.com', + primaryLinkLabel: 'linkedin', + secondaryLinks: ['https://www.linkedin.com'], + }, + performanceRating: 1, + createdAt: '2021-08-03T19:20:06.000Z', + phones: { + primaryPhoneNumber: '+1', + primaryPhoneCountryCode: '234-567-890', + additionalPhones: [], + }, + id: '123', + city: 'city', + companyId: '1', + intro: 'intro', + deletedAt: null, + workPreference: 'workPreference', +}; + +const mock: MockedResponse = { + request: { + query: gql` + query FindManyPeople( + $filter: PersonFilterInput + $orderBy: [PersonOrderByInput] + $lastCursor: String + $limit: Int + ) { + people( + filter: $filter + orderBy: $orderBy + first: $limit + after: $lastCursor + ) { + edges { + node { + ${PERSON_FRAGMENT_WITH_DEPTH_ZERO_RELATIONS} + } + cursor + } + pageInfo { + hasNextPage + hasPreviousPage + startCursor + endCursor + } + totalCount + } + } + `, + variables: { + limit: 30, + }, + }, + result: jest.fn(() => ({ + data: { + people: { + ...defaultResponseData, + edges: [ + { + node: mockPerson, + cursor: '1', + }, + { + node: mockPerson, + cursor: '2', + }, + ], + }, + }, + })), +}; + +const Wrapper = getJestMetadataAndApolloMocksAndActionMenuWrapper({ + apolloMocks: [mock], + componentInstanceId: 'recordIndexId', + contextStoreTargetedRecordsRule: { + mode: 'selection', + selectedRecordIds: [], + }, + contextStoreCurrentObjectMetadataNameSingular: 'person', +}); + +describe('useLazyFetchAllRecords', () => { + const objectNameSingular = 'person'; + const objectMetadataItem = generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === objectNameSingular, + ); + if (!objectMetadataItem) { + throw new Error('Object metadata item not found'); + } + + it('should handle one single page', async () => { + const { result } = renderHook( + () => + useLazyFetchAllRecords({ + objectNameSingular, + limit: 30, + }), + { + wrapper: Wrapper, + }, + ); + + let res: any; + + act(() => { + res = result.current.fetchAllRecords(); + }); + + expect(result.current.isDownloading).toBe(true); + + await waitFor(() => { + expect(result.current.isDownloading).toBe(false); + expect(result.current.progress).toEqual({ displayType: 'number' }); + }); + + expect(result.current.progress).toEqual({ displayType: 'number' }); + + const finalResult = await res; + + expect(finalResult).toEqual([mockPerson, mockPerson]); + }); +}); diff --git a/packages/twenty-front/src/modules/object-record/hooks/useFetchAllRecordIds.ts b/packages/twenty-front/src/modules/object-record/hooks/useFetchAllRecordIds.ts deleted file mode 100644 index 715cdc5af..000000000 --- a/packages/twenty-front/src/modules/object-record/hooks/useFetchAllRecordIds.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; -import { DEFAULT_QUERY_PAGE_SIZE } from '@/object-record/constants/DefaultQueryPageSize'; -import { UseFindManyRecordsParams } from '@/object-record/hooks/useFetchMoreRecordsWithPagination'; -import { useLazyFindManyRecords } from '@/object-record/hooks/useLazyFindManyRecords'; -import { useCallback } from 'react'; -import { isDefined } from '~/utils/isDefined'; - -type UseLazyFetchAllRecordIdsParams = Omit< - UseFindManyRecordsParams, - 'skip' -> & { pageSize?: number }; - -export const useFetchAllRecordIds = ({ - objectNameSingular, - filter, - orderBy, - pageSize = DEFAULT_QUERY_PAGE_SIZE, -}: UseLazyFetchAllRecordIdsParams) => { - const { fetchMore, findManyRecords } = useLazyFindManyRecords({ - objectNameSingular, - filter, - orderBy, - limit: pageSize, - recordGqlFields: { id: true }, - }); - - const { objectMetadataItem } = useObjectMetadataItem({ - objectNameSingular, - }); - - const fetchAllRecordIds = useCallback(async () => { - if (!isDefined(findManyRecords)) { - return []; - } - - const findManyRecordsDataResult = await findManyRecords(); - - const firstQueryResult = - findManyRecordsDataResult?.data?.[objectMetadataItem.namePlural]; - - const totalCount = firstQueryResult?.totalCount ?? 0; - - const recordsCount = firstQueryResult?.edges.length ?? 0; - - const recordIdSet = new Set( - firstQueryResult?.edges?.map((edge) => edge.node.id) ?? [], - ); - - const remainingCount = totalCount - recordsCount; - - const remainingPages = Math.ceil(remainingCount / pageSize); - - let lastCursor = firstQueryResult?.pageInfo.endCursor ?? null; - - for (let pageIndex = 0; pageIndex < remainingPages; pageIndex++) { - if (lastCursor === null) { - break; - } - - const rawResult = await fetchMore?.({ - variables: { - lastCursor: lastCursor, - limit: pageSize, - }, - }); - - const fetchMoreResult = rawResult?.data?.[objectMetadataItem.namePlural]; - - for (const edge of fetchMoreResult.edges) { - recordIdSet.add(edge.node.id); - } - - if (fetchMoreResult.pageInfo.hasNextPage === false) { - break; - } - - lastCursor = fetchMoreResult.pageInfo.endCursor ?? null; - } - - const recordIds = Array.from(recordIdSet); - - return recordIds; - }, [fetchMore, findManyRecords, objectMetadataItem.namePlural, pageSize]); - - return { - fetchAllRecordIds, - }; -}; diff --git a/packages/twenty-front/src/modules/object-record/hooks/useLazyFetchAllRecords.ts b/packages/twenty-front/src/modules/object-record/hooks/useLazyFetchAllRecords.ts new file mode 100644 index 000000000..1bc1c3f5d --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/hooks/useLazyFetchAllRecords.ts @@ -0,0 +1,141 @@ +import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; +import { UseFindManyRecordsParams } from '@/object-record/hooks/useFetchMoreRecordsWithPagination'; +import { useLazyFindManyRecords } from '@/object-record/hooks/useLazyFindManyRecords'; +import { useCallback, useState } from 'react'; +import { isDefined } from '~/utils/isDefined'; +import { sleep } from '~/utils/sleep'; +import { DEFAULT_QUERY_PAGE_SIZE } from '@/object-record/constants/DefaultQueryPageSize'; + +type UseLazyFetchAllRecordIdsParams = Omit< + UseFindManyRecordsParams, + 'skip' +> & { + pageSize?: number; + delayMs?: number; + maximumRequests?: number; +}; + +type ExportProgress = { + exportedRecordCount?: number; + totalRecordCount?: number; + displayType: 'percentage' | 'number'; +}; + +export const useLazyFetchAllRecords = ({ + objectNameSingular, + filter, + orderBy, + limit = DEFAULT_QUERY_PAGE_SIZE, + delayMs = 0, + maximumRequests = 100, + recordGqlFields, +}: UseLazyFetchAllRecordIdsParams) => { + const [isDownloading, setIsDownloading] = useState(false); + const [progress, setProgress] = useState({ + displayType: 'number', + }); + const { fetchMore, findManyRecords } = useLazyFindManyRecords({ + objectNameSingular, + filter, + orderBy, + limit, + recordGqlFields, + }); + + const { objectMetadataItem } = useObjectMetadataItem({ + objectNameSingular, + }); + + const fetchAllRecords = useCallback(async () => { + if (!isDefined(findManyRecords)) { + return []; + } + setIsDownloading(true); + + const findManyRecordsDataResult = await findManyRecords(); + + const firstQueryResult = + findManyRecordsDataResult?.data?.[objectMetadataItem.namePlural]; + + const totalCount = firstQueryResult?.totalCount ?? 0; + + const recordsCount = firstQueryResult?.edges.length ?? 0; + + const records = firstQueryResult?.edges?.map((edge) => edge.node) ?? []; + + setProgress({ + exportedRecordCount: recordsCount, + totalRecordCount: totalCount, + displayType: totalCount ? 'percentage' : 'number', + }); + + const remainingCount = totalCount - recordsCount; + + const remainingPages = Math.ceil(remainingCount / limit); + + let lastCursor = firstQueryResult?.pageInfo.endCursor ?? null; + + for ( + let pageIndex = 0; + pageIndex < Math.min(maximumRequests, remainingPages); + pageIndex++ + ) { + if (lastCursor === null) { + break; + } + + if (!isDefined(fetchMore)) { + break; + } + + if (delayMs > 0) { + await sleep(delayMs); + } + + const rawResult = await fetchMore({ + variables: { + lastCursor: lastCursor, + limit, + }, + }); + + const fetchMoreResult = rawResult?.data?.[objectMetadataItem.namePlural]; + + for (const edge of fetchMoreResult.edges) { + records.push(edge.node); + } + + setProgress({ + exportedRecordCount: records.length, + totalRecordCount: totalCount, + displayType: totalCount ? 'percentage' : 'number', + }); + + if (fetchMoreResult.pageInfo.hasNextPage === false) { + break; + } + + lastCursor = fetchMoreResult.pageInfo.endCursor ?? null; + } + + setIsDownloading(false); + setProgress({ + displayType: 'number', + }); + + return records; + }, [ + delayMs, + fetchMore, + findManyRecords, + objectMetadataItem.namePlural, + limit, + maximumRequests, + ]); + + return { + progress, + isDownloading, + fetchAllRecords, + }; +}; diff --git a/packages/twenty-front/src/modules/object-record/record-index/export/hooks/__tests__/useExportFetchRecords.test.ts b/packages/twenty-front/src/modules/object-record/record-index/export/hooks/__tests__/useExportFetchRecords.test.ts index 6621a234b..21e9775c0 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/export/hooks/__tests__/useExportFetchRecords.test.ts +++ b/packages/twenty-front/src/modules/object-record/record-index/export/hooks/__tests__/useExportFetchRecords.test.ts @@ -6,26 +6,14 @@ import { useExportFetchRecords, } from '../useExportFetchRecords'; -import { PERSON_FRAGMENT_WITH_DEPTH_ZERO_RELATIONS } from '@/object-record/hooks/__mocks__/personFragments'; import { useObjectOptionsForBoard } from '@/object-record/object-options-dropdown/hooks/useObjectOptionsForBoard'; import { recordGroupFieldMetadataComponentState } from '@/object-record/record-group/states/recordGroupFieldMetadataComponentState'; import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2'; import { ViewType } from '@/views/types/ViewType'; -import { MockedResponse } from '@apollo/client/testing'; import { expect } from '@storybook/test'; -import gql from 'graphql-tag'; import { getJestMetadataAndApolloMocksAndActionMenuWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksAndContextStoreWrapper'; import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; - -const defaultResponseData = { - pageInfo: { - hasNextPage: false, - hasPreviousPage: false, - startCursor: '', - endCursor: '', - }, - totalCount: 1, -}; +import { useLazyFetchAllRecords } from '@/object-record/hooks/useLazyFetchAllRecords'; const mockPerson = { __typename: 'Person', @@ -76,62 +64,8 @@ const mockPerson = { workPreference: 'workPreference', }; -const mocks: MockedResponse[] = [ - { - request: { - query: gql` - query FindManyPeople( - $filter: PersonFilterInput - $orderBy: [PersonOrderByInput] - $lastCursor: String - $limit: Int - ) { - people( - filter: $filter - orderBy: $orderBy - first: $limit - after: $lastCursor - ) { - edges { - node { - ${PERSON_FRAGMENT_WITH_DEPTH_ZERO_RELATIONS} - } - cursor - } - pageInfo { - hasNextPage - hasPreviousPage - startCursor - endCursor - } - totalCount - } - } - `, - variables: { - filter: {}, - limit: 30, - orderBy: [{ position: 'AscNullsFirst' }], - }, - }, - result: jest.fn(() => ({ - data: { - people: { - ...defaultResponseData, - edges: [ - { - node: mockPerson, - cursor: '1', - }, - ], - }, - }, - })), - }, -]; - -const WrapperWithResponse = getJestMetadataAndApolloMocksAndActionMenuWrapper({ - apolloMocks: mocks, +const Wrapper = getJestMetadataAndApolloMocksAndActionMenuWrapper({ + apolloMocks: [], componentInstanceId: 'recordIndexId', contextStoreTargetedRecordsRule: { mode: 'selection', @@ -140,43 +74,36 @@ const WrapperWithResponse = getJestMetadataAndApolloMocksAndActionMenuWrapper({ contextStoreCurrentObjectMetadataNameSingular: 'person', }); -const graphqlEmptyResponse = [ - { - ...mocks[0], - result: jest.fn(() => ({ - data: { - people: { - ...defaultResponseData, - edges: [], - }, - }, - })), - }, -]; - -const WrapperWithEmptyResponse = - getJestMetadataAndApolloMocksAndActionMenuWrapper({ - apolloMocks: graphqlEmptyResponse, - componentInstanceId: 'recordIndexId', - contextStoreTargetedRecordsRule: { - mode: 'selection', - selectedRecordIds: [], - }, - contextStoreCurrentObjectMetadataNameSingular: 'person', - }); +jest.mock('@/object-record/hooks/useLazyFetchAllRecords', () => ({ + useLazyFetchAllRecords: jest.fn(), +})); describe('useRecordData', () => { const recordIndexId = 'people'; const objectMetadataItem = generatedMockObjectMetadataItems.find( (item) => item.nameSingular === 'person', ); + let mockFetchAllRecords: jest.Mock; + + beforeEach(() => { + // Mock the hook's implementation + mockFetchAllRecords = jest.fn(); + (useLazyFetchAllRecords as jest.Mock).mockReturnValue({ + progress: 100, + isDownloading: false, + fetchAllRecords: mockFetchAllRecords, // Mock the function + }); + }); if (!objectMetadataItem) { throw new Error('Object metadata item not found'); } + describe('data fetching', () => { it('should handle no records', async () => { const callback = jest.fn(); + mockFetchAllRecords.mockReturnValue([]); + const { result } = renderHook( () => useExportFetchRecords({ @@ -188,7 +115,7 @@ describe('useRecordData', () => { viewType: ViewType.Kanban, }), { - wrapper: WrapperWithEmptyResponse, + wrapper: Wrapper, }, ); @@ -203,6 +130,7 @@ describe('useRecordData', () => { it('should call the callback function with fetched data', async () => { const callback = jest.fn(); + mockFetchAllRecords.mockReturnValue([mockPerson]); const { result } = renderHook( () => useExportFetchRecords({ @@ -212,7 +140,7 @@ describe('useRecordData', () => { pageSize: 30, delayMs: 0, }), - { wrapper: WrapperWithResponse }, + { wrapper: Wrapper }, ); await act(async () => { @@ -226,6 +154,7 @@ describe('useRecordData', () => { it('should call the callback function with kanban field included as column if view type is kanban', async () => { const callback = jest.fn(); + mockFetchAllRecords.mockReturnValue([mockPerson]); const { result } = renderHook( () => { const [recordGroupFieldMetadata, setRecordGroupFieldMetadata] = @@ -254,7 +183,7 @@ describe('useRecordData', () => { }; }, { - wrapper: WrapperWithResponse, + wrapper: Wrapper, }, ); @@ -316,6 +245,7 @@ describe('useRecordData', () => { it('should not call the callback function with kanban field included as column if view type is table', async () => { const callback = jest.fn(); + mockFetchAllRecords.mockReturnValue([mockPerson]); const { result } = renderHook( () => { const [recordGroupFieldMetadata, setRecordGroupFieldMetadata] = @@ -345,7 +275,7 @@ describe('useRecordData', () => { }; }, { - wrapper: WrapperWithResponse, + wrapper: Wrapper, }, ); diff --git a/packages/twenty-front/src/modules/object-record/record-index/export/hooks/useExportFetchRecords.ts b/packages/twenty-front/src/modules/object-record/record-index/export/hooks/useExportFetchRecords.ts index bc256a8bc..985d22628 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/export/hooks/useExportFetchRecords.ts +++ b/packages/twenty-front/src/modules/object-record/record-index/export/hooks/useExportFetchRecords.ts @@ -1,15 +1,11 @@ -import { useEffect, useState } from 'react'; - import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata'; import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition'; import { ObjectRecord } from '@/object-record/types/ObjectRecord'; -import { isDefined } from '~/utils/isDefined'; import { contextStoreFiltersComponentState } from '@/context-store/states/contextStoreFiltersComponentState'; import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState'; import { computeContextStoreFilters } from '@/context-store/utils/computeContextStoreFilters'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; -import { useLazyFindManyRecords } from '@/object-record/hooks/useLazyFindManyRecords'; import { EXPORT_TABLE_DATA_DEFAULT_PAGE_SIZE } from '@/object-record/object-options-dropdown/constants/ExportTableDataDefaultPageSize'; import { useObjectOptionsForBoard } from '@/object-record/object-options-dropdown/hooks/useObjectOptionsForBoard'; import { recordGroupFieldMetadataComponentState } from '@/object-record/record-group/states/recordGroupFieldMetadataComponentState'; @@ -17,6 +13,7 @@ import { useFindManyRecordIndexTableParams } from '@/object-record/record-index/ import { visibleTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/visibleTableColumnsComponentSelector'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { ViewType } from '@/views/types/ViewType'; +import { useLazyFetchAllRecords } from '@/object-record/hooks/useLazyFetchAllRecords'; export const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); @@ -38,12 +35,6 @@ export type UseRecordDataOptions = { viewType?: ViewType; }; -type ExportProgress = { - exportedRecordCount?: number; - totalRecordCount?: number; - displayType: 'percentage' | 'number'; -}; - export const useExportFetchRecords = ({ objectMetadataItem, delayMs, @@ -53,14 +44,6 @@ export const useExportFetchRecords = ({ callback, viewType = ViewType.Table, }: UseRecordDataOptions) => { - const [isDownloading, setIsDownloading] = useState(false); - const [inflight, setInflight] = useState(false); - const [pageCount, setPageCount] = useState(0); - const [progress, setProgress] = useState({ - displayType: 'number', - }); - const [previousRecordCount, setPreviousRecordCount] = useState(0); - const { hiddenBoardFields } = useObjectOptionsForBoard({ objectNameSingular: objectMetadataItem.nameSingular, recordBoardId: recordIndexId, @@ -99,92 +82,31 @@ export const useExportFetchRecords = ({ recordIndexId, ); - const { findManyRecords, totalCount, records, fetchMoreRecords, loading } = - useLazyFindManyRecords({ - ...findManyRecordsParams, - filter: queryFilter, - limit: pageSize, - }); + const finalColumns = [ + ...columns, + ...(hiddenKanbanFieldColumn && viewType === ViewType.Kanban + ? [hiddenKanbanFieldColumn] + : []), + ]; - useEffect(() => { - const fetchNextPage = async () => { - setInflight(true); - setPreviousRecordCount(records.length); - - await fetchMoreRecords(); - - setPageCount((state) => state + 1); - setProgress({ - exportedRecordCount: records.length, - totalRecordCount: totalCount, - displayType: totalCount ? 'percentage' : 'number', - }); - await sleep(delayMs); - setInflight(false); - }; - - if (!isDownloading || inflight || loading) { - return; - } - - if ( - pageCount >= maximumRequests || - (isDefined(totalCount) && records.length >= totalCount) - ) { - setPageCount(0); - - const complete = () => { - setPageCount(0); - setPreviousRecordCount(0); - setIsDownloading(false); - setProgress({ - displayType: 'number', - }); - }; - - const finalColumns = [ - ...columns, - ...(hiddenKanbanFieldColumn && viewType === ViewType.Kanban - ? [hiddenKanbanFieldColumn] - : []), - ]; - - const res = callback(records, finalColumns); - - if (res instanceof Promise) { - res.then(complete); - } else { - complete(); - } - } else { - fetchNextPage(); - } - }, [ + const { progress, isDownloading, fetchAllRecords } = useLazyFetchAllRecords({ + ...findManyRecordsParams, + filter: queryFilter, + limit: pageSize, delayMs, - fetchMoreRecords, - inflight, - isDownloading, - pageCount, - records, - totalCount, - columns, maximumRequests, - pageSize, - loading, - callback, - previousRecordCount, - hiddenKanbanFieldColumn, - viewType, - ]); + }); + + const getTableData = async () => { + const result = await fetchAllRecords(); + if (result.length > 0) { + callback(result, finalColumns); + } + }; return { progress, isDownloading, - getTableData: () => { - setPageCount(0); - setPreviousRecordCount(0); - setIsDownloading(true); - findManyRecords?.(); - }, + getTableData: getTableData, }; };