Introduce showNotification API

Summary: Introduced `showNotifcation` to the Sandy API.

Reviewed By: jknoxville

Differential Revision: D27012001

fbshipit-source-id: d3f237910a478400b0f925f0362af485c96072bb
This commit is contained in:
Michel Weststrate
2021-03-16 14:54:53 -07:00
committed by Facebook GitHub Bot
parent 2ca52f81d2
commit 4e2383cdb0
14 changed files with 92 additions and 21 deletions

View File

@@ -28,7 +28,6 @@ export {
FlipperPlugin, FlipperPlugin,
FlipperDevicePlugin, FlipperDevicePlugin,
callClient, callClient,
Notification,
BaseAction, BaseAction,
} from './plugin'; } from './plugin';
export {PluginClient, Props} from './plugin'; export {PluginClient, Props} from './plugin';
@@ -42,7 +41,7 @@ export {connect} from 'react-redux';
export {selectPlugin, StaticView} from './reducers/connections'; export {selectPlugin, StaticView} from './reducers/connections';
export {writeBufferToFile, bufferToBlob} from './utils/screenshot'; export {writeBufferToFile, bufferToBlob} from './utils/screenshot';
export {getPluginKey, getPersistedState} from './utils/pluginUtils'; 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 {Store, MiddlewareAPI, State as ReduxState} from './reducers/index';
export {default as BaseDevice} from './devices/BaseDevice'; export {default as BaseDevice} from './devices/BaseDevice';
export {DeviceLogEntry, LogLevel, DeviceLogListener} from 'flipper-plugin'; export {DeviceLogEntry, LogLevel, DeviceLogListener} from 'flipper-plugin';

View File

