Allow custom field extension validation
Signed-off-by: Stéphane MORI <stephane.mori@gmail.com>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder': patch
|
||||
---
|
||||
|
||||
Allow validation for custom field extension with type object
|
||||
@@ -13,23 +13,22 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { JsonObject, JsonValue } from '@backstage/types';
|
||||
import { LinearProgress } from '@material-ui/core';
|
||||
import { FormValidation, IChangeEvent } from '@rjsf/core';
|
||||
import { IChangeEvent } from '@rjsf/core';
|
||||
import qs from 'qs';
|
||||
import React, { useCallback, useContext, useState } from 'react';
|
||||
import { Navigate, useNavigate } from 'react-router';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import useAsync from 'react-use/lib/useAsync';
|
||||
import { scaffolderApiRef } from '../../api';
|
||||
import { CustomFieldValidator, FieldExtensionOptions } from '../../extensions';
|
||||
import { FieldExtensionOptions } from '../../extensions';
|
||||
import { SecretsContext } from '../secrets/SecretsContext';
|
||||
import { rootRouteRef, scaffolderTaskRouteRef } from '../../routes';
|
||||
import { MultistepJsonForm } from '../MultistepJsonForm';
|
||||
import { createValidator } from './createValidator';
|
||||
|
||||
import { Content, Header, InfoCard, Page } from '@backstage/core-components';
|
||||
import {
|
||||
ApiHolder,
|
||||
errorApiRef,
|
||||
useApi,
|
||||
useApiHolder,
|
||||
@@ -46,60 +45,6 @@ const useTemplateParameterSchema = (templateRef: string) => {
|
||||
return { schema: value, loading, error };
|
||||
};
|
||||
|
||||
function isObject(obj: unknown): obj is JsonObject {
|
||||
return typeof obj === 'object' && obj !== null && !Array.isArray(obj);
|
||||
}
|
||||
|
||||
export const createValidator = (
|
||||
rootSchema: JsonObject,
|
||||
validators: Record<string, undefined | CustomFieldValidator<unknown>>,
|
||||
context: {
|
||||
apiHolder: ApiHolder;
|
||||
},
|
||||
) => {
|
||||
function validate(
|
||||
schema: JsonObject,
|
||||
formData: JsonObject,
|
||||
errors: FormValidation,
|
||||
) {
|
||||
const schemaProps = schema.properties;
|
||||
if (!isObject(schemaProps)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const [key, propData] of Object.entries(formData)) {
|
||||
const propValidation = errors[key];
|
||||
|
||||
if (isObject(propData)) {
|
||||
const propSchemaProps = schemaProps[key];
|
||||
if (isObject(propSchemaProps)) {
|
||||
validate(
|
||||
propSchemaProps,
|
||||
propData as JsonObject,
|
||||
propValidation as FormValidation,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
const propSchema = schemaProps[key];
|
||||
const fieldName =
|
||||
isObject(propSchema) && (propSchema['ui:field'] as string);
|
||||
if (fieldName && typeof validators[fieldName] === 'function') {
|
||||
validators[fieldName]!(
|
||||
propData as JsonValue,
|
||||
propValidation,
|
||||
context,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (formData: JsonObject, errors: FormValidation) => {
|
||||
validate(rootSchema, formData, errors);
|
||||
return errors;
|
||||
};
|
||||
};
|
||||
|
||||
export const TemplatePage = ({
|
||||
customFieldExtensions = [],
|
||||
}: {
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { createValidator } from './createValidator';
|
||||
import { CustomFieldValidator } from '../../extensions';
|
||||
import { ApiHolder } from '@backstage/core-plugin-api';
|
||||
import { FormValidation } from '@rjsf/core';
|
||||
|
||||
describe('createValidator', () => {
|
||||
const validators: Record<string, undefined | CustomFieldValidator<unknown>> =
|
||||
{
|
||||
CustomPicker: (value, validation, _context) => {
|
||||
// @ts-ignore
|
||||
if (!value || !value.value) {
|
||||
validation.addError('Error !');
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const apiHolderMock: jest.Mocked<ApiHolder> = {
|
||||
get: jest.fn().mockImplementation(() => {
|
||||
return null;
|
||||
}),
|
||||
};
|
||||
|
||||
const context = {
|
||||
apiHolder: apiHolderMock,
|
||||
};
|
||||
|
||||
it('Should call validator for object property from a custom field extension', () => {
|
||||
/* GIVEN */
|
||||
const rootSchema = {
|
||||
title: 'Title',
|
||||
properties: {
|
||||
p1: {
|
||||
title: 'PropertyOn',
|
||||
type: 'object',
|
||||
'ui:field': 'CustomPicker',
|
||||
},
|
||||
},
|
||||
};
|
||||
const validator = createValidator(rootSchema, validators, context);
|
||||
|
||||
const formData = {
|
||||
p1: {},
|
||||
};
|
||||
const errors = {
|
||||
addError: jest.fn(),
|
||||
p1: {
|
||||
addError: jest.fn(),
|
||||
} as unknown as FormValidation,
|
||||
} as unknown as FormValidation;
|
||||
|
||||
/* WHEN */
|
||||
const result = validator(formData, errors);
|
||||
|
||||
/* THEN */
|
||||
expect(result).not.toBeNull();
|
||||
expect(result.p1.addError).toBeCalledTimes(1);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { CustomFieldValidator } from '../../extensions';
|
||||
import { FormValidation } from '@rjsf/core';
|
||||
import { JsonObject, JsonValue } from '@backstage/types';
|
||||
import { ApiHolder } from '@backstage/core-plugin-api';
|
||||
|
||||
function isObject(obj: unknown): obj is JsonObject {
|
||||
return typeof obj === 'object' && obj !== null && !Array.isArray(obj);
|
||||
}
|
||||
|
||||
export const createValidator = (
|
||||
rootSchema: JsonObject,
|
||||
validators: Record<string, undefined | CustomFieldValidator<unknown>>,
|
||||
context: {
|
||||
apiHolder: ApiHolder;
|
||||
},
|
||||
) => {
|
||||
function validate(
|
||||
schema: JsonObject,
|
||||
formData: JsonObject,
|
||||
errors: FormValidation,
|
||||
) {
|
||||
const schemaProps = schema.properties;
|
||||
const customObject = schema.type === 'object' && schemaProps === undefined;
|
||||
|
||||
if (!isObject(schemaProps) && !customObject) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (schemaProps) {
|
||||
for (const [key, propData] of Object.entries(formData)) {
|
||||
const propValidation = errors[key];
|
||||
|
||||
if (isObject(propData)) {
|
||||
const propSchemaProps = schemaProps[key];
|
||||
if (isObject(propSchemaProps)) {
|
||||
validate(
|
||||
propSchemaProps,
|
||||
propData as JsonObject,
|
||||
propValidation as FormValidation,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
const propSchema = schemaProps[key];
|
||||
const fieldName =
|
||||
isObject(propSchema) && (propSchema['ui:field'] as string);
|
||||
if (fieldName && typeof validators[fieldName] === 'function') {
|
||||
validators[fieldName]!(
|
||||
propData as JsonValue,
|
||||
propValidation,
|
||||
context,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (customObject) {
|
||||
const fieldName = schema['ui:field'] as string;
|
||||
if (fieldName && typeof validators[fieldName] === 'function') {
|
||||
validators[fieldName]!(formData, errors, context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (formData: JsonObject, errors: FormValidation) => {
|
||||
validate(rootSchema, formData, errors);
|
||||
return errors;
|
||||
};
|
||||
};
|
||||
@@ -13,4 +13,5 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
export { createValidator, TemplatePage } from './TemplatePage';
|
||||
export { TemplatePage } from './TemplatePage';
|
||||
export { createValidator } from './createValidator';
|
||||
|
||||
Reference in New Issue
Block a user