feat(core): Add special @tool displayOption (#14318)

Co-authored-by: कारतोफ्फेलस्क्रिप्ट™ <aditya@netroy.in>
This commit is contained in:
Charlie Kolb
2025-04-04 09:22:13 +02:00
committed by GitHub
parent 9104743a5f
commit 73748e300e
24 changed files with 589 additions and 48 deletions

View File

@@ -371,6 +371,7 @@ export class CredentialsHelper extends ICredentialsHelper {
true, true,
false, false,
null, null,
null,
) as ICredentialDataDecryptedObject; ) as ICredentialDataDecryptedObject;
if (decryptedDataOriginal.oauthTokenData !== undefined) { if (decryptedDataOriginal.oauthTokenData !== undefined) {

View File

@@ -219,6 +219,7 @@ export abstract class NodeExecutionContext implements Omit<FunctionsBase, 'getCr
additionalData.currentNodeParameters || node.parameters, additionalData.currentNodeParameters || node.parameters,
nodeCredentialDescription, nodeCredentialDescription,
node, node,
nodeType.description,
node.parameters, node.parameters,
) )
) { ) {

View File

@@ -33,7 +33,13 @@ function findPropertyFromParameterName(
return options.find( return options.find(
(i) => (i) =>
i.name === name && i.name === name &&
NodeHelpers.displayParameterPath(nodeParameters, i, currentParamPath, node), NodeHelpers.displayParameterPath(
nodeParameters,
i,
currentParamPath,
node,
nodeType.description,
),
); );
}; };

View File

@@ -160,7 +160,8 @@ export const validateValueAgainstSchema = (
const propertyDescription = nodeType.description.properties.find( const propertyDescription = nodeType.description.properties.find(
(prop) => (prop) =>
parameterPath[0] === prop.name && NodeHelpers.displayParameter(node.parameters, prop, node), parameterPath[0] === prop.name &&
NodeHelpers.displayParameter(node.parameters, prop, node, nodeType.description),
); );
if (!propertyDescription) { if (!propertyDescription) {

View File

@@ -829,8 +829,16 @@ export class RoutingNode {
}; };
let basePath = path ? `${path}.` : ''; let basePath = path ? `${path}.` : '';
const { node } = this.context; const { node, nodeType } = this.context;
if (!NodeHelpers.displayParameter(node.parameters, nodeProperties, node, node.parameters)) { if (
!NodeHelpers.displayParameter(
node.parameters,
nodeProperties,
node,
nodeType.description,
node.parameters,
)
) {
return undefined; return undefined;
} }
if (nodeProperties.routing) { if (nodeProperties.routing) {

View File

@@ -999,6 +999,7 @@ export class WorkflowExecute {
nodeIssues = NodeHelpers.getNodeParametersIssues( nodeIssues = NodeHelpers.getNodeParametersIssues(
nodeType.description.properties, nodeType.description.properties,
node, node,
nodeType.description,
inputData.pinDataNodeNames, inputData.pinDataNodeNames,
); );
} }

View File

@@ -663,6 +663,7 @@ async function saveCredential(): Promise<ICredentialsResponse | null> {
false, false,
false, false,
null, null,
null,
); );
assert(credentialTypeName.value); assert(credentialTypeName.value);

View File

@@ -337,7 +337,7 @@ const removeMismatchedOptionValues = (
); );
} }
if (!hasValidOptions && displayParameter(nodeParameterValues, prop, node.value)) { if (!hasValidOptions && displayParameter(nodeParameterValues, prop, node.value, nodeType)) {
unset(nodeParameterValues as object, prop.name); unset(nodeParameterValues as object, prop.name);
} }
}); });
@@ -395,6 +395,7 @@ const valueChanged = (parameterData: IUpdateInformation) => {
false, false,
false, false,
_node, _node,
nodeType,
); );
const oldNodeParameters = Object.assign({}, nodeParameters); const oldNodeParameters = Object.assign({}, nodeParameters);
@@ -453,6 +454,7 @@ const valueChanged = (parameterData: IUpdateInformation) => {
true, true,
false, false,
_node, _node,
nodeType,
); );
for (const key of Object.keys(nodeParameters as object)) { for (const key of Object.keys(nodeParameters as object)) {
@@ -487,6 +489,7 @@ const valueChanged = (parameterData: IUpdateInformation) => {
false, false,
false, false,
_node, _node,
nodeType,
); );
const oldNodeParameters = Object.assign({}, nodeParameters); const oldNodeParameters = Object.assign({}, nodeParameters);
@@ -535,6 +538,7 @@ const valueChanged = (parameterData: IUpdateInformation) => {
true, true,
false, false,
_node, _node,
nodeType,
); );
for (const key of Object.keys(nodeParameters as object)) { for (const key of Object.keys(nodeParameters as object)) {

View File

@@ -81,6 +81,7 @@ describe('ParameterInput.vue', () => {
}; };
mockNodeTypesState = { mockNodeTypesState = {
allNodeTypes: [], allNodeTypes: [],
getNodeType: vi.fn().mockReturnValue(null),
}; };
createAppModals(); createAppModals();
}); });

