diff --git a/packages/twenty-front/src/modules/object-record/constants/ObjectRecordTypename.ts b/packages/twenty-front/src/modules/object-record/constants/ObjectRecordTypename.ts new file mode 100644 index 000000000..8f99de186 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/constants/ObjectRecordTypename.ts @@ -0,0 +1,4 @@ +import { BaseObjectRecord } from '@/object-record/types/BaseObjectRecord'; + +export const OBJECT_RECORD_TYPENAME_KEY = + '__typename' satisfies keyof BaseObjectRecord; diff --git a/packages/twenty-front/src/modules/object-record/types/BaseObjectRecord.ts b/packages/twenty-front/src/modules/object-record/types/BaseObjectRecord.ts new file mode 100644 index 000000000..d4dade991 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/types/BaseObjectRecord.ts @@ -0,0 +1,4 @@ +export type BaseObjectRecord = { + id: string; + __typename: string; +}; diff --git a/packages/twenty-front/src/modules/object-record/types/ObjectRecord.ts b/packages/twenty-front/src/modules/object-record/types/ObjectRecord.ts index 1c511dd5a..ed49ffc41 100644 --- a/packages/twenty-front/src/modules/object-record/types/ObjectRecord.ts +++ b/packages/twenty-front/src/modules/object-record/types/ObjectRecord.ts @@ -1,4 +1,3 @@ -export type ObjectRecord = Record & { - id: string; - __typename: string; -}; +import { BaseObjectRecord } from '@/object-record/types/BaseObjectRecord'; + +export type ObjectRecord = Record & BaseObjectRecord; diff --git a/packages/twenty-front/src/modules/object-record/utils/__tests__/computeOptimisticRecordFromInput.test.ts b/packages/twenty-front/src/modules/object-record/utils/__tests__/computeOptimisticRecordFromInput.test.ts index 0f9152797..afc1479ba 100644 --- a/packages/twenty-front/src/modules/object-record/utils/__tests__/computeOptimisticRecordFromInput.test.ts +++ b/packages/twenty-front/src/modules/object-record/utils/__tests__/computeOptimisticRecordFromInput.test.ts @@ -3,17 +3,34 @@ import { computeOptimisticRecordFromInput } from '@/object-record/utils/computeO import { InMemoryCache } from '@apollo/client'; import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; +const getPersonObjectMetadaItem = () => { + const personObjectMetadataItem = generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'person', + ); + + if (!personObjectMetadataItem) { + throw new Error('Person object metadata item not found'); + } + + return personObjectMetadataItem; +}; + +const getCompanyObjectMetadataItem = () => { + const companyObjectMetadataItem = generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'company', + ); + + if (!companyObjectMetadataItem) { + throw new Error('Company object metadata item not found'); + } + + return companyObjectMetadataItem; +}; + describe('computeOptimisticRecordFromInput', () => { it('should generate correct optimistic record if no relation field is present', () => { const cache = new InMemoryCache(); - - const personObjectMetadataItem = generatedMockObjectMetadataItems.find( - (item) => item.nameSingular === 'person', - ); - - if (!personObjectMetadataItem) { - throw new Error('Person object metadata item not found'); - } + const personObjectMetadataItem = getPersonObjectMetadaItem(); const result = computeOptimisticRecordFromInput({ objectMetadataItems: generatedMockObjectMetadataItems, @@ -31,14 +48,7 @@ describe('computeOptimisticRecordFromInput', () => { it('should generate correct optimistic record if relation field is present but cache is empty', () => { const cache = new InMemoryCache(); - - const personObjectMetadataItem = generatedMockObjectMetadataItems.find( - (item) => item.nameSingular === 'person', - ); - - if (!personObjectMetadataItem) { - throw new Error('Person object metadata item not found'); - } + const personObjectMetadataItem = getPersonObjectMetadaItem(); const result = computeOptimisticRecordFromInput({ objectMetadataItems: generatedMockObjectMetadataItems, @@ -54,23 +64,48 @@ describe('computeOptimisticRecordFromInput', () => { }); }); + it('should generate correct optimistic record even if recordInput contains field __typename', () => { + const cache = new InMemoryCache(); + const personObjectMetadataItem = getPersonObjectMetadaItem(); + const companyObjectMetadataItem = getCompanyObjectMetadataItem(); + + const companyRecord = { + id: '123', + __typename: 'Company', + }; + + updateRecordFromCache({ + objectMetadataItems: generatedMockObjectMetadataItems, + objectMetadataItem: { + ...companyObjectMetadataItem, + fields: companyObjectMetadataItem.fields.filter( + (field) => field.name === 'id', + ), + }, + cache, + record: companyRecord, + }); + + const result = computeOptimisticRecordFromInput({ + objectMetadataItems: generatedMockObjectMetadataItems, + objectMetadataItem: personObjectMetadataItem, + recordInput: { + companyId: '123', + __typename: 'test', + }, + cache, + }); + + expect(result).toStrictEqual({ + companyId: '123', + company: companyRecord, + }); + }); + it('should generate correct optimistic record if relation field is present and cache is not empty', () => { const cache = new InMemoryCache(); - const personObjectMetadataItem = generatedMockObjectMetadataItems.find( - (item) => item.nameSingular === 'person', - ); - - if (!personObjectMetadataItem) { - throw new Error('Person object metadata item not found'); - } - - const companyObjectMetadataItem = generatedMockObjectMetadataItems.find( - (item) => item.nameSingular === 'company', - ); - - if (!companyObjectMetadataItem) { - throw new Error('Company object metadata item not found'); - } + const personObjectMetadataItem = getPersonObjectMetadaItem(); + const companyObjectMetadataItem = getCompanyObjectMetadataItem(); const companyRecord = { id: '123', @@ -106,14 +141,7 @@ describe('computeOptimisticRecordFromInput', () => { it('should generate correct optimistic record if relation field is null and cache is empty', () => { const cache = new InMemoryCache(); - - const personObjectMetadataItem = generatedMockObjectMetadataItems.find( - (item) => item.nameSingular === 'person', - ); - - if (!personObjectMetadataItem) { - throw new Error('Person object metadata item not found'); - } + const personObjectMetadataItem = getPersonObjectMetadaItem(); const result = computeOptimisticRecordFromInput({ objectMetadataItems: generatedMockObjectMetadataItems, @@ -130,14 +158,9 @@ describe('computeOptimisticRecordFromInput', () => { }); }); - it('should throw an error if recordInput contains fiels unrelated to the current objectMetadata', () => { + it('should throw an error if recordInput contains fields unrelated to the current objectMetadata', () => { const cache = new InMemoryCache(); - const personObjectMetadataItem = generatedMockObjectMetadataItems.find( - (item) => item.nameSingular === 'person', - ); - if (!personObjectMetadataItem) { - throw new Error('Person object metadata item not found'); - } + const personObjectMetadataItem = getPersonObjectMetadaItem(); expect(() => computeOptimisticRecordFromInput({ @@ -158,12 +181,7 @@ describe('computeOptimisticRecordFromInput', () => { it('should throw an error if recordInput contains both the relationFieldId and relationField', () => { const cache = new InMemoryCache(); - const personObjectMetadataItem = generatedMockObjectMetadataItems.find( - (item) => item.nameSingular === 'person', - ); - if (!personObjectMetadataItem) { - throw new Error('Person object metadata item not found'); - } + const personObjectMetadataItem = getPersonObjectMetadaItem(); expect(() => computeOptimisticRecordFromInput({ @@ -176,18 +194,13 @@ describe('computeOptimisticRecordFromInput', () => { cache, }), ).toThrowErrorMatchingInlineSnapshot( - `"Should never provide relation mutation through anything else than the fieldId e.g companyId"`, + `"Should never provide relation mutation through anything else than the fieldId e.g companyId and not company, encountered: company"`, ); }); it('should throw an error if recordInput contains both the relationFieldId and relationField even if null', () => { const cache = new InMemoryCache(); - const personObjectMetadataItem = generatedMockObjectMetadataItems.find( - (item) => item.nameSingular === 'person', - ); - if (!personObjectMetadataItem) { - throw new Error('Person object metadata item not found'); - } + const personObjectMetadataItem = getPersonObjectMetadaItem(); expect(() => computeOptimisticRecordFromInput({ @@ -200,7 +213,7 @@ describe('computeOptimisticRecordFromInput', () => { cache, }), ).toThrowErrorMatchingInlineSnapshot( - `"Should never provide relation mutation through anything else than the fieldId e.g companyId"`, + `"Should never provide relation mutation through anything else than the fieldId e.g companyId and not company, encountered: company"`, ); }); }); diff --git a/packages/twenty-front/src/modules/object-record/utils/computeOptimisticRecordFromInput.ts b/packages/twenty-front/src/modules/object-record/utils/computeOptimisticRecordFromInput.ts index 9e6f0dea7..d05b73eb7 100644 --- a/packages/twenty-front/src/modules/object-record/utils/computeOptimisticRecordFromInput.ts +++ b/packages/twenty-front/src/modules/object-record/utils/computeOptimisticRecordFromInput.ts @@ -5,6 +5,7 @@ import { getRecordFromCache, GetRecordFromCacheArgs, } from '@/object-record/cache/utils/getRecordFromCache'; +import { OBJECT_RECORD_TYPENAME_KEY } from '@/object-record/constants/ObjectRecordTypename'; import { isFieldRelation } from '@/object-record/record-field/types/guards/isFieldRelation'; import { isFieldUuid } from '@/object-record/record-field/types/guards/isFieldUuid'; import { ObjectRecord } from '@/object-record/types/ObjectRecord'; @@ -24,9 +25,13 @@ export const computeOptimisticRecordFromInput = ({ objectMetadataItems, }: ComputeOptimisticCacheRecordInputArgs) => { const unknownRecordInputFields = Object.keys(recordInput).filter( - (fieldName) => - objectMetadataItem.fields.find(({ name }) => name === fieldName) === - undefined, + (fieldName) => { + const isUnknownMetadataItemField = + objectMetadataItem.fields.find(({ name }) => name === fieldName) === + undefined; + const isTypenameField = fieldName === OBJECT_RECORD_TYPENAME_KEY; + return isUnknownMetadataItemField && !isTypenameField; + }, ); if (unknownRecordInputFields.length > 0) { throw new Error( @@ -93,7 +98,7 @@ export const computeOptimisticRecordFromInput = ({ if (!isUndefined(recordInputFieldValue)) { throw new Error( - 'Should never provide relation mutation through anything else than the fieldId e.g companyId', + `Should never provide relation mutation through anything else than the fieldId e.g companyId and not company, encountered: ${fieldMetadataItem.name}`, ); } diff --git a/packages/twenty-front/src/modules/views/hooks/internal/usePersistViewGroupRecords.ts b/packages/twenty-front/src/modules/views/hooks/internal/usePersistViewGroupRecords.ts index b006b7f04..803e6cc64 100644 --- a/packages/twenty-front/src/modules/views/hooks/internal/usePersistViewGroupRecords.ts +++ b/packages/twenty-front/src/modules/views/hooks/internal/usePersistViewGroupRecords.ts @@ -26,14 +26,12 @@ export const usePersistViewGroupRecords = () => { const createViewGroupRecords = useCallback( (viewGroupsToCreate: ViewGroup[], view: GraphQLView) => { - if (!viewGroupsToCreate.length) return; + if (viewGroupsToCreate.length === 0) return; return createManyRecords( viewGroupsToCreate.map((viewGroup) => ({ ...viewGroup, - view: { - id: view.id, - }, + viewId: view.id, })), ); },