From 4b86926752fb1304a46385cb46bdf34fda0d53b6 Mon Sep 17 00:00:00 2001 From: Mutasem Aldmour <4711238+mutdmour@users.noreply.github.com> Date: Wed, 20 Dec 2023 10:13:59 +0100 Subject: [PATCH] fix(editor): Fix copy/paste issue when switch node is in workflow (#8103) ## Summary Fix bug where copy/pasting a node breaks the view. Issue seems to be that copied workflow does not have Switch node when workflow is rerendering.. Screenshot 2023-12-19 at 18 16 23 ## Related tickets and issues [Linear ticket ADO-1504](https://linear.app/n8n/issue/ADO-1504/unwanted-nodes-connections-after-copypaste-of-n8n-form-trigger) ## Review / Merge checklist - [X] PR title and summary are descriptive. **Remember, the title automatically goes into the changelog. Use `(no-changelog)` otherwise.** ([conventions](https://github.com/n8n-io/n8n/blob/master/.github/pull_request_title_conventions.md)) - [ ] [Docs updated](https://github.com/n8n-io/n8n-docs) or follow-up ticket created. - [x] Tests included. > A bug is not considered fixed, unless a test is added to prevent it from happening again. > A feature is not complete without tests. --- cypress/e2e/10-undo-redo.cy.ts | 29 +++++++ .../fixtures/Test_workflow_form_switch.json | 78 +++++++++++++++++++ cypress/pages/workflow.ts | 3 - packages/editor-ui/src/components/Node.vue | 7 +- .../editor-ui/src/stores/nodeTypes.store.ts | 3 + 5 files changed, 114 insertions(+), 6 deletions(-) create mode 100644 cypress/fixtures/Test_workflow_form_switch.json diff --git a/cypress/e2e/10-undo-redo.cy.ts b/cypress/e2e/10-undo-redo.cy.ts index 6d3337456d..e904d891b1 100644 --- a/cypress/e2e/10-undo-redo.cy.ts +++ b/cypress/e2e/10-undo-redo.cy.ts @@ -325,4 +325,33 @@ describe('Undo/Redo', () => { WorkflowPage.getters.nodeConnections().should('have.length', 2); }); }); + + it('should be able to copy and paste pinned data nodes in workflows with dynamic Switch node', () => { + cy.fixture('Test_workflow_form_switch.json').then((data) => { + cy.get('body').paste(JSON.stringify(data)); + }); + WorkflowPage.actions.zoomToFit(); + + WorkflowPage.getters.canvasNodes().should('have.length', 2); + WorkflowPage.getters.nodeConnections().should('have.length', 1); + cy.get(WorkflowPage.getters.getEndpointSelector('input', 'Switch')).should('have.length', 1); + cy.get(WorkflowPage.getters.getEndpointSelector('input', 'Switch')) + .should('have.css', 'left', `637px`) + .should('have.css', 'top', `501px`); + + cy.fixture('Test_workflow_form_switch.json').then((data) => { + cy.get('body').paste(JSON.stringify(data)); + }); + WorkflowPage.getters.canvasNodes().should('have.length', 4); + WorkflowPage.getters.nodeConnections().should('have.length', 2); + + WorkflowPage.actions.hitUndo(); + + WorkflowPage.getters.canvasNodes().should('have.length', 2); + WorkflowPage.getters.nodeConnections().should('have.length', 1); + cy.get(WorkflowPage.getters.getEndpointSelector('input', 'Switch')).should('have.length', 1); + cy.get(WorkflowPage.getters.getEndpointSelector('input', 'Switch')) + .should('have.css', 'left', `637px`) + .should('have.css', 'top', `501px`); + }); }); diff --git a/cypress/fixtures/Test_workflow_form_switch.json b/cypress/fixtures/Test_workflow_form_switch.json new file mode 100644 index 0000000000..78349c3ae5 --- /dev/null +++ b/cypress/fixtures/Test_workflow_form_switch.json @@ -0,0 +1,78 @@ +{ + "name": "My workflow 8", + "nodes": [ + { + "parameters": { + "path": "d1cba915-ca18-4425-bcfb-133205fc815a", + "formTitle": "test", + "formFields": { + "values": [ + { + "fieldLabel": "test" + } + ] + }, + "options": {} + }, + "id": "9e685367-fb94-4376-a9a4-7f311d9f7e2d", + "name": "n8n Form Trigger", + "type": "n8n-nodes-base.formTrigger", + "typeVersion": 2, + "position": [ + 620, + 580 + ], + "webhookId": "d1cba915-ca18-4425-bcfb-133205fc815a" + }, + { + "parameters": {}, + "id": "0f4dfe66-51c0-4378-9eab-680f8140a572", + "name": "Switch", + "type": "n8n-nodes-base.switch", + "typeVersion": 2, + "position": [ + 800, + 580 + ] + } + ], + "pinData": { + "n8n Form Trigger": [ + { + "json": { + "name": "First item", + "code": 1 + } + }, + { + "json": { + "name": "Second item", + "code": 2 + } + } + ] + }, + "connections": { + "n8n Form Trigger": { + "main": [ + [ + { + "node": "Switch", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "active": false, + "settings": { + "executionOrder": "v1" + }, + "versionId": "d6c14bc8-a69f-47bb-b5ba-fe6e9db0a3a4", + "id": "UQSimcMQJGbTeTLG", + "meta": { + "instanceId": "a786b722078489c1fa382391a9f3476c2784761624deb2dfb4634827256d51a0" + }, + "tags": [] +} \ No newline at end of file diff --git a/cypress/pages/workflow.ts b/cypress/pages/workflow.ts index d775a0d61d..853877ed0f 100644 --- a/cypress/pages/workflow.ts +++ b/cypress/pages/workflow.ts @@ -351,9 +351,6 @@ export class WorkflowPage extends BasePage { hitCopy: () => { cy.get('body').type(META_KEY, { delay: 500, release: false }).type('c'); }, - hitPaste: () => { - cy.get('body').type(META_KEY, { delay: 500, release: false }).type('P'); - }, hitPinNodeShortcut: () => { cy.get('body').type('p'); }, diff --git a/packages/editor-ui/src/components/Node.vue b/packages/editor-ui/src/components/Node.vue index d250ed39cc..785ff3beb8 100644 --- a/packages/editor-ui/src/components/Node.vue +++ b/packages/editor-ui/src/components/Node.vue @@ -377,9 +377,10 @@ export default defineComponent({ styles['--configurable-node-input-count'] = nonMainInputs.length + spacerCount; } - const outputs = - NodeHelpers.getNodeOutputs(this.workflow, this.node, this.nodeType) || - ([] as Array); + let outputs = [] as Array; + if (this.workflow.nodes[this.node.name]) { + outputs = NodeHelpers.getNodeOutputs(this.workflow, this.node, this.nodeType); + } const outputTypes = NodeHelpers.getConnectionTypes(outputs); diff --git a/packages/editor-ui/src/stores/nodeTypes.store.ts b/packages/editor-ui/src/stores/nodeTypes.store.ts index 63776aeab3..9e5deda06a 100644 --- a/packages/editor-ui/src/stores/nodeTypes.store.ts +++ b/packages/editor-ui/src/stores/nodeTypes.store.ts @@ -96,6 +96,9 @@ export const useNodeTypesStore = defineStore(STORES.NODE_TYPES, { }, isConfigNode() { return (workflow: Workflow, node: INode, nodeTypeName: string): boolean => { + if (!workflow.nodes[node.name]) { + return false; + } const nodeType = this.getNodeType(nodeTypeName); if (!nodeType) { return false;