From ec384cc7917064279725a90a4387644b5a5ce8b7 Mon Sep 17 00:00:00 2001 From: Charles Bochet Date: Sun, 10 Mar 2024 23:42:23 +0100 Subject: [PATCH] Implement eager load relations on graphqlQueries (#4391) * Implement eager load relations on graphqlQueries * Fix tests * Fixes * Fixes --- .../src/hooks/useDefaultHomePagePath.tsx | 2 +- .../modules/activities/hooks/useActivities.ts | 2 +- .../hooks/useCreateActivityInCache.ts | 2 +- .../CurrentUserDueTaskCountEffect.tsx | 1 + .../hooks/__tests__/useEventTracker.test.tsx | 4 - .../favorites/hooks/__mocks__/useFavorites.ts | 142 +++++++- .../hooks/__tests__/useFavorites.test.tsx | 4 - .../components/ObjectMetadataNavItems.tsx | 2 +- ...useMapFieldMetadataToGraphQLQuery.test.tsx | 180 ---------- .../useMapFieldMetadataToGraphQLQuery.ts | 151 -------- .../hooks/useObjectMetadataItem.ts | 2 + .../mapFieldMetadataToGraphQLQuery.test.tsx | 329 ++++++++++++++++++ .../mapObjectMetadataToGraphQLQuery.test.tsx | 281 +++++++++++++++ .../__tests__/shouldFieldBeQueried.test.ts | 117 +++++++ .../utils/mapFieldMetadataToGraphQLQuery.ts | 112 ++++++ .../utils/mapObjectMetadataToGraphQLQuery.ts | 37 ++ .../utils/shouldFieldBeQueried.ts | 36 ++ .../cache/hooks/useAddRecordInCache.ts | 28 +- .../cache}/hooks/useCachedRootQuery.ts | 27 +- .../cache/hooks/useGetRecordFromCache.ts | 19 +- .../hooks/__mocks__/useCreateManyRecords.ts | 99 +----- .../hooks/__mocks__/useCreateOneRecord.ts | 80 +---- .../useExecuteQuickActionOnOneRecord.ts | 80 +---- .../hooks/__mocks__/useFindOneRecord.ts | 82 +---- .../hooks/__mocks__/useUpdateOneRecord.ts | 82 +---- .../useGenerateCreateManyRecordMutation.ts | 20 +- .../useGenerateCreateOneRecordMutation.ts | 20 +- ...teExecuteQuickActionOnOneRecordMutation.ts | 22 +- .../useGenerateFindDuplicateRecordsQuery.ts | 24 +- ...anyRecordsForMultipleMetadataItemsQuery.ts | 34 +- .../hooks/useGenerateFindManyRecordsQuery.ts | 30 +- .../hooks/useGenerateFindOneRecordQuery.ts | 24 +- .../useGenerateUpdateOneRecordMutation.ts | 18 +- .../object-record/hooks/useUpdateOneRecord.ts | 2 +- .../hooks/__tests__/usePersistField.test.tsx | 25 +- .../__tests__/useToggleEditOnlyInput.test.tsx | 25 +- .../__tests__/useMultiObjectSearch.test.tsx | 30 +- ...atchesSearchFilterAndSelectedItemsQuery.ts | 1 + ...archMatchesSearchFilterAndToSelectQuery.ts | 1 + .../useSpreadsheetRecordImport.test.tsx | 25 +- .../views/hooks/__tests__/useViewBar.test.tsx | 6 - .../__tests__/useViewBar_ViewFields.test.tsx | 14 +- 42 files changed, 1372 insertions(+), 850 deletions(-) delete mode 100644 packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useMapFieldMetadataToGraphQLQuery.test.tsx delete mode 100644 packages/twenty-front/src/modules/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery.ts create mode 100644 packages/twenty-front/src/modules/object-metadata/utils/__tests__/mapFieldMetadataToGraphQLQuery.test.tsx create mode 100644 packages/twenty-front/src/modules/object-metadata/utils/__tests__/mapObjectMetadataToGraphQLQuery.test.tsx create mode 100644 packages/twenty-front/src/modules/object-metadata/utils/__tests__/shouldFieldBeQueried.test.ts create mode 100644 packages/twenty-front/src/modules/object-metadata/utils/mapFieldMetadataToGraphQLQuery.ts create mode 100644 packages/twenty-front/src/modules/object-metadata/utils/mapObjectMetadataToGraphQLQuery.ts create mode 100644 packages/twenty-front/src/modules/object-metadata/utils/shouldFieldBeQueried.ts rename packages/twenty-front/src/modules/{apollo => object-record/cache}/hooks/useCachedRootQuery.ts (59%) diff --git a/packages/twenty-front/src/hooks/useDefaultHomePagePath.tsx b/packages/twenty-front/src/hooks/useDefaultHomePagePath.tsx index 6f0c25d4a7..d8d5995086 100644 --- a/packages/twenty-front/src/hooks/useDefaultHomePagePath.tsx +++ b/packages/twenty-front/src/hooks/useDefaultHomePagePath.tsx @@ -1,7 +1,7 @@ -import { useCachedRootQuery } from '@/apollo/hooks/useCachedRootQuery'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { QueryMethodName } from '@/object-metadata/types/QueryMethodName'; +import { useCachedRootQuery } from '@/object-record/cache/hooks/useCachedRootQuery'; export const useDefaultHomePagePath = () => { const { objectMetadataItem: companyObjectMetadataItem } = diff --git a/packages/twenty-front/src/modules/activities/hooks/useActivities.ts b/packages/twenty-front/src/modules/activities/hooks/useActivities.ts index 78283f883d..41813d0dd7 100644 --- a/packages/twenty-front/src/modules/activities/hooks/useActivities.ts +++ b/packages/twenty-front/src/modules/activities/hooks/useActivities.ts @@ -69,7 +69,7 @@ export const useActivities = ({ useFindManyRecords({ skip: skipActivities, objectNameSingular: CoreObjectNameSingular.Activity, - depth: 3, + depth: 1, filter, orderBy: activitiesOrderByVariables, onCompleted: useRecoilCallback( diff --git a/packages/twenty-front/src/modules/activities/hooks/useCreateActivityInCache.ts b/packages/twenty-front/src/modules/activities/hooks/useCreateActivityInCache.ts index 5acbc05da0..7cd4785e1f 100644 --- a/packages/twenty-front/src/modules/activities/hooks/useCreateActivityInCache.ts +++ b/packages/twenty-front/src/modules/activities/hooks/useCreateActivityInCache.ts @@ -32,7 +32,7 @@ export const useCreateActivityInCache = () => { const { record: currentWorkspaceMemberRecord } = useFindOneRecord({ objectNameSingular: CoreObjectNameSingular.WorkspaceMember, objectRecordId: currentWorkspaceMember?.id, - depth: 3, + depth: 0, }); const { injectIntoActivityTargetInlineCellCache } = diff --git a/packages/twenty-front/src/modules/activities/tasks/components/CurrentUserDueTaskCountEffect.tsx b/packages/twenty-front/src/modules/activities/tasks/components/CurrentUserDueTaskCountEffect.tsx index 802777746c..a6819f2342 100644 --- a/packages/twenty-front/src/modules/activities/tasks/components/CurrentUserDueTaskCountEffect.tsx +++ b/packages/twenty-front/src/modules/activities/tasks/components/CurrentUserDueTaskCountEffect.tsx @@ -17,6 +17,7 @@ export const CurrentUserDueTaskCountEffect = () => { const { records: tasks } = useFindManyRecords({ objectNameSingular: CoreObjectNameSingular.Activity, + depth: 0, filter: { type: { eq: 'Task' }, completedAt: { is: 'NULL' }, diff --git a/packages/twenty-front/src/modules/analytics/hooks/__tests__/useEventTracker.test.tsx b/packages/twenty-front/src/modules/analytics/hooks/__tests__/useEventTracker.test.tsx index fe22734e82..85d60977d7 100644 --- a/packages/twenty-front/src/modules/analytics/hooks/__tests__/useEventTracker.test.tsx +++ b/packages/twenty-front/src/modules/analytics/hooks/__tests__/useEventTracker.test.tsx @@ -7,10 +7,6 @@ import { RecoilRoot } from 'recoil'; import { useEventTracker } from '../useEventTracker'; -jest.mock('@/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery', () => ({ - useMapFieldMetadataToGraphQLQuery: () => () => '\n', -})); - const mocks: MockedResponse[] = [ { request: { diff --git a/packages/twenty-front/src/modules/favorites/hooks/__mocks__/useFavorites.ts b/packages/twenty-front/src/modules/favorites/hooks/__mocks__/useFavorites.ts index e616ba797d..65e6593a92 100644 --- a/packages/twenty-front/src/modules/favorites/hooks/__mocks__/useFavorites.ts +++ b/packages/twenty-front/src/modules/favorites/hooks/__mocks__/useFavorites.ts @@ -84,8 +84,77 @@ export const mocks = [ query: gql` mutation CreateOneFavorite($input: FavoriteCreateInput!) { createFavorite(data: $input) { - id + __typename + id + companyId + createdAt + personId + person { + __typename + xLink { + label + url } + id + createdAt + city + email + jobTitle + name { + firstName + lastName + } + phone + linkedinLink { + label + url + } + updatedAt + avatarUrl + companyId + } + position + workspaceMemberId + workspaceMember { + __typename + colorScheme + name { + firstName + lastName + } + locale + userId + avatarUrl + createdAt + updatedAt + id + } + company { + __typename + xLink { + label + url + } + linkedinLink { + label + url + } + domainName + annualRecurringRevenue { + amountMicros + currencyCode + } + createdAt + address + updatedAt + name + accountOwnerId + employees + id + idealCustomerProfile + } + updatedAt + } } `, variables: { @@ -132,8 +201,77 @@ export const mocks = [ $input: FavoriteUpdateInput! ) { updateFavorite(id: $idToUpdate, data: $input) { - id + __typename + id + companyId + createdAt + personId + person { + __typename + xLink { + label + url } + id + createdAt + city + email + jobTitle + name { + firstName + lastName + } + phone + linkedinLink { + label + url + } + updatedAt + avatarUrl + companyId + } + position + workspaceMemberId + workspaceMember { + __typename + colorScheme + name { + firstName + lastName + } + locale + userId + avatarUrl + createdAt + updatedAt + id + } + company { + __typename + xLink { + label + url + } + linkedinLink { + label + url + } + domainName + annualRecurringRevenue { + amountMicros + currencyCode + } + createdAt + address + updatedAt + name + accountOwnerId + employees + id + idealCustomerProfile + } + updatedAt + } } `, variables: { diff --git a/packages/twenty-front/src/modules/favorites/hooks/__tests__/useFavorites.test.tsx b/packages/twenty-front/src/modules/favorites/hooks/__tests__/useFavorites.test.tsx index 08d86a5b7a..a060750e48 100644 --- a/packages/twenty-front/src/modules/favorites/hooks/__tests__/useFavorites.test.tsx +++ b/packages/twenty-front/src/modules/favorites/hooks/__tests__/useFavorites.test.tsx @@ -25,10 +25,6 @@ jest.mock('uuid', () => ({ v4: jest.fn(() => mockId), })); -jest.mock('@/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery', () => ({ - useMapFieldMetadataToGraphQLQuery: () => () => '\n', -})); - jest.mock('@/object-record/hooks/useFindManyRecords', () => ({ useFindManyRecords: () => ({ records: initialFavorites }), })); diff --git a/packages/twenty-front/src/modules/object-metadata/components/ObjectMetadataNavItems.tsx b/packages/twenty-front/src/modules/object-metadata/components/ObjectMetadataNavItems.tsx index 765475cfa1..547a7e67f5 100644 --- a/packages/twenty-front/src/modules/object-metadata/components/ObjectMetadataNavItems.tsx +++ b/packages/twenty-front/src/modules/object-metadata/components/ObjectMetadataNavItems.tsx @@ -1,9 +1,9 @@ import { useLocation, useNavigate } from 'react-router-dom'; -import { useCachedRootQuery } from '@/apollo/hooks/useCachedRootQuery'; import { useObjectMetadataItemForSettings } from '@/object-metadata/hooks/useObjectMetadataItemForSettings'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { QueryMethodName } from '@/object-metadata/types/QueryMethodName'; +import { useCachedRootQuery } from '@/object-record/cache/hooks/useCachedRootQuery'; import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords'; import { useIcons } from '@/ui/display/icon/hooks/useIcons'; import { NavigationDrawerItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem'; diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useMapFieldMetadataToGraphQLQuery.test.tsx b/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useMapFieldMetadataToGraphQLQuery.test.tsx deleted file mode 100644 index 31d6bdfe99..0000000000 --- a/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useMapFieldMetadataToGraphQLQuery.test.tsx +++ /dev/null @@ -1,180 +0,0 @@ -import { renderHook } from '@testing-library/react'; -import { RecoilRoot, useSetRecoilState } from 'recoil'; - -import { useMapFieldMetadataToGraphQLQuery } from '@/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery'; -import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; -import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; -import { getObjectMetadataItemsMock } from '@/object-metadata/utils/getObjectMetadataItemsMock'; -import { RelationMetadataType } from '~/generated/graphql'; - -const mockObjectMetadataItems = getObjectMetadataItemsMock(); - -const formatGQLString = (inputString: string) => - inputString.replace(/^\s*[\r\n]/gm, ''); - -const getOneToManyRelation = () => { - const objectMetadataItem = mockObjectMetadataItems.find( - (item) => item.nameSingular === 'opportunity', - )!; - - return { - field: objectMetadataItem.fields.find((field) => field.name === 'company')!, - res: `company - { - __typename - id - xLink - { - label - url - } - linkedinLink - { - label - url - } -domainName - annualRecurringRevenue - { - amountMicros - currencyCode - } -createdAt -address -updatedAt -name -accountOwnerId -employees -id -idealCustomerProfile - }`, - }; -}; - -const getOneToOneRelationField = () => { - const objectMetadataItem = mockObjectMetadataItems.find( - (item) => item.nameSingular === 'opportunity', - )!; - - const oneToManyfield = objectMetadataItem.fields.find( - (field) => field.name === 'company', - )!; - - const field: FieldMetadataItem = { - ...oneToManyfield, - toRelationMetadata: { - ...oneToManyfield.toRelationMetadata!, - relationType: RelationMetadataType.OneToOne, - }, - }; - - return field; -}; - -const getOneToManyFromRelationField = () => { - const objectMetadataItem = mockObjectMetadataItems.find( - (item) => item.nameSingular === 'person', - )!; - - const field = objectMetadataItem.fields.find( - (field) => field.name === 'opportunities', - )!; - - return { - field, - res: `opportunities - { - edges { - node { - __typename - id - personId -pointOfContactId -updatedAt -companyId -pipelineStepId -probability -closeDate - amount - { - amountMicros - currencyCode - } -id -createdAt - } - } - }`, - }; -}; - -const getFullNameRelation = () => { - const objectMetadataItem = mockObjectMetadataItems.find( - (item) => item.nameSingular === 'person', - )!; - - const field = objectMetadataItem.fields.find( - (field) => field.name === 'name', - )!; - - return { - field, - res: `\n name\n {\n firstName\n lastName\n }\n `, - }; -}; - -describe('useMapFieldMetadataToGraphQLQuery', () => { - it('should work as expected', async () => { - const { result } = renderHook( - () => { - const setMetadataItems = useSetRecoilState(objectMetadataItemsState()); - setMetadataItems(mockObjectMetadataItems); - - return { - mapFieldMetadataToGraphQLQuery: useMapFieldMetadataToGraphQLQuery(), - }; - }, - { - wrapper: RecoilRoot, - }, - ); - - const oneToManyRelation = getOneToManyRelation(); - - const { mapFieldMetadataToGraphQLQuery } = result.current; - - const oneToManyRelationFieldRes = mapFieldMetadataToGraphQLQuery({ - field: oneToManyRelation.field, - }); - - expect(formatGQLString(oneToManyRelationFieldRes)).toEqual( - oneToManyRelation.res, - ); - - const oneToOneRelation = getOneToOneRelationField(); - - const oneToOneRelationFieldRes = mapFieldMetadataToGraphQLQuery({ - field: oneToOneRelation, - }); - - expect(formatGQLString(oneToOneRelationFieldRes)).toEqual( - oneToManyRelation.res, - ); - - const oneToManyFromRelation = getOneToManyFromRelationField(); - const oneToManyFromRelationFieldRes = mapFieldMetadataToGraphQLQuery({ - field: oneToManyFromRelation.field, - }); - - expect(formatGQLString(oneToManyFromRelationFieldRes)).toEqual( - oneToManyFromRelation.res, - ); - - const fullNameRelation = getFullNameRelation(); - const fullNameFieldRes = mapFieldMetadataToGraphQLQuery({ - field: fullNameRelation.field, - }); - - expect(fullNameFieldRes).toEqual(fullNameRelation.res); - }); -}); diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery.ts b/packages/twenty-front/src/modules/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery.ts deleted file mode 100644 index 6ddf40b418..0000000000 --- a/packages/twenty-front/src/modules/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery.ts +++ /dev/null @@ -1,151 +0,0 @@ -import { useRecoilValue } from 'recoil'; - -import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; -import { FieldType } from '@/object-record/record-field/types/FieldType'; - -import { FieldMetadataItem } from '../types/FieldMetadataItem'; - -export const useMapFieldMetadataToGraphQLQuery = () => { - const objectMetadataItems = useRecoilValue(objectMetadataItemsState()); - - const mapFieldMetadataToGraphQLQuery = ({ - field, - depth = 2, - }: { - field: FieldMetadataItem; - depth?: number; - }): any => { - // TODO: parse - const fieldType = field.type as FieldType; - - const fieldIsSimpleValue = ( - [ - 'UUID', - 'TEXT', - 'PHONE', - 'DATE_TIME', - 'EMAIL', - 'NUMBER', - 'BOOLEAN', - 'RATING', - 'SELECT', - 'POSITION', - ] as FieldType[] - ).includes(fieldType); - - if (fieldIsSimpleValue) { - return field.name; - } else if ( - fieldType === 'RELATION' && - field.toRelationMetadata?.relationType === 'ONE_TO_MANY' - ) { - const relationMetadataItem = objectMetadataItems.find( - (objectMetadataItem) => - objectMetadataItem.id === - (field.toRelationMetadata as any)?.fromObjectMetadata?.id, - ); - - if (depth > 1) { - return `${field.name} - { - __typename - id - ${(relationMetadataItem?.fields ?? []) - .map((field) => - mapFieldMetadataToGraphQLQuery({ - field, - depth: depth - 1, - }), - ) - .join('\n')} - }`; - } else { - return ''; - } - } else if ( - fieldType === 'RELATION' && - field.toRelationMetadata?.relationType === 'ONE_TO_ONE' - ) { - const relationMetadataItem = objectMetadataItems.find( - (objectMetadataItem) => - objectMetadataItem.id === - (field.toRelationMetadata as any)?.fromObjectMetadata?.id, - ); - - if (depth > 1) { - return `${field.name} - { - __typename - id - ${(relationMetadataItem?.fields ?? []) - .map((field) => - mapFieldMetadataToGraphQLQuery({ - field, - depth: depth - 1, - }), - ) - .join('\n')} - }`; - } else { - return ''; - } - } else if ( - fieldType === 'RELATION' && - field.fromRelationMetadata?.relationType === 'ONE_TO_MANY' - ) { - const relationMetadataItem = objectMetadataItems.find( - (objectMetadataItem) => - objectMetadataItem.id === - (field.fromRelationMetadata as any)?.toObjectMetadata?.id, - ); - - if (depth > 1) { - return `${field.name} - { - edges { - node { - __typename - id - ${(relationMetadataItem?.fields ?? []) - .map((field) => - mapFieldMetadataToGraphQLQuery({ - field, - depth: depth - 1, - }), - ) - .join('\n')} - } - } - }`; - } else { - return ''; - } - } else if (fieldType === 'LINK') { - return ` - ${field.name} - { - label - url - } - `; - } else if (fieldType === 'CURRENCY') { - return ` - ${field.name} - { - amountMicros - currencyCode - } - `; - } else if (fieldType === 'FULL_NAME') { - return ` - ${field.name} - { - firstName - lastName - } - `; - } - }; - - return mapFieldMetadataToGraphQLQuery; -}; diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/useObjectMetadataItem.ts b/packages/twenty-front/src/modules/object-metadata/hooks/useObjectMetadataItem.ts index 1a15600038..e0072f675a 100644 --- a/packages/twenty-front/src/modules/object-metadata/hooks/useObjectMetadataItem.ts +++ b/packages/twenty-front/src/modules/object-metadata/hooks/useObjectMetadataItem.ts @@ -40,6 +40,7 @@ export const EMPTY_MUTATION = gql` export const useObjectMetadataItem = ( { objectNameSingular }: ObjectMetadataItemIdentifier, depth?: number, + eagerLoadedRelations?: Record, ) => { const currentWorkspace = useRecoilValue(currentWorkspaceState()); @@ -90,6 +91,7 @@ export const useObjectMetadataItem = ( const findManyRecordsQuery = generateFindManyRecordsQuery({ objectMetadataItem, depth, + eagerLoadedRelations, }); const generateFindDuplicateRecordsQuery = diff --git a/packages/twenty-front/src/modules/object-metadata/utils/__tests__/mapFieldMetadataToGraphQLQuery.test.tsx b/packages/twenty-front/src/modules/object-metadata/utils/__tests__/mapFieldMetadataToGraphQLQuery.test.tsx new file mode 100644 index 0000000000..4ca474d31f --- /dev/null +++ b/packages/twenty-front/src/modules/object-metadata/utils/__tests__/mapFieldMetadataToGraphQLQuery.test.tsx @@ -0,0 +1,329 @@ +import { getObjectMetadataItemsMock } from '@/object-metadata/utils/getObjectMetadataItemsMock'; +import { mapFieldMetadataToGraphQLQuery } from '@/object-metadata/utils/mapFieldMetadataToGraphQLQuery'; + +const mockObjectMetadataItems = getObjectMetadataItemsMock(); + +const formatGQLString = (inputString: string) => + inputString.replace(/^\s*[\r\n]/gm, ''); + +const personObjectMetadataItem = mockObjectMetadataItems.find( + (item) => item.nameSingular === 'person', +); + +if (!personObjectMetadataItem) { + throw new Error('ObjectMetadataItem not found'); +} + +describe('mapFieldMetadataToGraphQLQuery', () => { + it('should return fieldName if simpleValue', async () => { + const res = mapFieldMetadataToGraphQLQuery({ + objectMetadataItems: mockObjectMetadataItems, + field: personObjectMetadataItem.fields.find( + (field) => field.name === 'id', + )!, + }); + expect(formatGQLString(res)).toEqual('id'); + }); + it('should return fieldName if composite', async () => { + const res = mapFieldMetadataToGraphQLQuery({ + objectMetadataItems: mockObjectMetadataItems, + field: personObjectMetadataItem.fields.find( + (field) => field.name === 'name', + )!, + }); + expect(formatGQLString(res)).toEqual(`name +{ + firstName + lastName +}`); + }); + it('should not return relation if depth is < 1', async () => { + const res = mapFieldMetadataToGraphQLQuery({ + objectMetadataItems: mockObjectMetadataItems, + relationFieldDepth: 0, + field: personObjectMetadataItem.fields.find( + (field) => field.name === 'company', + )!, + }); + expect(formatGQLString(res)).toEqual(''); + }); + + it('should return relation if it matches depth', async () => { + const res = mapFieldMetadataToGraphQLQuery({ + objectMetadataItems: mockObjectMetadataItems, + relationFieldDepth: 1, + field: personObjectMetadataItem.fields.find( + (field) => field.name === 'company', + )!, + }); + expect(formatGQLString(res)).toEqual(`company +{ +__typename +xLink +{ + label + url +} +linkedinLink +{ + label + url +} +domainName +annualRecurringRevenue +{ + amountMicros + currencyCode +} +createdAt +address +updatedAt +name +accountOwnerId +employees +id +idealCustomerProfile +}`); + }); + it('should return relation with all sub relations if it matches depth', async () => { + const res = mapFieldMetadataToGraphQLQuery({ + objectMetadataItems: mockObjectMetadataItems, + relationFieldDepth: 2, + field: personObjectMetadataItem.fields.find( + (field) => field.name === 'company', + )!, + }); + expect(formatGQLString(res)).toEqual(`company +{ +__typename +xLink +{ + label + url +} +accountOwner +{ +__typename +colorScheme +name +{ + firstName + lastName +} +locale +userId +avatarUrl +createdAt +updatedAt +id +} +linkedinLink +{ + label + url +} +attachments +{ + edges { + node { +__typename +updatedAt +createdAt +name +personId +activityId +companyId +id +authorId +type +fullPath +} + } +} +domainName +opportunities +{ + edges { + node { +__typename +personId +pointOfContactId +updatedAt +companyId +pipelineStepId +probability +closeDate +amount +{ + amountMicros + currencyCode +} +id +createdAt +} + } +} +annualRecurringRevenue +{ + amountMicros + currencyCode +} +createdAt +address +updatedAt +activityTargets +{ + edges { + node { +__typename +updatedAt +createdAt +personId +activityId +companyId +id +} + } +} +favorites +{ + edges { + node { +__typename +id +companyId +createdAt +personId +position +workspaceMemberId +updatedAt +} + } +} +people +{ + edges { + node { +__typename +xLink +{ + label + url +} +id +createdAt +city +email +jobTitle +name +{ + firstName + lastName +} +phone +linkedinLink +{ + label + url +} +updatedAt +avatarUrl +companyId +} + } +} +name +accountOwnerId +employees +id +idealCustomerProfile +}`); + }); + + it('should return eagerLoaded relations', async () => { + const res = mapFieldMetadataToGraphQLQuery({ + objectMetadataItems: mockObjectMetadataItems, + relationFieldDepth: 2, + relationFieldEagerLoad: { accountOwner: true, people: true }, + field: personObjectMetadataItem.fields.find( + (field) => field.name === 'company', + )!, + }); + expect(formatGQLString(res)).toEqual(`company +{ +__typename +xLink +{ + label + url +} +accountOwner +{ +__typename +colorScheme +name +{ + firstName + lastName +} +locale +userId +avatarUrl +createdAt +updatedAt +id +} +linkedinLink +{ + label + url +} +domainName +annualRecurringRevenue +{ + amountMicros + currencyCode +} +createdAt +address +updatedAt +people +{ + edges { + node { +__typename +xLink +{ + label + url +} +id +createdAt +city +email +jobTitle +name +{ + firstName + lastName +} +phone +linkedinLink +{ + label + url +} +updatedAt +avatarUrl +companyId +} + } +} +name +accountOwnerId +employees +id +idealCustomerProfile +}`); + }); +}); diff --git a/packages/twenty-front/src/modules/object-metadata/utils/__tests__/mapObjectMetadataToGraphQLQuery.test.tsx b/packages/twenty-front/src/modules/object-metadata/utils/__tests__/mapObjectMetadataToGraphQLQuery.test.tsx new file mode 100644 index 0000000000..7473906175 --- /dev/null +++ b/packages/twenty-front/src/modules/object-metadata/utils/__tests__/mapObjectMetadataToGraphQLQuery.test.tsx @@ -0,0 +1,281 @@ +import { getObjectMetadataItemsMock } from '@/object-metadata/utils/getObjectMetadataItemsMock'; +import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObjectMetadataToGraphQLQuery'; + +const mockObjectMetadataItems = getObjectMetadataItemsMock(); + +const formatGQLString = (inputString: string) => + inputString.replace(/^\s*[\r\n]/gm, ''); + +const personObjectMetadataItem = mockObjectMetadataItems.find( + (item) => item.nameSingular === 'person', +); + +if (!personObjectMetadataItem) { + throw new Error('ObjectMetadataItem not found'); +} + +describe('mapObjectMetadataToGraphQLQuery', () => { + it('should return typename if depth < 0', async () => { + const res = mapObjectMetadataToGraphQLQuery({ + objectMetadataItems: mockObjectMetadataItems, + objectMetadataItem: personObjectMetadataItem, + depth: -1, + }); + expect(formatGQLString(res)).toEqual(`{ +__typename +}`); + }); + + it('should return depth 0 if depth = 0', async () => { + const res = mapObjectMetadataToGraphQLQuery({ + objectMetadataItems: mockObjectMetadataItems, + objectMetadataItem: personObjectMetadataItem, + depth: 0, + }); + expect(formatGQLString(res)).toEqual(`{ +__typename +xLink +{ + label + url +} +id +createdAt +city +email +jobTitle +name +{ + firstName + lastName +} +phone +linkedinLink +{ + label + url +} +updatedAt +avatarUrl +companyId +}`); + }); + + it('should return depth 1 if depth = 1', async () => { + const res = mapObjectMetadataToGraphQLQuery({ + objectMetadataItems: mockObjectMetadataItems, + objectMetadataItem: personObjectMetadataItem, + depth: 1, + }); + expect(formatGQLString(res)).toEqual(`{ +__typename +opportunities +{ + edges { + node { +__typename +personId +pointOfContactId +updatedAt +companyId +pipelineStepId +probability +closeDate +amount +{ + amountMicros + currencyCode +} +id +createdAt +} + } +} +xLink +{ + label + url +} +id +pointOfContactForOpportunities +{ + edges { + node { +__typename +personId +pointOfContactId +updatedAt +companyId +pipelineStepId +probability +closeDate +amount +{ + amountMicros + currencyCode +} +id +createdAt +} + } +} +createdAt +company +{ +__typename +xLink +{ + label + url +} +linkedinLink +{ + label + url +} +domainName +annualRecurringRevenue +{ + amountMicros + currencyCode +} +createdAt +address +updatedAt +name +accountOwnerId +employees +id +idealCustomerProfile +} +city +email +activityTargets +{ + edges { + node { +__typename +updatedAt +createdAt +personId +activityId +companyId +id +} + } +} +jobTitle +favorites +{ + edges { + node { +__typename +id +companyId +createdAt +personId +position +workspaceMemberId +updatedAt +} + } +} +attachments +{ + edges { + node { +__typename +updatedAt +createdAt +name +personId +activityId +companyId +id +authorId +type +fullPath +} + } +} +name +{ + firstName + lastName +} +phone +linkedinLink +{ + label + url +} +updatedAt +avatarUrl +companyId +}`); + }); + + it('should eager load only specified relations', async () => { + const res = mapObjectMetadataToGraphQLQuery({ + objectMetadataItems: mockObjectMetadataItems, + objectMetadataItem: personObjectMetadataItem, + eagerLoadedRelations: { company: true }, + depth: 1, + }); + expect(formatGQLString(res)).toEqual(`{ +__typename +xLink +{ + label + url +} +id +createdAt +company +{ +__typename +xLink +{ + label + url +} +linkedinLink +{ + label + url +} +domainName +annualRecurringRevenue +{ + amountMicros + currencyCode +} +createdAt +address +updatedAt +name +accountOwnerId +employees +id +idealCustomerProfile +} +city +email +jobTitle +name +{ + firstName + lastName +} +phone +linkedinLink +{ + label + url +} +updatedAt +avatarUrl +companyId +}`); + }); +}); diff --git a/packages/twenty-front/src/modules/object-metadata/utils/__tests__/shouldFieldBeQueried.test.ts b/packages/twenty-front/src/modules/object-metadata/utils/__tests__/shouldFieldBeQueried.test.ts new file mode 100644 index 0000000000..956f3a5ca7 --- /dev/null +++ b/packages/twenty-front/src/modules/object-metadata/utils/__tests__/shouldFieldBeQueried.test.ts @@ -0,0 +1,117 @@ +import { shouldFieldBeQueried } from '@/object-metadata/utils/shouldFieldBeQueried'; +import { FieldMetadataType } from '~/generated-metadata/graphql'; + +describe('shouldFieldBeQueried', () => { + describe('if field is not relation', () => { + it('should be queried if depth is undefined', () => { + const res = shouldFieldBeQueried({ + field: { name: 'fieldName', type: FieldMetadataType.Boolean }, + }); + expect(res).toBe(true); + }); + + it('should be queried depth = 0', () => { + const res = shouldFieldBeQueried({ + depth: 0, + field: { name: 'fieldName', type: FieldMetadataType.Boolean }, + }); + expect(res).toBe(true); + }); + + it('should be queried depth > 0', () => { + const res = shouldFieldBeQueried({ + depth: 1, + field: { name: 'fieldName', type: FieldMetadataType.Boolean }, + }); + expect(res).toBe(true); + }); + + it('should NOT be queried depth < 0', () => { + const res = shouldFieldBeQueried({ + depth: -1, + field: { name: 'fieldName', type: FieldMetadataType.Boolean }, + }); + expect(res).toBe(false); + }); + + it('should not depends on eagerLoadedRelation', () => { + const res = shouldFieldBeQueried({ + depth: 0, + eagerLoadedRelations: { + fieldName: true, + }, + field: { name: 'fieldName', type: FieldMetadataType.Boolean }, + }); + expect(res).toBe(true); + }); + }); + + describe('if field is relation', () => { + it('should be queried if eagerLoadedRelation and depth are undefined', () => { + const res = shouldFieldBeQueried({ + field: { name: 'fieldName', type: FieldMetadataType.Relation }, + }); + expect(res).toBe(true); + }); + + it('should be queried if eagerLoadedRelation is undefined and depth = 1', () => { + const res = shouldFieldBeQueried({ + depth: 1, + field: { name: 'fieldName', type: FieldMetadataType.Relation }, + }); + expect(res).toBe(true); + }); + + it('should be queried if eagerLoadedRelation is undefined and depth > 1', () => { + const res = shouldFieldBeQueried({ + depth: 2, + field: { name: 'fieldName', type: FieldMetadataType.Relation }, + }); + expect(res).toBe(true); + }); + + it('should NOT be queried if eagerLoadedRelation is undefined and depth < 1', () => { + const res = shouldFieldBeQueried({ + depth: 0, + field: { name: 'fieldName', type: FieldMetadataType.Relation }, + }); + expect(res).toBe(false); + }); + + it('should be queried if eagerLoadedRelation is matching and depth > 1', () => { + const res = shouldFieldBeQueried({ + depth: 1, + eagerLoadedRelations: { fieldName: true }, + field: { name: 'fieldName', type: FieldMetadataType.Relation }, + }); + expect(res).toBe(true); + }); + + it('should NOT be queried if eagerLoadedRelation is matching and depth < 1', () => { + const res = shouldFieldBeQueried({ + depth: 0, + eagerLoadedRelations: { fieldName: true }, + field: { name: 'fieldName', type: FieldMetadataType.Relation }, + }); + expect(res).toBe(false); + }); + + it('should NOT be queried if eagerLoadedRelation is not matching (falsy) and depth < 1', () => { + const res = shouldFieldBeQueried({ + depth: 1, + eagerLoadedRelations: { fieldName: false }, + field: { name: 'fieldName', type: FieldMetadataType.Relation }, + }); + expect(res).toBe(false); + }); + + it('should NOT be queried if eagerLoadedRelation is not matching and depth < 1', () => { + const res = shouldFieldBeQueried({ + depth: 0, + eagerLoadedRelations: { anotherFieldName: true }, + field: { name: 'fieldName', type: FieldMetadataType.Relation }, + }); + expect(res).toBe(false); + }); + }); +}); diff --git a/packages/twenty-front/src/modules/object-metadata/utils/mapFieldMetadataToGraphQLQuery.ts b/packages/twenty-front/src/modules/object-metadata/utils/mapFieldMetadataToGraphQLQuery.ts new file mode 100644 index 0000000000..4603ac0cef --- /dev/null +++ b/packages/twenty-front/src/modules/object-metadata/utils/mapFieldMetadataToGraphQLQuery.ts @@ -0,0 +1,112 @@ +import { isUndefined } from '@sniptt/guards'; + +import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; +import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObjectMetadataToGraphQLQuery'; +import { FieldMetadataType } from '~/generated-metadata/graphql'; + +import { FieldMetadataItem } from '../types/FieldMetadataItem'; + +export const mapFieldMetadataToGraphQLQuery = ({ + objectMetadataItems, + field, + relationFieldDepth = 0, + relationFieldEagerLoad, +}: { + objectMetadataItems: ObjectMetadataItem[]; + field: Pick< + FieldMetadataItem, + 'name' | 'type' | 'toRelationMetadata' | 'fromRelationMetadata' + >; + relationFieldDepth?: number; + relationFieldEagerLoad?: Record; +}): any => { + const fieldType = field.type; + + const fieldIsSimpleValue = ( + [ + 'UUID', + 'TEXT', + 'PHONE', + 'DATE_TIME', + 'EMAIL', + 'NUMBER', + 'BOOLEAN', + 'RATING', + 'SELECT', + 'POSITION', + ] as FieldMetadataType[] + ).includes(fieldType); + + if (fieldIsSimpleValue) { + return field.name; + } else if ( + fieldType === 'RELATION' && + field.toRelationMetadata?.relationType === 'ONE_TO_MANY' && + relationFieldDepth > 0 + ) { + const relationMetadataItem = objectMetadataItems.find( + (objectMetadataItem) => + objectMetadataItem.id === + (field.toRelationMetadata as any)?.fromObjectMetadata?.id, + ); + + if (isUndefined(relationMetadataItem)) { + return ''; + } + + return `${field.name} +${mapObjectMetadataToGraphQLQuery({ + objectMetadataItems, + objectMetadataItem: relationMetadataItem, + eagerLoadedRelations: relationFieldEagerLoad, + depth: relationFieldDepth - 1, +})}`; + } else if ( + fieldType === 'RELATION' && + field.fromRelationMetadata?.relationType === 'ONE_TO_MANY' && + relationFieldDepth > 0 + ) { + const relationMetadataItem = objectMetadataItems.find( + (objectMetadataItem) => + objectMetadataItem.id === + (field.fromRelationMetadata as any)?.toObjectMetadata?.id, + ); + + if (isUndefined(relationMetadataItem)) { + return ''; + } + + return `${field.name} +{ + edges { + node ${mapObjectMetadataToGraphQLQuery({ + objectMetadataItems, + objectMetadataItem: relationMetadataItem, + eagerLoadedRelations: relationFieldEagerLoad, + depth: relationFieldDepth - 1, + })} + } +}`; + } else if (fieldType === 'LINK') { + return `${field.name} +{ + label + url +}`; + } else if (fieldType === 'CURRENCY') { + return `${field.name} +{ + amountMicros + currencyCode +} + `; + } else if (fieldType === 'FULL_NAME') { + return `${field.name} +{ + firstName + lastName +}`; + } + + return ''; +}; diff --git a/packages/twenty-front/src/modules/object-metadata/utils/mapObjectMetadataToGraphQLQuery.ts b/packages/twenty-front/src/modules/object-metadata/utils/mapObjectMetadataToGraphQLQuery.ts new file mode 100644 index 0000000000..e56fff7e12 --- /dev/null +++ b/packages/twenty-front/src/modules/object-metadata/utils/mapObjectMetadataToGraphQLQuery.ts @@ -0,0 +1,37 @@ +import { isUndefined } from '@sniptt/guards'; + +import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; +import { mapFieldMetadataToGraphQLQuery } from '@/object-metadata/utils/mapFieldMetadataToGraphQLQuery'; +import { shouldFieldBeQueried } from '@/object-metadata/utils/shouldFieldBeQueried'; + +export const mapObjectMetadataToGraphQLQuery = ({ + objectMetadataItems, + objectMetadataItem, + depth = 1, + eagerLoadedRelations, +}: { + objectMetadataItems: ObjectMetadataItem[]; + objectMetadataItem: Pick; + depth?: number; + eagerLoadedRelations?: Record; +}): any => { + return `{ +__typename +${(objectMetadataItem?.fields ?? []) + .filter((field) => field.isActive) + .filter((field) => + shouldFieldBeQueried({ field, depth, eagerLoadedRelations }), + ) + .map((field) => + mapFieldMetadataToGraphQLQuery({ + objectMetadataItems, + field, + relationFieldDepth: depth, + relationFieldEagerLoad: isUndefined(eagerLoadedRelations) + ? undefined + : eagerLoadedRelations[field.name] ?? undefined, + }), + ) + .join('\n')} +}`; +}; diff --git a/packages/twenty-front/src/modules/object-metadata/utils/shouldFieldBeQueried.ts b/packages/twenty-front/src/modules/object-metadata/utils/shouldFieldBeQueried.ts new file mode 100644 index 0000000000..7c04159436 --- /dev/null +++ b/packages/twenty-front/src/modules/object-metadata/utils/shouldFieldBeQueried.ts @@ -0,0 +1,36 @@ +import { isUndefined } from '@sniptt/guards'; + +import { FieldType } from '@/object-record/record-field/types/FieldType'; + +import { FieldMetadataItem } from '../types/FieldMetadataItem'; + +export const shouldFieldBeQueried = ({ + field, + depth, + eagerLoadedRelations, +}: { + field: Pick; + depth?: number; + eagerLoadedRelations?: Record; +}): any => { + const fieldType = field.type as FieldType; + + if (!isUndefined(depth) && depth < 0) { + return false; + } + + if (!isUndefined(depth) && depth < 1 && fieldType === 'RELATION') { + return false; + } + + if ( + fieldType === 'RELATION' && + !isUndefined(eagerLoadedRelations) && + (isUndefined(eagerLoadedRelations[field.name]) || + !eagerLoadedRelations[field.name]) + ) { + return false; + } + + return true; +}; diff --git a/packages/twenty-front/src/modules/object-record/cache/hooks/useAddRecordInCache.ts b/packages/twenty-front/src/modules/object-record/cache/hooks/useAddRecordInCache.ts index 46a908f1fb..c8d821927e 100644 --- a/packages/twenty-front/src/modules/object-record/cache/hooks/useAddRecordInCache.ts +++ b/packages/twenty-front/src/modules/object-record/cache/hooks/useAddRecordInCache.ts @@ -1,10 +1,10 @@ import { useApolloClient } from '@apollo/client'; import gql from 'graphql-tag'; -import { useRecoilCallback } from 'recoil'; +import { useRecoilCallback, useRecoilValue } from 'recoil'; -import { useMapFieldMetadataToGraphQLQuery } from '@/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery'; +import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; -import { MAX_QUERY_DEPTH_FOR_CACHE_INJECTION } from '@/object-record/cache/constants/MaxQueryDepthForCacheInjection'; +import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObjectMetadataToGraphQLQuery'; import { useInjectIntoFindOneRecordQueryCache } from '@/object-record/cache/hooks/useInjectIntoFindOneRecordQueryCache'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; import { ObjectRecord } from '@/object-record/types/ObjectRecord'; @@ -15,7 +15,7 @@ export const useAddRecordInCache = ({ }: { objectMetadataItem: ObjectMetadataItem; }) => { - const mapFieldMetadataToGraphQLQuery = useMapFieldMetadataToGraphQLQuery(); + const objectMetadataItems = useRecoilValue(objectMetadataItemsState()); const apolloClient = useApolloClient(); const { injectIntoFindOneRecordQueryCache } = @@ -29,18 +29,12 @@ export const useAddRecordInCache = ({ const fragment = gql` fragment Create${capitalize( objectMetadataItem.nameSingular, - )}InCache on ${capitalize(objectMetadataItem.nameSingular)} { - __typename - id - ${objectMetadataItem.fields - .map((field) => - mapFieldMetadataToGraphQLQuery({ - field, - depth: MAX_QUERY_DEPTH_FOR_CACHE_INJECTION, - }), - ) - .join('\n')} - } + )}InCache on ${capitalize( + objectMetadataItem.nameSingular, + )} ${mapObjectMetadataToGraphQLQuery({ + objectMetadataItems, + objectMetadataItem, + })} `; const cachedObjectRecord = { @@ -62,8 +56,8 @@ export const useAddRecordInCache = ({ }, [ objectMetadataItem, + objectMetadataItems, apolloClient, - mapFieldMetadataToGraphQLQuery, injectIntoFindOneRecordQueryCache, ], ); diff --git a/packages/twenty-front/src/modules/apollo/hooks/useCachedRootQuery.ts b/packages/twenty-front/src/modules/object-record/cache/hooks/useCachedRootQuery.ts similarity index 59% rename from packages/twenty-front/src/modules/apollo/hooks/useCachedRootQuery.ts rename to packages/twenty-front/src/modules/object-record/cache/hooks/useCachedRootQuery.ts index b16bb88947..8ffd3ad715 100644 --- a/packages/twenty-front/src/modules/apollo/hooks/useCachedRootQuery.ts +++ b/packages/twenty-front/src/modules/object-record/cache/hooks/useCachedRootQuery.ts @@ -1,9 +1,11 @@ import { useApolloClient } from '@apollo/client/react/hooks/useApolloClient'; import gql from 'graphql-tag'; +import { useRecoilValue } from 'recoil'; -import { useMapFieldMetadataToGraphQLQuery } from '@/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery'; +import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { QueryMethodName } from '@/object-metadata/types/QueryMethodName'; +import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObjectMetadataToGraphQLQuery'; export const useCachedRootQuery = ({ objectMetadataItem, @@ -12,33 +14,28 @@ export const useCachedRootQuery = ({ objectMetadataItem: ObjectMetadataItem | undefined; queryMethodName: QueryMethodName; }) => { - const mapFieldMetadataToGraphQLQuery = useMapFieldMetadataToGraphQLQuery(); const apolloClient = useApolloClient(); + const objectMetadataItems = useRecoilValue(objectMetadataItemsState()); if (!objectMetadataItem) { return { cachedRootQuery: null }; } - const buildRecordFieldsFragment = () => { - return objectMetadataItem.fields - .filter((field) => field.type !== 'RELATION') - .map((field) => mapFieldMetadataToGraphQLQuery({ field })) - .join(' \n'); - }; - const cacheReadFragment = gql` fragment RootQuery on Query { ${ QueryMethodName.FindMany === queryMethodName ? objectMetadataItem.namePlural : objectMetadataItem.nameSingular - } { - ${QueryMethodName.FindMany === queryMethodName ? 'edges { node { ' : ''} - ${buildRecordFieldsFragment()} - ${QueryMethodName.FindMany === queryMethodName ? '}}' : ''} - } - } + ${QueryMethodName.FindMany === queryMethodName ? '{ edges { node ' : ''} + ${mapObjectMetadataToGraphQLQuery({ + objectMetadataItems, + objectMetadataItem, + depth: 0, + })} + ${QueryMethodName.FindMany === queryMethodName ? '}}' : ''} + } `; const cachedRootQuery = apolloClient.readFragment({ diff --git a/packages/twenty-front/src/modules/object-record/cache/hooks/useGetRecordFromCache.ts b/packages/twenty-front/src/modules/object-record/cache/hooks/useGetRecordFromCache.ts index 9b6b45e817..9bda1c6460 100644 --- a/packages/twenty-front/src/modules/object-record/cache/hooks/useGetRecordFromCache.ts +++ b/packages/twenty-front/src/modules/object-record/cache/hooks/useGetRecordFromCache.ts @@ -1,7 +1,9 @@ import { gql, useApolloClient } from '@apollo/client'; +import { useRecoilValue } from 'recoil'; -import { useMapFieldMetadataToGraphQLQuery } from '@/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery'; +import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; +import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObjectMetadataToGraphQLQuery'; import { ObjectRecord } from '@/object-record/types/ObjectRecord'; import { isNullable } from '~/utils/isNullable'; import { capitalize } from '~/utils/string/capitalize'; @@ -11,7 +13,8 @@ export const useGetRecordFromCache = ({ }: { objectMetadataItem: ObjectMetadataItem; }) => { - const mapFieldMetadataToGraphQLQuery = useMapFieldMetadataToGraphQLQuery(); + const objectMetadataItems = useRecoilValue(objectMetadataItemsState()); + const apolloClient = useApolloClient(); return ( @@ -25,12 +28,12 @@ export const useGetRecordFromCache = ({ const capitalizedObjectName = capitalize(objectMetadataItem.nameSingular); const cacheReadFragment = gql` - fragment ${capitalizedObjectName}Fragment on ${capitalizedObjectName} { - id - ${objectMetadataItem.fields - .map((field) => mapFieldMetadataToGraphQLQuery({ field })) - .join('\n')} - } + fragment ${capitalizedObjectName}Fragment on ${capitalizedObjectName} ${mapObjectMetadataToGraphQLQuery( + { + objectMetadataItems, + objectMetadataItem, + }, + )} `; const cachedRecordId = cache.identify({ diff --git a/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useCreateManyRecords.ts b/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useCreateManyRecords.ts index 7fd08fc2e7..87cab48baa 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useCreateManyRecords.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useCreateManyRecords.ts @@ -5,72 +5,28 @@ import { Person } from '@/people/types/Person'; export const query = gql` mutation CreatePeople($data: [PersonCreateInput!]!) { createPeople(data: $data) { - id - opportunities { - edges { - node { - __typename - id - } + __typename + xLink { + label + url } - } - xLink { - label - url - } - id - pointOfContactForOpportunities { - edges { - node { - __typename - id - } - } - } - createdAt - company { - __typename id - } - city - email - activityTargets { - edges { - node { - __typename - id - } + createdAt + city + email + jobTitle + name { + firstName + lastName } - } - jobTitle - favorites { - edges { - node { - __typename - id - } + phone + linkedinLink { + label + url } - } - attachments { - edges { - node { - __typename - id - } - } - } - name { - firstName - lastName - } - phone - linkedinLink { - label - url - } - updatedAt - avatarUrl - companyId + updatedAt + avatarUrl + companyId } } `; @@ -86,32 +42,15 @@ const data = [ export const variables = { data }; export const responseData = { - opportunities: { - edges: [], - }, + __typeName: '', xLink: { label: '', url: '', }, - pointOfContactForOpportunities: { - edges: [], - }, createdAt: '', - company: { - id: '', - }, city: '', email: '', - activityTargets: { - edges: [], - }, jobTitle: '', - favorites: { - edges: [], - }, - attachments: { - edges: [], - }, name: { firstName: '', lastName: '', diff --git a/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useCreateOneRecord.ts b/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useCreateOneRecord.ts index b7a2ebc0e3..764a786102 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useCreateOneRecord.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useCreateOneRecord.ts @@ -3,72 +3,28 @@ import { gql } from '@apollo/client'; export const query = gql` mutation CreateOnePerson($input: PersonCreateInput!) { createPerson(data: $input) { - id - opportunities { - edges { - node { - __typename - id - } + __typename + xLink { + label + url } - } - xLink { - label - url - } - id - pointOfContactForOpportunities { - edges { - node { - __typename - id - } - } - } - createdAt - company { - __typename id - } - city - email - activityTargets { - edges { - node { - __typename - id - } + createdAt + city + email + jobTitle + name { + firstName + lastName } - } - jobTitle - favorites { - edges { - node { - __typename - id - } + phone + linkedinLink { + label + url } - } - attachments { - edges { - node { - __typename - id - } - } - } - name { - firstName - lastName - } - phone - linkedinLink { - label - url - } - updatedAt - avatarUrl - companyId + updatedAt + avatarUrl + companyId } } `; diff --git a/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useExecuteQuickActionOnOneRecord.ts b/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useExecuteQuickActionOnOneRecord.ts index 343e3e59e5..1e9a1b5073 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useExecuteQuickActionOnOneRecord.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useExecuteQuickActionOnOneRecord.ts @@ -5,72 +5,28 @@ export { responseData } from './useUpdateOneRecord'; export const query = gql` mutation ExecuteQuickActionOnOnePerson($idToExecuteQuickActionOn: ID!) { executeQuickActionOnPerson(id: $idToExecuteQuickActionOn) { - id - opportunities { - edges { - node { - __typename - id - } + __typename + xLink { + label + url } - } - xLink { - label - url - } - id - pointOfContactForOpportunities { - edges { - node { - __typename - id - } - } - } - createdAt - company { - __typename id - } - city - email - activityTargets { - edges { - node { - __typename - id - } + createdAt + city + email + jobTitle + name { + firstName + lastName } - } - jobTitle - favorites { - edges { - node { - __typename - id - } + phone + linkedinLink { + label + url } - } - attachments { - edges { - node { - __typename - id - } - } - } - name { - firstName - lastName - } - phone - linkedinLink { - label - url - } - updatedAt - avatarUrl - companyId + updatedAt + avatarUrl + companyId } } `; diff --git a/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useFindOneRecord.ts b/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useFindOneRecord.ts index 1da25c843e..d0907398b8 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useFindOneRecord.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useFindOneRecord.ts @@ -5,72 +5,28 @@ import { responseData as person } from './useUpdateOneRecord'; export const query = gql` query FindOnePerson($objectRecordId: UUID!) { person(filter: { id: { eq: $objectRecordId } }) { - id - opportunities { - edges { - node { - __typename - id + __typename + xLink { + label + url } - } - } - xLink { - label - url - } - id - pointOfContactForOpportunities { - edges { - node { - __typename - id + id + createdAt + city + email + jobTitle + name { + firstName + lastName } - } - } - createdAt - company { - __typename - id - } - city - email - activityTargets { - edges { - node { - __typename - id + phone + linkedinLink { + label + url } - } - } - jobTitle - favorites { - edges { - node { - __typename - id - } - } - } - attachments { - edges { - node { - __typename - id - } - } - } - name { - firstName - lastName - } - phone - linkedinLink { - label - url - } - updatedAt - avatarUrl - companyId + updatedAt + avatarUrl + companyId } } `; diff --git a/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useUpdateOneRecord.ts b/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useUpdateOneRecord.ts index e78e9e27b8..46b48e1d0d 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useUpdateOneRecord.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useUpdateOneRecord.ts @@ -3,72 +3,28 @@ import { gql } from '@apollo/client'; export const query = gql` mutation UpdateOnePerson($idToUpdate: ID!, $input: PersonUpdateInput!) { updatePerson(id: $idToUpdate, data: $input) { - id - opportunities { - edges { - node { - __typename - id - } + __typename + xLink { + label + url } - } - xLink { - label - url - } - id - pointOfContactForOpportunities { - edges { - node { - __typename - id - } - } - } - createdAt - company { - __typename id - } - city - email - activityTargets { - edges { - node { - __typename - id - } + createdAt + city + email + jobTitle + name { + firstName + lastName } - } - jobTitle - favorites { - edges { - node { - __typename - id - } + phone + linkedinLink { + label + url } - } - attachments { - edges { - node { - __typename - id - } - } - } - name { - firstName - lastName - } - phone - linkedinLink { - label - url - } - updatedAt - avatarUrl - companyId + updatedAt + avatarUrl + companyId } } `; @@ -127,6 +83,6 @@ export const variables = { }; export const responseData = { - ...basePerson, + ...{ ...basePerson, __typename: 'Person' }, ...connectedObjects, }; diff --git a/packages/twenty-front/src/modules/object-record/hooks/useGenerateCreateManyRecordMutation.ts b/packages/twenty-front/src/modules/object-record/hooks/useGenerateCreateManyRecordMutation.ts index 3a66eb825c..03d7316e87 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useGenerateCreateManyRecordMutation.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useGenerateCreateManyRecordMutation.ts @@ -1,8 +1,10 @@ import { gql } from '@apollo/client'; +import { useRecoilValue } from 'recoil'; -import { useMapFieldMetadataToGraphQLQuery } from '@/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery'; import { EMPTY_MUTATION } from '@/object-metadata/hooks/useObjectMetadataItem'; +import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; +import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObjectMetadataToGraphQLQuery'; import { isNullable } from '~/utils/isNullable'; import { capitalize } from '~/utils/string/capitalize'; @@ -15,7 +17,7 @@ export const useGenerateCreateManyRecordMutation = ({ }: { objectMetadataItem: ObjectMetadataItem; }) => { - const mapFieldMetadataToGraphQLQuery = useMapFieldMetadataToGraphQLQuery(); + const objectMetadataItems = useRecoilValue(objectMetadataItemsState()); if (isNullable(objectMetadataItem)) { return EMPTY_MUTATION; @@ -29,15 +31,9 @@ export const useGenerateCreateManyRecordMutation = ({ mutation Create${capitalize( objectMetadataItem.namePlural, )}($data: [${capitalize(objectMetadataItem.nameSingular)}CreateInput!]!) { - ${mutationResponseField}(data: $data) { - id - ${objectMetadataItem.fields - .map((field) => - mapFieldMetadataToGraphQLQuery({ - field, - }), - ) - .join('\n')} - } + ${mutationResponseField}(data: $data) ${mapObjectMetadataToGraphQLQuery({ + objectMetadataItems, + objectMetadataItem, + })} }`; }; diff --git a/packages/twenty-front/src/modules/object-record/hooks/useGenerateCreateOneRecordMutation.ts b/packages/twenty-front/src/modules/object-record/hooks/useGenerateCreateOneRecordMutation.ts index 9f2d1ccf57..4847e602b1 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useGenerateCreateOneRecordMutation.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useGenerateCreateOneRecordMutation.ts @@ -1,8 +1,10 @@ import { gql } from '@apollo/client'; +import { useRecoilValue } from 'recoil'; -import { useMapFieldMetadataToGraphQLQuery } from '@/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery'; import { EMPTY_MUTATION } from '@/object-metadata/hooks/useObjectMetadataItem'; +import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; +import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObjectMetadataToGraphQLQuery'; import { isNullable } from '~/utils/isNullable'; import { capitalize } from '~/utils/string/capitalize'; @@ -15,7 +17,7 @@ export const useGenerateCreateOneRecordMutation = ({ }: { objectMetadataItem: ObjectMetadataItem; }) => { - const mapFieldMetadataToGraphQLQuery = useMapFieldMetadataToGraphQLQuery(); + const objectMetadataItems = useRecoilValue(objectMetadataItemsState()); if (isNullable(objectMetadataItem)) { return EMPTY_MUTATION; @@ -29,16 +31,10 @@ export const useGenerateCreateOneRecordMutation = ({ return gql` mutation CreateOne${capitalizedObjectName}($input: ${capitalizedObjectName}CreateInput!) { - ${mutationResponseField}(data: $input) { - id - ${objectMetadataItem.fields - .map((field) => - mapFieldMetadataToGraphQLQuery({ - field, - }), - ) - .join('\n')} - } + ${mutationResponseField}(data: $input) ${mapObjectMetadataToGraphQLQuery({ + objectMetadataItems, + objectMetadataItem, + })} } `; }; diff --git a/packages/twenty-front/src/modules/object-record/hooks/useGenerateExecuteQuickActionOnOneRecordMutation.ts b/packages/twenty-front/src/modules/object-record/hooks/useGenerateExecuteQuickActionOnOneRecordMutation.ts index 17a11dbfdc..28beba7bba 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useGenerateExecuteQuickActionOnOneRecordMutation.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useGenerateExecuteQuickActionOnOneRecordMutation.ts @@ -1,8 +1,10 @@ import { gql } from '@apollo/client'; +import { useRecoilValue } from 'recoil'; -import { useMapFieldMetadataToGraphQLQuery } from '@/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery'; import { EMPTY_MUTATION } from '@/object-metadata/hooks/useObjectMetadataItem'; +import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; +import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObjectMetadataToGraphQLQuery'; import { isNullable } from '~/utils/isNullable'; import { capitalize } from '~/utils/string/capitalize'; @@ -19,7 +21,7 @@ export const useGenerateExecuteQuickActionOnOneRecordMutation = ({ }: { objectMetadataItem: ObjectMetadataItem; }) => { - const mapFieldMetadataToGraphQLQuery = useMapFieldMetadataToGraphQLQuery(); + const objectMetadataItems = useRecoilValue(objectMetadataItemsState()); if (isNullable(objectMetadataItem)) { return EMPTY_MUTATION; @@ -34,16 +36,12 @@ export const useGenerateExecuteQuickActionOnOneRecordMutation = ({ return gql` mutation ExecuteQuickActionOnOne${capitalizedObjectName}($idToExecuteQuickActionOn: ID!) { - ${graphQLFieldForExecuteQuickActionOnOneRecordMutation}(id: $idToExecuteQuickActionOn) { - id - ${objectMetadataItem.fields - .map((field) => - mapFieldMetadataToGraphQLQuery({ - field, - }), - ) - .join('\n')} - } + ${graphQLFieldForExecuteQuickActionOnOneRecordMutation}(id: $idToExecuteQuickActionOn) ${mapObjectMetadataToGraphQLQuery( + { + objectMetadataItems, + objectMetadataItem, + }, + )} } `; }; diff --git a/packages/twenty-front/src/modules/object-record/hooks/useGenerateFindDuplicateRecordsQuery.ts b/packages/twenty-front/src/modules/object-record/hooks/useGenerateFindDuplicateRecordsQuery.ts index eaf2149343..51bba8e5fe 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useGenerateFindDuplicateRecordsQuery.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useGenerateFindDuplicateRecordsQuery.ts @@ -1,7 +1,9 @@ import { gql } from '@apollo/client'; +import { useRecoilValue } from 'recoil'; -import { useMapFieldMetadataToGraphQLQuery } from '@/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery'; +import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; +import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObjectMetadataToGraphQLQuery'; import { capitalize } from '~/utils/string/capitalize'; export const getFindDuplicateRecordsQueryResponseField = ( @@ -9,13 +11,13 @@ export const getFindDuplicateRecordsQueryResponseField = ( ) => `${objectNameSingular}Duplicates`; export const useGenerateFindDuplicateRecordsQuery = () => { - const mapFieldMetadataToGraphQLQuery = useMapFieldMetadataToGraphQLQuery(); + const objectMetadataItems = useRecoilValue(objectMetadataItemsState()); return ({ objectMetadataItem, depth, }: { - objectMetadataItem: ObjectMetadataItem; + objectMetadataItem: Pick; depth?: number; }) => gql` query FindDuplicate${capitalize(objectMetadataItem.nameSingular)}($id: ID) { @@ -23,17 +25,11 @@ export const useGenerateFindDuplicateRecordsQuery = () => { objectMetadataItem.nameSingular, )}(id: $id) { edges { - node { - id - ${objectMetadataItem.fields - .map((field) => - mapFieldMetadataToGraphQLQuery({ - field, - depth, - }), - ) - .join('\n')} - } + node ${mapObjectMetadataToGraphQLQuery({ + objectMetadataItems, + objectMetadataItem, + depth, + })} cursor } pageInfo { diff --git a/packages/twenty-front/src/modules/object-record/hooks/useGenerateFindManyRecordsForMultipleMetadataItemsQuery.ts b/packages/twenty-front/src/modules/object-record/hooks/useGenerateFindManyRecordsForMultipleMetadataItemsQuery.ts index 2874d32e35..c453b0c1bc 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useGenerateFindManyRecordsForMultipleMetadataItemsQuery.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useGenerateFindManyRecordsForMultipleMetadataItemsQuery.ts @@ -1,7 +1,7 @@ import { gql } from '@apollo/client'; -import { useMapFieldMetadataToGraphQLQuery } from '@/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; +import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObjectMetadataToGraphQLQuery'; import { isNonEmptyArray } from '~/utils/isNonEmptyArray'; import { capitalize } from '~/utils/string/capitalize'; @@ -12,8 +12,6 @@ export const useGenerateFindManyRecordsForMultipleMetadataItemsQuery = ({ objectMetadataItems: ObjectMetadataItem[]; depth?: number; }) => { - const mapFieldMetadataToGraphQLQuery = useMapFieldMetadataToGraphQLQuery(); - const capitalizedObjectNameSingulars = objectMetadataItems.map( ({ nameSingular }) => capitalize(nameSingular), ); @@ -59,26 +57,22 @@ export const useGenerateFindManyRecordsForMultipleMetadataItemsQuery = ({ ) { ${objectMetadataItems .map( - ({ namePlural, nameSingular, fields }) => - `${namePlural}(filter: $filter${capitalize( - nameSingular, + (objectMetadataItem) => + `${objectMetadataItem.namePlural}(filter: $filter${capitalize( + objectMetadataItem.nameSingular, )}, orderBy: $orderBy${capitalize( - nameSingular, + objectMetadataItem.nameSingular, )}, first: $limit${capitalize( - nameSingular, - )}, after: $lastCursor${capitalize(nameSingular)}){ + objectMetadataItem.nameSingular, + )}, after: $lastCursor${capitalize( + objectMetadataItem.nameSingular, + )}){ edges { - node { - id - ${fields - .map((field) => - mapFieldMetadataToGraphQLQuery({ - field, - depth, - }), - ) - .join('\n')} - } + node ${mapObjectMetadataToGraphQLQuery({ + objectMetadataItems, + objectMetadataItem, + depth, + })} cursor } pageInfo { diff --git a/packages/twenty-front/src/modules/object-record/hooks/useGenerateFindManyRecordsQuery.ts b/packages/twenty-front/src/modules/object-record/hooks/useGenerateFindManyRecordsQuery.ts index 623a0416c4..db6d077817 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useGenerateFindManyRecordsQuery.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useGenerateFindManyRecordsQuery.ts @@ -1,18 +1,25 @@ import { gql } from '@apollo/client'; +import { useRecoilValue } from 'recoil'; -import { useMapFieldMetadataToGraphQLQuery } from '@/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery'; +import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; +import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObjectMetadataToGraphQLQuery'; import { capitalize } from '~/utils/string/capitalize'; export const useGenerateFindManyRecordsQuery = () => { - const mapFieldMetadataToGraphQLQuery = useMapFieldMetadataToGraphQLQuery(); + const objectMetadataItems = useRecoilValue(objectMetadataItemsState()); return ({ objectMetadataItem, depth, + eagerLoadedRelations, }: { - objectMetadataItem: ObjectMetadataItem; + objectMetadataItem: Pick< + ObjectMetadataItem, + 'fields' | 'nameSingular' | 'namePlural' + >; depth?: number; + eagerLoadedRelations?: Record; }) => gql` query FindMany${capitalize( objectMetadataItem.namePlural, @@ -25,17 +32,12 @@ export const useGenerateFindManyRecordsQuery = () => { objectMetadataItem.namePlural }(filter: $filter, orderBy: $orderBy, first: $limit, after: $lastCursor){ edges { - node { - id - ${objectMetadataItem.fields - .map((field) => - mapFieldMetadataToGraphQLQuery({ - field, - depth, - }), - ) - .join('\n')} - } + node ${mapObjectMetadataToGraphQLQuery({ + objectMetadataItems, + objectMetadataItem, + depth, + eagerLoadedRelations, + })} cursor } pageInfo { diff --git a/packages/twenty-front/src/modules/object-record/hooks/useGenerateFindOneRecordQuery.ts b/packages/twenty-front/src/modules/object-record/hooks/useGenerateFindOneRecordQuery.ts index e4f7b41c24..77a14395a8 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useGenerateFindOneRecordQuery.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useGenerateFindOneRecordQuery.ts @@ -1,17 +1,19 @@ import { gql } from '@apollo/client'; +import { useRecoilValue } from 'recoil'; -import { useMapFieldMetadataToGraphQLQuery } from '@/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery'; +import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; +import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObjectMetadataToGraphQLQuery'; import { capitalize } from '~/utils/string/capitalize'; export const useGenerateFindOneRecordQuery = () => { - const mapFieldMetadataToGraphQLQuery = useMapFieldMetadataToGraphQLQuery(); + const objectMetadataItems = useRecoilValue(objectMetadataItemsState()); return ({ objectMetadataItem, depth, }: { - objectMetadataItem: Pick; + objectMetadataItem: Pick; depth?: number; }) => { return gql` @@ -22,17 +24,11 @@ export const useGenerateFindOneRecordQuery = () => { id: { eq: $objectRecordId } - }){ - id - ${objectMetadataItem.fields - .map((field) => - mapFieldMetadataToGraphQLQuery({ - field, - depth, - }), - ) - .join('\n')} - } + })${mapObjectMetadataToGraphQLQuery({ + objectMetadataItems, + objectMetadataItem, + depth, + })} } `; }; diff --git a/packages/twenty-front/src/modules/object-record/hooks/useGenerateUpdateOneRecordMutation.ts b/packages/twenty-front/src/modules/object-record/hooks/useGenerateUpdateOneRecordMutation.ts index a7da5cbc9f..c982ae028b 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useGenerateUpdateOneRecordMutation.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useGenerateUpdateOneRecordMutation.ts @@ -1,8 +1,10 @@ import { gql } from '@apollo/client'; +import { useRecoilValue } from 'recoil'; -import { useMapFieldMetadataToGraphQLQuery } from '@/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery'; import { EMPTY_MUTATION } from '@/object-metadata/hooks/useObjectMetadataItem'; +import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; +import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObjectMetadataToGraphQLQuery'; import { isNullable } from '~/utils/isNullable'; import { capitalize } from '~/utils/string/capitalize'; @@ -15,7 +17,7 @@ export const useGenerateUpdateOneRecordMutation = ({ }: { objectMetadataItem: ObjectMetadataItem; }) => { - const mapFieldMetadataToGraphQLQuery = useMapFieldMetadataToGraphQLQuery(); + const objectMetadataItems = useRecoilValue(objectMetadataItemsState()); if (isNullable(objectMetadataItem)) { return EMPTY_MUTATION; @@ -29,12 +31,12 @@ export const useGenerateUpdateOneRecordMutation = ({ return gql` mutation UpdateOne${capitalizedObjectName}($idToUpdate: ID!, $input: ${capitalizedObjectName}UpdateInput!) { - ${mutationResponseField}(id: $idToUpdate, data: $input) { - id - ${objectMetadataItem.fields - .map((field) => mapFieldMetadataToGraphQLQuery({ field })) - .join('\n')} - } + ${mutationResponseField}(id: $idToUpdate, data: $input) ${mapObjectMetadataToGraphQLQuery( + { + objectMetadataItems, + objectMetadataItem, + }, + )} } `; }; diff --git a/packages/twenty-front/src/modules/object-record/hooks/useUpdateOneRecord.ts b/packages/twenty-front/src/modules/object-record/hooks/useUpdateOneRecord.ts index c76431c24c..e2e11bd00c 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useUpdateOneRecord.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useUpdateOneRecord.ts @@ -20,7 +20,7 @@ export const useUpdateOneRecord = < const apolloClient = useApolloClient(); const { objectMetadataItem, updateOneRecordMutation, getRecordFromCache } = - useObjectMetadataItem({ objectNameSingular }); + useObjectMetadataItem({ objectNameSingular }, 1); const { generateObjectRecordOptimisticResponse } = useGenerateObjectRecordOptimisticResponse({ diff --git a/packages/twenty-front/src/modules/object-record/record-field/hooks/__tests__/usePersistField.test.tsx b/packages/twenty-front/src/modules/object-record/record-field/hooks/__tests__/usePersistField.test.tsx index 84da3f62d3..45dd03a21c 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/hooks/__tests__/usePersistField.test.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/hooks/__tests__/usePersistField.test.tsx @@ -20,14 +20,31 @@ import { FieldDefinition } from '@/object-record/record-field/types/FieldDefinit import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata'; import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector'; -jest.mock('@/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery', () => ({ - useMapFieldMetadataToGraphQLQuery: () => () => '\n', -})); - const query = gql` mutation UpdateOnePerson($idToUpdate: ID!, $input: PersonUpdateInput!) { updatePerson(id: $idToUpdate, data: $input) { + __typename + xLink { + label + url + } id + createdAt + city + email + jobTitle + name { + firstName + lastName + } + phone + linkedinLink { + label + url + } + updatedAt + avatarUrl + companyId } } `; diff --git a/packages/twenty-front/src/modules/object-record/record-field/hooks/__tests__/useToggleEditOnlyInput.test.tsx b/packages/twenty-front/src/modules/object-record/record-field/hooks/__tests__/useToggleEditOnlyInput.test.tsx index f07739124d..3a073219f9 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/hooks/__tests__/useToggleEditOnlyInput.test.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/hooks/__tests__/useToggleEditOnlyInput.test.tsx @@ -14,10 +14,6 @@ import { } from '@/object-record/record-field/contexts/FieldContext'; import { useToggleEditOnlyInput } from '@/object-record/record-field/hooks/useToggleEditOnlyInput'; -jest.mock('@/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery', () => ({ - useMapFieldMetadataToGraphQLQuery: () => () => '\n', -})); - const entityId = 'entityId'; const mocks: MockedResponse[] = [ @@ -29,7 +25,28 @@ const mocks: MockedResponse[] = [ $input: CompanyUpdateInput! ) { updateCompany(id: $idToUpdate, data: $input) { + __typename + xLink { + label + url + } + linkedinLink { + label + url + } + domainName + annualRecurringRevenue { + amountMicros + currencyCode + } + createdAt + address + updatedAt + name + accountOwnerId + employees id + idealCustomerProfile } } `, diff --git a/packages/twenty-front/src/modules/object-record/relation-picker/hooks/__tests__/useMultiObjectSearch.test.tsx b/packages/twenty-front/src/modules/object-record/relation-picker/hooks/__tests__/useMultiObjectSearch.test.tsx index 23dafb5788..9ce41b1174 100644 --- a/packages/twenty-front/src/modules/object-record/relation-picker/hooks/__tests__/useMultiObjectSearch.test.tsx +++ b/packages/twenty-front/src/modules/object-record/relation-picker/hooks/__tests__/useMultiObjectSearch.test.tsx @@ -6,6 +6,7 @@ import { RecoilRoot, useSetRecoilState } from 'recoil'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { useMultiObjectSearch } from '@/object-record/relation-picker/hooks/useMultiObjectSearch'; +import { FieldMetadataType } from '~/generated/graphql'; const query = gql` query FindManyRecordsMultipleMetadataItems( @@ -22,6 +23,7 @@ const query = gql` ) { edges { node { + __typename id } cursor @@ -36,7 +38,7 @@ const query = gql` `; const response = { namePlural: { - edges: [{ node: { id: 'nodeId' }, cursor: 'cursor' }], + edges: [{ node: { __typename: 'Custom', id: 'nodeId' }, cursor: 'cursor' }], pageInfo: { startCursor: '', hasNextPage: '', endCursor: '' }, }, }; @@ -120,7 +122,17 @@ describe('useMultiObjectSearch', () => { namePlural: 'namePlural', nameSingular: 'nameSingular', updatedAt: 'updatedAt', - fields: [], + fields: [ + { + id: 'f6a0a73a-5ee6-442e-b764-39b682471240', + name: 'id', + label: 'id', + type: FieldMetadataType.Uuid, + createdAt: '2024-01-01T00:00:00.000Z', + updatedAt: '2024-01-01T00:00:00.000Z', + isActive: true, + }, + ], }, ]; act(() => { @@ -144,9 +156,19 @@ describe('useMultiObjectSearch', () => { namePlural: 'namePlural', nameSingular: 'nameSingular', updatedAt: 'updatedAt', - fields: [], + fields: [ + { + id: 'f6a0a73a-5ee6-442e-b764-39b682471240', + name: 'id', + label: 'id', + isActive: true, + type: FieldMetadataType.Uuid, + createdAt: '2024-01-01T00:00:00.000Z', + updatedAt: '2024-01-01T00:00:00.000Z', + }, + ], }, - record: { id: 'nodeId' }, + record: { id: 'nodeId', __typename: 'Custom' }, recordIdentifier: { id: 'nodeId', name: '', diff --git a/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectSearchMatchesSearchFilterAndSelectedItemsQuery.ts b/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectSearchMatchesSearchFilterAndSelectedItemsQuery.ts index 2afb0c8ea9..31855d7552 100644 --- a/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectSearchMatchesSearchFilterAndSelectedItemsQuery.ts +++ b/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectSearchMatchesSearchFilterAndSelectedItemsQuery.ts @@ -86,6 +86,7 @@ export const useMultiObjectSearchMatchesSearchFilterAndSelectedItemsQuery = ({ const multiSelectQueryForSelectedIds = useGenerateFindManyRecordsForMultipleMetadataItemsQuery({ objectMetadataItems: objectMetadataItemsUsedInSelectedIdsQuery, + depth: 0, }); const { diff --git a/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectSearchMatchesSearchFilterAndToSelectQuery.ts b/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectSearchMatchesSearchFilterAndToSelectQuery.ts index 6c17af6db2..5052bab768 100644 --- a/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectSearchMatchesSearchFilterAndToSelectQuery.ts +++ b/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectSearchMatchesSearchFilterAndToSelectQuery.ts @@ -87,6 +87,7 @@ export const useMultiObjectSearchMatchesSearchFilterAndToSelectQuery = ({ const multiSelectQuery = useGenerateFindManyRecordsForMultipleMetadataItemsQuery({ objectMetadataItems: nonSystemObjectMetadataItems, + depth: 0, }); const { diff --git a/packages/twenty-front/src/modules/object-record/spreadsheet-import/__tests__/useSpreadsheetRecordImport.test.tsx b/packages/twenty-front/src/modules/object-record/spreadsheet-import/__tests__/useSpreadsheetRecordImport.test.tsx index 248b81a94a..6e400e579d 100644 --- a/packages/twenty-front/src/modules/object-record/spreadsheet-import/__tests__/useSpreadsheetRecordImport.test.tsx +++ b/packages/twenty-front/src/modules/object-record/spreadsheet-import/__tests__/useSpreadsheetRecordImport.test.tsx @@ -16,17 +16,34 @@ jest.mock('uuid', () => ({ v4: jest.fn(() => companyId), })); -jest.mock('@/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery', () => ({ - useMapFieldMetadataToGraphQLQuery: () => () => '\n', -})); - const companyMocks = [ { request: { query: gql` mutation CreateCompanies($data: [CompanyCreateInput!]!) { createCompanies(data: $data) { + __typename + xLink { + label + url + } + linkedinLink { + label + url + } + domainName + annualRecurringRevenue { + amountMicros + currencyCode + } + createdAt + address + updatedAt + name + accountOwnerId + employees id + idealCustomerProfile } } `, diff --git a/packages/twenty-front/src/modules/views/hooks/__tests__/useViewBar.test.tsx b/packages/twenty-front/src/modules/views/hooks/__tests__/useViewBar.test.tsx index 3170a5f27d..d0a0de37cd 100644 --- a/packages/twenty-front/src/modules/views/hooks/__tests__/useViewBar.test.tsx +++ b/packages/twenty-front/src/modules/views/hooks/__tests__/useViewBar.test.tsx @@ -23,12 +23,6 @@ import { entityCountInCurrentViewScopedState } from '@/views/states/entityCountI import { viewEditModeScopedState } from '@/views/states/viewEditModeScopedState'; import { viewObjectMetadataIdScopeState } from '@/views/states/viewObjectMetadataIdScopeState'; -jest.mock('@/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery', () => { - return { - useMapFieldMetadataToGraphQLQuery: jest.fn().mockReturnValue(() => '\n'), - }; -}); - const mockedUuid = 'mocked-uuid'; jest.mock('uuid'); diff --git a/packages/twenty-front/src/modules/views/hooks/__tests__/useViewBar_ViewFields.test.tsx b/packages/twenty-front/src/modules/views/hooks/__tests__/useViewBar_ViewFields.test.tsx index 357ebd9db5..7097205dc4 100644 --- a/packages/twenty-front/src/modules/views/hooks/__tests__/useViewBar_ViewFields.test.tsx +++ b/packages/twenty-front/src/modules/views/hooks/__tests__/useViewBar_ViewFields.test.tsx @@ -14,12 +14,6 @@ import { ViewScope } from '@/views/scopes/ViewScope'; import { currentViewFieldsScopedFamilyState } from '@/views/states/currentViewFieldsScopedFamilyState'; import { ViewField } from '@/views/types/ViewField'; -jest.mock('@/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery', () => { - return { - useMapFieldMetadataToGraphQLQuery: jest.fn().mockReturnValue(() => '\n'), - }; -}); - const fieldMetadataId = '12ecdf87-506f-44a7-98c6-393e5f05b225'; const fieldDefinition: ColumnDefinition = { @@ -53,7 +47,15 @@ const mocks = [ query: gql` mutation CreateOneViewField($input: ViewFieldCreateInput!) { createViewField(data: $input) { + __typename + position + isVisible + fieldMetadataId + viewId id + size + createdAt + updatedAt } } `,