Fix the scaffolder validator for arrays when the item is a field in the object

Signed-off-by: Bogdan Nechyporenko <bnechyporenko@bol.com>
This commit is contained in:
Bogdan Nechyporenko
2023-03-02 14:24:58 +01:00
parent d06fc3f3e3
commit 0aae459629
3 changed files with 104 additions and 25 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-scaffolder': minor
---
Fix the scaffolder validator for arrays when the item is a field in the object
@@ -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<string, undefined | CustomFieldValidator<unknown>> =
{
@@ -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);
});
});
@@ -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) {