diff --git a/packages/twenty-front/src/modules/object-record/graphql/types/RecordGqlOperationVariables.ts b/packages/twenty-front/src/modules/object-record/graphql/types/RecordGqlOperationVariables.ts index 3abc3358a..c8ae83ae7 100644 --- a/packages/twenty-front/src/modules/object-record/graphql/types/RecordGqlOperationVariables.ts +++ b/packages/twenty-front/src/modules/object-record/graphql/types/RecordGqlOperationVariables.ts @@ -9,6 +9,5 @@ export type RecordGqlOperationVariables = { cursorFilter?: { cursor: string; cursorDirection: QueryCursorDirection; - limit: number; }; }; diff --git a/packages/twenty-front/src/modules/object-record/hooks/useFindManyRecords.ts b/packages/twenty-front/src/modules/object-record/hooks/useFindManyRecords.ts index 65295a050..3d8e2329f 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useFindManyRecords.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useFindManyRecords.ts @@ -82,7 +82,7 @@ export const useFindManyRecords = ({ : {}), orderBy, lastCursor: cursorFilter?.cursor ?? undefined, - limit: cursorFilter?.limit ?? limit, + limit, }, fetchPolicy: fetchPolicy, onCompleted: handleFindManyRecordsCompleted, diff --git a/packages/twenty-front/src/modules/object-record/multiple-objects/hooks/__tests__/useCombinedFindManyRecords.test.tsx b/packages/twenty-front/src/modules/object-record/multiple-objects/hooks/__tests__/useCombinedFindManyRecords.test.tsx new file mode 100644 index 000000000..5a03a45a0 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/multiple-objects/hooks/__tests__/useCombinedFindManyRecords.test.tsx @@ -0,0 +1,570 @@ +import { gql } from '@apollo/client'; +import { renderHook, waitFor } from '@testing-library/react'; +import { useSetRecoilState } from 'recoil'; + +import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; +import { RecordGqlFields } from '@/object-record/graphql/types/RecordGqlFields'; +import { RecordGqlOperationSignature } from '@/object-record/graphql/types/RecordGqlOperationSignature'; +import { useCombinedFindManyRecords } from '@/object-record/multiple-objects/hooks/useCombinedFindManyRecords'; +import { useGenerateCombinedFindManyRecordsQuery } from '@/object-record/multiple-objects/hooks/useGenerateCombinedFindManyRecordsQuery'; +import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; + +jest.mock( + '@/object-record/multiple-objects/hooks/useGenerateCombinedFindManyRecordsQuery', + () => ({ + useGenerateCombinedFindManyRecordsQuery: jest.fn(), + }), +); + +const mockQuery = gql` + query CombinedFindManyRecords( + $filterPerson: PersonFilterInput + $filterCompany: CompanyFilterInput + $orderByPerson: [PersonOrderByInput] + $orderByCompany: [CompanyOrderByInput] + $firstPerson: Int + $lastPerson: Int + $afterPerson: String + $beforePerson: String + $firstCompany: Int + $lastCompany: Int + $afterCompany: String + $beforeCompany: String + $limitPerson: Int + $limitCompany: Int + ) { + people( + filter: $filterPerson + orderBy: $orderByPerson + first: $firstPerson + after: $afterPerson + last: $lastPerson + before: $beforePerson + limit: $limitPerson + ) { + edges { + node { + __typename + id + name { + firstName + lastName + } + } + cursor + } + pageInfo { + hasNextPage + hasPreviousPage + startCursor + endCursor + } + totalCount + } + companies( + filter: $filterCompany + orderBy: $orderByCompany + first: $firstCompany + after: $afterCompany + last: $lastCompany + before: $beforeCompany + limit: $limitCompany + ) { + edges { + node { + __typename + id + name + } + cursor + } + pageInfo { + hasNextPage + hasPreviousPage + startCursor + endCursor + } + totalCount + } + } +`; + +type RenderUseCombinedFindManyRecordsHookParams = { + operationSignatures: RecordGqlOperationSignature[]; + mockVariables?: Record; + mockResponseData?: Record; + skip?: boolean; + expectedResult?: Record; + mockQueryResult?: any; +}; + +const renderUseCombinedFindManyRecordsHook = async ({ + operationSignatures, + mockVariables = {}, + mockResponseData, + skip = false, + expectedResult = {}, + mockQueryResult = mockQuery, +}: RenderUseCombinedFindManyRecordsHookParams) => { + (useGenerateCombinedFindManyRecordsQuery as jest.Mock).mockReturnValue( + mockQueryResult, + ); + + const mocks = [ + { + request: { + query: mockQuery, + variables: mockVariables, + }, + result: { + data: mockResponseData, + }, + }, + ]; + + const { result } = renderHook( + () => { + const setObjectMetadataItems = useSetRecoilState( + objectMetadataItemsState, + ); + setObjectMetadataItems(generatedMockObjectMetadataItems); + + return useCombinedFindManyRecords({ + operationSignatures, + skip, + }); + }, + { + wrapper: getJestMetadataAndApolloMocksWrapper({ apolloMocks: mocks }), + }, + ); + + expect(result.current.loading).toBe(!skip); + expect(result.current.result).toEqual({}); + + await waitFor(() => { + expect(result.current.loading).toBe(false); + }); + + expect(result.current.result).toEqual(expectedResult); + + return result; +}; + +describe('useCombinedFindManyRecords', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should return records for multiple objects', async () => { + const mockResponseData = { + people: { + edges: [ + { + node: { + __typename: 'Person', + id: '1', + name: { + firstName: 'John', + lastName: 'Doe', + }, + }, + cursor: 'cursor1', + }, + ], + pageInfo: { + hasNextPage: false, + hasPreviousPage: false, + startCursor: 'cursor1', + endCursor: 'cursor1', + }, + totalCount: 1, + }, + companies: { + edges: [ + { + node: { + __typename: 'Company', + id: '1', + name: 'Twenty', + }, + cursor: 'cursor1', + }, + ], + pageInfo: { + hasNextPage: false, + hasPreviousPage: false, + startCursor: 'cursor1', + endCursor: 'cursor1', + }, + totalCount: 1, + }, + }; + + await renderUseCombinedFindManyRecordsHook({ + operationSignatures: [ + { + objectNameSingular: 'person', + fields: { + id: true, + name: { + firstName: true, + lastName: true, + }, + } as RecordGqlFields, + variables: {}, + }, + { + objectNameSingular: 'company', + fields: { + id: true, + name: true, + } as RecordGqlFields, + variables: {}, + }, + ], + mockResponseData, + expectedResult: { + people: [ + { + __typename: 'Person', + id: '1', + name: { + firstName: 'John', + lastName: 'Doe', + }, + }, + ], + companies: [ + { + __typename: 'Company', + id: '1', + name: 'Twenty', + }, + ], + }, + }); + }); + + it('should handle forward pagination with after cursor and first limit', async () => { + const mockResponseData = { + people: { + edges: [ + { + node: { + __typename: 'Person', + id: '1', + name: { + firstName: 'John', + lastName: 'Doe', + }, + }, + cursor: 'cursor1', + }, + ], + pageInfo: { + hasNextPage: true, + hasPreviousPage: true, + startCursor: 'cursor1', + endCursor: 'cursor1', + }, + totalCount: 10, + }, + }; + + await renderUseCombinedFindManyRecordsHook({ + operationSignatures: [ + { + objectNameSingular: 'person', + fields: { + id: true, + name: { + firstName: true, + lastName: true, + }, + } as RecordGqlFields, + variables: { + limit: 1, + cursorFilter: { + cursor: 'previousCursor', + cursorDirection: 'after', + }, + }, + }, + ], + mockVariables: { + firstPerson: 1, + afterPerson: 'previousCursor', + }, + mockResponseData, + expectedResult: { + people: [ + { + __typename: 'Person', + id: '1', + name: { + firstName: 'John', + lastName: 'Doe', + }, + }, + ], + }, + }); + }); + + it('should handle backward pagination with before cursor and last limit', async () => { + const mockResponseData = { + people: { + edges: [ + { + node: { + __typename: 'Person', + id: '2', + name: { + firstName: 'Jane', + lastName: 'Smith', + }, + }, + cursor: 'cursor2', + }, + ], + pageInfo: { + hasNextPage: true, + hasPreviousPage: true, + startCursor: 'cursor2', + endCursor: 'cursor2', + }, + totalCount: 10, + }, + }; + + await renderUseCombinedFindManyRecordsHook({ + operationSignatures: [ + { + objectNameSingular: 'person', + fields: { + id: true, + name: { + firstName: true, + lastName: true, + }, + } as RecordGqlFields, + variables: { + limit: 1, + cursorFilter: { + cursor: 'nextCursor', + cursorDirection: 'before', + }, + }, + }, + ], + mockVariables: { + lastPerson: 1, + beforePerson: 'nextCursor', + }, + mockResponseData, + expectedResult: { + people: [ + { + __typename: 'Person', + id: '2', + name: { + firstName: 'Jane', + lastName: 'Smith', + }, + }, + ], + }, + }); + }); + + it('should handle limit-based pagination without cursor', async () => { + const mockResponseData = { + people: { + edges: [ + { + node: { + __typename: 'Person', + id: '3', + name: { + firstName: 'Alice', + lastName: 'Johnson', + }, + }, + cursor: 'cursor3', + }, + ], + pageInfo: { + hasNextPage: true, + hasPreviousPage: false, + startCursor: 'cursor3', + endCursor: 'cursor3', + }, + totalCount: 10, + }, + }; + + await renderUseCombinedFindManyRecordsHook({ + operationSignatures: [ + { + objectNameSingular: 'person', + fields: { + id: true, + name: { + firstName: true, + lastName: true, + }, + } as RecordGqlFields, + variables: { + limit: 1, + }, + }, + ], + mockVariables: { + limitPerson: 1, + }, + mockResponseData, + expectedResult: { + people: [ + { + __typename: 'Person', + id: '3', + name: { + firstName: 'Alice', + lastName: 'Johnson', + }, + }, + ], + }, + }); + }); + + it('should handle multiple objects with different pagination strategies', async () => { + const mockResponseData = { + people: { + edges: [ + { + node: { + __typename: 'Person', + id: '1', + name: { + firstName: 'John', + lastName: 'Doe', + }, + }, + cursor: 'cursor1', + }, + ], + pageInfo: { + hasNextPage: true, + hasPreviousPage: false, + startCursor: 'cursor1', + endCursor: 'cursor1', + }, + totalCount: 10, + }, + companies: { + edges: [ + { + node: { + __typename: 'Company', + id: '1', + name: 'Twenty', + }, + cursor: 'cursor1', + }, + ], + pageInfo: { + hasNextPage: false, + hasPreviousPage: false, + startCursor: 'cursor1', + endCursor: 'cursor1', + }, + totalCount: 1, + }, + }; + + await renderUseCombinedFindManyRecordsHook({ + operationSignatures: [ + { + objectNameSingular: 'person', + fields: { + id: true, + name: { + firstName: true, + lastName: true, + }, + } as RecordGqlFields, + variables: { + limit: 1, + cursorFilter: { + cursor: 'previousCursor', + cursorDirection: 'after', + }, + }, + }, + { + objectNameSingular: 'company', + fields: { + id: true, + name: true, + } as RecordGqlFields, + variables: { + limit: 1, + }, + }, + ], + mockVariables: { + firstPerson: 1, + afterPerson: 'previousCursor', + limitCompany: 1, + }, + mockResponseData, + expectedResult: { + people: [ + { + __typename: 'Person', + id: '1', + name: { + firstName: 'John', + lastName: 'Doe', + }, + }, + ], + companies: [ + { + __typename: 'Company', + id: '1', + name: 'Twenty', + }, + ], + }, + }); + }); + + it('should handle empty operation signatures', async () => { + await renderUseCombinedFindManyRecordsHook({ + operationSignatures: [], + mockResponseData: {}, + expectedResult: {}, + }); + }); + + it('should handle skip flag', async () => { + await renderUseCombinedFindManyRecordsHook({ + operationSignatures: [ + { + objectNameSingular: 'person', + fields: { + id: true, + } as RecordGqlFields, + variables: {}, + }, + ], + skip: true, + mockResponseData: {}, + expectedResult: {}, + }); + }); +}); diff --git a/packages/twenty-front/src/modules/object-record/multiple-objects/hooks/__tests__/useCombinedFindManyRecordsQueryVariables.test.ts b/packages/twenty-front/src/modules/object-record/multiple-objects/hooks/__tests__/useCombinedFindManyRecordsQueryVariables.test.ts new file mode 100644 index 000000000..e6257fe8d --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/multiple-objects/hooks/__tests__/useCombinedFindManyRecordsQueryVariables.test.ts @@ -0,0 +1,191 @@ +import { RecordGqlFields } from '@/object-record/graphql/types/RecordGqlFields'; +import { RecordGqlOperationSignature } from '@/object-record/graphql/types/RecordGqlOperationSignature'; +import { useCombinedFindManyRecordsQueryVariables } from '@/object-record/multiple-objects/hooks/useCombinedFindManyRecordsQueryVariables'; + +describe('useCombinedFindManyRecordsQueryVariables', () => { + it('should generate variables with after cursor and first limit', () => { + const operationSignatures: RecordGqlOperationSignature[] = [ + { + objectNameSingular: 'person', + fields: { + id: true, + name: { + firstName: true, + lastName: true, + }, + } as RecordGqlFields, + variables: { + filter: { id: { eq: '123' } }, + orderBy: [{ createdAt: 'AscNullsLast' }], + limit: 10, + cursorFilter: { + cursor: 'cursor123', + cursorDirection: 'after', + }, + }, + }, + ]; + + const result = useCombinedFindManyRecordsQueryVariables({ + operationSignatures, + }); + + expect(result).toEqual({ + filterPerson: { id: { eq: '123' } }, + orderByPerson: [{ createdAt: 'AscNullsLast' }], + afterPerson: 'cursor123', + firstPerson: 10, + }); + }); + + it('should generate variables with before cursor and last limit', () => { + const operationSignatures: RecordGqlOperationSignature[] = [ + { + objectNameSingular: 'person', + fields: { + id: true, + name: true, + } as RecordGqlFields, + variables: { + filter: { id: { eq: '123' } }, + orderBy: [{ createdAt: 'AscNullsLast' }], + limit: 10, + cursorFilter: { + cursor: 'cursor123', + cursorDirection: 'before', + }, + }, + }, + ]; + + const result = useCombinedFindManyRecordsQueryVariables({ + operationSignatures, + }); + + expect(result).toEqual({ + filterPerson: { id: { eq: '123' } }, + orderByPerson: [{ createdAt: 'AscNullsLast' }], + beforePerson: 'cursor123', + lastPerson: 10, + }); + }); + + it('should generate variables with limit only (no cursor)', () => { + const operationSignatures: RecordGqlOperationSignature[] = [ + { + objectNameSingular: 'person', + fields: { + id: true, + name: true, + } as RecordGqlFields, + variables: { + filter: { id: { eq: '123' } }, + orderBy: [{ createdAt: 'AscNullsLast' }], + limit: 10, + }, + }, + ]; + + const result = useCombinedFindManyRecordsQueryVariables({ + operationSignatures, + }); + + expect(result).toEqual({ + filterPerson: { id: { eq: '123' } }, + orderByPerson: [{ createdAt: 'AscNullsLast' }], + limitPerson: 10, + }); + }); + + it('should handle multiple objects with different pagination strategies', () => { + const operationSignatures: RecordGqlOperationSignature[] = [ + { + objectNameSingular: 'person', + fields: { + id: true, + } as RecordGqlFields, + variables: { + filter: { id: { eq: '123' } }, + limit: 10, + cursorFilter: { + cursor: 'cursor123', + cursorDirection: 'after', + }, + }, + }, + { + objectNameSingular: 'company', + fields: { + id: true, + } as RecordGqlFields, + variables: { + filter: { name: { eq: 'Twenty' } }, + limit: 20, + }, + }, + ]; + + const result = useCombinedFindManyRecordsQueryVariables({ + operationSignatures, + }); + + expect(result).toEqual({ + filterPerson: { id: { eq: '123' } }, + afterPerson: 'cursor123', + firstPerson: 10, + filterCompany: { name: { eq: 'Twenty' } }, + limitCompany: 20, + }); + }); + + it('should handle empty operation signatures', () => { + const result = useCombinedFindManyRecordsQueryVariables({ + operationSignatures: [], + }); + + expect(result).toEqual({}); + }); + + it('should handle empty variables', () => { + const operationSignatures: RecordGqlOperationSignature[] = [ + { + objectNameSingular: 'person', + fields: { + id: true, + } as RecordGqlFields, + variables: {}, + }, + ]; + + const result = useCombinedFindManyRecordsQueryVariables({ + operationSignatures, + }); + + expect(result).toEqual({}); + }); + + it('should handle cursor without limit', () => { + const operationSignatures: RecordGqlOperationSignature[] = [ + { + objectNameSingular: 'person', + fields: { + id: true, + } as RecordGqlFields, + variables: { + cursorFilter: { + cursor: 'cursor123', + cursorDirection: 'after', + }, + }, + }, + ]; + + const result = useCombinedFindManyRecordsQueryVariables({ + operationSignatures, + }); + + expect(result).toEqual({ + afterPerson: 'cursor123', + }); + }); +}); diff --git a/packages/twenty-front/src/modules/object-record/multiple-objects/hooks/useCombinedFindManyRecords.ts b/packages/twenty-front/src/modules/object-record/multiple-objects/hooks/useCombinedFindManyRecords.ts index b147b5535..bdfc71f2b 100644 --- a/packages/twenty-front/src/modules/object-record/multiple-objects/hooks/useCombinedFindManyRecords.ts +++ b/packages/twenty-front/src/modules/object-record/multiple-objects/hooks/useCombinedFindManyRecords.ts @@ -3,6 +3,7 @@ import { useQuery } from '@apollo/client'; import { getRecordsFromRecordConnection } from '@/object-record/cache/utils/getRecordsFromRecordConnection'; import { EMPTY_QUERY } from '@/object-record/constants/EmptyQuery'; import { RecordGqlOperationSignature } from '@/object-record/graphql/types/RecordGqlOperationSignature'; +import { useCombinedFindManyRecordsQueryVariables } from '@/object-record/multiple-objects/hooks/useCombinedFindManyRecordsQueryVariables'; import { useGenerateCombinedFindManyRecordsQuery } from '@/object-record/multiple-objects/hooks/useGenerateCombinedFindManyRecordsQuery'; import { MultiObjectRecordQueryResult } from '@/object-record/relation-picker/hooks/useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray'; @@ -17,10 +18,15 @@ export const useCombinedFindManyRecords = ({ operationSignatures, }); + const queryVariables = useCombinedFindManyRecordsQueryVariables({ + operationSignatures, + }); + const { data, loading } = useQuery( findManyQuery ?? EMPTY_QUERY, { skip, + variables: queryVariables, }, ); diff --git a/packages/twenty-front/src/modules/object-record/multiple-objects/hooks/useCombinedFindManyRecordsQueryVariables.ts b/packages/twenty-front/src/modules/object-record/multiple-objects/hooks/useCombinedFindManyRecordsQueryVariables.ts new file mode 100644 index 000000000..bbe9db107 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/multiple-objects/hooks/useCombinedFindManyRecordsQueryVariables.ts @@ -0,0 +1,75 @@ +import { RecordGqlOperationSignature } from '@/object-record/graphql/types/RecordGqlOperationSignature'; +import { isNonEmptyString } from '@sniptt/guards'; +import { capitalize, isDefined } from 'twenty-shared'; +import { isNonEmptyArray } from '~/utils/isNonEmptyArray'; + +export const useCombinedFindManyRecordsQueryVariables = ({ + operationSignatures, +}: { + operationSignatures: RecordGqlOperationSignature[]; +}) => { + if (!isNonEmptyArray(operationSignatures)) { + return {}; + } + + return operationSignatures.reduce( + (acc, { objectNameSingular, variables }) => { + const capitalizedName = capitalize(objectNameSingular); + + const filter = isDefined(variables?.filter) + ? { [`filter${capitalizedName}`]: variables.filter } + : {}; + + const orderBy = isDefined(variables?.orderBy) + ? { [`orderBy${capitalizedName}`]: variables.orderBy } + : {}; + + let limit = {}; + + const hasLimit = isDefined(variables.limit) && variables.limit > 0; + + const cursorDirection = variables.cursorFilter?.cursorDirection; + + let cursorFilter = {}; + + if (isNonEmptyString(variables.cursorFilter?.cursor)) { + if (cursorDirection === 'after') { + cursorFilter = { + [`after${capitalizedName}`]: variables.cursorFilter?.cursor, + }; + + if (hasLimit) { + cursorFilter = { + ...cursorFilter, + [`first${capitalizedName}`]: variables.limit, + }; + } + } else if (cursorDirection === 'before') { + cursorFilter = { + [`before${capitalizedName}`]: variables.cursorFilter?.cursor, + }; + + if (hasLimit) { + cursorFilter = { + ...cursorFilter, + [`last${capitalizedName}`]: variables.limit, + }; + } + } + } else if (hasLimit) { + limit = { + [`limit${capitalizedName}`]: variables.limit, + }; + } + + return { + ...acc, + ...filter, + ...orderBy, + ...limit, + ...cursorFilter, + }; + }, + {}, + ); +}; diff --git a/packages/twenty-front/src/modules/object-record/multiple-objects/hooks/useGenerateCombinedFindManyRecordsQuery.ts b/packages/twenty-front/src/modules/object-record/multiple-objects/hooks/useGenerateCombinedFindManyRecordsQuery.ts index 41edb6257..a276476dd 100644 --- a/packages/twenty-front/src/modules/object-record/multiple-objects/hooks/useGenerateCombinedFindManyRecordsQuery.ts +++ b/packages/twenty-front/src/modules/object-record/multiple-objects/hooks/useGenerateCombinedFindManyRecordsQuery.ts @@ -6,6 +6,7 @@ import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadat import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObjectMetadataToGraphQLQuery'; import { RecordGqlOperationSignature } from '@/object-record/graphql/types/RecordGqlOperationSignature'; import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields'; +import { getCombinedFindManyRecordsQueryFilteringPart } from '@/object-record/multiple-objects/utils/getCombinedFindManyRecordsQueryFilteringPart'; import { capitalize } from 'twenty-shared'; import { isNonEmptyArray } from '~/utils/isNonEmptyArray'; @@ -38,10 +39,10 @@ export const useGenerateCombinedFindManyRecordsQuery = ({ ) .join(', '); - const lastCursorPerMetadataItemArray = operationSignatures + const cursorFilteringPerMetadataItemArray = operationSignatures .map( ({ objectNameSingular }) => - `$lastCursor${capitalize(objectNameSingular)}: String`, + `$after${capitalize(objectNameSingular)}: String, $before${capitalize(objectNameSingular)}: String, $first${capitalize(objectNameSingular)}: Int, $last${capitalize(objectNameSingular)}: Int`, ) .join(', '); @@ -52,48 +53,42 @@ export const useGenerateCombinedFindManyRecordsQuery = ({ ) .join(', '); - const queryKeyWithObjectMetadataItemArray = operationSignatures.map( - (queryKey) => { + const queryOperationSignatureWithObjectMetadataItemArray = + operationSignatures.map((operationSignature) => { const objectMetadataItem = objectMetadataItems.find( (objectMetadataItem) => - objectMetadataItem.nameSingular === queryKey.objectNameSingular, + objectMetadataItem.nameSingular === + operationSignature.objectNameSingular, ); if (isUndefined(objectMetadataItem)) { throw new Error( - `Object metadata item not found for object name singular: ${queryKey.objectNameSingular}`, + `Object metadata item not found for object name singular: ${operationSignature.objectNameSingular}`, ); } - return { ...queryKey, objectMetadataItem }; - }, - ); + return { operationSignature, objectMetadataItem }; + }); return gql` query CombinedFindManyRecords( ${filterPerMetadataItemArray}, ${orderByPerMetadataItemArray}, - ${lastCursorPerMetadataItemArray}, + ${cursorFilteringPerMetadataItemArray}, ${limitPerMetadataItemArray} ) { - ${queryKeyWithObjectMetadataItemArray + ${queryOperationSignatureWithObjectMetadataItemArray .map( - ({ objectMetadataItem, fields }) => - `${objectMetadataItem.namePlural}(filter: $filter${capitalize( - objectMetadataItem.nameSingular, - )}, orderBy: $orderBy${capitalize( - objectMetadataItem.nameSingular, - )}, first: $limit${capitalize( - objectMetadataItem.nameSingular, - )}, after: $lastCursor${capitalize( - objectMetadataItem.nameSingular, - )}){ + ({ objectMetadataItem, operationSignature }) => + `${getCombinedFindManyRecordsQueryFilteringPart( + objectMetadataItem, + )} { edges { node ${mapObjectMetadataToGraphQLQuery({ objectMetadataItems: objectMetadataItems, objectMetadataItem, recordGqlFields: - fields ?? + operationSignature.fields ?? generateDepthOneRecordGqlFields({ objectMetadataItem, }), diff --git a/packages/twenty-front/src/modules/object-record/multiple-objects/utils/getCombinedFindManyRecordsQueryFilteringPart.ts b/packages/twenty-front/src/modules/object-record/multiple-objects/utils/getCombinedFindManyRecordsQueryFilteringPart.ts new file mode 100644 index 000000000..1353d0555 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/multiple-objects/utils/getCombinedFindManyRecordsQueryFilteringPart.ts @@ -0,0 +1,15 @@ +import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; +import { capitalize } from 'twenty-shared'; + +export const getCombinedFindManyRecordsQueryFilteringPart = ( + objectMetadataItem: ObjectMetadataItem, +) => { + return `${objectMetadataItem.namePlural}( + filter: $filter${capitalize(objectMetadataItem.nameSingular)}, + orderBy: $orderBy${capitalize(objectMetadataItem.nameSingular)}, + after: $after${capitalize(objectMetadataItem.nameSingular)}, + before: $before${capitalize(objectMetadataItem.nameSingular)}, + first: $first${capitalize(objectMetadataItem.nameSingular)}, + last: $last${capitalize(objectMetadataItem.nameSingular)}, + limit: $limit${capitalize(objectMetadataItem.nameSingular)})`; +}; diff --git a/packages/twenty-front/src/modules/object-record/record-show/hooks/useRecordShowPagePagination.ts b/packages/twenty-front/src/modules/object-record/record-show/hooks/useRecordShowPagePagination.ts index 1f3f149dd..460f60a1a 100644 --- a/packages/twenty-front/src/modules/object-record/record-show/hooks/useRecordShowPagePagination.ts +++ b/packages/twenty-front/src/modules/object-record/record-show/hooks/useRecordShowPagePagination.ts @@ -67,11 +67,11 @@ export const useRecordShowPagePagination = ( id: { neq: objectRecordId }, }, orderBy, + limit: isNonEmptyString(currentRecordCursorFromRequest) ? 1 : undefined, cursorFilter: isNonEmptyString(currentRecordCursorFromRequest) ? { cursorDirection: 'before', cursor: currentRecordCursorFromRequest, - limit: 1, } : undefined, objectNameSingular, @@ -90,11 +90,11 @@ export const useRecordShowPagePagination = ( }, fetchPolicy: 'network-only', orderBy, + limit: isNonEmptyString(currentRecordCursorFromRequest) ? 1 : undefined, cursorFilter: currentRecordCursorFromRequest ? { cursorDirection: 'after', cursor: currentRecordCursorFromRequest, - limit: 1, } : undefined, objectNameSingular,