mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2026-01-07 13:07:22 +00:00
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:
parent
f953d7aac6
commit
e672a064e2
@ -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="/"
|
||||
|
||||
@ -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]*$/;
|
||||
|
||||
@ -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.",
|
||||
|
||||
@ -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.",
|
||||
|
||||
@ -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 à l’API 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é.",
|
||||
|
||||
@ -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.",
|
||||
|
||||
@ -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.",
|
||||
|
||||
@ -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": "一旦您终止此提取工作流,所有正在运行和排队的工作流任务都将停止并标记为失败",
|
||||
|
||||
@ -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();
|
||||
});
|
||||
});
|
||||
@ -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;
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user