n8n/packages/cli/src/WorkflowExecuteAdditionalData.ts

283 lines
9.2 KiB
TypeScript
Raw Normal View History

2019-06-23 03:35:23 -07:00
import {
Db,
IExecutionDb,
IExecutionFlattedDb,
IPushDataExecutionFinished,
IWorkflowBase,
IWorkflowExecutionDataProcess,
2019-06-23 03:35:23 -07:00
Push,
ResponseHelper,
WebhookHelpers,
WorkflowHelpers,
} from './';
import {
UserSettings,
} from 'n8n-core';
2019-06-23 03:35:23 -07:00
import {
IDataObject,
2019-10-20 12:42:34 -07:00
INodeParameters,
2019-06-23 03:35:23 -07:00
IRun,
ITaskData,
IWorkflowCredentials,
2019-06-23 03:35:23 -07:00
IWorkflowExecuteAdditionalData,
IWorkflowExecuteHooks,
2019-06-23 03:35:23 -07:00
WorkflowExecuteMode,
} from 'n8n-workflow';
import * as config from '../config';
2019-06-23 03:35:23 -07:00
/**
* Checks if there was an error and if errorWorkflow is defined. If so it collects
* all the data and executes it
*
* @param {IWorkflowBase} workflowData The workflow which got executed
* @param {IRun} fullRunData The run which produced the error
* @param {WorkflowExecuteMode} mode The mode in which the workflow which did error got started in
* @param {string} [executionId] The id the execution got saved as
*/
function executeErrorWorkflow(workflowData: IWorkflowBase, fullRunData: IRun, mode: WorkflowExecuteMode, executionId?: string, retryOf?: string): void {
if (mode === 'manual') {
// Do not call error workflow when executed manually
return;
}
2019-06-23 03:35:23 -07:00
// Check if there was an error and if so if an errorWorkflow is set
if (fullRunData.data.resultData.error !== undefined && workflowData.settings !== undefined && workflowData.settings.errorWorkflow) {
const workflowErrorData = {
execution: {
id: executionId,
error: fullRunData.data.resultData.error,
lastNodeExecuted: fullRunData.data.resultData.lastNodeExecuted!,
mode,
retryOf,
2019-06-23 03:35:23 -07:00
},
workflow: {
id: workflowData.id !== undefined ? workflowData.id.toString() as string : undefined,
name: workflowData.name,
}
};
// Run the error workflow
WorkflowHelpers.executeErrorWorkflow(workflowData.settings.errorWorkflow as string, workflowErrorData);
}
}
/**
* Pushes the execution out to all connected clients
*
* @param {IRun} fullRunData The RunData of the finished execution
* @param {string} executionIdActive The id of the finished execution
* @param {string} [executionIdDb] The database id of finished execution
*/
2019-08-08 22:37:10 -07:00
export function pushExecutionFinished(fullRunData: IRun, executionIdActive: string, executionIdDb?: string, retryOf?: string) {
// Clone the object except the runData. That one is not supposed
// to be send. Because that data got send piece by piece after
// each node which finished executing
const pushRunData = {
...fullRunData,
data: {
...fullRunData.data,
resultData: {
...fullRunData.data.resultData,
runData: {},
},
},
};
// Push data to editor-ui once workflow finished
const sendData: IPushDataExecutionFinished = {
executionIdActive,
executionIdDb,
data: pushRunData,
2019-08-08 22:37:10 -07:00
retryOf,
};
2019-08-28 06:28:47 -07:00
const pushInstance = Push.getInstance();
pushInstance.send('executionFinished', sendData);
}
/**
* Returns the workflow execution hooks
*
* @param {WorkflowExecuteMode} mode
* @param {IWorkflowBase} workflowData
* @param {string} executionId
* @param {string} [sessionId]
* @param {string} [retryOf]
* @returns {IWorkflowExecuteHooks}
*/
const hooks = (mode: WorkflowExecuteMode, workflowData: IWorkflowBase, executionId: string, sessionId?: string, retryOf?: string): IWorkflowExecuteHooks => {
2019-06-23 03:35:23 -07:00
return {
nodeExecuteBefore: [
async (nodeName: string): Promise<void> => {
// Push data to session which started workflow before each
// node which starts rendering
2019-06-23 03:35:23 -07:00
if (sessionId === undefined) {
return;
}
2019-08-28 06:28:47 -07:00
const pushInstance = Push.getInstance();
pushInstance.send('nodeExecuteBefore', {
2019-06-23 03:35:23 -07:00
executionId,
nodeName,
}, sessionId);
2019-06-23 03:35:23 -07:00
},
],
nodeExecuteAfter: [
async (nodeName: string, data: ITaskData): Promise<void> => {
// Push data to session which started workflow after each rendered node
2019-06-23 03:35:23 -07:00
if (sessionId === undefined) {
return;
}
2019-08-28 06:28:47 -07:00
const pushInstance = Push.getInstance();
pushInstance.send('nodeExecuteAfter', {
2019-06-23 03:35:23 -07:00
executionId,
nodeName,
data,
}, sessionId);
2019-06-23 03:35:23 -07:00
},
],
workflowExecuteBefore: [
async (): Promise<void> => {
// Push data to editor-ui once workflow finished
2019-08-28 06:28:47 -07:00
const pushInstance = Push.getInstance();
pushInstance.send('executionStarted', {
executionId,
mode,
startedAt: new Date(),
retryOf,
workflowId: workflowData.id as string,
workflowName: workflowData.name,
});
}
],
2019-06-23 03:35:23 -07:00
workflowExecuteAfter: [
async (fullRunData: IRun, newStaticData: IDataObject): Promise<void> => {
2019-06-23 03:35:23 -07:00
try {
if (mode !== 'manual' && WorkflowHelpers.isWorkflowIdValid(workflowData.id as string) === true && newStaticData) {
// Workflow is saved so update in database
try {
await WorkflowHelpers.saveStaticDataById(workflowData.id as string, newStaticData);
} catch (e) {
// TODO: Add proper logging!
console.error(`There was a problem saving the workflow with id "${workflowData.id}" to save changed staticData: ${e.message}`);
}
}
2019-06-23 03:35:23 -07:00
let saveManualExecutions = config.get('executions.saveDataManualExecutions') as boolean;
if (workflowData.settings !== undefined && workflowData.settings.saveManualExecutions !== undefined) {
2019-06-23 03:35:23 -07:00
// Apply to workflow override
saveManualExecutions = workflowData.settings.saveManualExecutions as boolean;
2019-06-23 03:35:23 -07:00
}
if (mode === 'manual' && saveManualExecutions === false) {
2019-08-08 22:37:10 -07:00
pushExecutionFinished(fullRunData, executionId, undefined, retryOf);
executeErrorWorkflow(workflowData, fullRunData, mode, undefined, retryOf);
return;
}
// Check config to know if execution should be saved or not
let saveDataErrorExecution = config.get('executions.saveDataOnError') as string;
let saveDataSuccessExecution = config.get('executions.saveDataOnSuccess') as string;
if (workflowData.settings !== undefined) {
saveDataErrorExecution = (workflowData.settings.saveDataErrorExecution as string) || saveDataErrorExecution;
saveDataSuccessExecution = (workflowData.settings.saveDataSuccessExecution as string) || saveDataSuccessExecution;
}
2019-06-23 03:35:23 -07:00
const workflowDidSucceed = !fullRunData.data.resultData.error;
if (workflowDidSucceed === true && saveDataSuccessExecution === 'none' ||
workflowDidSucceed === false && saveDataErrorExecution === 'none'
) {
2019-08-08 22:37:10 -07:00
pushExecutionFinished(fullRunData, executionId, undefined, retryOf);
executeErrorWorkflow(workflowData, fullRunData, mode, undefined, retryOf);
2019-06-23 03:35:23 -07:00
return;
}
const fullExecutionData: IExecutionDb = {
data: fullRunData.data,
mode: fullRunData.mode,
finished: fullRunData.finished ? fullRunData.finished : false,
startedAt: fullRunData.startedAt,
stoppedAt: fullRunData.stoppedAt,
workflowData,
};
if (retryOf !== undefined) {
fullExecutionData.retryOf = retryOf.toString();
}
if (workflowData.id !== undefined && WorkflowHelpers.isWorkflowIdValid(workflowData.id.toString()) === true) {
fullExecutionData.workflowId = workflowData.id.toString();
}
const executionData = ResponseHelper.flattenExecutionData(fullExecutionData);
// Save the Execution in DB
const executionResult = await Db.collections.Execution!.save(executionData as IExecutionFlattedDb);
if (fullRunData.finished === true && retryOf !== undefined) {
// If the retry was successful save the reference it on the original execution
// await Db.collections.Execution!.save(executionData as IExecutionFlattedDb);
await Db.collections.Execution!.update(retryOf, { retrySuccessId: executionResult.id });
}
2019-08-08 22:37:10 -07:00
pushExecutionFinished(fullRunData, executionId, executionResult.id as string, retryOf);
executeErrorWorkflow(workflowData, fullRunData, mode, executionResult ? executionResult.id as string : undefined, retryOf);
2019-06-23 03:35:23 -07:00
} catch (error) {
2019-08-08 22:37:10 -07:00
pushExecutionFinished(fullRunData, executionId, undefined, retryOf);
executeErrorWorkflow(workflowData, fullRunData, mode, undefined, retryOf);
2019-06-23 03:35:23 -07:00
}
},
]
};
};
/**
* Returns the base additional data without webhooks
*
* @export
* @param {WorkflowExecuteMode} mode
* @param {IWorkflowCredentials} credentials
* @returns {Promise<IWorkflowExecuteAdditionalData>}
*/
2019-10-20 12:42:34 -07:00
export async function getBase(mode: WorkflowExecuteMode, credentials: IWorkflowCredentials, currentNodeParameters: INodeParameters[] = []): Promise<IWorkflowExecuteAdditionalData> {
2019-06-23 03:35:23 -07:00
const urlBaseWebhook = WebhookHelpers.getWebhookBaseUrl();
const timezone = config.get('generic.timezone') as string;
const webhookBaseUrl = urlBaseWebhook + config.get('endpoints.webhook') as string;
const webhookTestBaseUrl = urlBaseWebhook + config.get('endpoints.webhookTest') as string;
2019-06-23 03:35:23 -07:00
const encryptionKey = await UserSettings.getEncryptionKey();
if (encryptionKey === undefined) {
throw new Error('No encryption key got found to decrypt the credentials!');
}
return {
credentials,
2019-06-23 03:35:23 -07:00
encryptionKey,
timezone,
webhookBaseUrl,
webhookTestBaseUrl,
2019-10-20 12:42:34 -07:00
currentNodeParameters,
2019-06-23 03:35:23 -07:00
};
}
/**
* Returns the workflow hooks
*
* @export
* @param {IWorkflowExecutionDataProcess} data
* @param {string} executionId
* @returns {IWorkflowExecuteHooks}
*/
export function getHookMethods(data: IWorkflowExecutionDataProcess, executionId: string): IWorkflowExecuteHooks {
return hooks(data.executionMode, data.workflowData, executionId, data.sessionId, data.retryOf as string | undefined);
}