diff --git a/packages/cli/src/PublicApi/v1/handlers/credentials/credentials.service.ts b/packages/cli/src/PublicApi/v1/handlers/credentials/credentials.service.ts index ce6800506d..81a82616a9 100644 --- a/packages/cli/src/PublicApi/v1/handlers/credentials/credentials.service.ts +++ b/packages/cli/src/PublicApi/v1/handlers/credentials/credentials.service.ts @@ -1,5 +1,10 @@ import { Credentials } from 'n8n-core'; -import type { IDataObject, INodeProperties, INodePropertyOptions } from 'n8n-workflow'; +import type { + DisplayCondition, + IDataObject, + INodeProperties, + INodePropertyOptions, +} from 'n8n-workflow'; import * as Db from '@/Db'; import type { ICredentialsDb } from '@/Interfaces'; import { CredentialsEntity } from '@db/entities/CredentialsEntity'; @@ -182,7 +187,7 @@ export function toJsonSchema(properties: INodeProperties[]): IDataObject { if (property.displayOptions?.show) { const dependantName = Object.keys(property.displayOptions?.show)[0] || ''; const displayOptionsValues = property.displayOptions.show[dependantName]; - let dependantValue: string | number | boolean = ''; + let dependantValue: DisplayCondition | string | number | boolean = ''; if (displayOptionsValues && Array.isArray(displayOptionsValues) && displayOptionsValues[0]) { dependantValue = displayOptionsValues[0]; @@ -193,12 +198,75 @@ export function toJsonSchema(properties: INodeProperties[]): IDataObject { } if (!resolveProperties.includes(dependantName)) { + let conditionalValue; + if (typeof dependantValue === 'object' && dependantValue._cnd) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const [key, targetValue] = Object.entries(dependantValue._cnd)[0]; + + if (key === 'eq') { + conditionalValue = { + const: [targetValue], + }; + } else if (key === 'not') { + conditionalValue = { + not: { + const: [targetValue], + }, + }; + } else if (key === 'gt') { + conditionalValue = { + type: 'number', + exclusiveMinimum: [targetValue], + }; + } else if (key === 'gte') { + conditionalValue = { + type: 'number', + minimum: [targetValue], + }; + } else if (key === 'lt') { + conditionalValue = { + type: 'number', + exclusiveMaximum: [targetValue], + }; + } else if (key === 'lte') { + conditionalValue = { + type: 'number', + maximum: [targetValue], + }; + } else if (key === 'startsWith') { + conditionalValue = { + type: 'string', + pattern: `^${targetValue}`, + }; + } else if (key === 'endsWith') { + conditionalValue = { + type: 'string', + pattern: `${targetValue}$`, + }; + } else if (key === 'includes') { + conditionalValue = { + type: 'string', + pattern: `${targetValue}`, + }; + } else if (key === 'regex') { + conditionalValue = { + type: 'string', + pattern: `${targetValue}`, + }; + } else { + conditionalValue = { + enum: [dependantValue], + }; + } + } else { + conditionalValue = { + enum: [dependantValue], + }; + } propertyRequiredDependencies[dependantName] = { if: { properties: { - [dependantName]: { - enum: [dependantValue], - }, + [dependantName]: conditionalValue, }, }, then: { diff --git a/packages/nodes-base/nodes/BambooHr/v1/actions/employee/create/description.ts b/packages/nodes-base/nodes/BambooHr/v1/actions/employee/create/description.ts index 04279ea031..9c4dd63f7e 100644 --- a/packages/nodes-base/nodes/BambooHr/v1/actions/employee/create/description.ts +++ b/packages/nodes-base/nodes/BambooHr/v1/actions/employee/create/description.ts @@ -44,7 +44,7 @@ export const employeeCreateDescription: EmployeeProperties = [ }, default: '', }, - ...createEmployeeSharedDescription(true), + ...(createEmployeeSharedDescription(true) as EmployeeProperties), { displayName: 'Additional Fields', name: 'additionalFields', diff --git a/packages/nodes-base/nodes/BambooHr/v1/actions/employee/update/description.ts b/packages/nodes-base/nodes/BambooHr/v1/actions/employee/update/description.ts index 4ad427d5c2..83af844d74 100644 --- a/packages/nodes-base/nodes/BambooHr/v1/actions/employee/update/description.ts +++ b/packages/nodes-base/nodes/BambooHr/v1/actions/employee/update/description.ts @@ -30,7 +30,7 @@ export const employeeUpdateDescription: EmployeeProperties = [ description: 'Whether the employee to create was added to a pay schedule synced with Trax Payroll', }, - ...updateEmployeeSharedDescription(true), + ...(updateEmployeeSharedDescription(true) as EmployeeProperties), { displayName: 'Update Fields', name: 'updateFields', diff --git a/packages/workflow/src/Interfaces.ts b/packages/workflow/src/Interfaces.ts index 03f8bcd6cb..1d49a68cce 100644 --- a/packages/workflow/src/Interfaces.ts +++ b/packages/workflow/src/Interfaces.ts @@ -1160,7 +1160,31 @@ export type FilterTypeOptions = Partial<{ typeValidation: 'strict' | 'loose' | {}; // default = strict, `| {}` is a TypeScript trick to allow custom strings, but still give autocomplete }>; +export type DisplayCondition = + | { _cnd: { eq: NodeParameterValue } } + | { _cnd: { not: NodeParameterValue } } + | { _cnd: { gte: number | string } } + | { _cnd: { lte: number | string } } + | { _cnd: { gt: number | string } } + | { _cnd: { lt: number | string } } + | { _cnd: { between: { from: number | string; to: number | string } } } + | { _cnd: { startsWith: string } } + | { _cnd: { endsWith: string } } + | { _cnd: { includes: string } } + | { _cnd: { regex: string } }; + export interface IDisplayOptions { + hide?: { + [key: string]: Array | undefined; + }; + show?: { + '@version'?: Array; + [key: string]: Array | undefined; + }; + + hideOnCloud?: boolean; +} +export interface ICredentialsDisplayOptions { hide?: { [key: string]: NodeParameterValue[] | undefined; }; @@ -1402,7 +1426,7 @@ export interface INodeCredentialTestRequest { export interface INodeCredentialDescription { name: string; required?: boolean; - displayOptions?: IDisplayOptions; + displayOptions?: ICredentialsDisplayOptions; testedBy?: ICredentialTestRequest | string; // Name of a function inside `loadOptions.credentialTest` } diff --git a/packages/workflow/src/NodeHelpers.ts b/packages/workflow/src/NodeHelpers.ts index 7a840b05fd..900db3ad7c 100644 --- a/packages/workflow/src/NodeHelpers.ts +++ b/packages/workflow/src/NodeHelpers.ts @@ -41,6 +41,7 @@ import type { INodeOutputConfiguration, INodeInputConfiguration, GenericValue, + DisplayCondition, } from './Interfaces'; import { isFilterValue, @@ -282,6 +283,90 @@ export function applySpecialNodeParameters(nodeType: INodeType): void { } } +const getPropertyValues = ( + nodeValues: INodeParameters, + propertyName: string, + node: Pick | null, + nodeValuesRoot: INodeParameters, +) => { + let value; + if (propertyName.charAt(0) === '/') { + // Get the value from the root of the node + value = get(nodeValuesRoot, propertyName.slice(1)); + } else if (propertyName === '@version') { + value = node?.typeVersion || 0; + } else { + // Get the value from current level + value = get(nodeValues, propertyName); + } + + if (value && typeof value === 'object' && '__rl' in value && value.__rl) { + value = value.value; + } + + if (!Array.isArray(value)) { + return [value as NodeParameterValue]; + } else { + return value as NodeParameterValue[]; + } +}; + +const checkConditions = ( + conditions: Array, + actualValues: NodeParameterValue[], +) => { + return conditions.some((condition) => { + if ( + condition && + typeof condition === 'object' && + condition._cnd && + Object.keys(condition).length === 1 + ) { + const [key, targetValue] = Object.entries(condition._cnd)[0]; + + return actualValues.every((propertyValue) => { + if (key === 'eq') { + return isEqual(propertyValue, targetValue); + } + if (key === 'not') { + return !isEqual(propertyValue, targetValue); + } + if (key === 'gte') { + return (propertyValue as number) >= targetValue; + } + if (key === 'lte') { + return (propertyValue as number) <= targetValue; + } + if (key === 'gt') { + return (propertyValue as number) > targetValue; + } + if (key === 'lt') { + return (propertyValue as number) < targetValue; + } + if (key === 'between') { + const { from, to } = targetValue as { from: number; to: number }; + return (propertyValue as number) >= from && (propertyValue as number) <= to; + } + if (key === 'includes') { + return (propertyValue as string).includes(targetValue); + } + if (key === 'startsWith') { + return (propertyValue as string).startsWith(targetValue); + } + if (key === 'endsWith') { + return (propertyValue as string).endsWith(targetValue); + } + if (key === 'regex') { + return new RegExp(targetValue as string).test(propertyValue as string); + } + return false; + }); + } + + return actualValues.includes(condition as NodeParameterValue); + }); +}; + /** * Returns if the parameter should be displayed or not * @@ -300,76 +385,31 @@ export function displayParameter( return true; } + const { show, hide } = parameter.displayOptions; + nodeValuesRoot = nodeValuesRoot || nodeValues; - let value; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const values: any[] = []; - if (parameter.displayOptions.show) { + if (show) { // All the defined rules have to match to display parameter - for (const propertyName of Object.keys(parameter.displayOptions.show)) { - if (propertyName.charAt(0) === '/') { - // Get the value from the root of the node - value = get(nodeValuesRoot, propertyName.slice(1)); - } else if (propertyName === '@version') { - value = node?.typeVersion || 0; - } else { - // Get the value from current level - value = get(nodeValues, propertyName); - } - - if (value && typeof value === 'object' && '__rl' in value && value.__rl) { - value = value.value; - } - - values.length = 0; - if (!Array.isArray(value)) { - values.push(value); - } else { - values.push.apply(values, value); - } + for (const propertyName of Object.keys(show)) { + const values = getPropertyValues(nodeValues, propertyName, node, nodeValuesRoot); if (values.some((v) => typeof v === 'string' && v.charAt(0) === '=')) { return true; } - if ( - values.length === 0 || - !parameter.displayOptions.show[propertyName]!.some((v) => values.includes(v)) - ) { + if (values.length === 0 || !checkConditions(show[propertyName]!, values)) { return false; } } } - if (parameter.displayOptions.hide) { + if (hide) { // Any of the defined hide rules have to match to hide the parameter - for (const propertyName of Object.keys(parameter.displayOptions.hide)) { - if (propertyName.charAt(0) === '/') { - // Get the value from the root of the node - value = get(nodeValuesRoot, propertyName.slice(1)); - } else if (propertyName === '@version') { - value = node?.typeVersion || 0; - } else { - // Get the value from current level - value = get(nodeValues, propertyName); - } + for (const propertyName of Object.keys(hide)) { + const values = getPropertyValues(nodeValues, propertyName, node, nodeValuesRoot); - if (value && typeof value === 'object' && '__rl' in value && value.__rl) { - value = value.value; - } - - values.length = 0; - if (!Array.isArray(value)) { - values.push(value); - } else { - values.push.apply(values, value); - } - - if ( - values.length !== 0 && - parameter.displayOptions.hide[propertyName]!.some((v) => values.includes(v)) - ) { + if (values.length !== 0 && checkConditions(hide[propertyName]!, values)) { return false; } } diff --git a/packages/workflow/test/NodeHelpers.conditions.test.ts b/packages/workflow/test/NodeHelpers.conditions.test.ts new file mode 100644 index 0000000000..9e1bcec5f0 --- /dev/null +++ b/packages/workflow/test/NodeHelpers.conditions.test.ts @@ -0,0 +1,1936 @@ +import type { INodeParameters, INodeProperties } from '@/Interfaces'; +import { getNodeParameters } from '@/NodeHelpers'; + +describe('NodeHelpers', () => { + describe('getNodeParameters, displayOptions set using DisplayCondition', () => { + const tests: Array<{ + description: string; + input: { + nodePropertiesArray: INodeProperties[]; + nodeValues: INodeParameters | null; + }; + output: { + noneDisplayedFalse: { + defaultsFalse: INodeParameters; + defaultsTrue: INodeParameters; + }; + noneDisplayedTrue: { + defaultsFalse: INodeParameters; + defaultsTrue: INodeParameters; + }; + }; + }> = [ + { + description: 'simple values with displayOptions "show" number (gt). No values set.', + input: { + nodePropertiesArray: [ + { + name: 'number1', + displayName: 'number1', + type: 'number', + default: 2, + }, + { + name: 'string1', + displayName: 'string1', + displayOptions: { + show: { + number1: [{ _cnd: { gt: 1 } }], + }, + }, + type: 'string', + default: 'default string1', + }, + ], + nodeValues: {}, + }, + output: { + noneDisplayedFalse: { + defaultsFalse: {}, + defaultsTrue: { + number1: 2, + string1: 'default string1', + }, + }, + noneDisplayedTrue: { + defaultsFalse: {}, + defaultsTrue: { + number1: 2, + string1: 'default string1', + }, + }, + }, + }, + { + description: 'simple values with displayOptions "show" number (lt). No values set.', + input: { + nodePropertiesArray: [ + { + name: 'number1', + displayName: 'number1', + type: 'number', + default: 2, + }, + { + name: 'string1', + displayName: 'string1', + displayOptions: { + show: { + number1: [{ _cnd: { lt: 3 } }], + }, + }, + type: 'string', + default: 'default string1', + }, + ], + nodeValues: {}, + }, + output: { + noneDisplayedFalse: { + defaultsFalse: {}, + defaultsTrue: { + number1: 2, + string1: 'default string1', + }, + }, + noneDisplayedTrue: { + defaultsFalse: {}, + defaultsTrue: { + number1: 2, + string1: 'default string1', + }, + }, + }, + }, + { + description: 'simple values with displayOptions "show" number (between). No values set.', + input: { + nodePropertiesArray: [ + { + name: 'number1', + displayName: 'number1', + type: 'number', + default: 2, + }, + { + name: 'string1', + displayName: 'string1', + displayOptions: { + show: { + number1: [{ _cnd: { between: { from: 1, to: 3 } } }], + }, + }, + type: 'string', + default: 'default string1', + }, + ], + nodeValues: {}, + }, + output: { + noneDisplayedFalse: { + defaultsFalse: {}, + defaultsTrue: { + number1: 2, + string1: 'default string1', + }, + }, + noneDisplayedTrue: { + defaultsFalse: {}, + defaultsTrue: { + number1: 2, + string1: 'default string1', + }, + }, + }, + }, + { + description: 'simple values with displayOptions "show" number (lte). No values set.', + input: { + nodePropertiesArray: [ + { + name: 'number1', + displayName: 'number1', + type: 'number', + default: 2, + }, + { + name: 'string1', + displayName: 'string1', + displayOptions: { + show: { + number1: [{ _cnd: { lte: 3 } }], + }, + }, + type: 'string', + default: 'default string1', + }, + ], + nodeValues: {}, + }, + output: { + noneDisplayedFalse: { + defaultsFalse: {}, + defaultsTrue: { + number1: 2, + string1: 'default string1', + }, + }, + noneDisplayedTrue: { + defaultsFalse: {}, + defaultsTrue: { + number1: 2, + string1: 'default string1', + }, + }, + }, + }, + { + description: 'simple values with displayOptions "show" number (gte). No values set.', + input: { + nodePropertiesArray: [ + { + name: 'number1', + displayName: 'number1', + type: 'number', + default: 2, + }, + { + name: 'string1', + displayName: 'string1', + displayOptions: { + show: { + number1: [{ _cnd: { gte: 1 } }], + }, + }, + type: 'string', + default: 'default string1', + }, + ], + nodeValues: {}, + }, + output: { + noneDisplayedFalse: { + defaultsFalse: {}, + defaultsTrue: { + number1: 2, + string1: 'default string1', + }, + }, + noneDisplayedTrue: { + defaultsFalse: {}, + defaultsTrue: { + number1: 2, + string1: 'default string1', + }, + }, + }, + }, + { + description: + 'simple values with displayOptions "show" (match) which is boolean. All values set.', + input: { + nodePropertiesArray: [ + { + name: 'boolean1', + displayName: 'boolean1', + type: 'boolean', + default: false, + }, + { + name: 'string1', + displayName: 'string1', + displayOptions: { + show: { + boolean1: [{ _cnd: { eq: true } }], + }, + }, + type: 'string', + default: 'default string1', + }, + ], + nodeValues: { + boolean1: true, + string1: 'own string1', + }, + }, + output: { + noneDisplayedFalse: { + defaultsFalse: { + boolean1: true, + string1: 'own string1', + }, + defaultsTrue: { + boolean1: true, + string1: 'own string1', + }, + }, + noneDisplayedTrue: { + defaultsFalse: { + boolean1: true, + string1: 'own string1', + }, + defaultsTrue: { + boolean1: true, + string1: 'own string1', + }, + }, + }, + }, + { + description: + 'simple values with displayOptions "hide" (match) which is boolean. All values set.', + input: { + nodePropertiesArray: [ + { + name: 'boolean1', + displayName: 'boolean1', + type: 'boolean', + default: false, + }, + { + name: 'string1', + displayName: 'string1', + displayOptions: { + hide: { + boolean1: [{ _cnd: { eq: true } }], + }, + }, + type: 'string', + default: 'default string1', + }, + ], + nodeValues: { + boolean1: true, + }, + }, + output: { + noneDisplayedFalse: { + defaultsFalse: { + boolean1: true, + }, + defaultsTrue: { + boolean1: true, + }, + }, + noneDisplayedTrue: { + defaultsFalse: { + boolean1: true, + }, + defaultsTrue: { + boolean1: true, + string1: 'default string1', + }, + }, + }, + }, + { + description: + 'simple values with displayOptions "show" (match) which is boolean. One values set.', + input: { + nodePropertiesArray: [ + { + name: 'boolean1', + displayName: 'boolean1', + type: 'boolean', + default: false, + }, + { + name: 'string1', + displayName: 'string1', + displayOptions: { + show: { + boolean1: [{ _cnd: { eq: true } }], + }, + }, + type: 'string', + default: 'default string1', + }, + ], + nodeValues: { + boolean1: true, + }, + }, + output: { + noneDisplayedFalse: { + defaultsFalse: { + boolean1: true, + }, + defaultsTrue: { + boolean1: true, + string1: 'default string1', + }, + }, + noneDisplayedTrue: { + defaultsFalse: { + boolean1: true, + }, + defaultsTrue: { + boolean1: true, + string1: 'default string1', + }, + }, + }, + }, + { + description: 'simple values with displayOptions "show" (match). No values set.', + input: { + nodePropertiesArray: [ + { + name: 'mode', + displayName: 'mode', + type: 'string', + default: 'mode1', + }, + { + name: 'string1', + displayName: 'string1', + displayOptions: { + show: { + mode: [{ _cnd: { includes: 'mode1' } }], + }, + }, + type: 'string', + default: 'default string1', + }, + ], + nodeValues: {}, + }, + output: { + noneDisplayedFalse: { + defaultsFalse: {}, + defaultsTrue: { + mode: 'mode1', + string1: 'default string1', + }, + }, + noneDisplayedTrue: { + defaultsFalse: {}, + defaultsTrue: { + mode: 'mode1', + string1: 'default string1', + }, + }, + }, + }, + { + description: + 'simple values with displayOptions "show" (match) on two which depend on each other of which is boolean. One value should be displayed. One values set (none-default).', + input: { + nodePropertiesArray: [ + { + name: 'string1', + displayName: 'string1', + type: 'string', + default: 'default string1', + }, + { + name: 'boolean1', + displayName: 'boolean1', + displayOptions: { + show: { + string1: [{ _cnd: { eq: 'default string1' } }], + }, + }, + type: 'boolean', + default: false, + }, + { + name: 'string2', + displayName: 'string2', + displayOptions: { + show: { + boolean1: [{ _cnd: { not: false } }], + }, + }, + type: 'string', + default: 'default string2', + }, + ], + nodeValues: { + boolean1: true, + }, + }, + output: { + noneDisplayedFalse: { + defaultsFalse: { + boolean1: true, + }, + defaultsTrue: { + string1: 'default string1', + boolean1: true, + string2: 'default string2', + }, + }, + noneDisplayedTrue: { + defaultsFalse: { + boolean1: true, + }, + defaultsTrue: { + string1: 'default string1', + boolean1: true, + string2: 'default string2', + }, + }, + }, + }, + { + description: + 'simple values with displayOptions "show" (match) on two which depend on each other of which is boolean. One value should be displayed. One values set. (default)', + input: { + nodePropertiesArray: [ + { + name: 'string1', + displayName: 'string1', + type: 'string', + default: 'default string1', + }, + { + name: 'boolean1', + displayName: 'boolean1', + displayOptions: { + show: { + string1: [{ _cnd: { endsWith: 'string1' } }], + }, + }, + type: 'boolean', + default: false, + }, + { + name: 'string2', + displayName: 'string2', + displayOptions: { + show: { + boolean1: [{ _cnd: { eq: true } }], + }, + }, + type: 'string', + default: 'default string2', + }, + ], + nodeValues: { + boolean1: false, + }, + }, + output: { + noneDisplayedFalse: { + defaultsFalse: {}, + defaultsTrue: { + string1: 'default string1', + boolean1: false, + }, + }, + noneDisplayedTrue: { + defaultsFalse: {}, + defaultsTrue: { + string1: 'default string1', + boolean1: false, + string2: 'default string2', + }, + }, + }, + }, + { + description: 'simple values with displayOptions "show" (match). All values set.', + input: { + nodePropertiesArray: [ + { + name: 'mode', + displayName: 'mode', + type: 'string', + default: 'mode1', + }, + { + name: 'string1', + displayName: 'string1', + displayOptions: { + show: { + mode: [{ _cnd: { startsWith: 'mo' } }], + }, + }, + type: 'string', + default: 'default string1', + }, + ], + nodeValues: { + mode: 'mode1', + string1: 'default string1', + }, + }, + output: { + noneDisplayedFalse: { + defaultsFalse: {}, + defaultsTrue: { + mode: 'mode1', + string1: 'default string1', + }, + }, + noneDisplayedTrue: { + defaultsFalse: {}, + defaultsTrue: { + mode: 'mode1', + string1: 'default string1', + }, + }, + }, + }, + { + description: 'simple values with displayOptions "show" (no-match). No values set.', + input: { + nodePropertiesArray: [ + { + name: 'mode', + displayName: 'mode', + type: 'string', + default: 'mode1', + }, + { + name: 'string1', + displayName: 'string1', + displayOptions: { + show: { + mode: [{ _cnd: { endsWith: '2' } }], + }, + }, + type: 'string', + default: 'default string1', + }, + ], + nodeValues: {}, + }, + output: { + noneDisplayedFalse: { + defaultsFalse: {}, + defaultsTrue: { + mode: 'mode1', + }, + }, + noneDisplayedTrue: { + defaultsFalse: {}, + defaultsTrue: { + mode: 'mode1', + string1: 'default string1', + }, + }, + }, + }, + { + description: 'simple values with displayOptions "show" (no-match). All values set.', + input: { + nodePropertiesArray: [ + { + name: 'mode', + displayName: 'mode', + type: 'string', + default: 'mode1', + }, + { + name: 'string1', + displayName: 'string1', + displayOptions: { + show: { + mode: [{ _cnd: { startsWith: 'foo' } }], + }, + }, + type: 'string', + default: 'default string1', + }, + ], + nodeValues: { + mode: 'mode1', + string1: 'default string1', + }, + }, + output: { + noneDisplayedFalse: { + defaultsFalse: {}, + defaultsTrue: { + mode: 'mode1', + }, + }, + noneDisplayedTrue: { + defaultsFalse: {}, + defaultsTrue: { + mode: 'mode1', + string1: 'default string1', + }, + }, + }, + }, + + { + description: + 'complex type "fixedCollection" with "multipleValues: false" and with displayOptions "show" (match) on option. One value set.', + input: { + nodePropertiesArray: [ + { + name: 'values', + displayName: 'Values', + type: 'fixedCollection', + default: {}, + options: [ + { + name: 'number', + displayName: 'Number', + values: [ + { + name: 'mode', + displayName: 'mode', + type: 'string', + default: 'mode1', + }, + { + name: 'string1', + displayName: 'string1', + type: 'string', + displayOptions: { + show: { + mode: [{ _cnd: { endsWith: 'mode1' } }], + }, + }, + default: 'default string1', + }, + { + name: 'number1', + displayName: 'number1', + type: 'number', + default: 0, + }, + ], + }, + ], + }, + ], + nodeValues: { + values: { + number: { + number1: 1, + }, + }, + }, + }, + output: { + noneDisplayedFalse: { + defaultsFalse: { + values: { + number: { + number1: 1, + }, + }, + }, + defaultsTrue: { + values: { + number: { + mode: 'mode1', + string1: 'default string1', + number1: 1, + }, + }, + }, + }, + noneDisplayedTrue: { + defaultsFalse: { + values: { + number: { + number1: 1, + }, + }, + }, + defaultsTrue: { + values: { + number: { + mode: 'mode1', + string1: 'default string1', + number1: 1, + }, + }, + }, + }, + }, + }, + { + description: + 'complex type "fixedCollection" with "multipleValues: false" and with displayOptions "show" (match) on option which references root-value. One value set.', + input: { + nodePropertiesArray: [ + { + name: 'mode', + displayName: 'mode', + type: 'string', + default: 'mode1', + }, + { + name: 'values', + displayName: 'Values', + type: 'fixedCollection', + default: {}, + options: [ + { + name: 'number', + displayName: 'Number', + values: [ + { + name: 'string1', + displayName: 'string1', + type: 'string', + displayOptions: { + show: { + '/mode': [{ _cnd: { eq: 'mode1' } }], + }, + }, + default: 'default string1', + }, + { + name: 'number1', + displayName: 'number1', + type: 'number', + default: 0, + }, + ], + }, + ], + }, + ], + nodeValues: { + values: { + number: { + string1: 'own string1', + }, + }, + }, + }, + output: { + noneDisplayedFalse: { + defaultsFalse: { + values: { + number: { + string1: 'own string1', + }, + }, + }, + defaultsTrue: { + mode: 'mode1', + values: { + number: { + string1: 'own string1', + number1: 0, + }, + }, + }, + }, + noneDisplayedTrue: { + defaultsFalse: { + values: { + number: { + string1: 'own string1', + }, + }, + }, + defaultsTrue: { + mode: 'mode1', + values: { + number: { + string1: 'own string1', + number1: 0, + }, + }, + }, + }, + }, + }, + { + description: + 'complex type "fixedCollection" with "multipleValues: false" and with displayOptions "show" (no-match) on option which references root-value. One value set.', + input: { + nodePropertiesArray: [ + { + name: 'mode', + displayName: 'mode', + type: 'string', + default: 'mode1', + }, + { + name: 'values', + displayName: 'Values', + type: 'fixedCollection', + default: {}, + options: [ + { + name: 'number', + displayName: 'Number', + values: [ + { + name: 'string1', + displayName: 'string1', + type: 'string', + displayOptions: { + show: { + '/mode': [{ _cnd: { includes: 'de2' } }], + }, + }, + default: 'default string1', + }, + { + name: 'number1', + displayName: 'number1', + type: 'number', + default: 0, + }, + ], + }, + ], + }, + ], + nodeValues: { + values: { + number: { + string1: 'own string1', + }, + }, + }, + }, + output: { + noneDisplayedFalse: { + defaultsFalse: {}, + defaultsTrue: { + mode: 'mode1', + values: { + number: { + number1: 0, + }, + }, + }, + }, + noneDisplayedTrue: { + defaultsFalse: { + values: { + number: { + string1: 'own string1', + }, + }, + }, + defaultsTrue: { + mode: 'mode1', + values: { + number: { + string1: 'own string1', + number1: 0, + }, + }, + }, + }, + }, + }, + // Remember it is correct that default strings get returned here even when returnDefaults + // is set to false because if they would not, there would be no way to know which value + // got added and which one not. + { + description: + 'complex type "collection" with "multipleValues: false" and with displayOptions "show" (match) on option which references root-value. One value set.', + input: { + nodePropertiesArray: [ + { + name: 'mode', + displayName: 'mode', + type: 'string', + default: 'mode1', + }, + { + name: 'values', + displayName: 'Values', + type: 'collection', + default: {}, + options: [ + { + name: 'string1', + displayName: 'string1', + type: 'string', + displayOptions: { + show: { + '/mode': [{ _cnd: { eq: 'mode1' } }], + }, + }, + default: 'default string1', + }, + { + name: 'number1', + displayName: 'number1', + type: 'number', + default: 0, + }, + ], + }, + ], + nodeValues: { + values: { + string1: 'own string1', + }, + }, + }, + output: { + noneDisplayedFalse: { + defaultsFalse: { + values: { + string1: 'own string1', + }, + }, + defaultsTrue: { + mode: 'mode1', + values: { + string1: 'own string1', + }, + }, + }, + noneDisplayedTrue: { + defaultsFalse: { + values: { + string1: 'own string1', + }, + }, + defaultsTrue: { + mode: 'mode1', + values: { + string1: 'own string1', + }, + }, + }, + }, + }, + // Remember it is correct that default strings get returned here even when returnDefaults + // is set to false because if they would not, there would be no way to know which value + // got added and which one not. + { + description: + 'complex type "collection" with "multipleValues: false" and with displayOptions "show" (no-match) on option which references root-value. One value set.', + input: { + nodePropertiesArray: [ + { + name: 'mode', + displayName: 'mode', + type: 'string', + default: 'mode1', + }, + { + name: 'values', + displayName: 'Values', + type: 'collection', + default: {}, + options: [ + { + name: 'string1', + displayName: 'string1', + type: 'string', + displayOptions: { + show: { + '/mode': [{ _cnd: { eq: 'mode2' } }], + }, + }, + default: 'default string1', + }, + { + name: 'number1', + displayName: 'number1', + type: 'number', + default: 0, + }, + ], + }, + ], + nodeValues: { + values: { + string1: 'own string1', + }, + }, + }, + output: { + noneDisplayedFalse: { + defaultsFalse: { + // TODO: Write some code which cleans up data like that + values: {}, + }, + defaultsTrue: { + mode: 'mode1', + values: {}, + }, + }, + noneDisplayedTrue: { + defaultsFalse: { + values: { + string1: 'own string1', + }, + }, + defaultsTrue: { + mode: 'mode1', + values: { + string1: 'own string1', + }, + }, + }, + }, + }, + // Remember it is correct that default strings get returned here even when returnDefaults + // is set to false because if they would not, there would be no way to know which value + // got added and which one not. + { + description: + 'complex type "collection" with "multipleValues: true" and with displayOptions "show" (match) on option which references root-value. One value set.', + input: { + nodePropertiesArray: [ + { + name: 'mode', + displayName: 'mode', + type: 'string', + default: 'mode1', + }, + { + name: 'values', + displayName: 'Values', + type: 'collection', + typeOptions: { + multipleValues: true, + }, + default: {}, + options: [ + { + name: 'string1', + displayName: 'string1', + type: 'string', + displayOptions: { + show: { + '/mode': [{ _cnd: { eq: 'mode1' } }], + }, + }, + default: 'default string1', + }, + { + name: 'number1', + displayName: 'number1', + type: 'number', + default: 0, + }, + ], + }, + ], + nodeValues: { + values: [ + { + string1: 'own string1', + }, + ], + }, + }, + output: { + noneDisplayedFalse: { + defaultsFalse: { + values: [ + { + string1: 'own string1', + }, + ], + }, + defaultsTrue: { + mode: 'mode1', + values: [ + { + string1: 'own string1', + }, + ], + }, + }, + noneDisplayedTrue: { + defaultsFalse: { + values: [ + { + string1: 'own string1', + }, + ], + }, + defaultsTrue: { + mode: 'mode1', + values: [ + { + string1: 'own string1', + }, + ], + }, + }, + }, + }, + + // Remember it is correct that default strings get returned here even when returnDefaults + // is set to false because if they would not, there would be no way to know which value + // got added and which one not. + { + description: + 'complex type "collection" with "multipleValues: true" and with displayOptions "show" (no-match) on option which references root-value. One value set.', + input: { + nodePropertiesArray: [ + { + name: 'mode', + displayName: 'mode', + type: 'string', + default: 'mode1', + }, + { + name: 'values', + displayName: 'Values', + type: 'collection', + typeOptions: { + multipleValues: true, + }, + default: {}, + options: [ + { + name: 'string1', + displayName: 'string1', + type: 'string', + displayOptions: { + show: { + '/mode': [{ _cnd: { eq: 'mode2' } }], + }, + }, + default: 'default string1', + }, + { + name: 'number1', + displayName: 'number1', + type: 'number', + default: 0, + }, + ], + }, + ], + nodeValues: { + values: [ + { + string1: 'own string1', + number1: 0, + }, + ], + }, + }, + output: { + noneDisplayedFalse: { + defaultsFalse: { + values: [ + { + string1: 'own string1', + number1: 0, + }, + ], + }, + defaultsTrue: { + mode: 'mode1', + values: [ + { + string1: 'own string1', + number1: 0, + }, + ], + }, + }, + noneDisplayedTrue: { + defaultsFalse: { + values: [ + { + string1: 'own string1', + number1: 0, + }, + ], + }, + defaultsTrue: { + mode: 'mode1', + values: [ + { + string1: 'own string1', + number1: 0, + }, + ], + }, + }, + }, + }, + { + description: + 'complex type "fixedCollection" with "multipleValues: false" and with displayOptions "show" (no-match) on option. One value set also the not displayed one.', + input: { + nodePropertiesArray: [ + { + name: 'values', + displayName: 'Values', + type: 'fixedCollection', + default: {}, + options: [ + { + name: 'number', + displayName: 'Number', + values: [ + { + name: 'mode', + displayName: 'mode', + type: 'string', + default: 'mode1', + }, + { + displayName: 'string1', + name: 'string1', + type: 'string', + displayOptions: { + show: { + mode: [{ _cnd: { eq: 'mode1' } }], + }, + }, + default: 'default string1', + }, + { + displayName: 'number1', + name: 'number1', + type: 'number', + default: 0, + }, + ], + }, + ], + }, + ], + nodeValues: { + values: { + number: { + mode: 'mode2', + string1: 'default string1', + number1: 1, + }, + }, + }, + }, + output: { + noneDisplayedFalse: { + defaultsFalse: { + values: { + number: { + mode: 'mode2', + number1: 1, + }, + }, + }, + defaultsTrue: { + values: { + number: { + mode: 'mode2', + number1: 1, + }, + }, + }, + }, + noneDisplayedTrue: { + defaultsFalse: { + values: { + number: { + mode: 'mode2', + number1: 1, + }, + }, + }, + defaultsTrue: { + values: { + number: { + mode: 'mode2', + string1: 'default string1', + number1: 1, + }, + }, + }, + }, + }, + }, + + { + description: + 'two identically named properties of which only one gets displayed with different options. No value set at all.', + input: { + nodePropertiesArray: [ + { + displayName: 'mainOption', + name: 'mainOption', + type: 'options', + options: [ + { + name: 'option1', + value: 'option1', + }, + { + name: 'option2', + value: 'option2', + }, + ], + default: 'option1', + }, + { + displayName: 'subOption', + name: 'subOption', + type: 'options', + displayOptions: { + show: { + mainOption: [{ _cnd: { endsWith: '1' } }], + }, + }, + options: [ + { + name: 'option1a', + value: 'option1a', + }, + { + name: 'option1b', + value: 'option1b', + }, + ], + default: 'option1a', + }, + { + displayName: 'subOption', + name: 'subOption', + type: 'options', + displayOptions: { + show: { + mainOption: [{ _cnd: { endsWith: '2' } }], + }, + }, + options: [ + { + name: 'option2a', + value: 'option2a', + }, + { + name: 'option2b', + value: 'option2b', + }, + ], + default: 'option2a', + }, + ], + nodeValues: {}, + }, + output: { + noneDisplayedFalse: { + defaultsFalse: {}, + defaultsTrue: { + mainOption: 'option1', + subOption: 'option1a', + }, + }, + noneDisplayedTrue: { + defaultsFalse: {}, + defaultsTrue: { + mainOption: 'option1', + subOption: 'option1a', + }, + }, + }, + }, + { + description: + 'One property which is dependency on two identically named properties of which only one gets displayed with different options. No value set at all.', + input: { + nodePropertiesArray: [ + { + displayName: 'mainOption', + name: 'mainOption', + type: 'options', + options: [ + { + name: 'option1', + value: 'option1', + }, + { + name: 'option2', + value: 'option2', + }, + ], + default: 'option1', + }, + { + displayName: 'subOption', + name: 'subOption', + type: 'options', + displayOptions: { + show: { + mainOption: [{ _cnd: { eq: 'option1' } }], + }, + }, + options: [ + { + name: 'option1a', + value: 'option1a', + }, + { + name: 'option1b', + value: 'option1b', + }, + ], + default: 'option1a', + }, + { + displayName: 'subOption', + name: 'subOption', + type: 'options', + displayOptions: { + show: { + mainOption: [{ _cnd: { eq: 'option2' } }], + }, + }, + options: [ + { + name: 'option2a', + value: 'option2a', + }, + { + name: 'option2b', + value: 'option2b', + }, + ], + default: 'option2a', + }, + { + displayName: 'dependentParameter', + name: 'dependentParameter', + type: 'string', + default: 'value1', + required: true, + displayOptions: { + show: { + mainOption: [{ _cnd: { eq: 'option1' } }], + subOption: [{ _cnd: { includes: '1a' } }], + }, + }, + }, + { + displayName: 'dependentParameter', + name: 'dependentParameter', + type: 'string', + default: 'value2', + required: true, + displayOptions: { + show: { + mainOption: [{ _cnd: { eq: 'option2' } }], + subOption: [{ _cnd: { includes: '2a' } }], + }, + }, + }, + ], + nodeValues: {}, + }, + output: { + noneDisplayedFalse: { + defaultsFalse: {}, + defaultsTrue: { + mainOption: 'option1', + subOption: 'option1a', + dependentParameter: 'value1', + }, + }, + noneDisplayedTrue: { + defaultsFalse: {}, + defaultsTrue: { + mainOption: 'option1', + subOption: 'option1a', + dependentParameter: 'value1', + }, + }, + }, + }, + { + description: + 'One property which is dependency on two identically named properties of which only one gets displayed with different options. No value set at all. Order reversed', + input: { + nodePropertiesArray: [ + { + displayName: 'dependentParameter', + name: 'dependentParameter', + type: 'string', + default: 'value2', + required: true, + displayOptions: { + show: { + mainOption: [{ _cnd: { eq: 'option2' } }], + subOption: [{ _cnd: { includes: '2a' } }], + }, + }, + }, + { + displayName: 'subOption', + name: 'subOption', + type: 'options', + displayOptions: { + show: { + mainOption: [{ _cnd: { eq: 'option2' } }], + }, + }, + options: [ + { + name: 'option2a', + value: 'option2a', + }, + { + name: 'option2b', + value: 'option2b', + }, + ], + default: 'option2a', + }, + { + displayName: 'subOption', + name: 'subOption', + type: 'options', + displayOptions: { + show: { + mainOption: [{ _cnd: { eq: 'option1' } }], + }, + }, + options: [ + { + name: 'option1a', + value: 'option1a', + }, + { + name: 'option1b', + value: 'option1b', + }, + ], + default: 'option1a', + }, + { + displayName: 'dependentParameter', + name: 'dependentParameter', + type: 'string', + default: 'value1', + required: true, + displayOptions: { + show: { + mainOption: [{ _cnd: { eq: 'option1' } }], + subOption: [{ _cnd: { eq: 'option1a' } }], + }, + }, + }, + { + displayName: 'mainOption', + name: 'mainOption', + type: 'options', + options: [ + { + name: 'option1', + value: 'option1', + }, + { + name: 'option2', + value: 'option2', + }, + ], + default: 'option1', + }, + ], + nodeValues: {}, + }, + output: { + noneDisplayedFalse: { + defaultsFalse: {}, + defaultsTrue: { + mainOption: 'option1', + subOption: 'option1a', + dependentParameter: 'value1', + }, + }, + noneDisplayedTrue: { + defaultsFalse: {}, + defaultsTrue: { + mainOption: 'option1', + subOption: 'option1a', + dependentParameter: 'value1', + }, + }, + }, + }, + { + description: + 'One property which is dependency on two identically named properties of which only one gets displayed with different options. No value set at all.', + input: { + nodePropertiesArray: [ + { + displayName: 'mainOption', + name: 'mainOption', + type: 'options', + options: [ + { + name: 'option1', + value: 'option1', + }, + { + name: 'option2', + value: 'option2', + }, + ], + default: 'option1', + }, + { + displayName: 'subOption', + name: 'subOption', + type: 'options', + displayOptions: { + show: { + mainOption: [{ _cnd: { eq: 'option1' } }], + }, + }, + options: [ + { + name: 'option1a', + value: 'option1a', + }, + { + name: 'option1b', + value: 'option1b', + }, + ], + default: 'option1a', + }, + { + displayName: 'subOption', + name: 'subOption', + type: 'options', + displayOptions: { + show: { + mainOption: [{ _cnd: { eq: 'option2' } }], + }, + }, + options: [ + { + name: 'option2a', + value: 'option2a', + }, + { + name: 'option2b', + value: 'option2b', + }, + ], + default: 'option2a', + }, + { + displayName: 'dependentParameter', + name: 'dependentParameter', + type: 'string', + default: 'value1', + required: true, + displayOptions: { + show: { + mainOption: [{ _cnd: { eq: 'option1' } }], + subOption: [{ _cnd: { eq: 'option1a' } }], + }, + }, + }, + { + displayName: 'dependentParameter', + name: 'dependentParameter', + type: 'string', + default: 'value2', + required: true, + displayOptions: { + show: { + mainOption: [{ _cnd: { eq: 'option2' } }], + subOption: [{ _cnd: { eq: 'option2a' } }], + }, + }, + }, + ], + nodeValues: { + mainOption: 'option2', + }, + }, + output: { + noneDisplayedFalse: { + defaultsFalse: { + mainOption: 'option2', + }, + defaultsTrue: { + mainOption: 'option2', + subOption: 'option2a', + dependentParameter: 'value2', + }, + }, + noneDisplayedTrue: { + defaultsFalse: { + mainOption: 'option2', + }, + defaultsTrue: { + mainOption: 'option2', + subOption: 'option2a', + dependentParameter: 'value2', + }, + }, + }, + }, + { + description: + 'complex type "fixedCollection" with "multipleValues: true". Which contains parameters which get displayed on a parameter with a default expression with relative parameter references.', + input: { + nodePropertiesArray: [ + { + displayName: 'Values1', + name: 'values1', + type: 'fixedCollection', + typeOptions: { + multipleValues: true, + }, + description: 'The value to set.', + default: {}, + options: [ + { + displayName: 'Options1', + name: 'options1', + values: [ + { + displayName: 'Key', + name: 'key', + type: 'string', + default: '', + }, + { + displayName: 'Type', + name: 'type', + type: 'hidden', + default: '={{$parameter["&key"].split("|")[1]}}', + }, + { + displayName: 'Title Value', + name: 'titleValue', + displayOptions: { + show: { + type: [{ _cnd: { eq: 'title' } }], + }, + }, + type: 'string', + default: 'defaultTitle', + }, + { + displayName: 'Title Number', + name: 'numberValue', + displayOptions: { + show: { + type: [{ _cnd: { eq: 'number' } }], + }, + }, + type: 'number', + default: 1, + }, + ], + }, + ], + }, + ], + nodeValues: { + values1: { + options1: [ + { + key: 'asdf|title', + titleValue: 'different', + }, + ], + }, + }, + }, + output: { + noneDisplayedFalse: { + defaultsFalse: { + values1: { + options1: [ + { + key: 'asdf|title', + titleValue: 'different', + }, + ], + }, + }, + defaultsTrue: { + values1: { + options1: [ + { + key: 'asdf|title', + type: '={{$parameter["&key"].split("|")[1]}}', + // This is not great that it displays this theoretically hidden parameter + // but because we can not resolve the values for now + numberValue: 1, + titleValue: 'different', + }, + ], + }, + }, + }, + noneDisplayedTrue: { + defaultsFalse: { + values1: { + options1: [ + { + key: 'asdf|title', + titleValue: 'different', + }, + ], + }, + }, + defaultsTrue: { + values1: { + options1: [ + { + key: 'asdf|title', + type: '={{$parameter["&key"].split("|")[1]}}', + titleValue: 'different', + numberValue: 1, + }, + ], + }, + }, + }, + }, + }, + ]; + + for (const testData of tests) { + test(testData.description, () => { + // returnDefaults: false | returnNoneDisplayed: false + let result = getNodeParameters( + testData.input.nodePropertiesArray, + testData.input.nodeValues, + false, + false, + null, + ); + expect(result).toEqual(testData.output.noneDisplayedFalse.defaultsFalse); + + // returnDefaults: true | returnNoneDisplayed: false + result = getNodeParameters( + testData.input.nodePropertiesArray, + testData.input.nodeValues, + true, + false, + null, + ); + expect(result).toEqual(testData.output.noneDisplayedFalse.defaultsTrue); + + // returnDefaults: false | returnNoneDisplayed: true + result = getNodeParameters( + testData.input.nodePropertiesArray, + testData.input.nodeValues, + false, + true, + null, + ); + expect(result).toEqual(testData.output.noneDisplayedTrue.defaultsFalse); + + // returnDefaults: true | returnNoneDisplayed: true + result = getNodeParameters( + testData.input.nodePropertiesArray, + testData.input.nodeValues, + true, + true, + null, + ); + expect(result).toEqual(testData.output.noneDisplayedTrue.defaultsTrue); + }); + } + }); +});