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 6e32b7aee2..1632f1bf48 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 @@ -41,6 +41,7 @@ import { type OnBeforeDelete, type OnDelete, type OnNodeDrag, + type OnReconnect, } from '@xyflow/react'; import '@xyflow/react/dist/style.css'; import React, { @@ -102,6 +103,9 @@ export const WorkflowDiagramCanvasBase = ({ onConnect, onDeleteEdge, onNodeDragStop, + onReconnect, + onReconnectStart, + onReconnectEnd, handlePaneContextMenu, nodesConnectable = false, nodesDraggable = false, @@ -136,6 +140,9 @@ export const WorkflowDiagramCanvasBase = ({ onConnect?: (params: WorkflowConnection) => void; onDeleteEdge?: (edge: WorkflowDiagramEdge) => void; onNodeDragStop?: OnNodeDrag; + onReconnect?: OnReconnect; + onReconnectStart?: () => void; + onReconnectEnd?: () => void; nodesConnectable?: boolean; nodesDraggable?: boolean; handlePaneContextMenu?: ({ @@ -476,6 +483,9 @@ export const WorkflowDiagramCanvasBase = ({ onNodesChange={handleNodesChanges} onEdgesChange={handleEdgesChange} onConnect={handleConnect} + onReconnect={onReconnect} + onReconnectStart={onReconnectStart} + onReconnectEnd={onReconnectEnd} onNodeDragStop={onNodeDragStop} onBeforeDelete={onBeforeDelete} onDelete={onDelete} diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramCanvasEditable.tsx b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramCanvasEditable.tsx index 0747a2e47a..297b4a69b5 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramCanvasEditable.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/components/WorkflowDiagramCanvasEditable.tsx @@ -11,6 +11,8 @@ import { type WorkflowDiagramEdge, type WorkflowDiagramNode, } from '@/workflow/workflow-diagram/types/WorkflowDiagram'; +import { assertEdgeHasDefinedHandlesOrThrow } from '@/workflow/workflow-diagram/utils/assertEdgeHasDefinedHandlesOrThrow'; +import { assertWorkflowConnectionOrThrow } from '@/workflow/workflow-diagram/utils/assertWorkflowConnectionOrThrow'; import { getWorkflowVersionStatusTagProps } from '@/workflow/workflow-diagram/utils/getWorkflowVersionStatusTagProps'; import { WorkflowDiagramBlankEdge } from '@/workflow/workflow-diagram/workflow-edges/components/WorkflowDiagramBlankEdge'; import { WorkflowDiagramDefaultEdgeEditable } from '@/workflow/workflow-diagram/workflow-edges/components/WorkflowDiagramDefaultEdgeEditable'; @@ -21,8 +23,13 @@ import { useCreateEdge } from '@/workflow/workflow-steps/hooks/useCreateEdge'; import { useDeleteEdge } from '@/workflow/workflow-steps/hooks/useDeleteEdge'; import { useUpdateStep } from '@/workflow/workflow-steps/hooks/useUpdateStep'; import { useUpdateWorkflowVersionTrigger } from '@/workflow/workflow-trigger/hooks/useUpdateWorkflowVersionTrigger'; -import { addEdge, ReactFlowProvider } from '@xyflow/react'; -import React from 'react'; +import { + addEdge, + ReactFlowProvider, + type Connection, + type Edge, +} from '@xyflow/react'; +import React, { useCallback } from 'react'; import { isDefined } from 'twenty-shared/utils'; export const WorkflowDiagramCanvasEditable = () => { @@ -73,6 +80,30 @@ export const WorkflowDiagramCanvasEditable = () => { }); }; + const handleReconnect = useCallback( + async (oldEdge: Edge, connection: Connection) => { + assertEdgeHasDefinedHandlesOrThrow(oldEdge); + assertWorkflowConnectionOrThrow(connection); + + await deleteEdge({ + source: oldEdge.source, + target: oldEdge.target, + sourceConnectionOptions: getConnectionOptionsForSourceHandle({ + sourceHandleId: oldEdge.sourceHandle, + }), + }); + + await createEdge({ + source: connection.source, + target: connection.target, + connectionOptions: getConnectionOptionsForSourceHandle({ + sourceHandleId: connection.sourceHandle, + }), + }); + }, + [deleteEdge, createEdge], + ); + const onDeleteEdge = async (edge: WorkflowDiagramEdge) => { await deleteEdge({ source: edge.source, @@ -140,6 +171,7 @@ export const WorkflowDiagramCanvasEditable = () => { tagColor={tagProps.color} tagText={tagProps.text} onConnect={onConnect} + onReconnect={handleReconnect} onNodeDragStop={onNodeDragStop} handlePaneContextMenu={handlePaneContextMenu} nodesConnectable diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/utils/__tests__/getWorkflowVersionDiagram.test.ts b/packages/twenty-front/src/modules/workflow/workflow-diagram/utils/__tests__/getWorkflowVersionDiagram.test.ts index 217d23cbf1..af78a96581 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-diagram/utils/__tests__/getWorkflowVersionDiagram.test.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/utils/__tests__/getWorkflowVersionDiagram.test.ts @@ -159,6 +159,7 @@ describe('getWorkflowVersionDiagram', () => { "id": "8f3b2121-f194-4ba4-9fbf-0", "markerEnd": "edge-branch-arrow-default", "markerStart": undefined, + "reconnectable": "target", "selectable": true, "source": "trigger", "sourceHandle": "default", diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/utils/generateNodesAndEdgesForDefaultNode.ts b/packages/twenty-front/src/modules/workflow/workflow-diagram/utils/generateNodesAndEdgesForDefaultNode.ts index d6cfdf9e69..eaa4709b1a 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-diagram/utils/generateNodesAndEdgesForDefaultNode.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/utils/generateNodesAndEdgesForDefaultNode.ts @@ -79,8 +79,12 @@ export const generateNodesAndEdgesForDefaultNode = ({ nextStepId, }), }, - ...(edgeTypeBetweenTwoNodes.includes('editable') - ? { deletable: true, selectable: true } + ...(workflowContext === 'workflow' + ? { + deletable: true, + selectable: true, + reconnectable: 'target', + } : {}), }); } diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/utils/generateNodesAndEdgesForIteratorNode.ts b/packages/twenty-front/src/modules/workflow/workflow-diagram/utils/generateNodesAndEdgesForIteratorNode.ts index 9fac66faa8..4db16bd5a5 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-diagram/utils/generateNodesAndEdgesForIteratorNode.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/utils/generateNodesAndEdgesForIteratorNode.ts @@ -91,8 +91,12 @@ export const generateNodesAndEdgesForIteratorNode = ({ source: step.id, sourceHandle: WORKFLOW_DIAGRAM_ITERATOR_NODE_LOOP_HANDLE_ID, target: initialLoopStepId, - ...(edgeTypeBetweenTwoNodes.includes('editable') - ? { deletable: true, selectable: true } + ...(workflowContext === 'workflow' + ? { + deletable: true, + selectable: true, + reconnectable: 'target', + } : {}), targetHandle: WORKFLOW_DIAGRAM_NODE_DEFAULT_TARGET_HANDLE_ID, data: { @@ -115,8 +119,12 @@ export const generateNodesAndEdgesForIteratorNode = ({ source: step.id, sourceHandle: WORKFLOW_DIAGRAM_NODE_DEFAULT_SOURCE_HANDLE_ID, target: nextStepId, - ...(edgeTypeBetweenTwoNodes.includes('editable') - ? { deletable: true, selectable: true } + ...(workflowContext === 'workflow' + ? { + deletable: true, + selectable: true, + reconnectable: 'target', + } : {}), targetHandle: WORKFLOW_DIAGRAM_NODE_DEFAULT_TARGET_HANDLE_ID, data: { 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 a3c588f8cd..09519fe302 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 @@ -52,8 +52,12 @@ export const generateWorkflowDiagram = ({ source: TRIGGER_STEP_ID, sourceHandle: WORKFLOW_DIAGRAM_NODE_DEFAULT_SOURCE_HANDLE_ID, target: stepLinkToTriggerId, - ...(edgeTypeBetweenTwoNodes.includes('editable') - ? { deletable: true, selectable: true } + ...(workflowContext === 'workflow' + ? { + deletable: true, + selectable: true, + reconnectable: 'target', + } : {}), targetHandle: WORKFLOW_DIAGRAM_NODE_DEFAULT_TARGET_HANDLE_ID, });