From 4946bfcd3e0f16ab1d6373b31f9150141e69d5e2 Mon Sep 17 00:00:00 2001 From: Jan Date: Sat, 29 May 2021 13:41:25 -0500 Subject: [PATCH] :sparkles: Add functionality to send console.log messages to editor-UI (#1816) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * :sparkles: Send console.log messages to editor-UI * :zap: Send message only to session which started workflow * :zap: Made it also work in own process * :zap: Add support for console.log UI forward also to FunctionItem Node * :shirt: Fix lint issue * :shirt: Fix linting issue * :zap: Improve code Co-authored-by: Iván Ovejero Co-authored-by: Iván Ovejero --- packages/cli/src/Interfaces.ts | 8 +++++-- .../cli/src/WorkflowExecuteAdditionalData.ts | 21 +++++++++++++++++-- packages/cli/src/WorkflowRunner.ts | 5 +++++ packages/cli/src/WorkflowRunnerProcess.ts | 11 ++++++++++ packages/core/src/NodeExecuteFunctions.ts | 12 +++++++++++ packages/core/test/Helpers.ts | 1 + packages/editor-ui/src/Interface.ts | 9 ++++++-- .../src/components/mixins/pushConnection.ts | 7 +++++++ packages/nodes-base/nodes/Function.node.ts | 9 ++++++-- .../nodes-base/nodes/FunctionItem.node.ts | 10 ++++++--- packages/workflow/src/Interfaces.ts | 2 ++ 11 files changed, 84 insertions(+), 11 deletions(-) diff --git a/packages/cli/src/Interfaces.ts b/packages/cli/src/Interfaces.ts index 012408c957..19796bb997 100644 --- a/packages/cli/src/Interfaces.ts +++ b/packages/cli/src/Interfaces.ts @@ -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[]; diff --git a/packages/cli/src/WorkflowExecuteAdditionalData.ts b/packages/cli/src/WorkflowExecuteAdditionalData.ts index 809a8439f8..570baf536b 100644 --- a/packages/cli/src/WorkflowExecuteAdditionalData.ts +++ b/packages/cli/src/WorkflowExecuteAdditionalData.ts @@ -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 }); } - diff --git a/packages/cli/src/WorkflowRunner.ts b/packages/cli/src/WorkflowRunner.ts index 1cf4e43b35..56031d5a77 100644 --- a/packages/cli/src/WorkflowRunner.ts +++ b/packages/cli/src/WorkflowRunner.ts @@ -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; 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; diff --git a/packages/cli/src/WorkflowRunnerProcess.ts b/packages/cli/src/WorkflowRunnerProcess.ts index 4a09c76a89..109bfda98c 100644 --- a/packages/cli/src/WorkflowRunnerProcess.ts +++ b/packages/cli/src/WorkflowRunnerProcess.ts @@ -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 | IRun> => { const workflowData = await WorkflowExecuteAdditionalData.getWorkflowData(workflowInfo); diff --git a/packages/core/src/NodeExecuteFunctions.ts b/packages/core/src/NodeExecuteFunctions.ts index 36004fd10c..5b5698bf18 100644 --- a/packages/core/src/NodeExecuteFunctions.ts +++ b/packages/core/src/NodeExecuteFunctions.ts @@ -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, diff --git a/packages/core/test/Helpers.ts b/packages/core/test/Helpers.ts index 1a1fa9d12d..bba3a3452d 100644 --- a/packages/core/test/Helpers.ts +++ b/packages/core/test/Helpers.ts @@ -752,6 +752,7 @@ export function WorkflowExecuteAdditionalData(waitPromise: IDeferredPromise => {}, // tslint:disable-line:no-any + sendMessageToUI: (message: string) => {}, restApiUrl: '', encryptionKey: 'test', timezone: 'America/New_York', diff --git a/packages/editor-ui/src/Interface.ts b/packages/editor-ui/src/Interface.ts index 799540c1e8..b674cbf730 100644 --- a/packages/editor-ui/src/Interface.ts +++ b/packages/editor-ui/src/Interface.ts @@ -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; diff --git a/packages/editor-ui/src/components/mixins/pushConnection.ts b/packages/editor-ui/src/components/mixins/pushConnection.ts index 6a80f67920..b98efe7104 100644 --- a/packages/editor-ui/src/components/mixins/pushConnection.ts +++ b/packages/editor-ui/src/components/mixins/pushConnection.ts @@ -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 diff --git a/packages/nodes-base/nodes/Function.node.ts b/packages/nodes-base/nodes/Function.node.ts index 91f50f0617..37c1de1f52 100644 --- a/packages/nodes-base/nodes/Function.node.ts +++ b/packages/nodes-base/nodes/Function.node.ts @@ -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; diff --git a/packages/nodes-base/nodes/FunctionItem.node.ts b/packages/nodes-base/nodes/FunctionItem.node.ts index 8af33afeb9..3b3ca5e1df 100644 --- a/packages/nodes-base/nodes/FunctionItem.node.ts +++ b/packages/nodes-base/nodes/FunctionItem.node.ts @@ -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 diff --git a/packages/workflow/src/Interfaces.ts b/packages/workflow/src/Interfaces.ts index cecdaf925a..b25d3a94d6 100644 --- a/packages/workflow/src/Interfaces.ts +++ b/packages/workflow/src/Interfaces.ts @@ -223,6 +223,7 @@ export interface IExecuteFunctions { getTimezone(): string; getWorkflow(): IWorkflowMetadata; prepareOutputData(outputData: INodeExecutionData[], outputIndex?: number): Promise; + 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;