Optimistic rendering of morph relations (#14997)

The optimistic rendering of a morph relation Is broken. We intend to fix
it here.

- mapping of gql fields


Fixes https://github.com/twentyhq/core-team-issues/issues/1322

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Guillim
2025-10-10 19:11:00 +02:00
committed by GitHub
parent 00eef8ba33
commit 448091e81f
9 changed files with 814 additions and 198 deletions
@@ -2,6 +2,8 @@ import { isNull, isUndefined } from '@sniptt/guards';
import { type CurrentWorkspaceMember } from '@/auth/states/currentWorkspaceMemberState';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { getFieldMetadataFromGqlField } from '@/object-record/cache/utils/getFieldMetadataFromGqlField';
import { getFieldMetadataMorphRelationFromGqlField } from '@/object-record/cache/utils/getFieldMetadataMorphRelationFromGqlField';
import {
getRecordFromCache,
type GetRecordFromCacheArgs,
@@ -51,16 +53,9 @@ export const computeOptimisticRecordFromInput = ({
objectMetadataItem.fields.find((field) => {
if (!isFieldMorphRelation(field)) return false;
return field.morphRelations?.some((morphRelation) => {
const computedFieldName = computeMorphRelationFieldName({
fieldName: field.name,
relationType: morphRelation.type,
targetObjectMetadataNameSingular:
morphRelation.targetObjectMetadata.nameSingular,
targetObjectMetadataNamePlural:
morphRelation.targetObjectMetadata.namePlural,
});
return computedFieldName === recordKey.replace('Id', '');
return getFieldMetadataFromGqlField({
objectMetadataItem,
gqlField: recordKey,
});
});
@@ -120,7 +115,8 @@ export const computeOptimisticRecordFromInput = ({
}
const isRelationField = isFieldRelation(fieldMetadataItem);
if (!isRelationField) {
const isMorphRelationField = isFieldMorphRelation(fieldMetadataItem);
if (!isRelationField && !isMorphRelationField) {
if (!isDefined(recordInputFieldValue)) {
continue;
}
@@ -133,78 +129,183 @@ export const computeOptimisticRecordFromInput = ({
continue;
}
if (fieldMetadataItem.relation?.type === RelationType.ONE_TO_MANY) {
continue;
}
if (isRelationField) {
if (fieldMetadataItem.relation?.type === RelationType.ONE_TO_MANY) {
continue;
}
const isManyToOneRelation =
fieldMetadataItem.relation?.type === RelationType.MANY_TO_ONE;
if (!isManyToOneRelation) {
continue;
}
const isManyToOneRelation =
fieldMetadataItem.relation?.type === RelationType.MANY_TO_ONE;
if (!isManyToOneRelation) {
continue;
}
if (!isUndefined(recordInputFieldValue)) {
throw new Error(
`Should never provide relation mutation through anything else than the fieldId e.g companyId and not company, encountered: ${fieldMetadataItem.name}`,
if (!isUndefined(recordInputFieldValue)) {
throw new Error(
`Should never provide relation mutation through anything else than the fieldId e.g companyId and not company, encountered: ${fieldMetadataItem.name}`,
);
}
const relationFieldIdName = getForeignKeyNameFromRelationFieldName(
fieldMetadataItem.name,
);
}
const relationFieldIdName = getForeignKeyNameFromRelationFieldName(
fieldMetadataItem.name,
);
const recordInputFieldIdValue: string | null | undefined =
recordInput[relationFieldIdName];
const recordInputFieldIdValue: string | null | undefined =
recordInput[relationFieldIdName];
if (isUndefined(recordInputFieldIdValue)) {
continue;
}
if (isUndefined(recordInputFieldIdValue)) {
continue;
}
const relationIdFieldMetadataItem = objectMetadataItem.fields.find(
(field) => field.name === relationFieldIdName,
);
if (
!isDefined(relationIdFieldMetadataItem) &&
!isDefined(fieldMetadataItem.settings?.joinColumnName)
) {
throw new Error(
'Should never occur, encountered unknown relationId within relations definitions',
const relationIdFieldMetadataItem = objectMetadataItem.fields.find(
(field) => field.name === relationFieldIdName,
);
}
if (isNull(recordInputFieldIdValue)) {
optimisticRecord[relationFieldIdName] = null;
optimisticRecord[fieldMetadataItem.name] = null;
continue;
}
if (
!isDefined(relationIdFieldMetadataItem) &&
!isDefined(fieldMetadataItem.settings?.joinColumnName)
) {
throw new Error(
'Should never occur, encountered unknown relationId within relations definitions',
);
}
const targetNameSingular =
fieldMetadataItem.relation?.targetObjectMetadata.nameSingular;
const targetObjectMetataDataItem = objectMetadataItems.find(
({ nameSingular }) => nameSingular === targetNameSingular,
);
if (!isDefined(targetObjectMetataDataItem)) {
throw new Error(
'Should never occur, encountered invalid relation definition',
if (isNull(recordInputFieldIdValue)) {
optimisticRecord[relationFieldIdName] = null;
optimisticRecord[fieldMetadataItem.name] = null;
continue;
}
const targetNameSingular =
fieldMetadataItem.relation?.targetObjectMetadata.nameSingular;
const targetObjectMetataDataItem = objectMetadataItems.find(
({ nameSingular }) => nameSingular === targetNameSingular,
);
if (!isDefined(targetObjectMetataDataItem)) {
throw new Error(
'Should never occur, encountered invalid relation definition',
);
}
const cachedRecord = getRecordFromCache({
cache,
objectMetadataItem: targetObjectMetataDataItem,
objectMetadataItems,
recordId: recordInputFieldIdValue as string,
objectPermissionsByObjectMetadataId,
});
optimisticRecord[relationFieldIdName] = recordInputFieldIdValue;
if (!isDefined(cachedRecord) || Object.keys(cachedRecord).length <= 0) {
continue;
}
optimisticRecord[fieldMetadataItem.name] = cachedRecord;
}
const cachedRecord = getRecordFromCache({
cache,
objectMetadataItem: targetObjectMetataDataItem,
objectMetadataItems,
recordId: recordInputFieldIdValue as string,
objectPermissionsByObjectMetadataId,
});
if (isMorphRelationField) {
const relationType = fieldMetadataItem.settings?.relationType;
if (relationType === RelationType.ONE_TO_MANY) {
continue;
}
optimisticRecord[relationFieldIdName] = recordInputFieldIdValue;
const isManyToOneRelation = relationType === RelationType.MANY_TO_ONE;
if (!isManyToOneRelation) {
continue;
}
if (!isDefined(cachedRecord) || Object.keys(cachedRecord).length <= 0) {
continue;
if (!isUndefined(recordInputFieldValue)) {
throw new Error(
`Should never provide relation mutation through anything else than the fieldId e.g companyId and not company, encountered: ${fieldMetadataItem.name}`,
);
}
const relationFieldIdNames = fieldMetadataItem.morphRelations?.map(
(morphRelation) => {
return computeMorphRelationFieldName({
fieldName: fieldMetadataItem.name,
relationType,
targetObjectMetadataNameSingular:
morphRelation.targetObjectMetadata.nameSingular,
targetObjectMetadataNamePlural:
morphRelation.targetObjectMetadata.namePlural,
});
},
);
const relationFieldName = relationFieldIdNames?.find(
(relationFieldIdName) => recordInput[`${relationFieldIdName}Id`],
);
const relationFieldIdName = `${relationFieldName}Id`;
if (isUndefined(relationFieldName)) {
continue;
}
const recordInputFieldIdValue: string | null | undefined =
recordInput[relationFieldIdName];
if (isUndefined(recordInputFieldIdValue)) {
continue;
}
const relationIdFieldMetadataItem = getFieldMetadataFromGqlField({
objectMetadataItem,
gqlField: relationFieldName,
});
if (
!isDefined(relationIdFieldMetadataItem) &&
!isDefined(fieldMetadataItem.settings?.joinColumnName)
) {
throw new Error(
'Should never occur, encountered unknown relationId within relations definitions',
);
}
if (isNull(recordInputFieldIdValue)) {
optimisticRecord[relationFieldName] = null;
optimisticRecord[fieldMetadataItem.name] = null;
continue;
}
if (!isDefined(fieldMetadataItem.morphRelations)) {
throw new Error(
'Should never occur, encountered invalid relation definition',
);
}
const fieldMetadataMorphRelation =
getFieldMetadataMorphRelationFromGqlField({
objectMetadataItems,
fieldMetadata: { morphRelations: fieldMetadataItem.morphRelations },
gqlField: relationFieldName,
});
if (!isDefined(fieldMetadataMorphRelation?.targetObjectMetadata)) {
throw new Error(
'Should never occur, encountered invalid relation definition',
);
}
const cachedRecord = getRecordFromCache({
cache,
objectMetadataItem: fieldMetadataMorphRelation.targetObjectMetadata,
objectMetadataItems,
recordId: recordInputFieldIdValue as string,
objectPermissionsByObjectMetadataId,
});
optimisticRecord[relationFieldIdName] = recordInputFieldIdValue;
if (!isDefined(cachedRecord) || Object.keys(cachedRecord).length <= 0) {
continue;
}
optimisticRecord[relationFieldName] = cachedRecord;
}
optimisticRecord[fieldMetadataItem.name] = cachedRecord;
}
return optimisticRecord;