diff --git a/packages/twenty-front/src/modules/workflow/hooks/useRunWorkflowRunOpeningInCommandMenuSideEffects.ts b/packages/twenty-front/src/modules/workflow/hooks/useRunWorkflowRunOpeningInCommandMenuSideEffects.ts index 440a3620a39..1d1164870f4 100644 --- a/packages/twenty-front/src/modules/workflow/hooks/useRunWorkflowRunOpeningInCommandMenuSideEffects.ts +++ b/packages/twenty-front/src/modules/workflow/hooks/useRunWorkflowRunOpeningInCommandMenuSideEffects.ts @@ -30,6 +30,9 @@ export const useRunWorkflowRunOpeningInCommandMenuSideEffects = () => { const isWorkflowFilteringEnabled = useIsFeatureEnabled( FeatureFlagKey.IS_WORKFLOW_FILTERING_ENABLED, ); + const isWorkflowBranchEnabled = useIsFeatureEnabled( + FeatureFlagKey.IS_WORKFLOW_BRANCH_ENABLED, + ); const runWorkflowRunOpeningInCommandMenuSideEffects = useRecoilCallback( ({ snapshot, set }) => @@ -63,6 +66,7 @@ export const useRunWorkflowRunOpeningInCommandMenuSideEffects = () => { stepInfos: workflowRunRecord.state.stepInfos, trigger: workflowRunRecord.state.flow.trigger, isWorkflowFilteringEnabled, + isWorkflowBranchEnabled, }); if (!isDefined(stepToOpenByDefault)) { @@ -133,6 +137,7 @@ export const useRunWorkflowRunOpeningInCommandMenuSideEffects = () => { apolloCoreClient.cache, objectPermissionsByObjectMetadataId, isWorkflowFilteringEnabled, + isWorkflowBranchEnabled, openWorkflowRunViewStepInCommandMenu, getIcon, ], diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramCanvasBase.tsx b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramCanvasBase.tsx index 94a2efbeeaa..2807892aa0b 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramCanvasBase.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramCanvasBase.tsx @@ -38,6 +38,7 @@ import { type NodeChange, type NodeProps, type OnBeforeDelete, + type OnDelete, type OnNodeDrag, } from '@xyflow/react'; import '@xyflow/react/dist/style.css'; @@ -417,18 +418,30 @@ export const WorkflowDiagramCanvasBase = ({ const onBeforeDelete: OnBeforeDelete< WorkflowDiagramNode, WorkflowDiagramEdge - > = async (diagram) => { - if ( - diagram.nodes.length === 0 // We don't call deleteEdge when node diagram deletion is called - ) { - for (const edge of diagram.edges) { - onDeleteEdge?.(edge); - } - return diagram; + > = async ({ nodes, edges }) => { + if (!isWorkflowBranchEnabled) { + return false; } + + if (nodes.length === 0 && edges.length > 0) { + return true; + } + return false; }; + const onDelete: OnDelete = async ({ + edges, + }) => { + if (!isWorkflowBranchEnabled || !isDefined(onDeleteEdge)) { + return; + } + + for (const edge of edges) { + onDeleteEdge(edge); + } + }; + const onPaneContextMenu = useCallback( (event: MouseEvent | React.MouseEvent) => { event.preventDefault(); @@ -474,9 +487,8 @@ export const WorkflowDiagramCanvasBase = ({ onEdgesChange={handleEdgesChange} onConnect={isWorkflowBranchEnabled ? onConnect : undefined} onNodeDragStop={isWorkflowBranchEnabled ? onNodeDragStop : undefined} - onBeforeDelete={ - isWorkflowBranchEnabled ? onBeforeDelete : async () => false - } + onBeforeDelete={onBeforeDelete} + onDelete={onDelete} selectNodesOnDrag={false} proOptions={{ hideAttribution: true }} multiSelectionKeyCode={null} diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowRunVisualizerEffect.tsx b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowRunVisualizerEffect.tsx index 12589fd720d..0693a51c501 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowRunVisualizerEffect.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowRunVisualizerEffect.tsx @@ -68,6 +68,9 @@ export const WorkflowRunVisualizerEffect = ({ const isWorkflowFilteringEnabled = useIsFeatureEnabled( FeatureFlagKey.IS_WORKFLOW_FILTERING_ENABLED, ); + const isWorkflowBranchEnabled = useIsFeatureEnabled( + FeatureFlagKey.IS_WORKFLOW_BRANCH_ENABLED, + ); useEffect(() => { setWorkflowRunId(workflowRunId); @@ -120,6 +123,7 @@ export const WorkflowRunVisualizerEffect = ({ steps: workflowRunState.flow.steps, stepInfos: workflowRunState.stepInfos, isWorkflowFilteringEnabled, + isWorkflowBranchEnabled, }); if (workflowDiagramStatus !== 'done') { @@ -190,6 +194,7 @@ export const WorkflowRunVisualizerEffect = ({ [ flowState, getIcon, + isWorkflowBranchEnabled, isWorkflowFilteringEnabled, openWorkflowRunViewStepInCommandMenu, workflowDiagramState, diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/utils/__tests__/generateWorkflowRunDiagram.test.ts b/packages/twenty-front/src/modules/workflow/workflow-diagram/utils/__tests__/generateWorkflowRunDiagram.test.ts index 52ee64aec66..ec6758c8f3f 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-diagram/utils/__tests__/generateWorkflowRunDiagram.test.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/utils/__tests__/generateWorkflowRunDiagram.test.ts @@ -105,6 +105,7 @@ describe('generateWorkflowRunDiagram', () => { steps, stepInfos, isWorkflowFilteringEnabled: true, + isWorkflowBranchEnabled: true, }); expect(result).toMatchInlineSnapshot(` @@ -334,6 +335,7 @@ describe('generateWorkflowRunDiagram', () => { steps, stepInfos, isWorkflowFilteringEnabled: true, + isWorkflowBranchEnabled: true, }); expect(result).toMatchInlineSnapshot(` @@ -563,6 +565,7 @@ describe('generateWorkflowRunDiagram', () => { steps, stepInfos, isWorkflowFilteringEnabled: true, + isWorkflowBranchEnabled: true, }); expect(result).toMatchInlineSnapshot(` @@ -811,6 +814,7 @@ describe('generateWorkflowRunDiagram', () => { steps, stepInfos, isWorkflowFilteringEnabled: true, + isWorkflowBranchEnabled: true, }); expect(result).toMatchInlineSnapshot(` @@ -1039,6 +1043,7 @@ describe('generateWorkflowRunDiagram', () => { steps, stepInfos, isWorkflowFilteringEnabled: true, + isWorkflowBranchEnabled: true, }); expect(result).toMatchInlineSnapshot(` diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/utils/__tests__/transformFilterNodesAsEdges.test.ts b/packages/twenty-front/src/modules/workflow/workflow-diagram/utils/__tests__/transformFilterNodesAsEdges.test.ts index 09591e23192..60aaa01f014 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-diagram/utils/__tests__/transformFilterNodesAsEdges.test.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/utils/__tests__/transformFilterNodesAsEdges.test.ts @@ -46,6 +46,7 @@ describe('transformFilterNodesAsEdges', () => { nodes: diagram.nodes, edges: diagram.edges, defaultFilterEdgeType: 'filter--editable', + isWorkflowBranchEnabled: true, }); expect(result.nodes).toEqual(diagram.nodes); @@ -112,6 +113,7 @@ describe('transformFilterNodesAsEdges', () => { nodes: diagram.nodes, edges: diagram.edges, defaultFilterEdgeType: 'filter--editable', + isWorkflowBranchEnabled: true, }); // Should only have nodes A and C @@ -149,7 +151,8 @@ describe('transformFilterNodesAsEdges', () => { type: 'filter--editable', source: 'A', target: 'C', - deletable: false, + selectable: true, + deletable: true, data: { edgeType: 'filter', stepId: 'B', @@ -256,6 +259,7 @@ describe('transformFilterNodesAsEdges', () => { nodes: diagram.nodes, edges: diagram.edges, defaultFilterEdgeType: 'filter--editable', + isWorkflowBranchEnabled: true, }); // Should only have nodes A, C, and D @@ -275,7 +279,8 @@ describe('transformFilterNodesAsEdges', () => { type: 'filter--editable', source: 'A', target: 'C', - deletable: false, + selectable: true, + deletable: true, data: { edgeType: 'filter', name: 'Filter B1', @@ -293,7 +298,8 @@ describe('transformFilterNodesAsEdges', () => { type: 'filter--editable', source: 'C', target: 'D', - deletable: false, + selectable: true, + deletable: true, data: { edgeType: 'filter', name: 'Filter B2', @@ -346,6 +352,7 @@ describe('transformFilterNodesAsEdges', () => { nodes: diagram.nodes, edges: diagram.edges, defaultFilterEdgeType: 'filter--editable', + isWorkflowBranchEnabled: true, }); // Should only have node A (filter node B is removed) @@ -428,6 +435,7 @@ describe('transformFilterNodesAsEdges', () => { nodes: diagram.nodes, edges: diagram.edges, defaultFilterEdgeType: 'filter--editable', + isWorkflowBranchEnabled: true, }); // Should have trigger and C nodes @@ -465,7 +473,8 @@ describe('transformFilterNodesAsEdges', () => { type: 'filter--editable', source: 'trigger', target: 'C', - deletable: false, + selectable: true, + deletable: true, data: { edgeType: 'filter', name: 'Filter B', @@ -476,4 +485,86 @@ describe('transformFilterNodesAsEdges', () => { }, ]); }); + + it('should set selectable and deletable to false when isWorkflowBranchEnabled is false', () => { + const diagram: WorkflowDiagram = { + nodes: [ + { + id: 'A', + data: { + nodeType: 'action', + actionType: 'CODE', + name: 'Step A', + hasNextStepIds: true, + position: { x: 0, y: 0 }, + stepId: 'A', + }, + position: { x: 0, y: 0 }, + }, + { + id: 'B', + data: { + nodeType: 'action', + actionType: 'FILTER', + name: 'Filter B', + hasNextStepIds: true, + position: { x: 0, y: 150 }, + stepId: 'B', + }, + position: { x: 0, y: 150 }, + }, + { + id: 'C', + data: { + nodeType: 'action', + actionType: 'SEND_EMAIL', + name: 'Step C', + hasNextStepIds: false, + position: { x: 0, y: 300 }, + stepId: 'C', + }, + position: { x: 0, y: 300 }, + }, + ], + edges: [ + { + id: 'A-B', + source: 'A', + target: 'B', + data: { edgeType: 'default' }, + }, + { + id: 'B-C', + source: 'B', + target: 'C', + data: { edgeType: 'default' }, + }, + ], + }; + + const result = transformFilterNodesAsEdges({ + nodes: diagram.nodes, + edges: diagram.edges, + defaultFilterEdgeType: 'filter--editable', + isWorkflowBranchEnabled: false, + }); + + // Should have one edge with filter data + expect(result.edges).toHaveLength(1); + expect(result.edges[0]).toEqual({ + id: 'A-C-filter-B', + type: 'filter--editable', + source: 'A', + target: 'C', + selectable: false, + deletable: false, + data: { + edgeType: 'filter', + stepId: 'B', + name: 'Filter B', + runStatus: undefined, + filterSettings: {}, + }, + }); + }); }); diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/utils/generateWorkflowRunDiagram.ts b/packages/twenty-front/src/modules/workflow/workflow-diagram/utils/generateWorkflowRunDiagram.ts index dce14185ab4..67cd553378f 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-diagram/utils/generateWorkflowRunDiagram.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/utils/generateWorkflowRunDiagram.ts @@ -20,11 +20,13 @@ export const generateWorkflowRunDiagram = ({ steps, stepInfos, isWorkflowFilteringEnabled, + isWorkflowBranchEnabled, }: { trigger: WorkflowTrigger; steps: Array; stepInfos: WorkflowRunStepInfos | undefined; isWorkflowFilteringEnabled: boolean; + isWorkflowBranchEnabled: boolean; }): { diagram: WorkflowRunDiagram; stepToOpenByDefault: @@ -119,6 +121,7 @@ export const generateWorkflowRunDiagram = ({ nodes: workflowRunDiagramNodes, edges: workflowRunDiagramEdges, defaultFilterEdgeType: 'filter--run', + isWorkflowBranchEnabled, }), stepToOpenByDefault, }; diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/utils/getWorkflowVersionDiagram.ts b/packages/twenty-front/src/modules/workflow/workflow-diagram/utils/getWorkflowVersionDiagram.ts index 50145681d01..9b2999461fb 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-diagram/utils/getWorkflowVersionDiagram.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/utils/getWorkflowVersionDiagram.ts @@ -4,8 +4,8 @@ import { type WorkflowDiagramEdgeType, } from '@/workflow/workflow-diagram/types/WorkflowDiagram'; import { generateWorkflowDiagram } from '@/workflow/workflow-diagram/utils/generateWorkflowDiagram'; -import { isDefined } from 'twenty-shared/utils'; import { transformFilterNodesAsEdges } from '@/workflow/workflow-diagram/utils/transformFilterNodesAsEdges'; +import { isDefined } from 'twenty-shared/utils'; const EMPTY_DIAGRAM: WorkflowDiagram = { nodes: [], @@ -57,5 +57,6 @@ export const getWorkflowVersionDiagram = ({ nodes: diagram.nodes, edges: diagram.edges, defaultFilterEdgeType: isEditable ? 'filter--editable' : 'filter--readonly', + isWorkflowBranchEnabled: isWorkflowBranchEnabled === true, }); }; diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/utils/transformFilterNodesAsEdges.ts b/packages/twenty-front/src/modules/workflow/workflow-diagram/utils/transformFilterNodesAsEdges.ts index bf05727874e..ce943756f70 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-diagram/utils/transformFilterNodesAsEdges.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/utils/transformFilterNodesAsEdges.ts @@ -12,10 +12,12 @@ export const transformFilterNodesAsEdges = < nodes, edges, defaultFilterEdgeType, + isWorkflowBranchEnabled, }: { nodes: T[]; edges: U[]; defaultFilterEdgeType: WorkflowDiagramEdgeType; + isWorkflowBranchEnabled: boolean; }): { nodes: T[]; edges: U[] } => { const filterNodes = nodes.filter( (node) => @@ -53,7 +55,8 @@ export const transformFilterNodesAsEdges = < type: defaultFilterEdgeType, id: `${incomingEdge.source}-${outgoingEdge.target}-filter-${filterNode.id}`, target: outgoingEdge.target, - deletable: false, + selectable: isWorkflowBranchEnabled === true, + deletable: isWorkflowBranchEnabled === true, data: { ...incomingEdge.data, edgeType: 'filter',