diff --git a/cypress/e2e/12-canvas.cy.ts b/cypress/e2e/12-canvas.cy.ts index 8170b9caa1..0d4a0f414e 100644 --- a/cypress/e2e/12-canvas.cy.ts +++ b/cypress/e2e/12-canvas.cy.ts @@ -370,4 +370,40 @@ describe('Canvas Node Manipulation and Navigation', () => { NDVDialog.actions.close(); }); }); + + it('should render connections correctly if unkown nodes are present', () => { + const unknownNodeName = 'Unknown node'; + cy.createFixtureWorkflow('workflow-with-unknown-nodes.json', 'Unknown nodes'); + + WorkflowPage.getters.canvasNodeByName(`${unknownNodeName} 1`).should('exist'); + WorkflowPage.getters.canvasNodeByName(`${unknownNodeName} 2`).should('exist'); + WorkflowPage.actions.zoomToFit(); + + cy.draganddrop( + WorkflowPage.getters.getEndpointSelector('plus', `${unknownNodeName} 1`), + WorkflowPage.getters.getEndpointSelector('input', EDIT_FIELDS_SET_NODE_NAME), + ); + + cy.draganddrop( + WorkflowPage.getters.getEndpointSelector('plus', `${unknownNodeName} 2`), + WorkflowPage.getters.getEndpointSelector('input', `${EDIT_FIELDS_SET_NODE_NAME}1`), + ); + + WorkflowPage.actions.executeWorkflow(); + cy.contains('Node not found').should('be.visible'); + + WorkflowPage.getters + .canvasNodeByName(`${unknownNodeName} 1`) + .find('[data-test-id=delete-node-button]') + .click({ force: true }); + + WorkflowPage.getters + .canvasNodeByName(`${unknownNodeName} 2`) + .find('[data-test-id=delete-node-button]') + .click({ force: true }); + + WorkflowPage.actions.executeWorkflow(); + + cy.contains('Node not found').should('not.exist'); + }); }); diff --git a/cypress/fixtures/workflow-with-unknown-nodes.json b/cypress/fixtures/workflow-with-unknown-nodes.json new file mode 100644 index 0000000000..df23de5de0 --- /dev/null +++ b/cypress/fixtures/workflow-with-unknown-nodes.json @@ -0,0 +1,90 @@ +{ + "meta": { + "instanceId": "15bbf37b6a515ccc2f534cabcd8bd171ca33583ff7744b1e9420e5ce68e615bb" + }, + "nodes": [ + { + "parameters": {}, + "id": "40720511-19b6-4421-bdb0-3fb6efef4bc5", + "name": "When clicking \"Execute Workflow\"", + "type": "n8n-nodes-base.manualTrigger", + "typeVersion": 1, + "position": [ + 280, + 320 + ] + }, + { + "parameters": {}, + "id": "acdd1bdc-c642-4ea6-ad67-f4201b640cfa", + "name": "Unknown node 1", + "type": "n8n-nodes-base.thisNodeDoesntExist", + "typeVersion": 1, + "position": [ + 400, + 500 + ] + }, + { + "parameters": {}, + "id": "acdd1bdc-c642-4ea6-ad67-f4201b640ffa", + "name": "Unknown node 2", + "type": "n8n-nodes-base.thisNodeDoesntExistEither", + "typeVersion": 1, + "position": [ + 600, + 500 + ] + }, + { + "parameters": { + "options": {} + }, + "id": "fbe5163b-7474-4741-980a-e4956789be0a", + "name": "Edit Fields", + "type": "n8n-nodes-base.set", + "typeVersion": 3.2, + "position": [ + 500, + 320 + ] + }, + { + "parameters": { + "options": {} + }, + "id": "163313b9-64ff-4ffc-b00f-09b267d8132c", + "name": "Edit Fields1", + "type": "n8n-nodes-base.set", + "typeVersion": 3.2, + "position": [ + 720, + 320 + ] + } + ], + "connections": { + "When clicking \"Execute Workflow\"": { + "main": [ + [ + { + "node": "Edit Fields", + "type": "main", + "index": 0 + } + ] + ] + }, + "Edit Fields": { + "main": [ + [ + { + "node": "Edit Fields1", + "type": "main", + "index": 0 + } + ] + ] + } + } +} diff --git a/packages/editor-ui/src/components/Node.vue b/packages/editor-ui/src/components/Node.vue index 1bb21ee86e..da67476079 100644 --- a/packages/editor-ui/src/components/Node.vue +++ b/packages/editor-ui/src/components/Node.vue @@ -365,37 +365,39 @@ export default defineComponent({ top: this.position[1] + 'px', }; - const workflow = this.workflowsStore.getCurrentWorkflow(); - const inputs = - NodeHelpers.getNodeInputs(workflow, this.node, this.nodeType) || - ([] as Array); - const inputTypes = NodeHelpers.getConnectionTypes(inputs); + if (this.node && this.nodeType) { + const workflow = this.workflowsStore.getCurrentWorkflow(); + const inputs = + NodeHelpers.getNodeInputs(workflow, this.node, this.nodeType) || + ([] as Array); + const inputTypes = NodeHelpers.getConnectionTypes(inputs); - const nonMainInputs = inputTypes.filter((input) => input !== NodeConnectionType.Main); - if (nonMainInputs.length) { - const requiredNonMainInputs = inputs.filter( - (input) => typeof input !== 'string' && input.required, - ); + const nonMainInputs = inputTypes.filter((input) => input !== NodeConnectionType.Main); + if (nonMainInputs.length) { + const requiredNonMainInputs = inputs.filter( + (input) => typeof input !== 'string' && input.required, + ); - let spacerCount = 0; - if (NODE_INSERT_SPACER_BETWEEN_INPUT_GROUPS) { - const requiredNonMainInputsCount = requiredNonMainInputs.length; - const optionalNonMainInputsCount = nonMainInputs.length - requiredNonMainInputsCount; - spacerCount = requiredNonMainInputsCount > 0 && optionalNonMainInputsCount > 0 ? 1 : 0; + let spacerCount = 0; + if (NODE_INSERT_SPACER_BETWEEN_INPUT_GROUPS) { + const requiredNonMainInputsCount = requiredNonMainInputs.length; + const optionalNonMainInputsCount = nonMainInputs.length - requiredNonMainInputsCount; + spacerCount = requiredNonMainInputsCount > 0 && optionalNonMainInputsCount > 0 ? 1 : 0; + } + + styles['--configurable-node-input-count'] = nonMainInputs.length + spacerCount; } - styles['--configurable-node-input-count'] = nonMainInputs.length + spacerCount; + const outputs = + NodeHelpers.getNodeOutputs(workflow, this.node, this.nodeType) || + ([] as Array); + + const outputTypes = NodeHelpers.getConnectionTypes(outputs); + + const mainOutputs = outputTypes.filter((output) => output === NodeConnectionType.Main); + styles['--node-main-output-count'] = mainOutputs.length; } - const outputs = - NodeHelpers.getNodeOutputs(workflow, this.node, this.nodeType) || - ([] as Array); - - const outputTypes = NodeHelpers.getConnectionTypes(outputs); - - const mainOutputs = outputTypes.filter((output) => output === NodeConnectionType.Main); - styles['--node-main-output-count'] = mainOutputs.length; - return styles; }, nodeClass(): object {