From 4e2383cdb083ce1fd82381f11ed998bab01d6bc4 Mon Sep 17 00:00:00 2001 From: Michel Weststrate Date: Tue, 16 Mar 2021 14:54:53 -0700 Subject: [PATCH] Introduce showNotification API Summary: Introduced `showNotifcation` to the Sandy API. Reviewed By: jknoxville Differential Revision: D27012001 fbshipit-source-id: d3f237910a478400b0f925f0362af485c96072bb --- desktop/app/src/index.tsx | 3 +- desktop/app/src/plugin.tsx | 14 ++------- .../reducers/__tests__/notifications.node.tsx | 2 +- desktop/app/src/reducers/notifications.tsx | 3 +- .../notification/Notification.tsx | 3 +- .../src/utils/__tests__/exportData.node.tsx | 2 +- .../src/utils/flipperLibImplementation.tsx | 12 +++++++ .../flipper-plugin/src/__tests__/api.node.tsx | 1 + desktop/flipper-plugin/src/index.ts | 1 + .../flipper-plugin/src/plugin/FlipperLib.tsx | 2 ++ .../src/plugin/Notification.tsx | 19 ++++++++++++ .../flipper-plugin/src/plugin/PluginBase.tsx | 19 ++++++++++-- .../src/test-utils/test-utils.tsx | 1 + docs/extending/flipper-plugin.mdx | 31 +++++++++++++++++++ 14 files changed, 92 insertions(+), 21 deletions(-) create mode 100644 desktop/flipper-plugin/src/plugin/Notification.tsx diff --git a/desktop/app/src/index.tsx b/desktop/app/src/index.tsx index 43c438569..102c97b8f 100644 --- a/desktop/app/src/index.tsx +++ b/desktop/app/src/index.tsx @@ -28,7 +28,6 @@ export { FlipperPlugin, FlipperDevicePlugin, callClient, - Notification, BaseAction, } from './plugin'; export {PluginClient, Props} from './plugin'; @@ -42,7 +41,7 @@ export {connect} from 'react-redux'; export {selectPlugin, StaticView} from './reducers/connections'; export {writeBufferToFile, bufferToBlob} from './utils/screenshot'; export {getPluginKey, getPersistedState} from './utils/pluginUtils'; -export {Idler} from 'flipper-plugin'; +export {Idler, Notification} from 'flipper-plugin'; export {Store, MiddlewareAPI, State as ReduxState} from './reducers/index'; export {default as BaseDevice} from './devices/BaseDevice'; export {DeviceLogEntry, LogLevel, DeviceLogListener} from 'flipper-plugin'; diff --git a/desktop/app/src/plugin.tsx b/desktop/app/src/plugin.tsx index 9ee77c33c..8086c0dd9 100644 --- a/desktop/app/src/plugin.tsx +++ b/desktop/app/src/plugin.tsx @@ -11,7 +11,7 @@ import {KeyboardActions} from './MenuBar'; import {Logger} from './fb-interfaces/Logger'; import Client from './Client'; import {Store} from './reducers/index'; -import {ReactNode, Component} from 'react'; +import {Component} from 'react'; import BaseDevice from './devices/BaseDevice'; import {serialize, deserialize} from './utils/serialization'; import {StaticView} from './reducers/connections'; @@ -19,7 +19,7 @@ import {State as ReduxState} from './reducers'; import {DEFAULT_MAX_QUEUE_SIZE} from './reducers/pluginMessageQueue'; import {ActivatablePluginDetails} from 'flipper-plugin-lib'; import {Settings} from './reducers/settings'; -import {Idler, _SandyPluginDefinition} from 'flipper-plugin'; +import {Notification, Idler, _SandyPluginDefinition} from 'flipper-plugin'; type Parameters = {[key: string]: any}; @@ -68,16 +68,6 @@ export interface PluginClient { type PluginTarget = BaseDevice | Client; -export type Notification = { - id: string; - title: string; - message: string | ReactNode; - severity: 'warning' | 'error'; - timestamp?: number; - category?: string; - action?: string; -}; - export type Props = { logger: Logger; persistedState: T; diff --git a/desktop/app/src/reducers/__tests__/notifications.node.tsx b/desktop/app/src/reducers/__tests__/notifications.node.tsx index 65e0b37a1..5cdea7cf3 100644 --- a/desktop/app/src/reducers/__tests__/notifications.node.tsx +++ b/desktop/app/src/reducers/__tests__/notifications.node.tsx @@ -17,7 +17,7 @@ import { updateCategoryBlocklist, } from '../notifications'; -import {Notification} from '../../plugin'; +import {Notification} from 'flipper-plugin'; const notification: Notification = { id: 'id', diff --git a/desktop/app/src/reducers/notifications.tsx b/desktop/app/src/reducers/notifications.tsx index bc168fe44..6d8256f92 100644 --- a/desktop/app/src/reducers/notifications.tsx +++ b/desktop/app/src/reducers/notifications.tsx @@ -7,9 +7,10 @@ * @format */ -import {Notification} from '../plugin'; +import {Notification} from 'flipper-plugin'; import {Actions} from './'; import {getStringFromErrorLike} from '../utils'; + export type PluginNotification = { notification: Notification; pluginId: string; diff --git a/desktop/app/src/sandy-chrome/notification/Notification.tsx b/desktop/app/src/sandy-chrome/notification/Notification.tsx index 04d938996..03e8e79c5 100644 --- a/desktop/app/src/sandy-chrome/notification/Notification.tsx +++ b/desktop/app/src/sandy-chrome/notification/Notification.tsx @@ -8,7 +8,7 @@ */ import React, {useCallback, useMemo, useState} from 'react'; -import {Layout, theme} from 'flipper-plugin'; +import {Layout, theme, Notification as NotificationData} from 'flipper-plugin'; import {styled, Glyph} from '../../ui'; import {Input, Typography, Button, Collapse, Dropdown, Menu} from 'antd'; import { @@ -20,7 +20,6 @@ import { EllipsisOutlined, } from '@ant-design/icons'; import {LeftSidebar, SidebarTitle} from '../LeftSidebar'; -import {Notification as NotificationData} from '../../plugin'; import {useStore, useDispatch} from '../../utils/useStore'; import {ClientQuery} from '../../Client'; import {deconstructClientId} from '../../utils/clientUtils'; diff --git a/desktop/app/src/utils/__tests__/exportData.node.tsx b/desktop/app/src/utils/__tests__/exportData.node.tsx index 84a028597..abf26c04c 100644 --- a/desktop/app/src/utils/__tests__/exportData.node.tsx +++ b/desktop/app/src/utils/__tests__/exportData.node.tsx @@ -18,11 +18,11 @@ import { importDataToStore, } from '../exportData'; import {FlipperPlugin, FlipperDevicePlugin} from '../../plugin'; -import {Notification} from '../../plugin'; import {default as Client, ClientExport} from '../../Client'; import {selectedPlugins, State as PluginsState} from '../../reducers/plugins'; import {createMockFlipperWithPlugin} from '../../test-utils/createMockFlipperWithPlugin'; import { + Notification, TestUtils, _SandyPluginDefinition, createState, diff --git a/desktop/app/src/utils/flipperLibImplementation.tsx b/desktop/app/src/utils/flipperLibImplementation.tsx index 909535eaf..22d1882c1 100644 --- a/desktop/app/src/utils/flipperLibImplementation.tsx +++ b/desktop/app/src/utils/flipperLibImplementation.tsx @@ -15,6 +15,8 @@ import GK from '../fb-stubs/GK'; import type BaseDevice from '../devices/BaseDevice'; import {clipboard} from 'electron'; import constants from '../fb-stubs/constants'; +import {addNotification} from '../reducers/notifications'; +import {deconstructPluginKey} from './clientUtils'; export function initializeFlipperLibImplementation( store: Store, @@ -71,5 +73,15 @@ export function initializeFlipperLibImplementation( writeTextToClipboard(text: string) { clipboard.writeText(text); }, + showNotification(pluginId, notification) { + const parts = deconstructPluginKey(pluginId); + store.dispatch( + addNotification({ + pluginId: parts.pluginName, + client: parts.client, + notification, + }), + ); + }, }); } diff --git a/desktop/flipper-plugin/src/__tests__/api.node.tsx b/desktop/flipper-plugin/src/__tests__/api.node.tsx index d2efdbdd2..42771f7f3 100644 --- a/desktop/flipper-plugin/src/__tests__/api.node.tsx +++ b/desktop/flipper-plugin/src/__tests__/api.node.tsx @@ -72,6 +72,7 @@ test('Correct top level API exposed', () => { "Logger", "MenuEntry", "NormalizedMenuEntry", + "Notification", "PluginClient", "TrackType", ] diff --git a/desktop/flipper-plugin/src/index.ts b/desktop/flipper-plugin/src/index.ts index c78bfe313..0a157d13e 100644 --- a/desktop/flipper-plugin/src/index.ts +++ b/desktop/flipper-plugin/src/index.ts @@ -46,6 +46,7 @@ export { buildInMenuEntries as _buildInMenuEntries, DefaultKeyboardAction, } from './plugin/MenuEntry'; +export {Notification} from './plugin/Notification'; export {theme} from './ui/theme'; export {Layout} from './ui/Layout'; diff --git a/desktop/flipper-plugin/src/plugin/FlipperLib.tsx b/desktop/flipper-plugin/src/plugin/FlipperLib.tsx index e7e18556b..647762727 100644 --- a/desktop/flipper-plugin/src/plugin/FlipperLib.tsx +++ b/desktop/flipper-plugin/src/plugin/FlipperLib.tsx @@ -11,6 +11,7 @@ import {Logger} from '../utils/Logger'; import {RealFlipperDevice} from './DevicePlugin'; import {NormalizedMenuEntry} from './MenuEntry'; import {RealFlipperClient} from './Plugin'; +import {Notification} from './Notification'; /** * This interface exposes all global methods for which an implementation will be provided by Flipper itself @@ -33,6 +34,7 @@ export interface FlipperLib { deeplink: unknown, ): void; writeTextToClipboard(text: string): void; + showNotification(pluginKey: string, notification: Notification): void; } let flipperLibInstance: FlipperLib | undefined; diff --git a/desktop/flipper-plugin/src/plugin/Notification.tsx b/desktop/flipper-plugin/src/plugin/Notification.tsx new file mode 100644 index 000000000..5df1d1d2a --- /dev/null +++ b/desktop/flipper-plugin/src/plugin/Notification.tsx @@ -0,0 +1,19 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + */ + +export type Notification = { + id: string; + title: string; + message: string | React.ReactNode; + severity: 'warning' | 'error'; + timestamp?: number; + category?: string; + /** The action will be available as deeplink payload when the notification is clicked. */ + action?: string; +}; diff --git a/desktop/flipper-plugin/src/plugin/PluginBase.tsx b/desktop/flipper-plugin/src/plugin/PluginBase.tsx index ae46c6844..0694d109f 100644 --- a/desktop/flipper-plugin/src/plugin/PluginBase.tsx +++ b/desktop/flipper-plugin/src/plugin/PluginBase.tsx @@ -7,14 +7,15 @@ * @format */ -import {SandyPluginDefinition} from './SandyPluginDefinition'; +import {message} from 'antd'; import {EventEmitter} from 'events'; +import {SandyPluginDefinition} from './SandyPluginDefinition'; import {MenuEntry, NormalizedMenuEntry, normalizeMenuEntry} from './MenuEntry'; import {FlipperLib} from './FlipperLib'; import {Device, RealFlipperDevice} from './DevicePlugin'; import {batched} from '../state/batch'; import {Idler} from '../utils/Idler'; -import {message} from 'antd'; +import {Notification} from './Notification'; type StateExportHandler = ( idler: Idler, @@ -23,6 +24,9 @@ type StateExportHandler = ( type StateImportHandler = (data: T) => void; export interface BasePluginClient { + /** + * A key that uniquely identifies this plugin instance, captures the current device/client/plugin combination. + */ readonly pluginKey: string; readonly device: Device; @@ -74,6 +78,14 @@ export interface BasePluginClient { * Always returns `false` in open source. */ GK(gkName: string): boolean; + + /** + * Shows an urgent, system wide notification, that will also be registered in Flipper's notification pane. + * For on-screen notifications, we recommend to use either the `message` or `notification` API from `antd` directly. + * + * Clicking the notification will open this plugin. If the `action` id is set, it will be used as deeplink. + */ + showNotification(notification: Notification): void; } let currentPluginInstance: BasePluginInstance | undefined = undefined; @@ -262,6 +274,9 @@ export abstract class BasePluginInstance { }, createPaste: this.flipperLib.createPaste, GK: this.flipperLib.GK, + showNotification: (notification: Notification) => { + this.flipperLib.showNotification(this.pluginKey, notification); + }, }; } diff --git a/desktop/flipper-plugin/src/test-utils/test-utils.tsx b/desktop/flipper-plugin/src/test-utils/test-utils.tsx index 7ab522200..0e0d3cd1f 100644 --- a/desktop/flipper-plugin/src/test-utils/test-utils.tsx +++ b/desktop/flipper-plugin/src/test-utils/test-utils.tsx @@ -371,6 +371,7 @@ export function createMockFlipperLib(options?: StartPluginOptions): FlipperLib { selectPlugin: jest.fn(), isPluginAvailable: jest.fn().mockImplementation(() => false), writeTextToClipboard: jest.fn(), + showNotification: jest.fn(), }; } diff --git a/docs/extending/flipper-plugin.mdx b/docs/extending/flipper-plugin.mdx index 767b3ff9e..b46dbfb8b 100644 --- a/docs/extending/flipper-plugin.mdx +++ b/docs/extending/flipper-plugin.mdx @@ -50,6 +50,10 @@ The name of the application, for example 'Facebook', 'Instagram' or 'Slack'. A string that uniquely identifies the current application, is based on a combination of the application name and device serial on which the application is running. +#### `pluginKey` + +A key that uniquely identifies this plugin instance, captures the current device/client/plugin combination. + #### `isConnected` Returns whether there is currently an active connection. This is true if: @@ -268,6 +272,29 @@ Usage: `client.supportsMethod(method: string): Promise` Resolves to true if the client supports the specified method. Useful when adding functionality to existing plugins, when connectivity to older clients is still required. Also useful when client plugins are implemented on multitple platforms and don't all have feature parity. +#### showNotification + +Usage: `client.showNotification(notification)` + +Shows an urgent, system wide notification, that will also be registered in Flipper's notification pane. +For on-screen notifications, we recommend to use either the `message` or `notification` API from `antd` directly. + +Clicking the notification will open the sending plugin. If the `action` id is set, it will be used as deeplink. + +The notification interface is defined as: + +```typescript +interface Notification { + id: string; + title: string; + message: string | React.ReactNode; + severity: 'warning' | 'error'; + timestamp?: number; + category?: string; + action?: string; +}; +``` + #### `createPaste` Facebook only API. @@ -343,6 +370,10 @@ See the similarly named method under [`PluginClient`](#pluginclient). See the similarly named method under [`PluginClient`](#pluginclient). +#### `showNotification` + +See the similarly named method under [`PluginClient`](#pluginclient). + ### `isPluginAvailable` See the similarly named method under [`PluginClient`](#pluginclient).