Remove buggy dependencies (#9115)

Fixes double download
This commit is contained in:
martmull 2024-12-18 18:05:33 +01:00 committed by GitHub
parent f482b459a9
commit 5586270df4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 368 additions and 382 deletions

View File

@ -13,13 +13,14 @@ import { useFavorites } from '@/favorites/hooks/useFavorites';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { DELETE_MAX_COUNT } from '@/object-record/constants/DeleteMaxCount'; import { DELETE_MAX_COUNT } from '@/object-record/constants/DeleteMaxCount';
import { useDeleteManyRecords } from '@/object-record/hooks/useDeleteManyRecords'; import { useDeleteManyRecords } from '@/object-record/hooks/useDeleteManyRecords';
import { useFetchAllRecordIds } from '@/object-record/hooks/useFetchAllRecordIds';
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable'; import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal'; import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer'; import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useCallback, useContext, useState } from 'react'; import { useCallback, useContext, useState } from 'react';
import { IconTrash, isDefined } from 'twenty-ui'; 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 = ({ export const useDeleteMultipleRecordsAction = ({
objectMetadataItem, objectMetadataItem,
@ -60,15 +61,18 @@ export const useDeleteMultipleRecordsAction = ({
objectMetadataItem, objectMetadataItem,
); );
const { fetchAllRecordIds } = useFetchAllRecordIds({ const { fetchAllRecords: fetchAllRecordIds } = useLazyFetchAllRecords({
objectNameSingular: objectMetadataItem.nameSingular, objectNameSingular: objectMetadataItem.nameSingular,
filter: graphqlFilter, filter: graphqlFilter,
limit: DEFAULT_QUERY_PAGE_SIZE,
recordGqlFields: { id: true },
}); });
const { closeRightDrawer } = useRightDrawer(); const { closeRightDrawer } = useRightDrawer();
const handleDeleteClick = useCallback(async () => { const handleDeleteClick = useCallback(async () => {
const recordIdsToDelete = await fetchAllRecordIds(); const recordsToDelete = await fetchAllRecordIds();
const recordIdsToDelete = recordsToDelete.map((record) => record.id);
resetTableRowSelection(); resetTableRowSelection();

View File

@ -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),
);
});
});

View File

@ -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]);
});
});

View File

@ -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<T> = Omit<
UseFindManyRecordsParams<T>,
'skip'
> & { pageSize?: number };
export const useFetchAllRecordIds = <T>({
objectNameSingular,
filter,
orderBy,
pageSize = DEFAULT_QUERY_PAGE_SIZE,
}: UseLazyFetchAllRecordIdsParams<T>) => {
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,
};
};

View File

