mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
feat(core): Display conditions in displayOptions (no-changelog) (#7888)
This commit is contained in:
parent
d6deceacde
commit
ed7d6b7b3a
|
@ -1,5 +1,10 @@
|
||||||
import { Credentials } from 'n8n-core';
|
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 * as Db from '@/Db';
|
||||||
import type { ICredentialsDb } from '@/Interfaces';
|
import type { ICredentialsDb } from '@/Interfaces';
|
||||||
import { CredentialsEntity } from '@db/entities/CredentialsEntity';
|
import { CredentialsEntity } from '@db/entities/CredentialsEntity';
|
||||||
|
@ -182,7 +187,7 @@ export function toJsonSchema(properties: INodeProperties[]): IDataObject {
|
||||||
if (property.displayOptions?.show) {
|
if (property.displayOptions?.show) {
|
||||||
const dependantName = Object.keys(property.displayOptions?.show)[0] || '';
|
const dependantName = Object.keys(property.displayOptions?.show)[0] || '';
|
||||||
const displayOptionsValues = property.displayOptions.show[dependantName];
|
const displayOptionsValues = property.displayOptions.show[dependantName];
|
||||||
let dependantValue: string | number | boolean = '';
|
let dependantValue: DisplayCondition | string | number | boolean = '';
|
||||||
|
|
||||||
if (displayOptionsValues && Array.isArray(displayOptionsValues) && displayOptionsValues[0]) {
|
if (displayOptionsValues && Array.isArray(displayOptionsValues) && displayOptionsValues[0]) {
|
||||||
dependantValue = displayOptionsValues[0];
|
dependantValue = displayOptionsValues[0];
|
||||||
|
@ -193,12 +198,75 @@ export function toJsonSchema(properties: INodeProperties[]): IDataObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!resolveProperties.includes(dependantName)) {
|
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] = {
|
propertyRequiredDependencies[dependantName] = {
|
||||||
if: {
|
if: {
|
||||||
properties: {
|
properties: {
|
||||||
[dependantName]: {
|
[dependantName]: conditionalValue,
|
||||||
enum: [dependantValue],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
then: {
|
then: {
|
||||||
|
|
|
@ -44,7 +44,7 @@ export const employeeCreateDescription: EmployeeProperties = [
|
||||||
},
|
},
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
...createEmployeeSharedDescription(true),
|
...(createEmployeeSharedDescription(true) as EmployeeProperties),
|
||||||
{
|
{
|
||||||
displayName: 'Additional Fields',
|
displayName: 'Additional Fields',
|
||||||
name: 'additionalFields',
|
name: 'additionalFields',
|
||||||
|
|
|
@ -30,7 +30,7 @@ export const employeeUpdateDescription: EmployeeProperties = [
|
||||||
description:
|
description:
|
||||||
'Whether the employee to create was added to a pay schedule synced with Trax Payroll',
|
'Whether the employee to create was added to a pay schedule synced with Trax Payroll',
|
||||||
},
|
},
|
||||||
...updateEmployeeSharedDescription(true),
|
...(updateEmployeeSharedDescription(true) as EmployeeProperties),
|
||||||
{
|
{
|
||||||
displayName: 'Update Fields',
|
displayName: 'Update Fields',
|
||||||
name: 'updateFields',
|
name: 'updateFields',
|
||||||
|
|
|
@ -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
|
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 {
|
export interface IDisplayOptions {
|
||||||
|
hide?: {
|
||||||
|
[key: string]: Array<NodeParameterValue | DisplayCondition> | undefined;
|
||||||
|
};
|
||||||
|
show?: {
|
||||||
|
'@version'?: Array<number | DisplayCondition>;
|
||||||
|
[key: string]: Array<NodeParameterValue | DisplayCondition> | undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
hideOnCloud?: boolean;
|
||||||
|
}
|
||||||
|
export interface ICredentialsDisplayOptions {
|
||||||
hide?: {
|
hide?: {
|
||||||
[key: string]: NodeParameterValue[] | undefined;
|
[key: string]: NodeParameterValue[] | undefined;
|
||||||
};
|
};
|
||||||
|
@ -1402,7 +1426,7 @@ export interface INodeCredentialTestRequest {
|
||||||
export interface INodeCredentialDescription {
|
export interface INodeCredentialDescription {
|
||||||
name: string;
|
name: string;
|
||||||
required?: boolean;
|
required?: boolean;
|
||||||
displayOptions?: IDisplayOptions;
|
displayOptions?: ICredentialsDisplayOptions;
|
||||||
testedBy?: ICredentialTestRequest | string; // Name of a function inside `loadOptions.credentialTest`
|
testedBy?: ICredentialTestRequest | string; // Name of a function inside `loadOptions.credentialTest`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -41,6 +41,7 @@ import type {
|
||||||
INodeOutputConfiguration,
|
INodeOutputConfiguration,
|
||||||
INodeInputConfiguration,
|
INodeInputConfiguration,
|
||||||
GenericValue,
|
GenericValue,
|
||||||
|
DisplayCondition,
|
||||||
} from './Interfaces';
|
} from './Interfaces';
|
||||||
import {
|
import {
|
||||||
isFilterValue,
|
isFilterValue,
|
||||||
|
@ -282,6 +283,90 @@ export function applySpecialNodeParameters(nodeType: INodeType): void {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getPropertyValues = (
|
||||||
|
nodeValues: INodeParameters,
|
||||||
|
propertyName: string,
|
||||||
|
node: Pick<INode, 'typeVersion'> | 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<NodeParameterValue | DisplayCondition>,
|
||||||
|
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
|
* Returns if the parameter should be displayed or not
|
||||||
*
|
*
|
||||||
|
@ -300,76 +385,31 @@ export function displayParameter(
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { show, hide } = parameter.displayOptions;
|
||||||
|
|
||||||
nodeValuesRoot = nodeValuesRoot || nodeValues;
|
nodeValuesRoot = nodeValuesRoot || nodeValues;
|
||||||
|
|
||||||
let value;
|
if (show) {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
const values: any[] = [];
|
|
||||||
if (parameter.displayOptions.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(parameter.displayOptions.show)) {
|
for (const propertyName of Object.keys(show)) {
|
||||||
if (propertyName.charAt(0) === '/') {
|
const values = getPropertyValues(nodeValues, propertyName, node, nodeValuesRoot);
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (values.some((v) => typeof v === 'string' && v.charAt(0) === '=')) {
|
if (values.some((v) => typeof v === 'string' && v.charAt(0) === '=')) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (values.length === 0 || !checkConditions(show[propertyName]!, values)) {
|
||||||
values.length === 0 ||
|
|
||||||
!parameter.displayOptions.show[propertyName]!.some((v) => values.includes(v))
|
|
||||||
) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parameter.displayOptions.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(parameter.displayOptions.hide)) {
|
for (const propertyName of Object.keys(hide)) {
|
||||||
if (propertyName.charAt(0) === '/') {
|
const values = getPropertyValues(nodeValues, propertyName, node, nodeValuesRoot);
|
||||||
// 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) {
|
if (values.length !== 0 && checkConditions(hide[propertyName]!, values)) {
|
||||||
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))
|
|
||||||
) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
1936
packages/workflow/test/NodeHelpers.conditions.test.ts
Normal file
1936
packages/workflow/test/NodeHelpers.conditions.test.ts
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue