Fixed #4249: Test Connection button when adding/updating a service (#4298)

This commit is contained in:
darth-coder00 2022-04-21 02:59:31 +05:30 committed by GitHub
parent 67fb846dd9
commit 6c00b80f1d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 168 additions and 36 deletions

View File

@ -14,6 +14,7 @@
import { AxiosResponse } from 'axios';
import { isNil } from 'lodash';
import { ServiceOption } from 'Models';
import { ConfigData } from '../interface/service.interface';
import { getURLWithQueryFields } from '../utils/APIUtils';
import APIClient from './index';
@ -79,3 +80,15 @@ export const deleteService: Function = (
): Promise<AxiosResponse> => {
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);
};

View File

@ -101,7 +101,7 @@ const SelectServiceType = ({
theme="primary"
variant="text"
onClick={onCancel}>
<span>Back</span>
<span>Discard</span>
</Button>
<Button

View File

@ -15,6 +15,7 @@ import { ISubmitEvent } from '@rjsf/core';
import { cloneDeep, isNil } from 'lodash';
import { LoadingState } from 'Models';
import React, { FunctionComponent } from 'react';
import { TestConnection } from '../../axiosAPIs/serviceAPI';
import { ServiceCategory } from '../../enums/service.enum';
import {
DashboardConnection,
@ -30,10 +31,12 @@ import {
} from '../../generated/entity/services/messagingService';
import { PipelineService } from '../../generated/entity/services/pipelineService';
import { ConfigData } from '../../interface/service.interface';
import jsonData from '../../jsons/en';
import { getDashboardConfig } from '../../utils/DashboardServiceUtils';
import { getDatabaseConfig } from '../../utils/DatabaseServiceUtils';
import { getMessagingConfig } from '../../utils/MessagingServiceUtils';
import { getPipelineConfig } from '../../utils/PipelineServiceUtils';
import { showErrorToast, showSuccessToast } from '../../utils/ToastUtils';
import FormBuilder from '../common/FormBuilder/FormBuilder';
interface Props {
@ -62,6 +65,32 @@ const ConnectionConfigForm: FunctionComponent<Props> = ({
.connection.config as ConfigData)
: ({ pipelineUrl: (data as PipelineService).pipelineUrl } 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 = () => {
let connSch = {
schema: {},
@ -115,6 +144,7 @@ const ConnectionConfigForm: FunctionComponent<Props> = ({
uiSchema={connSch.uiSchema}
onCancel={onCancel}
onSubmit={onSave}
onTestConnection={handleTestConnection}
/>
);
};

View File

@ -18,8 +18,14 @@ import Form, {
ObjectFieldTemplateProps,
} from '@rjsf/core';
import classNames from 'classnames';
import { debounce, isEmpty, isEqual } from 'lodash';
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 SVGIcons, { Icons } from '../../../utils/SvgUtils';
import { Button } from '../../buttons/Button/Button';
@ -31,6 +37,7 @@ interface Props extends FormProps<ConfigData> {
showFormHeader?: boolean;
status?: LoadingState;
onCancel?: () => void;
onTestConnection?: (formData: ConfigData) => Promise<void>;
}
function ArrayFieldTemplate(props: ArrayFieldTemplateProps) {
@ -124,17 +131,26 @@ function ObjectFieldTemplate(props: ObjectFieldTemplateProps) {
}
const FormBuilder: FunctionComponent<Props> = ({
formData,
schema,
okText,
cancelText,
showFormHeader = false,
status = 'initial',
onCancel,
onSubmit,
onTestConnection,
...props
}: Props) => {
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 = () => {
setLocalFormData(formData);
if (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 (
<Form
ArrayFieldTemplate={ArrayFieldTemplate}
@ -153,48 +203,79 @@ const FormBuilder: FunctionComponent<Props> = ({
className={classNames('rjsf', props.className, {
'no-header': !showFormHeader,
})}
formData={localFormData}
ref={(form) => {
oForm = form;
}}
schema={schema}
onChange={(e) => {
handleChange(e.formData);
props.onChange && props.onChange(e);
}}
onSubmit={onSubmit}
{...props}>
<div className="tw-mt-6 tw-text-right" data-testid="buttons">
<Button
size="regular"
theme="primary"
variant="text"
onClick={handleCancel}>
{cancelText}
</Button>
{status === 'waiting' ? (
{isEmpty(schema) && (
<div className="tw-text-grey-muted tw-text-center">
No Connection Configs available.
</div>
)}
<div className="tw-mt-6 tw-flex tw-justify-between">
<div>
{!isEmpty(schema) && onTestConnection && (
<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
disabled
className="tw-w-16 tw-h-10 disabled:tw-opacity-100"
size="regular"
theme="primary"
variant="contained">
<Loader size="small" type="white" />
variant="text"
onClick={handleCancel}>
{cancelText}
</Button>
) : status === 'success' ? (
<Button
disabled
className="tw-w-16 tw-h-10 disabled:tw-opacity-100"
size="regular"
theme="primary"
variant="contained">
<FontAwesomeIcon icon="check" />
</Button>
) : (
<Button
className="tw-w-16 tw-h-10"
data-testid="saveManageTab"
size="regular"
theme="primary"
variant="contained"
onClick={handleSubmit}>
{okText}
</Button>
)}
{status === 'waiting' ? (
<Button
disabled
className="tw-w-16 tw-h-10 disabled:tw-opacity-100"
size="regular"
theme="primary"
variant="contained">
<Loader size="small" type="white" />
</Button>
) : status === 'success' ? (
<Button
disabled
className="tw-w-16 tw-h-10 disabled:tw-opacity-100"
size="regular"
theme="primary"
variant="contained">
<FontAwesomeIcon icon="check" />
</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>
</Form>
);

View File

@ -42,7 +42,7 @@ const SuccessScreen = ({
</div>
<p className="tw-mb-7" data-testid="success-line">
<span className="tw-mr-1 tw-font-semibold">&quot;{name}&quot;</span>
<span>has been successfuly created</span>
<span>has been created successfully</span>
</p>
<div>

View File

@ -102,6 +102,8 @@ const jsonData = {
'fetch-webhook-error': 'Error while fetching webhooks!',
'fetch-user-count-error': 'Error while getting users count!',
'test-connection-error': 'Error while testing connection!',
'unexpected-server-response': 'Unexpected response from server!',
'update-chart-error': 'Error while updating charts!',
@ -138,6 +140,7 @@ const jsonData = {
'delete-test': 'Test deleted successfully!',
'delete-message': 'Message deleted successfully!',
'delete-entity-success': 'Entity deleted successfully!',
'test-connection-success': 'Connection tested successfully!',
},
'form-error-messages': {
'empty-email': 'Email is required.',

View File

@ -449,6 +449,10 @@
width: 450px;
}
.Toastify__toast {
@apply tw-overflow-y-auto tw-max-h-80vh;
}
.Toastify__toast-body {
@apply tw-items-start;
}

View File

@ -170,6 +170,7 @@ module.exports = {
},
maxHeight: {
32: '8rem',
'80vh': '80vh',
'90vh': '90vh',
},
minHeight: {