From cec1c295a708f2e8c4ab4fd928ff8841b81e57ba Mon Sep 17 00:00:00 2001 From: Sachin Chaurasiya Date: Mon, 5 Jun 2023 13:52:00 +0530 Subject: [PATCH] fix(ui): json schema form field validation (#11867) * fix(ui): json schema form field validation * address comment * fix: duplicate key issue * add unit test --- .../FieldErrorTemplate.test.tsx | 64 +++++++++++++++++++ .../FieldErrorTemplate/FieldErrorTemplate.tsx | 37 +++++++++++ .../common/FormBuilder/FormBuilder.tsx | 2 + .../main/resources/ui/src/utils/formUtils.tsx | 10 ++- 4 files changed, 112 insertions(+), 1 deletion(-) create mode 100644 openmetadata-ui/src/main/resources/ui/src/components/JSONSchemaTemplate/FieldErrorTemplate/FieldErrorTemplate.test.tsx create mode 100644 openmetadata-ui/src/main/resources/ui/src/components/JSONSchemaTemplate/FieldErrorTemplate/FieldErrorTemplate.tsx diff --git a/openmetadata-ui/src/main/resources/ui/src/components/JSONSchemaTemplate/FieldErrorTemplate/FieldErrorTemplate.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/JSONSchemaTemplate/FieldErrorTemplate/FieldErrorTemplate.test.tsx new file mode 100644 index 00000000000..d6a31ec8c54 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/JSONSchemaTemplate/FieldErrorTemplate/FieldErrorTemplate.test.tsx @@ -0,0 +1,64 @@ +/* + * Copyright 2023 Collate. + * 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 { FieldErrorProps } from '@rjsf/utils'; +import { render } from '@testing-library/react'; +import React from 'react'; +import { FieldErrorTemplate } from './FieldErrorTemplate'; + +describe('FieldErrorTemplate', () => { + it('renders error list correctly', () => { + const errors = ['Error 1', 'Error 2']; + const schema = { $id: 'schema-id' }; + const idSchema = { $id: 'id-schema-id' }; + + const { container } = render( + + ); + + const errorItems = container.querySelectorAll( + '.ant-form-item-explain-error' + ); + + expect(errorItems).toHaveLength(errors.length); + + errorItems.forEach((item, index) => { + expect(item.textContent).toBe(errors[index]); + }); + }); + + it('renders null when errors are empty', () => { + const errors: string[] = []; + const schema = { $id: 'schema-id' }; + const idSchema = { $id: 'id-schema-id' }; + + const { container } = render( + + ); + + const errorItems = container.querySelectorAll( + '.ant-form-item-explain-error' + ); + + expect(errorItems).toHaveLength(0); + }); +}); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/JSONSchemaTemplate/FieldErrorTemplate/FieldErrorTemplate.tsx b/openmetadata-ui/src/main/resources/ui/src/components/JSONSchemaTemplate/FieldErrorTemplate/FieldErrorTemplate.tsx new file mode 100644 index 00000000000..2b4e0522b0a --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/JSONSchemaTemplate/FieldErrorTemplate/FieldErrorTemplate.tsx @@ -0,0 +1,37 @@ +/* + * Copyright 2023 Collate. + * 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 { FieldErrorProps } from '@rjsf/utils'; +import { isEmpty } from 'lodash'; +import React, { FC } from 'react'; + +export const FieldErrorTemplate: FC = (props) => { + const errorList = [...new Set(props.errors ?? [])]; + + if (isEmpty(errorList)) { + return null; + } + + return ( +
+
    + {errorList.map((error) => ( +
  • + {error} +
  • + ))} +
+
+ ); +}; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/FormBuilder/FormBuilder.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/FormBuilder/FormBuilder.tsx index ac466b0d1fb..32776835d45 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/FormBuilder/FormBuilder.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/FormBuilder/FormBuilder.tsx @@ -17,6 +17,7 @@ import { Button } from 'antd'; import classNames from 'classnames'; import { ArrayFieldTemplate } from 'components/JSONSchemaTemplate/ArrayFieldTemplate'; import DescriptionFieldTemplate from 'components/JSONSchemaTemplate/DescriptionFieldTemplate'; +import { FieldErrorTemplate } from 'components/JSONSchemaTemplate/FieldErrorTemplate/FieldErrorTemplate'; import { ObjectFieldTemplate } from 'components/JSONSchemaTemplate/ObjectFieldTemplate'; import PasswordWidget from 'components/JsonSchemaWidgets/PasswordWidget'; import { ServiceCategory } from 'enums/service.enum'; @@ -123,6 +124,7 @@ const FormBuilder: FunctionComponent = ({ ArrayFieldTemplate: ArrayFieldTemplate, ObjectFieldTemplate: ObjectFieldTemplate, DescriptionFieldTemplate: DescriptionFieldTemplate, + FieldErrorTemplate: FieldErrorTemplate, }} transformErrors={transformErrors} uiSchema={uiSchema} diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/formUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/formUtils.tsx index b04fd7cf9c0..3d5fc024264 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/formUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/formUtils.tsx @@ -192,7 +192,15 @@ export const generateFormFields = (fields: FieldProp[]) => { export const transformErrors: ErrorTransformer = (errors) => { const errorRet = errors.map((error) => { const { property } = error; - const id = 'root' + property?.replaceAll('.', '/'); + + /** + * For nested fields we have to check if it's property start with "." + * else we will just prepend the root to property + */ + const id = property?.startsWith('.') + ? 'root' + property?.replaceAll('.', '/') + : `root/${property}`; + // If element is not present in DOM, ignore error if (document.getElementById(id)) { const fieldName = error.params?.missingProperty;