From 954da6da9d36bc560ee59be87a8c23aed9f7f6f8 Mon Sep 17 00:00:00 2001 From: Thomas Trompette Date: Tue, 12 Aug 2025 12:30:08 +0200 Subject: [PATCH] Delete filter on branch deletion (#13834) When the target is not in the direct next step ids of the source (step or trigger), check if there is a filter between both. If yes, delete the filter and remove it from source next step ids. Also refactored the existing. --- .../view-run/utils/getIsInputTabDisabled.ts | 2 +- ...ndMenuWorkflowSelectTriggerTypeContent.tsx | 2 +- .../workflow/hooks/useStepsOutputSchema.ts | 2 +- .../utils/__tests__/findStepPosition.test.ts | 2 +- .../getStepDefinitionOrThrow.test.ts | 2 +- .../workflow/utils/findStepPosition.ts | 2 +- .../utils/getStepDefinitionOrThrow.ts | 2 +- .../WorkflowDiagramEmptyTriggerReadonly.tsx | 2 +- ...rkflowDiagramEmptyTriggerNodeDefinition.ts | 2 +- .../utils/generateWorkflowDiagram.ts | 2 +- .../utils/getWorkflowDiagramTriggerNode.ts | 2 +- .../getWorkflowPreviousStepId.test.ts | 2 +- .../getWorkflowRunStepContext.test.ts | 3 +- .../utils/getWorkflowPreviousStepId.ts | 2 +- .../utils/getWorkflowRunStepContext.ts | 2 +- .../constants/TriggerStepId.ts | 1 - .../useAvailableVariablesInWorkflowStep.ts | 2 +- ...low-version-edge.workspace-service.spec.ts | 241 +++++++++- .../workflow-version-edge.module.ts | 2 + ...workflow-version-edge.workspace-service.ts | 441 +++++++++++++----- ...low-version-step.workspace-service.spec.ts | 10 +- .../utils/__tests__/insert-step.spec.ts | 4 +- .../utils/__tests__/remove-step.spec.ts | 6 +- .../utils/insert-step.ts | 4 +- .../utils/remove-step.ts | 5 +- ...workflow-version-step.workspace-service.ts | 4 +- .../workflow-version.workspace-service.ts | 5 +- .../src/workflow/constants/TriggerStepId.ts | 1 + packages/twenty-shared/src/workflow/index.ts | 1 + 29 files changed, 605 insertions(+), 153 deletions(-) delete mode 100644 packages/twenty-front/src/modules/workflow/workflow-trigger/constants/TriggerStepId.ts create mode 100644 packages/twenty-shared/src/workflow/constants/TriggerStepId.ts diff --git a/packages/twenty-front/src/modules/command-menu/pages/workflow/step/view-run/utils/getIsInputTabDisabled.ts b/packages/twenty-front/src/modules/command-menu/pages/workflow/step/view-run/utils/getIsInputTabDisabled.ts index 3df398bdec1..71d5472368e 100644 --- a/packages/twenty-front/src/modules/command-menu/pages/workflow/step/view-run/utils/getIsInputTabDisabled.ts +++ b/packages/twenty-front/src/modules/command-menu/pages/workflow/step/view-run/utils/getIsInputTabDisabled.ts @@ -1,5 +1,5 @@ -import { TRIGGER_STEP_ID } from '@/workflow/workflow-trigger/constants/TriggerStepId'; import { type WorkflowRunStepStatus } from '@/workflow/types/Workflow'; +import { TRIGGER_STEP_ID } from 'twenty-shared/workflow'; export const getIsInputTabDisabled = ({ stepExecutionStatus, diff --git a/packages/twenty-front/src/modules/command-menu/pages/workflow/trigger-type/components/CommandMenuWorkflowSelectTriggerTypeContent.tsx b/packages/twenty-front/src/modules/command-menu/pages/workflow/trigger-type/components/CommandMenuWorkflowSelectTriggerTypeContent.tsx index d4fcc5df34a..b55084b4ac3 100644 --- a/packages/twenty-front/src/modules/command-menu/pages/workflow/trigger-type/components/CommandMenuWorkflowSelectTriggerTypeContent.tsx +++ b/packages/twenty-front/src/modules/command-menu/pages/workflow/trigger-type/components/CommandMenuWorkflowSelectTriggerTypeContent.tsx @@ -10,10 +10,10 @@ import { RightDrawerStepListContainer } from '@/workflow/workflow-steps/componen import { RightDrawerWorkflowSelectStepTitle } from '@/workflow/workflow-steps/components/RightDrawerWorkflowSelectStepTitle'; import { DATABASE_TRIGGER_TYPES } from '@/workflow/workflow-trigger/constants/DatabaseTriggerTypes'; import { OTHER_TRIGGER_TYPES } from '@/workflow/workflow-trigger/constants/OtherTriggerTypes'; -import { TRIGGER_STEP_ID } from '@/workflow/workflow-trigger/constants/TriggerStepId'; import { useUpdateWorkflowVersionTrigger } from '@/workflow/workflow-trigger/hooks/useUpdateWorkflowVersionTrigger'; import { getTriggerDefaultDefinition } from '@/workflow/workflow-trigger/utils/getTriggerDefaultDefinition'; import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; +import { TRIGGER_STEP_ID } from 'twenty-shared/workflow'; import { useIcons } from 'twenty-ui/display'; import { MenuItemCommand } from 'twenty-ui/navigation'; import { FeatureFlagKey } from '~/generated/graphql'; diff --git a/packages/twenty-front/src/modules/workflow/hooks/useStepsOutputSchema.ts b/packages/twenty-front/src/modules/workflow/hooks/useStepsOutputSchema.ts index 2633c8bc7dc..c01de8dfe31 100644 --- a/packages/twenty-front/src/modules/workflow/hooks/useStepsOutputSchema.ts +++ b/packages/twenty-front/src/modules/workflow/hooks/useStepsOutputSchema.ts @@ -2,7 +2,6 @@ import { stepsOutputSchemaFamilyState } from '@/workflow/states/stepsOutputSchem import { type WorkflowVersion } from '@/workflow/types/Workflow'; import { getStepOutputSchemaFamilyStateKey } from '@/workflow/utils/getStepOutputSchemaFamilyStateKey'; import { getActionIcon } from '@/workflow/workflow-steps/workflow-actions/utils/getActionIcon'; -import { TRIGGER_STEP_ID } from '@/workflow/workflow-trigger/constants/TriggerStepId'; import { getTriggerDefaultLabel } from '@/workflow/workflow-trigger/utils/getTriggerDefaultLabel'; import { getTriggerIcon } from '@/workflow/workflow-trigger/utils/getTriggerIcon'; import { @@ -11,6 +10,7 @@ import { } from '@/workflow/workflow-variables/types/StepOutputSchema'; import { useRecoilCallback } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; +import { TRIGGER_STEP_ID } from 'twenty-shared/workflow'; export const useStepsOutputSchema = () => { const populateStepsOutputSchema = useRecoilCallback( diff --git a/packages/twenty-front/src/modules/workflow/utils/__tests__/findStepPosition.test.ts b/packages/twenty-front/src/modules/workflow/utils/__tests__/findStepPosition.test.ts index ded5940e5ec..082410f1271 100644 --- a/packages/twenty-front/src/modules/workflow/utils/__tests__/findStepPosition.test.ts +++ b/packages/twenty-front/src/modules/workflow/utils/__tests__/findStepPosition.test.ts @@ -1,5 +1,5 @@ import { type WorkflowStep } from '@/workflow/types/Workflow'; -import { TRIGGER_STEP_ID } from '@/workflow/workflow-trigger/constants/TriggerStepId'; +import { TRIGGER_STEP_ID } from 'twenty-shared/workflow'; import { findStepPosition } from '../findStepPosition'; describe('findStepPosition', () => { diff --git a/packages/twenty-front/src/modules/workflow/utils/__tests__/getStepDefinitionOrThrow.test.ts b/packages/twenty-front/src/modules/workflow/utils/__tests__/getStepDefinitionOrThrow.test.ts index 2034ea02edd..bfdfb5ddc6c 100644 --- a/packages/twenty-front/src/modules/workflow/utils/__tests__/getStepDefinitionOrThrow.test.ts +++ b/packages/twenty-front/src/modules/workflow/utils/__tests__/getStepDefinitionOrThrow.test.ts @@ -2,7 +2,7 @@ import { type WorkflowAction, type WorkflowTrigger, } from '@/workflow/types/Workflow'; -import { TRIGGER_STEP_ID } from '@/workflow/workflow-trigger/constants/TriggerStepId'; +import { TRIGGER_STEP_ID } from 'twenty-shared/workflow'; import { getStepDefinitionOrThrow } from '../getStepDefinitionOrThrow'; describe('getStepDefinitionOrThrow', () => { diff --git a/packages/twenty-front/src/modules/workflow/utils/findStepPosition.ts b/packages/twenty-front/src/modules/workflow/utils/findStepPosition.ts index 1f9baee748d..9507288b920 100644 --- a/packages/twenty-front/src/modules/workflow/utils/findStepPosition.ts +++ b/packages/twenty-front/src/modules/workflow/utils/findStepPosition.ts @@ -1,6 +1,6 @@ import { type WorkflowStep } from '@/workflow/types/Workflow'; -import { TRIGGER_STEP_ID } from '@/workflow/workflow-trigger/constants/TriggerStepId'; import { isDefined } from 'twenty-shared/utils'; +import { TRIGGER_STEP_ID } from 'twenty-shared/workflow'; /** * This function returns the reference of the array where the step should be positioned diff --git a/packages/twenty-front/src/modules/workflow/utils/getStepDefinitionOrThrow.ts b/packages/twenty-front/src/modules/workflow/utils/getStepDefinitionOrThrow.ts index 5fc036e869c..e1a3c8dc5f0 100644 --- a/packages/twenty-front/src/modules/workflow/utils/getStepDefinitionOrThrow.ts +++ b/packages/twenty-front/src/modules/workflow/utils/getStepDefinitionOrThrow.ts @@ -3,8 +3,8 @@ import { type WorkflowTrigger, } from '@/workflow/types/Workflow'; import { findStepPosition } from '@/workflow/utils/findStepPosition'; -import { TRIGGER_STEP_ID } from '@/workflow/workflow-trigger/constants/TriggerStepId'; import { isDefined } from 'twenty-shared/utils'; +import { TRIGGER_STEP_ID } from 'twenty-shared/workflow'; export const getStepDefinitionOrThrow = ({ stepId, diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramEmptyTriggerReadonly.tsx b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramEmptyTriggerReadonly.tsx index 1a26f065ccf..e384ae9dc5f 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramEmptyTriggerReadonly.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramEmptyTriggerReadonly.tsx @@ -7,12 +7,12 @@ import { workflowVisualizerWorkflowIdComponentState } from '@/workflow/states/wo import { workflowVisualizerWorkflowVersionIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowVersionIdComponentState'; import { WorkflowDiagramStepNodeBase } from '@/workflow/workflow-diagram/components/WorkflowDiagramStepNodeBase'; import { workflowSelectedNodeComponentState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeComponentState'; -import { TRIGGER_STEP_ID } from '@/workflow/workflow-trigger/constants/TriggerStepId'; import styled from '@emotion/styled'; import { useLingui } from '@lingui/react/macro'; import { useContext } from 'react'; import { useSetRecoilState } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; +import { TRIGGER_STEP_ID } from 'twenty-shared/workflow'; import { useIcons } from 'twenty-ui/display'; const StyledStepNodeLabelIconContainer = styled.div` diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/constants/WorkflowDiagramEmptyTriggerNodeDefinition.ts b/packages/twenty-front/src/modules/workflow/workflow-diagram/constants/WorkflowDiagramEmptyTriggerNodeDefinition.ts index d45dbd43e28..f4d3cb82d69 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-diagram/constants/WorkflowDiagramEmptyTriggerNodeDefinition.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/constants/WorkflowDiagramEmptyTriggerNodeDefinition.ts @@ -1,6 +1,6 @@ import { type WorkflowDiagramEmptyTriggerNodeData } from '@/workflow/workflow-diagram/types/WorkflowDiagram'; -import { TRIGGER_STEP_ID } from '@/workflow/workflow-trigger/constants/TriggerStepId'; import { type Node } from '@xyflow/react'; +import { TRIGGER_STEP_ID } from 'twenty-shared/workflow'; export const WORKFLOW_DIAGRAM_EMPTY_TRIGGER_NODE_DEFINITION = { id: TRIGGER_STEP_ID, diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/utils/generateWorkflowDiagram.ts b/packages/twenty-front/src/modules/workflow/workflow-diagram/utils/generateWorkflowDiagram.ts index 843727c8ebd..e946f8755e4 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-diagram/utils/generateWorkflowDiagram.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/utils/generateWorkflowDiagram.ts @@ -15,9 +15,9 @@ import { import { getWorkflowDiagramTriggerNode } from '@/workflow/workflow-diagram/utils/getWorkflowDiagramTriggerNode'; import { WORKFLOW_DIAGRAM_EMPTY_TRIGGER_NODE_DEFINITION } from '@/workflow/workflow-diagram/constants/WorkflowDiagramEmptyTriggerNodeDefinition'; -import { TRIGGER_STEP_ID } from '@/workflow/workflow-trigger/constants/TriggerStepId'; import { getRootStepIds } from '@/workflow/workflow-trigger/utils/getRootStepIds'; import { isDefined } from 'twenty-shared/utils'; +import { TRIGGER_STEP_ID } from 'twenty-shared/workflow'; import { v4 } from 'uuid'; export const generateWorkflowDiagram = ({ diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/utils/getWorkflowDiagramTriggerNode.ts b/packages/twenty-front/src/modules/workflow/workflow-diagram/utils/getWorkflowDiagramTriggerNode.ts index 9150de67564..422809e3088 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-diagram/utils/getWorkflowDiagramTriggerNode.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/utils/getWorkflowDiagramTriggerNode.ts @@ -2,10 +2,10 @@ import { type WorkflowTrigger } from '@/workflow/types/Workflow'; import { splitWorkflowTriggerEventName } from '@/workflow/utils/splitWorkflowTriggerEventName'; import { type WorkflowDiagramStepNodeData } from '@/workflow/workflow-diagram/types/WorkflowDiagram'; import { DATABASE_TRIGGER_TYPES } from '@/workflow/workflow-trigger/constants/DatabaseTriggerTypes'; -import { TRIGGER_STEP_ID } from '@/workflow/workflow-trigger/constants/TriggerStepId'; import { getTriggerIcon } from '@/workflow/workflow-trigger/utils/getTriggerIcon'; import { type Node } from '@xyflow/react'; import { assertUnreachable, isDefined } from 'twenty-shared/utils'; +import { TRIGGER_STEP_ID } from 'twenty-shared/workflow'; export const getWorkflowDiagramTriggerNode = ({ trigger, diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/utils/__tests__/getWorkflowPreviousStepId.test.ts b/packages/twenty-front/src/modules/workflow/workflow-steps/utils/__tests__/getWorkflowPreviousStepId.test.ts index 3a43246a840..2975603193e 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/utils/__tests__/getWorkflowPreviousStepId.test.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/utils/__tests__/getWorkflowPreviousStepId.test.ts @@ -1,5 +1,5 @@ import { type WorkflowStep } from '@/workflow/types/Workflow'; -import { TRIGGER_STEP_ID } from '@/workflow/workflow-trigger/constants/TriggerStepId'; +import { TRIGGER_STEP_ID } from 'twenty-shared/workflow'; import { getWorkflowPreviousStepId } from '../getWorkflowPreviousStepId'; describe('getWorkflowPreviousStepId', () => { diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/utils/__tests__/getWorkflowRunStepContext.test.ts b/packages/twenty-front/src/modules/workflow/workflow-steps/utils/__tests__/getWorkflowRunStepContext.test.ts index df891da4644..37f2f55ce8c 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/utils/__tests__/getWorkflowRunStepContext.test.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/utils/__tests__/getWorkflowRunStepContext.test.ts @@ -1,7 +1,6 @@ import { type WorkflowRunFlow } from '@/workflow/types/Workflow'; -import { TRIGGER_STEP_ID } from '@/workflow/workflow-trigger/constants/TriggerStepId'; +import { StepStatus, TRIGGER_STEP_ID } from 'twenty-shared/workflow'; import { getWorkflowRunStepContext } from '../getWorkflowRunStepContext'; -import { StepStatus } from 'twenty-shared/workflow'; describe('getWorkflowRunStepContext', () => { it('should return an empty array for trigger step', () => { diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/utils/getWorkflowPreviousStepId.ts b/packages/twenty-front/src/modules/workflow/workflow-steps/utils/getWorkflowPreviousStepId.ts index 5efa6d5bf94..f3f344b14a4 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/utils/getWorkflowPreviousStepId.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/utils/getWorkflowPreviousStepId.ts @@ -1,5 +1,5 @@ import { type WorkflowStep } from '@/workflow/types/Workflow'; -import { TRIGGER_STEP_ID } from '@/workflow/workflow-trigger/constants/TriggerStepId'; +import { TRIGGER_STEP_ID } from 'twenty-shared/workflow'; export const getWorkflowPreviousStepId = ({ stepId, diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/utils/getWorkflowRunStepContext.ts b/packages/twenty-front/src/modules/workflow/workflow-steps/utils/getWorkflowRunStepContext.ts index 771b0e7bc48..eab8094e6af 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/utils/getWorkflowRunStepContext.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/utils/getWorkflowRunStepContext.ts @@ -1,8 +1,8 @@ import { type WorkflowRunFlow } from '@/workflow/types/Workflow'; import { getPreviousSteps } from '@/workflow/workflow-steps/utils/getWorkflowPreviousSteps'; -import { TRIGGER_STEP_ID } from '@/workflow/workflow-trigger/constants/TriggerStepId'; import { getWorkflowRunContext, + TRIGGER_STEP_ID, type WorkflowRunStepInfos, } from 'twenty-shared/workflow'; diff --git a/packages/twenty-front/src/modules/workflow/workflow-trigger/constants/TriggerStepId.ts b/packages/twenty-front/src/modules/workflow/workflow-trigger/constants/TriggerStepId.ts deleted file mode 100644 index 2325548eb18..00000000000 --- a/packages/twenty-front/src/modules/workflow/workflow-trigger/constants/TriggerStepId.ts +++ /dev/null @@ -1 +0,0 @@ -export const TRIGGER_STEP_ID = 'trigger'; diff --git a/packages/twenty-front/src/modules/workflow/workflow-variables/hooks/useAvailableVariablesInWorkflowStep.ts b/packages/twenty-front/src/modules/workflow/workflow-variables/hooks/useAvailableVariablesInWorkflowStep.ts index 33e23a03be1..9304736e7c0 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-variables/hooks/useAvailableVariablesInWorkflowStep.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-variables/hooks/useAvailableVariablesInWorkflowStep.ts @@ -4,7 +4,6 @@ import { stepsOutputSchemaFamilySelector } from '@/workflow/states/selectors/ste import { type InputSchemaPropertyType } from '@/workflow/types/InputSchema'; import { workflowSelectedNodeComponentState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeComponentState'; import { getPreviousSteps } from '@/workflow/workflow-steps/utils/getWorkflowPreviousSteps'; -import { TRIGGER_STEP_ID } from '@/workflow/workflow-trigger/constants/TriggerStepId'; import { type OutputSchema, type StepOutputSchema, @@ -12,6 +11,7 @@ import { import { filterOutputSchema } from '@/workflow/workflow-variables/utils/filterOutputSchema'; import { useRecoilValue } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; +import { TRIGGER_STEP_ID } from 'twenty-shared/workflow'; import { isEmptyObject } from '~/utils/isEmptyObject'; export const useAvailableVariablesInWorkflowStep = ({ diff --git a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-edge/__tests__/workflow-version-edge.workspace-service.spec.ts b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-edge/__tests__/workflow-version-edge.workspace-service.spec.ts index 89f0ab32e2c..17b3558a3df 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-edge/__tests__/workflow-version-edge.workspace-service.spec.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-edge/__tests__/workflow-version-edge.workspace-service.spec.ts @@ -1,14 +1,17 @@ import { Test, type TestingModule } from '@nestjs/testing'; +import { TRIGGER_STEP_ID } from 'twenty-shared/workflow'; + +import { type WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { type WorkflowVersionWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow-version.workspace-entity'; -import { type WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository'; +import { WorkflowCommonWorkspaceService } from 'src/modules/workflow/common/workspace-services/workflow-common.workspace-service'; +import { WorkflowVersionEdgeWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-version-edge/workflow-version-edge.workspace-service'; import { type WorkflowAction, WorkflowActionType, } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type'; import { WorkflowTriggerType } from 'src/modules/workflow/workflow-trigger/types/workflow-trigger.type'; -import { WorkflowVersionEdgeWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-version-edge/workflow-version-edge.workspace-service'; type MockWorkspaceRepository = Partial< WorkspaceRepository @@ -71,6 +74,7 @@ const mockWorkflowVersion = { describe('WorkflowVersionEdgeWorkspaceService', () => { let twentyORMGlobalManager: jest.Mocked; + let workflowCommonWorkspaceService: jest.Mocked; let service: WorkflowVersionEdgeWorkspaceService; let mockWorkflowVersionWorkspaceRepository: MockWorkspaceRepository; @@ -90,6 +94,12 @@ describe('WorkflowVersionEdgeWorkspaceService', () => { .mockResolvedValue(mockWorkflowVersionWorkspaceRepository), } as unknown as jest.Mocked; + workflowCommonWorkspaceService = { + getWorkflowVersionOrFail: jest + .fn() + .mockResolvedValue(mockWorkflowVersion), + } as unknown as jest.Mocked; + const module: TestingModule = await Test.createTestingModule({ providers: [ WorkflowVersionEdgeWorkspaceService, @@ -97,6 +107,10 @@ describe('WorkflowVersionEdgeWorkspaceService', () => { provide: TwentyORMGlobalManager, useValue: twentyORMGlobalManager, }, + { + provide: WorkflowCommonWorkspaceService, + useValue: workflowCommonWorkspaceService, + }, ], }).compile(); @@ -107,7 +121,7 @@ describe('WorkflowVersionEdgeWorkspaceService', () => { it('should throw if target does not exists', async () => { const call = async () => await service.createWorkflowVersionEdge({ - source: 'trigger', + source: TRIGGER_STEP_ID, target: 'not-existing-step', workflowVersionId: mockWorkflowVersionId, workspaceId: mockWorkspaceId, @@ -121,12 +135,19 @@ describe('WorkflowVersionEdgeWorkspaceService', () => { describe('with source is the trigger', () => { it('should create an edge between trigger and step-1', async () => { const result = await service.createWorkflowVersionEdge({ - source: 'trigger', + source: TRIGGER_STEP_ID, target: 'step-3', workflowVersionId: mockWorkflowVersionId, workspaceId: mockWorkspaceId, }); + expect( + workflowCommonWorkspaceService.getWorkflowVersionOrFail, + ).toHaveBeenCalledWith({ + workflowVersionId: mockWorkflowVersionId, + workspaceId: mockWorkspaceId, + }); + expect( mockWorkflowVersionWorkspaceRepository.update, ).toHaveBeenCalledWith(mockWorkflowVersionId, { @@ -148,7 +169,7 @@ describe('WorkflowVersionEdgeWorkspaceService', () => { it('should not duplicate stepIds if edge already exists', async () => { const result = await service.createWorkflowVersionEdge({ - source: 'trigger', + source: TRIGGER_STEP_ID, target: 'step-1', workflowVersionId: mockWorkflowVersionId, workspaceId: mockWorkspaceId, @@ -245,7 +266,7 @@ describe('WorkflowVersionEdgeWorkspaceService', () => { it('should throw if target does not exists', async () => { const call = async () => await service.deleteWorkflowVersionEdge({ - source: 'trigger', + source: TRIGGER_STEP_ID, target: 'not-existing-step', workflowVersionId: mockWorkflowVersionId, workspaceId: mockWorkspaceId, @@ -259,12 +280,19 @@ describe('WorkflowVersionEdgeWorkspaceService', () => { describe('with source is the trigger', () => { it('should delete an edge between trigger and step-1', async () => { const result = await service.deleteWorkflowVersionEdge({ - source: 'trigger', + source: TRIGGER_STEP_ID, target: 'step-1', workflowVersionId: mockWorkflowVersionId, workspaceId: mockWorkspaceId, }); + expect( + workflowCommonWorkspaceService.getWorkflowVersionOrFail, + ).toHaveBeenCalledWith({ + workflowVersionId: mockWorkflowVersionId, + workspaceId: mockWorkspaceId, + }); + expect( mockWorkflowVersionWorkspaceRepository.update, ).toHaveBeenCalledWith(mockWorkflowVersionId, { @@ -286,12 +314,19 @@ describe('WorkflowVersionEdgeWorkspaceService', () => { it('should not delete if edge does not exists', async () => { const result = await service.deleteWorkflowVersionEdge({ - source: 'trigger', + source: TRIGGER_STEP_ID, target: 'step-2', workflowVersionId: mockWorkflowVersionId, workspaceId: mockWorkspaceId, }); + expect( + workflowCommonWorkspaceService.getWorkflowVersionOrFail, + ).toHaveBeenCalledWith({ + workflowVersionId: mockWorkflowVersionId, + workspaceId: mockWorkspaceId, + }); + expect( mockWorkflowVersionWorkspaceRepository.update, ).not.toHaveBeenCalled(); @@ -377,5 +412,195 @@ describe('WorkflowVersionEdgeWorkspaceService', () => { ); }); }); + + describe('with filter steps', () => { + it('should delete the filter step when deleting edge from trigger to target through filter', async () => { + const mockStepsWithFilter = [ + { + id: 'step-1', + type: WorkflowActionType.FORM, + settings: { + errorHandlingOptions: { + continueOnFailure: { value: false }, + retryOnFailure: { value: false }, + }, + }, + nextStepIds: ['step-2'], + }, + { + id: 'step-2', + type: WorkflowActionType.SEND_EMAIL, + settings: { + errorHandlingOptions: { + continueOnFailure: { value: false }, + retryOnFailure: { value: false }, + }, + }, + nextStepIds: [], + }, + { + id: 'filter-step', + type: WorkflowActionType.FILTER, + settings: { + errorHandlingOptions: { + continueOnFailure: { value: false }, + retryOnFailure: { value: false }, + }, + }, + nextStepIds: ['step-2'], + }, + ] as WorkflowAction[]; + + const mockTriggerWithFilter = { + type: WorkflowTriggerType.MANUAL, + settings: {}, + nextStepIds: ['step-1', 'filter-step'], + }; + + const mockWorkflowVersionWithFilter = { + id: mockWorkflowVersionId, + trigger: mockTriggerWithFilter, + steps: mockStepsWithFilter, + status: 'DRAFT', + } as WorkflowVersionWorkspaceEntity; + + workflowCommonWorkspaceService.getWorkflowVersionOrFail.mockResolvedValue( + mockWorkflowVersionWithFilter, + ); + + const result = await service.deleteWorkflowVersionEdge({ + source: TRIGGER_STEP_ID, + target: 'step-2', + workflowVersionId: mockWorkflowVersionId, + workspaceId: mockWorkspaceId, + }); + + expect( + workflowCommonWorkspaceService.getWorkflowVersionOrFail, + ).toHaveBeenCalledWith({ + workflowVersionId: mockWorkflowVersionId, + workspaceId: mockWorkspaceId, + }); + + expect( + mockWorkflowVersionWorkspaceRepository.update, + ).toHaveBeenCalledWith(mockWorkflowVersionId, { + trigger: { + ...mockTriggerWithFilter, + nextStepIds: ['step-1'], + }, + steps: mockStepsWithFilter.filter( + (step) => step.id !== 'filter-step', + ), + }); + + expect(result).toEqual({ + triggerNextStepIds: ['step-1'], + stepsNextStepIds: { + 'step-1': ['step-2'], + 'step-2': [], + }, + }); + }); + it('should delete the filter step when deleting edge from step to target through filter', async () => { + const mockStepsWithFilter = [ + { + id: 'step-1', + type: WorkflowActionType.FORM, + settings: { + errorHandlingOptions: { + continueOnFailure: { value: false }, + retryOnFailure: { value: false }, + }, + }, + nextStepIds: ['step-2', 'filter-step'], + }, + { + id: 'step-2', + type: WorkflowActionType.SEND_EMAIL, + settings: { + errorHandlingOptions: { + continueOnFailure: { value: false }, + retryOnFailure: { value: false }, + }, + }, + nextStepIds: [], + }, + { + id: 'step-3', + type: WorkflowActionType.SEND_EMAIL, + settings: { + errorHandlingOptions: { + continueOnFailure: { value: false }, + retryOnFailure: { value: false }, + }, + }, + nextStepIds: [], + }, + { + id: 'filter-step', + type: WorkflowActionType.FILTER, + settings: { + errorHandlingOptions: { + continueOnFailure: { value: false }, + retryOnFailure: { value: false }, + }, + }, + nextStepIds: ['step-3'], + }, + ] as WorkflowAction[]; + + const mockWorkflowVersionWithFilter = { + id: mockWorkflowVersionId, + trigger: mockTrigger, + steps: mockStepsWithFilter, + status: 'DRAFT', + } as WorkflowVersionWorkspaceEntity; + + workflowCommonWorkspaceService.getWorkflowVersionOrFail.mockResolvedValue( + mockWorkflowVersionWithFilter, + ); + + const result = await service.deleteWorkflowVersionEdge({ + source: 'step-1', + target: 'step-3', + workflowVersionId: mockWorkflowVersionId, + workspaceId: mockWorkspaceId, + }); + + expect( + workflowCommonWorkspaceService.getWorkflowVersionOrFail, + ).toHaveBeenCalledWith({ + workflowVersionId: mockWorkflowVersionId, + workspaceId: mockWorkspaceId, + }); + + expect( + mockWorkflowVersionWorkspaceRepository.update, + ).toHaveBeenCalledWith(mockWorkflowVersionId, { + steps: mockStepsWithFilter + .map((step) => { + if (step.id === 'step-1') { + return { + ...step, + nextStepIds: ['step-2'], + }; + } + + return step; + }) + .filter((step) => step.id !== 'filter-step'), + }); + + expect(result).toEqual({ + triggerNextStepIds: ['step-1'], + stepsNextStepIds: { + 'step-1': ['step-2'], + 'step-2': [], + 'step-3': [], + }, + }); + }); + }); }); }); diff --git a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-edge/workflow-version-edge.module.ts b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-edge/workflow-version-edge.module.ts index 7aa64acd6cd..fa443d9ffdb 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-edge/workflow-version-edge.module.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-edge/workflow-version-edge.module.ts @@ -1,8 +1,10 @@ import { Module } from '@nestjs/common'; +import { WorkflowCommonModule } from 'src/modules/workflow/common/workflow-common.module'; import { WorkflowVersionEdgeWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-version-edge/workflow-version-edge.workspace-service'; @Module({ + imports: [WorkflowCommonModule], providers: [WorkflowVersionEdgeWorkspaceService], exports: [WorkflowVersionEdgeWorkspaceService], }) diff --git a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-edge/workflow-version-edge.workspace-service.ts b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-edge/workflow-version-edge.workspace-service.ts index 131a7c4c812..541b90ad176 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-edge/workflow-version-edge.workspace-service.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-edge/workflow-version-edge.workspace-service.ts @@ -1,21 +1,30 @@ import { Injectable } from '@nestjs/common'; import { isDefined } from 'twenty-shared/utils'; +import { TRIGGER_STEP_ID } from 'twenty-shared/workflow'; import { type WorkflowVersionStepChangesDTO } from 'src/engine/core-modules/workflow/dtos/workflow-version-step-changes.dto'; -import { type WorkflowVersionWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow-version.workspace-entity'; +import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { WorkflowVersionEdgeException, WorkflowVersionEdgeExceptionCode, } from 'src/modules/workflow/common/exceptions/workflow-version-edge.exception'; +import { type WorkflowVersionWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow-version.workspace-entity'; import { assertWorkflowVersionIsDraft } from 'src/modules/workflow/common/utils/assert-workflow-version-is-draft.util'; +import { WorkflowCommonWorkspaceService } from 'src/modules/workflow/common/workspace-services/workflow-common.workspace-service'; import { computeWorkflowVersionStepChanges } from 'src/modules/workflow/workflow-builder/utils/compute-workflow-version-step-updates.util'; -import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; +import { + type WorkflowAction, + WorkflowActionType, +} from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type'; +import { type WorkflowTrigger } from 'src/modules/workflow/workflow-trigger/types/workflow-trigger.type'; @Injectable() export class WorkflowVersionEdgeWorkspaceService { constructor( private readonly twentyORMGlobalManager: TwentyORMGlobalManager, + private readonly workflowCommonWorkspaceService: WorkflowCommonWorkspaceService, ) {} async createWorkflowVersionEdge({ @@ -36,26 +45,16 @@ export class WorkflowVersionEdgeWorkspaceService { { shouldBypassPermissionChecks: true }, ); - const workflowVersion = await workflowVersionRepository.findOne({ - where: { - id: workflowVersionId, - }, - }); - - if (!isDefined(workflowVersion)) { - throw new WorkflowVersionEdgeException( - 'WorkflowVersion not found', - WorkflowVersionEdgeExceptionCode.NOT_FOUND, - ); - } + const workflowVersion = + await this.workflowCommonWorkspaceService.getWorkflowVersionOrFail({ + workflowVersionId, + workspaceId, + }); assertWorkflowVersionIsDraft(workflowVersion); - const steps = workflowVersion.steps || []; - const trigger = workflowVersion.trigger; - - const isSourceTrigger = source === 'trigger'; + const steps = workflowVersion.steps || []; const targetStep = steps.find((step) => step.id === target); @@ -66,41 +65,150 @@ export class WorkflowVersionEdgeWorkspaceService { ); } + const isSourceTrigger = source === TRIGGER_STEP_ID; + if (isSourceTrigger) { - if (!isDefined(trigger)) { - throw new WorkflowVersionEdgeException( - `Trigger not found in workflowVersion '${workflowVersionId}'`, - WorkflowVersionEdgeExceptionCode.NOT_FOUND, - ); - } + return this.createTriggerEdge({ + trigger, + steps, + target, + workflowVersion, + workflowVersionRepository, + }); + } else { + return this.createStepEdge({ + trigger, + steps, + source, + target, + workflowVersion, + workflowVersionRepository, + }); + } + } - if (trigger.nextStepIds?.includes(target)) { - return computeWorkflowVersionStepChanges({ - trigger, - steps, - }); - } + async deleteWorkflowVersionEdge({ + source, + target, + workflowVersionId, + workspaceId, + }: { + source: string; + target: string; + workflowVersionId: string; + workspaceId: string; + }): Promise { + const workflowVersionRepository = + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspaceId, + 'workflowVersion', + { shouldBypassPermissionChecks: true }, + ); - const updatedTrigger = { - ...trigger, - nextStepIds: [...(trigger.nextStepIds ?? []), target], - }; - - await workflowVersionRepository.update(workflowVersion.id, { - trigger: updatedTrigger, + const workflowVersion = + await this.workflowCommonWorkspaceService.getWorkflowVersionOrFail({ + workflowVersionId, + workspaceId, }); + assertWorkflowVersionIsDraft(workflowVersion); + + const trigger = workflowVersion.trigger; + const steps = workflowVersion.steps || []; + + const targetStep = steps.find((step) => step.id === target); + + if (!isDefined(targetStep)) { + throw new WorkflowVersionEdgeException( + `Target step '${target}' not found in workflowVersion '${workflowVersionId}'`, + WorkflowVersionEdgeExceptionCode.NOT_FOUND, + ); + } + + const isSourceTrigger = source === TRIGGER_STEP_ID; + + if (isSourceTrigger) { + return this.deleteTriggerEdge({ + trigger, + steps, + target, + workflowVersion, + workflowVersionRepository, + }); + } else { + return this.deleteStepEdge({ + trigger, + steps, + source, + target, + workflowVersion, + workflowVersionRepository, + }); + } + } + + private async createTriggerEdge({ + trigger, + steps, + target, + workflowVersion, + workflowVersionRepository, + }: { + trigger: WorkflowTrigger | null; + steps: WorkflowAction[]; + target: string; + workflowVersion: WorkflowVersionWorkspaceEntity; + workflowVersionRepository: WorkspaceRepository; + }): Promise { + if (!isDefined(trigger)) { + throw new WorkflowVersionEdgeException( + `Trigger not found in workflowVersion '${workflowVersion.id}'`, + WorkflowVersionEdgeExceptionCode.NOT_FOUND, + ); + } + + if (trigger.nextStepIds?.includes(target)) { return computeWorkflowVersionStepChanges({ - trigger: updatedTrigger, + trigger, steps, }); } + const updatedTrigger = { + ...trigger, + nextStepIds: [...(trigger.nextStepIds ?? []), target], + }; + + await workflowVersionRepository.update(workflowVersion.id, { + trigger: updatedTrigger, + }); + + return computeWorkflowVersionStepChanges({ + trigger: updatedTrigger, + steps, + }); + } + + private async createStepEdge({ + trigger, + steps, + source, + target, + workflowVersion, + workflowVersionRepository, + }: { + trigger: WorkflowTrigger | null; + steps: WorkflowAction[]; + source: string; + target: string; + workflowVersion: WorkflowVersionWorkspaceEntity; + workflowVersionRepository: WorkspaceRepository; + }): Promise { const sourceStep = steps.find((step) => step.id === source); if (!isDefined(sourceStep)) { throw new WorkflowVersionEdgeException( - `Source step '${source}' not found in workflowVersion '${workflowVersionId}'`, + `Source step '${source}' not found in workflowVersion '${workflowVersion.id}'`, WorkflowVersionEdgeExceptionCode.NOT_FOUND, ); } @@ -135,106 +243,92 @@ export class WorkflowVersionEdgeWorkspaceService { }); } - async deleteWorkflowVersionEdge({ - source, + private async deleteTriggerEdge({ + trigger, + steps, target, - workflowVersionId, - workspaceId, + workflowVersion, + workflowVersionRepository, }: { - source: string; + trigger: WorkflowTrigger | null; + steps: WorkflowAction[]; target: string; - workflowVersionId: string; - workspaceId: string; + workflowVersion: WorkflowVersionWorkspaceEntity; + workflowVersionRepository: WorkspaceRepository; }): Promise { - const workflowVersionRepository = - await this.twentyORMGlobalManager.getRepositoryForWorkspace( - workspaceId, - 'workflowVersion', - { shouldBypassPermissionChecks: true }, + if (!isDefined(trigger)) { + throw new WorkflowVersionEdgeException( + `Trigger not found in workflowVersion '${workflowVersion.id}'`, + WorkflowVersionEdgeExceptionCode.NOT_FOUND, ); + } - const workflowVersion = await workflowVersionRepository.findOne({ - where: { - id: workflowVersionId, - }, + if (!trigger.nextStepIds?.includes(target)) { + return this.handleFilterBetweenTriggerAndTarget({ + trigger, + steps, + target, + workflowVersionId: workflowVersion.id, + workflowVersionRepository, + }); + } + + const updatedTrigger = { + ...trigger, + nextStepIds: trigger.nextStepIds?.filter( + (nextStepId: string) => nextStepId !== target, + ), + }; + + await workflowVersionRepository.update(workflowVersion.id, { + trigger: updatedTrigger, }); - if (!isDefined(workflowVersion)) { - throw new WorkflowVersionEdgeException( - 'WorkflowVersion not found', - WorkflowVersionEdgeExceptionCode.NOT_FOUND, - ); - } - - assertWorkflowVersionIsDraft(workflowVersion); - - const steps = workflowVersion.steps || []; - - const trigger = workflowVersion.trigger; - - const isSourceTrigger = source === 'trigger'; - - const targetStep = steps.find((step) => step.id === target); - - if (!isDefined(targetStep)) { - throw new WorkflowVersionEdgeException( - `Target step '${target}' not found in workflowVersion '${workflowVersionId}'`, - WorkflowVersionEdgeExceptionCode.NOT_FOUND, - ); - } - - if (isSourceTrigger) { - if (!isDefined(trigger)) { - throw new WorkflowVersionEdgeException( - `Trigger not found in workflowVersion '${workflowVersionId}'`, - WorkflowVersionEdgeExceptionCode.NOT_FOUND, - ); - } - - if (!trigger.nextStepIds?.includes(target)) { - return computeWorkflowVersionStepChanges({ - trigger, - steps, - }); - } - - const updatedTrigger = { - ...trigger, - nextStepIds: trigger.nextStepIds?.filter( - (nextStepId) => nextStepId !== target, - ), - }; - - await workflowVersionRepository.update(workflowVersion.id, { - trigger: updatedTrigger, - }); - - return computeWorkflowVersionStepChanges({ - trigger: updatedTrigger, - steps, - }); - } + return computeWorkflowVersionStepChanges({ + trigger: updatedTrigger, + steps, + }); + } + private async deleteStepEdge({ + trigger, + steps, + source, + target, + workflowVersion, + workflowVersionRepository, + }: { + trigger: WorkflowTrigger | null; + steps: WorkflowAction[]; + source: string; + target: string; + workflowVersion: WorkflowVersionWorkspaceEntity; + workflowVersionRepository: WorkspaceRepository; + }): Promise { const sourceStep = steps.find((step) => step.id === source); if (!isDefined(sourceStep)) { throw new WorkflowVersionEdgeException( - `Source step '${source}' not found in workflowVersion '${workflowVersionId}'`, + `Source step '${source}' not found in workflowVersion '${workflowVersion.id}'`, WorkflowVersionEdgeExceptionCode.NOT_FOUND, ); } if (!sourceStep.nextStepIds?.includes(target)) { - return computeWorkflowVersionStepChanges({ + return await this.handleFilterBetweenSourceAndTarget({ trigger, steps, + sourceStep, + target, + workflowVersionId: workflowVersion.id, + workflowVersionRepository, }); } const updatedSourceStep = { ...sourceStep, nextStepIds: sourceStep.nextStepIds?.filter( - (nextStepId) => nextStepId !== target, + (nextStepId: string) => nextStepId !== target, ), }; @@ -255,4 +349,125 @@ export class WorkflowVersionEdgeWorkspaceService { steps: updatedSteps, }); } + + private async handleFilterBetweenTriggerAndTarget({ + trigger, + steps, + target, + workflowVersionId, + workflowVersionRepository, + }: { + trigger: WorkflowTrigger; + steps: WorkflowAction[]; + target: string; + workflowVersionId: string; + workflowVersionRepository: WorkspaceRepository; + }): Promise { + const filterBetweenTriggerAndTarget = this.findFilterBetweenNodes({ + steps, + sourceNextStepIds: trigger.nextStepIds, + target, + }); + + if (!isDefined(filterBetweenTriggerAndTarget)) { + return computeWorkflowVersionStepChanges({ + trigger, + steps, + }); + } + + const updatedTrigger = { + ...trigger, + nextStepIds: trigger.nextStepIds?.filter( + (nextStepId: string) => nextStepId !== filterBetweenTriggerAndTarget.id, + ), + }; + + const updatedSteps = steps.filter( + (step) => step.id !== filterBetweenTriggerAndTarget.id, + ); + + await workflowVersionRepository.update(workflowVersionId, { + trigger: updatedTrigger, + steps: updatedSteps, + }); + + return computeWorkflowVersionStepChanges({ + trigger: updatedTrigger, + steps: updatedSteps, + }); + } + + private async handleFilterBetweenSourceAndTarget({ + trigger, + steps, + sourceStep, + target, + workflowVersionRepository, + workflowVersionId, + }: { + trigger: WorkflowTrigger | null; + steps: WorkflowAction[]; + sourceStep: WorkflowAction; + target: string; + workflowVersionRepository: WorkspaceRepository; + workflowVersionId: string; + }): Promise { + const filterBetweenSourceAndTarget = this.findFilterBetweenNodes({ + steps, + sourceNextStepIds: sourceStep.nextStepIds, + target, + }); + + if (!isDefined(filterBetweenSourceAndTarget)) { + return computeWorkflowVersionStepChanges({ + trigger, + steps, + }); + } + + const updatedSourceStep = { + ...sourceStep, + nextStepIds: sourceStep.nextStepIds?.filter( + (nextStepId: string) => nextStepId !== filterBetweenSourceAndTarget.id, + ), + }; + + const updatedSteps = steps + .map((step) => { + if (step.id === sourceStep.id) { + return updatedSourceStep; + } + + return step; + }) + .filter((step) => step.id !== filterBetweenSourceAndTarget.id); + + await workflowVersionRepository.update(workflowVersionId, { + steps: updatedSteps, + }); + + return computeWorkflowVersionStepChanges({ + trigger, + steps: updatedSteps, + }); + } + + private findFilterBetweenNodes({ + steps, + sourceNextStepIds, + target, + }: { + steps: WorkflowAction[]; + sourceNextStepIds: string[] | undefined; + target: string; + }) { + const nextStepFilters = steps.filter( + (step) => + sourceNextStepIds?.includes(step.id) && + step.type === WorkflowActionType.FILTER, + ); + + return nextStepFilters.find((step) => step.nextStepIds?.includes(target)); + } } diff --git a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/__tests__/workflow-version-step.workspace-service.spec.ts b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/__tests__/workflow-version-step.workspace-service.spec.ts index a71df2679d9..1c8280cc88d 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/__tests__/workflow-version-step.workspace-service.spec.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/__tests__/workflow-version-step.workspace-service.spec.ts @@ -1,6 +1,8 @@ import { Test, type TestingModule } from '@nestjs/testing'; import { getRepositoryToken } from '@nestjs/typeorm'; +import { TRIGGER_STEP_ID } from 'twenty-shared/workflow'; + import { AgentService } from 'src/engine/metadata-modules/agent/agent.service'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { ServerlessFunctionService } from 'src/engine/metadata-modules/serverless-function/serverless-function.service'; @@ -135,7 +137,7 @@ describe('WorkflowVersionStepWorkspaceService', () => { const result = await service.createWorkflowVersionStep({ input: { stepType: WorkflowActionType.FORM, - parentStepId: 'trigger', + parentStepId: TRIGGER_STEP_ID, nextStepId: undefined, workflowVersionId: mockWorkflowVersionId, }, @@ -160,7 +162,7 @@ describe('WorkflowVersionStepWorkspaceService', () => { const result = await service.createWorkflowVersionStep({ input: { stepType: WorkflowActionType.FORM, - parentStepId: 'trigger', + parentStepId: TRIGGER_STEP_ID, nextStepId: 'step-1', workflowVersionId: mockWorkflowVersionId, }, @@ -259,7 +261,7 @@ describe('WorkflowVersionStepWorkspaceService', () => { it('should delete trigger', async () => { const result = await service.deleteWorkflowVersionStep({ - stepIdToDelete: 'trigger', + stepIdToDelete: TRIGGER_STEP_ID, workflowVersionId: mockWorkflowVersionId, workspaceId: mockWorkspaceId, }); @@ -277,7 +279,7 @@ describe('WorkflowVersionStepWorkspaceService', () => { 'step-2': [], 'step-3': [], }, - deletedStepIds: ['trigger'], + deletedStepIds: [TRIGGER_STEP_ID], }); }); }); diff --git a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/utils/__tests__/insert-step.spec.ts b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/utils/__tests__/insert-step.spec.ts index c93636a57dd..2d03af8b595 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/utils/__tests__/insert-step.spec.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/utils/__tests__/insert-step.spec.ts @@ -1,3 +1,5 @@ +import { TRIGGER_STEP_ID } from 'twenty-shared/workflow'; + import { insertStep } from 'src/modules/workflow/workflow-builder/workflow-version-step/utils/insert-step'; import { type WorkflowAction, @@ -146,7 +148,7 @@ describe('insertStep', () => { existingTrigger, existingSteps: [step1], insertedStep: newStep, - parentStepId: 'trigger', + parentStepId: TRIGGER_STEP_ID, nextStepId: undefined, }); diff --git a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/utils/__tests__/remove-step.spec.ts b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/utils/__tests__/remove-step.spec.ts index f303f5b6b17..6e0083cb137 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/utils/__tests__/remove-step.spec.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/utils/__tests__/remove-step.spec.ts @@ -1,3 +1,5 @@ +import { TRIGGER_STEP_ID } from 'twenty-shared/workflow'; + import { removeStep } from 'src/modules/workflow/workflow-builder/workflow-version-step/utils/remove-step'; import { type WorkflowAction, @@ -187,7 +189,7 @@ describe('removeStep', () => { const result = removeStep({ existingTrigger: mockTrigger, existingSteps: [step1, step2, step3], - stepIdToDelete: 'trigger', + stepIdToDelete: TRIGGER_STEP_ID, stepToDeleteChildrenIds: ['1'], }); @@ -247,7 +249,7 @@ describe('removeStep', () => { const result = removeStep({ existingTrigger: { ...mockTrigger, nextStepIds: [] }, existingSteps: null, - stepIdToDelete: 'trigger', + stepIdToDelete: TRIGGER_STEP_ID, }); expect(result.updatedTrigger).toEqual(null); diff --git a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/utils/insert-step.ts b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/utils/insert-step.ts index 2a87077d0f6..4ef6a9eb7e0 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/utils/insert-step.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/utils/insert-step.ts @@ -1,3 +1,5 @@ +import { TRIGGER_STEP_ID } from 'twenty-shared/workflow'; + import { type WorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type'; import { type WorkflowTrigger } from 'src/modules/workflow/workflow-trigger/types/workflow-trigger.type'; @@ -22,7 +24,7 @@ export const insertStep = ({ let updatedExistingSteps = existingSteps; - if (parentStepId === 'trigger') { + if (parentStepId === TRIGGER_STEP_ID) { if (!existingTrigger) { throw new Error('Cannot insert step from undefined trigger'); } diff --git a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/utils/remove-step.ts b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/utils/remove-step.ts index 8a5a98ef08d..5f0c0e33fd1 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/utils/remove-step.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/utils/remove-step.ts @@ -1,4 +1,5 @@ import { isDefined } from 'twenty-shared/utils'; +import { TRIGGER_STEP_ID } from 'twenty-shared/workflow'; import { type WorkflowAction, @@ -181,7 +182,7 @@ const removeTrigger = ({ return { updatedSteps, updatedTrigger: null, - removedStepIds: ['trigger', ...stepIdsToRemove], + removedStepIds: [TRIGGER_STEP_ID, ...stepIdsToRemove], }; }; @@ -196,7 +197,7 @@ export const removeStep = ({ stepIdToDelete: string; stepToDeleteChildrenIds?: string[]; }) => { - if (stepIdToDelete === 'trigger') { + if (stepIdToDelete === TRIGGER_STEP_ID) { return removeTrigger({ existingSteps, triggerChildrenIds: stepToDeleteChildrenIds, diff --git a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step.workspace-service.ts b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step.workspace-service.ts index 644d977823b..49c485ef92c 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step.workspace-service.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step.workspace-service.ts @@ -4,7 +4,7 @@ import { InjectRepository } from '@nestjs/typeorm'; import { t } from '@lingui/core/macro'; import { FieldMetadataType } from 'twenty-shared/types'; import { isDefined, isValidUuid } from 'twenty-shared/utils'; -import { StepStatus } from 'twenty-shared/workflow'; +import { StepStatus, TRIGGER_STEP_ID } from 'twenty-shared/workflow'; import { Repository } from 'typeorm'; import { v4 } from 'uuid'; @@ -221,7 +221,7 @@ export class WorkflowVersionStepWorkspaceService { const existingTrigger = workflowVersion.trigger; const isDeletingTrigger = - stepIdToDelete === 'trigger' && isDefined(existingTrigger); + stepIdToDelete === TRIGGER_STEP_ID && isDefined(existingTrigger); if (!isDeletingTrigger && !isDefined(workflowVersion.steps)) { throw new WorkflowVersionStepException( 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 30ac9fd0e33..1bc82d9ffe8 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 @@ -1,8 +1,10 @@ import { Injectable } from '@nestjs/common'; import { isDefined } from 'twenty-shared/utils'; +import { TRIGGER_STEP_ID } from 'twenty-shared/workflow'; import { RecordPositionService } from 'src/engine/core-modules/record-position/services/record-position.service'; +import { type WorkflowStepPositionUpdateInput } from 'src/engine/core-modules/workflow/dtos/update-workflow-step-position-update-input.dto'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { WorkflowVersionStepException, @@ -17,7 +19,6 @@ import { assertWorkflowVersionIsDraft } from 'src/modules/workflow/common/utils/ import { assertWorkflowVersionTriggerIsDefined } from 'src/modules/workflow/common/utils/assert-workflow-version-trigger-is-defined.util'; import { WorkflowVersionStepWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step.workspace-service'; import { type WorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type'; -import { type WorkflowStepPositionUpdateInput } from 'src/engine/core-modules/workflow/dtos/update-workflow-step-position-update-input.dto'; @Injectable() export class WorkflowVersionWorkspaceService { @@ -139,7 +140,7 @@ export class WorkflowVersionWorkspaceService { assertWorkflowVersionIsDraft(workflowVersion); const triggerPosition = positions.find( - (position) => position.id === 'trigger', + (position) => position.id === TRIGGER_STEP_ID, ); const updatedTrigger = diff --git a/packages/twenty-shared/src/workflow/constants/TriggerStepId.ts b/packages/twenty-shared/src/workflow/constants/TriggerStepId.ts new file mode 100644 index 00000000000..9da1339acde --- /dev/null +++ b/packages/twenty-shared/src/workflow/constants/TriggerStepId.ts @@ -0,0 +1 @@ +export const TRIGGER_STEP_ID = 'trigger'; \ No newline at end of file diff --git a/packages/twenty-shared/src/workflow/index.ts b/packages/twenty-shared/src/workflow/index.ts index b6eab929393..83e3791b951 100644 --- a/packages/twenty-shared/src/workflow/index.ts +++ b/packages/twenty-shared/src/workflow/index.ts @@ -7,6 +7,7 @@ * |___/ */ +export { TRIGGER_STEP_ID } from './constants/TriggerStepId'; export type { WorkflowRunStepInfo, WorkflowRunStepInfos,