mirror of
https://github.com/n8n-io/n8n.git
synced 2024-11-10 06:34:05 -08:00
🔀 Merge branch 'external-hooks'
This commit is contained in:
commit
1451f94814
|
@ -11,6 +11,7 @@ import {
|
||||||
ActiveExecutions,
|
ActiveExecutions,
|
||||||
CredentialsOverwrites,
|
CredentialsOverwrites,
|
||||||
Db,
|
Db,
|
||||||
|
ExternalHooks,
|
||||||
GenericHelpers,
|
GenericHelpers,
|
||||||
IWorkflowBase,
|
IWorkflowBase,
|
||||||
IWorkflowExecutionDataProcess,
|
IWorkflowExecutionDataProcess,
|
||||||
|
@ -108,6 +109,10 @@ export class Execute extends Command {
|
||||||
const credentialsOverwrites = CredentialsOverwrites();
|
const credentialsOverwrites = CredentialsOverwrites();
|
||||||
await credentialsOverwrites.init();
|
await credentialsOverwrites.init();
|
||||||
|
|
||||||
|
// Load all external hooks
|
||||||
|
const externalHooks = ExternalHooks();
|
||||||
|
await externalHooks.init();
|
||||||
|
|
||||||
// Add the found types to an instance other parts of the application can use
|
// Add the found types to an instance other parts of the application can use
|
||||||
const nodeTypes = NodeTypes();
|
const nodeTypes = NodeTypes();
|
||||||
await nodeTypes.init(loadNodesAndCredentials.nodeTypes);
|
await nodeTypes.init(loadNodesAndCredentials.nodeTypes);
|
||||||
|
|
|
@ -5,7 +5,6 @@ import {
|
||||||
} from 'n8n-core';
|
} from 'n8n-core';
|
||||||
import { Command, flags } from '@oclif/command';
|
import { Command, flags } from '@oclif/command';
|
||||||
const open = require('open');
|
const open = require('open');
|
||||||
// import { dirname } from 'path';
|
|
||||||
|
|
||||||
import * as config from '../config';
|
import * as config from '../config';
|
||||||
import {
|
import {
|
||||||
|
@ -13,6 +12,7 @@ import {
|
||||||
CredentialTypes,
|
CredentialTypes,
|
||||||
CredentialsOverwrites,
|
CredentialsOverwrites,
|
||||||
Db,
|
Db,
|
||||||
|
ExternalHooks,
|
||||||
GenericHelpers,
|
GenericHelpers,
|
||||||
LoadNodesAndCredentials,
|
LoadNodesAndCredentials,
|
||||||
NodeTypes,
|
NodeTypes,
|
||||||
|
@ -113,6 +113,10 @@ export class Start extends Command {
|
||||||
const credentialsOverwrites = CredentialsOverwrites();
|
const credentialsOverwrites = CredentialsOverwrites();
|
||||||
await credentialsOverwrites.init();
|
await credentialsOverwrites.init();
|
||||||
|
|
||||||
|
// Load all external hooks
|
||||||
|
const externalHooks = ExternalHooks();
|
||||||
|
await externalHooks.init();
|
||||||
|
|
||||||
// Add the found types to an instance other parts of the application can use
|
// Add the found types to an instance other parts of the application can use
|
||||||
const nodeTypes = NodeTypes();
|
const nodeTypes = NodeTypes();
|
||||||
await nodeTypes.init(loadNodesAndCredentials.nodeTypes);
|
await nodeTypes.init(loadNodesAndCredentials.nodeTypes);
|
||||||
|
|
|
@ -271,6 +271,13 @@ const config = convict({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
externalHookFiles: {
|
||||||
|
doc: 'Files containing external hooks. Multiple files can be separated by colon (":")',
|
||||||
|
format: String,
|
||||||
|
default: '',
|
||||||
|
env: 'EXTERNAL_HOOK_FILES'
|
||||||
|
},
|
||||||
|
|
||||||
nodes: {
|
nodes: {
|
||||||
exclude: {
|
exclude: {
|
||||||
doc: 'Nodes not to load',
|
doc: 'Nodes not to load',
|
||||||
|
|
87
packages/cli/src/ExternalHooks.ts
Normal file
87
packages/cli/src/ExternalHooks.ts
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
import {
|
||||||
|
Db,
|
||||||
|
IExternalHooksFunctions,
|
||||||
|
IExternalHooksClass,
|
||||||
|
} from './';
|
||||||
|
|
||||||
|
import * as config from '../config';
|
||||||
|
|
||||||
|
|
||||||
|
class ExternalHooksClass implements IExternalHooksClass {
|
||||||
|
|
||||||
|
externalHooks: {
|
||||||
|
[key: string]: Array<() => {}>
|
||||||
|
} = {};
|
||||||
|
initDidRun = false;
|
||||||
|
|
||||||
|
|
||||||
|
async init(): Promise<void> {
|
||||||
|
console.log('ExternalHooks.init');
|
||||||
|
|
||||||
|
if (this.initDidRun === true) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const externalHookFiles = config.get('externalHookFiles').split(':');
|
||||||
|
|
||||||
|
console.log('externalHookFiles');
|
||||||
|
console.log(externalHookFiles);
|
||||||
|
|
||||||
|
// Load all the provided hook-files
|
||||||
|
for (let hookFilePath of externalHookFiles) {
|
||||||
|
hookFilePath = hookFilePath.trim();
|
||||||
|
if (hookFilePath !== '') {
|
||||||
|
console.log(' --- load: ' + hookFilePath);
|
||||||
|
try {
|
||||||
|
const hookFile = require(hookFilePath);
|
||||||
|
|
||||||
|
for (const resource of Object.keys(hookFile)) {
|
||||||
|
for (const operation of Object.keys(hookFile[resource])) {
|
||||||
|
// Save all the hook functions directly under their string
|
||||||
|
// format in an array
|
||||||
|
const hookString = `${resource}.${operation}`;
|
||||||
|
if (this.externalHooks[hookString] === undefined) {
|
||||||
|
this.externalHooks[hookString] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
this.externalHooks[hookString].push.apply(this.externalHooks[hookString], hookFile[resource][operation]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Problem loading external hook file "${hookFilePath}": ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.initDidRun = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async run(hookName: string, hookParameters?: any[]): Promise<void> { // tslint:disable-line:no-any
|
||||||
|
console.log('RUN NOW: ' + hookName);
|
||||||
|
|
||||||
|
const externalHookFunctions: IExternalHooksFunctions = {
|
||||||
|
dbCollections: Db.collections,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.externalHooks[hookName] === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(const externalHookFunction of this.externalHooks[hookName]) {
|
||||||
|
await externalHookFunction.apply(externalHookFunctions, hookParameters);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
let externalHooksInstance: ExternalHooksClass | undefined;
|
||||||
|
|
||||||
|
export function ExternalHooks(): ExternalHooksClass {
|
||||||
|
if (externalHooksInstance === undefined) {
|
||||||
|
externalHooksInstance = new ExternalHooksClass();
|
||||||
|
}
|
||||||
|
|
||||||
|
return externalHooksInstance;
|
||||||
|
}
|
|
@ -197,6 +197,30 @@ export interface IExecutingWorkflowData {
|
||||||
workflowExecution?: PCancelable<IRun>;
|
workflowExecution?: PCancelable<IRun>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IExternalHooks {
|
||||||
|
credentials?: {
|
||||||
|
create?: Array<{ (this: IExternalHooksFunctions, credentialsData: ICredentialsEncrypted): Promise<void>; }>
|
||||||
|
delete?: Array<{ (this: IExternalHooksFunctions, credentialId: string): Promise<void>; }>
|
||||||
|
update?: Array<{ (this: IExternalHooksFunctions, credentialsData: ICredentialsDb): Promise<void>; }>
|
||||||
|
};
|
||||||
|
workflow?: {
|
||||||
|
activate?: Array<{ (this: IExternalHooksFunctions, workflowData: IWorkflowDb): Promise<void>; }>
|
||||||
|
create?: Array<{ (this: IExternalHooksFunctions, workflowData: IWorkflowBase): Promise<void>; }>
|
||||||
|
delete?: Array<{ (this: IExternalHooksFunctions, workflowId: string): Promise<void>; }>
|
||||||
|
execute?: Array<{ (this: IExternalHooksFunctions, workflowData: IWorkflowDb, mode: WorkflowExecuteMode): Promise<void>; }>
|
||||||
|
update?: Array<{ (this: IExternalHooksFunctions, workflowData: IWorkflowDb): Promise<void>; }>
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IExternalHooksFunctions {
|
||||||
|
dbCollections: IDatabaseCollections;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IExternalHooksClass {
|
||||||
|
init(): Promise<void>;
|
||||||
|
run(hookName: string, hookParameters?: any[]): Promise<void>; // tslint:disable-line:no-any
|
||||||
|
}
|
||||||
|
|
||||||
export interface IN8nConfig {
|
export interface IN8nConfig {
|
||||||
database: IN8nConfigDatabase;
|
database: IN8nConfigDatabase;
|
||||||
endpoints: IN8nConfigEndpoints;
|
endpoints: IN8nConfigEndpoints;
|
||||||
|
|
|
@ -27,6 +27,7 @@ import {
|
||||||
CredentialsHelper,
|
CredentialsHelper,
|
||||||
CredentialTypes,
|
CredentialTypes,
|
||||||
Db,
|
Db,
|
||||||
|
ExternalHooks,
|
||||||
IActivationError,
|
IActivationError,
|
||||||
ICustomRequest,
|
ICustomRequest,
|
||||||
ICredentialsDb,
|
ICredentialsDb,
|
||||||
|
@ -41,6 +42,7 @@ import {
|
||||||
IExecutionsListResponse,
|
IExecutionsListResponse,
|
||||||
IExecutionsStopData,
|
IExecutionsStopData,
|
||||||
IExecutionsSummary,
|
IExecutionsSummary,
|
||||||
|
IExternalHooksClass,
|
||||||
IN8nUISettings,
|
IN8nUISettings,
|
||||||
IPackageVersions,
|
IPackageVersions,
|
||||||
IWorkflowBase,
|
IWorkflowBase,
|
||||||
|
@ -103,6 +105,7 @@ class App {
|
||||||
testWebhooks: TestWebhooks.TestWebhooks;
|
testWebhooks: TestWebhooks.TestWebhooks;
|
||||||
endpointWebhook: string;
|
endpointWebhook: string;
|
||||||
endpointWebhookTest: string;
|
endpointWebhookTest: string;
|
||||||
|
externalHooks: IExternalHooksClass;
|
||||||
saveDataErrorExecution: string;
|
saveDataErrorExecution: string;
|
||||||
saveDataSuccessExecution: string;
|
saveDataSuccessExecution: string;
|
||||||
saveManualExecutions: boolean;
|
saveManualExecutions: boolean;
|
||||||
|
@ -134,6 +137,8 @@ class App {
|
||||||
this.protocol = config.get('protocol');
|
this.protocol = config.get('protocol');
|
||||||
this.sslKey = config.get('ssl_key');
|
this.sslKey = config.get('ssl_key');
|
||||||
this.sslCert = config.get('ssl_cert');
|
this.sslCert = config.get('ssl_cert');
|
||||||
|
|
||||||
|
this.externalHooks = ExternalHooks();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -351,7 +356,7 @@ class App {
|
||||||
// Creates a new workflow
|
// Creates a new workflow
|
||||||
this.app.post('/rest/workflows', ResponseHelper.send(async (req: express.Request, res: express.Response): Promise<IWorkflowResponse> => {
|
this.app.post('/rest/workflows', ResponseHelper.send(async (req: express.Request, res: express.Response): Promise<IWorkflowResponse> => {
|
||||||
|
|
||||||
const newWorkflowData = req.body;
|
const newWorkflowData = req.body as IWorkflowBase;
|
||||||
|
|
||||||
newWorkflowData.name = newWorkflowData.name.trim();
|
newWorkflowData.name = newWorkflowData.name.trim();
|
||||||
newWorkflowData.createdAt = this.getCurrentDate();
|
newWorkflowData.createdAt = this.getCurrentDate();
|
||||||
|
@ -359,6 +364,8 @@ class App {
|
||||||
|
|
||||||
newWorkflowData.id = undefined;
|
newWorkflowData.id = undefined;
|
||||||
|
|
||||||
|
await this.externalHooks.run('workflow.create', [newWorkflowData]);
|
||||||
|
|
||||||
// Save the workflow in DB
|
// Save the workflow in DB
|
||||||
const result = await Db.collections.Workflow!.save(newWorkflowData);
|
const result = await Db.collections.Workflow!.save(newWorkflowData);
|
||||||
|
|
||||||
|
@ -434,9 +441,11 @@ class App {
|
||||||
// Updates an existing workflow
|
// Updates an existing workflow
|
||||||
this.app.patch('/rest/workflows/:id', ResponseHelper.send(async (req: express.Request, res: express.Response): Promise<IWorkflowResponse> => {
|
this.app.patch('/rest/workflows/:id', ResponseHelper.send(async (req: express.Request, res: express.Response): Promise<IWorkflowResponse> => {
|
||||||
|
|
||||||
const newWorkflowData = req.body;
|
const newWorkflowData = req.body as IWorkflowBase;
|
||||||
const id = req.params.id;
|
const id = req.params.id;
|
||||||
|
|
||||||
|
await this.externalHooks.run('workflow.update', [newWorkflowData]);
|
||||||
|
|
||||||
if (this.activeWorkflowRunner.isActive(id)) {
|
if (this.activeWorkflowRunner.isActive(id)) {
|
||||||
// When workflow gets saved always remove it as the triggers could have been
|
// When workflow gets saved always remove it as the triggers could have been
|
||||||
// changed and so the changes would not take effect
|
// changed and so the changes would not take effect
|
||||||
|
@ -478,6 +487,8 @@ class App {
|
||||||
if (responseData.active === true) {
|
if (responseData.active === true) {
|
||||||
// When the workflow is supposed to be active add it again
|
// When the workflow is supposed to be active add it again
|
||||||
try {
|
try {
|
||||||
|
await this.externalHooks.run('workflow.activate', [responseData]);
|
||||||
|
|
||||||
await this.activeWorkflowRunner.add(id);
|
await this.activeWorkflowRunner.add(id);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// If workflow could not be activated set it again to inactive
|
// If workflow could not be activated set it again to inactive
|
||||||
|
@ -502,6 +513,8 @@ class App {
|
||||||
this.app.delete('/rest/workflows/:id', ResponseHelper.send(async (req: express.Request, res: express.Response): Promise<boolean> => {
|
this.app.delete('/rest/workflows/:id', ResponseHelper.send(async (req: express.Request, res: express.Response): Promise<boolean> => {
|
||||||
const id = req.params.id;
|
const id = req.params.id;
|
||||||
|
|
||||||
|
await this.externalHooks.run('workflow.delete', [id]);
|
||||||
|
|
||||||
if (this.activeWorkflowRunner.isActive(id)) {
|
if (this.activeWorkflowRunner.isActive(id)) {
|
||||||
// Before deleting a workflow deactivate it
|
// Before deleting a workflow deactivate it
|
||||||
await this.activeWorkflowRunner.remove(id);
|
await this.activeWorkflowRunner.remove(id);
|
||||||
|
@ -663,6 +676,8 @@ class App {
|
||||||
this.app.delete('/rest/credentials/:id', ResponseHelper.send(async (req: express.Request, res: express.Response): Promise<boolean> => {
|
this.app.delete('/rest/credentials/:id', ResponseHelper.send(async (req: express.Request, res: express.Response): Promise<boolean> => {
|
||||||
const id = req.params.id;
|
const id = req.params.id;
|
||||||
|
|
||||||
|
await this.externalHooks.run('credentials.delete', [id]);
|
||||||
|
|
||||||
await Db.collections.Credentials!.delete({ id });
|
await Db.collections.Credentials!.delete({ id });
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -708,6 +723,8 @@ class App {
|
||||||
credentials.setData(incomingData.data, encryptionKey);
|
credentials.setData(incomingData.data, encryptionKey);
|
||||||
const newCredentialsData = credentials.getDataToSave() as ICredentialsDb;
|
const newCredentialsData = credentials.getDataToSave() as ICredentialsDb;
|
||||||
|
|
||||||
|
await this.externalHooks.run('credentials.create', [newCredentialsData]);
|
||||||
|
|
||||||
// Add special database related data
|
// Add special database related data
|
||||||
newCredentialsData.createdAt = this.getCurrentDate();
|
newCredentialsData.createdAt = this.getCurrentDate();
|
||||||
newCredentialsData.updatedAt = this.getCurrentDate();
|
newCredentialsData.updatedAt = this.getCurrentDate();
|
||||||
|
@ -783,6 +800,8 @@ class App {
|
||||||
// Add special database related data
|
// Add special database related data
|
||||||
newCredentialsData.updatedAt = this.getCurrentDate();
|
newCredentialsData.updatedAt = this.getCurrentDate();
|
||||||
|
|
||||||
|
await this.externalHooks.run('credentials.update', [newCredentialsData]);
|
||||||
|
|
||||||
// Update the credentials in DB
|
// Update the credentials in DB
|
||||||
await Db.collections.Credentials!.update(id, newCredentialsData);
|
await Db.collections.Credentials!.update(id, newCredentialsData);
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import {
|
import {
|
||||||
CredentialsHelper,
|
CredentialsHelper,
|
||||||
Db,
|
Db,
|
||||||
|
ExternalHooks,
|
||||||
IExecutionDb,
|
IExecutionDb,
|
||||||
IExecutionFlattedDb,
|
IExecutionFlattedDb,
|
||||||
IPushDataExecutionFinished,
|
IPushDataExecutionFinished,
|
||||||
|
@ -303,6 +304,10 @@ export async function executeWorkflow(workflowInfo: IExecuteWorkflowInfo, additi
|
||||||
workflowData = workflowInfo.code;
|
workflowData = workflowInfo.code;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const externalHooks = ExternalHooks();
|
||||||
|
await externalHooks.init();
|
||||||
|
await externalHooks.run('workflow.execute', [workflowData, mode]);
|
||||||
|
|
||||||
const nodeTypes = NodeTypes();
|
const nodeTypes = NodeTypes();
|
||||||
|
|
||||||
const workflowName = workflowData ? workflowData.name : undefined;
|
const workflowName = workflowData ? workflowData.name : undefined;
|
||||||
|
|
|
@ -2,6 +2,7 @@ import {
|
||||||
ActiveExecutions,
|
ActiveExecutions,
|
||||||
CredentialsOverwrites,
|
CredentialsOverwrites,
|
||||||
CredentialTypes,
|
CredentialTypes,
|
||||||
|
ExternalHooks,
|
||||||
ICredentialsOverwrite,
|
ICredentialsOverwrite,
|
||||||
ICredentialsTypeData,
|
ICredentialsTypeData,
|
||||||
IProcessMessageDataHook,
|
IProcessMessageDataHook,
|
||||||
|
@ -100,6 +101,9 @@ export class WorkflowRunner {
|
||||||
* @memberof WorkflowRunner
|
* @memberof WorkflowRunner
|
||||||
*/
|
*/
|
||||||
async run(data: IWorkflowExecutionDataProcess, loadStaticData?: boolean): Promise<string> {
|
async run(data: IWorkflowExecutionDataProcess, loadStaticData?: boolean): Promise<string> {
|
||||||
|
const externalHooks = ExternalHooks();
|
||||||
|
await externalHooks.run('workflow.execute', [data.workflowData, data.executionMode]);
|
||||||
|
|
||||||
const executionsProcess = config.get('executions.process') as string;
|
const executionsProcess = config.get('executions.process') as string;
|
||||||
if (executionsProcess === 'main') {
|
if (executionsProcess === 'main') {
|
||||||
return this.runMainProcess(data, loadStaticData);
|
return this.runMainProcess(data, loadStaticData);
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
export * from './CredentialsHelper';
|
export * from './CredentialsHelper';
|
||||||
export * from './CredentialTypes';
|
export * from './CredentialTypes';
|
||||||
export * from './CredentialsOverwrites';
|
export * from './CredentialsOverwrites';
|
||||||
|
export * from './ExternalHooks';
|
||||||
export * from './Interfaces';
|
export * from './Interfaces';
|
||||||
export * from './LoadNodesAndCredentials';
|
export * from './LoadNodesAndCredentials';
|
||||||
export * from './NodeTypes';
|
export * from './NodeTypes';
|
||||||
|
|
Loading…
Reference in a new issue