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..
## 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;