From 7adb5cc00d614fe58e6cc05cae610a00641f42bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tha=C3=AFs?= Date: Thu, 1 Feb 2024 12:09:32 -0300 Subject: [PATCH] feat: delete favorite in cache on related record deletion (#3751) * feat: delete favorite in cache on related record deletion * fix: fix useCreateOneRecord tests * fix: fix usePipelineSteps tests * fix: fix useCreateManyRecords tests * fix: add null relation field values in useGenerateObjectRecordOptimisticResponse --- .../triggerCreateRecordsOptimisticEffect.ts | 14 +++ .../triggerDeleteRecordsOptimisticEffect.ts | 18 ++- .../triggerUpdateRecordOptimisticEffect.ts | 30 ++++- .../triggerUpdateRelationsOptimisticEffect.ts | 112 ++++++++++++++++++ ...coreObjectNamesToDeleteOnRelationDetach.ts | 5 + .../hooks/useGenerateCachedObjectRecord.ts | 41 ------- ...eGenerateObjectRecordOptimisticResponse.ts | 72 +++++++++++ .../hooks/__mocks__/useCreateManyRecords.ts | 22 +++- .../hooks/__mocks__/useCreateOneRecord.ts | 4 - .../__tests__/useCreateManyRecords.test.tsx | 36 +++--- .../__tests__/useCreateOneRecord.test.tsx | 23 ++-- .../hooks/useCreateManyRecords.ts | 29 +++-- .../hooks/useCreateManyRecordsInCache.ts | 14 +-- .../object-record/hooks/useCreateOneRecord.ts | 26 ++-- .../hooks/useCreateOneRecordInCache.ts | 15 ++- .../hooks/useDeleteManyRecords.ts | 52 ++------ .../object-record/hooks/useDeleteOneRecord.ts | 49 +------- .../object-record/hooks/useUpdateOneRecord.ts | 75 +++--------- .../utils/sanitizeRecordInput.ts | 12 +- .../hooks/__mocks__/usePipelineSteps.ts | 2 +- 20 files changed, 376 insertions(+), 275 deletions(-) create mode 100644 packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerUpdateRelationsOptimisticEffect.ts create mode 100644 packages/twenty-front/src/modules/apollo/types/coreObjectNamesToDeleteOnRelationDetach.ts delete mode 100644 packages/twenty-front/src/modules/object-record/cache/hooks/useGenerateCachedObjectRecord.ts create mode 100644 packages/twenty-front/src/modules/object-record/cache/hooks/useGenerateObjectRecordOptimisticResponse.ts diff --git a/packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerCreateRecordsOptimisticEffect.ts b/packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerCreateRecordsOptimisticEffect.ts index 19dbe252c..7fb85b0b1 100644 --- a/packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerCreateRecordsOptimisticEffect.ts +++ b/packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerCreateRecordsOptimisticEffect.ts @@ -1,8 +1,10 @@ import { ApolloCache, StoreObject } from '@apollo/client'; import { isCachedObjectRecordConnection } from '@/apollo/optimistic-effect/utils/isCachedObjectRecordConnection'; +import { triggerUpdateRelationsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerUpdateRelationsOptimisticEffect'; import { CachedObjectRecord } from '@/apollo/types/CachedObjectRecord'; import { CachedObjectRecordEdge } from '@/apollo/types/CachedObjectRecordEdge'; +import { useGetRelationMetadata } from '@/object-metadata/hooks/useGetRelationMetadata'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { capitalize } from '~/utils/string/capitalize'; @@ -15,15 +17,27 @@ export const triggerCreateRecordsOptimisticEffect = ({ cache, objectMetadataItem, records, + getRelationMetadata, }: { cache: ApolloCache; objectMetadataItem: ObjectMetadataItem; records: CachedObjectRecord[]; + getRelationMetadata: ReturnType; }) => { const objectEdgeTypeName = `${capitalize( objectMetadataItem.nameSingular, )}Edge`; + records.forEach((record) => + triggerUpdateRelationsOptimisticEffect({ + cache, + objectMetadataItem, + previousRecord: null, + nextRecord: record, + getRelationMetadata, + }), + ); + cache.modify({ fields: { [objectMetadataItem.namePlural]: ( diff --git a/packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerDeleteRecordsOptimisticEffect.ts b/packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerDeleteRecordsOptimisticEffect.ts index eaa1ecc56..0ee502046 100644 --- a/packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerDeleteRecordsOptimisticEffect.ts +++ b/packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerDeleteRecordsOptimisticEffect.ts @@ -1,9 +1,11 @@ import { ApolloCache, StoreObject } from '@apollo/client'; import { isCachedObjectRecordConnection } from '@/apollo/optimistic-effect/utils/isCachedObjectRecordConnection'; +import { triggerUpdateRelationsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerUpdateRelationsOptimisticEffect'; import { CachedObjectRecord } from '@/apollo/types/CachedObjectRecord'; import { CachedObjectRecordEdge } from '@/apollo/types/CachedObjectRecordEdge'; import { CachedObjectRecordQueryVariables } from '@/apollo/types/CachedObjectRecordQueryVariables'; +import { useGetRelationMetadata } from '@/object-metadata/hooks/useGetRelationMetadata'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { isDefined } from '~/utils/isDefined'; import { parseApolloStoreFieldName } from '~/utils/parseApolloStoreFieldName'; @@ -12,10 +14,12 @@ export const triggerDeleteRecordsOptimisticEffect = ({ cache, objectMetadataItem, records, + getRelationMetadata, }: { cache: ApolloCache; objectMetadataItem: ObjectMetadataItem; - records: Pick[]; + records: CachedObjectRecord[]; + getRelationMetadata: ReturnType; }) => { cache.modify({ fields: { @@ -64,5 +68,15 @@ export const triggerDeleteRecordsOptimisticEffect = ({ }, }); - cache.gc(); + records.forEach((record) => { + triggerUpdateRelationsOptimisticEffect({ + cache, + objectMetadataItem, + previousRecord: record, + nextRecord: null, + getRelationMetadata, + }); + + cache.evict({ id: cache.identify(record) }); + }); }; diff --git a/packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerUpdateRecordOptimisticEffect.ts b/packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerUpdateRecordOptimisticEffect.ts index 23bf9be29..b83b4edbd 100644 --- a/packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerUpdateRecordOptimisticEffect.ts +++ b/packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerUpdateRecordOptimisticEffect.ts @@ -2,9 +2,11 @@ import { ApolloCache, StoreObject } from '@apollo/client'; import { isCachedObjectRecordConnection } from '@/apollo/optimistic-effect/utils/isCachedObjectRecordConnection'; import { sortCachedObjectEdges } from '@/apollo/optimistic-effect/utils/sortCachedObjectEdges'; +import { triggerUpdateRelationsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerUpdateRelationsOptimisticEffect'; import { CachedObjectRecord } from '@/apollo/types/CachedObjectRecord'; import { CachedObjectRecordEdge } from '@/apollo/types/CachedObjectRecordEdge'; import { CachedObjectRecordQueryVariables } from '@/apollo/types/CachedObjectRecordQueryVariables'; +import { useGetRelationMetadata } from '@/object-metadata/hooks/useGetRelationMetadata'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { isRecordMatchingFilter } from '@/object-record/record-filter/utils/isRecordMatchingFilter'; import { isDefined } from '~/utils/isDefined'; @@ -14,16 +16,29 @@ import { capitalize } from '~/utils/string/capitalize'; export const triggerUpdateRecordOptimisticEffect = ({ cache, objectMetadataItem, - record, + previousRecord, + nextRecord, + getRelationMetadata, }: { cache: ApolloCache; objectMetadataItem: ObjectMetadataItem; - record: CachedObjectRecord; + previousRecord: CachedObjectRecord; + nextRecord: CachedObjectRecord; + getRelationMetadata: ReturnType; }) => { const objectEdgeTypeName = `${capitalize( objectMetadataItem.nameSingular, )}Edge`; + triggerUpdateRelationsOptimisticEffect({ + cache, + objectMetadataItem, + previousRecord, + nextRecord, + getRelationMetadata, + }); + + // Optimistically update record lists cache.modify({ fields: { [objectMetadataItem.namePlural]: ( @@ -49,18 +64,20 @@ export const triggerUpdateRecordOptimisticEffect = ({ ); let nextCachedEdges = cachedEdges ? [...cachedEdges] : []; + // Test if the record matches this list's filters if (variables?.filter) { const matchesFilter = isRecordMatchingFilter({ - record, + record: nextRecord, filter: variables.filter, objectMetadataItem, }); const recordIndex = nextCachedEdges.findIndex( - (cachedEdge) => readField('id', cachedEdge.node) === record.id, + (cachedEdge) => readField('id', cachedEdge.node) === nextRecord.id, ); + // If after update, the record matches this list's filters, then add it to the list if (matchesFilter && recordIndex === -1) { - const nodeReference = toReference(record); + const nodeReference = toReference(nextRecord); nodeReference && nextCachedEdges.push({ __typename: objectEdgeTypeName, @@ -69,11 +86,13 @@ export const triggerUpdateRecordOptimisticEffect = ({ }); } + // If after update, the record does not match this list's filters anymore, then remove it from the list if (!matchesFilter && recordIndex > -1) { nextCachedEdges.splice(recordIndex, 1); } } + // Sort updated list if (variables?.orderBy) { nextCachedEdges = sortCachedObjectEdges({ edges: nextCachedEdges, @@ -82,6 +101,7 @@ export const triggerUpdateRecordOptimisticEffect = ({ }); } + // Limit the updated list to the required size if (isDefined(variables?.first)) { // If previous edges length was exactly at the required limit, // but after update next edges length is under the limit, diff --git a/packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerUpdateRelationsOptimisticEffect.ts b/packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerUpdateRelationsOptimisticEffect.ts new file mode 100644 index 000000000..f21652a65 --- /dev/null +++ b/packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerUpdateRelationsOptimisticEffect.ts @@ -0,0 +1,112 @@ +import { ApolloCache } from '@apollo/client'; + +import { isObjectRecordConnection } from '@/apollo/optimistic-effect/utils/isObjectRecordConnection'; +import { triggerAttachRelationOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerAttachRelationOptimisticEffect'; +import { triggerDeleteRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDeleteRecordsOptimisticEffect'; +import { triggerDetachRelationOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDetachRelationOptimisticEffect'; +import { CachedObjectRecord } from '@/apollo/types/CachedObjectRecord'; +import { coreObjectNamesToDeleteOnRelationDetach } from '@/apollo/types/coreObjectNamesToDeleteOnRelationDetach'; +import { useGetRelationMetadata } from '@/object-metadata/hooks/useGetRelationMetadata'; +import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; +import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; +import { ObjectRecordConnection } from '@/object-record/types/ObjectRecordConnection'; +import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; +import { isDefined } from '~/utils/isDefined'; + +export const triggerUpdateRelationsOptimisticEffect = ({ + cache, + objectMetadataItem, + previousRecord, + nextRecord, + getRelationMetadata, +}: { + cache: ApolloCache; + objectMetadataItem: ObjectMetadataItem; + previousRecord: CachedObjectRecord | null; + nextRecord: CachedObjectRecord | null; + getRelationMetadata: ReturnType; +}) => + // Optimistically update relation records + objectMetadataItem.fields.forEach((fieldMetadataItem) => { + if (nextRecord && !(fieldMetadataItem.name in nextRecord)) return; + + const relationMetadata = getRelationMetadata({ + fieldMetadataItem, + }); + + if (!relationMetadata) return; + + const { + // Object metadata for the related record + relationObjectMetadataItem, + // Field on the related record + relationFieldMetadataItem, + } = relationMetadata; + + const previousFieldValue: + | ObjectRecordConnection + | CachedObjectRecord + | null = previousRecord?.[fieldMetadataItem.name]; + const nextFieldValue: ObjectRecordConnection | CachedObjectRecord | null = + nextRecord?.[fieldMetadataItem.name]; + + if (isDeeplyEqual(previousFieldValue, nextFieldValue)) return; + + const isPreviousFieldValueRecordConnection = isObjectRecordConnection( + relationObjectMetadataItem.nameSingular, + previousFieldValue, + ); + const relationRecordsToDetach = isPreviousFieldValueRecordConnection + ? previousFieldValue.edges.map(({ node }) => node as CachedObjectRecord) + : [previousFieldValue].filter(isDefined); + + const isNextFieldValueRecordConnection = isObjectRecordConnection( + relationObjectMetadataItem.nameSingular, + nextFieldValue, + ); + const relationRecordsToAttach = isNextFieldValueRecordConnection + ? nextFieldValue.edges.map(({ node }) => node as CachedObjectRecord) + : [nextFieldValue].filter(isDefined); + + if (previousRecord && relationRecordsToDetach.length) { + const shouldDeleteRelationRecord = + coreObjectNamesToDeleteOnRelationDetach.includes( + relationObjectMetadataItem.nameSingular as CoreObjectNameSingular, + ); + + if (shouldDeleteRelationRecord) { + triggerDeleteRecordsOptimisticEffect({ + cache, + objectMetadataItem: relationObjectMetadataItem, + records: relationRecordsToDetach, + getRelationMetadata, + }); + } else { + relationRecordsToDetach.forEach((relationRecordToDetach) => { + triggerDetachRelationOptimisticEffect({ + cache, + objectNameSingular: objectMetadataItem.nameSingular, + recordId: previousRecord.id, + relationFieldName: relationFieldMetadataItem.name, + relationObjectMetadataNameSingular: + relationObjectMetadataItem.nameSingular, + relationRecordId: relationRecordToDetach.id, + }); + }); + } + } + + if (nextRecord && relationRecordsToAttach.length) { + relationRecordsToAttach.forEach((relationRecordToAttach) => + triggerAttachRelationOptimisticEffect({ + cache, + objectNameSingular: objectMetadataItem.nameSingular, + recordId: nextRecord.id, + relationFieldName: relationFieldMetadataItem.name, + relationObjectMetadataNameSingular: + relationObjectMetadataItem.nameSingular, + relationRecordId: relationRecordToAttach.id, + }), + ); + } + }); diff --git a/packages/twenty-front/src/modules/apollo/types/coreObjectNamesToDeleteOnRelationDetach.ts b/packages/twenty-front/src/modules/apollo/types/coreObjectNamesToDeleteOnRelationDetach.ts new file mode 100644 index 000000000..5e40afc6b --- /dev/null +++ b/packages/twenty-front/src/modules/apollo/types/coreObjectNamesToDeleteOnRelationDetach.ts @@ -0,0 +1,5 @@ +import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; + +export const coreObjectNamesToDeleteOnRelationDetach = [ + CoreObjectNameSingular.Favorite, +]; diff --git a/packages/twenty-front/src/modules/object-record/cache/hooks/useGenerateCachedObjectRecord.ts b/packages/twenty-front/src/modules/object-record/cache/hooks/useGenerateCachedObjectRecord.ts deleted file mode 100644 index 635056ed7..000000000 --- a/packages/twenty-front/src/modules/object-record/cache/hooks/useGenerateCachedObjectRecord.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { v4 } from 'uuid'; -import { z } from 'zod'; - -import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; -import { ObjectRecord } from '@/object-record/types/ObjectRecord'; -import { generateEmptyFieldValue } from '@/object-record/utils/generateEmptyFieldValue'; -import { capitalize } from '~/utils/string/capitalize'; - -export const useGenerateCachedObjectRecord = ({ - objectMetadataItem, -}: { - objectMetadataItem: ObjectMetadataItem; -}) => { - const generateCachedObjectRecord = < - GeneratedObjectRecord extends ObjectRecord, - >( - input: Record, - ) => { - const recordSchema = z.object( - Object.fromEntries( - objectMetadataItem.fields.map((fieldMetadataItem) => [ - fieldMetadataItem.name, - z.unknown().default(generateEmptyFieldValue(fieldMetadataItem)), - ]), - ), - ); - - return { - __typename: capitalize(objectMetadataItem.nameSingular), - ...recordSchema.parse({ - id: v4(), - createdAt: new Date().toISOString(), - ...input, - }), - } as GeneratedObjectRecord & { __typename: string }; - }; - - return { - generateCachedObjectRecord, - }; -}; diff --git a/packages/twenty-front/src/modules/object-record/cache/hooks/useGenerateObjectRecordOptimisticResponse.ts b/packages/twenty-front/src/modules/object-record/cache/hooks/useGenerateObjectRecordOptimisticResponse.ts new file mode 100644 index 000000000..f07a757a2 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/cache/hooks/useGenerateObjectRecordOptimisticResponse.ts @@ -0,0 +1,72 @@ +import { v4 } from 'uuid'; +import { z } from 'zod'; + +import { useGetRelationMetadata } from '@/object-metadata/hooks/useGetRelationMetadata'; +import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; +import { ObjectRecord } from '@/object-record/types/ObjectRecord'; +import { generateEmptyFieldValue } from '@/object-record/utils/generateEmptyFieldValue'; +import { capitalize } from '~/utils/string/capitalize'; + +export const useGenerateObjectRecordOptimisticResponse = ({ + objectMetadataItem, +}: { + objectMetadataItem: ObjectMetadataItem; +}) => { + const getRelationMetadata = useGetRelationMetadata(); + + const generateObjectRecordOptimisticResponse = < + GeneratedObjectRecord extends ObjectRecord, + >( + input: Record, + ) => { + const recordSchema = z.object( + Object.fromEntries( + objectMetadataItem.fields.map((fieldMetadataItem) => [ + fieldMetadataItem.name, + z.unknown().default(generateEmptyFieldValue(fieldMetadataItem)), + ]), + ), + ); + + const inputWithRelationFields = objectMetadataItem.fields.reduce( + (result, fieldMetadataItem) => { + const relationIdFieldName = `${fieldMetadataItem.name}Id`; + + if (!(relationIdFieldName in input)) return result; + + const relationMetadata = getRelationMetadata({ fieldMetadataItem }); + + if (!relationMetadata) return result; + + const relationRecordTypeName = capitalize( + relationMetadata.relationObjectMetadataItem.nameSingular, + ); + const relationRecordId = result[relationIdFieldName] as string | null; + + return { + ...result, + [fieldMetadataItem.name]: relationRecordId + ? { + __typename: relationRecordTypeName, + id: relationRecordId, + } + : null, + }; + }, + input, + ); + + return { + __typename: capitalize(objectMetadataItem.nameSingular), + ...recordSchema.parse({ + id: v4(), + createdAt: new Date().toISOString(), + ...inputWithRelationFields, + }), + } as GeneratedObjectRecord & { __typename: string }; + }; + + return { + generateObjectRecordOptimisticResponse, + }; +}; diff --git a/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useCreateManyRecords.ts b/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useCreateManyRecords.ts index e928f9947..72c5c6711 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useCreateManyRecords.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useCreateManyRecords.ts @@ -1,5 +1,7 @@ import { gql } from '@apollo/client'; +import { Person } from '@/people/types/Person'; + export const query = gql` mutation CreatePeople($data: [PersonCreateInput!]!) { createPeople(data: $data) { @@ -67,12 +69,15 @@ export const query = gql` } `; -export const variables = { - data: [ - { id: 'a7286b9a-c039-4a89-9567-2dfa7953cda9' }, - { id: '37faabcd-cb39-4a0a-8618-7e3fda9afca0' }, - ], -}; +const data = [ + { + id: 'a7286b9a-c039-4a89-9567-2dfa7953cda9', + name: { firstName: 'John', lastName: 'Doe' }, + }, + { id: '37faabcd-cb39-4a0a-8618-7e3fda9afca0', jobTitle: 'manager' }, +] satisfies Partial[]; + +export const variables = { data }; export const responseData = { opportunities: { @@ -114,3 +119,8 @@ export const responseData = { avatarUrl: '', companyId: '', }; + +export const response = data.map((personData) => ({ + ...responseData, + ...personData, +})); diff --git a/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useCreateOneRecord.ts b/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useCreateOneRecord.ts index 426b120a7..1599d7bc7 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useCreateOneRecord.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useCreateOneRecord.ts @@ -67,10 +67,6 @@ export const query = gql` } `; -export const variables = { - input: { id: 'a7286b9a-c039-4a89-9567-2dfa7953cda9' }, -}; - export const responseData = { opportunities: { edges: [], diff --git a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useCreateManyRecords.test.tsx b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useCreateManyRecords.test.tsx index f0da527d4..9eb14eadd 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useCreateManyRecords.test.tsx +++ b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useCreateManyRecords.test.tsx @@ -1,19 +1,27 @@ import { ReactNode } from 'react'; import { MockedProvider } from '@apollo/client/testing'; +import { mocked } from '@storybook/test'; import { act, renderHook } from '@testing-library/react'; import { RecoilRoot } from 'recoil'; +import { v4 } from 'uuid'; +import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { query, - responseData, + response, variables, } from '@/object-record/hooks/__mocks__/useCreateManyRecords'; import { useCreateManyRecords } from '@/object-record/hooks/useCreateManyRecords'; -const people = [ - { id: 'a7286b9a-c039-4a89-9567-2dfa7953cda9' }, - { id: '37faabcd-cb39-4a0a-8618-7e3fda9afca0' }, -]; +jest.mock('uuid', () => ({ + v4: jest.fn(), +})); + +mocked(v4) + .mockReturnValueOnce(variables.data[0].id) + .mockReturnValueOnce(variables.data[1].id); + +const input = variables.data.map(({ id: _id, ...personInput }) => personInput); const mocks = [ { @@ -23,10 +31,7 @@ const mocks = [ }, result: jest.fn(() => ({ data: { - createPeople: people.map((person) => ({ - id: person.id, - ...responseData, - })), + createPeople: response, }, })), }, @@ -43,19 +48,18 @@ const Wrapper = ({ children }: { children: ReactNode }) => ( describe('useCreateManyRecords', () => { it('works as expected', async () => { const { result } = renderHook( - () => useCreateManyRecords({ objectNameSingular: 'person' }), + () => + useCreateManyRecords({ + objectNameSingular: CoreObjectNameSingular.Person, + }), { wrapper: Wrapper, }, ); await act(async () => { - const res = await result.current.createManyRecords(people); - expect(res).toBeDefined(); - expect(Array.isArray(res)).toBe(true); - expect(res?.length).toBe(2); - expect(res?.[0].id).toBe(people[0].id); - expect(res?.[1].id).toBe(people[1].id); + const res = await result.current.createManyRecords(input); + expect(res).toEqual(response); }); expect(mocks[0].result).toHaveBeenCalled(); diff --git a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useCreateOneRecord.test.tsx b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useCreateOneRecord.test.tsx index 7deae9134..58a9e3fc2 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useCreateOneRecord.test.tsx +++ b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useCreateOneRecord.test.tsx @@ -3,24 +3,29 @@ import { MockedProvider } from '@apollo/client/testing'; import { act, renderHook } from '@testing-library/react'; import { RecoilRoot } from 'recoil'; +import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { query, responseData, - variables, } from '@/object-record/hooks/__mocks__/useCreateOneRecord'; import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord'; -const person = { id: 'a7286b9a-c039-4a89-9567-2dfa7953cda9' }; +const personId = 'a7286b9a-c039-4a89-9567-2dfa7953cda9'; +const input = { name: { firstName: 'John', lastName: 'Doe' } }; + +jest.mock('uuid', () => ({ + v4: jest.fn(() => personId), +})); const mocks = [ { request: { query, - variables, + variables: { input: { ...input, id: personId } }, }, result: jest.fn(() => ({ data: { - createPerson: { ...person, ...responseData }, + createPerson: { ...responseData, ...input, id: personId }, }, })), }, @@ -37,16 +42,20 @@ const Wrapper = ({ children }: { children: ReactNode }) => ( describe('useCreateOneRecord', () => { it('works as expected', async () => { const { result } = renderHook( - () => useCreateOneRecord({ objectNameSingular: 'person' }), + () => + useCreateOneRecord({ + objectNameSingular: CoreObjectNameSingular.Person, + }), { wrapper: Wrapper, }, ); await act(async () => { - const res = await result.current.createOneRecord(person); + const res = await result.current.createOneRecord(input); + console.log('res', res); expect(res).toBeDefined(); - expect(res).toHaveProperty('id', person.id); + expect(res).toHaveProperty('id', personId); }); expect(mocks[0].result).toHaveBeenCalled(); diff --git a/packages/twenty-front/src/modules/object-record/hooks/useCreateManyRecords.ts b/packages/twenty-front/src/modules/object-record/hooks/useCreateManyRecords.ts index 13e3b2c2e..5aa32ceab 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useCreateManyRecords.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useCreateManyRecords.ts @@ -1,9 +1,11 @@ import { useApolloClient } from '@apollo/client'; +import { v4 } from 'uuid'; import { triggerCreateRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerCreateRecordsOptimisticEffect'; +import { useGetRelationMetadata } from '@/object-metadata/hooks/useGetRelationMetadata'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { ObjectMetadataItemIdentifier } from '@/object-metadata/types/ObjectMetadataItemIdentifier'; -import { useGenerateCachedObjectRecord } from '@/object-record/cache/hooks/useGenerateCachedObjectRecord'; +import { useGenerateObjectRecordOptimisticResponse } from '@/object-record/cache/hooks/useGenerateObjectRecordOptimisticResponse'; import { getCreateManyRecordsMutationResponseField } from '@/object-record/hooks/useGenerateCreateManyRecordMutation'; import { ObjectRecord } from '@/object-record/types/ObjectRecord'; import { sanitizeRecordInput } from '@/object-record/utils/sanitizeRecordInput'; @@ -13,29 +15,33 @@ export const useCreateManyRecords = < >({ objectNameSingular, }: ObjectMetadataItemIdentifier) => { + const apolloClient = useApolloClient(); + const { objectMetadataItem, createManyRecordsMutation } = useObjectMetadataItem({ objectNameSingular, }); - const { generateCachedObjectRecord } = useGenerateCachedObjectRecord({ - objectMetadataItem, - }); + const { generateObjectRecordOptimisticResponse } = + useGenerateObjectRecordOptimisticResponse({ + objectMetadataItem, + }); - const apolloClient = useApolloClient(); + const getRelationMetadata = useGetRelationMetadata(); const createManyRecords = async (data: Partial[]) => { - const optimisticallyCreatedRecords = data.map((record) => - generateCachedObjectRecord(record), - ); - - const sanitizedCreateManyRecordsInput = data.map((input, index) => + const sanitizedCreateManyRecordsInput = data.map((input) => sanitizeRecordInput({ objectMetadataItem, - recordInput: { ...input, id: optimisticallyCreatedRecords[index].id }, + recordInput: { ...input, id: v4() }, }), ); + const optimisticallyCreatedRecords = sanitizedCreateManyRecordsInput.map( + (record) => + generateObjectRecordOptimisticResponse(record), + ); + const mutationResponseField = getCreateManyRecordsMutationResponseField( objectMetadataItem.namePlural, ); @@ -57,6 +63,7 @@ export const useCreateManyRecords = < cache, objectMetadataItem, records, + getRelationMetadata, }); }, }); diff --git a/packages/twenty-front/src/modules/object-record/hooks/useCreateManyRecordsInCache.ts b/packages/twenty-front/src/modules/object-record/hooks/useCreateManyRecordsInCache.ts index cc3d3fd93..f1f131c44 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useCreateManyRecordsInCache.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useCreateManyRecordsInCache.ts @@ -3,7 +3,7 @@ import { v4 } from 'uuid'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { ObjectMetadataItemIdentifier } from '@/object-metadata/types/ObjectMetadataItemIdentifier'; import { useAddRecordInCache } from '@/object-record/cache/hooks/useAddRecordInCache'; -import { useGenerateCachedObjectRecord } from '@/object-record/cache/hooks/useGenerateCachedObjectRecord'; +import { useGenerateObjectRecordOptimisticResponse } from '@/object-record/cache/hooks/useGenerateObjectRecordOptimisticResponse'; import { ObjectRecord } from '@/object-record/types/ObjectRecord'; export const useCreateManyRecordsInCache = ({ @@ -13,9 +13,10 @@ export const useCreateManyRecordsInCache = ({ objectNameSingular, }); - const { generateCachedObjectRecord } = useGenerateCachedObjectRecord({ - objectMetadataItem, - }); + const { generateObjectRecordOptimisticResponse } = + useGenerateObjectRecordOptimisticResponse({ + objectMetadataItem, + }); const addRecordInCache = useAddRecordInCache({ objectMetadataItem, @@ -30,9 +31,8 @@ export const useCreateManyRecordsInCache = ({ const createdRecordsInCache = [] as T[]; for (const record of recordsWithId) { - const generatedCachedObjectRecord = generateCachedObjectRecord({ - ...record, - }); + const generatedCachedObjectRecord = + generateObjectRecordOptimisticResponse(record); if (generatedCachedObjectRecord) { addRecordInCache(generatedCachedObjectRecord); diff --git a/packages/twenty-front/src/modules/object-record/hooks/useCreateOneRecord.ts b/packages/twenty-front/src/modules/object-record/hooks/useCreateOneRecord.ts index 00d72c531..33f5d4a20 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useCreateOneRecord.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useCreateOneRecord.ts @@ -1,8 +1,10 @@ import { useApolloClient } from '@apollo/client'; +import { v4 } from 'uuid'; import { triggerCreateRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerCreateRecordsOptimisticEffect'; +import { useGetRelationMetadata } from '@/object-metadata/hooks/useGetRelationMetadata'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; -import { useGenerateCachedObjectRecord } from '@/object-record/cache/hooks/useGenerateCachedObjectRecord'; +import { useGenerateObjectRecordOptimisticResponse } from '@/object-record/cache/hooks/useGenerateObjectRecordOptimisticResponse'; import { getCreateOneRecordMutationResponseField } from '@/object-record/hooks/useGenerateCreateOneRecordMutation'; import { ObjectRecord } from '@/object-record/types/ObjectRecord'; import { sanitizeRecordInput } from '@/object-record/utils/sanitizeRecordInput'; @@ -16,25 +18,27 @@ export const useCreateOneRecord = < >({ objectNameSingular, }: useCreateOneRecordProps) => { + const apolloClient = useApolloClient(); + const { objectMetadataItem, createOneRecordMutation } = useObjectMetadataItem( { objectNameSingular }, ); - // TODO: type this with a minimal type at least with Record - const apolloClient = useApolloClient(); + const { generateObjectRecordOptimisticResponse } = + useGenerateObjectRecordOptimisticResponse({ + objectMetadataItem, + }); - const { generateCachedObjectRecord } = useGenerateCachedObjectRecord({ - objectMetadataItem, - }); + const getRelationMetadata = useGetRelationMetadata(); const createOneRecord = async (input: Partial) => { const sanitizedCreateOneRecordInput = sanitizeRecordInput({ objectMetadataItem, - recordInput: input, + recordInput: { ...input, id: v4() }, }); const optimisticallyCreatedRecord = - generateCachedObjectRecord({ + generateObjectRecordOptimisticResponse({ ...input, ...sanitizedCreateOneRecordInput, }); @@ -45,10 +49,7 @@ export const useCreateOneRecord = < const createdObject = await apolloClient.mutate({ mutation: createOneRecordMutation, variables: { - input: { - ...sanitizedCreateOneRecordInput, - id: optimisticallyCreatedRecord.id, - }, + input: sanitizedCreateOneRecordInput, }, optimisticResponse: { [mutationResponseField]: optimisticallyCreatedRecord, @@ -62,6 +63,7 @@ export const useCreateOneRecord = < cache, objectMetadataItem, records: [record], + getRelationMetadata, }); }, }); diff --git a/packages/twenty-front/src/modules/object-record/hooks/useCreateOneRecordInCache.ts b/packages/twenty-front/src/modules/object-record/hooks/useCreateOneRecordInCache.ts index c1d10022e..358084a28 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useCreateOneRecordInCache.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useCreateOneRecordInCache.ts @@ -1,6 +1,6 @@ import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useAddRecordInCache } from '@/object-record/cache/hooks/useAddRecordInCache'; -import { useGenerateCachedObjectRecord } from '@/object-record/cache/hooks/useGenerateCachedObjectRecord'; +import { useGenerateObjectRecordOptimisticResponse } from '@/object-record/cache/hooks/useGenerateObjectRecordOptimisticResponse'; import { ObjectRecord } from '@/object-record/types/ObjectRecord'; type useCreateOneRecordInCacheProps = { @@ -14,19 +14,18 @@ export const useCreateOneRecordInCache = ({ objectNameSingular, }); - const { generateCachedObjectRecord } = useGenerateCachedObjectRecord({ - objectMetadataItem, - }); + const { generateObjectRecordOptimisticResponse } = + useGenerateObjectRecordOptimisticResponse({ + objectMetadataItem, + }); const addRecordInCache = useAddRecordInCache({ objectMetadataItem, }); const createOneRecordInCache = async (input: ObjectRecord) => { - const generatedCachedObjectRecord = generateCachedObjectRecord({ - createdAt: new Date().toISOString(), - ...input, - }); + const generatedCachedObjectRecord = + generateObjectRecordOptimisticResponse(input); addRecordInCache(generatedCachedObjectRecord); diff --git a/packages/twenty-front/src/modules/object-record/hooks/useDeleteManyRecords.ts b/packages/twenty-front/src/modules/object-record/hooks/useDeleteManyRecords.ts index 080a19cb2..fc0e8161a 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useDeleteManyRecords.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useDeleteManyRecords.ts @@ -1,13 +1,9 @@ import { useApolloClient } from '@apollo/client'; -import { isObjectRecordConnection } from '@/apollo/optimistic-effect/utils/isObjectRecordConnection'; import { triggerDeleteRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDeleteRecordsOptimisticEffect'; -import { triggerDetachRelationOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDetachRelationOptimisticEffect'; import { useGetRelationMetadata } from '@/object-metadata/hooks/useGetRelationMetadata'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { getDeleteManyRecordsMutationResponseField } from '@/object-record/hooks/useGenerateDeleteManyRecordMutation'; -import { ObjectRecord } from '@/object-record/types/ObjectRecord'; -import { ObjectRecordConnection } from '@/object-record/types/ObjectRecordConnection'; import { isDefined } from '~/utils/isDefined'; import { capitalize } from '~/utils/string/capitalize'; @@ -19,13 +15,13 @@ type useDeleteOneRecordProps = { export const useDeleteManyRecords = ({ objectNameSingular, }: useDeleteOneRecordProps) => { + const apolloClient = useApolloClient(); + const { objectMetadataItem, deleteManyRecordsMutation, getRecordFromCache } = useObjectMetadataItem({ objectNameSingular }); const getRelationMetadata = useGetRelationMetadata(); - const apolloClient = useApolloClient(); - const mutationResponseField = getDeleteManyRecordsMutationResponseField( objectMetadataItem.namePlural, ); @@ -47,49 +43,15 @@ export const useDeleteManyRecords = ({ if (!records?.length) return; - objectMetadataItem.fields.forEach((fieldMetadataItem) => { - const relationMetadata = getRelationMetadata({ fieldMetadataItem }); - - if (!relationMetadata) return; - - const { relationObjectMetadataItem, relationFieldMetadataItem } = - relationMetadata; - - records.forEach((record) => { - const cachedRecord = getRecordFromCache(record.id, cache); - - if (!cachedRecord) return; - - const previousFieldValue: - | ObjectRecordConnection - | ObjectRecord - | null = cachedRecord[fieldMetadataItem.name]; - - const relationRecordIds = isObjectRecordConnection( - relationObjectMetadataItem.nameSingular, - previousFieldValue, - ) - ? previousFieldValue.edges.map(({ node }) => node.id) - : [previousFieldValue?.id].filter(isDefined); - - relationRecordIds.forEach((relationRecordId) => - triggerDetachRelationOptimisticEffect({ - cache, - objectNameSingular, - recordId: record.id, - relationObjectMetadataNameSingular: - relationObjectMetadataItem.nameSingular, - relationFieldName: relationFieldMetadataItem.name, - relationRecordId, - }), - ); - }); - }); + const cachedRecords = records + .map((record) => getRecordFromCache(record.id, cache)) + .filter(isDefined); triggerDeleteRecordsOptimisticEffect({ cache, objectMetadataItem, - records: records, + records: cachedRecords, + getRelationMetadata, }); }, }); diff --git a/packages/twenty-front/src/modules/object-record/hooks/useDeleteOneRecord.ts b/packages/twenty-front/src/modules/object-record/hooks/useDeleteOneRecord.ts index 389b2e646..14ce35a42 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useDeleteOneRecord.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useDeleteOneRecord.ts @@ -1,15 +1,10 @@ import { useCallback } from 'react'; import { useApolloClient } from '@apollo/client'; -import { isObjectRecordConnection } from '@/apollo/optimistic-effect/utils/isObjectRecordConnection'; import { triggerDeleteRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDeleteRecordsOptimisticEffect'; -import { triggerDetachRelationOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDetachRelationOptimisticEffect'; import { useGetRelationMetadata } from '@/object-metadata/hooks/useGetRelationMetadata'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; -import { ObjectRecord } from '@/object-record/types/ObjectRecord'; -import { ObjectRecordConnection } from '@/object-record/types/ObjectRecordConnection'; import { getDeleteOneRecordMutationResponseField } from '@/object-record/utils/generateDeleteOneRecordMutation'; -import { isDefined } from '~/utils/isDefined'; import { capitalize } from '~/utils/string/capitalize'; type useDeleteOneRecordProps = { @@ -20,13 +15,13 @@ type useDeleteOneRecordProps = { export const useDeleteOneRecord = ({ objectNameSingular, }: useDeleteOneRecordProps) => { + const apolloClient = useApolloClient(); + const { objectMetadataItem, deleteOneRecordMutation, getRecordFromCache } = useObjectMetadataItem({ objectNameSingular }); const getRelationMetadata = useGetRelationMetadata(); - const apolloClient = useApolloClient(); - const mutationResponseField = getDeleteOneRecordMutationResponseField(objectNameSingular); @@ -46,47 +41,15 @@ export const useDeleteOneRecord = ({ if (!record) return; - objectMetadataItem.fields.forEach((fieldMetadataItem) => { - const relationMetadata = getRelationMetadata({ fieldMetadataItem }); + const cachedRecord = getRecordFromCache(record.id, cache); - if (!relationMetadata) return; - - const { relationObjectMetadataItem, relationFieldMetadataItem } = - relationMetadata; - - const cachedRecord = getRecordFromCache(record.id, cache); - - if (!cachedRecord) return; - - const previousFieldValue: - | ObjectRecordConnection - | ObjectRecord - | null = cachedRecord[fieldMetadataItem.name]; - - const relationRecordIds = isObjectRecordConnection( - relationObjectMetadataItem.nameSingular, - previousFieldValue, - ) - ? previousFieldValue.edges.map(({ node }) => node.id) - : [previousFieldValue?.id].filter(isDefined); - - relationRecordIds.forEach((relationRecordId) => - triggerDetachRelationOptimisticEffect({ - cache, - objectNameSingular, - recordId: record.id, - relationObjectMetadataNameSingular: - relationObjectMetadataItem.nameSingular, - relationFieldName: relationFieldMetadataItem.name, - relationRecordId, - }), - ); - }); + if (!cachedRecord) return; triggerDeleteRecordsOptimisticEffect({ cache, objectMetadataItem, - records: [record], + records: [cachedRecord], + getRelationMetadata, }); }, }); diff --git a/packages/twenty-front/src/modules/object-record/hooks/useUpdateOneRecord.ts b/packages/twenty-front/src/modules/object-record/hooks/useUpdateOneRecord.ts index 611c34dd2..031ed2c4c 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useUpdateOneRecord.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useUpdateOneRecord.ts @@ -1,16 +1,12 @@ import { useApolloClient } from '@apollo/client'; -import { isObjectRecordConnection } from '@/apollo/optimistic-effect/utils/isObjectRecordConnection'; -import { triggerAttachRelationOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerAttachRelationOptimisticEffect'; -import { triggerDetachRelationOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDetachRelationOptimisticEffect'; import { triggerUpdateRecordOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerUpdateRecordOptimisticEffect'; import { useGetRelationMetadata } from '@/object-metadata/hooks/useGetRelationMetadata'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; +import { useGenerateObjectRecordOptimisticResponse } from '@/object-record/cache/hooks/useGenerateObjectRecordOptimisticResponse'; import { getUpdateOneRecordMutationResponseField } from '@/object-record/hooks/useGenerateUpdateOneRecordMutation'; import { ObjectRecord } from '@/object-record/types/ObjectRecord'; import { sanitizeRecordInput } from '@/object-record/utils/sanitizeRecordInput'; -import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; -import { capitalize } from '~/utils/string/capitalize'; type useUpdateOneRecordProps = { objectNameSingular: string; @@ -21,12 +17,17 @@ export const useUpdateOneRecord = < >({ objectNameSingular, }: useUpdateOneRecordProps) => { + const apolloClient = useApolloClient(); + const { objectMetadataItem, updateOneRecordMutation, getRecordFromCache } = useObjectMetadataItem({ objectNameSingular }); - const getRelationMetadata = useGetRelationMetadata(); + const { generateObjectRecordOptimisticResponse } = + useGenerateObjectRecordOptimisticResponse({ + objectMetadataItem, + }); - const apolloClient = useApolloClient(); + const getRelationMetadata = useGetRelationMetadata(); const updateOneRecord = async ({ idToUpdate, @@ -42,13 +43,11 @@ export const useUpdateOneRecord = < recordInput: updateOneRecordInput, }); - const optimisticallyUpdatedRecord = { + const optimisticallyUpdatedRecord = generateObjectRecordOptimisticResponse({ ...(cachedRecord ?? {}), - ...updateOneRecordInput, ...sanitizedUpdateOneRecordInput, - __typename: capitalize(objectNameSingular), id: idToUpdate, - }; + }); const mutationResponseField = getUpdateOneRecordMutationResponseField(objectNameSingular); @@ -65,60 +64,14 @@ export const useUpdateOneRecord = < update: (cache, { data }) => { const record = data?.[mutationResponseField]; - if (!record) return; - - objectMetadataItem.fields.forEach((fieldMetadataItem) => { - const relationMetadata = getRelationMetadata({ fieldMetadataItem }); - - if (!relationMetadata) return; - - const { relationObjectMetadataItem, relationFieldMetadataItem } = - relationMetadata; - - const previousFieldValue = cachedRecord?.[fieldMetadataItem.name]; - const nextFieldValue = - updateOneRecordInput[fieldMetadataItem.name] ?? null; - - if ( - !(fieldMetadataItem.name in updateOneRecordInput) || - isObjectRecordConnection( - relationObjectMetadataItem.nameSingular, - previousFieldValue, - ) || - isDeeplyEqual(previousFieldValue, nextFieldValue) - ) { - return; - } - - if (previousFieldValue) { - triggerDetachRelationOptimisticEffect({ - cache, - objectNameSingular, - recordId: record.id, - relationObjectMetadataNameSingular: - relationObjectMetadataItem.nameSingular, - relationFieldName: relationFieldMetadataItem.name, - relationRecordId: previousFieldValue.id, - }); - } - - if (nextFieldValue) { - triggerAttachRelationOptimisticEffect({ - cache, - objectNameSingular, - recordId: record.id, - relationObjectMetadataNameSingular: - relationObjectMetadataItem.nameSingular, - relationFieldName: relationFieldMetadataItem.name, - relationRecordId: nextFieldValue.id, - }); - } - }); + if (!record || !cachedRecord) return; triggerUpdateRecordOptimisticEffect({ cache, objectMetadataItem, - record, + previousRecord: cachedRecord, + nextRecord: record, + getRelationMetadata, }); }, }); diff --git a/packages/twenty-front/src/modules/object-record/utils/sanitizeRecordInput.ts b/packages/twenty-front/src/modules/object-record/utils/sanitizeRecordInput.ts index 4bdc0c6ee..943cede54 100644 --- a/packages/twenty-front/src/modules/object-record/utils/sanitizeRecordInput.ts +++ b/packages/twenty-front/src/modules/object-record/utils/sanitizeRecordInput.ts @@ -13,22 +13,22 @@ export const sanitizeRecordInput = ({ return Object.fromEntries( Object.entries(recordInput) .map<[string, unknown] | undefined>(([fieldName, fieldValue]) => { - const fieldDefinition = objectMetadataItem.fields.find( + const fieldMetadataItem = objectMetadataItem.fields.find( (field) => field.name === fieldName, ); - if (!fieldDefinition) return undefined; + if (!fieldMetadataItem) return undefined; if ( - fieldDefinition.type === FieldMetadataType.Relation && + fieldMetadataItem.type === FieldMetadataType.Relation && isFieldRelationValue(fieldValue) ) { - const relationIdFieldName = `${fieldDefinition.name}Id`; - const relationIdFieldDefinition = objectMetadataItem.fields.find( + const relationIdFieldName = `${fieldMetadataItem.name}Id`; + const relationIdFieldMetadataItem = objectMetadataItem.fields.find( (field) => field.name === relationIdFieldName, ); - return relationIdFieldDefinition + return relationIdFieldMetadataItem ? [relationIdFieldName, fieldValue?.id ?? null] : undefined; } diff --git a/packages/twenty-front/src/modules/pipeline/hooks/__mocks__/usePipelineSteps.ts b/packages/twenty-front/src/modules/pipeline/hooks/__mocks__/usePipelineSteps.ts index 7035b73ae..13e741596 100644 --- a/packages/twenty-front/src/modules/pipeline/hooks/__mocks__/usePipelineSteps.ts +++ b/packages/twenty-front/src/modules/pipeline/hooks/__mocks__/usePipelineSteps.ts @@ -34,7 +34,7 @@ export const currentPipelineId = 'f088c8c9-05d2-4276-b065-b863cc7d0b33'; const data = { color: 'yellow', - id: 'columnId', + id: mockId, position: 1, name: 'Column Title', };