Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/compass-components/src/hooks/use-toast.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export type ToastProperties = Pick<
| 'timeout'
| 'dismissible'
| 'onClose'
| 'className'
>;

const defaultToastProperties: Partial<ToastProperties> = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import {
spacing,
openToast,
closeToast,
Icon,
Button,
} from '@mongodb-js/compass-components';
import type { ConnectionInfo } from '@mongodb-js/connection-info';
import { getConnectionTitle } from '@mongodb-js/connection-info';
Expand Down Expand Up @@ -38,74 +40,101 @@ export function getConnectingStatusText(connectionInfo: ConnectionInfo) {

type ConnectionErrorToastBodyProps = {
info?: ConnectionInfo | null;
error: Error;
showReviewButton: boolean;
showDebugButton: boolean;
onReview: () => void;
onDebug: () => void;
};

const connectionErrorToastStyles = css({
// the gap on the right after the buttons takes up a lot of space from the
// description, so we remove it and add a little bit of margin elsewhere
gap: 0,
'[data-testid="lg-toast-content"] > div, [data-testid="lg-toast-content"] > div > p + p':
{
// don't cut off the glow of the button
overflow: 'visible',
},
});

const connectionErrorToastBodyStyles = css({
display: 'grid',
gridAutoFlow: 'column',
gap: spacing[200],
});

const connectionErrorToastActionMessageStyles = css({});
const connectionErrorActionsStyles = css({
display: 'flex',
flexDirection: 'column',
textAlign: 'right',
// replacing the gap with a margin so the button glow does not get cut off
marginRight: spacing[100],
gap: spacing[100],
justifyContent: 'center',
});

const connectionErrorTextStyles = css({
overflow: 'hidden',
textOverflow: 'ellipsis',
const connectionErrorStyles = css({
display: 'flex',
flexDirection: 'column',
});

const connectionErrorTitleStyles = css({
fontWeight: 'bold',
});

const debugActionStyles = css({
display: 'flex',
alignItems: 'center',
gap: spacing[100],
justifyContent: 'right',
textWrap: 'nowrap',
});

function ConnectionErrorToastBody({
info,
error,
showReviewButton,
showDebugButton,
onReview,
onDebug,
}: ConnectionErrorToastBodyProps): React.ReactElement {
return (
<span className={connectionErrorToastBodyStyles}>
<span
data-testid="connection-error-text"
className={connectionErrorTextStyles}
>
There was a problem connecting{' '}
{info ? `to ${getConnectionTitle(info)}` : ''}
</span>
{info && showReviewButton && (
<Link
className={connectionErrorToastActionMessageStyles}
hideExternalIcon={true}
onClick={onReview}
data-testid="connection-error-review"
<span className={connectionErrorStyles}>
<span
data-testid="connection-error-title"
className={connectionErrorTitleStyles}
>
REVIEW
</Link>
)}
</span>
);
}

type ConnectionDebugToastBodyProps = {
onDebug: () => void;
};

function ConnectionDebugToastBody({
onDebug,
}: ConnectionDebugToastBodyProps): React.ReactElement {
return (
<span className={connectionErrorToastBodyStyles}>
<span
data-testid="connection-debug-text"
className={connectionErrorTextStyles}
>
Diagnose the issue and explore solutions with the assistant
{info ? getConnectionTitle(info) : 'Connection failed'}
</span>
<span data-testid="connection-error-text">{error.message}</span>
</span>
<span className={connectionErrorActionsStyles}>
{info && showReviewButton && (
<span>
<Button
onClick={onReview}
data-testid="connection-error-review"
size="small"
>
Review
</Button>
</span>
)}
{info && showDebugButton && (
<span className={debugActionStyles}>
<Icon glyph="Sparkle" size="small"></Icon>
<Link
hideExternalIcon={true}
onClick={onDebug}
data-testid="connection-error-debug"
>
Debug for me
</Link>
</span>
)}
</span>
<Link
className={connectionErrorToastActionMessageStyles}
hideExternalIcon={true}
onClick={onDebug}
data-testid="connection-error-debug"
>
DEBUG FOR ME
</Link>
</span>
);
}
Expand Down Expand Up @@ -150,51 +179,50 @@ const openConnectionSucceededToast = (connectionInfo: ConnectionInfo) => {
});
};

const openConnectionFailedToast = (
const openConnectionFailedToast = ({
connectionInfo,
error,
showReviewButton,
showDebugButton,
onReviewClick,
onDebugClick,
}: {
// Connection info might be missing if we failed connecting before we
// could even resolve connection info. Currently the only case where this
// can happen is autoconnect flow
connectionInfo: ConnectionInfo | null | undefined,
error: Error,
showReviewButton: boolean,
onReviewClick: () => void
) => {
connectionInfo: ConnectionInfo | null | undefined;
error: Error;
showReviewButton: boolean;
showDebugButton: boolean;
onReviewClick: () => void;
onDebugClick: () => void;
}) => {
const failedToastId = connectionInfo?.id ?? 'failed';

// TODO(COMPASS-9746): close the existing connection toast and make a new one
// for the failure so that the debug toast will appear below the failure one
openToast(`connection-status--${failedToastId}`, {
title: error.message,
// we place the title inside the description to get the layout we need
title: '',
description: (
<ConnectionErrorToastBody
info={connectionInfo}
error={error}
showReviewButton={showReviewButton}
showDebugButton={showDebugButton}
onReview={() => {
closeToast(`connection-status--${failedToastId}`);
if (!showDebugButton) {
// don't close the toast if there are two actions so that the user
// can still use the other one
closeToast(`connection-status--${failedToastId}`);
}
onReviewClick();
}}
/>
),
variant: 'warning',
});
};

const openDebugConnectionErrorToast = (
connectionInfo: ConnectionInfo,
error: Error,
onDebugClick: () => void
) => {
openToast(`debug-connection-error--${connectionInfo.id}`, {
title: 'Need help debugging your connection error?',
description: (
<ConnectionDebugToastBody
onDebug={() => {
closeToast(`debug-connection-error--${connectionInfo.id}`);
onDebugClick();
}}
/>
),
variant: 'note',
variant: 'warning',
className: connectionErrorToastStyles,
});
};

Expand Down Expand Up @@ -262,7 +290,6 @@ export function getNotificationTriggers() {
openConnectionStartedToast,
openConnectionSucceededToast,
openConnectionFailedToast,
openDebugConnectionErrorToast,
openMaximumConnectionsReachedToast,
closeConnectionStatusToast: (connectionId: string) => {
return closeToast(`connection-status--${connectionId}`);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ describe('CompassConnections store', function () {
}
});

it('should show debug toast in addition to the connection error toast if connection fails and the assistant is enabled', async function () {
it('should show debug action in addition to review if connection fails and the assistant is enabled', async function () {
const { connectionsStore } = renderCompassConnections({
preferences: {
enableAIAssistant: true,
Expand All @@ -169,8 +169,7 @@ describe('CompassConnections store', function () {
});

await waitFor(() => {
expect(screen.getByText('Need help debugging your connection error?'))
.to.exist;
expect(screen.getByText('Debug for me')).to.exist;
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1270,24 +1270,29 @@ const connectionAttemptError = (
_getState,
{ track, getExtraConnectionData, compassAssistant }
) => {
const { openConnectionFailedToast, openDebugConnectionErrorToast } =
getNotificationTriggers();
const { openConnectionFailedToast } = getNotificationTriggers();

const isAssistanceEnabled = compassAssistant.getIsAssistantEnabled();
if (isAssistanceEnabled && connectionInfo) {
openDebugConnectionErrorToast(connectionInfo, err, () => {
compassAssistant.interpretConnectionError({
connectionInfo,
error: err,
});
});
}

const showReviewButton = !!connectionInfo && !connectionInfo.atlasMetadata;
openConnectionFailedToast(connectionInfo, err, showReviewButton, () => {
if (connectionInfo) {
dispatch(editConnection(connectionInfo.id));
}
openConnectionFailedToast({
connectionInfo,
error: err,
showReviewButton,
showDebugButton: isAssistanceEnabled,
onReviewClick() {
if (connectionInfo) {
dispatch(editConnection(connectionInfo.id));
}
},
onDebugClick() {
if (connectionInfo) {
compassAssistant.interpretConnectionError({
connectionInfo,
error: err,
});
}
},
});

track(
Expand Down
2 changes: 1 addition & 1 deletion packages/compass-e2e-tests/helpers/commands/connect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ export async function waitForConnectionResult(
await browser
.$(Selectors.ConnectionToastErrorText)
.waitForDisplayed(waitOptions);
return browser.$(Selectors.LGToastTitle).getText();
return browser.$(Selectors.ConnectionToastErrorText).getText();
} else {
const exhaustiveCheck: never = connectionStatus;
throw new Error(`Unhandled connectionStatus case: ${exhaustiveCheck}`);
Expand Down
2 changes: 2 additions & 0 deletions packages/compass-e2e-tests/helpers/selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,8 @@ export const ConnectionModalSaveButton = '[data-testid="save-button"]';
export const connectionToastById = (connectionId: string) => {
return `[data-testid="toast-connection-status--${connectionId}"]`;
};
export const ConnectionToastTitleText =
'[data-testid="connection-error-title"]';
export const ConnectionToastErrorText = '[data-testid="connection-error-text"]';
export const ConnectionToastErrorReviewButton =
'[data-testid="connection-error-review"]';
Expand Down
Loading
Loading