mirror of
https://github.com/twentyhq/twenty.git
synced 2024-12-22 19:41:53 +03:00
parent
f482b459a9
commit
5586270df4
@ -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();
|
||||
|
||||
|
@ -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),
|
||||
);
|
||||
});
|
||||
});
|
@ -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]);
|
||||
});
|
||||
});
|
@ -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,
|
||||
};
|
||||
};
|
@ -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,
|
||||
};
|
||||
};
|
@ -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,
|
||||
},
|
||||
);
|
||||
|
||||
|
@ -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<ExportProgress>({
|
||||
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,
|
||||
};
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user