From cfd797b8adc6684bd8cbedffbd30eed3f2b6cb8f Mon Sep 17 00:00:00 2001 From: Harshil Agrawal Date: Fri, 5 Nov 2021 15:45:20 +0100 Subject: [PATCH 01/18] :zap: Add codex files (#2412) --- .../nodes/Aws/Textract/AwsTextract.node.json | 20 ++++++++++++++++ .../Google/Drive/GoogleDriveTrigger.node.json | 20 ++++++++++++++++ .../nodes/LocalFileTrigger.node.json | 24 +++++++++++++++++++ .../Dynamics/MicrosoftDynamicsCrm.node.json | 21 ++++++++++++++++ 4 files changed, 85 insertions(+) create mode 100644 packages/nodes-base/nodes/Aws/Textract/AwsTextract.node.json create mode 100644 packages/nodes-base/nodes/Google/Drive/GoogleDriveTrigger.node.json create mode 100644 packages/nodes-base/nodes/LocalFileTrigger.node.json create mode 100644 packages/nodes-base/nodes/Microsoft/Dynamics/MicrosoftDynamicsCrm.node.json diff --git a/packages/nodes-base/nodes/Aws/Textract/AwsTextract.node.json b/packages/nodes-base/nodes/Aws/Textract/AwsTextract.node.json new file mode 100644 index 0000000000..370cfd8874 --- /dev/null +++ b/packages/nodes-base/nodes/Aws/Textract/AwsTextract.node.json @@ -0,0 +1,20 @@ +{ + "node": "n8n-nodes-base.awsTextract", + "nodeVersion": "1.0", + "codexVersion": "1.0", + "categories": [ + "Utility" + ], + "resources": { + "credentialDocumentation": [ + { + "url": "https://docs.n8n.io/credentials/aws" + } + ], + "primaryDocumentation": [ + { + "url": "https://docs.n8n.io/nodes/n8n-nodes-base.awsTextract/" + } + ] + } +} diff --git a/packages/nodes-base/nodes/Google/Drive/GoogleDriveTrigger.node.json b/packages/nodes-base/nodes/Google/Drive/GoogleDriveTrigger.node.json new file mode 100644 index 0000000000..dc0a632b98 --- /dev/null +++ b/packages/nodes-base/nodes/Google/Drive/GoogleDriveTrigger.node.json @@ -0,0 +1,20 @@ +{ + "node": "n8n-nodes-base.googleDriveTrigger", + "nodeVersion": "1.0", + "codexVersion": "1.0", + "categories": [ + "Data & Storage" + ], + "resources": { + "credentialDocumentation": [ + { + "url": "https://docs.n8n.io/credentials/google" + } + ], + "primaryDocumentation": [ + { + "url": "https://docs.n8n.io/nodes/n8n-nodes-base.googleDriveTrigger/" + } + ] + } +} diff --git a/packages/nodes-base/nodes/LocalFileTrigger.node.json b/packages/nodes-base/nodes/LocalFileTrigger.node.json new file mode 100644 index 0000000000..3ca3c38063 --- /dev/null +++ b/packages/nodes-base/nodes/LocalFileTrigger.node.json @@ -0,0 +1,24 @@ +{ + "node": "n8n-nodes-base.localFileTrigger", + "nodeVersion": "1.0", + "codexVersion": "1.0", + "categories": [ + "Core Nodes" + ], + "resources": { + "primaryDocumentation": [ + { + "url": "https://docs.n8n.io/nodes/n8n-nodes-base.localFileTrigger/" + } + ] + }, + "alias": [ + "Watch", + "Monitor" + ], + "subcategories": { + "Core Nodes":[ + "Files" + ] + } +} diff --git a/packages/nodes-base/nodes/Microsoft/Dynamics/MicrosoftDynamicsCrm.node.json b/packages/nodes-base/nodes/Microsoft/Dynamics/MicrosoftDynamicsCrm.node.json new file mode 100644 index 0000000000..4773de0577 --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/Dynamics/MicrosoftDynamicsCrm.node.json @@ -0,0 +1,21 @@ +{ + "node": "n8n-nodes-base.microsoftDynamicsCrm", + "nodeVersion": "1.0", + "codexVersion": "1.0", + "categories": [ + "Marketing & Content", + "Sales" + ], + "resources": { + "credentialDocumentation": [ + { + "url": "https://docs.n8n.io/credentials/microsoft" + } + ], + "primaryDocumentation": [ + { + "url": "https://docs.n8n.io/nodes/n8n-nodes-base.microsoftDynamicsCrm/" + } + ] + } +} From 35787455ab7da14aae52a54990f87980b9678215 Mon Sep 17 00:00:00 2001 From: nikozila Date: Fri, 5 Nov 2021 18:17:05 +0330 Subject: [PATCH 02/18] :zap: Add hook: workflow.afterCreate (#2407) --- packages/cli/src/Server.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/cli/src/Server.ts b/packages/cli/src/Server.ts index e9de494f44..a3e0f2d1f5 100644 --- a/packages/cli/src/Server.ts +++ b/packages/cli/src/Server.ts @@ -679,6 +679,7 @@ class App { // @ts-ignore savedWorkflow.id = savedWorkflow.id.toString(); + await this.externalHooks.run('workflow.afterCreate', [savedWorkflow]); void InternalHooksManager.getInstance().onWorkflowCreated(newWorkflow as IWorkflowBase); return savedWorkflow; }, From 3ec52c1875eba4ef87857ad398068e8f95705fe6 Mon Sep 17 00:00:00 2001 From: Michele Paiano Date: Fri, 5 Nov 2021 15:49:31 +0100 Subject: [PATCH 03/18] :bug: Zendesk node: fix user External ID option name (#2392) Incorrect name of the "External ID" option prevents the "external_id" property from being correctly valued on Zendesk. Co-authored-by: MizziMizzi --- packages/nodes-base/nodes/Zendesk/UserDescription.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/nodes-base/nodes/Zendesk/UserDescription.ts b/packages/nodes-base/nodes/Zendesk/UserDescription.ts index eb7a1c706f..f181e5c1b0 100644 --- a/packages/nodes-base/nodes/Zendesk/UserDescription.ts +++ b/packages/nodes-base/nodes/Zendesk/UserDescription.ts @@ -130,7 +130,7 @@ export const userFields = [ }, { displayName: 'External ID', - name: 'externalId', + name: 'external_id', type: 'string', default: '', description: 'A unique identifier from another system', @@ -387,7 +387,7 @@ export const userFields = [ }, { displayName: 'External ID', - name: 'externalId', + name: 'external_id', type: 'string', default: '', description: 'A unique identifier from another system', From 70a9f0446e8538ee45c9f67c36818d471b2f314e Mon Sep 17 00:00:00 2001 From: Mutasem Aldmour <4711238+mutdmour@users.noreply.github.com> Date: Fri, 5 Nov 2021 16:40:33 +0100 Subject: [PATCH 04/18] :bug: Fix importing unknown types with credentials (#2414) --- packages/editor-ui/src/modules/credentials.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/editor-ui/src/modules/credentials.ts b/packages/editor-ui/src/modules/credentials.ts index 9f2b70c7c0..ac20203608 100644 --- a/packages/editor-ui/src/modules/credentials.ts +++ b/packages/editor-ui/src/modules/credentials.ts @@ -98,7 +98,7 @@ const module: Module = { }, getCredentialsByType: (state: ICredentialsState, getters: any) => { // tslint:disable-line:no-any return (credentialType: string): ICredentialsResponse[] => { - return getters.allCredentialsByType[credentialType]; + return getters.allCredentialsByType[credentialType] || []; }; }, getNodesWithAccess (state: ICredentialsState, getters: any, rootState: IRootState, rootGetters: any) { // tslint:disable-line:no-any From 7b8d388d17fa35f9a10ce4a5951b515ee7bafc62 Mon Sep 17 00:00:00 2001 From: Jan Date: Fri, 5 Nov 2021 10:45:51 -0600 Subject: [PATCH 05/18] Add Webhook response node (#2254) * :sparkles: Add Webhook-Response-Node * :zap: Replace callback function with promise * :sparkles: Add support for Bull and binary-data * :sparkles: Add string response option * :zap: Remove some comments * :sparkles: Make more generically possible & fix issue multi call in queue mode * :zap: Fix startup and eslint issues * :zap: Improvements to webhook response node and functionality * :zap: Replace data with more generic type * :zap: Make statusMessage optional * :zap: Change parameter order * :zap: Move Response Code underneath options * :zap: Hide Response Code on Webhook node if mode responseNode got selected * :zap: Minor improvements * :zap: Add missing file and fix lint issue * :zap: Fix some node linting issues * :zap: Apply feedback * :zap: Minor improvements --- packages/cli/commands/worker.ts | 14 +- packages/cli/src/ActiveExecutions.ts | 32 +- packages/cli/src/ActiveWorkflowRunner.ts | 16 +- packages/cli/src/Interfaces.ts | 13 +- packages/cli/src/Queue.ts | 19 +- packages/cli/src/ResponseHelper.ts | 5 + packages/cli/src/Server.ts | 24 +- packages/cli/src/WebhookHelpers.ts | 101 ++++++- packages/cli/src/WebhookServer.ts | 48 ++- packages/cli/src/WorkflowRunner.ts | 37 ++- packages/cli/src/WorkflowRunnerProcess.ts | 11 + packages/core/src/NodeExecuteFunctions.ts | 4 + packages/core/src/index.ts | 1 - packages/core/test/Helpers.ts | 3 +- packages/core/test/WorkflowExecute.test.ts | 12 +- .../nodes-base/nodes/RespondToWebhook.node.ts | 278 ++++++++++++++++++ packages/nodes-base/nodes/Wait.node.ts | 5 + packages/nodes-base/nodes/Webhook.node.ts | 64 ++-- packages/nodes-base/package.json | 1 + .../{core => workflow}/src/DeferredPromise.ts | 0 packages/workflow/src/Interfaces.ts | 17 +- packages/workflow/src/Workflow.ts | 23 +- packages/workflow/src/index.ts | 1 + 23 files changed, 664 insertions(+), 65 deletions(-) create mode 100644 packages/nodes-base/nodes/RespondToWebhook.node.ts rename packages/{core => workflow}/src/DeferredPromise.ts (100%) diff --git a/packages/cli/commands/worker.ts b/packages/cli/commands/worker.ts index 9a06868f3a..28a02b9b39 100644 --- a/packages/cli/commands/worker.ts +++ b/packages/cli/commands/worker.ts @@ -12,7 +12,7 @@ import * as PCancelable from 'p-cancelable'; import { Command, flags } from '@oclif/command'; import { UserSettings, WorkflowExecute } from 'n8n-core'; -import { INodeTypes, IRun, Workflow, LoggerProxy } from 'n8n-workflow'; +import { IExecuteResponsePromiseData, INodeTypes, IRun, Workflow, LoggerProxy } from 'n8n-workflow'; import { FindOneOptions } from 'typeorm'; @@ -25,11 +25,13 @@ import { GenericHelpers, IBullJobData, IBullJobResponse, + IBullWebhookResponse, IExecutionFlattedDb, InternalHooksManager, LoadNodesAndCredentials, NodeTypes, ResponseHelper, + WebhookHelpers, WorkflowExecuteAdditionalData, } from '../src'; @@ -172,6 +174,16 @@ export class Worker extends Command { currentExecutionDb.workflowData, { retryOf: currentExecutionDb.retryOf as string }, ); + + additionalData.hooks.hookFunctions.sendResponse = [ + async (response: IExecuteResponsePromiseData): Promise => { + await job.progress({ + executionId: job.data.executionId as string, + response: WebhookHelpers.encodeWebhookResponse(response), + } as IBullWebhookResponse); + }, + ]; + additionalData.executionId = jobData.executionId; let workflowExecute: WorkflowExecute; diff --git a/packages/cli/src/ActiveExecutions.ts b/packages/cli/src/ActiveExecutions.ts index dac67322c6..cd02ebe679 100644 --- a/packages/cli/src/ActiveExecutions.ts +++ b/packages/cli/src/ActiveExecutions.ts @@ -5,9 +5,12 @@ /* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-non-null-assertion */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ -import { IRun } from 'n8n-workflow'; - -import { createDeferredPromise } from 'n8n-core'; +import { + createDeferredPromise, + IDeferredPromise, + IExecuteResponsePromiseData, + IRun, +} from 'n8n-workflow'; import { ChildProcess } from 'child_process'; // eslint-disable-next-line import/no-extraneous-dependencies @@ -116,6 +119,28 @@ export class ActiveExecutions { this.activeExecutions[executionId].workflowExecution = workflowExecution; } + attachResponsePromise( + executionId: string, + responsePromise: IDeferredPromise, + ): void { + if (this.activeExecutions[executionId] === undefined) { + throw new Error( + `No active execution with id "${executionId}" got found to attach to workflowExecution to!`, + ); + } + + this.activeExecutions[executionId].responsePromise = responsePromise; + } + + resolveResponsePromise(executionId: string, response: IExecuteResponsePromiseData): void { + if (this.activeExecutions[executionId] === undefined) { + return; + } + + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + this.activeExecutions[executionId].responsePromise?.resolve(response); + } + /** * Remove an active execution * @@ -193,6 +218,7 @@ export class ActiveExecutions { this.activeExecutions[executionId].postExecutePromises.push(waitPromise); + // eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access return waitPromise.promise(); } diff --git a/packages/cli/src/ActiveWorkflowRunner.ts b/packages/cli/src/ActiveWorkflowRunner.ts index 181671c4ff..dd8ac09c3c 100644 --- a/packages/cli/src/ActiveWorkflowRunner.ts +++ b/packages/cli/src/ActiveWorkflowRunner.ts @@ -12,7 +12,9 @@ import { ActiveWorkflows, NodeExecuteFunctions } from 'n8n-core'; import { + IDeferredPromise, IExecuteData, + IExecuteResponsePromiseData, IGetExecutePollFunctions, IGetExecuteTriggerFunctions, INode, @@ -40,8 +42,6 @@ import { NodeTypes, ResponseHelper, WebhookHelpers, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - WorkflowCredentials, WorkflowExecuteAdditionalData, WorkflowHelpers, WorkflowRunner, @@ -550,6 +550,7 @@ export class ActiveWorkflowRunner { data: INodeExecutionData[][], additionalData: IWorkflowExecuteAdditionalDataWorkflow, mode: WorkflowExecuteMode, + responsePromise?: IDeferredPromise, ) { const nodeExecutionStack: IExecuteData[] = [ { @@ -580,7 +581,7 @@ export class ActiveWorkflowRunner { }; const workflowRunner = new WorkflowRunner(); - return workflowRunner.run(runData, true); + return workflowRunner.run(runData, true, undefined, undefined, responsePromise); } /** @@ -641,13 +642,16 @@ export class ActiveWorkflowRunner { mode, activation, ); - returnFunctions.emit = (data: INodeExecutionData[][]): void => { + returnFunctions.emit = ( + data: INodeExecutionData[][], + responsePromise?: IDeferredPromise, + ): void => { // eslint-disable-next-line @typescript-eslint/restrict-template-expressions Logger.debug(`Received trigger for workflow "${workflow.name}"`); WorkflowHelpers.saveStaticData(workflow); // eslint-disable-next-line id-denylist - this.runWorkflow(workflowData, node, data, additionalData, mode).catch((err) => - console.error(err), + this.runWorkflow(workflowData, node, data, additionalData, mode, responsePromise).catch( + (error) => console.error(error), ); }; return returnFunctions; diff --git a/packages/cli/src/Interfaces.ts b/packages/cli/src/Interfaces.ts index d5c11a8f3f..556aa74492 100644 --- a/packages/cli/src/Interfaces.ts +++ b/packages/cli/src/Interfaces.ts @@ -7,19 +7,19 @@ import { ICredentialsEncrypted, ICredentialType, IDataObject, + IDeferredPromise, + IExecuteResponsePromiseData, IRun, IRunData, IRunExecutionData, ITaskData, ITelemetrySettings, IWorkflowBase as IWorkflowBaseWorkflow, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - IWorkflowCredentials, Workflow, WorkflowExecuteMode, } from 'n8n-workflow'; -import { IDeferredPromise, WorkflowExecute } from 'n8n-core'; +import { WorkflowExecute } from 'n8n-core'; // eslint-disable-next-line import/no-extraneous-dependencies import * as PCancelable from 'p-cancelable'; @@ -47,6 +47,11 @@ export interface IBullJobResponse { success: boolean; } +export interface IBullWebhookResponse { + executionId: string; + response: IExecuteResponsePromiseData; +} + export interface ICustomRequest extends Request { parsedUrl: Url | undefined; } @@ -237,6 +242,7 @@ export interface IExecutingWorkflowData { process?: ChildProcess; startedAt: Date; postExecutePromises: Array>; + responsePromise?: IDeferredPromise; workflowExecution?: PCancelable; } @@ -490,6 +496,7 @@ export interface IPushDataConsoleMessage { export interface IResponseCallbackData { data?: IDataObject | IDataObject[]; + headers?: object; noWebhookResponse?: boolean; responseCode?: number; } diff --git a/packages/cli/src/Queue.ts b/packages/cli/src/Queue.ts index 9143c59ee4..5d215a2bd6 100644 --- a/packages/cli/src/Queue.ts +++ b/packages/cli/src/Queue.ts @@ -1,12 +1,21 @@ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ import * as Bull from 'bull'; import * as config from '../config'; // eslint-disable-next-line import/no-cycle -import { IBullJobData } from './Interfaces'; +import { IBullJobData, IBullWebhookResponse } from './Interfaces'; +// eslint-disable-next-line import/no-cycle +import * as ActiveExecutions from './ActiveExecutions'; +// eslint-disable-next-line import/no-cycle +import * as WebhookHelpers from './WebhookHelpers'; export class Queue { + private activeExecutions: ActiveExecutions.ActiveExecutions; + private jobQueue: Bull.Queue; constructor() { + this.activeExecutions = ActiveExecutions.getInstance(); + const prefix = config.get('queue.bull.prefix') as string; const redisOptions = config.get('queue.bull.redis') as object; // Disabling ready check is necessary as it allows worker to @@ -16,6 +25,14 @@ export class Queue { // More here: https://github.com/OptimalBits/bull/issues/890 // @ts-ignore this.jobQueue = new Bull('jobs', { prefix, redis: redisOptions, enableReadyCheck: false }); + + this.jobQueue.on('global:progress', (jobId, progress: IBullWebhookResponse) => { + this.activeExecutions.resolveResponsePromise( + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + progress.executionId, + WebhookHelpers.decodeWebhookResponse(progress.response), + ); + }); } async add(jobData: IBullJobData, jobOptions: object): Promise { diff --git a/packages/cli/src/ResponseHelper.ts b/packages/cli/src/ResponseHelper.ts index f6deb551be..e8430c695d 100644 --- a/packages/cli/src/ResponseHelper.ts +++ b/packages/cli/src/ResponseHelper.ts @@ -72,11 +72,16 @@ export function sendSuccessResponse( data: any, raw?: boolean, responseCode?: number, + responseHeader?: object, ) { if (responseCode !== undefined) { res.status(responseCode); } + if (responseHeader) { + res.header(responseHeader); + } + if (raw === true) { if (typeof data === 'string') { res.send(data); diff --git a/packages/cli/src/Server.ts b/packages/cli/src/Server.ts index a3e0f2d1f5..655ef52e78 100644 --- a/packages/cli/src/Server.ts +++ b/packages/cli/src/Server.ts @@ -2669,7 +2669,13 @@ class App { return; } - ResponseHelper.sendSuccessResponse(res, response.data, true, response.responseCode); + ResponseHelper.sendSuccessResponse( + res, + response.data, + true, + response.responseCode, + response.headers, + ); }, ); @@ -2720,7 +2726,13 @@ class App { return; } - ResponseHelper.sendSuccessResponse(res, response.data, true, response.responseCode); + ResponseHelper.sendSuccessResponse( + res, + response.data, + true, + response.responseCode, + response.headers, + ); }, ); @@ -2746,7 +2758,13 @@ class App { return; } - ResponseHelper.sendSuccessResponse(res, response.data, true, response.responseCode); + ResponseHelper.sendSuccessResponse( + res, + response.data, + true, + response.responseCode, + response.headers, + ); }, ); diff --git a/packages/cli/src/WebhookHelpers.ts b/packages/cli/src/WebhookHelpers.ts index ef0c47ac21..203bf20b08 100644 --- a/packages/cli/src/WebhookHelpers.ts +++ b/packages/cli/src/WebhookHelpers.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable no-param-reassign */ /* eslint-disable @typescript-eslint/prefer-optional-chain */ /* eslint-disable @typescript-eslint/no-shadow */ @@ -18,9 +19,13 @@ import { get } from 'lodash'; import { BINARY_ENCODING, NodeExecuteFunctions } from 'n8n-core'; import { + createDeferredPromise, IBinaryKeyData, IDataObject, + IDeferredPromise, IExecuteData, + IExecuteResponsePromiseData, + IN8nHttpFullResponse, INode, IRunExecutionData, IWebhookData, @@ -34,20 +39,20 @@ import { } from 'n8n-workflow'; // eslint-disable-next-line import/no-cycle import { - ActiveExecutions, GenericHelpers, IExecutionDb, IResponseCallbackData, IWorkflowDb, IWorkflowExecutionDataProcess, ResponseHelper, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - WorkflowCredentials, WorkflowExecuteAdditionalData, WorkflowHelpers, WorkflowRunner, } from '.'; +// eslint-disable-next-line import/no-cycle +import * as ActiveExecutions from './ActiveExecutions'; + const activeExecutions = ActiveExecutions.getInstance(); /** @@ -91,6 +96,35 @@ export function getWorkflowWebhooks( return returnData; } +export function decodeWebhookResponse( + response: IExecuteResponsePromiseData, +): IExecuteResponsePromiseData { + if ( + typeof response === 'object' && + typeof response.body === 'object' && + (response.body as IDataObject)['__@N8nEncodedBuffer@__'] + ) { + response.body = Buffer.from( + (response.body as IDataObject)['__@N8nEncodedBuffer@__'] as string, + BINARY_ENCODING, + ); + } + + return response; +} + +export function encodeWebhookResponse( + response: IExecuteResponsePromiseData, +): IExecuteResponsePromiseData { + if (typeof response === 'object' && Buffer.isBuffer(response.body)) { + response.body = { + '__@N8nEncodedBuffer@__': response.body.toString(BINARY_ENCODING), + }; + } + + return response; +} + /** * Returns all the webhooks which should be created for the give workflow * @@ -169,7 +203,7 @@ export async function executeWebhook( 200, ) as number; - if (!['onReceived', 'lastNode'].includes(responseMode as string)) { + if (!['onReceived', 'lastNode', 'responseNode'].includes(responseMode as string)) { // If the mode is not known we error. Is probably best like that instead of using // the default that people know as early as possible (probably already testing phase) // that something does not resolve properly. @@ -356,9 +390,52 @@ export async function executeWebhook( workflowData, }; + let responsePromise: IDeferredPromise | undefined; + if (responseMode === 'responseNode') { + responsePromise = await createDeferredPromise(); + responsePromise + .promise() + .then((response: IN8nHttpFullResponse) => { + if (didSendResponse) { + return; + } + + if (Buffer.isBuffer(response.body)) { + res.header(response.headers); + res.end(response.body); + + responseCallback(null, { + noWebhookResponse: true, + }); + } else { + // TODO: This probably needs some more changes depending on the options on the + // Webhook Response node + responseCallback(null, { + data: response.body as IDataObject, + headers: response.headers, + responseCode: response.statusCode, + }); + } + + didSendResponse = true; + }) + .catch(async (error) => { + Logger.error( + `Error with Webhook-Response for execution "${executionId}": "${error.message}"`, + { executionId, workflowId: workflow.id }, + ); + }); + } + // Start now to run the workflow const workflowRunner = new WorkflowRunner(); - executionId = await workflowRunner.run(runData, true, !didSendResponse, executionId); + executionId = await workflowRunner.run( + runData, + true, + !didSendResponse, + executionId, + responsePromise, + ); Logger.verbose( `Started execution of workflow "${workflow.name}" from webhook with execution ID ${executionId}`, @@ -398,6 +475,20 @@ export async function executeWebhook( return data; } + if (responseMode === 'responseNode') { + if (!didSendResponse) { + // Return an error if no Webhook-Response node did send any data + responseCallback(null, { + data: { + message: 'Workflow executed sucessfully.', + }, + responseCode, + }); + didSendResponse = true; + } + return undefined; + } + if (returnData === undefined) { if (!didSendResponse) { responseCallback(null, { diff --git a/packages/cli/src/WebhookServer.ts b/packages/cli/src/WebhookServer.ts index 4cf3afc7b4..c63526bfcc 100644 --- a/packages/cli/src/WebhookServer.ts +++ b/packages/cli/src/WebhookServer.ts @@ -64,7 +64,13 @@ export function registerProductionWebhooks() { return; } - ResponseHelper.sendSuccessResponse(res, response.data, true, response.responseCode); + ResponseHelper.sendSuccessResponse( + res, + response.data, + true, + response.responseCode, + response.headers, + ); }, ); @@ -115,7 +121,13 @@ export function registerProductionWebhooks() { return; } - ResponseHelper.sendSuccessResponse(res, response.data, true, response.responseCode); + ResponseHelper.sendSuccessResponse( + res, + response.data, + true, + response.responseCode, + response.headers, + ); }, ); @@ -141,7 +153,13 @@ export function registerProductionWebhooks() { return; } - ResponseHelper.sendSuccessResponse(res, response.data, true, response.responseCode); + ResponseHelper.sendSuccessResponse( + res, + response.data, + true, + response.responseCode, + response.headers, + ); }, ); @@ -173,7 +191,13 @@ export function registerProductionWebhooks() { return; } - ResponseHelper.sendSuccessResponse(res, response.data, true, response.responseCode); + ResponseHelper.sendSuccessResponse( + res, + response.data, + true, + response.responseCode, + response.headers, + ); }, ); @@ -199,7 +223,13 @@ export function registerProductionWebhooks() { return; } - ResponseHelper.sendSuccessResponse(res, response.data, true, response.responseCode); + ResponseHelper.sendSuccessResponse( + res, + response.data, + true, + response.responseCode, + response.headers, + ); }, ); @@ -225,7 +255,13 @@ export function registerProductionWebhooks() { return; } - ResponseHelper.sendSuccessResponse(res, response.data, true, response.responseCode); + ResponseHelper.sendSuccessResponse( + res, + response.data, + true, + response.responseCode, + response.headers, + ); }, ); } diff --git a/packages/cli/src/WorkflowRunner.ts b/packages/cli/src/WorkflowRunner.ts index 8984384aaa..fd18ff3d04 100644 --- a/packages/cli/src/WorkflowRunner.ts +++ b/packages/cli/src/WorkflowRunner.ts @@ -15,6 +15,8 @@ import { IProcessMessage, WorkflowExecute } from 'n8n-core'; import { ExecutionError, + IDeferredPromise, + IExecuteResponsePromiseData, IRun, LoggerProxy as Logger, Workflow, @@ -41,9 +43,7 @@ import { IBullJobResponse, ICredentialsOverwrite, ICredentialsTypeData, - IExecutionDb, IExecutionFlattedDb, - IExecutionResponse, IProcessMessageDataHook, ITransferNodeTypes, IWorkflowExecutionDataProcess, @@ -51,6 +51,7 @@ import { NodeTypes, Push, ResponseHelper, + WebhookHelpers, WorkflowExecuteAdditionalData, WorkflowHelpers, } from '.'; @@ -146,6 +147,7 @@ export class WorkflowRunner { loadStaticData?: boolean, realtime?: boolean, executionId?: string, + responsePromise?: IDeferredPromise, ): Promise { const executionsProcess = config.get('executions.process') as string; const executionsMode = config.get('executions.mode') as string; @@ -153,11 +155,17 @@ export class WorkflowRunner { if (executionsMode === 'queue' && data.executionMode !== 'manual') { // Do not run "manual" executions in bull because sending events to the // frontend would not be possible - executionId = await this.runBull(data, loadStaticData, realtime, executionId); + executionId = await this.runBull( + data, + loadStaticData, + realtime, + executionId, + responsePromise, + ); } else if (executionsProcess === 'main') { - executionId = await this.runMainProcess(data, loadStaticData, executionId); + executionId = await this.runMainProcess(data, loadStaticData, executionId, responsePromise); } else { - executionId = await this.runSubprocess(data, loadStaticData, executionId); + executionId = await this.runSubprocess(data, loadStaticData, executionId, responsePromise); } const postExecutePromise = this.activeExecutions.getPostExecutePromise(executionId); @@ -200,6 +208,7 @@ export class WorkflowRunner { data: IWorkflowExecutionDataProcess, loadStaticData?: boolean, restartExecutionId?: string, + responsePromise?: IDeferredPromise, ): Promise { if (loadStaticData === true && data.workflowData.id) { data.workflowData.staticData = await WorkflowHelpers.getStaticDataById( @@ -256,6 +265,15 @@ export class WorkflowRunner { executionId, true, ); + + additionalData.hooks.hookFunctions.sendResponse = [ + async (response: IExecuteResponsePromiseData): Promise => { + if (responsePromise) { + responsePromise.resolve(response); + } + }, + ]; + additionalData.sendMessageToUI = WorkflowExecuteAdditionalData.sendMessageToUI.bind({ sessionId: data.sessionId, }); @@ -341,11 +359,15 @@ export class WorkflowRunner { loadStaticData?: boolean, realtime?: boolean, restartExecutionId?: string, + responsePromise?: IDeferredPromise, ): Promise { // TODO: If "loadStaticData" is set to true it has to load data new on worker // Register the active execution const executionId = await this.activeExecutions.add(data, undefined, restartExecutionId); + if (responsePromise) { + this.activeExecutions.attachResponsePromise(executionId, responsePromise); + } const jobData: IBullJobData = { executionId, @@ -545,6 +567,7 @@ export class WorkflowRunner { data: IWorkflowExecutionDataProcess, loadStaticData?: boolean, restartExecutionId?: string, + responsePromise?: IDeferredPromise, ): Promise { let startedAt = new Date(); const subprocess = fork(pathJoin(__dirname, 'WorkflowRunnerProcess.js')); @@ -653,6 +676,10 @@ export class WorkflowRunner { } else if (message.type === 'end') { clearTimeout(executionTimeout); this.activeExecutions.remove(executionId, message.data.runData); + } else if (message.type === 'sendResponse') { + if (responsePromise) { + responsePromise.resolve(WebhookHelpers.decodeWebhookResponse(message.data.response)); + } } else if (message.type === 'sendMessageToUI') { // eslint-disable-next-line @typescript-eslint/no-unsafe-call WorkflowExecuteAdditionalData.sendMessageToUI.bind({ sessionId: data.sessionId })( diff --git a/packages/cli/src/WorkflowRunnerProcess.ts b/packages/cli/src/WorkflowRunnerProcess.ts index d7039d69af..e8b8274c9f 100644 --- a/packages/cli/src/WorkflowRunnerProcess.ts +++ b/packages/cli/src/WorkflowRunnerProcess.ts @@ -10,6 +10,7 @@ import { IProcessMessage, UserSettings, WorkflowExecute } from 'n8n-core'; import { ExecutionError, IDataObject, + IExecuteResponsePromiseData, IExecuteWorkflowInfo, ILogger, INodeExecutionData, @@ -33,6 +34,7 @@ import { IWorkflowExecuteProcess, IWorkflowExecutionDataProcessWithExecution, NodeTypes, + WebhookHelpers, WorkflowExecuteAdditionalData, WorkflowHelpers, } from '.'; @@ -200,6 +202,15 @@ export class WorkflowRunnerProcess { workflowTimeout <= 0 ? undefined : Date.now() + workflowTimeout * 1000, ); additionalData.hooks = this.getProcessForwardHooks(); + + additionalData.hooks.hookFunctions.sendResponse = [ + async (response: IExecuteResponsePromiseData): Promise => { + await sendToParentProcess('sendResponse', { + response: WebhookHelpers.encodeWebhookResponse(response), + }); + }, + ]; + additionalData.executionId = inputData.executionId; // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/packages/core/src/NodeExecuteFunctions.ts b/packages/core/src/NodeExecuteFunctions.ts index d6dc5da8ad..29b0c140e7 100644 --- a/packages/core/src/NodeExecuteFunctions.ts +++ b/packages/core/src/NodeExecuteFunctions.ts @@ -22,6 +22,7 @@ import { ICredentialsExpressionResolveValues, IDataObject, IExecuteFunctions, + IExecuteResponsePromiseData, IExecuteSingleFunctions, IExecuteWorkflowInfo, IHttpRequestOptions, @@ -1635,6 +1636,9 @@ export function getExecuteFunctions( Logger.warn(`There was a problem sending messsage to UI: ${error.message}`); } }, + async sendResponse(response: IExecuteResponsePromiseData): Promise { + await additionalData.hooks?.executeHookFunctions('sendResponse', [response]); + }, helpers: { httpRequest, prepareBinaryData, diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 15d1cce2b8..b0c6167aa9 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -12,7 +12,6 @@ export * from './ActiveWorkflows'; export * from './ActiveWebhooks'; export * from './Constants'; export * from './Credentials'; -export * from './DeferredPromise'; export * from './Interfaces'; export * from './LoadNodeParameterOptions'; export * from './NodeExecuteFunctions'; diff --git a/packages/core/test/Helpers.ts b/packages/core/test/Helpers.ts index 387ac67a85..eb59201828 100644 --- a/packages/core/test/Helpers.ts +++ b/packages/core/test/Helpers.ts @@ -4,6 +4,7 @@ import { ICredentialDataDecryptedObject, ICredentialsHelper, IDataObject, + IDeferredPromise, IExecuteWorkflowInfo, INodeCredentialsDetails, INodeExecutionData, @@ -20,7 +21,7 @@ import { WorkflowHooks, } from 'n8n-workflow'; -import { Credentials, IDeferredPromise, IExecuteFunctions } from '../src'; +import { Credentials, IExecuteFunctions } from '../src'; export class CredentialsHelper extends ICredentialsHelper { getDecrypted( diff --git a/packages/core/test/WorkflowExecute.test.ts b/packages/core/test/WorkflowExecute.test.ts index 364fb23d5a..b1ac658dc6 100644 --- a/packages/core/test/WorkflowExecute.test.ts +++ b/packages/core/test/WorkflowExecute.test.ts @@ -1,6 +1,14 @@ -import { IConnections, ILogger, INode, IRun, LoggerProxy, Workflow } from 'n8n-workflow'; +import { + createDeferredPromise, + IConnections, + ILogger, + INode, + IRun, + LoggerProxy, + Workflow, +} from 'n8n-workflow'; -import { createDeferredPromise, WorkflowExecute } from '../src'; +import { WorkflowExecute } from '../src'; import * as Helpers from './Helpers'; diff --git a/packages/nodes-base/nodes/RespondToWebhook.node.ts b/packages/nodes-base/nodes/RespondToWebhook.node.ts new file mode 100644 index 0000000000..3525168158 --- /dev/null +++ b/packages/nodes-base/nodes/RespondToWebhook.node.ts @@ -0,0 +1,278 @@ +import { + BINARY_ENCODING, +} from 'n8n-core'; + +import { + IDataObject, + IExecuteFunctions, + IN8nHttpFullResponse, + IN8nHttpResponse, + INodeExecutionData, + INodeType, + INodeTypeDescription, + NodeOperationError, +} from 'n8n-workflow'; + +export class RespondToWebhook implements INodeType { + description: INodeTypeDescription = { + displayName: 'Respond to Webhook', + icon: 'file:webhook.svg', + name: 'respondToWebhook', + group: ['transform'], + version: 1, + description: 'Returns data for Webhook', + defaults: { + name: 'Respond to Webhook', + color: '#885577', + }, + inputs: ['main'], + outputs: ['main'], + credentials: [ + ], + properties: [ + { + displayName: 'Respond With', + name: 'respondWith', + type: 'options', + options: [ + { + name: 'First Incoming Item', + value: 'firstIncomingItem', + }, + { + name: 'Text', + value: 'text', + }, + { + name: 'JSON', + value: 'json', + }, + { + name: 'Binary', + value: 'binary', + }, + { + name: 'No Data', + value: 'noData', + }, + ], + default: 'firstIncomingItem', + description: 'The data that should be returned', + }, + { + displayName: 'When using expressions, note that this node will only run for the first item in the input data.', + name: 'webhookNotice', + type: 'notice', + displayOptions: { + show: { + respondWith: [ + 'json', + 'text', + ], + }, + }, + default: '', + }, + { + displayName: 'Response Body', + name: 'responseBody', + type: 'json', + displayOptions: { + show: { + respondWith: [ + 'json', + ], + }, + }, + default: '', + placeholder: '{ "key": "value" }', + description: 'The HTTP Response JSON data', + }, + { + displayName: 'Response Body', + name: 'responseBody', + type: 'string', + displayOptions: { + show: { + respondWith: [ + 'text', + ], + }, + }, + default: '', + placeholder: 'e.g. Workflow started', + description: 'The HTTP Response text data', + }, + { + displayName: 'Response Data Source', + name: 'responseDataSource', + type: 'options', + displayOptions: { + show: { + respondWith: [ + 'binary', + ], + }, + }, + options: [ + { + name: 'Choose Automatically From Input', + value: 'automatically', + description: 'Use if input data will contain a single piece of binary data', + }, + { + name: 'Specify Myself', + value: 'set', + description: 'Enter the name of the input field the binary data will be in', + }, + ], + default: 'automatically', + }, + { + displayName: 'Input Field Name', + name: 'inputFieldName', + type: 'string', + required: true, + default: 'data', + displayOptions: { + show: { + respondWith: [ + 'binary', + ], + responseDataSource: [ + 'set', + ], + }, + }, + description: 'The name of the node input field with the binary data', + }, + + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Option', + default: {}, + options: [ + { + displayName: 'Response Code', + name: 'responseCode', + type: 'number', + typeOptions: { + minValue: 100, + maxValue: 599, + }, + default: 200, + description: 'The HTTP Response code to return. Defaults to 200.', + }, + { + displayName: 'Response Headers', + name: 'responseHeaders', + placeholder: 'Add Response Header', + description: 'Add headers to the webhook response', + type: 'fixedCollection', + typeOptions: { + multipleValues: true, + }, + default: {}, + options: [ + { + name: 'entries', + displayName: 'Entries', + values: [ + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + description: 'Name of the header', + }, + { + displayName: 'Value', + name: 'value', + type: 'string', + default: '', + description: 'Value of the header', + }, + ], + }, + ], + }, + ], + }, + ], + }; + + execute(this: IExecuteFunctions): Promise { + const items = this.getInputData(); + + const respondWith = this.getNodeParameter('respondWith', 0) as string; + const options = this.getNodeParameter('options', 0, {}) as IDataObject; + + const headers = {} as IDataObject; + if (options.responseHeaders) { + for (const header of (options.responseHeaders as IDataObject).entries as IDataObject[]) { + if (typeof header.name !== 'string') { + header.name = header.name?.toString(); + } + headers[header.name?.toLowerCase() as string] = header.value?.toString(); + } + } + + let responseBody: IN8nHttpResponse; + if (respondWith === 'json') { + const responseBodyParameter = this.getNodeParameter('responseBody', 0) as string; + if (responseBodyParameter) { + responseBody = JSON.parse(responseBodyParameter); + } + } else if (respondWith === 'firstIncomingItem') { + responseBody = items[0].json; + } else if (respondWith === 'text') { + responseBody = this.getNodeParameter('responseBody', 0) as string; + } else if (respondWith === 'binary') { + const item = this.getInputData()[0]; + + if (item.binary === undefined) { + throw new NodeOperationError(this.getNode(), 'No binary data exists on the first item!'); + } + + let responseBinaryPropertyName: string; + + const responseDataSource = this.getNodeParameter('responseDataSource', 0) as string; + + if (responseDataSource === 'set') { + responseBinaryPropertyName = this.getNodeParameter('inputFieldName', 0) as string; + } else { + const binaryKeys = Object.keys(item.binary); + if (binaryKeys.length === 0) { + throw new NodeOperationError(this.getNode(), 'No binary data exists on the first item!'); + } + responseBinaryPropertyName = binaryKeys[0]; + } + + const binaryData = item.binary[responseBinaryPropertyName]; + + if (binaryData === undefined) { + throw new NodeOperationError(this.getNode(), `No binary data property "${responseBinaryPropertyName}" does not exists on item!`); + } + + if (headers['content-type']) { + headers['content-type'] = binaryData.mimeType; + } + responseBody = Buffer.from(binaryData.data, BINARY_ENCODING); + } else if (respondWith !== 'noData') { + throw new NodeOperationError(this.getNode(), `The Response Data option "${respondWith}" is not supported!`); + } + + const response: IN8nHttpFullResponse = { + body: responseBody, + headers, + statusCode: options.responseCode as number || 200, + }; + + this.sendResponse(response); + + return this.prepareOutputData(items); + } + +} diff --git a/packages/nodes-base/nodes/Wait.node.ts b/packages/nodes-base/nodes/Wait.node.ts index 2a5f064a62..e8c3609b1b 100644 --- a/packages/nodes-base/nodes/Wait.node.ts +++ b/packages/nodes-base/nodes/Wait.node.ts @@ -304,6 +304,11 @@ export class Wait implements INodeType { value: 'lastNode', description: 'Returns data of the last executed node', }, + { + name: 'Response Node finishes', + value: 'responseNode', + description: 'Returns data the response node did set', + }, ], default: 'onReceived', description: 'When and how to respond to the webhook', diff --git a/packages/nodes-base/nodes/Webhook.node.ts b/packages/nodes-base/nodes/Webhook.node.ts index e04fe2813f..1e53c1529c 100644 --- a/packages/nodes-base/nodes/Webhook.node.ts +++ b/packages/nodes-base/nodes/Webhook.node.ts @@ -9,7 +9,6 @@ import { INodeType, INodeTypeDescription, IWebhookResponseData, - NodeApiError, NodeOperationError, } from 'n8n-workflow'; @@ -143,10 +142,54 @@ export class Webhook implements INodeType { required: true, description: 'The path to listen to.', }, + { + displayName: 'Respond', + name: 'responseMode', + type: 'options', + options: [ + { + name: 'Immediately', + value: 'onReceived', + description: 'As soon as this node executes', + }, + { + name: 'When last node finishes', + value: 'lastNode', + description: 'Returns data of the last-executed node', + }, + { + name: 'Using \'Respond to Webhook\' node', + value: 'responseNode', + description: 'Response defined in that node', + }, + ], + default: 'onReceived', + description: 'When and how to respond to the webhook.', + }, + { + displayName: 'Insert a \'Respond to Webhook\' node to control when and how you respond. More details', + name: 'webhookNotice', + type: 'notice', + displayOptions: { + show: { + responseMode: [ + 'responseNode', + ], + }, + }, + default: '', + }, { displayName: 'Response Code', name: 'responseCode', type: 'number', + displayOptions: { + hide: { + responseMode: [ + 'responseNode', + ], + }, + }, typeOptions: { minValue: 100, maxValue: 599, @@ -154,25 +197,6 @@ export class Webhook implements INodeType { default: 200, description: 'The HTTP Response code to return', }, - { - displayName: 'Respond When', - name: 'responseMode', - type: 'options', - options: [ - { - name: 'Webhook received', - value: 'onReceived', - description: 'Returns directly with defined Response Code', - }, - { - name: 'Last node finishes', - value: 'lastNode', - description: 'Returns data of the last executed node', - }, - ], - default: 'onReceived', - description: 'When and how to respond to the webhook.', - }, { displayName: 'Response Data', name: 'responseData', diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 969fc9971e..9d107f7f14 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -552,6 +552,7 @@ "dist/nodes/Reddit/Reddit.node.js", "dist/nodes/Redis/Redis.node.js", "dist/nodes/RenameKeys.node.js", + "dist/nodes/RespondToWebhook.node.js", "dist/nodes/Rocketchat/Rocketchat.node.js", "dist/nodes/RssFeedRead.node.js", "dist/nodes/Rundeck/Rundeck.node.js", diff --git a/packages/core/src/DeferredPromise.ts b/packages/workflow/src/DeferredPromise.ts similarity index 100% rename from packages/core/src/DeferredPromise.ts rename to packages/workflow/src/DeferredPromise.ts diff --git a/packages/workflow/src/Interfaces.ts b/packages/workflow/src/Interfaces.ts index b700d069ff..7d7dfb6dab 100644 --- a/packages/workflow/src/Interfaces.ts +++ b/packages/workflow/src/Interfaces.ts @@ -6,6 +6,7 @@ import * as express from 'express'; import * as FormData from 'form-data'; import { URLSearchParams } from 'url'; +import { IDeferredPromise } from './DeferredPromise'; import { Workflow } from './Workflow'; import { WorkflowHooks } from './WorkflowHooks'; import { WorkflowOperationError } from './WorkflowErrors'; @@ -208,6 +209,9 @@ export interface IDataObject { [key: string]: GenericValue | IDataObject | GenericValue[] | IDataObject[]; } +// export type IExecuteResponsePromiseData = IDataObject; +export type IExecuteResponsePromiseData = IDataObject | IN8nHttpFullResponse; + export interface INodeTypeNameVersion { name: string; version: number; @@ -324,13 +328,13 @@ export interface IHttpRequestOptions { json?: boolean; } -export type IN8nHttpResponse = IDataObject | Buffer | GenericValue | GenericValue[]; +export type IN8nHttpResponse = IDataObject | Buffer | GenericValue | GenericValue[] | null; export interface IN8nHttpFullResponse { body: IN8nHttpResponse; headers: IDataObject; statusCode: number; - statusMessage: string; + statusMessage?: string; } export interface IExecuteFunctions { @@ -371,7 +375,8 @@ export interface IExecuteFunctions { outputIndex?: number, ): Promise; putExecutionToWait(waitTill: Date): Promise; - sendMessageToUI(message: any): void; + sendMessageToUI(message: any): void; // tslint:disable-line:no-any + sendResponse(response: IExecuteResponsePromiseData): void; // tslint:disable-line:no-any helpers: { httpRequest( requestOptions: IHttpRequestOptions, @@ -492,7 +497,10 @@ export interface IPollFunctions { } export interface ITriggerFunctions { - emit(data: INodeExecutionData[][]): void; + emit( + data: INodeExecutionData[][], + responsePromise?: IDeferredPromise, + ): void; getCredentials(type: string): Promise; getMode(): WorkflowExecuteMode; getActivationMode(): WorkflowActivateMode; @@ -975,6 +983,7 @@ export interface IWorkflowExecuteHooks { nodeExecuteBefore?: Array<(nodeName: string) => Promise>; workflowExecuteAfter?: Array<(data: IRun, newStaticData: IDataObject) => Promise>; workflowExecuteBefore?: Array<(workflow: Workflow, data: IRunExecutionData) => Promise>; + sendResponse?: Array<(response: IExecuteResponsePromiseData) => Promise>; } export interface IWorkflowExecuteAdditionalData { diff --git a/packages/workflow/src/Workflow.ts b/packages/workflow/src/Workflow.ts index 860eaea0dd..256b5aa2bc 100644 --- a/packages/workflow/src/Workflow.ts +++ b/packages/workflow/src/Workflow.ts @@ -16,6 +16,8 @@ import { Expression, IConnections, + IDeferredPromise, + IExecuteResponsePromiseData, IGetExecuteTriggerFunctions, INode, INodeExecuteFunctions, @@ -946,10 +948,23 @@ export class Workflow { // Add the manual trigger response which resolves when the first time data got emitted triggerResponse!.manualTriggerResponse = new Promise((resolve) => { - // eslint-disable-next-line @typescript-eslint/no-shadow - triggerFunctions.emit = ((resolve) => (data: INodeExecutionData[][]) => { - resolve(data); - })(resolve); + triggerFunctions.emit = ( + (resolveEmit) => + ( + data: INodeExecutionData[][], + responsePromise?: IDeferredPromise, + ) => { + additionalData.hooks!.hookFunctions.sendResponse = [ + async (response: IExecuteResponsePromiseData): Promise => { + if (responsePromise) { + responsePromise.resolve(response); + } + }, + ]; + + resolveEmit(data); + } + )(resolve); }); return triggerResponse; diff --git a/packages/workflow/src/index.ts b/packages/workflow/src/index.ts index e73f572cd0..9913f7a3ef 100644 --- a/packages/workflow/src/index.ts +++ b/packages/workflow/src/index.ts @@ -3,6 +3,7 @@ import * as LoggerProxy from './LoggerProxy'; import * as NodeHelpers from './NodeHelpers'; import * as ObservableObject from './ObservableObject'; +export * from './DeferredPromise'; export * from './Interfaces'; export * from './Expression'; export * from './NodeErrors'; From 8c4040dc5b6cb3f3d1ba0303f082fdbfc3cf2c25 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 5 Nov 2021 11:19:23 -0600 Subject: [PATCH 06/18] :zap: Minor improvements to RespondToWebhook node --- packages/cli/src/NodeTypes.ts | 3 +++ packages/nodes-base/nodes/Wait.node.ts | 14 +++++++------- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/packages/cli/src/NodeTypes.ts b/packages/cli/src/NodeTypes.ts index b6ed97511d..ff4e8c027c 100644 --- a/packages/cli/src/NodeTypes.ts +++ b/packages/cli/src/NodeTypes.ts @@ -40,6 +40,9 @@ class NodeTypesClass implements INodeTypes { } getByNameAndVersion(nodeType: string, version?: number): INodeType { + if (this.nodeTypes[nodeType] === undefined) { + throw new Error(`The node-type "${nodeType}" is not known!`); + } return NodeHelpers.getVersionedTypeNode(this.nodeTypes[nodeType].type, version); } } diff --git a/packages/nodes-base/nodes/Wait.node.ts b/packages/nodes-base/nodes/Wait.node.ts index e8c3609b1b..78279c5417 100644 --- a/packages/nodes-base/nodes/Wait.node.ts +++ b/packages/nodes-base/nodes/Wait.node.ts @@ -283,7 +283,7 @@ export class Wait implements INodeType { description: 'The HTTP Response code to return', }, { - displayName: 'Respond When', + displayName: 'Respond', name: 'responseMode', type: 'options', displayOptions: { @@ -295,19 +295,19 @@ export class Wait implements INodeType { }, options: [ { - name: 'Webhook received', + name: 'Immediately', value: 'onReceived', - description: 'Returns directly with defined Response Code', + description: 'As soon as this node executes', }, { - name: 'Last node finishes', + name: 'When last node finishes', value: 'lastNode', - description: 'Returns data of the last executed node', + description: 'Returns data of the last-executed node', }, { - name: 'Response Node finishes', + name: 'Using \'Respond to Webhook\' node', value: 'responseNode', - description: 'Returns data the response node did set', + description: 'Response defined in that node', }, ], default: 'onReceived', From 18597808f31d6c47af607d37964198bd66c3f5f7 Mon Sep 17 00:00:00 2001 From: Ricardo Espinoza Date: Fri, 5 Nov 2021 13:37:50 -0400 Subject: [PATCH 07/18] :sparkles: Add Dropcontact node (#2394) * Add a new dropcontact node * Improvements to #2389 * :zap: Add credentials verification * :zap: Small improvement * :zap: set default time to 45 seconds * :zap: Improvements * :zap: Improvements * :zap: Improvements * :zap: Improvements * :zap: Improvements * :bug: Set siren and language correctly Co-authored-by: PaulineDropcontact Co-authored-by: Jan Oberhauser --- .../credentials/DropcontactApi.credentials.ts | 18 + .../nodes/Dropcontact/Dropcontact.node.ts | 370 ++++++++++++++++++ .../nodes/Dropcontact/GenericFunction.ts | 82 ++++ .../nodes/Dropcontact/dropcontact.svg | 3 + packages/nodes-base/package.json | 2 + 5 files changed, 475 insertions(+) create mode 100644 packages/nodes-base/credentials/DropcontactApi.credentials.ts create mode 100644 packages/nodes-base/nodes/Dropcontact/Dropcontact.node.ts create mode 100644 packages/nodes-base/nodes/Dropcontact/GenericFunction.ts create mode 100644 packages/nodes-base/nodes/Dropcontact/dropcontact.svg diff --git a/packages/nodes-base/credentials/DropcontactApi.credentials.ts b/packages/nodes-base/credentials/DropcontactApi.credentials.ts new file mode 100644 index 0000000000..de91630b01 --- /dev/null +++ b/packages/nodes-base/credentials/DropcontactApi.credentials.ts @@ -0,0 +1,18 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + +export class DropcontactApi implements ICredentialType { + name = 'dropcontactApi'; + displayName = 'Dropcontact API'; + documentationUrl = 'dropcontact'; + properties = [ + { + displayName: 'API Key', + name: 'apiKey', + type: 'string' as NodePropertyTypes, + default: '', + }, + ]; +} diff --git a/packages/nodes-base/nodes/Dropcontact/Dropcontact.node.ts b/packages/nodes-base/nodes/Dropcontact/Dropcontact.node.ts new file mode 100644 index 0000000000..6269903dd3 --- /dev/null +++ b/packages/nodes-base/nodes/Dropcontact/Dropcontact.node.ts @@ -0,0 +1,370 @@ +import { + IExecuteFunctions, +} from 'n8n-core'; + +import { + ICredentialDataDecryptedObject, + ICredentialsDecrypted, + ICredentialTestFunctions, + IDataObject, + INodeExecutionData, + INodeType, + INodeTypeDescription, + NodeApiError, + NodeCredentialTestResult, +} from 'n8n-workflow'; + +import { + dropcontactApiRequest, + validateCrendetials, +} from './GenericFunction'; + +export class Dropcontact implements INodeType { + description: INodeTypeDescription = { + displayName: 'Dropcontact', + name: 'dropcontact', + icon: 'file:dropcontact.svg', + group: ['transform'], + version: 1, + description: 'Find B2B emails and enrich contacts', + subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', + defaults: { + name: 'Dropcontact', + color: '#0ABA9F', + }, + inputs: ['main'], + outputs: ['main'], + credentials: [ + { + name: 'dropcontactApi', + required: true, + testedBy: 'dropcontactApiCredentialTest', + }, + ], + properties: [ + { + displayName: 'Resource', + noDataExpression: true, + name: 'resource', + type: 'options', + options: [ + { + name: 'Contact', + value: 'contact', + }, + ], + default: 'contact', + required: true, + }, + { + displayName: 'Operation', + noDataExpression: true, + name: 'operation', + type: 'options', + options: [ + { + name: 'Enrich', + value: 'enrich', + description: 'Find B2B emails and enrich your contact from his name and his website', + }, + { + name: 'Fetch Request', + value: 'fetchRequest', + }, + ], + default: 'enrich', + required: true, + }, + { + displayName: 'Request ID', + name: 'requestId', + type: 'string', + required: true, + displayOptions: { + show: { + resource: [ + 'contact', + ], + operation: [ + 'fetchRequest', + ], + }, + }, + default: '', + }, + { + displayName: 'Email', + name: 'email', + type: 'string', + displayOptions: { + show: { + resource: [ + 'contact', + ], + operation: [ + 'enrich', + ], + }, + }, + default: '', + }, + { + displayName: 'Simplify Output (Faster)', + name: 'simplify', + type: 'boolean', + displayOptions: { + show: { + resource: [ + 'contact', + ], + operation: [ + 'enrich', + ], + }, + }, + default: false, + description: 'When off, waits for the contact data before completing. Waiting time can be adjusted with Extend Wait Time option. When on, returns a request_id that can be used later in the Fetch Request operation.', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'contact', + ], + operation: [ + 'enrich', + ], + }, + }, + options: [ + { + displayName: 'Company SIREN Number', + name: 'num_siren', + type: 'string', + default: '', + }, + { + displayName: 'Company SIRET Code', + name: 'siret', + type: 'string', + default: '', + }, + { + displayName: 'Company Name', + name: 'company', + type: 'string', + default: '', + }, + { + displayName: 'Country', + name: 'country', + type: 'string', + default: '', + }, + { + displayName: 'First Name', + name: 'first_name', + type: 'string', + default: '', + }, + { + displayName: 'Full Name', + name: 'full_name', + type: 'string', + default: '', + }, + { + displayName: 'Last Name', + name: 'last_name', + type: 'string', + default: '', + }, + { + displayName: 'LinkedIn Profile', + name: 'linkedin', + type: 'string', + default: '', + }, + { + displayName: 'Phone Number', + name: 'phone', + type: 'string', + default: '', + }, + { + displayName: 'Website', + name: 'website', + type: 'string', + default: '', + }, + ], + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + displayOptions: { + show: { + resource: [ + 'contact', + ], + operation: [ + 'enrich', + ], + }, + }, + placeholder: 'Add Option', + default: {}, + options: [ + { + displayName: 'Data Fetch Wait Time', + name: 'waitTime', + type: 'number', + typeOptions: { + minValue: 1, + }, + displayOptions: { + show: { + '/simplify': [ + false, + ], + }, + }, + default: 45, + description: 'When not simplifying the response, data will be fetched in two steps. This parameter controls how long to wait (in seconds) before trying the second step', + }, + { + displayName: 'French Company Enrich', + name: 'siren', + type: 'boolean', + default: false, + description: `Whether you want the SIREN number, NAF code, TVA number, company address and informations about the company leader.
+ Only applies to french companies`, + }, + { + displayName: 'Language', + name: 'language', + type: 'options', + options: [ + { + name: 'English', + value: 'en', + }, + { + name: 'French', + value: 'fr', + }, + ], + default: 'en', + description: 'Whether the response is in English or French', + }, + ], + }, + ], + }; + + methods = { + credentialTest: { + async dropcontactApiCredentialTest(this: ICredentialTestFunctions, credential: ICredentialsDecrypted): Promise { + try { + await validateCrendetials.call(this, credential.data as ICredentialDataDecryptedObject); + } catch (error) { + return { + status: 'Error', + message: 'The API Key included in the request is invalid', + }; + } + + return { + status: 'OK', + message: 'Connection successful!', + }; + }, + }, + }; + + async execute(this: IExecuteFunctions): Promise { + const entryData = this.getInputData(); + const resource = this.getNodeParameter('resource', 0) as string; + const operation = this.getNodeParameter('operation', 0) as string; + // tslint:disable-next-line: no-any + let responseData: any; + const returnData: IDataObject[] = []; + + if (resource === 'contact') { + if (operation === 'enrich') { + const options = this.getNodeParameter('options', 0) as IDataObject; + const data = []; + const simplify = this.getNodeParameter('simplify', 0) as boolean; + + const siren = options.siren === true ? true : false; + const language = options.language ? options.language : 'en'; + + for (let i = 0; i < entryData.length; i++) { + const email = this.getNodeParameter('email', i) as string; + const additionalFields = this.getNodeParameter('additionalFields', i); + const body: IDataObject = {}; + if (email !== '') { + body.email = email; + } + Object.assign(body, additionalFields); + data.push(body); + } + + responseData = await dropcontactApiRequest.call(this, 'POST', '/batch', { data, siren, language }, {}) as { request_id: string, error: string, success: boolean }; + + if (!responseData.success) { + if (this.continueOnFail()) { + returnData.push({ error: responseData.reason || 'invalid request' }); + } else { + throw new NodeApiError(this.getNode(), { error: responseData.reason || 'invalid request' }); + } + } + + if (simplify === false) { + const waitTime = this.getNodeParameter('options.waitTime', 0, 45) as number; + // tslint:disable-next-line: no-any + const delay = (ms: any) => new Promise(res => setTimeout(res, ms * 1000)); + await delay(waitTime); + responseData = await dropcontactApiRequest.call(this, 'GET', `/batch/${responseData.request_id}`, {}, {}); + if (!responseData.success) { + if (this.continueOnFail()) { + responseData.push({ error: responseData.reason }); + } else { + throw new NodeApiError(this.getNode(), { + error: responseData.reason, + description: 'Hint: Increase the Wait Time to avoid this error', + }); + } + } else { + returnData.push(...responseData.data); + } + } else { + returnData.push(responseData); + } + } + + if (operation === 'fetchRequest') { + for (let i = 0; i < entryData.length; i++) { + const requestId = this.getNodeParameter('requestId', i) as string; + responseData = await dropcontactApiRequest.call(this, 'GET', `/batch/${requestId}`, {}, {}) as { request_id: string, error: string, success: boolean }; + if (!responseData.success) { + if (this.continueOnFail()) { + responseData.push({ error: responseData.reason || 'invalid request' }); + } else { + throw new NodeApiError(this.getNode(), { error: responseData.reason || 'invalid request' }); + } + } + returnData.push(...responseData.data); + } + } + } + + return [this.helpers.returnJsonArray(returnData)]; + } +} diff --git a/packages/nodes-base/nodes/Dropcontact/GenericFunction.ts b/packages/nodes-base/nodes/Dropcontact/GenericFunction.ts new file mode 100644 index 0000000000..e5af1e6202 --- /dev/null +++ b/packages/nodes-base/nodes/Dropcontact/GenericFunction.ts @@ -0,0 +1,82 @@ +import { + IExecuteFunctions, + IHookFunctions, +} from 'n8n-core'; + +import { + ICredentialDataDecryptedObject, + ICredentialTestFunctions, + IDataObject, + ILoadOptionsFunctions, + NodeApiError, +} from 'n8n-workflow'; + +import { + OptionsWithUri, +} from 'request'; + +/** + * Make an authenticated API request to Bubble. + */ +export async function dropcontactApiRequest( + this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions, + method: string, + endpoint: string, + body: IDataObject, + qs: IDataObject, +) { + + const { apiKey } = await this.getCredentials('dropcontactApi') as { + apiKey: string, + }; + + const options: OptionsWithUri = { + headers: { + 'user-agent': 'n8n', + 'X-Access-Token': apiKey, + }, + method, + uri: `https://api.dropcontact.io${endpoint}`, + qs, + body, + json: true, + }; + + if (!Object.keys(body).length) { + delete options.body; + } + + if (!Object.keys(qs).length) { + delete options.qs; + } + + try { + return await this.helpers.request!(options); + } catch (error) { + throw new NodeApiError(this.getNode(), error); + } +} + +export async function validateCrendetials(this: ICredentialTestFunctions, decryptedCredentials: ICredentialDataDecryptedObject): Promise { // tslint:disable-line:no-any + const credentials = decryptedCredentials; + + const { apiKey } = credentials as { + apiKey: string, + }; + + const options: OptionsWithUri = { + headers: { + 'user-agent': 'n8n', + 'X-Access-Token': apiKey, + }, + method: 'POST', + body: { + data: [{ email: '' }], + }, + uri: `https://api.dropcontact.io/batch`, + json: true, + }; + + return this.helpers.request!(options); +} + diff --git a/packages/nodes-base/nodes/Dropcontact/dropcontact.svg b/packages/nodes-base/nodes/Dropcontact/dropcontact.svg new file mode 100644 index 0000000000..447973cc55 --- /dev/null +++ b/packages/nodes-base/nodes/Dropcontact/dropcontact.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 9d107f7f14..5edffc0b58 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -78,6 +78,7 @@ "dist/credentials/DriftOAuth2Api.credentials.js", "dist/credentials/DropboxApi.credentials.js", "dist/credentials/DropboxOAuth2Api.credentials.js", + "dist/credentials/DropcontactApi.credentials.js", "dist/credentials/EgoiApi.credentials.js", "dist/credentials/ElasticsearchApi.credentials.js", "dist/credentials/ElasticSecurityApi.credentials.js", @@ -379,6 +380,7 @@ "dist/nodes/Disqus/Disqus.node.js", "dist/nodes/Drift/Drift.node.js", "dist/nodes/Dropbox/Dropbox.node.js", + "dist/nodes/Dropcontact/Dropcontact.node.js", "dist/nodes/EditImage.node.js", "dist/nodes/Egoi/Egoi.node.js", "dist/nodes/Elastic/ElasticSecurity/ElasticSecurity.node.js", From 2e8f09dcfd79796668b4eaef9246a978a3500f53 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 5 Nov 2021 17:51:06 +0000 Subject: [PATCH 08/18] =?UTF-8?q?:bookmark:=20Release=C2=A0n8n-workflow@0.?= =?UTF-8?q?75.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 a86acf16d1..3027a02013 100644 --- a/packages/workflow/package.json +++ b/packages/workflow/package.json @@ -1,6 +1,6 @@ { "name": "n8n-workflow", - "version": "0.74.0", + "version": "0.75.0", "description": "Workflow base code of n8n", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", From ace08020170ebc17e5528b279543ccfaeb40b24c Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 5 Nov 2021 17:51:16 +0000 Subject: [PATCH 09/18] :arrow_up: Set n8n-workflow@0.75.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 853d398478..74e372f80e 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -50,7 +50,7 @@ "form-data": "^4.0.0", "lodash.get": "^4.4.2", "mime-types": "^2.1.27", - "n8n-workflow": "~0.74.0", + "n8n-workflow": "~0.75.0", "oauth-1.0a": "^2.2.6", "p-cancelable": "^2.0.0", "qs": "^6.10.1", From dc06ee60fb60b6cd2c8587d805289635d6531ac1 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 5 Nov 2021 17:51:16 +0000 Subject: [PATCH 10/18] =?UTF-8?q?:bookmark:=20Release=C2=A0n8n-core@0.92.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 74e372f80e..9ac21f0dec 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "n8n-core", - "version": "0.91.0", + "version": "0.92.0", "description": "Core functionality of n8n", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", From 9e218152780cc89408c15e3b9f8299bfe6b5e5f1 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 5 Nov 2021 17:51:23 +0000 Subject: [PATCH 11/18] :arrow_up: Set n8n-core@0.92.0 and n8n-workflow@0.75.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 4d45c28310..a834ef27b6 100644 --- a/packages/node-dev/package.json +++ b/packages/node-dev/package.json @@ -60,8 +60,8 @@ "change-case": "^4.1.1", "copyfiles": "^2.1.1", "inquirer": "^7.0.1", - "n8n-core": "~0.91.0", - "n8n-workflow": "~0.74.0", + "n8n-core": "~0.92.0", + "n8n-workflow": "~0.75.0", "oauth-1.0a": "^2.2.6", "replace-in-file": "^6.0.0", "request": "^2.88.2", From c16b20bd3da8f970c0af4c0c6c5cc9368f7b7129 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 5 Nov 2021 17:51:23 +0000 Subject: [PATCH 12/18] =?UTF-8?q?:bookmark:=20Release=C2=A0n8n-node-dev@0.?= =?UTF-8?q?32.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 a834ef27b6..baed211bb4 100644 --- a/packages/node-dev/package.json +++ b/packages/node-dev/package.json @@ -1,6 +1,6 @@ { "name": "n8n-node-dev", - "version": "0.31.0", + "version": "0.32.0", "description": "CLI to simplify n8n credentials/node development", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", From d427f942bdcd71dd9bd244eea2f7d4479552909c Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 5 Nov 2021 17:51:32 +0000 Subject: [PATCH 13/18] :arrow_up: Set n8n-core@0.92.0 and n8n-workflow@0.75.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 5edffc0b58..7d09952011 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -671,7 +671,7 @@ "@types/xml2js": "^0.4.3", "gulp": "^4.0.0", "jest": "^26.4.2", - "n8n-workflow": "~0.74.0", + "n8n-workflow": "~0.75.0", "nodelinter": "^0.1.9", "ts-jest": "^26.3.0", "tslint": "^6.1.2", @@ -711,7 +711,7 @@ "mqtt": "4.2.6", "mssql": "^6.2.0", "mysql2": "~2.3.0", - "n8n-core": "~0.91.0", + "n8n-core": "~0.92.0", "node-ssh": "^12.0.0", "nodemailer": "^6.5.0", "pdf-parse": "^1.1.1", From 298c88e326a3c84949d8f05dc6614c5cdeb104c5 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 5 Nov 2021 17:51:32 +0000 Subject: [PATCH 14/18] =?UTF-8?q?:bookmark:=20Release=C2=A0n8n-nodes-base@?= =?UTF-8?q?0.145.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 7d09952011..3034a6ab2c 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -1,6 +1,6 @@ { "name": "n8n-nodes-base", - "version": "0.144.1", + "version": "0.145.0", "description": "Base nodes of n8n", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", From 55455524e437592d154fa847ce5f7bef5bd883ce Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 5 Nov 2021 17:52:10 +0000 Subject: [PATCH 15/18] :arrow_up: Set n8n-workflow@0.75.0 on n8n-editor-ui --- 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 60e7485c89..c93fe1296b 100644 --- a/packages/editor-ui/package.json +++ b/packages/editor-ui/package.json @@ -71,7 +71,7 @@ "lodash.debounce": "^4.0.8", "lodash.get": "^4.4.2", "lodash.set": "^4.3.2", - "n8n-workflow": "~0.74.0", + "n8n-workflow": "~0.75.0", "sass": "^1.26.5", "normalize-wheel": "^1.0.1", "prismjs": "^1.17.1", From 9ac41953ab7e13b512442e99eb139f3d9a2512bc Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 5 Nov 2021 17:52:10 +0000 Subject: [PATCH 16/18] =?UTF-8?q?:bookmark:=20Release=C2=A0n8n-editor-ui@0?= =?UTF-8?q?.115.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 c93fe1296b..35a98903e2 100644 --- a/packages/editor-ui/package.json +++ b/packages/editor-ui/package.json @@ -1,6 +1,6 @@ { "name": "n8n-editor-ui", - "version": "0.114.0", + "version": "0.115.0", "description": "Workflow Editor UI for n8n", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", From 7f8dbfa4056b55606ef563232974f3255dd9384d Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 5 Nov 2021 17:52:48 +0000 Subject: [PATCH 17/18] :arrow_up: Set n8n-core@0.92.0, n8n-editor-ui@0.115.0, n8n-nodes-base@0.145.0 and n8n-workflow@0.75.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 f4924adbe7..ec78029cb2 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -110,10 +110,10 @@ "localtunnel": "^2.0.0", "lodash.get": "^4.4.2", "mysql2": "~2.3.0", - "n8n-core": "~0.91.0", - "n8n-editor-ui": "~0.114.0", - "n8n-nodes-base": "~0.144.1", - "n8n-workflow": "~0.74.0", + "n8n-core": "~0.92.0", + "n8n-editor-ui": "~0.115.0", + "n8n-nodes-base": "~0.145.0", + "n8n-workflow": "~0.75.0", "oauth-1.0a": "^2.2.6", "open": "^7.0.0", "pg": "^8.3.0", From 27543fcdd4064d6527e3c8e31c2e4927a85acf26 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 5 Nov 2021 17:52:48 +0000 Subject: [PATCH 18/18] =?UTF-8?q?:bookmark:=20Release=C2=A0n8n@0.148.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 ec78029cb2..29cee52fde 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "n8n", - "version": "0.147.1", + "version": "0.148.0", "description": "n8n Workflow Automation Tool", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io",