Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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