From c49c4bb55a609dc4f9d25933a2e33856e5a31b54 Mon Sep 17 00:00:00 2001 From: Mutasem Aldmour Date: Wed, 6 Nov 2024 17:39:59 +0100 Subject: [PATCH 01/48] merge in Jan's branch to master, handle conflicts --- .../RetrieverWorkflow.node.ts | 20 ++++-- .../tools/ToolWorkflow/ToolWorkflow.node.ts | 36 +++++++--- packages/cli/src/interfaces.ts | 8 --- .../src/workflow-execute-additional-data.ts | 55 ++++++++++++--- packages/core/src/NodeExecuteFunctions.ts | 63 ++++++++++++----- packages/core/src/WorkflowExecute.ts | 1 + .../execute-single-context.ts | 12 ++++ .../ExecuteWorkflow/ExecuteWorkflow.node.ts | 68 ++++++++++++++++--- packages/workflow/src/Interfaces.ts | 24 ++++++- 9 files changed, 228 insertions(+), 59 deletions(-) diff --git a/packages/@n8n/nodes-langchain/nodes/retrievers/RetrieverWorkflow/RetrieverWorkflow.node.ts b/packages/@n8n/nodes-langchain/nodes/retrievers/RetrieverWorkflow/RetrieverWorkflow.node.ts index 0aafabf1d4c0c..fab3c2dabe8d7 100644 --- a/packages/@n8n/nodes-langchain/nodes/retrievers/RetrieverWorkflow/RetrieverWorkflow.node.ts +++ b/packages/@n8n/nodes-langchain/nodes/retrievers/RetrieverWorkflow/RetrieverWorkflow.node.ts @@ -17,6 +17,7 @@ import { Document } from '@langchain/core/documents'; import type { SetField, SetNodeOptions } from 'n8n-nodes-base/dist/nodes/Set/v2/helpers/interfaces'; import * as manual from 'n8n-nodes-base/dist/nodes/Set/v2/manual.mode'; +import get from 'lodash/get'; import type { CallbackManagerForRetrieverRun } from '@langchain/core/callbacks/manager'; import { logWrapper } from '../../../utils/logWrapper'; @@ -293,6 +294,8 @@ export class RetrieverWorkflow implements INodeType { }; async supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise { + const workflowProxy = this.getWorkflowDataProxy(0); + class WorkflowRetriever extends BaseRetriever { lc_namespace = ['n8n-nodes-langchain', 'retrievers', 'workflow']; @@ -384,21 +387,30 @@ export class RetrieverWorkflow implements INodeType { const items = [newItem] as INodeExecutionData[]; - let receivedItems: INodeExecutionData[][]; + let receivedData; try { - receivedItems = (await this.executeFunctions.executeWorkflow( + receivedData = await this.executeFunctions.executeWorkflow( workflowInfo, items, config?.getChild(), - )) as INodeExecutionData[][]; + { + startMetadata: { + executionId: workflowProxy.$execution.id, + workflowId: workflowProxy.$workflow.id, + }, + }, + ); } catch (error) { // Make sure a valid error gets returned that can by json-serialized else it will // not show up in the frontend throw new NodeOperationError(this.executeFunctions.getNode(), error as Error); } + // eslint-disable-next-line lodash/path-style + const receivedItems = get(receivedData, ['data', 0]) ?? []; + const returnData: Document[] = []; - for (const [index, itemData] of receivedItems[0].entries()) { + for (const [index, itemData] of receivedItems.entries()) { const pageContent = objectToString(itemData.json); returnData.push( new Document({ diff --git a/packages/@n8n/nodes-langchain/nodes/tools/ToolWorkflow/ToolWorkflow.node.ts b/packages/@n8n/nodes-langchain/nodes/tools/ToolWorkflow/ToolWorkflow.node.ts index f912e162d9fc9..24fc802940cf3 100644 --- a/packages/@n8n/nodes-langchain/nodes/tools/ToolWorkflow/ToolWorkflow.node.ts +++ b/packages/@n8n/nodes-langchain/nodes/tools/ToolWorkflow/ToolWorkflow.node.ts @@ -14,8 +14,10 @@ import type { ISupplyDataFunctions, SupplyData, ExecutionError, + ExecuteWorkflowData, IDataObject, INodeParameterResourceLocator, + ITaskMetadata, } from 'n8n-workflow'; import { NodeConnectionType, NodeOperationError, jsonParse } from 'n8n-workflow'; @@ -360,6 +362,7 @@ export class ToolWorkflow implements INodeType { async supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise { const name = this.getNodeParameter('name', itemIndex) as string; const description = this.getNodeParameter('description', itemIndex) as string; + let executionId: string | undefined = undefined; const useSchema = this.getNodeParameter('specifyInputSchema', itemIndex) as boolean; let tool: DynamicTool | DynamicStructuredTool | undefined = undefined; @@ -440,13 +443,17 @@ export class ToolWorkflow implements INodeType { const items = [newItem] as INodeExecutionData[]; - let receivedData: INodeExecutionData; + const workflowProxy = this.getWorkflowDataProxy(0); + + let receivedData: ExecuteWorkflowData; try { - receivedData = (await this.executeWorkflow( - workflowInfo, - items, - runManager?.getChild(), - )) as INodeExecutionData; + receivedData = await this.executeWorkflow(workflowInfo, items, runManager?.getChild(), { + startMetadata: { + executionId: workflowProxy.$execution.id, + workflowId: workflowProxy.$workflow.id, + }, + }); + executionId = receivedData.executionId; } catch (error) { // Make sure a valid error gets returned that can by json-serialized else it will // not show up in the frontend @@ -454,6 +461,7 @@ export class ToolWorkflow implements INodeType { } const response: string | undefined = get(receivedData, [ + 'data', 0, 0, 'json', @@ -503,10 +511,22 @@ export class ToolWorkflow implements INodeType { response = `There was an error: "${executionError.message}"`; } + let metadata: ITaskMetadata | undefined; + if (executionId) { + metadata = { + executionId, + }; + } + if (executionError) { - void this.addOutputData(NodeConnectionType.AiTool, index, executionError); + void this.addOutputData(NodeConnectionType.AiTool, index, executionError, metadata); } else { - void this.addOutputData(NodeConnectionType.AiTool, index, [[{ json: { response } }]]); + void this.addOutputData( + NodeConnectionType.AiTool, + index, + [[{ json: { response } }]], + metadata, + ); } return response; }; diff --git a/packages/cli/src/interfaces.ts b/packages/cli/src/interfaces.ts index 65b3a5634664e..4069d4fe025a9 100644 --- a/packages/cli/src/interfaces.ts +++ b/packages/cli/src/interfaces.ts @@ -1,6 +1,5 @@ import type { Scope } from '@n8n/permissions'; import type { Application } from 'express'; -import type { WorkflowExecute } from 'n8n-core'; import type { ExecutionError, ICredentialDataDecryptedObject, @@ -14,7 +13,6 @@ import type { ITelemetryTrackProperties, IWorkflowBase, CredentialLoadingDetails, - Workflow, WorkflowExecuteMode, ExecutionStatus, ExecutionSummary, @@ -296,12 +294,6 @@ export interface IWorkflowErrorData { }; } -export interface IWorkflowExecuteProcess { - startedAt: Date; - workflow: Workflow; - workflowExecute: WorkflowExecute; -} - export interface IWorkflowStatisticsDataLoaded { dataLoaded: boolean; } diff --git a/packages/cli/src/workflow-execute-additional-data.ts b/packages/cli/src/workflow-execute-additional-data.ts index 351cd6f059966..13ef7e11fe54d 100644 --- a/packages/cli/src/workflow-execute-additional-data.ts +++ b/packages/cli/src/workflow-execute-additional-data.ts @@ -36,6 +36,8 @@ import type { ExecuteWorkflowOptions, IWorkflowExecutionDataProcess, EnvProviderState, + ITaskMetadata, + ExecuteWorkflowData, } from 'n8n-workflow'; import { Container } from 'typedi'; @@ -45,11 +47,7 @@ import { CredentialsHelper } from '@/credentials-helper'; import { ExecutionRepository } from '@/databases/repositories/execution.repository'; import type { AiEventMap, AiEventPayload } from '@/events/maps/ai.event-map'; import { ExternalHooks } from '@/external-hooks'; -import type { - IWorkflowExecuteProcess, - IWorkflowErrorData, - UpdateExecutionPayload, -} from '@/interfaces'; +import type { IWorkflowErrorData, UpdateExecutionPayload } from '@/interfaces'; import { NodeTypes } from '@/node-types'; import { Push } from '@/push'; import { WorkflowStatisticsService } from '@/services/workflow-statistics.service'; @@ -681,6 +679,7 @@ function hookFunctionsSaveWorker(): IWorkflowExecuteHooks { export async function getRunData( workflowData: IWorkflowBase, inputData?: INodeExecutionData[], + metadata?: ITaskMetadata, ): Promise { const mode = 'integrated'; @@ -700,6 +699,7 @@ export async function getRunData( data: { main: [inputData], }, + metadata, source: null, }); @@ -771,14 +771,12 @@ export async function executeWorkflow( workflowInfo: IExecuteWorkflowInfo, additionalData: IWorkflowExecuteAdditionalData, options: ExecuteWorkflowOptions, -): Promise | IWorkflowExecuteProcess> { +): Promise { const externalHooks = Container.get(ExternalHooks); await externalHooks.init(); const nodeTypes = Container.get(NodeTypes); const activeExecutions = Container.get(ActiveExecutions); - const eventService = Container.get(EventService); - const executionRepository = Container.get(ExecutionRepository); const workflowData = options.loadedWorkflowData ?? @@ -796,10 +794,44 @@ export async function executeWorkflow( settings: workflowData.settings, }); - const runData = options.loadedRunData ?? (await getRunData(workflowData, options.inputData)); + const runData = + options.loadedRunData ?? + (await getRunData(workflowData, options.inputData, options.startMetadata)); const executionId = await activeExecutions.add(runData); + // We wrap it in another promise that we can depending on the setting return + // the execution ID before the execution is finished + const executionPromise = startExecution( + additionalData, + options, + workflow, + executionId, + runData, + workflowData, + externalHooks, + ); + + if (options.doNotWaitToFinish) { + return { executionId, data: [null] }; + } + + return await executionPromise; +} + +async function startExecution( + additionalData: IWorkflowExecuteAdditionalData, + options: ExecuteWorkflowOptions, + workflow: Workflow, + executionId: string, + runData: IWorkflowExecutionDataProcess, + workflowData: IWorkflowBase, + externalHooks: ExternalHooks, +): Promise { + const eventService = Container.get(EventService); + const activeExecutions = Container.get(ActiveExecutions); + const executionRepository = Container.get(ExecutionRepository); + /** * A subworkflow execution in queue mode is not enqueued, but rather runs in the * same worker process as the parent execution. Hence ensure the subworkflow @@ -921,7 +953,10 @@ export async function executeWorkflow( activeExecutions.finalizeExecution(executionId, data); const returnData = WorkflowHelpers.getDataLastExecutedNodeData(data); - return returnData!.data!.main; + return { + executionId, + data: returnData!.data!.main, + }; } activeExecutions.finalizeExecution(executionId, data); diff --git a/packages/core/src/NodeExecuteFunctions.ts b/packages/core/src/NodeExecuteFunctions.ts index 383624b569bb6..a8f530b648e04 100644 --- a/packages/core/src/NodeExecuteFunctions.ts +++ b/packages/core/src/NodeExecuteFunctions.ts @@ -33,12 +33,14 @@ import get from 'lodash/get'; import isEmpty from 'lodash/isEmpty'; import merge from 'lodash/merge'; import pick from 'lodash/pick'; +import set from 'lodash/set'; import { DateTime } from 'luxon'; import { extension, lookup } from 'mime-types'; import type { BinaryHelperFunctions, CloseFunction, ContextType, + ExecuteWorkflowData, FieldType, FileSystemHelperFunctions, FunctionsBase, @@ -78,6 +80,7 @@ import type { IRunExecutionData, ITaskData, ITaskDataConnections, + ITaskMetadata, ITriggerFunctions, IWebhookData, IWebhookDescription, @@ -2710,6 +2713,7 @@ const addExecutionDataFunctions = async ( sourceNodeName: string, sourceNodeRunIndex: number, currentNodeRunIndex: number, + metadata?: ITaskMetadata, ): Promise => { if (connectionType === NodeConnectionType.Main) { throw new ApplicationError('Setting type is not supported for main connection', { @@ -2735,6 +2739,7 @@ const addExecutionDataFunctions = async ( if (taskData === undefined) { return; } + taskData.metadata = metadata; } taskData = taskData!; @@ -3568,6 +3573,13 @@ export function getExecuteTriggerFunctions( return new TriggerContext(workflow, node, additionalData, mode, activation); } +function setMetadata(executeData: IExecuteData, key: string, value: string) { + if (!executeData.metadata) { + executeData.metadata = {}; + } + set(executeData.metadata, key, value); +} + /** * Returns the execute functions regular nodes have access to. */ @@ -3603,6 +3615,9 @@ export function getExecuteFunctions( itemIndex, ), getExecuteData: () => executeData, + setMetadata: (key: string, value: string): void => { + return setMetadata(executeData, key, value); + }, continueOnFail: () => { return continueOnFail(node); }, @@ -3624,23 +3639,28 @@ export function getExecuteFunctions( workflowInfo: IExecuteWorkflowInfo, inputData?: INodeExecutionData[], parentCallbackManager?: CallbackManager, - ): Promise { + options?: { + doNotWaitToFinish?: boolean; + startMetadata?: ITaskMetadata; + }, + ): Promise { return await additionalData .executeWorkflow(workflowInfo, additionalData, { + ...options, parentWorkflowId: workflow.id?.toString(), inputData, parentWorkflowSettings: workflow.settings, node, parentCallbackManager, }) - .then( - async (result) => - await Container.get(BinaryDataService).duplicateBinaryData( - workflow.id, - additionalData.executionId!, - result, - ), - ); + .then(async (result) => { + const data = await Container.get(BinaryDataService).duplicateBinaryData( + workflow.id, + additionalData.executionId!, + result.data, + ); + return { ...result, data }; + }); }, getContext(type: ContextType): IContextObject { return NodeHelpers.getContext(runExecutionData, type, node); @@ -3834,6 +3854,7 @@ export function getExecuteFunctions( connectionType: NodeConnectionType, currentNodeRunIndex: number, data: INodeExecutionData[][] | ExecutionBaseError, + metadata?: ITaskMetadata, ): void { addExecutionDataFunctions( 'output', @@ -3845,6 +3866,7 @@ export function getExecuteFunctions( node.name, runIndex, currentNodeRunIndex, + metadata, ).catch((error) => { Logger.warn( `There was a problem logging output data of node "${this.getNode().name}": ${ @@ -3953,7 +3975,11 @@ export function getSupplyDataFunctions( workflowInfo: IExecuteWorkflowInfo, inputData?: INodeExecutionData[], parentCallbackManager?: CallbackManager, - ) => + options?: { + doNotWaitToFinish?: boolean; + startMetadata?: ITaskMetadata; + }, + ): Promise => await additionalData .executeWorkflow(workflowInfo, additionalData, { parentWorkflowId: workflow.id?.toString(), @@ -3961,15 +3987,16 @@ export function getSupplyDataFunctions( parentWorkflowSettings: workflow.settings, node, parentCallbackManager, + ...options, }) - .then( - async (result) => - await Container.get(BinaryDataService).duplicateBinaryData( - workflow.id, - additionalData.executionId!, - result, - ), - ), + .then(async (result) => { + const data = await Container.get(BinaryDataService).duplicateBinaryData( + workflow.id, + additionalData.executionId!, + result.data, + ); + return { ...result, data }; + }), getNodeOutputs() { const nodeType = workflow.nodeTypes.getByNameAndVersion(node.type, node.typeVersion); return NodeHelpers.getNodeOutputs(workflow, node, nodeType.description).map((output) => { diff --git a/packages/core/src/WorkflowExecute.ts b/packages/core/src/WorkflowExecute.ts index f8c32351bb8eb..f70099a26c2d6 100644 --- a/packages/core/src/WorkflowExecute.ts +++ b/packages/core/src/WorkflowExecute.ts @@ -1448,6 +1448,7 @@ export class WorkflowExecute { startTime, executionTime: new Date().getTime() - startTime, source: !executionData.source ? [] : executionData.source.main, + metadata: executionData.metadata, executionStatus: 'success', }; diff --git a/packages/core/src/node-execution-context/execute-single-context.ts b/packages/core/src/node-execution-context/execute-single-context.ts index 6d8ef2a083afd..714951dc5604d 100644 --- a/packages/core/src/node-execution-context/execute-single-context.ts +++ b/packages/core/src/node-execution-context/execute-single-context.ts @@ -1,3 +1,4 @@ +import set from 'lodash/set'; import type { ICredentialDataDecryptedObject, IGetNodeParameterOptions, @@ -36,6 +37,13 @@ import { BinaryHelpers } from './helpers/binary-helpers'; import { RequestHelpers } from './helpers/request-helpers'; import { NodeExecutionContext } from './node-execution-context'; +function setMetadata(executeData: IExecuteData, key: string, value: string) { + if (!executeData.metadata) { + executeData.metadata = {}; + } + set(executeData.metadata, key, value); +} + export class ExecuteSingleContext extends NodeExecutionContext implements IExecuteSingleFunctions { readonly helpers: IExecuteSingleFunctions['helpers']; @@ -79,6 +87,10 @@ export class ExecuteSingleContext extends NodeExecutionContext implements IExecu this.abortSignal?.addEventListener('abort', fn); } + setMetadata(key: string, value: string): void { + return setMetadata(this.executeData, key, value); + } + continueOnFail() { return continueOnFail(this.node); } diff --git a/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflow.node.ts b/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflow.node.ts index 26af55f210098..30df915d44a3e 100644 --- a/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflow.node.ts +++ b/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflow.node.ts @@ -1,5 +1,6 @@ import { NodeConnectionType, NodeOperationError } from 'n8n-workflow'; import type { + ExecuteWorkflowData, IExecuteFunctions, INodeExecutionData, INodeType, @@ -209,8 +210,10 @@ export class ExecuteWorkflow implements INodeType { const mode = this.getNodeParameter('mode', 0, false) as string; const items = this.getInputData(); + const workflowProxy = this.getWorkflowDataProxy(0); + if (mode === 'each') { - let returnData: INodeExecutionData[][] = []; + const returnData: INodeExecutionData[][] = []; for (let i = 0; i < items.length; i++) { try { @@ -222,14 +225,26 @@ export class ExecuteWorkflow implements INodeType { const workflowInfo = await getWorkflowInfo.call(this, source, i); if (waitForSubWorkflow) { - const workflowResult: INodeExecutionData[][] = await this.executeWorkflow( + const executionResult: ExecuteWorkflowData = await this.executeWorkflow( workflowInfo, [items[i]], + undefined, + { + startMetadata: { + executionId: workflowProxy.$execution.id, + workflowId: workflowProxy.$workflow.id, + }, + }, ); + const workflowResult = executionResult.data as INodeExecutionData[][]; for (const [outputIndex, outputData] of workflowResult.entries()) { for (const item of outputData) { item.pairedItem = { item: i }; + item.metadata = { + executionId: executionResult.executionId, + workflowId: workflowInfo.id, + }; } if (returnData[outputIndex] === undefined) { @@ -239,8 +254,29 @@ export class ExecuteWorkflow implements INodeType { returnData[outputIndex].push(...outputData); } } else { - void this.executeWorkflow(workflowInfo, [items[i]]); - returnData = [items]; + const executionResult: ExecuteWorkflowData = await this.executeWorkflow( + workflowInfo, + [items[i]], + undefined, + { + doNotWaitToFinish: true, + startMetadata: { + executionId: workflowProxy.$execution.id, + workflowId: workflowProxy.$workflow.id, + }, + }, + ); + + if (returnData.length === 0) { + returnData.push([]); + } + + returnData[0].push({ + ...items[i], + metadata: { + executionId: executionResult.executionId, + }, + }); } } catch (error) { if (this.continueOnFail()) { @@ -268,15 +304,29 @@ export class ExecuteWorkflow implements INodeType { ) as boolean; const workflowInfo = await getWorkflowInfo.call(this, source); + const executionResult: ExecuteWorkflowData = await this.executeWorkflow( + workflowInfo, + items, + undefined, + { + doNotWaitToFinish: !waitForSubWorkflow, + startMetadata: { + executionId: workflowProxy.$execution.id, + workflowId: workflowProxy.$workflow.id, + }, + }, + ); + + this.setMetadata('executionId', executionResult.executionId); + if (workflowInfo.id !== undefined) { + this.setMetadata('workflowId', workflowInfo.id); + } + if (!waitForSubWorkflow) { - void this.executeWorkflow(workflowInfo, items); return [items]; } - const workflowResult: INodeExecutionData[][] = await this.executeWorkflow( - workflowInfo, - items, - ); + const workflowResult = executionResult.data as INodeExecutionData[][]; const fallbackPairedItemData = generatePairedItemData(items.length); diff --git a/packages/workflow/src/Interfaces.ts b/packages/workflow/src/Interfaces.ts index 57ec23b54494f..88b22730aa9a2 100644 --- a/packages/workflow/src/Interfaces.ts +++ b/packages/workflow/src/Interfaces.ts @@ -484,6 +484,7 @@ export interface ISourceDataConnections { export interface IExecuteData { data: ITaskDataConnections; + metadata?: ITaskMetadata; node: INode; source: ITaskDataConnectionsSource | null; } @@ -935,6 +936,7 @@ export type ContextType = 'flow' | 'node'; type BaseExecutionFunctions = FunctionsBaseWithRequiredKeys<'getMode'> & { continueOnFail(): boolean; + setMetadata(key: string, value: string): void; evaluateExpression(expression: string, itemIndex: number): NodeParameterValueType; getContext(type: ContextType): IContextObject; getExecuteData(): IExecuteData; @@ -952,7 +954,11 @@ export type IExecuteFunctions = ExecuteFunctions.GetNodeParameterFn & workflowInfo: IExecuteWorkflowInfo, inputData?: INodeExecutionData[], parentCallbackManager?: CallbackManager, - ): Promise; + options?: { + doNotWaitToFinish?: boolean; + startMetadata?: ITaskMetadata; + }, + ): Promise; getInputConnectionData( inputName: NodeConnectionType, itemIndex: number, @@ -975,6 +981,7 @@ export type IExecuteFunctions = ExecuteFunctions.GetNodeParameterFn & connectionType: NodeConnectionType, currentNodeRunIndex: number, data: INodeExecutionData[][] | ExecutionError, + metadata?: ITaskMetadata, ): void; nodeHelpers: NodeHelperFunctions; @@ -1207,6 +1214,10 @@ export interface INodeExecutionData { binary?: IBinaryKeyData; error?: NodeApiError | NodeOperationError; pairedItem?: IPairedItemData | IPairedItemData[] | number; + metadata?: { + executionId?: string; + workflowId?: string; + }; index?: number; } @@ -1543,6 +1554,11 @@ export interface ITriggerResponse { manualTriggerResponse?: Promise; } +export interface ExecuteWorkflowData { + executionId: string; + data: Array; +} + export type WebhookSetupMethodNames = 'checkExists' | 'create' | 'delete'; export namespace MultiPartFormData { @@ -2127,6 +2143,8 @@ export interface ITaskSubRunMetadata { } export interface ITaskMetadata { + executionId?: string; + workflowId?: string; subRun?: ITaskSubRunMetadata[]; } @@ -2254,6 +2272,8 @@ export interface ExecuteWorkflowOptions { loadedRunData?: IWorkflowExecutionDataProcess; parentWorkflowSettings?: IWorkflowSettings; parentCallbackManager?: CallbackManager; + startMetadata?: ITaskMetadata; + doNotWaitToFinish?: boolean; } export type AiEvent = @@ -2287,7 +2307,7 @@ export interface IWorkflowExecuteAdditionalData { workflowInfo: IExecuteWorkflowInfo, additionalData: IWorkflowExecuteAdditionalData, options: ExecuteWorkflowOptions, - ) => Promise; + ) => Promise; executionId?: string; restartExecutionId?: string; hooks?: WorkflowHooks; From f456191c737265d82799469bf2eae98abe69df72 Mon Sep 17 00:00:00 2001 From: Mutasem Aldmour Date: Wed, 6 Nov 2024 17:55:32 +0100 Subject: [PATCH 02/48] add comment --- packages/cli/src/workflow-execute-additional-data.ts | 2 ++ .../core/src/node-execution-context/execute-single-context.ts | 1 + 2 files changed, 3 insertions(+) diff --git a/packages/cli/src/workflow-execute-additional-data.ts b/packages/cli/src/workflow-execute-additional-data.ts index 13ef7e11fe54d..24b3305c72719 100644 --- a/packages/cli/src/workflow-execute-additional-data.ts +++ b/packages/cli/src/workflow-execute-additional-data.ts @@ -813,12 +813,14 @@ export async function executeWorkflow( ); if (options.doNotWaitToFinish) { + // todo check if not breaking change return { executionId, data: [null] }; } return await executionPromise; } +// todo simplify async function startExecution( additionalData: IWorkflowExecuteAdditionalData, options: ExecuteWorkflowOptions, diff --git a/packages/core/src/node-execution-context/execute-single-context.ts b/packages/core/src/node-execution-context/execute-single-context.ts index 714951dc5604d..92a4d6239944e 100644 --- a/packages/core/src/node-execution-context/execute-single-context.ts +++ b/packages/core/src/node-execution-context/execute-single-context.ts @@ -37,6 +37,7 @@ import { BinaryHelpers } from './helpers/binary-helpers'; import { RequestHelpers } from './helpers/request-helpers'; import { NodeExecutionContext } from './node-execution-context'; +// todo simplify function setMetadata(executeData: IExecuteData, key: string, value: string) { if (!executeData.metadata) { executeData.metadata = {}; From 152ca977198e750d268b89ca8b619c00bc3bb3d4 Mon Sep 17 00:00:00 2001 From: Mutasem Aldmour Date: Thu, 7 Nov 2024 12:43:26 +0100 Subject: [PATCH 03/48] feat: bring back UI --- packages/editor-ui/src/Interface.ts | 1 + packages/editor-ui/src/components/RunData.vue | 77 ++++++++++++++++++- .../src/components/RunDataAi/RunDataAi.vue | 1 + .../components/RunDataAi/RunDataAiContent.vue | 10 +++ .../WorkflowLMChat/WorkflowLMChat.vue | 10 +-- .../global/GlobalExecutionsListItem.vue | 8 +- .../src/composables/useExecutionHelpers.ts | 12 +++ .../src/composables/useNodeHelpers.ts | 42 ++++++---- 8 files changed, 132 insertions(+), 29 deletions(-) diff --git a/packages/editor-ui/src/Interface.ts b/packages/editor-ui/src/Interface.ts index aec28890705c4..04daff95d5a47 100644 --- a/packages/editor-ui/src/Interface.ts +++ b/packages/editor-ui/src/Interface.ts @@ -182,6 +182,7 @@ export interface IAiDataContent { metadata: { executionTime: number; startTime: number; + executionId?: string; }; } diff --git a/packages/editor-ui/src/components/RunData.vue b/packages/editor-ui/src/components/RunData.vue index a3d354d7e716e..79f2072b30f03 100644 --- a/packages/editor-ui/src/components/RunData.vue +++ b/packages/editor-ui/src/components/RunData.vue @@ -58,12 +58,13 @@ import { useNodeTypesStore } from '@/stores/nodeTypes.store'; import { useNodeHelpers } from '@/composables/useNodeHelpers'; import { useNodeType } from '@/composables/useNodeType'; import { useToast } from '@/composables/useToast'; -import { isEqual, isObject } from 'lodash-es'; +import { get, isEqual, isObject } from 'lodash-es'; import { useExternalHooks } from '@/composables/useExternalHooks'; import { useSourceControlStore } from '@/stores/sourceControl.store'; import { useRootStore } from '@/stores/root.store'; import RunDataPinButton from '@/components/RunDataPinButton.vue'; import { getGenericHints } from '@/utils/nodeViewUtils'; +import { useExecutionHelpers } from '@/composables/useExecutionHelpers'; const LazyRunDataTable = defineAsyncComponent( async () => await import('@/components/RunDataTable.vue'), @@ -180,6 +181,7 @@ export default defineComponent({ const { isSubNodeType } = useNodeType({ node, }); + const { openExecutionInNewTab } = useExecutionHelpers(); return { ...useToast(), @@ -187,6 +189,7 @@ export default defineComponent({ nodeHelpers, pinnedData, isSubNodeType, + openExecutionInNewTab, }; }, data() { @@ -220,6 +223,43 @@ export default defineComponent({ useSourceControlStore, useRootStore, ), + subWorkflowData(): { executionId: string; workflowId?: string } | null { + if (!this.node) { + return null; + } + const metadata = get(this.workflowRunData, [this.node.name, this.runIndex, 'metadata'], null); + if (metadata?.executionId) { + return { + executionId: metadata?.executionId, + workflowId: metadata?.workflowId, + }; + } + return null; + }, + itemSubWorkflowData(): Array<{ + metadata?: { executionId?: string; workflowId?: string }; + itemIndex: number; + }> { + const items = this.getData(this.runIndex, this.currentOutputIndex); + return items + .map((item, itemIndex) => { + return { + metadata: item.metadata, + itemIndex, + }; + }) + .filter((item) => { + return !!item.metadata; + }); + }, + hasInputOverwrite(): boolean { + if (!this.node) { + return false; + } + const taskData = this.nodeHelpers.getNodeTaskData(this.node, this.runIndex); + if (taskData === null) return false; + return !!taskData.inputOverride; + }, isReadOnlyRoute() { return this.$route?.meta?.readOnlyCanvas === true; }, @@ -654,6 +694,15 @@ export default defineComponent({ this.hidePinDataDiscoveryTooltip(); }, methods: { + getData( + runIndex: number, + outputIndex: number, + connectionType: NodeConnectionType = NodeConnectionType.Main, + ) { + const rawInputData = this.getRawInputData(runIndex, outputIndex, connectionType); + const pinOrLiveData = this.getPinDataOrLiveData(rawInputData); + return this.getFilteredData(pinOrLiveData); + }, getResolvedNodeOutputs() { if (this.node && this.nodeType) { const workflowNode = this.workflow.getNode(this.node.name); @@ -1383,6 +1432,32 @@ export default defineComponent({ +
+ +
+ + + +
+
+
{ }} +
  • + EXECUTION ID {{ runMeta?.executionId }} +
  • {{ $locale.baseText('runData.aiContentBlock.tokens', { diff --git a/packages/editor-ui/src/components/WorkflowLMChat/WorkflowLMChat.vue b/packages/editor-ui/src/components/WorkflowLMChat/WorkflowLMChat.vue index e76ab8241a87a..b2576b908d14e 100644 --- a/packages/editor-ui/src/components/WorkflowLMChat/WorkflowLMChat.vue +++ b/packages/editor-ui/src/components/WorkflowLMChat/WorkflowLMChat.vue @@ -12,7 +12,6 @@ import { CHAT_TRIGGER_NODE_TYPE, MANUAL_CHAT_TRIGGER_NODE_TYPE, MODAL_CONFIRM, - VIEWS, WORKFLOW_LM_CHAT_MODAL_KEY, } from '@/constants'; @@ -58,6 +57,7 @@ import { usePinnedData } from '@/composables/usePinnedData'; import { get, last } from 'lodash-es'; import { isEmpty } from '@/utils/typesUtils'; import { chatEventBus } from '@n8n/chat/event-buses'; +import { useExecutionHelpers } from '@/composables/useExecutionHelpers'; const LazyRunDataAi = defineAsyncComponent( async () => await import('@/components/RunDataAi/RunDataAi.vue'), @@ -82,6 +82,8 @@ const workflowsStore = useWorkflowsStore(); const nodeTypesStore = useNodeTypesStore(); const uiStore = useUIStore(); +const executionHelpers = useExecutionHelpers(); + const { showError } = useToast(); const messages: Ref = ref([]); const currentSessionId = ref(String(Date.now())); @@ -428,11 +430,7 @@ async function sendMessage(message: string, files?: File[]) { } function displayExecution(executionId: string) { - const route = router.resolve({ - name: VIEWS.EXECUTION_PREVIEW, - params: { name: workflow.value.id, executionId }, - }); - window.open(route.href, '_blank'); + executionHelpers.openExecutionInNewTab(executionId, workflow.value.id); } function isTextMessage(message: ChatMessage): message is ChatMessageText { return message.type === 'text' || !message.type; diff --git a/packages/editor-ui/src/components/executions/global/GlobalExecutionsListItem.vue b/packages/editor-ui/src/components/executions/global/GlobalExecutionsListItem.vue index f23dbbc4acc3e..1b258f3e53c06 100644 --- a/packages/editor-ui/src/components/executions/global/GlobalExecutionsListItem.vue +++ b/packages/editor-ui/src/components/executions/global/GlobalExecutionsListItem.vue @@ -2,7 +2,7 @@ import { ref, computed, useCssModule } from 'vue'; import type { ExecutionSummary } from 'n8n-workflow'; import { useI18n } from '@/composables/useI18n'; -import { VIEWS, WAIT_TIME_UNLIMITED } from '@/constants'; +import { WAIT_TIME_UNLIMITED } from '@/constants'; import { useRouter } from 'vue-router'; import { convertToDisplayDate } from '@/utils/formatters/dateFormatter'; import { i18n as locale } from '@/plugins/i18n'; @@ -138,11 +138,7 @@ function formatDate(fullDate: Date | string | number) { } function displayExecution() { - const route = router.resolve({ - name: VIEWS.EXECUTION_PREVIEW, - params: { name: props.execution.workflowId, executionId: props.execution.id }, - }); - window.open(route.href, '_blank'); + executionHelpers.openExecutionInNewTab(props.execution.id, props.execution.workflowId); } function onStopExecution() { diff --git a/packages/editor-ui/src/composables/useExecutionHelpers.ts b/packages/editor-ui/src/composables/useExecutionHelpers.ts index c87006c92a206..a68cfd85bfc75 100644 --- a/packages/editor-ui/src/composables/useExecutionHelpers.ts +++ b/packages/editor-ui/src/composables/useExecutionHelpers.ts @@ -1,6 +1,8 @@ import type { ExecutionSummary } from 'n8n-workflow'; import { convertToDisplayDate } from '@/utils/formatters/dateFormatter'; import { useI18n } from '@/composables/useI18n'; +import { useRouter } from 'vue-router'; +import { VIEWS } from '@/constants'; export interface IExecutionUIData { name: string; @@ -14,6 +16,7 @@ export interface IExecutionUIData { export function useExecutionHelpers() { const i18n = useI18n(); + const router = useRouter(); function getUIDetails(execution: ExecutionSummary): IExecutionUIData { const status = { @@ -69,9 +72,18 @@ export function useExecutionHelpers() { return ['crashed', 'error'].includes(execution.status) && !execution.retrySuccessId; } + function openExecutionInNewTab(executionId: string, workflowId?: string) { + const route = router.resolve({ + name: VIEWS.EXECUTION_PREVIEW, + params: { name: workflowId, executionId }, + }); + window.open(route.href, '_blank'); + } + return { getUIDetails, formatDate, isExecutionRetriable, + openExecutionInNewTab, }; } diff --git a/packages/editor-ui/src/composables/useNodeHelpers.ts b/packages/editor-ui/src/composables/useNodeHelpers.ts index f2f33a5ef7c2e..42a573bdf225a 100644 --- a/packages/editor-ui/src/composables/useNodeHelpers.ts +++ b/packages/editor-ui/src/composables/useNodeHelpers.ts @@ -565,6 +565,29 @@ export function useNodeHelpers() { } } + function getNodeTaskData(node: INodeUi | null, runIndex = 0) { + if (node === null) { + return null; + } + if (workflowsStore.getWorkflowExecution === null) { + return null; + } + + const executionData = workflowsStore.getWorkflowExecution.data; + if (!executionData?.resultData) { + // unknown status + return null; + } + const runData = executionData.resultData.runData; + + const taskData = get(runData, [node.name, runIndex]); + if (!taskData) { + return null; + } + + return taskData; + } + function getNodeInputData( node: INodeUi | null, runIndex = 0, @@ -582,22 +605,8 @@ export function useNodeHelpers() { runIndex = runIndex - 1; } - if (node === null) { - return []; - } - if (workflowsStore.getWorkflowExecution === null) { - return []; - } - - const executionData = workflowsStore.getWorkflowExecution.data; - if (!executionData?.resultData) { - // unknown status - return []; - } - const runData = executionData.resultData.runData; - - const taskData = get(runData, [node.name, runIndex]); - if (!taskData) { + const taskData = getNodeTaskData(node, runIndex); + if (taskData === null) { return []; } @@ -1281,5 +1290,6 @@ export function useNodeHelpers() { removeConnectionByConnectionInfo, addPinDataConnections, removePinDataConnections, + getNodeTaskData, }; } From 932ca0781a43a14d7d97bfcaed0dd802707c7543 Mon Sep 17 00:00:00 2001 From: Mutasem Aldmour Date: Thu, 7 Nov 2024 12:44:33 +0100 Subject: [PATCH 04/48] fix: type --- .../components/executions/global/GlobalExecutionsListItem.vue | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/editor-ui/src/components/executions/global/GlobalExecutionsListItem.vue b/packages/editor-ui/src/components/executions/global/GlobalExecutionsListItem.vue index 1b258f3e53c06..6da106d8182fc 100644 --- a/packages/editor-ui/src/components/executions/global/GlobalExecutionsListItem.vue +++ b/packages/editor-ui/src/components/executions/global/GlobalExecutionsListItem.vue @@ -3,7 +3,6 @@ import { ref, computed, useCssModule } from 'vue'; import type { ExecutionSummary } from 'n8n-workflow'; import { useI18n } from '@/composables/useI18n'; import { WAIT_TIME_UNLIMITED } from '@/constants'; -import { useRouter } from 'vue-router'; import { convertToDisplayDate } from '@/utils/formatters/dateFormatter'; import { i18n as locale } from '@/plugins/i18n'; import ExecutionsTime from '@/components/executions/ExecutionsTime.vue'; @@ -35,7 +34,6 @@ const props = withDefaults( const style = useCssModule(); const i18n = useI18n(); -const router = useRouter(); const executionHelpers = useExecutionHelpers(); const isStopping = ref(false); From 3df228c52f2ada0d8cf70cc679b5227916ef3c19 Mon Sep 17 00:00:00 2001 From: Mutasem Aldmour Date: Thu, 7 Nov 2024 15:34:09 +0100 Subject: [PATCH 05/48] feat: Add link to parent execution --- packages/editor-ui/src/components/RunData.vue | 23 ++++++++++++------- .../src/plugins/i18n/locales/en.json | 1 + 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/packages/editor-ui/src/components/RunData.vue b/packages/editor-ui/src/components/RunData.vue index 79f2072b30f03..1d3d264dc22a4 100644 --- a/packages/editor-ui/src/components/RunData.vue +++ b/packages/editor-ui/src/components/RunData.vue @@ -1432,15 +1432,17 @@ export default defineComponent({ -
    - + + > + {{ $locale.baseText('runData.openParentExecution') }} +
    From 9705e42929a3e565f7fd3d434acff8a4d0407fce Mon Sep 17 00:00:00 2001 From: Mutasem Aldmour Date: Thu, 7 Nov 2024 18:12:24 +0100 Subject: [PATCH 09/48] try and fix link --- packages/editor-ui/src/components/RunData.vue | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/packages/editor-ui/src/components/RunData.vue b/packages/editor-ui/src/components/RunData.vue index 0b301a6d5832f..b12f89c532368 100644 --- a/packages/editor-ui/src/components/RunData.vue +++ b/packages/editor-ui/src/components/RunData.vue @@ -505,7 +505,7 @@ const subWorkflowData = computed((): { executionId: string; workflowId?: string if (!node.value) { return null; } - const metadata = get(workflowRunData, [node.value.name, props.runIndex, 'metadata'], null); + const metadata = get(workflowRunData.value, [node.value.name, props.runIndex, 'metadata'], null); if (metadata?.executionId) { return { executionId: metadata?.executionId, @@ -1378,13 +1378,15 @@ defineExpose({ enterEditMode }); -
    +
    {{ $locale.baseText('runData.openParentExecution') }} From 37c8a3e28b987630695db81787246f740cca41b3 Mon Sep 17 00:00:00 2001 From: Mutasem Aldmour Date: Thu, 7 Nov 2024 19:18:10 +0100 Subject: [PATCH 10/48] fix: links to subworkflow --- packages/editor-ui/src/components/RunData.vue | 17 +++++++++++------ .../editor-ui/src/plugins/i18n/locales/en.json | 1 + 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/packages/editor-ui/src/components/RunData.vue b/packages/editor-ui/src/components/RunData.vue index 09dc8a4dc0782..c66ad072cb0a5 100644 --- a/packages/editor-ui/src/components/RunData.vue +++ b/packages/editor-ui/src/components/RunData.vue @@ -1379,17 +1379,18 @@ defineExpose({ enterEditMode }); @@ -2108,6 +2109,10 @@ defineExpose({ enterEditMode }); .parentExecutionInfo { font-size: var(--font-size-xs); padding: 0 0 var(--spacing-s) var(--spacing-s); + + svg { + padding-bottom: 2px; + } } diff --git a/packages/editor-ui/src/plugins/i18n/locales/en.json b/packages/editor-ui/src/plugins/i18n/locales/en.json index 23e4db84bb8a1..82a5b6d48feb7 100644 --- a/packages/editor-ui/src/plugins/i18n/locales/en.json +++ b/packages/editor-ui/src/plugins/i18n/locales/en.json @@ -1553,6 +1553,7 @@ "resourceMapper.addAllFields": "Add All {fieldWord}", "resourceMapper.removeAllFields": "Remove All {fieldWord}", "resourceMapper.refreshFieldList": "Refresh {fieldWord} List", + "runData.openSubExecution": "Open Sub Execution", "runData.openParentExecution": "Open Parent Execution", "runData.emptyItemHint": "This is an item, but it's empty.", "runData.emptyArray": "[empty array]", From 9f5669396fac2e34b9120839a1b4bee5afe2afcd Mon Sep 17 00:00:00 2001 From: Mutasem Aldmour Date: Fri, 8 Nov 2024 14:40:33 +0100 Subject: [PATCH 11/48] add metadata to output data --- packages/core/src/NodeExecuteFunctions.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/core/src/NodeExecuteFunctions.ts b/packages/core/src/NodeExecuteFunctions.ts index bfce64eb51f1e..0c8ff962617a8 100644 --- a/packages/core/src/NodeExecuteFunctions.ts +++ b/packages/core/src/NodeExecuteFunctions.ts @@ -4161,6 +4161,7 @@ export function getSupplyDataFunctions( connectionType: NodeConnectionType, currentNodeRunIndex: number, data: INodeExecutionData[][], + metadata?: ITaskMetadata, ): void { addExecutionDataFunctions( 'output', @@ -4172,6 +4173,7 @@ export function getSupplyDataFunctions( node.name, runIndex, currentNodeRunIndex, + metadata, ).catch((error) => { Logger.warn( `There was a problem logging output data of node "${this.getNode().name}": ${ From cf5125ed7547bc5408a95723e3ab934cc6d5a147 Mon Sep 17 00:00:00 2001 From: Mutasem Aldmour Date: Fri, 8 Nov 2024 15:19:35 +0100 Subject: [PATCH 12/48] fix: design --- .../src/components/RunDataAi/RunDataAiContent.vue | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/editor-ui/src/components/RunDataAi/RunDataAiContent.vue b/packages/editor-ui/src/components/RunDataAi/RunDataAiContent.vue index 76a4894c62304..8066d716f290b 100644 --- a/packages/editor-ui/src/components/RunDataAi/RunDataAiContent.vue +++ b/packages/editor-ui/src/components/RunDataAi/RunDataAiContent.vue @@ -137,9 +137,10 @@ const outputError = computed(() => {
  • - EXECUTION ID {{ runMeta?.executionId }} + + + {{ $locale.baseText('runData.openSubExecution') }} +
  • {{ From e9bbc714f157f53822e2db64b09b19f0d1274ef3 Mon Sep 17 00:00:00 2001 From: Mutasem Aldmour Date: Fri, 8 Nov 2024 17:02:38 +0100 Subject: [PATCH 13/48] fix: bug with setting metadata --- packages/core/src/WorkflowExecute.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/core/src/WorkflowExecute.ts b/packages/core/src/WorkflowExecute.ts index f70099a26c2d6..b1c41593dd4a5 100644 --- a/packages/core/src/WorkflowExecute.ts +++ b/packages/core/src/WorkflowExecute.ts @@ -408,7 +408,10 @@ export class WorkflowExecute { let metaRunData: ITaskMetadata; for (const nodeName of Object.keys(metadata)) { for ([index, metaRunData] of metadata[nodeName].entries()) { - runData[nodeName][index].metadata = metaRunData; + runData[nodeName][index].metadata = { + ...(runData[nodeName][index].metadata ?? {}), + ...metaRunData, + }; } } } From 2dcf04db76b1e59a90046bd4a163180d9a7f8c1a Mon Sep 17 00:00:00 2001 From: Mutasem Aldmour Date: Fri, 8 Nov 2024 18:13:07 +0100 Subject: [PATCH 14/48] feat: support workflow retrieval debugging --- .../RetrieverWorkflow/RetrieverWorkflow.node.ts | 1 + packages/@n8n/nodes-langchain/utils/logWrapper.ts | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/@n8n/nodes-langchain/nodes/retrievers/RetrieverWorkflow/RetrieverWorkflow.node.ts b/packages/@n8n/nodes-langchain/nodes/retrievers/RetrieverWorkflow/RetrieverWorkflow.node.ts index fab3c2dabe8d7..b2e762db88721 100644 --- a/packages/@n8n/nodes-langchain/nodes/retrievers/RetrieverWorkflow/RetrieverWorkflow.node.ts +++ b/packages/@n8n/nodes-langchain/nodes/retrievers/RetrieverWorkflow/RetrieverWorkflow.node.ts @@ -418,6 +418,7 @@ export class RetrieverWorkflow implements INodeType { metadata: { ...baseMetadata, itemIndex: index, + executionId: receivedData.executionId, }, }), ); diff --git a/packages/@n8n/nodes-langchain/utils/logWrapper.ts b/packages/@n8n/nodes-langchain/utils/logWrapper.ts index 10e55ba6ef910..3a3a11dad2175 100644 --- a/packages/@n8n/nodes-langchain/utils/logWrapper.ts +++ b/packages/@n8n/nodes-langchain/utils/logWrapper.ts @@ -220,8 +220,14 @@ export function logWrapper( arguments: [query, config], })) as Array>>; + const executionId: string | undefined = response[0]?.metadata?.executionId as string; + const workflowId: string | undefined = response[0]?.metadata?.workflowId as string; + logAiEvent(executeFunctions, 'ai-documents-retrieved', { query }); - executeFunctions.addOutputData(connectionType, index, [[{ json: { response } }]]); + executeFunctions.addOutputData(connectionType, index, [[{ json: { response } }]], { + executionId, + workflowId, + }); return response; }; } From 80ae58f2aff448dfc855b8a4872f4760c7124346 Mon Sep 17 00:00:00 2001 From: Mutasem Aldmour Date: Mon, 11 Nov 2024 10:12:01 +0100 Subject: [PATCH 15/48] feat: add telemetry --- packages/editor-ui/src/components/RunData.vue | 21 ++++++++++++++++++- .../components/RunDataAi/RunDataAiContent.vue | 19 ++++++++++++++++- .../editor-ui/src/components/RunDataTable.vue | 4 ++++ 3 files changed, 42 insertions(+), 2 deletions(-) diff --git a/packages/editor-ui/src/components/RunData.vue b/packages/editor-ui/src/components/RunData.vue index 1190020f404ce..e2ea3ec13d4bc 100644 --- a/packages/editor-ui/src/components/RunData.vue +++ b/packages/editor-ui/src/components/RunData.vue @@ -1210,6 +1210,23 @@ function onSearchClear() { document.dispatchEvent(new KeyboardEvent('keyup', { key: '/' })); } +function onOpenRelatedExecution(executionId: string, workflowId?: string) { + if (!nodeType.value) { + return; + } + + openExecutionInNewTab(executionId, workflowId); + + // todo better distinguish these two + const isTrigger = nodeType.value.group.includes('trigger'); + telemetry.track( + isTrigger ? 'User clicked parent execution button' : 'User clicked inspect sub-workflow', + { + view: displayMode.value, + }, + ); +} + defineExpose({ enterEditMode }); @@ -1390,7 +1407,9 @@ defineExpose({ enterEditMode }); :class="$style.parentExecutionInfo" > {{ diff --git a/packages/editor-ui/src/components/RunDataAi/RunDataAiContent.vue b/packages/editor-ui/src/components/RunDataAi/RunDataAiContent.vue index 8066d716f290b..622b06d83f41b 100644 --- a/packages/editor-ui/src/components/RunDataAi/RunDataAiContent.vue +++ b/packages/editor-ui/src/components/RunDataAi/RunDataAiContent.vue @@ -12,6 +12,7 @@ import { computed } from 'vue'; import NodeIcon from '@/components/NodeIcon.vue'; import AiRunContentBlock from './AiRunContentBlock.vue'; import { useExecutionHelpers } from '@/composables/useExecutionHelpers'; +import { useTelemetry } from '@/composables/useTelemetry'; interface RunMeta { startTimeMs: number; @@ -29,6 +30,8 @@ const props = defineProps<{ const nodeTypesStore = useNodeTypesStore(); const workflowsStore = useWorkflowsStore(); +const telemetry = useTelemetry(); + const { openExecutionInNewTab } = useExecutionHelpers(); type TokenUsageData = { @@ -105,6 +108,20 @@ const outputError = computed(() => { | NodeError | undefined; }); + +// todo unify function across components +function openExecution({ executionId }: RunMeta) { + if (!executionId) { + return; + } + + // todo add workflow id + openExecutionInNewTab(executionId); + + telemetry.track('User clicked inspect sub-workflow', { + view: 'ai', + }); +}