diff --git a/.vscode/launch.json b/.vscode/launch.json index 448d745236..5501fe4439 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -47,9 +47,7 @@ "request": "launch", "skipFiles": ["/**"], "type": "node", - "env": { - // "N8N_PORT": "5679", - }, + "envFile": "${workspaceFolder}/.env", "outputCapture": "std", "killBehavior": "polite" }, diff --git a/CHANGELOG.md b/CHANGELOG.md index f85ab264be..7c7a1e036f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,67 @@ +# [1.68.0](https://github.com/n8n-io/n8n/compare/n8n@1.67.0...n8n@1.68.0) (2024-11-13) + + +### Bug Fixes + +* **AI Agent Node:** Throw better errors for non-tool agents when using structured tools ([#11582](https://github.com/n8n-io/n8n/issues/11582)) ([9b6123d](https://github.com/n8n-io/n8n/commit/9b6123dfb2648f880c7829211fa07666611ad0ea)) +* **Auto-fixing Output Parser Node:** Only run retry chain on parsing errors ([#11569](https://github.com/n8n-io/n8n/issues/11569)) ([21b31e4](https://github.com/n8n-io/n8n/commit/21b31e488ff6ab0bcf3c79edcd17b9e37d4c64a4)) +* **core:** Continue with error output reverse items in success branch ([#11684](https://github.com/n8n-io/n8n/issues/11684)) ([6d5ee83](https://github.com/n8n-io/n8n/commit/6d5ee832966fab96043b0d65697c059ced61d334)) +* **core:** Ensure task runner server closes websocket connection correctly ([#11633](https://github.com/n8n-io/n8n/issues/11633)) ([b496bf3](https://github.com/n8n-io/n8n/commit/b496bf3147d2cd873d24371be02cb7ea5dbd8621)) +* **core:** Handle websocket connection error more gracefully in task runners ([#11635](https://github.com/n8n-io/n8n/issues/11635)) ([af7d6e6](https://github.com/n8n-io/n8n/commit/af7d6e68d0436ff8a3d4e8410dc8ee4f3a035c44)) +* **core:** Improve model sub-nodes error handling ([#11418](https://github.com/n8n-io/n8n/issues/11418)) ([57467d0](https://github.com/n8n-io/n8n/commit/57467d0285d67509322630c4c01130022f274a41)) +* **core:** Make push work for waiting webhooks ([#11678](https://github.com/n8n-io/n8n/issues/11678)) ([600479b](https://github.com/n8n-io/n8n/commit/600479bf36ba8870d4aecacad19a2dc5f2d97959)) +* **core:** Revert all the context helpers changes ([#11616](https://github.com/n8n-io/n8n/issues/11616)) ([20fd38f](https://github.com/n8n-io/n8n/commit/20fd38f3517f7ef35604ba16abb4d951270b4d50)) +* **core:** Set the authentication methad to `email` during startup if the SAML configuration in the database has been corrupted ([#11600](https://github.com/n8n-io/n8n/issues/11600)) ([6439291](https://github.com/n8n-io/n8n/commit/6439291738dec16261979d6d835acbc63743d51a)) +* **core:** Use cached value in retrieval of personal project owner ([#11533](https://github.com/n8n-io/n8n/issues/11533)) ([04029d8](https://github.com/n8n-io/n8n/commit/04029d82a11b52990890380ba7094055b18e7c1f)) +* Credentials save button is hidden unless you make changes to the ([#11492](https://github.com/n8n-io/n8n/issues/11492)) ([835fbfe](https://github.com/n8n-io/n8n/commit/835fbfe337dd8dc0d0b0318c7227e174484e1328)) +* **editor:** Add stickies to node insert position conflict check allowlist ([#11624](https://github.com/n8n-io/n8n/issues/11624)) ([fc39e3c](https://github.com/n8n-io/n8n/commit/fc39e3ca16231c176957e2504d55df6b416874fe)) +* **editor:** Adjust Scrollbar Width of RunData Header Row ([#11561](https://github.com/n8n-io/n8n/issues/11561)) ([d17d76a](https://github.com/n8n-io/n8n/commit/d17d76a85d5425bc091d29fc84605ffbccbca984)) +* **editor:** Cap NDV Output View Tab Index to prevent rare edge case ([#11614](https://github.com/n8n-io/n8n/issues/11614)) ([a6c8ee4](https://github.com/n8n-io/n8n/commit/a6c8ee4a82e6055766dc1307f79c774c17bb5f4d)) +* **editor:** Do not show hover tooltip when autocomplete is active ([#11653](https://github.com/n8n-io/n8n/issues/11653)) ([23caf43](https://github.com/n8n-io/n8n/commit/23caf43e30342a21d45c825f438aa1e6193601d1)) +* **editor:** Enable pinning main output with error and always allow unpinning ([#11452](https://github.com/n8n-io/n8n/issues/11452)) ([40c8882](https://github.com/n8n-io/n8n/commit/40c88822acdcda6401bd92b9cf89d013c44b8453)) +* **editor:** Fix collapsing nested items in expression modal schema view ([#11645](https://github.com/n8n-io/n8n/issues/11645)) ([41dea52](https://github.com/n8n-io/n8n/commit/41dea522fbfb1c9acee51f47f384973914454b5f)) +* **editor:** Fix default workflow settings ([#11632](https://github.com/n8n-io/n8n/issues/11632)) ([658568e](https://github.com/n8n-io/n8n/commit/658568e2700bfd5b61da53f3052403d0098c2d90)) +* **editor:** Fix duplicate chat trigger ([#11693](https://github.com/n8n-io/n8n/issues/11693)) ([a025848](https://github.com/n8n-io/n8n/commit/a025848ec4be96f74d4de2ab104256b6d89bb837)) +* **editor:** Fix hiding SQL query output when trying to select ([#11649](https://github.com/n8n-io/n8n/issues/11649)) ([4dbf2f4](https://github.com/n8n-io/n8n/commit/4dbf2f4256111985b367030020f1494b8a8b95af)) +* **editor:** Fix scrolling in code edit modal ([#11647](https://github.com/n8n-io/n8n/issues/11647)) ([8f695f3](https://github.com/n8n-io/n8n/commit/8f695f3417820e7b9bb04b78972f6abbd61abbe8)) +* **editor:** Prevent error being thrown in RLC while loading ([#11676](https://github.com/n8n-io/n8n/issues/11676)) ([ca8cb45](https://github.com/n8n-io/n8n/commit/ca8cb455ba59831295c238afb11aeab6ad18428e)) +* **editor:** Prevent NodeCreator from swallowing AskAssistant enter event ([#11532](https://github.com/n8n-io/n8n/issues/11532)) ([db94f16](https://github.com/n8n-io/n8n/commit/db94f169fcd03983fc78a3b4c5e11543610825bf)) +* **editor:** Show node executing status shortly before switching to success on new canvas ([#11675](https://github.com/n8n-io/n8n/issues/11675)) ([b0ba24c](https://github.com/n8n-io/n8n/commit/b0ba24cbbc55cebc26e9f62ead7396c4c8fbd062)) +* **editor:** Show only error title and 'Open errored node' button; hide 'Ask Assistant' in root for sub-node errors ([#11573](https://github.com/n8n-io/n8n/issues/11573)) ([8cba100](https://github.com/n8n-io/n8n/commit/8cba1004888f60ca653ee069501c13b3cadcc561)) +* **Facebook Lead Ads Trigger Node:** Fix issue with optional fields ([#11692](https://github.com/n8n-io/n8n/issues/11692)) ([70d315b](https://github.com/n8n-io/n8n/commit/70d315b3d5b8f5f3e0f39527bba11e254a52028e)) +* **Google BigQuery Node:** Add item index to insert error ([#11702](https://github.com/n8n-io/n8n/issues/11702)) ([145d092](https://github.com/n8n-io/n8n/commit/145d0921b217bbd4b625beaacfa14429560bf51b)) +* **Google Drive Node:** Fix file upload for streams ([#11698](https://github.com/n8n-io/n8n/issues/11698)) ([770230f](https://github.com/n8n-io/n8n/commit/770230fbfe0b9e86527254e201c4602fbced94ff)) +* **In-Memory Vector Store Node:** Fix displaying execution data of connected embedding nodes ([#11701](https://github.com/n8n-io/n8n/issues/11701)) ([40ade15](https://github.com/n8n-io/n8n/commit/40ade151724f4af28a6ed959fd9363450ea711fd)) +* **Item List Output Parser Node:** Fix number of items parameter issue ([#11696](https://github.com/n8n-io/n8n/issues/11696)) ([01ebe9d](https://github.com/n8n-io/n8n/commit/01ebe9dd38629afbab954fb489f3ef2bb7ab5b34)) +* **n8n Form Node:** Find completion page ([#11674](https://github.com/n8n-io/n8n/issues/11674)) ([ed3ad6d](https://github.com/n8n-io/n8n/commit/ed3ad6d684597f7c4b7419dfa81d476e66f10eba)) +* **n8n Form Node:** Open form page if form trigger has pin data ([#11673](https://github.com/n8n-io/n8n/issues/11673)) ([f0492bd](https://github.com/n8n-io/n8n/commit/f0492bd3bb0d94802a2707fb1cf861313b6ea808)) +* **n8n Form Node:** Trigger page stack in waiting if error in workflow ([#11671](https://github.com/n8n-io/n8n/issues/11671)) ([94b5873](https://github.com/n8n-io/n8n/commit/94b5873248212a5500f02cf3c0d74df6f9d8fb26)) +* **n8n Form Trigger Node:** Checkboxes different sizes ([#11677](https://github.com/n8n-io/n8n/issues/11677)) ([c08d23c](https://github.com/n8n-io/n8n/commit/c08d23c00f01bb6fcb3b75f02e0338af375f9b32)) +* NDV search bugs ([#11613](https://github.com/n8n-io/n8n/issues/11613)) ([c32cf64](https://github.com/n8n-io/n8n/commit/c32cf644a6b8c21558e802449329877845de70b1)) +* **Notion Node:** Extract page url ([#11643](https://github.com/n8n-io/n8n/issues/11643)) ([cbdd535](https://github.com/n8n-io/n8n/commit/cbdd535fe0cb4e032ea82f008dcf35cc5f2264c2)) +* **Redis Chat Memory Node:** Respect the SSL flag from the credential ([#11689](https://github.com/n8n-io/n8n/issues/11689)) ([b5cbf75](https://github.com/n8n-io/n8n/commit/b5cbf7566d351d8a8e9972f13ff5867ff1c8d7d0)) +* **Supabase Node:** Reset query parameters in get many operation ([#11630](https://github.com/n8n-io/n8n/issues/11630)) ([7458229](https://github.com/n8n-io/n8n/commit/74582290c04d2dd32300b1a6c7715862ae837d34)) +* **Switch Node:** Maintain output connections ([#11162](https://github.com/n8n-io/n8n/issues/11162)) ([9bd79fc](https://github.com/n8n-io/n8n/commit/9bd79fceebc4453d0fe40ae5f628d5e31ff2b326)) + + +### Features + +* **AI Transform Node:** Show warning for binary data ([#11560](https://github.com/n8n-io/n8n/issues/11560)) ([ddbb263](https://github.com/n8n-io/n8n/commit/ddbb263dce0fc458abc95d850217251bb49d2b83)) +* **core:** Make all http requests made with `httpRequestWithAuthentication` abortable ([#11704](https://github.com/n8n-io/n8n/issues/11704)) ([0d8aada](https://github.com/n8n-io/n8n/commit/0d8aada49005d6a6078e8460003a0de61a8f423c)) +* **editor:** Improve how we show default Agent prompt and Memory session parameters ([#11491](https://github.com/n8n-io/n8n/issues/11491)) ([565f8cd](https://github.com/n8n-io/n8n/commit/565f8cd8c78b534a50e272997d659d162fa86625)) +* **editor:** Improve workflow loading performance on new canvas ([#11629](https://github.com/n8n-io/n8n/issues/11629)) ([f1e2df7](https://github.com/n8n-io/n8n/commit/f1e2df7d0753aa0f33cf299100a063bf89cc8b35)) +* **editor:** Redesign Canvas Chat ([#11634](https://github.com/n8n-io/n8n/issues/11634)) ([a412ab7](https://github.com/n8n-io/n8n/commit/a412ab7ebfcd6aa9051a8ca36e34f1067102c998)) +* **editor:** Restrict when a ChatTrigger Node is added automatically ([#11523](https://github.com/n8n-io/n8n/issues/11523)) ([93a6f85](https://github.com/n8n-io/n8n/commit/93a6f858fa3eb53f8b48b2a3d6b7377279dd6ed1)) +* Github star button in-app ([#11695](https://github.com/n8n-io/n8n/issues/11695)) ([0fd684d](https://github.com/n8n-io/n8n/commit/0fd684d90c830f8b0aab12b7f78a1fa5619c62c9)) +* **Oura Node:** Update node for v2 api ([#11604](https://github.com/n8n-io/n8n/issues/11604)) ([3348fbb](https://github.com/n8n-io/n8n/commit/3348fbb1547c430ff8707b298640e3461d3f6536)) + + +### Performance Improvements + +* **editor:** Add lint rules for optimization-friendly syntax ([#11681](https://github.com/n8n-io/n8n/issues/11681)) ([88295c7](https://github.com/n8n-io/n8n/commit/88295c70495ae3d017674d5745972a346fcbaf12)) + + + # [1.67.0](https://github.com/n8n-io/n8n/compare/n8n@1.66.0...n8n@1.67.0) (2024-11-06) diff --git a/cypress/e2e/1-workflows.cy.ts b/cypress/e2e/1-workflows.cy.ts index a6683bbee4..eb93c07ee6 100644 --- a/cypress/e2e/1-workflows.cy.ts +++ b/cypress/e2e/1-workflows.cy.ts @@ -23,6 +23,7 @@ describe('Workflows', () => { }); it('should create multiple new workflows using add workflow button', () => { + cy.viewport(1920, 1080); [...Array(multipleWorkflowsCount).keys()].forEach(() => { cy.visit(WorkflowsPage.url); WorkflowsPage.getters.createWorkflowButton().click(); @@ -35,6 +36,7 @@ describe('Workflows', () => { }); it('should search for a workflow', () => { + cy.viewport(1920, 1080); // One Result WorkflowsPage.getters.searchBar().type('Empty State Card Workflow'); WorkflowsPage.getters.workflowCards().should('have.length', 1); @@ -60,6 +62,7 @@ describe('Workflows', () => { }); it('should delete all the workflows', () => { + cy.viewport(1920, 1080); WorkflowsPage.getters.workflowCards().should('have.length', multipleWorkflowsCount + 1); WorkflowsPage.getters.workflowCards().each(($el) => { @@ -75,6 +78,7 @@ describe('Workflows', () => { }); it('should respect tag querystring filter when listing workflows', () => { + cy.viewport(1920, 1080); WorkflowsPage.getters.newWorkflowButtonCard().click(); cy.createFixtureWorkflow('Test_workflow_2.json', getUniqueWorkflowName('My New Workflow')); diff --git a/cypress/e2e/17-workflow-tags.cy.ts b/cypress/e2e/17-workflow-tags.cy.ts index 26ea7cbe2c..e8dacca2b8 100644 --- a/cypress/e2e/17-workflow-tags.cy.ts +++ b/cypress/e2e/17-workflow-tags.cy.ts @@ -53,6 +53,7 @@ describe('Workflow tags', () => { }); it('should detach a tag inline by clicking on X on tag pill', () => { + cy.viewport(1920, 1080); wf.getters.createTagButton().click(); wf.actions.addTags(TEST_TAGS); wf.getters.nthTagPill(1).click(); @@ -73,6 +74,7 @@ describe('Workflow tags', () => { }); it('should not show non existing tag as a selectable option', () => { + cy.viewport(1920, 1080); const NON_EXISTING_TAG = 'My Test Tag'; wf.getters.createTagButton().click(); diff --git a/cypress/e2e/19-execution.cy.ts b/cypress/e2e/19-execution.cy.ts index 5be2399253..b8b30dd7a9 100644 --- a/cypress/e2e/19-execution.cy.ts +++ b/cypress/e2e/19-execution.cy.ts @@ -514,6 +514,7 @@ describe('Execution', () => { }); it('should send proper payload for node rerun', () => { + cy.viewport(1920, 1080); cy.createFixtureWorkflow('Multiple_trigger_node_rerun.json', 'Multiple trigger node rerun'); workflowPage.getters.zoomToFitButton().click(); diff --git a/cypress/e2e/20-workflow-executions.cy.ts b/cypress/e2e/20-workflow-executions.cy.ts index 5788af171c..bfa9bca1d9 100644 --- a/cypress/e2e/20-workflow-executions.cy.ts +++ b/cypress/e2e/20-workflow-executions.cy.ts @@ -101,6 +101,7 @@ describe('Workflow Executions', () => { }); it('should show workflow data in executions tab after hard reload and modify name and tags', () => { + cy.viewport(1920, 1080); executionsTab.actions.switchToExecutionsTab(); checkMainHeaderELements(); workflowPage.getters.saveButton().find('button').should('not.exist'); diff --git a/cypress/e2e/34-template-credentials-setup.cy.ts b/cypress/e2e/34-template-credentials-setup.cy.ts index 815f4b1ceb..a2f9a019f7 100644 --- a/cypress/e2e/34-template-credentials-setup.cy.ts +++ b/cypress/e2e/34-template-credentials-setup.cy.ts @@ -182,6 +182,7 @@ describe('Template credentials setup', () => { }); it('should fill credentials from workflow editor', () => { + cy.viewport(1920, 1080); templateCredentialsSetupPage.visitTemplateCredentialSetupPage(testTemplate.id); templateCredentialsSetupPage.getters.skipLink().click(); diff --git a/cypress/e2e/39-projects.cy.ts b/cypress/e2e/39-projects.cy.ts index 0d4154e646..bbd0508662 100644 --- a/cypress/e2e/39-projects.cy.ts +++ b/cypress/e2e/39-projects.cy.ts @@ -176,7 +176,7 @@ describe('Projects', { disableAutoLogin: true }, () => { let menuItems = cy.getByTestId('menu-item'); menuItems.filter('[class*=active_]').should('have.length', 1); - menuItems.filter(':contains("Home")[class*=active_]').should('exist'); + menuItems.filter(':contains("Overview")[class*=active_]').should('exist'); projects.getMenuItems().first().click(); @@ -222,7 +222,7 @@ describe('Projects', { disableAutoLogin: true }, () => { menuItems = cy.getByTestId('menu-item'); menuItems.filter('[class*=active_]').should('have.length', 1); - menuItems.filter(':contains("Home")[class*=active_]').should('exist'); + menuItems.filter(':contains("Overview")[class*=active_]').should('exist'); workflowsPage.getters.workflowCards().should('have.length', 2).first().click(); @@ -230,7 +230,7 @@ describe('Projects', { disableAutoLogin: true }, () => { cy.getByTestId('execute-workflow-button').should('be.visible'); menuItems = cy.getByTestId('menu-item'); - menuItems.filter(':contains("Home")[class*=active_]').should('not.exist'); + menuItems.filter(':contains("Overview")[class*=active_]').should('not.exist'); menuItems = cy.getByTestId('menu-item'); menuItems.filter('[class*=active_]').should('have.length', 1); diff --git a/cypress/e2e/45-ai-assistant.cy.ts b/cypress/e2e/45-ai-assistant.cy.ts index 9b50fef44a..7f9c79e0e6 100644 --- a/cypress/e2e/45-ai-assistant.cy.ts +++ b/cypress/e2e/45-ai-assistant.cy.ts @@ -36,6 +36,7 @@ describe('AI Assistant::enabled', () => { }); it('renders placeholder UI', () => { + cy.viewport(1920, 1080); aiAssistant.getters.askAssistantFloatingButton().should('be.visible'); aiAssistant.getters.askAssistantFloatingButton().click(); aiAssistant.getters.askAssistantChat().should('be.visible'); diff --git a/cypress/e2e/5-ndv.cy.ts b/cypress/e2e/5-ndv.cy.ts index 96b917d4c3..9ad1fceae1 100644 --- a/cypress/e2e/5-ndv.cy.ts +++ b/cypress/e2e/5-ndv.cy.ts @@ -66,6 +66,7 @@ describe('NDV', () => { }); it('should disconect Switch outputs if rules order was changed', () => { + cy.viewport(1920, 1080); cy.createFixtureWorkflow('NDV-test-switch_reorder.json', 'NDV test switch reorder'); workflowPage.actions.zoomToFit(); @@ -232,6 +233,7 @@ describe('NDV', () => { ndv.getters.outputPanel().find('[class*=_pagination]').should('exist'); }); it('should display large schema', () => { + cy.viewport(1920, 1080); cy.createFixtureWorkflow( 'Test_workflow_schema_test_pinned_data.json', 'NDV test schema view 2', @@ -718,6 +720,7 @@ describe('NDV', () => { }); it('Should open appropriate node creator after clicking on connection hint link', () => { + cy.viewport(1920, 1080); const nodeCreator = new NodeCreator(); const hintMapper = { Memory: 'AI Nodes', diff --git a/package.json b/package.json index c7d4512b2e..bdf3221845 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "n8n-monorepo", - "version": "1.67.0", + "version": "1.68.0", "private": true, "engines": { "node": ">=20.15", diff --git a/packages/@n8n/chat/package.json b/packages/@n8n/chat/package.json index a6098892f0..6ad77655b4 100644 --- a/packages/@n8n/chat/package.json +++ b/packages/@n8n/chat/package.json @@ -1,6 +1,6 @@ { "name": "@n8n/chat", - "version": "0.30.0", + "version": "0.31.0", "scripts": { "dev": "pnpm run storybook", "build": "pnpm build:vite && pnpm build:bundle", diff --git a/packages/@n8n/config/package.json b/packages/@n8n/config/package.json index 4867390bf7..20d1aec726 100644 --- a/packages/@n8n/config/package.json +++ b/packages/@n8n/config/package.json @@ -1,6 +1,6 @@ { "name": "@n8n/config", - "version": "1.17.0", + "version": "1.18.0", "scripts": { "clean": "rimraf dist .turbo", "dev": "pnpm watch", diff --git a/packages/@n8n/nodes-langchain/package.json b/packages/@n8n/nodes-langchain/package.json index 38acd88c9a..23805646ee 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.67.0", + "version": "1.68.0", "description": "", "main": "index.js", "scripts": { diff --git a/packages/@n8n/task-runner/package.json b/packages/@n8n/task-runner/package.json index 8350667099..b595fefe6e 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.5.0", + "version": "1.6.0", "scripts": { "clean": "rimraf dist .turbo", "start": "node dist/start.js", diff --git a/packages/cli/README.md b/packages/cli/README.md index 526f463167..0235c694da 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -48,7 +48,7 @@ It will download everything that is needed to start n8n. You can then access n8n by opening: [http://localhost:5678](http://localhost:5678) -**Note:** The minimum required version for Node.js is v14.15. Make sure to update Node.js to v14.15 or above. +**Note:** The minimum required version for Node.js is v18. Make sure to update Node.js to v18 or above. ### Run with Docker diff --git a/packages/cli/package.json b/packages/cli/package.json index 5c589116ee..88f8438931 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "n8n", - "version": "1.67.0", + "version": "1.68.0", "description": "n8n Workflow Automation Tool", "main": "dist/index", "types": "dist/index.d.ts", diff --git a/packages/cli/src/databases/entities/test-definition.ee.ts b/packages/cli/src/databases/entities/test-definition.ee.ts index 1ec08e9e66..f8ec8d6cb7 100644 --- a/packages/cli/src/databases/entities/test-definition.ee.ts +++ b/packages/cli/src/databases/entities/test-definition.ee.ts @@ -35,6 +35,9 @@ export class TestDefinition extends WithTimestamps { }) name: string; + @Column('text') + description: string; + /** * Relation to the workflow under test */ diff --git a/packages/cli/src/databases/migrations/common/1731404028106-AddDescriptionToTestDefinition.ts b/packages/cli/src/databases/migrations/common/1731404028106-AddDescriptionToTestDefinition.ts new file mode 100644 index 0000000000..5063bdf232 --- /dev/null +++ b/packages/cli/src/databases/migrations/common/1731404028106-AddDescriptionToTestDefinition.ts @@ -0,0 +1,11 @@ +import type { MigrationContext, ReversibleMigration } from '@/databases/types'; + +export class AddDescriptionToTestDefinition1731404028106 implements ReversibleMigration { + async up({ schemaBuilder: { addColumns, column } }: MigrationContext) { + await addColumns('test_definition', [column('description').text]); + } + + async down({ schemaBuilder: { dropColumns } }: MigrationContext) { + await dropColumns('test_definition', ['description']); + } +} diff --git a/packages/cli/src/databases/migrations/mysqldb/index.ts b/packages/cli/src/databases/migrations/mysqldb/index.ts index ebe7cf76c0..5f0b23d601 100644 --- a/packages/cli/src/databases/migrations/mysqldb/index.ts +++ b/packages/cli/src/databases/migrations/mysqldb/index.ts @@ -69,6 +69,7 @@ import { SeparateExecutionCreationFromStart1727427440136 } from '../common/17274 import { AddMissingPrimaryKeyOnAnnotationTagMapping1728659839644 } from '../common/1728659839644-AddMissingPrimaryKeyOnAnnotationTagMapping'; import { UpdateProcessedDataValueColumnToText1729607673464 } from '../common/1729607673464-UpdateProcessedDataValueColumnToText'; import { CreateTestDefinitionTable1730386903556 } from '../common/1730386903556-CreateTestDefinitionTable'; +import { AddDescriptionToTestDefinition1731404028106 } from '../common/1731404028106-AddDescriptionToTestDefinition'; export const mysqlMigrations: Migration[] = [ InitialMigration1588157391238, @@ -140,4 +141,5 @@ export const mysqlMigrations: Migration[] = [ AddMissingPrimaryKeyOnAnnotationTagMapping1728659839644, UpdateProcessedDataValueColumnToText1729607673464, CreateTestDefinitionTable1730386903556, + AddDescriptionToTestDefinition1731404028106, ]; diff --git a/packages/cli/src/databases/migrations/postgresdb/index.ts b/packages/cli/src/databases/migrations/postgresdb/index.ts index 731ddc2680..5372256371 100644 --- a/packages/cli/src/databases/migrations/postgresdb/index.ts +++ b/packages/cli/src/databases/migrations/postgresdb/index.ts @@ -69,6 +69,7 @@ import { SeparateExecutionCreationFromStart1727427440136 } from '../common/17274 import { AddMissingPrimaryKeyOnAnnotationTagMapping1728659839644 } from '../common/1728659839644-AddMissingPrimaryKeyOnAnnotationTagMapping'; import { UpdateProcessedDataValueColumnToText1729607673464 } from '../common/1729607673464-UpdateProcessedDataValueColumnToText'; import { CreateTestDefinitionTable1730386903556 } from '../common/1730386903556-CreateTestDefinitionTable'; +import { AddDescriptionToTestDefinition1731404028106 } from '../common/1731404028106-AddDescriptionToTestDefinition'; export const postgresMigrations: Migration[] = [ InitialMigration1587669153312, @@ -140,4 +141,5 @@ export const postgresMigrations: Migration[] = [ AddMissingPrimaryKeyOnAnnotationTagMapping1728659839644, UpdateProcessedDataValueColumnToText1729607673464, CreateTestDefinitionTable1730386903556, + AddDescriptionToTestDefinition1731404028106, ]; diff --git a/packages/cli/src/databases/migrations/sqlite/1731404028106-AddDescriptionToTestDefinition.ts b/packages/cli/src/databases/migrations/sqlite/1731404028106-AddDescriptionToTestDefinition.ts new file mode 100644 index 0000000000..3c3ad4e4ed --- /dev/null +++ b/packages/cli/src/databases/migrations/sqlite/1731404028106-AddDescriptionToTestDefinition.ts @@ -0,0 +1,5 @@ +import { AddDescriptionToTestDefinition1731404028106 as BaseMigration } from '../common/1731404028106-AddDescriptionToTestDefinition'; + +export class AddDescriptionToTestDefinition1731404028106 extends BaseMigration { + transaction = false as const; +} diff --git a/packages/cli/src/databases/migrations/sqlite/index.ts b/packages/cli/src/databases/migrations/sqlite/index.ts index 8004894ccf..e9b09ff483 100644 --- a/packages/cli/src/databases/migrations/sqlite/index.ts +++ b/packages/cli/src/databases/migrations/sqlite/index.ts @@ -39,6 +39,7 @@ import { DropRoleMapping1705429061930 } from './1705429061930-DropRoleMapping'; import { AddActivatedAtUserSetting1717498465931 } from './1717498465931-AddActivatedAtUserSetting'; import { AddApiKeysTable1724951148974 } from './1724951148974-AddApiKeysTable'; import { AddMissingPrimaryKeyOnAnnotationTagMapping1728659839644 } from './1728659839644-AddMissingPrimaryKeyOnAnnotationTagMapping'; +import { AddDescriptionToTestDefinition1731404028106 } from './1731404028106-AddDescriptionToTestDefinition'; import { UniqueWorkflowNames1620821879465 } from '../common/1620821879465-UniqueWorkflowNames'; import { UpdateWorkflowCredentials1630330987096 } from '../common/1630330987096-UpdateWorkflowCredentials'; import { AddNodeIds1658930531669 } from '../common/1658930531669-AddNodeIds'; @@ -134,6 +135,7 @@ const sqliteMigrations: Migration[] = [ AddMissingPrimaryKeyOnAnnotationTagMapping1728659839644, UpdateProcessedDataValueColumnToText1729607673464, CreateTestDefinitionTable1730386903556, + AddDescriptionToTestDefinition1731404028106, ]; export { sqliteMigrations }; diff --git a/packages/cli/src/evaluation/test-definition.schema.ts b/packages/cli/src/evaluation/test-definition.schema.ts index ffb6d82ab5..8f71ee6858 100644 --- a/packages/cli/src/evaluation/test-definition.schema.ts +++ b/packages/cli/src/evaluation/test-definition.schema.ts @@ -4,13 +4,16 @@ export const testDefinitionCreateRequestBodySchema = z .object({ name: z.string().min(1).max(255), workflowId: z.string().min(1), + description: z.string().optional(), evaluationWorkflowId: z.string().min(1).optional(), + annotationTagId: z.string().min(1).optional(), }) .strict(); export const testDefinitionPatchRequestBodySchema = z .object({ name: z.string().min(1).max(255).optional(), + description: z.string().optional(), evaluationWorkflowId: z.string().min(1).optional(), annotationTagId: z.string().min(1).optional(), }) diff --git a/packages/cli/src/evaluation/test-definition.service.ee.ts b/packages/cli/src/evaluation/test-definition.service.ee.ts index 6431568363..404167078b 100644 --- a/packages/cli/src/evaluation/test-definition.service.ee.ts +++ b/packages/cli/src/evaluation/test-definition.service.ee.ts @@ -26,6 +26,7 @@ export class TestDefinitionService { private toEntityLike(attrs: { name?: string; + description?: string; workflowId?: string; evaluationWorkflowId?: string; annotationTagId?: string; @@ -41,6 +42,10 @@ export class TestDefinitionService { entity.name = attrs.name?.trim(); } + if (attrs.description) { + entity.description = attrs.description.trim(); + } + if (attrs.workflowId) { entity.workflow = { id: attrs.workflowId, diff --git a/packages/cli/src/sso/saml/__tests__/saml-validator.test.ts b/packages/cli/src/sso/saml/__tests__/saml-validator.test.ts new file mode 100644 index 0000000000..8594676ab2 --- /dev/null +++ b/packages/cli/src/sso/saml/__tests__/saml-validator.test.ts @@ -0,0 +1,352 @@ +import { Logger } from '@/logging/logger.service'; +import { mockInstance } from '@test/mocking'; + +import { validateMetadata, validateResponse } from '../saml-validator'; + +describe('saml-validator', () => { + mockInstance(Logger); + + describe('validateMetadata', () => { + test('successfully validates metadata containing ws federation tags', async () => { + // ARRANGE + const metadata = ` + + + + + + + + + + + + hoeupPMPzijHu6caNarGYjsG0eKm4DOFUhjo0bPo0Ls= + + + + DQnnT/5se4dqYN86R35MCdbyKVl64lGPLSIVrxFxrOQ9YRK1br7Z1Bt1/LQD4f92z+GwAl+9tZTWhuoy6OGHCV6LlqBEztW43KnlCKw6eaNg4/6NluzJ/XeknXYLURDnfFVyGbLQAYWGND4Qm8CUXO/GjGfWTZuArvrDDC36/2FA41jKXtf1InxGFx1Bbaskx3n3KCFFth/V9knbnc1zftEe022aQluPRoGccROOI4ZeLUFL6+1gYlxjx0gFIOTRiuvrzR765lHNrF7iZ4aD+XukqtkGEtxTkiLoB+Bnr8Fd7IF5rV5FKTZWSxo+ZFcLimrDGtFPItVrC/oKRc+MGA== + + + + MIIC8DCCAdigAwIBAgIQf+iroClVKohAtsyk0Ne13TANBgkqhkiG9w0BAQsFADA0MTIwMAYDVQQDEylNaWNyb3NvZnQgQXp1cmUgRmVkZXJhdGVkIFNTTyBDZXJ0aWZpY2F0ZTAeFw0yNDExMTMxMDEwNTNaFw0yNzExMTMxMDEwNTNaMDQxMjAwBgNVBAMTKU1pY3Jvc29mdCBBenVyZSBGZWRlcmF0ZWQgU1NPIENlcnRpZmljYXRlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwE8Ad1OMQKfaHi6YrsEcmMNwIAQ86h7JmnuABf5xLNd27jaMF4FVxHbEtC/BYxtcmwld5zbkCVXQ6PT6VoeYIjHMVnptFXg15EGgjnqpxWsjLDQNoSdSQu8VhG+8Yb5M7KPt+UEZfsRZVrgqMjdSEMVrOzPMD8KMB7wnghYX6npcZhn7D5w/F9gVDpI1Um8M/FIUKYVSYFjky1i24WvKmcBf71mAacZp48Zuj5by/ELIb6gAjpW5xpd02smpLthy/Yo4XDIQQurFOfjqyZd8xAZu/SfPsbjtymWw59tgd9RdYISl6O/241kY9h6Ojtx6WShOVDi6q+bJrfj9Z8WKcQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQCiVxiQ9KpjihliQzIW45YO0EvRJtoPtyVAh9RiSGozbTl4otfrUJf8nbRtj7iZBRuuW4rrtRAH5kDb+i1wNUUQED2Pl/l4x5cN0oBytP3GSymq6NJx1gUOBO1BrNY+c3r5yHOUyj5qpbw9UkqpG1AqQkLLeZqB/yVCyOBQT7SKTbXVYhGefFM/+6z0/rGsWZN5OF6/2NC06ws1v4In28Atgpg4XxFh5TL7rPMJ11ca5MN9lHJoIUsvls053eQBcd7vJneqzd904B6WtPld6KOJK4dzIt9edHzPhaz158awWwx3iHsMn1Y/T0WVy5/4ZTzxY/i4U3t1Yt8ktxewVJYT + + + + + + + + + MIIC8DCCAdigAwIBAgIQf+iroClVKohAtsyk0Ne13TANBgkqhkiG9w0BAQsFADA0MTIwMAYDVQQDEylNaWNyb3NvZnQgQXp1cmUgRmVkZXJhdGVkIFNTTyBDZXJ0aWZpY2F0ZTAeFw0yNDExMTMxMDEwNTNaFw0yNzExMTMxMDEwNTNaMDQxMjAwBgNVBAMTKU1pY3Jvc29mdCBBenVyZSBGZWRlcmF0ZWQgU1NPIENlcnRpZmljYXRlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwE8Ad1OMQKfaHi6YrsEcmMNwIAQ86h7JmnuABf5xLNd27jaMF4FVxHbEtC/BYxtcmwld5zbkCVXQ6PT6VoeYIjHMVnptFXg15EGgjnqpxWsjLDQNoSdSQu8VhG+8Yb5M7KPt+UEZfsRZVrgqMjdSEMVrOzPMD8KMB7wnghYX6npcZhn7D5w/F9gVDpI1Um8M/FIUKYVSYFjky1i24WvKmcBf71mAacZp48Zuj5by/ELIb6gAjpW5xpd02smpLthy/Yo4XDIQQurFOfjqyZd8xAZu/SfPsbjtymWw59tgd9RdYISl6O/241kY9h6Ojtx6WShOVDi6q+bJrfj9Z8WKcQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQCiVxiQ9KpjihliQzIW45YO0EvRJtoPtyVAh9RiSGozbTl4otfrUJf8nbRtj7iZBRuuW4rrtRAH5kDb+i1wNUUQED2Pl/l4x5cN0oBytP3GSymq6NJx1gUOBO1BrNY+c3r5yHOUyj5qpbw9UkqpG1AqQkLLeZqB/yVCyOBQT7SKTbXVYhGefFM/+6z0/rGsWZN5OF6/2NC06ws1v4In28Atgpg4XxFh5TL7rPMJ11ca5MN9lHJoIUsvls053eQBcd7vJneqzd904B6WtPld6KOJK4dzIt9edHzPhaz158awWwx3iHsMn1Y/T0WVy5/4ZTzxY/i4U3t1Yt8ktxewVJYT + + + + + + Name + The mutable display name of the user. + + + Subject + An immutable, globally unique, non-reusable identifier of the user that is + unique to the application for which a token is issued. + + + Given Name + First name of the user. + + + Surname + Last name of the user. + + + Display Name + Display name of the user. + + + Nick Name + Nick name of the user. + + + Authentication Instant + The time (UTC) when the user is authenticated to Windows Azure Active + Directory. + + + Authentication Method + The method that Windows Azure Active Directory uses to authenticate users. + + + ObjectIdentifier + Primary identifier for the user in the directory. Immutable, globally + unique, non-reusable. + + + TenantId + Identifier for the user's tenant. + + + IdentityProvider + Identity provider for the user. + + + Email + Email address of the user. + + + Groups + Groups of the user. + + + External Access Token + Access token issued by external identity provider. + + + External Access Token Expiration + UTC expiration time of access token issued by external identity provider. + + + External OpenID 2.0 Identifier + OpenID 2.0 identifier issued by external identity provider. + + + GroupsOverageClaim + Issued when number of user's group claims exceeds return limit. + + + Role Claim + Roles that the user or Service Principal is attached to + + + RoleTemplate Id Claim + Role template id of the Built-in Directory Roles that the user is a member + of + + + + + https://login.microsoftonline.com/random-issuer/wsfed + + + + + https://login.microsoftonline.com/random-issuer/wsfed + + + + + + + + + MIIC8DCCAdigAwIBAgIQf+iroClVKohAtsyk0Ne13TANBgkqhkiG9w0BAQsFADA0MTIwMAYDVQQDEylNaWNyb3NvZnQgQXp1cmUgRmVkZXJhdGVkIFNTTyBDZXJ0aWZpY2F0ZTAeFw0yNDExMTMxMDEwNTNaFw0yNzExMTMxMDEwNTNaMDQxMjAwBgNVBAMTKU1pY3Jvc29mdCBBenVyZSBGZWRlcmF0ZWQgU1NPIENlcnRpZmljYXRlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwE8Ad1OMQKfaHi6YrsEcmMNwIAQ86h7JmnuABf5xLNd27jaMF4FVxHbEtC/BYxtcmwld5zbkCVXQ6PT6VoeYIjHMVnptFXg15EGgjnqpxWsjLDQNoSdSQu8VhG+8Yb5M7KPt+UEZfsRZVrgqMjdSEMVrOzPMD8KMB7wnghYX6npcZhn7D5w/F9gVDpI1Um8M/FIUKYVSYFjky1i24WvKmcBf71mAacZp48Zuj5by/ELIb6gAjpW5xpd02smpLthy/Yo4XDIQQurFOfjqyZd8xAZu/SfPsbjtymWw59tgd9RdYISl6O/241kY9h6Ojtx6WShOVDi6q+bJrfj9Z8WKcQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQCiVxiQ9KpjihliQzIW45YO0EvRJtoPtyVAh9RiSGozbTl4otfrUJf8nbRtj7iZBRuuW4rrtRAH5kDb+i1wNUUQED2Pl/l4x5cN0oBytP3GSymq6NJx1gUOBO1BrNY+c3r5yHOUyj5qpbw9UkqpG1AqQkLLeZqB/yVCyOBQT7SKTbXVYhGefFM/+6z0/rGsWZN5OF6/2NC06ws1v4In28Atgpg4XxFh5TL7rPMJ11ca5MN9lHJoIUsvls053eQBcd7vJneqzd904B6WtPld6KOJK4dzIt9edHzPhaz158awWwx3iHsMn1Y/T0WVy5/4ZTzxY/i4U3t1Yt8ktxewVJYT + + + + + + https://sts.windows.net/random-issuer/ + + + + + https://login.microsoftonline.com/random-issuer/wsfed + + + + + https://login.microsoftonline.com/random-issuer/wsfed + + + + + + + + + MIIC8DCCAdigAwIBAgIQf+iroClVKohAtsyk0Ne13TANBgkqhkiG9w0BAQsFADA0MTIwMAYDVQQDEylNaWNyb3NvZnQgQXp1cmUgRmVkZXJhdGVkIFNTTyBDZXJ0aWZpY2F0ZTAeFw0yNDExMTMxMDEwNTNaFw0yNzExMTMxMDEwNTNaMDQxMjAwBgNVBAMTKU1pY3Jvc29mdCBBenVyZSBGZWRlcmF0ZWQgU1NPIENlcnRpZmljYXRlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwE8Ad1OMQKfaHi6YrsEcmMNwIAQ86h7JmnuABf5xLNd27jaMF4FVxHbEtC/BYxtcmwld5zbkCVXQ6PT6VoeYIjHMVnptFXg15EGgjnqpxWsjLDQNoSdSQu8VhG+8Yb5M7KPt+UEZfsRZVrgqMjdSEMVrOzPMD8KMB7wnghYX6npcZhn7D5w/F9gVDpI1Um8M/FIUKYVSYFjky1i24WvKmcBf71mAacZp48Zuj5by/ELIb6gAjpW5xpd02smpLthy/Yo4XDIQQurFOfjqyZd8xAZu/SfPsbjtymWw59tgd9RdYISl6O/241kY9h6Ojtx6WShOVDi6q+bJrfj9Z8WKcQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQCiVxiQ9KpjihliQzIW45YO0EvRJtoPtyVAh9RiSGozbTl4otfrUJf8nbRtj7iZBRuuW4rrtRAH5kDb+i1wNUUQED2Pl/l4x5cN0oBytP3GSymq6NJx1gUOBO1BrNY+c3r5yHOUyj5qpbw9UkqpG1AqQkLLeZqB/yVCyOBQT7SKTbXVYhGefFM/+6z0/rGsWZN5OF6/2NC06ws1v4In28Atgpg4XxFh5TL7rPMJ11ca5MN9lHJoIUsvls053eQBcd7vJneqzd904B6WtPld6KOJK4dzIt9edHzPhaz158awWwx3iHsMn1Y/T0WVy5/4ZTzxY/i4U3t1Yt8ktxewVJYT + + + + + + + + `; + + // ACT + const result = await validateMetadata(metadata); + + // ASSERT + expect(result).toBe(true); + }); + + test('rejects invalid metadata', async () => { + // ARRANGE + // Invalid because required children are missing + const metadata = ` + + `; + + // ACT + const result = await validateMetadata(metadata); + + // ASSERT + expect(result).toBe(false); + }); + }); + + describe('validateResponse', () => { + test('successfully validates response', async () => { + // ARRANGE + const response = ` + + https://sts.windows.net/random-issuer/ + + + + + https://sts.windows.net/random-issuer/ + + + + + + + + + + + random_digest + + + + cmFuZG9tX3NpZ25hdHVyZQo= + + + + cmFuZG9tX3NpZ25hdHVyZQo= + + + + + + random_name_id + + + + + + + http://localhost:5678/rest/sso/saml/metadata + + + + + random-issuer + + + 4663f730-51c5-4490-a38a-19dda804865a + + + Danny n8n + + + mail + + + Danny + + + Martini + + + danny@n8n.io + + + random_name_id + + + Danny + + + Martini + + + danny@n8n.io + + + + + urn:oasis:names:tc:SAML:2.0:ac:classes:Unspecified + + + +`; + + // ACT + const result = await validateResponse(response); + + // ASSERT + expect(result).toBe(true); + }); + + test('rejects invalidate response', async () => { + // ARRANGE + // Invalid because required children are missing + const response = ` +`; + + // ACT + const result = await validateResponse(response); + + // ASSERT + expect(result).toBe(false); + }); + }); +}); diff --git a/packages/cli/src/sso/saml/__tests__/saml.service.ee.test.ts b/packages/cli/src/sso/saml/__tests__/saml.service.ee.test.ts index 9692cb9ec7..8bd5e32da2 100644 --- a/packages/cli/src/sso/saml/__tests__/saml.service.ee.test.ts +++ b/packages/cli/src/sso/saml/__tests__/saml.service.ee.test.ts @@ -98,7 +98,7 @@ describe('SamlService', () => { expect(samlService.reset).toHaveBeenCalledTimes(0); }); - test('does not call reset if no error is trown', async () => { + test('does not call reset if no error is thrown', async () => { // ARRANGE jest.spyOn(samlService, 'reset'); diff --git a/packages/cli/src/sso/saml/saml-validator.ts b/packages/cli/src/sso/saml/saml-validator.ts index 06a93cc4fb..07e9853f90 100644 --- a/packages/cli/src/sso/saml/saml-validator.ts +++ b/packages/cli/src/sso/saml/saml-validator.ts @@ -3,61 +3,36 @@ import type { XMLFileInfo } from 'xmllint-wasm'; import { Logger } from '@/logging/logger.service'; -let xml: XMLFileInfo; -let xmldsigCore: XMLFileInfo; -let xmlXenc: XMLFileInfo; let xmlMetadata: XMLFileInfo; -let xmlAssertion: XMLFileInfo; let xmlProtocol: XMLFileInfo; +let preload: XMLFileInfo[] = []; + // eslint-disable-next-line @typescript-eslint/consistent-type-imports let xmllintWasm: typeof import('xmllint-wasm') | undefined; // dynamically load schema files async function loadSchemas(): Promise { - if (!xml || xml.contents === '') { - Container.get(Logger).debug('Loading XML schema files for SAML validation into memory'); - const f = await import('./schema/xml.xsd'); - xml = { - fileName: 'xml.xsd', - contents: f.xsdXml, - }; - } - if (!xmldsigCore || xmldsigCore.contents === '') { - const f = await import('./schema/xmldsig-core-schema.xsd'); - xmldsigCore = { - fileName: 'xmldsig-core-schema.xsd', - contents: f.xsdXmldsigCore, - }; - } - if (!xmlXenc || xmlXenc.contents === '') { - const f = await import('./schema/xenc-schema.xsd'); - xmlXenc = { - fileName: 'xenc-schema.xsd', - contents: f.xsdXenc, - }; - } - if (!xmlMetadata || xmlMetadata.contents === '') { - const f = await import('./schema/saml-schema-metadata-2.0.xsd'); - xmlMetadata = { - fileName: 'saml-schema-metadata-2.0.xsd', - contents: f.xsdSamlSchemaMetadata20, - }; - } - if (!xmlAssertion || xmlAssertion.contents === '') { - const f = await import('./schema/saml-schema-assertion-2.0.xsd'); - xmlAssertion = { - fileName: 'saml-schema-assertion-2.0.xsd', - contents: f.xsdSamlSchemaAssertion20, - }; - } - if (!xmlProtocol || xmlProtocol.contents === '') { - const f = await import('./schema/saml-schema-protocol-2.0.xsd'); - xmlProtocol = { - fileName: 'saml-schema-protocol-2.0.xsd', - contents: f.xsdSamlSchemaProtocol20, - }; - } + xmlProtocol = (await import('./schema/saml-schema-protocol-2.0.xsd')).xmlFileInfo; + xmlMetadata = (await import('./schema/saml-schema-metadata-2.0.xsd')).xmlFileInfo; + preload = ( + await Promise.all([ + // SAML + import('./schema/saml-schema-assertion-2.0.xsd'), + import('./schema/xmldsig-core-schema.xsd'), + import('./schema/xenc-schema.xsd'), + import('./schema/xml.xsd'), + + // WS-Federation + import('./schema/ws-federation.xsd'), + import('./schema/oasis-200401-wss-wssecurity-secext-1.0.xsd'), + import('./schema/oasis-200401-wss-wssecurity-utility-1.0.xsd'), + import('./schema/ws-addr.xsd'), + import('./schema/metadata-exchange.xsd'), + import('./schema/ws-securitypolicy-1.2.xsd'), + import('./schema/ws-authorization.xsd'), + ]) + ).map((m) => m.xmlFileInfo); } // dynamically load xmllint-wasm @@ -82,7 +57,7 @@ export async function validateMetadata(metadata: string): Promise { ], extension: 'schema', schema: [xmlMetadata], - preload: [xmlProtocol, xmlAssertion, xmldsigCore, xmlXenc, xml], + preload: [xmlProtocol, ...preload], }); if (validationResult?.valid) { logger.debug('SAML Metadata is valid'); @@ -118,7 +93,7 @@ export async function validateResponse(response: string): Promise { ], extension: 'schema', schema: [xmlProtocol], - preload: [xmlMetadata, xmlAssertion, xmldsigCore, xmlXenc, xml], + preload: [xmlMetadata, ...preload], }); if (validationResult?.valid) { logger.debug('SAML Response is valid'); diff --git a/packages/cli/src/sso/saml/schema/metadata-exchange.xsd.ts b/packages/cli/src/sso/saml/schema/metadata-exchange.xsd.ts new file mode 100644 index 0000000000..d24876d682 --- /dev/null +++ b/packages/cli/src/sso/saml/schema/metadata-exchange.xsd.ts @@ -0,0 +1,117 @@ +import type { XMLFileInfo } from 'xmllint-wasm'; + +export const xmlFileInfo: XMLFileInfo = { + fileName: 'MetadataExchange.xsd', + contents: ` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +`, +}; diff --git a/packages/cli/src/sso/saml/schema/oasis-200401-wss-wssecurity-secext-1.0.xsd.ts b/packages/cli/src/sso/saml/schema/oasis-200401-wss-wssecurity-secext-1.0.xsd.ts new file mode 100644 index 0000000000..7a823c060b --- /dev/null +++ b/packages/cli/src/sso/saml/schema/oasis-200401-wss-wssecurity-secext-1.0.xsd.ts @@ -0,0 +1,200 @@ +import type { XMLFileInfo } from 'xmllint-wasm'; + +export const xmlFileInfo: XMLFileInfo = { + fileName: 'oasis-200401-wss-wssecurity-secext-1.0.xsd', + contents: ` + + + + + + + + This type represents an element with arbitrary attributes. + + + + + + + + + + + This type is used for password elements per Section 4.1. + + + + + + + + + + This type is used for elements containing stringified binary data. + + + + + + + + + + This type represents a username token per Section 4.1 + + + + + + + + + + + A security token that is encoded in binary + + + + + + + + + + A security token key identifier + + + + + + + + + + Typedef to allow a list of usages (as URIs). + + + + + + This global attribute is used to indicate the usage of a referenced or indicated token within the containing context + + + + + This type represents a reference to an external security token. + + + + + + + + This type represents a reference to an embedded security token. + + + + + + + + + + This type is used reference a security token. + + + + + + + + + + + This complexType defines header block to use for security-relevant data directed at a specific SOAP actor. + + + + + The use of "any" is to allow extensibility and different forms of security data. + + + + + + + + This complexType defines a container for elements to be specified from any namespace as properties/parameters of a DSIG transformation. + + + + + The use of "any" is to allow extensibility from any namespace. + + + + + + + + This element defines the wsse:UsernameToken element per Section 4.1. + + + + + This element defines the wsse:BinarySecurityToken element per Section 4.2. + + + + + This element defines a security token reference + + + + + This element defines a security token embedded reference + + + + + This element defines a key identifier reference + + + + + This element defines the wsse:SecurityTokenReference per Section 4.3. + + + + + This element defines the wsse:Security SOAP header element per Section 4. + + + + + This element contains properties for transformations from any namespace, including DSIG. + + + + + + + + + + + + + + + +`, +}; diff --git a/packages/cli/src/sso/saml/schema/oasis-200401-wss-wssecurity-utility-1.0.xsd.ts b/packages/cli/src/sso/saml/schema/oasis-200401-wss-wssecurity-utility-1.0.xsd.ts new file mode 100644 index 0000000000..b6238e39c2 --- /dev/null +++ b/packages/cli/src/sso/saml/schema/oasis-200401-wss-wssecurity-utility-1.0.xsd.ts @@ -0,0 +1,113 @@ +import type { XMLFileInfo } from 'xmllint-wasm'; + +export const xmlFileInfo: XMLFileInfo = { + fileName: 'oasis-200401-wss-wssecurity-utility-1.0.xsd', + contents: ` + + + + + + +This type defines the fault code value for Timestamp message expiration. + + + + + + + + + + +This global attribute supports annotating arbitrary elements with an ID. + + + + + + +Convenience attribute group used to simplify this schema. + + + + + + + + + +This type is for elements whose [children] is a psuedo-dateTime and can have arbitrary attributes. + + + + + + + + + + + +This type is for elements whose [children] is an anyURI and can have arbitrary attributes. + + + + + + + + + + + + +This complex type ties together the timestamp related elements into a composite type. + + + + + + + + + + + + + + +This element allows Timestamps to be applied anywhere element wildcards are present, +including as a SOAP header. + + + + + + + +This element allows an expiration time to be applied anywhere element wildcards are present. + + + + + + +This element allows a creation time to be applied anywhere element wildcards are present. + + + +`, +}; diff --git a/packages/cli/src/sso/saml/schema/saml-schema-assertion-2.0.xsd.ts b/packages/cli/src/sso/saml/schema/saml-schema-assertion-2.0.xsd.ts index 5000484753..7121427920 100644 --- a/packages/cli/src/sso/saml/schema/saml-schema-assertion-2.0.xsd.ts +++ b/packages/cli/src/sso/saml/schema/saml-schema-assertion-2.0.xsd.ts @@ -1,4 +1,8 @@ -export const xsdSamlSchemaAssertion20 = ` +import type { XMLFileInfo } from 'xmllint-wasm'; + +export const xmlFileInfo: XMLFileInfo = { + fileName: 'saml-schema-assertion-2.0.xsd', + contents: ` -`; + `, +}; diff --git a/packages/cli/src/sso/saml/schema/saml-schema-metadata-2.0.xsd.ts b/packages/cli/src/sso/saml/schema/saml-schema-metadata-2.0.xsd.ts index 6ed44930af..677872c03d 100644 --- a/packages/cli/src/sso/saml/schema/saml-schema-metadata-2.0.xsd.ts +++ b/packages/cli/src/sso/saml/schema/saml-schema-metadata-2.0.xsd.ts @@ -1,4 +1,8 @@ -export const xsdSamlSchemaMetadata20 = ` +import type { XMLFileInfo } from 'xmllint-wasm'; + +export const xmlFileInfo: XMLFileInfo = { + fileName: 'saml-schema-metadata-2.0.xsd', + contents: ` schemaLocation="saml-schema-assertion-2.0.xsd"/> + Document identifier: saml-schema-metadata-2.0 @@ -333,4 +339,5 @@ export const xsdSamlSchemaMetadata20 = ` -`; +`, +}; diff --git a/packages/cli/src/sso/saml/schema/saml-schema-protocol-2.0.xsd.ts b/packages/cli/src/sso/saml/schema/saml-schema-protocol-2.0.xsd.ts index e8bb9d0aae..ffc5a11640 100644 --- a/packages/cli/src/sso/saml/schema/saml-schema-protocol-2.0.xsd.ts +++ b/packages/cli/src/sso/saml/schema/saml-schema-protocol-2.0.xsd.ts @@ -1,4 +1,8 @@ -export const xsdSamlSchemaProtocol20 = ` +import type { XMLFileInfo } from 'xmllint-wasm'; + +export const xmlFileInfo: XMLFileInfo = { + fileName: 'saml-schema-protocol-2.0.xsd', + contents: ` -`; +`, +}; diff --git a/packages/cli/src/sso/saml/schema/ws-addr.xsd.ts b/packages/cli/src/sso/saml/schema/ws-addr.xsd.ts new file mode 100644 index 0000000000..9a275a5dcc --- /dev/null +++ b/packages/cli/src/sso/saml/schema/ws-addr.xsd.ts @@ -0,0 +1,142 @@ +import type { XMLFileInfo } from 'xmllint-wasm'; + +export const xmlFileInfo: XMLFileInfo = { + fileName: 'ws-addr.xsd', + contents: ` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +`, +}; diff --git a/packages/cli/src/sso/saml/schema/ws-authorization.xsd.ts b/packages/cli/src/sso/saml/schema/ws-authorization.xsd.ts new file mode 100644 index 0000000000..e12fb570fa --- /dev/null +++ b/packages/cli/src/sso/saml/schema/ws-authorization.xsd.ts @@ -0,0 +1,150 @@ +import type { XMLFileInfo } from 'xmllint-wasm'; + +export const xmlFileInfo: XMLFileInfo = { + fileName: 'ws-authorization.xsd', + contents: ` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +`, +}; diff --git a/packages/cli/src/sso/saml/schema/ws-federation.xsd.ts b/packages/cli/src/sso/saml/schema/ws-federation.xsd.ts new file mode 100644 index 0000000000..5006c633ed --- /dev/null +++ b/packages/cli/src/sso/saml/schema/ws-federation.xsd.ts @@ -0,0 +1,475 @@ +import type { XMLFileInfo } from 'xmllint-wasm'; + +export const xmlFileInfo: XMLFileInfo = { + fileName: 'ws-federation.xsd', + contents: ` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +`, +}; diff --git a/packages/cli/src/sso/saml/schema/ws-securitypolicy-1.2.xsd.ts b/packages/cli/src/sso/saml/schema/ws-securitypolicy-1.2.xsd.ts new file mode 100644 index 0000000000..6beb4a7c86 --- /dev/null +++ b/packages/cli/src/sso/saml/schema/ws-securitypolicy-1.2.xsd.ts @@ -0,0 +1,1210 @@ +import type { XMLFileInfo } from 'xmllint-wasm'; + +export const xmlFileInfo: XMLFileInfo = { + fileName: 'ws-securitypolicy-1.2.xsd', + contents: ` + + + + + + + + + + 4.1.1 SignedParts Assertion + + + + + + + 4.2.1 EncryptedParts Assertion + + + + + + + + + + + + + + + + + + + + + + + 4.1.2 SignedElements Assertion + + + + + + + 4.2.2 EncryptedElements Assertion + + + + + + + 4.3.1 RequiredElements Assertion + + + + + + + + + + + + + + + + + 5.1 Token Inclusion + + + + + + + + + + + + + + + + + + + + 5.4.1 UsernameToken Assertion + + + + + + + + + + + + + + + + + + + + 5.4.1 UsernameToken Assertion + + + + + + + 5.4.1 UsernameToken Assertion + + + + + + + 5.4.1 UsernameToken Assertion + + + + + + + 5.4.1 UsernameToken Assertion + + + + + + + + + + + + + + + 5.4.2 IssuedToken Assertion + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5.4.2 IssuedToken Assertion + + + + + + + 5.4.2 IssuedToken Assertion + + + + + + + 5.4.2 IssuedToken Assertion + + + + + + + 5.4.2 IssuedToken Assertion + + + + + + + 5.4.2 IssuedToken Assertion + + + + + + + + 5.4.3 X509Token Assertion + + + + + + + + + + + 5.4.3 X509Token Assertion + + + + + + + 5.4.3 X509Token Assertion + + + + + + + 5.4.3 X509Token Assertion + + + + + + + 5.4.3 X509Token Assertion + + + + + + + 5.4.3 X509Token Assertion + + + + + + + 5.4.3 X509Token Assertion + + + + + + + 5.4.3 X509Token Assertion + + + + + + + 5.4.3 X509Token Assertion + + + + + + + 5.4.3 X509Token Assertion + + + + + + + 5.4.3 X509Token Assertion + + + + + + + 5.4.3 X509Token Assertion + + + + + + + + 5.4.4 KerberosToken Assertion + + + + + + + + + + + + 5.4.4 KerberosToken Assertion + + + + + + + 5.4.4 KerberosToken Assertion + + + + + + + + 5.4.5 SpnegoContextToken Assertion + + + + + + + + + + + + + + + + + + + + + + 5.4.5 SpnegoContextToken Assertion + + + + + + + 5.4.5 SpnegoContextToken Assertion + + + + + + + 5.4.5 SpnegoContextToken Assertion + + + + + + + + 5.4.6 SecurityContextToken Assertion + + + + + + + + + + + 5.4.6 SecurityContextToken Assertion + + + + + + + 5.4.6 SecurityContextToken Assertion + + + + + + + + 5.4.7 SecureConversationToken Assertion + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5.4.7 SecureConversationToken Assertion + + + + + + + + 5.4.8 SamlToken Assertion + + + + + + + + + + + + 5.4.8 SamlToken Assertion + + + + + + + 5.4.8 SamlToken Assertion + + + + + + + 5.4.8 SamlToken Assertion + + + + + + + + 5.4.9 RelToken Assertion + + + + + + + + + + + + 5.4.9 RelToken Assertion + + + + + + + 5.4.9 RelToken Assertion + + + + + + + 5.4.9 RelToken Assertion + + + + + + + 5.4.9 RelToken Assertion + + + + + + + + 5.4.10 HttpsToken Assertion + + + + + + + 5.4.10 HttpsToken Assertion + + + + + + + 5.4.10 HttpsToken Assertion + + + + + + + 5.4.10 HttpsToken Assertion + + + + + + + + 5.4.11 KeyValueToken Assertion + + + + + + + + + + + + + + + 5.4.11 KeyValueToken Assertion + + + + + + + + + 7.1 AlgorithmSuite Assertion + + + + + + + + + + + + + + 7.1 AlgorithmSuite Assertion + + + + + + + 7.1 AlgorithmSuite Assertion + + + + + + + 7.1 AlgorithmSuite Assertion + + + + + + + 7.1 AlgorithmSuite Assertion + + + + + + + 7.1 AlgorithmSuite Assertion + + + + + + + 7.1 AlgorithmSuite Assertion + + + + + + + 7.1 AlgorithmSuite Assertion + + + + + + + 7.1 AlgorithmSuite Assertion + + + + + + + 7.1 AlgorithmSuite Assertion + + + + + + + 7.1 AlgorithmSuite Assertion + + + + + + + 7.1 AlgorithmSuite Assertion + + + + + + + 7.1 AlgorithmSuite Assertion + + + + + + + 7.1 AlgorithmSuite Assertion + + + + + + + 7.1 AlgorithmSuite Assertion + + + + + + + 7.1 AlgorithmSuite Assertion + + + + + + + 7.1 AlgorithmSuite Assertion + + + + + + + 7.1 AlgorithmSuite Assertion + + + + + + + 7.1 AlgorithmSuite Assertion + + + + + + + 7.1 AlgorithmSuite Assertion + + + + + + + 7.1 AlgorithmSuite Assertion + + + + + + + 7.1 AlgorithmSuite Assertion + + + + + + + 7.1 AlgorithmSuite Assertion + + + + + + + + 7.2 Layout Assertion + + + + + + + + 7.2 Layout Assertion + + + + + + + 7.2 Layout Assertion + + + + + + + 7.2 Layout Assertion + + + + + + + 7.2 Layout Assertion + + + + + + + + 7.3 TransportBinding Assertion + + + + + + + + 7.3 TransportBinding Assertion + + + + + + + + + + 7.3 TransportBinding Assertion + + + + + + + + 7.4 SymmetricBinding Assertion + + + + + + + 7.4 SymmetricBinding Assertion + + + + + + + 8=7.4 SymmetricBinding Assertion + + + + + + + 7.4 SymmetricBinding Assertion + + + + + + + + + + + 7.4 SymmetricBinding Assertion + + + + + + + 7.4 SymmetricBinding Assertion + + + + + + + 7.4 SymmetricBinding Assertion + + + + + + + 7.4 SymmetricBinding Assertion + + + + + + + + 7.5 AsymmetricBinding Assertion + + + + + + + + 7.5 AsymmetricBinding Assertion + + + + + + + + 7.5 AsymmetricBinding Assertion + + + + + + + + 7.5 AsymmetricBinding Assertion + + + + + + + + 7.5 AsymmetricBinding Assertion + + + + + + + + 7.5 AsymmetricBinding Assertion + + + + + + + + 7.5 AsymmetricBinding Assertion + + + + + + + + + + + + + + + + 8.1 SupportingTokens Assertion + + + + + + + + + + + + + 8.2 SignedSupportingTokens Assertion + + + + + + + + + + + + + 8.3 EndorsingSupportingTokens Assertion + + + + + + + + + + + + + 8.4 SignedEndorsingSupportingTokens Assertion + + + + + + + + + + + + + 8.5 SignedEncryptedSupportingTokens Assertion + + + + + + + + + + + + + 8.6 EncryptedSupportingTokens Assertion + + + + + + + + + + + + + 8.7 EndorsingEncryptedSupportingTokens Assertion + + + + + + + + + + + + + 8.8 SignedEndorsingEncryptedSupportingTokens Assertion + + + + + + + + + + + + + + 9.1 Wss10 Assertion + + + + + + + + 9.1 Wss10 Assertion + + + + + + + 9.1 Wss10 Assertion + + + + + + + 9.1 Wss10 Assertion + + + + + + + 9.1 Wss10 Assertion + + + + + + + + 9.2 Wss11 Assertion + + + + + + + + + + + + 9.2 Wss11 Assertion + + + + + + + 9.2 Wss11 Assertion + + + + + + + 9.2 Wss11 Assertion + + + + + + + + + 10.1 Trust13 Assertion + + + + + + + + 10.1 Trust13 Assertion + + + + + + + 10.1 Trust13 Assertion + + + + + + + 10.1 Trust13 Assertion + + + + + + + 10.1 Trust13 Assertion + + + + + + + 10.1 Trust13 Assertion + + + + + + + 10.1 Trust13 Assertion + + + + + + + 10.1 Trust13 Assertion + + + + +`, +}; diff --git a/packages/cli/src/sso/saml/schema/xenc-schema.xsd.ts b/packages/cli/src/sso/saml/schema/xenc-schema.xsd.ts index de9d3ca34e..1630937858 100644 --- a/packages/cli/src/sso/saml/schema/xenc-schema.xsd.ts +++ b/packages/cli/src/sso/saml/schema/xenc-schema.xsd.ts @@ -1,4 +1,8 @@ -export const xsdXenc = ` +import type { XMLFileInfo } from 'xmllint-wasm'; + +export const xmlFileInfo: XMLFileInfo = { + fileName: 'xenc-schema.xsd', + contents: ` -`; +`, +}; diff --git a/packages/cli/src/sso/saml/schema/xml.xsd.ts b/packages/cli/src/sso/saml/schema/xml.xsd.ts index 4487356ea5..d07b490c2e 100644 --- a/packages/cli/src/sso/saml/schema/xml.xsd.ts +++ b/packages/cli/src/sso/saml/schema/xml.xsd.ts @@ -1,4 +1,8 @@ -export const xsdXml = ` +import type { XMLFileInfo } from 'xmllint-wasm'; + +export const xmlFileInfo: XMLFileInfo = { + fileName: 'xml.xsd', + contents: ` @@ -114,4 +118,5 @@ export const xsdXml = ` -`; +`, +}; diff --git a/packages/cli/src/sso/saml/schema/xmldsig-core-schema.xsd.ts b/packages/cli/src/sso/saml/schema/xmldsig-core-schema.xsd.ts index 9cd615b616..ea0014b889 100644 --- a/packages/cli/src/sso/saml/schema/xmldsig-core-schema.xsd.ts +++ b/packages/cli/src/sso/saml/schema/xmldsig-core-schema.xsd.ts @@ -1,4 +1,8 @@ -export const xsdXmldsigCore = ` +import type { XMLFileInfo } from 'xmllint-wasm'; + +export const xmlFileInfo: XMLFileInfo = { + fileName: 'xmldsig-core-schema.xsd', + contents: ` -`; +`, +}; diff --git a/packages/cli/src/webhooks/__tests__/waiting-forms.test.ts b/packages/cli/src/webhooks/__tests__/waiting-forms.test.ts new file mode 100644 index 0000000000..bec6f95d7f --- /dev/null +++ b/packages/cli/src/webhooks/__tests__/waiting-forms.test.ts @@ -0,0 +1,199 @@ +import { mock } from 'jest-mock-extended'; +import { FORM_NODE_TYPE, type Workflow } from 'n8n-workflow'; + +import type { ExecutionRepository } from '@/databases/repositories/execution.repository'; +import { WaitingForms } from '@/webhooks/waiting-forms'; + +describe('WaitingForms', () => { + const executionRepository = mock(); + const waitingWebhooks = new WaitingForms(mock(), mock(), executionRepository); + + beforeEach(() => { + jest.restoreAllMocks(); + }); + + describe('findCompletionPage', () => { + it('should return lastNodeExecuted if it is a non-disabled form completion node', () => { + const workflow = mock({ + getParentNodes: jest.fn().mockReturnValue([]), + nodes: { + Form1: { + disabled: undefined, + type: FORM_NODE_TYPE, + parameters: { + operation: 'completion', + }, + }, + }, + }); + + const result = waitingWebhooks.findCompletionPage(workflow, {}, 'Form1'); + expect(result).toBe('Form1'); + }); + + it('should return undefined if lastNodeExecuted is disabled', () => { + const workflow = mock({ + getParentNodes: jest.fn().mockReturnValue([]), + nodes: { + Form1: { + disabled: true, + type: FORM_NODE_TYPE, + parameters: { + operation: 'completion', + }, + }, + }, + }); + + const result = waitingWebhooks.findCompletionPage(workflow, {}, 'Form1'); + expect(result).toBeUndefined(); + }); + + it('should return undefined if lastNodeExecuted is not a form node', () => { + const workflow = mock({ + getParentNodes: jest.fn().mockReturnValue([]), + nodes: { + NonForm: { + disabled: undefined, + type: 'other-node-type', + parameters: {}, + }, + }, + }); + + const result = waitingWebhooks.findCompletionPage(workflow, {}, 'NonForm'); + expect(result).toBeUndefined(); + }); + + it('should return undefined if lastNodeExecuted operation is not completion', () => { + const workflow = mock({ + getParentNodes: jest.fn().mockReturnValue([]), + nodes: { + Form1: { + disabled: undefined, + type: FORM_NODE_TYPE, + parameters: { + operation: 'page', + }, + }, + }, + }); + + const result = waitingWebhooks.findCompletionPage(workflow, {}, 'Form1'); + expect(result).toBeUndefined(); + }); + + it('should find first valid completion form in parent nodes if lastNodeExecuted is not valid', () => { + const workflow = mock({ + getParentNodes: jest.fn().mockReturnValue(['Form1', 'Form2', 'Form3']), + nodes: { + LastNode: { + disabled: undefined, + type: 'other-node-type', + parameters: {}, + }, + Form1: { + disabled: true, + type: FORM_NODE_TYPE, + parameters: { + operation: 'completion', + }, + }, + Form2: { + disabled: undefined, + type: FORM_NODE_TYPE, + parameters: { + operation: 'completion', + }, + }, + Form3: { + disabled: undefined, + type: FORM_NODE_TYPE, + parameters: { + operation: 'completion', + }, + }, + }, + }); + + const runData = { + Form2: [], + Form3: [], + }; + + const result = waitingWebhooks.findCompletionPage(workflow, runData, 'LastNode'); + expect(result).toBe('Form3'); + }); + + it('should return undefined if no valid completion form is found in parent nodes', () => { + const workflow = mock({ + getParentNodes: jest.fn().mockReturnValue(['Form1', 'Form2']), + nodes: { + LastNode: { + disabled: undefined, + type: 'other-node-type', + parameters: {}, + }, + Form1: { + disabled: true, + type: FORM_NODE_TYPE, + parameters: { + operation: 'completion', + }, + }, + Form2: { + disabled: undefined, + type: FORM_NODE_TYPE, + parameters: { + operation: 'submit', + }, + }, + }, + }); + + const result = waitingWebhooks.findCompletionPage(workflow, {}, 'LastNode'); + expect(result).toBeUndefined(); + }); + + it('should skip parent nodes without runData', () => { + const workflow = mock({ + getParentNodes: jest.fn().mockReturnValue(['Form1', 'Form2', 'Form3']), + nodes: { + LastNode: { + disabled: undefined, + type: 'other-node-type', + parameters: {}, + }, + Form1: { + disabled: undefined, + type: FORM_NODE_TYPE, + parameters: { + operation: 'completion', + }, + }, + Form2: { + disabled: undefined, + type: FORM_NODE_TYPE, + parameters: { + operation: 'completion', + }, + }, + Form3: { + disabled: undefined, + type: FORM_NODE_TYPE, + parameters: { + operation: 'completion', + }, + }, + }, + }); + + const runData = { + Form2: [], + }; + + const result = waitingWebhooks.findCompletionPage(workflow, runData, 'LastNode'); + expect(result).toBe('Form2'); + }); + }); +}); diff --git a/packages/cli/src/webhooks/waiting-forms.ts b/packages/cli/src/webhooks/waiting-forms.ts index 5a491c1fb3..cd2de74ed0 100644 --- a/packages/cli/src/webhooks/waiting-forms.ts +++ b/packages/cli/src/webhooks/waiting-forms.ts @@ -1,5 +1,6 @@ import axios from 'axios'; import type express from 'express'; +import type { IRunData } from 'n8n-workflow'; import { FORM_NODE_TYPE, sleep, Workflow } from 'n8n-workflow'; import { Service } from 'typedi'; @@ -57,6 +58,29 @@ export class WaitingForms extends WaitingWebhooks { } catch (error) {} } + findCompletionPage(workflow: Workflow, runData: IRunData, lastNodeExecuted: string) { + const parentNodes = workflow.getParentNodes(lastNodeExecuted); + const lastNode = workflow.nodes[lastNodeExecuted]; + + if ( + !lastNode.disabled && + lastNode.type === FORM_NODE_TYPE && + lastNode.parameters.operation === 'completion' + ) { + return lastNodeExecuted; + } else { + return parentNodes.reverse().find((nodeName) => { + const node = workflow.nodes[nodeName]; + return ( + !node.disabled && + node.type === FORM_NODE_TYPE && + node.parameters.operation === 'completion' && + runData[nodeName] + ); + }); + } + } + async executeWebhook( req: WaitingWebhookRequest, res: express.Response, @@ -89,35 +113,19 @@ export class WaitingForms extends WaitingWebhooks { throw new ConflictError(`The execution "${executionId}" is running already.`); } - let completionPage; + let lastNodeExecuted = execution.data.resultData.lastNodeExecuted as string; + if (execution.finished) { + // find the completion page to render + // if there is no completion page, render the default page const workflow = this.getWorkflow(execution); - const parentNodes = workflow.getParentNodes( - execution.data.resultData.lastNodeExecuted as string, + const completionPage = this.findCompletionPage( + workflow, + execution.data.resultData.runData, + lastNodeExecuted, ); - const lastNodeExecuted = execution.data.resultData.lastNodeExecuted as string; - const lastNode = workflow.nodes[lastNodeExecuted]; - - if ( - !lastNode.disabled && - lastNode.type === FORM_NODE_TYPE && - lastNode.parameters.operation === 'completion' - ) { - completionPage = lastNodeExecuted; - } else { - completionPage = Object.keys(workflow.nodes).find((nodeName) => { - const node = workflow.nodes[nodeName]; - return ( - parentNodes.includes(nodeName) && - !node.disabled && - node.type === FORM_NODE_TYPE && - node.parameters.operation === 'completion' - ); - }); - } - if (!completionPage) { res.render('form-trigger-completion', { title: 'Form Submitted', @@ -128,16 +136,16 @@ export class WaitingForms extends WaitingWebhooks { return { noWebhookResponse: true, }; + } else { + lastNodeExecuted = completionPage; } } - const targetNode = completionPage || (execution.data.resultData.lastNodeExecuted as string); - return await this.getWebhookExecutionData({ execution, req, res, - lastNodeExecuted: targetNode, + lastNodeExecuted, executionId, suffix, }); diff --git a/packages/cli/test/integration/evaluation/test-definitions.api.test.ts b/packages/cli/test/integration/evaluation/test-definitions.api.test.ts index 5d75b21ffd..3309bc08d4 100644 --- a/packages/cli/test/integration/evaluation/test-definitions.api.test.ts +++ b/packages/cli/test/integration/evaluation/test-definitions.api.test.ts @@ -163,9 +163,36 @@ describe('POST /evaluation/test-definitions', () => { }); expect(resp.statusCode).toBe(200); - expect(resp.body.data.name).toBe('test'); - expect(resp.body.data.workflowId).toBe(workflowUnderTest.id); - expect(resp.body.data.evaluationWorkflowId).toBe(evaluationWorkflow.id); + expect(resp.body.data).toEqual( + expect.objectContaining({ + name: 'test', + workflowId: workflowUnderTest.id, + evaluationWorkflowId: evaluationWorkflow.id, + }), + ); + }); + + test('should create test definition with all fields', async () => { + const resp = await authOwnerAgent.post('/evaluation/test-definitions').send({ + name: 'test', + description: 'test description', + workflowId: workflowUnderTest.id, + evaluationWorkflowId: evaluationWorkflow.id, + annotationTagId: annotationTag.id, + }); + + expect(resp.statusCode).toBe(200); + expect(resp.body.data).toEqual( + expect.objectContaining({ + name: 'test', + description: 'test description', + workflowId: workflowUnderTest.id, + evaluationWorkflowId: evaluationWorkflow.id, + annotationTag: expect.objectContaining({ + id: annotationTag.id, + }), + }), + ); }); test('should return error if name is empty', async () => { diff --git a/packages/core/package.json b/packages/core/package.json index 8628aa92ef..5ad1ac4d5d 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "n8n-core", - "version": "1.67.0", + "version": "1.68.0", "description": "Core functionality of n8n", "main": "dist/index", "types": "dist/index.d.ts", diff --git a/packages/core/src/NodeExecuteFunctions.ts b/packages/core/src/NodeExecuteFunctions.ts index e645309644..b3701c1ca3 100644 --- a/packages/core/src/NodeExecuteFunctions.ts +++ b/packages/core/src/NodeExecuteFunctions.ts @@ -918,6 +918,10 @@ function convertN8nRequestToAxios(n8nRequest: IHttpRequestOptions): AxiosRequest axiosRequest.params = n8nRequest.qs; + if (n8nRequest.abortSignal) { + axiosRequest.signal = n8nRequest.abortSignal; + } + if (n8nRequest.baseURL !== undefined) { axiosRequest.baseURL = n8nRequest.baseURL; } @@ -1718,6 +1722,11 @@ export async function httpRequestWithAuthentication( ) { removeEmptyBody(requestOptions); + // Cancel this request on execution cancellation + if ('getExecutionCancelSignal' in this) { + requestOptions.abortSignal = this.getExecutionCancelSignal(); + } + let credentialsDecrypted: ICredentialDataDecryptedObject | undefined; try { const parentTypes = additionalData.credentialsHelper.getParentTypes(credentialsType); diff --git a/packages/core/src/WorkflowExecute.ts b/packages/core/src/WorkflowExecute.ts index f8c32351bb..bb1b1633df 100644 --- a/packages/core/src/WorkflowExecute.ts +++ b/packages/core/src/WorkflowExecute.ts @@ -1243,7 +1243,7 @@ export class WorkflowExecute { : []; while (items.length) { - const item = items.pop(); + const item = items.shift(); if (item === undefined) { continue; } diff --git a/packages/core/test/WorkflowExecute.test.ts b/packages/core/test/WorkflowExecute.test.ts index 6d1927fb88..ab69a7574b 100644 --- a/packages/core/test/WorkflowExecute.test.ts +++ b/packages/core/test/WorkflowExecute.test.ts @@ -188,7 +188,11 @@ describe('WorkflowExecute', () => { if (nodeData.data === undefined) { return null; } - return nodeData.data.main[0]; + return nodeData.data.main[0]!.map((entry) => { + // remove pairedItem from entry if it is an error output test + if (testData.description.includes('error_outputs')) delete entry.pairedItem; + return entry; + }); }); expect(resultData).toEqual(testData.output.nodeData[nodeName]); diff --git a/packages/core/test/workflows/error_outputs.json b/packages/core/test/workflows/error_outputs.json index f8e698bd52..5e143b644e 100644 --- a/packages/core/test/workflows/error_outputs.json +++ b/packages/core/test/workflows/error_outputs.json @@ -1,13 +1,13 @@ { - "name": "Error Output - Test Workflow", + "name": "My workflow 105", "nodes": [ { "parameters": {}, - "id": "c41b46f0-3e76-4655-b5ea-4d15af58c138", + "id": "a94bc1fb-1f39-404b-b149-a76c4fbaed25", "name": "When clicking \"Execute Workflow\"", "type": "n8n-nodes-base.manualTrigger", "typeVersion": 1, - "position": [-680, 460] + "position": [-60, 780] }, { "parameters": { @@ -21,11 +21,11 @@ }, "options": {} }, - "id": "247f4118-d80f-49ab-8d9a-0cdbbb9271df", + "id": "6ba26bdf-91e2-4f18-8f4c-09e98aa4a9df", "name": "Success", "type": "n8n-nodes-base.set", "typeVersion": 3.2, - "position": [200, 860] + "position": [820, 1180] }, { "parameters": { @@ -39,21 +39,21 @@ }, "options": {} }, - "id": "311e3650-d89c-405a-9c8d-c238f48a8a5a", + "id": "e3d1eadf-0994-4806-97ce-c5c5f673c624", "name": "Error", "type": "n8n-nodes-base.set", "typeVersion": 3.2, - "position": [200, 1040] + "position": [820, 1360] }, { "parameters": { "jsCode": "return [\n {\n \"id\": \"23423532\",\n \"name\": \"Jay Gatsby\",\n \"email\": \"gatsby@west-egg.com\",\n \"notes\": \"Keeps asking about a green light??\",\n \"country\": \"US\",\n \"created\": \"1925-04-10\"\n },\n {\n \"id\": \"23423533\",\n \"name\": \"José Arcadio Buendía\",\n \"email\": \"jab@macondo.co\",\n \"notes\": \"Lots of people named after him. Very confusing\",\n \"country\": \"CO\",\n \"created\": \"1967-05-05\"\n },\n {\n \"id\": \"23423534\",\n \"name\": \"Max Sendak\",\n \"email\": \"info@in-and-out-of-weeks.org\",\n \"notes\": \"Keeps rolling his terrible eyes\",\n \"country\": \"US\",\n \"created\": \"1963-04-09\"\n },\n {\n \"id\": \"23423535\",\n \"name\": \"Zaphod Beeblebrox\",\n \"email\": \"captain@heartofgold.com\",\n \"notes\": \"Felt like I was talking to more than one person\",\n \"country\": null,\n \"created\": \"1979-10-12\"\n },\n {\n \"id\": \"23423536\",\n \"name\": \"Edmund Pevensie\",\n \"email\": \"edmund@narnia.gov\",\n \"notes\": \"Passionate sailor\",\n \"country\": \"UK\",\n \"created\": \"1950-10-16\"\n }\n]" }, - "id": "179d4fe7-1ae7-4957-a77d-12c3ca6d141b", + "id": "01adfc2d-141d-4843-b2d6-04115a476bc1", "name": "Mock Data", "type": "n8n-nodes-base.code", "typeVersion": 2, - "position": [-460, 460] + "position": [160, 780] }, { "parameters": { @@ -61,11 +61,11 @@ "height": 414, "width": 564 }, - "id": "1ec2a8b6-54e2-4319-90b3-30b387855b36", + "id": "8ca689eb-7910-43ad-bd10-fae35a8fc203", "name": "Sticky Note", "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, - "position": [-160, 780] + "position": [460, 1100] }, { "parameters": { @@ -73,11 +73,11 @@ "height": 279, "width": 564 }, - "id": "49a2b7d9-8bd1-4cdf-9649-2d93668b0f8f", + "id": "a17460d6-b0c0-432d-ac6f-8ff684357c8d", "name": "Sticky Note1", "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, - "position": [-160, 140] + "position": [460, 460] }, { "parameters": { @@ -91,22 +91,22 @@ }, "options": {} }, - "id": "9852f1d9-95b4-4ef7-bb18-8f0bab81a0bc", + "id": "46df5463-4289-4e61-9f80-87e035931bda", "name": "Combined", "type": "n8n-nodes-base.set", "typeVersion": 3.2, - "position": [180, 240] + "position": [800, 560] }, { "parameters": { "mode": "runOnceForEachItem", "jsCode": "// Add a new field called 'myNewField' to the JSON of the item\n$input.item.json.myNewField = 1;\n\nif ($input.item.json.country === 'US') {\n throw new Error('This is an error');\n}\n\nreturn $input.item;" }, - "id": "40d4dba3-3db7-4eb5-aa27-e76f955a5e09", + "id": "a4708520-aaca-4618-b7a2-94da268fba37", "name": "Throw Error", "type": "n8n-nodes-base.code", "typeVersion": 2, - "position": [-140, 960], + "position": [480, 1280], "errorOutput": true, "onError": "continueErrorOutput" }, @@ -116,22 +116,22 @@ "height": 279, "width": 564 }, - "id": "8eb3dd54-c1dd-4167-abfa-c06d044c63f3", + "id": "f0a450cd-4124-490d-964f-a71b645f770c", "name": "Sticky Note2", "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, - "position": [-160, 460] + "position": [460, 780] }, { "parameters": { "mode": "runOnceForEachItem", "jsCode": "// Add a new field called 'myNewField' to the JSON of the item\n$input.item.json.myNewField = 1;\n\nif ($input.item.json.country === 'US') {\n throw new Error('This is an error');\n}\n\nreturn $input.item;" }, - "id": "19a3d6ac-e610-4296-9b7a-9ed19d242bdb", + "id": "823f12e6-cbfc-4545-8505-fab158d1effe", "name": "Throw Error2", "type": "n8n-nodes-base.code", "typeVersion": 2, - "position": [-120, 560], + "position": [500, 880], "onError": "continueRegularOutput" }, { @@ -146,22 +146,22 @@ }, "options": {} }, - "id": "5f803fdc-7d88-4c12-8886-6092cfbc03c6", + "id": "8f88d130-9a13-4236-81c0-157f8a8990c0", "name": "Combined1", "type": "n8n-nodes-base.set", "typeVersion": 3.2, - "position": [180, 560] + "position": [800, 880] }, { "parameters": { "mode": "runOnceForEachItem", "jsCode": "// Add a new field called 'myNewField' to the JSON of the item\n$input.item.json.myNewField = 1;\n\nif ($input.item.json.country === 'US') {\n throw new Error('This is an error');\n}\n\nreturn $input.item;" }, - "id": "c2696c1f-1abd-4549-9ad9-e62017dc14b8", + "id": "1a3f4beb-0d1e-44fe-a411-5bd1096ffd74", "name": "Throw Error1", "type": "n8n-nodes-base.code", "typeVersion": 2, - "position": [-120, 240], + "position": [500, 560], "continueOnFail": true }, { @@ -176,11 +176,11 @@ }, "options": {} }, - "id": "01740d7e-e572-408a-9fae-729068803113", + "id": "c617a3d7-15e3-49b4-a7dd-d45c5e059a22", "name": "Success1", "type": "n8n-nodes-base.set", "typeVersion": 3.2, - "position": [200, 1320] + "position": [820, 1640] }, { "parameters": { @@ -188,22 +188,22 @@ "height": 509.71047006830065, "width": 1183.725293692246 }, - "id": "ed409181-4847-4d65-af45-f45078a6343e", + "id": "046de2cf-970a-4925-b87d-16e8cca511fd", "name": "Sticky Note3", "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, - "position": [-160, 1240] + "position": [460, 1560] }, { "parameters": { "mode": "runOnceForEachItem", "jsCode": "// Add a new field called 'myNewField' to the JSON of the item\n$input.item.json.myNewField = 1;\n\nif ($input.item.json.country === 'US') {\n throw new Error('This is an error');\n}\n\nreturn $input.item;" }, - "id": "93d03f38-b928-4b4b-832a-3f1a5deebb2d", + "id": "9ec21de1-dfca-4fff-b5a7-a56364239d7b", "name": "Throw Error3", "type": "n8n-nodes-base.code", "typeVersion": 2, - "position": [-140, 1420], + "position": [480, 1740], "errorOutput": true, "onError": "continueErrorOutput" }, @@ -211,11 +211,11 @@ "parameters": { "options": {} }, - "id": "c92a6ce5-41ea-4fb9-a07b-c4e98f905b12", + "id": "e3605953-75cf-4036-99f7-05e3971a6a75", "name": "Edit Fields", "type": "n8n-nodes-base.set", "typeVersion": 3.2, - "position": [420, 1500], + "position": [1040, 1820], "onError": "continueErrorOutput" }, { @@ -230,11 +230,11 @@ }, "options": {} }, - "id": "ab838cc1-0987-4b41-bdc5-fe17f38e0691", + "id": "a71cfb77-adfd-4c77-9a8e-7e58cbd0931b", "name": "Success2", "type": "n8n-nodes-base.set", "typeVersion": 3.2, - "position": [700, 1360] + "position": [1320, 1680] }, { "parameters": { @@ -248,11 +248,11 @@ }, "options": {} }, - "id": "22e04172-19b9-4735-9dd0-a3e2fa3bf000", + "id": "ea9d02e9-1716-4f69-a14a-9133f5184886", "name": "Error2", "type": "n8n-nodes-base.set", "typeVersion": 3.2, - "position": [700, 1580] + "position": [1320, 1900] }, { "parameters": { @@ -266,101 +266,19 @@ }, "options": {} }, - "id": "69e7257a-1ba8-46ba-9394-d38d65b2e567", + "id": "17780679-f7a3-4b1b-b6ee-f3f61e0843ad", "name": "Error1", "type": "n8n-nodes-base.set", "typeVersion": 3.2, - "position": [200, 1500] + "position": [820, 1820] } ], "pinData": { - "Error": [ - { - "json": { - "id": "23423534", - "name": "Max Sendak", - "email": "info@in-and-out-of-weeks.org", - "notes": "Keeps rolling his terrible eyes", - "country": "US", - "created": "1963-04-09", - "error": "This is an error [line 5, for item 2]", - "originalName": "Max Sendak" - }, - "pairedItem": { - "item": 0 - } - }, - { - "json": { - "id": "23423532", - "name": "Jay Gatsby", - "email": "gatsby@west-egg.com", - "notes": "Keeps asking about a green light??", - "country": "US", - "created": "1925-04-10", - "error": "This is an error [line 5, for item 0]", - "originalName": "Jay Gatsby" - }, - "pairedItem": { - "item": 1 - } - } - ], - "Success": [ - { - "json": { - "id": "23423536", - "name": "Edmund Pevensie", - "email": "edmund@narnia.gov", - "notes": "Passionate sailor", - "country": "UK", - "created": "1950-10-16", - "myNewField": 1, - "originalName": "Edmund Pevensie" - }, - "pairedItem": { - "item": 0 - } - }, - { - "json": { - "id": "23423535", - "name": "Zaphod Beeblebrox", - "email": "captain@heartofgold.com", - "notes": "Felt like I was talking to more than one person", - "country": null, - "created": "1979-10-12", - "myNewField": 1, - "originalName": "Zaphod Beeblebrox" - }, - "pairedItem": { - "item": 1 - } - }, - { - "json": { - "id": "23423533", - "name": "José Arcadio Buendía", - "email": "jab@macondo.co", - "notes": "Lots of people named after him. Very confusing", - "country": "CO", - "created": "1967-05-05", - "myNewField": 1, - "originalName": "José Arcadio Buendía" - }, - "pairedItem": { - "item": 2 - } - } - ], "Combined": [ { "json": { "error": "This is an error [line 5, for item 0]", "originalName": "Jay Gatsby" - }, - "pairedItem": { - "item": 0 } }, { @@ -373,18 +291,12 @@ "created": "1967-05-05", "myNewField": 1, "originalName": "José Arcadio Buendía" - }, - "pairedItem": { - "item": 1 } }, { "json": { "error": "This is an error [line 5, for item 2]", "originalName": "Max Sendak" - }, - "pairedItem": { - "item": 2 } }, { @@ -397,9 +309,6 @@ "created": "1979-10-12", "myNewField": 1, "originalName": "Zaphod Beeblebrox" - }, - "pairedItem": { - "item": 3 } }, { @@ -412,9 +321,6 @@ "created": "1950-10-16", "myNewField": 1, "originalName": "Edmund Pevensie" - }, - "pairedItem": { - "item": 4 } } ], @@ -423,9 +329,6 @@ "json": { "error": "This is an error [line 5, for item 0]", "originalName": "Jay Gatsby" - }, - "pairedItem": { - "item": 0 } }, { @@ -438,18 +341,12 @@ "created": "1967-05-05", "myNewField": 1, "originalName": "José Arcadio Buendía" - }, - "pairedItem": { - "item": 1 } }, { "json": { "error": "This is an error [line 5, for item 2]", "originalName": "Max Sendak" - }, - "pairedItem": { - "item": 2 } }, { @@ -462,9 +359,6 @@ "created": "1979-10-12", "myNewField": 1, "originalName": "Zaphod Beeblebrox" - }, - "pairedItem": { - "item": 3 } }, { @@ -477,26 +371,20 @@ "created": "1950-10-16", "myNewField": 1, "originalName": "Edmund Pevensie" - }, - "pairedItem": { - "item": 4 } } ], "Success1": [ { "json": { - "id": "23423536", - "name": "Edmund Pevensie", - "email": "edmund@narnia.gov", - "notes": "Passionate sailor", - "country": "UK", - "created": "1950-10-16", + "id": "23423533", + "name": "José Arcadio Buendía", + "email": "jab@macondo.co", + "notes": "Lots of people named after him. Very confusing", + "country": "CO", + "created": "1967-05-05", "myNewField": 1, - "originalName": "Edmund Pevensie" - }, - "pairedItem": { - "item": 0 + "originalName": "José Arcadio Buendía" } }, { @@ -509,43 +397,22 @@ "created": "1979-10-12", "myNewField": 1, "originalName": "Zaphod Beeblebrox" - }, - "pairedItem": { - "item": 1 } }, { "json": { - "id": "23423533", - "name": "José Arcadio Buendía", - "email": "jab@macondo.co", - "notes": "Lots of people named after him. Very confusing", - "country": "CO", - "created": "1967-05-05", + "id": "23423536", + "name": "Edmund Pevensie", + "email": "edmund@narnia.gov", + "notes": "Passionate sailor", + "country": "UK", + "created": "1950-10-16", "myNewField": 1, - "originalName": "José Arcadio Buendía" - }, - "pairedItem": { - "item": 2 + "originalName": "Edmund Pevensie" } } ], "Error1": [ - { - "json": { - "id": "23423534", - "name": "Max Sendak", - "email": "info@in-and-out-of-weeks.org", - "notes": "Keeps rolling his terrible eyes", - "country": "US", - "created": "1963-04-09", - "error": "This is an error [line 5, for item 2]", - "originalName": "Max Sendak" - }, - "pairedItem": { - "item": 0 - } - }, { "json": { "id": "23423532", @@ -556,9 +423,18 @@ "created": "1925-04-10", "error": "This is an error [line 5, for item 0]", "originalName": "Jay Gatsby" - }, - "pairedItem": { - "item": 1 + } + }, + { + "json": { + "id": "23423534", + "name": "Max Sendak", + "email": "info@in-and-out-of-weeks.org", + "notes": "Keeps rolling his terrible eyes", + "country": "US", + "created": "1963-04-09", + "error": "This is an error [line 5, for item 2]", + "originalName": "Max Sendak" } } ], @@ -573,9 +449,6 @@ "created": "1925-04-10", "error": "This is an error [line 5, for item 0]", "originalName": "Jay Gatsby" - }, - "pairedItem": { - "item": 0 } }, { @@ -588,9 +461,70 @@ "created": "1963-04-09", "error": "This is an error [line 5, for item 2]", "originalName": "Max Sendak" - }, - "pairedItem": { - "item": 1 + } + } + ], + "Error": [ + { + "json": { + "id": "23423532", + "name": "Jay Gatsby", + "email": "gatsby@west-egg.com", + "notes": "Keeps asking about a green light??", + "country": "US", + "created": "1925-04-10", + "error": "This is an error [line 5, for item 0]", + "originalName": "Jay Gatsby" + } + }, + { + "json": { + "id": "23423534", + "name": "Max Sendak", + "email": "info@in-and-out-of-weeks.org", + "notes": "Keeps rolling his terrible eyes", + "country": "US", + "created": "1963-04-09", + "error": "This is an error [line 5, for item 2]", + "originalName": "Max Sendak" + } + } + ], + "Success": [ + { + "json": { + "id": "23423533", + "name": "José Arcadio Buendía", + "email": "jab@macondo.co", + "notes": "Lots of people named after him. Very confusing", + "country": "CO", + "created": "1967-05-05", + "myNewField": 1, + "originalName": "José Arcadio Buendía" + } + }, + { + "json": { + "id": "23423535", + "name": "Zaphod Beeblebrox", + "email": "captain@heartofgold.com", + "notes": "Felt like I was talking to more than one person", + "country": null, + "created": "1979-10-12", + "myNewField": 1, + "originalName": "Zaphod Beeblebrox" + } + }, + { + "json": { + "id": "23423536", + "name": "Edmund Pevensie", + "email": "edmund@narnia.gov", + "notes": "Passionate sailor", + "country": "UK", + "created": "1950-10-16", + "myNewField": 1, + "originalName": "Edmund Pevensie" } } ] @@ -725,10 +659,11 @@ "settings": { "executionOrder": "v1" }, - "versionId": "e73e1eda-293c-4ee2-87b9-923873241774", - "id": "UgoluWRMeg7fPLCB", + "versionId": "94aaa2ce-558a-4fed-948a-09860174272a", "meta": { - "instanceId": "021d3c82ba2d3bc090cbf4fc81c9312668bcc34297e022bb3438c5c88a43a5ff" + "templateCredsSetupCompleted": true, + "instanceId": "27cc9b56542ad45b38725555722c50a1c3fee1670bbb67980558314ee08517c4" }, + "id": "FJvJXVvjM5rw3sUM", "tags": [] } diff --git a/packages/design-system/package.json b/packages/design-system/package.json index 94f1cba0aa..11da59ddaa 100644 --- a/packages/design-system/package.json +++ b/packages/design-system/package.json @@ -1,6 +1,6 @@ { "name": "n8n-design-system", - "version": "1.57.0", + "version": "1.58.0", "main": "src/main.ts", "import": "src/main.ts", "scripts": { diff --git a/packages/design-system/src/components/N8nCallout/Callout.vue b/packages/design-system/src/components/N8nCallout/Callout.vue index 9d866715c1..6a796ba32a 100644 --- a/packages/design-system/src/components/N8nCallout/Callout.vue +++ b/packages/design-system/src/components/N8nCallout/Callout.vue @@ -23,6 +23,7 @@ interface CalloutProps { iconless?: boolean; slim?: boolean; roundCorners?: boolean; + onlyBottomBorder?: boolean; } defineOptions({ name: 'N8nCallout' }); @@ -38,6 +39,7 @@ const classes = computed(() => [ $style[props.theme], props.slim ? $style.slim : '', props.roundCorners ? $style.round : '', + props.onlyBottomBorder ? $style.onlyBottomBorder : '', ]); const getIcon = computed( @@ -95,6 +97,12 @@ const getIconSize = computed(() => { border-radius: var(--border-radius-base); } +.onlyBottomBorder { + border-top: 0; + border-left: 0; + border-right: 0; +} + .messageSection { display: flex; align-items: center; diff --git a/packages/editor-ui/package.json b/packages/editor-ui/package.json index 5d5c63a298..c9b866bb37 100644 --- a/packages/editor-ui/package.json +++ b/packages/editor-ui/package.json @@ -1,6 +1,6 @@ { "name": "n8n-editor-ui", - "version": "1.67.0", + "version": "1.68.0", "description": "Workflow Editor UI for n8n", "main": "index.js", "scripts": { @@ -74,6 +74,7 @@ "vue": "catalog:frontend", "vue-agile": "^2.0.0", "vue-chartjs": "^5.2.0", + "vue-github-button": "^3.1.3", "vue-i18n": "^9.2.2", "vue-json-pretty": "2.2.4", "vue-markdown-render": "catalog:frontend", diff --git a/packages/editor-ui/src/components/CanvasChat/CanvasChat.vue b/packages/editor-ui/src/components/CanvasChat/CanvasChat.vue index 4d551a67be..51bf1bf8be 100644 --- a/packages/editor-ui/src/components/CanvasChat/CanvasChat.vue +++ b/packages/editor-ui/src/components/CanvasChat/CanvasChat.vue @@ -52,6 +52,7 @@ const isChatOpen = computed(() => { const result = workflowsStore.isChatPanelOpen; return result; }); +const canvasNodes = computed(() => workflowsStore.allNodes); const isLogsOpen = computed(() => workflowsStore.isLogsPanelOpen); const previousChatMessages = computed(() => workflowsStore.getPastChatMessages); @@ -70,7 +71,7 @@ const { runWorkflow } = useRunWorkflow({ router }); const { chatTriggerNode, connectedNode, allowFileUploads, setChatTriggerNode, setConnectedNode } = useChatTrigger({ workflow, - canvasNodes: workflowsStore.allNodes, + canvasNodes, getNodeByName: workflowsStore.getNodeByName, getNodeType: nodeTypesStore.getNodeType, }); diff --git a/packages/editor-ui/src/components/CanvasChat/composables/useChatTrigger.ts b/packages/editor-ui/src/components/CanvasChat/composables/useChatTrigger.ts index 9ec2e346a6..7d0cff0d8d 100644 --- a/packages/editor-ui/src/components/CanvasChat/composables/useChatTrigger.ts +++ b/packages/editor-ui/src/components/CanvasChat/composables/useChatTrigger.ts @@ -1,5 +1,5 @@ -import type { ComputedRef } from 'vue'; -import { ref, computed } from 'vue'; +import type { ComputedRef, MaybeRef } from 'vue'; +import { ref, computed, unref } from 'vue'; import { CHAIN_SUMMARIZATION_LANGCHAIN_NODE_TYPE, NodeConnectionType, @@ -19,7 +19,7 @@ import type { INodeUi } from '@/Interface'; export interface ChatTriggerDependencies { getNodeByName: (name: string) => INodeUi | null; getNodeType: (type: string, version: number) => INodeTypeDescription | null; - canvasNodes: INodeUi[]; + canvasNodes: MaybeRef; workflow: ComputedRef; } @@ -52,7 +52,7 @@ export function useChatTrigger({ /** Gets the chat trigger node from the workflow */ function setChatTriggerNode() { - const triggerNode = canvasNodes.find((node) => + const triggerNode = unref(canvasNodes).find((node) => [CHAT_TRIGGER_NODE_TYPE, MANUAL_CHAT_TRIGGER_NODE_TYPE].includes(node.type), ); diff --git a/packages/editor-ui/src/components/MainHeader/MainHeader.vue b/packages/editor-ui/src/components/MainHeader/MainHeader.vue index 0625d2ff2c..67428421e0 100644 --- a/packages/editor-ui/src/components/MainHeader/MainHeader.vue +++ b/packages/editor-ui/src/components/MainHeader/MainHeader.vue @@ -18,6 +18,8 @@ import { useWorkflowsStore } from '@/stores/workflows.store'; import { useExecutionsStore } from '@/stores/executions.store'; import { usePushConnection } from '@/composables/usePushConnection'; +import GithubButton from 'vue-github-button'; + const router = useRouter(); const route = useRoute(); const locale = useI18n(); @@ -161,7 +163,7 @@ async function navigateToExecutionsView(openInNewTab: boolean) { diff --git a/packages/editor-ui/src/components/Projects/ProjectHeader.test.ts b/packages/editor-ui/src/components/Projects/ProjectHeader.test.ts index 46b18718bb..01c96a8ad8 100644 --- a/packages/editor-ui/src/components/Projects/ProjectHeader.test.ts +++ b/packages/editor-ui/src/components/Projects/ProjectHeader.test.ts @@ -65,7 +65,7 @@ describe('ProjectHeader', () => { it('should render the correct title', async () => { const { getByText, rerender } = renderComponent(); - expect(getByText('Home')).toBeVisible(); + expect(getByText('Overview')).toBeVisible(); projectsStore.currentProject = { type: ProjectTypes.Personal } as Project; await rerender({}); diff --git a/packages/editor-ui/src/components/Projects/ProjectHeader.vue b/packages/editor-ui/src/components/Projects/ProjectHeader.vue index a89a195975..6432bfcfcb 100644 --- a/packages/editor-ui/src/components/Projects/ProjectHeader.vue +++ b/packages/editor-ui/src/components/Projects/ProjectHeader.vue @@ -23,7 +23,7 @@ const headerIcon = computed(() => { const projectName = computed(() => { if (!projectsStore.currentProject) { - return i18n.baseText('projects.menu.home'); + return i18n.baseText('projects.menu.overview'); } else if (projectsStore.currentProject.type === ProjectTypes.Personal) { return i18n.baseText('projects.menu.personal'); } else { diff --git a/packages/editor-ui/src/components/Projects/ProjectNavigation.vue b/packages/editor-ui/src/components/Projects/ProjectNavigation.vue index 47513ea1d6..fd9145bb99 100644 --- a/packages/editor-ui/src/components/Projects/ProjectNavigation.vue +++ b/packages/editor-ui/src/components/Projects/ProjectNavigation.vue @@ -27,7 +27,7 @@ const isCreatingProject = ref(false); const isComponentMounted = ref(false); const home = computed(() => ({ id: 'home', - label: locale.baseText('projects.menu.home'), + label: locale.baseText('projects.menu.overview'), icon: 'home', route: { to: { name: VIEWS.HOMEPAGE }, diff --git a/packages/editor-ui/src/components/ResourceLocator/ResourceLocatorDropdown.vue b/packages/editor-ui/src/components/ResourceLocator/ResourceLocatorDropdown.vue index 01c7b4071d..a4e00af9f7 100644 --- a/packages/editor-ui/src/components/ResourceLocator/ResourceLocatorDropdown.vue +++ b/packages/editor-ui/src/components/ResourceLocator/ResourceLocatorDropdown.vue @@ -145,7 +145,12 @@ function onKeyDown(e: KeyboardEvent) { } } } else if (e.key === 'Enter') { - emit('update:modelValue', sortedResources.value[hoverIndex.value].value); + const selected = sortedResources.value[hoverIndex.value]?.value; + + // Selected resource can be empty when loading or empty results + if (selected) { + emit('update:modelValue', selected); + } } } diff --git a/packages/editor-ui/src/components/banners/BaseBanner.vue b/packages/editor-ui/src/components/banners/BaseBanner.vue index 32af54cec0..bac58e74db 100644 --- a/packages/editor-ui/src/components/banners/BaseBanner.vue +++ b/packages/editor-ui/src/components/banners/BaseBanner.vue @@ -39,6 +39,7 @@ async function onCloseClick() { icon-size="medium" :round-corners="false" :data-test-id="`banners-${props.name}`" + :only-bottom-border="true" >
@@ -78,10 +79,4 @@ async function onCloseClick() { align-items: center; gap: var(--spacing-l); } - -:global(.n8n-callout) { - border-top: 0; - border-left: 0; - border-right: 0; -} diff --git a/packages/editor-ui/src/components/banners/__snapshots__/V1Banner.test.ts.snap b/packages/editor-ui/src/components/banners/__snapshots__/V1Banner.test.ts.snap index 41daf10517..29665752de 100644 --- a/packages/editor-ui/src/components/banners/__snapshots__/V1Banner.test.ts.snap +++ b/packages/editor-ui/src/components/banners/__snapshots__/V1Banner.test.ts.snap @@ -3,7 +3,7 @@ exports[`V1 Banner > should render banner 1`] = `