@@ -11,7 +11,7 @@ import {KeyboardActions} from './MenuBar';
import {Logger} from './fb-interfaces/Logger'; import {Logger} from './fb-interfaces/Logger';
import Client from './Client'; import Client from './Client';
import {Store} from './reducers/index'; import {Store} from './reducers/index';
import {ReactNode, Component} from 'react'; import {Component} from 'react';
import BaseDevice from './devices/BaseDevice'; import BaseDevice from './devices/BaseDevice';
import {serialize, deserialize} from './utils/serialization'; import {serialize, deserialize} from './utils/serialization';
import {StaticView} from './reducers/connections'; import {StaticView} from './reducers/connections';
@@ -19,7 +19,7 @@ import {State as ReduxState} from './reducers';
import {DEFAULT_MAX_QUEUE_SIZE} from './reducers/pluginMessageQueue'; import {DEFAULT_MAX_QUEUE_SIZE} from './reducers/pluginMessageQueue';
import {ActivatablePluginDetails} from 'flipper-plugin-lib'; import {ActivatablePluginDetails} from 'flipper-plugin-lib';
import {Settings} from './reducers/settings'; import {Settings} from './reducers/settings';
import {Idler, _SandyPluginDefinition} from 'flipper-plugin'; import {Notification, Idler, _SandyPluginDefinition} from 'flipper-plugin';
type Parameters = {[key: string]: any}; type Parameters = {[key: string]: any};
@@ -68,16 +68,6 @@ export interface PluginClient {
type PluginTarget = BaseDevice | Client; 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<T> = { export type Props<T> = {
logger: Logger; logger: Logger;
persistedState: T; persistedState: T;

View File

@@ -17,7 +17,7 @@ import {
updateCategoryBlocklist, updateCategoryBlocklist,
} from '../notifications'; } from '../notifications';
import {Notification} from '../../plugin'; import {Notification} from 'flipper-plugin';
const notification: Notification = { const notification: Notification = {
id: 'id', id: 'id',

View File

@@ -7,9 +7,10 @@
* @format * @format
*/ */
import {Notification} from '../plugin'; import {Notification} from 'flipper-plugin';
import {Actions} from './'; import {Actions} from './';
import {getStringFromErrorLike} from '../utils'; import {getStringFromErrorLike} from '../utils';
export type PluginNotification = { export type PluginNotification = {
notification: Notification; notification: Notification;
pluginId: string; pluginId: string;

View File

@@ -8,7 +8,7 @@
*/ */
import React, {useCallback, useMemo, useState} from 'react'; 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 {styled, Glyph} from '../../ui';
import {Input, Typography, Button, Collapse, Dropdown, Menu} from 'antd'; import {Input, Typography, Button, Collapse, Dropdown, Menu} from 'antd';
import { import {
@@ -20,7 +20,6 @@ import {
EllipsisOutlined, EllipsisOutlined,
} from '@ant-design/icons'; } from '@ant-design/icons';
import {LeftSidebar, SidebarTitle} from '../LeftSidebar'; import {LeftSidebar, SidebarTitle} from '../LeftSidebar';
import {Notification as NotificationData} from '../../plugin';
import {useStore, useDispatch} from '../../utils/useStore'; import {useStore, useDispatch} from '../../utils/useStore';
import {ClientQuery} from '../../Client'; import {ClientQuery} from '../../Client';
import {deconstructClientId} from '../../utils/clientUtils'; import {deconstructClientId} from '../../utils/clientUtils';

View File

@@ -18,11 +18,11 @@ import {
importDataToStore, importDataToStore,
} from '../exportData'; } from '../exportData';
import {FlipperPlugin, FlipperDevicePlugin} from '../../plugin'; import {FlipperPlugin, FlipperDevicePlugin} from '../../plugin';
import {Notification} from '../../plugin';
import {default as Client, ClientExport} from '../../Client'; import {default as Client, ClientExport} from '../../Client';
import {selectedPlugins, State as PluginsState} from '../../reducers/plugins'; import {selectedPlugins, State as PluginsState} from '../../reducers/plugins';
import {createMockFlipperWithPlugin} from '../../test-utils/createMockFlipperWithPlugin'; import {createMockFlipperWithPlugin} from '../../test-utils/createMockFlipperWithPlugin';
import { import {
Notification,
TestUtils, TestUtils,
_SandyPluginDefinition, _SandyPluginDefinition,
createState, createState,

View File

@@ -15,6 +15,8 @@ import GK from '../fb-stubs/GK';
import type BaseDevice from '../devices/BaseDevice'; import type BaseDevice from '../devices/BaseDevice';
import {clipboard} from 'electron'; import {clipboard} from 'electron';
import constants from '../fb-stubs/constants'; import constants from '../fb-stubs/constants';
import {addNotification} from '../reducers/notifications';
import {deconstructPluginKey} from './clientUtils';
export function initializeFlipperLibImplementation( export function initializeFlipperLibImplementation(
store: Store, store: Store,
@@ -71,5 +73,15 @@ export function initializeFlipperLibImplementation(
writeTextToClipboard(text: string) { writeTextToClipboard(text: string) {
clipboard.writeText(text); clipboard.writeText(text);
}, },
showNotification(pluginId, notification) {
const parts = deconstructPluginKey(pluginId);
store.dispatch(
addNotification({
pluginId: parts.pluginName,
client: parts.client,
notification,
}),
);
},
}); });
} }

View File

@@ -72,6 +72,7 @@ test('Correct top level API exposed', () => {
"Logger", "Logger",
"MenuEntry", "MenuEntry",
"NormalizedMenuEntry", "NormalizedMenuEntry",
"Notification",
"PluginClient", "PluginClient",
"TrackType", "TrackType",
] ]

View File

@@ -46,6 +46,7 @@ export {
buildInMenuEntries as _buildInMenuEntries, buildInMenuEntries as _buildInMenuEntries,
DefaultKeyboardAction, DefaultKeyboardAction,
} from './plugin/MenuEntry'; } from './plugin/MenuEntry';
export {Notification} from './plugin/Notification';
export {theme} from './ui/theme'; export {theme} from './ui/theme';
export {Layout} from './ui/Layout'; export {Layout} from './ui/Layout';

View File

@@ -11,6 +11,7 @@ import {Logger} from '../utils/Logger';
import {RealFlipperDevice} from './DevicePlugin'; import {RealFlipperDevice} from './DevicePlugin';
import {NormalizedMenuEntry} from './MenuEntry'; import {NormalizedMenuEntry} from './MenuEntry';
import {RealFlipperClient} from './Plugin'; import {RealFlipperClient} from './Plugin';
import {Notification} from './Notification';
/** /**
* This interface exposes all global methods for which an implementation will be provided by Flipper itself * 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, deeplink: unknown,
): void; ): void;
writeTextToClipboard(text: string): void; writeTextToClipboard(text: string): void;
showNotification(pluginKey: string, notification: Notification): void;
} }
let flipperLibInstance: FlipperLib | undefined; let flipperLibInstance: FlipperLib | undefined;

View File

@@ -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;
};

View File

@@ -7,14 +7,15 @@
* @format * @format
*/ */
import {SandyPluginDefinition} from './SandyPluginDefinition'; import {message} from 'antd';
import {EventEmitter} from 'events'; import {EventEmitter} from 'events';
import {SandyPluginDefinition} from './SandyPluginDefinition';
import {MenuEntry, NormalizedMenuEntry, normalizeMenuEntry} from './MenuEntry'; import {MenuEntry, NormalizedMenuEntry, normalizeMenuEntry} from './MenuEntry';
import {FlipperLib} from './FlipperLib'; import {FlipperLib} from './FlipperLib';
import {Device, RealFlipperDevice} from './DevicePlugin'; import {Device, RealFlipperDevice} from './DevicePlugin';
import {batched} from '../state/batch'; import {batched} from '../state/batch';
import {Idler} from '../utils/Idler'; import {Idler} from '../utils/Idler';
import {message} from 'antd'; import {Notification} from './Notification';
type StateExportHandler<T = any> = ( type StateExportHandler<T = any> = (
idler: Idler, idler: Idler,
@@ -23,6 +24,9 @@ type StateExportHandler<T = any> = (
type StateImportHandler<T = any> = (data: T) => void; type StateImportHandler<T = any> = (data: T) => void;
export interface BasePluginClient { export interface BasePluginClient {
/**
* A key that uniquely identifies this plugin instance, captures the current device/client/plugin combination.
*/
readonly pluginKey: string; readonly pluginKey: string;
readonly device: Device; readonly device: Device;
@@ -74,6 +78,14 @@ export interface BasePluginClient {
* Always returns `false` in open source. * Always returns `false` in open source.
*/ */
GK(gkName: string): boolean; 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; let currentPluginInstance: BasePluginInstance | undefined = undefined;
@@ -262,6 +274,9 @@ export abstract class BasePluginInstance {
}, },
createPaste: this.flipperLib.createPaste, createPaste: this.flipperLib.createPaste,
GK: this.flipperLib.GK, GK: this.flipperLib.GK,
showNotification: (notification: Notification) => {
this.flipperLib.showNotification(this.pluginKey, notification);
},
}; };
} }

View File

@@ -371,6 +371,7 @@ export function createMockFlipperLib(options?: StartPluginOptions): FlipperLib {
selectPlugin: jest.fn(), selectPlugin: jest.fn(),
isPluginAvailable: jest.fn().mockImplementation(() => false), isPluginAvailable: jest.fn().mockImplementation(() => false),
writeTextToClipboard: jest.fn(), writeTextToClipboard: jest.fn(),
showNotification: jest.fn(),
}; };
} }

View File

@@ -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. 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` #### `isConnected`
Returns whether there is currently an active connection. This is true if: Returns whether there is currently an active connection. This is true if:
@@ -268,6 +272,29 @@ Usage: `client.supportsMethod(method: string): Promise<Boolean>`
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. 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` #### `createPaste`
Facebook only API. Facebook only API.
@@ -343,6 +370,10 @@ See the similarly named method under [`PluginClient`](#pluginclient).
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` ### `isPluginAvailable`
See the similarly named method under [`PluginClient`](#pluginclient). See the similarly named method under [`PluginClient`](#pluginclient).