From afce58c19a4265edc4cfa8a3b0445aaafe18950c Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sun, 9 May 2021 14:15:47 -0500 Subject: [PATCH 01/88] :bug: Fix issue with Google Sheets update with custom key row #1736 --- packages/nodes-base/nodes/Google/Sheet/GoogleSheet.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/nodes-base/nodes/Google/Sheet/GoogleSheet.ts b/packages/nodes-base/nodes/Google/Sheet/GoogleSheet.ts index 2aa2541f23..aca82ddea2 100644 --- a/packages/nodes-base/nodes/Google/Sheet/GoogleSheet.ts +++ b/packages/nodes-base/nodes/Google/Sheet/GoogleSheet.ts @@ -286,7 +286,7 @@ export class GoogleSheet { throw new NodeOperationError(this.executeFunctions.getNode(), `The range "${range}" is not valid.`); } - const keyRowRange = `${sheet ? sheet + '!' : ''}${rangeStartSplit[1]}${dataStartRowIndex}:${rangeEndSplit[1]}${dataStartRowIndex}`; + const keyRowRange = `${sheet ? sheet + '!' : ''}${rangeStartSplit[1]}${keyRowIndex + 1}:${rangeEndSplit[1]}${keyRowIndex + 1}`; const sheetDatakeyRow = await this.getData(this.encodeRange(keyRowRange), valueRenderMode); @@ -302,7 +302,7 @@ export class GoogleSheet { throw new NodeOperationError(this.executeFunctions.getNode(), `Could not find column for key "${indexKey}"!`); } - const startRowIndex = rangeStartSplit[2] || ''; + const startRowIndex = rangeStartSplit[2] || dataStartRowIndex; const endRowIndex = rangeEndSplit[2] || ''; const keyColumn = this.getColumnWithOffset(rangeStartSplit[1], keyIndex); From b05c4d5a55f9208937d16f2fa37a4ad2286586f3 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sun, 9 May 2021 19:24:17 +0000 Subject: [PATCH 02/88] =?UTF-8?q?:bookmark:=20Release=C2=A0n8n-core@0.70.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 69a76944f1..35b12c5b17 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "n8n-core", - "version": "0.69.0", + "version": "0.70.0", "description": "Core functionality of n8n", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", From 897d3c9391b1b186f7cabd8b2e024c2be49a3ea5 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sun, 9 May 2021 19:24:34 +0000 Subject: [PATCH 03/88] :arrow_up: Set n8n-core@0.70.0 on n8n-node-dev --- 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 64c637beb4..5eaea0bc0d 100644 --- a/packages/node-dev/package.json +++ b/packages/node-dev/package.json @@ -59,7 +59,7 @@ "change-case": "^4.1.1", "copyfiles": "^2.1.1", "inquirer": "^7.0.1", - "n8n-core": "^0.67.0", + "n8n-core": "~0.70.0", "n8n-workflow": "^0.55.0", "oauth-1.0a": "^2.2.6", "replace-in-file": "^6.0.0", From 75a677269a363b8c66bf4c4264a08e8d4cb3ac7f Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sun, 9 May 2021 19:24:34 +0000 Subject: [PATCH 04/88] =?UTF-8?q?:bookmark:=20Release=C2=A0n8n-node-dev@0.?= =?UTF-8?q?12.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 5eaea0bc0d..1a69587813 100644 --- a/packages/node-dev/package.json +++ b/packages/node-dev/package.json @@ -1,6 +1,6 @@ { "name": "n8n-node-dev", - "version": "0.11.0", + "version": "0.12.0", "description": "CLI to simplify n8n credentials/node development", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", From a6f97838a7c027bdcd6cb2983cbcb992a05af504 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sun, 9 May 2021 19:24:54 +0000 Subject: [PATCH 05/88] :arrow_up: Set n8n-core@0.70.0 on n8n-nodes-base --- 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 9c2eff8a4e..3ad0b62e5a 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -623,7 +623,7 @@ "mqtt": "4.2.6", "mssql": "^6.2.0", "mysql2": "~2.2.0", - "n8n-core": "~0.69.0", + "n8n-core": "~0.70.0", "nodemailer": "^6.5.0", "pdf-parse": "^1.1.1", "pg": "^8.3.0", From 5f0b017c77e1e15c2fcdb69fbeb60e3d2976218b Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sun, 9 May 2021 19:24:54 +0000 Subject: [PATCH 06/88] =?UTF-8?q?:bookmark:=20Release=C2=A0n8n-nodes-base@?= =?UTF-8?q?0.116.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 3ad0b62e5a..4e2d4bd19f 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -1,6 +1,6 @@ { "name": "n8n-nodes-base", - "version": "0.115.0", + "version": "0.116.0", "description": "Base nodes of n8n", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", From 8ce1086262b7d5bc60ea3567458f62ef57d30b0e Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sun, 9 May 2021 19:25:53 +0000 Subject: [PATCH 07/88] =?UTF-8?q?:bookmark:=20Release=C2=A0n8n-editor-ui@0?= =?UTF-8?q?.89.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 ccf591fe6b..54311ff034 100644 --- a/packages/editor-ui/package.json +++ b/packages/editor-ui/package.json @@ -1,6 +1,6 @@ { "name": "n8n-editor-ui", - "version": "0.88.0", + "version": "0.89.0", "description": "Workflow Editor UI for n8n", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", From ffacdcdbce38d71e92ed5aa931beefa9bdac2b29 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sun, 9 May 2021 19:26:48 +0000 Subject: [PATCH 08/88] :arrow_up: Set n8n-core@0.70.0, n8n-editor-ui@0.89.0 and n8n-nodes-base@0.116.0 on n8n --- packages/cli/package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index aa48f92da2..892ccc83c6 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -104,9 +104,9 @@ "localtunnel": "^2.0.0", "lodash.get": "^4.4.2", "mysql2": "~2.2.0", - "n8n-core": "~0.69.0", - "n8n-editor-ui": "~0.88.0", - "n8n-nodes-base": "~0.115.0", + "n8n-core": "~0.70.0", + "n8n-editor-ui": "~0.89.0", + "n8n-nodes-base": "~0.116.0", "n8n-workflow": "~0.57.0", "oauth-1.0a": "^2.2.6", "open": "^7.0.0", From 958657278bffe6dcd2ebf4b3efd7b13fe063bc77 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sun, 9 May 2021 19:26:49 +0000 Subject: [PATCH 09/88] =?UTF-8?q?:bookmark:=20Release=C2=A0n8n@0.119.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 892ccc83c6..468adbf379 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "n8n", - "version": "0.118.0", + "version": "0.119.0", "description": "n8n Workflow Automation Tool", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", From e3ab0b86792a983faca498eb4a32eea47f20444a Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sun, 9 May 2021 17:55:27 -0500 Subject: [PATCH 10/88] :books: Fix job link --- README.md | 4 +--- docker/images/n8n/README.md | 4 +--- packages/cli/README.md | 4 +--- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 6e1e7e1766..1fd4f1128c 100644 --- a/README.md +++ b/README.md @@ -63,9 +63,7 @@ If you have problems or questions go to our forum, we will then try to help you ## Jobs If you are interested in working for n8n and so shape the future of the project -check out our job posts: - -[https://n8n.join.com](https://n8n.join.com) +check out our [job posts](https://apply.workable.com/n8n/) diff --git a/docker/images/n8n/README.md b/docker/images/n8n/README.md index ef0d7123e7..436942ef40 100644 --- a/docker/images/n8n/README.md +++ b/docker/images/n8n/README.md @@ -262,9 +262,7 @@ If you have problems or questions go to our forum, we will then try to help you ## Jobs If you are interested in working for n8n and so shape the future of the project -check out our job posts: - -[https://n8n.join.com](https://n8n.join.com) +check out our [job posts](https://apply.workable.com/n8n/) diff --git a/packages/cli/README.md b/packages/cli/README.md index e7f951bbbe..61da7bcf35 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -87,9 +87,7 @@ If you have problems or questions go to our forum, we will then try to help you ## Jobs If you are interested in working for n8n and so shape the future of the project -check out our job posts: - -[https://n8n.join.com](https://n8n.join.com) +check out our [job posts](https://apply.workable.com/n8n/) ## Upgrading From 1d13f7d46ceea6b8f74bdfd05b49bfffebd3e367 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sun, 9 May 2021 17:56:57 -0500 Subject: [PATCH 11/88] :books: Change example docker command to persist data --- docker/images/n8n/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docker/images/n8n/README.md b/docker/images/n8n/README.md index 436942ef40..cb484addb0 100644 --- a/docker/images/n8n/README.md +++ b/docker/images/n8n/README.md @@ -49,6 +49,7 @@ Additional information and example workflows on the n8n.io website: [https://n8n docker run -it --rm \ --name n8n \ -p 5678:5678 \ + -v ~/.n8n:/home/node/.n8n \ n8nio/n8n ``` From b47de284f52580fc4ae5fe20ede7d39bb17c3e9b Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sun, 9 May 2021 22:41:18 -0500 Subject: [PATCH 12/88] :arrow_up: Set n8n-workflow@0.57.0 on n8n-node-dev --- 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 1a69587813..c9beb8b379 100644 --- a/packages/node-dev/package.json +++ b/packages/node-dev/package.json @@ -60,7 +60,7 @@ "copyfiles": "^2.1.1", "inquirer": "^7.0.1", "n8n-core": "~0.70.0", - "n8n-workflow": "^0.55.0", + "n8n-workflow": "^0.57.0", "oauth-1.0a": "^2.2.6", "replace-in-file": "^6.0.0", "request": "^2.88.2", From caa55a022b5789e56674808e89c54ba9bec0279b Mon Sep 17 00:00:00 2001 From: Ahsan Virani Date: Wed, 12 May 2021 05:12:53 +0200 Subject: [PATCH 13/88] :sparkles: Add more FE hooks (#1772) * :sparkles: add FE hook for expressionEdit dialogVisibleChanged * :zap: update FE hook for expressionEdit dialogVisibleChanged * :sparkles: add FE hook for expressionEdit itemSelected * :sparkles: add FE hook for nodeSettings valueChanged * :sparkles: add FE hook for nodeSettings credentialSelected * cleanup --- .../editor-ui/src/components/ExpressionEdit.vue | 17 +++++++++++++++-- .../editor-ui/src/components/NodeSettings.vue | 8 ++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/packages/editor-ui/src/components/ExpressionEdit.vue b/packages/editor-ui/src/components/ExpressionEdit.vue index ada71f6869..c073ce7793 100644 --- a/packages/editor-ui/src/components/ExpressionEdit.vue +++ b/packages/editor-ui/src/components/ExpressionEdit.vue @@ -30,7 +30,7 @@
Result
- + @@ -52,7 +52,11 @@ import { Workflow, } from 'n8n-workflow'; -export default Vue.extend({ +import { externalHooks } from '@/components/mixins/externalHooks'; + +import mixins from 'vue-typed-mixins'; + +export default mixins(externalHooks).extend({ name: 'ExpressionEdit', props: [ 'dialogVisible', @@ -81,7 +85,16 @@ export default Vue.extend({ }, itemSelected (eventData: IVariableItemSelected) { + // User inserted item from Expression Editor variable selector (this.$refs.inputFieldExpression as any).itemSelected(eventData); // tslint:disable-line:no-any + + this.$externalHooks().run('expressionEdit.itemSelected', { parameter: this.parameter, value: this.value, selectedItem: eventData }); + }, + }, + watch: { + dialogVisible (newValue) { + const resolvedExpressionValue = this.$refs.expressionResult && (this.$refs.expressionResult as any).getValue() || undefined; // tslint:disable-line:no-any + this.$externalHooks().run('expressionEdit.dialogVisibleChanged', { dialogVisible: newValue, parameter: this.parameter, value: this.value, resolvedExpressionValue }); }, }, }); diff --git a/packages/editor-ui/src/components/NodeSettings.vue b/packages/editor-ui/src/components/NodeSettings.vue index 07f8ac7b2d..7bdf7ea5dd 100644 --- a/packages/editor-ui/src/components/NodeSettings.vue +++ b/packages/editor-ui/src/components/NodeSettings.vue @@ -59,12 +59,14 @@ import NodeCredentials from '@/components/NodeCredentials.vue'; import NodeWebhooks from '@/components/NodeWebhooks.vue'; import { get, set, unset } from 'lodash'; +import { externalHooks } from '@/components/mixins/externalHooks'; import { genericHelpers } from '@/components/mixins/genericHelpers'; import { nodeHelpers } from '@/components/mixins/nodeHelpers'; import mixins from 'vue-typed-mixins'; export default mixins( + externalHooks, genericHelpers, nodeHelpers, ) @@ -323,6 +325,8 @@ export default mixins( // Update the issues this.updateNodeCredentialIssues(node); + + this.$externalHooks().run('nodeSettings.credentialSelected', { updateInformation }); }, valueChanged (parameterData: IUpdateInformation) { let newValue: NodeParameterValue; @@ -357,6 +361,7 @@ export default mixins( // Get only the parameters which are different to the defaults let nodeParameters = NodeHelpers.getNodeParameters(nodeType.properties, node.parameters, false, false); + const oldNodeParameters = Object.assign({}, nodeParameters); // Copy the data because it is the data of vuex so make sure that // we do not edit it directly @@ -404,7 +409,10 @@ export default mixins( name: node.name, value: nodeParameters, }; + this.$store.commit('setNodeParameters', updateInformation); + + this.$externalHooks().run('nodeSettings.valueChanged', { parameterPath, newValue, parameters: this.parameters, oldNodeParameters }); this.updateNodeParameterIssues(node, nodeType); this.updateNodeCredentialIssues(node); From 6ffc544888524a9c76eacf93c5e3326da54c75af Mon Sep 17 00:00:00 2001 From: Omar Ajoue Date: Wed, 12 May 2021 05:15:22 +0200 Subject: [PATCH 14/88] :zap: Add await statement to hooks (#1775) --- packages/cli/src/WorkflowRunnerProcess.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/cli/src/WorkflowRunnerProcess.ts b/packages/cli/src/WorkflowRunnerProcess.ts index 14ae4a1abf..4a09c76a89 100644 --- a/packages/cli/src/WorkflowRunnerProcess.ts +++ b/packages/cli/src/WorkflowRunnerProcess.ts @@ -194,9 +194,9 @@ export class WorkflowRunnerProcess { * @param {any[]} parameters * @memberof WorkflowRunnerProcess */ - sendHookToParentProcess(hook: string, parameters: any[]) { // tslint:disable-line:no-any + async sendHookToParentProcess(hook: string, parameters: any[]) { // tslint:disable-line:no-any try { - sendToParentProcess('processHook', { + await sendToParentProcess('processHook', { hook, parameters, }); @@ -217,22 +217,22 @@ export class WorkflowRunnerProcess { const hookFunctions: IWorkflowExecuteHooks = { nodeExecuteBefore: [ async (nodeName: string): Promise => { - this.sendHookToParentProcess('nodeExecuteBefore', [nodeName]); + await this.sendHookToParentProcess('nodeExecuteBefore', [nodeName]); }, ], nodeExecuteAfter: [ async (nodeName: string, data: ITaskData): Promise => { - this.sendHookToParentProcess('nodeExecuteAfter', [nodeName, data]); + await this.sendHookToParentProcess('nodeExecuteAfter', [nodeName, data]); }, ], workflowExecuteBefore: [ async (): Promise => { - this.sendHookToParentProcess('workflowExecuteBefore', []); + await this.sendHookToParentProcess('workflowExecuteBefore', []); }, ], workflowExecuteAfter: [ async (fullRunData: IRun, newStaticData?: IDataObject): Promise => { - this.sendHookToParentProcess('workflowExecuteAfter', [fullRunData, newStaticData]); + await this.sendHookToParentProcess('workflowExecuteAfter', [fullRunData, newStaticData]); }, ], }; From 0867c7bbfe60f821129d2bd961c74f668827458a Mon Sep 17 00:00:00 2001 From: Omar Ajoue Date: Wed, 12 May 2021 05:43:24 +0200 Subject: [PATCH 15/88] :bug: Fix issue with long credential names in MySQL (#1771) --- .../1620729500000-ChangeCredentialDataSize.ts | 18 ++++++++++++++++++ .../src/databases/mysqldb/migrations/index.ts | 2 ++ 2 files changed, 20 insertions(+) create mode 100644 packages/cli/src/databases/mysqldb/migrations/1620729500000-ChangeCredentialDataSize.ts diff --git a/packages/cli/src/databases/mysqldb/migrations/1620729500000-ChangeCredentialDataSize.ts b/packages/cli/src/databases/mysqldb/migrations/1620729500000-ChangeCredentialDataSize.ts new file mode 100644 index 0000000000..789e1d2172 --- /dev/null +++ b/packages/cli/src/databases/mysqldb/migrations/1620729500000-ChangeCredentialDataSize.ts @@ -0,0 +1,18 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; +import * as config from '../../../../config'; + +export class ChangeCredentialDataSize1620729500000 implements MigrationInterface { + name = 'ChangeCredentialDataSize1620729500000'; + + async up(queryRunner: QueryRunner): Promise { + const tablePrefix = config.get('database.tablePrefix'); + + await queryRunner.query('ALTER TABLE `' + tablePrefix + 'credentials_entity` MODIFY COLUMN `data` MEDIUMTEXT NOT NULL'); + } + + async down(queryRunner: QueryRunner): Promise { + const tablePrefix = config.get('database.tablePrefix'); + + await queryRunner.query('ALTER TABLE `' + tablePrefix + 'credentials_entity` MODIFY COLUMN `data` TEXT NOT NULL'); + } +} diff --git a/packages/cli/src/databases/mysqldb/migrations/index.ts b/packages/cli/src/databases/mysqldb/migrations/index.ts index 08ca45edbf..f5c110fec2 100644 --- a/packages/cli/src/databases/mysqldb/migrations/index.ts +++ b/packages/cli/src/databases/mysqldb/migrations/index.ts @@ -4,6 +4,7 @@ import { CreateIndexStoppedAt1594902918301 } from './1594902918301-CreateIndexSt import { AddWebhookId1611149998770 } from './1611149998770-AddWebhookId'; import { MakeStoppedAtNullable1607431743767 } from './1607431743767-MakeStoppedAtNullable'; import { ChangeDataSize1615306975123 } from './1615306975123-ChangeDataSize'; +import { ChangeCredentialDataSize1620729500000 } from './1620729500000-ChangeCredentialDataSize'; export const mysqlMigrations = [ InitialMigration1588157391238, @@ -12,4 +13,5 @@ export const mysqlMigrations = [ AddWebhookId1611149998770, MakeStoppedAtNullable1607431743767, ChangeDataSize1615306975123, + ChangeCredentialDataSize1620729500000, ]; From 6134175a06518b0d93dce50d1908979521afbe33 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Tue, 11 May 2021 23:04:56 -0500 Subject: [PATCH 16/88] :bug: Fix MySQL credential bug --- .../migrations/1620729500000-ChangeCredentialDataSize.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/cli/src/databases/mysqldb/migrations/1620729500000-ChangeCredentialDataSize.ts b/packages/cli/src/databases/mysqldb/migrations/1620729500000-ChangeCredentialDataSize.ts index 789e1d2172..ea1bd42049 100644 --- a/packages/cli/src/databases/mysqldb/migrations/1620729500000-ChangeCredentialDataSize.ts +++ b/packages/cli/src/databases/mysqldb/migrations/1620729500000-ChangeCredentialDataSize.ts @@ -7,12 +7,12 @@ export class ChangeCredentialDataSize1620729500000 implements MigrationInterface async up(queryRunner: QueryRunner): Promise { const tablePrefix = config.get('database.tablePrefix'); - await queryRunner.query('ALTER TABLE `' + tablePrefix + 'credentials_entity` MODIFY COLUMN `data` MEDIUMTEXT NOT NULL'); + await queryRunner.query('ALTER TABLE `' + tablePrefix + 'credentials_entity` MODIFY COLUMN `type` varchar(128) NOT NULL'); } async down(queryRunner: QueryRunner): Promise { const tablePrefix = config.get('database.tablePrefix'); - await queryRunner.query('ALTER TABLE `' + tablePrefix + 'credentials_entity` MODIFY COLUMN `data` TEXT NOT NULL'); + await queryRunner.query('ALTER TABLE `' + tablePrefix + 'credentials_entity` MODIFY COLUMN `type` varchar(32) NOT NULL'); } } From 7bc79c879d96d577e77b0990dd3536451f88a59c Mon Sep 17 00:00:00 2001 From: Omar Ajoue Date: Wed, 12 May 2021 06:13:39 +0200 Subject: [PATCH 17/88] :pencil: Add database type and execution mode to issues template (#1762) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Added database type and execution mode to issues template * Bump n8n version suggestion Co-authored-by: Iván Ovejero * Bump node.js version as suggested Co-authored-by: Iván Ovejero Co-authored-by: Iván Ovejero --- .github/ISSUE_TEMPLATE/bug_report.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 98d3702d9c..9a150b04b1 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -22,8 +22,10 @@ A clear and concise description of what you expected to happen. **Environment (please complete the following information):** - OS: [e.g. Ubuntu Linux 18.04] - - n8n Version [e.g. 0.26.0] - - Node.js Version [e.g. 10.16.0] + - n8n Version [e.g. 0.119.0] + - Node.js Version [e.g. 14.16.0] + - Database system [e.g. SQLite; n8n uses SQLite as default otherwise changed] + - Operation mode [e.g. own; operation modes are `own`, `main` and `queue`. Default is `own`] **Additional context** Add any other context about the problem here. From c632f7982f5c3d40213c3ca8790a9f65bdad40e1 Mon Sep 17 00:00:00 2001 From: Ricardo Espinoza Date: Wed, 12 May 2021 00:20:40 -0400 Subject: [PATCH 18/88] :zap: Add parameter to include credentials in querystring (Woocommerce) (#1756) --- .../credentials/WooCommerceApi.credentials.ts | 9 +++++++++ .../nodes-base/nodes/WooCommerce/GenericFunctions.ts | 9 ++++++++- .../nodes-base/nodes/WooCommerce/WooCommerce.node.ts | 10 +++++----- 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/packages/nodes-base/credentials/WooCommerceApi.credentials.ts b/packages/nodes-base/credentials/WooCommerceApi.credentials.ts index aa3809f05c..9a95411cbf 100644 --- a/packages/nodes-base/credentials/WooCommerceApi.credentials.ts +++ b/packages/nodes-base/credentials/WooCommerceApi.credentials.ts @@ -27,5 +27,14 @@ export class WooCommerceApi implements ICredentialType { default: '', placeholder: 'https://example.com', }, + { + displayName: 'Include Credentials in Query', + name: 'includeCredentialsInQuery', + type: 'boolean' as NodePropertyTypes, + default: false, + description: `Occasionally, some servers may not parse the Authorization header correctly
+ (if you see a “Consumer key is missing” error when authenticating over SSL, you have a server issue).
+ In this case, you may provide the consumer key/secret as query string parameters instead.`, + }, ]; } diff --git a/packages/nodes-base/nodes/WooCommerce/GenericFunctions.ts b/packages/nodes-base/nodes/WooCommerce/GenericFunctions.ts index f08f6e272e..3e42c88505 100644 --- a/packages/nodes-base/nodes/WooCommerce/GenericFunctions.ts +++ b/packages/nodes-base/nodes/WooCommerce/GenericFunctions.ts @@ -9,6 +9,7 @@ import { ILoadOptionsFunctions, IWebhookFunctions, } from 'n8n-core'; + import { ICredentialDataDecryptedObject, IDataObject, @@ -36,6 +37,7 @@ export async function woocommerceApiRequest(this: IHookFunctions | IExecuteFunct if (credentials === undefined) { throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } + let options: OptionsWithUri = { auth: { user: credentials.consumerKey as string, @@ -47,11 +49,16 @@ export async function woocommerceApiRequest(this: IHookFunctions | IExecuteFunct uri: uri || `${credentials.url}/wp-json/wc/v3${resource}`, json: true, }; + + if (credentials.includeCredentialsInQuery === true) { + delete options.auth; + Object.assign(qs, { consumer_key: credentials.consumerKey, consumer_secret: credentials.consumerSecret }); + } + if (!Object.keys(body).length) { delete options.form; } options = Object.assign({}, options, option); - try { return await this.helpers.request!(options); } catch (error) { diff --git a/packages/nodes-base/nodes/WooCommerce/WooCommerce.node.ts b/packages/nodes-base/nodes/WooCommerce/WooCommerce.node.ts index 04c76fd4d0..025318bdcf 100644 --- a/packages/nodes-base/nodes/WooCommerce/WooCommerce.node.ts +++ b/packages/nodes-base/nodes/WooCommerce/WooCommerce.node.ts @@ -367,7 +367,7 @@ export class WooCommerce implements INodeType { //https://woocommerce.github.io/woocommerce-rest-api-docs/#retrieve-a-product if (operation === 'get') { const productId = this.getNodeParameter('productId', i) as string; - responseData = await woocommerceApiRequest.call(this,'GET', `/products/${productId}`, {}, qs); + responseData = await woocommerceApiRequest.call(this, 'GET', `/products/${productId}`, {}, qs); } //https://woocommerce.github.io/woocommerce-rest-api-docs/#list-all-products if (operation === 'getAll') { @@ -413,7 +413,7 @@ export class WooCommerce implements INodeType { qs.status = options.status as string; } if (options.stockStatus) { - qs.stock_status = options.stockStatus as string; + qs.stock_status = options.stockStatus as string; } if (options.tag) { qs.tag = options.tag as string; @@ -434,7 +434,7 @@ export class WooCommerce implements INodeType { //https://woocommerce.github.io/woocommerce-rest-api-docs/#delete-a-product if (operation === 'delete') { const productId = this.getNodeParameter('productId', i) as string; - responseData = await woocommerceApiRequest.call(this,'DELETE', `/products/${productId}`, {}, { force: true }); + responseData = await woocommerceApiRequest.call(this, 'DELETE', `/products/${productId}`, {}, { force: true }); } } if (resource === 'order') { @@ -583,7 +583,7 @@ export class WooCommerce implements INodeType { //https://woocommerce.github.io/woocommerce-rest-api-docs/#retrieve-an-order if (operation === 'get') { const orderId = this.getNodeParameter('orderId', i) as string; - responseData = await woocommerceApiRequest.call(this,'GET', `/orders/${orderId}`, {}, qs); + responseData = await woocommerceApiRequest.call(this, 'GET', `/orders/${orderId}`, {}, qs); } //https://woocommerce.github.io/woocommerce-rest-api-docs/#list-all-orders if (operation === 'getAll') { @@ -629,7 +629,7 @@ export class WooCommerce implements INodeType { //https://woocommerce.github.io/woocommerce-rest-api-docs/#delete-an-order if (operation === 'delete') { const orderId = this.getNodeParameter('orderId', i) as string; - responseData = await woocommerceApiRequest.call(this,'DELETE', `/orders/${orderId}`, {}, { force: true }); + responseData = await woocommerceApiRequest.call(this, 'DELETE', `/orders/${orderId}`, {}, { force: true }); } } if (Array.isArray(responseData)) { From c739a498f19668bf480b6b806d32bef4e84d3b2b Mon Sep 17 00:00:00 2001 From: Omar Ajoue Date: Wed, 12 May 2021 17:51:54 +0200 Subject: [PATCH 19/88] :bug: Fix ordering when auto refreshing (#1761) * Fix ordering when auto refreshing * Fix ordering for current executinos as well --- packages/cli/src/Server.ts | 1 + packages/editor-ui/src/components/ExecutionsList.vue | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/cli/src/Server.ts b/packages/cli/src/Server.ts index cbe43697f3..fb4523af3c 100644 --- a/packages/cli/src/Server.ts +++ b/packages/cli/src/Server.ts @@ -1732,6 +1732,7 @@ class App { } ); } + returnData.sort((a, b) => parseInt(b.id, 10) - parseInt(a.id, 10)); return returnData; } diff --git a/packages/editor-ui/src/components/ExecutionsList.vue b/packages/editor-ui/src/components/ExecutionsList.vue index d576c6898b..e0e129a737 100644 --- a/packages/editor-ui/src/components/ExecutionsList.vue +++ b/packages/editor-ui/src/components/ExecutionsList.vue @@ -438,7 +438,8 @@ export default mixins( this.$store.commit('setActiveExecutions', results[1]); - const alreadyPresentExecutionIds = this.finishedExecutions.map(exec => exec.id); + // execution IDs are typed as string, int conversion is necessary so we can order. + const alreadyPresentExecutionIds = this.finishedExecutions.map(exec => parseInt(exec.id, 10)); let lastId = 0; const gaps = [] as number[]; for(let i = results[0].results.length - 1; i >= 0; i--) { @@ -459,7 +460,7 @@ export default mixins( // Check new results from end to start // Add new items accordingly. - const executionIndex = alreadyPresentExecutionIds.indexOf(currentItem.id); + const executionIndex = alreadyPresentExecutionIds.indexOf(currentId); if (executionIndex !== -1) { // Execution that we received is already present. @@ -477,7 +478,7 @@ export default mixins( // Find the correct position to place this newcomer let j; for (j = this.finishedExecutions.length - 1; j >= 0; j--) { - if (currentItem.id < this.finishedExecutions[j].id) { + if (currentId < parseInt(this.finishedExecutions[j].id, 10)) { this.finishedExecutions.splice(j + 1, 0, currentItem); break; } From 6460ce3965514b44e850352040a00ba6cf66e65b Mon Sep 17 00:00:00 2001 From: MedAliMarz Date: Wed, 12 May 2021 20:48:48 +0200 Subject: [PATCH 20/88] :bug: Fix Clockify Trigger bug (#1778) * Fix the empty results bug * Fix return value --- .../nodes-base/nodes/Clockify/ClockifyTrigger.node.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/nodes-base/nodes/Clockify/ClockifyTrigger.node.ts b/packages/nodes-base/nodes/Clockify/ClockifyTrigger.node.ts index 9f6384ed70..fb4637e8f9 100644 --- a/packages/nodes-base/nodes/Clockify/ClockifyTrigger.node.ts +++ b/packages/nodes-base/nodes/Clockify/ClockifyTrigger.node.ts @@ -24,7 +24,7 @@ export class ClockifyTrigger implements INodeType { displayName: 'Clockify Trigger', icon: 'file:clockify.png', name: 'clockifyTrigger', - group: ['trigger'], + group: [ 'trigger' ], version: 1, description: 'Watches Clockify For Events', defaults: { @@ -32,7 +32,7 @@ export class ClockifyTrigger implements INodeType { color: '#000000', }, inputs: [], - outputs: ['main'], + outputs: [ 'main' ], credentials: [ { name: 'clockifyApi', @@ -109,7 +109,7 @@ export class ClockifyTrigger implements INodeType { qs.start = webhookData.lastTimeChecked; qs.end = moment().tz(workflowTimezone).format('YYYY-MM-DDTHH:mm:ss') + 'Z'; qs.hydrated = true; - qs['in-progress'] = false; + qs[ 'in-progress' ] = false; break; } @@ -117,8 +117,8 @@ export class ClockifyTrigger implements INodeType { webhookData.lastTimeChecked = qs.end; if (Array.isArray(result) && result.length !== 0) { - result = [this.helpers.returnJsonArray(result)]; + return [ this.helpers.returnJsonArray(result) ]; } - return result; + return null; } } From 779da62845430a09ebd442fc33a0687951a3bfe9 Mon Sep 17 00:00:00 2001 From: MedAliMarz Date: Wed, 12 May 2021 20:50:53 +0200 Subject: [PATCH 21/88] :zap: Enable sandbox env in Paddle (#1777) * Enable sandbox env in Paddle * Change sandbox option displayName --- packages/nodes-base/credentials/PaddleApi.credentials.ts | 6 ++++++ packages/nodes-base/nodes/Paddle/GenericFunctions.ts | 6 +++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/nodes-base/credentials/PaddleApi.credentials.ts b/packages/nodes-base/credentials/PaddleApi.credentials.ts index 76c0d5bfb3..5aba2bb032 100644 --- a/packages/nodes-base/credentials/PaddleApi.credentials.ts +++ b/packages/nodes-base/credentials/PaddleApi.credentials.ts @@ -20,5 +20,11 @@ export class PaddleApi implements ICredentialType { type: 'string' as NodePropertyTypes, default: '', }, + { + displayName: 'Use Sandbox environment API', + name: 'sandbox', + type: 'boolean' as NodePropertyTypes, + default: false, + }, ]; } diff --git a/packages/nodes-base/nodes/Paddle/GenericFunctions.ts b/packages/nodes-base/nodes/Paddle/GenericFunctions.ts index 0baed95f52..b169582b5e 100644 --- a/packages/nodes-base/nodes/Paddle/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Paddle/GenericFunctions.ts @@ -16,17 +16,21 @@ import { export async function paddleApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions | IWebhookFunctions, endpoint: string, method: string, body: any = {}, query?: IDataObject, uri?: string): Promise { // tslint:disable-line:no-any const credentials = this.getCredentials('paddleApi'); + const productionUrl = 'https://vendors.paddle.com/api'; + const sandboxUrl = 'https://sandbox-vendors.paddle.com/api'; if (credentials === undefined) { throw new NodeOperationError(this.getNode(), 'Could not retrieve credentials!'); } + const isSandbox = credentials.sandbox; + const options: OptionsWithUri = { method, headers: { 'content-type': 'application/json', }, - uri: `https://vendors.paddle.com/api${endpoint}`, + uri: `${isSandbox === true ? sandboxUrl : productionUrl}${endpoint}`, body, json: true, }; From b42e1dc366e051dfdd46c39d61bc1bc03ece7772 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ovejero?= Date: Wed, 12 May 2021 21:21:56 +0200 Subject: [PATCH 22/88] :bug: Fix parsing issues in Firestore node (#1759) * :zap: Fix empty document parsing * :zap: Add geopoint parsing * :zap: Fix date misdetection * :fire: Remove logging * :zap: Add ISO-8601 format to date validation --- .../Google/Firebase/CloudFirestore/GenericFunctions.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/nodes-base/nodes/Google/Firebase/CloudFirestore/GenericFunctions.ts b/packages/nodes-base/nodes/Google/Firebase/CloudFirestore/GenericFunctions.ts index 8200a95064..4c5501dbaf 100644 --- a/packages/nodes-base/nodes/Google/Firebase/CloudFirestore/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Google/Firebase/CloudFirestore/GenericFunctions.ts @@ -12,6 +12,8 @@ import { IDataObject, NodeApiError, } from 'n8n-workflow'; +import * as moment from 'moment-timezone'; + export async function googleApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri: string | null = null): Promise { // tslint:disable-line:no-any const options: OptionsWithUri = { @@ -58,6 +60,7 @@ export async function googleApiRequestAllItems(this: IExecuteFunctions | ILoadOp return returnData; } +const isValidDate = (str: string) => moment(str, ['YYYY-MM-DD HH:mm:ss Z', moment.ISO_8601], true).isValid(); // Both functions below were taken from Stack Overflow jsonToDocument was fixed as it was unable to handle null values correctly // https://stackoverflow.com/questions/62246410/how-to-convert-a-firestore-document-to-plain-json-and-vice-versa @@ -73,7 +76,7 @@ export function jsonToDocument(value: string | number | IDataObject | IDataObjec } else { return { 'integerValue': value }; } - } else if (Date.parse(value as string)) { + } else if (isValidDate(value as string)) { const date = new Date(Date.parse(value as string)); return { 'timestampValue': date.toISOString() }; } else if (typeof value === 'string') { @@ -108,13 +111,14 @@ export function fullDocumentToJson(data: IDataObject): IDataObject { export function documentToJson(fields: IDataObject): IDataObject { + if (fields === undefined) return {}; const result = {}; for (const f of Object.keys(fields)) { const key = f, value = fields[f], isDocumentType = ['stringValue', 'booleanValue', 'doubleValue', - 'integerValue', 'timestampValue', 'mapValue', 'arrayValue', 'nullValue'].find(t => t === key); + 'integerValue', 'timestampValue', 'mapValue', 'arrayValue', 'nullValue', 'geoPointValue'].find(t => t === key); if (isDocumentType) { - const item = ['stringValue', 'booleanValue', 'doubleValue', 'integerValue', 'timestampValue', 'nullValue'] + const item = ['stringValue', 'booleanValue', 'doubleValue', 'integerValue', 'timestampValue', 'nullValue', 'geoPointValue'] .find(t => t === key); if (item) { return value as IDataObject; From 1888f5c4a54af25fb95cf6d63a6251b88b01ee90 Mon Sep 17 00:00:00 2001 From: Harshil Agrawal Date: Wed, 12 May 2021 21:52:31 +0200 Subject: [PATCH 23/88] :books: Update documentation for npm package (#1770) --- packages/cli/README.md | 143 +++++++++++++++++++++++++++-------------- 1 file changed, 95 insertions(+), 48 deletions(-) diff --git a/packages/cli/README.md b/packages/cli/README.md index 61da7bcf35..d7aad52434 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -2,7 +2,7 @@ ![n8n.io - Workflow Automation](https://raw.githubusercontent.com/n8n-io/n8n/master/assets/n8n-logo.png) -n8n is a free and open [fair-code](http://faircode.io) distributed node based Workflow Automation Tool. It can be self-hosted, easily extended, and so also used with internal tools. +n8n is a free and open [fair-code](http://faircode.io) distributed node-based Workflow Automation Tool. You can self-host n8n, easily extend it, and even use it with internal tools. n8n.io - Screenshot @@ -11,98 +11,145 @@ n8n is a free and open [fair-code](http://faircode.io) distributed node based Wo - [Demo](#demo) +- [Getting Started](#getting-started) + - [Use npx](#use-npx) + - [Run with Docker](#run-with-docker) + - [Install with npm](#install-with-npm) + - [Sign-up on n8n.cloud](#sign-up-on-n8n.cloud) - [Available integrations](#available-integrations) - [Documentation](#documentation) - [Create Custom Nodes](#create-custom-nodes) -- [Hosted n8n](#hosted-n8n) +- [Contributing](#contributing) - [What does n8n mean and how do you pronounce it](#what-does-n8n-mean-and-how-do-you-pronounce-it) - [Support](#support) - [Jobs](#jobs) - [Upgrading](#upgrading) - [License](#license) -- [Development](#development) ## Demo -[:tv: A short demo (< 3 min)](https://www.youtube.com/watch?v=3w7xIMKLVAg) -which shows how to create a simple workflow which automatically sends a new -Slack notification every time a Github repository received or lost a star. +📺 Here's a [short demo (<3 min)](https://www.youtube.com/watch?v=3w7xIMKLVAg) that shows how to create a simple workflow to automatically sends a notification on Slack every time a GitHub repository gets starred or un-starred. +## Getting Started + +There are a couple of ways to get started with n8n. + +### Use npx + +To spin up n8n using npx, you can run: + +```bash +npx n8n +``` + +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. + +### Run with Docker + +To play around with n8n, you can also start it using Docker: + +```bash +docker run -it --rm \ + --name n8n \ + -p 5678:5678 \ + n8nio/n8n +``` + +Be aware that all the data will be lost once the Docker container gets removed. To persist the data mount the `~/.n8n` folder: + +```bash +docker run -it --rm \ + --name n8n \ + -p 5678:5678 \ + -v ~/.n8n:/home/node/.n8n \ + n8nio/n8n +``` + +n8n also offers a Docker image for Raspberry Pi: `n8nio/n8n:latest-rpi`. + +Refer to the [documentation](https://github.com/n8n-io/n8n/blob/master/docker/images/n8n/README.md) for more information on the Docker setup. + +### Install with npm + +To install n8n globally using npm: + +```bash +npm install n8n -g +``` + +After the installation, start n8n running the following command: + +```bash +n8n +# or +n8n start +``` + +### Sign-up on n8n.cloud + +Sign-up for an [n8n.cloud](https://www.n8n.cloud/) account. + +While n8n.cloud and n8n are the same in terms of features, n8n.cloud provides certain conveniences such as: +- Not having to set up and maintain your n8n instance +- Managed OAuth for authentication +- Easily upgrading to the newer n8n versions ## Available integrations -n8n has 200+ different nodes to automate workflows. The list can be found on: [https://n8n.io/nodes](https://n8n.io/nodes) - +n8n has 280+ different nodes that allow you to connect various services and build your automation workflows. You can find the list of all the integrations at [https://n8n.io/integrations](https://n8n.io/integrations) ## Documentation -The official n8n documentation can be found under: [https://docs.n8n.io](https://docs.n8n.io) +To learn more about n8n, refer to the official documentation here: [https://docs.n8n.io](https://docs.n8n.io) -Additional information and example workflows on the n8n.io website: [https://n8n.io](https://n8n.io) +You can find additional information and example workflows on the [n8n.io](https://n8n.io) website. ## Create Custom Nodes -It is very easy to create own nodes for n8n. More information about that can -be found in the documentation of "n8n-node-dev" which is a small CLI which -helps with n8n-node-development. +You can create custom nodes for n8n. Follow the instructions mentioned in the documentation to create your node: [Creating nodes](https://docs.n8n.io/nodes/creating-nodes/create-node.html) -[To n8n-node-dev](https://github.com/n8n-io/n8n/tree/master/packages/node-dev) +## Contributing -Additional information can be found on the [ documentation page](https://docs.n8n.io/#/create-node). +🐛 Did you find a bug? +✨ Do you want to contribute a feature? -## Hosted n8n +The [CONTRIBUTING guide](https://github.com/n8n-io/n8n/blob/master/CONTRIBUTING.md) will help you set up your development environment. -If you are interested in a hosted version of n8n on our infrastructure please contact us via: -[hosting@n8n.io](mailto:hosting@n8n.io) +You can find more information on how you can contribute to the project on our documentation: [How can I contribute?](https://docs.n8n.io/reference/contributing.html) +## What does n8n mean, and how do you pronounce it? +**Short answer:** n8n is an abbreviation for "nodemation", and it is pronounced as n-eight-n. -## What does n8n mean and how do you pronounce it? - -**Short answer:** It means "nodemation" and it is pronounced as n-eight-n. - -**Long answer:** I get that question quite often (more often than I expected) -so I decided it is probably best to answer it here. While looking for a -good name for the project with a free domain I realized very quickly that all the -good ones I could think of were already taken. So, in the end, I chose -nodemation. "node-" in the sense that it uses a Node-View and that it uses -Node.js and "-mation" for "automation" which is what the project is supposed to help with. -However, I did not like how long the name was and I could not imagine writing -something that long every time in the CLI. That is when I then ended up on -"n8n". Sure does not work perfectly but does neither for Kubernetes (k8s) and -did not hear anybody complain there. So I guess it should be ok. +**Long answer:** In n8n, you build your automation ("-mation") workflows by connecting different nodes in the Editor UI. The project is also built using Node.js. As a consequence, the project was named nodemation. +However, the name was long, and it wouldn't be a good idea to use such a long name in the CLI. Hence, nodemation got abbreviated as "n8n" (there are eight characters between the first and the last n!). ## Support -If you have problems or questions go to our forum, we will then try to help you asap: - -[https://community.n8n.io](https://community.n8n.io) - - +If you run into issues or have any questions reach out to us via our community forum: [https://community.n8n.io](https://community.n8n.io). ## Jobs -If you are interested in working for n8n and so shape the future of the project -check out our [job posts](https://apply.workable.com/n8n/) +If you are interested in working at n8n and building the project, check out the [job openings](https://apply.workable.com/n8n/). ## Upgrading -Before you upgrade to the latest version make sure to check here if there are any breaking changes which concern you: -[Breaking Changes](https://github.com/n8n-io/n8n/blob/master/packages/cli/BREAKING-CHANGES.md) +Before you upgrade to the latest version, make sure to check the changelogs: [Changelog](https://docs.n8n.io/reference/changelog.html) +You can also find breaking changes here: [Breaking Changes](./BREAKING-CHANGES.md) ## License -n8n is [fair-code](http://faircode.io) distributed under [**Apache 2.0 with Commons Clause**](https://github.com/n8n-io/n8n/blob/master/packages/cli/LICENSE.md) license +n8n is [fair-code](http://faircode.io) distributed under [**Apache 2.0 with Commons Clause**](https://github.com/n8n-io/n8n/blob/master/packages/cli/LICENSE.md) license. -Additional information about license can be found in the [FAQ](https://docs.n8n.io/#/faq?id=license) - - -## Development - -Have you found a bug :bug: ? Or maybe you have a nice feature :sparkles: to contribute ? The [CONTRIBUTING guide](https://github.com/n8n-io/n8n/blob/master/CONTRIBUTING.md) will help you get your development environment ready in minutes. +Additional information on the license can be found in the [FAQ](https://docs.n8n.io/reference/faq.html#license) From 40510a7ab4ceba46d6c96ae9e65dac02b4d231b1 Mon Sep 17 00:00:00 2001 From: DeskYT <54146274+DeskYT@users.noreply.github.com> Date: Thu, 13 May 2021 01:21:18 +0300 Subject: [PATCH 24/88] :zap: Fix hubspotApiRequestAllItems for some (#1781) --- packages/nodes-base/nodes/Hubspot/GenericFunctions.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/nodes-base/nodes/Hubspot/GenericFunctions.ts b/packages/nodes-base/nodes/Hubspot/GenericFunctions.ts index dd98e667da..352e2da88b 100644 --- a/packages/nodes-base/nodes/Hubspot/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Hubspot/GenericFunctions.ts @@ -76,9 +76,7 @@ export async function hubspotApiRequestAllItems(this: IHookFunctions | IExecuteF return returnData; } } while ( - responseData['has-more'] !== undefined && - responseData['has-more'] !== null && - responseData['has-more'] !== false + responseData['hasMore'] || responseData['has-more'] ); return returnData; } From bbf7c87c0414efa8ac9c54cf65f21b1bfba2e45f Mon Sep 17 00:00:00 2001 From: Snyk bot Date: Thu, 13 May 2021 01:38:00 +0300 Subject: [PATCH 25/88] :arrow_up: Set amqplib@0.7.1 on n8n-nodes-base Snyk has created this PR to upgrade amqplib from 0.6.0 to 0.7.1. See this package in npm: https://www.npmjs.com/package/amqplib See this project in Snyk: https://app.snyk.io/org/janober/project/a08454f4-33a1-49bc-bb2a-f31792e94f42?utm_source=github&utm_medium=upgrade-pr --- 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 4e2d4bd19f..fac858637f 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -595,7 +595,7 @@ "@types/lossless-json": "^1.0.0", "@types/promise-ftp": "^1.3.4", "@types/snowflake-sdk": "^1.5.1", - "amqplib": "^0.6.0", + "amqplib": "^0.7.1", "aws4": "^1.8.0", "basic-auth": "^2.0.1", "change-case": "^4.1.1", From 744dd1fda6ffa0b7ccbb6ac304176df03c5a6641 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Wed, 12 May 2021 18:00:46 -0500 Subject: [PATCH 26/88] :zap: Improved logging and wait for workflow deactivate --- packages/cli/src/ActiveWorkflowRunner.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/cli/src/ActiveWorkflowRunner.ts b/packages/cli/src/ActiveWorkflowRunner.ts index 9f9b9f3580..c0ae7c76be 100644 --- a/packages/cli/src/ActiveWorkflowRunner.ts +++ b/packages/cli/src/ActiveWorkflowRunner.ts @@ -517,8 +517,8 @@ export class ActiveWorkflowRunner { if (workflowInstance.getTriggerNodes().length !== 0 || workflowInstance.getPollNodes().length !== 0) { - await this.activeWorkflows.add(workflowId, workflowInstance, additionalData, mode, activation, getTriggerFunctions, getPollFunctions); - Logger.info(`Successfully activated workflow "${workflowData.name}"`); + await this.activeWorkflows.add(workflowId, workflowInstance, additionalData, mode, activation, getTriggerFunctions, getPollFunctions); + Logger.verbose(`Successfully activated workflow "${workflowData.name}"`, { workflowId, workflowName: workflowData.name }); } if (this.activationErrors[workflowId] !== undefined) { @@ -569,7 +569,8 @@ export class ActiveWorkflowRunner { // if it's active in memory then it's a trigger // so remove from list of actives workflows if (this.activeWorkflows.isActive(workflowId)) { - this.activeWorkflows.remove(workflowId); + await this.activeWorkflows.remove(workflowId); + Logger.verbose(`Successfully deactivated workflow "${workflowId}"`, { workflowId }); } return; From 4801510b0cbc86390f8e3ca8eef008c2880f14ec Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Wed, 12 May 2021 18:01:12 -0500 Subject: [PATCH 27/88] :zap: Minor improvements --- packages/cli/src/ActiveWorkflowRunner.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/cli/src/ActiveWorkflowRunner.ts b/packages/cli/src/ActiveWorkflowRunner.ts index c0ae7c76be..60b10b5b08 100644 --- a/packages/cli/src/ActiveWorkflowRunner.ts +++ b/packages/cli/src/ActiveWorkflowRunner.ts @@ -35,7 +35,7 @@ import { } from 'n8n-workflow'; import * as express from 'express'; -import { +import { LoggerProxy as Logger, } from 'n8n-workflow'; @@ -67,15 +67,15 @@ export class ActiveWorkflowRunner { for (const workflowData of workflowsData) { console.log(` - ${workflowData.name}`); - Logger.debug(`Initializing active workflow "${workflowData.name}" (startup)`, {workflowName: workflowData.name, workflowId: workflowData.id}); + Logger.debug(`Initializing active workflow "${workflowData.name}" (startup)`, { workflowName: workflowData.name, workflowId: workflowData.id }); try { await this.add(workflowData.id.toString(), 'init', workflowData); - Logger.verbose(`Successfully started workflow "${workflowData.name}"`, {workflowName: workflowData.name, workflowId: workflowData.id}); + Logger.verbose(`Successfully started workflow "${workflowData.name}"`, { workflowName: workflowData.name, workflowId: workflowData.id }); console.log(` => Started`); } catch (error) { console.log(` => ERROR: Workflow could not be activated:`); console.log(` ${error.message}`); - Logger.error(`Unable to initialize workflow "${workflowData.name}" (startup)`, {workflowName: workflowData.name, workflowId: workflowData.id}); + Logger.error(`Unable to initialize workflow "${workflowData.name}" (startup)`, { workflowName: workflowData.name, workflowId: workflowData.id }); } } Logger.verbose('Finished initializing active workflows (startup)'); @@ -188,7 +188,7 @@ export class ActiveWorkflowRunner { } const nodeTypes = NodeTypes(); - const workflow = new Workflow({ id: webhook.workflowId.toString(), name: workflowData.name, nodes: workflowData.nodes, connections: workflowData.connections, active: workflowData.active, nodeTypes, staticData: workflowData.staticData, settings: workflowData.settings}); + const workflow = new Workflow({ id: webhook.workflowId.toString(), name: workflowData.name, nodes: workflowData.nodes, connections: workflowData.connections, active: workflowData.active, nodeTypes, staticData: workflowData.staticData, settings: workflowData.settings }); const credentials = await WorkflowCredentials([workflow.getNode(webhook.node as string) as INode]); @@ -225,8 +225,8 @@ export class ActiveWorkflowRunner { * @returns {Promise} * @memberof ActiveWorkflowRunner */ - async getWebhookMethods(path: string) : Promise { - const webhooks = await Db.collections.Webhook?.find({ webhookPath: path}) as IWebhookDb[]; + async getWebhookMethods(path: string): Promise { + const webhooks = await Db.collections.Webhook?.find({ webhookPath: path }) as IWebhookDb[]; // Gather all request methods in string array const webhookMethods: string[] = webhooks.map(webhook => webhook.method); @@ -463,7 +463,7 @@ export class ActiveWorkflowRunner { * @returns {IGetExecuteTriggerFunctions} * @memberof ActiveWorkflowRunner */ - getExecuteTriggerFunctions(workflowData: IWorkflowDb, additionalData: IWorkflowExecuteAdditionalDataWorkflow, mode: WorkflowExecuteMode, activation: WorkflowActivateMode): IGetExecuteTriggerFunctions{ + getExecuteTriggerFunctions(workflowData: IWorkflowDb, additionalData: IWorkflowExecuteAdditionalDataWorkflow, mode: WorkflowExecuteMode, activation: WorkflowActivateMode): IGetExecuteTriggerFunctions { return ((workflow: Workflow, node: INode) => { const returnFunctions = NodeExecuteFunctions.getExecuteTriggerFunctions(workflow, node, additionalData, mode, activation); returnFunctions.emit = (data: INodeExecutionData[][]): void => { From 6cf5e3c974137e5392a8fef24d9ac484a8f4dc36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ovejero?= Date: Fri, 14 May 2021 21:17:39 +0200 Subject: [PATCH 28/88] :art: Replace Zulip logo (#1790) --- packages/nodes-base/nodes/Zulip/Zulip.node.ts | 2 +- packages/nodes-base/nodes/Zulip/zulip.png | Bin 2389 -> 0 bytes packages/nodes-base/nodes/Zulip/zulip.svg | 12 ++++++++++++ 3 files changed, 13 insertions(+), 1 deletion(-) delete mode 100644 packages/nodes-base/nodes/Zulip/zulip.png create mode 100644 packages/nodes-base/nodes/Zulip/zulip.svg diff --git a/packages/nodes-base/nodes/Zulip/Zulip.node.ts b/packages/nodes-base/nodes/Zulip/Zulip.node.ts index 15eadc4861..86b5af80bf 100644 --- a/packages/nodes-base/nodes/Zulip/Zulip.node.ts +++ b/packages/nodes-base/nodes/Zulip/Zulip.node.ts @@ -41,7 +41,7 @@ export class Zulip implements INodeType { description: INodeTypeDescription = { displayName: 'Zulip', name: 'zulip', - icon: 'file:zulip.png', + icon: 'file:zulip.svg', group: ['output'], version: 1, subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', diff --git a/packages/nodes-base/nodes/Zulip/zulip.png b/packages/nodes-base/nodes/Zulip/zulip.png deleted file mode 100644 index 77b3d0411ba26cba02e94df6f59e261c008644dd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2389 zcmW+%2{_c-8$OdESLk1Ei6LX``w}%>U0EmFFd@4LLo=mpqfl{WVv4L8DKsSw$&77~ zC9;ftnK47zw`jX=A+DPG|J?8S&iT%Hzwdj`d!FY!&!ONPPf3W$iU9y5Y;CNZp?G3v zhzji>C=D|Jc5UMvT&ZxImOc}4NVr;CE-w)N-K!k4w zUyQJKG;r9JV*`*ww6#+l*4$3EYGSa0)Af9~3j!eq2&owDVUF!+hSXL)=v4N zg+{Oiif9Hx^=l_bt}&>}6c;5jPUD7C+vnN3nbAkxFJqo($I>#aB0aRC?9?MpXjW^EF2Xn5g=g0K5wD zK>%wQumfFdOJ$r+b3D$3;HY@rPVI`VPN)q?#T+?@0m;^?m#x$T%}_z6>VBpOoF4E} z0{%$gCIy`L6!z5Ty{<~Cco<%MCo(t1?=I=gUxcIg0<5lhDW<#Y-gdXVZfkBXFE61|BP0F%lto0OR%ak%v)fu3 zj2nIo2YrRbS}*W4n-S4}dr+|V zKH%yuGBnSO$SVgM)yIt4y|qa@{W_#CN2n%qU%oRwzjj;tEE4$i9xFE(w>hwTfAt%~ z8zU$)JM7hw!%M9LAb67W?Aoz=xuRuOqBNwqa3Ri4)}Y?u`0||E za_oH$ig|ijVfI8+J{&1q$wOFuO$<{{gxzuu$THI|zTtf=ZJbClGVctVp79rNS0)Qt z65S(5y6f(;Q2xIpE%OoR>S&V9lNibKz~k`$B&~}=;U@1hwqfT7+n!%2EA58S*`6!m znDXD(hOuDLGfQVF(V`fspi0{|5yl|rUCt$}3GwUj=2AIXG& zT%3f{eA`(?UhCgDcMINH89&WIee9jXBgNA8z(ytZgfx5B zYB!gS(WQGtwImm=O9pgwq%EsAB11Ue&Utc5!WJ(spa(Tw_)Ln)5;L{ke|D zD^#}Rgdt%*k&2>FNj?Akt^H`$LVy;W&>sxkJGoBysC$avlw%P1&5QbBAs~4#Lga-z zVqa>wicg--K$;4CTHmqY-|#8pbL_7Xd-GNI?lxlHP|QMl>_=D2Bz!EjUtdcMFAST- z(ZAqm7mj_JfW1$bI_VRmzF8$H2P-M}BEZ-Pks6N>cP-j}(%aNKMe^=fBwjU52yZ}aXSWl#2GGtSCJ3v%pG8wIPH<))hYM87;_Sc(`dZXj1C zcrd>)lKqn#p%Ij+nY0;Xob~;WCxTQh%-<~EJ7Py3$eXYWPp@p{>B*hWnp6l;H9GF112Mxl5^7Xf$ zM^sgFpNVrmZroB)JNk{?fr4|-9th0@Zqg^?CUcf<&d;xxHXv*%4!0juTWnVPvY*}h zG-nP9a|?}dw!Iwz_0z3F`rBWNFN<>ypZvZ|U9D@;@hglDdD2SHX-s-79V-4dSDE}} z;*s9)z|B2lqK3f|{bSmTsVrq3ahngtp>ZA_u2E6_^N2=0)zImt;~E+SZw~9Y(t)tR z;nxX=Ek|EgPAumuzS`=lJfP(!_h;;nt>%aAulQQ0*B>9UfUV>R7EP0nYm31dordv0 ze|)-lfk^yNMGhy(s3+k7=P?zjD;FVoLbYpGgh_N6471s$u{qRZprRA|o|e8wH~wDL ze`$O%are_H&+#FAnnlssOJ-N+r6rq-s1F~CN0`JhcrSx`3_T(aPRA3AsR$>UWgoZY zUB$rB>wlztmSMXs6`(U8WM>ze>I%%g;u|=1!?gW(BD|T=^6E?}3v-Huuxx=Je(1#z zU^-E(JLNxXT09eZI^{CyyWxw^v47uLFzse$P;&t1e2X>jCn52>rR9Q?*2WB0?mSXJ yP!JG&1cdtn0$*@kb&F*7<=6SeU$lP4!tCY1f+nn7JX2xkr?9nlw5qW1Py9cZf1I5F diff --git a/packages/nodes-base/nodes/Zulip/zulip.svg b/packages/nodes-base/nodes/Zulip/zulip.svg new file mode 100644 index 0000000000..82b59ed82c --- /dev/null +++ b/packages/nodes-base/nodes/Zulip/zulip.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file From 1aa0a5cc8d6e5154aa4c9604b7f7284773c6d2ee Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 14 May 2021 18:16:48 -0500 Subject: [PATCH 29/88] :zap: Make it possible to read sibling parameter --- packages/core/src/NodeExecuteFunctions.ts | 8 +++--- .../src/components/VariableSelector.vue | 2 +- packages/workflow/src/Expression.ts | 14 +++++----- packages/workflow/src/WorkflowDataProxy.ts | 28 ++++++++++++++----- 4 files changed, 33 insertions(+), 19 deletions(-) diff --git a/packages/core/src/NodeExecuteFunctions.ts b/packages/core/src/NodeExecuteFunctions.ts index aeeb39c810..726526afc1 100644 --- a/packages/core/src/NodeExecuteFunctions.ts +++ b/packages/core/src/NodeExecuteFunctions.ts @@ -691,7 +691,7 @@ export function getExecuteFunctions(workflow: Workflow, runExecutionData: IRunEx return continueOnFail(node); }, evaluateExpression: (expression: string, itemIndex: number) => { - return workflow.expression.resolveSimpleParameterValue('=' + expression, runExecutionData, runIndex, itemIndex, node.name, connectionInputData, mode); + return workflow.expression.resolveSimpleParameterValue('=' + expression, {}, runExecutionData, runIndex, itemIndex, node.name, connectionInputData, mode); }, async executeWorkflow(workflowInfo: IExecuteWorkflowInfo, inputData?: INodeExecutionData[]): Promise { // tslint:disable-line:no-any return additionalData.executeWorkflow(workflowInfo, additionalData, inputData); @@ -742,7 +742,7 @@ export function getExecuteFunctions(workflow: Workflow, runExecutionData: IRunEx return getWorkflowMetadata(workflow); }, getWorkflowDataProxy: (itemIndex: number): IWorkflowDataProxyData => { - const dataProxy = new WorkflowDataProxy(workflow, runExecutionData, runIndex, itemIndex, node.name, connectionInputData, mode); + const dataProxy = new WorkflowDataProxy(workflow, runExecutionData, runIndex, itemIndex, node.name, connectionInputData, {}, mode); return dataProxy.getDataProxy(); }, getWorkflowStaticData(type: string): IDataObject { @@ -789,7 +789,7 @@ export function getExecuteSingleFunctions(workflow: Workflow, runExecutionData: }, evaluateExpression: (expression: string, evaluateItemIndex: number | undefined) => { evaluateItemIndex = evaluateItemIndex === undefined ? itemIndex : evaluateItemIndex; - return workflow.expression.resolveSimpleParameterValue('=' + expression, runExecutionData, runIndex, evaluateItemIndex, node.name, connectionInputData, mode); + return workflow.expression.resolveSimpleParameterValue('=' + expression, {}, runExecutionData, runIndex, evaluateItemIndex, node.name, connectionInputData, mode); }, getContext(type: string): IContextObject { return NodeHelpers.getContext(runExecutionData, type, node); @@ -841,7 +841,7 @@ export function getExecuteSingleFunctions(workflow: Workflow, runExecutionData: return getWorkflowMetadata(workflow); }, getWorkflowDataProxy: (): IWorkflowDataProxyData => { - const dataProxy = new WorkflowDataProxy(workflow, runExecutionData, runIndex, itemIndex, node.name, connectionInputData, mode); + const dataProxy = new WorkflowDataProxy(workflow, runExecutionData, runIndex, itemIndex, node.name, connectionInputData, {}, mode); return dataProxy.getDataProxy(); }, getWorkflowStaticData(type: string): IDataObject { diff --git a/packages/editor-ui/src/components/VariableSelector.vue b/packages/editor-ui/src/components/VariableSelector.vue index c4d031859a..2ddb7458b9 100644 --- a/packages/editor-ui/src/components/VariableSelector.vue +++ b/packages/editor-ui/src/components/VariableSelector.vue @@ -379,7 +379,7 @@ export default mixins( return returnData; } - const dataProxy = new WorkflowDataProxy(workflow, runExecutionData, runIndex, itemIndex, nodeName, connectionInputData, 'manual'); + const dataProxy = new WorkflowDataProxy(workflow, runExecutionData, runIndex, itemIndex, nodeName, connectionInputData, {}, 'manual'); const proxy = dataProxy.getDataProxy(); // @ts-ignore diff --git a/packages/workflow/src/Expression.ts b/packages/workflow/src/Expression.ts index 0cfc80e2a2..c653422308 100644 --- a/packages/workflow/src/Expression.ts +++ b/packages/workflow/src/Expression.ts @@ -59,7 +59,7 @@ export class Expression { * @returns {(NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[])} * @memberof Workflow */ - resolveSimpleParameterValue(parameterValue: NodeParameterValue, runExecutionData: IRunExecutionData | null, runIndex: number, itemIndex: number, activeNodeName: string, connectionInputData: INodeExecutionData[], mode: WorkflowExecuteMode, returnObjectAsString = false, selfData = {}): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] { + resolveSimpleParameterValue(parameterValue: NodeParameterValue, siblingParameters: INodeParameters, runExecutionData: IRunExecutionData | null, runIndex: number, itemIndex: number, activeNodeName: string, connectionInputData: INodeExecutionData[], mode: WorkflowExecuteMode, returnObjectAsString = false, selfData = {}): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] { // Check if it is an expression if (typeof parameterValue !== 'string' || parameterValue.charAt(0) !== '=') { // Is no expression so return value @@ -72,7 +72,7 @@ export class Expression { parameterValue = parameterValue.substr(1); // Generate a data proxy which allows to query workflow data - const dataProxy = new WorkflowDataProxy(this.workflow, runExecutionData, runIndex, itemIndex, activeNodeName, connectionInputData, mode, -1, selfData); + const dataProxy = new WorkflowDataProxy(this.workflow, runExecutionData, runIndex, itemIndex, activeNodeName, connectionInputData, siblingParameters, mode, -1, selfData); const data = dataProxy.getDataProxy(); // Execute the expression @@ -179,17 +179,17 @@ export class Expression { }; // Helper function which resolves a parameter value depending on if it is simply or not - const resolveParameterValue = (value: NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[]) => { + const resolveParameterValue = (value: NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[], siblingParameters: INodeParameters) => { if (isComplexParameter(value)) { return this.getParameterValue(value, runExecutionData, runIndex, itemIndex, activeNodeName, connectionInputData, mode, returnObjectAsString, selfData); } else { - return this.resolveSimpleParameterValue(value as NodeParameterValue, runExecutionData, runIndex, itemIndex, activeNodeName, connectionInputData, mode, returnObjectAsString, selfData); + return this.resolveSimpleParameterValue(value as NodeParameterValue, siblingParameters, runExecutionData, runIndex, itemIndex, activeNodeName, connectionInputData, mode, returnObjectAsString, selfData); } }; // Check if it value is a simple one that we can get it resolved directly if (!isComplexParameter(parameterValue)) { - return this.resolveSimpleParameterValue(parameterValue as NodeParameterValue, runExecutionData, runIndex, itemIndex, activeNodeName, connectionInputData, mode, returnObjectAsString, selfData); + return this.resolveSimpleParameterValue(parameterValue as NodeParameterValue, {}, runExecutionData, runIndex, itemIndex, activeNodeName, connectionInputData, mode, returnObjectAsString, selfData); } // The parameter value is complex so resolve depending on type @@ -198,7 +198,7 @@ export class Expression { // Data is an array const returnData = []; for (const item of parameterValue) { - returnData.push(resolveParameterValue(item)); + returnData.push(resolveParameterValue(item, {})); } if (returnObjectAsString === true && typeof returnData === 'object') { @@ -212,7 +212,7 @@ export class Expression { // Data is an object const returnData: INodeParameters = {}; for (const key of Object.keys(parameterValue)) { - returnData[key] = resolveParameterValue((parameterValue as INodeParameters)[key]); + returnData[key] = resolveParameterValue((parameterValue as INodeParameters)[key], parameterValue as INodeParameters); } if (returnObjectAsString === true && typeof returnData === 'object') { diff --git a/packages/workflow/src/WorkflowDataProxy.ts b/packages/workflow/src/WorkflowDataProxy.ts index b81a0263f0..562a4897f6 100644 --- a/packages/workflow/src/WorkflowDataProxy.ts +++ b/packages/workflow/src/WorkflowDataProxy.ts @@ -1,9 +1,11 @@ import { IDataObject, INodeExecutionData, + INodeParameters, IRunExecutionData, IWorkflowDataProxyData, NodeHelpers, + NodeParameterValue, Workflow, WorkflowExecuteMode, } from './'; @@ -18,12 +20,13 @@ export class WorkflowDataProxy { private itemIndex: number; private activeNodeName: string; private connectionInputData: INodeExecutionData[]; + private siblingParameters: INodeParameters; private mode: WorkflowExecuteMode; private selfData: IDataObject; - constructor(workflow: Workflow, runExecutionData: IRunExecutionData | null, runIndex: number, itemIndex: number, activeNodeName: string, connectionInputData: INodeExecutionData[], mode: WorkflowExecuteMode, defaultReturnRunIndex = -1, selfData = {}) { + constructor(workflow: Workflow, runExecutionData: IRunExecutionData | null, runIndex: number, itemIndex: number, activeNodeName: string, connectionInputData: INodeExecutionData[], siblingParameters: INodeParameters, mode: WorkflowExecuteMode, defaultReturnRunIndex = -1, selfData = {}) { this.workflow = workflow; this.runExecutionData = runExecutionData; this.defaultReturnRunIndex = defaultReturnRunIndex; @@ -31,6 +34,7 @@ export class WorkflowDataProxy { this.itemIndex = itemIndex; this.activeNodeName = activeNodeName; this.connectionInputData = connectionInputData; + this.siblingParameters = siblingParameters; this.mode = mode; this.selfData = selfData; } @@ -108,12 +112,22 @@ export class WorkflowDataProxy { get(target, name, receiver) { name = name.toString(); - if (!node.parameters.hasOwnProperty(name)) { - // Parameter does not exist on node - throw new Error(`Could not find parameter "${name}" on node "${nodeName}"`); - } + let returnValue: INodeParameters | NodeParameterValue | NodeParameterValue[] | INodeParameters[]; + if (name[0] === '/') { + const key = name.slice(1); + if (!that.siblingParameters.hasOwnProperty(key)) { + throw new Error(`Could not find sibling parameter "${key}" on node "${nodeName}"`); - const returnValue = node.parameters[name]; + } + returnValue = that.siblingParameters[key]; + } else { + if (!node.parameters.hasOwnProperty(name)) { + // Parameter does not exist on node + throw new Error(`Could not find parameter "${name}" on node "${nodeName}"`); + } + + returnValue = node.parameters[name]; + } if (typeof returnValue === 'string' && returnValue.charAt(0) === '=') { // The found value is an expression so resolve it @@ -361,7 +375,7 @@ export class WorkflowDataProxy { }, $item: (itemIndex: number, runIndex?: number) => { const defaultReturnRunIndex = runIndex === undefined ? -1 : runIndex; - const dataProxy = new WorkflowDataProxy(this.workflow, this.runExecutionData, this.runIndex, itemIndex, this.activeNodeName, this.connectionInputData, that.mode, defaultReturnRunIndex); + const dataProxy = new WorkflowDataProxy(this.workflow, this.runExecutionData, this.runIndex, itemIndex, this.activeNodeName, this.connectionInputData, that.siblingParameters, that.mode, defaultReturnRunIndex); return dataProxy.getDataProxy(); }, $items: (nodeName?: string, outputIndex?: number, runIndex?: number) => { From 05dc0c327e63957b7e2c4c11e140f9a5bd30c88f Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 14 May 2021 18:20:21 -0500 Subject: [PATCH 30/88] :zap: Change used symbol for sibling parameter --- packages/workflow/src/WorkflowDataProxy.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/workflow/src/WorkflowDataProxy.ts b/packages/workflow/src/WorkflowDataProxy.ts index 562a4897f6..fe52de7a07 100644 --- a/packages/workflow/src/WorkflowDataProxy.ts +++ b/packages/workflow/src/WorkflowDataProxy.ts @@ -113,7 +113,7 @@ export class WorkflowDataProxy { name = name.toString(); let returnValue: INodeParameters | NodeParameterValue | NodeParameterValue[] | INodeParameters[]; - if (name[0] === '/') { + if (name[0] === '&') { const key = name.slice(1); if (!that.siblingParameters.hasOwnProperty(key)) { throw new Error(`Could not find sibling parameter "${key}" on node "${nodeName}"`); From cb2887a8ab6a7b6e3ae23fdf8bdffda30a5142d8 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sat, 15 May 2021 00:28:51 -0500 Subject: [PATCH 31/88] :bug: Fix issue that expressions did not resolve for displayOptions --- .../src/components/ParameterInput.vue | 2 +- .../src/components/ParameterInputList.vue | 40 +++++++++++++++++-- .../src/components/mixins/workflowHelpers.ts | 15 ++++++- 3 files changed, 51 insertions(+), 6 deletions(-) diff --git a/packages/editor-ui/src/components/ParameterInput.vue b/packages/editor-ui/src/components/ParameterInput.vue index 225859590f..25a68a8173 100644 --- a/packages/editor-ui/src/components/ParameterInput.vue +++ b/packages/editor-ui/src/components/ParameterInput.vue @@ -466,7 +466,7 @@ export default mixins( } else if (typeof nodeParameters[key] === 'object') { returnData[key] = this.getResolveNodeParameters(nodeParameters[key] as INodeParameters); } else { - returnData[key] = this.resolveExpression(nodeParameters[key] as string); + returnData[key] = this.resolveExpression(nodeParameters[key] as string, nodeParameters); } } return returnData; diff --git a/packages/editor-ui/src/components/ParameterInputList.vue b/packages/editor-ui/src/components/ParameterInputList.vue index 939452e3a0..c34c0c94f4 100644 --- a/packages/editor-ui/src/components/ParameterInputList.vue +++ b/packages/editor-ui/src/components/ParameterInputList.vue @@ -86,7 +86,7 @@ import { IUpdateInformation } from '@/Interface'; import MultipleParameter from '@/components/MultipleParameter.vue'; import { genericHelpers } from '@/components/mixins/genericHelpers'; -import { nodeHelpers } from '@/components/mixins/nodeHelpers'; +import { workflowHelpers } from '@/components/mixins/workflowHelpers'; import ParameterInputFull from '@/components/ParameterInputFull.vue'; import { get } from 'lodash'; @@ -95,7 +95,7 @@ import mixins from 'vue-typed-mixins'; export default mixins( genericHelpers, - nodeHelpers, + workflowHelpers, ) .extend({ name: 'ParameterInputList', @@ -157,7 +157,41 @@ export default mixins( // If it is not defined no need to do a proper check return true; } - return this.displayParameter(this.nodeValues, parameter, this.path); + + const nodeValues = {}; + let rawValues = this.nodeValues; + if (this.path) { + rawValues = get(this.nodeValues, this.path); + } + + // Resolve expressions + const resolveKeys = Object.keys(rawValues); + let key: string; + let i = 0; + do { + key = resolveKeys.shift(); + if (typeof rawValues[key] === 'string' && rawValues[key].charAt(0) === '=') { + // Contains an expression that + if (rawValues[key].includes('$parameter') && resolveKeys.some(parameterName => rawValues[key].includes(parameterName))) { + // Contains probably an expression of a missing parameter so skip + resolveKeys.push(key); + continue; + } else { + // Contains probably no expression with a missing parameter so resolve + nodeValues[key] = this.resolveExpression(rawValues[key], nodeValues); + } + } else { + // Does not contain an expression, add directly + nodeValues[key] = rawValues[key]; + } + // TODO: Think about how to calculate this best + if (i++ > 50) { + // Make sure we do not get caught + break; + } + } while(resolveKeys.length !== 0); + + return this.displayParameter(nodeValues, parameter, ''); }, valueChanged (parameterData: IUpdateInformation): void { this.$emit('valueChanged', parameterData); diff --git a/packages/editor-ui/src/components/mixins/workflowHelpers.ts b/packages/editor-ui/src/components/mixins/workflowHelpers.ts index b3ba72b272..30dbc34e5b 100644 --- a/packages/editor-ui/src/components/mixins/workflowHelpers.ts +++ b/packages/editor-ui/src/components/mixins/workflowHelpers.ts @@ -2,9 +2,11 @@ import { PLACEHOLDER_EMPTY_WORKFLOW_ID } from '@/constants'; import { IConnections, + IDataObject, INode, INodeExecutionData, INodeIssues, + INodeParameters, INodeType, INodeTypes, INodeTypeData, @@ -336,7 +338,7 @@ export const workflowHelpers = mixins( }, // Executes the given expression and returns its value - resolveExpression (expression: string) { + resolveExpression (expression: string, siblingParameters: INodeParameters) { const inputIndex = 0; const itemIndex = 0; const runIndex = 0; @@ -362,7 +364,16 @@ export const workflowHelpers = mixins( connectionInputData = []; } - return workflow.expression.getParameterValue(expression, runExecutionData, runIndex, itemIndex, activeNode.name, connectionInputData, 'manual', true); + const parameters = { + '__xxxxxxx__': expression, + ...siblingParameters, + }; + const returnData = workflow.expression.getParameterValue(parameters, runExecutionData, runIndex, itemIndex, activeNode.name, connectionInputData, 'manual', false) as IDataObject; + + if (typeof returnData['__xxxxxxx__'] === 'object') { + return workflow.expression.convertObjectValueToString(returnData['__xxxxxxx__'] as object); + } + return returnData['__xxxxxxx__']; }, // Saves the currently loaded workflow to the database. From 446c2845403453b7b6d928616d2d393495e16099 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sat, 15 May 2021 00:36:41 -0500 Subject: [PATCH 32/88] :shirt: Fix lint issue --- packages/editor-ui/src/components/ParameterInputList.vue | 6 +++--- packages/editor-ui/src/components/mixins/workflowHelpers.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/editor-ui/src/components/ParameterInputList.vue b/packages/editor-ui/src/components/ParameterInputList.vue index c34c0c94f4..1fc75ebf16 100644 --- a/packages/editor-ui/src/components/ParameterInputList.vue +++ b/packages/editor-ui/src/components/ParameterInputList.vue @@ -76,9 +76,9 @@