Update test connection modal to include error messages (#20248)

* update test connection modal to include error messages

* update test

* update as per comments

* updated component

* fix the error message and improve the code with unit test

* minor improvement

* fix the unit test and some improvement

---------

Co-authored-by: Ashish Gupta <ashish@getcollate.io>
This commit is contained in:
Sweta Agarwalla 2025-03-17 19:32:43 +05:30 committed by GitHub
parent 269d9b66d3
commit 07ac8fa5eb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 114 additions and 135 deletions

View File

@ -18,7 +18,7 @@ export interface InlineAlertProps {
alertClassName?: string;
type?: InlineAlertType;
heading: string;
description: ReactNode;
description?: ReactNode;
subDescription?: ReactNode;
onClose?: () => void;
}

View File

@ -43,6 +43,14 @@ describe('InlineAlert', () => {
expect(screen.getByText('Test Description')).toBeInTheDocument();
});
it('should not render description when it is not provided', () => {
render(<InlineAlert {...mockProps} description={undefined} />);
expect(
screen.queryByTestId('inline-alert-description')
).not.toBeInTheDocument();
});
it('should handle show more/less functionality when subDescription is provided', () => {
const subDescription = 'Additional details here';
render(<InlineAlert {...mockProps} subDescription={subDescription} />);

View File

@ -102,19 +102,25 @@ function InlineAlert({
<Typography.Text className="font-semibold text-sm">
{heading}
</Typography.Text>
<Typography.Paragraph className="m-b-0 text-sm">
{description}
</Typography.Paragraph>
{description && (
<Typography.Paragraph
className="m-b-0 text-sm"
data-testid="inline-alert-description">
{description}
</Typography.Paragraph>
)}
{subDescription && showMore && (
<Typography.Paragraph className="m-b-0 text-sm">
<Typography.Paragraph
className="m-b-0 text-sm"
data-testid="inline-alert-sub-description">
{subDescription}
</Typography.Paragraph>
)}
{subDescription && (
<Button
className="text-xs p-0 m-0 w-fit-content"
className="text-xs p-0 m-0 w-fit-content h-auto"
data-testid={`read-${showMore ? 'less' : 'more'}-button`}
type="link"
onClick={handleToggleShowMore}>

View File

@ -58,7 +58,7 @@ import {
getTestConnectionName,
shouldTestConnection,
} from '../../../utils/ServiceUtils';
import { showErrorToast } from '../../../utils/ToastUtils';
import { getErrorText } from '../../../utils/StringsUtils';
import Loader from '../Loader/Loader';
import './test-connection.style.less';
import { TestConnectionProps, TestStatus } from './TestConnection.interface';
@ -86,6 +86,11 @@ const TestConnection: FC<TestConnectionProps> = ({
TEST_CONNECTION_INITIAL_MESSAGE
);
const [errorMessage, setErrorMessage] = useState<{
description?: string;
subDescription?: string;
}>();
const [testConnectionStep, setTestConnectionStep] = useState<
TestConnectionStep[]
>([]);
@ -346,7 +351,18 @@ const TestConnection: FC<TestConnectionProps> = ({
setIsTestingConnection(false);
setMessage(TEST_CONNECTION_FAILURE_MESSAGE);
setTestStatus(StatusType.Failed);
showErrorToast(error as AxiosError);
if ((error as AxiosError)?.status === 500) {
setErrorMessage({
description: t('server.unexpected-response'),
});
} else {
setErrorMessage({
subDescription: getErrorText(
error as AxiosError,
t('server.unexpected-error')
),
});
}
// delete the workflow if there is an exception
const workflowId = currentWorkflowRef.current?.id;
@ -373,6 +389,10 @@ const TestConnection: FC<TestConnectionProps> = ({
setDialogOpen(false);
};
const handleCloseErrorMessage = () => {
setErrorMessage(undefined);
};
useEffect(() => {
currentWorkflowRef.current = currentWorkflow; // update ref with latest value of currentWorkflow state variable
}, [currentWorkflow]);
@ -478,6 +498,8 @@ const TestConnection: FC<TestConnectionProps> = ({
</Button>
)}
<TestConnectionModal
errorMessage={errorMessage}
handleCloseErrorMessage={handleCloseErrorMessage}
isConnectionTimeout={isConnectionTimeout}
isOpen={dialogOpen}
isTestingConnection={isTestingConnection}

View File

@ -14,6 +14,11 @@ import { act, fireEvent, render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
import TestConnectionModal from './TestConnectionModal';
jest.mock('../../InlineAlert/InlineAlert', () => {
return jest.fn().mockReturnValue(<p>InlineAlert</p>);
});
const onCancelMock = jest.fn();
const onConfirmMock = jest.fn();
@ -36,115 +41,55 @@ const testConnectionStepResult = [
];
const mockOnTestConnection = jest.fn();
const mockHandleCloseErrorMessage = jest.fn();
const isConnectionTimeout = false;
const commonProps = {
isOpen: true,
isConnectionTimeout: false,
isTestingConnection: false,
progress: 10,
testConnectionStep,
testConnectionStepResult,
onCancel: onCancelMock,
onConfirm: onConfirmMock,
onTestConnection: mockOnTestConnection,
handleCloseErrorMessage: mockHandleCloseErrorMessage,
};
describe('TestConnectionModal', () => {
it('Should render the modal title', () => {
render(
<TestConnectionModal
isOpen
isConnectionTimeout={isConnectionTimeout}
isTestingConnection={false}
progress={10}
testConnectionStep={testConnectionStep}
testConnectionStepResult={testConnectionStepResult}
onCancel={onCancelMock}
onConfirm={onConfirmMock}
onTestConnection={mockOnTestConnection}
/>
);
render(<TestConnectionModal {...commonProps} />);
expect(screen.getByText('label.connection-status')).toBeInTheDocument();
});
it('Should render the steps and their results', () => {
render(
<TestConnectionModal
isOpen
isConnectionTimeout={isConnectionTimeout}
isTestingConnection={false}
progress={10}
testConnectionStep={testConnectionStep}
testConnectionStepResult={testConnectionStepResult}
onCancel={onCancelMock}
onConfirm={onConfirmMock}
onTestConnection={mockOnTestConnection}
/>
);
render(<TestConnectionModal {...commonProps} />);
expect(screen.getByText('Step 1')).toBeInTheDocument();
expect(screen.getByText('Step 2')).toBeInTheDocument();
});
it('Should render the success icon for a passing step', () => {
render(
<TestConnectionModal
isOpen
isConnectionTimeout={isConnectionTimeout}
isTestingConnection={false}
progress={10}
testConnectionStep={testConnectionStep}
testConnectionStepResult={testConnectionStepResult}
onCancel={onCancelMock}
onConfirm={onConfirmMock}
onTestConnection={mockOnTestConnection}
/>
);
render(<TestConnectionModal {...commonProps} />);
expect(screen.getByTestId('success-badge')).toBeInTheDocument();
});
it('Should render the fail icon for a failing step', () => {
render(
<TestConnectionModal
isOpen
isConnectionTimeout={isConnectionTimeout}
isTestingConnection={false}
progress={10}
testConnectionStep={testConnectionStep}
testConnectionStepResult={testConnectionStepResult}
onCancel={onCancelMock}
onConfirm={onConfirmMock}
onTestConnection={mockOnTestConnection}
/>
);
render(<TestConnectionModal {...commonProps} />);
expect(screen.getByTestId('fail-badge')).toBeInTheDocument();
});
it('Should render the awaiting status for a step being tested', () => {
render(
<TestConnectionModal
isOpen
isTestingConnection
isConnectionTimeout={isConnectionTimeout}
progress={10}
testConnectionStep={testConnectionStep}
testConnectionStepResult={testConnectionStepResult}
onCancel={onCancelMock}
onConfirm={onConfirmMock}
onTestConnection={mockOnTestConnection}
/>
);
render(<TestConnectionModal {...commonProps} isTestingConnection />);
expect(screen.getAllByText('label.awaiting-status...')).toHaveLength(2);
});
it('Should call onCancel when the cancel button is clicked', () => {
render(
<TestConnectionModal
isOpen
isConnectionTimeout={isConnectionTimeout}
isTestingConnection={false}
progress={10}
testConnectionStep={testConnectionStep}
testConnectionStepResult={testConnectionStepResult}
onCancel={onCancelMock}
onConfirm={onConfirmMock}
onTestConnection={mockOnTestConnection}
/>
);
render(<TestConnectionModal {...commonProps} />);
const cancelButton = screen.getByText('Cancel');
fireEvent.click(cancelButton);
@ -153,19 +98,7 @@ describe('TestConnectionModal', () => {
});
it('Should call onConfirm when the confirm button is clicked', () => {
render(
<TestConnectionModal
isOpen
isConnectionTimeout={isConnectionTimeout}
isTestingConnection={false}
progress={10}
testConnectionStep={testConnectionStep}
testConnectionStepResult={testConnectionStepResult}
onCancel={onCancelMock}
onConfirm={onConfirmMock}
onTestConnection={mockOnTestConnection}
/>
);
render(<TestConnectionModal {...commonProps} />);
const okButton = screen.getByText('OK');
fireEvent.click(okButton);
@ -174,19 +107,7 @@ describe('TestConnectionModal', () => {
});
it('Should render the progress bar with proper value', () => {
render(
<TestConnectionModal
isOpen
isConnectionTimeout={isConnectionTimeout}
isTestingConnection={false}
progress={90}
testConnectionStep={testConnectionStep}
testConnectionStepResult={testConnectionStepResult}
onCancel={onCancelMock}
onConfirm={onConfirmMock}
onTestConnection={mockOnTestConnection}
/>
);
render(<TestConnectionModal {...commonProps} progress={90} />);
const progressBarValue = screen.getByTestId('progress-bar-value');
expect(progressBarValue).toBeInTheDocument();
@ -196,17 +117,7 @@ describe('TestConnectionModal', () => {
it('Should render the timeout widget if "isConnectionTimeout" is true', () => {
render(
<TestConnectionModal
isConnectionTimeout
isOpen
isTestingConnection={false}
progress={90}
testConnectionStep={testConnectionStep}
testConnectionStepResult={testConnectionStepResult}
onCancel={onCancelMock}
onConfirm={onConfirmMock}
onTestConnection={mockOnTestConnection}
/>
<TestConnectionModal {...commonProps} isConnectionTimeout progress={90} />
);
expect(
@ -216,17 +127,7 @@ describe('TestConnectionModal', () => {
it('Try again button should work', async () => {
render(
<TestConnectionModal
isConnectionTimeout
isOpen
isTestingConnection={false}
progress={90}
testConnectionStep={testConnectionStep}
testConnectionStepResult={testConnectionStepResult}
onCancel={onCancelMock}
onConfirm={onConfirmMock}
onTestConnection={mockOnTestConnection}
/>
<TestConnectionModal {...commonProps} isConnectionTimeout progress={90} />
);
const tryAgainButton = screen.getByTestId('try-again-button');
@ -239,4 +140,28 @@ describe('TestConnectionModal', () => {
expect(mockOnTestConnection).toHaveBeenCalled();
});
it('should not show error message component if error message is not passed', async () => {
render(<TestConnectionModal {...commonProps} />);
const errorComponent = screen.queryByText('InlineAlert');
expect(errorComponent).not.toBeInTheDocument();
});
it('should show error message component if error message is passed', async () => {
render(
<TestConnectionModal
{...commonProps}
errorMessage={{
description: 'Error description',
subDescription: 'Error sub description',
}}
/>
);
const errorComponent = screen.getByText('InlineAlert');
expect(errorComponent).toBeInTheDocument();
});
});

View File

@ -22,8 +22,10 @@ import React, { FC } from 'react';
import { useTranslation } from 'react-i18next';
import { ReactComponent as IconTimeOut } from '../../../../assets/svg/ic-time-out.svg';
import { ReactComponent as IconTimeOutButton } from '../../../../assets/svg/ic-timeout-button.svg';
import { TEST_CONNECTION_FAILURE_MESSAGE } from '../../../../constants/Services.constant';
import { TestConnectionStepResult } from '../../../../generated/entity/automations/workflow';
import { TestConnectionStep } from '../../../../generated/entity/services/connections/testConnectionDefinition';
import InlineAlert from '../../InlineAlert/InlineAlert';
import ConnectionStepCard from '../ConnectionStepCard/ConnectionStepCard';
import './test-connection-modal.less';
interface TestConnectionModalProps {
@ -36,6 +38,11 @@ interface TestConnectionModalProps {
onCancel: () => void;
onConfirm: () => void;
onTestConnection: () => void;
errorMessage?: {
description?: string;
subDescription?: string;
};
handleCloseErrorMessage: () => void;
}
const TestConnectionModal: FC<TestConnectionModalProps> = ({
@ -48,6 +55,8 @@ const TestConnectionModal: FC<TestConnectionModalProps> = ({
onCancel,
isConnectionTimeout,
onTestConnection,
errorMessage,
handleCloseErrorMessage,
}) => {
const { t } = useTranslation();
@ -77,6 +86,15 @@ const TestConnectionModal: FC<TestConnectionModalProps> = ({
className="p-x-md w-full overflow-hidden"
direction="vertical"
size={16}>
{errorMessage && (
<InlineAlert
heading={TEST_CONNECTION_FAILURE_MESSAGE}
{...errorMessage}
type="error"
onClose={handleCloseErrorMessage}
/>
)}
<Progress
className="test-connection-progress-bar"
format={getProgressFormat}