From df89685e1548219f4c06614287abafbc96697817 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Thu, 26 Oct 2023 09:46:26 +0200 Subject: [PATCH] fix(editor): Fixes the issue that Switch Node can not be created (#7516) Fixes the issue that currently no Switch-Node can be created by for example pressing + on the parent node or via tab when another node is already selected. Github issue / Community forum post (link here to close automatically): --------- Signed-off-by: Oleg Ivaniv Co-authored-by: Oleg Ivaniv --- cypress/e2e/12-canvas.cy.ts | 6 +++ packages/editor-ui/src/views/NodeView.vue | 53 ++++++++++++++++------- 2 files changed, 44 insertions(+), 15 deletions(-) diff --git a/cypress/e2e/12-canvas.cy.ts b/cypress/e2e/12-canvas.cy.ts index 9e2b8abe06c48..94900b82065ad 100644 --- a/cypress/e2e/12-canvas.cy.ts +++ b/cypress/e2e/12-canvas.cy.ts @@ -30,6 +30,7 @@ describe('Canvas Node Manipulation and Navigation', () => { it('should add switch node and test connections', () => { const desiredOutputs = 4; + WorkflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME, true); WorkflowPage.actions.addNodeToCanvas(SWITCH_NODE_NAME, true, true); for (let i = 0; i < desiredOutputs; i++) { @@ -43,9 +44,14 @@ describe('Canvas Node Manipulation and Navigation', () => { WorkflowPage.actions.addNodeToCanvas(EDIT_FIELDS_SET_NODE_NAME, false); WorkflowPage.actions.zoomToFit(); } + WorkflowPage.getters.nodeViewBackground().click({ force: true }); + WorkflowPage.getters.canvasNodePlusEndpointByName(`${EDIT_FIELDS_SET_NODE_NAME}3`).click(); + WorkflowPage.actions.addNodeToCanvas(SWITCH_NODE_NAME, false); WorkflowPage.actions.saveWorkflowOnButtonClick(); cy.reload(); cy.waitForLoad(); + // Make sure outputless switch was connected correctly + cy.get(`[data-target-node="${SWITCH_NODE_NAME}1"][data-source-node="${EDIT_FIELDS_SET_NODE_NAME}3"]`).should('be.visible'); // Make sure all connections are there after reload for (let i = 0; i < desiredOutputs; i++) { const setName = `${EDIT_FIELDS_SET_NODE_NAME}${i > 0 ? i : ''}`; diff --git a/packages/editor-ui/src/views/NodeView.vue b/packages/editor-ui/src/views/NodeView.vue index 76fac04a15772..0d9459f3abc33 100644 --- a/packages/editor-ui/src/views/NodeView.vue +++ b/packages/editor-ui/src/views/NodeView.vue @@ -265,6 +265,7 @@ import type { IWorkflowBase, Workflow, ConnectionTypes, + INodeOutputConfiguration, } from 'n8n-workflow'; import { deepCopy, @@ -1970,29 +1971,47 @@ export default defineComponent({ this.canvasStore.newNodeInsertPosition = null; } else { let yOffset = 0; + const workflow = this.getCurrentWorkflow(); if (lastSelectedConnection) { const sourceNodeType = this.nodeTypesStore.getNodeType( lastSelectedNode.type, lastSelectedNode.typeVersion, ); - const offsets = [ - [-100, 100], - [-140, 0, 140], - [-240, -100, 100, 240], - ]; - if (sourceNodeType && sourceNodeType.outputs.length > 1) { - const offset = offsets[sourceNodeType.outputs.length - 2]; - const sourceOutputIndex = lastSelectedConnection.__meta - ? lastSelectedConnection.__meta.sourceOutputIndex - : 0; - yOffset = offset[sourceOutputIndex]; + if (sourceNodeType) { + const offsets = [ + [-100, 100], + [-140, 0, 140], + [-240, -100, 100, 240], + ]; + const sourceNodeOutputs = NodeHelpers.getNodeOutputs( + workflow, + lastSelectedNode!, + sourceNodeType, + ); + const sourceNodeOutputTypes = NodeHelpers.getConnectionTypes(sourceNodeOutputs); + const sourceNodeOutputMainOutputs = sourceNodeOutputTypes.filter( + (output) => output === NodeConnectionType.Main, + ); + if (sourceNodeOutputMainOutputs.length > 1) { + const offset = offsets[sourceNodeOutputMainOutputs.length - 2]; + const sourceOutputIndex = lastSelectedConnection.__meta + ? lastSelectedConnection.__meta.sourceOutputIndex + : 0; + yOffset = offset[sourceOutputIndex]; + } } } - const workflow = this.getCurrentWorkflow(); - const workflowNode = workflow.getNode(newNodeData.name); - const outputs = NodeHelpers.getNodeOutputs(workflow, workflowNode!, nodeTypeData); + let outputs: Array = []; + try { + // It fails when the outputs are an expression. As those node have + // normally no outputs by default and the only reason we need the + // outputs here is to calculate the position it is fine to assume + // that they have no outputs and are so treated as a regular node + // with only "main" outputs. + outputs = NodeHelpers.getNodeOutputs(workflow, newNodeData!, nodeTypeData); + } catch (e) {} const outputTypes = NodeHelpers.getConnectionTypes(outputs); const lastSelectedNodeType = this.nodeTypesStore.getNodeType( lastSelectedNode.type, @@ -2000,7 +2019,10 @@ export default defineComponent({ ); // If node has only scoped outputs, position it below the last selected node - if (outputTypes.every((outputName) => outputName !== NodeConnectionType.Main)) { + if ( + outputTypes.length > 0 && + outputTypes.every((outputName) => outputName !== NodeConnectionType.Main) + ) { const lastSelectedNodeWorkflow = workflow.getNode(lastSelectedNode.name); const lastSelectedInputs = NodeHelpers.getNodeInputs( workflow, @@ -2025,6 +2047,7 @@ export default defineComponent({ [100, 0], ); } else { + // Has only main outputs or no outputs at all const inputs = NodeHelpers.getNodeInputs( workflow, lastSelectedNode,