bump(ui): rjsf to v5 (#11549)

* bump(ui): rjsf to v5
It will fix validation and extra parameter issues

* address comments
fix code smells
fix cypress failure

* revert schema files

* bigquery fix
sonar cloud fix

* fix big query

* fix superset

* fix cypress

* fix cypress for big query

* fix failures

* fix java issue

* revert connectionBasicType.json
create custom SupersetConnection.json

* fix Tableau service issues #11637
This commit is contained in:
Chirag Madlani 2023-05-18 20:33:54 +05:30 committed by GitHub
parent 2b7ee97d52
commit 763b96b8fe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 579 additions and 167 deletions

View File

@ -30,7 +30,7 @@ from metadata.generated.schema.entity.services.connections.database.postgresConn
PostgresConnection,
)
from metadata.generated.schema.entity.utils.supersetApiConnection import (
SupersetAPIConnection,
SupersetApiConnection,
)
from metadata.ingestion.connections.test_connections import (
test_connection_engine_step,
@ -55,7 +55,7 @@ def get_connection(connection: SupersetConnection) -> SupersetAPIClient:
"""
Create connection
"""
if isinstance(connection.connection, SupersetAPIConnection):
if isinstance(connection.connection, SupersetApiConnection):
return SupersetAPIClient(connection)
if isinstance(connection.connection, PostgresConnection):
return pg_get_connection(connection=connection.connection)

View File

@ -18,7 +18,7 @@ from metadata.generated.schema.entity.services.connections.metadata.openMetadata
OpenMetadataConnection,
)
from metadata.generated.schema.entity.utils.supersetApiConnection import (
SupersetAPIConnection,
SupersetApiConnection,
)
from metadata.generated.schema.metadataIngestion.workflow import (
Source as WorkflowSource,
@ -41,6 +41,6 @@ class SupersetSource:
raise InvalidSourceException(
f"Expected SupersetConnection, but got {connection}"
)
if isinstance(connection.connection, SupersetAPIConnection):
if isinstance(connection.connection, SupersetApiConnection):
return SupersetAPISource(config, metadata_config)
return SupersetDBSource(config, metadata_config)

View File

@ -1,7 +1,7 @@
{
"$id": "https://open-metadata.org/schema/entity/services/connections/dashboard/supersetConnection.json",
"$id": "https://open-metadata.org/schema/entity/services/connections/dashboard/supersetApiConnection.json",
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "SupersetAPIConnection",
"title": "SupersetApiConnection",
"description": "Superset API Connection Config",
"type": "object",
"definitions": {

View File

@ -37,29 +37,31 @@ describe('BigQuery Ingestion', () => {
goToAddNewServicePage(SERVICE_TYPE.Database);
const connectionInput = () => {
const clientEmail = Cypress.env('bigqueryClientEmail');
cy.get('.form-group > #root\\/type')
cy.get('.form-group > #root\\/credentials\\/gcsConfig\\/type')
.scrollIntoView()
.type('service_account');
checkServiceFieldSectionHighlighting('type');
cy.get('#root\\/projectId')
cy.get('#root\\/credentials\\/gcsConfig\\/projectId')
.scrollIntoView()
.type(Cypress.env('bigqueryProjectId'));
checkServiceFieldSectionHighlighting('projectId');
cy.get('#root\\/privateKeyId')
cy.get('#root\\/credentials\\/gcsConfig\\/privateKeyId')
.scrollIntoView()
.type(Cypress.env('bigqueryPrivateKeyId'));
checkServiceFieldSectionHighlighting('privateKeyId');
cy.get('#root\\/privateKey')
cy.get('#root\\/credentials\\/gcsConfig\\/privateKey')
.scrollIntoView()
.type(Cypress.env('bigqueryPrivateKey'));
checkServiceFieldSectionHighlighting('privateKey');
cy.get('#root\\/clientEmail').scrollIntoView().type(clientEmail);
cy.get('#root\\/credentials\\/gcsConfig\\/clientEmail')
.scrollIntoView()
.type(clientEmail);
checkServiceFieldSectionHighlighting('clientEmail');
cy.get('#root\\/clientId')
cy.get('#root\\/credentials\\/gcsConfig\\/clientId')
.scrollIntoView()
.type(Cypress.env('bigqueryClientId'));
checkServiceFieldSectionHighlighting('clientId');
cy.get('#root\\/clientX509CertUrl')
cy.get('#root\\/credentials\\/gcsConfig\\/clientX509CertUrl')
.scrollIntoView()
.type(
`https://www.googleapis.com/robot/v1/metadata/x509/${encodeURIComponent(
@ -71,10 +73,10 @@ describe('BigQuery Ingestion', () => {
.scrollIntoView()
.click();
checkServiceFieldSectionHighlighting('taxonomyProjectID');
cy.get('#root\\/taxonomyProjectID_0')
cy.get('#root\\/taxonomyProjectID\\/0')
.scrollIntoView()
.type(Cypress.env('bigqueryProjectIdTaxonomy'));
checkServiceFieldSectionHighlighting('taxonomyProjectID');
// checkServiceFieldSectionHighlighting('taxonomyProjectID');
};
const addIngestionInput = () => {

View File

@ -41,10 +41,10 @@ describe('Superset Ingestion', () => {
.click();
const connectionInput = () => {
cy.get('#root\\/username')
cy.get('#root\\/connection\\/username')
.scrollIntoView()
.type(Cypress.env('supersetUsername'));
cy.get('#root\\/password')
cy.get('#root\\/connection\\/password')
.scrollIntoView()
.type(Cypress.env('supersetPassword'));
cy.get('#root\\/hostPort')

View File

@ -23,9 +23,10 @@
"@github/g-emoji-element": "^1.1.5",
"@okta/okta-auth-js": "^6.4.0",
"@okta/okta-react": "^6.4.3",
"@rjsf/antd": "^5.0.0-beta.12",
"@rjsf/core": "^4.1.1",
"@rjsf/utils": "^5.0.0-beta.12",
"@rjsf/antd": "5.4.0",
"@rjsf/core": "5.4.0",
"@rjsf/utils": "5.4.0",
"@rjsf/validator-ajv8": "5.4.0",
"@toast-ui/react-editor": "^3.1.8",
"analytics": "^0.8.1",
"antd": "4.24.0",

View File

@ -15,6 +15,7 @@ const $RefParser = require('@apidevtools/json-schema-ref-parser');
const path = require('path');
const fs = require('fs');
const fse = require('fs-extra');
const process = require('process');
const cwd = process.cwd();
@ -34,13 +35,15 @@ const globalParserOptions = {
},
};
const parser = new $RefParser(globalParserOptions);
async function parseSchema(filePath, destPath) {
try {
const fileDir = `${cwd}/${path.dirname(filePath)}`;
const fileName = path.basename(filePath);
process.chdir(fileDir);
const parser = new $RefParser(globalParserOptions);
const schema = await parser.parse(fileName);
const parsedSchema = await parser.parse(fileName);
const schema = await parser.dereference(parsedSchema);
const api = await parser.bundle(schema);
const dirname = `${cwd}/${path.dirname(destPath)}`;
if (!fs.existsSync(dirname)) {

View File

@ -12,7 +12,9 @@
*/
import { Card, Space, Typography } from 'antd';
import { AxiosError } from 'axios';
import ResizablePanels from 'components/common/ResizablePanels/ResizablePanels';
import { HTTP_STATUS_CODE } from 'constants/auth.constants';
import {
SERVICE_DEFAULT_ERROR_MAP,
STEPS_FOR_ADD_SERVICE,
@ -23,6 +25,7 @@ import { capitalize, isEmpty, isUndefined } from 'lodash';
import { LoadingState } from 'Models';
import React, { useEffect, useState } from 'react';
import { useHistory } from 'react-router-dom';
import { showErrorToast } from 'utils/ToastUtils';
import { getServiceDetailsPath } from '../../constants/constants';
import { GlobalSettingsMenuCategory } from '../../constants/GlobalSettings.constants';
import { delimiterRegex, nameWithSpace } from '../../constants/regex.constants';
@ -174,6 +177,19 @@ const AddService = ({
await fetchAirflowStatus();
} catch (error) {
if (
(error as AxiosError).response?.status === HTTP_STATUS_CODE.CONFLICT
) {
showErrorToast(
t('server.entity-already-exist', {
entity: t('label.service'),
name: serviceName,
})
);
return;
}
return error;
} finally {
setSaveServiceState('initial');
@ -276,8 +292,8 @@ const AddService = ({
status={saveServiceState}
onCancel={handleConnectionDetailsBackClick}
onFocus={handleFieldFocus}
onSave={(e) => {
handleConfigUpdate(e.formData);
onSave={async (e) => {
e.formData && (await handleConfigUpdate(e.formData));
}}
/>
)}

View File

@ -12,7 +12,7 @@
*/
import { PlusOutlined } from '@ant-design/icons';
import { ArrayFieldTemplateProps } from '@rjsf/core';
import { ArrayFieldTemplateProps } from '@rjsf/utils';
import { Button } from 'antd';
import classNames from 'classnames';
import { t } from 'i18next';

View File

@ -1,3 +1,5 @@
import { RegistryFieldsType } from '@rjsf/utils';
/*
* Copyright 2023 Collate.
* Licensed under the Apache License, Version 2.0 (the "License");
@ -14,6 +16,6 @@ const CustomDescriptionField = () => {
return null;
};
export const customFields = {
export const customFields: RegistryFieldsType = {
DescriptionField: CustomDescriptionField,
};

View File

@ -12,7 +12,7 @@
*/
import { PlusOutlined } from '@ant-design/icons';
import { ObjectFieldTemplateProps } from '@rjsf/core';
import { ObjectFieldTemplateProps } from '@rjsf/utils';
import { Button, Space } from 'antd';
import classNames from 'classnames';
import { isUndefined } from 'lodash';

View File

@ -11,7 +11,8 @@
* limitations under the License.
*/
import { ISubmitEvent } from '@rjsf/core';
import { IChangeEvent } from '@rjsf/core';
import validator from '@rjsf/validator-ajv8';
import { StorageServiceType } from 'generated/entity/data/container';
import { cloneDeep, isNil } from 'lodash';
import { LoadingState } from 'Models';
@ -41,8 +42,8 @@ interface Props {
serviceType: string;
serviceCategory: ServiceCategory;
status: LoadingState;
onFocus: (fieldName: string) => void;
onSave: (data: ISubmitEvent<ConfigData>) => void;
onFocus: (id: string) => void;
onSave: (data: IChangeEvent<ConfigData>) => Promise<void>;
disableTestConnection?: boolean;
onCancel?: () => void;
}
@ -63,9 +64,10 @@ const ConnectionConfigForm: FunctionComponent<Props> = ({
? ((data as ServicesType).connection?.config as ConfigData)
: ({} as ConfigData);
const handleSave = (data: ISubmitEvent<ConfigData>) => {
const handleSave = async (data: IChangeEvent<ConfigData>) => {
const updatedFormData = formatFormDataForSubmit(data.formData);
onSave({ ...data, formData: updatedFormData });
await onSave({ ...data, formData: updatedFormData });
};
const getConfigFields = () => {
@ -132,6 +134,7 @@ const ConnectionConfigForm: FunctionComponent<Props> = ({
serviceType={serviceType}
status={status}
uiSchema={connSch.uiSchema}
validator={validator}
onCancel={onCancel}
onFocus={onFocus}
onSubmit={handleSave}

View File

@ -11,7 +11,7 @@
* limitations under the License.
*/
import { ISubmitEvent } from '@rjsf/core';
import { IChangeEvent } from '@rjsf/core';
import { LoadingState, ServicesData } from 'Models';
import React, { useState } from 'react';
import { useHistory } from 'react-router-dom';
@ -34,7 +34,7 @@ interface ServiceConfigProps {
serviceCategory: ServiceCategory
) => Promise<void>;
disableTestConnection: boolean;
onFocus: (fieldName: string) => void;
onFocus: (id: string) => void;
}
export const Field = ({ children }: { children: React.ReactNode }) => {
@ -53,21 +53,24 @@ const ServiceConfig = ({
const history = useHistory();
const [status, setStatus] = useState<LoadingState>('initial');
const handleOnSaveClick = (e: ISubmitEvent<ConfigData>) => {
const handleOnSaveClick = async (e: IChangeEvent<ConfigData>) => {
if (!e.formData) {
return;
}
setStatus('waiting');
handleUpdate(e.formData, serviceCategory)
.then(() => {
setTimeout(() => {
setStatus('success');
history.push(getPathByServiceFQN(serviceCategory, serviceFQN));
}, 200);
})
.finally(() => {
setTimeout(() => {
setStatus('initial');
}, 500);
});
try {
await handleUpdate(e.formData, serviceCategory);
setTimeout(() => {
setStatus('success');
history.push(getPathByServiceFQN(serviceCategory, serviceFQN));
}, 200);
} catch (err) {
// Nothing here
} finally {
setTimeout(() => {
setStatus('initial');
}, 500);
}
};
const onCancel = () => {

View File

@ -169,7 +169,7 @@ const ServiceConnectionDetails = ({
: {};
return (
<Col span={12}>
<Col key={key} span={12}>
<Row>
<Col span={8}>
<Space size={0}>

View File

@ -15,7 +15,6 @@ import React from 'react';
import { LabelCountSkeletonProps } from '../../Skeleton.interfaces';
const LabelCountSkeleton = ({
key,
isSelect,
isLabel,
isCount,
@ -27,7 +26,7 @@ const LabelCountSkeleton = ({
...props
}: LabelCountSkeletonProps) => {
return (
<Row justify="space-between" key={key}>
<Row justify="space-between">
{isSelect || isLabel ? (
<Col span={firstColSize}>
<div className="w-48 flex">

View File

@ -12,27 +12,26 @@
*/
import { CheckOutlined } from '@ant-design/icons';
import Form from '@rjsf/antd';
import CoreForm, { AjvError, FormProps, IChangeEvent } from '@rjsf/core';
import validateFormData from '@rjsf/core/lib/validate';
import { Button as AntDButton } from 'antd';
import Form, { FormProps, IChangeEvent } from '@rjsf/core';
import { Button } from 'antd';
import classNames from 'classnames';
import { ArrayFieldTemplate } from 'components/JSONSchemaTemplate/ArrayFieldTemplate';
import { customFields } from 'components/JSONSchemaTemplate/CustomFields';
import { ObjectFieldTemplate } from 'components/JSONSchemaTemplate/ObjectFieldTemplate';
import { ServiceCategory } from 'enums/service.enum';
import { useAirflowStatus } from 'hooks/useAirflowStatus';
import { t } from 'i18next';
import { isEmpty, isUndefined, startCase } from 'lodash';
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 { ConfigData } from '../../../interface/service.interface';
import { formatFormDataForRender } from '../../../utils/JSONSchemaFormUtils';
import { ArrayFieldTemplate } from '../../JSONSchemaTemplate/ArrayFieldTemplate';
import { ObjectFieldTemplate } from '../../JSONSchemaTemplate/ObjectFieldTemplate';
import Loader from '../../Loader/Loader';
import TestConnection from '../TestConnection/TestConnection';
interface Props extends FormProps<ConfigData> {
interface Props extends FormProps {
okText: string;
cancelText: string;
disableTestConnection: boolean;
@ -42,7 +41,6 @@ interface Props extends FormProps<ConfigData> {
showFormHeader?: boolean;
status?: LoadingState;
onCancel?: () => void;
onFocus: (fieldName: string) => void;
}
const FormBuilder: FunctionComponent<Props> = ({
@ -64,7 +62,7 @@ const FormBuilder: FunctionComponent<Props> = ({
}: Props) => {
const { isAirflowAvailable } = useAirflowStatus();
const formRef = useRef<CoreForm<ConfigData>>();
const formRef = useRef<Form<ConfigData>>(null);
const [localFormData, setLocalFormData] = useState<ConfigData | undefined>(
formatFormDataForRender(formData ?? {})
);
@ -97,39 +95,20 @@ const FormBuilder: FunctionComponent<Props> = ({
}
};
const handleSubmit = () => {
if (formRef.current) {
formRef.current.submit();
}
};
const handleChange = (updatedData: ConfigData) => {
setLocalFormData(updatedData);
};
const transformErrors = (errors: AjvError[]) =>
errors.map((error) => {
const fieldName = error.params.missingProperty;
const customMessage = `${startCase(fieldName)} is required`;
error.message = customMessage;
return error;
});
const handleRequiredFieldsValidation = () => {
const validationObject = validateFormData(localFormData, schema);
const isFormValid = isEmpty(validationObject.errors);
if (!isFormValid) {
formRef.current?.submit();
}
return Boolean(formRef.current?.validateForm());
};
return isFormValid;
const handleFormChange = (e: IChangeEvent<ConfigData>) => {
setLocalFormData(e.formData);
props.onChange && props.onChange(e);
};
return (
<Form
ArrayFieldTemplate={ArrayFieldTemplate}
ObjectFieldTemplate={ObjectFieldTemplate}
focusOnFirstError
noHtml5Validate
omitExtraData
className={classNames('rjsf', props.className, {
'no-header': !showFormHeader,
})}
@ -140,12 +119,13 @@ const FormBuilder: FunctionComponent<Props> = ({
ref={formRef}
schema={schema}
showErrorList={false}
templates={{
ArrayFieldTemplate: ArrayFieldTemplate,
ObjectFieldTemplate: ObjectFieldTemplate,
}}
transformErrors={transformErrors}
uiSchema={uiSchema}
onChange={(e: IChangeEvent) => {
handleChange(e.formData);
props.onChange && props.onChange(e);
}}
onChange={handleFormChange}
onFocus={onFocus}
onSubmit={onSubmit}
{...props}>
@ -176,31 +156,31 @@ const FormBuilder: FunctionComponent<Props> = ({
<div className="tw-mt-6 tw-flex tw-justify-between">
<div />
<div className="tw-text-right" data-testid="buttons">
<AntDButton type="link" onClick={handleCancel}>
<Button type="link" onClick={handleCancel}>
{cancelText}
</AntDButton>
</Button>
{status === 'waiting' ? (
<AntDButton
<Button
disabled
className="p-x-md p-y-xxs h-auto rounded-6"
type="primary">
<Loader size="small" type="white" />
</AntDButton>
</Button>
) : status === 'success' ? (
<AntDButton
<Button
disabled
className="p-x-md p-y-xxs h-auto rounded-6"
type="primary">
<CheckOutlined />
</AntDButton>
</Button>
) : (
<AntDButton
<Button
className="font-medium p-x-md p-y-xxs h-auto rounded-6"
data-testid="submit-btn"
type="primary"
onClick={handleSubmit}>
htmlType="submit"
type="primary">
{okText}
</AntDButton>
</Button>
)}
</div>
</div>

View File

@ -61,28 +61,9 @@ const AddServicePage = () => {
setAddIngestion(value);
};
const onAddServiceSave = (data: DataObj) => {
return new Promise<void>((resolve, reject) => {
postService(serviceCategory, data)
.then((res) => {
if (res) {
setNewServiceData(res);
resolve();
} else {
showErrorToast(
t('server.create-entity-error', { entity: t('label.service') })
);
reject();
}
})
.catch((err: AxiosError) => {
showErrorToast(
err,
t('server.create-entity-error', { entity: t('label.service') })
);
reject();
});
});
const onAddServiceSave = async (data: DataObj) => {
const res = await postService(serviceCategory, data);
setNewServiceData(res);
};
const onIngestionDeploy = (id?: string) => {

View File

@ -0,0 +1,371 @@
{
"$id": "https://open-metadata.org/schema/entity/services/connections/dashboard/supersetConnection.json",
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "SupersetConnection",
"description": "Superset Connection Config",
"type": "object",
"javaType": "org.openmetadata.schema.services.connections.dashboard.SupersetConnection",
"definitions": {
"supersetType": {
"description": "Superset service type",
"type": "string",
"enum": ["Superset"],
"default": "Superset"
}
},
"properties": {
"type": {
"title": "Service Type",
"description": "Service Type",
"default": "Superset",
"type": "string",
"enum": ["Superset"]
},
"hostPort": {
"expose": true,
"title": "Host and Port",
"description": "URL for the superset instance.",
"type": "string",
"format": "uri",
"default": "http://localhost:8088"
},
"connection": {
"title": "Superset Connection",
"description": "Choose between API or database connection fetch metadata from superset.",
"oneOf": [
{
"$id": "https://open-metadata.org/schema/entity/services/connections/dashboard/supersetApiConnection.json",
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "SupersetApiConnection",
"description": "Superset API Connection Config",
"type": "object",
"definitions": {
"apiProvider": {
"title": "Provider",
"description": "Authentication provider for the Superset service. For basic user/password authentication, the default value `db` can be used. This parameter is used internally to connect to Superset's REST API.",
"type": "string",
"enum": ["db", "ldap"],
"default": "db"
}
},
"properties": {
"provider": {
"title": "Provider",
"description": "Authentication provider for the Superset service. For basic user/password authentication, the default value `db` can be used. This parameter is used internally to connect to Superset's REST API.",
"default": "db",
"type": "string",
"enum": ["db", "ldap"]
},
"username": {
"title": "Username",
"description": "Username for Superset.",
"type": "string"
},
"password": {
"title": "Password",
"description": "Password for Superset.",
"type": "string",
"format": "password"
}
},
"additionalProperties": false,
"required": ["provider", "password", "username"]
},
{
"$id": "https://open-metadata.org/schema/entity/services/connections/database/postgresConnection.json",
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "PostgresConnection",
"description": "Postgres Database Connection Config",
"type": "object",
"javaType": "org.openmetadata.schema.services.connections.database.PostgresConnection",
"definitions": {
"postgresType": {
"description": "Service type.",
"type": "string",
"enum": ["Postgres"],
"default": "Postgres"
},
"postgresScheme": {
"description": "SQLAlchemy driver scheme options.",
"type": "string",
"enum": ["postgresql+psycopg2"],
"default": "postgresql+psycopg2"
}
},
"properties": {
"type": {
"title": "Service Type",
"description": "Service Type",
"default": "Postgres",
"type": "string",
"enum": ["Postgres"]
},
"scheme": {
"title": "Connection Scheme",
"description": "SQLAlchemy driver scheme options.",
"default": "postgresql+psycopg2",
"type": "string",
"enum": ["postgresql+psycopg2"]
},
"username": {
"title": "Username",
"description": "Username to connect to Postgres. This user should have privileges to read all the metadata in Postgres.",
"type": "string"
},
"password": {
"title": "Password",
"description": "Password to connect to Postgres.",
"type": "string",
"format": "password"
},
"hostPort": {
"title": "Host and Port",
"description": "Host and port of the Postgres service.",
"type": "string"
},
"database": {
"title": "Database",
"description": "Database of the data source. This is optional parameter, if you would like to restrict the metadata reading to a single database. When left blank, OpenMetadata Ingestion attempts to scan all the databases.",
"type": "string"
},
"sslMode": {
"title": "SSL Mode",
"description": "SSL Mode to connect to postgres database.",
"enum": [
"disable",
"allow",
"prefer",
"require",
"verify-ca",
"verify-full"
],
"default": "disable"
},
"sslConfig": {
"description": "Client SSL configuration",
"javaType": "org.openmetadata.schema.security.ssl.SSLConfig",
"oneOf": [
{
"$id": "https://open-metadata.org/schema/security/ssl/validateSSLClientConfig.json",
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "ValidateSSLClientConfig",
"description": "OpenMetadata Client configured to validate SSL certificates.",
"type": "object",
"javaType": "org.openmetadata.schema.security.ssl.ValidateSSLClientConfig",
"properties": {
"certificatePath": {
"description": "CA certificate path. E.g., /path/to/public.cert. Will be used if Verify SSL is set to `validate`.",
"type": "string"
}
},
"additionalProperties": false
}
]
},
"classificationName": {
"title": "Classification Name",
"description": "Custom OpenMetadata Classification name for Postgres policy tags.",
"type": "string",
"default": "PostgresPolicyTags"
},
"ingestAllDatabases": {
"title": "Ingest All Databases",
"description": "Ingest data from all databases in Postgres. You can use databaseFilterPattern on top of this.",
"type": "boolean",
"default": false
},
"connectionOptions": {
"title": "Connection Options",
"javaType": "org.openmetadata.schema.services.connections.database.ConnectionOptions",
"description": "Additional connection options to build the URL that can be sent to service during the connection.",
"additionalProperties": {
"type": "string"
}
},
"connectionArguments": {
"title": "Connection Arguments",
"javaType": "org.openmetadata.schema.services.connections.database.ConnectionArguments",
"description": "Additional connection arguments such as security or protocol configs that can be sent to service during connection.",
"additionalProperties": {
".{1,}": {
"type": "string"
}
}
},
"supportsMetadataExtraction": {
"title": "Supports Metadata Extraction",
"description": "Supports Metadata Extraction.",
"type": "boolean",
"default": true
},
"supportsUsageExtraction": {
"description": "Supports Usage Extraction.",
"type": "boolean",
"default": true
},
"supportsLineageExtraction": {
"description": "Supports Lineage Extraction.",
"type": "boolean",
"default": true
},
"supportsDBTExtraction": {
"description": "Supports DBT Extraction.",
"type": "boolean",
"default": true
},
"supportsProfiler": {
"title": "Supports Profiler",
"description": "Supports Profiler",
"type": "boolean",
"default": true
},
"supportsDatabase": {
"title": "Supports Database",
"description": "The source service supports the database concept in its hierarchy",
"type": "boolean",
"default": true
},
"supportsQueryComment": {
"title": "Supports Query Comment",
"description": "For Database Services using SQLAlchemy, True to enable running a comment for all queries run from OpenMetadata.",
"type": "boolean",
"default": true
}
},
"additionalProperties": false,
"required": ["hostPort", "username", "database"]
},
{
"$id": "https://open-metadata.org/schema/entity/services/connections/database/mysqlConnection.json",
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "MysqlConnection",
"description": "Mysql Database Connection Config",
"type": "object",
"javaType": "org.openmetadata.schema.services.connections.database.MysqlConnection",
"definitions": {
"mySQLType": {
"description": "Service type.",
"type": "string",
"enum": ["Mysql"],
"default": "Mysql"
},
"mySQLScheme": {
"description": "SQLAlchemy driver scheme options.",
"type": "string",
"enum": ["mysql+pymysql"],
"default": "mysql+pymysql"
}
},
"properties": {
"type": {
"title": "Service Type",
"description": "Service Type",
"default": "Mysql",
"type": "string",
"enum": ["Mysql"]
},
"scheme": {
"title": "Connection Scheme",
"description": "SQLAlchemy driver scheme options.",
"default": "mysql+pymysql",
"type": "string",
"enum": ["mysql+pymysql"]
},
"username": {
"title": "Username",
"description": "Username to connect to MySQL. This user should have privileges to read all the metadata in Mysql.",
"type": "string"
},
"password": {
"title": "Password",
"description": "Password to connect to MySQL.",
"type": "string",
"format": "password"
},
"hostPort": {
"title": "Host and Port",
"description": "Host and port of the MySQL service.",
"type": "string"
},
"databaseName": {
"title": "Database Name",
"description": "Optional name to give to the database in OpenMetadata. If left blank, we will use default as the database name.",
"type": "string"
},
"databaseSchema": {
"title": "Database Schema",
"description": "Database Schema of the data source. This is optional parameter, if you would like to restrict the metadata reading to a single schema. When left blank, OpenMetadata Ingestion attempts to scan all the schemas.",
"type": "string"
},
"sslCA": {
"title": "SSL CA",
"description": "Provide the path to ssl ca file",
"type": "string"
},
"sslCert": {
"title": "SSL Client Certificate File",
"description": "Provide the path to ssl client certificate file (ssl_cert)",
"type": "string"
},
"sslKey": {
"title": "SSL Client Key File",
"description": "Provide the path to ssl client certificate file (ssl_key)",
"type": "string"
},
"connectionOptions": {
"title": "Connection Options",
"javaType": "org.openmetadata.schema.services.connections.database.ConnectionOptions",
"description": "Additional connection options to build the URL that can be sent to service during the connection.",
"additionalProperties": {
"type": "string"
}
},
"connectionArguments": {
"title": "Connection Arguments",
"javaType": "org.openmetadata.schema.services.connections.database.ConnectionArguments",
"description": "Additional connection arguments such as security or protocol configs that can be sent to service during connection.",
"additionalProperties": {
".{1,}": {
"type": "string"
}
}
},
"supportsMetadataExtraction": {
"title": "Supports Metadata Extraction",
"description": "Supports Metadata Extraction.",
"type": "boolean",
"default": true
},
"supportsDBTExtraction": {
"description": "Supports DBT Extraction.",
"type": "boolean",
"default": true
},
"supportsProfiler": {
"title": "Supports Profiler",
"description": "Supports Profiler",
"type": "boolean",
"default": true
},
"supportsQueryComment": {
"title": "Supports Query Comment",
"description": "For Database Services using SQLAlchemy, True to enable running a comment for all queries run from OpenMetadata.",
"type": "boolean",
"default": true
}
},
"additionalProperties": false,
"required": ["hostPort", "username"]
}
]
},
"supportsMetadataExtraction": {
"title": "Supports Metadata Extraction",
"description": "Supports Metadata Extraction.",
"type": "boolean",
"default": true
}
},
"additionalProperties": false,
"required": ["hostPort", "connection"]
}

View File

@ -25,8 +25,8 @@ import modeConnection from '../jsons/connectionSchemas/connections/dashboard/mod
import powerBIConnection from '../jsons/connectionSchemas/connections/dashboard/powerBIConnection.json';
import quicksightConnection from '../jsons/connectionSchemas/connections/dashboard/quickSightConnection.json';
import redashConnection from '../jsons/connectionSchemas/connections/dashboard/redashConnection.json';
import supersetConnection from '../jsons/connectionSchemas/connections/dashboard/supersetConnection.json';
import tableauConnection from '../jsons/connectionSchemas/connections/dashboard/tableauConnection.json';
import supersetConnection from './ConnectionSchemas/SupersetConnection.json';
export const getDashboardURL = (config: DashboardConnection['config']) => {
return !isUndefined(config) && !isEmpty(config.hostPort)

View File

@ -10,6 +10,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ErrorTransformer } from '@rjsf/utils';
import {
Divider,
Form,
@ -27,6 +28,7 @@ import RichTextEditor from 'components/common/rich-text-editor/RichTextEditor';
import { RichTextEditorProp } from 'components/common/rich-text-editor/RichTextEditor.interface';
import SliderWithInput from 'components/SliderWithInput/SliderWithInput';
import { SliderWithInputProps } from 'components/SliderWithInput/SliderWithInput.interface';
import { compact, startCase } from 'lodash';
import React, { Fragment, ReactNode } from 'react';
import i18n from './i18next/LocalUtil';
@ -164,3 +166,26 @@ export const getField = (field: FieldProp) => {
export const generateFormFields = (fields: FieldProp[]) => {
return <>{fields.map((field) => getField(field))}</>;
};
export const transformErrors: ErrorTransformer = (errors) => {
const errorRet = errors.map((error) => {
const { property } = error;
const id = 'root' + property?.replaceAll('.', '/');
// If element is not present in DOM, ignore error
if (document.getElementById(id)) {
const fieldName = error.params?.missingProperty;
if (fieldName) {
const customMessage = i18n.t('message.field-text-is-required', {
fieldText: startCase(fieldName),
});
error.message = customMessage;
return error;
}
}
return null;
});
return compact(errorRet);
};

View File

@ -108,9 +108,9 @@
resize-observer-polyfill "^1.5.1"
"@apidevtools/json-schema-ref-parser@^9.0.9":
version "9.0.9"
resolved "https://registry.yarnpkg.com/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.0.9.tgz#d720f9256e3609621280584f2b47ae165359268b"
integrity sha512-GBD2Le9w2+lVFoc4vswGI/TjkNIZSVp7+9xPf+X3uidBfWnAeUWmquteSyt0+VCrhNMWj/FTABISQrD3Z/YA+w==
version "9.1.2"
resolved "https://registry.yarnpkg.com/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.1.2.tgz#8ff5386b365d4c9faa7c8b566ff16a46a577d9b8"
integrity sha512-r1w81DpR+KyRWd3f+rk6TNqMgedmAxZP5v5KWlXQWlgMUUtyEJch0DKEci1SorPMiSeM8XPl7MZ3miJ60JIpQg==
dependencies:
"@jsdevtools/ono" "^7.1.3"
"@types/json-schema" "^7.0.6"
@ -2883,30 +2883,31 @@
classcat "^5.0.3"
zustand "^4.1.1"
"@rjsf/antd@^5.0.0-beta.12":
version "5.0.0-beta.12"
resolved "https://registry.yarnpkg.com/@rjsf/antd/-/antd-5.0.0-beta.12.tgz#357579a120af186a18b894c9f5c4b2c111f6b536"
integrity sha512-2U5x4DV3TeVQFjX6YJgLBw1ND/sJoZlUyePTyygOPqltto4wPUdXdJS7m5+zRinowlCZrcr9l2u7RCwA4Bnpwg==
"@rjsf/core@^4.1.1":
version "4.1.1"
resolved "https://registry.yarnpkg.com/@rjsf/core/-/core-4.1.1.tgz#6ba23585b0fc891247e795bed1ab2c6ce44755fa"
integrity sha512-/R37fLwnhAavQVlYoILwYIha0ymgKtMWdtehgiODcf++rl4/jq38RjXhqF5wQT9uOYAHmtutokJsTFei1VY4bw==
"@rjsf/antd@5.4.0":
version "5.4.0"
resolved "https://registry.yarnpkg.com/@rjsf/antd/-/antd-5.4.0.tgz#194a784272a5782aeea35d80e25ee5a5b076bfa4"
integrity sha512-mC96NITW/CFfPtF/J8EdxStaP/Jc/EfEaIB00GyUWbZUYjXFJ+cAeltfXFQPOrrlC6gFCDRbkZexRW+Hazv6eA==
dependencies:
"@types/json-schema" "^7.0.7"
ajv "^6.7.0"
core-js-pure "^3.6.5"
json-schema-merge-allof "^0.6.0"
jsonpointer "^5.0.0"
classnames "^2.3.2"
lodash "^4.17.15"
nanoid "^3.1.23"
prop-types "^15.7.2"
react-is "^16.9.0"
lodash-es "^4.17.15"
rc-picker "^2.6.11"
"@rjsf/utils@^5.0.0-beta.12":
version "5.0.0-beta.12"
resolved "https://registry.yarnpkg.com/@rjsf/utils/-/utils-5.0.0-beta.12.tgz#a03f9da784896744a1908e8fd4dec0a36e90704b"
integrity sha512-RH965WMGp3Z25iQ2PTn+BFkQusJuvG0e2fl8V+wx1N21EkrZnW2+twouWUmqNqHwke2Z0OOimF5picZVgobl7g==
"@rjsf/core@5.4.0":
version "5.4.0"
resolved "https://registry.yarnpkg.com/@rjsf/core/-/core-5.4.0.tgz#d75e42a5b8fe9af7b62b04fd8b93629f4e1fa5a7"
integrity sha512-0DmEmTCkpnGYju3HycEJb0MoYCp7Swd/R1abllmUg0sqCeffdtYSI4pWL3M1gwyMFT+JWgZWDYQ2CqJUZ9n0NA==
dependencies:
lodash "^4.17.15"
lodash-es "^4.17.15"
markdown-to-jsx "^7.2.0"
nanoid "^3.3.4"
prop-types "^15.7.2"
"@rjsf/utils@5.4.0":
version "5.4.0"
resolved "https://registry.yarnpkg.com/@rjsf/utils/-/utils-5.4.0.tgz#ce9d54882e3214c0fdc9f278dfd8570f1743d50c"
integrity sha512-R9vgwqFrPV7+6JzTzHzYeULIH4FEdp10zixTxhgh4WSEbTP9L9leei9O4fa924EFGHuBFgtZeH2cwL20pr5cUw==
dependencies:
json-schema-merge-allof "^0.8.1"
jsonpointer "^5.0.1"
@ -2914,6 +2915,16 @@
lodash-es "^4.17.15"
react-is "^18.2.0"
"@rjsf/validator-ajv8@5.4.0":
version "5.4.0"
resolved "https://registry.yarnpkg.com/@rjsf/validator-ajv8/-/validator-ajv8-5.4.0.tgz#e26ac6f7dff47ae5ca00bd02b49e0fc1e78d54da"
integrity sha512-OjWPQGU2tu+KzOgZqqsyhdiamyZPLXsChgdIQJ2sz4gMuedL2LgfNfOc6STcf01kXMXft2MoDZ9lHGlAHlv2Vw==
dependencies:
ajv "^8.12.0"
ajv-formats "^2.1.1"
lodash "^4.17.15"
lodash-es "^4.17.15"
"@sheerun/mutationobserver-shim@^0.3.2":
version "0.3.3"
resolved "https://registry.yarnpkg.com/@sheerun/mutationobserver-shim/-/mutationobserver-shim-0.3.3.tgz#5405ee8e444ed212db44e79351f0c70a582aae25"
@ -4352,7 +4363,7 @@ ajv-keywords@^5.0.0:
dependencies:
fast-deep-equal "^3.1.3"
ajv@^6.10.0, ajv@^6.12.2, ajv@^6.12.4, ajv@^6.12.5, ajv@^6.4.0, ajv@^6.7.0:
ajv@^6.10.0, ajv@^6.12.2, ajv@^6.12.4, ajv@^6.12.5, ajv@^6.4.0:
version "6.12.6"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
@ -4382,6 +4393,16 @@ ajv@^8.0.1:
require-from-string "^2.0.2"
uri-js "^4.2.2"
ajv@^8.12.0:
version "8.12.0"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.12.0.tgz#d1a0527323e22f53562c567c00991577dfbe19d1"
integrity sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==
dependencies:
fast-deep-equal "^3.1.1"
json-schema-traverse "^1.0.0"
require-from-string "^2.0.2"
uri-js "^4.2.2"
amdefine@>=0.0.4:
version "1.0.1"
resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5"
@ -5660,7 +5681,7 @@ compute-gcd@^1.2.1:
validate.io-function "^1.0.2"
validate.io-integer-array "^1.0.0"
compute-lcm@^1.1.0, compute-lcm@^1.1.2:
compute-lcm@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/compute-lcm/-/compute-lcm-1.1.2.tgz#9107c66b9dca28cefb22b4ab4545caac4034af23"
integrity sha512-OFNPdQAXnQhDSKioX8/XYT6sdUlXwpeMjfd6ApxMJfyZ4GxmLR1xvMERctlYhlHwIiz6CSpBc2+qYKjHGZw4TQ==
@ -5777,7 +5798,7 @@ core-js-pure@^3.16.0:
resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.16.1.tgz#b997df2669c957a5b29f06e95813a171f993592e"
integrity sha512-TyofCdMzx0KMhi84mVRS8rL1XsRk2SPUNz2azmth53iRN0/08Uim9fdhQTaZTG1LqaXHYVci4RDHka6WrXfnvg==
core-js-pure@^3.20.2, core-js-pure@^3.6.5:
core-js-pure@^3.20.2:
version "3.21.1"
resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.21.1.tgz#8c4d1e78839f5f46208de7230cebfb72bc3bdb51"
integrity sha512-12VZfFIu+wyVbBebyHmRTuEE/tZrB4tJToWcwAMcsp3h4+sHR+fMJWbKpYiCRWlhFBq+KNyO8rIV9rTkeVmznQ==
@ -9455,15 +9476,6 @@ json-schema-compare@^0.2.2:
dependencies:
lodash "^4.17.4"
json-schema-merge-allof@^0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/json-schema-merge-allof/-/json-schema-merge-allof-0.6.0.tgz#64d48820fec26b228db837475ce3338936bf59a5"
integrity sha512-LEw4VMQVRceOPLuGRWcxW5orTTiR9ZAtqTAe4rQUjNADTeR81bezBVFa0MqIwp0YmHIM1KkhSjZM7o+IQhaPbQ==
dependencies:
compute-lcm "^1.1.0"
json-schema-compare "^0.2.2"
lodash "^4.17.4"
json-schema-merge-allof@^0.8.1:
version "0.8.1"
resolved "https://registry.yarnpkg.com/json-schema-merge-allof/-/json-schema-merge-allof-0.8.1.tgz#ed2828cdd958616ff74f932830a26291789eaaf2"
@ -9562,11 +9574,6 @@ jsonpath-plus@^6.0.1:
resolved "https://registry.yarnpkg.com/jsonpath-plus/-/jsonpath-plus-6.0.1.tgz#9a3e16cedadfab07a3d8dc4e8cd5df4ed8f49c4d"
integrity sha512-EvGovdvau6FyLexFH2OeXfIITlgIbgZoAZe3usiySeaIDm5QS+A10DKNpaPBBqqRSZr2HN6HVNXxtwUAr2apEw==
jsonpointer@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-5.0.0.tgz#f802669a524ec4805fa7389eadbc9921d5dc8072"
integrity sha512-PNYZIdMjVIvVgDSYKTT63Y+KZ6IZvGRNNWcxwD+GNnUz1MKPfv30J8ueCjdwcN0nDx2SlshgyB7Oy0epAzVRRg==
jsonpointer@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-5.0.1.tgz#2110e0af0900fd37467b5907ecd13a7884a1b559"
@ -9958,6 +9965,11 @@ mark.js@^8.11.1:
resolved "https://registry.yarnpkg.com/mark.js/-/mark.js-8.11.1.tgz#180f1f9ebef8b0e638e4166ad52db879beb2ffc5"
integrity sha1-GA8fnr74sOY45BZq1S24eb6y/8U=
markdown-to-jsx@^7.2.0:
version "7.2.0"
resolved "https://registry.yarnpkg.com/markdown-to-jsx/-/markdown-to-jsx-7.2.0.tgz#e7b46b65955f6a04d48a753acd55874a14bdda4b"
integrity sha512-3l4/Bigjm4bEqjCR6Xr+d4DtM1X6vvtGsMGSjJYyep8RjjIvcWtrXBS8Wbfe1/P+atKNMccpsraESIaWVplzVg==
marked@^0.7.0:
version "0.7.0"
resolved "https://registry.yarnpkg.com/marked/-/marked-0.7.0.tgz#b64201f051d271b1edc10a04d1ae9b74bb8e5c0e"
@ -11786,6 +11798,20 @@ rc-pagination@~3.1.17:
"@babel/runtime" "^7.10.1"
classnames "^2.2.1"
rc-picker@^2.6.11:
version "2.7.2"
resolved "https://registry.yarnpkg.com/rc-picker/-/rc-picker-2.7.2.tgz#bf656ca274228c84b955dfaa7705738908cb900f"
integrity sha512-KbUKgbzgWVN5L+V9xhZDKSmseHIyFneBlmuMtMrZ9fU7Oypw6D+owS5kuUicIEV08Y17oXt8dUqauMeC5IFBPg==
dependencies:
"@babel/runtime" "^7.10.1"
classnames "^2.2.1"
date-fns "2.x"
dayjs "1.x"
moment "^2.24.0"
rc-trigger "^5.0.4"
rc-util "^5.4.0"
shallowequal "^1.1.0"
rc-picker@~2.6.11:
version "2.6.11"
resolved "https://registry.yarnpkg.com/rc-picker/-/rc-picker-2.6.11.tgz#d4a55e46480517cd1bfea5f5acd28b1d6be232d2"
@ -12110,7 +12136,7 @@ react-is@16.10.2:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.10.2.tgz#984120fd4d16800e9a738208ab1fba422d23b5ab"
integrity sha512-INBT1QEgtcCCgvccr5/86CfD71fw9EPmDxgiJX4I2Ddr6ZsV6iFXsuby+qWJPtmNuMY0zByTsG4468P7nHuNWA==
react-is@^16.12.0, react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.4, react-is@^16.8.6, react-is@^16.9.0:
react-is@^16.12.0, react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.4, react-is@^16.8.6:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==