From d3be7775b2fd3185b63d076d096e0264e4a3d512 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Mon, 18 Jul 2022 14:43:10 -0500 Subject: [PATCH] ble/alerts: move missing service from notifications --- src/ble/alerts/MissingService.tsx | 35 +++++++++++++++++++++++++++++ src/ble/alerts/i18n.ts | 3 +++ src/ble/alerts/index.ts | 3 ++- src/ble/alerts/translations/en.json | 5 +++++ src/ble/sagas.test.ts | 18 +++++++++++++++ src/ble/sagas.ts | 18 +++++++++++++++ src/notifications/sagas.test.ts | 22 ------------------ src/notifications/sagas.ts | 24 -------------------- 8 files changed, 81 insertions(+), 47 deletions(-) create mode 100644 src/ble/alerts/MissingService.tsx diff --git a/src/ble/alerts/MissingService.tsx b/src/ble/alerts/MissingService.tsx new file mode 100644 index 00000000..caef2390 --- /dev/null +++ b/src/ble/alerts/MissingService.tsx @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2022 The Pybricks Authors + +import { Intent } from '@blueprintjs/core'; +import React from 'react'; +import { CreateToast } from '../../i18nToaster'; +import { I18nId, useI18n } from './i18n'; + +type MissingServiceProps = { + serviceName: string; + hubName: string; +}; + +const MissingService: React.VoidFunctionComponent = ({ + serviceName, + hubName, +}) => { + const i18n = useI18n(); + return ( + <> +

{i18n.translate(I18nId.MissingServiceMessage, { serviceName })}

+

{i18n.translate(I18nId.MissingServiceSuggestion1)}

+

{i18n.translate(I18nId.MissingServiceSuggestion2, { hubName })}

+ + ); +}; + +export const missingService: CreateToast = (onAction, props) => { + return { + message: , + icon: 'error', + intent: Intent.DANGER, + onDismiss: () => onAction('dismiss'), + }; +}; diff --git a/src/ble/alerts/i18n.ts b/src/ble/alerts/i18n.ts index c48428f5..c524cbde 100644 --- a/src/ble/alerts/i18n.ts +++ b/src/ble/alerts/i18n.ts @@ -16,4 +16,7 @@ export enum I18nId { BluetoothNotAvailableMessage = 'bluetoothNotAvailable.message', BluetoothNotAvailableSuggestion = 'bluetoothNotAvailable.suggestion', NoGattMessage = 'noGatt.message', + MissingServiceMessage = 'missingService.message', + MissingServiceSuggestion1 = 'missingService.suggestion1', + MissingServiceSuggestion2 = 'missingService.suggestion2', } diff --git a/src/ble/alerts/index.ts b/src/ble/alerts/index.ts index d3f08281..65488a6a 100644 --- a/src/ble/alerts/index.ts +++ b/src/ble/alerts/index.ts @@ -2,8 +2,9 @@ // Copyright (c) 2022 The Pybricks Authors import { bluetoothNotAvailable } from './BluetoothNotAvailable'; +import { missingService } from './MissingService'; import { noGatt } from './NoGatt'; import { noWebBluetooth } from './NoWebBluetooth'; // gathers all of the alert creation functions for passing up to the top level -export default { bluetoothNotAvailable, noGatt, noWebBluetooth }; +export default { bluetoothNotAvailable, missingService, noGatt, noWebBluetooth }; diff --git a/src/ble/alerts/translations/en.json b/src/ble/alerts/translations/en.json index c1acbf34..3e90df14 100644 --- a/src/ble/alerts/translations/en.json +++ b/src/ble/alerts/translations/en.json @@ -11,5 +11,10 @@ }, "noGatt": { "message": "The web browser did not give permission to use Bluetooth Low Energy." + }, + "missingService": { + "message": "Connected to hub but failed to get {serviceName} service.", + "suggestion1": "Ensure that you are using the most recent firmware.", + "suggestion2": "If the problem persists, try removing the \"{hubName}\" device in your OS Bluetooth settings, then try connecting again." } } diff --git a/src/ble/sagas.test.ts b/src/ble/sagas.test.ts index ddccf7b3..7389def0 100644 --- a/src/ble/sagas.test.ts +++ b/src/ble/sagas.test.ts @@ -349,6 +349,12 @@ describe('connect action is dispatched', () => { await runConnectUntil(saga, ConnectRunPoint.Connect); + await expect(saga.take()).resolves.toEqual( + alertsShowAlert('ble', 'missingService', { + serviceName: 'Device Information', + hubName: 'test name', + }), + ); await expect(saga.take()).resolves.toEqual( bleDidFailToConnectPybricks({ reason: BleDeviceFailToConnectReasonType.NoDeviceInfoService, @@ -478,6 +484,12 @@ describe('connect action is dispatched', () => { await runConnectUntil(saga, ConnectRunPoint.DidReceivePnpId); + await expect(saga.take()).resolves.toEqual( + alertsShowAlert('ble', 'missingService', { + serviceName: 'Pybricks', + hubName: 'test name', + }), + ); await expect(saga.take()).resolves.toEqual( bleDidFailToConnectPybricks({ reason: BleDeviceFailToConnectReasonType.NoPybricksService, @@ -554,6 +566,12 @@ describe('connect action is dispatched', () => { await runConnectUntil(saga, ConnectRunPoint.DidReceivePnpId); + await expect(saga.take()).resolves.toEqual( + alertsShowAlert('ble', 'missingService', { + serviceName: 'Nordic UART', + hubName: 'test name', + }), + ); await expect(saga.take()).resolves.toEqual( bleDidFailToConnectPybricks({ // FIXME: this is wrong error diff --git a/src/ble/sagas.ts b/src/ble/sagas.ts index f9549b59..782a69ba 100644 --- a/src/ble/sagas.ts +++ b/src/ble/sagas.ts @@ -186,6 +186,12 @@ function* handleBleConnectPybricks(): Generator { ); if (!deviceInfoService) { + yield* put( + alertsShowAlert('ble', 'missingService', { + serviceName: 'Device Information', + hubName: device.name || 'Pybricks Hub', + }), + ); yield* put( bleDidFailToConnectPybricks({ reason: Reason.NoDeviceInfoService }), ); @@ -249,6 +255,12 @@ function* handleBleConnectPybricks(): Generator { ); if (!pybricksService) { + yield* put( + alertsShowAlert('ble', 'missingService', { + serviceName: 'Pybricks', + hubName: device.name || 'Pybricks Hub', + }), + ); yield* put( bleDidFailToConnectPybricks({ reason: Reason.NoPybricksService, @@ -312,6 +324,12 @@ function* handleBleConnectPybricks(): Generator { ); if (!uartService) { + yield* put( + alertsShowAlert('ble', 'missingService', { + serviceName: 'Nordic UART', + hubName: device.name || 'Pybricks Hub', + }), + ); yield* put( bleDidFailToConnectPybricks({ reason: Reason.NoPybricksService, diff --git a/src/notifications/sagas.test.ts b/src/notifications/sagas.test.ts index bc1543e2..ed48626c 100644 --- a/src/notifications/sagas.test.ts +++ b/src/notifications/sagas.test.ts @@ -12,10 +12,6 @@ import { AnyAction } from 'redux'; import { AsyncSaga, uuid } from '../../test'; import { appDidCheckForUpdate } from '../app/actions'; import { bleDIServiceDidReceiveFirmwareRevision } from '../ble-device-info-service/actions'; -import { - BleDeviceFailToConnectReasonType, - bleDidFailToConnectPybricks, -} from '../ble/actions'; import { editorDidFailToOpenFile } from '../editor/actions'; import { EditorError } from '../editor/error'; import { @@ -61,12 +57,6 @@ function createTestToasterSaga(): { toaster: IToaster; saga: AsyncSaga } { } test.each([ - bleDidFailToConnectPybricks({ - reason: BleDeviceFailToConnectReasonType.NoDeviceInfoService, - }), - bleDidFailToConnectPybricks({ - reason: BleDeviceFailToConnectReasonType.NoPybricksService, - }), bootloaderDidFailToConnect(BootloaderConnectionFailureReason.Unknown, { message: 'test', }), @@ -122,18 +112,6 @@ test.each([ }); test.each([ - bleDidFailToConnectPybricks({ - reason: BleDeviceFailToConnectReasonType.NoWebBluetooth, - }), - bleDidFailToConnectPybricks({ - reason: BleDeviceFailToConnectReasonType.NoBluetooth, - }), - bleDidFailToConnectPybricks({ reason: BleDeviceFailToConnectReasonType.Canceled }), - bleDidFailToConnectPybricks({ reason: BleDeviceFailToConnectReasonType.NoGatt }), - bleDidFailToConnectPybricks({ - reason: BleDeviceFailToConnectReasonType.Unknown, - err: { name: 'test', message: 'unknown' }, - }), bootloaderDidFailToConnect(BootloaderConnectionFailureReason.NoWebBluetooth), bootloaderDidFailToConnect(BootloaderConnectionFailureReason.NoBluetooth), bootloaderDidFailToConnect(BootloaderConnectionFailureReason.Canceled), diff --git a/src/notifications/sagas.ts b/src/notifications/sagas.ts index e7c3a09a..33636225 100644 --- a/src/notifications/sagas.ts +++ b/src/notifications/sagas.ts @@ -14,10 +14,6 @@ import { getAlertProps } from '../alerts'; import { appDidCheckForUpdate, appReload } from '../app/actions'; import { appName } from '../app/constants'; import { bleDIServiceDidReceiveFirmwareRevision } from '../ble-device-info-service/actions'; -import { - BleDeviceFailToConnectReasonType, - bleDidFailToConnectPybricks, -} from '../ble/actions'; import { editorDidFailToOpenFile } from '../editor/actions'; import { EditorError } from '../editor/error'; import { @@ -170,25 +166,6 @@ function* showUnexpectedError(messageId: I18nId, error: Error): Generator { ); } -function* showBleDeviceDidFailToConnectError( - action: ReturnType, -): Generator { - switch (action.reason) { - case BleDeviceFailToConnectReasonType.NoPybricksService: - yield* showSingleton(Level.Error, I18nId.BleGattServiceNotFound, { - serviceName: 'Pybricks', - hubName: 'Pybricks Hub', - }); - break; - case BleDeviceFailToConnectReasonType.NoDeviceInfoService: - yield* showSingleton(Level.Error, I18nId.BleGattServiceNotFound, { - serviceName: 'Device Information', - hubName: 'Pybricks Hub', - }); - break; - } -} - function* showBootloaderDidFailToConnectError( action: ReturnType, ): Generator { @@ -421,7 +398,6 @@ function* showExplorerFailToDelete( } export default function* (): Generator { - yield* takeEvery(bleDidFailToConnectPybricks, showBleDeviceDidFailToConnectError); yield* takeEvery(bootloaderDidFailToConnect, showBootloaderDidFailToConnectError); yield* takeEvery(didFailToFinish, showFlashFirmwareError); yield* takeEvery(didCompile, dismissCompilerError);