From bcb097256bf322aa8a61f9da57d4cd26864cb9f4 Mon Sep 17 00:00:00 2001 From: Guillim Date: Tue, 18 Nov 2025 18:49:18 +0100 Subject: [PATCH] fix-15838 (#15898) I know you did this change in order to make the frontend faster and less resource eating. However it broke this optimistic rendering part @lucasbordeau. I suggest this quick fix, but I am open to a sharper one as well Fixes https://github.com/twentyhq/twenty/issues/15838 --------- Co-authored-by: Lucas Bordeau Co-authored-by: Charles Bochet --- ...nerateActivityTargetGqlFields.test.ts.snap | 155 ++++++++++++++++++ .../generateActivityTargetGqlFields.test.ts | 67 ++++++++ .../utils/generateActivityTargetGqlFields.ts | 52 ++++-- .../generateDepthRecordGqlFieldsFromFields.ts | 5 +- .../hooks/useRecordsFieldVisibleGqlFields.ts | 10 +- ...ordForShowPageOperationSignatureFactory.ts | 6 +- 6 files changed, 274 insertions(+), 21 deletions(-) create mode 100644 packages/twenty-front/src/modules/object-record/graphql/record-gql-fields/utils/__tests__/__snapshots__/generateActivityTargetGqlFields.test.ts.snap create mode 100644 packages/twenty-front/src/modules/object-record/graphql/record-gql-fields/utils/__tests__/generateActivityTargetGqlFields.test.ts diff --git a/packages/twenty-front/src/modules/object-record/graphql/record-gql-fields/utils/__tests__/__snapshots__/generateActivityTargetGqlFields.test.ts.snap b/packages/twenty-front/src/modules/object-record/graphql/record-gql-fields/utils/__tests__/__snapshots__/generateActivityTargetGqlFields.test.ts.snap new file mode 100644 index 0000000000..2878c5420f --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/graphql/record-gql-fields/utils/__tests__/__snapshots__/generateActivityTargetGqlFields.test.ts.snap @@ -0,0 +1,155 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`generateActivityTargetGqlFields snapshot tests should match snapshot for Note with loadRelations="activity" 1`] = ` +{ + "id": true, + "note": { + "id": true, + "title": true, + }, +} +`; + +exports[`generateActivityTargetGqlFields snapshot tests should match snapshot for Note with loadRelations="both" 1`] = ` +{ + "company": { + "domainName": true, + "id": true, + "name": true, + }, + "companyId": true, + "createdAt": true, + "deletedAt": true, + "id": true, + "note": { + "id": true, + "title": true, + }, + "noteId": true, + "opportunity": { + "id": true, + "name": true, + }, + "opportunityId": true, + "person": { + "avatarUrl": true, + "id": true, + "name": true, + }, + "personId": true, + "pet": { + "id": true, + "name": true, + }, + "petId": true, + "rocket": { + "id": true, + "name": true, + }, + "rocketId": true, + "surveyResult": { + "id": true, + "name": true, + }, + "surveyResultId": true, + "updatedAt": true, +} +`; + +exports[`generateActivityTargetGqlFields snapshot tests should match snapshot for Note with loadRelations="relations" 1`] = ` +{ + "company": true, + "companyId": true, + "createdAt": true, + "deletedAt": true, + "id": true, + "opportunity": true, + "opportunityId": true, + "person": true, + "personId": true, + "pet": true, + "petId": true, + "rocket": true, + "rocketId": true, + "surveyResult": true, + "surveyResultId": true, + "updatedAt": true, +} +`; + +exports[`generateActivityTargetGqlFields snapshot tests should match snapshot for Task with loadRelations="activity" 1`] = ` +{ + "id": true, + "task": { + "id": true, + "title": true, + }, +} +`; + +exports[`generateActivityTargetGqlFields snapshot tests should match snapshot for Task with loadRelations="both" 1`] = ` +{ + "company": { + "domainName": true, + "id": true, + "name": true, + }, + "companyId": true, + "createdAt": true, + "deletedAt": true, + "id": true, + "opportunity": { + "id": true, + "name": true, + }, + "opportunityId": true, + "person": { + "avatarUrl": true, + "id": true, + "name": true, + }, + "personId": true, + "pet": { + "id": true, + "name": true, + }, + "petId": true, + "rocket": { + "id": true, + "name": true, + }, + "rocketId": true, + "surveyResult": { + "id": true, + "name": true, + }, + "surveyResultId": true, + "task": { + "id": true, + "title": true, + }, + "taskId": true, + "updatedAt": true, +} +`; + +exports[`generateActivityTargetGqlFields snapshot tests should match snapshot for Task with loadRelations="relations" 1`] = ` +{ + "company": true, + "companyId": true, + "createdAt": true, + "deletedAt": true, + "id": true, + "opportunity": true, + "opportunityId": true, + "person": true, + "personId": true, + "pet": true, + "petId": true, + "rocket": true, + "rocketId": true, + "surveyResult": true, + "surveyResultId": true, + "updatedAt": true, +} +`; diff --git a/packages/twenty-front/src/modules/object-record/graphql/record-gql-fields/utils/__tests__/generateActivityTargetGqlFields.test.ts b/packages/twenty-front/src/modules/object-record/graphql/record-gql-fields/utils/__tests__/generateActivityTargetGqlFields.test.ts new file mode 100644 index 0000000000..57b316f407 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/graphql/record-gql-fields/utils/__tests__/generateActivityTargetGqlFields.test.ts @@ -0,0 +1,67 @@ +import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; +import { generateActivityTargetGqlFields } from '@/object-record/graphql/record-gql-fields/utils/generateActivityTargetGqlFields'; +import { generatedMockObjectMetadataItems } from '~/testing/utils/generatedMockObjectMetadataItems'; + +describe('generateActivityTargetGqlFields', () => { + describe('snapshot tests', () => { + it('should match snapshot for Note with loadRelations="activity"', () => { + const result = generateActivityTargetGqlFields({ + objectMetadataItems: generatedMockObjectMetadataItems, + activityObjectNameSingular: CoreObjectNameSingular.Note, + loadRelations: 'activity', + }); + + expect(result).toMatchSnapshot(); + }); + + it('should match snapshot for Note with loadRelations="both"', () => { + const result = generateActivityTargetGqlFields({ + objectMetadataItems: generatedMockObjectMetadataItems, + activityObjectNameSingular: CoreObjectNameSingular.Note, + loadRelations: 'both', + }); + + expect(result).toMatchSnapshot(); + }); + + it('should match snapshot for Note with loadRelations="relations"', () => { + const result = generateActivityTargetGqlFields({ + objectMetadataItems: generatedMockObjectMetadataItems, + activityObjectNameSingular: CoreObjectNameSingular.Note, + loadRelations: 'relations', + }); + + expect(result).toMatchSnapshot(); + }); + + it('should match snapshot for Task with loadRelations="activity"', () => { + const result = generateActivityTargetGqlFields({ + objectMetadataItems: generatedMockObjectMetadataItems, + activityObjectNameSingular: CoreObjectNameSingular.Task, + loadRelations: 'activity', + }); + + expect(result).toMatchSnapshot(); + }); + + it('should match snapshot for Task with loadRelations="both"', () => { + const result = generateActivityTargetGqlFields({ + objectMetadataItems: generatedMockObjectMetadataItems, + activityObjectNameSingular: CoreObjectNameSingular.Task, + loadRelations: 'both', + }); + + expect(result).toMatchSnapshot(); + }); + + it('should match snapshot for Task with loadRelations="relations"', () => { + const result = generateActivityTargetGqlFields({ + objectMetadataItems: generatedMockObjectMetadataItems, + activityObjectNameSingular: CoreObjectNameSingular.Task, + loadRelations: 'relations', + }); + + expect(result).toMatchSnapshot(); + }); + }); +}); diff --git a/packages/twenty-front/src/modules/object-record/graphql/record-gql-fields/utils/generateActivityTargetGqlFields.ts b/packages/twenty-front/src/modules/object-record/graphql/record-gql-fields/utils/generateActivityTargetGqlFields.ts index 6975cab153..576b937b7f 100644 --- a/packages/twenty-front/src/modules/object-record/graphql/record-gql-fields/utils/generateActivityTargetGqlFields.ts +++ b/packages/twenty-front/src/modules/object-record/graphql/record-gql-fields/utils/generateActivityTargetGqlFields.ts @@ -5,19 +5,20 @@ import { generateDepthRecordGqlFieldsFromFields } from '@/object-record/graphql/ import { isDefined } from 'twenty-shared/utils'; export type GenerateDepthRecordGqlFields = { - objectMetadataItems: ObjectMetadataItem[]; + objectMetadataItems: Pick< + ObjectMetadataItem, + 'id' | 'nameSingular' | 'fields' | 'labelIdentifierFieldMetadataId' + >[]; activityObjectNameSingular: | CoreObjectNameSingular.Note | CoreObjectNameSingular.Task; - depth: 0 | 1; - shouldOnlyLoadActivityIdentifiers?: boolean; + loadRelations?: 'activity' | 'relations' | 'both'; }; export const generateActivityTargetGqlFields = ({ objectMetadataItems, activityObjectNameSingular, - depth, - shouldOnlyLoadActivityIdentifiers = true, + loadRelations = 'both', }: GenerateDepthRecordGqlFields) => { const isNote = activityObjectNameSingular === CoreObjectNameSingular.Note; const activityTargetNameSingular = isNote @@ -41,10 +42,10 @@ export const generateActivityTargetGqlFields = ({ return {}; } - if (shouldOnlyLoadActivityIdentifiers) { - const activityLabelIdentifierFieldMetadataItem = - getLabelIdentifierFieldMetadataItem(activityObjectMetadataItem); + const activityLabelIdentifierFieldMetadataItem = + getLabelIdentifierFieldMetadataItem(activityObjectMetadataItem); + if (loadRelations === 'activity') { return { id: true, [activityObjectNameSingular]: { @@ -54,15 +55,42 @@ export const generateActivityTargetGqlFields = ({ : {}), }, }; - } else { + } + + if (loadRelations === 'both') { + return { + id: true, + [activityObjectNameSingular]: { + id: true, + ...(isDefined(activityLabelIdentifierFieldMetadataItem) + ? { [activityLabelIdentifierFieldMetadataItem.name]: true } + : {}), + }, + ...generateDepthRecordGqlFieldsFromFields({ + depth: 1, + fields: activityTargetObjectMetadataItem.fields, + objectMetadataItems, + shouldOnlyLoadRelationIdentifiers: true, + }), + }; + } + + if (loadRelations === 'relations') { return { ...generateDepthRecordGqlFieldsFromFields({ - depth, - fields: activityTargetObjectMetadataItem.fields, + depth: 1, + fields: activityTargetObjectMetadataItem.fields.filter( + (fieldMetadataItem) => + fieldMetadataItem.name !== 'task' && + fieldMetadataItem.name !== 'note', + ), objectMetadataItems, shouldOnlyLoadRelationIdentifiers: false, }), - [activityObjectNameSingular]: true, }; } + + throw new Error( + `Invalid loadRelations value: ${loadRelations}. Please use 'activity', 'relations', or 'both'.`, + ); }; diff --git a/packages/twenty-front/src/modules/object-record/graphql/record-gql-fields/utils/generateDepthRecordGqlFieldsFromFields.ts b/packages/twenty-front/src/modules/object-record/graphql/record-gql-fields/utils/generateDepthRecordGqlFieldsFromFields.ts index 90e2bd009b..ce85bf1342 100644 --- a/packages/twenty-front/src/modules/object-record/graphql/record-gql-fields/utils/generateDepthRecordGqlFieldsFromFields.ts +++ b/packages/twenty-front/src/modules/object-record/graphql/record-gql-fields/utils/generateDepthRecordGqlFieldsFromFields.ts @@ -7,7 +7,10 @@ import { FieldMetadataType, RelationType } from 'twenty-shared/types'; import { computeMorphRelationFieldName, isDefined } from 'twenty-shared/utils'; export type GenerateDepthRecordGqlFieldsFromFields = { - objectMetadataItems: ObjectMetadataItem[]; + objectMetadataItems: Pick< + ObjectMetadataItem, + 'id' | 'fields' | 'labelIdentifierFieldMetadataId' | 'nameSingular' + >[]; fields: Pick< FieldMetadataItem, 'name' | 'type' | 'settings' | 'morphRelations' | 'relation' diff --git a/packages/twenty-front/src/modules/object-record/record-field/hooks/useRecordsFieldVisibleGqlFields.ts b/packages/twenty-front/src/modules/object-record/record-field/hooks/useRecordsFieldVisibleGqlFields.ts index f49e61582f..06895afd40 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/hooks/useRecordsFieldVisibleGqlFields.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/hooks/useRecordsFieldVisibleGqlFields.ts @@ -49,6 +49,10 @@ export const useRecordsFieldVisibleGqlFields = ({ ? fieldMetadataItemByFieldMetadataItemId[additionalFieldMetadataId] : undefined; + const isObjectAnActivity = + objectMetadataItem.nameSingular === CoreObjectNameSingular.Note || + objectMetadataItem.nameSingular === CoreObjectNameSingular.Task; + return { id: true, ...(isDefined(additionalFieldMetadataItem) @@ -67,15 +71,13 @@ export const useRecordsFieldVisibleGqlFields = ({ deletedAt: true, noteTargets: generateActivityTargetGqlFields({ activityObjectNameSingular: CoreObjectNameSingular.Note, - depth: 0, objectMetadataItems, - shouldOnlyLoadActivityIdentifiers: true, + loadRelations: isObjectAnActivity ? 'relations' : 'activity', }), taskTargets: generateActivityTargetGqlFields({ activityObjectNameSingular: CoreObjectNameSingular.Task, - depth: 0, objectMetadataItems, - shouldOnlyLoadActivityIdentifiers: true, + loadRelations: isObjectAnActivity ? 'relations' : 'activity', }), }; }; diff --git a/packages/twenty-front/src/modules/object-record/record-show/graphql/operations/factories/findOneRecordForShowPageOperationSignatureFactory.ts b/packages/twenty-front/src/modules/object-record/record-show/graphql/operations/factories/findOneRecordForShowPageOperationSignatureFactory.ts index 430dc7c64a..b4dd0e8107 100644 --- a/packages/twenty-front/src/modules/object-record/record-show/graphql/operations/factories/findOneRecordForShowPageOperationSignatureFactory.ts +++ b/packages/twenty-front/src/modules/object-record/record-show/graphql/operations/factories/findOneRecordForShowPageOperationSignatureFactory.ts @@ -28,15 +28,13 @@ export const buildFindOneRecordForShowPageOperationSignature: RecordGqlOperation }), noteTargets: generateActivityTargetGqlFields({ activityObjectNameSingular: CoreObjectNameSingular.Note, - depth: 1, + loadRelations: 'both', objectMetadataItems, - shouldOnlyLoadActivityIdentifiers: false, }), taskTargets: generateActivityTargetGqlFields({ activityObjectNameSingular: CoreObjectNameSingular.Task, - depth: 1, + loadRelations: 'both', objectMetadataItems, - shouldOnlyLoadActivityIdentifiers: false, }), }, });