mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
fix: Improve filter component error handling (#8832)
This commit is contained in:
parent
5d52bda865
commit
76fe960a76
|
@ -16,6 +16,7 @@ import { type FilterOperatorId } from './constants';
|
||||||
import {
|
import {
|
||||||
getFilterOperator,
|
getFilterOperator,
|
||||||
handleOperatorChange,
|
handleOperatorChange,
|
||||||
|
isEmptyInput,
|
||||||
operatorTypeToNodeProperty,
|
operatorTypeToNodeProperty,
|
||||||
resolveCondition,
|
resolveCondition,
|
||||||
} from './utils';
|
} from './utils';
|
||||||
|
@ -54,12 +55,20 @@ const operatorId = computed<FilterOperatorId>(() => {
|
||||||
});
|
});
|
||||||
const operator = computed(() => getFilterOperator(operatorId.value));
|
const operator = computed(() => getFilterOperator(operatorId.value));
|
||||||
|
|
||||||
|
const isEmpty = computed(() => {
|
||||||
|
if (operator.value.singleValue) {
|
||||||
|
return isEmptyInput(condition.value.leftValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
return isEmptyInput(condition.value.leftValue) && isEmptyInput(condition.value.rightValue);
|
||||||
|
});
|
||||||
|
|
||||||
const conditionResult = computed(() =>
|
const conditionResult = computed(() =>
|
||||||
resolveCondition({ condition: condition.value, options: props.options }),
|
resolveCondition({ condition: condition.value, options: props.options }),
|
||||||
);
|
);
|
||||||
|
|
||||||
const allIssues = computed(() => {
|
const allIssues = computed(() => {
|
||||||
if (conditionResult.value.status === 'validation_error') {
|
if (conditionResult.value.status === 'validation_error' && !isEmpty.value) {
|
||||||
return [conditionResult.value.error];
|
return [conditionResult.value.error];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -56,6 +56,10 @@ export const handleOperatorChange = ({
|
||||||
return condition;
|
return condition;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const isEmptyInput = (value: unknown): boolean => {
|
||||||
|
return value === '' || value === '=';
|
||||||
|
};
|
||||||
|
|
||||||
export const resolveCondition = ({
|
export const resolveCondition = ({
|
||||||
condition,
|
condition,
|
||||||
options,
|
options,
|
||||||
|
|
|
@ -83,7 +83,7 @@ export class FilterV2 implements INodeType {
|
||||||
set(
|
set(
|
||||||
error,
|
error,
|
||||||
'description',
|
'description',
|
||||||
"Try to change the operator, switch ON the option 'Less Strict Type Validation', or change the type with an expression",
|
"Try changing the type of comparison. Alternatively you can enable 'Less Strict Type Validation' in the options.",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
set(error, 'context.itemIndex', itemIndex);
|
set(error, 'context.itemIndex', itemIndex);
|
||||||
|
|
|
@ -83,7 +83,7 @@ export class IfV2 implements INodeType {
|
||||||
set(
|
set(
|
||||||
error,
|
error,
|
||||||
'description',
|
'description',
|
||||||
"Try to change the operator, switch ON the option 'Less Strict Type Validation', or change the type with an expression",
|
"Try changing the type of comparison. Alternatively you can enable 'Less Strict Type Validation' in the options.",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
set(error, 'context.itemIndex', itemIndex);
|
set(error, 'context.itemIndex', itemIndex);
|
||||||
|
|
|
@ -350,7 +350,7 @@ export class SwitchV3 implements INodeType {
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (!options.looseTypeValidation) {
|
if (!options.looseTypeValidation) {
|
||||||
error.description =
|
error.description =
|
||||||
"Try to change the operator, switch ON the option 'Less Strict Type Validation', or change the type with an expression";
|
"Try changing the type of comparison. Alternatively you can enable 'Less Strict Type Validation' in the options.";
|
||||||
}
|
}
|
||||||
set(error, 'context.itemIndex', itemIndex);
|
set(error, 'context.itemIndex', itemIndex);
|
||||||
set(error, 'node', this.getNode());
|
set(error, 'node', this.getNode());
|
||||||
|
|
|
@ -32,11 +32,16 @@ function parseSingleFilterValue(
|
||||||
type: FilterOperatorType,
|
type: FilterOperatorType,
|
||||||
strict = false,
|
strict = false,
|
||||||
): ValidationResult {
|
): ValidationResult {
|
||||||
return type === 'any' || value === null || value === undefined || value === ''
|
return type === 'any' || value === null || value === undefined
|
||||||
? ({ valid: true, newValue: value } as ValidationResult)
|
? ({ valid: true, newValue: value } as ValidationResult)
|
||||||
: validateFieldType('filter', value, type, { strict, parseStrings: true });
|
: validateFieldType('filter', value, type, { strict, parseStrings: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const withIndefiniteArticle = (noun: string): string => {
|
||||||
|
const article = 'aeiou'.includes(noun.charAt(0)) ? 'an' : 'a';
|
||||||
|
return `${article} ${noun}`;
|
||||||
|
};
|
||||||
|
|
||||||
function parseFilterConditionValues(
|
function parseFilterConditionValues(
|
||||||
condition: FilterConditionValue,
|
condition: FilterConditionValue,
|
||||||
options: FilterOptionsValue,
|
options: FilterOptionsValue,
|
||||||
|
@ -63,50 +68,29 @@ function parseFilterConditionValues(
|
||||||
condition.rightValue.startsWith('='));
|
condition.rightValue.startsWith('='));
|
||||||
const leftValueString = String(condition.leftValue);
|
const leftValueString = String(condition.leftValue);
|
||||||
const rightValueString = String(condition.rightValue);
|
const rightValueString = String(condition.rightValue);
|
||||||
const errorDescription = 'Try to change the operator, or change the type with an expression';
|
const suffix =
|
||||||
const inCondition = errorFormat === 'full' ? ` in condition ${index + 1} ` : ' ';
|
errorFormat === 'full' ? `[condition ${index}, item ${itemIndex}]` : `[item ${itemIndex}]`;
|
||||||
const itemSuffix = `[item ${itemIndex}]`;
|
|
||||||
|
|
||||||
if (!leftValid && !rightValid) {
|
const composeInvalidTypeMessage = (type: string, fromType: string, value: string) => {
|
||||||
const providedValues = 'The provided values';
|
fromType = fromType.toLocaleLowerCase();
|
||||||
let types = `'${operator.type}'`;
|
|
||||||
if (rightType !== operator.type) {
|
|
||||||
types = `'${operator.type}' and '${rightType}' respectively`;
|
|
||||||
}
|
|
||||||
if (strict) {
|
if (strict) {
|
||||||
return {
|
return `Wrong type: '${value}' is ${withIndefiniteArticle(
|
||||||
ok: false,
|
fromType,
|
||||||
error: new FilterError(
|
)} but was expecting ${withIndefiniteArticle(type)} ${suffix}`;
|
||||||
`${providedValues} '${leftValueString}' and '${rightValueString}'${inCondition}are not of the expected type ${types} ${itemSuffix}`,
|
|
||||||
errorDescription,
|
|
||||||
),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
return `Conversion error: the ${fromType} '${value}' can't be converted to ${withIndefiniteArticle(
|
||||||
return {
|
type,
|
||||||
ok: false,
|
)} ${suffix}`;
|
||||||
error: new FilterError(
|
|
||||||
`${providedValues} '${leftValueString}' and '${rightValueString}'${inCondition}cannot be converted to the expected type ${types} ${itemSuffix}`,
|
|
||||||
errorDescription,
|
|
||||||
),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const composeInvalidTypeMessage = (field: 'left' | 'right', type: string, value: string) => {
|
|
||||||
const fieldNumber = field === 'left' ? 1 : 2;
|
|
||||||
|
|
||||||
if (strict) {
|
|
||||||
return `The provided value ${fieldNumber} '${value}'${inCondition}is not of the expected type '${type}' ${itemSuffix}`;
|
|
||||||
}
|
|
||||||
return `The provided value ${fieldNumber} '${value}'${inCondition}cannot be converted to the expected type '${type}' ${itemSuffix}`;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const invalidTypeDescription = 'Try changing the type of comparison.';
|
||||||
|
|
||||||
if (!leftValid) {
|
if (!leftValid) {
|
||||||
return {
|
return {
|
||||||
ok: false,
|
ok: false,
|
||||||
error: new FilterError(
|
error: new FilterError(
|
||||||
composeInvalidTypeMessage('left', operator.type, leftValueString),
|
composeInvalidTypeMessage(operator.type, typeof condition.leftValue, leftValueString),
|
||||||
errorDescription,
|
invalidTypeDescription,
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -115,8 +99,8 @@ function parseFilterConditionValues(
|
||||||
return {
|
return {
|
||||||
ok: false,
|
ok: false,
|
||||||
error: new FilterError(
|
error: new FilterError(
|
||||||
composeInvalidTypeMessage('right', rightType, rightValueString),
|
composeInvalidTypeMessage(rightType, typeof condition.rightValue, rightValueString),
|
||||||
errorDescription,
|
invalidTypeDescription,
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -301,7 +285,7 @@ export function executeFilterCondition(
|
||||||
|
|
||||||
switch (condition.operator.operation) {
|
switch (condition.operator.operation) {
|
||||||
case 'empty':
|
case 'empty':
|
||||||
return !!left && Object.keys(left).length === 0;
|
return !left || Object.keys(left).length === 0;
|
||||||
case 'notEmpty':
|
case 'notEmpty':
|
||||||
return !!left && Object.keys(left).length !== 0;
|
return !!left && Object.keys(left).length !== 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -116,7 +116,7 @@ describe('FilterParameter', () => {
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
).toThrowError(
|
).toThrowError(
|
||||||
"The provided values '15' and 'true' in condition 1 are not of the expected type 'number' [item 0]",
|
"Wrong type: '15' is a string but was expecting a number [condition 0, item 0]",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -136,7 +136,7 @@ describe('FilterParameter', () => {
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
).toThrowError(
|
).toThrowError(
|
||||||
"The provided value 1 '[]' in condition 1 is not of the expected type 'array' [item 0]",
|
"Wrong type: '[]' is a string but was expecting an array [condition 0, item 0]",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -155,7 +155,7 @@ describe('FilterParameter', () => {
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
).toThrowError(
|
).toThrowError(
|
||||||
"The provided value 1 '{}' in condition 1 is not of the expected type 'object' [item 0]",
|
"Wrong type: '{}' is a string but was expecting an object [condition 0, item 0]",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -201,7 +201,7 @@ describe('FilterParameter', () => {
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
).toThrowError(
|
).toThrowError(
|
||||||
"The provided values 'a string' and '15' in condition 1 cannot be converted to the expected type 'boolean'",
|
"Conversion error: the string 'a string' can't be converted to a boolean [condition 0, item 0]",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1011,8 +1011,8 @@ describe('FilterParameter', () => {
|
||||||
it.each([
|
it.each([
|
||||||
{ left: {}, expected: true },
|
{ left: {}, expected: true },
|
||||||
{ left: { foo: 'bar' }, expected: false },
|
{ left: { foo: 'bar' }, expected: false },
|
||||||
{ left: undefined, expected: false },
|
{ left: undefined, expected: true },
|
||||||
{ left: null, expected: false },
|
{ left: null, expected: true },
|
||||||
])('object:empty($left) === $expected', ({ left, expected }) => {
|
])('object:empty($left) === $expected', ({ left, expected }) => {
|
||||||
const result = executeFilter(
|
const result = executeFilter(
|
||||||
filterFactory({
|
filterFactory({
|
||||||
|
|
Loading…
Reference in a new issue