Show a message if a background plugin is not starred

Summary:
Since background plugins don't receive data anymore when not starred, we should hint the user about this.

For this diff, I reused the existing statusbar. Although this solution is quite ugly, I think it is better than introducing yet another notification / warning mechanism. Probably we should revisit the layout of this status bar in the future.

Reviewed By: jknoxville

Differential Revision: D19251588

fbshipit-source-id: 1dfd07be383d4ba318f344ebff4b08ed36194c58
This commit is contained in:
Michel Weststrate
2020-01-02 07:12:06 -08:00
committed by Facebook Github Bot
parent 04fcaddded
commit 9acf23596e
5 changed files with 86 additions and 39 deletions

View File

@@ -80,7 +80,13 @@ static persistedStateReducer<PersistedState>(
The job of the `persistedStateReducer` is to merge incoming data into the state, so that next time the plugin is activated, the persisted state will be ready.
If a plugin has a `persistedStateReducer`, and the plugin is not open in flipper, incoming messages are queued until the plugin is opened.
The amount of events that is cached for a plugin is controlled by the optional static field `maxQueueSize`, which by default is set to `10000` events.
The number of events that are cached for a plugin is controlled by the optional static field `maxQueueSize`, which defaults to `10000` events.
<div class="warning">
Note that if a plugin is not starred by the user, it will not receive any messages when it is not selected by the user. Even when it has a `persistedStateReducer`. This prevents plugins that are not actively used by the user from wasting a lot of CPU / memory.
</div>
The data that is produced from `persistedStateReducer` should be immutable, but also structurally sharing unchanged parts of the state with the previous state to avoid performance hiccups. To simplify this process we recommend using the [Immer](https://immerjs.github.io/immer/docs/introduction) package.
Immer makes it possible to keep the reducer concise by directly supporting "writing" to the current state, and keeping track of that in the background.

View File

@@ -33,7 +33,7 @@ import React, {PureComponent} from 'react';
import {connect, ReactReduxContext} from 'react-redux';
import {setPluginState} from './reducers/pluginStates';
import {selectPlugin} from './reducers/connections';
import {State as Store} from './reducers/index';
import {State as Store, MiddlewareAPI} from './reducers/index';
import {activateMenuItems} from './MenuBar';
import {Message} from './reducers/pluginMessageQueue';
import {Idler} from './utils/Idler';
@@ -146,6 +146,10 @@ class PluginContainer extends PureComponent<Props, State> {
state = {progress: {current: 0, total: 0}};
get store(): MiddlewareAPI {
return this.context.store;
}
componentWillUnmount() {
if (this.plugin) {
this.plugin._teardown();
@@ -179,7 +183,7 @@ class PluginContainer extends PureComponent<Props, State> {
processMessageQueue(
activePlugin,
pluginKey,
this.context.store,
this.store,
progress => {
this.setState({progress});
},

View File

@@ -8,11 +8,12 @@
*/
import {colors} from '../ui/components/colors';
import {styled} from '../ui';
import {styled, Glyph} from '../ui';
import {connect} from 'react-redux';
import {State} from '../reducers';
import React, {ReactElement} from 'react';
import Text from '../ui/components/Text';
import {pluginIsStarred} from '../reducers/connections';
const StatusBarContainer = styled(Text)({
backgroundColor: colors.macOSTitleBarBackgroundBlur,
@@ -21,17 +22,18 @@ const StatusBarContainer = styled(Text)({
padding: '0 10px',
textOverflow: 'ellipsis',
overflow: 'hidden',
textAlign: 'center',
});
type Props = {
statusMessage: string | null;
statusMessage: React.ReactNode | string | null;
};
export function statusBarView(props: Props): ReactElement | null {
const {statusMessage} = props;
if (statusMessage) {
return (
<StatusBarContainer italic={true} whiteSpace="nowrap">
<StatusBarContainer whiteSpace="nowrap">
{statusMessage}
</StatusBarContainer>
);
@@ -40,13 +42,45 @@ export function statusBarView(props: Props): ReactElement | null {
}
}
export default connect<Props, void, {}, State>(
({application: {statusMessages}}) => {
if (statusMessages.length > 0) {
return {statusMessage: statusMessages[statusMessages.length - 1]};
}
export default connect<Props, void, {}, State>((state: State) => {
const {
application: {statusMessages},
} = state;
if (statusMessages.length > 0) {
return {statusMessage: statusMessages[statusMessages.length - 1]};
} else if (isPreviewingBackgroundPlugin(state)) {
return {
statusMessage: null,
statusMessage: (
<>
<Glyph
name="caution-triangle"
color={colors.light20}
size={12}
variant="filled"
style={{marginRight: 8}}
/>
The current plugin would like to send messages while it is in the
background. However, since this plugin is not starred, these messages
will be dropped. Star this plugin to unlock its full capabilities.
</>
),
};
},
)(statusBarView);
}
return {
statusMessage: null,
};
})(statusBarView);
function isPreviewingBackgroundPlugin(state: State): boolean {
const {
connections: {selectedApp, selectedPlugin},
} = state;
if (!selectedPlugin || !selectedApp) {
return false;
}
const activePlugin = state.plugins.clientPlugins.get(selectedPlugin);
if (!activePlugin || !activePlugin.persistedStateReducer) {
return false;
}
return !pluginIsStarred(state.connections, selectedPlugin);
}

View File

@@ -7,6 +7,8 @@
* @format
*/
import {produce} from 'immer';
import BaseDevice from '../devices/BaseDevice';
import MacDevice from '../devices/MacDevice';
import Client from '../Client';
@@ -23,7 +25,8 @@ import NotificationScreen from '../chrome/NotificationScreen';
import SupportRequestForm from '../fb-stubs/SupportRequestFormManager';
import SupportRequestFormV2 from '../fb-stubs/SupportRequestFormV2';
import SupportRequestDetails from '../fb-stubs/SupportRequestDetails';
import {produce} from 'immer';
import {getPluginKey} from '../utils/pluginUtils';
import {deconstructClientId} from '../utils/clientUtils';
export type StaticView =
| null
@@ -581,3 +584,23 @@ function updateSelection(state: Readonly<State>): State {
return {...state, ...updates};
}
export function getSelectedPluginKey(state: State): string | undefined {
return state.selectedPlugin
? getPluginKey(
state.selectedApp,
state.selectedDevice,
state.selectedPlugin,
)
: undefined;
}
export function pluginIsStarred(state: State, pluginId: string): boolean {
const {selectedApp} = state;
if (!selectedApp) {
return false;
}
const appInfo = deconstructClientId(selectedApp);
const starred = state.userStarredPlugins[appInfo.app];
return starred && starred.indexOf(pluginId) > -1;
}

View File

@@ -17,8 +17,7 @@ import {
Message,
} from '../reducers/pluginMessageQueue';
import {Idler, BaseIdler} from './Idler';
import {getPluginKey} from './pluginUtils';
import {deconstructClientId} from './clientUtils';
import {pluginIsStarred, getSelectedPluginKey} from '../reducers/connections';
const MAX_BACKGROUND_TASK_TIME = 25;
@@ -138,14 +137,15 @@ export function processMessageLater(
},
message: {method: string; params?: any},
) {
const isSelected = pluginKey === getSelectedPluginKey(store.getState());
const isSelected =
pluginKey === getSelectedPluginKey(store.getState().connections);
switch (true) {
case isSelected && getPendingMessages(store, pluginKey).length === 0:
processMessageImmediately(store, pluginKey, plugin, message);
break;
case isSelected:
case plugin instanceof FlipperDevicePlugin:
case pluginIsStarred(store.getState(), plugin.id):
case pluginIsStarred(store.getState().connections, plugin.id):
store.dispatch(
queueMessage(
pluginKey,
@@ -158,26 +158,6 @@ export function processMessageLater(
}
}
function getSelectedPluginKey(state: State): string | undefined {
return state.connections.selectedPlugin
? getPluginKey(
state.connections.selectedApp,
state.connections.selectedDevice,
state.connections.selectedPlugin,
)
: undefined;
}
function pluginIsStarred(state: State, pluginId: string): boolean {
const {selectedApp} = state.connections;
if (!selectedApp) {
return false;
}
const appInfo = deconstructClientId(selectedApp);
const starred = state.connections.userStarredPlugins[appInfo.app];
return starred && starred.indexOf(pluginId) > -1;
}
export async function processMessageQueue(
plugin: {
defaultPersistedState: any;