mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-08-20 23:18:01 +00:00
This commit is contained in:
parent
67fb846dd9
commit
6c00b80f1d
@ -14,6 +14,7 @@
|
|||||||
import { AxiosResponse } from 'axios';
|
import { AxiosResponse } from 'axios';
|
||||||
import { isNil } from 'lodash';
|
import { isNil } from 'lodash';
|
||||||
import { ServiceOption } from 'Models';
|
import { ServiceOption } from 'Models';
|
||||||
|
import { ConfigData } from '../interface/service.interface';
|
||||||
import { getURLWithQueryFields } from '../utils/APIUtils';
|
import { getURLWithQueryFields } from '../utils/APIUtils';
|
||||||
import APIClient from './index';
|
import APIClient from './index';
|
||||||
|
|
||||||
@ -79,3 +80,15 @@ export const deleteService: Function = (
|
|||||||
): Promise<AxiosResponse> => {
|
): Promise<AxiosResponse> => {
|
||||||
return APIClient.delete(`/services/${serviceName}/${id}`);
|
return APIClient.delete(`/services/${serviceName}/${id}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const TestConnection = (
|
||||||
|
data: ConfigData,
|
||||||
|
type: string
|
||||||
|
): Promise<AxiosResponse> => {
|
||||||
|
const payload = {
|
||||||
|
connection: { config: data },
|
||||||
|
connectionType: type,
|
||||||
|
};
|
||||||
|
|
||||||
|
return APIClient.post(`/services/ingestionPipelines/testConnection`, payload);
|
||||||
|
};
|
||||||
|
@ -101,7 +101,7 @@ const SelectServiceType = ({
|
|||||||
theme="primary"
|
theme="primary"
|
||||||
variant="text"
|
variant="text"
|
||||||
onClick={onCancel}>
|
onClick={onCancel}>
|
||||||
<span>Back</span>
|
<span>Discard</span>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
|
@ -15,6 +15,7 @@ import { ISubmitEvent } from '@rjsf/core';
|
|||||||
import { cloneDeep, isNil } from 'lodash';
|
import { cloneDeep, isNil } from 'lodash';
|
||||||
import { LoadingState } from 'Models';
|
import { LoadingState } from 'Models';
|
||||||
import React, { FunctionComponent } from 'react';
|
import React, { FunctionComponent } from 'react';
|
||||||
|
import { TestConnection } from '../../axiosAPIs/serviceAPI';
|
||||||
import { ServiceCategory } from '../../enums/service.enum';
|
import { ServiceCategory } from '../../enums/service.enum';
|
||||||
import {
|
import {
|
||||||
DashboardConnection,
|
DashboardConnection,
|
||||||
@ -30,10 +31,12 @@ import {
|
|||||||
} from '../../generated/entity/services/messagingService';
|
} from '../../generated/entity/services/messagingService';
|
||||||
import { PipelineService } from '../../generated/entity/services/pipelineService';
|
import { PipelineService } from '../../generated/entity/services/pipelineService';
|
||||||
import { ConfigData } from '../../interface/service.interface';
|
import { ConfigData } from '../../interface/service.interface';
|
||||||
|
import jsonData from '../../jsons/en';
|
||||||
import { getDashboardConfig } from '../../utils/DashboardServiceUtils';
|
import { getDashboardConfig } from '../../utils/DashboardServiceUtils';
|
||||||
import { getDatabaseConfig } from '../../utils/DatabaseServiceUtils';
|
import { getDatabaseConfig } from '../../utils/DatabaseServiceUtils';
|
||||||
import { getMessagingConfig } from '../../utils/MessagingServiceUtils';
|
import { getMessagingConfig } from '../../utils/MessagingServiceUtils';
|
||||||
import { getPipelineConfig } from '../../utils/PipelineServiceUtils';
|
import { getPipelineConfig } from '../../utils/PipelineServiceUtils';
|
||||||
|
import { showErrorToast, showSuccessToast } from '../../utils/ToastUtils';
|
||||||
import FormBuilder from '../common/FormBuilder/FormBuilder';
|
import FormBuilder from '../common/FormBuilder/FormBuilder';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@ -62,6 +65,32 @@ const ConnectionConfigForm: FunctionComponent<Props> = ({
|
|||||||
.connection.config as ConfigData)
|
.connection.config as ConfigData)
|
||||||
: ({ pipelineUrl: (data as PipelineService).pipelineUrl } as ConfigData)
|
: ({ pipelineUrl: (data as PipelineService).pipelineUrl } as ConfigData)
|
||||||
: ({} as ConfigData);
|
: ({} as ConfigData);
|
||||||
|
|
||||||
|
const handleTestConnection = (formData: ConfigData) => {
|
||||||
|
return new Promise<void>((resolve, reject) => {
|
||||||
|
TestConnection(formData, 'Database')
|
||||||
|
.then((res) => {
|
||||||
|
// This api only responds with status 200 on success
|
||||||
|
// No data sent on api success
|
||||||
|
if (res.status === 200) {
|
||||||
|
showSuccessToast(
|
||||||
|
jsonData['api-success-messages']['test-connection-success']
|
||||||
|
);
|
||||||
|
resolve();
|
||||||
|
} else {
|
||||||
|
throw jsonData['api-error-messages']['unexpected-server-response'];
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
showErrorToast(
|
||||||
|
err,
|
||||||
|
jsonData['api-error-messages']['test-connection-error']
|
||||||
|
);
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const getDatabaseFields = () => {
|
const getDatabaseFields = () => {
|
||||||
let connSch = {
|
let connSch = {
|
||||||
schema: {},
|
schema: {},
|
||||||
@ -115,6 +144,7 @@ const ConnectionConfigForm: FunctionComponent<Props> = ({
|
|||||||
uiSchema={connSch.uiSchema}
|
uiSchema={connSch.uiSchema}
|
||||||
onCancel={onCancel}
|
onCancel={onCancel}
|
||||||
onSubmit={onSave}
|
onSubmit={onSave}
|
||||||
|
onTestConnection={handleTestConnection}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -18,8 +18,14 @@ import Form, {
|
|||||||
ObjectFieldTemplateProps,
|
ObjectFieldTemplateProps,
|
||||||
} from '@rjsf/core';
|
} from '@rjsf/core';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import { debounce, isEmpty, isEqual } from 'lodash';
|
||||||
import { LoadingState } from 'Models';
|
import { LoadingState } from 'Models';
|
||||||
import React, { Fragment, FunctionComponent } from 'react';
|
import React, {
|
||||||
|
Fragment,
|
||||||
|
FunctionComponent,
|
||||||
|
useCallback,
|
||||||
|
useState,
|
||||||
|
} from 'react';
|
||||||
import { ConfigData } from '../../../interface/service.interface';
|
import { ConfigData } from '../../../interface/service.interface';
|
||||||
import SVGIcons, { Icons } from '../../../utils/SvgUtils';
|
import SVGIcons, { Icons } from '../../../utils/SvgUtils';
|
||||||
import { Button } from '../../buttons/Button/Button';
|
import { Button } from '../../buttons/Button/Button';
|
||||||
@ -31,6 +37,7 @@ interface Props extends FormProps<ConfigData> {
|
|||||||
showFormHeader?: boolean;
|
showFormHeader?: boolean;
|
||||||
status?: LoadingState;
|
status?: LoadingState;
|
||||||
onCancel?: () => void;
|
onCancel?: () => void;
|
||||||
|
onTestConnection?: (formData: ConfigData) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function ArrayFieldTemplate(props: ArrayFieldTemplateProps) {
|
function ArrayFieldTemplate(props: ArrayFieldTemplateProps) {
|
||||||
@ -124,17 +131,26 @@ function ObjectFieldTemplate(props: ObjectFieldTemplateProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const FormBuilder: FunctionComponent<Props> = ({
|
const FormBuilder: FunctionComponent<Props> = ({
|
||||||
|
formData,
|
||||||
|
schema,
|
||||||
okText,
|
okText,
|
||||||
cancelText,
|
cancelText,
|
||||||
showFormHeader = false,
|
showFormHeader = false,
|
||||||
status = 'initial',
|
status = 'initial',
|
||||||
onCancel,
|
onCancel,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
|
onTestConnection,
|
||||||
...props
|
...props
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
let oForm: Form<ConfigData> | null;
|
let oForm: Form<ConfigData> | null;
|
||||||
|
const [localFormData, setLocalFormData] = useState<ConfigData | undefined>(
|
||||||
|
formData
|
||||||
|
);
|
||||||
|
const [connectionTesting, setConnectionTesting] = useState<boolean>(false);
|
||||||
|
const [connectionTested, setConnectionTested] = useState<boolean>(false);
|
||||||
|
|
||||||
const handleCancel = () => {
|
const handleCancel = () => {
|
||||||
|
setLocalFormData(formData);
|
||||||
if (onCancel) {
|
if (onCancel) {
|
||||||
onCancel();
|
onCancel();
|
||||||
}
|
}
|
||||||
@ -146,6 +162,40 @@ const FormBuilder: FunctionComponent<Props> = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleTestConnection = () => {
|
||||||
|
if (localFormData && onTestConnection) {
|
||||||
|
setConnectionTesting(true);
|
||||||
|
onTestConnection(localFormData)
|
||||||
|
.then(() => {
|
||||||
|
setConnectionTested(true);
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
setConnectionTested(false);
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setConnectionTesting(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const debouncedOnChange = useCallback(
|
||||||
|
(updatedData: ConfigData): void => {
|
||||||
|
setLocalFormData(updatedData);
|
||||||
|
},
|
||||||
|
[setLocalFormData]
|
||||||
|
);
|
||||||
|
|
||||||
|
const debounceOnSearch = useCallback(debounce(debouncedOnChange, 1500), [
|
||||||
|
debouncedOnChange,
|
||||||
|
]);
|
||||||
|
const handleChange = (updatedData: ConfigData) => {
|
||||||
|
debounceOnSearch(updatedData);
|
||||||
|
setConnectionTested(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const disableTestConnection = () => {
|
||||||
|
return connectionTesting || isEqual(localFormData, formData);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form
|
<Form
|
||||||
ArrayFieldTemplate={ArrayFieldTemplate}
|
ArrayFieldTemplate={ArrayFieldTemplate}
|
||||||
@ -153,48 +203,79 @@ const FormBuilder: FunctionComponent<Props> = ({
|
|||||||
className={classNames('rjsf', props.className, {
|
className={classNames('rjsf', props.className, {
|
||||||
'no-header': !showFormHeader,
|
'no-header': !showFormHeader,
|
||||||
})}
|
})}
|
||||||
|
formData={localFormData}
|
||||||
ref={(form) => {
|
ref={(form) => {
|
||||||
oForm = form;
|
oForm = form;
|
||||||
}}
|
}}
|
||||||
|
schema={schema}
|
||||||
|
onChange={(e) => {
|
||||||
|
handleChange(e.formData);
|
||||||
|
props.onChange && props.onChange(e);
|
||||||
|
}}
|
||||||
onSubmit={onSubmit}
|
onSubmit={onSubmit}
|
||||||
{...props}>
|
{...props}>
|
||||||
<div className="tw-mt-6 tw-text-right" data-testid="buttons">
|
{isEmpty(schema) && (
|
||||||
<Button
|
<div className="tw-text-grey-muted tw-text-center">
|
||||||
size="regular"
|
No Connection Configs available.
|
||||||
theme="primary"
|
</div>
|
||||||
variant="text"
|
)}
|
||||||
onClick={handleCancel}>
|
<div className="tw-mt-6 tw-flex tw-justify-between">
|
||||||
{cancelText}
|
<div>
|
||||||
</Button>
|
{!isEmpty(schema) && onTestConnection && (
|
||||||
{status === 'waiting' ? (
|
<Button
|
||||||
|
className={classNames({
|
||||||
|
'tw-opacity-40': disableTestConnection(),
|
||||||
|
})}
|
||||||
|
disabled={disableTestConnection()}
|
||||||
|
size="regular"
|
||||||
|
theme="primary"
|
||||||
|
variant="outlined"
|
||||||
|
onClick={handleTestConnection}>
|
||||||
|
Test Connection
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="tw-text-right" data-testid="buttons">
|
||||||
<Button
|
<Button
|
||||||
disabled
|
|
||||||
className="tw-w-16 tw-h-10 disabled:tw-opacity-100"
|
|
||||||
size="regular"
|
size="regular"
|
||||||
theme="primary"
|
theme="primary"
|
||||||
variant="contained">
|
variant="text"
|
||||||
<Loader size="small" type="white" />
|
onClick={handleCancel}>
|
||||||
|
{cancelText}
|
||||||
</Button>
|
</Button>
|
||||||
) : status === 'success' ? (
|
{status === 'waiting' ? (
|
||||||
<Button
|
<Button
|
||||||
disabled
|
disabled
|
||||||
className="tw-w-16 tw-h-10 disabled:tw-opacity-100"
|
className="tw-w-16 tw-h-10 disabled:tw-opacity-100"
|
||||||
size="regular"
|
size="regular"
|
||||||
theme="primary"
|
theme="primary"
|
||||||
variant="contained">
|
variant="contained">
|
||||||
<FontAwesomeIcon icon="check" />
|
<Loader size="small" type="white" />
|
||||||
</Button>
|
</Button>
|
||||||
) : (
|
) : status === 'success' ? (
|
||||||
<Button
|
<Button
|
||||||
className="tw-w-16 tw-h-10"
|
disabled
|
||||||
data-testid="saveManageTab"
|
className="tw-w-16 tw-h-10 disabled:tw-opacity-100"
|
||||||
size="regular"
|
size="regular"
|
||||||
theme="primary"
|
theme="primary"
|
||||||
variant="contained"
|
variant="contained">
|
||||||
onClick={handleSubmit}>
|
<FontAwesomeIcon icon="check" />
|
||||||
{okText}
|
</Button>
|
||||||
</Button>
|
) : (
|
||||||
)}
|
<Button
|
||||||
|
className={classNames('tw-w-16 tw-h-10', {
|
||||||
|
'tw-opacity-40': !connectionTested && !isEmpty(schema),
|
||||||
|
})}
|
||||||
|
data-testid="saveManageTab"
|
||||||
|
disabled={!connectionTested && !isEmpty(schema)}
|
||||||
|
size="regular"
|
||||||
|
theme="primary"
|
||||||
|
variant="contained"
|
||||||
|
onClick={handleSubmit}>
|
||||||
|
{okText}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
|
@ -42,7 +42,7 @@ const SuccessScreen = ({
|
|||||||
</div>
|
</div>
|
||||||
<p className="tw-mb-7" data-testid="success-line">
|
<p className="tw-mb-7" data-testid="success-line">
|
||||||
<span className="tw-mr-1 tw-font-semibold">"{name}"</span>
|
<span className="tw-mr-1 tw-font-semibold">"{name}"</span>
|
||||||
<span>has been successfuly created</span>
|
<span>has been created successfully</span>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
@ -102,6 +102,8 @@ const jsonData = {
|
|||||||
'fetch-webhook-error': 'Error while fetching webhooks!',
|
'fetch-webhook-error': 'Error while fetching webhooks!',
|
||||||
'fetch-user-count-error': 'Error while getting users count!',
|
'fetch-user-count-error': 'Error while getting users count!',
|
||||||
|
|
||||||
|
'test-connection-error': 'Error while testing connection!',
|
||||||
|
|
||||||
'unexpected-server-response': 'Unexpected response from server!',
|
'unexpected-server-response': 'Unexpected response from server!',
|
||||||
|
|
||||||
'update-chart-error': 'Error while updating charts!',
|
'update-chart-error': 'Error while updating charts!',
|
||||||
@ -138,6 +140,7 @@ const jsonData = {
|
|||||||
'delete-test': 'Test deleted successfully!',
|
'delete-test': 'Test deleted successfully!',
|
||||||
'delete-message': 'Message deleted successfully!',
|
'delete-message': 'Message deleted successfully!',
|
||||||
'delete-entity-success': 'Entity deleted successfully!',
|
'delete-entity-success': 'Entity deleted successfully!',
|
||||||
|
'test-connection-success': 'Connection tested successfully!',
|
||||||
},
|
},
|
||||||
'form-error-messages': {
|
'form-error-messages': {
|
||||||
'empty-email': 'Email is required.',
|
'empty-email': 'Email is required.',
|
||||||
|
@ -449,6 +449,10 @@
|
|||||||
width: 450px;
|
width: 450px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.Toastify__toast {
|
||||||
|
@apply tw-overflow-y-auto tw-max-h-80vh;
|
||||||
|
}
|
||||||
|
|
||||||
.Toastify__toast-body {
|
.Toastify__toast-body {
|
||||||
@apply tw-items-start;
|
@apply tw-items-start;
|
||||||
}
|
}
|
||||||
|
@ -170,6 +170,7 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
maxHeight: {
|
maxHeight: {
|
||||||
32: '8rem',
|
32: '8rem',
|
||||||
|
'80vh': '80vh',
|
||||||
'90vh': '90vh',
|
'90vh': '90vh',
|
||||||
},
|
},
|
||||||
minHeight: {
|
minHeight: {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user