diff --git a/.changeset/silent-dryers-end.md b/.changeset/silent-dryers-end.md new file mode 100644 index 0000000000..642a5fb7da --- /dev/null +++ b/.changeset/silent-dryers-end.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder': minor +--- + +Fix the scaffolder validator for arrays when the item is a field in the object diff --git a/plugins/scaffolder/src/components/TemplatePage/createValidator.test.ts b/plugins/scaffolder/src/components/TemplatePage/createValidator.test.ts index f5be77811a..1858e40241 100644 --- a/plugins/scaffolder/src/components/TemplatePage/createValidator.test.ts +++ b/plugins/scaffolder/src/components/TemplatePage/createValidator.test.ts @@ -19,6 +19,12 @@ import { CustomFieldValidator } from '@backstage/plugin-scaffolder-react'; import { ApiHolder } from '@backstage/core-plugin-api'; import { FieldValidation, FormValidation } from '@rjsf/core'; +type CustomLinkType = { + url: string; + title: string; + icon: string; +}; + describe('createValidator', () => { const validators: Record> = { @@ -31,6 +37,23 @@ describe('createValidator', () => { fieldValidation.addError('Error !'); } }, + CustomLink: ( + values: unknown, + fieldValidation: FieldValidation, + _context: { apiHolder: ApiHolder }, + ) => { + const input = values as CustomLinkType[]; + for (const item of input) { + const validGitlabUrlRegex = + /gitlab\.(?:stg\.)?spotify\.com\?owner=.*&repo=.*/; + + if (!item || !validGitlabUrlRegex.test(item.url)) { + fieldValidation.addError( + `Make sure to put in a valid gitlab clone url.`, + ); + } + } + }, TagPicker: ( values: unknown, fieldValidation: FieldValidation, @@ -123,4 +146,53 @@ describe('createValidator', () => { expect(result).not.toBeNull(); expect(result.tags.addError).toHaveBeenCalledTimes(1); }); + + it('should call validator for array object property from a custom field extension', () => { + /* GIVEN */ + const rootSchema = { + title: 'My links', + properties: { + links: { + title: 'Links', + type: 'array', + items: { + type: 'object', + required: ['url', 'title', 'icon'], + properties: { + url: { + title: 'url', + description: 'url', + type: 'object', + 'ui:field': 'CustomLink', + }, + }, + }, + }, + }, + }; + const validator = createValidator(rootSchema, validators, context); + + const formData = { + links: [ + { + url: 'http://gitlab.spotify.nl/owener=me&repo=test', + icon: 'subject', + title: 'My repository for testing features', + } as CustomLinkType, + ], + }; + const errors = { + addError: jest.fn(), + links: { + addError: jest.fn(), + } as unknown as FormValidation, + } as unknown as FormValidation; + + /* WHEN */ + const result = validator(formData, errors); + + /* THEN */ + expect(result).not.toBeNull(); + expect(result.links.addError).toHaveBeenCalledTimes(1); + }); }); diff --git a/plugins/scaffolder/src/components/TemplatePage/createValidator.ts b/plugins/scaffolder/src/components/TemplatePage/createValidator.ts index 123a7e800e..8bfe8c8313 100644 --- a/plugins/scaffolder/src/components/TemplatePage/createValidator.ts +++ b/plugins/scaffolder/src/components/TemplatePage/createValidator.ts @@ -50,40 +50,42 @@ export const createValidator = ( for (const [key, propData] of Object.entries(formData)) { const propValidation = errors[key]; - const propSchemaProps = schemaProps[key]; - if (isObject(propData)) { - if (isObject(propSchemaProps)) { - validate( - propSchemaProps, - propData as JsonObject, - propValidation as FormValidation, - ); + const doValidate = (item: JsonValue | undefined) => { + if (item && isObject(item)) { + const fieldName = item['ui:field'] as string; + if (fieldName && typeof validators[fieldName] === 'function') { + validators[fieldName]!( + propData as JsonObject[], + propValidation, + context, + ); + } } + }; + + const propSchemaProps = schemaProps[key]; + if (isObject(propData) && isObject(propSchemaProps)) { + validate( + propSchemaProps, + propData as JsonObject, + propValidation as FormValidation, + ); } else if (isArray(propData)) { if (isObject(propSchemaProps)) { const { items } = propSchemaProps; if (isObject(items)) { - const fieldName = items['ui:field'] as string; - if (fieldName && typeof validators[fieldName] === 'function') { - validators[fieldName]!( - propData as JsonObject[], - propValidation, - context, - ); + if (items.type === 'object') { + const properties = (items?.properties ?? []) as JsonObject[]; + for (const [, value] of Object.entries(properties)) { + doValidate(value); + } + } else { + doValidate(items); } } } } else { - const fieldName = - isObject(propSchemaProps) && - (propSchemaProps['ui:field'] as string); - if (fieldName && typeof validators[fieldName] === 'function') { - validators[fieldName]!( - propData as JsonValue, - propValidation, - context, - ); - } + doValidate(propSchemaProps); } } } else if (customObject) {