mirror of
https://github.com/n8n-io/n8n.git
synced 2026-09-19 01:45:48 +08:00
feat(core): Expose $evaluation.runId expression global in eval executions (#32843)
This commit is contained in:
+1
@@ -284,6 +284,7 @@ describe('BuiltInsParser', () => {
|
||||
'Interval',
|
||||
'Duration',
|
||||
'$execution',
|
||||
'$evaluation',
|
||||
'$vars',
|
||||
'$secrets',
|
||||
'$executionId',
|
||||
|
||||
@@ -99,6 +99,8 @@ export interface PartialAdditionalData {
|
||||
executionTimeoutTimestamp?: number;
|
||||
userId?: string;
|
||||
variables: IDataObject;
|
||||
/** Parent evaluation TestRun.id, exposed to Code nodes as `$evaluation.runId`. */
|
||||
evaluationRunId?: string;
|
||||
}
|
||||
|
||||
/** RPC methods that are exposed directly to the Code Node */
|
||||
|
||||
@@ -783,6 +783,7 @@ describe('TestRunnerService', () => {
|
||||
},
|
||||
userId: metadata.userId,
|
||||
forceFullExecutionData: true,
|
||||
evaluationRunId: metadata.testRunId,
|
||||
triggerToStartFrom: {
|
||||
name: triggerNodeName,
|
||||
},
|
||||
@@ -931,6 +932,7 @@ describe('TestRunnerService', () => {
|
||||
},
|
||||
},
|
||||
userId: metadata.userId,
|
||||
evaluationRunId: metadata.testRunId,
|
||||
triggerToStartFrom: {
|
||||
name: triggerNodeName,
|
||||
},
|
||||
@@ -945,6 +947,7 @@ describe('TestRunnerService', () => {
|
||||
},
|
||||
manualData: {
|
||||
userId: metadata.userId,
|
||||
evaluationRunId: metadata.testRunId,
|
||||
triggerToStartFrom: {
|
||||
name: triggerNodeName,
|
||||
},
|
||||
|
||||
@@ -290,6 +290,7 @@ export class TestRunnerService {
|
||||
},
|
||||
},
|
||||
userId: metadata.userId,
|
||||
evaluationRunId: metadata.testRunId,
|
||||
triggerToStartFrom: {
|
||||
name: triggerNode.name,
|
||||
},
|
||||
@@ -305,6 +306,7 @@ export class TestRunnerService {
|
||||
},
|
||||
manualData: {
|
||||
userId: metadata.userId,
|
||||
evaluationRunId: metadata.testRunId,
|
||||
triggerToStartFrom: {
|
||||
name: triggerNode.name,
|
||||
},
|
||||
|
||||
@@ -165,6 +165,7 @@ export class JobProcessor {
|
||||
});
|
||||
additionalData.streamingEnabled = job.data.streamingEnabled;
|
||||
additionalData.restartExecutionId = job.data.restartExecutionId;
|
||||
additionalData.evaluationRunId = execution.data.manualData?.evaluationRunId;
|
||||
|
||||
const { pushRef } = job.data;
|
||||
|
||||
|
||||
+2
@@ -24,6 +24,7 @@ const additionalData = mock<PartialAdditionalData>({
|
||||
currentNodeParameters: undefined,
|
||||
executionTimeoutTimestamp: undefined,
|
||||
restartExecutionId: undefined,
|
||||
evaluationRunId: 'test-run-id-123',
|
||||
});
|
||||
|
||||
const node = mock<INode>();
|
||||
@@ -108,6 +109,7 @@ describe('DataRequestResponseBuilder', () => {
|
||||
currentNodeParameters: undefined,
|
||||
executionTimeoutTimestamp: undefined,
|
||||
restartExecutionId: undefined,
|
||||
evaluationRunId: 'test-run-id-123',
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -50,6 +50,7 @@ export class DataRequestResponseBuilder {
|
||||
executionTimeoutTimestamp: additionalData.executionTimeoutTimestamp,
|
||||
restartExecutionId: additionalData.restartExecutionId,
|
||||
userId: additionalData.userId,
|
||||
evaluationRunId: additionalData.evaluationRunId,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -521,6 +521,8 @@ async function startExecution(
|
||||
// mode (e.g. 'manual') even though their own WorkflowExecute runs as 'integrated'
|
||||
additionalDataIntegrated.rootExecutionMode =
|
||||
additionalData.rootExecutionMode ?? options.executionMode;
|
||||
// Propagate the eval run id so sub-workflows of an eval run expose `$evaluation.runId`
|
||||
additionalDataIntegrated.evaluationRunId = additionalData.evaluationRunId;
|
||||
if (additionalData.httpResponse) {
|
||||
additionalDataIntegrated.httpResponse = additionalData.httpResponse;
|
||||
}
|
||||
|
||||
@@ -378,6 +378,7 @@ export class WorkflowRunner {
|
||||
additionalData.encryptedRunnerIdentity = data.encryptedRunnerIdentity;
|
||||
|
||||
additionalData.executionId = executionId;
|
||||
additionalData.evaluationRunId = data.evaluationRunId;
|
||||
|
||||
this.logger.debug(
|
||||
`Execution for workflow ${data.workflowData.name} was assigned id ${executionId}`,
|
||||
|
||||
+14
@@ -156,6 +156,20 @@ describe('getAdditionalKeys', () => {
|
||||
expect(result.$execution?.customData).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should expose $evaluation.runId when evaluationRunId is set', () => {
|
||||
const dataWithRunId = { ...additionalData, evaluationRunId: 'run-123' };
|
||||
const result = getAdditionalKeys(dataWithRunId, 'manual', null);
|
||||
|
||||
expect(result.$evaluation).toEqual({ runId: 'run-123' });
|
||||
});
|
||||
|
||||
it('should leave $evaluation undefined when evaluationRunId is unset', () => {
|
||||
const dataWithoutRunId = { ...additionalData, evaluationRunId: undefined };
|
||||
const result = getAdditionalKeys(dataWithoutRunId, 'manual', null);
|
||||
|
||||
expect(result.$evaluation).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should respect metadata KV limit', () => {
|
||||
const result = getAdditionalKeys(additionalData, 'manual', runExecutionData);
|
||||
const customData = result.$execution?.customData;
|
||||
|
||||
@@ -41,6 +41,11 @@ export function getAdditionalKeys(
|
||||
? createExecutionCustomData({ runExecutionData, mode })
|
||||
: undefined,
|
||||
},
|
||||
// Gated on the value's presence, not on `mode`: sub-workflows of an eval run
|
||||
// execute as 'integrated' but inherit evaluationRunId, so they must expose it too.
|
||||
$evaluation: additionalData.evaluationRunId
|
||||
? { runId: additionalData.evaluationRunId }
|
||||
: undefined,
|
||||
$vars: additionalData.variables,
|
||||
$secrets: options.isCredential ? getSecretsProxy(additionalData) : undefined,
|
||||
|
||||
|
||||
@@ -2820,6 +2820,7 @@ export type IWorkflowDataProxyAdditionalKeys = IDataObject & {
|
||||
resumeFormUrl: string;
|
||||
customData?: IWorkflowExecutionCustomData;
|
||||
};
|
||||
$evaluation?: { runId: string };
|
||||
$vars?: IDataObject;
|
||||
$secrets?: IDataObject;
|
||||
$pageCount?: number;
|
||||
@@ -3266,6 +3267,8 @@ export interface IWorkflowExecutionDataProcess {
|
||||
tracingContext?: { traceparent: string; tracestate?: string };
|
||||
/** Encrypted credential context for a manual editor-triggered execution. */
|
||||
encryptedRunnerIdentity?: string;
|
||||
/** Parent evaluation TestRun.id, exposed to expressions as `$evaluation.runId`. */
|
||||
evaluationRunId?: string;
|
||||
}
|
||||
|
||||
export interface ExecuteWorkflowOptions {
|
||||
@@ -3375,6 +3378,8 @@ export interface IWorkflowExecuteAdditionalData {
|
||||
* data consistently across the entire execution tree.
|
||||
*/
|
||||
rootExecutionMode?: WorkflowExecuteMode;
|
||||
/** Parent evaluation TestRun.id, exposed to expressions as `$evaluation.runId`. */
|
||||
evaluationRunId?: string;
|
||||
startRunnerTask<T, E = unknown>(
|
||||
additionalData: IWorkflowExecuteAdditionalData,
|
||||
jobType: string,
|
||||
|
||||
@@ -51,6 +51,6 @@ export interface IRunExecutionDataV0 {
|
||||
/** Data needed for a worker to run a manual execution. */
|
||||
manualData?: Pick<
|
||||
IWorkflowExecutionDataProcess,
|
||||
'dirtyNodeNames' | 'triggerToStartFrom' | 'userId'
|
||||
'dirtyNodeNames' | 'triggerToStartFrom' | 'userId' | 'evaluationRunId'
|
||||
>;
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ export interface IRunExecutionDataV1 {
|
||||
/** Data needed for a worker to run a manual execution. */
|
||||
manualData?: Pick<
|
||||
IWorkflowExecutionDataProcess,
|
||||
'dirtyNodeNames' | 'triggerToStartFrom' | 'userId'
|
||||
'dirtyNodeNames' | 'triggerToStartFrom' | 'userId' | 'evaluationRunId'
|
||||
>;
|
||||
|
||||
/** Metadata about whether and how this execution's data was redacted. */
|
||||
|
||||
Reference in New Issue
Block a user