From 91938e34d96452d9de46590dd913fb659d25c855 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Tue, 19 Jan 2021 18:49:32 -0600 Subject: [PATCH] add special notification for unexpected errors Since these errors are not expected, we want to make it really easy to report them. --- src/components/Notification.tsx | 6 +-- .../UnexpectedErrorNotification.tsx | 54 +++++++++++++++++++ src/components/notification-i18n.en.json | 4 +- src/components/notification-i18n.ts | 4 +- src/sagas/notification.test.ts | 4 +- src/sagas/notification.ts | 16 +++++- 6 files changed, 80 insertions(+), 8 deletions(-) create mode 100644 src/components/UnexpectedErrorNotification.tsx diff --git a/src/components/Notification.tsx b/src/components/Notification.tsx index 7a2edbee..3976044f 100644 --- a/src/components/Notification.tsx +++ b/src/components/Notification.tsx @@ -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; diff --git a/src/components/UnexpectedErrorNotification.tsx b/src/components/UnexpectedErrorNotification.tsx new file mode 100644 index 00000000..8f9957ea --- /dev/null +++ b/src/components/UnexpectedErrorNotification.tsx @@ -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 ( + <> +

{i18n.translate(messageId, { errorMessage: err.message })}

+
+ + + + {i18n.translate(MessageId.ReportBug)} + + +
+ + ); +} diff --git a/src/components/notification-i18n.en.json b/src/components/notification-i18n.en.json index 90dc0233..d0ae6e09 100644 --- a/src/components/notification-i18n.en.json +++ b/src/components/notification-i18n.en.json @@ -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": { diff --git a/src/components/notification-i18n.ts b/src/components/notification-i18n.ts index e5948e18..48414268 100644 --- a/src/components/notification-i18n.ts +++ b/src/components/notification-i18n.ts @@ -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', diff --git a/src/sagas/notification.test.ts b/src/sagas/notification.test.ts index 5ffd07f1..9b529eb9 100644 --- a/src/sagas/notification.test.ts +++ b/src/sagas/notification.test.ts @@ -27,7 +27,9 @@ test.each([ reason: BleDeviceFailToConnectReasonType.Unknown, err: { name: 'test', message: 'unknown' }, }), - bootloaderDidFailToConnect(BootloaderConnectionFailureReason.Unknown), + bootloaderDidFailToConnect(BootloaderConnectionFailureReason.Unknown, { + message: 'test', + }), bootloaderDidFailToConnect(BootloaderConnectionFailureReason.NoWebBluetooth), bootloaderDidFailToConnect(BootloaderConnectionFailureReason.GattServiceNotFound), storageChanged('test'), diff --git a/src/sagas/notification.ts b/src/sagas/notification.ts index d386583b..687e0e59 100644 --- a/src/sagas/notification.ts +++ b/src/sagas/notification.ts @@ -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; } }