From 67941f548dc46814e5f94379106aa7fd0e4b6e88 Mon Sep 17 00:00:00 2001 From: Sachin Chaurasiya Date: Wed, 31 May 2023 15:12:55 +0530 Subject: [PATCH] chore(#11742): test connection status modal improvements (#11744) * chore(#11742): test connection status modal improvements * fix: unit test * chore: simplify the checks * fix: card styling * refactor: failed badge * chore: handle reset state * feat: add test connection timeout widget * move constants to separate file * chore: reset progress on test connection * chore: update message * chore: remove hardcode condition --- .../ui/src/assets/svg/ic-time-out.svg | 17 +++++ .../ui/src/assets/svg/ic-timeout-button.svg | 4 + .../common/TestConnection/TestConnection.tsx | 68 +++++++++-------- .../TestConnectionModal.test.tsx | 69 ++++++++++++++++- .../TestConnectionModal.tsx | 75 +++++++++++++++---- .../test-connection-modal.less | 30 ++++++++ .../ui/src/constants/Services.constant.ts | 32 ++++++++ .../ui/src/locale/languages/en-us.json | 2 + .../ui/src/locale/languages/es-es.json | 2 + .../ui/src/locale/languages/fr-fr.json | 2 + .../ui/src/locale/languages/ja-jp.json | 2 + .../ui/src/locale/languages/pt-br.json | 2 + .../ui/src/locale/languages/zh-cn.json | 2 + 13 files changed, 258 insertions(+), 49 deletions(-) create mode 100644 openmetadata-ui/src/main/resources/ui/src/assets/svg/ic-time-out.svg create mode 100644 openmetadata-ui/src/main/resources/ui/src/assets/svg/ic-timeout-button.svg create mode 100644 openmetadata-ui/src/main/resources/ui/src/components/common/TestConnection/TestConnectionModal/test-connection-modal.less diff --git a/openmetadata-ui/src/main/resources/ui/src/assets/svg/ic-time-out.svg b/openmetadata-ui/src/main/resources/ui/src/assets/svg/ic-time-out.svg new file mode 100644 index 00000000000..a0d7750e2f6 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/assets/svg/ic-time-out.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/openmetadata-ui/src/main/resources/ui/src/assets/svg/ic-timeout-button.svg b/openmetadata-ui/src/main/resources/ui/src/assets/svg/ic-timeout-button.svg new file mode 100644 index 00000000000..83b4d363a79 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/assets/svg/ic-timeout-button.svg @@ -0,0 +1,4 @@ + + + + diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/TestConnection/TestConnection.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/TestConnection/TestConnection.tsx index 06b8e4488cc..855a05ec3ae 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/TestConnection/TestConnection.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/TestConnection/TestConnection.tsx @@ -51,6 +51,13 @@ import { AIRFLOW_DOCS } from 'constants/docs.constants'; import { FETCHING_EXPIRY_TIME, FETCH_INTERVAL, + TEST_CONNECTION_FAILURE_MESSAGE, + TEST_CONNECTION_INFO_MESSAGE, + TEST_CONNECTION_INITIAL_MESSAGE, + TEST_CONNECTION_PROGRESS_PERCENTAGE, + TEST_CONNECTION_SUCCESS_MESSAGE, + TEST_CONNECTION_TESTING_MESSAGE, + TEST_CONNECTION_WARNING_MESSAGE, WORKFLOW_COMPLETE_STATUS, } from 'constants/Services.constant'; import { useAirflowStatus } from 'hooks/useAirflowStatus'; @@ -70,28 +77,14 @@ const TestConnection: FC = ({ const { t } = useTranslation(); const { isAirflowAvailable } = useAirflowStatus(); - const initialMessage = t( - 'message.test-your-connection-before-creating-service' - ); - - const successMessage = t('message.connection-test-successful'); - - const failureMessage = t('message.connection-test-failed'); - - const testingMessage = t( - 'message.testing-your-connection-may-take-two-minutes' - ); - - const infoMessage = t('message.test-connection-taking-too-long'); - - const warningMessage = t('message.connection-test-warning'); - // local state const [isTestingConnection, setIsTestingConnection] = useState(false); const [dialogOpen, setDialogOpen] = useState(false); - const [message, setMessage] = useState(initialMessage); + const [message, setMessage] = useState( + TEST_CONNECTION_INITIAL_MESSAGE + ); const [testConnectionStep, setTestConnectionStep] = useState< TestConnectionStep[] @@ -104,7 +97,12 @@ const TestConnection: FC = ({ const [currentWorkflow, setCurrentWorkflow] = useState(); const [testStatus, setTestStatus] = useState(); - const [progress, setProgress] = useState(0); + const [progress, setProgress] = useState( + TEST_CONNECTION_PROGRESS_PERCENTAGE.ZERO + ); + + const [isConnectionTimeout, setIsConnectionTimeout] = + useState(false); /** * Current workflow reference @@ -163,6 +161,8 @@ const TestConnection: FC = ({ setCurrentWorkflow(undefined); setTestConnectionStepResult([]); setTestStatus(undefined); + setIsConnectionTimeout(false); + setProgress(0); }; const handleDeleteWorkflow = async (workflowId: string) => { @@ -177,7 +177,7 @@ const TestConnection: FC = ({ // handlers const testConnection = async () => { setIsTestingConnection(true); - setMessage(testingMessage); + setMessage(TEST_CONNECTION_TESTING_MESSAGE); handleResetState(); // current interval id @@ -198,21 +198,21 @@ const TestConnection: FC = ({ // fetch the connection steps for current connectionType await fetchConnectionDefinition(); - setProgress(10); + setProgress(TEST_CONNECTION_PROGRESS_PERCENTAGE.TEN); // create the workflow const response = await addWorkflow(createWorkflowData); - setProgress(20); + setProgress(TEST_CONNECTION_PROGRESS_PERCENTAGE.TWENTY); // trigger the workflow const status = await triggerWorkflowById(response.id); - setProgress(40); + setProgress(TEST_CONNECTION_PROGRESS_PERCENTAGE.FORTY); if (status !== 200) { setTestStatus(StatusType.Failed); - setMessage(failureMessage); + setMessage(TEST_CONNECTION_FAILURE_MESSAGE); setIsTestingConnection(false); return; @@ -224,7 +224,7 @@ const TestConnection: FC = ({ */ intervalId = toNumber( setInterval(async () => { - setProgress((prev) => prev + 1); + setProgress((prev) => prev + TEST_CONNECTION_PROGRESS_PERCENTAGE.ONE); const workflowResponse = await getWorkflowData(response.id); const { response: testConnectionResponse } = workflowResponse; const { status: testConnectionStatus, steps = [] } = @@ -241,10 +241,10 @@ const TestConnection: FC = ({ return; } - setProgress(90); + setProgress(TEST_CONNECTION_PROGRESS_PERCENTAGE.HUNDRED); if (isTestConnectionSuccess) { setTestStatus(StatusType.Successful); - setMessage(successMessage); + setMessage(TEST_CONNECTION_SUCCESS_MESSAGE); } else { const isMandatoryStepsFailing = steps.some( (step) => step.mandatory && !step.passed @@ -253,7 +253,9 @@ const TestConnection: FC = ({ isMandatoryStepsFailing ? StatusType.Failed : 'Warning' ); setMessage( - isMandatoryStepsFailing ? failureMessage : warningMessage + isMandatoryStepsFailing + ? TEST_CONNECTION_FAILURE_MESSAGE + : TEST_CONNECTION_WARNING_MESSAGE ); } @@ -265,7 +267,6 @@ const TestConnection: FC = ({ // delete the workflow once it's finished await handleDeleteWorkflow(workflowResponse.id); - setProgress(100); }, FETCH_INTERVAL) ); @@ -283,17 +284,18 @@ const TestConnection: FC = ({ ); if (!isWorkflowCompleted) { - setMessage(infoMessage); + setMessage(TEST_CONNECTION_INFO_MESSAGE); + setIsConnectionTimeout(true); } setIsTestingConnection(false); - setProgress(100); + setProgress(TEST_CONNECTION_PROGRESS_PERCENTAGE.HUNDRED); }, FETCHING_EXPIRY_TIME); } catch (error) { - setProgress(100); + setProgress(TEST_CONNECTION_PROGRESS_PERCENTAGE.HUNDRED); clearInterval(intervalId); setIsTestingConnection(false); - setMessage(failureMessage); + setMessage(TEST_CONNECTION_FAILURE_MESSAGE); setTestStatus(StatusType.Failed); showErrorToast(error as AxiosError); } @@ -402,6 +404,7 @@ const TestConnection: FC = ({ )} = ({ testConnectionStepResult={testConnectionStepResult} onCancel={() => setDialogOpen(false)} onConfirm={() => setDialogOpen(false)} + onTestConnection={handleTestConnection} /> ); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/TestConnection/TestConnectionModal/TestConnectionModal.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/TestConnection/TestConnectionModal/TestConnectionModal.test.tsx index f70a6f7657f..bc0be16c541 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/TestConnection/TestConnectionModal/TestConnectionModal.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/TestConnection/TestConnectionModal/TestConnectionModal.test.tsx @@ -10,7 +10,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { fireEvent, render, screen } from '@testing-library/react'; +import { act, fireEvent, render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import React from 'react'; import TestConnectionModal from './TestConnectionModal'; const onCancelMock = jest.fn(); @@ -34,17 +35,23 @@ const testConnectionStepResult = [ }, ]; +const mockOnTestConnection = jest.fn(); + +const isConnectionTimeout = false; + describe('TestConnectionModal', () => { it('Should render the modal title', () => { render( ); @@ -55,12 +62,14 @@ describe('TestConnectionModal', () => { render( ); @@ -72,12 +81,14 @@ describe('TestConnectionModal', () => { render( ); @@ -88,12 +99,14 @@ describe('TestConnectionModal', () => { render( ); @@ -105,11 +118,13 @@ describe('TestConnectionModal', () => { ); @@ -120,12 +135,14 @@ describe('TestConnectionModal', () => { render( ); const cancelButton = screen.getByText('Cancel'); @@ -139,12 +156,14 @@ describe('TestConnectionModal', () => { render( ); const okButton = screen.getByText('OK'); @@ -158,12 +177,14 @@ describe('TestConnectionModal', () => { render( ); const progressBarValue = screen.getByTestId('progress-bar-value'); @@ -172,4 +193,50 @@ describe('TestConnectionModal', () => { expect(progressBarValue).toHaveTextContent('90%'); }); + + it('Should render the timeout widget if "isConnectionTimeout" is true', () => { + render( + + ); + + expect( + screen.getByTestId('test-connection-timeout-widget') + ).toBeInTheDocument(); + }); + + it('Try again button should work', async () => { + render( + + ); + + const tryAgainButton = screen.getByTestId('try-again-button'); + + expect(tryAgainButton).toBeInTheDocument(); + + await act(async () => { + userEvent.click(tryAgainButton); + }); + + expect(mockOnTestConnection).toHaveBeenCalled(); + }); }); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/TestConnection/TestConnectionModal/TestConnectionModal.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/TestConnection/TestConnectionModal/TestConnectionModal.tsx index 0c8fc0bb03c..d4affc0a49e 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/TestConnection/TestConnectionModal/TestConnectionModal.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/TestConnection/TestConnectionModal/TestConnectionModal.tsx @@ -10,21 +10,32 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Modal, Progress, Space } from 'antd'; +import { + Button, + Modal, + Progress, + ProgressProps, + Space, + Typography, +} from 'antd'; +import { ReactComponent as IconTimeOut } from 'assets/svg/ic-time-out.svg'; +import { ReactComponent as IconTimeOutButton } from 'assets/svg/ic-timeout-button.svg'; import { TestConnectionStepResult } from 'generated/entity/automations/workflow'; import { TestConnectionStep } from 'generated/entity/services/connections/testConnectionDefinition'; import React, { FC } from 'react'; import { useTranslation } from 'react-i18next'; import ConnectionStepCard from '../ConnectionStepCard/ConnectionStepCard'; - +import './test-connection-modal.less'; interface TestConnectionModalProps { isOpen: boolean; isTestingConnection: boolean; testConnectionStep: TestConnectionStep[]; testConnectionStepResult: TestConnectionStepResult[]; progress: number; + isConnectionTimeout: boolean; onCancel: () => void; onConfirm: () => void; + onTestConnection: () => void; } const TestConnectionModal: FC = ({ @@ -35,6 +46,8 @@ const TestConnectionModal: FC = ({ testConnectionStepResult, onCancel, onConfirm, + isConnectionTimeout, + onTestConnection, }) => { const { t } = useTranslation(); @@ -44,6 +57,10 @@ const TestConnectionModal: FC = ({ ); }; + const getProgressFormat: ProgressProps['format'] = (progress) => { + return {`${progress}%`}; + }; + return ( = ({ ( - {`${per}%`} - )} + format={getProgressFormat} percent={progress} strokeColor="#B3D4F4" /> - {testConnectionStep.map((step) => { - const currentStepResult = getConnectionStepResult(step); + {isConnectionTimeout ? ( + + + + {t('label.connection-timeout')} + + + {t('message.test-connection-taking-too-long')} + + + + ) : ( + <> + {testConnectionStep.map((step) => { + const currentStepResult = getConnectionStepResult(step); - return ( - - ); - })} + return ( + + ); + })} + + )} ); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/TestConnection/TestConnectionModal/test-connection-modal.less b/openmetadata-ui/src/main/resources/ui/src/components/common/TestConnection/TestConnectionModal/test-connection-modal.less new file mode 100644 index 00000000000..a3d846b3886 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/TestConnection/TestConnectionModal/test-connection-modal.less @@ -0,0 +1,30 @@ +/* + * 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 url('../../../../styles/variables.less'); + +.timeout-widget { + background: @white; + border: 1px solid rgba(0, 0, 0, 0.1); + border-radius: 5px; + padding: 56px; + text-align: center; + h5 { + font-weight: 500; + margin-bottom: 0px; + } + .try-again-button { + display: flex; + gap: 2px; + align-items: center; + } +} diff --git a/openmetadata-ui/src/main/resources/ui/src/constants/Services.constant.ts b/openmetadata-ui/src/main/resources/ui/src/constants/Services.constant.ts index c6b007f730a..5f965c1861d 100644 --- a/openmetadata-ui/src/main/resources/ui/src/constants/Services.constant.ts +++ b/openmetadata-ui/src/main/resources/ui/src/constants/Services.constant.ts @@ -315,6 +315,14 @@ export const WORKFLOW_COMPLETE_STATUS = [ WorkflowStatus.Failed, WorkflowStatus.Successful, ]; +export const TEST_CONNECTION_PROGRESS_PERCENTAGE = { + ZERO: 0, + ONE: 1, + TEN: 10, + TWENTY: 20, + FORTY: 40, + HUNDRED: 100, +}; export const INGESTION_GUIDE_MAP = { [PipelineType.Usage]: addUsageIngestionGuide, @@ -338,3 +346,27 @@ export const BETA_SERVICES = [ DatabaseServiceType.Impala, PipelineServiceType.Spline, ]; + +export const TEST_CONNECTION_INITIAL_MESSAGE = i18n.t( + 'message.test-your-connection-before-creating-service' +); + +export const TEST_CONNECTION_SUCCESS_MESSAGE = i18n.t( + 'message.connection-test-successful' +); + +export const TEST_CONNECTION_FAILURE_MESSAGE = i18n.t( + 'message.connection-test-failed' +); + +export const TEST_CONNECTION_TESTING_MESSAGE = i18n.t( + 'message.testing-your-connection-may-take-two-minutes' +); + +export const TEST_CONNECTION_INFO_MESSAGE = i18n.t( + 'message.test-connection-taking-too-long' +); + +export const TEST_CONNECTION_WARNING_MESSAGE = i18n.t( + 'message.connection-test-warning' +); diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/en-us.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/en-us.json index 1a3b4fb2ff0..d58a111bcdc 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/en-us.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/en-us.json @@ -142,6 +142,7 @@ "connection-details": "Connection Details", "connection-entity": "Connection {{entity}}", "connection-status": "Connection status", + "connection-timeout": "Connection Timeout", "connection-timeout-plural": "Connection Timeout", "connector": "Connector", "container": "Container", @@ -909,6 +910,7 @@ "trigger": "Trigger", "trigger-type": "Trigger type", "triggering-lowercase": "triggering", + "try-again": "Try Again", "tuesday": "Tuesday", "type": "Type", "type-filed-name": "Type {{fieldName}}", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/es-es.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/es-es.json index bafc9018b77..c6ebba9a753 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/es-es.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/es-es.json @@ -142,6 +142,7 @@ "connection-details": "Detalles de conexión", "connection-entity": "Conexión {{entity}}", "connection-status": "Estado de la conexión", + "connection-timeout": "Connection Timeout", "connection-timeout-plural": "Tiempo de conexión expirado", "connector": "Conector", "container": "Contenedor", @@ -909,6 +910,7 @@ "trigger": "Desencadenador", "trigger-type": "Trigger type", "triggering-lowercase": "desencadenamiento", + "try-again": "Try Again", "tuesday": "Martes", "type": "Tipo", "type-filed-name": "Tipo {{fieldName}}", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/fr-fr.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/fr-fr.json index 9bd85a6a9a6..2e8ee4f5af4 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/fr-fr.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/fr-fr.json @@ -142,6 +142,7 @@ "connection-details": "Détails de Connexion", "connection-entity": "Connexion {{entity}}", "connection-status": "Statut de la Connexion", + "connection-timeout": "Connection Timeout", "connection-timeout-plural": "Délais d'Attente de la Connexion", "connector": "Connecteur", "container": "Conteneur", @@ -909,6 +910,7 @@ "trigger": "Déclencheur", "trigger-type": "Type de déclencheur", "triggering-lowercase": "déclenchement", + "try-again": "Try Again", "tuesday": "Mardi", "type": "Type", "type-filed-name": "Type {{fieldName}}", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/ja-jp.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/ja-jp.json index 965d9ee6d93..6bc1f1434dd 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/ja-jp.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/ja-jp.json @@ -142,6 +142,7 @@ "connection-details": "接続の詳細", "connection-entity": "{{entity}}との接続", "connection-status": "Connection status", + "connection-timeout": "Connection Timeout", "connection-timeout-plural": "接続のタイムアウト", "connector": "コネクタ", "container": "コンテナ", @@ -909,6 +910,7 @@ "trigger": "トリガー", "trigger-type": "Trigger type", "triggering-lowercase": "triggering", + "try-again": "Try Again", "tuesday": "火曜日", "type": "Type", "type-filed-name": "Type {{fieldName}}", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/pt-br.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/pt-br.json index 6184bede588..17256456e84 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/pt-br.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/pt-br.json @@ -142,6 +142,7 @@ "connection-details": "Detalhes da conexão", "connection-entity": "Conexão {{entity}}", "connection-status": "Connection status", + "connection-timeout": "Connection Timeout", "connection-timeout-plural": "tempos limites de conexão", "connector": "Conector", "container": "Container", @@ -909,6 +910,7 @@ "trigger": "Disparador", "trigger-type": "Trigger type", "triggering-lowercase": "disparador", + "try-again": "Try Again", "tuesday": "Terça-feira", "type": "Tipo", "type-filed-name": "Digite {{fieldName}}", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/zh-cn.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/zh-cn.json index 68dfd0062e7..f31f4a35a30 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/zh-cn.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/zh-cn.json @@ -142,6 +142,7 @@ "connection-details": "连接详细信息", "connection-entity": "连接{{entity}}", "connection-status": "连接状态", + "connection-timeout": "Connection Timeout", "connection-timeout-plural": "连接超时", "connector": "连接器", "container": "存储容器", @@ -909,6 +910,7 @@ "trigger": "触发器", "trigger-type": "触发器类型", "triggering-lowercase": "触发", + "try-again": "Try Again", "tuesday": "星期二", "type": "类型", "type-filed-name": "{{fieldName}}类型",