From 40fd8b318bfa9010344707c2d529e5774bf31bee Mon Sep 17 00:00:00 2001 From: Mutasem Aldmour Date: Thu, 14 Nov 2024 12:17:18 +0100 Subject: [PATCH] add e2e tests --- cypress/composables/executions.ts | 29 ++ cypress/composables/ndv.ts | 33 ++ cypress/composables/workflow.ts | 24 ++ cypress/e2e/47-subworkflow-debugging.cy.ts | 140 +++++++ ...ubworkflow-debugging-execute-workflow.json | 354 ++++++++++++++++++ .../editor-ui/src/components/RunData.test.ts | 28 +- packages/editor-ui/src/components/RunData.vue | 10 +- .../components/RunDataAi/RunDataAiContent.vue | 8 +- .../editor-ui/src/components/RunDataTable.vue | 25 +- .../src/composables/useExecutionHelpers.ts | 24 +- 10 files changed, 650 insertions(+), 25 deletions(-) create mode 100644 cypress/composables/executions.ts create mode 100644 cypress/e2e/47-subworkflow-debugging.cy.ts create mode 100644 cypress/fixtures/Subworkflow-debugging-execute-workflow.json diff --git a/cypress/composables/executions.ts b/cypress/composables/executions.ts new file mode 100644 index 0000000000000..cd07eb7e6a289 --- /dev/null +++ b/cypress/composables/executions.ts @@ -0,0 +1,29 @@ +/** + * Getters + */ + +export const getExecutionsSidebar = () => cy.getByTestId('executions-sidebar'); + +export const getWorkflowExecutionPreviewIframe = () => cy.getByTestId('workflow-preview-iframe'); + +export const getExecutionPreviewBody = () => + getWorkflowExecutionPreviewIframe() + .its('0.contentDocument.body') + .then((el) => cy.wrap(el)); + +export const getExecutionPreviewBodyNodes = () => + getExecutionPreviewBody().findChildByTestId('canvas-node'); + +export const getExecutionPreviewBodyNodesByName = (name: string) => + getExecutionPreviewBody().findChildByTestId('canvas-node').filter(`[data-name="${name}"]`).eq(0); + +export function getExecutionPreviewOutputPanelRelatedExecutionLink() { + return getExecutionPreviewBody().findChildByTestId('related-execution-link'); +} + +/** + * Actions + */ + +export const openExecutionPreviewNode = (name: string) => + getExecutionPreviewBodyNodesByName(name).dblclick(); diff --git a/cypress/composables/ndv.ts b/cypress/composables/ndv.ts index b7ea33cb69e6e..05d783ec5e8f9 100644 --- a/cypress/composables/ndv.ts +++ b/cypress/composables/ndv.ts @@ -48,10 +48,38 @@ export function getOutputTableRow(row: number) { return getOutputTableRows().eq(row); } +export function getOutputTableHeaders() { + return getOutputPanelDataContainer().find('table thead th'); +} + +export function getOutputTableHeaderByText(text: string) { + return getOutputTableHeaders().contains(text); +} + +export function getOutputTbodyCell(row: number, col: number) { + return getOutputTableRows().eq(row).find('td').eq(col); +} + +export function getOutputRunSelector() { + return getOutputPanel().findChildByTestId('run-selector'); +} + +export function getOutputRunSelectorInput() { + return getOutputRunSelector().find('input'); +} + export function getOutputPanelTable() { return getOutputPanelDataContainer().get('table'); } +export function getOutputPanelItemsCount() { + return getOutputPanel().getByTestId('ndv-items-count'); +} + +export function getOutputPanelRelatedExecutionLink() { + return getOutputPanel().getByTestId('related-execution-link'); +} + /** * Actions */ @@ -90,3 +118,8 @@ export function setParameterSelectByContent(name: string, content: string) { getParameterInputByName(name).realClick(); getVisibleSelect().find('.option-headline').contains(content).click(); } + +export function changeOutputRunSelector(runName: string) { + getOutputRunSelector().click(); + getVisibleSelect().find('.el-select-dropdown__item').contains(runName).click(); +} diff --git a/cypress/composables/workflow.ts b/cypress/composables/workflow.ts index bab18587e0227..251db6e75d498 100644 --- a/cypress/composables/workflow.ts +++ b/cypress/composables/workflow.ts @@ -76,6 +76,14 @@ export function getCanvasNodes() { ); } +export function getSaveButton() { + return cy.getByTestId('workflow-save-button'); +} + +export function getZoomToFitButton() { + return cy.getByTestId('zoom-to-fit'); +} + /** * Actions */ @@ -170,3 +178,19 @@ export function clickManualChatButton() { export function openNode(nodeName: string) { getNodeByName(nodeName).dblclick(); } + +export function saveWorkflowOnButtonClick() { + cy.intercept('POST', '/rest/workflows').as('createWorkflow'); + getSaveButton().should('contain', 'Save'); + getSaveButton().click(); + getSaveButton().should('contain', 'Saved'); + cy.url().should('not.have.string', '/new'); +} + +export function pasteWorkflow(workflow: object) { + cy.get('body').paste(JSON.stringify(workflow)); +} + +export function clickZoomToFit() { + getZoomToFitButton().click(); +} diff --git a/cypress/e2e/47-subworkflow-debugging.cy.ts b/cypress/e2e/47-subworkflow-debugging.cy.ts new file mode 100644 index 0000000000000..f808bdd044ef8 --- /dev/null +++ b/cypress/e2e/47-subworkflow-debugging.cy.ts @@ -0,0 +1,140 @@ +import { + getExecutionPreviewOutputPanelRelatedExecutionLink, + getExecutionsSidebar, + getWorkflowExecutionPreviewIframe, + openExecutionPreviewNode, +} from '../composables/executions'; +import { + changeOutputRunSelector, + getOutputPanelItemsCount, + getOutputPanelRelatedExecutionLink, + getOutputRunSelectorInput, + getOutputTableHeaders, + getOutputTableRows, + getOutputTbodyCell, +} from '../composables/ndv'; +import { + clickExecuteWorkflowButton, + clickZoomToFit, + getCanvasNodes, + navigateToNewWorkflowPage, + openNode, + pasteWorkflow, + saveWorkflowOnButtonClick, +} from '../composables/workflow'; +import SUBWORKFLOW_DEBUGGING_EXAMPLE from '../fixtures/Subworkflow-debugging-execute-workflow.json'; + +describe('Subworkflow debugging', () => { + beforeEach(() => { + navigateToNewWorkflowPage(); + pasteWorkflow(SUBWORKFLOW_DEBUGGING_EXAMPLE); + saveWorkflowOnButtonClick(); + getCanvasNodes().should('have.length', 11); + clickZoomToFit(); + + clickExecuteWorkflowButton(); + }); + + describe('can inspect sub executed workflow', () => { + it('(Run once with all items/ Wait for Sub-workflow completion) (default behavior)', () => { + openNode('Execute Workflow with param'); + + getOutputPanelItemsCount().should('contain.text', '2 items, 1 sub-execution'); + getOutputPanelRelatedExecutionLink().should('contain.text', 'Inspect Sub-Execution'); + getOutputPanelRelatedExecutionLink().should('have.attr', 'href'); + + // ensure workflow executed and waited on output + getOutputTableHeaders().should('have.length', 2); + getOutputTbodyCell(1, 0).should('have.text', 'world Natalie Moore'); + }); + + it('(Run once for each item/ Wait for Sub-workflow completion)', () => { + openNode('Execute Workflow with param1'); + + getOutputPanelItemsCount().should('contain.text', '2 items, 2 sub-execution'); + getOutputPanelRelatedExecutionLink().should('not.exist'); + + // ensure workflow executed and waited on output + getOutputTableHeaders().should('have.length', 3); + getOutputTbodyCell(1, 0).find('a').should('have.attr', 'href'); + getOutputTbodyCell(1, 1).should('have.text', 'world Natalie Moore'); + }); + + it('(Run once with all items/ Wait for Sub-workflow completion)', () => { + openNode('Execute Workflow with param2'); + + getOutputPanelItemsCount().should('not.exist'); + getOutputPanelRelatedExecutionLink().should('contain.text', 'Inspect Sub-Execution'); + getOutputPanelRelatedExecutionLink().should('have.attr', 'href'); + + // ensure workflow executed but returned same data as input + getOutputRunSelectorInput().should('have.value', '2 of 2 (3 items, 1 sub-execution)'); + getOutputTableHeaders().should('have.length', 6); + getOutputTableHeaders().eq(0).should('have.text', 'uid'); + getOutputTableRows().should('have.length', 4); + getOutputTbodyCell(1, 1).should('include.text', 'Jon_Ebert@yahoo.com'); + + changeOutputRunSelector('1 of 2 (2 items, 1 sub-execution)'); + getOutputRunSelectorInput().should('have.value', '1 of 2 (2 items, 1 sub-execution)'); + getOutputTableHeaders().should('have.length', 6); + getOutputTableHeaders().eq(0).should('have.text', 'uid'); + getOutputTableRows().should('have.length', 3); + getOutputTbodyCell(1, 1).should('include.text', 'Terry.Dach@hotmail.com'); + }); + + it('(Run once for each item/ Wait for Sub-workflow completion)', () => { + openNode('Execute Workflow with param3'); + + // ensure workflow executed but returned same data as input + getOutputRunSelectorInput().should('have.value', '2 of 2 (3 items, 3 sub-executions)'); + getOutputTableHeaders().should('have.length', 7); + getOutputTableHeaders().eq(1).should('have.text', 'uid'); + getOutputTableRows().should('have.length', 4); + getOutputTbodyCell(1, 0).find('a').should('have.attr', 'href'); + getOutputTbodyCell(1, 2).should('include.text', 'Jon_Ebert@yahoo.com'); + + changeOutputRunSelector('1 of 2 (2 items, 2 sub-executions)'); + getOutputRunSelectorInput().should('have.value', '1 of 2 (2 items, 2 sub-executions)'); + getOutputTableHeaders().should('have.length', 7); + getOutputTableHeaders().eq(1).should('have.text', 'uid'); + getOutputTableRows().should('have.length', 3); + + getOutputTbodyCell(1, 0).find('a').should('have.attr', 'href'); + getOutputTbodyCell(1, 2).should('include.text', 'Terry.Dach@hotmail.com'); + }); + }); + + it('can inspect parent executions', () => { + cy.url().then((workflowUrl) => { + openNode('Execute Workflow with param'); + + getOutputPanelItemsCount().should('contain.text', '2 items, 1 sub-execution'); + getOutputPanelRelatedExecutionLink().should('contain.text', 'Inspect Sub-Execution'); + getOutputPanelRelatedExecutionLink().should('have.attr', 'href'); + + // ensure workflow executed and waited on output + getOutputTableHeaders().should('have.length', 2); + getOutputTbodyCell(1, 0).should('have.text', 'world Natalie Moore'); + + // cypress cannot handle new tabs so removing it + getOutputPanelRelatedExecutionLink().invoke('removeAttr', 'target').click(); + + getExecutionsSidebar().should('be.visible'); + getWorkflowExecutionPreviewIframe().should('be.visible'); + openExecutionPreviewNode('Execute Workflow Trigger'); + + getExecutionPreviewOutputPanelRelatedExecutionLink().should( + 'include.text', + 'Inspect Parent Execution', + ); + + getExecutionPreviewOutputPanelRelatedExecutionLink() + .invoke('removeAttr', 'target') + .click({ force: true }); + + cy.url().then((currentUrl) => { + expect(currentUrl === workflowUrl); + }); + }); + }); +}); diff --git a/cypress/fixtures/Subworkflow-debugging-execute-workflow.json b/cypress/fixtures/Subworkflow-debugging-execute-workflow.json new file mode 100644 index 0000000000000..c336a80b4190d --- /dev/null +++ b/cypress/fixtures/Subworkflow-debugging-execute-workflow.json @@ -0,0 +1,354 @@ +{ + "meta": { + "instanceId": "08ce71ad998aeaade0abedb8dd96153d8eaa03fcb84cfccc1530095bf9ee478e" + }, + "nodes": [ + { + "parameters": {}, + "id": "4535ce3e-280e-49b0-8854-373472ec86d1", + "name": "When clicking ‘Test workflow’", + "type": "n8n-nodes-base.manualTrigger", + "typeVersion": 1, + "position": [80, 860] + }, + { + "parameters": { + "category": "randomData", + "randomDataSeed": "0", + "randomDataCount": 2 + }, + "id": "d7fba18a-d51f-4509-af45-68cd9425ac6b", + "name": "DebugHelper1", + "type": "n8n-nodes-base.debugHelper", + "typeVersion": 1, + "position": [280, 860] + }, + { + "parameters": { + "source": "parameter", + "workflowJson": "{\n \"meta\": {\n \"instanceId\": \"a786b722078489c1fa382391a9f3476c2784761624deb2dfb4634827256d51a0\"\n },\n \"nodes\": [\n {\n \"parameters\": {},\n \"type\": \"n8n-nodes-base.executeWorkflowTrigger\",\n \"typeVersion\": 1,\n \"position\": [\n 0,\n 0\n ],\n \"id\": \"00600a51-e63a-4b6e-93f5-f01d50a21e0c\",\n \"name\": \"Execute Workflow Trigger\"\n },\n {\n \"parameters\": {\n \"assignments\": {\n \"assignments\": [\n {\n \"id\": \"87ff01af-2e28-48da-ae6c-304040200b15\",\n \"name\": \"hello\",\n \"value\": \"=world {{ $json.firstname }} {{ $json.lastname }}\",\n \"type\": \"string\"\n }\n ]\n },\n \"includeOtherFields\": false,\n \"options\": {}\n },\n \"type\": \"n8n-nodes-base.set\",\n \"typeVersion\": 3.4,\n \"position\": [\n 280,\n 0\n ],\n \"id\": \"642219a1-d655-4a30-af5c-fcccbb690322\",\n \"name\": \"Edit Fields\"\n }\n ],\n \"connections\": {\n \"Execute Workflow Trigger\": {\n \"main\": [\n [\n {\n \"node\": \"Edit Fields\",\n \"type\": \"main\",\n \"index\": 0\n }\n ]\n ]\n }\n },\n \"pinData\": {}\n}", + "mode": "each", + "options": { + "waitForSubWorkflow": false + } + }, + "type": "n8n-nodes-base.executeWorkflow", + "typeVersion": 1.1, + "position": [680, 1540], + "id": "f90a25da-dd89-4bf8-8f5b-bf8ee1de0b70", + "name": "Execute Workflow with param3" + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "c93f26bd-3489-467b-909e-6462e1463707", + "name": "uid", + "value": "={{ $json.uid }}", + "type": "string" + }, + { + "id": "3dd706ce-d925-4219-8531-ad12369972fe", + "name": "email", + "value": "={{ $json.email }}", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [900, 1540], + "id": "3be57648-3be8-4b0f-abfa-8fdcafee804d", + "name": "Edit Fields8" + }, + { + "parameters": { + "source": "parameter", + "workflowJson": "{\n \"meta\": {\n \"instanceId\": \"a786b722078489c1fa382391a9f3476c2784761624deb2dfb4634827256d51a0\"\n },\n \"nodes\": [\n {\n \"parameters\": {},\n \"type\": \"n8n-nodes-base.executeWorkflowTrigger\",\n \"typeVersion\": 1,\n \"position\": [\n 0,\n 0\n ],\n \"id\": \"00600a51-e63a-4b6e-93f5-f01d50a21e0c\",\n \"name\": \"Execute Workflow Trigger\"\n },\n {\n \"parameters\": {\n \"assignments\": {\n \"assignments\": [\n {\n \"id\": \"87ff01af-2e28-48da-ae6c-304040200b15\",\n \"name\": \"hello\",\n \"value\": \"=world {{ $json.firstname }} {{ $json.lastname }}\",\n \"type\": \"string\"\n }\n ]\n },\n \"includeOtherFields\": false,\n \"options\": {}\n },\n \"type\": \"n8n-nodes-base.set\",\n \"typeVersion\": 3.4,\n \"position\": [\n 280,\n 0\n ],\n \"id\": \"642219a1-d655-4a30-af5c-fcccbb690322\",\n \"name\": \"Edit Fields\"\n }\n ],\n \"connections\": {\n \"Execute Workflow Trigger\": {\n \"main\": [\n [\n {\n \"node\": \"Edit Fields\",\n \"type\": \"main\",\n \"index\": 0\n }\n ]\n ]\n }\n },\n \"pinData\": {}\n}", + "options": { + "waitForSubWorkflow": false + } + }, + "type": "n8n-nodes-base.executeWorkflow", + "typeVersion": 1.1, + "position": [620, 1220], + "id": "dabc2356-3660-4d17-b305-936a002029ba", + "name": "Execute Workflow with param2" + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "c93f26bd-3489-467b-909e-6462e1463707", + "name": "uid", + "value": "={{ $json.uid }}", + "type": "string" + }, + { + "id": "3dd706ce-d925-4219-8531-ad12369972fe", + "name": "email", + "value": "={{ $json.email }}", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [840, 1220], + "id": "9d2a9dda-e2a1-43e8-a66f-a8a555692e5f", + "name": "Edit Fields7" + }, + { + "parameters": { + "source": "parameter", + "workflowJson": "{\n \"meta\": {\n \"instanceId\": \"a786b722078489c1fa382391a9f3476c2784761624deb2dfb4634827256d51a0\"\n },\n \"nodes\": [\n {\n \"parameters\": {},\n \"type\": \"n8n-nodes-base.executeWorkflowTrigger\",\n \"typeVersion\": 1,\n \"position\": [\n 0,\n 0\n ],\n \"id\": \"00600a51-e63a-4b6e-93f5-f01d50a21e0c\",\n \"name\": \"Execute Workflow Trigger\"\n },\n {\n \"parameters\": {\n \"assignments\": {\n \"assignments\": [\n {\n \"id\": \"87ff01af-2e28-48da-ae6c-304040200b15\",\n \"name\": \"hello\",\n \"value\": \"=world {{ $json.firstname }} {{ $json.lastname }}\",\n \"type\": \"string\"\n }\n ]\n },\n \"includeOtherFields\": false,\n \"options\": {}\n },\n \"type\": \"n8n-nodes-base.set\",\n \"typeVersion\": 3.4,\n \"position\": [\n 280,\n 0\n ],\n \"id\": \"642219a1-d655-4a30-af5c-fcccbb690322\",\n \"name\": \"Edit Fields\"\n }\n ],\n \"connections\": {\n \"Execute Workflow Trigger\": {\n \"main\": [\n [\n {\n \"node\": \"Edit Fields\",\n \"type\": \"main\",\n \"index\": 0\n }\n ]\n ]\n }\n },\n \"pinData\": {}\n}", + "mode": "each", + "options": { + "waitForSubWorkflow": true + } + }, + "type": "n8n-nodes-base.executeWorkflow", + "typeVersion": 1.1, + "position": [560, 900], + "id": "07e47f60-622a-484c-ab24-35f6f2280595", + "name": "Execute Workflow with param1" + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "c93f26bd-3489-467b-909e-6462e1463707", + "name": "uid", + "value": "={{ $json.uid }}", + "type": "string" + }, + { + "id": "3dd706ce-d925-4219-8531-ad12369972fe", + "name": "email", + "value": "={{ $json.email }}", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [760, 900], + "id": "80563d0a-0bab-444f-a04c-4041a505d78b", + "name": "Edit Fields6" + }, + { + "parameters": { + "source": "parameter", + "workflowJson": "{\n \"meta\": {\n \"instanceId\": \"a786b722078489c1fa382391a9f3476c2784761624deb2dfb4634827256d51a0\"\n },\n \"nodes\": [\n {\n \"parameters\": {},\n \"type\": \"n8n-nodes-base.executeWorkflowTrigger\",\n \"typeVersion\": 1,\n \"position\": [\n 0,\n 0\n ],\n \"id\": \"00600a51-e63a-4b6e-93f5-f01d50a21e0c\",\n \"name\": \"Execute Workflow Trigger\"\n },\n {\n \"parameters\": {\n \"assignments\": {\n \"assignments\": [\n {\n \"id\": \"87ff01af-2e28-48da-ae6c-304040200b15\",\n \"name\": \"hello\",\n \"value\": \"=world {{ $json.firstname }} {{ $json.lastname }}\",\n \"type\": \"string\"\n }\n ]\n },\n \"includeOtherFields\": false,\n \"options\": {}\n },\n \"type\": \"n8n-nodes-base.set\",\n \"typeVersion\": 3.4,\n \"position\": [\n 280,\n 0\n ],\n \"id\": \"642219a1-d655-4a30-af5c-fcccbb690322\",\n \"name\": \"Edit Fields\"\n }\n ],\n \"connections\": {\n \"Execute Workflow Trigger\": {\n \"main\": [\n [\n {\n \"node\": \"Edit Fields\",\n \"type\": \"main\",\n \"index\": 0\n }\n ]\n ]\n }\n },\n \"pinData\": {}\n}", + "options": { + "waitForSubWorkflow": true + } + }, + "type": "n8n-nodes-base.executeWorkflow", + "typeVersion": 1.1, + "position": [560, 580], + "id": "f04af481-f4d9-4d91-a60a-a377580e8393", + "name": "Execute Workflow with param" + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "c93f26bd-3489-467b-909e-6462e1463707", + "name": "uid", + "value": "={{ $json.uid }}", + "type": "string" + }, + { + "id": "3dd706ce-d925-4219-8531-ad12369972fe", + "name": "email", + "value": "={{ $json.email }}", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [760, 580], + "id": "80c10607-a0ac-4090-86a1-890da0a2aa52", + "name": "Edit Fields2" + }, + { + "parameters": { + "content": "## Execute Workflow (Run once with all items/ DONT Wait for Sub-workflow completion)", + "height": 254.84308966329985, + "width": 457.58120569815793 + }, + "id": "534ef523-3453-4a16-9ff0-8ac9f025d47d", + "name": "Sticky Note5", + "type": "n8n-nodes-base.stickyNote", + "typeVersion": 1, + "position": [500, 1080] + }, + { + "parameters": { + "content": "## Execute Workflow (Run once with for each item/ DONT Wait for Sub-workflow completion) ", + "height": 284.59778445962905, + "width": 457.58120569815793 + }, + "id": "838f0fa3-5ee4-4d1a-afb8-42e009f1aa9e", + "name": "Sticky Note4", + "type": "n8n-nodes-base.stickyNote", + "typeVersion": 1, + "position": [580, 1400] + }, + { + "parameters": { + "category": "randomData", + "randomDataSeed": "1", + "randomDataCount": 3 + }, + "id": "86699a49-2aa7-488e-8ea9-828404c98f08", + "name": "DebugHelper", + "type": "n8n-nodes-base.debugHelper", + "typeVersion": 1, + "position": [320, 1120] + }, + { + "parameters": { + "content": "## Execute Workflow (Run once with for each item/ Wait for Sub-workflow completion) ", + "height": 284.59778445962905, + "width": 457.58120569815793 + }, + "id": "885d35f0-8ae6-45ec-821b-a82c27e7577a", + "name": "Sticky Note3", + "type": "n8n-nodes-base.stickyNote", + "typeVersion": 1, + "position": [480, 760] + }, + { + "parameters": { + "content": "## Execute Workflow (Run once with all items/ Wait for Sub-workflow completion) (default behavior)", + "height": 254.84308966329985, + "width": 457.58120569815793 + }, + "id": "505bd7f2-767e-41b8-9325-77300aed5883", + "name": "Sticky Note2", + "type": "n8n-nodes-base.stickyNote", + "typeVersion": 1, + "position": [460, 460] + } + ], + "connections": { + "When clicking ‘Test workflow’": { + "main": [ + [ + { + "node": "DebugHelper1", + "type": "main", + "index": 0 + }, + { + "node": "DebugHelper", + "type": "main", + "index": 0 + } + ] + ] + }, + "DebugHelper1": { + "main": [ + [ + { + "node": "Execute Workflow with param3", + "type": "main", + "index": 0 + }, + { + "node": "Execute Workflow with param2", + "type": "main", + "index": 0 + }, + { + "node": "Execute Workflow with param1", + "type": "main", + "index": 0 + }, + { + "node": "Execute Workflow with param", + "type": "main", + "index": 0 + } + ] + ] + }, + "Execute Workflow with param3": { + "main": [ + [ + { + "node": "Edit Fields8", + "type": "main", + "index": 0 + } + ] + ] + }, + "Execute Workflow with param2": { + "main": [ + [ + { + "node": "Edit Fields7", + "type": "main", + "index": 0 + } + ] + ] + }, + "Execute Workflow with param1": { + "main": [ + [ + { + "node": "Edit Fields6", + "type": "main", + "index": 0 + } + ] + ] + }, + "Execute Workflow with param": { + "main": [ + [ + { + "node": "Edit Fields2", + "type": "main", + "index": 0 + } + ] + ] + }, + "DebugHelper": { + "main": [ + [ + { + "node": "Execute Workflow with param2", + "type": "main", + "index": 0 + }, + { + "node": "Execute Workflow with param3", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "pinData": {} +} diff --git a/packages/editor-ui/src/components/RunData.test.ts b/packages/editor-ui/src/components/RunData.test.ts index 322d9ac8dee7b..370ef1182dfaa 100644 --- a/packages/editor-ui/src/components/RunData.test.ts +++ b/packages/editor-ui/src/components/RunData.test.ts @@ -12,8 +12,11 @@ import type { INodeExecutionData, ITaskData, ITaskMetadata } from 'n8n-workflow' import { setActivePinia } from 'pinia'; import { useNodeTypesStore } from '../stores/nodeTypes.store'; -const { openRelatedExecution } = vi.hoisted(() => ({ - openRelatedExecution: vi.fn(), +const MOCK_EXECUTION_URL = 'execution.url/123'; + +const { trackOpeningRelatedExecution, resolveRelatedExecutionUrl } = vi.hoisted(() => ({ + trackOpeningRelatedExecution: vi.fn(), + resolveRelatedExecutionUrl: vi.fn(), })); vi.mock('vue-router', () => { @@ -26,7 +29,8 @@ vi.mock('vue-router', () => { vi.mock('@/composables/useExecutionHelpers', () => ({ useExecutionHelpers: () => ({ - openRelatedExecution, + trackOpeningRelatedExecution, + resolveRelatedExecutionUrl, }), })); @@ -42,6 +46,10 @@ const nodes = [ ] as INodeUi[]; describe('RunData', () => { + beforeAll(() => { + resolveRelatedExecutionUrl.mockReturnValue('execution.url/123'); + }); + it("should render pin button in output panel disabled when there's binary data", () => { const { getByTestId } = render({ defaultRunItems: [ @@ -253,11 +261,13 @@ describe('RunData', () => { expect(getByTestId('related-execution-link')).toBeInTheDocument(); expect(getByTestId('related-execution-link')).toHaveTextContent('Inspect Sub-Execution 123'); + expect(resolveRelatedExecutionUrl).toHaveBeenCalledWith(metadata); + expect(getByTestId('related-execution-link')).toHaveAttribute('href', MOCK_EXECUTION_URL); expect(getByTestId('ndv-items-count')).toHaveTextContent('1 item, 1 sub-execution'); getByTestId('related-execution-link').click(); - expect(openRelatedExecution).toHaveBeenCalledWith(metadata, 'table'); + expect(trackOpeningRelatedExecution).toHaveBeenCalledWith(metadata, 'table'); }); it('should render parent-execution link in header', async () => { @@ -280,11 +290,13 @@ describe('RunData', () => { expect(getByTestId('related-execution-link')).toBeInTheDocument(); expect(getByTestId('related-execution-link')).toHaveTextContent('Inspect Parent Execution 123'); + expect(resolveRelatedExecutionUrl).toHaveBeenCalledWith(metadata); + expect(getByTestId('related-execution-link')).toHaveAttribute('href', MOCK_EXECUTION_URL); expect(getByTestId('ndv-items-count')).toHaveTextContent('1 item'); getByTestId('related-execution-link').click(); - expect(openRelatedExecution).toHaveBeenCalledWith(metadata, 'table'); + expect(trackOpeningRelatedExecution).toHaveBeenCalledWith(metadata, 'table'); }); it('should render sub-execution link in header with multiple items', async () => { @@ -311,11 +323,13 @@ describe('RunData', () => { expect(getByTestId('related-execution-link')).toBeInTheDocument(); expect(getByTestId('related-execution-link')).toHaveTextContent('Inspect Sub-Execution 123'); + expect(resolveRelatedExecutionUrl).toHaveBeenCalledWith(metadata); + expect(getByTestId('related-execution-link')).toHaveAttribute('href', MOCK_EXECUTION_URL); expect(getByTestId('ndv-items-count')).toHaveTextContent('2 items, 3 sub-executions'); getByTestId('related-execution-link').click(); - expect(openRelatedExecution).toHaveBeenCalledWith(metadata, 'json'); + expect(trackOpeningRelatedExecution).toHaveBeenCalledWith(metadata, 'json'); }); it('should render sub-execution link in header with multiple runs', async () => { @@ -359,7 +373,7 @@ describe('RunData', () => { expect(getByTestId('run-selector')).toBeInTheDocument(); getByTestId('related-execution-link').click(); - expect(openRelatedExecution).toHaveBeenCalledWith(metadata, 'json'); + expect(trackOpeningRelatedExecution).toHaveBeenCalledWith(metadata, 'json'); }); const render = ({ diff --git a/packages/editor-ui/src/components/RunData.vue b/packages/editor-ui/src/components/RunData.vue index 27eaefb25abe5..32dab1da0fadc 100644 --- a/packages/editor-ui/src/components/RunData.vue +++ b/packages/editor-ui/src/components/RunData.vue @@ -182,7 +182,7 @@ const nodeHelpers = useNodeHelpers(); const externalHooks = useExternalHooks(); const telemetry = useTelemetry(); const i18n = useI18n(); -const { openRelatedExecution } = useExecutionHelpers(); +const { trackOpeningRelatedExecution, resolveRelatedExecutionUrl } = useExecutionHelpers(); const node = toRef(props, 'node'); @@ -1414,7 +1414,9 @@ defineExpose({ enterEditMode }); " :class="$style.relatedExecutionInfo" data-test-id="related-execution-link" - @click.stop="openRelatedExecution(activeTaskMetadata, displayMode)" + :href="resolveRelatedExecutionUrl(activeTaskMetadata)" + target="_blank" + @click.stop="trackOpeningRelatedExecution(activeTaskMetadata, displayMode)" > {{ getExecutionLinkLabel(activeTaskMetadata) }} @@ -1496,7 +1498,9 @@ defineExpose({ enterEditMode }); " :class="$style.relatedExecutionInfo" data-test-id="related-execution-link" - @click.stop="openRelatedExecution(activeTaskMetadata, displayMode)" + :href="resolveRelatedExecutionUrl(activeTaskMetadata)" + target="_blank" + @click.stop="trackOpeningRelatedExecution(activeTaskMetadata, displayMode)" > {{ getExecutionLinkLabel(activeTaskMetadata) }} diff --git a/packages/editor-ui/src/components/RunDataAi/RunDataAiContent.vue b/packages/editor-ui/src/components/RunDataAi/RunDataAiContent.vue index 48f85030abfad..2f916080271af 100644 --- a/packages/editor-ui/src/components/RunDataAi/RunDataAiContent.vue +++ b/packages/editor-ui/src/components/RunDataAi/RunDataAiContent.vue @@ -32,7 +32,7 @@ const props = defineProps<{ const nodeTypesStore = useNodeTypesStore(); const workflowsStore = useWorkflowsStore(); -const { openRelatedExecution } = useExecutionHelpers(); +const { trackOpeningRelatedExecution, resolveRelatedExecutionUrl } = useExecutionHelpers(); type TokenUsageData = { completionTokens: number; @@ -140,7 +140,11 @@ const outputError = computed(() => {
  • - + {{ $locale.baseText('runData.openSubExecution', { diff --git a/packages/editor-ui/src/components/RunDataTable.vue b/packages/editor-ui/src/components/RunDataTable.vue index c1b03d8fabed3..68e80a38a8e23 100644 --- a/packages/editor-ui/src/components/RunDataTable.vue +++ b/packages/editor-ui/src/components/RunDataTable.vue @@ -64,7 +64,7 @@ const workflowsStore = useWorkflowsStore(); const i18n = useI18n(); const telemetry = useTelemetry(); -const { openRelatedExecution } = useExecutionHelpers(); +const { trackOpeningRelatedExecution, resolveRelatedExecutionUrl } = useExecutionHelpers(); const { hoveringItem, @@ -455,7 +455,9 @@ watch(focusedMappableInput, (curr) => { icon="external-link-alt" data-test-id="debug-sub-execution" size="mini" - @click="openRelatedExecution(tableData.metadata.data[index1], 'table')" + :href="resolveRelatedExecutionUrl(tableData.metadata.data[index1])" + target="_blank" + @click="trackOpeningRelatedExecution(tableData.metadata.data[index1], 'table')" /> @@ -582,15 +584,20 @@ watch(focusedMappableInput, (curr) => { placement="left" :hide-after="0" > - + :href="resolveRelatedExecutionUrl(tableData.metadata.data[index1])" + target="_blank" + @click="trackOpeningRelatedExecution(tableData.metadata.data[index1], 'table')" + > + +