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 <bordeau.lucas@gmail.com>
Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Guillim
2025-11-18 18:49:18 +01:00
committed by GitHub
parent f6cd51ba97
commit bcb097256b
6 changed files with 274 additions and 21 deletions
@@ -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,
}
`;
@@ -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();
});
});
});
@@ -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'.`,
);
};
@@ -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'
@@ -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',
}),
};
};
@@ -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,
}),
},
});