@ -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<T> = Omit<
UseFindManyRecordsParams<T>,
'skip'
> & {
pageSize?: number;
delayMs?: number;
maximumRequests?: number;
};
type ExportProgress = {
exportedRecordCount?: number;
totalRecordCount?: number;
displayType: 'percentage' | 'number';
};
export const useLazyFetchAllRecords = <T>({
objectNameSingular,
filter,
orderBy,
limit = DEFAULT_QUERY_PAGE_SIZE,
delayMs = 0,
maximumRequests = 100,
recordGqlFields,
}: UseLazyFetchAllRecordIdsParams<T>) => {
const [isDownloading, setIsDownloading] = useState(false);
const [progress, setProgress] = useState<ExportProgress>({
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,
};
};

View File

@ -6,26 +6,14 @@ import {
useExportFetchRecords, useExportFetchRecords,
} from '../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 { useObjectOptionsForBoard } from '@/object-record/object-options-dropdown/hooks/useObjectOptionsForBoard';
import { recordGroupFieldMetadataComponentState } from '@/object-record/record-group/states/recordGroupFieldMetadataComponentState'; import { recordGroupFieldMetadataComponentState } from '@/object-record/record-group/states/recordGroupFieldMetadataComponentState';
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2'; import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
import { ViewType } from '@/views/types/ViewType'; import { ViewType } from '@/views/types/ViewType';
import { MockedResponse } from '@apollo/client/testing';
import { expect } from '@storybook/test'; import { expect } from '@storybook/test';
import gql from 'graphql-tag';
import { getJestMetadataAndApolloMocksAndActionMenuWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksAndContextStoreWrapper'; import { getJestMetadataAndApolloMocksAndActionMenuWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksAndContextStoreWrapper';
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
import { useLazyFetchAllRecords } from '@/object-record/hooks/useLazyFetchAllRecords';
const defaultResponseData = {
pageInfo: {
hasNextPage: false,
hasPreviousPage: false,
startCursor: '',
endCursor: '',
},
totalCount: 1,
};
const mockPerson = { const mockPerson = {
__typename: 'Person', __typename: 'Person',
@ -76,62 +64,8 @@ const mockPerson = {
workPreference: 'workPreference', workPreference: 'workPreference',
}; };
const mocks: MockedResponse[] = [ const Wrapper = getJestMetadataAndApolloMocksAndActionMenuWrapper({
{ apolloMocks: [],
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,
componentInstanceId: 'recordIndexId', componentInstanceId: 'recordIndexId',
contextStoreTargetedRecordsRule: { contextStoreTargetedRecordsRule: {
mode: 'selection', mode: 'selection',
@ -140,43 +74,36 @@ const WrapperWithResponse = getJestMetadataAndApolloMocksAndActionMenuWrapper({
contextStoreCurrentObjectMetadataNameSingular: 'person', contextStoreCurrentObjectMetadataNameSingular: 'person',
}); });
const graphqlEmptyResponse = [ jest.mock('@/object-record/hooks/useLazyFetchAllRecords', () => ({
{ useLazyFetchAllRecords: jest.fn(),
...mocks[0], }));
result: jest.fn(() => ({
data: {
people: {
...defaultResponseData,
edges: [],
},
},
})),
},
];
const WrapperWithEmptyResponse =
getJestMetadataAndApolloMocksAndActionMenuWrapper({
apolloMocks: graphqlEmptyResponse,
componentInstanceId: 'recordIndexId',
contextStoreTargetedRecordsRule: {
mode: 'selection',
selectedRecordIds: [],
},
contextStoreCurrentObjectMetadataNameSingular: 'person',
});
describe('useRecordData', () => { describe('useRecordData', () => {
const recordIndexId = 'people'; const recordIndexId = 'people';
const objectMetadataItem = generatedMockObjectMetadataItems.find( const objectMetadataItem = generatedMockObjectMetadataItems.find(
(item) => item.nameSingular === 'person', (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) { if (!objectMetadataItem) {
throw new Error('Object metadata item not found'); throw new Error('Object metadata item not found');
} }
describe('data fetching', () => { describe('data fetching', () => {
it('should handle no records', async () => { it('should handle no records', async () => {
const callback = jest.fn(); const callback = jest.fn();
mockFetchAllRecords.mockReturnValue([]);
const { result } = renderHook( const { result } = renderHook(
() => () =>
useExportFetchRecords({ useExportFetchRecords({
@ -188,7 +115,7 @@ describe('useRecordData', () => {
viewType: ViewType.Kanban, viewType: ViewType.Kanban,
}), }),
{ {
wrapper: WrapperWithEmptyResponse, wrapper: Wrapper,
}, },
); );
@ -203,6 +130,7 @@ describe('useRecordData', () => {
it('should call the callback function with fetched data', async () => { it('should call the callback function with fetched data', async () => {
const callback = jest.fn(); const callback = jest.fn();
mockFetchAllRecords.mockReturnValue([mockPerson]);
const { result } = renderHook( const { result } = renderHook(
() => () =>
useExportFetchRecords({ useExportFetchRecords({
@ -212,7 +140,7 @@ describe('useRecordData', () => {
pageSize: 30, pageSize: 30,
delayMs: 0, delayMs: 0,
}), }),
{ wrapper: WrapperWithResponse }, { wrapper: Wrapper },
); );
await act(async () => { 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 () => { it('should call the callback function with kanban field included as column if view type is kanban', async () => {
const callback = jest.fn(); const callback = jest.fn();
mockFetchAllRecords.mockReturnValue([mockPerson]);
const { result } = renderHook( const { result } = renderHook(
() => { () => {
const [recordGroupFieldMetadata, setRecordGroupFieldMetadata] = 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 () => { it('should not call the callback function with kanban field included as column if view type is table', async () => {
const callback = jest.fn(); const callback = jest.fn();
mockFetchAllRecords.mockReturnValue([mockPerson]);
const { result } = renderHook( const { result } = renderHook(
() => { () => {
const [recordGroupFieldMetadata, setRecordGroupFieldMetadata] = const [recordGroupFieldMetadata, setRecordGroupFieldMetadata] =
@ -345,7 +275,7 @@ describe('useRecordData', () => {
}; };
}, },
{ {
wrapper: WrapperWithResponse, wrapper: Wrapper,
}, },
); );

View File

@ -1,15 +1,11 @@
import { useEffect, useState } from 'react';
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata'; import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition'; import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
import { ObjectRecord } from '@/object-record/types/ObjectRecord'; import { ObjectRecord } from '@/object-record/types/ObjectRecord';
import { isDefined } from '~/utils/isDefined';
import { contextStoreFiltersComponentState } from '@/context-store/states/contextStoreFiltersComponentState'; import { contextStoreFiltersComponentState } from '@/context-store/states/contextStoreFiltersComponentState';
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState'; import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
import { computeContextStoreFilters } from '@/context-store/utils/computeContextStoreFilters'; import { computeContextStoreFilters } from '@/context-store/utils/computeContextStoreFilters';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; 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 { 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 { useObjectOptionsForBoard } from '@/object-record/object-options-dropdown/hooks/useObjectOptionsForBoard';
import { recordGroupFieldMetadataComponentState } from '@/object-record/record-group/states/recordGroupFieldMetadataComponentState'; 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 { visibleTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/visibleTableColumnsComponentSelector';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { ViewType } from '@/views/types/ViewType'; import { ViewType } from '@/views/types/ViewType';
import { useLazyFetchAllRecords } from '@/object-record/hooks/useLazyFetchAllRecords';
export const sleep = (ms: number) => export const sleep = (ms: number) =>
new Promise((resolve) => setTimeout(resolve, ms)); new Promise((resolve) => setTimeout(resolve, ms));
@ -38,12 +35,6 @@ export type UseRecordDataOptions = {
viewType?: ViewType; viewType?: ViewType;
}; };
type ExportProgress = {
exportedRecordCount?: number;
totalRecordCount?: number;
displayType: 'percentage' | 'number';
};
export const useExportFetchRecords = ({ export const useExportFetchRecords = ({
objectMetadataItem, objectMetadataItem,
delayMs, delayMs,
@ -53,14 +44,6 @@ export const useExportFetchRecords = ({
callback, callback,
viewType = ViewType.Table, viewType = ViewType.Table,
}: UseRecordDataOptions) => { }: UseRecordDataOptions) => {
const [isDownloading, setIsDownloading] = useState(false);
const [inflight, setInflight] = useState(false);
const [pageCount, setPageCount] = useState(0);
const [progress, setProgress] = useState<ExportProgress>({
displayType: 'number',
});
const [previousRecordCount, setPreviousRecordCount] = useState(0);
const { hiddenBoardFields } = useObjectOptionsForBoard({ const { hiddenBoardFields } = useObjectOptionsForBoard({
objectNameSingular: objectMetadataItem.nameSingular, objectNameSingular: objectMetadataItem.nameSingular,
recordBoardId: recordIndexId, recordBoardId: recordIndexId,
@ -99,49 +82,6 @@ export const useExportFetchRecords = ({
recordIndexId, recordIndexId,
); );
const { findManyRecords, totalCount, records, fetchMoreRecords, loading } =
useLazyFindManyRecords({
...findManyRecordsParams,
filter: queryFilter,
limit: pageSize,
});
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 = [ const finalColumns = [
...columns, ...columns,
...(hiddenKanbanFieldColumn && viewType === ViewType.Kanban ...(hiddenKanbanFieldColumn && viewType === ViewType.Kanban
@ -149,42 +89,24 @@ export const useExportFetchRecords = ({
: []), : []),
]; ];
const res = callback(records, finalColumns); const { progress, isDownloading, fetchAllRecords } = useLazyFetchAllRecords({
...findManyRecordsParams,
if (res instanceof Promise) { filter: queryFilter,
res.then(complete); limit: pageSize,
} else {
complete();
}
} else {
fetchNextPage();
}
}, [
delayMs, delayMs,
fetchMoreRecords,
inflight,
isDownloading,
pageCount,
records,
totalCount,
columns,
maximumRequests, maximumRequests,
pageSize, });
loading,
callback, const getTableData = async () => {
previousRecordCount, const result = await fetchAllRecords();
hiddenKanbanFieldColumn, if (result.length > 0) {
viewType, callback(result, finalColumns);
]); }
};
return { return {
progress, progress,
isDownloading, isDownloading,
getTableData: () => { getTableData: getTableData,
setPageCount(0);
setPreviousRecordCount(0);
setIsDownloading(true);
findManyRecords?.();
},
}; };
}; };