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 <me@olegivaniv.com>
Co-authored-by: Oleg Ivaniv <me@olegivaniv.com>
This commit is contained in:
Jan Oberhauser 2023-10-26 09:46:26 +02:00 committed by GitHub
parent 742c8a8534
commit df89685e15
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 44 additions and 15 deletions

View file

@ -30,6 +30,7 @@ describe('Canvas Node Manipulation and Navigation', () => {
it('should add switch node and test connections', () => { it('should add switch node and test connections', () => {
const desiredOutputs = 4; const desiredOutputs = 4;
WorkflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME, true);
WorkflowPage.actions.addNodeToCanvas(SWITCH_NODE_NAME, true, true); WorkflowPage.actions.addNodeToCanvas(SWITCH_NODE_NAME, true, true);
for (let i = 0; i < desiredOutputs; i++) { 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.addNodeToCanvas(EDIT_FIELDS_SET_NODE_NAME, false);
WorkflowPage.actions.zoomToFit(); 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(); WorkflowPage.actions.saveWorkflowOnButtonClick();
cy.reload(); cy.reload();
cy.waitForLoad(); 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 // Make sure all connections are there after reload
for (let i = 0; i < desiredOutputs; i++) { for (let i = 0; i < desiredOutputs; i++) {
const setName = `${EDIT_FIELDS_SET_NODE_NAME}${i > 0 ? i : ''}`; const setName = `${EDIT_FIELDS_SET_NODE_NAME}${i > 0 ? i : ''}`;

View file

@ -265,6 +265,7 @@ import type {
IWorkflowBase, IWorkflowBase,
Workflow, Workflow,
ConnectionTypes, ConnectionTypes,
INodeOutputConfiguration,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { import {
deepCopy, deepCopy,
@ -1970,29 +1971,47 @@ export default defineComponent({
this.canvasStore.newNodeInsertPosition = null; this.canvasStore.newNodeInsertPosition = null;
} else { } else {
let yOffset = 0; let yOffset = 0;
const workflow = this.getCurrentWorkflow();
if (lastSelectedConnection) { if (lastSelectedConnection) {
const sourceNodeType = this.nodeTypesStore.getNodeType( const sourceNodeType = this.nodeTypesStore.getNodeType(
lastSelectedNode.type, lastSelectedNode.type,
lastSelectedNode.typeVersion, lastSelectedNode.typeVersion,
); );
const offsets = [ if (sourceNodeType) {
[-100, 100], const offsets = [
[-140, 0, 140], [-100, 100],
[-240, -100, 100, 240], [-140, 0, 140],
]; [-240, -100, 100, 240],
if (sourceNodeType && sourceNodeType.outputs.length > 1) { ];
const offset = offsets[sourceNodeType.outputs.length - 2]; const sourceNodeOutputs = NodeHelpers.getNodeOutputs(
const sourceOutputIndex = lastSelectedConnection.__meta workflow,
? lastSelectedConnection.__meta.sourceOutputIndex lastSelectedNode!,
: 0; sourceNodeType,
yOffset = offset[sourceOutputIndex]; );
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(); let outputs: Array<ConnectionTypes | INodeOutputConfiguration> = [];
const workflowNode = workflow.getNode(newNodeData.name); try {
const outputs = NodeHelpers.getNodeOutputs(workflow, workflowNode!, nodeTypeData); // 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 outputTypes = NodeHelpers.getConnectionTypes(outputs);
const lastSelectedNodeType = this.nodeTypesStore.getNodeType( const lastSelectedNodeType = this.nodeTypesStore.getNodeType(
lastSelectedNode.type, lastSelectedNode.type,
@ -2000,7 +2019,10 @@ export default defineComponent({
); );
// If node has only scoped outputs, position it below the last selected node // 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 lastSelectedNodeWorkflow = workflow.getNode(lastSelectedNode.name);
const lastSelectedInputs = NodeHelpers.getNodeInputs( const lastSelectedInputs = NodeHelpers.getNodeInputs(
workflow, workflow,
@ -2025,6 +2047,7 @@ export default defineComponent({
[100, 0], [100, 0],
); );
} else { } else {
// Has only main outputs or no outputs at all
const inputs = NodeHelpers.getNodeInputs( const inputs = NodeHelpers.getNodeInputs(
workflow, workflow,
lastSelectedNode, lastSelectedNode,