diff --git a/cypress/e2e/12-canvas.cy.ts b/cypress/e2e/12-canvas.cy.ts index 9e2b8abe06..94900b8206 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 76fac04a15..0d9459f3ab 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,