diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index a4f667ac46..330174e6dd 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -11,3 +11,7 @@ # refactor: Run lintfix (no-changelog) (#7537) 62c096710fab2f7e886518abdbded34b55e93f62 + +# refactor: Move test files alongside tested files (#11504) + +7e58fc4fec468aca0b45d5bfe6150e1af632acbc diff --git a/CHANGELOG.md b/CHANGELOG.md index baa7b95b7c..4a380fa531 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,45 @@ +# [1.66.0](https://github.com/n8n-io/n8n/compare/n8n@1.65.0...n8n@1.66.0) (2024-10-31) + + +### Bug Fixes + +* **Asana Node:** Fix issue with pagination ([#11415](https://github.com/n8n-io/n8n/issues/11415)) ([04c075a](https://github.com/n8n-io/n8n/commit/04c075a46bcc7b1964397f0244b0fde99476212d)) +* **core:** Add 'user_id' to `license-community-plus-registered` telemetry event ([#11430](https://github.com/n8n-io/n8n/issues/11430)) ([7a8dafe](https://github.com/n8n-io/n8n/commit/7a8dafe9902fbc0d5001c50579c34959b95211ab)) +* **core:** Add safeguard for command publishing ([#11337](https://github.com/n8n-io/n8n/issues/11337)) ([656439e](https://github.com/n8n-io/n8n/commit/656439e87138f9f96dea5a683cfdac3f661ffefb)) +* **core:** Ensure `LoggerProxy` is not scoped ([#11379](https://github.com/n8n-io/n8n/issues/11379)) ([f4ea943](https://github.com/n8n-io/n8n/commit/f4ea943c9cb2321e41705de6c5c27535a0f5eae0)) +* **core:** Ensure `remove-triggers-and-pollers` command is not debounced ([#11486](https://github.com/n8n-io/n8n/issues/11486)) ([529d4fc](https://github.com/n8n-io/n8n/commit/529d4fc3ef5206bd1b02d27634342cc50b45997e)) +* **core:** Ensure job processor does not reprocess amended executions ([#11438](https://github.com/n8n-io/n8n/issues/11438)) ([c152a3a](https://github.com/n8n-io/n8n/commit/c152a3ac56f140a39eea4771a94f5a3082118df7)) +* **core:** Fix Message Event Bus Metrics not counting up for labeled metrics ([#11396](https://github.com/n8n-io/n8n/issues/11396)) ([7fc3b25](https://github.com/n8n-io/n8n/commit/7fc3b25d21c6c4f1802f34b1ae065a65cac3001b)) +* **core:** Fix resolving of $fromAI expression via `evaluateExpression` ([#11397](https://github.com/n8n-io/n8n/issues/11397)) ([2e64464](https://github.com/n8n-io/n8n/commit/2e6446454defbd3e5a47b66e6fd46d4f1b9fbd0f)) +* **core:** Make execution and its data creation atomic ([#11392](https://github.com/n8n-io/n8n/issues/11392)) ([ed30d43](https://github.com/n8n-io/n8n/commit/ed30d43236bf3c6b657022636a02a41be01aa152)) +* **core:** On unhandled rejections, extract the original exception correctly ([#11389](https://github.com/n8n-io/n8n/issues/11389)) ([8608bae](https://github.com/n8n-io/n8n/commit/8608baeb7ec302daddc8adca6e39778dcf7b2eda)) +* **editor:** Fix TypeError: Cannot read properties of undefined (reading '0') ([#11399](https://github.com/n8n-io/n8n/issues/11399)) ([ae37c52](https://github.com/n8n-io/n8n/commit/ae37c520a91c75e353e818944b36a3619c0d8b4a)) +* **editor:** Add Retry button for AI Assistant errors ([#11345](https://github.com/n8n-io/n8n/issues/11345)) ([7699240](https://github.com/n8n-io/n8n/commit/7699240073122cdef31cf109fd37fa66961f588a)) +* **editor:** Change tooltip for workflow with execute workflow trigger ([#11374](https://github.com/n8n-io/n8n/issues/11374)) ([dcd6038](https://github.com/n8n-io/n8n/commit/dcd6038c3085135803cdaa546a239359a6d449eb)) +* **editor:** Ensure toasts show above modal overlays ([#11410](https://github.com/n8n-io/n8n/issues/11410)) ([351134f](https://github.com/n8n-io/n8n/commit/351134f786af933f5f310bf8d9897269387635a0)) +* **editor:** Fit view consistently after nodes are initialized on new canvas ([#11457](https://github.com/n8n-io/n8n/issues/11457)) ([497d637](https://github.com/n8n-io/n8n/commit/497d637fc5308b9c4a06bc764152fde1f1a9c130)) +* **editor:** Fix adding connections when initializing workspace in templates view on new canvas ([#11451](https://github.com/n8n-io/n8n/issues/11451)) ([ea47b02](https://github.com/n8n-io/n8n/commit/ea47b025fb16c967d4fc73dcacc6e260d2aecd61)) +* **editor:** Fix rendering of AI logs ([#11450](https://github.com/n8n-io/n8n/issues/11450)) ([73b0a80](https://github.com/n8n-io/n8n/commit/73b0a80ac92b4f4b5a300d0ec1c833b4395a222a)) +* **editor:** Hide data mapping tooltip in credential edit modal ([#11356](https://github.com/n8n-io/n8n/issues/11356)) ([ff14dcb](https://github.com/n8n-io/n8n/commit/ff14dcb3a1ddaea4eca7c1ecd2e92c0abb0c413c)) +* **editor:** Prevent running workflow that has issues if listening to webhook ([#11402](https://github.com/n8n-io/n8n/issues/11402)) ([8b0a48f](https://github.com/n8n-io/n8n/commit/8b0a48f53010378e497e4cc362fda75a958cf363)) +* **editor:** Run external hooks after settings have been initialized ([#11423](https://github.com/n8n-io/n8n/issues/11423)) ([0ab24c8](https://github.com/n8n-io/n8n/commit/0ab24c814abd1787268750ba808993ab2735ac52)) +* **editor:** Support middle click to scroll when using a mouse on new canvas ([#11384](https://github.com/n8n-io/n8n/issues/11384)) ([46f3b4a](https://github.com/n8n-io/n8n/commit/46f3b4a258f89f02e0d2bd1eef25a22e3a721167)) +* **HTTP Request Tool Node:** Fix HTML response optimization issue ([#11439](https://github.com/n8n-io/n8n/issues/11439)) ([cf37e94](https://github.com/n8n-io/n8n/commit/cf37e94dd875e9f6ab1f189146fb34e7296af93c)) +* **n8n Form Node:** Form Trigger does not wait in multi-form mode ([#11404](https://github.com/n8n-io/n8n/issues/11404)) ([151f4dd](https://github.com/n8n-io/n8n/commit/151f4dd7b8f800af424f8ae64cb8238975fb3cb8)) +* Update required node js version in CONTRIBUTING.md ([#11437](https://github.com/n8n-io/n8n/issues/11437)) ([4f511aa](https://github.com/n8n-io/n8n/commit/4f511aab68651caa8fe47f70cd7cdb88bb06a3e2)) + + +### Features + +* **Anthropic Chat Model Node:** Add model claude-3-5-sonnet-20241022 ([#11465](https://github.com/n8n-io/n8n/issues/11465)) ([f6c8890](https://github.com/n8n-io/n8n/commit/f6c8890a8069de221b9b96e735418ecc9624cf7b)) +* **core:** Handle nodes with multiple inputs and connections during partial executions ([#11376](https://github.com/n8n-io/n8n/issues/11376)) ([cb7c4d2](https://github.com/n8n-io/n8n/commit/cb7c4d29a6f042b590822e5b9c67fff0a8f0863d)) +* **editor:** Add descriptive header to projects /workflow ([#11203](https://github.com/n8n-io/n8n/issues/11203)) ([5d19e8f](https://github.com/n8n-io/n8n/commit/5d19e8f2b45dc1abc5a8253f9e3a0fdacb1ebd91)) +* **editor:** Improve placeholder for vector store tool ([#11483](https://github.com/n8n-io/n8n/issues/11483)) ([629e092](https://github.com/n8n-io/n8n/commit/629e09240785bc648ff6575f97910fbb4e77cdab)) +* **editor:** Remove edge execution animation on new canvas ([#11446](https://github.com/n8n-io/n8n/issues/11446)) ([a701d87](https://github.com/n8n-io/n8n/commit/a701d87f5ba94ffc811e424b60e188b26ac6c1c5)) +* **editor:** Update ownership pills ([#11155](https://github.com/n8n-io/n8n/issues/11155)) ([8147038](https://github.com/n8n-io/n8n/commit/8147038cf87dca657602e617e49698065bf1a63f)) + + + # [1.65.0](https://github.com/n8n-io/n8n/compare/n8n@1.64.0...n8n@1.65.0) (2024-10-24) diff --git a/cypress/composables/ndv.ts b/cypress/composables/ndv.ts index 5b3690e6a6..b7ea33cb69 100644 --- a/cypress/composables/ndv.ts +++ b/cypress/composables/ndv.ts @@ -40,6 +40,14 @@ export function getOutputPanelDataContainer() { return getOutputPanel().getByTestId('ndv-data-container'); } +export function getOutputTableRows() { + return getOutputPanelDataContainer().find('table tr'); +} + +export function getOutputTableRow(row: number) { + return getOutputTableRows().eq(row); +} + export function getOutputPanelTable() { return getOutputPanelDataContainer().get('table'); } diff --git a/cypress/composables/workflow.ts b/cypress/composables/workflow.ts index 394a35af18..bab18587e0 100644 --- a/cypress/composables/workflow.ts +++ b/cypress/composables/workflow.ts @@ -69,6 +69,13 @@ export function getNodeCreatorPlusButton() { return cy.getByTestId('node-creator-plus-button'); } +export function getCanvasNodes() { + return cy.ifCanvasVersion( + () => cy.getByTestId('canvas-node'), + () => cy.getByTestId('canvas-node').not('[data-node-type="n8n-nodes-internal.addNodes"]'), + ); +} + /** * Actions */ diff --git a/cypress/e2e/233-AI-switch-to-logs-on-error.cy.ts b/cypress/e2e/233-AI-switch-to-logs-on-error.cy.ts index eca3af81fb..79f33b841c 100644 --- a/cypress/e2e/233-AI-switch-to-logs-on-error.cy.ts +++ b/cypress/e2e/233-AI-switch-to-logs-on-error.cy.ts @@ -90,6 +90,14 @@ function createRunDataWithError(inputMessage: string) { routine: 'InitPostgres', } as unknown as Error, } as ExecutionError, + metadata: { + subRun: [ + { + node: 'Postgres Chat Memory', + runIndex: 0, + }, + ], + }, }), createMockNodeExecutionData(AGENT_NODE_NAME, { executionStatus: 'error', @@ -124,14 +132,6 @@ function createRunDataWithError(inputMessage: string) { description: 'Internal error', message: 'Internal error', } as unknown as ExecutionError, - metadata: { - subRun: [ - { - node: 'Postgres Chat Memory', - runIndex: 0, - }, - ], - }, }), ]; } diff --git a/cypress/e2e/30-langchain.cy.ts b/cypress/e2e/30-langchain.cy.ts index 0deec76e9f..e23b7e4da3 100644 --- a/cypress/e2e/30-langchain.cy.ts +++ b/cypress/e2e/30-langchain.cy.ts @@ -278,6 +278,9 @@ describe('Langchain Integration', () => { }, }, }, + metadata: { + subRun: [{ node: AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME, runIndex: 0 }], + }, inputOverride: { ai_languageModel: [ [ @@ -316,9 +319,6 @@ describe('Langchain Integration', () => { jsonData: { main: { output: 'Hi there! How can I assist you today?' }, }, - metadata: { - subRun: [{ node: AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME, runIndex: 0 }], - }, }), ], lastNodeExecuted: AGENT_NODE_NAME, diff --git a/cypress/e2e/31-demo.cy.ts b/cypress/e2e/31-demo.cy.ts index 32307361fd..7f13d1659a 100644 --- a/cypress/e2e/31-demo.cy.ts +++ b/cypress/e2e/31-demo.cy.ts @@ -1,21 +1,29 @@ -import workflow from '../fixtures/Manual_wait_set.json'; +import { getOutputTableRow } from '../composables/ndv'; +import { getCanvasNodes, openNode } from '../composables/workflow'; +import SIMPLE_WORKFLOW from '../fixtures/Manual_wait_set.json'; +import WORKFLOW_WITH_PINNED from '../fixtures/Webhook_set_pinned.json'; import { importWorkflow, visitDemoPage } from '../pages/demo'; import { errorToast } from '../pages/notifications'; -import { WorkflowPage } from '../pages/workflow'; - -const workflowPage = new WorkflowPage(); describe('Demo', () => { beforeEach(() => { cy.overrideSettings({ previewMode: true }); - cy.signout(); }); it('can import template', () => { visitDemoPage(); errorToast().should('not.exist'); - importWorkflow(workflow); - workflowPage.getters.canvasNodes().should('have.length', 3); + importWorkflow(SIMPLE_WORKFLOW); + getCanvasNodes().should('have.length', 3); + }); + + it('can import workflow with pin data', () => { + visitDemoPage(); + importWorkflow(WORKFLOW_WITH_PINNED); + getCanvasNodes().should('have.length', 2); + openNode('Webhook'); + getOutputTableRow(0).should('include.text', 'headers'); + getOutputTableRow(1).should('include.text', 'dragons'); }); it('can override theme to dark', () => { diff --git a/package.json b/package.json index 09c576a8c3..bbcfb52778 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "n8n-monorepo", - "version": "1.65.0", + "version": "1.66.0", "private": true, "engines": { "node": ">=20.15", diff --git a/packages/@n8n/api-types/package.json b/packages/@n8n/api-types/package.json index b3d28f18cc..252794f7d1 100644 --- a/packages/@n8n/api-types/package.json +++ b/packages/@n8n/api-types/package.json @@ -1,6 +1,6 @@ { "name": "@n8n/api-types", - "version": "0.5.0", + "version": "0.6.0", "scripts": { "clean": "rimraf dist .turbo", "dev": "pnpm watch", diff --git a/packages/@n8n/config/package.json b/packages/@n8n/config/package.json index ad583c1108..d1dddf3e70 100644 --- a/packages/@n8n/config/package.json +++ b/packages/@n8n/config/package.json @@ -1,6 +1,6 @@ { "name": "@n8n/config", - "version": "1.15.0", + "version": "1.16.0", "scripts": { "clean": "rimraf dist .turbo", "dev": "pnpm watch", diff --git a/packages/@n8n/nodes-langchain/nodes/tools/ToolVectorStore/ToolVectorStore.node.ts b/packages/@n8n/nodes-langchain/nodes/tools/ToolVectorStore/ToolVectorStore.node.ts index b4016f06ca..6f4aa19fb3 100644 --- a/packages/@n8n/nodes-langchain/nodes/tools/ToolVectorStore/ToolVectorStore.node.ts +++ b/packages/@n8n/nodes-langchain/nodes/tools/ToolVectorStore/ToolVectorStore.node.ts @@ -63,7 +63,7 @@ export class ToolVectorStore implements INodeType { name: 'name', type: 'string', default: '', - placeholder: 'e.g. state_of_union_address', + placeholder: 'e.g. company_knowledge_base', validateType: 'string-alphanumeric', description: 'Name of the vector store', }, @@ -72,7 +72,7 @@ export class ToolVectorStore implements INodeType { name: 'description', type: 'string', default: '', - placeholder: 'The most recent state of the Union address', + placeholder: 'Retrieves data about [insert information about your data here]...', typeOptions: { rows: 3, }, diff --git a/packages/@n8n/nodes-langchain/package.json b/packages/@n8n/nodes-langchain/package.json index 3672f73464..05bf348851 100644 --- a/packages/@n8n/nodes-langchain/package.json +++ b/packages/@n8n/nodes-langchain/package.json @@ -1,6 +1,6 @@ { "name": "@n8n/n8n-nodes-langchain", - "version": "1.65.0", + "version": "1.66.0", "description": "", "main": "index.js", "scripts": { diff --git a/packages/@n8n/permissions/package.json b/packages/@n8n/permissions/package.json index d92c2c20ac..2e68a6b628 100644 --- a/packages/@n8n/permissions/package.json +++ b/packages/@n8n/permissions/package.json @@ -1,6 +1,6 @@ { "name": "@n8n/permissions", - "version": "0.15.0", + "version": "0.16.0", "scripts": { "clean": "rimraf dist .turbo", "dev": "pnpm watch", diff --git a/packages/@n8n/task-runner/package.json b/packages/@n8n/task-runner/package.json index fca6b9a22c..74c3e323bb 100644 --- a/packages/@n8n/task-runner/package.json +++ b/packages/@n8n/task-runner/package.json @@ -1,6 +1,6 @@ { "name": "@n8n/task-runner", - "version": "1.3.0", + "version": "1.4.0", "scripts": { "clean": "rimraf dist .turbo", "start": "node dist/start.js", diff --git a/packages/cli/package.json b/packages/cli/package.json index 2d95aa1cfa..0404b8c116 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "n8n", - "version": "1.65.0", + "version": "1.66.0", "description": "n8n Workflow Automation Tool", "main": "dist/index", "types": "dist/index.d.ts", diff --git a/packages/cli/src/scaling/__tests__/publisher.service.test.ts b/packages/cli/src/scaling/__tests__/publisher.service.test.ts index f69ad08cb5..fb0a340c11 100644 --- a/packages/cli/src/scaling/__tests__/publisher.service.test.ts +++ b/packages/cli/src/scaling/__tests__/publisher.service.test.ts @@ -65,6 +65,42 @@ describe('Publisher', () => { JSON.stringify({ ...msg, senderId: hostId, selfSend: false, debounce: true }), ); }); + + it('should not debounce `add-webhooks-triggers-and-pollers`', async () => { + const publisher = new Publisher(logger, redisClientService, instanceSettings); + const msg = mock({ command: 'add-webhooks-triggers-and-pollers' }); + + await publisher.publishCommand(msg); + + expect(client.publish).toHaveBeenCalledWith( + 'n8n.commands', + JSON.stringify({ + ...msg, + _isMockObject: true, + senderId: hostId, + selfSend: true, + debounce: false, + }), + ); + }); + + it('should not debounce `remove-triggers-and-pollers`', async () => { + const publisher = new Publisher(logger, redisClientService, instanceSettings); + const msg = mock({ command: 'remove-triggers-and-pollers' }); + + await publisher.publishCommand(msg); + + expect(client.publish).toHaveBeenCalledWith( + 'n8n.commands', + JSON.stringify({ + ...msg, + _isMockObject: true, + senderId: hostId, + selfSend: true, + debounce: false, + }), + ); + }); }); describe('publishWorkerResponse', () => { diff --git a/packages/cli/src/scaling/constants.ts b/packages/cli/src/scaling/constants.ts index e56596e4a0..336da006d8 100644 --- a/packages/cli/src/scaling/constants.ts +++ b/packages/cli/src/scaling/constants.ts @@ -1,3 +1,5 @@ +import type { PubSub } from './pubsub/pubsub.types'; + export const QUEUE_NAME = 'jobs'; export const JOB_TYPE_NAME = 'job'; @@ -11,7 +13,7 @@ export const WORKER_RESPONSE_PUBSUB_CHANNEL = 'n8n.worker-response'; /** * Commands that should be sent to the sender as well, e.g. during workflow activation and * deactivation in multi-main setup. */ -export const SELF_SEND_COMMANDS = new Set([ +export const SELF_SEND_COMMANDS = new Set([ 'add-webhooks-triggers-and-pollers', 'remove-triggers-and-pollers', ]); @@ -20,7 +22,8 @@ export const SELF_SEND_COMMANDS = new Set([ * Commands that should not be debounced when received, e.g. during webhook handling in * multi-main setup. */ -export const IMMEDIATE_COMMANDS = new Set([ +export const IMMEDIATE_COMMANDS = new Set([ 'add-webhooks-triggers-and-pollers', + 'remove-triggers-and-pollers', 'relay-execution-lifecycle-event', ]); diff --git a/packages/cli/src/services/__tests__/url.service.test.ts b/packages/cli/src/services/__tests__/url.service.test.ts new file mode 100644 index 0000000000..c89d62789c --- /dev/null +++ b/packages/cli/src/services/__tests__/url.service.test.ts @@ -0,0 +1,43 @@ +import type { GlobalConfig } from '@n8n/config'; +import { mock } from 'jest-mock-extended'; + +import config from '@/config'; + +import { UrlService } from '../url.service'; + +describe('UrlService', () => { + beforeEach(() => { + process.env.WEBHOOK_URL = undefined; + config.load(config.default); + }); + + describe('getInstanceBaseUrl', () => { + it('should set URL from N8N_EDITOR_BASE_URL', () => { + config.set('editorBaseUrl', 'https://example.com/'); + process.env.WEBHOOK_URL = undefined; + const urlService = new UrlService(mock()); + expect(urlService.getInstanceBaseUrl()).toBe('https://example.com'); + }); + + it('should set URL from WEBHOOK_URL', () => { + config.set('editorBaseUrl', ''); + process.env.WEBHOOK_URL = 'https://example.com/'; + const urlService = new UrlService(mock()); + expect(urlService.getInstanceBaseUrl()).toBe('https://example.com'); + }); + + it('should trim quotes when setting URL from N8N_EDITOR_BASE_URL', () => { + config.set('editorBaseUrl', '"https://example.com"'); + process.env.WEBHOOK_URL = undefined; + const urlService = new UrlService(mock()); + expect(urlService.getInstanceBaseUrl()).toBe('https://example.com'); + }); + + it('should trim quotes when setting URL from WEBHOOK_URL', () => { + config.set('editorBaseUrl', ''); + process.env.WEBHOOK_URL = '"https://example.com/"'; + const urlService = new UrlService(mock()); + expect(urlService.getInstanceBaseUrl()).toBe('https://example.com'); + }); + }); +}); diff --git a/packages/cli/src/services/url.service.ts b/packages/cli/src/services/url.service.ts index 43b53f28ad..f9d3fcdbbd 100644 --- a/packages/cli/src/services/url.service.ts +++ b/packages/cli/src/services/url.service.ts @@ -14,7 +14,7 @@ export class UrlService { /** Returns the base URL of the webhooks */ getWebhookBaseUrl() { - let urlBaseWebhook = process.env.WEBHOOK_URL ?? this.baseUrl; + let urlBaseWebhook = this.trimQuotes(process.env.WEBHOOK_URL) || this.baseUrl; if (!urlBaseWebhook.endsWith('/')) { urlBaseWebhook += '/'; } @@ -23,7 +23,7 @@ export class UrlService { /** Return the n8n instance base URL without trailing slash */ getInstanceBaseUrl(): string { - const n8nBaseUrl = config.getEnv('editorBaseUrl') || this.getWebhookBaseUrl(); + const n8nBaseUrl = this.trimQuotes(config.getEnv('editorBaseUrl')) || this.getWebhookBaseUrl(); return n8nBaseUrl.endsWith('/') ? n8nBaseUrl.slice(0, n8nBaseUrl.length - 1) : n8nBaseUrl; } @@ -36,4 +36,9 @@ export class UrlService { } return `${protocol}://${host}:${port}${path}`; } + + /** Remove leading and trailing double quotes from a URL. */ + private trimQuotes(url?: string) { + return url?.replace(/^["]+|["]+$/g, '') ?? ''; + } } diff --git a/packages/core/package.json b/packages/core/package.json index 0e010052b2..f9a9d90856 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "n8n-core", - "version": "1.65.0", + "version": "1.66.0", "description": "Core functionality of n8n", "main": "dist/index", "types": "dist/index.d.ts", diff --git a/packages/design-system/package.json b/packages/design-system/package.json index 8aec0ab60f..d2f39c3e0e 100644 --- a/packages/design-system/package.json +++ b/packages/design-system/package.json @@ -1,6 +1,6 @@ { "name": "n8n-design-system", - "version": "1.55.0", + "version": "1.56.0", "main": "src/main.ts", "import": "src/main.ts", "scripts": { diff --git a/packages/editor-ui/package.json b/packages/editor-ui/package.json index 5a4280252e..880ac59e3d 100644 --- a/packages/editor-ui/package.json +++ b/packages/editor-ui/package.json @@ -1,6 +1,6 @@ { "name": "n8n-editor-ui", - "version": "1.65.0", + "version": "1.66.0", "description": "Workflow Editor UI for n8n", "main": "index.js", "scripts": { diff --git a/packages/editor-ui/src/Interface.ts b/packages/editor-ui/src/Interface.ts index e639f537df..b6691ac76b 100644 --- a/packages/editor-ui/src/Interface.ts +++ b/packages/editor-ui/src/Interface.ts @@ -52,7 +52,6 @@ import type { AI_NODE_CREATOR_VIEW, CREDENTIAL_EDIT_MODAL_KEY, SignInType, - FAKE_DOOR_FEATURES, TRIGGER_NODE_CREATOR_VIEW, REGULAR_NODE_CREATOR_VIEW, AI_OTHERS_NODE_CREATOR_VIEW, @@ -62,7 +61,6 @@ import type { BulkCommand, Undoable } from '@/models/history'; import type { PartialBy, TupleToUnion } from '@/utils/typeHelpers'; import type { ProjectSharingData } from '@/types/projects.types'; -import type { BaseTextKey } from './plugins/i18n'; export * from 'n8n-design-system/types'; @@ -1036,24 +1034,6 @@ export interface NotificationOptions extends Partial message: string | ElementNotificationOptions['message']; } -export type IFakeDoor = { - id: FAKE_DOOR_FEATURES; - featureName: BaseTextKey; - icon?: string; - infoText?: BaseTextKey; - actionBoxTitle: BaseTextKey; - actionBoxDescription: BaseTextKey; - actionBoxButtonLabel?: BaseTextKey; - linkURL: string; - uiLocations: IFakeDoorLocation[]; -}; - -export type IFakeDoorLocation = - | 'settings' - | 'settings/users' - | 'credentialsModal' - | 'workflowShareModal'; - export type NodeFilterType = | typeof REGULAR_NODE_CREATOR_VIEW | typeof TRIGGER_NODE_CREATOR_VIEW diff --git a/packages/editor-ui/src/components/AssignmentCollection/__tests__/Assignment.test.ts b/packages/editor-ui/src/components/AssignmentCollection/Assignment.test.ts similarity index 97% rename from packages/editor-ui/src/components/AssignmentCollection/__tests__/Assignment.test.ts rename to packages/editor-ui/src/components/AssignmentCollection/Assignment.test.ts index be96dc8dd9..164047d475 100644 --- a/packages/editor-ui/src/components/AssignmentCollection/__tests__/Assignment.test.ts +++ b/packages/editor-ui/src/components/AssignmentCollection/Assignment.test.ts @@ -1,7 +1,7 @@ import { createComponentRenderer } from '@/__tests__/render'; import { createTestingPinia } from '@pinia/testing'; import userEvent from '@testing-library/user-event'; -import Assignment from '../Assignment.vue'; +import Assignment from './Assignment.vue'; import { defaultSettings } from '@/__tests__/defaults'; import { STORES } from '@/constants'; import { merge } from 'lodash-es'; diff --git a/packages/editor-ui/src/components/AssignmentCollection/__tests__/AssignmentCollection.test.ts b/packages/editor-ui/src/components/AssignmentCollection/AssignmentCollection.test.ts similarity index 97% rename from packages/editor-ui/src/components/AssignmentCollection/__tests__/AssignmentCollection.test.ts rename to packages/editor-ui/src/components/AssignmentCollection/AssignmentCollection.test.ts index 626df62a4a..534c734638 100644 --- a/packages/editor-ui/src/components/AssignmentCollection/__tests__/AssignmentCollection.test.ts +++ b/packages/editor-ui/src/components/AssignmentCollection/AssignmentCollection.test.ts @@ -4,7 +4,7 @@ import { createTestingPinia } from '@pinia/testing'; import userEvent from '@testing-library/user-event'; import { fireEvent, within } from '@testing-library/vue'; import * as workflowHelpers from '@/composables/useWorkflowHelpers'; -import AssignmentCollection from '../AssignmentCollection.vue'; +import AssignmentCollection from './AssignmentCollection.vue'; import { STORES } from '@/constants'; import { cleanupAppModals, createAppModals, SETTINGS_STORE_DEFAULT_STATE } from '@/__tests__/utils'; @@ -117,7 +117,7 @@ describe('AssignmentCollection.vue', () => { await dropAssignment({ key: 'objectKey', value: {}, dropArea }); await dropAssignment({ key: 'arrayKey', value: [], dropArea }); - let assignments = await findAllByTestId('assignment'); + const assignments = await findAllByTestId('assignment'); expect(assignments.length).toBe(5); expect(getAssignmentType(assignments[0])).toEqual('Boolean'); diff --git a/packages/editor-ui/src/components/AssignmentCollection/AssignmentCollection.vue b/packages/editor-ui/src/components/AssignmentCollection/AssignmentCollection.vue index fb13948302..24545bd7b9 100644 --- a/packages/editor-ui/src/components/AssignmentCollection/AssignmentCollection.vue +++ b/packages/editor-ui/src/components/AssignmentCollection/AssignmentCollection.vue @@ -98,10 +98,10 @@ function getIssues(index: number): string[] { return issues.value[`${props.parameter.name}.${index}`] ?? []; } -function optionSelected(action: 'clearAll' | 'addAll') { +function optionSelected(action: string) { if (action === 'clearAll') { state.paramValue.assignments = []; - } else { + } else if (action === 'addAll' && inputData.value) { const newAssignments = inputDataToAssignments(inputData.value); state.paramValue.assignments = state.paramValue.assignments.concat(newAssignments); } diff --git a/packages/editor-ui/src/components/AssignmentCollection/__tests__/TypeSelect.test.ts b/packages/editor-ui/src/components/AssignmentCollection/TypeSelect.test.ts similarity index 96% rename from packages/editor-ui/src/components/AssignmentCollection/__tests__/TypeSelect.test.ts rename to packages/editor-ui/src/components/AssignmentCollection/TypeSelect.test.ts index 78f4b683c6..19360f22d3 100644 --- a/packages/editor-ui/src/components/AssignmentCollection/__tests__/TypeSelect.test.ts +++ b/packages/editor-ui/src/components/AssignmentCollection/TypeSelect.test.ts @@ -1,7 +1,7 @@ import { createComponentRenderer } from '@/__tests__/render'; import { createTestingPinia } from '@pinia/testing'; import userEvent from '@testing-library/user-event'; -import TypeSelect from '../TypeSelect.vue'; +import TypeSelect from './TypeSelect.vue'; const DEFAULT_SETUP = { pinia: createTestingPinia(), diff --git a/packages/editor-ui/src/components/__tests__/BannersStack.test.ts b/packages/editor-ui/src/components/BannersStack.test.ts similarity index 100% rename from packages/editor-ui/src/components/__tests__/BannersStack.test.ts rename to packages/editor-ui/src/components/BannersStack.test.ts diff --git a/packages/editor-ui/src/components/__tests__/ButtonParameter.test.ts b/packages/editor-ui/src/components/ButtonParameter.test.ts similarity index 100% rename from packages/editor-ui/src/components/__tests__/ButtonParameter.test.ts rename to packages/editor-ui/src/components/ButtonParameter.test.ts diff --git a/packages/editor-ui/src/components/__tests__/ChangePasswordModal.test.ts b/packages/editor-ui/src/components/ChangePasswordModal.test.ts similarity index 100% rename from packages/editor-ui/src/components/__tests__/ChangePasswordModal.test.ts rename to packages/editor-ui/src/components/ChangePasswordModal.test.ts diff --git a/packages/editor-ui/src/components/__tests__/ChatEmbedModal.test.ts b/packages/editor-ui/src/components/ChatEmbedModal.test.ts similarity index 100% rename from packages/editor-ui/src/components/__tests__/ChatEmbedModal.test.ts rename to packages/editor-ui/src/components/ChatEmbedModal.test.ts diff --git a/packages/editor-ui/src/components/CodeNodeEditor/completions/__tests__/itemField.completions.test.ts b/packages/editor-ui/src/components/CodeNodeEditor/completions/itemField.completions.test.ts similarity index 96% rename from packages/editor-ui/src/components/CodeNodeEditor/completions/__tests__/itemField.completions.test.ts rename to packages/editor-ui/src/components/CodeNodeEditor/completions/itemField.completions.test.ts index 98ba5e0f6f..7bcba353e4 100644 --- a/packages/editor-ui/src/components/CodeNodeEditor/completions/__tests__/itemField.completions.test.ts +++ b/packages/editor-ui/src/components/CodeNodeEditor/completions/itemField.completions.test.ts @@ -1,6 +1,6 @@ import { CompletionContext } from '@codemirror/autocomplete'; import { EditorSelection, EditorState } from '@codemirror/state'; -import { useItemFieldCompletions } from '../itemField.completions'; +import { useItemFieldCompletions } from './itemField.completions'; describe('inputMethodCompletions', () => { test('should return completions for $input.item.|', () => { diff --git a/packages/editor-ui/src/components/__tests__/CollaborationPane.test.ts b/packages/editor-ui/src/components/CollaborationPane.test.ts similarity index 100% rename from packages/editor-ui/src/components/__tests__/CollaborationPane.test.ts rename to packages/editor-ui/src/components/CollaborationPane.test.ts diff --git a/packages/editor-ui/src/components/__tests__/CollectionParameter.test.ts b/packages/editor-ui/src/components/CollectionParameter.test.ts similarity index 94% rename from packages/editor-ui/src/components/__tests__/CollectionParameter.test.ts rename to packages/editor-ui/src/components/CollectionParameter.test.ts index 8f1c0b0af2..4b0e25c063 100644 --- a/packages/editor-ui/src/components/__tests__/CollectionParameter.test.ts +++ b/packages/editor-ui/src/components/CollectionParameter.test.ts @@ -1,6 +1,6 @@ import { createComponentRenderer } from '@/__tests__/render'; import { createTestingPinia } from '@pinia/testing'; -import CollectionParameter from '../CollectionParameter.vue'; +import CollectionParameter from './CollectionParameter.vue'; const renderComponent = createComponentRenderer(CollectionParameter, { pinia: createTestingPinia(), diff --git a/packages/editor-ui/src/components/__tests__/CommunityPackageInstallModal.spec.ts b/packages/editor-ui/src/components/CommunityPackageInstallModal.spec.ts similarity index 95% rename from packages/editor-ui/src/components/__tests__/CommunityPackageInstallModal.spec.ts rename to packages/editor-ui/src/components/CommunityPackageInstallModal.spec.ts index a93ba7c878..bbf2701068 100644 --- a/packages/editor-ui/src/components/__tests__/CommunityPackageInstallModal.spec.ts +++ b/packages/editor-ui/src/components/CommunityPackageInstallModal.spec.ts @@ -1,5 +1,5 @@ import { createComponentRenderer } from '@/__tests__/render'; -import CommunityPackageInstallModal from '../CommunityPackageInstallModal.vue'; +import CommunityPackageInstallModal from './CommunityPackageInstallModal.vue'; import { createTestingPinia } from '@pinia/testing'; import { COMMUNITY_PACKAGE_INSTALL_MODAL_KEY, STORES } from '@/constants'; import userEvent from '@testing-library/user-event'; diff --git a/packages/editor-ui/src/components/__tests__/CopyInput.test.ts b/packages/editor-ui/src/components/CopyInput.test.ts similarity index 100% rename from packages/editor-ui/src/components/__tests__/CopyInput.test.ts rename to packages/editor-ui/src/components/CopyInput.test.ts diff --git a/packages/editor-ui/src/components/CredentialEdit/CredentialEdit.vue b/packages/editor-ui/src/components/CredentialEdit/CredentialEdit.vue index efbde8d1fb..c6d5ee07ec 100644 --- a/packages/editor-ui/src/components/CredentialEdit/CredentialEdit.vue +++ b/packages/editor-ui/src/components/CredentialEdit/CredentialEdit.vue @@ -22,7 +22,6 @@ import { NodeHelpers } from 'n8n-workflow'; import CredentialConfig from '@/components/CredentialEdit/CredentialConfig.vue'; import CredentialInfo from '@/components/CredentialEdit/CredentialInfo.vue'; import CredentialSharing from '@/components/CredentialEdit/CredentialSharing.ee.vue'; -import FeatureComingSoon from '@/components/FeatureComingSoon.vue'; import InlineNameEdit from '@/components/InlineNameEdit.vue'; import Modal from '@/components/Modal.vue'; import SaveButton from '@/components/SaveButton.vue'; @@ -518,14 +517,13 @@ async function loadCurrentCredential() { function onTabSelect(tab: string) { activeTab.value = tab; - const tabName: string = tab.replaceAll('coming-soon/', ''); const credType: string = credentialType.value ? credentialType.value.name : ''; const activeNode: INode | null = ndvStore.activeNode; telemetry.track('User viewed credential tab', { credential_type: credType, node_type: activeNode ? activeNode.type : null, - tab: tabName, + tab, workflow_id: workflowsStore.workflowId, credential_id: credentialId.value, sharing_enabled: EnterpriseEditionFeature.Sharing, @@ -1130,9 +1128,6 @@ function resetCredentialData(): void {
-
- -
diff --git a/packages/editor-ui/src/components/CredentialEdit/CredentialSharing.ee.vue b/packages/editor-ui/src/components/CredentialEdit/CredentialSharing.ee.vue index 1c2242195c..e54f247cea 100644 --- a/packages/editor-ui/src/components/CredentialEdit/CredentialSharing.ee.vue +++ b/packages/editor-ui/src/components/CredentialEdit/CredentialSharing.ee.vue @@ -1,6 +1,7 @@ diff --git a/packages/editor-ui/src/components/CredentialEdit/__tests__/OauthButton.test.ts b/packages/editor-ui/src/components/CredentialEdit/OauthButton.test.ts similarity index 100% rename from packages/editor-ui/src/components/CredentialEdit/__tests__/OauthButton.test.ts rename to packages/editor-ui/src/components/CredentialEdit/OauthButton.test.ts diff --git a/packages/editor-ui/src/components/__tests__/CredentialIcon.test.ts b/packages/editor-ui/src/components/CredentialIcon.test.ts similarity index 97% rename from packages/editor-ui/src/components/__tests__/CredentialIcon.test.ts rename to packages/editor-ui/src/components/CredentialIcon.test.ts index 79bcc4b882..32a8ef6d72 100644 --- a/packages/editor-ui/src/components/__tests__/CredentialIcon.test.ts +++ b/packages/editor-ui/src/components/CredentialIcon.test.ts @@ -7,7 +7,7 @@ import CredentialIcon from '@/components/CredentialIcon.vue'; import { createComponentRenderer } from '@/__tests__/render'; import { useCredentialsStore } from '@/stores/credentials.store'; import { useRootStore } from '@/stores/root.store'; -import { useNodeTypesStore } from '../../stores/nodeTypes.store'; +import { useNodeTypesStore } from '../stores/nodeTypes.store'; describe('CredentialIcon', () => { const renderComponent = createComponentRenderer(CredentialIcon, { diff --git a/packages/editor-ui/src/components/DropArea/__tests__/DropArea.test.ts b/packages/editor-ui/src/components/DropArea/DropArea.test.ts similarity index 96% rename from packages/editor-ui/src/components/DropArea/__tests__/DropArea.test.ts rename to packages/editor-ui/src/components/DropArea/DropArea.test.ts index d9c8371d72..2ad454006a 100644 --- a/packages/editor-ui/src/components/DropArea/__tests__/DropArea.test.ts +++ b/packages/editor-ui/src/components/DropArea/DropArea.test.ts @@ -4,7 +4,7 @@ import { createTestingPinia } from '@pinia/testing'; import userEvent from '@testing-library/user-event'; import { fireEvent } from '@testing-library/vue'; import { createPinia, setActivePinia } from 'pinia'; -import DropArea from '../DropArea.vue'; +import DropArea from './DropArea.vue'; const renderComponent = createComponentRenderer(DropArea, { pinia: createTestingPinia(), diff --git a/packages/editor-ui/src/components/Error/__tests__/NodeErrorView.test.ts b/packages/editor-ui/src/components/Error/NodeErrorView.test.ts similarity index 100% rename from packages/editor-ui/src/components/Error/__tests__/NodeErrorView.test.ts rename to packages/editor-ui/src/components/Error/NodeErrorView.test.ts diff --git a/packages/editor-ui/src/components/__tests__/ExpressionEditorModalInput.test.ts b/packages/editor-ui/src/components/ExpressionEditorModalInput.test.ts similarity index 100% rename from packages/editor-ui/src/components/__tests__/ExpressionEditorModalInput.test.ts rename to packages/editor-ui/src/components/ExpressionEditorModalInput.test.ts diff --git a/packages/editor-ui/src/components/__tests__/ExpressionParameterInput.test.ts b/packages/editor-ui/src/components/ExpressionParameterInput.test.ts similarity index 100% rename from packages/editor-ui/src/components/__tests__/ExpressionParameterInput.test.ts rename to packages/editor-ui/src/components/ExpressionParameterInput.test.ts diff --git a/packages/editor-ui/src/components/FeatureComingSoon.vue b/packages/editor-ui/src/components/FeatureComingSoon.vue deleted file mode 100644 index fd24a35196..0000000000 --- a/packages/editor-ui/src/components/FeatureComingSoon.vue +++ /dev/null @@ -1,82 +0,0 @@ - - - - - diff --git a/packages/editor-ui/src/components/FilterConditions/__tests__/FilterConditions.test.ts b/packages/editor-ui/src/components/FilterConditions/FilterConditions.test.ts similarity index 99% rename from packages/editor-ui/src/components/FilterConditions/__tests__/FilterConditions.test.ts rename to packages/editor-ui/src/components/FilterConditions/FilterConditions.test.ts index 0f00c1bd3d..24cd1ef73c 100644 --- a/packages/editor-ui/src/components/FilterConditions/__tests__/FilterConditions.test.ts +++ b/packages/editor-ui/src/components/FilterConditions/FilterConditions.test.ts @@ -6,7 +6,7 @@ import { useNDVStore } from '@/stores/ndv.store'; import { createTestingPinia } from '@pinia/testing'; import userEvent from '@testing-library/user-event'; import { within, waitFor } from '@testing-library/vue'; -import { getFilterOperator } from '../utils'; +import { getFilterOperator } from './utils'; import { get } from 'lodash-es'; const DEFAULT_SETUP = { diff --git a/packages/editor-ui/src/components/FilterConditions/__tests__/utils.test.ts b/packages/editor-ui/src/components/FilterConditions/utils.test.ts similarity index 98% rename from packages/editor-ui/src/components/FilterConditions/__tests__/utils.test.ts rename to packages/editor-ui/src/components/FilterConditions/utils.test.ts index aac05defc1..7cdfb76e23 100644 --- a/packages/editor-ui/src/components/FilterConditions/__tests__/utils.test.ts +++ b/packages/editor-ui/src/components/FilterConditions/utils.test.ts @@ -1,4 +1,4 @@ -import { getFilterOperator, handleOperatorChange, inferOperatorType } from '../utils'; +import { getFilterOperator, handleOperatorChange, inferOperatorType } from './utils'; describe('FilterConditions > utils', () => { describe('handleOperatorChange', () => { diff --git a/packages/editor-ui/src/components/__tests__/HtmlEditor.test.ts b/packages/editor-ui/src/components/HtmlEditor.test.ts similarity index 97% rename from packages/editor-ui/src/components/__tests__/HtmlEditor.test.ts rename to packages/editor-ui/src/components/HtmlEditor.test.ts index 7d5f80d428..5de134f652 100644 --- a/packages/editor-ui/src/components/__tests__/HtmlEditor.test.ts +++ b/packages/editor-ui/src/components/HtmlEditor.test.ts @@ -7,7 +7,7 @@ import HtmlEditor from '@/components/HtmlEditor/HtmlEditor.vue'; import { userEvent } from '@testing-library/user-event'; import { waitFor } from '@testing-library/vue'; import { setActivePinia } from 'pinia'; -import { htmlEditorEventBus } from '../../event-bus'; +import { htmlEditorEventBus } from '../event-bus'; const DEFAULT_SETUP = { props: { diff --git a/packages/editor-ui/src/components/InlineExpressionEditor/__tests__/InlineExpressionEditorOutput.test.ts b/packages/editor-ui/src/components/InlineExpressionEditor/InlineExpressionEditorOutput.test.ts similarity index 96% rename from packages/editor-ui/src/components/InlineExpressionEditor/__tests__/InlineExpressionEditorOutput.test.ts rename to packages/editor-ui/src/components/InlineExpressionEditor/InlineExpressionEditorOutput.test.ts index 8728fecb8c..76d3f2452c 100644 --- a/packages/editor-ui/src/components/InlineExpressionEditor/__tests__/InlineExpressionEditorOutput.test.ts +++ b/packages/editor-ui/src/components/InlineExpressionEditor/InlineExpressionEditorOutput.test.ts @@ -1,6 +1,6 @@ import { renderComponent } from '@/__tests__/render'; import { createTestingPinia } from '@pinia/testing'; -import InlineExpressionEditorOutput from '../InlineExpressionEditorOutput.vue'; +import InlineExpressionEditorOutput from './InlineExpressionEditorOutput.vue'; describe('InlineExpressionEditorOutput.vue', () => { test('should render duplicate segments correctly', async () => { diff --git a/packages/editor-ui/src/components/InlineExpressionEditor/__tests__/InlineExpressionTip.test.ts b/packages/editor-ui/src/components/InlineExpressionEditor/InlineExpressionTip.test.ts similarity index 100% rename from packages/editor-ui/src/components/InlineExpressionEditor/__tests__/InlineExpressionTip.test.ts rename to packages/editor-ui/src/components/InlineExpressionEditor/InlineExpressionTip.test.ts diff --git a/packages/editor-ui/src/components/InputTriple/__tests__/InputTriple.test.ts b/packages/editor-ui/src/components/InputTriple/InputTriple.test.ts similarity index 95% rename from packages/editor-ui/src/components/InputTriple/__tests__/InputTriple.test.ts rename to packages/editor-ui/src/components/InputTriple/InputTriple.test.ts index a29373ec25..a802317e0e 100644 --- a/packages/editor-ui/src/components/InputTriple/__tests__/InputTriple.test.ts +++ b/packages/editor-ui/src/components/InputTriple/InputTriple.test.ts @@ -1,5 +1,5 @@ import { createComponentRenderer } from '@/__tests__/render'; -import InputTriple from '../InputTriple.vue'; +import InputTriple from './InputTriple.vue'; const renderComponent = createComponentRenderer(InputTriple); diff --git a/packages/editor-ui/src/components/InviteUsersModal.vue b/packages/editor-ui/src/components/InviteUsersModal.vue index aa698371e6..e43fc7ab4a 100644 --- a/packages/editor-ui/src/components/InviteUsersModal.vue +++ b/packages/editor-ui/src/components/InviteUsersModal.vue @@ -15,6 +15,7 @@ import { useSettingsStore } from '@/stores/settings.store'; import { useUIStore } from '@/stores/ui.store'; import { createFormEventBus, createEventBus } from 'n8n-design-system/utils'; import { useClipboard } from '@/composables/useClipboard'; +import { usePageRedirectionHelper } from '@/composables/usePageRedirectionHelper'; const NAME_EMAIL_FORMAT_REGEX = /^.* <(.*)>$/; @@ -43,6 +44,7 @@ export default defineComponent({ return { clipboard, ...useToast(), + ...usePageRedirectionHelper(), }; }, data() { @@ -277,7 +279,7 @@ export default defineComponent({ } }, goToUpgradeAdvancedPermissions() { - void this.uiStore.goToUpgrade('advanced-permissions', 'upgrade-advanced-permissions'); + void this.goToUpgrade('advanced-permissions', 'upgrade-advanced-permissions'); }, }, }); diff --git a/packages/editor-ui/src/components/__tests__/JsEditor.test.ts b/packages/editor-ui/src/components/JsEditor.test.ts similarity index 100% rename from packages/editor-ui/src/components/__tests__/JsEditor.test.ts rename to packages/editor-ui/src/components/JsEditor.test.ts diff --git a/packages/editor-ui/src/components/__tests__/JsonEditor.test.ts b/packages/editor-ui/src/components/JsonEditor.test.ts similarity index 100% rename from packages/editor-ui/src/components/__tests__/JsonEditor.test.ts rename to packages/editor-ui/src/components/JsonEditor.test.ts diff --git a/packages/editor-ui/src/components/MainHeader/WorkflowDetails.vue b/packages/editor-ui/src/components/MainHeader/WorkflowDetails.vue index 33a830af92..e13f47b3eb 100644 --- a/packages/editor-ui/src/components/MainHeader/WorkflowDetails.vue +++ b/packages/editor-ui/src/components/MainHeader/WorkflowDetails.vue @@ -56,6 +56,7 @@ import { useTelemetry } from '@/composables/useTelemetry'; import type { BaseTextKey } from '@/plugins/i18n'; import { useNpsSurveyStore } from '@/stores/npsSurvey.store'; import { useNodeViewVersionSwitcher } from '@/composables/useNodeViewVersionSwitcher'; +import { usePageRedirectionHelper } from '@/composables/usePageRedirectionHelper'; const props = defineProps<{ readOnly?: boolean; @@ -89,6 +90,7 @@ const message = useMessage(); const toast = useToast(); const documentTitle = useDocumentTitle(); const workflowHelpers = useWorkflowHelpers({ router }); +const pageRedirectionHelper = usePageRedirectionHelper(); const isTagsEditEnabled = ref(false); const isNameEditEnabled = ref(false); @@ -584,11 +586,11 @@ async function onWorkflowMenuSelect(action: WORKFLOW_MENU_ACTIONS): Promise { break; } case 'cloud-admin': { - void cloudPlanStore.redirectToDashboard(); + void pageRedirectionHelper.goToDashboard(); break; } case 'quickstart': diff --git a/packages/editor-ui/src/components/__tests__/MainSidebarSourceControl.test.ts b/packages/editor-ui/src/components/MainSidebarSourceControl.test.ts similarity index 100% rename from packages/editor-ui/src/components/__tests__/MainSidebarSourceControl.test.ts rename to packages/editor-ui/src/components/MainSidebarSourceControl.test.ts diff --git a/packages/editor-ui/src/components/Node.vue b/packages/editor-ui/src/components/Node.vue index 5e2cc7e8ca..1f1ea90ab5 100644 --- a/packages/editor-ui/src/components/Node.vue +++ b/packages/editor-ui/src/components/Node.vue @@ -271,7 +271,7 @@ const nodeClass = computed(() => { const nodeExecutionStatus = computed(() => { const nodeExecutionRunData = workflowsStore.getWorkflowRunData?.[props.name]; if (nodeExecutionRunData) { - return nodeExecutionRunData.filter(Boolean)[0]?.executionStatus ?? ''; + return nodeExecutionRunData.filter(Boolean)?.[0]?.executionStatus ?? ''; } return ''; }); diff --git a/packages/editor-ui/src/components/Node/NodeCreator/__tests__/CategoryItem.test.ts b/packages/editor-ui/src/components/Node/NodeCreator/CategoryItem.test.ts similarity index 95% rename from packages/editor-ui/src/components/Node/NodeCreator/__tests__/CategoryItem.test.ts rename to packages/editor-ui/src/components/Node/NodeCreator/CategoryItem.test.ts index fa290aa550..4d64abb728 100644 --- a/packages/editor-ui/src/components/Node/NodeCreator/__tests__/CategoryItem.test.ts +++ b/packages/editor-ui/src/components/Node/NodeCreator/CategoryItem.test.ts @@ -1,5 +1,5 @@ import { screen } from '@testing-library/vue'; -import CategoryItem from '../ItemTypes/CategoryItem.vue'; +import CategoryItem from './ItemTypes/CategoryItem.vue'; import { createComponentRenderer } from '@/__tests__/render'; const renderComponent = createComponentRenderer(CategoryItem); diff --git a/packages/editor-ui/src/components/Node/NodeCreator/__tests__/ItemsRenderer.test.ts b/packages/editor-ui/src/components/Node/NodeCreator/ItemsRenderer.test.ts similarity index 99% rename from packages/editor-ui/src/components/Node/NodeCreator/__tests__/ItemsRenderer.test.ts rename to packages/editor-ui/src/components/Node/NodeCreator/ItemsRenderer.test.ts index f0737f328e..717e7a1eb1 100644 --- a/packages/editor-ui/src/components/Node/NodeCreator/__tests__/ItemsRenderer.test.ts +++ b/packages/editor-ui/src/components/Node/NodeCreator/ItemsRenderer.test.ts @@ -8,7 +8,7 @@ import { mockActionCreateElement, mockViewCreateElement, mockSectionCreateElement, -} from './utils'; +} from './__tests__/utils'; import ItemsRenderer from '@/components/Node/NodeCreator/Renderers/ItemsRenderer.vue'; import { createComponentRenderer } from '@/__tests__/render'; diff --git a/packages/editor-ui/src/components/Node/NodeCreator/Modes/ActionsMode.vue b/packages/editor-ui/src/components/Node/NodeCreator/Modes/ActionsMode.vue index 0c2816e2d8..728215c02c 100644 --- a/packages/editor-ui/src/components/Node/NodeCreator/Modes/ActionsMode.vue +++ b/packages/editor-ui/src/components/Node/NodeCreator/Modes/ActionsMode.vue @@ -29,6 +29,7 @@ import CategorizedItemsRenderer from '../Renderers/CategorizedItemsRenderer.vue' import type { IDataObject } from 'n8n-workflow'; import { useTelemetry } from '@/composables/useTelemetry'; import { useI18n } from '@/composables/useI18n'; +import { useNodeCreatorStore } from '@/stores/nodeCreator.store'; const emit = defineEmits<{ nodeTypeSelected: [value: [actionKey: string, nodeName: string] | [nodeName: string]]; @@ -47,6 +48,8 @@ const { actionsCategoryLocales, } = useActions(); +const nodeCreatorStore = useNodeCreatorStore(); + // We only inject labels if search is empty const parsedTriggerActions = computed(() => parseActions(actions.value, actionsCategoryLocales.value.triggers, false), @@ -182,7 +185,7 @@ function trackActionsView() { }; void useExternalHooks().run('nodeCreateList.onViewActions', trackingPayload); - telemetry?.trackNodesPanel('nodeCreateList.onViewActions', trackingPayload); + nodeCreatorStore.onViewActions(trackingPayload); } function resetSearch() { @@ -206,7 +209,7 @@ function addHttpNode() { void useExternalHooks().run('nodeCreateList.onActionsCustmAPIClicked', { app_identifier, }); - telemetry?.trackNodesPanel('nodeCreateList.onActionsCustmAPIClicked', { app_identifier }); + nodeCreatorStore.onActionsCustomAPIClicked({ app_identifier }); } // Anonymous component to handle triggers and actions rendering order diff --git a/packages/editor-ui/src/components/Node/NodeCreator/Modes/NodesMode.vue b/packages/editor-ui/src/components/Node/NodeCreator/Modes/NodesMode.vue index 08f8d14cbb..ce833afa17 100644 --- a/packages/editor-ui/src/components/Node/NodeCreator/Modes/NodesMode.vue +++ b/packages/editor-ui/src/components/Node/NodeCreator/Modes/NodesMode.vue @@ -23,7 +23,6 @@ import ItemsRenderer from '../Renderers/ItemsRenderer.vue'; import CategorizedItemsRenderer from '../Renderers/CategorizedItemsRenderer.vue'; import NoResults from '../Panel/NoResults.vue'; import { useI18n } from '@/composables/useI18n'; -import { useTelemetry } from '@/composables/useTelemetry'; import { getNodeIcon, getNodeIconColor, getNodeIconUrl } from '@/utils/nodeTypesUtils'; import { useUIStore } from '@/stores/ui.store'; @@ -36,11 +35,10 @@ const emit = defineEmits<{ }>(); const i18n = useI18n(); -const telemetry = useTelemetry(); const uiStore = useUIStore(); const rootStore = useRootStore(); -const { mergedNodes, actions } = useNodeCreatorStore(); +const { mergedNodes, actions, onSubcategorySelected } = useNodeCreatorStore(); const { pushViewStack, popViewStack } = useViewStacks(); const { registerKeyHook } = useKeyboardNavigation(); @@ -83,7 +81,7 @@ function onSelected(item: INodeCreateElement) { sections: item.properties.sections, }); - telemetry.trackNodesPanel('nodeCreateList.onSubcategorySelected', { + onSubcategorySelected({ subcategory: item.key, }); } @@ -153,9 +151,6 @@ function onSelected(item: INodeCreateElement) { if (item.type === 'link') { window.open(item.properties.url, '_blank'); - telemetry.trackNodesPanel('nodeCreateList.onLinkSelected', { - link: item.properties.url, - }); } } diff --git a/packages/editor-ui/src/components/Node/NodeCreator/__tests__/NodesListPanel.test.ts b/packages/editor-ui/src/components/Node/NodeCreator/NodesListPanel.test.ts similarity index 98% rename from packages/editor-ui/src/components/Node/NodeCreator/__tests__/NodesListPanel.test.ts rename to packages/editor-ui/src/components/Node/NodeCreator/NodesListPanel.test.ts index 62db84094e..c451f2c764 100644 --- a/packages/editor-ui/src/components/Node/NodeCreator/__tests__/NodesListPanel.test.ts +++ b/packages/editor-ui/src/components/Node/NodeCreator/NodesListPanel.test.ts @@ -4,8 +4,8 @@ import { createPinia } from 'pinia'; import { screen, fireEvent } from '@testing-library/vue'; import type { INodeTypeDescription } from 'n8n-workflow'; import { useNodeCreatorStore } from '@/stores/nodeCreator.store'; -import { mockSimplifiedNodeType } from './utils'; -import NodesListPanel from '../Panel/NodesListPanel.vue'; +import { mockSimplifiedNodeType } from './__tests__/utils'; +import NodesListPanel from './Panel/NodesListPanel.vue'; import { REGULAR_NODE_CREATOR_VIEW } from '@/constants'; import type { NodeFilterType } from '@/Interface'; import { createComponentRenderer } from '@/__tests__/render'; diff --git a/packages/editor-ui/src/components/Node/NodeCreator/Panel/NodesListPanel.vue b/packages/editor-ui/src/components/Node/NodeCreator/Panel/NodesListPanel.vue index 72e31851f9..6cb414515d 100644 --- a/packages/editor-ui/src/components/Node/NodeCreator/Panel/NodesListPanel.vue +++ b/packages/editor-ui/src/components/Node/NodeCreator/Panel/NodesListPanel.vue @@ -17,12 +17,15 @@ import SearchBar from './SearchBar.vue'; import ActionsRenderer from '../Modes/ActionsMode.vue'; import NodesRenderer from '../Modes/NodesMode.vue'; import { useI18n } from '@/composables/useI18n'; +import { useDebounce } from '@/composables/useDebounce'; const i18n = useI18n(); +const { callDebounced } = useDebounce(); const { mergedNodes } = useNodeCreatorStore(); const { pushViewStack, popViewStack, updateCurrentViewStack } = useViewStacks(); const { setActiveItemIndex, attachKeydownEvent, detachKeydownEvent } = useKeyboardNavigation(); +const nodeCreatorStore = useNodeCreatorStore(); const activeViewStack = computed(() => useViewStacks().activeViewStack); @@ -55,6 +58,19 @@ function onSearch(value: string) { if (activeViewStack.value.uuid) { updateCurrentViewStack({ search: value }); void setActiveItemIndex(getDefaultActiveIndex(value)); + if (value.length) { + callDebounced( + nodeCreatorStore.onNodeFilterChanged, + { trailing: true, debounceTime: 2000 }, + { + newValue: value, + filteredNodes: activeViewStack.value.items ?? [], + filterMode: activeViewStack.value.rootView ?? 'Regular', + subcategory: activeViewStack.value.subcategory, + title: activeViewStack.value.title, + }, + ); + } } } @@ -299,6 +315,7 @@ function onBackButton() { margin-top: var(--spacing-4xs); font-size: var(--font-size-s); line-height: 19px; + color: var(--color-text-base); font-weight: var(--font-weight-regular); } diff --git a/packages/editor-ui/src/components/Node/NodeCreator/Renderers/CategorizedItemsRenderer.vue b/packages/editor-ui/src/components/Node/NodeCreator/Renderers/CategorizedItemsRenderer.vue index 93b542c72a..b493d53680 100644 --- a/packages/editor-ui/src/components/Node/NodeCreator/Renderers/CategorizedItemsRenderer.vue +++ b/packages/editor-ui/src/components/Node/NodeCreator/Renderers/CategorizedItemsRenderer.vue @@ -8,7 +8,7 @@ import { useKeyboardNavigation } from '../composables/useKeyboardNavigation'; import { useViewStacks } from '../composables/useViewStacks'; import ItemsRenderer from './ItemsRenderer.vue'; import CategoryItem from '../ItemTypes/CategoryItem.vue'; -import { useTelemetry } from '@/composables/useTelemetry'; +import { useNodeCreatorStore } from '@/stores/nodeCreator.store'; export interface Props { elements: INodeCreateElement[]; @@ -24,10 +24,10 @@ const props = withDefaults(defineProps(), { elements: () => [], }); -const telemetry = useTelemetry(); const { popViewStack } = useViewStacks(); const { registerKeyHook } = useKeyboardNavigation(); const { workflowId } = useWorkflowsStore(); +const nodeCreatorStore = useNodeCreatorStore(); const activeItemId = computed(() => useKeyboardNavigation()?.activeItemId); const actionCount = computed(() => props.elements.filter(({ type }) => type === 'action').length); @@ -38,10 +38,11 @@ function toggleExpanded() { } function setExpanded(isExpanded: boolean) { + const prev = expanded.value; expanded.value = isExpanded; - if (expanded.value) { - telemetry.trackNodesPanel('nodeCreateList.onCategoryExpanded', { + if (expanded.value && !prev) { + nodeCreatorStore.onCategoryExpanded({ category_name: props.category, workflow_id: workflowId, }); diff --git a/packages/editor-ui/src/components/Node/NodeCreator/composables/useActions.ts b/packages/editor-ui/src/components/Node/NodeCreator/composables/useActions.ts index b83dccd705..1058ed2950 100644 --- a/packages/editor-ui/src/components/Node/NodeCreator/composables/useActions.ts +++ b/packages/editor-ui/src/components/Node/NodeCreator/composables/useActions.ts @@ -339,7 +339,11 @@ export const useActions = () => { return storeWatcher; } - function trackActionSelected(action: IUpdateInformation, telemetry: Telemetry, rootView: string) { + function trackActionSelected( + action: IUpdateInformation, + _telemetry: Telemetry, + rootView: string, + ) { const payload = { node_type: action.key, action: action.name, @@ -347,7 +351,7 @@ export const useActions = () => { resource: (action.value as INodeParameters).resource || '', }; void useExternalHooks().run('nodeCreateList.addAction', payload); - telemetry?.trackNodesPanel('nodeCreateList.addAction', payload); + useNodeCreatorStore().onAddActions(payload); } return { diff --git a/packages/editor-ui/src/components/Node/NodeCreator/__tests__/useActions.test.ts b/packages/editor-ui/src/components/Node/NodeCreator/useActions.test.ts similarity index 99% rename from packages/editor-ui/src/components/Node/NodeCreator/__tests__/useActions.test.ts rename to packages/editor-ui/src/components/Node/NodeCreator/useActions.test.ts index 80878f0e96..cf8fa94014 100644 --- a/packages/editor-ui/src/components/Node/NodeCreator/__tests__/useActions.test.ts +++ b/packages/editor-ui/src/components/Node/NodeCreator/useActions.test.ts @@ -3,7 +3,7 @@ import { createTestingPinia } from '@pinia/testing'; import { useNodeCreatorStore } from '@/stores/nodeCreator.store'; import { useNodeTypesStore } from '@/stores/nodeTypes.store'; import { useWorkflowsStore } from '@/stores/workflows.store'; -import { useActions } from '../composables/useActions'; +import { useActions } from './composables/useActions'; import { AGENT_NODE_TYPE, GITHUB_TRIGGER_NODE_TYPE, diff --git a/packages/editor-ui/src/components/Node/NodeCreator/__tests__/useActionsGeneration.test.ts b/packages/editor-ui/src/components/Node/NodeCreator/useActionsGeneration.test.ts similarity index 99% rename from packages/editor-ui/src/components/Node/NodeCreator/__tests__/useActionsGeneration.test.ts rename to packages/editor-ui/src/components/Node/NodeCreator/useActionsGeneration.test.ts index 171e2a0b48..679fd05284 100644 --- a/packages/editor-ui/src/components/Node/NodeCreator/__tests__/useActionsGeneration.test.ts +++ b/packages/editor-ui/src/components/Node/NodeCreator/useActionsGeneration.test.ts @@ -1,5 +1,5 @@ import { NodeConnectionType, type INodeProperties, type INodeTypeDescription } from 'n8n-workflow'; -import { useActionsGenerator } from '../composables/useActionsGeneration'; +import { useActionsGenerator } from './composables/useActionsGeneration'; describe('useActionsGenerator', () => { const { generateMergedNodesAndActions } = useActionsGenerator(); diff --git a/packages/editor-ui/src/components/Node/NodeCreator/__tests__/useKeyboardNavigation.test.ts b/packages/editor-ui/src/components/Node/NodeCreator/useKeyboardNavigation.test.ts similarity index 97% rename from packages/editor-ui/src/components/Node/NodeCreator/__tests__/useKeyboardNavigation.test.ts rename to packages/editor-ui/src/components/Node/NodeCreator/useKeyboardNavigation.test.ts index b53190db39..d8d3b7f934 100644 --- a/packages/editor-ui/src/components/Node/NodeCreator/__tests__/useKeyboardNavigation.test.ts +++ b/packages/editor-ui/src/components/Node/NodeCreator/useKeyboardNavigation.test.ts @@ -1,6 +1,6 @@ import userEvent from '@testing-library/user-event'; import { defineComponent, computed } from 'vue'; -import { useKeyboardNavigation } from '../composables/useKeyboardNavigation'; +import { useKeyboardNavigation } from './composables/useKeyboardNavigation'; import { createComponentRenderer } from '@/__tests__/render'; import { createPinia } from 'pinia'; diff --git a/packages/editor-ui/src/components/Node/NodeCreator/__tests__/utils.test.ts b/packages/editor-ui/src/components/Node/NodeCreator/utils.test.ts similarity index 95% rename from packages/editor-ui/src/components/Node/NodeCreator/__tests__/utils.test.ts rename to packages/editor-ui/src/components/Node/NodeCreator/utils.test.ts index 6d8174dfaa..deabf36577 100644 --- a/packages/editor-ui/src/components/Node/NodeCreator/__tests__/utils.test.ts +++ b/packages/editor-ui/src/components/Node/NodeCreator/utils.test.ts @@ -1,6 +1,10 @@ import type { SectionCreateElement } from '@/Interface'; -import { formatTriggerActionName, groupItemsInSections, sortNodeCreateElements } from '../utils'; -import { mockActionCreateElement, mockNodeCreateElement, mockSectionCreateElement } from './utils'; +import { formatTriggerActionName, groupItemsInSections, sortNodeCreateElements } from './utils'; +import { + mockActionCreateElement, + mockNodeCreateElement, + mockSectionCreateElement, +} from './__tests__/utils'; describe('NodeCreator - utils', () => { describe('groupItemsInSections', () => { diff --git a/packages/editor-ui/src/components/__tests__/NodeDetailsView.test.ts b/packages/editor-ui/src/components/NodeDetailsView.test.ts similarity index 100% rename from packages/editor-ui/src/components/__tests__/NodeDetailsView.test.ts rename to packages/editor-ui/src/components/NodeDetailsView.test.ts diff --git a/packages/editor-ui/src/components/NodeSettings.vue b/packages/editor-ui/src/components/NodeSettings.vue index 6ae5412b94..7c07cd5006 100644 --- a/packages/editor-ui/src/components/NodeSettings.vue +++ b/packages/editor-ui/src/components/NodeSettings.vue @@ -124,7 +124,7 @@ const currentWorkflow = computed(() => ); const hasForeignCredential = computed(() => props.foreignCredentials.length > 0); const isHomeProjectTeam = computed( - () => currentWorkflow.value.homeProject?.type === ProjectTypes.Team, + () => currentWorkflow.value?.homeProject?.type === ProjectTypes.Team, ); const isReadOnly = computed( () => props.readOnly || (hasForeignCredential.value && !isHomeProjectTeam.value), diff --git a/packages/editor-ui/src/components/OutputPanel.vue b/packages/editor-ui/src/components/OutputPanel.vue index bf3cbbe6eb..4c513c07d1 100644 --- a/packages/editor-ui/src/components/OutputPanel.vue +++ b/packages/editor-ui/src/components/OutputPanel.vue @@ -100,14 +100,15 @@ const isTriggerNode = computed(() => { }); const hasAiMetadata = computed(() => { + if (isNodeRunning.value || !workflowRunData.value) { + return false; + } + if (node.value) { - const resultData = workflowsStore.getWorkflowResultDataByNodeName(node.value.name); + const connectedSubNodes = props.workflow.getParentNodes(node.value.name, 'ALL_NON_MAIN'); + const resultData = connectedSubNodes.map(workflowsStore.getWorkflowResultDataByNodeName); - if (!resultData || !Array.isArray(resultData) || resultData.length === 0) { - return false; - } - - return !!resultData[resultData.length - 1].metadata; + return resultData && Array.isArray(resultData) && resultData.length > 0; } return false; }); @@ -295,6 +296,7 @@ const activatePane = () => { :block-u-i="blockUI" :is-production-execution-preview="isProductionExecutionPreview" :is-pane-active="isPaneActive" + :hide-pagination="outputMode === 'logs'" pane-type="output" :data-output-type="outputMode" @activate-pane="activatePane" @@ -368,7 +370,7 @@ const activatePane = () => { diff --git a/packages/editor-ui/src/components/__tests__/RunData.test.ts b/packages/editor-ui/src/components/RunData.test.ts similarity index 100% rename from packages/editor-ui/src/components/__tests__/RunData.test.ts rename to packages/editor-ui/src/components/RunData.test.ts diff --git a/packages/editor-ui/src/components/RunData.vue b/packages/editor-ui/src/components/RunData.vue index 1c2d0b78a6..40c79b1deb 100644 --- a/packages/editor-ui/src/components/RunData.vue +++ b/packages/editor-ui/src/components/RunData.vue @@ -162,6 +162,10 @@ export default defineComponent({ type: Boolean, default: false, }, + hidePagination: { + type: Boolean, + default: false, + }, }, setup(props) { const ndvStore = useNDVStore(); @@ -1743,6 +1747,7 @@ export default defineComponent({
import type { Ref } from 'vue'; import { computed, ref, watch } from 'vue'; -import type { ITaskSubRunMetadata, ITaskDataConnections } from 'n8n-workflow'; -import { NodeConnectionType } from 'n8n-workflow'; +import type { ITaskDataConnections, NodeConnectionType, Workflow, ITaskData } from 'n8n-workflow'; import type { IAiData, IAiDataContent, INodeUi } from '@/Interface'; import { useNodeTypesStore } from '@/stores/nodeTypes.store'; import { useWorkflowsStore } from '@/stores/workflows.store'; @@ -28,29 +27,21 @@ export interface Props { runIndex?: number; hideTitle?: boolean; slim?: boolean; + workflow: Workflow; } const props = withDefaults(defineProps(), { runIndex: 0 }); const workflowsStore = useWorkflowsStore(); const nodeTypesStore = useNodeTypesStore(); const selectedRun: Ref = ref([]); - function isTreeNodeSelected(node: TreeNode) { return selectedRun.value.some((run) => run.node === node.node && run.runIndex === node.runIndex); } function getReferencedData( - reference: ITaskSubRunMetadata, + taskData: ITaskData, withInput: boolean, withOutput: boolean, ): IAiDataContent[] { - const resultData = workflowsStore.getWorkflowResultDataByNodeName(reference.node); - - if (!resultData?.[reference.runIndex]) { - return []; - } - - const taskData = resultData[reference.runIndex]; - if (!taskData) { return []; } @@ -98,18 +89,18 @@ function onItemClick(data: TreeNode) { return; } + + const selectedNodeRun = workflowsStore.getWorkflowResultDataByNodeName(data.node)?.[ + data.runIndex + ]; + if (!selectedNodeRun) { + return; + } selectedRun.value = [ { node: data.node, runIndex: data.runIndex, - data: getReferencedData( - { - node: data.node, - runIndex: data.runIndex, - }, - true, - true, - ), + data: getReferencedData(selectedNodeRun, true, true), }, ]; } @@ -145,21 +136,20 @@ const createNode = ( }); function getTreeNodeData(nodeName: string, currentDepth: number): TreeNode[] { - const { connectionsByDestinationNode } = workflowsStore.getCurrentWorkflow(); - const connections = connectionsByDestinationNode[nodeName]; - // eslint-disable-next-line @typescript-eslint/no-use-before-define + const connections = props.workflow.connectionsByDestinationNode[nodeName]; const resultData = aiData.value?.filter((data) => data.node === nodeName) ?? []; if (!connections) { return resultData.map((d) => createNode(nodeName, currentDepth, d)); } - const nonMainConnectionsKeys = Object.keys(connections).filter( - (key) => key !== NodeConnectionType.Main, - ); - const children = nonMainConnectionsKeys.flatMap((key) => - connections[key][0].flatMap((node) => getTreeNodeData(node.node, currentDepth + 1)), - ); + // Get the first level of children + const connectedSubNodes = props.workflow.getParentNodes(nodeName, 'ALL_NON_MAIN', 1); + + const children = connectedSubNodes + // Only include sub-nodes which have data + .filter((name) => aiData.value?.find((data) => data.node === name)) + .flatMap((name) => getTreeNodeData(name, currentDepth + 1)); children.sort((a, b) => a.startTime - b.startTime); @@ -170,35 +160,49 @@ function getTreeNodeData(nodeName: string, currentDepth: number): TreeNode[] { return [createNode(nodeName, currentDepth, undefined, children)]; } -const aiData = computed(() => { - const resultData = workflowsStore.getWorkflowResultDataByNodeName(props.node.name); +const aiData = computed(() => { + const result: AIResult[] = []; + const connectedSubNodes = props.workflow.getParentNodes(props.node.name, 'ALL_NON_MAIN'); + const rootNodeResult = workflowsStore.getWorkflowResultDataByNodeName(props.node.name); + const rootNodeStartTime = rootNodeResult?.[0]?.startTime ?? 0; + const rootNodeEndTime = rootNodeStartTime + (rootNodeResult?.[0]?.executionTime ?? 0); - if (!resultData || !Array.isArray(resultData)) { - return; - } + connectedSubNodes.forEach((nodeName) => { + const nodeRunData = workflowsStore.getWorkflowResultDataByNodeName(nodeName) ?? []; - const subRun = resultData[props.runIndex].metadata?.subRun; - if (!Array.isArray(subRun)) { - return; - } - // Extend the subRun with the data and sort by adding execution time + startTime and comparing them - const subRunWithData = subRun.flatMap((run) => - getReferencedData(run, false, true).map((data) => ({ ...run, data })), - ); + nodeRunData.forEach((run, runIndex) => { + const referenceData = { + data: getReferencedData(run, false, true)[0], + node: nodeName, + runIndex, + }; - subRunWithData.sort((a, b) => { - const aTime = a.data?.metadata?.startTime || 0; - const bTime = b.data?.metadata?.startTime || 0; + result.push(referenceData); + }); + }); + + // Sort the data by start time + result.sort((a, b) => { + const aTime = a.data?.metadata?.startTime ?? 0; + const bTime = b.data?.metadata?.startTime ?? 0; return aTime - bTime; }); - return subRunWithData; + // Only show data that is within the root node's execution time + // This is because sub-node could be connected to multiple root nodes + const currentNodeResult = result.filter((r) => { + const startTime = r.data?.metadata?.startTime ?? 0; + + return startTime >= rootNodeStartTime && startTime <= rootNodeEndTime; + }); + + return currentNodeResult; }); const executionTree = computed(() => { const rootNode = props.node; - const tree = getTreeNodeData(rootNode.name, 0); + const tree = getTreeNodeData(rootNode.name, 1); return tree || []; }); @@ -206,7 +210,7 @@ watch(() => props.runIndex, selectFirst, { immediate: true });