mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
Merge remote-tracking branch 'origin/feature-sub-workflow-inputs' into ADO-2904
This commit is contained in:
commit
f716a607a5
|
@ -5,11 +5,14 @@ import {
|
||||||
type IExecuteFunctions,
|
type IExecuteFunctions,
|
||||||
type INodeType,
|
type INodeType,
|
||||||
type INodeTypeDescription,
|
type INodeTypeDescription,
|
||||||
|
validateFieldType,
|
||||||
|
type FieldType,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
const INPUT_SOURCE = 'inputSource';
|
const INPUT_SOURCE = 'inputSource';
|
||||||
const FIELDS = 'fields';
|
const FIELDS = 'fields';
|
||||||
const WORKFLOW_INPUTS = 'workflowInputs';
|
const WORKFLOW_INPUTS = 'workflowInputs';
|
||||||
|
const INPUT_OPTIONS = 'inputOptions';
|
||||||
const VALUES = 'values';
|
const VALUES = 'values';
|
||||||
|
|
||||||
function hasFields(context: IExecuteFunctions, index: number): boolean {
|
function hasFields(context: IExecuteFunctions, index: number): boolean {
|
||||||
|
@ -29,8 +32,9 @@ function parseJson(
|
||||||
index: number,
|
index: number,
|
||||||
): Array<{
|
): Array<{
|
||||||
name: string;
|
name: string;
|
||||||
|
type: FieldType;
|
||||||
}> {
|
}> {
|
||||||
return [{ name: 'dummy' }];
|
return [{ name: 'dummy', type: 'number' }];
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSchema(
|
function getSchema(
|
||||||
|
@ -38,17 +42,22 @@ function getSchema(
|
||||||
index: number,
|
index: number,
|
||||||
): Array<{
|
): Array<{
|
||||||
name: string;
|
name: string;
|
||||||
|
type: FieldType;
|
||||||
}> {
|
}> {
|
||||||
const inputSource = context.getNodeParameter(INPUT_SOURCE, index) as string;
|
const inputSource = context.getNodeParameter(INPUT_SOURCE, index) as string;
|
||||||
if (inputSource === FIELDS) {
|
if (inputSource === FIELDS) {
|
||||||
const fields = context.getNodeParameter(`${WORKFLOW_INPUTS}.${VALUES}`, index, []) as Array<{
|
const fields = context.getNodeParameter(`${WORKFLOW_INPUTS}.${VALUES}`, index, []) as Array<{
|
||||||
name: string;
|
name: string;
|
||||||
|
type: FieldType;
|
||||||
}>;
|
}>;
|
||||||
return fields;
|
return fields;
|
||||||
} else {
|
} else {
|
||||||
return parseJson(context, index);
|
return parseJson(context, index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
type ValueOptions = { name: string; value: FieldType };
|
||||||
|
|
||||||
|
const DEFAULT_PLACEHOLDER = null;
|
||||||
|
|
||||||
export class ExecuteWorkflowTrigger implements INodeType {
|
export class ExecuteWorkflowTrigger implements INodeType {
|
||||||
description: INodeTypeDescription = {
|
description: INodeTypeDescription = {
|
||||||
|
@ -69,8 +78,9 @@ export class ExecuteWorkflowTrigger implements INodeType {
|
||||||
outputs: [NodeConnectionType.Main],
|
outputs: [NodeConnectionType.Main],
|
||||||
properties: [
|
properties: [
|
||||||
{
|
{
|
||||||
displayName:
|
displayName: `When an ‘Execute Workflow’ node calls this workflow, the execution starts here.<br><br>
|
||||||
"When an ‘execute workflow’ node calls this workflow, the execution starts here. Any data passed into the 'execute workflow' node will be output by this node.",
|
Specified fields below will be output by this node with values provided by the calling workflow.<br><br>
|
||||||
|
If you don't provide fields, all data passed into the 'Execute Workflow' node will be passed through instead.`,
|
||||||
name: 'notice',
|
name: 'notice',
|
||||||
type: 'notice',
|
type: 'notice',
|
||||||
default: '',
|
default: '',
|
||||||
|
@ -139,40 +149,93 @@ export class ExecuteWorkflowTrigger implements INodeType {
|
||||||
description: 'Name of the field',
|
description: 'Name of the field',
|
||||||
noDataExpression: true,
|
noDataExpression: true,
|
||||||
},
|
},
|
||||||
// {
|
{
|
||||||
// displayName: 'Type',
|
displayName: 'Type',
|
||||||
// name: 'type',
|
name: 'type',
|
||||||
// type: 'options',
|
type: 'options',
|
||||||
// description: 'The field value type',
|
description: 'The field value type',
|
||||||
// // eslint-disable-next-line n8n-nodes-base/node-param-options-type-unsorted-items
|
// eslint-disable-next-line n8n-nodes-base/node-param-options-type-unsorted-items
|
||||||
// options: [
|
options: [
|
||||||
// {
|
// This is not a FieldType type, but will
|
||||||
// name: 'String',
|
// hit the default case in the type check function
|
||||||
// value: 'stringValue',
|
{
|
||||||
// },
|
name: 'Allow Any Type',
|
||||||
// {
|
value: 'any',
|
||||||
// name: 'Number',
|
},
|
||||||
// value: 'numberValue',
|
{
|
||||||
// },
|
name: 'String',
|
||||||
// {
|
value: 'string',
|
||||||
// name: 'Boolean',
|
},
|
||||||
// value: 'booleanValue',
|
{
|
||||||
// },
|
name: 'Number',
|
||||||
// {
|
value: 'number',
|
||||||
// name: 'Array',
|
},
|
||||||
// value: 'arrayValue',
|
{
|
||||||
// },
|
name: 'Boolean',
|
||||||
// {
|
value: 'boolean',
|
||||||
// name: 'Object',
|
},
|
||||||
// value: 'objectValue',
|
{
|
||||||
// },
|
name: 'Array',
|
||||||
// ],
|
value: 'array',
|
||||||
// default: 'stringValue',
|
},
|
||||||
// },
|
{
|
||||||
|
name: 'Object',
|
||||||
|
value: 'object',
|
||||||
|
},
|
||||||
|
// Intentional omission of `dateTime`, `time`, `string-alphanumeric`, `form-fields`, `jwt` and `url`
|
||||||
|
] as ValueOptions[],
|
||||||
|
default: 'string',
|
||||||
|
noDataExpression: true,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Input Options',
|
||||||
|
name: INPUT_OPTIONS,
|
||||||
|
placeholder: 'Options',
|
||||||
|
type: 'collection',
|
||||||
|
description: 'Options controlling how input data is handled, converted and rejected',
|
||||||
|
displayOptions: {
|
||||||
|
show: { '@version': [{ _cnd: { gte: 1.1 } }] },
|
||||||
|
},
|
||||||
|
default: {},
|
||||||
|
// Note that, while the defaults are true, the user has to add these in the first place
|
||||||
|
// We default to false if absent in the execute function below
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Attempt to Convert Types',
|
||||||
|
name: 'attemptToConvertTypes',
|
||||||
|
type: 'boolean',
|
||||||
|
default: true,
|
||||||
|
description:
|
||||||
|
'Whether to attempt conversion on type mismatch, rather than directly returning an Error',
|
||||||
|
noDataExpression: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Ignore Type Mismatch Errors',
|
||||||
|
name: 'ignoreTypeErrors',
|
||||||
|
type: 'boolean',
|
||||||
|
default: true,
|
||||||
|
description: 'Whether type mismatches should be ignored rather than returning an Error',
|
||||||
|
noDataExpression: true,
|
||||||
|
},
|
||||||
|
// REVIEW: Note that by having this here we commit to passing the binary data
|
||||||
|
// to the sub-workflow in the first place, otherwise we'd need this on the parent
|
||||||
|
// or at least for the parent to read this from this node.
|
||||||
|
// Is there significant cost to switching to the sub-workflow or is it all one big workflow under the hood?
|
||||||
|
{
|
||||||
|
displayName: 'Include Binary Data',
|
||||||
|
name: 'includeBinaryData',
|
||||||
|
type: 'boolean',
|
||||||
|
default: true,
|
||||||
|
description:
|
||||||
|
'Whether binary data should be included from the parent. If set to false, binary data will be removed.',
|
||||||
|
noDataExpression: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -189,9 +252,26 @@ export class ExecuteWorkflowTrigger implements INodeType {
|
||||||
const items: INodeExecutionData[] = [];
|
const items: INodeExecutionData[] = [];
|
||||||
|
|
||||||
for (const [itemIndex, item] of inputData.entries()) {
|
for (const [itemIndex, item] of inputData.entries()) {
|
||||||
|
const attemptToConvertTypes = this.getNodeParameter(
|
||||||
|
`${INPUT_OPTIONS}.attemptToConvertTypes`,
|
||||||
|
itemIndex,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
const ignoreTypeErrors = this.getNodeParameter(
|
||||||
|
`${INPUT_OPTIONS}.ignoreTypeErrors`,
|
||||||
|
itemIndex,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
const includeBinaryData = this.getNodeParameter(
|
||||||
|
`${INPUT_OPTIONS}.includeBinaryData`,
|
||||||
|
itemIndex,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
// Fields listed here will explicitly overwrite original fields
|
// Fields listed here will explicitly overwrite original fields
|
||||||
const newItem: INodeExecutionData = {
|
const newItem: INodeExecutionData = {
|
||||||
json: {},
|
json: {},
|
||||||
|
index: itemIndex,
|
||||||
// TODO: Ensure we handle sub-execution jumps correctly.
|
// TODO: Ensure we handle sub-execution jumps correctly.
|
||||||
// metadata: {
|
// metadata: {
|
||||||
// subExecution: {
|
// subExecution: {
|
||||||
|
@ -204,13 +284,47 @@ export class ExecuteWorkflowTrigger implements INodeType {
|
||||||
try {
|
try {
|
||||||
const newParams = getSchema(this, itemIndex);
|
const newParams = getSchema(this, itemIndex);
|
||||||
|
|
||||||
for (const { name } of newParams) {
|
for (const { name, type } of newParams) {
|
||||||
/** TODO type check goes here */
|
if (!item.json.hasOwnProperty(name)) {
|
||||||
newItem.json[name] = name in item.json ? item.json[name] : /* TODO default */ null;
|
newItem.json[name] = DEFAULT_PLACEHOLDER;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We always parse strings rather than blindly accepting anything as a string
|
||||||
|
// Which is the behavior of this function
|
||||||
|
// Also note we intentionally pass `any` in here for `type`, which hits a
|
||||||
|
// permissive default case in the function
|
||||||
|
const result = validateFieldType(name, item.json[name], type, {
|
||||||
|
strict: !attemptToConvertTypes,
|
||||||
|
parseStrings: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!result.valid) {
|
||||||
|
if (ignoreTypeErrors) {
|
||||||
|
newItem.json[name] = item.json[name];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new NodeOperationError(this.getNode(), result.errorMessage, {
|
||||||
|
itemIndex,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// If the value is `null` or `undefined`, then `newValue` is not in the returned object
|
||||||
|
if (result.hasOwnProperty('newValue')) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||||
|
newItem.json[name] = result.newValue;
|
||||||
|
} else {
|
||||||
|
newItem.json[name] = item.json[name];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO Do we want to copy non-json data (e.g. binary) as well?
|
if (includeBinaryData) {
|
||||||
items.push(Object.assign({}, item, newItem));
|
// Important not to assign directly to avoid modifying upstream data
|
||||||
|
items.push(Object.assign({}, item, newItem));
|
||||||
|
} else {
|
||||||
|
items.push(newItem);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (this.continueOnFail()) {
|
if (this.continueOnFail()) {
|
||||||
/** todo error case? */
|
/** todo error case? */
|
||||||
|
|
Loading…
Reference in a new issue