fix(AWS Bedrock Chat Model Node): Do not show issues for arbitrary model names (#17079)

This commit is contained in:
Eugene
2025-07-15 10:02:27 +02:00
committed by GitHub
parent 42c61909c4
commit 5bb5a65edf
6 changed files with 180 additions and 11 deletions

View File

@@ -60,6 +60,7 @@ export class LmChatAwsBedrock implements INodeType {
displayName: 'Model',
name: 'model',
type: 'options',
allowArbitraryValues: true, // Hide issues when model name is specified in the expression and does not match any of the options
description:
'The model which will generate the completion. <a href="https://docs.aws.amazon.com/bedrock/latest/userguide/foundation-models.html">Learn more</a>.',
typeOptions: {

View File

@@ -417,17 +417,19 @@ const getIssues = computed<string[]>(() => {
['options', 'multiOptions'].includes(props.parameter.type) &&
!remoteParameterOptionsLoading.value &&
remoteParameterOptionsLoadingIssues.value === null &&
parameterOptions.value
parameterOptions.value &&
(!isModelValueExpression.value || props.expressionEvaluated !== null)
) {
// Check if the value resolves to a valid option
// Currently it only displays an error in the node itself in
// Check if the value resolves to a valid option.
// For expressions do not validate if there is no evaluated value.
// Currently, it only displays an error in the node itself in
// case the value is not valid. The workflow can still be executed
// and the error is not displayed on the node in the workflow
const validOptions = parameterOptions.value.map((options) => options.value);
let checkValues: string[] = [];
if (!shouldSkipParamValidation(displayValue.value)) {
if (!shouldSkipParamValidation(props.parameter, displayValue.value)) {
if (Array.isArray(displayValue.value)) {
checkValues = checkValues.concat(displayValue.value);
} else {

View File

@@ -14,11 +14,11 @@ import { mockedStore } from '@/__tests__/utils';
import type { INodeUi } from '@/Interface';
describe('useNodeSettingsParameters', () => {
describe('setValue', () => {
beforeEach(() => {
setActivePinia(createTestingPinia());
});
beforeEach(() => {
setActivePinia(createTestingPinia());
});
describe('setValue', () => {
afterEach(() => {
vi.clearAllMocks();
});

View File

@@ -6,6 +6,7 @@ import type {
IDataObject,
INodeTypeDescription,
INodePropertyOptions,
INodeProperties,
} from 'n8n-workflow';
import {
updateDynamicConnections,
@@ -13,8 +14,9 @@ import {
nameIsParameter,
formatAsExpression,
parseFromExpression,
shouldSkipParamValidation,
} from './nodeSettingsUtils';
import { SWITCH_NODE_TYPE } from '@/constants';
import { CUSTOM_API_CALL_KEY, SWITCH_NODE_TYPE } from '@/constants';
import type { INodeUi, IUpdateInformation } from '@/Interface';
describe('updateDynamicConnections', () => {
@@ -396,3 +398,158 @@ describe('parseFromExpression', () => {
expect(parseFromExpression({}, undefined, 'json', null, [])).toBeNull();
});
});
describe('shouldSkipParamValidation', () => {
describe('CUSTOM_API_CALL_KEY detection', () => {
it('should skip validation when value is CUSTOM_API_CALL_KEY', () => {
const parameter: INodeProperties = {
name: 'testParam',
displayName: 'Test Parameter',
type: 'string',
default: '',
};
const result = shouldSkipParamValidation(parameter, CUSTOM_API_CALL_KEY);
expect(result).toBe(true);
});
it('should skip validation when value is a string containing CUSTOM_API_CALL_KEY', () => {
const parameter: INodeProperties = {
name: 'testParam',
displayName: 'Test Parameter',
type: 'string',
default: '',
};
const valueWithKey = `some prefix ${CUSTOM_API_CALL_KEY} some suffix`;
const result = shouldSkipParamValidation(parameter, valueWithKey);
expect(result).toBe(true);
});
it('should not skip validation when value is a string not containing CUSTOM_API_CALL_KEY', () => {
const parameter: INodeProperties = {
name: 'testParam',
displayName: 'Test Parameter',
type: 'string',
default: '',
};
const result = shouldSkipParamValidation(parameter, 'regular string value');
expect(result).toBe(false);
});
});
describe('options parameter type with allowArbitraryValues', () => {
it('should skip validation for options parameter with allowArbitraryValues=true', () => {
const parameter: INodeProperties = {
name: 'optionsParam',
displayName: 'Options Parameter',
type: 'options',
options: [
{ name: 'Option 1', value: 'option1' },
{ name: 'Option 2', value: 'option2' },
],
allowArbitraryValues: true,
default: '',
};
const result = shouldSkipParamValidation(parameter, 'arbitrary_value');
expect(result).toBe(true);
});
it('should not skip validation for options parameter with allowArbitraryValues=false', () => {
const parameter: INodeProperties = {
name: 'optionsParam',
displayName: 'Options Parameter',
type: 'options',
options: [
{ name: 'Option 1', value: 'option1' },
{ name: 'Option 2', value: 'option2' },
],
allowArbitraryValues: false,
default: '',
};
const result = shouldSkipParamValidation(parameter, 'arbitrary_value');
expect(result).toBe(false);
});
it('should not skip validation for options parameter with allowArbitraryValues=undefined', () => {
const parameter: INodeProperties = {
name: 'optionsParam',
displayName: 'Options Parameter',
type: 'options',
options: [
{ name: 'Option 1', value: 'option1' },
{ name: 'Option 2', value: 'option2' },
],
default: '',
};
const result = shouldSkipParamValidation(parameter, 'arbitrary_value');
expect(result).toBe(false);
});
});
describe('multiOptions parameter type with allowArbitraryValues', () => {
it('should skip validation for multiOptions parameter with allowArbitraryValues=true', () => {
const parameter: INodeProperties = {
name: 'multiOptionsParam',
displayName: 'Multi Options Parameter',
type: 'multiOptions',
options: [
{ name: 'Option 1', value: 'option1' },
{ name: 'Option 2', value: 'option2' },
],
allowArbitraryValues: true,
default: [],
};
const result = shouldSkipParamValidation(parameter, ['arbitrary_value']);
expect(result).toBe(true);
});
it('should not skip validation for multiOptions parameter with allowArbitraryValues=false', () => {
const parameter: INodeProperties = {
name: 'multiOptionsParam',
displayName: 'Multi Options Parameter',
type: 'multiOptions',
options: [
{ name: 'Option 1', value: 'option1' },
{ name: 'Option 2', value: 'option2' },
],
allowArbitraryValues: false,
default: [],
};
const result = shouldSkipParamValidation(parameter, ['arbitrary_value']);
expect(result).toBe(false);
});
});
describe('non-options parameter types', () => {
const nonOptionsParameterTypes = [
'string',
'number',
'boolean',
'json',
'dateTime',
'color',
] as Array<INodeProperties['type']>;
nonOptionsParameterTypes.forEach((type) => {
it(`should not skip validation for ${type} parameter type regardless of allowArbitraryValues`, () => {
const parameter: INodeProperties = {
name: 'testParam',
displayName: 'Test Parameter',
type,
allowArbitraryValues: true,
default: '',
};
const result = shouldSkipParamValidation(parameter, 'test_value');
expect(result).toBe(false);
});
});
});
});

View File

@@ -359,6 +359,13 @@ export function parseFromExpression(
return null;
}
export function shouldSkipParamValidation(value: string | number | boolean | null) {
return typeof value === 'string' && value.includes(CUSTOM_API_CALL_KEY);
export function shouldSkipParamValidation(
parameter: INodeProperties,
value: NodeParameterValueType,
) {
return (
(typeof value === 'string' && value.includes(CUSTOM_API_CALL_KEY)) ||
(['options', 'multiOptions'].includes(parameter.type) &&
Boolean(parameter.allowArbitraryValues))
);
}

View File

@@ -1466,6 +1466,8 @@ export interface INodeProperties {
// allows to skip validation during execution or set custom validation/casting logic inside node
// inline error messages would still be shown in UI
ignoreValidationDuringExecution?: boolean;
// for type: options | multiOptions skip validation of the value (e.g. when value is not in the list and specified via expression)
allowArbitraryValues?: boolean;
}
export interface INodePropertyModeTypeOptions {