add special notification for unexpected errors

Since these errors are not expected, we want to make it really easy to
report them.
This commit is contained in:
David Lechner
2021-01-21 12:37:29 -06:00
parent 2659c064fa
commit 91938e34d9
6 changed files with 80 additions and 8 deletions
+3 -3
View File
@@ -1,13 +1,13 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2020 The Pybricks Authors
// Copyright (c) 2021 The Pybricks Authors
// provides translation for notification text
import { Replacements, useI18n } from '@shopify/react-i18n';
import React from 'react';
import { MessageId } from './notification-i18n';
import en from './notification-i18n.en.json';
// provides translation for notification text
type OwnProps = {
messageId: MessageId;
replacements?: Replacements;
@@ -0,0 +1,54 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2021 The Pybricks Authors
// Provides special notification contents for unexpected errors.
import { AnchorButton, Button, ButtonGroup, Intent } from '@blueprintjs/core';
import { useI18n } from '@shopify/react-i18n';
import React from 'react';
import { MessageId } from './notification-i18n';
import en from './notification-i18n.en.json';
type OwnProps = {
messageId: MessageId;
err: Error;
};
export default function UnexpectedErrorNotification(props: OwnProps): JSX.Element {
const [i18n] = useI18n({
id: 'notification',
translations: { en },
fallback: en,
});
const { messageId, err } = props;
return (
<>
<p>{i18n.translate(messageId, { errorMessage: err.message })}</p>
<div>
<ButtonGroup minimal={true} fill={true}>
<Button
intent={Intent.DANGER}
icon="duplicate"
onClick={() =>
navigator.clipboard.writeText(
`\`\`\`\n${err.stack || err.message}\n\`\`\``,
)
}
>
{i18n.translate(MessageId.CopyErrorMessage)}
</Button>
<AnchorButton
intent={Intent.DANGER}
icon="virus"
href={`https://github.com/pybricks/support/issues?q=${encodeURIComponent(
'is:issue',
)}+${encodeURIComponent(err.message)}`}
target="_blank"
>
{i18n.translate(MessageId.ReportBug)}
</AnchorButton>
</ButtonGroup>
</div>
</>
);
}
+3 -1
View File
@@ -1,9 +1,11 @@
{
"copyErrorMessage": "Copy Error Message",
"reportBug": "Report Bug",
"ble": {
"gattPermission": "The web browser did not give permission to use Bluetooth Low Energy",
"gattServiceNotFound": "Connected to hub but failed to get {serviceName} service. Try removing the \"{hubName}\" device in your OS Bluetooth settings, then try again.",
"noWebBluetooth": "This web browser does not support Web Bluetooth or it is not enabled.",
"connectFailed": "Unexpected error while trying to connect. Check console log and report the error."
"unexpectedError": "Unexpected error while trying to connect: {errorMessage}"
},
"editor": {
"programChanged": {
+3 -1
View File
@@ -4,7 +4,9 @@
// Notification translation keys.
export enum MessageId {
BleConnectFailed = 'ble.connectFailed',
CopyErrorMessage = 'copyErrorMessage',
ReportBug = 'reportBug',
BleUnexpectedError = 'ble.unexpectedError',
BleGattPermission = 'ble.gattPermission',
BleGattServiceNotFound = 'ble.gattServiceNotFound',
BleNoWebBluetooth = 'ble.noWebBluetooth',
+3 -1
View File
@@ -27,7 +27,9 @@ test.each([
reason: BleDeviceFailToConnectReasonType.Unknown,
err: { name: 'test', message: 'unknown' },
}),
bootloaderDidFailToConnect(BootloaderConnectionFailureReason.Unknown),
bootloaderDidFailToConnect(BootloaderConnectionFailureReason.Unknown, <Error>{
message: 'test',
}),
bootloaderDidFailToConnect(BootloaderConnectionFailureReason.NoWebBluetooth),
bootloaderDidFailToConnect(BootloaderConnectionFailureReason.GattServiceNotFound),
storageChanged('test'),
+14 -2
View File
@@ -30,6 +30,7 @@ import { MpyActionType, MpyDidFailToCompileAction } from '../actions/mpy';
import { NotificationActionType, NotificationAddAction } from '../actions/notification';
import { ServiceWorkerActionType } from '../actions/service-worker';
import Notification from '../components/Notification';
import UnexpectedErrorNotification from '../components/UnexpectedErrorNotification';
import { MessageId } from '../components/notification-i18n';
import { appName } from '../settings/ui';
@@ -140,6 +141,17 @@ function* showSingleton(
);
}
/** Shows a special notification for unexpected errors. */
function* showUnexpectedError(messageId: MessageId, err: Error): Generator {
const { toaster } = (yield getContext('notification')) as NotificationContext;
toaster.show({
intent: mapIntent(Level.Error),
icon: mapIcon(Level.Error),
message: React.createElement(UnexpectedErrorNotification, { messageId, err }),
timeout: 0,
});
}
function* showBleDeviceDidFailToConnectError(
action: BleDeviceDidFailToConnectAction,
): Generator {
@@ -165,7 +177,7 @@ function* showBleDeviceDidFailToConnectError(
);
break;
case BleDeviceFailToConnectReasonType.Unknown:
yield* showSingleton(Level.Error, MessageId.BleConnectFailed);
yield* showUnexpectedError(MessageId.BleUnexpectedError, action.err);
break;
}
}
@@ -191,7 +203,7 @@ function* showBootloaderDidFailToConnectError(
);
break;
case BootloaderConnectionFailureReason.Unknown:
yield* showSingleton(Level.Error, MessageId.BleConnectFailed);
yield* showUnexpectedError(MessageId.BleUnexpectedError, action.err);
break;
}
}