chore(ui): add validation for custom options and arguments in json schema form (#11897)

* chore(ui): add validation for custom options and arguments in json schema form

* chore: update the regex

* add unit test
This commit is contained in:
Sachin Chaurasiya 2023-06-06 21:19:33 +05:30 committed by GitHub
parent f953d7aac6
commit e672a064e2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 138 additions and 1 deletions

View File

@ -27,7 +27,7 @@ import { isEmpty, isUndefined } from 'lodash';
import { LoadingState } from 'Models';
import React, { FunctionComponent, useEffect, useRef, useState } from 'react';
import { getPipelineServiceHostIp } from 'rest/ingestionPipelineAPI';
import { transformErrors } from 'utils/formUtils';
import { customValidate, transformErrors } from 'utils/formUtils';
import { ConfigData } from '../../../interface/service.interface';
import { formatFormDataForRender } from '../../../utils/JSONSchemaFormUtils';
import Loader from '../../Loader/Loader';
@ -114,6 +114,7 @@ const FormBuilder: FunctionComponent<Props> = ({
className={classNames('rjsf', props.className, {
'no-header': !showFormHeader,
})}
customValidate={customValidate}
formContext={{ handleFocus: onFocus }}
formData={localFormData}
idSeparator="/"

View File

@ -38,3 +38,5 @@ export const MARKDOWN_MATCH_ID = /\$\(id="(.*?)"\)/;
export const CUSTOM_PROPERTY_NAME_REGEX = /^[a-z][a-zA-Z0-9]*$/;
export const ENDS_WITH_NUMBER_REGEX = /\d+$/;
export const VALID_OBJECT_KEY_REGEX = /^[_$a-zA-Z][_$a-zA-Z0-9]*$/;

View File

@ -1167,6 +1167,7 @@
"ingestion-pipeline-name-message": "Name that identifies this pipeline instance uniquely.",
"ingestion-pipeline-name-successfully-deployed-entity": "You are all set! The <Ingestion Pipeline Name> has been successfully deployed. The {{entity}} will run at a regular interval as per the schedule.",
"instance-identifier": "A Name that uniquely identifies this configuration instance.",
"invalid-object-key": "Invalid object key. Must start with a letter, underscore, or dollar sign, followed by letters, underscores, dollar signs, or digits.",
"invalid-property-name": "Invalid Property Name",
"jwt-token": "Token you have generated that can be used to access the OpenMetadata API.",
"kill-ingestion-warning": "Once you kill this Ingestion, all running and queued workflows will be stopped and marked as Failed.",

View File

@ -1167,6 +1167,7 @@
"ingestion-pipeline-name-message": "Nombre que identifica esta instancia de pipeline de manera única.",
"ingestion-pipeline-name-successfully-deployed-entity": "¡Listo! El <Nombre del Pipeline de Ingestión> se ha implementado correctamente. El {{entity}} se ejecutará a intervalos regulares según el horario.",
"instance-identifier": "Un nombre que identifica de manera única esta instancia de configuración.",
"invalid-object-key": "Invalid object key. Must start with a letter, underscore, or dollar sign, followed by letters, underscores, dollar signs, or digits.",
"invalid-property-name": "Nombre de propiedad no válido",
"jwt-token": "Token que ha generado que se puede utilizar para acceder a la API de OpenMetadata.",
"kill-ingestion-warning": "Una vez que se detenga esta ingestión, se detendrán y marcarán como fallidas todos los flujos de trabajo en ejecución y en cola.",

View File

@ -1167,6 +1167,7 @@
"ingestion-pipeline-name-message": "Nom qui identifie de manière unique cette instance de pipeline.",
"ingestion-pipeline-name-successfully-deployed-entity": "C'est bon! Le pipeline d'ingestion a été déployé avec succès. {{entity}} s'exécutera à intervalles réguliers selon le calendrier.",
"instance-identifier": "Un nom qui identifie de manière unique cette instance de configuration.",
"invalid-object-key": "Invalid object key. Must start with a letter, underscore, or dollar sign, followed by letters, underscores, dollar signs, or digits.",
"invalid-property-name": "Nom de propriété non valide",
"jwt-token": "Le Jeton que vous avez généré peut être utilisé pour accéder à lAPI OpenMetadata.",
"kill-ingestion-warning": "Une fois que vous avez interrompu cette Ingestion, tous les workflows en cours d'éxécutions et en files d'attente seront arrêtés et marqués comme aillant échoué.",

View File

@ -1167,6 +1167,7 @@
"ingestion-pipeline-name-message": "このパイプラインを一意に識別する名前",
"ingestion-pipeline-name-successfully-deployed-entity": "You are all set! The <Ingestion Pipeline Name> has been successfully deployed. The {{entity}} will run at a regular interval as per the schedule.",
"instance-identifier": "A Name that uniquely identifies this configuration instance.",
"invalid-object-key": "Invalid object key. Must start with a letter, underscore, or dollar sign, followed by letters, underscores, dollar signs, or digits.",
"invalid-property-name": "無効なプロパティ名",
"jwt-token": "Token you have generated that can be used to access the OpenMetadata API.",
"kill-ingestion-warning": "Once you kill this Ingestion, all running and queued workflows will be stopped and marked as Failed.",

View File

@ -1167,6 +1167,7 @@
"ingestion-pipeline-name-message": "Nome que identifica exclusivamente esta instância do pipeline.",
"ingestion-pipeline-name-successfully-deployed-entity": "Tudo pronto! O <Nome do Pipeline de Ingestão> foi implantado com sucesso. O {{entity}} será executado em intervalos regulares de acordo com o agendamento.",
"instance-identifier": "Um nome que identifica exclusivamente esta instância de configuração.",
"invalid-object-key": "Invalid object key. Must start with a letter, underscore, or dollar sign, followed by letters, underscores, dollar signs, or digits.",
"invalid-property-name": "Nome de propriedade inválido",
"jwt-token": "Token que você gerou que pode ser usado para acessar a API do OpenMetadata.",
"kill-ingestion-warning": "Uma vez que você interromper esta Ingestão, todos os fluxos de trabalho em execução e em fila serão interrompidos e marcados como Falha.",

View File

@ -1167,6 +1167,7 @@
"ingestion-pipeline-name-message": "唯一标识此工作流实例的名称",
"ingestion-pipeline-name-successfully-deployed-entity": "您已准备就绪!提取工作流已成功部署,{{entity}}将按照计划定期运行。",
"instance-identifier": "唯一标识此配置实例的名称",
"invalid-object-key": "Invalid object key. Must start with a letter, underscore, or dollar sign, followed by letters, underscores, dollar signs, or digits.",
"invalid-property-name": "无效属性名称",
"jwt-token": "您生成的,用于访问 OpenMetadata API 的令牌",
"kill-ingestion-warning": "一旦您终止此提取工作流,所有正在运行和排队的工作流任务都将停止并标记为失败",

View File

@ -0,0 +1,98 @@
/*
* 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 { FormValidation } from '@rjsf/utils';
import { customValidate } from './formUtils';
describe('customValidate', () => {
it('should add error message when connectionArguments have invalid object keys', () => {
const formData = {
connectionArguments: {
validKey: 'value',
'"invalidKey"': 'value',
},
connectionOptions: {
option1: 'value',
},
};
const errors = {
addError: jest.fn(),
connectionOptions: {
addError: jest.fn(),
},
connectionArguments: {
addError: jest.fn(),
},
};
customValidate?.(formData, errors as unknown as FormValidation);
expect(errors.connectionArguments.addError).toHaveBeenCalledWith(
'message.invalid-object-key'
);
expect(errors.connectionOptions.addError).not.toHaveBeenCalled();
});
it('should add error message when connectionOptions have invalid object keys', () => {
const formData = {
connectionArguments: {
validKey: 'value',
},
connectionOptions: {
option1: 'value',
'invalid Key': 'value',
},
};
const errors = {
addError: jest.fn(),
connectionOptions: {
addError: jest.fn(),
},
connectionArguments: {
addError: jest.fn(),
},
};
customValidate?.(formData, errors as unknown as FormValidation);
expect(errors.connectionArguments.addError).not.toHaveBeenCalled();
expect(errors.connectionOptions.addError).toHaveBeenCalledWith(
'message.invalid-object-key'
);
});
it('should not add error message when all object keys are valid', () => {
const formData = {
connectionArguments: {
validKey1: 'value',
validKey2: 'value',
},
connectionOptions: {
option1: 'value',
option2: 'value',
},
};
const errors = {
connectionArguments: {
addError: jest.fn(),
},
connectionOptions: {
addError: jest.fn(),
},
};
customValidate?.(formData, errors as unknown as FormValidation);
expect(errors.connectionArguments.addError).not.toHaveBeenCalled();
expect(errors.connectionOptions.addError).not.toHaveBeenCalled();
});
});

View File

@ -10,6 +10,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { FormProps } from '@rjsf/core';
import { ErrorTransformer } from '@rjsf/utils';
import {
Divider,
@ -31,6 +32,7 @@ import { UserTeamSelectableList } from 'components/common/UserTeamSelectableList
import { UserSelectDropdownProps } from 'components/common/UserTeamSelectableList/UserTeamSelectableList.interface';
import SliderWithInput from 'components/SliderWithInput/SliderWithInput';
import { SliderWithInputProps } from 'components/SliderWithInput/SliderWithInput.interface';
import { VALID_OBJECT_KEY_REGEX } from 'constants/regex.constants';
import { FieldProp, FieldTypes } from 'interface/FormUtils.interface';
import { compact, startCase } from 'lodash';
import TagSuggestion, {
@ -219,3 +221,31 @@ export const transformErrors: ErrorTransformer = (errors) => {
return compact(errorRet);
};
export const customValidate: FormProps['customValidate'] = (
formData,
errors
) => {
const { connectionArguments = {}, connectionOptions = {} } = formData;
const connectionArgumentsKeys = Object.keys(connectionArguments);
const connectionOptionsKeys = Object.keys(connectionOptions);
const connectionArgumentsHasError = connectionArgumentsKeys.some(
(key) => !VALID_OBJECT_KEY_REGEX.test(key)
);
const connectionOptionsHasError = connectionOptionsKeys.some(
(key) => !VALID_OBJECT_KEY_REGEX.test(key)
);
if (connectionArgumentsHasError && errors?.connectionArguments) {
errors.connectionArguments?.addError(i18n.t('message.invalid-object-key'));
}
if (connectionOptionsHasError && errors?.connectionOptions) {
errors.connectionOptions?.addError(i18n.t('message.invalid-object-key'));
}
return errors;
};