mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
Done
This commit is contained in:
parent
f9db4bb501
commit
91c40367e1
|
@ -11,11 +11,11 @@ import {
|
||||||
WorkflowHelpers,
|
WorkflowHelpers,
|
||||||
WorkflowRunner,
|
WorkflowRunner,
|
||||||
WorkflowExecuteAdditionalData,
|
WorkflowExecuteAdditionalData,
|
||||||
|
IWebhookDb,
|
||||||
} from './';
|
} from './';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ActiveWorkflows,
|
ActiveWorkflows,
|
||||||
ActiveWebhooks,
|
|
||||||
NodeExecuteFunctions,
|
NodeExecuteFunctions,
|
||||||
} from 'n8n-core';
|
} from 'n8n-core';
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ import {
|
||||||
INode,
|
INode,
|
||||||
INodeExecutionData,
|
INodeExecutionData,
|
||||||
IRunExecutionData,
|
IRunExecutionData,
|
||||||
IWebhookData,
|
NodeHelpers,
|
||||||
IWorkflowExecuteAdditionalData as IWorkflowExecuteAdditionalDataWorkflow,
|
IWorkflowExecuteAdditionalData as IWorkflowExecuteAdditionalDataWorkflow,
|
||||||
WebhookHttpMethod,
|
WebhookHttpMethod,
|
||||||
Workflow,
|
Workflow,
|
||||||
|
@ -38,19 +38,21 @@ import * as express from 'express';
|
||||||
|
|
||||||
export class ActiveWorkflowRunner {
|
export class ActiveWorkflowRunner {
|
||||||
private activeWorkflows: ActiveWorkflows | null = null;
|
private activeWorkflows: ActiveWorkflows | null = null;
|
||||||
private activeWebhooks: ActiveWebhooks | null = null;
|
|
||||||
private activationErrors: {
|
private activationErrors: {
|
||||||
[key: string]: IActivationError;
|
[key: string]: IActivationError;
|
||||||
} = {};
|
} = {};
|
||||||
|
|
||||||
|
|
||||||
async init() {
|
async init() {
|
||||||
|
|
||||||
// Get the active workflows from database
|
// Get the active workflows from database
|
||||||
|
|
||||||
|
// NOTE
|
||||||
|
// Here I guess we can have a flag on the workflow table like hasTrigger
|
||||||
|
// so intead of pulling all the active wehhooks just pull the actives that have a trigger
|
||||||
const workflowsData: IWorkflowDb[] = await Db.collections.Workflow!.find({ active: true }) as IWorkflowDb[];
|
const workflowsData: IWorkflowDb[] = await Db.collections.Workflow!.find({ active: true }) as IWorkflowDb[];
|
||||||
|
|
||||||
this.activeWebhooks = new ActiveWebhooks();
|
|
||||||
|
|
||||||
// Add them as active workflows
|
|
||||||
this.activeWorkflows = new ActiveWorkflows();
|
this.activeWorkflows = new ActiveWorkflows();
|
||||||
|
|
||||||
if (workflowsData.length !== 0) {
|
if (workflowsData.length !== 0) {
|
||||||
|
@ -58,20 +60,27 @@ export class ActiveWorkflowRunner {
|
||||||
console.log(' Start Active Workflows:');
|
console.log(' Start Active Workflows:');
|
||||||
console.log(' ================================');
|
console.log(' ================================');
|
||||||
|
|
||||||
|
const nodeTypes = NodeTypes();
|
||||||
|
|
||||||
for (const workflowData of workflowsData) {
|
for (const workflowData of workflowsData) {
|
||||||
console.log(` - ${workflowData.name}`);
|
|
||||||
try {
|
const workflow = new Workflow({ id: workflowData.id.toString(), name: workflowData.name, nodes: workflowData.nodes, connections: workflowData.connections, active: workflowData.active, nodeTypes, staticData: workflowData.staticData, settings: workflowData.settings});
|
||||||
await this.add(workflowData.id.toString(), workflowData);
|
|
||||||
console.log(` => Started`);
|
if (workflow.getTriggerNodes().length !== 0
|
||||||
} catch (error) {
|
|| workflow.getPollNodes().length !== 0) {
|
||||||
console.log(` => ERROR: Workflow could not be activated:`);
|
console.log(` - ${workflowData.name}`);
|
||||||
console.log(` ${error.message}`);
|
try {
|
||||||
|
await this.add(workflowData.id.toString(), workflowData);
|
||||||
|
console.log(` => Started`);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(` => ERROR: Workflow could not be activated:`);
|
||||||
|
console.log(` ${error.message}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes all the currently active workflows
|
* Removes all the currently active workflows
|
||||||
*
|
*
|
||||||
|
@ -94,7 +103,6 @@ export class ActiveWorkflowRunner {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if a webhook for the given method and path exists and executes the workflow.
|
* Checks if a webhook for the given method and path exists and executes the workflow.
|
||||||
*
|
*
|
||||||
|
@ -110,30 +118,41 @@ export class ActiveWorkflowRunner {
|
||||||
throw new ResponseHelper.ResponseError('The "activeWorkflows" instance did not get initialized yet.', 404, 404);
|
throw new ResponseHelper.ResponseError('The "activeWorkflows" instance did not get initialized yet.', 404, 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
const webhookData: IWebhookData | undefined = this.activeWebhooks!.get(httpMethod, path);
|
const webhook = await Db.collections.Webhook?.findOne({ webhookPath: path, method: httpMethod }) as IWebhookDb;
|
||||||
|
|
||||||
if (webhookData === undefined) {
|
// check if something exist
|
||||||
|
if (webhook === undefined) {
|
||||||
// The requested webhook is not registered
|
// The requested webhook is not registered
|
||||||
throw new ResponseHelper.ResponseError(`The requested webhook "${httpMethod} ${path}" is not registered.`, 404, 404);
|
throw new ResponseHelper.ResponseError(`The requested webhook "${httpMethod} ${path}" is not registered.`, 404, 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
const workflowData = await Db.collections.Workflow!.findOne(webhookData.workflowId);
|
const workflowData = await Db.collections.Workflow!.findOne(webhook.workflowId);
|
||||||
if (workflowData === undefined) {
|
if (workflowData === undefined) {
|
||||||
throw new ResponseHelper.ResponseError(`Could not find workflow with id "${webhookData.workflowId}"`, 404, 404);
|
throw new ResponseHelper.ResponseError(`Could not find workflow with id "${webhook.workflowId}"`, 404, 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
const nodeTypes = NodeTypes();
|
const nodeTypes = NodeTypes();
|
||||||
const workflow = new Workflow({ id: webhookData.workflowId, 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]);
|
||||||
|
|
||||||
|
const additionalData = await WorkflowExecuteAdditionalData.getBase(credentials);
|
||||||
|
|
||||||
|
const webhookData = NodeHelpers.getNodeWebhooks(workflow, workflow.getNode(webhook.node as string) as INode, additionalData).filter((webhook) => {
|
||||||
|
return (webhook.httpMethod === httpMethod && webhook.path === path);
|
||||||
|
})[0];
|
||||||
|
|
||||||
// Get the node which has the webhook defined to know where to start from and to
|
// Get the node which has the webhook defined to know where to start from and to
|
||||||
// get additional data
|
// get additional data
|
||||||
const workflowStartNode = workflow.getNode(webhookData.node);
|
const workflowStartNode = workflow.getNode(webhookData.node);
|
||||||
|
|
||||||
if (workflowStartNode === null) {
|
if (workflowStartNode === null) {
|
||||||
throw new ResponseHelper.ResponseError('Could not find node to process webhook.', 404, 404);
|
throw new ResponseHelper.ResponseError('Could not find node to process webhook.', 404, 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const executionMode = 'webhook';
|
const executionMode = 'webhook';
|
||||||
|
//@ts-ignore
|
||||||
WebhookHelpers.executeWebhook(workflow, webhookData, workflowData, workflowStartNode, executionMode, undefined, req, res, (error: Error | null, data: object) => {
|
WebhookHelpers.executeWebhook(workflow, webhookData, workflowData, workflowStartNode, executionMode, undefined, req, res, (error: Error | null, data: object) => {
|
||||||
if (error !== null) {
|
if (error !== null) {
|
||||||
return reject(error);
|
return reject(error);
|
||||||
|
@ -143,19 +162,14 @@ export class ActiveWorkflowRunner {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the ids of the currently active workflows
|
* Returns the ids of the currently active workflows
|
||||||
*
|
*
|
||||||
* @returns {string[]}
|
* @returns {string[]}
|
||||||
* @memberof ActiveWorkflowRunner
|
* @memberof ActiveWorkflowRunner
|
||||||
*/
|
*/
|
||||||
getActiveWorkflows(): string[] {
|
getActiveWorkflows(): Promise<IWorkflowDb[]> {
|
||||||
if (this.activeWorkflows === null) {
|
return Db.collections.Workflow?.find({ select: ['id'] }) as Promise<IWorkflowDb[]>;
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.activeWorkflows.allActiveWorkflows();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -166,15 +180,11 @@ export class ActiveWorkflowRunner {
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
* @memberof ActiveWorkflowRunner
|
* @memberof ActiveWorkflowRunner
|
||||||
*/
|
*/
|
||||||
isActive(id: string): boolean {
|
async isActive(id: string): Promise<boolean> {
|
||||||
if (this.activeWorkflows !== null) {
|
const workflow = await Db.collections.Workflow?.findOne({ id }) as IWorkflowDb;
|
||||||
return this.activeWorkflows.isActive(id);
|
return workflow?.active as boolean;
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return error if there was a problem activating the workflow
|
* Return error if there was a problem activating the workflow
|
||||||
*
|
*
|
||||||
|
@ -190,7 +200,6 @@ export class ActiveWorkflowRunner {
|
||||||
return this.activationErrors[id];
|
return this.activationErrors[id];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds all the webhooks of the workflow
|
* Adds all the webhooks of the workflow
|
||||||
*
|
*
|
||||||
|
@ -202,12 +211,47 @@ export class ActiveWorkflowRunner {
|
||||||
*/
|
*/
|
||||||
async addWorkflowWebhooks(workflow: Workflow, additionalData: IWorkflowExecuteAdditionalDataWorkflow, mode: WorkflowExecuteMode): Promise<void> {
|
async addWorkflowWebhooks(workflow: Workflow, additionalData: IWorkflowExecuteAdditionalDataWorkflow, mode: WorkflowExecuteMode): Promise<void> {
|
||||||
const webhooks = WebhookHelpers.getWorkflowWebhooks(workflow, additionalData);
|
const webhooks = WebhookHelpers.getWorkflowWebhooks(workflow, additionalData);
|
||||||
|
let path = '';
|
||||||
|
|
||||||
for (const webhookData of webhooks) {
|
for (const webhookData of webhooks) {
|
||||||
await this.activeWebhooks!.add(workflow, webhookData, mode);
|
|
||||||
// Save static data!
|
const node = workflow.getNode(webhookData.node) as INode;
|
||||||
await WorkflowHelpers.saveStaticData(workflow);
|
node.name = webhookData.node;
|
||||||
|
|
||||||
|
path = node.parameters.path as string;
|
||||||
|
|
||||||
|
if (node.parameters.path === undefined) {
|
||||||
|
path = workflow.getSimpleParameterValue(node, webhookData.webhookDescription['path'], 'GET') as string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const webhook = {
|
||||||
|
workflowId: webhookData.workflowId,
|
||||||
|
webhookPath: NodeHelpers.getNodeWebhookPath(workflow.id as string, node, path),
|
||||||
|
node: node.name,
|
||||||
|
method: webhookData.httpMethod,
|
||||||
|
} as IWebhookDb;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await Db.collections.Webhook?.insert(webhook);
|
||||||
|
|
||||||
|
const webhookExists = await workflow.runWebhookMethod('checkExists', webhookData, NodeExecuteFunctions, mode, false);
|
||||||
|
if (webhookExists === false) {
|
||||||
|
// If webhook does not exist yet create it
|
||||||
|
await workflow.runWebhookMethod('create', webhookData, NodeExecuteFunctions, mode, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
// The workflow was saved with two webhooks with the
|
||||||
|
// same path/method so delete all webhooks saved
|
||||||
|
|
||||||
|
await Db.collections.Webhook?.delete({ workflowId: workflow.id });
|
||||||
|
|
||||||
|
// then show error to the user
|
||||||
|
throw new Error(error.message || error.detail);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
// Save static data!
|
||||||
|
await WorkflowHelpers.saveStaticData(workflow);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -227,10 +271,22 @@ export class ActiveWorkflowRunner {
|
||||||
const nodeTypes = NodeTypes();
|
const nodeTypes = NodeTypes();
|
||||||
const workflow = new Workflow({ id: workflowId, name: workflowData.name, nodes: workflowData.nodes, connections: workflowData.connections, active: workflowData.active, nodeTypes, staticData: workflowData.staticData, settings: workflowData.settings });
|
const workflow = new Workflow({ id: workflowId, name: workflowData.name, nodes: workflowData.nodes, connections: workflowData.connections, active: workflowData.active, nodeTypes, staticData: workflowData.staticData, settings: workflowData.settings });
|
||||||
|
|
||||||
await this.activeWebhooks!.removeWorkflow(workflow);
|
const mode = 'internal';
|
||||||
|
|
||||||
// Save the static workflow data if needed
|
const credentials = await WorkflowCredentials(workflowData.nodes);
|
||||||
await WorkflowHelpers.saveStaticData(workflow);
|
const additionalData = await WorkflowExecuteAdditionalData.getBase(credentials);
|
||||||
|
|
||||||
|
const webhooks = WebhookHelpers.getWorkflowWebhooks(workflow, additionalData);
|
||||||
|
|
||||||
|
for (const webhookData of webhooks) {
|
||||||
|
await workflow.runWebhookMethod('delete', webhookData, NodeExecuteFunctions, mode, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
const webhook = {
|
||||||
|
workflowId: workflowData.id,
|
||||||
|
} as IWebhookDb;
|
||||||
|
|
||||||
|
await Db.collections.Webhook?.delete(webhook);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -322,7 +378,6 @@ export class ActiveWorkflowRunner {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Makes a workflow active
|
* Makes a workflow active
|
||||||
*
|
*
|
||||||
|
@ -361,7 +416,11 @@ export class ActiveWorkflowRunner {
|
||||||
|
|
||||||
// Add the workflows which have webhooks defined
|
// Add the workflows which have webhooks defined
|
||||||
await this.addWorkflowWebhooks(workflowInstance, additionalData, mode);
|
await this.addWorkflowWebhooks(workflowInstance, additionalData, mode);
|
||||||
await this.activeWorkflows.add(workflowId, workflowInstance, additionalData, getTriggerFunctions, getPollFunctions);
|
|
||||||
|
if (workflowInstance.getTriggerNodes().length !== 0
|
||||||
|
|| workflowInstance.getPollNodes().length !== 0) {
|
||||||
|
await this.activeWorkflows.add(workflowId, workflowInstance, additionalData, getTriggerFunctions, getPollFunctions);
|
||||||
|
}
|
||||||
|
|
||||||
if (this.activationErrors[workflowId] !== undefined) {
|
if (this.activationErrors[workflowId] !== undefined) {
|
||||||
// If there were activation errors delete them
|
// If there were activation errors delete them
|
||||||
|
@ -386,7 +445,6 @@ export class ActiveWorkflowRunner {
|
||||||
await WorkflowHelpers.saveStaticData(workflowInstance!);
|
await WorkflowHelpers.saveStaticData(workflowInstance!);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Makes a workflow inactive
|
* Makes a workflow inactive
|
||||||
*
|
*
|
||||||
|
@ -395,6 +453,7 @@ export class ActiveWorkflowRunner {
|
||||||
* @memberof ActiveWorkflowRunner
|
* @memberof ActiveWorkflowRunner
|
||||||
*/
|
*/
|
||||||
async remove(workflowId: string): Promise<void> {
|
async remove(workflowId: string): Promise<void> {
|
||||||
|
|
||||||
if (this.activeWorkflows !== null) {
|
if (this.activeWorkflows !== null) {
|
||||||
// Remove all the webhooks of the workflow
|
// Remove all the webhooks of the workflow
|
||||||
await this.removeWorkflowWebhooks(workflowId);
|
await this.removeWorkflowWebhooks(workflowId);
|
||||||
|
@ -404,8 +463,13 @@ export class ActiveWorkflowRunner {
|
||||||
delete this.activationErrors[workflowId];
|
delete this.activationErrors[workflowId];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove the workflow from the "list" of active workflows
|
// if it's active in memory then it's a trigger
|
||||||
return this.activeWorkflows.remove(workflowId);
|
// so remove from list of actives workflows
|
||||||
|
if (this.activeWorkflows.isActive(workflowId)) {
|
||||||
|
this.activeWorkflows.remove(workflowId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error(`The "activeWorkflows" instance did not get initialized yet.`);
|
throw new Error(`The "activeWorkflows" instance did not get initialized yet.`);
|
||||||
|
|
|
@ -27,10 +27,12 @@ export let collections: IDatabaseCollections = {
|
||||||
Credentials: null,
|
Credentials: null,
|
||||||
Execution: null,
|
Execution: null,
|
||||||
Workflow: null,
|
Workflow: null,
|
||||||
|
Webhook: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
import {
|
import {
|
||||||
InitialMigration1587669153312
|
InitialMigration1587669153312,
|
||||||
|
WebhookModel1589476000887,
|
||||||
} from './databases/postgresdb/migrations';
|
} from './databases/postgresdb/migrations';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
@ -81,7 +83,7 @@ export async function init(): Promise<IDatabaseCollections> {
|
||||||
port: await GenericHelpers.getConfigValue('database.postgresdb.port') as number,
|
port: await GenericHelpers.getConfigValue('database.postgresdb.port') as number,
|
||||||
username: await GenericHelpers.getConfigValue('database.postgresdb.user') as string,
|
username: await GenericHelpers.getConfigValue('database.postgresdb.user') as string,
|
||||||
schema: config.get('database.postgresdb.schema'),
|
schema: config.get('database.postgresdb.schema'),
|
||||||
migrations: [InitialMigration1587669153312],
|
migrations: [InitialMigration1587669153312, WebhookModel1589476000887],
|
||||||
migrationsRun: true,
|
migrationsRun: true,
|
||||||
migrationsTableName: `${entityPrefix}migrations`,
|
migrationsTableName: `${entityPrefix}migrations`,
|
||||||
};
|
};
|
||||||
|
@ -135,6 +137,7 @@ export async function init(): Promise<IDatabaseCollections> {
|
||||||
collections.Credentials = getRepository(entities.CredentialsEntity);
|
collections.Credentials = getRepository(entities.CredentialsEntity);
|
||||||
collections.Execution = getRepository(entities.ExecutionEntity);
|
collections.Execution = getRepository(entities.ExecutionEntity);
|
||||||
collections.Workflow = getRepository(entities.WorkflowEntity);
|
collections.Workflow = getRepository(entities.WorkflowEntity);
|
||||||
|
collections.Webhook = getRepository(entities.WebhookEntity);
|
||||||
|
|
||||||
return collections;
|
return collections;
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,8 +40,16 @@ export interface IDatabaseCollections {
|
||||||
Credentials: Repository<ICredentialsDb> | null;
|
Credentials: Repository<ICredentialsDb> | null;
|
||||||
Execution: Repository<IExecutionFlattedDb> | null;
|
Execution: Repository<IExecutionFlattedDb> | null;
|
||||||
Workflow: Repository<IWorkflowDb> | null;
|
Workflow: Repository<IWorkflowDb> | null;
|
||||||
|
Webhook: Repository<IWebhookDb> | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IWebhookDb {
|
||||||
|
id?: number | ObjectID;
|
||||||
|
workflowId: number | string | ObjectID;
|
||||||
|
webhookPath: string;
|
||||||
|
method: string;
|
||||||
|
node: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface IWorkflowBase extends IWorkflowBaseWorkflow {
|
export interface IWorkflowBase extends IWorkflowBaseWorkflow {
|
||||||
id?: number | string | ObjectID;
|
id?: number | string | ObjectID;
|
||||||
|
|
|
@ -427,7 +427,9 @@ class App {
|
||||||
const newWorkflowData = req.body;
|
const newWorkflowData = req.body;
|
||||||
const id = req.params.id;
|
const id = req.params.id;
|
||||||
|
|
||||||
if (this.activeWorkflowRunner.isActive(id)) {
|
const isActive = await this.activeWorkflowRunner.isActive(id);
|
||||||
|
|
||||||
|
if (isActive) {
|
||||||
// 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
|
||||||
await this.activeWorkflowRunner.remove(id);
|
await this.activeWorkflowRunner.remove(id);
|
||||||
|
@ -492,7 +494,9 @@ 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;
|
||||||
|
|
||||||
if (this.activeWorkflowRunner.isActive(id)) {
|
const isActive = await this.activeWorkflowRunner.isActive(id);
|
||||||
|
|
||||||
|
if (isActive) {
|
||||||
// Before deleting a workflow deactivate it
|
// Before deleting a workflow deactivate it
|
||||||
await this.activeWorkflowRunner.remove(id);
|
await this.activeWorkflowRunner.remove(id);
|
||||||
}
|
}
|
||||||
|
@ -503,6 +507,7 @@ class App {
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
this.app.post('/rest/workflows/run', ResponseHelper.send(async (req: express.Request, res: express.Response): Promise<IExecutionPushResponse> => {
|
this.app.post('/rest/workflows/run', ResponseHelper.send(async (req: express.Request, res: express.Response): Promise<IExecutionPushResponse> => {
|
||||||
const workflowData = req.body.workflowData;
|
const workflowData = req.body.workflowData;
|
||||||
const runData: IRunData | undefined = req.body.runData;
|
const runData: IRunData | undefined = req.body.runData;
|
||||||
|
@ -632,7 +637,8 @@ class App {
|
||||||
|
|
||||||
// Returns the active workflow ids
|
// Returns the active workflow ids
|
||||||
this.app.get('/rest/active', ResponseHelper.send(async (req: express.Request, res: express.Response): Promise<string[]> => {
|
this.app.get('/rest/active', ResponseHelper.send(async (req: express.Request, res: express.Response): Promise<string[]> => {
|
||||||
return this.activeWorkflowRunner.getActiveWorkflows();
|
const activeWorkflows = await this.activeWorkflowRunner.getActiveWorkflows();
|
||||||
|
return activeWorkflows.map(workflow => workflow.id.toString()) as string[];
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -69,6 +69,33 @@ export function getWorkflowWebhooks(workflow: Workflow, additionalData: IWorkflo
|
||||||
return returnData;
|
return returnData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all the webhooks which should be created for the give workflow
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @param {string} workflowId
|
||||||
|
* @param {Workflow} workflow
|
||||||
|
* @returns {IWebhookData[]}
|
||||||
|
*/
|
||||||
|
export function getWorkflowWebhooksBasic(workflow: Workflow): IWebhookData[] {
|
||||||
|
// Check all the nodes in the workflow if they have webhooks
|
||||||
|
|
||||||
|
const returnData: IWebhookData[] = [];
|
||||||
|
|
||||||
|
let parentNodes: string[] | undefined;
|
||||||
|
|
||||||
|
for (const node of Object.values(workflow.nodes)) {
|
||||||
|
if (parentNodes !== undefined && !parentNodes.includes(node.name)) {
|
||||||
|
// If parentNodes are given check only them if they have webhooks
|
||||||
|
// and no other ones
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
returnData.push.apply(returnData, NodeHelpers.getNodeWebhooksBasic(workflow, node));
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnData;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Executes a webhook
|
* Executes a webhook
|
||||||
|
|
31
packages/cli/src/databases/mongodb/WebhookEntity.ts
Normal file
31
packages/cli/src/databases/mongodb/WebhookEntity.ts
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
import {
|
||||||
|
Column,
|
||||||
|
Entity,
|
||||||
|
Unique,
|
||||||
|
ObjectIdColumn,
|
||||||
|
ObjectID,
|
||||||
|
} from 'typeorm';
|
||||||
|
|
||||||
|
import {
|
||||||
|
IWebhookDb,
|
||||||
|
} from '../../Interfaces';
|
||||||
|
|
||||||
|
@Entity()
|
||||||
|
@Unique(['webhookPath', 'method'])
|
||||||
|
export class WebhookEntity implements IWebhookDb {
|
||||||
|
|
||||||
|
@ObjectIdColumn()
|
||||||
|
id: ObjectID;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
workflowId: number;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
webhookPath: string;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
method: string;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
node: string;
|
||||||
|
}
|
|
@ -1,3 +1,5 @@
|
||||||
export * from './CredentialsEntity';
|
export * from './CredentialsEntity';
|
||||||
export * from './ExecutionEntity';
|
export * from './ExecutionEntity';
|
||||||
export * from './WorkflowEntity';
|
export * from './WorkflowEntity';
|
||||||
|
export * from './WebhookEntity';
|
||||||
|
|
||||||
|
|
30
packages/cli/src/databases/mysqldb/WebhookEntity.ts
Normal file
30
packages/cli/src/databases/mysqldb/WebhookEntity.ts
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
import {
|
||||||
|
Column,
|
||||||
|
Entity,
|
||||||
|
Unique,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
} from 'typeorm';
|
||||||
|
|
||||||
|
import {
|
||||||
|
IWebhookDb,
|
||||||
|
} from '../../Interfaces';
|
||||||
|
|
||||||
|
@Entity()
|
||||||
|
@Unique(['webhookPath', 'method'])
|
||||||
|
export class WebhookEntity implements IWebhookDb {
|
||||||
|
|
||||||
|
@PrimaryGeneratedColumn()
|
||||||
|
id: number;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
workflowId: number;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
webhookPath: string;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
method: string;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
node: string;
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
export * from './CredentialsEntity';
|
export * from './CredentialsEntity';
|
||||||
export * from './ExecutionEntity';
|
export * from './ExecutionEntity';
|
||||||
export * from './WorkflowEntity';
|
export * from './WorkflowEntity';
|
||||||
|
export * from './WebhookEntity';
|
||||||
|
|
30
packages/cli/src/databases/postgresdb/WebhookEntity.ts
Normal file
30
packages/cli/src/databases/postgresdb/WebhookEntity.ts
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
import {
|
||||||
|
Column,
|
||||||
|
Entity,
|
||||||
|
Unique,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
} from 'typeorm';
|
||||||
|
|
||||||
|
import {
|
||||||
|
IWebhookDb,
|
||||||
|
} from '../../';
|
||||||
|
|
||||||
|
@Entity()
|
||||||
|
@Unique(['webhookPath', 'method'])
|
||||||
|
export class WebhookEntity implements IWebhookDb {
|
||||||
|
|
||||||
|
@PrimaryGeneratedColumn()
|
||||||
|
id: number;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
workflowId: number;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
webhookPath: string;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
method: string;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
node: string;
|
||||||
|
}
|
|
@ -1,3 +1,5 @@
|
||||||
export * from './CredentialsEntity';
|
export * from './CredentialsEntity';
|
||||||
export * from './ExecutionEntity';
|
export * from './ExecutionEntity';
|
||||||
export * from './WorkflowEntity';
|
export * from './WorkflowEntity';
|
||||||
|
export * from './WebhookEntity';
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
import {MigrationInterface, QueryRunner} from 'typeorm';
|
||||||
|
|
||||||
|
import { IWorkflowDb, NodeTypes, WebhookHelpers } from '../../..';
|
||||||
|
import { Workflow } from 'n8n-workflow/dist/src/Workflow';
|
||||||
|
import {
|
||||||
|
IWebhookDb,
|
||||||
|
} from '../../../Interfaces';
|
||||||
|
|
||||||
|
import * as config from '../../../../config';
|
||||||
|
|
||||||
|
export class WebhookModel1589476000887 implements MigrationInterface {
|
||||||
|
name = 'WebhookModel1589476000887';
|
||||||
|
|
||||||
|
async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
let tablePrefix = config.get('database.tablePrefix');
|
||||||
|
const schema = config.get('database.postgresdb.schema');
|
||||||
|
if (schema) {
|
||||||
|
tablePrefix = schema + '.' + tablePrefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
await queryRunner.query(`CREATE TABLE ${tablePrefix}webhook_entity ("id" SERIAL NOT NULL, "workflowId" integer NOT NULL, "webhookPath" character varying NOT NULL, "method" character varying NOT NULL, "node" character varying NOT NULL, CONSTRAINT "UQ_b21ace2e13596ccd87dc9bf4ea6" UNIQUE ("webhookPath", "method"), CONSTRAINT "PK_202217c8b912cf70b93b1e87256" PRIMARY KEY ("id"))`, undefined);
|
||||||
|
|
||||||
|
const workflows = await queryRunner.query(`SELECT * FROM ${tablePrefix}workflow_entity WHERE active=true`) as IWorkflowDb[];
|
||||||
|
const data: IWebhookDb[] = [];
|
||||||
|
const nodeTypes = NodeTypes();
|
||||||
|
for (const workflow of workflows) {
|
||||||
|
const workflowInstance = new Workflow({ id: workflow.id as string, name: workflow.name, nodes: workflow.nodes, connections: workflow.connections, active: workflow.active, nodeTypes, staticData: workflow.staticData, settings: workflow.settings });
|
||||||
|
// I'm writing something more simple than this. I tried to use the built in method
|
||||||
|
// getWorkflowWebhooks but it needs additionaldata and to get it I need the credentials
|
||||||
|
// and for some reason when I use
|
||||||
|
// const credentials = await WorkflowCredentials(node);
|
||||||
|
// to get the credentials I got an error I think is cuz the database is yet not ready.
|
||||||
|
const webhooks = WebhookHelpers.getWorkflowWebhooksBasic(workflowInstance);
|
||||||
|
for (const webhook of webhooks) {
|
||||||
|
data.push({
|
||||||
|
workflowId: workflowInstance.id as string,
|
||||||
|
webhookPath: webhook.path,
|
||||||
|
method: webhook.httpMethod,
|
||||||
|
node: webhook.node,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.length !== 0) {
|
||||||
|
await queryRunner.manager.createQueryBuilder()
|
||||||
|
.insert()
|
||||||
|
.into(`${tablePrefix}webhook_entity`)
|
||||||
|
.values(data)
|
||||||
|
.execute();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
let tablePrefix = config.get('database.tablePrefix');
|
||||||
|
const schema = config.get('database.postgresdb.schema');
|
||||||
|
if (schema) {
|
||||||
|
tablePrefix = schema + '.' + tablePrefix;
|
||||||
|
}
|
||||||
|
await queryRunner.query(`DROP TABLE ${tablePrefix}webhook_entity`, undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1 +1,3 @@
|
||||||
export * from './1587669153312-InitialMigration';
|
export * from './1587669153312-InitialMigration';
|
||||||
|
export * from './1589476000887-WebhookModel';
|
||||||
|
|
||||||
|
|
30
packages/cli/src/databases/sqlite/WebhookEntity.ts
Normal file
30
packages/cli/src/databases/sqlite/WebhookEntity.ts
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
import {
|
||||||
|
Column,
|
||||||
|
Entity,
|
||||||
|
Unique,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
} from 'typeorm';
|
||||||
|
|
||||||
|
import {
|
||||||
|
IWebhookDb,
|
||||||
|
} from '../../Interfaces';
|
||||||
|
|
||||||
|
@Entity()
|
||||||
|
@Unique(['webhookPath', 'method'])
|
||||||
|
export class WebhookEntity implements IWebhookDb {
|
||||||
|
|
||||||
|
@PrimaryGeneratedColumn()
|
||||||
|
id: number;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
workflowId: number;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
webhookPath: string;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
method: string;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
node: string;
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
export * from './CredentialsEntity';
|
export * from './CredentialsEntity';
|
||||||
export * from './ExecutionEntity';
|
export * from './ExecutionEntity';
|
||||||
export * from './WorkflowEntity';
|
export * from './WorkflowEntity';
|
||||||
|
export * from './WebhookEntity';
|
||||||
|
|
|
@ -23,7 +23,9 @@
|
||||||
"test:e2e": "vue-cli-service test:e2e",
|
"test:e2e": "vue-cli-service test:e2e",
|
||||||
"test:unit": "vue-cli-service test:unit"
|
"test:unit": "vue-cli-service test:unit"
|
||||||
},
|
},
|
||||||
"dependencies": {},
|
"dependencies": {
|
||||||
|
"uuid": "^8.1.0"
|
||||||
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@beyonk/google-fonts-webpack-plugin": "^1.2.3",
|
"@beyonk/google-fonts-webpack-plugin": "^1.2.3",
|
||||||
"@fortawesome/fontawesome-svg-core": "^1.2.19",
|
"@fortawesome/fontawesome-svg-core": "^1.2.19",
|
||||||
|
|
|
@ -126,6 +126,8 @@ import RunData from '@/components/RunData.vue';
|
||||||
|
|
||||||
import mixins from 'vue-typed-mixins';
|
import mixins from 'vue-typed-mixins';
|
||||||
|
|
||||||
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
|
||||||
import { debounce } from 'lodash';
|
import { debounce } from 'lodash';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import {
|
import {
|
||||||
|
@ -946,6 +948,10 @@ export default mixins(
|
||||||
// Check if node-name is unique else find one that is
|
// Check if node-name is unique else find one that is
|
||||||
newNodeData.name = this.getUniqueNodeName(newNodeData.name);
|
newNodeData.name = this.getUniqueNodeName(newNodeData.name);
|
||||||
|
|
||||||
|
if (nodeTypeData.webhooks && nodeTypeData.webhooks.length) {
|
||||||
|
newNodeData.webhookPath = uuidv4();
|
||||||
|
}
|
||||||
|
|
||||||
await this.addNodes([newNodeData]);
|
await this.addNodes([newNodeData]);
|
||||||
|
|
||||||
// Automatically deselect all nodes and select the current one and also active
|
// Automatically deselect all nodes and select the current one and also active
|
||||||
|
|
|
@ -297,6 +297,7 @@ export interface INode {
|
||||||
continueOnFail?: boolean;
|
continueOnFail?: boolean;
|
||||||
parameters: INodeParameters;
|
parameters: INodeParameters;
|
||||||
credentials?: INodeCredentials;
|
credentials?: INodeCredentials;
|
||||||
|
webhookPath?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -791,6 +791,59 @@ export function getNodeWebhooks(workflow: Workflow, node: INode, additionalData:
|
||||||
return returnData;
|
return returnData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getNodeWebhooksBasic(workflow: Workflow, node: INode): IWebhookData[] {
|
||||||
|
if (node.disabled === true) {
|
||||||
|
// Node is disabled so webhooks will also not be enabled
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const nodeType = workflow.nodeTypes.getByName(node.type) as INodeType;
|
||||||
|
|
||||||
|
if (nodeType.description.webhooks === undefined) {
|
||||||
|
// Node does not have any webhooks so return
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const workflowId = workflow.id || '__UNSAVED__';
|
||||||
|
|
||||||
|
const returnData: IWebhookData[] = [];
|
||||||
|
for (const webhookDescription of nodeType.description.webhooks) {
|
||||||
|
let nodeWebhookPath = workflow.getSimpleParameterValue(node, webhookDescription['path'], 'GET');
|
||||||
|
if (nodeWebhookPath === undefined) {
|
||||||
|
// TODO: Use a proper logger
|
||||||
|
console.error(`No webhook path could be found for node "${node.name}" in workflow "${workflowId}".`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeWebhookPath = nodeWebhookPath.toString();
|
||||||
|
|
||||||
|
if (nodeWebhookPath.charAt(0) === '/') {
|
||||||
|
nodeWebhookPath = nodeWebhookPath.slice(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const path = getNodeWebhookPath(workflowId, node, nodeWebhookPath);
|
||||||
|
|
||||||
|
const httpMethod = workflow.getSimpleParameterValue(node, webhookDescription['httpMethod'], 'GET');
|
||||||
|
|
||||||
|
if (httpMethod === undefined) {
|
||||||
|
// TODO: Use a proper logger
|
||||||
|
console.error(`The webhook "${path}" for node "${node.name}" in workflow "${workflowId}" could not be added because the httpMethod is not defined.`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
//@ts-ignore
|
||||||
|
returnData.push({
|
||||||
|
httpMethod: httpMethod.toString() as WebhookHttpMethod,
|
||||||
|
node: node.name,
|
||||||
|
path,
|
||||||
|
webhookDescription,
|
||||||
|
workflowId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnData;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the webhook path
|
* Returns the webhook path
|
||||||
|
@ -802,7 +855,16 @@ export function getNodeWebhooks(workflow: Workflow, node: INode, additionalData:
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
export function getNodeWebhookPath(workflowId: string, node: INode, path: string): string {
|
export function getNodeWebhookPath(workflowId: string, node: INode, path: string): string {
|
||||||
return `${workflowId}/${encodeURIComponent(node.name.toLowerCase())}/${path}`;
|
let webhookPath = '';
|
||||||
|
if (node.webhookPath === undefined) {
|
||||||
|
webhookPath = `${workflowId}/${encodeURIComponent(node.name.toLowerCase())}/${path}`;
|
||||||
|
} else {
|
||||||
|
if (node.type === 'n8n-nodes-base.webhook') {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
webhookPath = `${node.webhookPath}/${path}`;
|
||||||
|
}
|
||||||
|
return webhookPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue