[Fix] Bug with one to many update in table #16340 (#17416)

fixes #16340 

we are updating the cache partially to prevent the flow of the corrupted
fields into the cache

the fields get corrupted during the mutation process potentially
overriding the recoil cache as we are passing the `newRecordCache`
directly in `upsertRecordsInStore`, the newRecordCache is thin and hence
the recoil wipes out the fields that are undefined or empty



we prevent this by extracting the partial data ( only the data which is
being updated in the field ) and passing it to `upsertRecordsInStore`,
as it only touched the specific fields and updates the data, leaving the
other fields untouched.


https://github.com/user-attachments/assets/5256bef7-70c3-47b3-b2ce-dd02ee1a2de8

---------

Co-authored-by: Arun kumar <arunkumar@Aruns-MacBook-Air.local>
Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
This commit is contained in:
Arun
2026-01-26 18:57:56 +05:30
committed by GitHub
parent 406e269cf1
commit 4a5ffcc9d2
3 changed files with 44 additions and 1 deletions
@@ -1,4 +1,4 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`generateDepthRecordGqlFieldsFromObject should generate depth one record gql fields from object 1`] = `
{
@@ -29,6 +29,10 @@ exports[`generateDepthRecordGqlFieldsFromObject should generate depth one record
"name": true,
"noteTargets": {
"id": true,
"note": {
"id": true,
"title": true,
},
},
"opportunities": {
"id": true,
@@ -44,6 +48,10 @@ exports[`generateDepthRecordGqlFieldsFromObject should generate depth one record
"tagline": true,
"taskTargets": {
"id": true,
"task": {
"id": true,
"title": true,
},
},
"timelineActivities": {
"id": true,
@@ -1,8 +1,11 @@
import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { getImageIdentifierFieldMetadataItem } from '@/object-metadata/utils/getImageIdentifierFieldMetadataItem';
import { getLabelIdentifierFieldMetadataItem } from '@/object-metadata/utils/getLabelIdentifierFieldMetadataItem';
import { type RecordGqlFields } from '@/object-record/graphql/record-gql-fields/types/RecordGqlFields';
import { generateActivityTargetGqlFields } from '@/object-record/graphql/record-gql-fields/utils/generateActivityTargetGqlFields';
import { generateJunctionRelationGqlFields } from '@/object-record/graphql/record-gql-fields/utils/generateJunctionRelationGqlFields';
import { isJunctionRelationField } from '@/object-record/record-field/ui/utils/junction/isJunctionRelationField';
import { FieldMetadataType, RelationType } from 'twenty-shared/types';
@@ -54,6 +57,28 @@ export const generateDepthRecordGqlFieldsFromFields = ({
);
}
const isActivityTargetField =
fieldMetadata.name === CoreObjectNamePlural.NoteTarget ||
fieldMetadata.name === CoreObjectNamePlural.TaskTarget;
if (isActivityTargetField && depth === 1) {
const activityTargetObjectNameSingular =
fieldMetadata.name === CoreObjectNamePlural.NoteTarget
? CoreObjectNameSingular.Note
: CoreObjectNameSingular.Task;
const activityTargetGqlFields = generateActivityTargetGqlFields({
activityObjectNameSingular: activityTargetObjectNameSingular,
objectMetadataItems,
loadRelations: 'activity',
});
return {
...recordGqlFields,
[fieldMetadata.name]: activityTargetGqlFields,
};
}
if (isJunctionRelationField(fieldMetadata)) {
const junctionGqlFields = generateJunctionRelationGqlFields({
fieldMetadataItem: fieldMetadata,
@@ -129,6 +129,11 @@ export const PERSON_FRAGMENT_WITH_DEPTH_ONE_RELATIONS = `
node {
__typename
id
note {
__typename
id
title
}
}
}
}
@@ -154,6 +159,11 @@ export const PERSON_FRAGMENT_WITH_DEPTH_ONE_RELATIONS = `
node {
__typename
id
task {
__typename
id
title
}
}
}
}