feat(editor): Relocate workflow ID expression notice (no-changelog) (#12942)

This commit is contained in:
Milorad FIlipović
2025-01-31 12:32:18 +01:00
committed by GitHub
parent 1ca6a9799a
commit 066908060f
8 changed files with 488 additions and 5 deletions

View File

@@ -164,8 +164,9 @@ export class WorkflowDataProxy {
*
* @private
* @param {string} nodeName The name of the node to query data from
* @param {boolean} [resolveValue=true] If the expression value should get resolved
*/
private nodeParameterGetter(nodeName: string) {
private nodeParameterGetter(nodeName: string, resolveValue = true) {
const that = this;
const node = this.workflow.nodes[nodeName];
@@ -223,7 +224,7 @@ export class WorkflowDataProxy {
}
}
if (typeof returnValue === 'string' && returnValue.charAt(0) === '=') {
if (resolveValue && typeof returnValue === 'string' && returnValue.charAt(0) === '=') {
// The found value is an expression so resolve it
return that.workflow.expression.getParameterValue(
returnValue,
@@ -1359,6 +1360,7 @@ export class WorkflowDataProxy {
$node: this.nodeGetter(),
$self: this.selfGetter(),
$parameter: this.nodeParameterGetter(this.activeNodeName),
$rawParameter: this.nodeParameterGetter(this.activeNodeName, false),
$prevNode: this.prevNodeGetter(),
$runIndex: this.runIndex,
$mode: this.mode,

View File

@@ -604,12 +604,237 @@ const manualTriggerNode: LoadedClass<INodeType> = {
},
};
const executeWorkflowNode: LoadedClass<INodeType> = {
type: {
description: {
name: 'n8n-nodes-base.executeWorkflow',
displayName: 'Execute Sub-workflow',
icon: 'fa:sign-in-alt',
iconColor: 'orange-red',
group: ['transform'],
version: [1, 1.1, 1.2],
subtitle: '={{"Workflow: " + $parameter["workflowId"]}}',
description: 'Execute another workflow',
defaults: { name: 'Execute Workflow', color: '#ff6d5a' },
inputs: [],
outputs: [],
properties: [
{
displayName: 'Operation',
name: 'operation',
type: 'hidden',
noDataExpression: true,
default: 'call_workflow',
options: [{ name: 'Execute a Sub-Workflow', value: 'call_workflow' }],
},
{
displayName:
'This node is out of date. Please upgrade by removing it and adding a new one',
name: 'outdatedVersionWarning',
type: 'notice',
displayOptions: { show: { '@version': [{ _cnd: { lte: 1.1 } }] } },
default: '',
},
{
displayName: 'Source',
name: 'source',
type: 'options',
options: [
{
name: 'Database',
value: 'database',
description: 'Load the workflow from the database by ID',
},
{
name: 'Local File',
value: 'localFile',
description: 'Load the workflow from a locally saved file',
},
{
name: 'Parameter',
value: 'parameter',
description: 'Load the workflow from a parameter',
},
{ name: 'URL', value: 'url', description: 'Load the workflow from an URL' },
],
default: 'database',
description: 'Where to get the workflow to execute from',
displayOptions: { show: { '@version': [{ _cnd: { lte: 1.1 } }] } },
},
{
displayName: 'Source',
name: 'source',
type: 'options',
options: [
{
name: 'Database',
value: 'database',
description: 'Load the workflow from the database by ID',
},
{
name: 'Define Below',
value: 'parameter',
description: 'Pass the JSON code of a workflow',
},
],
default: 'database',
description: 'Where to get the workflow to execute from',
displayOptions: { show: { '@version': [{ _cnd: { gte: 1.2 } }] } },
},
{
displayName: 'Workflow ID',
name: 'workflowId',
type: 'string',
displayOptions: { show: { source: ['database'], '@version': [1] } },
default: '',
required: true,
hint: 'Can be found in the URL of the workflow',
description:
"Note on using an expression here: if this node is set to run once with all items, they will all be sent to the <em>same</em> workflow. That workflow's ID will be calculated by evaluating the expression for the <strong>first input item</strong>.",
},
{
displayName: 'Workflow',
name: 'workflowId',
type: 'workflowSelector',
displayOptions: { show: { source: ['database'], '@version': [{ _cnd: { gte: 1.1 } }] } },
default: '',
required: true,
},
{
displayName: 'Workflow Path',
name: 'workflowPath',
type: 'string',
displayOptions: { show: { source: ['localFile'] } },
default: '',
placeholder: '/data/workflow.json',
required: true,
description: 'The path to local JSON workflow file to execute',
},
{
displayName: 'Workflow JSON',
name: 'workflowJson',
type: 'json',
typeOptions: { rows: 10 },
displayOptions: { show: { source: ['parameter'] } },
default: '\n\n\n',
required: true,
description: 'The workflow JSON code to execute',
},
{
displayName: 'Workflow URL',
name: 'workflowUrl',
type: 'string',
displayOptions: { show: { source: ['url'] } },
default: '',
placeholder: 'https://example.com/workflow.json',
required: true,
description: 'The URL from which to load the workflow from',
},
{
displayName:
'Any data you pass into this node will be output by the Execute Workflow Trigger. <a href="https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.executeworkflow/" target="_blank">More info</a>',
name: 'executeWorkflowNotice',
type: 'notice',
default: '',
displayOptions: { show: { '@version': [{ _cnd: { lte: 1.1 } }] } },
},
{
displayName: 'Workflow Inputs',
name: 'workflowInputs',
type: 'resourceMapper',
noDataExpression: true,
default: { mappingMode: 'defineBelow', value: null },
required: true,
typeOptions: {
loadOptionsDependsOn: ['workflowId.value'],
resourceMapper: {
localResourceMapperMethod: 'loadSubWorkflowInputs',
valuesLabel: 'Workflow Inputs',
mode: 'map',
fieldWords: { singular: 'input', plural: 'inputs' },
addAllFields: true,
multiKeyMatch: false,
supportAutoMap: false,
showTypeConversionOptions: true,
},
},
displayOptions: {
show: { source: ['database'], '@version': [{ _cnd: { gte: 1.2 } }] },
hide: { workflowId: [''] },
},
},
{
displayName: 'Mode',
name: 'mode',
type: 'options',
noDataExpression: true,
options: [
{
name: 'Run once with all items',
value: 'once',
description: 'Pass all items into a single execution of the sub-workflow',
},
{
name: 'Run once for each item',
value: 'each',
description: 'Call the sub-workflow individually for each item',
},
],
default: 'once',
},
{
displayName: 'Options',
name: 'options',
type: 'collection',
default: {},
placeholder: 'Add option',
options: [
{
displayName: 'Wait For Sub-Workflow Completion',
name: 'waitForSubWorkflow',
type: 'boolean',
default: true,
description:
'Whether the main workflow should wait for the sub-workflow to complete its execution before proceeding',
},
],
},
],
hints: [
{
type: 'info',
message:
"Note on using an expression for workflow ID: if this node is set to run once with all items, they will all be sent to the <em>same</em> workflow. That workflow's ID will be calculated by evaluating the expression for the <strong>first input item</strong>.",
displayCondition:
'={{ $rawParameter.workflowId.startsWith("=") && $nodeVersion >= 1.2 }}',
whenToDisplay: 'always',
location: 'outputPane',
},
],
codex: {
categories: ['Core Nodes'],
subcategories: { 'Core Nodes': ['Helpers', 'Flow'] },
alias: ['n8n', 'call', 'sub', 'workflow', 'sub-workflow', 'subworkflow'],
resources: {
primaryDocumentation: [
{
url: 'https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.executeworkflow/',
},
],
},
},
},
},
sourcePath: '',
};
export class NodeTypes implements INodeTypes {
nodeTypes: INodeTypeData = {
'n8n-nodes-base.stickyNote': stickyNode,
'n8n-nodes-base.set': setNode,
'test.googleSheets': googleSheetsNode,
'test.set': setNode,
'n8n-nodes-base.executeWorkflow': executeWorkflowNode,
'test.setMulti': {
sourcePath: '',
type: {

View File

@@ -549,4 +549,45 @@ describe('WorkflowDataProxy', () => {
expect(() => getFromAIProxy().$fromAI('invalid!')).toThrow(ExpressionError);
});
});
describe('$rawParameter', () => {
const fixture = loadFixture('rawParameter');
const proxy = getProxyFromFixture(fixture.workflow, fixture.run, 'Execute Workflow', 'manual', {
connectionType: NodeConnectionType.Main,
throwOnMissingExecutionData: false,
runIndex: 0,
});
test('returns simple raw parameter value', () => {
expect(proxy.$rawParameter.options).toEqual({
waitForSubWorkflow: '={{ true }}',
});
});
test('returns raw parameter value for resource locator values', () => {
expect(proxy.$rawParameter.workflowId).toEqual('={{ $json.foo }}');
});
test('returns raw parameter value when there is no run data', () => {
const noRunDataProxy = getProxyFromFixture(
fixture.workflow,
{
data: { resultData: { runData: {} } },
mode: 'manual',
startedAt: new Date(),
status: 'success',
},
'Execute Workflow',
'manual',
{
connectionType: NodeConnectionType.Main,
throwOnMissingExecutionData: false,
runIndex: 0,
},
);
expect(noRunDataProxy.$rawParameter.options).toEqual({
waitForSubWorkflow: '={{ true }}',
});
});
});
});

View File

@@ -0,0 +1,117 @@
{
"data": {
"startData": {},
"resultData": {
"runData": {
"_custom": {
"type": "reactive",
"stateTypeName": "Reactive",
"value": {
"Manual trigger": [
{
"_custom": {
"type": "reactive",
"stateTypeName": "Reactive",
"value": {
"hints": [],
"startTime": 1738314562475,
"executionTime": 1,
"source": [],
"executionStatus": "success",
"data": { "main": [[{ "json": {}, "pairedItem": { "item": 0 } }]] }
}
}
}
],
"Edit Fields": [
{
"_custom": {
"type": "reactive",
"stateTypeName": "Reactive",
"value": {
"hints": [],
"startTime": 1738314562477,
"executionTime": 0,
"source": [{ "previousNode": "Manual trigger" }],
"executionStatus": "success",
"data": {
"main": [[{ "json": { "foo": "test" }, "pairedItem": { "item": 0 } }]]
}
}
}
}
],
"Execute Workflow": [
{
"hints": [],
"startTime": 1738314562478,
"executionTime": 2,
"source": [{ "previousNode": "Edit Fields" }],
"executionStatus": "error",
"error": {
"level": "error",
"tags": { "packageName": "cli" },
"extra": { "workflowId": "1.2" },
"message": "Workflow does not exist.",
"stack": "Error: Workflow does not exist.\n at getWorkflowData (/Users/miloradfilipovic/workspace/n8n/packages/cli/src/workflow-execute-additional-data.ts:124:10)\n at Object.executeWorkflow (/Users/miloradfilipovic/workspace/n8n/packages/cli/src/workflow-execute-additional-data.ts:155:4)\n at ExecuteContext.executeWorkflow (/Users/miloradfilipovic/workspace/n8n/packages/core/src/execution-engine/node-execution-context/base-execute-context.ts:120:18)\n at ExecuteContext.execute (/Users/miloradfilipovic/workspace/n8n/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflow/ExecuteWorkflow.node.ts:397:50)\n at WorkflowExecute.runNode (/Users/miloradfilipovic/workspace/n8n/packages/core/src/execution-engine/workflow-execute.ts:1097:8)\n at /Users/miloradfilipovic/workspace/n8n/packages/core/src/execution-engine/workflow-execute.ts:1503:27\n at /Users/miloradfilipovic/workspace/n8n/packages/core/src/execution-engine/workflow-execute.ts:2064:11"
}
}
]
}
}
},
"pinData": {},
"lastNodeExecuted": "Execute Workflow",
"error": {
"level": "error",
"tags": { "packageName": "cli" },
"extra": { "workflowId": "1.2" },
"message": "Workflow does not exist.",
"stack": "Error: Workflow does not exist.\n at getWorkflowData (/Users/miloradfilipovic/workspace/n8n/packages/cli/src/workflow-execute-additional-data.ts:124:10)\n at Object.executeWorkflow (/Users/miloradfilipovic/workspace/n8n/packages/cli/src/workflow-execute-additional-data.ts:155:4)\n at ExecuteContext.executeWorkflow (/Users/miloradfilipovic/workspace/n8n/packages/core/src/execution-engine/node-execution-context/base-execute-context.ts:120:18)\n at ExecuteContext.execute (/Users/miloradfilipovic/workspace/n8n/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflow/ExecuteWorkflow.node.ts:397:50)\n at WorkflowExecute.runNode (/Users/miloradfilipovic/workspace/n8n/packages/core/src/execution-engine/workflow-execute.ts:1097:8)\n at /Users/miloradfilipovic/workspace/n8n/packages/core/src/execution-engine/workflow-execute.ts:1503:27\n at /Users/miloradfilipovic/workspace/n8n/packages/core/src/execution-engine/workflow-execute.ts:2064:11"
}
},
"executionData": {
"contextData": {},
"nodeExecutionStack": [
{
"node": {
"parameters": {
"operation": "call_workflow",
"source": "database",
"workflowId": {
"__rl": true,
"mode": "id",
"value": "=1.2",
"cachedResultName": "=1.2"
},
"workflowInputs": {
"mappingMode": "defineBelow",
"value": {},
"matchingColumns": [],
"schema": [],
"attemptToConvertTypes": false,
"convertFieldsToString": true
},
"mode": "once",
"options": {}
},
"type": "n8n-nodes-base.executeWorkflow",
"typeVersion": 1.2,
"position": [120, -100],
"id": "62717ac7-614d-4e3f-b2ec-1e28688068c4",
"name": "Execute Workflow"
},
"data": { "main": [[{ "json": { "foo": "test" }, "pairedItem": { "item": 0 } }]] },
"source": { "main": [{ "previousNode": "Edit Fields" }] }
}
],
"metadata": {},
"waitingExecution": {},
"waitingExecutionSource": {}
},
"mode": "manual",
"startedAt": "2024-02-08T15:45:18.848Z",
"stoppedAt": "2024-02-08T15:45:18.862Z",
"status": "success"
}
}

View File

@@ -0,0 +1,80 @@
{
"nodes": [
{
"id": "804e5ba7-4b1d-48c2-abfa-a36717a9fa66",
"name": "Manual trigger",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [-320, -100],
"parameters": {}
},
{
"id": "f995b1a2-8a49-4f0c-ae0d-8fb4c600cdef",
"name": "Edit Fields",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [-100, -100],
"parameters": {
"assignments": {
"assignments": [
{
"id": "f4d80089-a3d7-470f-8c07-dec07e37f339",
"name": "foo",
"value": "={{ test }}",
"type": "string"
}
]
},
"options": {}
}
},
{
"id": "62717ac7-614d-4e3f-b2ec-1e28688068c4",
"name": "Execute Workflow",
"type": "n8n-nodes-base.executeWorkflow",
"typeVersion": 1.2,
"position": [120, -100],
"parameters": {
"workflowId": {
"__rl": true,
"value": "={{ $json.foo }}",
"mode": "id"
},
"workflowInputs": {
"mappingMode": "defineBelow",
"value": {},
"matchingColumns": [],
"schema": [],
"attemptToConvertTypes": false,
"convertFieldsToString": true
},
"options": { "waitForSubWorkflow": "={{ true }}" }
}
}
],
"connections": {
"Manual trigger": {
"main": [
[
{
"node": "Edit Fields",
"type": "main",
"index": 0
}
]
]
},
"Edit Fields": {
"main": [
[
{
"node": "Execute Workflow",
"type": "main",
"index": 0
}
]
]
}
},
"pinData": {}
}