View File

@@ -346,6 +346,7 @@ const getIssues = computed<string[]>(() => {
node.value.parameters, node.value.parameters,
newPath.join('.'), newPath.join('.'),
node.value, node.value,
nodeTypesStore.getNodeType(node.value.type, node.value.typeVersion),
); );
if (props.parameter.type === 'credentialsSelect' && displayValue.value === '') { if (props.parameter.type === 'credentialsSelect' && displayValue.value === '') {

View File

@@ -466,7 +466,13 @@ function getParameterIssues(parameter: INodeProperties): string[] {
if (!node.value || !showIssuesInLabelFor.includes(parameter.type)) { if (!node.value || !showIssuesInLabelFor.includes(parameter.type)) {
return []; return [];
} }
const issues = NodeHelpers.getParameterIssues(parameter, node.value.parameters, '', node.value); const issues = NodeHelpers.getParameterIssues(
parameter,
node.value.parameters,
'',
node.value,
nodeType.value,
);
return issues.parameters?.[parameter.name] ?? []; return issues.parameters?.[parameter.name] ?? [];
} }

View File

@@ -407,6 +407,7 @@ function updateNodeIssues(): void {
const parameterIssues = NodeHelpers.getNodeParametersIssues( const parameterIssues = NodeHelpers.getNodeParametersIssues(
nodeType.value?.properties ?? [], nodeType.value?.properties ?? [],
props.node, props.node,
nodeType.value,
); );
if (parameterIssues) { if (parameterIssues) {
ndvStore.updateNodeParameterIssues(parameterIssues); ndvStore.updateNodeParameterIssues(parameterIssues);

View File

@@ -874,6 +874,7 @@ export function useCanvasOperations({ router }: { router: ReturnType<typeof useR
true, true,
false, false,
node, node,
nodeTypeDescription,
); );
node.parameters = nodeParameters ?? {}; node.parameters = nodeParameters ?? {};

View File

@@ -106,7 +106,17 @@ export function useNodeHelpers() {
node: INodeUi | null, node: INodeUi | null,
displayKey: 'displayOptions' | 'disabledOptions' = 'displayOptions', displayKey: 'displayOptions' | 'disabledOptions' = 'displayOptions',
) { ) {
return NodeHelpers.displayParameterPath(nodeValues, parameter, path, node, displayKey); const nodeTypeDescription = node?.type
? nodeTypesStore.getNodeType(node.type, node.typeVersion)
: null;
return NodeHelpers.displayParameterPath(
nodeValues,
parameter,
path,
node,
nodeTypeDescription,
displayKey,
);
} }
function getNodeIssues( function getNodeIssues(
@@ -137,7 +147,7 @@ export function useNodeHelpers() {
// Add potential parameter issues // Add potential parameter issues
if (!ignoreIssues.includes('parameters')) { if (!ignoreIssues.includes('parameters')) {
nodeIssues = NodeHelpers.getNodeParametersIssues(nodeType.properties, node); nodeIssues = NodeHelpers.getNodeParametersIssues(nodeType.properties, node, nodeType);
} }
if (!ignoreIssues.includes('credentials')) { if (!ignoreIssues.includes('credentials')) {
@@ -287,6 +297,7 @@ export function useNodeHelpers() {
const fullNodeIssues: INodeIssues | null = NodeHelpers.getNodeParametersIssues( const fullNodeIssues: INodeIssues | null = NodeHelpers.getNodeParametersIssues(
localNodeType.properties, localNodeType.properties,
node, node,
nodeType ?? null,
); );
let newIssues: INodeIssueObjectProperty | null = null; let newIssues: INodeIssueObjectProperty | null = null;

View File

@@ -589,6 +589,7 @@ export function useWorkflowHelpers(options: { router: ReturnType<typeof useRoute
isCredentialOnly, isCredentialOnly,
false, false,
node, node,
nodeType,
); );
nodeData.parameters = nodeParameters !== null ? nodeParameters : {}; nodeData.parameters = nodeParameters !== null ? nodeParameters : {};

View File

@@ -1331,6 +1331,7 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, () => {
true, true,
false, false,
latestNode, latestNode,
nodeType,
); );
if (latestNode) { if (latestNode) {

View File

@@ -338,8 +338,7 @@ export function getGenericHints({
true, true,
false, false,
node, node,
undefined, nodeType,
false,
); );
const assignments = const assignments =

View File

@@ -22,11 +22,17 @@ export function getNodeTypeDisplayableCredentials(
// credentials can have conditional requirements that depend on // credentials can have conditional requirements that depend on
// node parameters. // node parameters.
const nodeParameters = const nodeParameters =
NodeHelpers.getNodeParameters(nodeType.properties, node.parameters, true, false, node) ?? NodeHelpers.getNodeParameters(
node.parameters; nodeType.properties,
node.parameters,
true,
false,
node,
nodeType,
) ?? node.parameters;
const displayableCredentials = nodeTypeCreds.filter((credentialTypeDescription) => { const displayableCredentials = nodeTypeCreds.filter((credentialTypeDescription) => {
return NodeHelpers.displayParameter(nodeParameters, credentialTypeDescription, node); return NodeHelpers.displayParameter(nodeParameters, credentialTypeDescription, node, nodeType);
}); });
return displayableCredentials; return displayableCredentials;

View File

@@ -1376,6 +1376,7 @@ export interface IDisplayOptions {
}; };
show?: { show?: {
'@version'?: Array<number | DisplayCondition>; '@version'?: Array<number | DisplayCondition>;
'@tool'?: [boolean];
[key: string]: Array<NodeParameterValue | DisplayCondition> | undefined; [key: string]: Array<NodeParameterValue | DisplayCondition> | undefined;
}; };

View File

@@ -392,6 +392,7 @@ const getPropertyValues = (
nodeValues: INodeParameters, nodeValues: INodeParameters,
propertyName: string, propertyName: string,
node: Pick<INode, 'typeVersion'> | null, node: Pick<INode, 'typeVersion'> | null,
nodeTypeDescription: INodeTypeDescription | null,
nodeValuesRoot: INodeParameters, nodeValuesRoot: INodeParameters,
) => { ) => {
let value; let value;
@@ -400,6 +401,8 @@ const getPropertyValues = (
value = get(nodeValuesRoot, propertyName.slice(1)); value = get(nodeValuesRoot, propertyName.slice(1));
} else if (propertyName === '@version') { } else if (propertyName === '@version') {
value = node?.typeVersion || 0; value = node?.typeVersion || 0;
} else if (propertyName === '@tool') {
value = nodeTypeDescription?.name.endsWith('Tool') ?? false;
} else { } else {
// Get the value from current level // Get the value from current level
value = get(nodeValues, propertyName); value = get(nodeValues, propertyName);
@@ -487,6 +490,7 @@ export function displayParameter(
nodeValues: INodeParameters, nodeValues: INodeParameters,
parameter: INodeProperties | INodeCredentialDescription, parameter: INodeProperties | INodeCredentialDescription,
node: Pick<INode, 'typeVersion'> | null, // Allow null as it does also get used by credentials and they do not have versioning yet node: Pick<INode, 'typeVersion'> | null, // Allow null as it does also get used by credentials and they do not have versioning yet
nodeTypeDescription: INodeTypeDescription | null,
nodeValuesRoot?: INodeParameters, nodeValuesRoot?: INodeParameters,
displayKey: 'displayOptions' | 'disabledOptions' = 'displayOptions', displayKey: 'displayOptions' | 'disabledOptions' = 'displayOptions',
) { ) {
@@ -501,7 +505,13 @@ export function displayParameter(
if (show) { if (show) {
// All the defined rules have to match to display parameter // All the defined rules have to match to display parameter
for (const propertyName of Object.keys(show)) { for (const propertyName of Object.keys(show)) {
const values = getPropertyValues(nodeValues, propertyName, node, nodeValuesRoot); const values = getPropertyValues(
nodeValues,
propertyName,
node,
nodeTypeDescription,
nodeValuesRoot,
);
if (values.some((v) => typeof v === 'string' && v.charAt(0) === '=')) { if (values.some((v) => typeof v === 'string' && v.charAt(0) === '=')) {
return true; return true;
@@ -516,7 +526,13 @@ export function displayParameter(
if (hide) { if (hide) {
// Any of the defined hide rules have to match to hide the parameter // Any of the defined hide rules have to match to hide the parameter
for (const propertyName of Object.keys(hide)) { for (const propertyName of Object.keys(hide)) {
const values = getPropertyValues(nodeValues, propertyName, node, nodeValuesRoot); const values = getPropertyValues(
nodeValues,
propertyName,
node,
nodeTypeDescription,
nodeValuesRoot,
);
if (values.length !== 0 && checkConditions(hide[propertyName]!, values)) { if (values.length !== 0 && checkConditions(hide[propertyName]!, values)) {
return false; return false;
@@ -541,6 +557,7 @@ export function displayParameterPath(
parameter: INodeProperties | INodeCredentialDescription, parameter: INodeProperties | INodeCredentialDescription,
path: string, path: string,
node: Pick<INode, 'typeVersion'> | null, node: Pick<INode, 'typeVersion'> | null,
nodeTypeDescription: INodeTypeDescription | null,
displayKey: 'displayOptions' | 'disabledOptions' = 'displayOptions', displayKey: 'displayOptions' | 'disabledOptions' = 'displayOptions',
) { ) {
let resolvedNodeValues = nodeValues; let resolvedNodeValues = nodeValues;
@@ -554,7 +571,14 @@ export function displayParameterPath(
nodeValuesRoot = get(nodeValues, 'parameters') as INodeParameters; nodeValuesRoot = get(nodeValues, 'parameters') as INodeParameters;
} }
return displayParameter(resolvedNodeValues, parameter, node, nodeValuesRoot, displayKey); return displayParameter(
resolvedNodeValues,
parameter,
node,
nodeTypeDescription,
nodeValuesRoot,
displayKey,
);
} }
/** /**
@@ -698,6 +722,14 @@ export function getParameterResolveOrder(
return executionOrder; return executionOrder;
} }
export type GetNodeParametersOptions = {
onlySimpleTypes?: boolean;
dataIsResolved?: boolean; // If nodeValues are already fully resolved (so that all default values got added already)
nodeValuesRoot?: INodeParameters;
parentType?: string;
parameterDependencies?: IParameterDependencies;
};
/** /**
* Returns the node parameter values. Depending on the settings it either just returns the none * Returns the node parameter values. Depending on the settings it either just returns the none
* default values or it applies all the default values. * default values or it applies all the default values.
@@ -706,9 +738,7 @@ export function getParameterResolveOrder(
* @param {INodeParameters} nodeValues The node parameter data * @param {INodeParameters} nodeValues The node parameter data
* @param {boolean} returnDefaults If default values get added or only none default values returned * @param {boolean} returnDefaults If default values get added or only none default values returned
* @param {boolean} returnNoneDisplayed If also values which should not be displayed should be returned * @param {boolean} returnNoneDisplayed If also values which should not be displayed should be returned
* @param {boolean} [onlySimpleTypes=false] If only simple types should be resolved * @param {GetNodeParametersOptions} options Optional properties
* @param {boolean} [dataIsResolved=false] If nodeValues are already fully resolved (so that all default values got added already)
* @param {INodeParameters} [nodeValuesRoot] The root node-parameter-data
*/ */
// eslint-disable-next-line complexity // eslint-disable-next-line complexity
export function getNodeParameters( export function getNodeParameters(
@@ -717,12 +747,11 @@ export function getNodeParameters(
returnDefaults: boolean, returnDefaults: boolean,
returnNoneDisplayed: boolean, returnNoneDisplayed: boolean,
node: Pick<INode, 'typeVersion'> | null, node: Pick<INode, 'typeVersion'> | null,
onlySimpleTypes = false, nodeTypeDescription: INodeTypeDescription | null,
dataIsResolved = false, options?: GetNodeParametersOptions,
nodeValuesRoot?: INodeParameters,
parentType?: string,
parameterDependencies?: IParameterDependencies,
): INodeParameters | null { ): INodeParameters | null {
let { nodeValuesRoot, parameterDependencies } = options ?? {};
const { onlySimpleTypes = false, dataIsResolved = false, parentType } = options ?? {};
if (parameterDependencies === undefined) { if (parameterDependencies === undefined) {
parameterDependencies = getParameterDependencies(nodePropertiesArray); parameterDependencies = getParameterDependencies(nodePropertiesArray);
} }
@@ -752,11 +781,14 @@ export function getNodeParameters(
true, true,
true, true,
node, node,
true, nodeTypeDescription,
true, {
onlySimpleTypes: true,
dataIsResolved: true,
nodeValuesRoot, nodeValuesRoot,
parentType, parentType,
parameterDependencies, parameterDependencies,
},
) as INodeParameters; ) as INodeParameters;
} }
@@ -781,7 +813,13 @@ export function getNodeParameters(
if ( if (
!returnNoneDisplayed && !returnNoneDisplayed &&
!displayParameter(nodeValuesDisplayCheck, nodeProperties, node, nodeValuesRoot) !displayParameter(
nodeValuesDisplayCheck,
nodeProperties,
node,
nodeTypeDescription,
nodeValuesRoot,
)
) { ) {
if (!returnNoneDisplayed || !returnDefaults) { if (!returnNoneDisplayed || !returnDefaults) {
continue; continue;
@@ -792,7 +830,15 @@ export function getNodeParameters(
// Is a simple property so can be set as it is // Is a simple property so can be set as it is
if (duplicateParameterNames.includes(nodeProperties.name)) { if (duplicateParameterNames.includes(nodeProperties.name)) {
if (!displayParameter(nodeValuesDisplayCheck, nodeProperties, node, nodeValuesRoot)) { if (
!displayParameter(
nodeValuesDisplayCheck,
nodeProperties,
node,
nodeTypeDescription,
nodeValuesRoot,
)
) {
continue; continue;
} }
} }
@@ -871,10 +917,13 @@ export function getNodeParameters(
returnDefaults, returnDefaults,
returnNoneDisplayed, returnNoneDisplayed,
node, node,
false, nodeTypeDescription,
false, {
onlySimpleTypes: false,
dataIsResolved: false,
nodeValuesRoot, nodeValuesRoot,
nodeProperties.type, parentType: nodeProperties.type,
},
); );
if (tempNodeParameters !== null) { if (tempNodeParameters !== null) {
@@ -944,10 +993,13 @@ export function getNodeParameters(
returnDefaults, returnDefaults,
returnNoneDisplayed, returnNoneDisplayed,
node, node,
false, nodeTypeDescription,
false, {
onlySimpleTypes: false,
dataIsResolved: false,
nodeValuesRoot, nodeValuesRoot,
nodeProperties.type, parentType: nodeProperties.type,
},
); );
if (tempValue !== null) { if (tempValue !== null) {
tempArrayValue.push(tempValue); tempArrayValue.push(tempValue);
@@ -972,10 +1024,13 @@ export function getNodeParameters(
returnDefaults, returnDefaults,
returnNoneDisplayed, returnNoneDisplayed,
node, node,
false, nodeTypeDescription,
false, {
onlySimpleTypes: false,
dataIsResolved: false,
nodeValuesRoot, nodeValuesRoot,
nodeProperties.type, parentType: nodeProperties.type,
},
); );
if (tempValue !== null) { if (tempValue !== null) {
Object.assign(tempNodeParameters, tempValue); Object.assign(tempNodeParameters, tempValue);
@@ -1236,6 +1291,7 @@ export function getNodeOutputs(
export function getNodeParametersIssues( export function getNodeParametersIssues(
nodePropertiesArray: INodeProperties[], nodePropertiesArray: INodeProperties[],
node: INode, node: INode,
nodeTypeDescription: INodeTypeDescription | null,
pinDataNodeNames?: string[], pinDataNodeNames?: string[],
): INodeIssues | null { ): INodeIssues | null {
const foundIssues: INodeIssues = {}; const foundIssues: INodeIssues = {};
@@ -1247,7 +1303,13 @@ export function getNodeParametersIssues(
} }
for (const nodeProperty of nodePropertiesArray) { for (const nodeProperty of nodePropertiesArray) {
propertyIssues = getParameterIssues(nodeProperty, node.parameters, '', node); propertyIssues = getParameterIssues(
nodeProperty,
node.parameters,
'',
node,
nodeTypeDescription,
);
mergeIssues(foundIssues, propertyIssues); mergeIssues(foundIssues, propertyIssues);
} }
@@ -1456,9 +1518,16 @@ export function getParameterIssues(
nodeValues: INodeParameters, nodeValues: INodeParameters,
path: string, path: string,
node: INode, node: INode,
nodeTypeDescription: INodeTypeDescription | null,
): INodeIssues { ): INodeIssues {
const foundIssues: INodeIssues = {}; const foundIssues: INodeIssues = {};
const isDisplayed = displayParameterPath(nodeValues, nodeProperties, path, node); const isDisplayed = displayParameterPath(
nodeValues,
nodeProperties,
path,
node,
nodeTypeDescription,
);
if (nodeProperties.required === true) { if (nodeProperties.required === true) {
if (isDisplayed) { if (isDisplayed) {
const value = getParameterValueByPath(nodeValues, nodeProperties.name, path); const value = getParameterValueByPath(nodeValues, nodeProperties.name, path);
@@ -1636,7 +1705,13 @@ export function getParameterIssues(
let propertyIssues; let propertyIssues;
for (const optionData of checkChildNodeProperties) { for (const optionData of checkChildNodeProperties) {
propertyIssues = getParameterIssues(optionData.data, nodeValues, optionData.basePath, node); propertyIssues = getParameterIssues(
optionData.data,
nodeValues,
optionData.basePath,
node,
nodeTypeDescription,
);
mergeIssues(foundIssues, propertyIssues); mergeIssues(foundIssues, propertyIssues);
} }

View File

@@ -198,6 +198,7 @@ export function generateNodesGraph(
true, true,
false, false,
stickyNote, stickyNote,
stickyType.description,
) ?? {}; ) ?? {};
} catch { } catch {
// prevent node param resolution from failing graph generation // prevent node param resolution from failing graph generation
@@ -376,6 +377,7 @@ export function generateNodesGraph(
true, true,
false, false,
node, node,
nodeType.description,
); );
if (nodeParameters) { if (nodeParameters) {

View File

@@ -112,6 +112,7 @@ export class Workflow {
true, true,
false, false,
node, node,
nodeType.description,
); );
node.parameters = nodeParameters !== null ? nodeParameters : {}; node.parameters = nodeParameters !== null ? nodeParameters : {};
} }

View File

@@ -2166,6 +2166,7 @@ describe('NodeHelpers', () => {
false, false,
false, false,
null, null,
null,
); );
expect(result).toEqual(testData.output.noneDisplayedFalse.defaultsFalse); expect(result).toEqual(testData.output.noneDisplayedFalse.defaultsFalse);
@@ -2176,6 +2177,7 @@ describe('NodeHelpers', () => {
true, true,
false, false,
null, null,
null,
); );
expect(result).toEqual(testData.output.noneDisplayedFalse.defaultsTrue); expect(result).toEqual(testData.output.noneDisplayedFalse.defaultsTrue);
@@ -2186,6 +2188,7 @@ describe('NodeHelpers', () => {
false, false,
true, true,
null, null,
null,
); );
expect(result).toEqual(testData.output.noneDisplayedTrue.defaultsFalse); expect(result).toEqual(testData.output.noneDisplayedTrue.defaultsFalse);
@@ -2196,6 +2199,7 @@ describe('NodeHelpers', () => {
true, true,
true, true,
null, null,
null,
); );
expect(result).toEqual(testData.output.noneDisplayedTrue.defaultsTrue); expect(result).toEqual(testData.output.noneDisplayedTrue.defaultsTrue);
}); });

View File

@@ -16,6 +16,7 @@ import {
getParameterIssues, getParameterIssues,
isTriggerNode, isTriggerNode,
isExecutable, isExecutable,
displayParameter,
} from '@/NodeHelpers'; } from '@/NodeHelpers';
import type { Workflow } from '@/Workflow'; import type { Workflow } from '@/Workflow';
@@ -3420,6 +3421,7 @@ describe('NodeHelpers', () => {
false, false,
false, false,
null, null,
null,
); );
expect(result).toEqual(testData.output.noneDisplayedFalse.defaultsFalse); expect(result).toEqual(testData.output.noneDisplayedFalse.defaultsFalse);
@@ -3430,6 +3432,7 @@ describe('NodeHelpers', () => {
true, true,
false, false,
null, null,
null,
); );
expect(result).toEqual(testData.output.noneDisplayedFalse.defaultsTrue); expect(result).toEqual(testData.output.noneDisplayedFalse.defaultsTrue);
@@ -3440,6 +3443,7 @@ describe('NodeHelpers', () => {
false, false,
true, true,
null, null,
null,
); );
expect(result).toEqual(testData.output.noneDisplayedTrue.defaultsFalse); expect(result).toEqual(testData.output.noneDisplayedTrue.defaultsFalse);
@@ -3450,6 +3454,7 @@ describe('NodeHelpers', () => {
true, true,
true, true,
null, null,
null,
); );
expect(result).toEqual(testData.output.noneDisplayedTrue.defaultsTrue); expect(result).toEqual(testData.output.noneDisplayedTrue.defaultsTrue);
}); });
@@ -4193,6 +4198,7 @@ describe('NodeHelpers', () => {
testData.input.nodeValues, testData.input.nodeValues,
testData.input.path, testData.input.path,
testData.input.node, testData.input.node,
null,
); );
expect(result).toEqual(testData.output); expect(result).toEqual(testData.output);
}); });
@@ -4209,6 +4215,18 @@ describe('NodeHelpers', () => {
parameters: {}, parameters: {},
}; };
const testNodeType: INodeTypeDescription = {
name: 'Test Node',
version: 0,
defaults: {},
inputs: [],
outputs: [],
properties: [],
displayName: '',
group: [],
description: '',
};
it('Should validate required dateTime parameters if empty string', () => { it('Should validate required dateTime parameters if empty string', () => {
const nodeProperties: INodeProperties = { const nodeProperties: INodeProperties = {
displayName: 'Date Time', displayName: 'Date Time',
@@ -4221,7 +4239,7 @@ describe('NodeHelpers', () => {
testDateTime: '', testDateTime: '',
}; };
const result = getParameterIssues(nodeProperties, nodeValues, '', testNode); const result = getParameterIssues(nodeProperties, nodeValues, '', testNode, null);
expect(result).toEqual({ expect(result).toEqual({
parameters: { parameters: {
@@ -4242,7 +4260,7 @@ describe('NodeHelpers', () => {
testDateTime: undefined, testDateTime: undefined,
}; };
const result = getParameterIssues(nodeProperties, nodeValues, '', testNode); const result = getParameterIssues(nodeProperties, nodeValues, '', testNode, testNodeType);
expect(result).toEqual({ expect(result).toEqual({
parameters: { parameters: {
@@ -4513,4 +4531,393 @@ describe('NodeHelpers', () => {
}); });
} }
}); });
describe('displayParameter', () => {
const testNode: INode = {
id: '12345',
name: 'Test Node',
typeVersion: 1,
type: 'n8n-nodes-base.testNode',
position: [1, 1],
parameters: {},
};
const testNodeType: INodeTypeDescription = {
name: 'Test Node',
version: 0,
defaults: {},
inputs: [],
outputs: [],
properties: [],
displayName: '',
group: [],
description: '',
};
const defaultTestInput = {
nodeValues: {},
parameter: {
displayName: 'Test Parameter',
name: 'testParameter',
type: 'string',
default: '',
} as INodeProperties,
node: testNode,
nodeTypeDescription: testNodeType,
nodeValuesRoot: undefined as undefined | INodeParameters,
displayKey: 'displayOptions' as 'displayOptions' | 'disabledOptions',
};
const tests: Array<[string, typeof defaultTestInput, boolean]> = [
['Should return true if no displayOptions are defined', { ...defaultTestInput }, true],
[
'Should return true if displayOptions.show conditions are met',
{
...defaultTestInput,
nodeValues: { condition: 'value1' },
parameter: {
...defaultTestInput.parameter,
displayOptions: {
show: {
condition: ['value1'],
},
},
},
},
true,
],
[
'Should return false if displayOptions.show conditions are not met',
{
...defaultTestInput,
nodeValues: { condition: 'value2' },
parameter: {
...defaultTestInput.parameter,
displayOptions: {
show: {
condition: ['value1'],
},
},
},
},
false,
],
[
'Should return false if displayOptions.hide conditions are met',
{
...defaultTestInput,
nodeValues: { condition: 'value1' },
parameter: {
...defaultTestInput.parameter,
displayOptions: {
hide: {
condition: ['value1'],
},
},
},
},
false,
],
[
'Should return true if displayOptions.hide conditions are not met',
{
...defaultTestInput,
nodeValues: { condition: 'value2' },
parameter: {
...defaultTestInput.parameter,
displayOptions: {
hide: {
condition: ['value1'],
},
},
},
},
true,
],
[
'Should return true if displayOptions.show and hide conditions are both met',
{
...defaultTestInput,
nodeValues: { condition: 'value1' },
parameter: {
...defaultTestInput.parameter,
displayOptions: {
show: {
condition: ['value1'],
},
hide: {
condition: ['value1'],
},
},
},
},
false, // Hide takes precedence over show
],
[
'Should return true if displayOptions.show conditions are met with multiple values',
{
...defaultTestInput,
nodeValues: { condition: 'value2' },
parameter: {
...defaultTestInput.parameter,
displayOptions: {
show: {
condition: ['value1', 'value2'],
},
},
},
},
true,
],
[
'Should return false if displayOptions.hide conditions are met with multiple values',
{
...defaultTestInput,
nodeValues: { condition: 'value2' },
parameter: {
...defaultTestInput.parameter,
displayOptions: {
hide: {
condition: ['value1', 'value2'],
},
},
},
},
false,
],
[
'Should return true if @tool is true in nodeTypeDescription of tool',
{
...defaultTestInput,
nodeTypeDescription: {
...testNodeType,
name: testNodeType.name + 'Tool',
},
parameter: {
...defaultTestInput.parameter,
displayOptions: {
show: {
'@tool': [true],
},
},
},
},
true,
],
[
'Should return false if @tool is true in nodeTypeDescription of non-tool',
{
...defaultTestInput,
parameter: {
...defaultTestInput.parameter,
displayOptions: {
show: {
'@tool': [true],
},
},
},
},
false,
],
[
'Should return true if @version condition is met',
{
...defaultTestInput,
node: {
...testNode,
typeVersion: 2,
},
parameter: {
...defaultTestInput.parameter,
displayOptions: {
show: {
'@version': [
{
_cnd: {
gte: 2,
},
},
],
},
},
},
},
true,
],
[
'Should return false if @version condition is not met',
{
...defaultTestInput,
node: {
...testNode,
typeVersion: 1,
},
parameter: {
...defaultTestInput.parameter,
displayOptions: {
show: {
'@version': [
{
_cnd: {
gte: 2,
},
},
],
},
},
},
},
false,
],
[
'Should return true if @tool and @version conditions are both met',
{
...defaultTestInput,
node: {
...testNode,
typeVersion: 2,
},
nodeTypeDescription: {
...testNodeType,
name: testNodeType.name + 'Tool',
},
parameter: {
...defaultTestInput.parameter,
displayOptions: {
show: {
'@tool': [true],
'@version': [
{
_cnd: {
gte: 2,
},
},
],
},
},
},
},
true,
],
[
'Should return false if @tool is true but @version condition is not met',
{
...defaultTestInput,
node: {
...testNode,
typeVersion: 1,
},
nodeTypeDescription: {
...testNodeType,
name: testNodeType.name + 'Tool',
},
parameter: {
...defaultTestInput.parameter,
displayOptions: {
show: {
'@tool': [true],
'@version': [
{
_cnd: {
gte: 2,
},
},
],
},
},
},
},
false,
],
[
'Should return true if no disabledOptions are defined',
{
...defaultTestInput,
displayKey: 'disabledOptions',
},
true,
],
[
'Should return false if disabledOptions.hide conditions are met',
{
...defaultTestInput,
nodeValues: { condition: 'value1' },
parameter: {
...defaultTestInput.parameter,
disabledOptions: {
hide: {
condition: ['value1'],
},
},
},
displayKey: 'disabledOptions',
},
false,
],
[
'Should return true if disabledOptions.hide conditions are not met',
{
...defaultTestInput,
nodeValues: { condition: 'value2' },
parameter: {
...defaultTestInput.parameter,
disabledOptions: {
hide: {
condition: ['value1'],
},
},
},
displayKey: 'disabledOptions',
},
true,
],
[
'Should return true if nodeValuesRoot contains a matching value for displayOptions.show',
{
...defaultTestInput,
nodeValues: {},
nodeValuesRoot: { condition: 'value1' },
parameter: {
...defaultTestInput.parameter,
displayOptions: {
show: {
'/condition': ['value1'],
},
},
},
},
true,
],
[
'Should return false if nodeValuesRoot does not contain a matching value for displayOptions.show',
{
...defaultTestInput,
nodeValues: { condition: 'value1' },
nodeValuesRoot: { anotherKey: 'value1' },
parameter: {
...defaultTestInput.parameter,
displayOptions: {
show: {
'/condition': ['value1'],
},
},
},
},
false,
],
];
for (const [description, input, expected] of tests) {
test(description, () => {
const result = displayParameter(
input.nodeValues,
input.parameter,
input.node,
input.nodeTypeDescription,
input.nodeValuesRoot,
input.displayKey,
);
expect(result).toEqual(expected);
});
}
});
}); });