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 b2e762db88..434048153c 100644 --- a/packages/@n8n/nodes-langchain/nodes/retrievers/RetrieverWorkflow/RetrieverWorkflow.node.ts +++ b/packages/@n8n/nodes-langchain/nodes/retrievers/RetrieverWorkflow/RetrieverWorkflow.node.ts @@ -352,6 +352,9 @@ export class RetrieverWorkflow implements INodeType { }, ); } + + // same as current workflow + baseMetadata.workflowId = workflowProxy.$workflow.id; } const rawData: IDataObject = { query }; @@ -395,8 +398,10 @@ export class RetrieverWorkflow implements INodeType { config?.getChild(), { startMetadata: { - executionId: workflowProxy.$execution.id, - workflowId: workflowProxy.$workflow.id, + parentExecution: { + executionId: workflowProxy.$execution.id, + workflowId: workflowProxy.$workflow.id, + }, }, }, ); 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 24fc802940..caa50fa685 100644 --- a/packages/@n8n/nodes-langchain/nodes/tools/ToolWorkflow/ToolWorkflow.node.ts +++ b/packages/@n8n/nodes-langchain/nodes/tools/ToolWorkflow/ToolWorkflow.node.ts @@ -360,9 +360,13 @@ export class ToolWorkflow implements INodeType { }; async supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise { + const workflowProxy = this.getWorkflowDataProxy(0); + const name = this.getNodeParameter('name', itemIndex) as string; const description = this.getNodeParameter('description', itemIndex) as string; - let executionId: string | undefined = undefined; + + let subExecutionId: string | undefined; + let subWorkflowId: string | undefined; const useSchema = this.getNodeParameter('specifyInputSchema', itemIndex) as boolean; let tool: DynamicTool | DynamicStructuredTool | undefined = undefined; @@ -399,11 +403,16 @@ export class ToolWorkflow implements INodeType { ) as INodeParameterResourceLocator; workflowInfo.id = value as string; } + + subWorkflowId = workflowInfo.id; } else if (source === 'parameter') { // Read workflow from parameter const workflowJson = this.getNodeParameter('workflowJson', itemIndex) as string; try { workflowInfo.code = JSON.parse(workflowJson) as IWorkflowBase; + + // subworkflow is same as parent workflow + subWorkflowId = workflowProxy.$workflow.id; } catch (error) { throw new NodeOperationError( this.getNode(), @@ -443,17 +452,17 @@ export class ToolWorkflow implements INodeType { const items = [newItem] as INodeExecutionData[]; - const workflowProxy = this.getWorkflowDataProxy(0); - let receivedData: ExecuteWorkflowData; try { receivedData = await this.executeWorkflow(workflowInfo, items, runManager?.getChild(), { startMetadata: { - executionId: workflowProxy.$execution.id, - workflowId: workflowProxy.$workflow.id, + parentExecution: { + executionId: workflowProxy.$execution.id, + workflowId: workflowProxy.$workflow.id, + }, }, }); - executionId = receivedData.executionId; + subExecutionId = 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 @@ -512,9 +521,12 @@ export class ToolWorkflow implements INodeType { } let metadata: ITaskMetadata | undefined; - if (executionId) { + if (subExecutionId && subWorkflowId) { metadata = { - executionId, + subExecution: { + executionId: subExecutionId, + workflowId: subWorkflowId, + }, }; } diff --git a/packages/@n8n/nodes-langchain/utils/logWrapper.ts b/packages/@n8n/nodes-langchain/utils/logWrapper.ts index 3a3a11dad2..fa1a38b31a 100644 --- a/packages/@n8n/nodes-langchain/utils/logWrapper.ts +++ b/packages/@n8n/nodes-langchain/utils/logWrapper.ts @@ -10,7 +10,12 @@ import type { Tool } from '@langchain/core/tools'; import { VectorStore } from '@langchain/core/vectorstores'; import { TextSplitter } from '@langchain/textsplitters'; import type { BaseDocumentLoader } from 'langchain/dist/document_loaders/base'; -import type { IExecuteFunctions, INodeExecutionData, ISupplyDataFunctions } from 'n8n-workflow'; +import type { + IExecuteFunctions, + INodeExecutionData, + ISupplyDataFunctions, + ITaskMetadata, +} from 'n8n-workflow'; import { NodeOperationError, NodeConnectionType } from 'n8n-workflow'; import { logAiEvent, isToolsInstance, isBaseChatMemory, isBaseChatMessageHistory } from './helpers'; @@ -223,11 +228,21 @@ export function logWrapper( const executionId: string | undefined = response[0]?.metadata?.executionId as string; const workflowId: string | undefined = response[0]?.metadata?.workflowId as string; + const metadata: ITaskMetadata = {}; + if (executionId && workflowId) { + metadata.subExecution = { + executionId, + workflowId, + }; + } + logAiEvent(executeFunctions, 'ai-documents-retrieved', { query }); - executeFunctions.addOutputData(connectionType, index, [[{ json: { response } }]], { - executionId, - workflowId, - }); + executeFunctions.addOutputData( + connectionType, + index, + [[{ json: { response } }]], + metadata, + ); return response; }; } diff --git a/packages/core/src/NodeExecuteFunctions.ts b/packages/core/src/NodeExecuteFunctions.ts index 0c8ff96261..ce9a7aecd0 100644 --- a/packages/core/src/NodeExecuteFunctions.ts +++ b/packages/core/src/NodeExecuteFunctions.ts @@ -33,7 +33,6 @@ 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 { @@ -3583,13 +3582,6 @@ 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. */ @@ -3625,8 +3617,11 @@ export function getExecuteFunctions( itemIndex, ), getExecuteData: () => executeData, - setMetadata: (key: string, value: string): void => { - return setMetadata(executeData, key, value); + setMetadata: (metadata: ITaskMetadata): void => { + executeData.metadata = { + ...(executeData.metadata ?? {}), + ...metadata, + }; }, continueOnFail: () => { return continueOnFail(node); 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 e0ad751807..dd08aa0fc6 100644 --- a/packages/core/src/node-execution-context/execute-single-context.ts +++ b/packages/core/src/node-execution-context/execute-single-context.ts @@ -1,4 +1,3 @@ -import set from 'lodash/set'; import type { ICredentialDataDecryptedObject, IGetNodeParameterOptions, @@ -14,6 +13,7 @@ import type { ContextType, AiEvent, ISourceData, + ITaskMetadata, } from 'n8n-workflow'; import { ApplicationError, @@ -37,14 +37,6 @@ import { import { NodeExecutionContext } from './node-execution-context'; -// todo simplify -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']; @@ -94,8 +86,11 @@ export class ExecuteSingleContext extends NodeExecutionContext implements IExecu this.abortSignal?.addEventListener('abort', fn); } - setMetadata(key: string, value: string): void { - return setMetadata(this.executeData, key, value); + setMetadata(metadata: ITaskMetadata): void { + this.executeData.metadata = { + ...(this.executeData.metadata ?? {}), + ...metadata, + }; } continueOnFail() { diff --git a/packages/editor-ui/src/Interface.ts b/packages/editor-ui/src/Interface.ts index ba3cad9ab8..635b983edd 100644 --- a/packages/editor-ui/src/Interface.ts +++ b/packages/editor-ui/src/Interface.ts @@ -182,7 +182,10 @@ export interface IAiDataContent { metadata: { executionTime: number; startTime: number; - executionId?: string; + subExecution?: { + workflowId: string; + executionId: string; + }; }; } diff --git a/packages/editor-ui/src/components/RunData.vue b/packages/editor-ui/src/components/RunData.vue index 097fa3c6cf..f68ccab6e6 100644 --- a/packages/editor-ui/src/components/RunData.vue +++ b/packages/editor-ui/src/components/RunData.vue @@ -9,6 +9,7 @@ import type { INodeOutputConfiguration, IRunData, IRunExecutionData, + ITaskMetadata, NodeError, NodeHint, Workflow, @@ -508,18 +509,17 @@ const pinButtonDisabled = computed( readOnlyEnv.value, ); -const subWorkflowData = computed((): { executionId: string; workflowId?: string } | null => { +const subWorkflowData = computed((): ITaskMetadata | null => { if (!node.value) { return null; } const metadata = get(workflowRunData.value, [node.value.name, props.runIndex, 'metadata'], null); - if (metadata?.executionId) { - return { - executionId: metadata?.executionId, - workflowId: metadata?.workflowId, - }; + console.log('yo', metadata); + if (!metadata?.parentExecution && !metadata?.subExecution) { + return null; } - return null; + + return metadata; }); const hasInputOverwrite = computed((): boolean => { @@ -1210,17 +1210,16 @@ function onSearchClear() { document.dispatchEvent(new KeyboardEvent('keyup', { key: '/' })); } -function onOpenRelatedExecution(executionId: string, workflowId?: string) { - if (!nodeType.value) { +function onOpenRelatedExecution({ parentExecution, subExecution }: ITaskMetadata) { + const info = parentExecution || subExecution; + if (!info) { return; } - openExecutionInNewTab(executionId, workflowId); + openExecutionInNewTab(info.executionId, info.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', + parentExecution ? 'User clicked parent execution button' : 'User clicked inspect sub-workflow', { view: displayMode.value, }, @@ -1455,14 +1454,10 @@ defineExpose({ enterEditMode }); v-if="subWorkflowData && !(paneType === 'input' && hasInputOverwrite)" :class="$style.parentExecutionInfo" > - + {{ - nodeType?.group.includes('trigger') + subWorkflowData.parentExecution ? $locale.baseText('runData.openParentExecution') : $locale.baseText('runData.openSubExecution') }} diff --git a/packages/editor-ui/src/components/RunDataAi/RunDataAi.vue b/packages/editor-ui/src/components/RunDataAi/RunDataAi.vue index 390f6a40bf..1aa3c29f92 100644 --- a/packages/editor-ui/src/components/RunDataAi/RunDataAi.vue +++ b/packages/editor-ui/src/components/RunDataAi/RunDataAi.vue @@ -61,7 +61,7 @@ function getReferencedData( metadata: { executionTime: taskData.executionTime, startTime: taskData.startTime, - executionId: taskData.metadata?.executionId, + subExecution: taskData.metadata?.subExecution, }, }); }); diff --git a/packages/editor-ui/src/components/RunDataAi/RunDataAiContent.vue b/packages/editor-ui/src/components/RunDataAi/RunDataAiContent.vue index 622b06d83f..06664a29b0 100644 --- a/packages/editor-ui/src/components/RunDataAi/RunDataAiContent.vue +++ b/packages/editor-ui/src/components/RunDataAi/RunDataAiContent.vue @@ -20,7 +20,10 @@ interface RunMeta { node: INodeTypeDescription | null; type: 'input' | 'output'; connectionType: NodeConnectionType; - executionId?: string; + subExecution?: { + workflowId: string; + executionId: string; + }; } const props = defineProps<{ inputData: IAiData; @@ -82,7 +85,7 @@ function extractRunMeta(run: IAiDataContent) { node: nodeType, type: run.inOut, connectionType: run.type, - executionId: run.metadata.executionId, + subExecution: run.metadata?.subExecution, }; return runMeta; @@ -110,13 +113,8 @@ const outputError = computed(() => { }); // todo unify function across components -function openExecution({ executionId }: RunMeta) { - if (!executionId) { - return; - } - - // todo add workflow id - openExecutionInNewTab(executionId); +function openExecution({ executionId, workflowId }: { workflowId: string; executionId: string }) { + openExecutionInNewTab(executionId, workflowId); telemetry.track('User clicked inspect sub-workflow', { view: 'ai', @@ -153,8 +151,8 @@ function openExecution({ executionId }: RunMeta) { }} -
  • - +
  • + {{ $locale.baseText('runData.openSubExecution') }} diff --git a/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflow.node.ts b/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflow.node.ts index 30df915d44..579322c755 100644 --- a/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflow.node.ts +++ b/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflow.node.ts @@ -231,8 +231,10 @@ export class ExecuteWorkflow implements INodeType { undefined, { startMetadata: { - executionId: workflowProxy.$execution.id, - workflowId: workflowProxy.$workflow.id, + parentExecution: { + executionId: workflowProxy.$execution.id, + workflowId: workflowProxy.$workflow.id, + }, }, }, ); @@ -261,8 +263,10 @@ export class ExecuteWorkflow implements INodeType { { doNotWaitToFinish: true, startMetadata: { - executionId: workflowProxy.$execution.id, - workflowId: workflowProxy.$workflow.id, + parentExecution: { + executionId: workflowProxy.$execution.id, + workflowId: workflowProxy.$workflow.id, + }, }, }, ); @@ -311,16 +315,20 @@ export class ExecuteWorkflow implements INodeType { { doNotWaitToFinish: !waitForSubWorkflow, startMetadata: { - executionId: workflowProxy.$execution.id, - workflowId: workflowProxy.$workflow.id, + parentExecution: { + executionId: workflowProxy.$execution.id, + workflowId: workflowProxy.$workflow.id, + }, }, }, ); - this.setMetadata('executionId', executionResult.executionId); - if (workflowInfo.id !== undefined) { - this.setMetadata('workflowId', workflowInfo.id); - } + this.setMetadata({ + subExecution: { + executionId: executionResult.executionId, + workflowId: workflowInfo.id ?? (workflowProxy.$workflow.id as string), + }, + }); if (!waitForSubWorkflow) { return [items]; diff --git a/packages/workflow/src/Interfaces.ts b/packages/workflow/src/Interfaces.ts index 88b22730aa..1aadafd13a 100644 --- a/packages/workflow/src/Interfaces.ts +++ b/packages/workflow/src/Interfaces.ts @@ -936,7 +936,7 @@ export type ContextType = 'flow' | 'node'; type BaseExecutionFunctions = FunctionsBaseWithRequiredKeys<'getMode'> & { continueOnFail(): boolean; - setMetadata(key: string, value: string): void; + setMetadata(metadata: ITaskMetadata): void; evaluateExpression(expression: string, itemIndex: number): NodeParameterValueType; getContext(type: ContextType): IContextObject; getExecuteData(): IExecuteData; @@ -2143,8 +2143,14 @@ export interface ITaskSubRunMetadata { } export interface ITaskMetadata { - executionId?: string; - workflowId?: string; + parentExecution?: { + executionId: string; + workflowId: string; + }; + subExecution?: { + executionId: string; + workflowId: string; + }; subRun?: ITaskSubRunMetadata[]; }