From 7b84db47085fb80956e7d6522edbe5d368854caa Mon Sep 17 00:00:00 2001 From: Thomas Trompette Date: Sun, 12 Oct 2025 18:06:12 +0200 Subject: [PATCH] Only fetch steps and trigger for the current workflow version (#15003) Avoid fetching full steps and trigger for versions that are not the current version. Because those won't be used anyway. Better for performances. Only difficulty was for the `createDraft` mutation. I needed to return the full created version so I can store it in cache and use it as new `currentVersion`. Otherwise the current version is considered as incomplete for a short time, since workflow is fetched separately from the current version. --------- Co-authored-by: Charles Bochet --- .../src/generated-metadata/graphql.ts | 21 +++++- .../twenty-front/src/generated/graphql.ts | 13 +++- .../components/CalendarEventDetailsEffect.tsx | 6 +- .../components/EventCardCalendarEvent.tsx | 4 +- .../message/components/EventCardMessage.tsx | 4 +- .../triggerAttachRelationOptimisticEffect.ts | 52 ++++++++++++- .../triggerCreateRecordsOptimisticEffect.ts | 6 ++ .../triggerDestroyRecordsOptimisticEffect.ts | 11 +++ .../triggerDetachRelationOptimisticEffect.ts | 40 +++++++++- .../triggerUpdateRecordOptimisticEffect.ts | 14 +++- ...ggerUpdateRecordOptimisticEffectByBatch.ts | 13 +++- .../triggerUpdateRelationsOptimisticEffect.ts | 53 +++++++++++-- .../CommandMenuCalendarEventPage.tsx | 4 +- .../hooks/useEmailThreadInCommandMenu.ts | 4 +- .../cache/hooks/useDeleteRecordFromCache.ts | 7 +- .../cache/utils/deleteRecordFromCache.ts | 10 +++ .../cache/utils/getRecordFromCache.ts | 7 +- .../generateDepthRecordGqlFieldsFromObject.ts | 2 +- .../hooks/useCreateManyRecords.ts | 8 ++ .../object-record/hooks/useCreateOneRecord.ts | 8 ++ .../hooks/useDeleteManyRecords.ts | 6 ++ .../object-record/hooks/useDeleteOneRecord.ts | 19 +++-- .../hooks/useDestroyManyRecords.ts | 5 ++ .../hooks/useDestroyOneRecord.ts | 14 +++- .../useLoadSelectedRecordsInContextStore.tsx | 11 ++- .../hooks/useMergeManyRecords.ts | 10 +-- .../hooks/useRestoreManyRecords.ts | 6 ++ .../object-record/hooks/useUpdateOneRecord.ts | 8 ++ .../hooks/useUpdateOneRecordV2.ts | 14 ++++ .../RecordIndexCalendarDataLoaderEffect.tsx | 2 +- ...ordDetailRelationRecordsListItemEffect.tsx | 6 +- .../record-field/ui/hooks/usePersistField.ts | 30 ++++---- .../hooks/useLoadRecordIndexBoardColumn.ts | 2 +- .../record-merge/hooks/useMergePreview.ts | 4 +- .../hooks/useUpsertRecordsInStore.ts | 7 +- .../RecordTableNoRecordGroupAddNew.tsx | 8 +- .../hooks/useAssignRecordsToStore.ts | 32 -------- .../hooks/useTriggerFetchPages.ts | 8 +- .../useTriggerInitialRecordTableDataLoad.ts | 8 +- .../utils/computeOptimisticRecordFromInput.ts | 13 ---- .../components/ListenRecordUpdatesEffect.tsx | 4 + .../usePersistViewFilterGroupRecords.ts | 74 +------------------ .../createDraftFromWorkflowVersion.ts | 6 ++ .../hooks/useActivateWorkflowVersion.ts | 15 +++- .../hooks/useDeactivateWorkflowVersion.ts | 11 ++- .../workflow/hooks/useRunWorkflowVersion.tsx | 3 + .../hooks/useWorkflowWithCurrentVersion.ts | 56 +++++++------- .../src/modules/workflow/types/Workflow.ts | 4 +- .../components/WorkflowDiagramEffect.tsx | 9 +-- .../decorators/RecordStoreDecorator.tsx | 6 +- .../testing/jest/JestRecordStoreSetter.tsx | 4 +- .../workflow/dtos/workflow-version.dto.ts | 30 +++++++- .../resolvers/workflow-version.resolver.ts | 16 ++-- .../workflow-version.workspace-service.ts | 6 +- 54 files changed, 485 insertions(+), 259 deletions(-) delete mode 100644 packages/twenty-front/src/modules/object-record/record-table/virtualization/hooks/useAssignRecordsToStore.ts diff --git a/packages/twenty-front/src/generated-metadata/graphql.ts b/packages/twenty-front/src/generated-metadata/graphql.ts index 1c72e53ad9..0b11eb1685 100644 --- a/packages/twenty-front/src/generated-metadata/graphql.ts +++ b/packages/twenty-front/src/generated-metadata/graphql.ts @@ -1689,7 +1689,7 @@ export type Mutation = { createCoreViewGroup: CoreViewGroup; createCoreViewSort: CoreViewSort; createDatabaseConfigVariable: Scalars['Boolean']; - createDraftFromWorkflowVersion: WorkflowVersion; + createDraftFromWorkflowVersion: WorkflowVersionDto; createEmailingDomain: EmailingDomain; createFile: File; createOIDCIdentityProvider: SetupSsoOutput; @@ -4468,9 +4468,16 @@ export type WorkflowStepPositionUpdateInput = { position: WorkflowStepPositionInput; }; -export type WorkflowVersion = { - __typename?: 'WorkflowVersion'; +export type WorkflowVersionDto = { + __typename?: 'WorkflowVersionDTO'; + createdAt: Scalars['String']; id: Scalars['UUID']; + name: Scalars['String']; + status: Scalars['String']; + steps?: Maybe; + trigger?: Maybe; + updatedAt: Scalars['String']; + workflowId: Scalars['UUID']; }; export type WorkflowVersionStepChanges = { @@ -5911,7 +5918,7 @@ export type CreateDraftFromWorkflowVersionMutationVariables = Exact<{ }>; -export type CreateDraftFromWorkflowVersionMutation = { __typename?: 'Mutation', createDraftFromWorkflowVersion: { __typename?: 'WorkflowVersion', id: string } }; +export type CreateDraftFromWorkflowVersionMutation = { __typename?: 'Mutation', createDraftFromWorkflowVersion: { __typename?: 'WorkflowVersionDTO', id: string, name: string, status: string, trigger?: any | null, steps?: any | null, createdAt: string, updatedAt: string } }; export type CreateWorkflowVersionEdgeMutationVariables = Exact<{ input: CreateWorkflowVersionEdgeInput; @@ -13103,6 +13110,12 @@ export const CreateDraftFromWorkflowVersionDocument = gql` mutation CreateDraftFromWorkflowVersion($input: CreateDraftFromWorkflowVersionInput!) { createDraftFromWorkflowVersion(input: $input) { id + name + status + trigger + steps + createdAt + updatedAt } } `; diff --git a/packages/twenty-front/src/generated/graphql.ts b/packages/twenty-front/src/generated/graphql.ts index 214a6e15cc..a5bd78405e 100644 --- a/packages/twenty-front/src/generated/graphql.ts +++ b/packages/twenty-front/src/generated/graphql.ts @@ -1646,7 +1646,7 @@ export type Mutation = { createCoreViewGroup: CoreViewGroup; createCoreViewSort: CoreViewSort; createDatabaseConfigVariable: Scalars['Boolean']; - createDraftFromWorkflowVersion: WorkflowVersion; + createDraftFromWorkflowVersion: WorkflowVersionDto; createEmailingDomain: EmailingDomain; createFile: File; createOIDCIdentityProvider: SetupSsoOutput; @@ -4296,9 +4296,16 @@ export type WorkflowStepPositionUpdateInput = { position: WorkflowStepPositionInput; }; -export type WorkflowVersion = { - __typename?: 'WorkflowVersion'; +export type WorkflowVersionDto = { + __typename?: 'WorkflowVersionDTO'; + createdAt: Scalars['String']; id: Scalars['UUID']; + name: Scalars['String']; + status: Scalars['String']; + steps?: Maybe; + trigger?: Maybe; + updatedAt: Scalars['String']; + workflowId: Scalars['UUID']; }; export type WorkflowVersionStepChanges = { diff --git a/packages/twenty-front/src/modules/activities/calendar/components/CalendarEventDetailsEffect.tsx b/packages/twenty-front/src/modules/activities/calendar/components/CalendarEventDetailsEffect.tsx index 6cbae88551..f702bbcef0 100644 --- a/packages/twenty-front/src/modules/activities/calendar/components/CalendarEventDetailsEffect.tsx +++ b/packages/twenty-front/src/modules/activities/calendar/components/CalendarEventDetailsEffect.tsx @@ -9,15 +9,15 @@ type CalendarEventDetailsEffectProps = { export const CalendarEventDetailsEffect = ({ record, }: CalendarEventDetailsEffectProps) => { - const { upsertRecords } = useUpsertRecordsInStore(); + const { upsertRecordsInStore } = useUpsertRecordsInStore(); useEffect(() => { if (!record) { return; } - upsertRecords([record]); - }, [record, upsertRecords]); + upsertRecordsInStore([record]); + }, [record, upsertRecordsInStore]); return <>; }; diff --git a/packages/twenty-front/src/modules/activities/timeline-activities/rows/calendar/components/EventCardCalendarEvent.tsx b/packages/twenty-front/src/modules/activities/timeline-activities/rows/calendar/components/EventCardCalendarEvent.tsx index 7242e213ca..7a0481b362 100644 --- a/packages/twenty-front/src/modules/activities/timeline-activities/rows/calendar/components/EventCardCalendarEvent.tsx +++ b/packages/twenty-front/src/modules/activities/timeline-activities/rows/calendar/components/EventCardCalendarEvent.tsx @@ -94,7 +94,7 @@ export const EventCardCalendarEvent = ({ }: { calendarEventId: string; }) => { - const { upsertRecords } = useUpsertRecordsInStore(); + const { upsertRecordsInStore } = useUpsertRecordsInStore(); const { openCalendarEventInCommandMenu } = useOpenCalendarEventInCommandMenu(); @@ -118,7 +118,7 @@ export const EventCardCalendarEvent = ({ }, }, onCompleted: (data) => { - upsertRecords([data]); + upsertRecordsInStore([data]); }, }); diff --git a/packages/twenty-front/src/modules/activities/timeline-activities/rows/message/components/EventCardMessage.tsx b/packages/twenty-front/src/modules/activities/timeline-activities/rows/message/components/EventCardMessage.tsx index ce352da039..5ddd1e36be 100644 --- a/packages/twenty-front/src/modules/activities/timeline-activities/rows/message/components/EventCardMessage.tsx +++ b/packages/twenty-front/src/modules/activities/timeline-activities/rows/message/components/EventCardMessage.tsx @@ -59,7 +59,7 @@ export const EventCardMessage = ({ messageId: string; authorFullName: string; }) => { - const { upsertRecords } = useUpsertRecordsInStore(); + const { upsertRecordsInStore } = useUpsertRecordsInStore(); const { openEmailThreadInCommandMenu } = useOpenEmailThreadInCommandMenu(); const { @@ -80,7 +80,7 @@ export const EventCardMessage = ({ }, }, onCompleted: (data) => { - upsertRecords([data]); + upsertRecordsInStore([data]); }, }); diff --git a/packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerAttachRelationOptimisticEffect.ts b/packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerAttachRelationOptimisticEffect.ts index 91a30e5a96..4e5eedc342 100644 --- a/packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerAttachRelationOptimisticEffect.ts +++ b/packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerAttachRelationOptimisticEffect.ts @@ -1,26 +1,45 @@ import { type ApolloCache, type StoreObject } from '@apollo/client'; +import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { type RecordGqlRefEdge } from '@/object-record/cache/types/RecordGqlRefEdge'; +import { getRecordFromCache } from '@/object-record/cache/utils/getRecordFromCache'; +import { getRecordFromRecordNode } from '@/object-record/cache/utils/getRecordFromRecordNode'; import { isObjectRecordConnectionWithRefs } from '@/object-record/cache/utils/isObjectRecordConnectionWithRefs'; +import { type ObjectRecord } from '@/object-record/types/ObjectRecord'; +import { type ObjectPermissions } from 'twenty-shared/types'; import { capitalize, isDefined } from 'twenty-shared/utils'; export const triggerAttachRelationOptimisticEffect = ({ cache, sourceObjectNameSingular, sourceRecordId, - targetObjectNameSingular, + targetObjectMetadataItem, fieldNameOnTargetRecord, targetRecordId, + upsertRecordsInStore, + objectMetadataItems, + objectPermissionsByObjectMetadataId, }: { cache: ApolloCache; sourceObjectNameSingular: string; sourceRecordId: string; - targetObjectNameSingular: string; + targetObjectMetadataItem: Pick< + ObjectMetadataItem, + 'fields' | 'nameSingular' | 'id' | 'readableFields' + >; fieldNameOnTargetRecord: string; targetRecordId: string; + objectMetadataItems: ObjectMetadataItem[]; + objectPermissionsByObjectMetadataId: Record< + string, + ObjectPermissions & { objectMetadataId: string } + >; + upsertRecordsInStore: (records: ObjectRecord[]) => void; }) => { const sourceRecordTypeName = capitalize(sourceObjectNameSingular); - const targetRecordTypeName = capitalize(targetObjectNameSingular); + const targetRecordTypeName = capitalize( + targetObjectMetadataItem.nameSingular, + ); const targetRecordCacheId = cache.identify({ id: targetRecordId, @@ -65,6 +84,19 @@ export const triggerAttachRelationOptimisticEffect = ({ }, ]; + upsertRecordsInStore([ + getRecordFromRecordNode({ + recordNode: { + id: targetRecordId, + [fieldNameOnTargetRecord]: { + ...targetRecordFieldValue, + edges: nextEdges, + }, + __typename: targetRecordTypeName, + }, + }), + ]); + return { ...targetRecordFieldValue, edges: nextEdges, @@ -76,4 +108,18 @@ export const triggerAttachRelationOptimisticEffect = ({ }, }, }); + + const newCachedRecord = getRecordFromCache({ + cache, + objectMetadataItem: targetObjectMetadataItem, + objectMetadataItems, + recordId: targetRecordId, + objectPermissionsByObjectMetadataId, + }); + + if (!isDefined(newCachedRecord)) { + return; + } + + upsertRecordsInStore([newCachedRecord]); }; 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 7233be3f7d..68d3200410 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 @@ -13,6 +13,7 @@ import { type CachedObjectRecordQueryVariables } from '@/apollo/types/CachedObje import { encodeCursor } from '@/apollo/utils/encodeCursor'; import { getRecordFromCache } from '@/object-record/cache/utils/getRecordFromCache'; import { getRecordNodeFromRecord } from '@/object-record/cache/utils/getRecordNodeFromRecord'; +import { type ObjectRecord } from '@/object-record/types/ObjectRecord'; import { type ObjectPermissions } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; import { parseApolloStoreFieldName } from '~/utils/parseApolloStoreFieldName'; @@ -33,7 +34,9 @@ type TriggerCreateRecordsOptimisticEffectArgs = { string, ObjectPermissions & { objectMetadataId: string } >; + upsertRecordsInStore: (records: ObjectRecord[]) => void; }; + export const triggerCreateRecordsOptimisticEffect = ({ cache, objectMetadataItem, @@ -42,6 +45,7 @@ export const triggerCreateRecordsOptimisticEffect = ({ shouldMatchRootQueryFilter, checkForRecordInCache = false, objectPermissionsByObjectMetadataId, + upsertRecordsInStore, }: TriggerCreateRecordsOptimisticEffectArgs) => { const getRecordNodeFromCache = (recordId: string): RecordGqlNode | null => { const cachedRecord = getRecordFromCache({ @@ -69,6 +73,8 @@ export const triggerCreateRecordsOptimisticEffect = ({ currentSourceRecord, updatedSourceRecord: record, objectMetadataItems, + upsertRecordsInStore, + objectPermissionsByObjectMetadataId, }); }); diff --git a/packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerDestroyRecordsOptimisticEffect.ts b/packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerDestroyRecordsOptimisticEffect.ts index ff74876670..627c1e4789 100644 --- a/packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerDestroyRecordsOptimisticEffect.ts +++ b/packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerDestroyRecordsOptimisticEffect.ts @@ -5,6 +5,8 @@ import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataI import { type RecordGqlRefEdge } from '@/object-record/cache/types/RecordGqlRefEdge'; import { isObjectRecordConnectionWithRefs } from '@/object-record/cache/utils/isObjectRecordConnectionWithRefs'; import { type RecordGqlNode } from '@/object-record/graphql/types/RecordGqlNode'; +import { type ObjectRecord } from '@/object-record/types/ObjectRecord'; +import { type ObjectPermissions } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; export const triggerDestroyRecordsOptimisticEffect = ({ @@ -12,11 +14,18 @@ export const triggerDestroyRecordsOptimisticEffect = ({ objectMetadataItem, recordsToDestroy, objectMetadataItems, + upsertRecordsInStore, + objectPermissionsByObjectMetadataId, }: { cache: ApolloCache; objectMetadataItem: ObjectMetadataItem; recordsToDestroy: RecordGqlNode[]; objectMetadataItems: ObjectMetadataItem[]; + upsertRecordsInStore: (records: ObjectRecord[]) => void; + objectPermissionsByObjectMetadataId: Record< + string, + ObjectPermissions & { objectMetadataId: string } + >; }) => { cache.modify({ fields: { @@ -75,6 +84,8 @@ export const triggerDestroyRecordsOptimisticEffect = ({ currentSourceRecord: recordToDestroy, updatedSourceRecord: null, objectMetadataItems, + objectPermissionsByObjectMetadataId, + upsertRecordsInStore, }); cache.evict({ id: cache.identify(recordToDestroy) }); diff --git a/packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerDetachRelationOptimisticEffect.ts b/packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerDetachRelationOptimisticEffect.ts index 6fc7c2ce55..ca63bf4c67 100644 --- a/packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerDetachRelationOptimisticEffect.ts +++ b/packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerDetachRelationOptimisticEffect.ts @@ -1,24 +1,42 @@ import { type ApolloCache, type StoreObject } from '@apollo/client'; +import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; +import { getRecordFromCache } from '@/object-record/cache/utils/getRecordFromCache'; import { isObjectRecordConnectionWithRefs } from '@/object-record/cache/utils/isObjectRecordConnectionWithRefs'; -import { capitalize } from 'twenty-shared/utils'; +import { type ObjectRecord } from '@/object-record/types/ObjectRecord'; +import { type ObjectPermissions } from 'twenty-shared/types'; +import { capitalize, isDefined } from 'twenty-shared/utils'; export const triggerDetachRelationOptimisticEffect = ({ cache, sourceObjectNameSingular, sourceRecordId, - targetObjectNameSingular, + targetObjectMetadataItem, fieldNameOnTargetRecord, targetRecordId, + objectMetadataItems, + objectPermissionsByObjectMetadataId, + upsertRecordsInStore, }: { cache: ApolloCache; sourceObjectNameSingular: string; sourceRecordId: string; - targetObjectNameSingular: string; + targetObjectMetadataItem: Pick< + ObjectMetadataItem, + 'fields' | 'nameSingular' | 'id' | 'readableFields' + >; fieldNameOnTargetRecord: string; targetRecordId: string; + objectMetadataItems: ObjectMetadataItem[]; + objectPermissionsByObjectMetadataId: Record< + string, + ObjectPermissions & { objectMetadataId: string } + >; + upsertRecordsInStore: (records: ObjectRecord[]) => void; }) => { - const targetRecordTypeName = capitalize(targetObjectNameSingular); + const targetRecordTypeName = capitalize( + targetObjectMetadataItem.nameSingular, + ); const targetRecordCacheId = cache.identify({ id: targetRecordId, @@ -58,4 +76,18 @@ export const triggerDetachRelationOptimisticEffect = ({ }, }, }); + + const newCachedRecord = getRecordFromCache({ + cache, + objectMetadataItem: targetObjectMetadataItem, + objectMetadataItems, + recordId: targetRecordId, + objectPermissionsByObjectMetadataId, + }); + + if (!isDefined(newCachedRecord)) { + return; + } + + upsertRecordsInStore([newCachedRecord]); }; 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 a1c098f195..ce22f5d887 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 @@ -9,9 +9,10 @@ import { getEdgeTypename } from '@/object-record/cache/utils/getEdgeTypename'; import { isObjectRecordConnectionWithRefs } from '@/object-record/cache/utils/isObjectRecordConnectionWithRefs'; import { type RecordGqlNode } from '@/object-record/graphql/types/RecordGqlNode'; import { isRecordMatchingFilter } from '@/object-record/record-filter/utils/isRecordMatchingFilter'; -import { parseApolloStoreFieldName } from '~/utils/parseApolloStoreFieldName'; +import { type ObjectRecord } from '@/object-record/types/ObjectRecord'; +import { type ObjectPermissions } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; - +import { parseApolloStoreFieldName } from '~/utils/parseApolloStoreFieldName'; // TODO: add extensive unit tests for this function // That will also serve as documentation export const triggerUpdateRecordOptimisticEffect = ({ @@ -20,12 +21,19 @@ export const triggerUpdateRecordOptimisticEffect = ({ currentRecord, updatedRecord, objectMetadataItems, + objectPermissionsByObjectMetadataId, + upsertRecordsInStore, }: { cache: ApolloCache; objectMetadataItem: ObjectMetadataItem; currentRecord: RecordGqlNode; updatedRecord: RecordGqlNode; objectMetadataItems: ObjectMetadataItem[]; + objectPermissionsByObjectMetadataId: Record< + string, + ObjectPermissions & { objectMetadataId: string } + >; + upsertRecordsInStore: (records: ObjectRecord[]) => void; }) => { triggerUpdateRelationsOptimisticEffect({ cache, @@ -33,6 +41,8 @@ export const triggerUpdateRecordOptimisticEffect = ({ currentSourceRecord: currentRecord, updatedSourceRecord: updatedRecord, objectMetadataItems, + objectPermissionsByObjectMetadataId, + upsertRecordsInStore, }); cache.modify({ diff --git a/packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerUpdateRecordOptimisticEffectByBatch.ts b/packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerUpdateRecordOptimisticEffectByBatch.ts index 43c7583208..05bd68c67a 100644 --- a/packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerUpdateRecordOptimisticEffectByBatch.ts +++ b/packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerUpdateRecordOptimisticEffectByBatch.ts @@ -9,8 +9,10 @@ import { getEdgeTypename } from '@/object-record/cache/utils/getEdgeTypename'; import { isObjectRecordConnectionWithRefs } from '@/object-record/cache/utils/isObjectRecordConnectionWithRefs'; import { type RecordGqlNode } from '@/object-record/graphql/types/RecordGqlNode'; import { isRecordMatchingFilter } from '@/object-record/record-filter/utils/isRecordMatchingFilter'; -import { parseApolloStoreFieldName } from '~/utils/parseApolloStoreFieldName'; +import { type ObjectRecord } from '@/object-record/types/ObjectRecord'; +import { type ObjectPermissions } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; +import { parseApolloStoreFieldName } from '~/utils/parseApolloStoreFieldName'; // TODO: add extensive unit tests for this function // That will also serve as documentation @@ -20,12 +22,19 @@ export const triggerUpdateRecordOptimisticEffectByBatch = ({ currentRecords, updatedRecords, objectMetadataItems, + objectPermissionsByObjectMetadataId, + upsertRecordsInStore, }: { cache: ApolloCache; objectMetadataItem: ObjectMetadataItem; currentRecords: RecordGqlNode[]; updatedRecords: RecordGqlNode[]; objectMetadataItems: ObjectMetadataItem[]; + objectPermissionsByObjectMetadataId: Record< + string, + ObjectPermissions & { objectMetadataId: string } + >; + upsertRecordsInStore: (records: ObjectRecord[]) => void; }) => { for (const [index, currentRecord] of currentRecords.entries()) { triggerUpdateRelationsOptimisticEffect({ @@ -34,6 +43,8 @@ export const triggerUpdateRecordOptimisticEffectByBatch = ({ currentSourceRecord: currentRecord, updatedSourceRecord: updatedRecords[index], objectMetadataItems, + objectPermissionsByObjectMetadataId, + upsertRecordsInStore, }); } 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 index 1d8959c633..8b8777d7ce 100644 --- a/packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerUpdateRelationsOptimisticEffect.ts +++ b/packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerUpdateRelationsOptimisticEffect.ts @@ -12,9 +12,10 @@ import { type RecordGqlConnection } from '@/object-record/graphql/types/RecordGq import { type RecordGqlNode } from '@/object-record/graphql/types/RecordGqlNode'; import { isFieldMorphRelation } from '@/object-record/record-field/ui/types/guards/isFieldMorphRelation'; import { isFieldRelation } from '@/object-record/record-field/ui/types/guards/isFieldRelation'; +import { type ObjectRecord } from '@/object-record/types/ObjectRecord'; import { type ApolloCache } from '@apollo/client'; import { isArray } from '@sniptt/guards'; -import { FieldMetadataType } from 'twenty-shared/types'; +import { FieldMetadataType, type ObjectPermissions } from 'twenty-shared/types'; import { computeMorphRelationFieldName, CustomError, @@ -28,13 +29,21 @@ type TriggerUpdateRelationsOptimisticEffectArgs = { currentSourceRecord: RecordGqlNode | null; updatedSourceRecord: RecordGqlNode | null; objectMetadataItems: ObjectMetadataItem[]; + objectPermissionsByObjectMetadataId: Record< + string, + ObjectPermissions & { objectMetadataId: string } + >; + upsertRecordsInStore: (records: ObjectRecord[]) => void; }; + export const triggerUpdateRelationsOptimisticEffect = ({ cache, sourceObjectMetadataItem, currentSourceRecord, updatedSourceRecord, objectMetadataItems, + objectPermissionsByObjectMetadataId, + upsertRecordsInStore, }: TriggerUpdateRelationsOptimisticEffectArgs) => { const isDeletion = isDefined(updatedSourceRecord) && @@ -57,6 +66,8 @@ export const triggerUpdateRelationsOptimisticEffect = ({ sourceObjectMetadataItem, cache, isDeletion, + upsertRecordsInStore, + objectPermissionsByObjectMetadataId, }); } @@ -69,6 +80,8 @@ export const triggerUpdateRelationsOptimisticEffect = ({ sourceObjectMetadataItem, cache, isDeletion, + upsertRecordsInStore, + objectPermissionsByObjectMetadataId, }); } }); @@ -82,6 +95,8 @@ const triggerUpdateRelationOptimisticEffect = ({ sourceObjectMetadataItem, cache, isDeletion, + upsertRecordsInStore, + objectPermissionsByObjectMetadataId, }: { fieldMetadataItemOnSourceRecord: FieldMetadataItem; updatedSourceRecord: RecordGqlNode | null; @@ -90,6 +105,11 @@ const triggerUpdateRelationOptimisticEffect = ({ sourceObjectMetadataItem: ObjectMetadataItem; cache: ApolloCache; isDeletion: boolean; + upsertRecordsInStore: (records: ObjectRecord[]) => void; + objectPermissionsByObjectMetadataId: Record< + string, + ObjectPermissions & { objectMetadataId: string } + >; }) => { const fieldDoesNotExist = isDefined(updatedSourceRecord) && @@ -175,6 +195,8 @@ const triggerUpdateRelationOptimisticEffect = ({ objectMetadataItem: fullTargetObjectMetadataItem, recordsToDestroy: targetRecordsToDetachFrom, objectMetadataItems, + upsertRecordsInStore, + objectPermissionsByObjectMetadataId, }); } else if (isDefined(currentSourceRecord)) { targetRecordsToDetachFrom.forEach((targetRecordToDetachFrom) => { @@ -183,8 +205,11 @@ const triggerUpdateRelationOptimisticEffect = ({ sourceObjectNameSingular: sourceObjectMetadataItem.nameSingular, sourceRecordId: currentSourceRecord.id, fieldNameOnTargetRecord: gqlFieldNameOnTargetRecord, - targetObjectNameSingular: targetObjectMetadata.nameSingular, + targetObjectMetadataItem: fullTargetObjectMetadataItem, targetRecordId: targetRecordToDetachFrom.id, + objectMetadataItems, + objectPermissionsByObjectMetadataId, + upsertRecordsInStore, }); }); } @@ -201,8 +226,11 @@ const triggerUpdateRelationOptimisticEffect = ({ sourceObjectNameSingular: sourceObjectMetadataItem.nameSingular, sourceRecordId: updatedSourceRecord.id, fieldNameOnTargetRecord: gqlFieldNameOnTargetRecord, - targetObjectNameSingular: targetObjectMetadata.nameSingular, + targetObjectMetadataItem: fullTargetObjectMetadataItem, targetRecordId: targetRecordToAttachTo.id, + objectMetadataItems, + objectPermissionsByObjectMetadataId, + upsertRecordsInStore, }), ); } @@ -216,6 +244,8 @@ const triggerUpdateMorphRelationOptimisticEffect = ({ sourceObjectMetadataItem, cache, isDeletion, + objectPermissionsByObjectMetadataId, + upsertRecordsInStore, }: { fieldMetadataItemOnSourceRecord: FieldMetadataItem; updatedSourceRecord: RecordGqlNode | null; @@ -224,6 +254,11 @@ const triggerUpdateMorphRelationOptimisticEffect = ({ sourceObjectMetadataItem: ObjectMetadataItem; cache: ApolloCache; isDeletion: boolean; + upsertRecordsInStore: (records: ObjectRecord[]) => void; + objectPermissionsByObjectMetadataId: Record< + string, + ObjectPermissions & { objectMetadataId: string } + >; }) => { const morphRelations = fieldMetadataItemOnSourceRecord.morphRelations; if (!morphRelations) { @@ -309,6 +344,8 @@ const triggerUpdateMorphRelationOptimisticEffect = ({ objectMetadataItem: fullTargetObjectMetadataItem, recordsToDestroy: targetRecordsToDetachFrom, objectMetadataItems, + objectPermissionsByObjectMetadataId, + upsertRecordsInStore, }); } else if (isDefined(currentSourceRecord)) { targetRecordsToDetachFrom.forEach((targetRecordToDetachFrom) => { @@ -317,8 +354,11 @@ const triggerUpdateMorphRelationOptimisticEffect = ({ sourceObjectNameSingular: sourceObjectMetadataItem.nameSingular, sourceRecordId: currentSourceRecord.id, fieldNameOnTargetRecord: targetFieldMetadata.name, - targetObjectNameSingular: targetObjectMetadata.nameSingular, + targetObjectMetadataItem: fullTargetObjectMetadataItem, + objectMetadataItems, + objectPermissionsByObjectMetadataId, targetRecordId: targetRecordToDetachFrom.id, + upsertRecordsInStore, }); }); } @@ -335,8 +375,11 @@ const triggerUpdateMorphRelationOptimisticEffect = ({ sourceObjectNameSingular: sourceObjectMetadataItem.nameSingular, sourceRecordId: updatedSourceRecord.id, fieldNameOnTargetRecord: targetFieldMetadata.name, - targetObjectNameSingular: targetObjectMetadata.nameSingular, + targetObjectMetadataItem: fullTargetObjectMetadataItem, + objectMetadataItems, + objectPermissionsByObjectMetadataId, targetRecordId: targetRecordToAttachTo.id, + upsertRecordsInStore, }), ); } diff --git a/packages/twenty-front/src/modules/command-menu/pages/calendar-event/components/CommandMenuCalendarEventPage.tsx b/packages/twenty-front/src/modules/command-menu/pages/calendar-event/components/CommandMenuCalendarEventPage.tsx index fc8592cc1f..6284c39905 100644 --- a/packages/twenty-front/src/modules/command-menu/pages/calendar-event/components/CommandMenuCalendarEventPage.tsx +++ b/packages/twenty-front/src/modules/command-menu/pages/calendar-event/components/CommandMenuCalendarEventPage.tsx @@ -8,7 +8,7 @@ import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useU import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; export const CommandMenuCalendarEventPage = () => { - const { upsertRecords } = useUpsertRecordsInStore(); + const { upsertRecordsInStore } = useUpsertRecordsInStore(); const viewableRecordId = useRecoilComponentValue( viewableRecordIdComponentState, ); @@ -20,7 +20,7 @@ export const CommandMenuCalendarEventPage = () => { recordGqlFields: FIND_ONE_CALENDAR_EVENT_OPERATION_SIGNATURE.fields, // TODO: this is not executed on sub-sequent runs, make sure that it is intended onCompleted: (record) => { - upsertRecords([record]); + upsertRecordsInStore([record]); }, }); diff --git a/packages/twenty-front/src/modules/command-menu/pages/message-thread/hooks/useEmailThreadInCommandMenu.ts b/packages/twenty-front/src/modules/command-menu/pages/message-thread/hooks/useEmailThreadInCommandMenu.ts index 6d7042da78..09c2d40df4 100644 --- a/packages/twenty-front/src/modules/command-menu/pages/message-thread/hooks/useEmailThreadInCommandMenu.ts +++ b/packages/twenty-front/src/modules/command-menu/pages/message-thread/hooks/useEmailThreadInCommandMenu.ts @@ -21,7 +21,7 @@ export const useEmailThreadInCommandMenu = () => { const viewableRecordId = useRecoilComponentValue( viewableRecordIdComponentState, ); - const { upsertRecords } = useUpsertRecordsInStore(); + const { upsertRecordsInStore } = useUpsertRecordsInStore(); const [lastMessageId, setLastMessageId] = useState(null); const [lastMessageChannelId, setLastMessageChannelId] = useState< string | null @@ -35,7 +35,7 @@ export const useEmailThreadInCommandMenu = () => { id: true, }, onCompleted: (record) => { - upsertRecords([record]); + upsertRecordsInStore([record]); }, }); diff --git a/packages/twenty-front/src/modules/object-record/cache/hooks/useDeleteRecordFromCache.ts b/packages/twenty-front/src/modules/object-record/cache/hooks/useDeleteRecordFromCache.ts index 89f18147bd..e48c75f2b4 100644 --- a/packages/twenty-front/src/modules/object-record/cache/hooks/useDeleteRecordFromCache.ts +++ b/packages/twenty-front/src/modules/object-record/cache/hooks/useDeleteRecordFromCache.ts @@ -2,6 +2,8 @@ import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems'; import { deleteRecordFromCache } from '@/object-record/cache/utils/deleteRecordFromCache'; +import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions'; +import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore'; import { type ObjectRecord } from '@/object-record/types/ObjectRecord'; export const useDeleteRecordFromCache = ({ @@ -10,11 +12,12 @@ export const useDeleteRecordFromCache = ({ objectNameSingular: string; }) => { const apolloCoreClient = useApolloCoreClient(); + const { objectPermissionsByObjectMetadataId } = useObjectPermissions(); const { objectMetadataItem } = useObjectMetadataItem({ objectNameSingular, }); - + const { upsertRecordsInStore } = useUpsertRecordsInStore(); const { objectMetadataItems } = useObjectMetadataItems(); return (recordToDestroy: ObjectRecord) => { @@ -23,6 +26,8 @@ export const useDeleteRecordFromCache = ({ objectMetadataItems, recordToDestroy, cache: apolloCoreClient.cache, + upsertRecordsInStore, + objectPermissionsByObjectMetadataId, }); }; }; diff --git a/packages/twenty-front/src/modules/object-record/cache/utils/deleteRecordFromCache.ts b/packages/twenty-front/src/modules/object-record/cache/utils/deleteRecordFromCache.ts index 588b6631c8..66afcc448d 100644 --- a/packages/twenty-front/src/modules/object-record/cache/utils/deleteRecordFromCache.ts +++ b/packages/twenty-front/src/modules/object-record/cache/utils/deleteRecordFromCache.ts @@ -4,17 +4,25 @@ import { triggerDestroyRecordsOptimisticEffect } from '@/apollo/optimistic-effec import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { getObjectTypename } from '@/object-record/cache/utils/getObjectTypename'; import { type ObjectRecord } from '@/object-record/types/ObjectRecord'; +import { type ObjectPermissions } from 'twenty-shared/types'; export const deleteRecordFromCache = ({ objectMetadataItem, objectMetadataItems, recordToDestroy, cache, + upsertRecordsInStore, + objectPermissionsByObjectMetadataId, }: { objectMetadataItem: ObjectMetadataItem; objectMetadataItems: ObjectMetadataItem[]; recordToDestroy: ObjectRecord; cache: ApolloCache; + objectPermissionsByObjectMetadataId: Record< + string, + ObjectPermissions & { objectMetadataId: string } + >; + upsertRecordsInStore: (records: ObjectRecord[]) => void; }) => { triggerDestroyRecordsOptimisticEffect({ cache, @@ -26,5 +34,7 @@ export const deleteRecordFromCache = ({ __typename: getObjectTypename(objectMetadataItem.nameSingular), }, ], + upsertRecordsInStore, + objectPermissionsByObjectMetadataId, }); }; diff --git a/packages/twenty-front/src/modules/object-record/cache/utils/getRecordFromCache.ts b/packages/twenty-front/src/modules/object-record/cache/utils/getRecordFromCache.ts index 18e683741b..88673c76b0 100644 --- a/packages/twenty-front/src/modules/object-record/cache/utils/getRecordFromCache.ts +++ b/packages/twenty-front/src/modules/object-record/cache/utils/getRecordFromCache.ts @@ -12,10 +12,13 @@ import { isEmptyObject } from '~/utils/isEmptyObject'; import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull'; export type GetRecordFromCacheArgs = { - cache: ApolloCache; + cache: ApolloCache; recordId: string; objectMetadataItems: ObjectMetadataItem[]; - objectMetadataItem: ObjectMetadataItem; + objectMetadataItem: Pick< + ObjectMetadataItem, + 'fields' | 'nameSingular' | 'id' | 'readableFields' + >; recordGqlFields?: RecordGqlFields; objectPermissionsByObjectMetadataId: Record< string, diff --git a/packages/twenty-front/src/modules/object-record/graphql/record-gql-fields/utils/generateDepthRecordGqlFieldsFromObject.ts b/packages/twenty-front/src/modules/object-record/graphql/record-gql-fields/utils/generateDepthRecordGqlFieldsFromObject.ts index 63b2fc9884..99fc6736cb 100644 --- a/packages/twenty-front/src/modules/object-record/graphql/record-gql-fields/utils/generateDepthRecordGqlFieldsFromObject.ts +++ b/packages/twenty-front/src/modules/object-record/graphql/record-gql-fields/utils/generateDepthRecordGqlFieldsFromObject.ts @@ -3,7 +3,7 @@ import { generateDepthRecordGqlFieldsFromFields } from '@/object-record/graphql/ export type GenerateDepthRecordGqlFields = { objectMetadataItems: ObjectMetadataItem[]; - objectMetadataItem: ObjectMetadataItem; + objectMetadataItem: Pick; depth: 0 | 1; shouldOnlyLoadRelationIdentifiers?: boolean; }; 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 c99e585c5f..a2d1b4e18e 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useCreateManyRecords.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useCreateManyRecords.ts @@ -18,6 +18,7 @@ import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions import { useRefetchAggregateQueries } from '@/object-record/hooks/useRefetchAggregateQueries'; import { useRegisterObjectOperation } from '@/object-record/hooks/useRegisterObjectOperation'; import { type FieldActorForInputValue } from '@/object-record/record-field/ui/types/FieldMetadata'; +import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore'; import { type ObjectRecord } from '@/object-record/types/ObjectRecord'; import { computeOptimisticRecordFromInput } from '@/object-record/utils/computeOptimisticRecordFromInput'; import { getCreateManyRecordsMutationResponseField } from '@/object-record/utils/getCreateManyRecordsMutationResponseField'; @@ -51,6 +52,7 @@ export const useCreateManyRecords = < shouldRefetchAggregateQueries = true, }: useCreateManyRecordsProps) => { const { registerObjectOperation } = useRegisterObjectOperation(); + const { upsertRecordsInStore } = useUpsertRecordsInStore(); const apolloCoreClient = useApolloCoreClient(); @@ -171,6 +173,7 @@ export const useCreateManyRecords = < objectMetadataItems, shouldMatchRootQueryFilter, objectPermissionsByObjectMetadataId, + upsertRecordsInStore, }); } @@ -208,6 +211,7 @@ export const useCreateManyRecords = < shouldMatchRootQueryFilter, checkForRecordInCache: true, objectPermissionsByObjectMetadataId, + upsertRecordsInStore, }); }, }) @@ -218,6 +222,8 @@ export const useCreateManyRecords = < objectMetadataItem, cache: apolloCoreClient.cache, recordToDestroy, + upsertRecordsInStore, + objectPermissionsByObjectMetadataId, }); }); @@ -226,6 +232,8 @@ export const useCreateManyRecords = < objectMetadataItem, recordsToDestroy: recordsCreatedInCache, objectMetadataItems, + upsertRecordsInStore, + objectPermissionsByObjectMetadataId, }); throw error; 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 f62ba4f16a..8a7317c19c 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useCreateOneRecord.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useCreateOneRecord.ts @@ -17,6 +17,7 @@ import { useCreateOneRecordMutation } from '@/object-record/hooks/useCreateOneRe import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions'; import { useRefetchAggregateQueries } from '@/object-record/hooks/useRefetchAggregateQueries'; import { useRegisterObjectOperation } from '@/object-record/hooks/useRegisterObjectOperation'; +import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore'; import { type ObjectRecord } from '@/object-record/types/ObjectRecord'; import { computeOptimisticCreateRecordBaseRecordInput } from '@/object-record/utils/computeOptimisticCreateRecordBaseRecordInput'; import { computeOptimisticRecordFromInput } from '@/object-record/utils/computeOptimisticRecordFromInput'; @@ -39,6 +40,7 @@ export const useCreateOneRecord = < skipPostOptimisticEffect = false, shouldMatchRootQueryFilter, }: useCreateOneRecordProps) => { + const { upsertRecordsInStore } = useUpsertRecordsInStore(); const { registerObjectOperation } = useRegisterObjectOperation(); const apolloCoreClient = useApolloCoreClient(); const [loading, setLoading] = useState(false); @@ -123,6 +125,7 @@ export const useCreateOneRecord = < objectMetadataItems, shouldMatchRootQueryFilter, objectPermissionsByObjectMetadataId, + upsertRecordsInStore, }); } } @@ -147,6 +150,7 @@ export const useCreateOneRecord = < shouldMatchRootQueryFilter, checkForRecordInCache: true, objectPermissionsByObjectMetadataId, + upsertRecordsInStore, }); } @@ -163,6 +167,8 @@ export const useCreateOneRecord = < objectMetadataItem, cache: apolloCoreClient.cache, recordToDestroy: recordCreatedInCache, + upsertRecordsInStore, + objectPermissionsByObjectMetadataId, }); triggerDestroyRecordsOptimisticEffect({ @@ -170,6 +176,8 @@ export const useCreateOneRecord = < objectMetadataItem, recordsToDestroy: [recordCreatedInCache], objectMetadataItems, + upsertRecordsInStore, + objectPermissionsByObjectMetadataId, }); throw error; 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 3630a1c263..2e3229ff2f 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useDeleteManyRecords.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useDeleteManyRecords.ts @@ -13,6 +13,7 @@ import { useDeleteManyRecordsMutation } from '@/object-record/hooks/useDeleteMan import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions'; import { useRefetchAggregateQueries } from '@/object-record/hooks/useRefetchAggregateQueries'; import { useRegisterObjectOperation } from '@/object-record/hooks/useRegisterObjectOperation'; +import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore'; import { type ObjectRecord } from '@/object-record/types/ObjectRecord'; import { getDeleteManyRecordsMutationResponseField } from '@/object-record/utils/getDeleteManyRecordsMutationResponseField'; import { useRecoilValue } from 'recoil'; @@ -34,6 +35,7 @@ export const useDeleteManyRecords = ({ objectNameSingular, }: useDeleteManyRecordProps) => { const { registerObjectOperation } = useRegisterObjectOperation(); + const { upsertRecordsInStore } = useUpsertRecordsInStore(); const apiConfig = useRecoilValue(apiConfigState); const mutationPageSize = @@ -133,6 +135,8 @@ export const useDeleteManyRecords = ({ currentRecords: cachedRecordsNode, updatedRecords: computedOptimisticRecordsNode, objectMetadataItems, + objectPermissionsByObjectMetadataId, + upsertRecordsInStore, }); } @@ -203,6 +207,8 @@ export const useDeleteManyRecords = ({ currentRecords: computedOptimisticRecordsNode, updatedRecords: cachedRecordsNode, objectMetadataItems, + objectPermissionsByObjectMetadataId, + upsertRecordsInStore, }); throw error; 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 e5d8052c78..82f2746958 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useDeleteOneRecord.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useDeleteOneRecord.ts @@ -13,6 +13,7 @@ import { useDeleteOneRecordMutation } from '@/object-record/hooks/useDeleteOneRe import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions'; import { useRefetchAggregateQueries } from '@/object-record/hooks/useRefetchAggregateQueries'; import { useRegisterObjectOperation } from '@/object-record/hooks/useRegisterObjectOperation'; +import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore'; import { type ObjectRecord } from '@/object-record/types/ObjectRecord'; import { getDeleteOneRecordMutationResponseField } from '@/object-record/utils/getDeleteOneRecordMutationResponseField'; import { isNull } from '@sniptt/guards'; @@ -26,6 +27,7 @@ export const useDeleteOneRecord = ({ objectNameSingular, }: useDeleteOneRecordProps) => { const { registerObjectOperation } = useRegisterObjectOperation(); + const { upsertRecordsInStore } = useUpsertRecordsInStore(); const apolloCoreClient = useApolloCoreClient(); const { objectMetadataItem } = useObjectMetadataItem({ @@ -100,6 +102,8 @@ export const useDeleteOneRecord = ({ currentRecord: cachedRecordNode, updatedRecord: optimisticRecordNode, objectMetadataItems, + objectPermissionsByObjectMetadataId, + upsertRecordsInStore, }); } @@ -121,6 +125,8 @@ export const useDeleteOneRecord = ({ currentRecord: optimisticRecordNode, updatedRecord: record, objectMetadataItems, + objectPermissionsByObjectMetadataId, + upsertRecordsInStore, }); }, }) @@ -150,6 +156,8 @@ export const useDeleteOneRecord = ({ currentRecord: optimisticRecordNode, updatedRecord: cachedRecordNode, objectMetadataItems, + objectPermissionsByObjectMetadataId, + upsertRecordsInStore, }); throw error; @@ -164,16 +172,17 @@ export const useDeleteOneRecord = ({ return deletedRecord.data?.[mutationResponseField] ?? null; }, [ - apolloCoreClient, - deleteOneRecordMutation, getRecordFromCache, - mutationResponseField, + apolloCoreClient, objectMetadataItem, objectMetadataItems, - objectPermissionsByObjectMetadataId, + deleteOneRecordMutation, refetchAggregateQueries, - objectNameSingular, registerObjectOperation, + objectNameSingular, + mutationResponseField, + objectPermissionsByObjectMetadataId, + upsertRecordsInStore, ], ); diff --git a/packages/twenty-front/src/modules/object-record/hooks/useDestroyManyRecords.ts b/packages/twenty-front/src/modules/object-record/hooks/useDestroyManyRecords.ts index fd42a39c1d..528b7448dd 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useDestroyManyRecords.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useDestroyManyRecords.ts @@ -10,6 +10,7 @@ import { useDestroyManyRecordsMutation } from '@/object-record/hooks/useDestroyM import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions'; import { useRefetchAggregateQueries } from '@/object-record/hooks/useRefetchAggregateQueries'; import { useRegisterObjectOperation } from '@/object-record/hooks/useRegisterObjectOperation'; +import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore'; import { type ObjectRecord } from '@/object-record/types/ObjectRecord'; import { getDestroyManyRecordsMutationResponseField } from '@/object-record/utils/getDestroyManyRecordsMutationResponseField'; import { useRecoilValue } from 'recoil'; @@ -31,6 +32,7 @@ export const useDestroyManyRecords = ({ objectNameSingular, }: useDestroyManyRecordProps) => { const { registerObjectOperation } = useRegisterObjectOperation(); + const { upsertRecordsInStore } = useUpsertRecordsInStore(); const apiConfig = useRecoilValue(apiConfigState); const mutationPageSize = @@ -112,6 +114,8 @@ export const useDestroyManyRecords = ({ objectMetadataItem, recordsToDestroy: cachedRecords, objectMetadataItems, + upsertRecordsInStore, + objectPermissionsByObjectMetadataId, }); }, }) @@ -123,6 +127,7 @@ export const useDestroyManyRecords = ({ recordsToCreate: cachedRecords, objectMetadataItems, objectPermissionsByObjectMetadataId, + upsertRecordsInStore, }); } throw error; diff --git a/packages/twenty-front/src/modules/object-record/hooks/useDestroyOneRecord.ts b/packages/twenty-front/src/modules/object-record/hooks/useDestroyOneRecord.ts index ec813cea4d..3126752d6f 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useDestroyOneRecord.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useDestroyOneRecord.ts @@ -9,6 +9,7 @@ import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordF import { useDestroyOneRecordMutation } from '@/object-record/hooks/useDestroyOneRecordMutation'; import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions'; import { useRegisterObjectOperation } from '@/object-record/hooks/useRegisterObjectOperation'; +import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore'; import { getDestroyOneRecordMutationResponseField } from '@/object-record/utils/getDestroyOneRecordMutationResponseField'; import { capitalize, isDefined } from 'twenty-shared/utils'; @@ -21,6 +22,7 @@ export const useDestroyOneRecord = ({ objectNameSingular, }: useDestroyOneRecordProps) => { const { registerObjectOperation } = useRegisterObjectOperation(); + const { upsertRecordsInStore } = useUpsertRecordsInStore(); const apolloCoreClient = useApolloCoreClient(); @@ -67,6 +69,8 @@ export const useDestroyOneRecord = ({ objectMetadataItem, recordsToDestroy: [cachedRecord], objectMetadataItems, + upsertRecordsInStore, + objectPermissionsByObjectMetadataId, }); }, }) @@ -78,6 +82,7 @@ export const useDestroyOneRecord = ({ recordsToCreate: [originalRecord], objectMetadataItems, objectPermissionsByObjectMetadataId, + upsertRecordsInStore, }); } @@ -91,15 +96,16 @@ export const useDestroyOneRecord = ({ return deletedRecord.data?.[mutationResponseField] ?? null; }, [ + getRecordFromCache, apolloCoreClient, destroyOneRecordMutation, - getRecordFromCache, mutationResponseField, - objectMetadataItem, objectNameSingular, - objectMetadataItems, - objectPermissionsByObjectMetadataId, registerObjectOperation, + objectMetadataItem, + objectMetadataItems, + upsertRecordsInStore, + objectPermissionsByObjectMetadataId, ], ); diff --git a/packages/twenty-front/src/modules/object-record/hooks/useLoadSelectedRecordsInContextStore.tsx b/packages/twenty-front/src/modules/object-record/hooks/useLoadSelectedRecordsInContextStore.tsx index bee9b29e38..b4c05b367d 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useLoadSelectedRecordsInContextStore.tsx +++ b/packages/twenty-front/src/modules/object-record/hooks/useLoadSelectedRecordsInContextStore.tsx @@ -17,7 +17,7 @@ export const useLoadSelectedRecordsInContextStore = ({ objectRecordIds, objectMetadataItemId, }: UseLoadSelectedRecordsInContextStoreProps) => { - const { upsertRecords } = useUpsertRecordsInStore(); + const { upsertRecordsInStore } = useUpsertRecordsInStore(); const { findManyRecordsLazy } = useLazyFindManyRecords({ objectNameSingular, @@ -57,10 +57,15 @@ export const useLoadSelectedRecordsInContextStore = ({ const { records } = await findManyRecordsLazy(); - upsertRecords(records ?? []); + upsertRecordsInStore(records ?? []); }; }, - [objectRecordIds, objectMetadataItemId, findManyRecordsLazy, upsertRecords], + [ + objectRecordIds, + objectMetadataItemId, + findManyRecordsLazy, + upsertRecordsInStore, + ], ); return { diff --git a/packages/twenty-front/src/modules/object-record/hooks/useMergeManyRecords.ts b/packages/twenty-front/src/modules/object-record/hooks/useMergeManyRecords.ts index 688bb7285b..1e77c6b269 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useMergeManyRecords.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useMergeManyRecords.ts @@ -116,14 +116,14 @@ export const useMergeManyRecords = < } }, [ - apolloCoreClient, - findDuplicateRecordsQuery, - findOneRecordQuery, - mergeManyRecordsMutation, objectMetadataItem.namePlural, - refetchAggregateQueries, + apolloCoreClient, + mergeManyRecordsMutation, + findOneRecordQuery, + findDuplicateRecordsQuery, registerObjectOperation, objectNameSingular, + refetchAggregateQueries, ], ); diff --git a/packages/twenty-front/src/modules/object-record/hooks/useRestoreManyRecords.ts b/packages/twenty-front/src/modules/object-record/hooks/useRestoreManyRecords.ts index d7696b41e5..c3cf1c660f 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useRestoreManyRecords.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useRestoreManyRecords.ts @@ -11,6 +11,7 @@ import { DEFAULT_MUTATION_BATCH_SIZE } from '@/object-record/constants/DefaultMu import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions'; import { useRegisterObjectOperation } from '@/object-record/hooks/useRegisterObjectOperation'; import { useRestoreManyRecordsMutation } from '@/object-record/hooks/useRestoreManyRecordsMutation'; +import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore'; import { type ObjectRecord } from '@/object-record/types/ObjectRecord'; import { getRestoreManyRecordsMutationResponseField } from '@/object-record/utils/getRestoreManyRecordsMutationResponseField'; import { useRecoilValue } from 'recoil'; @@ -32,6 +33,7 @@ export const useRestoreManyRecords = ({ objectNameSingular, }: useRestoreManyRecordProps) => { const { registerObjectOperation } = useRegisterObjectOperation(); + const { upsertRecordsInStore } = useUpsertRecordsInStore(); const apiConfig = useRecoilValue(apiConfigState); @@ -122,6 +124,8 @@ export const useRestoreManyRecords = ({ currentRecord: cachedRecordWithConnection, updatedRecord: optimisticRecordWithConnection, objectMetadataItems, + objectPermissionsByObjectMetadataId, + upsertRecordsInStore, }); } }); @@ -182,6 +186,8 @@ export const useRestoreManyRecords = ({ currentRecord: optimisticRecordWithConnection, updatedRecord: cachedRecordWithConnection, objectMetadataItems, + objectPermissionsByObjectMetadataId, + upsertRecordsInStore, }); } }); 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 9b666f38ad..a41d97f7ad 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useUpdateOneRecord.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useUpdateOneRecord.ts @@ -13,6 +13,7 @@ import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions import { useRefetchAggregateQueries } from '@/object-record/hooks/useRefetchAggregateQueries'; import { useRegisterObjectOperation } from '@/object-record/hooks/useRegisterObjectOperation'; import { useUpdateOneRecordMutation } from '@/object-record/hooks/useUpdateOneRecordMutation'; +import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore'; import { type ObjectRecord } from '@/object-record/types/ObjectRecord'; import { computeOptimisticRecordFromInput } from '@/object-record/utils/computeOptimisticRecordFromInput'; import { getUpdateOneRecordMutationResponseField } from '@/object-record/utils/getUpdateOneRecordMutationResponseField'; @@ -38,6 +39,7 @@ export const useUpdateOneRecord = < recordGqlFields, }: useUpdateOneRecordProps) => { const { registerObjectOperation } = useRegisterObjectOperation(); + const { upsertRecordsInStore } = useUpsertRecordsInStore(); const apolloCoreClient = useApolloCoreClient(); const { objectMetadataItem } = useObjectMetadataItem({ @@ -138,6 +140,8 @@ export const useUpdateOneRecord = < currentRecord: cachedRecordWithConnection, updatedRecord: optimisticRecordWithConnection, objectMetadataItems, + objectPermissionsByObjectMetadataId, + upsertRecordsInStore, }); } @@ -167,6 +171,8 @@ export const useUpdateOneRecord = < currentRecord: computedOptimisticRecord, updatedRecord: record, objectMetadataItems, + objectPermissionsByObjectMetadataId, + upsertRecordsInStore, }); }, }) @@ -213,6 +219,8 @@ export const useUpdateOneRecord = < currentRecord: optimisticRecordWithConnection, updatedRecord: cachedRecordWithConnection, objectMetadataItems, + objectPermissionsByObjectMetadataId, + upsertRecordsInStore, }); throw error; diff --git a/packages/twenty-front/src/modules/object-record/hooks/useUpdateOneRecordV2.ts b/packages/twenty-front/src/modules/object-record/hooks/useUpdateOneRecordV2.ts index 659146d492..a001e27b5f 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useUpdateOneRecordV2.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useUpdateOneRecordV2.ts @@ -11,6 +11,8 @@ import { type RecordGqlFields } from '@/object-record/graphql/record-gql-fields/ import { generateDepthRecordGqlFieldsFromObject } from '@/object-record/graphql/record-gql-fields/utils/generateDepthRecordGqlFieldsFromObject'; import { generateDepthRecordGqlFieldsFromRecord } from '@/object-record/graphql/record-gql-fields/utils/generateDepthRecordGqlFieldsFromRecord'; import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions'; +import { useRegisterObjectOperation } from '@/object-record/hooks/useRegisterObjectOperation'; +import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore'; import { type ObjectRecord } from '@/object-record/types/ObjectRecord'; import { computeOptimisticRecordFromInput } from '@/object-record/utils/computeOptimisticRecordFromInput'; import { getUpdateOneRecordMutationResponseField } from '@/object-record/utils/getUpdateOneRecordMutationResponseField'; @@ -29,6 +31,8 @@ type UpdateOneRecordArgs = { }; export const useUpdateOneRecordV2 = () => { const apolloCoreClient = useApolloCoreClient(); + const { upsertRecordsInStore } = useUpsertRecordsInStore(); + const { registerObjectOperation } = useRegisterObjectOperation(); const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); @@ -133,6 +137,8 @@ export const useUpdateOneRecordV2 = () => { currentRecord: cachedRecordWithConnection, updatedRecord: optimisticRecordWithConnection, objectMetadataItems, + objectPermissionsByObjectMetadataId, + upsertRecordsInStore, }); } @@ -171,6 +177,8 @@ export const useUpdateOneRecordV2 = () => { currentRecord: computedOptimisticRecord, updatedRecord: record, objectMetadataItems, + objectPermissionsByObjectMetadataId, + upsertRecordsInStore, }); }, }) @@ -217,11 +225,17 @@ export const useUpdateOneRecordV2 = () => { currentRecord: optimisticRecordWithConnection, updatedRecord: cachedRecordWithConnection, objectMetadataItems, + upsertRecordsInStore, + objectPermissionsByObjectMetadataId, }); throw error; }); + registerObjectOperation(objectMetadataItem.nameSingular, { + type: 'update-one', + result: { updatedRecord, updateInput: updateOneRecordInput }, + }); return updatedRecord?.data?.[mutationResponseField] ?? null; }; diff --git a/packages/twenty-front/src/modules/object-record/record-calendar/components/RecordIndexCalendarDataLoaderEffect.tsx b/packages/twenty-front/src/modules/object-record/record-calendar/components/RecordIndexCalendarDataLoaderEffect.tsx index 3e8ac986b9..9ec60dead7 100644 --- a/packages/twenty-front/src/modules/object-record/record-calendar/components/RecordIndexCalendarDataLoaderEffect.tsx +++ b/packages/twenty-front/src/modules/object-record/record-calendar/components/RecordIndexCalendarDataLoaderEffect.tsx @@ -26,7 +26,7 @@ export const RecordIndexCalendarDataLoaderEffect = () => { const { objectMetadataItem } = useRecordCalendarContextOrThrow(); - const { upsertRecords: upsertRecordsInStore } = useUpsertRecordsInStore(); + const { upsertRecordsInStore } = useUpsertRecordsInStore(); const setRecordIndexAllRecordIdsSelector = useSetRecoilComponentState( recordIndexAllRecordIdsComponentSelector, diff --git a/packages/twenty-front/src/modules/object-record/record-field-list/record-detail-section/relation/components/RecordDetailRelationRecordsListItemEffect.tsx b/packages/twenty-front/src/modules/object-record/record-field-list/record-detail-section/relation/components/RecordDetailRelationRecordsListItemEffect.tsx index af4d42fa6b..88358779f3 100644 --- a/packages/twenty-front/src/modules/object-record/record-field-list/record-detail-section/relation/components/RecordDetailRelationRecordsListItemEffect.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field-list/record-detail-section/relation/components/RecordDetailRelationRecordsListItemEffect.tsx @@ -24,13 +24,13 @@ export const RecordDetailRelationRecordsListItemEffect = ({ objectRecordId: relationRecordId, }); - const { upsertRecords } = useUpsertRecordsInStore(); + const { upsertRecordsInStore } = useUpsertRecordsInStore(); useEffect(() => { if (isDefined(record)) { - upsertRecords([record]); + upsertRecordsInStore([record]); } - }, [record, upsertRecords]); + }, [record, upsertRecordsInStore]); return null; }; diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/hooks/usePersistField.ts b/packages/twenty-front/src/modules/object-record/record-field/ui/hooks/usePersistField.ts index bd70155a49..7bb3dfbb41 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/hooks/usePersistField.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/hooks/usePersistField.ts @@ -30,6 +30,7 @@ import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById'; import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems'; import { getRecordFromCache } from '@/object-record/cache/utils/getRecordFromCache'; +import { getRecordFromRecordNode } from '@/object-record/cache/utils/getRecordFromRecordNode'; import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions'; import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord'; import { isFieldArray } from '@/object-record/record-field/ui/types/guards/isFieldArray'; @@ -42,6 +43,7 @@ import { isFieldRichText } from '@/object-record/record-field/ui/types/guards/is import { isFieldRichTextV2 } from '@/object-record/record-field/ui/types/guards/isFieldRichTextV2'; import { isFieldRichTextValue } from '@/object-record/record-field/ui/types/guards/isFieldRichTextValue'; import { isFieldRichTextV2Value } from '@/object-record/record-field/ui/types/guards/isFieldRichTextValueV2'; +import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore'; import { getForeignKeyNameFromRelationFieldName } from '@/object-record/utils/getForeignKeyNameFromRelationFieldName'; import { computeMorphRelationFieldName, isDefined } from 'twenty-shared/utils'; import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; @@ -72,11 +74,13 @@ export const usePersistField = ({ const { updateOneRecord } = useUpdateOneRecord({ objectNameSingular: objectMetadataItem?.nameSingular ?? '', }); + + const { upsertRecordsInStore } = useUpsertRecordsInStore(); const { objectPermissionsByObjectMetadataId } = useObjectPermissions(); const persistField = useRecoilCallback( ({ set, snapshot }) => - ({ + async ({ recordId, fieldDefinition, valueToPersist, @@ -202,7 +206,7 @@ export const usePersistField = ({ return; } - updateOneRecord?.({ + const newRecord = await updateOneRecord?.({ idToUpdate: recordId, updateOneRecordInput: { [getForeignKeyNameFromRelationFieldName(fieldName)]: @@ -210,10 +214,11 @@ export const usePersistField = ({ }, }); - set( - recordStoreFamilySelector({ recordId, fieldName }), - valueToPersist, - ); + upsertRecordsInStore([ + getRecordFromRecordNode({ + recordNode: newRecord, + }), + ]); return; } @@ -263,19 +268,17 @@ export const usePersistField = ({ candidateObjectMetadataItem.namePlural, }); - updateOneRecord?.({ + const newRecord = await updateOneRecord?.({ idToUpdate: recordId, updateOneRecordInput: { [`${computedFieldName}Id`]: valueToPersist.id, }, }); - set( - recordStoreFamilySelector({ - recordId, - fieldName: computedFieldName, + upsertRecordsInStore([ + getRecordFromRecordNode({ + recordNode: newRecord, }), - valueToPersist, - ); + ]); return; } } @@ -310,6 +313,7 @@ export const usePersistField = ({ objectMetadataItems, objectPermissionsByObjectMetadataId, updateOneRecord, + upsertRecordsInStore, ], ); diff --git a/packages/twenty-front/src/modules/object-record/record-index/hooks/useLoadRecordIndexBoardColumn.ts b/packages/twenty-front/src/modules/object-record/record-index/hooks/useLoadRecordIndexBoardColumn.ts index a8be60f881..5a20afa5da 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/hooks/useLoadRecordIndexBoardColumn.ts +++ b/packages/twenty-front/src/modules/object-record/record-index/hooks/useLoadRecordIndexBoardColumn.ts @@ -42,7 +42,7 @@ export const useLoadRecordIndexBoardColumn = ({ objectNameSingular, }); const { setRecordIdsForColumn } = useSetRecordIdsForColumn(recordBoardId); - const { upsertRecords: upsertRecordsInStore } = useUpsertRecordsInStore(); + const { upsertRecordsInStore } = useUpsertRecordsInStore(); const recordGroupDefinition = useRecoilValue( recordGroupDefinitionFamilyState(columnId), diff --git a/packages/twenty-front/src/modules/object-record/record-merge/hooks/useMergePreview.ts b/packages/twenty-front/src/modules/object-record/record-merge/hooks/useMergePreview.ts index 4aa7d69cad..357121a3a1 100644 --- a/packages/twenty-front/src/modules/object-record/record-merge/hooks/useMergePreview.ts +++ b/packages/twenty-front/src/modules/object-record/record-merge/hooks/useMergePreview.ts @@ -31,7 +31,7 @@ export const useMergePreview = ({ const { mergeManyRecords } = useMergeManyRecords({ objectNameSingular, }); - const { upsertRecords } = useUpsertRecordsInStore(); + const { upsertRecordsInStore } = useUpsertRecordsInStore(); const { isLoading: isLoadingRelationships } = useMergeRecordRelationships({ objectNameSingular, @@ -56,7 +56,7 @@ export const useMergePreview = ({ } setMergePreviewRecord(previewRecord); - upsertRecords([previewRecord]); + upsertRecordsInStore([previewRecord]); } catch { setMergePreviewRecord(null); } finally { diff --git a/packages/twenty-front/src/modules/object-record/record-store/hooks/useUpsertRecordsInStore.ts b/packages/twenty-front/src/modules/object-record/record-store/hooks/useUpsertRecordsInStore.ts index e85b933984..8a53f1dfb9 100644 --- a/packages/twenty-front/src/modules/object-record/record-store/hooks/useUpsertRecordsInStore.ts +++ b/packages/twenty-front/src/modules/object-record/record-store/hooks/useUpsertRecordsInStore.ts @@ -2,9 +2,10 @@ import { useRecoilCallback } from 'recoil'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; import { type ObjectRecord } from '@/object-record/types/ObjectRecord'; +import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; export const useUpsertRecordsInStore = () => { - const upsertRecords = useRecoilCallback( + const upsertRecordsInStore = useRecoilCallback( ({ set, snapshot }) => (records: ObjectRecord[]) => { for (const record of records) { @@ -12,7 +13,7 @@ export const useUpsertRecordsInStore = () => { .getLoadable(recordStoreFamilyState(record.id)) .getValue(); - if (JSON.stringify(currentRecord) !== JSON.stringify(record)) { + if (!isDeeplyEqual(currentRecord, record)) { set(recordStoreFamilyState(record.id), { ...currentRecord, ...record, @@ -24,6 +25,6 @@ export const useUpsertRecordsInStore = () => { ); return { - upsertRecords, + upsertRecordsInStore, }; }; diff --git a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableNoRecordGroupAddNew.tsx b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableNoRecordGroupAddNew.tsx index e61a75fbe0..10d72e612e 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableNoRecordGroupAddNew.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableNoRecordGroupAddNew.tsx @@ -1,9 +1,9 @@ import { useObjectPermissionsForObject } from '@/object-record/hooks/useObjectPermissionsForObject'; import { hasAnySoftDeleteFilterOnViewComponentSelector } from '@/object-record/record-filter/states/hasAnySoftDeleteFilterOnView'; +import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore'; import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext'; import { useCreateNewIndexRecord } from '@/object-record/record-table/hooks/useCreateNewIndexRecord'; import { RecordTableActionRow } from '@/object-record/record-table/record-table-row/components/RecordTableActionRow'; -import { useAssignRecordsToStore } from '@/object-record/record-table/virtualization/hooks/useAssignRecordsToStore'; import { useLoadRecordsToVirtualRows } from '@/object-record/record-table/virtualization/hooks/useLoadRecordsToVirtualRows'; import { totalNumberOfRecordsToVirtualizeComponentState } from '@/object-record/record-table/virtualization/states/totalNumberOfRecordsToVirtualizeComponentState'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; @@ -34,7 +34,7 @@ export const RecordTableNoRecordGroupAddNew = () => { ); const { loadRecordsToVirtualRows } = useLoadRecordsToVirtualRows(); - const { assignRecordsToStore } = useAssignRecordsToStore(); + const { upsertRecordsInStore } = useUpsertRecordsInStore(); const handleButtonClick = useRecoilCallback( () => async () => { @@ -42,7 +42,7 @@ export const RecordTableNoRecordGroupAddNew = () => { position: 'last', }); - assignRecordsToStore({ records: [createdRecord] }); + upsertRecordsInStore([createdRecord]); if (isDefined(totalNumberOfRecordsToVirtualize)) { loadRecordsToVirtualRows({ @@ -53,7 +53,7 @@ export const RecordTableNoRecordGroupAddNew = () => { }, [ createNewIndexRecord, - assignRecordsToStore, + upsertRecordsInStore, loadRecordsToVirtualRows, totalNumberOfRecordsToVirtualize, ], diff --git a/packages/twenty-front/src/modules/object-record/record-table/virtualization/hooks/useAssignRecordsToStore.ts b/packages/twenty-front/src/modules/object-record/record-table/virtualization/hooks/useAssignRecordsToStore.ts deleted file mode 100644 index c103f074b8..0000000000 --- a/packages/twenty-front/src/modules/object-record/record-table/virtualization/hooks/useAssignRecordsToStore.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { useRecoilCallback } from 'recoil'; - -import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; -import { type ObjectRecord } from '@/object-record/types/ObjectRecord'; -import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; - -export const useAssignRecordsToStore = () => { - const assignRecordsToStore = useRecoilCallback( - ({ set, snapshot }) => - ({ records }: { records: T[] }) => { - for (const record of records) { - const currentRecord = snapshot - .getLoadable(recordStoreFamilyState(record.id)) - .getValue(); - - if (!isDeeplyEqual(currentRecord, record)) { - const newRecord = { - ...currentRecord, - ...record, - }; - - set(recordStoreFamilyState(record.id), newRecord); - } - } - }, - [], - ); - - return { - assignRecordsToStore, - }; -}; diff --git a/packages/twenty-front/src/modules/object-record/record-table/virtualization/hooks/useTriggerFetchPages.ts b/packages/twenty-front/src/modules/object-record/record-table/virtualization/hooks/useTriggerFetchPages.ts index e8fb99535c..fd1f0d3da3 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/virtualization/hooks/useTriggerFetchPages.ts +++ b/packages/twenty-front/src/modules/object-record/record-table/virtualization/hooks/useTriggerFetchPages.ts @@ -1,7 +1,7 @@ import { useLazyFindManyRecordsWithOffset } from '@/object-record/hooks/useLazyFindManyRecordsWithOffset'; +import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore'; import { RECORD_TABLE_ROW_HEIGHT } from '@/object-record/record-table/constants/RecordTableRowHeight'; import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext'; -import { useAssignRecordsToStore } from '@/object-record/record-table/virtualization/hooks/useAssignRecordsToStore'; import { useLoadRecordsToVirtualRows } from '@/object-record/record-table/virtualization/hooks/useLoadRecordsToVirtualRows'; import { dataPagesLoadedComponentState } from '@/object-record/record-table/virtualization/states/dataPagesLoadedComponentState'; @@ -30,7 +30,7 @@ export const useTriggerFetchPages = () => { const { objectNameSingular } = useRecordTableContextOrThrow(); const { scrollWrapperHTMLElement } = useScrollWrapperHTMLElement(); - const { assignRecordsToStore } = useAssignRecordsToStore(); + const { upsertRecordsInStore } = useUpsertRecordsInStore(); const { loadRecordsToVirtualRows } = useLoadRecordsToVirtualRows(); @@ -193,7 +193,7 @@ export const useTriggerFetchPages = () => { startingRealIndex: startingRealIndexInThisUIPage, }); - assignRecordsToStore({ records: recordsSlice }); + upsertRecordsInStore(recordsSlice); } } @@ -210,7 +210,7 @@ export const useTriggerFetchPages = () => { findManyRecordsLazyWithOffset, totalNumberOfRecordsToVirtualizeCallbackState, loadRecordsToVirtualRows, - assignRecordsToStore, + upsertRecordsInStore, lastScrollPositionCallbackState, scrollWrapperHTMLElement, lowDetailsActivatedCallbackState, diff --git a/packages/twenty-front/src/modules/object-record/record-table/virtualization/hooks/useTriggerInitialRecordTableDataLoad.ts b/packages/twenty-front/src/modules/object-record/record-table/virtualization/hooks/useTriggerInitialRecordTableDataLoad.ts index 20fb392df5..f0e3d72d72 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/virtualization/hooks/useTriggerInitialRecordTableDataLoad.ts +++ b/packages/twenty-front/src/modules/object-record/record-table/virtualization/hooks/useTriggerInitialRecordTableDataLoad.ts @@ -1,5 +1,6 @@ import { useRecordIndexTableFetchMore } from '@/object-record/record-index/hooks/useRecordIndexTableFetchMore'; import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector'; +import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore'; import { RECORD_TABLE_HORIZONTAL_SCROLL_SHADOW_VISIBILITY_CSS_VARIABLE_NAME } from '@/object-record/record-table/constants/RecordTableHorizontalScrollShadowVisibilityCssVariableName'; import { RECORD_TABLE_VERTICAL_SCROLL_SHADOW_VISIBILITY_CSS_VARIABLE_NAME } from '@/object-record/record-table/constants/RecordTableVerticalScrollShadowVisibilityCssVariableName'; import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext'; @@ -8,7 +9,6 @@ import { isRecordTableInitialLoadingComponentState } from '@/object-record/recor import { isRecordTableScrolledHorizontallyComponentState } from '@/object-record/record-table/states/isRecordTableScrolledHorizontallyComponentState'; import { isRecordTableScrolledVerticallyComponentState } from '@/object-record/record-table/states/isRecordTableScrolledVerticallyComponentState'; import { updateRecordTableCSSVariable } from '@/object-record/record-table/utils/updateRecordTableCSSVariable'; -import { useAssignRecordsToStore } from '@/object-record/record-table/virtualization/hooks/useAssignRecordsToStore'; import { useLoadRecordsToVirtualRows } from '@/object-record/record-table/virtualization/hooks/useLoadRecordsToVirtualRows'; import { useReapplyRowSelection } from '@/object-record/record-table/virtualization/hooks/useReapplyRowSelection'; import { useResetNumberOfRecordsToVirtualize } from '@/object-record/record-table/virtualization/hooks/useResetNumberOfRecordsToVirtualize'; @@ -92,7 +92,7 @@ export const useTriggerInitialRecordTableDataLoad = () => { useResetNumberOfRecordsToVirtualize(); const { resetTableFocuses } = useResetTableFocuses(recordTableId); - const { assignRecordsToStore } = useAssignRecordsToStore(); + const { upsertRecordsInStore } = useUpsertRecordsInStore(); const { loadRecordsToVirtualRows } = useLoadRecordsToVirtualRows(); @@ -165,7 +165,7 @@ export const useTriggerInitialRecordTableDataLoad = () => { totalCount, }); - assignRecordsToStore({ records }); + upsertRecordsInStore(records); loadRecordsToVirtualRows({ records, @@ -210,7 +210,7 @@ export const useTriggerInitialRecordTableDataLoad = () => { dataLoadingStatusByRealIndexCallbackState, recordIdByRealIndexCallbackState, resetNumberOfRecordsToVirtualize, - assignRecordsToStore, + upsertRecordsInStore, loadRecordsToVirtualRows, reapplyRowSelection, ], 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 3bf593ba5f..4a53eb20d8 100644 --- a/packages/twenty-front/src/modules/object-record/utils/computeOptimisticRecordFromInput.ts +++ b/packages/twenty-front/src/modules/object-record/utils/computeOptimisticRecordFromInput.ts @@ -157,19 +157,6 @@ export const computeOptimisticRecordFromInput = ({ 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', - ); - } - if (isNull(recordInputFieldIdValue)) { optimisticRecord[relationFieldIdName] = null; optimisticRecord[fieldMetadataItem.name] = null; diff --git a/packages/twenty-front/src/modules/subscription/components/ListenRecordUpdatesEffect.tsx b/packages/twenty-front/src/modules/subscription/components/ListenRecordUpdatesEffect.tsx index 0a85f81ff6..5ae2c2d6ab 100644 --- a/packages/twenty-front/src/modules/subscription/components/ListenRecordUpdatesEffect.tsx +++ b/packages/twenty-front/src/modules/subscription/components/ListenRecordUpdatesEffect.tsx @@ -8,6 +8,7 @@ import { getRecordNodeFromRecord } from '@/object-record/cache/utils/getRecordNo import { updateRecordFromCache } from '@/object-record/cache/utils/updateRecordFromCache'; import { useGenerateDepthRecordGqlFieldsFromObject } from '@/object-record/graphql/record-gql-fields/hooks/useGenerateDepthRecordGqlFieldsFromObject'; import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions'; +import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; import { type ObjectRecord } from '@/object-record/types/ObjectRecord'; import { useOnDbEvent } from '@/subscription/hooks/useOnDbEvent'; @@ -49,6 +50,7 @@ export const ListenRecordUpdatesEffect = ({ }, [], ); + const { upsertRecordsInStore } = useUpsertRecordsInStore(); useOnDbEvent({ input: { recordId, action: DatabaseEventAction.UPDATED }, @@ -89,6 +91,8 @@ export const ListenRecordUpdatesEffect = ({ currentRecord: cachedRecordNode, updatedRecord: updatedRecord, objectMetadataItems, + objectPermissionsByObjectMetadataId, + upsertRecordsInStore, }); setRecordInStore(computedOptimisticRecord); diff --git a/packages/twenty-front/src/modules/views/hooks/internal/usePersistViewFilterGroupRecords.ts b/packages/twenty-front/src/modules/views/hooks/internal/usePersistViewFilterGroupRecords.ts index 9c00814985..c6e3bad900 100644 --- a/packages/twenty-front/src/modules/views/hooks/internal/usePersistViewFilterGroupRecords.ts +++ b/packages/twenty-front/src/modules/views/hooks/internal/usePersistViewFilterGroupRecords.ts @@ -1,34 +1,15 @@ import { useCallback } from 'react'; -import { triggerCreateRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerCreateRecordsOptimisticEffect'; -import { triggerDestroyRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDestroyRecordsOptimisticEffect'; -import { triggerUpdateRecordOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerUpdateRecordOptimisticEffect'; -import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; -import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems'; -import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; -import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordFromCache'; -import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions'; import { CREATE_CORE_VIEW_FILTER_GROUP } from '@/views/graphql/mutations/createCoreViewFilterGroup'; import { DESTROY_CORE_VIEW_FILTER_GROUP } from '@/views/graphql/mutations/destroyCoreViewFilterGroup'; import { UPDATE_CORE_VIEW_FILTER_GROUP } from '@/views/graphql/mutations/updateCoreViewFilterGroup'; import { type GraphQLView } from '@/views/types/GraphQLView'; import { type ViewFilterGroup } from '@/views/types/ViewFilterGroup'; import { useApolloClient } from '@apollo/client'; -import { isNull } from '@sniptt/guards'; import { isDefined } from 'twenty-shared/utils'; import { type CoreViewFilterGroup } from '~/generated/graphql'; export const usePersistViewFilterGroupRecords = () => { - const { objectMetadataItem } = useObjectMetadataItem({ - objectNameSingular: CoreObjectNameSingular.ViewFilterGroup, - }); - - const getRecordFromCache = useGetRecordFromCache({ - objectNameSingular: CoreObjectNameSingular.ViewFilterGroup, - }); - - const { objectMetadataItems } = useObjectMetadataItems(); - const { objectPermissionsByObjectMetadataId } = useObjectPermissions(); const apolloClient = useApolloClient(); const createCoreViewFilterGroupRecord = useCallback( @@ -47,18 +28,6 @@ export const usePersistViewFilterGroupRecords = () => { viewFilterGroup.positionInViewFilterGroup, } satisfies Partial, }, - update: (cache, { data }) => { - const record = data?.createCoreViewFilterGroup; - if (!isDefined(record)) return; - - triggerCreateRecordsOptimisticEffect({ - cache, - objectMetadataItem, - recordsToCreate: [record], - objectMetadataItems, - objectPermissionsByObjectMetadataId, - }); - }, }); if (!result.data) { @@ -67,12 +36,7 @@ export const usePersistViewFilterGroupRecords = () => { return { newRecordId: result.data.createCoreViewFilterGroup.id }; }, - [ - apolloClient, - objectMetadataItem, - objectMetadataItems, - objectPermissionsByObjectMetadataId, - ], + [apolloClient], ); const createCoreViewFilterGroupRecords = useCallback( @@ -133,29 +97,11 @@ export const usePersistViewFilterGroupRecords = () => { viewFilterGroup.positionInViewFilterGroup, } satisfies Partial, }, - update: (cache, { data }) => { - const record = data?.updateCoreViewFilterGroup; - if (!isDefined(record)) return; - - const cachedRecord = getRecordFromCache( - record.id, - cache, - ); - if (isNull(cachedRecord)) return; - - triggerUpdateRecordOptimisticEffect({ - cache, - objectMetadataItem, - currentRecord: cachedRecord, - updatedRecord: record, - objectMetadataItems, - }); - }, }), ), ); }, - [apolloClient, getRecordFromCache, objectMetadataItem, objectMetadataItems], + [apolloClient], ); const deleteCoreViewFilterGroupRecords = useCallback( @@ -168,25 +114,11 @@ export const usePersistViewFilterGroupRecords = () => { variables: { id: viewFilterGroupId, }, - update: (cache, { data }) => { - const record = data?.destroyCoreViewFilterGroup; - if (!isDefined(record)) return; - - const cachedRecord = getRecordFromCache(record.id, cache); - if (isNull(cachedRecord)) return; - - triggerDestroyRecordsOptimisticEffect({ - cache, - objectMetadataItem, - recordsToDestroy: [cachedRecord], - objectMetadataItems, - }); - }, }), ), ); }, - [apolloClient, getRecordFromCache, objectMetadataItem, objectMetadataItems], + [apolloClient], ); return { diff --git a/packages/twenty-front/src/modules/workflow/graphql/mutations/createDraftFromWorkflowVersion.ts b/packages/twenty-front/src/modules/workflow/graphql/mutations/createDraftFromWorkflowVersion.ts index 3c64c0c490..4e0858ecbf 100644 --- a/packages/twenty-front/src/modules/workflow/graphql/mutations/createDraftFromWorkflowVersion.ts +++ b/packages/twenty-front/src/modules/workflow/graphql/mutations/createDraftFromWorkflowVersion.ts @@ -6,6 +6,12 @@ export const CREATE_DRAFT_FROM_WORKFLOW_VERSION = gql` ) { createDraftFromWorkflowVersion(input: $input) { id + name + status + trigger + steps + createdAt + updatedAt } } `; diff --git a/packages/twenty-front/src/modules/workflow/hooks/useActivateWorkflowVersion.ts b/packages/twenty-front/src/modules/workflow/hooks/useActivateWorkflowVersion.ts index 1d182c9886..c4ec090c9f 100644 --- a/packages/twenty-front/src/modules/workflow/hooks/useActivateWorkflowVersion.ts +++ b/packages/twenty-front/src/modules/workflow/hooks/useActivateWorkflowVersion.ts @@ -3,8 +3,11 @@ import { useMutation } from '@apollo/client'; import { triggerUpdateRecordOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerUpdateRecordOptimisticEffect'; import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; +import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { modifyRecordFromCache } from '@/object-record/cache/utils/modifyRecordFromCache'; +import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions'; +import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore'; import { ACTIVATE_WORKFLOW_VERSION } from '@/workflow/graphql/mutations/activateWorkflowVersion'; import { type WorkflowVersion } from '@/workflow/types/Workflow'; import { isDefined } from 'twenty-shared/utils'; @@ -21,11 +24,15 @@ export const useActivateWorkflowVersion = () => { >(ACTIVATE_WORKFLOW_VERSION, { client: apolloCoreClient, }); + const { upsertRecordsInStore } = useUpsertRecordsInStore(); const { objectMetadataItem: objectMetadataItemWorkflowVersion } = useObjectMetadataItem({ objectNameSingular: CoreObjectNameSingular.WorkflowVersion, }); + const { objectMetadataItems } = useObjectMetadataItems(); + + const { objectPermissionsByObjectMetadataId } = useObjectPermissions(); const activateWorkflowVersion = async ({ workflowVersionId, @@ -75,7 +82,9 @@ export const useActivateWorkflowVersion = () => { ...newlyActiveWorkflowVersion, status: 'ACTIVE', }, - objectMetadataItems: [objectMetadataItemWorkflowVersion], + objectMetadataItems: objectMetadataItems, + objectPermissionsByObjectMetadataId, + upsertRecordsInStore, }); } @@ -100,7 +109,9 @@ export const useActivateWorkflowVersion = () => { ...workflowVersion, status: 'ARCHIVED', }, - objectMetadataItems: [objectMetadataItemWorkflowVersion], + objectMetadataItems, + objectPermissionsByObjectMetadataId, + upsertRecordsInStore, }); } }, diff --git a/packages/twenty-front/src/modules/workflow/hooks/useDeactivateWorkflowVersion.ts b/packages/twenty-front/src/modules/workflow/hooks/useDeactivateWorkflowVersion.ts index 14bd5147d2..a9034eb272 100644 --- a/packages/twenty-front/src/modules/workflow/hooks/useDeactivateWorkflowVersion.ts +++ b/packages/twenty-front/src/modules/workflow/hooks/useDeactivateWorkflowVersion.ts @@ -5,6 +5,8 @@ import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { modifyRecordFromCache } from '@/object-record/cache/utils/modifyRecordFromCache'; +import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions'; +import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore'; import { DEACTIVATE_WORKFLOW_VERSION } from '@/workflow/graphql/mutations/deactivateWorkflowVersion'; import { type WorkflowVersion } from '@/workflow/types/Workflow'; import { isDefined } from 'twenty-shared/utils'; @@ -12,9 +14,12 @@ import { type DeactivateWorkflowVersionMutation, type DeactivateWorkflowVersionMutationVariables, } from '~/generated-metadata/graphql'; +import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems'; export const useDeactivateWorkflowVersion = () => { const apolloCoreClient = useApolloCoreClient(); + const { objectPermissionsByObjectMetadataId } = useObjectPermissions(); + const { objectMetadataItems } = useObjectMetadataItems(); const [mutate] = useMutation< DeactivateWorkflowVersionMutation, DeactivateWorkflowVersionMutationVariables @@ -22,6 +27,8 @@ export const useDeactivateWorkflowVersion = () => { client: apolloCoreClient, }); + const { upsertRecordsInStore } = useUpsertRecordsInStore(); + const { objectMetadataItem: objectMetadataItemWorkflowVersion } = useObjectMetadataItem({ objectNameSingular: CoreObjectNameSingular.WorkflowVersion, @@ -67,7 +74,9 @@ export const useDeactivateWorkflowVersion = () => { ...workflowVersion, status: 'DEACTIVATED', }, - objectMetadataItems: [objectMetadataItemWorkflowVersion], + objectMetadataItems, + objectPermissionsByObjectMetadataId, + upsertRecordsInStore, }); }, }); diff --git a/packages/twenty-front/src/modules/workflow/hooks/useRunWorkflowVersion.tsx b/packages/twenty-front/src/modules/workflow/hooks/useRunWorkflowVersion.tsx index 4aa343493f..babb35fe82 100644 --- a/packages/twenty-front/src/modules/workflow/hooks/useRunWorkflowVersion.tsx +++ b/packages/twenty-front/src/modules/workflow/hooks/useRunWorkflowVersion.tsx @@ -11,6 +11,7 @@ import { getObjectTypename } from '@/object-record/cache/utils/getObjectTypename import { getRecordNodeFromRecord } from '@/object-record/cache/utils/getRecordNodeFromRecord'; import { useGenerateDepthRecordGqlFieldsFromObject } from '@/object-record/graphql/record-gql-fields/hooks/useGenerateDepthRecordGqlFieldsFromObject'; import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions'; +import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; import { computeOptimisticCreateRecordBaseRecordInput } from '@/object-record/utils/computeOptimisticCreateRecordBaseRecordInput'; import { computeOptimisticRecordFromInput } from '@/object-record/utils/computeOptimisticRecordFromInput'; @@ -27,6 +28,7 @@ import { export const useRunWorkflowVersion = () => { const apolloCoreClient = useApolloCoreClient(); + const { upsertRecordsInStore } = useUpsertRecordsInStore(); const { objectMetadataItem } = useObjectMetadataItem({ objectNameSingular: CoreObjectNameSingular.WorkflowRun, @@ -128,6 +130,7 @@ export const useRunWorkflowVersion = () => { objectMetadataItems, shouldMatchRootQueryFilter: true, objectPermissionsByObjectMetadataId, + upsertRecordsInStore, }); setRecordInStore(recordCreatedInCache); diff --git a/packages/twenty-front/src/modules/workflow/hooks/useWorkflowWithCurrentVersion.ts b/packages/twenty-front/src/modules/workflow/hooks/useWorkflowWithCurrentVersion.ts index aa04551289..3210666f84 100644 --- a/packages/twenty-front/src/modules/workflow/hooks/useWorkflowWithCurrentVersion.ts +++ b/packages/twenty-front/src/modules/workflow/hooks/useWorkflowWithCurrentVersion.ts @@ -2,16 +2,21 @@ import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSi import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord'; import { type Workflow, + type WorkflowVersion, type WorkflowWithCurrentVersion, } from '@/workflow/types/Workflow'; -import { useMemo } from 'react'; import { isDefined } from 'twenty-shared/utils'; +type WorkflowWithAllVersions = Omit & { + versions: Array< + Pick + >; +}; + export const useWorkflowWithCurrentVersion = ( workflowId: string | undefined, ): WorkflowWithCurrentVersion | undefined => { - // TODO: we should only load the data for the current version - const { record: workflow } = useFindOneRecord({ + const { record: workflow } = useFindOneRecord({ objectNameSingular: CoreObjectNameSingular.Workflow, objectRecordId: workflowId, recordGqlFields: { @@ -23,41 +28,38 @@ export const useWorkflowWithCurrentVersion = ( id: true, status: true, name: true, - steps: true, - trigger: true, - workflowId: true, - deletedAt: true, createdAt: true, - updatedAt: true, }, }, skip: !isDefined(workflowId), }); - return useMemo(() => { - if (!isDefined(workflow)) { - return undefined; - } + const draftVersion = workflow?.versions.find( + (workflowVersion) => workflowVersion.status === 'DRAFT', + ); - const draftVersion = workflow.versions.find( - (workflowVersion) => workflowVersion.status === 'DRAFT', - ); + const workflowVersions = [...(workflow?.versions ?? [])]; - const workflowVersions = [...workflow.versions]; + workflowVersions.sort((a, b) => (a.createdAt > b.createdAt ? -1 : 1)); - workflowVersions.sort((a, b) => (a.createdAt > b.createdAt ? -1 : 1)); + const latestVersion = workflowVersions[0]; - const latestVersion = workflowVersions[0]; + const currentVersionWithoutSteps = draftVersion ?? latestVersion; - const currentVersion = draftVersion ?? latestVersion; + const { record: currentVersionWithSteps } = useFindOneRecord( + { + objectNameSingular: CoreObjectNameSingular.WorkflowVersion, + objectRecordId: currentVersionWithoutSteps?.id, + skip: !isDefined(currentVersionWithoutSteps?.id), + }, + ); - if (!isDefined(currentVersion)) { - return undefined; - } + if (!isDefined(workflow) || !isDefined(currentVersionWithSteps)) { + return undefined; + } - return { - ...workflow, - currentVersion, - }; - }, [workflow]); + return { + ...workflow, + currentVersion: currentVersionWithSteps, + }; }; diff --git a/packages/twenty-front/src/modules/workflow/types/Workflow.ts b/packages/twenty-front/src/modules/workflow/types/Workflow.ts index 088a58d131..1d615254e3 100644 --- a/packages/twenty-front/src/modules/workflow/types/Workflow.ts +++ b/packages/twenty-front/src/modules/workflow/types/Workflow.ts @@ -131,7 +131,9 @@ export type Workflow = { __typename: 'Workflow'; id: string; name: string; - versions: Array; + versions: Array< + Pick + >; lastPublishedVersionId: string; statuses: Array | null; }; diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramEffect.tsx b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramEffect.tsx index ba63353ca0..1e868cb504 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramEffect.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramEffect.tsx @@ -38,6 +38,8 @@ export const WorkflowDiagramEffect = () => { workflowLastCreatedStepIdComponentState, ); + const currentVersion = workflowWithCurrentVersion?.currentVersion; + const computeAndMergeNewWorkflowDiagram = useRecoilCallback( ({ snapshot, set }) => { return (currentVersion: WorkflowVersion) => { @@ -84,13 +86,10 @@ export const WorkflowDiagramEffect = () => { [workflowDiagramState, workflowLastCreatedStepIdState], ); - const currentVersion = workflowWithCurrentVersion?.currentVersion; - useEffect(() => { if (!isDefined(currentVersion)) { - setFlow(undefined); - setWorkflowDiagram(undefined); - + // setFlow(undefined); + // setWorkflowDiagram(undefined); return; } diff --git a/packages/twenty-front/src/testing/decorators/RecordStoreDecorator.tsx b/packages/twenty-front/src/testing/decorators/RecordStoreDecorator.tsx index 1b56643ddc..39be92a6b4 100644 --- a/packages/twenty-front/src/testing/decorators/RecordStoreDecorator.tsx +++ b/packages/twenty-front/src/testing/decorators/RecordStoreDecorator.tsx @@ -1,15 +1,15 @@ -import { useEffect } from 'react'; import { type Decorator } from '@storybook/react'; +import { useEffect } from 'react'; import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore'; export const RecordStoreDecorator: Decorator = (Story, context) => { const { records } = context.parameters; - const { upsertRecords } = useUpsertRecordsInStore(); + const { upsertRecordsInStore } = useUpsertRecordsInStore(); useEffect(() => { - upsertRecords(records); + upsertRecordsInStore(records); }); return ; diff --git a/packages/twenty-front/src/testing/jest/JestRecordStoreSetter.tsx b/packages/twenty-front/src/testing/jest/JestRecordStoreSetter.tsx index f781c32fa5..076582405d 100644 --- a/packages/twenty-front/src/testing/jest/JestRecordStoreSetter.tsx +++ b/packages/twenty-front/src/testing/jest/JestRecordStoreSetter.tsx @@ -10,10 +10,10 @@ export const JestRecordStoreSetter = ({ children: ReactNode; records: ObjectRecord[]; }) => { - const { upsertRecords } = useUpsertRecordsInStore(); + const { upsertRecordsInStore } = useUpsertRecordsInStore(); useEffect(() => { - upsertRecords(records); + upsertRecordsInStore(records); }); return <>{children}; diff --git a/packages/twenty-server/src/engine/core-modules/workflow/dtos/workflow-version.dto.ts b/packages/twenty-server/src/engine/core-modules/workflow/dtos/workflow-version.dto.ts index 7442b6e735..6fa822248e 100644 --- a/packages/twenty-server/src/engine/core-modules/workflow/dtos/workflow-version.dto.ts +++ b/packages/twenty-server/src/engine/core-modules/workflow/dtos/workflow-version.dto.ts @@ -1,9 +1,35 @@ import { Field, ObjectType } from '@nestjs/graphql'; -import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars'; +import GraphQLJSON from 'graphql-type-json'; -@ObjectType('WorkflowVersion') +import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars'; +import { WorkflowVersionStatus } from 'src/modules/workflow/common/standard-objects/workflow-version.workspace-entity'; +import { WorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type'; +import { WorkflowTrigger } from 'src/modules/workflow/workflow-trigger/types/workflow-trigger.type'; + +@ObjectType('WorkflowVersionDTO') export class WorkflowVersionDTO { @Field(() => UUIDScalarType) id: string; + + @Field(() => String) + name: string; + + @Field(() => String) + createdAt: string; + + @Field(() => String) + updatedAt: string; + + @Field(() => UUIDScalarType) + workflowId: string; + + @Field(() => String) + status: WorkflowVersionStatus; + + @Field(() => GraphQLJSON, { nullable: true }) + trigger: WorkflowTrigger | null; + + @Field(() => GraphQLJSON, { nullable: true }) + steps: WorkflowAction[] | null; } diff --git a/packages/twenty-server/src/engine/core-modules/workflow/resolvers/workflow-version.resolver.ts b/packages/twenty-server/src/engine/core-modules/workflow/resolvers/workflow-version.resolver.ts index 4240d732b9..7673b1af7c 100644 --- a/packages/twenty-server/src/engine/core-modules/workflow/resolvers/workflow-version.resolver.ts +++ b/packages/twenty-server/src/engine/core-modules/workflow/resolvers/workflow-version.resolver.ts @@ -4,6 +4,7 @@ import { Args, Mutation, Resolver } from '@nestjs/graphql'; import { PreventNestToAutoLogGraphqlErrorsFilter } from 'src/engine/core-modules/graphql/filters/prevent-nest-to-auto-log-graphql-errors.filter'; import { ResolverValidationPipe } from 'src/engine/core-modules/graphql/pipes/resolver-validation.pipe'; import { CreateDraftFromWorkflowVersionInput } from 'src/engine/core-modules/workflow/dtos/create-draft-from-workflow-version-input.dto'; +import { UpdateWorkflowVersionPositionsInput } from 'src/engine/core-modules/workflow/dtos/update-workflow-version-positions-input.dto'; import { WorkflowVersionDTO } from 'src/engine/core-modules/workflow/dtos/workflow-version.dto'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator'; @@ -13,7 +14,6 @@ import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard'; import { PermissionFlagType } from 'src/engine/metadata-modules/permissions/constants/permission-flag-type.constants'; import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter'; import { WorkflowVersionWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-version/workflow-version.workspace-service'; -import { UpdateWorkflowVersionPositionsInput } from 'src/engine/core-modules/workflow/dtos/update-workflow-version-positions-input.dto'; @Resolver() @UsePipes(ResolverValidationPipe) @@ -40,15 +40,11 @@ export class WorkflowVersionResolver { workflowVersionIdToCopy, }: CreateDraftFromWorkflowVersionInput, ): Promise { - return { - id: await this.workflowVersionWorkspaceService.createDraftFromWorkflowVersion( - { - workspaceId, - workflowId, - workflowVersionIdToCopy, - }, - ), - }; + return this.workflowVersionWorkspaceService.createDraftFromWorkflowVersion({ + workspaceId, + workflowId, + workflowVersionIdToCopy, + }); } @Mutation(() => Boolean) diff --git a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version/workflow-version.workspace-service.ts b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version/workflow-version.workspace-service.ts index d0522128ff..3025dfcd7c 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version/workflow-version.workspace-service.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version/workflow-version.workspace-service.ts @@ -115,7 +115,11 @@ export class WorkflowVersionWorkspaceService { trigger: newWorkflowVersionTrigger, }); - return draftWorkflowVersion.id; + return { + ...draftWorkflowVersion, + steps: newWorkflowVersionSteps, + trigger: newWorkflowVersionTrigger, + }; } async updateWorkflowVersionPositions({