From b5736a28fddf9d2e90720fd55bf1c46ac2a3e11b Mon Sep 17 00:00:00 2001 From: Thomas Trompette Date: Mon, 10 Feb 2025 17:46:36 +0100 Subject: [PATCH] Fix workflow activation optimistic rendering in Cmd+K (#10109) Close https://discord.com/channels/1130383047699738754/1334441759484149793 Using refetch queries was not working for certain use cases. To find manual active workflows in cmd+k, we use a query with specific filters that was complicated to refetch. Finally I will update the cache manually. It was not properly updated before because the json value of the version trigger was stringified without spaces. So the entity was not found in apollo cache. --- ...activateWorkflowSingleRecordAction.test.ts | 1 - ...useDeactivateWorkflowSingleRecordAction.ts | 1 - .../hooks/useWorkflowRunRecordActions.tsx | 10 +-- .../hooks/useRunWorkflowActions.tsx | 7 +- .../utils/isMatchingRawJsonFilter.ts | 4 +- .../RecordShowPageWorkflowHeader.tsx | 1 - .../RecordShowPageWorkflowVersionHeader.tsx | 1 - .../hooks/useActivateWorkflowVersion.ts | 82 ++++++++++++++++--- ...ctiveWorkflowVersionsWithManualTrigger.ts} | 15 ++-- .../hooks/useDeactivateWorkflowVersion.ts | 52 +++++++++--- 10 files changed, 127 insertions(+), 47 deletions(-) rename packages/twenty-front/src/modules/workflow/hooks/{useAllActiveWorkflowVersions.ts => useActiveWorkflowVersionsWithManualTrigger.ts} (85%) diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/__tests__/useDeactivateWorkflowSingleRecordAction.test.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/__tests__/useDeactivateWorkflowSingleRecordAction.test.ts index a8323cefc84..bc96d3faa8f 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/__tests__/useDeactivateWorkflowSingleRecordAction.test.ts +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/__tests__/useDeactivateWorkflowSingleRecordAction.test.ts @@ -162,7 +162,6 @@ describe('useDeactivateWorkflowSingleRecordAction', () => { expect(deactivateWorkflowVersionMock).toHaveBeenCalledWith({ workflowVersionId: activeWorkflowMock.currentVersion.id, - workflowId: activeWorkflowMock.id, }); }); }); diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useDeactivateWorkflowSingleRecordAction.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useDeactivateWorkflowSingleRecordAction.ts index b6709245d35..0fae1e97a9c 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useDeactivateWorkflowSingleRecordAction.ts +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useDeactivateWorkflowSingleRecordAction.ts @@ -23,7 +23,6 @@ export const useDeactivateWorkflowSingleRecordAction: ActionHookWithoutObjectMet deactivateWorkflowVersion({ workflowVersionId: workflowWithCurrentVersion.currentVersion.id, - workflowId: workflowWithCurrentVersion.id, }); }; diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/workflow-run-record-actions/hooks/useWorkflowRunRecordActions.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/workflow-run-record-actions/hooks/useWorkflowRunRecordActions.tsx index 7740a7665e9..d5b468f9e10 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/workflow-run-record-actions/hooks/useWorkflowRunRecordActions.tsx +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/workflow-run-record-actions/hooks/useWorkflowRunRecordActions.tsx @@ -7,7 +7,7 @@ import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/s import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; -import { useAllActiveWorkflowVersions } from '@/workflow/hooks/useAllActiveWorkflowVersions'; +import { useActiveWorkflowVersionsWithManualTrigger } from '@/workflow/hooks/useActiveWorkflowVersionsWithManualTrigger'; import { useRunWorkflowVersion } from '@/workflow/hooks/useRunWorkflowVersion'; import { msg } from '@lingui/core/macro'; @@ -38,10 +38,10 @@ export const useWorkflowRunRecordActions = ({ recordStoreFamilyState(selectedRecordId), ); - const { records: activeWorkflowVersions } = useAllActiveWorkflowVersions({ - objectMetadataItem, - triggerType: 'MANUAL', - }); + const { records: activeWorkflowVersions } = + useActiveWorkflowVersionsWithManualTrigger({ + objectMetadataItem, + }); const { runWorkflowVersion } = useRunWorkflowVersion(); diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-agnostic-actions/run-workflow-actions/hooks/useRunWorkflowActions.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-agnostic-actions/run-workflow-actions/hooks/useRunWorkflowActions.tsx index 4e0696a0917..30f85c765db 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-agnostic-actions/run-workflow-actions/hooks/useRunWorkflowActions.tsx +++ b/packages/twenty-front/src/modules/action-menu/actions/record-agnostic-actions/run-workflow-actions/hooks/useRunWorkflowActions.tsx @@ -2,7 +2,7 @@ import { ActionMenuEntryScope, ActionMenuEntryType, } from '@/action-menu/types/ActionMenuEntry'; -import { useAllActiveWorkflowVersions } from '@/workflow/hooks/useAllActiveWorkflowVersions'; +import { useActiveWorkflowVersionsWithManualTrigger } from '@/workflow/hooks/useActiveWorkflowVersionsWithManualTrigger'; import { useRunWorkflowVersion } from '@/workflow/hooks/useRunWorkflowVersion'; import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { msg } from '@lingui/core/macro'; @@ -16,9 +16,8 @@ export const useRunWorkflowActions = () => { FeatureFlagKey.IsWorkflowEnabled, ); - const { records: activeWorkflowVersions } = useAllActiveWorkflowVersions({ - triggerType: 'MANUAL', - }); + const { records: activeWorkflowVersions } = + useActiveWorkflowVersionsWithManualTrigger({}); const { runWorkflowVersion } = useRunWorkflowVersion(); diff --git a/packages/twenty-front/src/modules/object-record/record-filter/utils/isMatchingRawJsonFilter.ts b/packages/twenty-front/src/modules/object-record/record-filter/utils/isMatchingRawJsonFilter.ts index 8251bca722a..8f897f59a68 100644 --- a/packages/twenty-front/src/modules/object-record/record-filter/utils/isMatchingRawJsonFilter.ts +++ b/packages/twenty-front/src/modules/object-record/record-filter/utils/isMatchingRawJsonFilter.ts @@ -10,9 +10,9 @@ export const isMatchingRawJsonFilter = ({ switch (true) { case rawJsonFilter.like !== undefined: { const regexPattern = rawJsonFilter.like.replace(/%/g, '.*'); - const regexCaseInsensitive = new RegExp(`^${regexPattern}$`, 'i'); + const regexCaseInsensitive = new RegExp(`^${regexPattern}$`, 'is'); - const stringValue = JSON.stringify(value); + const stringValue = JSON.stringify(value, null, 1); return regexCaseInsensitive.test(stringValue); } diff --git a/packages/twenty-front/src/modules/workflow/components/RecordShowPageWorkflowHeader.tsx b/packages/twenty-front/src/modules/workflow/components/RecordShowPageWorkflowHeader.tsx index fb10a84bf60..4e2be00705c 100644 --- a/packages/twenty-front/src/modules/workflow/components/RecordShowPageWorkflowHeader.tsx +++ b/packages/twenty-front/src/modules/workflow/components/RecordShowPageWorkflowHeader.tsx @@ -124,7 +124,6 @@ export const RecordShowPageWorkflowHeader = ({ return deactivateWorkflowVersion({ workflowVersionId: workflowWithCurrentVersion.currentVersion.id, - workflowId: workflowWithCurrentVersion.id, }); }} /> diff --git a/packages/twenty-front/src/modules/workflow/components/RecordShowPageWorkflowVersionHeader.tsx b/packages/twenty-front/src/modules/workflow/components/RecordShowPageWorkflowVersionHeader.tsx index 8b4771f9401..f4b9e74ac88 100644 --- a/packages/twenty-front/src/modules/workflow/components/RecordShowPageWorkflowVersionHeader.tsx +++ b/packages/twenty-front/src/modules/workflow/components/RecordShowPageWorkflowVersionHeader.tsx @@ -128,7 +128,6 @@ export const RecordShowPageWorkflowVersionHeader = ({ onClick={() => { return deactivateWorkflowVersion({ workflowVersionId: workflowVersion.id, - workflowId: workflowVersion.workflowId, }); }} /> diff --git a/packages/twenty-front/src/modules/workflow/hooks/useActivateWorkflowVersion.ts b/packages/twenty-front/src/modules/workflow/hooks/useActivateWorkflowVersion.ts index 70bca3a7c8f..e3bd3867bdf 100644 --- a/packages/twenty-front/src/modules/workflow/hooks/useActivateWorkflowVersion.ts +++ b/packages/twenty-front/src/modules/workflow/hooks/useActivateWorkflowVersion.ts @@ -1,8 +1,12 @@ import { useApolloClient, useMutation } from '@apollo/client'; +import { triggerUpdateRecordOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerUpdateRecordOptimisticEffect'; +import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; -import { useFindManyRecordsQuery } from '@/object-record/hooks/useFindManyRecordsQuery'; +import { modifyRecordFromCache } from '@/object-record/cache/utils/modifyRecordFromCache'; import { ACTIVATE_WORKFLOW_VERSION } from '@/workflow/graphql/mutations/activateWorkflowVersion'; +import { WorkflowVersion } from '@/workflow/types/Workflow'; +import { isDefined } from 'twenty-shared'; import { ActivateWorkflowVersionMutation, ActivateWorkflowVersionMutationVariables, @@ -17,8 +21,8 @@ export const useActivateWorkflowVersion = () => { client: apolloClient, }); - const { findManyRecordsQuery: findManyWorkflowVersionsQuery } = - useFindManyRecordsQuery({ + const { objectMetadataItem: objectMetadataItemWorkflowVersion } = + useObjectMetadataItem({ objectNameSingular: CoreObjectNameSingular.WorkflowVersion, }); @@ -33,14 +37,72 @@ export const useActivateWorkflowVersion = () => { variables: { workflowVersionId, }, - refetchQueries: [ - { - query: findManyWorkflowVersionsQuery, - variables: { - workflowId, + update: () => { + modifyRecordFromCache({ + cache: apolloClient.cache, + recordId: workflowVersionId, + objectMetadataItem: objectMetadataItemWorkflowVersion, + fieldModifiers: { + status: () => 'ACTIVE', }, - }, - ], + }); + + const cacheSnapshot = apolloClient.cache.extract(); + const allWorkflowVersions: Array = Object.values( + cacheSnapshot, + ).filter( + (item) => + item.__typename === 'WorkflowVersion' && + item.workflowId === workflowId, + ); + + const previousActiveWorkflowVersions = allWorkflowVersions.filter( + (version) => + version.status === 'ACTIVE' && version.id !== workflowVersionId, + ); + + const newlyActiveWorkflowVersion = allWorkflowVersions.find( + (version) => version.id === workflowVersionId, + ); + + if (isDefined(newlyActiveWorkflowVersion)) { + triggerUpdateRecordOptimisticEffect({ + cache: apolloClient.cache, + objectMetadataItem: objectMetadataItemWorkflowVersion, + currentRecord: newlyActiveWorkflowVersion, + updatedRecord: { + ...newlyActiveWorkflowVersion, + status: 'ACTIVE', + }, + objectMetadataItems: [objectMetadataItemWorkflowVersion], + }); + } + + for (const workflowVersion of previousActiveWorkflowVersions) { + apolloClient.cache.modify({ + id: apolloClient.cache.identify(workflowVersion), + fields: { + status: () => { + return workflowVersion.id !== workflowVersionId && + workflowVersion.status === 'ACTIVE' + ? 'ARCHIVED' + : workflowVersion.status; + }, + }, + }); + + triggerUpdateRecordOptimisticEffect({ + cache: apolloClient.cache, + objectMetadataItem: objectMetadataItemWorkflowVersion, + currentRecord: workflowVersion, + updatedRecord: { + ...workflowVersion, + status: 'ARCHIVED', + }, + objectMetadataItems: [objectMetadataItemWorkflowVersion], + }); + } + }, }); }; diff --git a/packages/twenty-front/src/modules/workflow/hooks/useAllActiveWorkflowVersions.ts b/packages/twenty-front/src/modules/workflow/hooks/useActiveWorkflowVersionsWithManualTrigger.ts similarity index 85% rename from packages/twenty-front/src/modules/workflow/hooks/useAllActiveWorkflowVersions.ts rename to packages/twenty-front/src/modules/workflow/hooks/useActiveWorkflowVersionsWithManualTrigger.ts index b3d6586c5d6..a5f57488587 100644 --- a/packages/twenty-front/src/modules/workflow/hooks/useAllActiveWorkflowVersions.ts +++ b/packages/twenty-front/src/modules/workflow/hooks/useActiveWorkflowVersionsWithManualTrigger.ts @@ -3,19 +3,13 @@ import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSi import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields'; import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords'; -import { - Workflow, - WorkflowTriggerType, - WorkflowVersion, -} from '@/workflow/types/Workflow'; +import { Workflow, WorkflowVersion } from '@/workflow/types/Workflow'; import { isDefined } from 'twenty-shared'; -export const useAllActiveWorkflowVersions = ({ +export const useActiveWorkflowVersionsWithManualTrigger = ({ objectMetadataItem, - triggerType, }: { objectMetadataItem?: ObjectMetadataItem; - triggerType: WorkflowTriggerType; }) => { const filters = [ { @@ -25,7 +19,7 @@ export const useAllActiveWorkflowVersions = ({ }, { trigger: { - like: `%"type": "${triggerType}"%`, + like: `%"type": "MANUAL"%`, }, }, ]; @@ -63,7 +57,8 @@ export const useAllActiveWorkflowVersions = ({ return { records: records.filter( (record) => - record.trigger?.type !== 'CRON' && + record.status === 'ACTIVE' && + record.trigger?.type === 'MANUAL' && !isDefined(record.trigger?.settings.objectType), ), }; diff --git a/packages/twenty-front/src/modules/workflow/hooks/useDeactivateWorkflowVersion.ts b/packages/twenty-front/src/modules/workflow/hooks/useDeactivateWorkflowVersion.ts index 41957884941..7deac76b371 100644 --- a/packages/twenty-front/src/modules/workflow/hooks/useDeactivateWorkflowVersion.ts +++ b/packages/twenty-front/src/modules/workflow/hooks/useDeactivateWorkflowVersion.ts @@ -1,8 +1,12 @@ import { useApolloClient, useMutation } from '@apollo/client'; +import { triggerUpdateRecordOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerUpdateRecordOptimisticEffect'; +import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; -import { useFindManyRecordsQuery } from '@/object-record/hooks/useFindManyRecordsQuery'; +import { modifyRecordFromCache } from '@/object-record/cache/utils/modifyRecordFromCache'; import { DEACTIVATE_WORKFLOW_VERSION } from '@/workflow/graphql/mutations/deactivateWorkflowVersion'; +import { WorkflowVersion } from '@/workflow/types/Workflow'; +import { isDefined } from 'twenty-shared'; import { DeactivateWorkflowVersionMutation, DeactivateWorkflowVersionMutationVariables, @@ -17,30 +21,54 @@ export const useDeactivateWorkflowVersion = () => { client: apolloClient, }); - const { findManyRecordsQuery: findManyWorkflowVersionsQuery } = - useFindManyRecordsQuery({ + const { objectMetadataItem: objectMetadataItemWorkflowVersion } = + useObjectMetadataItem({ objectNameSingular: CoreObjectNameSingular.WorkflowVersion, }); const deactivateWorkflowVersion = async ({ workflowVersionId, - workflowId, }: { workflowVersionId: string; - workflowId: string; }) => { await mutate({ variables: { workflowVersionId, }, - refetchQueries: [ - { - query: findManyWorkflowVersionsQuery, - variables: { - workflowId, + update: () => { + modifyRecordFromCache({ + cache: apolloClient.cache, + recordId: workflowVersionId, + objectMetadataItem: objectMetadataItemWorkflowVersion, + fieldModifiers: { + status: () => 'DEACTIVATED', }, - }, - ], + }); + + const cacheSnapshot = apolloClient.cache.extract(); + const workflowVersion: WorkflowVersion | undefined = Object.values( + cacheSnapshot, + ).find( + (item) => + item.__typename === 'WorkflowVersion' && + item.id === workflowVersionId, + ); + + if (!isDefined(workflowVersion)) { + return; + } + + triggerUpdateRecordOptimisticEffect({ + cache: apolloClient.cache, + objectMetadataItem: objectMetadataItemWorkflowVersion, + currentRecord: workflowVersion, + updatedRecord: { + ...workflowVersion, + status: 'DEACTIVATED', + }, + objectMetadataItems: [objectMetadataItemWorkflowVersion], + }); + }, }); };