From ebba7c87cdc96b08f8a2075d6f4907f7671dea4b Mon Sep 17 00:00:00 2001 From: Tomi Turtiainen <10324676+tomi@users.noreply.github.com> Date: Fri, 7 Jun 2024 16:12:59 +0300 Subject: [PATCH] fix(editor): Color node connections correctly in execution preview for nodes that have pinned data (#9669) --- cypress/e2e/20-workflow-executions.cy.ts | 3 +- ...06-ADO-pinned-data-execution-preview.cy.ts | 65 ++++++++++++++++++ cypress/fixtures/Webhook_set_pinned.json | 67 +++++++++++++++++++ cypress/pages/workflow-executions-tab.ts | 1 + packages/editor-ui/src/components/Node.vue | 14 ++-- .../src/components/WorkflowPreview.vue | 11 +-- .../src/composables/useContextMenu.ts | 4 +- packages/editor-ui/src/constants.ts | 2 +- packages/editor-ui/src/views/NodeView.vue | 28 +++++--- 9 files changed, 169 insertions(+), 26 deletions(-) create mode 100644 cypress/e2e/2106-ADO-pinned-data-execution-preview.cy.ts create mode 100644 cypress/fixtures/Webhook_set_pinned.json diff --git a/cypress/e2e/20-workflow-executions.cy.ts b/cypress/e2e/20-workflow-executions.cy.ts index 3bcea60d6783b..bf5ecb17b3e53 100644 --- a/cypress/e2e/20-workflow-executions.cy.ts +++ b/cypress/e2e/20-workflow-executions.cy.ts @@ -84,7 +84,8 @@ describe('Current Workflow Executions', () => { executionsTab.actions.switchToExecutionsTab(); cy.wait(['@getExecution']); - cy.getByTestId('workflow-preview-iframe') + executionsTab.getters + .workflowExecutionPreviewIframe() .should('be.visible') .its('0.contentDocument.body') // Access the body of the iframe document .should('not.be.empty') // Ensure the body is not empty diff --git a/cypress/e2e/2106-ADO-pinned-data-execution-preview.cy.ts b/cypress/e2e/2106-ADO-pinned-data-execution-preview.cy.ts new file mode 100644 index 0000000000000..386d8762aa760 --- /dev/null +++ b/cypress/e2e/2106-ADO-pinned-data-execution-preview.cy.ts @@ -0,0 +1,65 @@ +import { v4 as uuid } from 'uuid'; +import { WorkflowExecutionsTab, WorkflowPage as WorkflowPageClass } from '../pages'; +import { BACKEND_BASE_URL } from '../constants'; + +const workflowPage = new WorkflowPageClass(); +const executionsTab = new WorkflowExecutionsTab(); + +describe('ADO-2106 connections should be colored correctly for pinned data in executions preview', () => { + beforeEach(() => { + workflowPage.actions.visit(); + }); + + beforeEach(() => { + cy.createFixtureWorkflow('Webhook_set_pinned.json', `Webhook set pinned ${uuid()}`); + workflowPage.actions.deselectAll(); + workflowPage.getters.zoomToFitButton().click(); + + workflowPage.getters.getConnectionBetweenNodes('Webhook', 'Set').should('have.class', 'pinned'); + }); + + it('should not color connections for pinned data nodes for production executions', () => { + workflowPage.actions.activateWorkflow(); + + // Execute the workflow + cy.request('POST', `${BACKEND_BASE_URL}/webhook/23fc3930-b8f9-41d9-89db-b647291a2201`, { + here: 'is some data', + }).then((response) => { + expect(response.status).to.eq(200); + }); + + executionsTab.actions.switchToExecutionsTab(); + + executionsTab.getters.successfulExecutionListItems().should('have.length', 1); + + executionsTab.getters + .workflowExecutionPreviewIframe() + .should('be.visible') + .its('0.contentDocument.body') + .should('not.be.empty') + .then(cy.wrap) + .find(`.jtk-connector[data-source-node="Webhook"][data-target-node="Set"]`) + .should('have.class', 'success') + .should('have.class', 'has-run') + .should('not.have.class', 'pinned'); + }); + + it('should color connections for pinned data nodes for manual executions', () => { + workflowPage.actions.executeWorkflow(); + + executionsTab.actions.switchToExecutionsTab(); + + executionsTab.getters.successfulExecutionListItems().should('have.length', 1); + + executionsTab.getters + .workflowExecutionPreviewIframe() + .should('be.visible') + .its('0.contentDocument.body') + .should('not.be.empty') + .then(cy.wrap) + .find(`.jtk-connector[data-source-node="Webhook"][data-target-node="Set"]`) + .should('have.class', 'success') + .should('have.class', 'has-run') + .should('have.class', 'pinned'); + }); +}); diff --git a/cypress/fixtures/Webhook_set_pinned.json b/cypress/fixtures/Webhook_set_pinned.json new file mode 100644 index 0000000000000..12401db243f3f --- /dev/null +++ b/cypress/fixtures/Webhook_set_pinned.json @@ -0,0 +1,67 @@ +{ + "nodes": [ + { + "parameters": { + "options": {} + }, + "id": "bd816131-d8ad-4b4c-90d6-59fdab2e6307", + "name": "Set", + "type": "n8n-nodes-base.set", + "typeVersion": 1, + "position": [ + 720, + 460 + ] + }, + { + "parameters": { + "httpMethod": "POST", + "path": "23fc3930-b8f9-41d9-89db-b647291a2201", + "options": {} + }, + "id": "82fe0f6c-854a-4eb9-b311-d7b43025c047", + "name": "Webhook", + "type": "n8n-nodes-base.webhook", + "typeVersion": 1, + "position": [ + 460, + 460 + ], + "webhookId": "23fc3930-b8f9-41d9-89db-b647291a2201" + } + ], + "connections": { + "Webhook": { + "main": [ + [ + { + "node": "Set", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "pinData": { + "Webhook": [ + { + "headers": { + "host": "localhost:5678", + "content-length": "37", + "accept": "*/*", + "content-type": "application/json", + "accept-encoding": "gzip" + }, + "params": {}, + "query": {}, + "body": { + "here": "be", + "dragons": true + }, + "webhookUrl": "http://localhost:5678/webhook-test/23fc3930-b8f9-41d9-89db-b647291a2201", + "executionMode": "test" + } + ] + } +} diff --git a/cypress/pages/workflow-executions-tab.ts b/cypress/pages/workflow-executions-tab.ts index cf9665a8b8e33..474a00745e017 100644 --- a/cypress/pages/workflow-executions-tab.ts +++ b/cypress/pages/workflow-executions-tab.ts @@ -24,6 +24,7 @@ export class WorkflowExecutionsTab extends BasePage { executionPreviewId: () => this.getters.executionPreviewDetails().find('[data-test-id="execution-preview-id"]'), executionDebugButton: () => cy.getByTestId('execution-debug-button'), + workflowExecutionPreviewIframe: () => cy.getByTestId('workflow-preview-iframe'), }; actions = { toggleNodeEnabled: (nodeName: string) => { diff --git a/packages/editor-ui/src/components/Node.vue b/packages/editor-ui/src/components/Node.vue index 74aa86a208d3a..9dd4af5130d11 100644 --- a/packages/editor-ui/src/components/Node.vue +++ b/packages/editor-ui/src/components/Node.vue @@ -108,9 +108,9 @@
@@ -187,7 +187,7 @@ import { LOCAL_STORAGE_PIN_DATA_DISCOVERY_CANVAS_FLAG, MANUAL_TRIGGER_NODE_TYPE, NODE_INSERT_SPACER_BETWEEN_INPUT_GROUPS, - NOT_DUPLICATABE_NODE_TYPES, + NOT_DUPLICATABLE_NODE_TYPES, SIMULATE_NODE_TYPE, SIMULATE_TRIGGER_NODE_TYPE, WAIT_TIME_UNLIMITED, @@ -287,7 +287,7 @@ export default defineComponent({ }, isDuplicatable(): boolean { if (!this.nodeType) return true; - if (NOT_DUPLICATABE_NODE_TYPES.includes(this.nodeType.name)) return false; + if (NOT_DUPLICATABLE_NODE_TYPES.includes(this.nodeType.name)) return false; return ( this.nodeType.maxNodes === undefined || this.sameTypeNodes.length < this.nodeType.maxNodes ); @@ -493,7 +493,7 @@ export default defineComponent({ position(): XYPosition { return this.node ? this.node.position : [0, 0]; }, - showDisabledLinethrough(): boolean { + showDisabledLineThrough(): boolean { return ( !this.isConfigurableNode && !!(this.data?.disabled && this.inputs.length === 1 && this.outputs.length === 1) @@ -1118,7 +1118,7 @@ export default defineComponent({ } } -.disabled-linethrough { +.disabled-line-through { border: 1px solid var(--color-foreground-dark); position: absolute; top: 49px; @@ -1189,7 +1189,7 @@ export default defineComponent({ overflow: auto; } -.disabled-linethrough { +.disabled-line-through { z-index: 8; } diff --git a/packages/editor-ui/src/components/WorkflowPreview.vue b/packages/editor-ui/src/components/WorkflowPreview.vue index 84d290a9122af..8abeef22d0486 100644 --- a/packages/editor-ui/src/components/WorkflowPreview.vue +++ b/packages/editor-ui/src/components/WorkflowPreview.vue @@ -27,7 +27,6 @@ import { onMounted, onBeforeUnmount, ref, computed, watch } from 'vue'; import { useI18n } from '@/composables/useI18n'; import { useToast } from '@/composables/useToast'; import type { IWorkflowDb, IWorkflowTemplate } from '@/Interface'; -import { useRootStore } from '@/stores/n8nRoot.store'; import { useExecutionsStore } from '@/stores/executions.store'; const props = withDefaults( @@ -44,6 +43,9 @@ const props = withDefaults( { loading: false, mode: 'workflow', + workflow: undefined, + executionId: undefined, + executionMode: undefined, loaderType: 'image', canOpenNDV: true, hideNodeIssues: false, @@ -56,7 +58,6 @@ const emit = defineEmits<{ const i18n = useI18n(); const toast = useToast(); -const rootStore = useRootStore(); const executionsStore = useExecutionsStore(); const iframeRef = ref