Add functionality to send console.log messages to editor-UI (#1816)

*  Send console.log messages to editor-UI

*  Send message only to session which started workflow

*  Made it also work in own process

*  Add support for console.log UI forward also to FunctionItem Node

* 👕 Fix lint issue

* 👕 Fix linting issue

*  Improve code

Co-authored-by: Iván Ovejero <ivov.src@gmail.com>

Co-authored-by: Iván Ovejero <ivov.src@gmail.com>
This commit is contained in:
Jan 2021-05-29 13:41:25 -05:00 committed by GitHub
parent 8e793e27b3
commit 4946bfcd3e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 84 additions and 11 deletions

View file

@ -337,11 +337,11 @@ export interface IPackageVersions {
}
export interface IPushData {
data: IPushDataExecutionFinished | IPushDataNodeExecuteAfter | IPushDataNodeExecuteBefore | IPushDataTestWebhook;
data: IPushDataExecutionFinished | IPushDataNodeExecuteAfter | IPushDataNodeExecuteBefore | IPushDataTestWebhook | IPushDataConsoleMessage;
type: IPushDataType;
}
export type IPushDataType = 'executionFinished' | 'executionStarted' | 'nodeExecuteAfter' | 'nodeExecuteBefore' | 'testWebhookDeleted' | 'testWebhookReceived';
export type IPushDataType = 'executionFinished' | 'executionStarted' | 'nodeExecuteAfter' | 'nodeExecuteBefore' | 'sendConsoleMessage' | 'testWebhookDeleted' | 'testWebhookReceived';
export interface IPushDataExecutionFinished {
data: IRun;
@ -376,6 +376,10 @@ export interface IPushDataTestWebhook {
workflowId: string;
}
export interface IPushDataConsoleMessage {
source: string;
message: string;
}
export interface IResponseCallbackData {
data?: IDataObject | IDataObject[];

View file

@ -627,7 +627,7 @@ export async function executeWorkflow(workflowInfo: IExecuteWorkflowInfo, additi
// If no timeout was given from the parent, then we use our timeout.
subworkflowTimeout = Math.min(additionalData.executionTimeoutTimestamp || Number.MAX_SAFE_INTEGER, Date.now() + (workflowData.settings.executionTimeout as number * 1000));
}
additionalDataIntegrated.executionTimeoutTimestamp = subworkflowTimeout;
@ -663,6 +663,24 @@ export async function executeWorkflow(workflowInfo: IExecuteWorkflowInfo, additi
}
export function sendMessageToUI(source: string, message: string) {
if (this.sessionId === undefined) {
return;
}
// Push data to session which started workflow
try {
const pushInstance = Push.getInstance();
pushInstance.send('sendConsoleMessage', {
source: `Node: "${source}"`,
message,
}, this.sessionId);
} catch (error) {
Logger.warn(`There was a problem sending messsage to UI: ${error.message}`);
}
}
/**
* Returns the base additional data without webhooks
*
@ -785,4 +803,3 @@ export function getWorkflowHooksMain(data: IWorkflowExecutionDataProcess, execut
return new WorkflowHooks(hookFunctions, data.executionMode, executionId, data.workflowData, { sessionId: data.sessionId, retryOf: data.retryOf as string });
}

View file

@ -182,6 +182,8 @@ export class WorkflowRunner {
additionalData.hooks = WorkflowExecuteAdditionalData.getWorkflowHooksMain(data, executionId, true);
additionalData.sendMessageToUI = WorkflowExecuteAdditionalData.sendMessageToUI.bind({sessionId: data.sessionId});
let workflowExecution: PCancelable<IRun>;
if (data.executionData !== undefined) {
Logger.debug(`Execution ID ${executionId} had Execution data. Running with payload.`, {executionId});
@ -468,6 +470,9 @@ export class WorkflowRunner {
clearTimeout(executionTimeout);
this.activeExecutions.remove(executionId!, message.data.runData);
} else if (message.type === 'sendMessageToUI') {
WorkflowExecuteAdditionalData.sendMessageToUI.bind({ sessionId: data.sessionId })(message.data.source, message.data.message);
} else if (message.type === 'processError') {
clearTimeout(executionTimeout);
const executionError = message.data.executionError as ExecutionError;

View file

@ -137,6 +137,17 @@ export class WorkflowRunnerProcess {
const additionalData = await WorkflowExecuteAdditionalData.getBase(this.data.credentials, undefined, workflowTimeout <= 0 ? undefined : Date.now() + workflowTimeout * 1000);
additionalData.hooks = this.getProcessForwardHooks();
additionalData.sendMessageToUI = async (source: string, message: string) => {
if (workflowRunner.data!.executionMode !== 'manual') {
return;
}
try {
await sendToParentProcess('sendMessageToUI', { source, message });
} catch (error) {
this.logger.error(`There was a problem sending UI data to parent process: "${error.message}"`);
}
};
const executeWorkflowFunction = additionalData.executeWorkflow;
additionalData.executeWorkflow = async (workflowInfo: IExecuteWorkflowInfo, additionalData: IWorkflowExecuteAdditionalData, inputData?: INodeExecutionData[] | undefined): Promise<Array<INodeExecutionData[] | null> | IRun> => {
const workflowData = await WorkflowExecuteAdditionalData.getWorkflowData(workflowInfo);

View file

@ -749,6 +749,18 @@ export function getExecuteFunctions(workflow: Workflow, runExecutionData: IRunEx
return workflow.getStaticData(type, node);
},
prepareOutputData: NodeHelpers.prepareOutputData,
sendMessageToUI(message: string): void {
if (mode !== 'manual') {
return;
}
try {
if (additionalData.sendMessageToUI) {
additionalData.sendMessageToUI(node.name, message);
}
} catch (error) {
Logger.warn(`There was a problem sending messsage to UI: ${error.message}`);
}
},
helpers: {
prepareBinaryData,
request: requestPromiseWithDefaults,

View file

@ -752,6 +752,7 @@ export function WorkflowExecuteAdditionalData(waitPromise: IDeferredPromise<IRun
credentialsHelper: new CredentialsHelper({}, ''),
hooks: new WorkflowHooks(hookFunctions, 'trigger', '1', workflowData),
executeWorkflow: async (workflowInfo: IExecuteWorkflowInfo): Promise<any> => {}, // tslint:disable-line:no-any
sendMessageToUI: (message: string) => {},
restApiUrl: '',
encryptionKey: 'test',
timezone: 'America/New_York',

View file

@ -356,11 +356,11 @@ export interface IExecutionDeleteFilter {
}
export interface IPushData {
data: IPushDataExecutionFinished | IPushDataNodeExecuteAfter | IPushDataNodeExecuteBefore | IPushDataTestWebhook;
data: IPushDataExecutionFinished | IPushDataNodeExecuteAfter | IPushDataNodeExecuteBefore | IPushDataTestWebhook | IPushDataConsoleMessage;
type: IPushDataType;
}
export type IPushDataType = 'executionFinished' | 'executionStarted' | 'nodeExecuteAfter' | 'nodeExecuteBefore' | 'testWebhookDeleted' | 'testWebhookReceived';
export type IPushDataType = 'executionFinished' | 'executionStarted' | 'nodeExecuteAfter' | 'nodeExecuteBefore' | 'sendConsoleMessage' | 'testWebhookDeleted' | 'testWebhookReceived';
export interface IPushDataExecutionStarted {
executionId: string;
@ -397,6 +397,11 @@ export interface IPushDataTestWebhook {
workflowId: string;
}
export interface IPushDataConsoleMessage {
source: string;
message: string;
}
export interface IN8nUISettings {
endpointWebhook: string;
endpointWebhookTest: string;

View file

@ -1,6 +1,7 @@
import {
IExecutionsCurrentSummaryExtended,
IPushData,
IPushDataConsoleMessage,
IPushDataExecutionFinished,
IPushDataExecutionStarted,
IPushDataNodeExecuteAfter,
@ -161,6 +162,12 @@ export const pushConnection = mixins(
return false;
}
if (receivedData.type === 'sendConsoleMessage') {
const pushData = receivedData.data as IPushDataConsoleMessage;
console.log(pushData.source, pushData.message); // eslint-disable-line no-console
return true;
}
if (!['testWebhookReceived'].includes(receivedData.type) && isRetry !== true && this.pushMessageQueue.length) {
// If there are already messages in the queue add the new one that all of them
// get executed in order

View file

@ -60,8 +60,10 @@ export class Function implements INodeType {
// By default use data from first item
Object.assign(sandbox, sandbox.$item(0));
const mode = this.getMode();
const options = {
console: 'inherit',
console: (mode === 'manual') ? 'redirect' : 'inherit',
sandbox,
require: {
external: false as boolean | { modules: string[] },
@ -77,9 +79,12 @@ export class Function implements INodeType {
options.require.external = { modules: process.env.NODE_FUNCTION_ALLOW_EXTERNAL.split(',') };
}
const vm = new NodeVM(options);
if (mode === 'manual') {
vm.on('console.log', this.sendMessageToUI);
}
// Get the code to execute
const functionCode = this.getNodeParameter('functionCode', 0) as string;

View file

@ -73,8 +73,10 @@ export class FunctionItem implements INodeType {
const dataProxy = this.getWorkflowDataProxy(itemIndex);
Object.assign(sandbox, dataProxy);
const mode = this.getMode();
const options = {
console: 'inherit',
console: (mode === 'manual') ? 'redirect' : 'inherit',
sandbox,
require: {
external: false as boolean | { modules: string[] },
@ -92,11 +94,13 @@ export class FunctionItem implements INodeType {
const vm = new NodeVM(options);
if (mode === 'manual') {
vm.on('console.log', this.sendMessageToUI);
}
// Get the code to execute
const functionCode = this.getNodeParameter('functionCode', itemIndex) as string;
let jsonData: IDataObject;
try {
// Execute the function code

View file

@ -223,6 +223,7 @@ export interface IExecuteFunctions {
getTimezone(): string;
getWorkflow(): IWorkflowMetadata;
prepareOutputData(outputData: INodeExecutionData[], outputIndex?: number): Promise<INodeExecutionData[][]>;
sendMessageToUI(message: string): void;
helpers: {
[key: string]: (...args: any[]) => any //tslint:disable-line:no-any
};
@ -744,6 +745,7 @@ export interface IWorkflowExecuteAdditionalData {
httpResponse?: express.Response;
httpRequest?: express.Request;
restApiUrl: string;
sendMessageToUI?: (source: string, message: string) => void;
timezone: string;
webhookBaseUrl: string;
webhookTestBaseUrl: string;