From 439c9c6e1f61b40ecd3b4f0fb585c1354124f110 Mon Sep 17 00:00:00 2001 From: Alex Grozav Date: Fri, 27 Jan 2023 17:09:55 +0200 Subject: [PATCH 001/358] fix: Fix RecycleScroller end index (no-changelog) (#5272) --- .../src/components/N8nRecycleScroller/RecycleScroller.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/design-system/src/components/N8nRecycleScroller/RecycleScroller.vue b/packages/design-system/src/components/N8nRecycleScroller/RecycleScroller.vue index a9943b12016368..eb4e11f76e4bd9 100644 --- a/packages/design-system/src/components/N8nRecycleScroller/RecycleScroller.vue +++ b/packages/design-system/src/components/N8nRecycleScroller/RecycleScroller.vue @@ -87,7 +87,7 @@ export default defineComponent({ }); const index = foundIndex + props.offset; - return index === -1 ? props.items.length - 1 : index; + return foundIndex === -1 ? props.items.length - 1 : index; }); const visibleItems = computed(() => { From b9768b3b80d5a6190ff1b7560ef374ecc219bd2f Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 27 Jan 2023 15:26:41 +0000 Subject: [PATCH 002/358] =?UTF-8?q?:bookmark:=20Release=C2=A0n8n-workflow@?= =?UTF-8?q?0.134.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/workflow/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/workflow/package.json b/packages/workflow/package.json index d67bf6ade3d648..06e5bac8b46bbe 100644 --- a/packages/workflow/package.json +++ b/packages/workflow/package.json @@ -1,6 +1,6 @@ { "name": "n8n-workflow", - "version": "0.133.2", + "version": "0.134.0", "description": "Workflow base code of n8n", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", From 8c9412767f19ff966602fdcbcc3da19f2c8d630f Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 27 Jan 2023 15:26:54 +0000 Subject: [PATCH 003/358] :arrow_up: Set n8n-workflow@0.134.0 on n8n-core --- packages/core/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/package.json b/packages/core/package.json index 2cd75d60aa4833..758e63684f46e3 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -55,7 +55,7 @@ "form-data": "^4.0.0", "lodash.get": "^4.4.2", "mime-types": "^2.1.27", - "n8n-workflow": "~0.133.2", + "n8n-workflow": "~0.134.0", "oauth-1.0a": "^2.2.6", "p-cancelable": "^2.0.0", "pretty-bytes": "^5.6.0", From 69f38186a56f0c5000bce05c6d210e9bd318e21b Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 27 Jan 2023 15:26:54 +0000 Subject: [PATCH 004/358] =?UTF-8?q?:bookmark:=20Release=C2=A0n8n-core@0.15?= =?UTF-8?q?2.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/core/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/package.json b/packages/core/package.json index 758e63684f46e3..c588b8d2458d52 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "n8n-core", - "version": "0.151.2", + "version": "0.152.0", "description": "Core functionality of n8n", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", From f61f83f256f3731222303b82ef6e70b8d7ae8eab Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 27 Jan 2023 15:27:06 +0000 Subject: [PATCH 005/358] :arrow_up: Set n8n-core@0.152.0 and n8n-workflow@0.134.0 on n8n-node-dev --- packages/node-dev/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/node-dev/package.json b/packages/node-dev/package.json index f41b15b370ed3c..36c3e5eb5705d4 100644 --- a/packages/node-dev/package.json +++ b/packages/node-dev/package.json @@ -59,8 +59,8 @@ "change-case": "^4.1.1", "fast-glob": "^3.2.5", "inquirer": "^7.0.1", - "n8n-core": "~0.151.2", - "n8n-workflow": "~0.133.2", + "n8n-core": "~0.152.0", + "n8n-workflow": "~0.134.0", "oauth-1.0a": "^2.2.6", "replace-in-file": "^6.0.0", "request": "^2.88.2", From 77287dc87b1d5ede2ec49e55e7a8fa11088a063a Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 27 Jan 2023 15:27:06 +0000 Subject: [PATCH 006/358] =?UTF-8?q?:bookmark:=20Release=C2=A0n8n-node-dev@?= =?UTF-8?q?0.91.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/node-dev/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node-dev/package.json b/packages/node-dev/package.json index 36c3e5eb5705d4..49ca5c31e4fb03 100644 --- a/packages/node-dev/package.json +++ b/packages/node-dev/package.json @@ -1,6 +1,6 @@ { "name": "n8n-node-dev", - "version": "0.90.2", + "version": "0.91.0", "description": "CLI to simplify n8n credentials/node development", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", From db496f5aaf84ef3e1fd394bbc65e357dd70f4ac2 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 27 Jan 2023 15:27:20 +0000 Subject: [PATCH 007/358] :arrow_up: Set n8n-core@0.152.0 and n8n-workflow@0.134.0 on n8n-nodes-base --- packages/nodes-base/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index f5cb67fdfce913..4f633034b18312 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -758,7 +758,7 @@ "@types/xml2js": "^0.4.3", "eslint-plugin-n8n-nodes-base": "^1.12.0", "gulp": "^4.0.0", - "n8n-workflow": "~0.133.2" + "n8n-workflow": "~0.134.0" }, "dependencies": { "@kafkajs/confluent-schema-registry": "1.0.6", @@ -797,7 +797,7 @@ "mqtt": "4.2.6", "mssql": "^8.1.2", "mysql2": "~2.3.0", - "n8n-core": "~0.151.2", + "n8n-core": "~0.152.0", "node-html-markdown": "^1.1.3", "node-ssh": "^12.0.0", "nodemailer": "^6.7.1", From da19e50f83c2258f89de5c6c0d113d42494f2224 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 27 Jan 2023 15:27:20 +0000 Subject: [PATCH 008/358] =?UTF-8?q?:bookmark:=20Release=C2=A0n8n-nodes-bas?= =?UTF-8?q?e@0.211.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/nodes-base/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 4f633034b18312..fb050da457d96e 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -1,6 +1,6 @@ { "name": "n8n-nodes-base", - "version": "0.210.1", + "version": "0.211.0", "description": "Base nodes of n8n", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", From c8205dd55e9b31b35a3e32462229076dc75067e1 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 27 Jan 2023 15:28:37 +0000 Subject: [PATCH 009/358] =?UTF-8?q?:bookmark:=20Release=C2=A0n8n-design-sy?= =?UTF-8?q?stem@0.52.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/design-system/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/design-system/package.json b/packages/design-system/package.json index 917b91203b3cc9..1727c4a9f7d46e 100644 --- a/packages/design-system/package.json +++ b/packages/design-system/package.json @@ -1,6 +1,6 @@ { "name": "n8n-design-system", - "version": "0.51.1", + "version": "0.52.0", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", "author": { From 3906346dc95ddd2cded23af99d24faa680c51ef8 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 27 Jan 2023 15:28:59 +0000 Subject: [PATCH 010/358] :arrow_up: Set n8n-design-system@0.52.0 and n8n-workflow@0.134.0 on n8n-editor-ui --- packages/editor-ui/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/editor-ui/package.json b/packages/editor-ui/package.json index 1041edb9b0e54a..bc7c5e20322f3c 100644 --- a/packages/editor-ui/package.json +++ b/packages/editor-ui/package.json @@ -59,8 +59,8 @@ "lodash.set": "^4.3.2", "luxon": "^2.3.0", "monaco-editor": "^0.33.0", - "n8n-design-system": "~0.51.1", - "n8n-workflow": "~0.133.2", + "n8n-design-system": "~0.52.0", + "n8n-workflow": "~0.134.0", "normalize-wheel": "^1.0.1", "pinia": "^2.0.22", "prettier": "^2.8.3", From 966abdc37f83193884aa2147c71b327421b5ab9a Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 27 Jan 2023 15:29:00 +0000 Subject: [PATCH 011/358] =?UTF-8?q?:bookmark:=20Release=C2=A0n8n-editor-ui?= =?UTF-8?q?@0.179.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/editor-ui/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/editor-ui/package.json b/packages/editor-ui/package.json index bc7c5e20322f3c..1f2a8db3e1703a 100644 --- a/packages/editor-ui/package.json +++ b/packages/editor-ui/package.json @@ -1,6 +1,6 @@ { "name": "n8n-editor-ui", - "version": "0.178.1", + "version": "0.179.0", "description": "Workflow Editor UI for n8n", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", From 5f867d1b45643764f3918d108dfc8891c95892f6 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 27 Jan 2023 15:33:48 +0000 Subject: [PATCH 012/358] :arrow_up: Set n8n-core@0.152.0, n8n-editor-ui@0.179.0, n8n-nodes-base@0.211.0 and n8n-workflow@0.134.0 on n8n --- packages/cli/package.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index c769d88d59d0c1..b9c1977cd76eec 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -165,10 +165,10 @@ "lodash.unset": "^4.5.2", "luxon": "^3.1.0", "mysql2": "~2.3.3", - "n8n-core": "~0.151.2", - "n8n-editor-ui": "~0.178.1", - "n8n-nodes-base": "~0.210.1", - "n8n-workflow": "~0.133.2", + "n8n-core": "~0.152.0", + "n8n-editor-ui": "~0.179.0", + "n8n-nodes-base": "~0.211.0", + "n8n-workflow": "~0.134.0", "nodemailer": "^6.7.1", "oauth-1.0a": "^2.2.6", "open": "^7.0.0", From 97126a3a5671c168577b828f449e56f6905a424e Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 27 Jan 2023 15:33:49 +0000 Subject: [PATCH 013/358] =?UTF-8?q?:bookmark:=20Release=C2=A0n8n@0.213.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/cli/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index b9c1977cd76eec..98fe5c97b59861 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "n8n", - "version": "0.212.1", + "version": "0.213.0", "description": "n8n Workflow Automation Tool", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", From 731ce96621eb7cd94995a820a1fd2d2edc8c4a0a Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 27 Jan 2023 10:19:06 -0600 Subject: [PATCH 014/358] :books: Update CHANGELOG.md and main package.json to 0.213.0 --- CHANGELOG.md | 33 +++++++++++++++++++++++++++++++++ package.json | 2 +- pnpm-lock.yaml | 22 +++++++++++----------- 3 files changed, 45 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 135c170d7ecdde..4d124452bb022a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,36 @@ +# [0.213.0](https://github.com/n8n-io/n8n/compare/n8n@0.212.1...n8n@0.213.0) (2023-01-27) + + +### Bug Fixes + +* **core:** Do not crash express app on unhandled rejected promises ([#5252](https://github.com/n8n-io/n8n/issues/5252)) ([7e229a3](https://github.com/n8n-io/n8n/commit/7e229a3d38990022172d4df98afd3dc31dca6e63)) +* **core:** Handle missing binary metadata in download urls ([#5242](https://github.com/n8n-io/n8n/issues/5242)) ([21579a8](https://github.com/n8n-io/n8n/commit/21579a8a2af53f3fb4174afc2013cfad43511a31)) +* **core:** Upsert credentials and workflows in the import: commands ([#5231](https://github.com/n8n-io/n8n/issues/5231)) ([259296c](https://github.com/n8n-io/n8n/commit/259296c5c940bd5dcebec5ad3c9acc99a7923b8f)) +* **core:** Validate numeric IDs in the public API ([#5251](https://github.com/n8n-io/n8n/issues/5251)) ([68e4083](https://github.com/n8n-io/n8n/commit/68e4083bbdb8200966dc9e702bed9ca5cbc1cdf4)) +* **editor:** Do not request workflow data twice when opening a workflow ([#5246](https://github.com/n8n-io/n8n/issues/5246)) ([901e94d](https://github.com/n8n-io/n8n/commit/901e94dc01c4b352301053990f197eda48c30b41)) +* **editor:** Execution list micro optimization ([#5244](https://github.com/n8n-io/n8n/issues/5244)) ([a1710fb](https://github.com/n8n-io/n8n/commit/a1710fbd272a0f3980a8f323bbccf58806e9b900)) +* **editor:** Fix node authentication options ordering and hiding options based on node version ([#5268](https://github.com/n8n-io/n8n/issues/5268)) ([7d74181](https://github.com/n8n-io/n8n/commit/7d7418140eb03da6014ff8ac51668fc427d10c33)) +* **editor:** Fix save modal appearing after duplicating a workflow ([#5247](https://github.com/n8n-io/n8n/issues/5247)) ([c711c53](https://github.com/n8n-io/n8n/commit/c711c53ad6b044b8f90a237d6d8d1ce631359dc3)) +* **editor:** Prevent workflow execution list infinite no network error ([#5230](https://github.com/n8n-io/n8n/issues/5230)) ([0d33329](https://github.com/n8n-io/n8n/commit/0d33329bc87b705760fdc70ccb39374cbd71f6f6)) +* Extension being too eager and making calls when it shouldn't ([#5232](https://github.com/n8n-io/n8n/issues/5232)) ([09bdd96](https://github.com/n8n-io/n8n/commit/09bdd96d290166cced6faf5da4dda83d6d270aa3)) +* **Google Drive Node:** Use the correct mimetype on converted downloads ([#5240](https://github.com/n8n-io/n8n/issues/5240)) ([58d0890](https://github.com/n8n-io/n8n/commit/58d0890dc319c918183fd81999dc87ea9c4732fd)) +* **HelpScout Node:** Fix tag search not working when getting all conversations ([#5239](https://github.com/n8n-io/n8n/issues/5239)) ([6d36782](https://github.com/n8n-io/n8n/commit/6d36782463cda1d211270ecf9bd1a8cccca22cdc)) +* **Notion (Beta) Node:** Fix create database page with multiple relation IDs not working ([#5260](https://github.com/n8n-io/n8n/issues/5260)) ([8ce85e3](https://github.com/n8n-io/n8n/commit/8ce85e37592da061164db18af52852e8ed1d2046)) + + +### Features + +* **core:** Add LDAP support ([#3835](https://github.com/n8n-io/n8n/issues/3835)) ([0c70a40](https://github.com/n8n-io/n8n/commit/0c70a4031702d3c770968d5679d2900def7225a8)) +* **editor:** Adjust Google sign-in button to adhere to the guidelines ([#5248](https://github.com/n8n-io/n8n/issues/5248)) ([73cbddc](https://github.com/n8n-io/n8n/commit/73cbddcb2dc046f1795740a5dd2258577df4d049)) +* **editor:** Simplify NDV by moving authentication details to credentials modal ([#5067](https://github.com/n8n-io/n8n/issues/5067)) ([b321c5e](https://github.com/n8n-io/n8n/commit/b321c5e4ec5aefa605991861db68efc860e0f122)) +* **GitLab Node:** Add file operations (create, delete, edit, get, list) ([#5167](https://github.com/n8n-io/n8n/issues/5167)) ([cedf2e0](https://github.com/n8n-io/n8n/commit/cedf2e012c7309cd225f9810d30315c851bcab3a)) +* HTML node ([#5107](https://github.com/n8n-io/n8n/issues/5107)) ([74e6f5d](https://github.com/n8n-io/n8n/commit/74e6f5d190d010831fc2ef98afdd9dff3dc93b3c)) +* Improve workflow list performance using RecycleScroller and on-demand sharing data loading ([#5181](https://github.com/n8n-io/n8n/issues/5181)) ([874c735](https://github.com/n8n-io/n8n/commit/874c735d0af81c3c81cf82fb9bdf1232608d6400)), closes [#5125](https://github.com/n8n-io/n8n/issues/5125) +* **Jira Software Node:** Use resource locator component ([#5090](https://github.com/n8n-io/n8n/issues/5090)) ([237b1d8](https://github.com/n8n-io/n8n/commit/237b1d8614ffd19215eaee6a0cb9422a16cf0a5c)) +* **Send Email Node:** Overhaul ([832fb87](https://github.com/n8n-io/n8n/commit/832fb87954d480ed46913c8b0f8067c96db28aab)) + + + ## [0.212.1](https://github.com/n8n-io/n8n/compare/n8n@0.212.0...n8n@0.212.1) (2023-01-23) ### Bug Fixes diff --git a/package.json b/package.json index 373c2843503a58..ff0f3e858d5b77 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "n8n", - "version": "0.212.1", + "version": "0.213.0", "private": true, "homepage": "https://n8n.io", "engines": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5fc0335535f567..7bbbbf612ee207 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -195,10 +195,10 @@ importers: lodash.unset: ^4.5.2 luxon: ^3.1.0 mysql2: ~2.3.3 - n8n-core: ~0.151.2 - n8n-editor-ui: ~0.178.1 - n8n-nodes-base: ~0.210.1 - n8n-workflow: ~0.133.2 + n8n-core: ~0.152.0 + n8n-editor-ui: ~0.179.0 + n8n-nodes-base: ~0.211.0 + n8n-workflow: ~0.134.0 nodemailer: ^6.7.1 nodemon: ^2.0.2 oauth-1.0a: ^2.2.6 @@ -391,7 +391,7 @@ importers: form-data: ^4.0.0 lodash.get: ^4.4.2 mime-types: ^2.1.27 - n8n-workflow: ~0.133.2 + n8n-workflow: ~0.134.0 oauth-1.0a: ^2.2.6 p-cancelable: ^2.0.0 pretty-bytes: ^5.6.0 @@ -570,8 +570,8 @@ importers: lodash.set: ^4.3.2 luxon: ^2.3.0 monaco-editor: ^0.33.0 - n8n-design-system: ~0.51.1 - n8n-workflow: ~0.133.2 + n8n-design-system: ~0.52.0 + n8n-workflow: ~0.134.0 normalize-wheel: ^1.0.1 pinia: ^2.0.22 prettier: ^2.8.3 @@ -697,8 +697,8 @@ importers: change-case: ^4.1.1 fast-glob: ^3.2.5 inquirer: ^7.0.1 - n8n-core: ~0.151.2 - n8n-workflow: ~0.133.2 + n8n-core: ~0.152.0 + n8n-workflow: ~0.134.0 oauth-1.0a: ^2.2.6 replace-in-file: ^6.0.0 request: ^2.88.2 @@ -790,8 +790,8 @@ importers: mqtt: 4.2.6 mssql: ^8.1.2 mysql2: ~2.3.0 - n8n-core: ~0.151.2 - n8n-workflow: ~0.133.2 + n8n-core: ~0.152.0 + n8n-workflow: ~0.134.0 node-html-markdown: ^1.1.3 node-ssh: ^12.0.0 nodemailer: ^6.7.1 From 911d656f995a9a7f50db7e97ae25fcc3230ae4a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milorad=20FIlipovi=C4=87?= Date: Mon, 30 Jan 2023 09:32:04 +0100 Subject: [PATCH 015/358] fix(editor): Handling router errors when navigation is canceled by user (#5271) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 🔨 Handling router errors in main sidebar, removing unused code * 🔨 Handling router errors in modals --- .../editor-ui/src/components/MainSidebar.vue | 75 ++++--------------- .../src/components/WorkflowShareModal.ee.vue | 7 +- 2 files changed, 19 insertions(+), 63 deletions(-) diff --git a/packages/editor-ui/src/components/MainSidebar.vue b/packages/editor-ui/src/components/MainSidebar.vue index 229f826fd9c5b0..b76dcdf874d713 100644 --- a/packages/editor-ui/src/components/MainSidebar.vue +++ b/packages/editor-ui/src/components/MainSidebar.vue @@ -104,15 +104,7 @@ import { workflowHelpers } from '@/mixins/workflowHelpers'; import { workflowRun } from '@/mixins/workflowRun'; import mixins from 'vue-typed-mixins'; -import { - MODAL_CANCEL, - MODAL_CLOSE, - MODAL_CONFIRMED, - ABOUT_MODAL_KEY, - VERSIONS_MODAL_KEY, - VIEWS, - PLACEHOLDER_EMPTY_WORKFLOW_ID, -} from '@/constants'; +import { ABOUT_MODAL_KEY, VERSIONS_MODAL_KEY, VIEWS } from '@/constants'; import { userHelpers } from '@/mixins/userHelpers'; import { debounceHelper } from '@/mixins/debounce'; import Vue from 'vue'; @@ -123,6 +115,7 @@ import { useUsersStore } from '@/stores/users'; import { useWorkflowsStore } from '@/stores/workflows'; import { useRootStore } from '@/stores/n8nRootStore'; import { useVersionsStore } from '@/stores/versions'; +import { isNavigationFailure, NavigationFailureType, Route } from 'vue-router'; export default mixins( genericHelpers, @@ -371,25 +364,25 @@ export default mixins( switch (key) { case 'workflows': { if (this.$router.currentRoute.name !== VIEWS.WORKFLOWS) { - this.$router.push({ name: VIEWS.WORKFLOWS }); + this.goToRoute({ name: VIEWS.WORKFLOWS }); } break; } case 'templates': { if (this.$router.currentRoute.name !== VIEWS.TEMPLATES) { - this.$router.push({ name: VIEWS.TEMPLATES }); + this.goToRoute({ name: VIEWS.TEMPLATES }); } break; } case 'credentials': { if (this.$router.currentRoute.name !== VIEWS.CREDENTIALS) { - this.$router.push({ name: VIEWS.CREDENTIALS }); + this.goToRoute({ name: VIEWS.CREDENTIALS }); } break; } case 'executions': { if (this.$router.currentRoute.name !== VIEWS.EXECUTIONS) { - this.$router.push({ name: VIEWS.EXECUTIONS }); + this.goToRoute({ name: VIEWS.EXECUTIONS }); } break; } @@ -398,7 +391,7 @@ export default mixins( if (defaultRoute) { const routeProps = this.$router.resolve({ name: defaultRoute }); if (this.$router.currentRoute.name !== defaultRoute) { - this.$router.push(routeProps.route.path); + this.goToRoute(routeProps.route.path); } } break; @@ -419,55 +412,13 @@ export default mixins( break; } }, - async createNewWorkflow(): Promise { - const result = this.uiStore.stateIsDirty; - if (result) { - const confirmModal = await this.confirmModal( - this.$locale.baseText('generic.unsavedWork.confirmMessage.message'), - this.$locale.baseText('generic.unsavedWork.confirmMessage.headline'), - 'warning', - this.$locale.baseText('generic.unsavedWork.confirmMessage.confirmButtonText'), - this.$locale.baseText('generic.unsavedWork.confirmMessage.cancelButtonText'), - true, - ); - if (confirmModal === MODAL_CONFIRMED) { - const saved = await this.saveCurrentWorkflow({}, false); - if (saved) await this.settingsStore.fetchPromptsData(); - if (this.$router.currentRoute.name === VIEWS.NEW_WORKFLOW) { - this.$root.$emit('newWorkflow'); - } else { - this.$router.push({ name: VIEWS.NEW_WORKFLOW }); - } - this.$showMessage({ - title: this.$locale.baseText('mainSidebar.showMessage.handleSelect2.title'), - type: 'success', - }); - } else if (confirmModal === MODAL_CANCEL) { - this.uiStore.stateIsDirty = false; - if (this.$router.currentRoute.name === VIEWS.NEW_WORKFLOW) { - this.$root.$emit('newWorkflow'); - } else { - this.workflowsStore.setWorkflowId(PLACEHOLDER_EMPTY_WORKFLOW_ID); - this.$router.push({ name: VIEWS.NEW_WORKFLOW }); - } - this.$showMessage({ - title: this.$locale.baseText('mainSidebar.showMessage.handleSelect2.title'), - type: 'success', - }); - } else if (confirmModal === MODAL_CLOSE) { - return; - } - } else { - if (this.$router.currentRoute.name !== VIEWS.NEW_WORKFLOW) { - this.workflowsStore.setWorkflowId(PLACEHOLDER_EMPTY_WORKFLOW_ID); - this.$router.push({ name: VIEWS.NEW_WORKFLOW }); + goToRoute(route: string | { name: string }) { + this.$router.push(route).catch((failure) => { + // Catch navigation failures caused by route guards + if (!isNavigationFailure(failure)) { + console.error(failure); } - this.$showMessage({ - title: this.$locale.baseText('mainSidebar.showMessage.handleSelect3.title'), - type: 'success', - }); - } - this.$titleReset(); + }); }, findFirstAccessibleSettingsRoute() { // Get all settings rotes by filtering them by pageCategory property diff --git a/packages/editor-ui/src/components/WorkflowShareModal.ee.vue b/packages/editor-ui/src/components/WorkflowShareModal.ee.vue index 3f5961b06619ac..15f2c6f15b6057 100644 --- a/packages/editor-ui/src/components/WorkflowShareModal.ee.vue +++ b/packages/editor-ui/src/components/WorkflowShareModal.ee.vue @@ -155,6 +155,7 @@ import { useWorkflowsEEStore } from '@/stores/workflows.ee'; import { ITelemetryTrackProperties } from 'n8n-workflow'; import { useUsageStore } from '@/stores/usage'; import { BaseTextKey } from '@/plugins/i18n'; +import { isNavigationFailure } from 'vue-router'; export default mixins(showMessage).extend({ name: 'workflow-share-modal', @@ -427,7 +428,11 @@ export default mixins(showMessage).extend({ await this.usersStore.fetchUsers(); }, goToUsersSettings() { - this.$router.push({ name: VIEWS.USERS_SETTINGS }); + this.$router.push({ name: VIEWS.USERS_SETTINGS }).catch((failure) => { + if (!isNavigationFailure(failure)) { + console.error(failure); + } + }); this.modalBus.$emit('close'); }, trackTelemetry(data: ITelemetryTrackProperties) { From dbcbe595ccec2075b5aded5cb00b11aafd61db02 Mon Sep 17 00:00:00 2001 From: OlegIvaniv Date: Mon, 30 Jan 2023 11:02:05 +0100 Subject: [PATCH 016/358] ci(core): Fix docker nightly/custom image build (no-changelog) (#5284) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ci(core): Copy patches dir to Docker (no-changelog) * Update patch * Update package-lock * reapply the patch * skip patchedDependencies after the frontend is built --------- Co-authored-by: कारतोफ्फेलस्क्रिप्ट™ --- docker/images/n8n-custom/Dockerfile | 2 ++ patches/element-ui@2.15.12.patch | 3 +-- pnpm-lock.yaml | 6 +++--- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/docker/images/n8n-custom/Dockerfile b/docker/images/n8n-custom/Dockerfile index 6f49901cdced8b..76ee6ded63d80c 100644 --- a/docker/images/n8n-custom/Dockerfile +++ b/docker/images/n8n-custom/Dockerfile @@ -6,6 +6,7 @@ FROM n8nio/base:${NODE_VERSION} as builder COPY turbo.json package.json .npmrc pnpm-lock.yaml pnpm-workspace.yaml tsconfig.json ./ COPY scripts ./scripts COPY packages ./packages +COPY patches ./patches RUN apk add --update libc6-compat jq RUN corepack enable && corepack prepare --activate @@ -15,6 +16,7 @@ USER node RUN pnpm install --frozen-lockfile RUN pnpm build RUN rm -rf node_modules +RUN jq 'del(.pnpm.patchedDependencies)' package.json > package.json.tmp; mv package.json.tmp package.json RUN jq '{name: .name, version: .version}' packages/editor-ui/package.json > editor-ui.tmp; mv editor-ui.tmp packages/editor-ui/package.json RUN jq '{name: .name, version: .version}' packages/design-system/package.json > design-system.tmp; mv design-system.tmp packages/design-system/package.json RUN NODE_ENV=production pnpm install --prod --no-optional diff --git a/patches/element-ui@2.15.12.patch b/patches/element-ui@2.15.12.patch index 1b598f92bc4c09..3f7ef6e50a9156 100644 --- a/patches/element-ui@2.15.12.patch +++ b/patches/element-ui@2.15.12.patch @@ -130,7 +130,7 @@ index 6209b8b7f21c6eea447dd4671b7c4cfc1ef5e9c2..a26772af8dcdcbc0e3a6058bda30c7f9 if (reference.nodeType === 1) { Object(dom_["off"])(reference, 'mouseenter', this.show); diff --git a/packages/popover/src/main.vue b/packages/popover/src/main.vue -index ab5d060182c2e671989f1aba190c85a074d2c754..d87f1b592d7d408d185229b9f9e29070ec2c6fe8 100644 +index ab5d060182c2e671989f1aba190c85a074d2c754..8b464ad39a78ac6efead5f687d977500642e45dc 100644 --- a/packages/popover/src/main.vue +++ b/packages/popover/src/main.vue @@ -98,16 +98,9 @@ export default { @@ -217,7 +217,6 @@ index ab5d060182c2e671989f1aba190c85a074d2c754..d87f1b592d7d408d185229b9f9e29070 } }; -\ No newline at end of file diff --git a/packages/tooltip/src/main.js b/packages/tooltip/src/main.js index dc930ec58d42328a4d62cddb22fd513db31793cf..d9a6afc80d27ea89b838a47f395e249131c1370c 100644 --- a/packages/tooltip/src/main.js diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7bbbbf612ee207..81c68eca183bfc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -15,7 +15,7 @@ overrides: patchedDependencies: element-ui@2.15.12: - hash: aaa3sc7bmwb4jwg35ga5npx4he + hash: prckukfdop5sl2her6de25cod4 path: patches/element-ui@2.15.12.patch importers: @@ -474,7 +474,7 @@ importers: webpack: ^4.46.0 xss: ^1.0.14 dependencies: - element-ui: 2.15.12_aaa3sc7bmwb4jwg35ga5npx4he_vue@2.7.14 + element-ui: 2.15.12_chf3rdrkdm2au7cxasektiaxfy_vue@2.7.14 markdown-it: 13.0.1 markdown-it-emoji: 2.0.2 markdown-it-link-attributes: 4.0.1 @@ -10915,7 +10915,7 @@ packages: resolution: {integrity: sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==} dev: true - /element-ui/2.15.12_aaa3sc7bmwb4jwg35ga5npx4he_vue@2.7.14: + /element-ui/2.15.12_chf3rdrkdm2au7cxasektiaxfy_vue@2.7.14: resolution: {integrity: sha512-Y5FMT2BPOindU2GkDEQ5ZKUVxDawKONRNMh2eL3uBx1FOtvUJ+L6IxXLVsNxq4WnaX/UnVNgWXebl7DobygZMg==} peerDependencies: vue: ^2.5.17 From 5b9c650e55c4cc089b14bf8f5322157a4f70f1f0 Mon Sep 17 00:00:00 2001 From: agobrech <45268029+agobrech@users.noreply.github.com> Date: Mon, 30 Jan 2023 12:20:33 +0100 Subject: [PATCH 017/358] test: Add unit testing to nodes (no-changelog) (#4890) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 🧪 Add base for building unit testing within nodes * Improve helper functions * 🧪 If node test * 🧪 Airtable node test * 🧪 If node test improvements * 🧪 Airtable node test improvements * ♻️ cleanup node unit tests * ♻️ refactor getting node result data to use helper method * :zap: removed unused variables * ♻️ Helper to read json files --------- Co-authored-by: Marcus Co-authored-by: Michael Kret --- .../test/nodes/Airtable/Airtable.node.test.ts | 64 ++++++ .../test/nodes/Airtable/workflow.json | 63 ++++++ .../nodes-base/test/nodes/ExecuteWorkflow.ts | 24 +++ packages/nodes-base/test/nodes/Helpers.ts | 183 ++++++++++++++++ .../nodes-base/test/nodes/If/If.node.test.ts | 58 +++++ .../nodes-base/test/nodes/If/workflow.json | 115 ++++++++++ .../nodes-base/test/nodes/Set/SetNode.test.ts | 200 ++++++++++++++++++ .../test/nodes/Start/StartNode.test.ts | 48 +++++ packages/nodes-base/test/nodes/types.ts | 17 ++ 9 files changed, 772 insertions(+) create mode 100644 packages/nodes-base/test/nodes/Airtable/Airtable.node.test.ts create mode 100644 packages/nodes-base/test/nodes/Airtable/workflow.json create mode 100644 packages/nodes-base/test/nodes/ExecuteWorkflow.ts create mode 100644 packages/nodes-base/test/nodes/Helpers.ts create mode 100644 packages/nodes-base/test/nodes/If/If.node.test.ts create mode 100644 packages/nodes-base/test/nodes/If/workflow.json create mode 100644 packages/nodes-base/test/nodes/Set/SetNode.test.ts create mode 100644 packages/nodes-base/test/nodes/Start/StartNode.test.ts create mode 100644 packages/nodes-base/test/nodes/types.ts diff --git a/packages/nodes-base/test/nodes/Airtable/Airtable.node.test.ts b/packages/nodes-base/test/nodes/Airtable/Airtable.node.test.ts new file mode 100644 index 00000000000000..cb03648804a243 --- /dev/null +++ b/packages/nodes-base/test/nodes/Airtable/Airtable.node.test.ts @@ -0,0 +1,64 @@ +import { INodeType } from 'n8n-workflow'; +import { executeWorkflow } from '../ExecuteWorkflow'; +import * as Helpers from '../Helpers'; +import { WorkflowTestData } from '../types'; +import nock from 'nock'; + +import { ManualTrigger } from '../../../nodes/ManualTrigger/ManualTrigger.node'; +import { Airtable } from '../../../nodes/Airtable/Airtable.node'; + +const records = [ + { + id: 'rec2BWBoyS5QsS7pT', + createdTime: '2022-08-25T08:22:34.000Z', + fields: { + name: 'Tim', + email: 'tim@email.com', + }, + }, +]; + +describe('Execute Airtable Node', () => { + beforeEach(() => { + nock.disableNetConnect(); + nock('https://api.airtable.com/v0') + .get('/appIaXXdDqS5ORr4V/tbljyBEdYzCPF0NDh?pageSize=100') + .reply(200, { records }); + }); + + afterEach(() => { + nock.restore(); + }); + + const tests: Array = [ + { + description: 'List Airtable Records', + input: { + workflowData: Helpers.readJsonFileSync('test/nodes/Airtable/workflow.json'), + }, + output: { + nodeData: { + Airtable: [[...records]], + }, + }, + }, + ]; + + const nodes: INodeType[] = [new ManualTrigger(), new Airtable()]; + const nodeTypes = Helpers.setup(nodes); + + for (const testData of tests) { + test(testData.description, async () => { + // execute workflow + const { result } = await executeWorkflow(testData, nodeTypes); + + // check if result node data matches expected test data + const resultNodeData = Helpers.getResultNodeData(result, testData); + resultNodeData.forEach(({ nodeName, resultData }) => + expect(resultData).toEqual(testData.output.nodeData[nodeName]), + ); + + expect(result.finished).toEqual(true); + }); + } +}); diff --git a/packages/nodes-base/test/nodes/Airtable/workflow.json b/packages/nodes-base/test/nodes/Airtable/workflow.json new file mode 100644 index 00000000000000..43b959b92e249f --- /dev/null +++ b/packages/nodes-base/test/nodes/Airtable/workflow.json @@ -0,0 +1,63 @@ +{ + "meta": { + "instanceId": "104a4d08d8897b8bdeb38aaca515021075e0bd8544c983c2bb8c86e6a8e6081c" + }, + "nodes": [ + { + "parameters": {}, + "id": "f857c37f-36c1-4c9c-9b5f-f6ef49db67e3", + "name": "On clicking 'execute'", + "type": "n8n-nodes-base.manualTrigger", + "typeVersion": 1, + "position": [ + 820, + 380 + ] + }, + { + "parameters": { + "operation": "list", + "application": { + "__rl": true, + "value": "https://airtable.com/appIaXXdDqS5ORr4V/tbljyBEdYzCPF0NDh/viwInsMdsxffad0aU", + "mode": "url", + "__regex": "https://airtable.com/([a-zA-Z0-9]{2,})" + }, + "table": { + "__rl": true, + "value": "https://airtable.com/appIaXXdDqS5ORr4V/tbljyBEdYzCPF0NDh/viwInsMdsxffad0aU", + "mode": "url", + "__regex": "https://airtable.com/[a-zA-Z0-9]{2,}/([a-zA-Z0-9]{2,})" + }, + "additionalOptions": {} + }, + "id": "5654d3b3-fe83-4988-889b-94f107d41807", + "name": "Airtable", + "type": "n8n-nodes-base.airtable", + "typeVersion": 1, + "position": [ + 1020, + 380 + ], + "credentials": { + "airtableApi": { + "id": "20", + "name": "Airtable account" + } + } + } + ], + "connections": { + "On clicking 'execute'": { + "main": [ + [ + { + "node": "Airtable", + "type": "main", + "index": 0 + } + ] + ] + } + } +} diff --git a/packages/nodes-base/test/nodes/ExecuteWorkflow.ts b/packages/nodes-base/test/nodes/ExecuteWorkflow.ts new file mode 100644 index 00000000000000..a9b62a35ab7865 --- /dev/null +++ b/packages/nodes-base/test/nodes/ExecuteWorkflow.ts @@ -0,0 +1,24 @@ +import { WorkflowExecute } from 'n8n-core'; +import { createDeferredPromise, INodeTypes, IRun, Workflow } from 'n8n-workflow'; +import * as Helpers from './Helpers'; + +export async function executeWorkflow(testData, nodeTypes: INodeTypes) { + const executionMode = 'manual'; + const workflowInstance = new Workflow({ + id: 'test', + nodes: testData.input.workflowData.nodes, + connections: testData.input.workflowData.connections, + active: false, + nodeTypes, + }); + + const waitPromise = await createDeferredPromise(); + const nodeExecutionOrder: string[] = []; + const additionalData = Helpers.WorkflowExecuteAdditionalData(waitPromise, nodeExecutionOrder); + + const workflowExecute = new WorkflowExecute(additionalData, executionMode); + + const executionData = await workflowExecute.run(workflowInstance); + const result = await waitPromise.promise(); + return { executionData, result, nodeExecutionOrder }; +} diff --git a/packages/nodes-base/test/nodes/Helpers.ts b/packages/nodes-base/test/nodes/Helpers.ts new file mode 100644 index 00000000000000..5c5ab95fe05ea0 --- /dev/null +++ b/packages/nodes-base/test/nodes/Helpers.ts @@ -0,0 +1,183 @@ +import { readFileSync } from 'fs'; +import { Credentials } from 'n8n-core'; +import { + ICredentialDataDecryptedObject, + ICredentialsHelper, + IDeferredPromise, + IExecuteWorkflowInfo, + IHttpRequestHelper, + IHttpRequestOptions, + ILogger, + INode, + INodeCredentialsDetails, + INodeType, + INodeTypeData, + INodeTypes, + IRun, + ITaskData, + IVersionedNodeType, + IWorkflowBase, + IWorkflowExecuteAdditionalData, + LoggerProxy, + NodeHelpers, + WorkflowHooks, +} from 'n8n-workflow'; +import { WorkflowTestData } from './types'; + +export class CredentialsHelper extends ICredentialsHelper { + async authenticate( + credentials: ICredentialDataDecryptedObject, + typeName: string, + requestParams: IHttpRequestOptions, + ): Promise { + return requestParams; + } + + async preAuthentication( + helpers: IHttpRequestHelper, + credentials: ICredentialDataDecryptedObject, + typeName: string, + node: INode, + credentialsExpired: boolean, + ): Promise { + return undefined; + } + + getParentTypes(name: string): string[] { + return []; + } + + async getDecrypted( + nodeCredentials: INodeCredentialsDetails, + type: string, + ): Promise { + return {}; + } + + async getCredentials( + nodeCredentials: INodeCredentialsDetails, + type: string, + ): Promise { + return new Credentials({ id: null, name: '' }, '', [], ''); + } + + async updateCredentials( + nodeCredentials: INodeCredentialsDetails, + type: string, + data: ICredentialDataDecryptedObject, + ): Promise {} +} + +export function WorkflowExecuteAdditionalData( + waitPromise: IDeferredPromise, + nodeExecutionOrder: string[], +): IWorkflowExecuteAdditionalData { + const hookFunctions = { + nodeExecuteAfter: [ + async (nodeName: string, data: ITaskData): Promise => { + nodeExecutionOrder.push(nodeName); + }, + ], + workflowExecuteAfter: [ + async (fullRunData: IRun): Promise => { + waitPromise.resolve(fullRunData); + }, + ], + }; + + const workflowData: IWorkflowBase = { + name: '', + createdAt: new Date(), + updatedAt: new Date(), + active: true, + nodes: [], + connections: {}, + }; + + return { + credentialsHelper: new CredentialsHelper(''), + hooks: new WorkflowHooks(hookFunctions, 'trigger', '1', workflowData), + executeWorkflow: async (workflowInfo: IExecuteWorkflowInfo): Promise => {}, + sendMessageToUI: (message: string) => {}, + restApiUrl: '', + encryptionKey: 'test', + timezone: 'America/New_York', + webhookBaseUrl: 'webhook', + webhookWaitingBaseUrl: 'webhook-waiting', + webhookTestBaseUrl: 'webhook-test', + userId: '123', + }; +} + +class NodeTypesClass implements INodeTypes { + nodeTypes: INodeTypeData = {}; + getByName(nodeType: string): INodeType | IVersionedNodeType { + return this.nodeTypes[nodeType].type; + } + + addNode(nodeTypeName: string, nodeType: INodeType | IVersionedNodeType) { + const loadedNode = { + [nodeTypeName]: { + sourcePath: '', + type: nodeType, + }, + }; + this.nodeTypes = { + ...this.nodeTypes, + ...loadedNode, + }; + //Object.assign(this.nodeTypes, loadedNode); + } + + getByNameAndVersion(nodeType: string, version?: number): INodeType { + return NodeHelpers.getVersionedNodeType(this.nodeTypes[nodeType].type, version); + } +} + +let nodeTypesInstance: NodeTypesClass | undefined; + +export function NodeTypes(): NodeTypesClass { + if (nodeTypesInstance === undefined) { + nodeTypesInstance = new NodeTypesClass(); + } + return nodeTypesInstance; +} + +export function setup(nodes: INodeType[]) { + const nodeTypes = NodeTypes(); + for (const node of nodes) { + nodeTypes.addNode('n8n-nodes-base.' + node.description.name, node); + } + const fakeLogger = { + log: () => {}, + debug: () => {}, + verbose: () => {}, + info: () => {}, + warn: () => {}, + error: () => {}, + } as ILogger; + LoggerProxy.init(fakeLogger); + return nodeTypes; +} + +export function getResultNodeData(result: IRun, testData: WorkflowTestData) { + return Object.keys(testData.output.nodeData).map((nodeName) => { + if (result.data.resultData.runData[nodeName] === undefined) { + throw new Error(`Data for node "${nodeName}" is missing!`); + } + const resultData = result.data.resultData.runData[nodeName].map((nodeData) => { + if (nodeData.data === undefined) { + return null; + } + return nodeData.data.main[0]!.map((entry) => entry.json); + }); + return { + nodeName, + resultData, + }; + }); +} + +export function readJsonFileSync(path: string) { + return JSON.parse(readFileSync(path, 'utf-8')); +} diff --git a/packages/nodes-base/test/nodes/If/If.node.test.ts b/packages/nodes-base/test/nodes/If/If.node.test.ts new file mode 100644 index 00000000000000..5c141755476c88 --- /dev/null +++ b/packages/nodes-base/test/nodes/If/If.node.test.ts @@ -0,0 +1,58 @@ +import { INodeType } from 'n8n-workflow'; +import * as Helpers from '../Helpers'; +import { WorkflowTestData } from '../types'; + +import { ManualTrigger } from '../../../nodes/ManualTrigger/ManualTrigger.node'; +import { Set } from '../../../nodes/Set/Set.node'; +import { If } from '../../../nodes/If/If.node'; +import { NoOp } from '../../../nodes/NoOp/NoOp.node'; +import { Code } from '../../../nodes/Code/Code.node'; + +import { executeWorkflow } from '../ExecuteWorkflow'; + +describe('Execute If Node', () => { + const tests: Array = [ + { + description: 'should execute IF node true/false boolean', + input: { + workflowData: Helpers.readJsonFileSync('test/nodes/If/workflow.json'), + }, + output: { + nodeData: { + 'On True': [ + [ + { + value: true, + }, + ], + ], + 'On False': [ + [ + { + value: false, + }, + ], + ], + }, + }, + }, + ]; + + const nodes: INodeType[] = [new ManualTrigger(), new Code(), new Set(), new If(), new NoOp()]; + const nodeTypes = Helpers.setup(nodes); + + for (const testData of tests) { + test(testData.description, async () => { + // execute workflow + const { result } = await executeWorkflow(testData, nodeTypes); + + // check if result node data matches expected test data + const resultNodeData = Helpers.getResultNodeData(result, testData); + resultNodeData.forEach(({ nodeName, resultData }) => + expect(resultData).toEqual(testData.output.nodeData[nodeName]), + ); + + expect(result.finished).toEqual(true); + }); + } +}); diff --git a/packages/nodes-base/test/nodes/If/workflow.json b/packages/nodes-base/test/nodes/If/workflow.json new file mode 100644 index 00000000000000..9a33888f5a9119 --- /dev/null +++ b/packages/nodes-base/test/nodes/If/workflow.json @@ -0,0 +1,115 @@ +{ + "meta": { + "instanceId": "104a4d08d8897b8bdeb38aaca515021075e0bd8544c983c2bb8c86e6a8e6081c" + }, + "nodes": [ + { + "parameters": {}, + "id": "47003824-c11f-4ae3-80a5-0e1a6d840b21", + "name": "On clicking 'execute'", + "type": "n8n-nodes-base.manualTrigger", + "typeVersion": 1, + "position": [ + 720, + 460 + ] + }, + { + "parameters": { + "conditions": { + "boolean": [ + { + "value1": "={{ $json[\"value\"] }}", + "value2": true + } + ] + } + }, + "id": "5420fe7d-a216-44e0-b91f-188ba5b6a340", + "name": "IF", + "type": "n8n-nodes-base.if", + "typeVersion": 1, + "position": [ + 1160, + 460 + ] + }, + { + "parameters": {}, + "id": "52d58f32-7faf-4874-afff-e6842bd02430", + "name": "On False", + "type": "n8n-nodes-base.noOp", + "typeVersion": 1, + "position": [ + 1400, + 580 + ] + }, + { + "parameters": {}, + "id": "9be683ac-cd3f-4ba1-8fa4-052102c3d891", + "name": "On True", + "type": "n8n-nodes-base.noOp", + "typeVersion": 1, + "position": [ + 1400, + 340 + ] + }, + { + "parameters": { + "jsCode": "return [\n { value: true },\n { value: false }\n];" + }, + "id": "5b3207e7-37e3-43c8-a4da-1ffebb0de134", + "name": "Code", + "type": "n8n-nodes-base.code", + "typeVersion": 1, + "position": [ + 940, + 460 + ] + } + ], + "connections": { + "On clicking 'execute'": { + "main": [ + [ + { + "node": "Code", + "type": "main", + "index": 0 + } + ] + ] + }, + "IF": { + "main": [ + [ + { + "node": "On True", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "On False", + "type": "main", + "index": 0 + } + ] + ] + }, + "Code": { + "main": [ + [ + { + "node": "IF", + "type": "main", + "index": 0 + } + ] + ] + } + } +} diff --git a/packages/nodes-base/test/nodes/Set/SetNode.test.ts b/packages/nodes-base/test/nodes/Set/SetNode.test.ts new file mode 100644 index 00000000000000..75407df9bfed9f --- /dev/null +++ b/packages/nodes-base/test/nodes/Set/SetNode.test.ts @@ -0,0 +1,200 @@ +import { INodeType } from 'n8n-workflow'; +import * as Helpers from '../Helpers'; +import { Start } from '../../../nodes/Start/Start.node'; +import { Set } from '../../../nodes/Set/Set.node'; +import { executeWorkflow } from '../ExecuteWorkflow'; +import { WorkflowTestData } from '../types'; + +describe('Execute Set Node', () => { + const tests: Array = [ + { + description: 'should set value', + input: { + workflowData: { + nodes: [ + { + id: 'uuid-1', + parameters: {}, + name: 'Start', + type: 'n8n-nodes-base.start', + typeVersion: 1, + position: [100, 300], + }, + { + id: 'uuid-2', + parameters: { + values: { + number: [ + { + name: 'value1', + value: 1, + }, + ], + }, + }, + name: 'Set', + type: 'n8n-nodes-base.set', + typeVersion: 1, + position: [280, 300], + }, + ], + connections: { + Start: { + main: [ + [ + { + node: 'Set', + type: 'main', + index: 0, + }, + ], + ], + }, + }, + }, + }, + output: { + nodeExecutionOrder: ['Start', 'Set'], + nodeData: { + Set: [ + [ + { + value1: 1, + }, + ], + ], + }, + }, + }, + + { + description: 'should set multiple values', + input: { + workflowData: { + nodes: [ + { + id: 'uuid-1', + parameters: {}, + name: 'Start', + type: 'n8n-nodes-base.start', + typeVersion: 1, + position: [100, 300], + }, + { + id: 'uuid-2', + parameters: { + values: { + number: [ + { + name: 'value1', + value: 1, + }, + ], + boolean: [ + { + name: 'value2', + value: true, + }, + ], + }, + }, + name: 'Set', + type: 'n8n-nodes-base.set', + typeVersion: 1, + position: [280, 300], + }, + { + id: 'uuid-3', + parameters: { + values: { + number: [ + { + name: 'value1', + value: 2, + }, + ], + boolean: [ + { + name: 'value2', + value: false, + }, + ], + }, + }, + name: 'Set1', + type: 'n8n-nodes-base.set', + typeVersion: 1, + position: [280, 300], + }, + ], + connections: { + Start: { + main: [ + [ + { + node: 'Set', + type: 'main', + index: 0, + }, + ], + ], + }, + Set: { + main: [ + [ + { + node: 'Set1', + type: 'main', + index: 0, + }, + ], + ], + }, + }, + }, + }, + output: { + nodeExecutionOrder: ['Start', 'Set'], + nodeData: { + Set: [ + [ + { + value1: 1, + value2: true, + }, + ], + ], + Set1: [ + [ + { + value1: 2, + value2: false, + }, + ], + ], + }, + }, + }, + ]; + + const nodes: INodeType[] = [new Start(), new Set()]; + const nodeTypes = Helpers.setup(nodes); + + for (const testData of tests) { + test(testData.description, async () => { + // execute workflow + const { result } = await executeWorkflow(testData, nodeTypes); + + // check if result node data matches expected test data + const resultNodeData = Helpers.getResultNodeData(result, testData); + resultNodeData.forEach(({ nodeName, resultData }) => + expect(resultData).toEqual(testData.output.nodeData[nodeName]), + ); + + // Check if other data has correct value + expect(result.finished).toEqual(true); + expect(result.data.executionData!.contextData).toEqual({}); + expect(result.data.executionData!.nodeExecutionStack).toEqual([]); + }); + } +}); diff --git a/packages/nodes-base/test/nodes/Start/StartNode.test.ts b/packages/nodes-base/test/nodes/Start/StartNode.test.ts new file mode 100644 index 00000000000000..5c9ccf8c6cf7bd --- /dev/null +++ b/packages/nodes-base/test/nodes/Start/StartNode.test.ts @@ -0,0 +1,48 @@ +import { INodeType } from 'n8n-workflow'; +import * as Helpers from '../Helpers'; +import { Start } from '../../../nodes/Start/Start.node'; +import { WorkflowTestData } from '../types'; +import { executeWorkflow } from '../ExecuteWorkflow'; + +describe('Execute Start Node', () => { + const tests: Array = [ + { + description: 'should run start node', + input: { + workflowData: { + nodes: [ + { + id: 'uuid-1', + parameters: {}, + name: 'Start', + type: 'n8n-nodes-base.start', + typeVersion: 1, + position: [100, 300], + }, + ], + connections: {}, + }, + }, + output: { + nodeExecutionOrder: ['Start'], + nodeData: {}, + }, + }, + ]; + + const nodes: INodeType[] = [new Start()]; + const nodeTypes = Helpers.setup(nodes); + + for (const testData of tests) { + test(testData.description, async () => { + // execute workflow + const { result, nodeExecutionOrder } = await executeWorkflow(testData, nodeTypes); + // Check if the nodes did execute in the correct order + expect(nodeExecutionOrder).toEqual(testData.output.nodeExecutionOrder); + // Check if other data has correct value + expect(result.finished).toEqual(true); + expect(result.data.executionData!.contextData).toEqual({}); + expect(result.data.executionData!.nodeExecutionStack).toEqual([]); + }); + } +}); diff --git a/packages/nodes-base/test/nodes/types.ts b/packages/nodes-base/test/nodes/types.ts new file mode 100644 index 00000000000000..37ffd12aea03bc --- /dev/null +++ b/packages/nodes-base/test/nodes/types.ts @@ -0,0 +1,17 @@ +import { INode, IConnections } from 'n8n-workflow'; + +export interface WorkflowTestData { + description: string; + input: { + workflowData: { + nodes: INode[]; + connections: IConnections; + }; + }; + output: { + nodeExecutionOrder?: string[]; + nodeData: { + [key: string]: any[][]; + }; + }; +} From 6092f6c41ee28f3482675b940b1fdc600ae29971 Mon Sep 17 00:00:00 2001 From: Mutasem Aldmour <4711238+mutdmour@users.noreply.github.com> Date: Mon, 30 Jan 2023 14:42:08 +0300 Subject: [PATCH 018/358] feat(editor): Add mapping support for data paths (#5191) * feat: add data path flag * chore: update types * feat: use path for data * feat: add support for multiple values * fix: handle if not prev node * fix: update node * fix: handle multi part path * feat: add support for multiple vals for field * feat: add support for table transforms * feat: use dot notation * feat: fix bug where brackets removed * fix: handle dots, fix unit tests * test: update snapshot * test: fix tests * test: add test for edge case --- .../src/components/ParameterInputFull.vue | 23 +- .../editor-ui/src/components/RunDataJson.vue | 13 +- .../src/components/RunDataSchema.test.ts | 19 + .../src/components/RunDataSchemaItem.vue | 8 +- .../editor-ui/src/components/RunDataTable.vue | 30 +- .../__snapshots__/RunDataSchema.test.ts.snap | 520 +++++++++++++++++- .../src/utils/__tests__/typesUtils.test.ts | 77 ++- packages/editor-ui/src/utils/mappingUtils.ts | 31 ++ packages/editor-ui/src/utils/typesUtils.ts | 3 +- .../CompareDatasets/CompareDatasets.node.ts | 4 + .../nodes/ItemLists/ItemLists.node.ts | 9 + .../nodes/ItemLists/summarize.operation.ts | 5 + .../nodes-base/nodes/Merge/v2/MergeV2.node.ts | 2 + packages/workflow/src/Interfaces.ts | 1 + 14 files changed, 679 insertions(+), 66 deletions(-) create mode 100644 packages/editor-ui/src/utils/mappingUtils.ts diff --git a/packages/editor-ui/src/components/ParameterInputFull.vue b/packages/editor-ui/src/components/ParameterInputFull.vue index 3c8e3821ae5c0a..f286a7e82f7ed6 100644 --- a/packages/editor-ui/src/components/ParameterInputFull.vue +++ b/packages/editor-ui/src/components/ParameterInputFull.vue @@ -229,12 +229,31 @@ export default mixins(showMessage).extend({ } }, onDrop(data: string) { - this.forceShowExpression = true; + const useDataPath = !!this.parameter.requiresDataPath && data.startsWith('{{ $json'); + if (!useDataPath) { + this.forceShowExpression = true; + } setTimeout(() => { if (this.node) { const prevValue = this.isResourceLocator ? this.value.value : this.value; let updatedValue: string; - if (typeof prevValue === 'string' && prevValue.startsWith('=') && prevValue.length > 1) { + if (useDataPath) { + const newValue = data + .replace('{{ $json', '') + .replace(new RegExp('^\\.'), '') + .replace(new RegExp('}}$'), '') + .trim(); + + if (prevValue && this.parameter.requiresDataPath === 'multiple') { + updatedValue = `${prevValue}, ${newValue}`; + } else { + updatedValue = newValue; + } + } else if ( + typeof prevValue === 'string' && + prevValue.startsWith('=') && + prevValue.length > 1 + ) { updatedValue = `${prevValue} ${data}`; } else { updatedValue = `=${data}`; diff --git a/packages/editor-ui/src/components/RunDataJson.vue b/packages/editor-ui/src/components/RunDataJson.vue index e7981a72ca1869..15a6c0289ca0a3 100644 --- a/packages/editor-ui/src/components/RunDataJson.vue +++ b/packages/editor-ui/src/components/RunDataJson.vue @@ -79,6 +79,7 @@ import { externalHooks } from '@/mixins/externalHooks'; import { mapStores } from 'pinia'; import { useNDVStore } from '@/stores/ndv'; import MappingPill from './MappingPill.vue'; +import { getMappedExpression } from '@/utils/mappingUtils'; const runDataJsonActions = () => import('@/components/RunDataJsonActions.vue'); @@ -169,11 +170,13 @@ export default mixins(externalHooks).extend({ return shorten(el.dataset.name || '', 16, 2); }, getJsonParameterPath(path: string): string { - const convertedPath = convertPath(path); - return `{{ ${convertedPath.replace( - /^(\["?\d"?])/, - this.distanceFromActive === 1 ? '$json' : `$node["${this.node!.name}"].json`, - )} }}`; + const subPath = path.replace(/^(\["?\d"?])/, ''); // remove item position + + return getMappedExpression({ + nodeName: this.node.name, + distanceFromActive: this.distanceFromActive, + path: subPath, + }); }, onDragStart(el: HTMLElement) { if (el && el.dataset.path) { diff --git a/packages/editor-ui/src/components/RunDataSchema.test.ts b/packages/editor-ui/src/components/RunDataSchema.test.ts index f9c5fe295db448..ff71b2aa003463 100644 --- a/packages/editor-ui/src/components/RunDataSchema.test.ts +++ b/packages/editor-ui/src/components/RunDataSchema.test.ts @@ -60,4 +60,23 @@ describe('RunDataJsonSchema.vue', () => { }); expect(container).toMatchSnapshot(); }); + + it('renders schema with spaces and dots', () => { + renderOptions.props.data = [ + { + 'hello world': [ + { + test: { + 'more to think about': 1, + }, + 'test.how': 'ignore', + }, + ], + }, + ]; + const { container } = render(RunDataJsonSchema, renderOptions, (vue) => { + vue.use(PiniaVuePlugin); + }); + expect(container).toMatchSnapshot(); + }); }); diff --git a/packages/editor-ui/src/components/RunDataSchemaItem.vue b/packages/editor-ui/src/components/RunDataSchemaItem.vue index fde51fed48f058..e6457fe93ab50e 100644 --- a/packages/editor-ui/src/components/RunDataSchemaItem.vue +++ b/packages/editor-ui/src/components/RunDataSchemaItem.vue @@ -2,6 +2,7 @@ import { computed } from 'vue'; import { INodeUi, Schema } from '@/Interface'; import { checkExhaustive, shorten } from '@/utils'; +import { getMappedExpression } from '@/utils/mappingUtils'; type Props = { schema: Schema; @@ -35,7 +36,12 @@ const text = computed(() => ); const getJsonParameterPath = (path: string): string => - `{{ ${props.distanceFromActive === 1 ? '$json' : `$node["${props.node!.name}"].json`}${path} }}`; + getMappedExpression({ + nodeName: props.node!.name, + distanceFromActive: props.distanceFromActive, + path, + }); + const transitionDelay = (i: number) => `${i * 0.033}s`; const getIconBySchemaType = (type: Schema['type']): string => { diff --git a/packages/editor-ui/src/components/RunDataTable.vue b/packages/editor-ui/src/components/RunDataTable.vue index fc103f6e27bff7..3f2c1e5b7c5b07 100644 --- a/packages/editor-ui/src/components/RunDataTable.vue +++ b/packages/editor-ui/src/components/RunDataTable.vue @@ -170,6 +170,7 @@ import { mapStores } from 'pinia'; import { useWorkflowsStore } from '@/stores/workflows'; import { useNDVStore } from '@/stores/ndv'; import MappingPill from './MappingPill.vue'; +import { getMappedExpression } from '@/utils/mappingUtils'; const MAX_COLUMNS_LIMIT = 40; @@ -315,11 +316,11 @@ export default mixins(externalHooks).extend({ return ''; } - if (this.distanceFromActive === 1) { - return `{{ $json["${column}"] }}`; - } - - return `{{ $node["${this.node.name}"].json["${column}"] }}`; + return getMappedExpression({ + nodeName: this.node.name, + distanceFromActive: this.distanceFromActive, + path: [column], + }); }, getPathNameFromTarget(el: HTMLElement) { if (!el) { @@ -343,21 +344,12 @@ export default mixins(externalHooks).extend({ if (!this.node) { return ''; } - - const expr = path.reduce((accu: string, key: string | number) => { - if (typeof key === 'number') { - return `${accu}[${key}]`; - } - - return `${accu}["${key}"]`; - }, ''); const column = this.tableData.columns[colIndex]; - - if (this.distanceFromActive === 1) { - return `{{ $json["${column}"]${expr} }}`; - } - - return `{{ $node["${this.node.name}"].json["${column}"]${expr} }}`; + return getMappedExpression({ + nodeName: this.node.name, + distanceFromActive: this.distanceFromActive, + path: [column, ...path], + }); }, isEmpty(value: unknown): boolean { return ( diff --git a/packages/editor-ui/src/components/__snapshots__/RunDataSchema.test.ts.snap b/packages/editor-ui/src/components/__snapshots__/RunDataSchema.test.ts.snap index ca3faf29b15a76..aac0e13b91e200 100644 --- a/packages/editor-ui/src/components/__snapshots__/RunDataSchema.test.ts.snap +++ b/packages/editor-ui/src/components/__snapshots__/RunDataSchema.test.ts.snap @@ -33,9 +33,9 @@ exports[`RunDataJsonSchema.vue > renders schema for data 1`] = ` class="label" data-depth="1" data-name="name" - data-path="[\\"name\\"]" + data-path=".name" data-target="mappable" - data-value="{{ $json[\\"name\\"] }}" + data-value="{{ $json.name }}" > renders schema for data 1`] = ` class="label" data-depth="1" data-name="age" - data-path="[\\"age\\"]" + data-path=".age" data-target="mappable" - data-value="{{ $json[\\"age\\"] }}" + data-value="{{ $json.age }}" > renders schema for data 1`] = ` class="label" data-depth="1" data-name="hobbies" - data-path="[\\"hobbies\\"]" + data-path=".hobbies" data-target="mappable" - data-value="{{ $json[\\"hobbies\\"] }}" + data-value="{{ $json.hobbies }}" > renders schema for data 1`] = ` class="label" data-depth="2" data-name="string[0]" - data-path="[\\"hobbies\\"][0]" + data-path=".hobbies[0]" data-target="mappable" - data-value="{{ $json[\\"hobbies\\"][0] }}" + data-value="{{ $json.hobbies[0] }}" > renders schema for data 1`] = ` class="label" data-depth="2" data-name="string[1]" - data-path="[\\"hobbies\\"][1]" + data-path=".hobbies[1]" data-target="mappable" - data-value="{{ $json[\\"hobbies\\"][1] }}" + data-value="{{ $json.hobbies[1] }}" > renders schema for empty data 1`] = ` `; + +exports[`RunDataJsonSchema.vue > renders schema with spaces 1`] = ` +
+
+
+
+
+ + + + +
+
+
+ + + + + hello world + + +
+ + + +
+
+
+ + + + hello world + + + [0] + + +
+ + + +
+
+
+ + + + + test + + +
+ + + +
+
+
+ + + + + more to think about + + +
+ + 1 + + + + +
+
+
+
+
+ + + + + test.how + + +
+ + ignore + + + + +
+
+
+
+
+
+
+
+ +
+
+`; + +exports[`RunDataJsonSchema.vue > renders schema with spaces and dots 1`] = ` +
+
+
+
+
+ + + + +
+
+
+ + + + + hello world + + +
+ + + +
+
+
+ + + + hello world + + + [0] + + +
+ + + +
+
+
+ + + + + test + + +
+ + + +
+
+
+ + + + + more to think about + + +
+ + 1 + + + + +
+
+
+
+
+ + + + + test.how + + +
+ + ignore + + + + +
+
+
+
+
+
+
+
+ +
+
+`; diff --git a/packages/editor-ui/src/utils/__tests__/typesUtils.test.ts b/packages/editor-ui/src/utils/__tests__/typesUtils.test.ts index 3807d42f6b86df..569b98d4085d58 100644 --- a/packages/editor-ui/src/utils/__tests__/typesUtils.test.ts +++ b/packages/editor-ui/src/utils/__tests__/typesUtils.test.ts @@ -257,10 +257,31 @@ describe('Utils', () => { type: 'array', key: 'people', value: [ - { type: 'string', value: 'Joe', key: '0', path: '["people"][0]' }, - { type: 'string', value: 'John', key: '1', path: '["people"][1]' }, + { type: 'string', value: 'Joe', key: '0', path: '.people[0]' }, + { type: 'string', value: 'John', key: '1', path: '.people[1]' }, ], - path: '["people"]', + path: '.people', + }, + ], + path: '', + }, + ], + [ + { 'with space': [], 'with.dot': 'test' }, + { + type: 'object', + value: [ + { + type: 'array', + key: 'with space', + value: [], + path: '["with space"]', + }, + { + type: 'string', + key: 'with.dot', + value: 'test', + path: '["with.dot"]', }, ], path: '', @@ -278,8 +299,8 @@ describe('Utils', () => { type: 'object', key: '0', value: [ - { type: 'string', key: 'name', value: 'John', path: '[0]["name"]' }, - { type: 'number', key: 'age', value: '22', path: '[0]["age"]' }, + { type: 'string', key: 'name', value: 'John', path: '[0].name' }, + { type: 'number', key: 'age', value: '22', path: '[0].age' }, ], path: '[0]', }, @@ -287,8 +308,8 @@ describe('Utils', () => { type: 'object', key: '1', value: [ - { type: 'string', key: 'name', value: 'Joe', path: '[1]["name"]' }, - { type: 'number', key: 'age', value: '33', path: '[1]["age"]' }, + { type: 'string', key: 'name', value: 'Joe', path: '[1].name' }, + { type: 'number', key: 'age', value: '33', path: '[1].age' }, ], path: '[1]', }, @@ -308,16 +329,16 @@ describe('Utils', () => { type: 'object', key: '0', value: [ - { type: 'string', key: 'name', value: 'John', path: '[0]["name"]' }, - { type: 'number', key: 'age', value: '22', path: '[0]["age"]' }, + { type: 'string', key: 'name', value: 'John', path: '[0].name' }, + { type: 'number', key: 'age', value: '22', path: '[0].age' }, { type: 'array', key: 'hobbies', value: [ - { type: 'string', key: '0', value: 'surfing', path: '[0]["hobbies"][0]' }, - { type: 'string', key: '1', value: 'traveling', path: '[0]["hobbies"][1]' }, + { type: 'string', key: '0', value: 'surfing', path: '[0].hobbies[0]' }, + { type: 'string', key: '1', value: 'traveling', path: '[0].hobbies[1]' }, ], - path: '[0]["hobbies"]', + path: '[0].hobbies', }, ], path: '[0]', @@ -326,16 +347,16 @@ describe('Utils', () => { type: 'object', key: '1', value: [ - { type: 'string', key: 'name', value: 'Joe', path: '[1]["name"]' }, - { type: 'number', key: 'age', value: '33', path: '[1]["age"]' }, + { type: 'string', key: 'name', value: 'Joe', path: '[1].name' }, + { type: 'number', key: 'age', value: '33', path: '[1].age' }, { type: 'array', key: 'hobbies', value: [ - { type: 'string', key: '0', value: 'skateboarding', path: '[1]["hobbies"][0]' }, - { type: 'string', key: '1', value: 'gaming', path: '[1]["hobbies"][1]' }, + { type: 'string', key: '0', value: 'skateboarding', path: '[1].hobbies[0]' }, + { type: 'string', key: '1', value: 'gaming', path: '[1].hobbies[1]' }, ], - path: '[1]["hobbies"]', + path: '[1].hobbies', }, ], path: '[1]', @@ -381,8 +402,8 @@ describe('Utils', () => { type: 'object', key: '0', value: [ - { type: 'string', key: 'name', value: 'John', path: '[0][0]["name"]' }, - { type: 'number', key: 'age', value: '22', path: '[0][0]["age"]' }, + { type: 'string', key: 'name', value: 'John', path: '[0][0].name' }, + { type: 'number', key: 'age', value: '22', path: '[0][0].age' }, ], path: '[0][0]', }, @@ -390,8 +411,8 @@ describe('Utils', () => { type: 'object', key: '1', value: [ - { type: 'string', key: 'name', value: 'Joe', path: '[0][1]["name"]' }, - { type: 'number', key: 'age', value: '33', path: '[0][1]["age"]' }, + { type: 'string', key: 'name', value: 'Joe', path: '[0][1].name' }, + { type: 'number', key: 'age', value: '33', path: '[0][1].age' }, ], path: '[0][1]', }, @@ -430,16 +451,16 @@ describe('Utils', () => { type: 'string', key: '0', value: '2022-11-22T00:00:00.000Z', - path: '[0]["dates"][0][0]', + path: '[0].dates[0][0]', }, { type: 'string', key: '1', value: '2022-11-23T00:00:00.000Z', - path: '[0]["dates"][0][1]', + path: '[0].dates[0][1]', }, ], - path: '[0]["dates"][0]', + path: '[0].dates[0]', }, { type: 'array', @@ -449,19 +470,19 @@ describe('Utils', () => { type: 'string', key: '0', value: '2022-12-22T00:00:00.000Z', - path: '[0]["dates"][1][0]', + path: '[0].dates[1][0]', }, { type: 'string', key: '1', value: '2022-12-23T00:00:00.000Z', - path: '[0]["dates"][1][1]', + path: '[0].dates[1][1]', }, ], - path: '[0]["dates"][1]', + path: '[0].dates[1]', }, ], - path: '[0]["dates"]', + path: '[0].dates', }, ], path: '[0]', diff --git a/packages/editor-ui/src/utils/mappingUtils.ts b/packages/editor-ui/src/utils/mappingUtils.ts new file mode 100644 index 00000000000000..32889597f8f169 --- /dev/null +++ b/packages/editor-ui/src/utils/mappingUtils.ts @@ -0,0 +1,31 @@ +export function generatePath(root: string, path: Array): string { + return path.reduce((accu: string, part: string | number) => { + if (typeof part === 'number') { + return `${accu}[${part}]`; + } + + if (part.includes(' ') || part.includes('.')) { + return `${accu}["${part}"]`; + } + + return `${accu}.${part}`; + }, root); +} + +export function getMappedExpression({ + nodeName, + distanceFromActive, + path, +}: { + nodeName: string; + distanceFromActive: number; + path: Array | string; +}) { + const root = distanceFromActive === 1 ? '$json' : generatePath('$node', [nodeName, 'json']); + + if (typeof path === 'string') { + return `{{ ${root}${path} }}`; + } + + return `{{ ${generatePath(root, path)} }}`; +} diff --git a/packages/editor-ui/src/utils/typesUtils.ts b/packages/editor-ui/src/utils/typesUtils.ts index b1692cb51283e9..52f3010b1c3a71 100644 --- a/packages/editor-ui/src/utils/typesUtils.ts +++ b/packages/editor-ui/src/utils/typesUtils.ts @@ -2,6 +2,7 @@ import dateformat from 'dateformat'; import { IDataObject, jsonParse } from 'n8n-workflow'; import { Schema, Optional, Primitives } from '@/Interface'; import { isObj } from '@/utils/typeGuards'; +import { generatePath } from '@/utils/mappingUtils'; /* Constants and utility functions than can be used to manipulate different data types and objects @@ -231,7 +232,7 @@ export const getSchema = (input: Optional, path = ''): Sche type: 'object', value: Object.entries(input).map(([k, v]) => ({ key: k, - ...getSchema(v, path + `["${k}"]`), + ...getSchema(v, generatePath(path, [k])), })), path, }; diff --git a/packages/nodes-base/nodes/CompareDatasets/CompareDatasets.node.ts b/packages/nodes-base/nodes/CompareDatasets/CompareDatasets.node.ts index 497e9b9db48524..4acc603164d129 100644 --- a/packages/nodes-base/nodes/CompareDatasets/CompareDatasets.node.ts +++ b/packages/nodes-base/nodes/CompareDatasets/CompareDatasets.node.ts @@ -52,6 +52,7 @@ export class CompareDatasets implements INodeType { // eslint-disable-next-line n8n-nodes-base/node-param-placeholder-miscased-id placeholder: 'e.g. id', hint: ' Enter the field name as text', + requiresDataPath: 'single', }, { displayName: 'Input B Field', @@ -61,6 +62,7 @@ export class CompareDatasets implements INodeType { // eslint-disable-next-line n8n-nodes-base/node-param-placeholder-miscased-id placeholder: 'e.g. id', hint: ' Enter the field name as text', + requiresDataPath: 'single', }, ], }, @@ -126,6 +128,7 @@ export class CompareDatasets implements INodeType { resolve: ['mix'], }, }, + requiresDataPath: 'multiple', }, { displayName: 'Options', @@ -143,6 +146,7 @@ export class CompareDatasets implements INodeType { hint: 'Enter the field names as text, separated by commas', description: "Fields that shouldn't be included when checking whether two items are the same", + requiresDataPath: 'multiple', }, { displayName: 'Fuzzy Compare', diff --git a/packages/nodes-base/nodes/ItemLists/ItemLists.node.ts b/packages/nodes-base/nodes/ItemLists/ItemLists.node.ts index e4fc5d8ea258d6..d7aa9b384634ac 100644 --- a/packages/nodes-base/nodes/ItemLists/ItemLists.node.ts +++ b/packages/nodes-base/nodes/ItemLists/ItemLists.node.ts @@ -139,6 +139,7 @@ export class ItemLists implements INodeType { }, }, description: 'The name of the input field to break out into separate items', + requiresDataPath: 'single', }, { displayName: 'Include', @@ -197,6 +198,7 @@ export class ItemLists implements INodeType { // eslint-disable-next-line n8n-nodes-base/node-param-placeholder-miscased-id placeholder: 'e.g. id', hint: ' Enter the field name as text', + requiresDataPath: 'single', }, ], }, @@ -256,6 +258,7 @@ export class ItemLists implements INodeType { // eslint-disable-next-line n8n-nodes-base/node-param-placeholder-miscased-id placeholder: 'e.g. id', hint: ' Enter the field name as text', + requiresDataPath: 'single', }, { displayName: 'Rename Field', @@ -276,6 +279,7 @@ export class ItemLists implements INodeType { default: '', description: 'The name of the field to put the aggregated data in. Leave blank to use the input field name.', + requiresDataPath: 'single', }, ], }, @@ -346,6 +350,7 @@ export class ItemLists implements INodeType { // eslint-disable-next-line n8n-nodes-base/node-param-placeholder-miscased-id placeholder: 'e.g. id', hint: ' Enter the field name as text', + requiresDataPath: 'single', }, ], }, @@ -382,6 +387,7 @@ export class ItemLists implements INodeType { // eslint-disable-next-line n8n-nodes-base/node-param-placeholder-miscased-id placeholder: 'e.g. id', hint: ' Enter the field name as text', + requiresDataPath: 'single', }, ], }, @@ -453,6 +459,7 @@ export class ItemLists implements INodeType { // eslint-disable-next-line n8n-nodes-base/node-param-placeholder-miscased-id placeholder: 'e.g. id', hint: ' Enter the field name as text', + requiresDataPath: 'single', }, ], }, @@ -488,6 +495,7 @@ export class ItemLists implements INodeType { // eslint-disable-next-line n8n-nodes-base/node-param-placeholder-miscased-id placeholder: 'e.g. id', hint: ' Enter the field name as text', + requiresDataPath: 'single', }, ], }, @@ -544,6 +552,7 @@ export class ItemLists implements INodeType { // eslint-disable-next-line n8n-nodes-base/node-param-placeholder-miscased-id placeholder: 'e.g. id', hint: ' Enter the field name as text', + requiresDataPath: 'single', }, { displayName: 'Order', diff --git a/packages/nodes-base/nodes/ItemLists/summarize.operation.ts b/packages/nodes-base/nodes/ItemLists/summarize.operation.ts index 1a793cbfa15688..24d8f6fc6a27b1 100644 --- a/packages/nodes-base/nodes/ItemLists/summarize.operation.ts +++ b/packages/nodes-base/nodes/ItemLists/summarize.operation.ts @@ -123,6 +123,7 @@ export const description: INodeProperties[] = [ aggregation: [...NUMERICAL_AGGREGATIONS, 'countUnique', 'count'], }, }, + requiresDataPath: 'single', }, { displayName: 'Field', @@ -138,6 +139,7 @@ export const description: INodeProperties[] = [ aggregation: NUMERICAL_AGGREGATIONS, }, }, + requiresDataPath: 'single', }, { displayName: 'Field', @@ -153,6 +155,7 @@ export const description: INodeProperties[] = [ aggregation: ['countUnique', 'count'], }, }, + requiresDataPath: 'single', }, // ---------------------------------------------------------------------------------------------------------- { @@ -245,6 +248,7 @@ export const description: INodeProperties[] = [ '/options.outputFormat': ['singleItem'], }, }, + requiresDataPath: 'multiple', }, { displayName: 'Fields to Group By', @@ -261,6 +265,7 @@ export const description: INodeProperties[] = [ '/options.outputFormat': ['singleItem'], }, }, + requiresDataPath: 'multiple', }, // ---------------------------------------------------------------------------------------------------------- { diff --git a/packages/nodes-base/nodes/Merge/v2/MergeV2.node.ts b/packages/nodes-base/nodes/Merge/v2/MergeV2.node.ts index 4865c0b1962d83..de3ee69cbdfbd3 100644 --- a/packages/nodes-base/nodes/Merge/v2/MergeV2.node.ts +++ b/packages/nodes-base/nodes/Merge/v2/MergeV2.node.ts @@ -122,6 +122,7 @@ const versionDescription: INodeTypeDescription = { // eslint-disable-next-line n8n-nodes-base/node-param-placeholder-miscased-id placeholder: 'e.g. id', hint: ' Enter the field name as text', + requiresDataPath: 'single', }, { displayName: 'Input 2 Field', @@ -131,6 +132,7 @@ const versionDescription: INodeTypeDescription = { // eslint-disable-next-line n8n-nodes-base/node-param-placeholder-miscased-id placeholder: 'e.g. id', hint: ' Enter the field name as text', + requiresDataPath: 'single', }, ], }, diff --git a/packages/workflow/src/Interfaces.ts b/packages/workflow/src/Interfaces.ts index adb7ac8230c2f4..eb74598da8387c 100644 --- a/packages/workflow/src/Interfaces.ts +++ b/packages/workflow/src/Interfaces.ts @@ -1039,6 +1039,7 @@ export interface INodeProperties { >; extractValue?: INodePropertyValueExtractor; modes?: INodePropertyMode[]; + requiresDataPath?: 'single' | 'multiple'; } export interface INodePropertyModeTypeOptions { From ae998ea123d7aabcbcf6fbb2da183ea8e02e0f17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=A4=95=E0=A4=BE=E0=A4=B0=E0=A4=A4=E0=A5=8B=E0=A4=AB?= =?UTF-8?q?=E0=A5=8D=E0=A4=AB=E0=A5=87=E0=A4=B2=E0=A4=B8=E0=A5=8D=E0=A4=95?= =?UTF-8?q?=E0=A5=8D=E0=A4=B0=E0=A4=BF=E0=A4=AA=E0=A5=8D=E0=A4=9F=E2=84=A2?= Date: Mon, 30 Jan 2023 13:05:54 +0100 Subject: [PATCH 019/358] ci(editor): Update RunDataSchema test (no-changelog) (#5287) ci(editor-ui): Update RunDataSchema test (no-changelog) --- .../__snapshots__/RunDataSchema.test.ts.snap | 306 ++---------------- 1 file changed, 28 insertions(+), 278 deletions(-) diff --git a/packages/editor-ui/src/components/__snapshots__/RunDataSchema.test.ts.snap b/packages/editor-ui/src/components/__snapshots__/RunDataSchema.test.ts.snap index aac0e13b91e200..f15e0bd5a2b993 100644 --- a/packages/editor-ui/src/components/__snapshots__/RunDataSchema.test.ts.snap +++ b/packages/editor-ui/src/components/__snapshots__/RunDataSchema.test.ts.snap @@ -260,287 +260,37 @@ exports[`RunDataJsonSchema.vue > renders schema for empty data 1`] = `
`; -exports[`RunDataJsonSchema.vue > renders schema with spaces 1`] = ` -
-
-
-
-
- - - - -
-
-
- - - - - hello world - - -
- - - -
-
-
- - - - hello world - - - [0] - - -
- - - -
-
-
- - - - - test - - -
- - - -
-
-
- - - - - more to think about - - -
- - 1 - - - - -
-
-
-
-
- - - - - test.how - - -
- - ignore - - - - -
-
-
-
-
-
-
-
- -
-
-`; - exports[`RunDataJsonSchema.vue > renders schema with spaces and dots 1`] = `
renders schema with spaces and dots 1`] = ` type="checkbox" />
renders schema with spaces and dots 1`] = ` hello world [0] @@ -613,7 +363,7 @@ exports[`RunDataJsonSchema.vue > renders schema with spaces and dots 1`] = ` type="checkbox" />
renders schema with spaces and dots 1`] = ` type="checkbox" />
renders schema with spaces and dots 1`] = `
1 @@ -708,15 +458,15 @@ exports[`RunDataJsonSchema.vue > renders schema with spaces and dots 1`] = `
renders schema with spaces and dots 1`] = `
ignore From eda3b8aba0145f5dce4106a842245ff7836a4f60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=A4=95=E0=A4=BE=E0=A4=B0=E0=A4=A4=E0=A5=8B=E0=A4=AB?= =?UTF-8?q?=E0=A5=8D=E0=A4=AB=E0=A5=87=E0=A4=B2=E0=A4=B8=E0=A5=8D=E0=A4=95?= =?UTF-8?q?=E0=A5=8D=E0=A4=B0=E0=A4=BF=E0=A4=AA=E0=A5=8D=E0=A4=9F=E2=84=A2?= Date: Mon, 30 Jan 2023 13:09:09 +0100 Subject: [PATCH 020/358] ci: Re-enable CI jobs on nodejs 14.x (no-changelog) (#5153) Revert "ci: Disable CI jobs on nodejs 14.x (no-changelog) (#5132)" This reverts commit b3adcbd8134442a2b644fb50f2508df1299ab4b8. --- .github/workflows/ci-master.yml | 2 +- .github/workflows/ci-pull-requests.yml | 2 +- .github/workflows/e2e-tests.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci-master.yml b/.github/workflows/ci-master.yml index cbfe0df95a92bd..e3ef37b1809862 100644 --- a/.github/workflows/ci-master.yml +++ b/.github/workflows/ci-master.yml @@ -13,7 +13,7 @@ jobs: strategy: matrix: - node-version: [16.x] + node-version: [14.x, 16.x] steps: - uses: actions/checkout@v3 diff --git a/.github/workflows/ci-pull-requests.yml b/.github/workflows/ci-pull-requests.yml index f659b259e0d899..130e1753bdb6e1 100644 --- a/.github/workflows/ci-pull-requests.yml +++ b/.github/workflows/ci-pull-requests.yml @@ -10,7 +10,7 @@ jobs: strategy: matrix: - node-version: [16.x] + node-version: [14.x, 16.x] steps: - uses: actions/checkout@v3 diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index bb6b52cd0d5703..232d0f508bf40c 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -31,7 +31,7 @@ jobs: strategy: matrix: - node-version: [16.x] + node-version: [14.x, 16.x] steps: - name: Call Start URL - optionally From b2f59c3f39762387d6f921c725997c21e2a4ea09 Mon Sep 17 00:00:00 2001 From: Alex Grozav Date: Mon, 30 Jan 2023 14:28:59 +0200 Subject: [PATCH 021/358] feat: Add multiple workflows text search filtering E2E test scenarios (no-changelog) (#5276) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit feat: Add multiple workflows text search filtering E2E test scenarios Co-authored-by: कारतोफ्फेलस्क्रिप्ट™ --- cypress/e2e/1-workflows.cy.ts | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/cypress/e2e/1-workflows.cy.ts b/cypress/e2e/1-workflows.cy.ts index 5d0a3cc8066b6e..303ec68b5a2bfe 100644 --- a/cypress/e2e/1-workflows.cy.ts +++ b/cypress/e2e/1-workflows.cy.ts @@ -11,6 +11,8 @@ const lastName = randLastName(); const WorkflowsPage = new WorkflowsPageClass(); const WorkflowPage = new WorkflowPageClass(); +const multipleWorkflowsCount = 5; + describe('Workflows', () => { before(() => { cy.resetAll(); @@ -38,31 +40,39 @@ describe('Workflows', () => { WorkflowPage.getters.workflowTags().should('contain.text', 'some-tag-2'); }); - it('should create a new workflow using add workflow button', () => { + it('should create multiple new workflows using add workflow button', () => { WorkflowsPage.getters.newWorkflowButtonCard().should('not.exist'); - WorkflowsPage.getters.createWorkflowButton().click(); - cy.createFixtureWorkflow('Test_workflow_2.json', `Add Workflow Button Workflow ${uuid()}`); + [...Array(multipleWorkflowsCount).keys()].forEach(() => { + cy.visit(WorkflowsPage.url); + WorkflowsPage.getters.createWorkflowButton().click(); + + cy.createFixtureWorkflow('Test_workflow_2.json', `My New Workflow ${uuid()}`); - WorkflowPage.getters.workflowTags().should('contain.text', 'other-tag-1'); - WorkflowPage.getters.workflowTags().should('contain.text', 'other-tag-2'); + WorkflowPage.getters.workflowTags().should('contain.text', 'other-tag-1'); + WorkflowPage.getters.workflowTags().should('contain.text', 'other-tag-2'); + }); }); it('should search for a workflow', () => { + // One Result WorkflowsPage.getters.searchBar().type('Empty State Card Workflow'); - WorkflowsPage.getters.workflowCards().should('have.length', 1); WorkflowsPage.getters .workflowCard('Empty State Card Workflow') .should('contain.text', 'Empty State Card Workflow'); - WorkflowsPage.getters.searchBar().clear().type('Add Workflow Button Workflow'); + // Multiple Results + WorkflowsPage.getters.searchBar().clear().type('My New Workflow'); + WorkflowsPage.getters.workflowCards().should('have.length', multipleWorkflowsCount); + WorkflowsPage.getters.workflowCard('My New Workflow').should('contain.text', 'My New Workflow'); - WorkflowsPage.getters.workflowCards().should('have.length', 1); - WorkflowsPage.getters - .workflowCard('Add Workflow Button Workflow') - .should('contain.text', 'Add Workflow Button Workflow'); + // All Results + WorkflowsPage.getters.searchBar().clear().type('Workflow'); + WorkflowsPage.getters.workflowCards().should('have.length', multipleWorkflowsCount + 1); + WorkflowsPage.getters.workflowCard('Workflow').should('contain.text', 'Workflow'); + // No Results WorkflowsPage.getters.searchBar().clear().type('Some non-existent workflow'); WorkflowsPage.getters.workflowCards().should('not.exist'); @@ -70,7 +80,7 @@ describe('Workflows', () => { }); it('should delete all the workflows', () => { - WorkflowsPage.getters.workflowCards().should('have.length', 2); + WorkflowsPage.getters.workflowCards().should('have.length', multipleWorkflowsCount + 1); WorkflowsPage.getters.workflowCards().each(($el) => { const workflowName = $el.find('[data-test-id="workflow-card-name"]').text(); From 72249e0de803184d019b1cba587a7c905bf68f8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=A4=95=E0=A4=BE=E0=A4=B0=E0=A4=A4=E0=A5=8B=E0=A4=AB?= =?UTF-8?q?=E0=A5=8D=E0=A4=AB=E0=A5=87=E0=A4=B2=E0=A4=B8=E0=A5=8D=E0=A4=95?= =?UTF-8?q?=E0=A5=8D=E0=A4=B0=E0=A4=BF=E0=A4=AA=E0=A5=8D=E0=A4=9F=E2=84=A2?= Date: Mon, 30 Jan 2023 14:42:30 +0100 Subject: [PATCH 022/358] refactor(core): Load and validate all config at startup (no-changelog) (#5283) --- packages/cli/src/CredentialsOverwrites.ts | 9 +-- packages/cli/src/Db.ts | 35 ++++------ packages/cli/src/GenericHelpers.ts | 68 +------------------ packages/cli/src/Server.ts | 40 +++-------- .../email/UserManagementMailer.ts | 5 +- packages/cli/src/WaitTracker.ts | 6 +- packages/cli/src/WorkflowRunnerProcess.ts | 3 +- packages/cli/src/commands/db/revert.ts | 2 +- packages/cli/src/commands/execute.ts | 2 +- packages/cli/src/commands/executeBatch.ts | 2 +- packages/cli/src/commands/start.ts | 6 +- packages/cli/src/commands/webhook.ts | 2 +- packages/cli/src/commands/worker.ts | 2 +- packages/cli/src/config/index.ts | 45 +++++++++--- packages/cli/src/databases/config.ts | 13 ++-- .../cli/src/executions/executions.service.ts | 3 +- 16 files changed, 80 insertions(+), 163 deletions(-) diff --git a/packages/cli/src/CredentialsOverwrites.ts b/packages/cli/src/CredentialsOverwrites.ts index 414f3eb349e5df..c3a5702df113f9 100644 --- a/packages/cli/src/CredentialsOverwrites.ts +++ b/packages/cli/src/CredentialsOverwrites.ts @@ -1,18 +1,15 @@ +import config from '@/config'; import type { ICredentialDataDecryptedObject, ICredentialTypes } from 'n8n-workflow'; import { deepCopy, LoggerProxy as Logger, jsonParse } from 'n8n-workflow'; import type { ICredentialsOverwrite } from '@/Interfaces'; -import * as GenericHelpers from '@/GenericHelpers'; class CredentialsOverwritesClass { private overwriteData: ICredentialsOverwrite = {}; private resolvedTypes: string[] = []; - constructor(private credentialTypes: ICredentialTypes) {} - - async init() { - const data = (await GenericHelpers.getConfigValue('credentials.overwrite.data')) as string; - + constructor(private credentialTypes: ICredentialTypes) { + const data = config.getEnv('credentials.overwrite.data'); const overwriteData = jsonParse(data, { errorMessage: 'The credentials-overwrite is not valid JSON.', }); diff --git a/packages/cli/src/Db.ts b/packages/cli/src/Db.ts index 76bdd1e9057810..68ff6a0adaf38f 100644 --- a/packages/cli/src/Db.ts +++ b/packages/cli/src/Db.ts @@ -14,7 +14,6 @@ import type { import { DataSource as Connection } from 'typeorm'; import type { TlsOptions } from 'tls'; import type { DatabaseType, IDatabaseCollections } from '@/Interfaces'; -import * as GenericHelpers from '@/GenericHelpers'; import config from '@/config'; @@ -44,17 +43,13 @@ export function linkRepository( return connection.getRepository(entityClass); } -export async function getConnectionOptions(dbType: DatabaseType): Promise { +export function getConnectionOptions(dbType: DatabaseType): ConnectionOptions { switch (dbType) { case 'postgresdb': - const sslCa = (await GenericHelpers.getConfigValue('database.postgresdb.ssl.ca')) as string; - const sslCert = (await GenericHelpers.getConfigValue( - 'database.postgresdb.ssl.cert', - )) as string; - const sslKey = (await GenericHelpers.getConfigValue('database.postgresdb.ssl.key')) as string; - const sslRejectUnauthorized = (await GenericHelpers.getConfigValue( - 'database.postgresdb.ssl.rejectUnauthorized', - )) as boolean; + const sslCa = config.getEnv('database.postgresdb.ssl.ca'); + const sslCert = config.getEnv('database.postgresdb.ssl.cert'); + const sslKey = config.getEnv('database.postgresdb.ssl.key'); + const sslRejectUnauthorized = config.getEnv('database.postgresdb.ssl.rejectUnauthorized'); let ssl: TlsOptions | undefined; if (sslCa !== '' || sslCert !== '' || sslKey !== '' || !sslRejectUnauthorized) { @@ -68,7 +63,7 @@ export async function getConnectionOptions(dbType: DatabaseType): Promise { if (isInitialized) return collections; - const dbType = (await GenericHelpers.getConfigValue('database.type')) as DatabaseType; - const connectionOptions = testConnectionOptions ?? (await getConnectionOptions(dbType)); + const dbType = config.getEnv('database.type'); + const connectionOptions = testConnectionOptions ?? getConnectionOptions(dbType); - let loggingOption: LoggerOptions = (await GenericHelpers.getConfigValue( - 'database.logging.enabled', - )) as boolean; + let loggingOption: LoggerOptions = config.getEnv('database.logging.enabled'); if (loggingOption) { - const optionsString = ( - (await GenericHelpers.getConfigValue('database.logging.options')) as string - ).replace(/\s+/g, ''); + const optionsString = config.getEnv('database.logging.options').replace(/\s+/g, ''); if (optionsString === 'all') { loggingOption = optionsString; @@ -112,9 +103,7 @@ export async function init( } } - const maxQueryExecutionTime = (await GenericHelpers.getConfigValue( - 'database.logging.maxQueryExecutionTime', - )) as string; + const maxQueryExecutionTime = config.getEnv('database.logging.maxQueryExecutionTime'); Object.assign(connectionOptions, { entities: Object.values(entities), diff --git a/packages/cli/src/GenericHelpers.ts b/packages/cli/src/GenericHelpers.ts index 90810af1a57eab..e3940a620b7c34 100644 --- a/packages/cli/src/GenericHelpers.ts +++ b/packages/cli/src/GenericHelpers.ts @@ -5,22 +5,19 @@ /* eslint-disable no-underscore-dangle */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ import type express from 'express'; -import { readFile as fsReadFile } from 'fs/promises'; import type { ExecutionError, - IDataObject, INode, IRunExecutionData, Workflow, WorkflowExecuteMode, } from 'n8n-workflow'; import { validate } from 'class-validator'; +import { Like } from 'typeorm'; import config from '@/config'; import * as Db from '@/Db'; import type { ICredentialsDb, IExecutionDb, IExecutionFlattedDb, IWorkflowDb } from '@/Interfaces'; import * as ResponseHelper from '@/ResponseHelper'; -// eslint-disable-next-line import/order -import { Like } from 'typeorm'; import type { WorkflowEntity } from '@db/entities/WorkflowEntity'; import type { CredentialsEntity } from '@db/entities/CredentialsEntity'; import type { TagEntity } from '@db/entities/TagEntity'; @@ -28,7 +25,6 @@ import type { User } from '@db/entities/User'; /** * Returns the base URL n8n is reachable from - * */ export function getBaseUrl(): string { const protocol = config.getEnv('protocol'); @@ -44,73 +40,11 @@ export function getBaseUrl(): string { /** * Returns the session id if one is set - * */ export function getSessionId(req: express.Request): string | undefined { return req.headers.sessionid as string | undefined; } -/** - * Extracts configuration schema for key - */ -function extractSchemaForKey(configKey: string, configSchema: IDataObject): IDataObject { - const configKeyParts = configKey.split('.'); - - // eslint-disable-next-line no-restricted-syntax - for (const key of configKeyParts) { - if (configSchema[key] === undefined) { - throw new Error(`Key "${key}" of ConfigKey "${configKey}" does not exist`); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - } else if ((configSchema[key]! as IDataObject)._cvtProperties === undefined) { - configSchema = configSchema[key] as IDataObject; - } else { - configSchema = (configSchema[key] as IDataObject)._cvtProperties as IDataObject; - } - } - return configSchema; -} - -/** - * Gets value from config with support for "_FILE" environment variables - * - * @param {string} configKey The key of the config data to get - */ -export async function getConfigValue( - configKey: string, -): Promise { - // Get the environment variable - const configSchema = config.getSchema(); - // @ts-ignore - const currentSchema = extractSchemaForKey(configKey, configSchema._cvtProperties as IDataObject); - // Check if environment variable is defined for config key - if (currentSchema.env === undefined) { - // No environment variable defined, so return value from config - // @ts-ignore - return config.getEnv(configKey); - } - - // Check if special file environment variable exists - const fileEnvironmentVariable = process.env[`${currentSchema.env}_FILE`]; - if (fileEnvironmentVariable === undefined) { - // Does not exist, so return value from config - // @ts-ignore - return config.getEnv(configKey); - } - - let data; - try { - data = await fsReadFile(fileEnvironmentVariable, 'utf8'); - } catch (error) { - if (error.code === 'ENOENT') { - throw new Error(`The file "${fileEnvironmentVariable}" could not be found.`); - } - - throw error; - } - - return data; -} - /** * Generate a unique name for a workflow or credentials entity. * diff --git a/packages/cli/src/Server.ts b/packages/cli/src/Server.ts index 257d6e20450524..37875490feb03a 100644 --- a/packages/cli/src/Server.ts +++ b/packages/cli/src/Server.ts @@ -409,23 +409,17 @@ class Server extends AbstractServer { // Check for basic auth credentials if activated const basicAuthActive = config.getEnv('security.basicAuth.active'); if (basicAuthActive) { - const basicAuthUser = (await GenericHelpers.getConfigValue( - 'security.basicAuth.user', - )) as string; + const basicAuthUser = config.getEnv('security.basicAuth.user'); if (basicAuthUser === '') { throw new Error('Basic auth is activated but no user got defined. Please set one!'); } - const basicAuthPassword = (await GenericHelpers.getConfigValue( - 'security.basicAuth.password', - )) as string; + const basicAuthPassword = config.getEnv('security.basicAuth.password'); if (basicAuthPassword === '') { throw new Error('Basic auth is activated but no password got defined. Please set one!'); } - const basicAuthHashEnabled = (await GenericHelpers.getConfigValue( - 'security.basicAuth.hash', - )) as boolean; + const basicAuthHashEnabled = config.getEnv('security.basicAuth.hash') as boolean; let validPassword: null | string = null; @@ -483,31 +477,19 @@ class Server extends AbstractServer { // Check for and validate JWT if configured const jwtAuthActive = config.getEnv('security.jwtAuth.active'); if (jwtAuthActive) { - const jwtAuthHeader = (await GenericHelpers.getConfigValue( - 'security.jwtAuth.jwtHeader', - )) as string; + const jwtAuthHeader = config.getEnv('security.jwtAuth.jwtHeader'); if (jwtAuthHeader === '') { throw new Error('JWT auth is activated but no request header was defined. Please set one!'); } - const jwksUri = (await GenericHelpers.getConfigValue('security.jwtAuth.jwksUri')) as string; + const jwksUri = config.getEnv('security.jwtAuth.jwksUri'); if (jwksUri === '') { throw new Error('JWT auth is activated but no JWK Set URI was defined. Please set one!'); } - const jwtHeaderValuePrefix = (await GenericHelpers.getConfigValue( - 'security.jwtAuth.jwtHeaderValuePrefix', - )) as string; - const jwtIssuer = (await GenericHelpers.getConfigValue( - 'security.jwtAuth.jwtIssuer', - )) as string; - const jwtNamespace = (await GenericHelpers.getConfigValue( - 'security.jwtAuth.jwtNamespace', - )) as string; - const jwtAllowedTenantKey = (await GenericHelpers.getConfigValue( - 'security.jwtAuth.jwtAllowedTenantKey', - )) as string; - const jwtAllowedTenant = (await GenericHelpers.getConfigValue( - 'security.jwtAuth.jwtAllowedTenant', - )) as string; + const jwtHeaderValuePrefix = config.getEnv('security.jwtAuth.jwtHeaderValuePrefix'); + const jwtIssuer = config.getEnv('security.jwtAuth.jwtIssuer'); + const jwtNamespace = config.getEnv('security.jwtAuth.jwtNamespace'); + const jwtAllowedTenantKey = config.getEnv('security.jwtAuth.jwtAllowedTenantKey'); + const jwtAllowedTenant = config.getEnv('security.jwtAuth.jwtAllowedTenant'); // eslint-disable-next-line no-inner-declarations function isTenantAllowed(decodedToken: object): boolean { @@ -1456,7 +1438,7 @@ export async function start(): Promise { const binaryDataConfig = config.getEnv('binaryDataManager'); const diagnosticInfo: IDiagnosticInfo = { basicAuthActive: config.getEnv('security.basicAuth.active'), - databaseType: (await GenericHelpers.getConfigValue('database.type')) as DatabaseType, + databaseType: config.getEnv('database.type'), disableProductionWebhooksOnMainProcess: config.getEnv( 'endpoints.disableProductionWebhooksOnMainProcess', ), diff --git a/packages/cli/src/UserManagement/email/UserManagementMailer.ts b/packages/cli/src/UserManagement/email/UserManagementMailer.ts index 67cc4ec6e28f62..2c9ef671981778 100644 --- a/packages/cli/src/UserManagement/email/UserManagementMailer.ts +++ b/packages/cli/src/UserManagement/email/UserManagementMailer.ts @@ -2,7 +2,6 @@ import { existsSync } from 'fs'; import { readFile } from 'fs/promises'; import Handlebars from 'handlebars'; import { join as pathJoin } from 'path'; -import * as GenericHelpers from '@/GenericHelpers'; import config from '@/config'; import type { InviteEmailData, @@ -23,9 +22,7 @@ async function getTemplate( ): Promise