Unify computation of available plugins
Summary: While trying to change something, discovered we have 3 different mechanisms in our code base to compute active plugins; the plugin list component, support form, and export flipper trace form had all their own, subtly different implementations of computing which plugins are available to the user. Also removed some hardcoded exceptions for e.g. Logs plugin, which in the next diff and onward will be just a vanilla plugin without special casing Unified that, which some how went a bit deeper than hoped, trough some hoops in in circular deps. Also unified to use the same testing utils, to avoid some gobbling objects manually together, with resulted in a bunch of unexpected NPEs. Found out that we actually still have unit tests using Flow in the process :-P. Converted one to TS. Reviewed By: nikoant Differential Revision: D26103172 fbshipit-source-id: 2fce2577d97d98543cb9312b3d013f24faee43aa
This commit is contained in:
committed by
Facebook GitHub Bot
parent
5320015776
commit
e1daa449ba
@@ -938,6 +938,7 @@ test('Sandy plugins support isPluginSupported + selectPlugin', async () => {
|
|||||||
definition,
|
definition,
|
||||||
{
|
{
|
||||||
additionalPlugins: [definition2, definition3],
|
additionalPlugins: [definition2, definition3],
|
||||||
|
dontEnableAdditionalPlugins: true,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import {ShareType} from '../reducers/application';
|
|||||||
import {State as Store} from '../reducers';
|
import {State as Store} from '../reducers';
|
||||||
import {ActiveSheet} from '../reducers/application';
|
import {ActiveSheet} from '../reducers/application';
|
||||||
import {selectedPlugins as actionForSelectedPlugins} from '../reducers/plugins';
|
import {selectedPlugins as actionForSelectedPlugins} from '../reducers/plugins';
|
||||||
import {getActivePersistentPlugins} from '../utils/pluginUtils';
|
import {getExportablePlugins} from '../utils/pluginUtils';
|
||||||
import {
|
import {
|
||||||
ACTIVE_SHEET_SHARE_DATA,
|
ACTIVE_SHEET_SHARE_DATA,
|
||||||
setActiveSheet as getActiveSheetAction,
|
setActiveSheet as getActiveSheetAction,
|
||||||
@@ -103,25 +103,18 @@ class ExportDataPluginSheet extends Component<Props, {}> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default connect<StateFromProps, DispatchFromProps, OwnProps, Store>(
|
export default connect<StateFromProps, DispatchFromProps, OwnProps, Store>(
|
||||||
({
|
(state) => {
|
||||||
application: {share},
|
const selectedClient = state.connections.clients.find((o) => {
|
||||||
plugins,
|
return o.id === state.connections.selectedApp;
|
||||||
pluginStates,
|
|
||||||
pluginMessageQueue,
|
|
||||||
connections: {selectedApp, clients},
|
|
||||||
}) => {
|
|
||||||
const selectedClient = clients.find((o) => {
|
|
||||||
return o.id === selectedApp;
|
|
||||||
});
|
});
|
||||||
const availablePluginsToExport = getActivePersistentPlugins(
|
const availablePluginsToExport = getExportablePlugins(
|
||||||
pluginStates,
|
state,
|
||||||
pluginMessageQueue,
|
state.connections.selectedDevice ?? undefined,
|
||||||
plugins,
|
|
||||||
selectedClient,
|
selectedClient,
|
||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
share,
|
share: state.application.share,
|
||||||
selectedPlugins: plugins.selectedPlugins,
|
selectedPlugins: state.plugins.selectedPlugins,
|
||||||
availablePluginsToExport,
|
availablePluginsToExport,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -159,6 +159,9 @@ class RowComponent extends Component<RowComponentProps> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated use Ant Design instead
|
||||||
|
*/
|
||||||
export default class ListView extends Component<Props, State> {
|
export default class ListView extends Component<Props, State> {
|
||||||
state: State = {selectedElements: new Set([])};
|
state: State = {selectedElements: new Set([])};
|
||||||
static getDerivedStateFromProps(props: Props, _state: State) {
|
static getDerivedStateFromProps(props: Props, _state: State) {
|
||||||
|
|||||||
@@ -8,96 +8,79 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Client from '../../Client';
|
|
||||||
import {create, act, ReactTestRenderer} from 'react-test-renderer';
|
import {create, act, ReactTestRenderer} from 'react-test-renderer';
|
||||||
import configureStore from 'redux-mock-store';
|
|
||||||
import {Provider} from 'react-redux';
|
import {Provider} from 'react-redux';
|
||||||
import {default as BaseDevice} from '../../devices/BaseDevice';
|
|
||||||
import ExportDataPluginSheet from '../ExportDataPluginSheet';
|
import ExportDataPluginSheet from '../ExportDataPluginSheet';
|
||||||
import {FlipperPlugin, FlipperDevicePlugin} from '../../plugin';
|
import {FlipperPlugin, FlipperDevicePlugin} from '../../plugin';
|
||||||
|
import {getExportablePlugins, getPluginKey} from '../../utils/pluginUtils';
|
||||||
|
import {createMockFlipperWithPlugin} from '../../test-utils/createMockFlipperWithPlugin';
|
||||||
|
import {setPluginState} from '../../reducers/pluginStates';
|
||||||
|
|
||||||
function generateClientIdentifier(device: BaseDevice, app: string): string {
|
class TestPlugin extends FlipperPlugin<any, any, any> {
|
||||||
const {os, deviceType, serial} = device;
|
static details = {
|
||||||
const identifier = `${app}#${os}#${deviceType}#${serial}`;
|
title: 'TestPlugin',
|
||||||
return identifier;
|
id: 'TestPlugin',
|
||||||
|
} as any;
|
||||||
}
|
}
|
||||||
|
|
||||||
class TestPlugin extends FlipperPlugin<any, any, any> {}
|
|
||||||
TestPlugin.title = 'TestPlugin';
|
TestPlugin.title = 'TestPlugin';
|
||||||
TestPlugin.id = 'TestPlugin';
|
TestPlugin.id = 'TestPlugin';
|
||||||
TestPlugin.defaultPersistedState = {msg: 'Test plugin'};
|
TestPlugin.defaultPersistedState = {msg: 'Test plugin'};
|
||||||
class TestDevicePlugin extends FlipperDevicePlugin<any, any, any> {}
|
|
||||||
|
class TestDevicePlugin extends FlipperDevicePlugin<any, any, any> {
|
||||||
|
static details = {
|
||||||
|
title: 'TestDevicePlugin',
|
||||||
|
id: 'TestDevicePlugin',
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
static supportsDevice() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
TestDevicePlugin.title = 'TestDevicePlugin';
|
TestDevicePlugin.title = 'TestDevicePlugin';
|
||||||
TestDevicePlugin.id = 'TestDevicePlugin';
|
TestDevicePlugin.id = 'TestDevicePlugin';
|
||||||
TestDevicePlugin.defaultPersistedState = {msg: 'TestDevicePlugin'};
|
TestDevicePlugin.defaultPersistedState = {msg: 'TestDevicePlugin'};
|
||||||
|
|
||||||
function getStore(selectedPlugins: Array<string>) {
|
|
||||||
const logger = {
|
|
||||||
track: () => {},
|
|
||||||
info: () => {},
|
|
||||||
warn: () => {},
|
|
||||||
error: () => {},
|
|
||||||
debug: () => {},
|
|
||||||
trackTimeSince: () => {},
|
|
||||||
};
|
|
||||||
const selectedDevice = new BaseDevice(
|
|
||||||
'serial',
|
|
||||||
'emulator',
|
|
||||||
'TestiPhone',
|
|
||||||
'iOS',
|
|
||||||
);
|
|
||||||
|
|
||||||
const clientId = generateClientIdentifier(selectedDevice, 'app');
|
|
||||||
const pluginStates: {[key: string]: any} = {};
|
|
||||||
pluginStates[`${clientId}#TestDevicePlugin`] = {
|
|
||||||
msg: 'Test Device plugin',
|
|
||||||
};
|
|
||||||
pluginStates[`${clientId}#TestPlugin`] = {
|
|
||||||
msg: 'Test plugin',
|
|
||||||
};
|
|
||||||
const mockStore = configureStore([])({
|
|
||||||
application: {share: {closeOnFinish: false, type: 'link'}},
|
|
||||||
plugins: {
|
|
||||||
clientPlugins: new Map([['TestPlugin', TestPlugin]]),
|
|
||||||
devicePlugins: new Map([['TestDevicePlugin', TestDevicePlugin]]),
|
|
||||||
gatekeepedPlugins: [],
|
|
||||||
disabledPlugins: [],
|
|
||||||
failedPlugins: [],
|
|
||||||
selectedPlugins,
|
|
||||||
},
|
|
||||||
pluginStates,
|
|
||||||
pluginMessageQueue: [],
|
|
||||||
connections: {selectedApp: clientId, clients: []},
|
|
||||||
});
|
|
||||||
|
|
||||||
const client = new Client(
|
|
||||||
clientId,
|
|
||||||
{
|
|
||||||
app: 'app',
|
|
||||||
os: 'iOS',
|
|
||||||
device: 'TestiPhone',
|
|
||||||
device_id: 'serial',
|
|
||||||
},
|
|
||||||
null,
|
|
||||||
logger,
|
|
||||||
// @ts-ignore
|
|
||||||
mockStore,
|
|
||||||
['TestPlugin', 'TestDevicePlugin'],
|
|
||||||
selectedDevice,
|
|
||||||
);
|
|
||||||
mockStore.dispatch({
|
|
||||||
type: 'NEW_CLIENT',
|
|
||||||
payload: client,
|
|
||||||
});
|
|
||||||
|
|
||||||
return mockStore;
|
|
||||||
}
|
|
||||||
|
|
||||||
test('SettingsSheet snapshot with nothing enabled', async () => {
|
test('SettingsSheet snapshot with nothing enabled', async () => {
|
||||||
let root: ReactTestRenderer;
|
let root: ReactTestRenderer;
|
||||||
|
const {
|
||||||
|
store,
|
||||||
|
togglePlugin,
|
||||||
|
client,
|
||||||
|
device,
|
||||||
|
pluginKey,
|
||||||
|
} = await createMockFlipperWithPlugin(TestPlugin, {
|
||||||
|
additionalPlugins: [TestDevicePlugin],
|
||||||
|
});
|
||||||
|
|
||||||
|
togglePlugin();
|
||||||
|
|
||||||
|
store.dispatch(
|
||||||
|
setPluginState({
|
||||||
|
pluginKey,
|
||||||
|
state: {test: '1'},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(getExportablePlugins(store.getState(), device, client)).toEqual([]);
|
||||||
|
|
||||||
|
// makes device plugin visible
|
||||||
|
store.dispatch(
|
||||||
|
setPluginState({
|
||||||
|
pluginKey: getPluginKey(undefined, device, 'TestDevicePlugin'),
|
||||||
|
state: {test: '1'},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(getExportablePlugins(store.getState(), device, client)).toEqual([
|
||||||
|
{
|
||||||
|
id: 'TestDevicePlugin',
|
||||||
|
label: 'TestDevicePlugin',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
root = create(
|
root = create(
|
||||||
<Provider store={getStore([])}>
|
<Provider store={store}>
|
||||||
<ExportDataPluginSheet onHide={() => {}} />
|
<ExportDataPluginSheet onHide={() => {}} />
|
||||||
</Provider>,
|
</Provider>,
|
||||||
);
|
);
|
||||||
@@ -108,9 +91,42 @@ test('SettingsSheet snapshot with nothing enabled', async () => {
|
|||||||
|
|
||||||
test('SettingsSheet snapshot with one plugin enabled', async () => {
|
test('SettingsSheet snapshot with one plugin enabled', async () => {
|
||||||
let root: ReactTestRenderer;
|
let root: ReactTestRenderer;
|
||||||
|
const {store, device, client, pluginKey} = await createMockFlipperWithPlugin(
|
||||||
|
TestPlugin,
|
||||||
|
{
|
||||||
|
additionalPlugins: [TestDevicePlugin],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// enabled, but no data
|
||||||
|
expect(getExportablePlugins(store.getState(), device, client)).toEqual([]);
|
||||||
|
|
||||||
|
store.dispatch(
|
||||||
|
setPluginState({
|
||||||
|
pluginKey,
|
||||||
|
state: {test: '1'},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
store.dispatch(
|
||||||
|
setPluginState({
|
||||||
|
pluginKey: getPluginKey(undefined, device, 'TestDevicePlugin'),
|
||||||
|
state: {test: '1'},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
expect(getExportablePlugins(store.getState(), device, client)).toEqual([
|
||||||
|
{
|
||||||
|
id: 'TestDevicePlugin',
|
||||||
|
label: 'TestDevicePlugin',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'TestPlugin',
|
||||||
|
label: 'TestPlugin',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
root = create(
|
root = create(
|
||||||
<Provider store={getStore(['TestPlugin'])}>
|
<Provider store={store}>
|
||||||
<ExportDataPluginSheet onHide={() => {}} />
|
<ExportDataPluginSheet onHide={() => {}} />
|
||||||
</Provider>,
|
</Provider>,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -59,47 +59,6 @@ exports[`SettingsSheet snapshot with nothing enabled 1`] = `
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
|
||||||
className="css-18abd42-View-FlexBox-FlexColumn ecr18to0"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="css-auhar3-TooltipContainer e1abcqbd0"
|
|
||||||
onMouseEnter={[Function]}
|
|
||||||
onMouseLeave={[Function]}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="css-1jrm6r3"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="css-wospjg-View-FlexBox-FlexRow epz0qe20"
|
|
||||||
style={
|
|
||||||
Object {
|
|
||||||
"alignItems": "center",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
className="css-xsnw23-Text e19o3fcp0"
|
|
||||||
>
|
|
||||||
TestPlugin
|
|
||||||
</span>
|
|
||||||
<div
|
|
||||||
className="css-t4wmtk-View-FlexBox-Spacer e13mj6h80"
|
|
||||||
/>
|
|
||||||
<input
|
|
||||||
checked={false}
|
|
||||||
className="css-1pxrk7-CheckboxContainer e28aqfo0"
|
|
||||||
disabled={false}
|
|
||||||
onChange={[Function]}
|
|
||||||
type="checkbox"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="css-1p0wwd3-View"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@@ -233,7 +192,7 @@ exports[`SettingsSheet snapshot with one plugin enabled 1`] = `
|
|||||||
className="css-t4wmtk-View-FlexBox-Spacer e13mj6h80"
|
className="css-t4wmtk-View-FlexBox-Spacer e13mj6h80"
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
checked={true}
|
checked={false}
|
||||||
className="css-1pxrk7-CheckboxContainer e28aqfo0"
|
className="css-1pxrk7-CheckboxContainer e28aqfo0"
|
||||||
disabled={false}
|
disabled={false}
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
@@ -277,7 +236,7 @@ exports[`SettingsSheet snapshot with one plugin enabled 1`] = `
|
|||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
className="ant-btn ant-btn-primary"
|
className="ant-btn ant-btn-primary"
|
||||||
disabled={false}
|
disabled={true}
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -7,13 +7,12 @@
|
|||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {Store} from '../reducers/index';
|
import type {Store} from '../reducers/index';
|
||||||
import {Logger} from '../fb-interfaces/Logger';
|
import type {Logger} from '../fb-interfaces/Logger';
|
||||||
import {PluginDefinition} from '../plugin';
|
import type {PluginDefinition} from '../plugin';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import adbkit from 'adbkit';
|
import adbkit from 'adbkit';
|
||||||
import * as Flipper from '../index';
|
|
||||||
import {
|
import {
|
||||||
registerPlugins,
|
registerPlugins,
|
||||||
addGatekeepedPlugins,
|
addGatekeepedPlugins,
|
||||||
@@ -52,7 +51,7 @@ export default async (store: Store, logger: Logger) => {
|
|||||||
const globalObject: any = typeof window === 'undefined' ? global : window;
|
const globalObject: any = typeof window === 'undefined' ? global : window;
|
||||||
globalObject.React = React;
|
globalObject.React = React;
|
||||||
globalObject.ReactDOM = ReactDOM;
|
globalObject.ReactDOM = ReactDOM;
|
||||||
globalObject.Flipper = Flipper;
|
globalObject.Flipper = require('../index');
|
||||||
globalObject.adbkit = adbkit;
|
globalObject.adbkit = adbkit;
|
||||||
globalObject.FlipperPlugin = FlipperPluginSDK;
|
globalObject.FlipperPlugin = FlipperPluginSDK;
|
||||||
globalObject.Immer = Immer;
|
globalObject.Immer = Immer;
|
||||||
|
|||||||
@@ -13,15 +13,15 @@ import {produce} from 'immer';
|
|||||||
import type BaseDevice from '../devices/BaseDevice';
|
import type BaseDevice from '../devices/BaseDevice';
|
||||||
import MacDevice from '../devices/MacDevice';
|
import MacDevice from '../devices/MacDevice';
|
||||||
import type Client from '../Client';
|
import type Client from '../Client';
|
||||||
import {UninitializedClient} from '../UninitializedClient';
|
import type {UninitializedClient} from '../UninitializedClient';
|
||||||
import {isEqual} from 'lodash';
|
import {isEqual} from 'lodash';
|
||||||
import {performance} from 'perf_hooks';
|
import {performance} from 'perf_hooks';
|
||||||
import {Actions} from '.';
|
import type {Actions} from '.';
|
||||||
import {WelcomeScreenStaticView} from '../sandy-chrome/WelcomeScreen';
|
import {WelcomeScreenStaticView} from '../sandy-chrome/WelcomeScreen';
|
||||||
import {getPluginKey, isDevicePluginDefinition} from '../utils/pluginUtils';
|
import {getPluginKey, isDevicePluginDefinition} from '../utils/pluginUtils';
|
||||||
import {deconstructClientId} from '../utils/clientUtils';
|
import {deconstructClientId} from '../utils/clientUtils';
|
||||||
import {PluginDefinition} from '../plugin';
|
import type {PluginDefinition} from '../plugin';
|
||||||
import {RegisterPluginAction} from './plugins';
|
import type {RegisterPluginAction} from './plugins';
|
||||||
import MetroDevice from '../devices/MetroDevice';
|
import MetroDevice from '../devices/MetroDevice';
|
||||||
import {Logger} from 'flipper-plugin';
|
import {Logger} from 'flipper-plugin';
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {Actions} from '.';
|
import type {Actions} from '.';
|
||||||
import {deconstructPluginKey} from '../utils/clientUtils';
|
import {deconstructPluginKey} from '../utils/clientUtils';
|
||||||
|
|
||||||
export type State = {
|
export type State = {
|
||||||
@@ -31,10 +31,8 @@ export type Action =
|
|||||||
payload: {clientId: string; devicePlugins: Set<string>};
|
payload: {clientId: string; devicePlugins: Set<string>};
|
||||||
};
|
};
|
||||||
|
|
||||||
const INITIAL_STATE: State = {};
|
|
||||||
|
|
||||||
export default function reducer(
|
export default function reducer(
|
||||||
state: State | undefined = INITIAL_STATE,
|
state: State | undefined = {},
|
||||||
action: Actions,
|
action: Actions,
|
||||||
): State {
|
): State {
|
||||||
if (action.type === 'SET_PLUGIN_STATE') {
|
if (action.type === 'SET_PLUGIN_STATE') {
|
||||||
|
|||||||
@@ -7,13 +7,17 @@
|
|||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {DevicePluginMap, ClientPluginMap, PluginDefinition} from '../plugin';
|
import type {
|
||||||
import {
|
DevicePluginMap,
|
||||||
|
ClientPluginMap,
|
||||||
|
PluginDefinition,
|
||||||
|
} from '../plugin';
|
||||||
|
import type {
|
||||||
DownloadablePluginDetails,
|
DownloadablePluginDetails,
|
||||||
ActivatablePluginDetails,
|
ActivatablePluginDetails,
|
||||||
BundledPluginDetails,
|
BundledPluginDetails,
|
||||||
} from 'flipper-plugin-lib';
|
} from 'flipper-plugin-lib';
|
||||||
import {Actions} from '.';
|
import type {Actions} from '.';
|
||||||
import produce from 'immer';
|
import produce from 'immer';
|
||||||
import {isDevicePluginDefinition} from '../utils/pluginUtils';
|
import {isDevicePluginDefinition} from '../utils/pluginUtils';
|
||||||
|
|
||||||
@@ -65,20 +69,18 @@ export type Action =
|
|||||||
payload: Array<BundledPluginDetails>;
|
payload: Array<BundledPluginDetails>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const INITIAL_STATE: State = {
|
|
||||||
devicePlugins: new Map(),
|
|
||||||
clientPlugins: new Map(),
|
|
||||||
loadedPlugins: new Map(),
|
|
||||||
bundledPlugins: new Map(),
|
|
||||||
gatekeepedPlugins: [],
|
|
||||||
disabledPlugins: [],
|
|
||||||
failedPlugins: [],
|
|
||||||
selectedPlugins: [],
|
|
||||||
marketplacePlugins: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function reducer(
|
export default function reducer(
|
||||||
state: State | undefined = INITIAL_STATE,
|
state: State | undefined = {
|
||||||
|
devicePlugins: new Map(),
|
||||||
|
clientPlugins: new Map(),
|
||||||
|
loadedPlugins: new Map(),
|
||||||
|
bundledPlugins: new Map(),
|
||||||
|
gatekeepedPlugins: [],
|
||||||
|
disabledPlugins: [],
|
||||||
|
failedPlugins: [],
|
||||||
|
selectedPlugins: [],
|
||||||
|
marketplacePlugins: [],
|
||||||
|
},
|
||||||
action: Actions,
|
action: Actions,
|
||||||
): State {
|
): State {
|
||||||
if (action.type === 'REGISTER_PLUGINS') {
|
if (action.type === 'REGISTER_PLUGINS') {
|
||||||
|
|||||||
@@ -13,18 +13,14 @@ import {deconstructClientId} from '../utils/clientUtils';
|
|||||||
import {starPlugin as setStarPlugin} from './connections';
|
import {starPlugin as setStarPlugin} from './connections';
|
||||||
import {showStatusUpdatesForDuration} from '../utils/promiseTimeout';
|
import {showStatusUpdatesForDuration} from '../utils/promiseTimeout';
|
||||||
import {selectedPlugins as setSelectedPlugins} from './plugins';
|
import {selectedPlugins as setSelectedPlugins} from './plugins';
|
||||||
import {getEnabledOrExportPersistedStatePlugins} from '../utils/pluginUtils';
|
|
||||||
import {addStatusMessage, removeStatusMessage} from './application';
|
import {addStatusMessage, removeStatusMessage} from './application';
|
||||||
import constants from '../fb-stubs/constants';
|
import constants from '../fb-stubs/constants';
|
||||||
import {getInstance} from '../fb-stubs/Logger';
|
import {getInstance} from '../fb-stubs/Logger';
|
||||||
import {logPlatformSuccessRate} from '../utils/metrics';
|
import {logPlatformSuccessRate} from '../utils/metrics';
|
||||||
import {getActivePersistentPlugins} from '../utils/pluginUtils';
|
import {getExportablePlugins} from '../utils/pluginUtils';
|
||||||
export const SUPPORT_FORM_PREFIX = 'support-form-v2';
|
export const SUPPORT_FORM_PREFIX = 'support-form-v2';
|
||||||
import {State as PluginStatesState} from './pluginStates';
|
|
||||||
import {State as PluginsState} from '../reducers/plugins';
|
|
||||||
import {State as PluginMessageQueueState} from '../reducers/pluginMessageQueue';
|
|
||||||
import Client from '../Client';
|
import Client from '../Client';
|
||||||
import {OS} from '../devices/BaseDevice';
|
import BaseDevice, {OS} from '../devices/BaseDevice';
|
||||||
|
|
||||||
const {DEFAULT_SUPPORT_GROUP} = constants;
|
const {DEFAULT_SUPPORT_GROUP} = constants;
|
||||||
|
|
||||||
@@ -197,13 +193,11 @@ export class Group {
|
|||||||
selectedGroup: this,
|
selectedGroup: this,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
const pluginsList = selectedClient
|
const pluginsList = getExportablePlugins(
|
||||||
? getEnabledOrExportPersistedStatePlugins(
|
store.getState(),
|
||||||
store.getState().connections.userStarredPlugins,
|
store.getState().connections.selectedDevice ?? undefined,
|
||||||
selectedClient,
|
selectedClient,
|
||||||
store.getState().plugins,
|
);
|
||||||
)
|
|
||||||
: [];
|
|
||||||
|
|
||||||
store.dispatch(
|
store.dispatch(
|
||||||
setSelectedPlugins(
|
setSelectedPlugins(
|
||||||
@@ -225,17 +219,11 @@ export class Group {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getWarningMessage(
|
getWarningMessage(
|
||||||
plugins: PluginsState,
|
state: Parameters<typeof getExportablePlugins>[0],
|
||||||
pluginsState: PluginStatesState,
|
device: BaseDevice | undefined,
|
||||||
pluginsMessageQueue: PluginMessageQueueState,
|
|
||||||
client: Client,
|
client: Client,
|
||||||
): string | null {
|
): string | null {
|
||||||
const activePersistentPlugins = getActivePersistentPlugins(
|
const activePersistentPlugins = getExportablePlugins(state, device, client);
|
||||||
pluginsState,
|
|
||||||
pluginsMessageQueue,
|
|
||||||
plugins,
|
|
||||||
client,
|
|
||||||
);
|
|
||||||
const emptyPlugins: Array<string> = [];
|
const emptyPlugins: Array<string> = [];
|
||||||
for (const plugin of this.requiredPlugins) {
|
for (const plugin of this.requiredPlugins) {
|
||||||
if (
|
if (
|
||||||
|
|||||||
@@ -20,17 +20,15 @@ import {
|
|||||||
import {Glyph, Layout, styled} from '../../ui';
|
import {Glyph, Layout, styled} from '../../ui';
|
||||||
import {theme, NUX, Tracked} from 'flipper-plugin';
|
import {theme, NUX, Tracked} from 'flipper-plugin';
|
||||||
import {useDispatch, useStore} from '../../utils/useStore';
|
import {useDispatch, useStore} from '../../utils/useStore';
|
||||||
import {getPluginTitle, sortPluginsByName} from '../../utils/pluginUtils';
|
|
||||||
import {
|
import {
|
||||||
ClientPluginDefinition,
|
computePluginLists,
|
||||||
DevicePluginDefinition,
|
getPluginTitle,
|
||||||
PluginDefinition,
|
getPluginTooltip,
|
||||||
} from '../../plugin';
|
} from '../../utils/pluginUtils';
|
||||||
import {selectPlugin, starPlugin} from '../../reducers/connections';
|
import {selectPlugin, starPlugin} from '../../reducers/connections';
|
||||||
import Client from '../../Client';
|
import Client from '../../Client';
|
||||||
import {State} from '../../reducers';
|
|
||||||
import BaseDevice from '../../devices/BaseDevice';
|
import BaseDevice from '../../devices/BaseDevice';
|
||||||
import {PluginDetails, DownloadablePluginDetails} from 'flipper-plugin-lib';
|
import {DownloadablePluginDetails} from 'flipper-plugin-lib';
|
||||||
import {useMemoize} from '../../utils/useMemoize';
|
import {useMemoize} from '../../utils/useMemoize';
|
||||||
import MetroDevice from '../../devices/MetroDevice';
|
import MetroDevice from '../../devices/MetroDevice';
|
||||||
import {
|
import {
|
||||||
@@ -40,7 +38,6 @@ import {
|
|||||||
} from '../../reducers/pluginDownloads';
|
} from '../../reducers/pluginDownloads';
|
||||||
import {activatePlugin, uninstallPlugin} from '../../reducers/pluginManager';
|
import {activatePlugin, uninstallPlugin} from '../../reducers/pluginManager';
|
||||||
import {BundledPluginDetails} from 'plugin-lib';
|
import {BundledPluginDetails} from 'plugin-lib';
|
||||||
import {filterNewestVersionOfEachPlugin} from '../../dispatcher/plugins';
|
|
||||||
import {reportUsage} from '../../utils/metrics';
|
import {reportUsage} from '../../utils/metrics';
|
||||||
|
|
||||||
const {SubMenu} = Menu;
|
const {SubMenu} = Menu;
|
||||||
@@ -465,132 +462,6 @@ const PluginGroup = memo(function PluginGroup({
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
function getPluginTooltip(details: PluginDetails): string {
|
|
||||||
return `${getPluginTitle(details)} (${details.id}@${details.version}) ${
|
|
||||||
details.description ?? ''
|
|
||||||
}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function computePluginLists(
|
|
||||||
device: BaseDevice | undefined,
|
|
||||||
metroDevice: BaseDevice | undefined,
|
|
||||||
client: Client | undefined,
|
|
||||||
plugins: State['plugins'],
|
|
||||||
userStarredPlugins: State['connections']['userStarredPlugins'],
|
|
||||||
_pluginsChanged?: number, // this argument is purely used to invalidate the memoization cache
|
|
||||||
) {
|
|
||||||
const devicePlugins: DevicePluginDefinition[] =
|
|
||||||
device?.devicePlugins.map((name) => plugins.devicePlugins.get(name)!) ?? [];
|
|
||||||
const metroPlugins: DevicePluginDefinition[] =
|
|
||||||
metroDevice?.devicePlugins.map(
|
|
||||||
(name) => plugins.devicePlugins.get(name)!,
|
|
||||||
) ?? [];
|
|
||||||
const enabledPlugins: ClientPluginDefinition[] = [];
|
|
||||||
const disabledPlugins: ClientPluginDefinition[] = [];
|
|
||||||
const unavailablePlugins: [plugin: PluginDetails, reason: string][] = [];
|
|
||||||
const downloadablePlugins: (
|
|
||||||
| DownloadablePluginDetails
|
|
||||||
| BundledPluginDetails
|
|
||||||
)[] = [];
|
|
||||||
|
|
||||||
if (device) {
|
|
||||||
// find all device plugins that aren't part of the current device / metro
|
|
||||||
const detectedDevicePlugins = new Set([
|
|
||||||
...device.devicePlugins,
|
|
||||||
...(metroDevice?.devicePlugins ?? []),
|
|
||||||
]);
|
|
||||||
for (const [name, definition] of plugins.devicePlugins.entries()) {
|
|
||||||
if (!detectedDevicePlugins.has(name)) {
|
|
||||||
unavailablePlugins.push([
|
|
||||||
definition.details,
|
|
||||||
`Device plugin '${getPluginTitle(
|
|
||||||
definition.details,
|
|
||||||
)}' is not supported by the current device type.`,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// process problematic plugins
|
|
||||||
plugins.disabledPlugins.forEach((plugin) => {
|
|
||||||
unavailablePlugins.push([plugin, 'Plugin is disabled by configuration']);
|
|
||||||
});
|
|
||||||
plugins.gatekeepedPlugins.forEach((plugin) => {
|
|
||||||
unavailablePlugins.push([
|
|
||||||
plugin,
|
|
||||||
`This plugin is only available to members of gatekeeper '${plugin.gatekeeper}'`,
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
plugins.failedPlugins.forEach(([plugin, error]) => {
|
|
||||||
unavailablePlugins.push([
|
|
||||||
plugin,
|
|
||||||
`Flipper failed to load this plugin: '${error}'`,
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
// process all client plugins
|
|
||||||
if (device && client) {
|
|
||||||
const clientPlugins = Array.from(plugins.clientPlugins.values()).sort(
|
|
||||||
sortPluginsByName,
|
|
||||||
);
|
|
||||||
const favoritePlugins = getFavoritePlugins(
|
|
||||||
device,
|
|
||||||
client,
|
|
||||||
clientPlugins,
|
|
||||||
client && userStarredPlugins[client.query.app],
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
clientPlugins.forEach((plugin) => {
|
|
||||||
if (!client.supportsPlugin(plugin.id)) {
|
|
||||||
unavailablePlugins.push([
|
|
||||||
plugin.details,
|
|
||||||
`Plugin '${getPluginTitle(
|
|
||||||
plugin.details,
|
|
||||||
)}' is installed in Flipper, but not supported by the client application`,
|
|
||||||
]);
|
|
||||||
} else if (favoritePlugins.includes(plugin)) {
|
|
||||||
enabledPlugins.push(plugin);
|
|
||||||
} else {
|
|
||||||
disabledPlugins.push(plugin);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
const uninstalledMarketplacePlugins = filterNewestVersionOfEachPlugin(
|
|
||||||
[...plugins.bundledPlugins.values()],
|
|
||||||
plugins.marketplacePlugins,
|
|
||||||
).filter((p) => !plugins.loadedPlugins.has(p.id));
|
|
||||||
uninstalledMarketplacePlugins.forEach((plugin) => {
|
|
||||||
if (client.supportsPlugin(plugin.id)) {
|
|
||||||
downloadablePlugins.push(plugin);
|
|
||||||
} else {
|
|
||||||
unavailablePlugins.push([
|
|
||||||
plugin,
|
|
||||||
`Plugin '${getPluginTitle(
|
|
||||||
plugin,
|
|
||||||
)}' is not installed in Flipper and not supported by the client application`,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
devicePlugins.sort(sortPluginsByName);
|
|
||||||
metroPlugins.sort(sortPluginsByName);
|
|
||||||
unavailablePlugins.sort(([a], [b]) => {
|
|
||||||
return getPluginTitle(a) > getPluginTitle(b) ? 1 : -1;
|
|
||||||
});
|
|
||||||
downloadablePlugins.sort((a, b) => {
|
|
||||||
return getPluginTitle(a) > getPluginTitle(b) ? 1 : -1;
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
devicePlugins,
|
|
||||||
metroPlugins,
|
|
||||||
enabledPlugins,
|
|
||||||
disabledPlugins,
|
|
||||||
unavailablePlugins,
|
|
||||||
downloadablePlugins,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dimensions are hardcoded as they correlate strongly
|
// Dimensions are hardcoded as they correlate strongly
|
||||||
const PluginMenu = styled(Menu)({
|
const PluginMenu = styled(Menu)({
|
||||||
userSelect: 'none',
|
userSelect: 'none',
|
||||||
@@ -652,28 +523,3 @@ function iconStyle(disabled: boolean) {
|
|||||||
height: 24,
|
height: 24,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function getFavoritePlugins(
|
|
||||||
device: BaseDevice,
|
|
||||||
client: Client,
|
|
||||||
allPlugins: PluginDefinition[],
|
|
||||||
starredPlugins: undefined | string[],
|
|
||||||
returnFavoredPlugins: boolean, // if false, unfavoried plugins are returned
|
|
||||||
): PluginDefinition[] {
|
|
||||||
if (device.isArchived) {
|
|
||||||
if (!returnFavoredPlugins) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
// for archived plugins, all stored plugins are enabled
|
|
||||||
return allPlugins.filter(
|
|
||||||
(plugin) => client.plugins.indexOf(plugin.id) !== -1,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (!starredPlugins || !starredPlugins.length) {
|
|
||||||
return returnFavoredPlugins ? [] : allPlugins;
|
|
||||||
}
|
|
||||||
return allPlugins.filter((plugin) => {
|
|
||||||
const idx = starredPlugins.indexOf(plugin.id);
|
|
||||||
return idx === -1 ? !returnFavoredPlugins : returnFavoredPlugins;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import {
|
|||||||
createMockFlipperWithPlugin,
|
createMockFlipperWithPlugin,
|
||||||
MockFlipperResult,
|
MockFlipperResult,
|
||||||
} from '../../../test-utils/createMockFlipperWithPlugin';
|
} from '../../../test-utils/createMockFlipperWithPlugin';
|
||||||
import {computePluginLists} from '../PluginList';
|
|
||||||
import {findBestClient, findBestDevice, findMetroDevice} from '../AppInspect';
|
import {findBestClient, findBestDevice, findMetroDevice} from '../AppInspect';
|
||||||
import {FlipperPlugin} from '../../../plugin';
|
import {FlipperPlugin} from '../../../plugin';
|
||||||
import MetroDevice from '../../../devices/MetroDevice';
|
import MetroDevice from '../../../devices/MetroDevice';
|
||||||
@@ -29,6 +28,7 @@ import {
|
|||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
import * as LogsPluginModule from '../../../../../plugins/logs/index';
|
import * as LogsPluginModule from '../../../../../plugins/logs/index';
|
||||||
import {createMockDownloadablePluginDetails} from '../../../utils/testUtils';
|
import {createMockDownloadablePluginDetails} from '../../../utils/testUtils';
|
||||||
|
import {computePluginLists} from '../../../utils/pluginUtils';
|
||||||
|
|
||||||
const logsPlugin = new _SandyPluginDefinition(
|
const logsPlugin = new _SandyPluginDefinition(
|
||||||
createMockPluginDetails({id: 'DeviceLogs'}),
|
createMockPluginDetails({id: 'DeviceLogs'}),
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ export type MockFlipperResult = {
|
|||||||
createDevice(serial: string): BaseDevice;
|
createDevice(serial: string): BaseDevice;
|
||||||
createClient(device: BaseDevice, name: string): Promise<Client>;
|
createClient(device: BaseDevice, name: string): Promise<Client>;
|
||||||
logger: Logger;
|
logger: Logger;
|
||||||
|
togglePlugin(plugin?: string): void;
|
||||||
};
|
};
|
||||||
|
|
||||||
type MockOptions = Partial<{
|
type MockOptions = Partial<{
|
||||||
@@ -56,6 +57,7 @@ type MockOptions = Partial<{
|
|||||||
*/
|
*/
|
||||||
onSend?: (pluginId: string, method: string, params?: object) => any;
|
onSend?: (pluginId: string, method: string, params?: object) => any;
|
||||||
additionalPlugins?: PluginDefinition[];
|
additionalPlugins?: PluginDefinition[];
|
||||||
|
dontEnableAdditionalPlugins?: true;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
export async function createMockFlipperWithPlugin(
|
export async function createMockFlipperWithPlugin(
|
||||||
@@ -108,7 +110,12 @@ export async function createMockFlipperWithPlugin(
|
|||||||
null, // create a stub connection to avoid this plugin to be archived?
|
null, // create a stub connection to avoid this plugin to be archived?
|
||||||
logger,
|
logger,
|
||||||
store,
|
store,
|
||||||
isDevicePluginDefinition(pluginClazz) ? [] : [pluginClazz.id],
|
[
|
||||||
|
...(isDevicePluginDefinition(pluginClazz) ? [] : [pluginClazz.id]),
|
||||||
|
...(options?.dontEnableAdditionalPlugins
|
||||||
|
? []
|
||||||
|
: options?.additionalPlugins?.map((p) => p.id) ?? []),
|
||||||
|
],
|
||||||
device,
|
device,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -159,6 +166,18 @@ export async function createMockFlipperWithPlugin(
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (!options?.dontEnableAdditionalPlugins) {
|
||||||
|
options?.additionalPlugins?.forEach((plugin) => {
|
||||||
|
if (!isDevicePluginDefinition(plugin)) {
|
||||||
|
store.dispatch(
|
||||||
|
starPlugin({
|
||||||
|
plugin,
|
||||||
|
selectedApp: client.query.app,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
await client.init();
|
await client.init();
|
||||||
|
|
||||||
// As convenience, by default we select the new client, star the plugin, and select it
|
// As convenience, by default we select the new client, star the plugin, and select it
|
||||||
@@ -204,6 +223,20 @@ export async function createMockFlipperWithPlugin(
|
|||||||
createClient,
|
createClient,
|
||||||
logger,
|
logger,
|
||||||
pluginKey: getPluginKey(client.id, device, pluginClazz.id),
|
pluginKey: getPluginKey(client.id, device, pluginClazz.id),
|
||||||
|
togglePlugin(id?: string) {
|
||||||
|
const plugin = id
|
||||||
|
? store.getState().plugins.clientPlugins.get(id)
|
||||||
|
: pluginClazz;
|
||||||
|
if (!plugin) {
|
||||||
|
throw new Error('unknown plugin ' + id);
|
||||||
|
}
|
||||||
|
store.dispatch(
|
||||||
|
starPlugin({
|
||||||
|
plugin,
|
||||||
|
selectedApp: client.query.app,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,293 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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
|
|
||||||
*/
|
|
||||||
|
|
||||||
import {
|
|
||||||
getPersistentPlugins,
|
|
||||||
getActivePersistentPlugins,
|
|
||||||
} from '../pluginUtils.tsx';
|
|
||||||
import type {State as PluginsState} from '../../reducers/plugins.tsx';
|
|
||||||
import type {State as PluginStatesState} from '../../reducers/pluginStates.tsx';
|
|
||||||
import type {PluginDetails} from 'flipper-plugin-lib';
|
|
||||||
import type {State as PluginMessageQueueState} from '../../reducers/pluginStates.tsx';
|
|
||||||
import {FlipperBasePlugin} from '../../plugin';
|
|
||||||
import type {ReduxState} from '../../reducers/index.tsx';
|
|
||||||
|
|
||||||
function createMockFlipperPluginWithDefaultPersistedState(id: string) {
|
|
||||||
return class MockFlipperPluginWithDefaultPersistedState extends FlipperBasePlugin<
|
|
||||||
*,
|
|
||||||
*,
|
|
||||||
{msg: string},
|
|
||||||
> {
|
|
||||||
static id = id;
|
|
||||||
static defaultPersistedState = {msg: 'MockFlipperPluginWithPersistedState'};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function createMockFlipperPluginWithExportPersistedState(id: string) {
|
|
||||||
return class MockFlipperPluginWithExportPersistedState extends FlipperBasePlugin<
|
|
||||||
*,
|
|
||||||
*,
|
|
||||||
{msg: string},
|
|
||||||
> {
|
|
||||||
static id = id;
|
|
||||||
static exportPersistedState = (
|
|
||||||
callClient: (string, ?Object) => Promise<Object>,
|
|
||||||
persistedState: ?{msg: string},
|
|
||||||
store: ?ReduxState,
|
|
||||||
supportsMethod?: (string) => Promise<boolean>,
|
|
||||||
): Promise<?{msg: string}> => {
|
|
||||||
return Promise.resolve({
|
|
||||||
msg: 'MockFlipperPluginWithExportPersistedState',
|
|
||||||
});
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function createMockFlipperPluginWithNoPersistedState(id: string) {
|
|
||||||
return class MockFlipperPluginWithNoPersistedState extends FlipperBasePlugin<
|
|
||||||
*,
|
|
||||||
*,
|
|
||||||
*,
|
|
||||||
> {
|
|
||||||
static id = id;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function mockPluginState(
|
|
||||||
gatekeepedPlugins: Array<PluginDetails>,
|
|
||||||
disabledPlugins: Array<PluginDetails>,
|
|
||||||
failedPlugins: Array<[PluginDetails, string]>,
|
|
||||||
): PluginsState {
|
|
||||||
return {
|
|
||||||
devicePlugins: new Map([
|
|
||||||
[
|
|
||||||
'DevicePlugin1',
|
|
||||||
createMockFlipperPluginWithDefaultPersistedState('DevicePlugin1'),
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'DevicePlugin2',
|
|
||||||
createMockFlipperPluginWithDefaultPersistedState('DevicePlugin2'),
|
|
||||||
],
|
|
||||||
]),
|
|
||||||
clientPlugins: new Map([
|
|
||||||
[
|
|
||||||
'ClientPlugin1',
|
|
||||||
createMockFlipperPluginWithDefaultPersistedState('ClientPlugin1'),
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'ClientPlugin2',
|
|
||||||
createMockFlipperPluginWithDefaultPersistedState('ClientPlugin2'),
|
|
||||||
],
|
|
||||||
]),
|
|
||||||
gatekeepedPlugins,
|
|
||||||
disabledPlugins,
|
|
||||||
failedPlugins,
|
|
||||||
selectedPlugins: [],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function mockPluginDefinition(name: string): PluginDetails {
|
|
||||||
return {
|
|
||||||
name,
|
|
||||||
id: name,
|
|
||||||
out: 'out',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
test('getPersistentPlugins with the plugins getting excluded', () => {
|
|
||||||
const state = mockPluginState(
|
|
||||||
[mockPluginDefinition('DevicePlugin1')],
|
|
||||||
[mockPluginDefinition('ClientPlugin1')],
|
|
||||||
[[mockPluginDefinition('DevicePlugin2'), 'DevicePlugin2']],
|
|
||||||
);
|
|
||||||
const list = getPersistentPlugins(state);
|
|
||||||
expect(list).toEqual(['ClientPlugin2']);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('getPersistentPlugins with no plugins getting excluded', () => {
|
|
||||||
const state = mockPluginState([], [], []);
|
|
||||||
const list = getPersistentPlugins(state);
|
|
||||||
expect(list).toEqual([
|
|
||||||
'ClientPlugin1',
|
|
||||||
'ClientPlugin2',
|
|
||||||
'DevicePlugin1',
|
|
||||||
'DevicePlugin2',
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('getPersistentPlugins, where the plugins with exportPersistedState not getting excluded', () => {
|
|
||||||
const state: PluginsState = {
|
|
||||||
devicePlugins: new Map([
|
|
||||||
[
|
|
||||||
'DevicePlugin1',
|
|
||||||
createMockFlipperPluginWithExportPersistedState('DevicePlugin1'),
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'DevicePlugin2',
|
|
||||||
createMockFlipperPluginWithExportPersistedState('DevicePlugin2'),
|
|
||||||
],
|
|
||||||
]),
|
|
||||||
clientPlugins: new Map([
|
|
||||||
[
|
|
||||||
'ClientPlugin1',
|
|
||||||
createMockFlipperPluginWithExportPersistedState('ClientPlugin1'),
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'ClientPlugin2',
|
|
||||||
createMockFlipperPluginWithExportPersistedState('ClientPlugin2'),
|
|
||||||
],
|
|
||||||
]),
|
|
||||||
gatekeepedPlugins: [],
|
|
||||||
disabledPlugins: [],
|
|
||||||
failedPlugins: [],
|
|
||||||
selectedPlugins: [],
|
|
||||||
};
|
|
||||||
const list = getPersistentPlugins(state);
|
|
||||||
expect(list).toEqual([
|
|
||||||
'ClientPlugin1',
|
|
||||||
'ClientPlugin2',
|
|
||||||
'DevicePlugin1',
|
|
||||||
'DevicePlugin2',
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('getPersistentPlugins, where the non persistent plugins getting excluded', () => {
|
|
||||||
const state: PluginsState = {
|
|
||||||
devicePlugins: new Map([
|
|
||||||
[
|
|
||||||
'DevicePlugin1',
|
|
||||||
createMockFlipperPluginWithNoPersistedState('DevicePlugin1'),
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'DevicePlugin2',
|
|
||||||
createMockFlipperPluginWithDefaultPersistedState('DevicePlugin2'),
|
|
||||||
],
|
|
||||||
]),
|
|
||||||
clientPlugins: new Map([
|
|
||||||
[
|
|
||||||
'ClientPlugin1',
|
|
||||||
createMockFlipperPluginWithDefaultPersistedState('ClientPlugin1'),
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'ClientPlugin2',
|
|
||||||
createMockFlipperPluginWithNoPersistedState('ClientPlugin2'),
|
|
||||||
],
|
|
||||||
]),
|
|
||||||
gatekeepedPlugins: [],
|
|
||||||
disabledPlugins: [],
|
|
||||||
failedPlugins: [],
|
|
||||||
selectedPlugins: [],
|
|
||||||
};
|
|
||||||
const list = getPersistentPlugins(state);
|
|
||||||
expect(list).toEqual(['ClientPlugin1', 'DevicePlugin2']);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('getActivePersistentPlugins, where the non persistent plugins getting excluded', () => {
|
|
||||||
const state: PluginsState = {
|
|
||||||
devicePlugins: new Map([
|
|
||||||
[
|
|
||||||
'DevicePlugin1',
|
|
||||||
createMockFlipperPluginWithNoPersistedState('DevicePlugin1'),
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'DevicePlugin2',
|
|
||||||
createMockFlipperPluginWithDefaultPersistedState('DevicePlugin2'),
|
|
||||||
],
|
|
||||||
]),
|
|
||||||
clientPlugins: new Map([
|
|
||||||
[
|
|
||||||
'ClientPlugin1',
|
|
||||||
createMockFlipperPluginWithDefaultPersistedState('ClientPlugin1'),
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'ClientPlugin2',
|
|
||||||
createMockFlipperPluginWithNoPersistedState('ClientPlugin2'),
|
|
||||||
],
|
|
||||||
]),
|
|
||||||
gatekeepedPlugins: [],
|
|
||||||
disabledPlugins: [],
|
|
||||||
failedPlugins: [],
|
|
||||||
selectedPlugins: [],
|
|
||||||
};
|
|
||||||
const plugins: PluginStatesState = {
|
|
||||||
'serial#app#DevicePlugin1': {msg: 'DevicePlugin1'},
|
|
||||||
'serial#app#DevicePlugin2': {msg: 'DevicePlugin2'},
|
|
||||||
'serial#app#ClientPlugin1': {msg: 'ClientPlugin1'},
|
|
||||||
'serial#app#ClientPlugin2': {msg: 'ClientPlugin2'},
|
|
||||||
};
|
|
||||||
const queues: PluginMessageQueueState = {};
|
|
||||||
const list = getActivePersistentPlugins(plugins, queues, state);
|
|
||||||
expect(list).toEqual([
|
|
||||||
{
|
|
||||||
id: 'ClientPlugin1',
|
|
||||||
label: 'ClientPlugin1',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'DevicePlugin2',
|
|
||||||
label: 'DevicePlugin2',
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('getActivePersistentPlugins, where the plugins not in pluginState or queue gets excluded', () => {
|
|
||||||
const state: PluginsState = {
|
|
||||||
devicePlugins: new Map([
|
|
||||||
[
|
|
||||||
'DevicePlugin1',
|
|
||||||
createMockFlipperPluginWithDefaultPersistedState('DevicePlugin1'),
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'DevicePlugin2',
|
|
||||||
createMockFlipperPluginWithDefaultPersistedState('DevicePlugin2'),
|
|
||||||
],
|
|
||||||
]),
|
|
||||||
clientPlugins: new Map([
|
|
||||||
[
|
|
||||||
'ClientPlugin1',
|
|
||||||
createMockFlipperPluginWithDefaultPersistedState('ClientPlugin1'),
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'ClientPlugin2',
|
|
||||||
createMockFlipperPluginWithDefaultPersistedState('ClientPlugin2'),
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'ClientPlugin3',
|
|
||||||
createMockFlipperPluginWithDefaultPersistedState('ClientPlugin3'),
|
|
||||||
],
|
|
||||||
]),
|
|
||||||
gatekeepedPlugins: [],
|
|
||||||
disabledPlugins: [],
|
|
||||||
failedPlugins: [],
|
|
||||||
selectedPlugins: [],
|
|
||||||
};
|
|
||||||
const plugins: PluginStatesState = {
|
|
||||||
'serial#app#DevicePlugin1': {msg: 'DevicePlugin1'},
|
|
||||||
'serial#app#ClientPlugin2': {msg: 'ClientPlugin2'},
|
|
||||||
};
|
|
||||||
const queues: PluginMessageQueueState = {
|
|
||||||
'serial#app#ClientPlugin3': [
|
|
||||||
{method: 'msg', params: {msg: 'ClientPlugin3'}},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
const list = getActivePersistentPlugins(plugins, queues, state);
|
|
||||||
expect(list).toEqual([
|
|
||||||
{
|
|
||||||
id: 'ClientPlugin2',
|
|
||||||
label: 'ClientPlugin2',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'ClientPlugin3',
|
|
||||||
label: 'ClientPlugin3',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'DevicePlugin1',
|
|
||||||
label: 'DevicePlugin1',
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
144
desktop/app/src/utils/__tests__/pluginUtils.node.tsx
Normal file
144
desktop/app/src/utils/__tests__/pluginUtils.node.tsx
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {getExportablePlugins, getPluginKey} from '../pluginUtils';
|
||||||
|
import {FlipperPlugin, FlipperDevicePlugin} from '../../plugin';
|
||||||
|
import {createMockFlipperWithPlugin} from '../../test-utils/createMockFlipperWithPlugin';
|
||||||
|
|
||||||
|
function createMockFlipperPluginWithDefaultPersistedState(id: string) {
|
||||||
|
return class MockFlipperPluginWithDefaultPersistedState extends FlipperPlugin<
|
||||||
|
any,
|
||||||
|
any,
|
||||||
|
any
|
||||||
|
> {
|
||||||
|
static id = id;
|
||||||
|
static defaultPersistedState = {msg: 'MockFlipperPluginWithPersistedState'};
|
||||||
|
['constructor']: any;
|
||||||
|
|
||||||
|
subscriptions = null as any;
|
||||||
|
client = null as any;
|
||||||
|
realClient = null as any;
|
||||||
|
getDevice = null as any;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function createMockDeviceFlipperPlugin(id: string) {
|
||||||
|
return class MockFlipperDevicePlugin extends FlipperDevicePlugin<
|
||||||
|
any,
|
||||||
|
any,
|
||||||
|
any
|
||||||
|
> {
|
||||||
|
static id = id;
|
||||||
|
['constructor']: any;
|
||||||
|
|
||||||
|
static supportsDevice() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function createMockFlipperPluginWithExportPersistedState(id: string) {
|
||||||
|
return class MockFlipperPluginWithExportPersistedState extends FlipperPlugin<
|
||||||
|
any,
|
||||||
|
any,
|
||||||
|
any
|
||||||
|
> {
|
||||||
|
static id = id;
|
||||||
|
static exportPersistedState = (): Promise<any> => {
|
||||||
|
return Promise.resolve({
|
||||||
|
msg: 'MockFlipperPluginWithExportPersistedState',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
['constructor']: any;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function createMockFlipperPluginWithNoPersistedState(id: string) {
|
||||||
|
return class MockFlipperPluginWithNoPersistedState extends FlipperPlugin<
|
||||||
|
any,
|
||||||
|
any,
|
||||||
|
any
|
||||||
|
> {
|
||||||
|
static id = id;
|
||||||
|
['constructor']: any;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
test('getActivePersistentPlugins, where the non persistent plugins getting excluded', async () => {
|
||||||
|
const {store, device, client} = await createMockFlipperWithPlugin(
|
||||||
|
createMockFlipperPluginWithDefaultPersistedState('ClientPlugin1'),
|
||||||
|
{
|
||||||
|
additionalPlugins: [
|
||||||
|
createMockFlipperPluginWithDefaultPersistedState('ClientPlugin2'),
|
||||||
|
createMockFlipperPluginWithNoPersistedState('ClientPlugin3'),
|
||||||
|
createMockFlipperPluginWithNoPersistedState('ClientPlugin4'),
|
||||||
|
createMockFlipperPluginWithExportPersistedState('ClientPlugin5'),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
const state = store.getState();
|
||||||
|
|
||||||
|
state.pluginStates = {
|
||||||
|
[getPluginKey(client.id, device, 'ClientPlugin1')]: {msg: 'DevicePlugin1'},
|
||||||
|
[getPluginKey(client.id, device, 'ClientPlugin4')]: {msg: 'ClientPlugin2'},
|
||||||
|
};
|
||||||
|
|
||||||
|
const list = getExportablePlugins(state, device, client);
|
||||||
|
expect(list).toEqual([
|
||||||
|
{
|
||||||
|
id: 'ClientPlugin1',
|
||||||
|
label: 'ClientPlugin1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'ClientPlugin4',
|
||||||
|
label: 'ClientPlugin4',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'ClientPlugin5',
|
||||||
|
label: 'ClientPlugin5',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('getActivePersistentPlugins, where the plugins not in pluginState or queue gets excluded', async () => {
|
||||||
|
const {store, device, client} = await createMockFlipperWithPlugin(
|
||||||
|
createMockFlipperPluginWithDefaultPersistedState('Plugin1'),
|
||||||
|
{
|
||||||
|
additionalPlugins: [
|
||||||
|
createMockDeviceFlipperPlugin('DevicePlugin2'),
|
||||||
|
createMockFlipperPluginWithDefaultPersistedState('ClientPlugin1'),
|
||||||
|
createMockFlipperPluginWithDefaultPersistedState('ClientPlugin2'),
|
||||||
|
createMockFlipperPluginWithDefaultPersistedState('ClientPlugin3'),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const state = store.getState();
|
||||||
|
|
||||||
|
state.pluginStates = {
|
||||||
|
[getPluginKey(client.id, device, 'ClientPlugin2')]: {msg: 'ClientPlugin2'},
|
||||||
|
};
|
||||||
|
state.pluginMessageQueue = {
|
||||||
|
[getPluginKey(client.id, device, 'ClientPlugin3')]: [
|
||||||
|
{method: 'msg', params: {msg: 'ClientPlugin3'}},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const list = getExportablePlugins(store.getState(), device, client);
|
||||||
|
expect(list).toEqual([
|
||||||
|
{
|
||||||
|
id: 'ClientPlugin2', // has state
|
||||||
|
label: 'ClientPlugin2',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'ClientPlugin3', // queued
|
||||||
|
label: 'ClientPlugin3',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
@@ -7,8 +7,8 @@
|
|||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Client from '../Client';
|
import type Client from '../Client';
|
||||||
import BaseDevice from '../devices/BaseDevice';
|
import type BaseDevice from '../devices/BaseDevice';
|
||||||
|
|
||||||
/* A Client uniuely identifies an app running on some device.
|
/* A Client uniuely identifies an app running on some device.
|
||||||
|
|
||||||
|
|||||||
@@ -13,14 +13,20 @@ import {
|
|||||||
PluginDefinition,
|
PluginDefinition,
|
||||||
DevicePluginDefinition,
|
DevicePluginDefinition,
|
||||||
isSandyPlugin,
|
isSandyPlugin,
|
||||||
|
ClientPluginDefinition,
|
||||||
} from '../plugin';
|
} from '../plugin';
|
||||||
import {State as PluginStatesState} from '../reducers/pluginStates';
|
import type {State} from '../reducers';
|
||||||
import {State as PluginsState} from '../reducers/plugins';
|
import type {State as PluginStatesState} from '../reducers/pluginStates';
|
||||||
import {State as PluginMessageQueueState} from '../reducers/pluginMessageQueue';
|
import type {State as PluginsState} from '../reducers/plugins';
|
||||||
import {deconstructPluginKey, deconstructClientId} from './clientUtils';
|
|
||||||
import {_SandyPluginDefinition} from 'flipper-plugin';
|
import {_SandyPluginDefinition} from 'flipper-plugin';
|
||||||
|
import type BaseDevice from '../devices/BaseDevice';
|
||||||
type Client = import('../Client').default;
|
import type Client from '../Client';
|
||||||
|
import type {
|
||||||
|
BundledPluginDetails,
|
||||||
|
DownloadablePluginDetails,
|
||||||
|
PluginDetails,
|
||||||
|
} from 'flipper-plugin-lib';
|
||||||
|
import {filterNewestVersionOfEachPlugin} from '../dispatcher/plugins';
|
||||||
|
|
||||||
export const defaultEnabledBackgroundPlugins = ['Navigation']; // The navigation plugin is enabled always, to make sure the navigation features works
|
export const defaultEnabledBackgroundPlugins = ['Navigation']; // The navigation plugin is enabled always, to make sure the navigation features works
|
||||||
|
|
||||||
@@ -34,8 +40,8 @@ export function pluginsClassMap(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getPluginKey(
|
export function getPluginKey(
|
||||||
selectedAppId: string | null,
|
selectedAppId: string | null | undefined,
|
||||||
baseDevice: {serial: string} | null,
|
baseDevice: {serial: string} | null | undefined,
|
||||||
pluginID: string,
|
pluginID: string,
|
||||||
): string {
|
): string {
|
||||||
if (selectedAppId) {
|
if (selectedAppId) {
|
||||||
@@ -64,153 +70,71 @@ export function getPersistedState<PersistedState>(
|
|||||||
return persistedState;
|
return persistedState;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export function getExportablePlugins(
|
||||||
*
|
state: Pick<
|
||||||
* @param starredPlugin starredPlugin is the dictionary of client and its enabled plugin
|
State,
|
||||||
* @param client Optional paramater indicating the selected client.
|
'plugins' | 'connections' | 'pluginStates' | 'pluginMessageQueue'
|
||||||
* @param plugins Plugins from the state which has the mapping to Plugin's Class.
|
>,
|
||||||
|
device: BaseDevice | undefined | null,
|
||||||
* Returns plugins which are enabled or which has exportPersistedState function defined for the passed client.
|
client?: Client,
|
||||||
* Note all device plugins are enabled.
|
|
||||||
*/
|
|
||||||
|
|
||||||
export function getEnabledOrExportPersistedStatePlugins(
|
|
||||||
starredPlugin: {
|
|
||||||
[client: string]: string[];
|
|
||||||
},
|
|
||||||
client: Client,
|
|
||||||
plugins: PluginsState,
|
|
||||||
): Array<{id: string; label: string}> {
|
|
||||||
const appName = deconstructClientId(client.id).app;
|
|
||||||
const pluginsMap: Map<string, PluginDefinition> = pluginsClassMap(plugins);
|
|
||||||
// Enabled Plugins with no exportPersistedState function defined
|
|
||||||
const enabledPlugins = starredPlugin[appName]
|
|
||||||
? starredPlugin[appName]
|
|
||||||
.map((pluginName) => pluginsMap.get(pluginName)!)
|
|
||||||
.filter(Boolean)
|
|
||||||
.filter((plugin) => {
|
|
||||||
return !plugin.exportPersistedState;
|
|
||||||
})
|
|
||||||
.sort(sortPluginsByName)
|
|
||||||
.map((plugin) => {
|
|
||||||
return {id: plugin.id, label: getPluginTitle(plugin)};
|
|
||||||
})
|
|
||||||
: [];
|
|
||||||
// Device Plugins
|
|
||||||
const devicePlugins = Array.from(plugins.devicePlugins.keys())
|
|
||||||
.filter((plugin) => {
|
|
||||||
return client.plugins.includes(plugin);
|
|
||||||
})
|
|
||||||
.map((plugin) => {
|
|
||||||
return {
|
|
||||||
id: plugin,
|
|
||||||
label: getPluginTitle(plugins.devicePlugins.get(plugin)!),
|
|
||||||
};
|
|
||||||
})
|
|
||||||
.filter(Boolean);
|
|
||||||
// Plugins which have defined exportPersistedState.
|
|
||||||
const exportPersistedStatePlugins = client.plugins
|
|
||||||
.filter((name) => {
|
|
||||||
return pluginsMap.get(name)?.exportPersistedState != null;
|
|
||||||
})
|
|
||||||
.map((name) => {
|
|
||||||
const plugin = pluginsMap.get(name)!;
|
|
||||||
return {id: plugin.id, label: getPluginTitle(plugin)};
|
|
||||||
});
|
|
||||||
return [
|
|
||||||
...devicePlugins,
|
|
||||||
...enabledPlugins,
|
|
||||||
...exportPersistedStatePlugins,
|
|
||||||
{id: 'DeviceLogs', label: 'Logs'},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param pluginsState PluginsState of the Redux Store.
|
|
||||||
* @param plugins Plugins from the state which has the mapping to Plugin's Class.
|
|
||||||
* @param selectedClient Optional paramater indicating the selected client.
|
|
||||||
* Returns active persistent plugin, which means plugins which has the data in redux store or has the `exportPersistedState` function defined which can return the plugin's data when called.
|
|
||||||
* If the selectedClient is defined then the active persistent plugins only for the selectedClient will be returned, otherwise it will return all active persistent plugins.
|
|
||||||
*/
|
|
||||||
export function getActivePersistentPlugins(
|
|
||||||
pluginsState: PluginStatesState,
|
|
||||||
pluginsMessageQueue: PluginMessageQueueState,
|
|
||||||
plugins: PluginsState,
|
|
||||||
selectedClient?: Client,
|
|
||||||
): {id: string; label: string}[] {
|
): {id: string; label: string}[] {
|
||||||
const pluginsMap = pluginsClassMap(plugins);
|
const availablePlugins = computePluginLists(
|
||||||
return getPersistentPlugins(plugins)
|
device ?? undefined,
|
||||||
.map((pluginName) => pluginsMap.get(pluginName)!)
|
undefined,
|
||||||
.sort(sortPluginsByName)
|
client,
|
||||||
.filter((plugin) => {
|
state.plugins,
|
||||||
if (plugin.id == 'DeviceLogs') {
|
state.connections.userStarredPlugins,
|
||||||
return true;
|
);
|
||||||
}
|
|
||||||
if (selectedClient) {
|
return [
|
||||||
const pluginKey = getPluginKey(
|
...availablePlugins.devicePlugins.filter((plugin) => {
|
||||||
selectedClient.id,
|
return isExportablePlugin(state, device, client, plugin);
|
||||||
{serial: selectedClient.query.device_id},
|
}),
|
||||||
plugin.id,
|
...availablePlugins.enabledPlugins.filter((plugin) => {
|
||||||
);
|
return isExportablePlugin(state, device, client, plugin);
|
||||||
// If there is a selected client, active persistent plugins are those that (can) have persisted state
|
}),
|
||||||
return (
|
].map((p) => ({
|
||||||
selectedClient.isEnabledPlugin(plugin.id) &&
|
id: p.id,
|
||||||
// this plugin can fetch and export state
|
label: getPluginTitle(p),
|
||||||
(plugin.exportPersistedState ||
|
}));
|
||||||
// this plugin has some persisted state already
|
|
||||||
pluginsState[pluginKey] ||
|
|
||||||
pluginsMessageQueue[pluginKey] ||
|
|
||||||
// this plugin has some persistable sandy state
|
|
||||||
selectedClient.sandyPluginStates.get(plugin.id)?.isPersistable())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
{
|
|
||||||
// If there is no selected client, active persistent plugin is the plugin which is just persistent.
|
|
||||||
const pluginsWithReduxData = [
|
|
||||||
...new Set([
|
|
||||||
...Object.keys(pluginsState),
|
|
||||||
...Object.keys(pluginsMessageQueue),
|
|
||||||
]),
|
|
||||||
].map((key) => deconstructPluginKey(key).pluginName);
|
|
||||||
return (
|
|
||||||
(plugin && plugin.exportPersistedState != undefined) ||
|
|
||||||
isSandyPlugin(plugin) ||
|
|
||||||
pluginsWithReduxData.includes(plugin.id)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.map((plugin) => ({
|
|
||||||
id: plugin.id,
|
|
||||||
label: getPluginTitle(plugin),
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
function isExportablePlugin(
|
||||||
* Returns all enabled plugins that are potentially exportable
|
{
|
||||||
* @param plugins
|
pluginStates,
|
||||||
*/
|
pluginMessageQueue,
|
||||||
export function getPersistentPlugins(plugins: PluginsState): Array<string> {
|
}: Pick<State, 'pluginStates' | 'pluginMessageQueue'>,
|
||||||
const pluginsMap = pluginsClassMap(plugins);
|
device: BaseDevice | undefined | null,
|
||||||
|
client: Client | undefined,
|
||||||
[...plugins.disabledPlugins, ...plugins.gatekeepedPlugins].forEach(
|
plugin: PluginDefinition,
|
||||||
(plugin) => {
|
): boolean {
|
||||||
pluginsMap.delete(plugin.name);
|
// can generate an export when requested
|
||||||
},
|
if (plugin.exportPersistedState) {
|
||||||
);
|
return true;
|
||||||
plugins.failedPlugins.forEach(([details]) => {
|
}
|
||||||
pluginsMap.delete(details.id);
|
const pluginKey = isDevicePluginDefinition(plugin)
|
||||||
});
|
? getPluginKey(undefined, device, plugin.id)
|
||||||
|
: getPluginKey(client?.id, undefined, plugin.id);
|
||||||
return Array.from(pluginsMap.keys()).filter((plugin) => {
|
// plugin has exportable redux state
|
||||||
const pluginClass = pluginsMap.get(plugin);
|
if (pluginStates[pluginKey]) {
|
||||||
return (
|
return true;
|
||||||
plugin == 'DeviceLogs' ||
|
}
|
||||||
isSandyPlugin(pluginClass) ||
|
// plugin has exportable sandy state
|
||||||
pluginClass?.defaultPersistedState ||
|
if (client?.sandyPluginStates.get(plugin.id)?.isPersistable()) {
|
||||||
pluginClass?.exportPersistedState
|
return true;
|
||||||
);
|
}
|
||||||
});
|
if (device?.sandyPluginStates.get(plugin.id)?.isPersistable()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// plugin has pending messages and a persisted state reducer or isSandy
|
||||||
|
if (
|
||||||
|
pluginMessageQueue[pluginKey] &&
|
||||||
|
((plugin as any).defaultPersistedState || isSandyPlugin(plugin))
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// nothing to serialize
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getPluginTitle(pluginClass: {
|
export function getPluginTitle(pluginClass: {
|
||||||
@@ -242,3 +166,154 @@ export function isDevicePluginDefinition(
|
|||||||
(definition instanceof _SandyPluginDefinition && definition.isDevicePlugin)
|
(definition instanceof _SandyPluginDefinition && definition.isDevicePlugin)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getPluginTooltip(details: PluginDetails): string {
|
||||||
|
return `${getPluginTitle(details)} (${details.id}@${details.version}) ${
|
||||||
|
details.description ?? ''
|
||||||
|
}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function computePluginLists(
|
||||||
|
device: BaseDevice | undefined,
|
||||||
|
metroDevice: BaseDevice | undefined,
|
||||||
|
client: Client | undefined,
|
||||||
|
plugins: State['plugins'],
|
||||||
|
userStarredPlugins: State['connections']['userStarredPlugins'],
|
||||||
|
_pluginsChanged?: number, // this argument is purely used to invalidate the memoization cache
|
||||||
|
) {
|
||||||
|
const devicePlugins: DevicePluginDefinition[] =
|
||||||
|
device?.devicePlugins.map((name) => plugins.devicePlugins.get(name)!) ?? [];
|
||||||
|
const metroPlugins: DevicePluginDefinition[] =
|
||||||
|
metroDevice?.devicePlugins.map(
|
||||||
|
(name) => plugins.devicePlugins.get(name)!,
|
||||||
|
) ?? [];
|
||||||
|
const enabledPlugins: ClientPluginDefinition[] = [];
|
||||||
|
const disabledPlugins: ClientPluginDefinition[] = [];
|
||||||
|
const unavailablePlugins: [plugin: PluginDetails, reason: string][] = [];
|
||||||
|
const downloadablePlugins: (
|
||||||
|
| DownloadablePluginDetails
|
||||||
|
| BundledPluginDetails
|
||||||
|
)[] = [];
|
||||||
|
|
||||||
|
if (device) {
|
||||||
|
// find all device plugins that aren't part of the current device / metro
|
||||||
|
const detectedDevicePlugins = new Set([
|
||||||
|
...device.devicePlugins,
|
||||||
|
...(metroDevice?.devicePlugins ?? []),
|
||||||
|
]);
|
||||||
|
for (const [name, definition] of plugins.devicePlugins.entries()) {
|
||||||
|
if (!detectedDevicePlugins.has(name)) {
|
||||||
|
unavailablePlugins.push([
|
||||||
|
definition.details,
|
||||||
|
`Device plugin '${getPluginTitle(
|
||||||
|
definition.details,
|
||||||
|
)}' is not supported by the current device type.`,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// process problematic plugins
|
||||||
|
plugins.disabledPlugins.forEach((plugin) => {
|
||||||
|
unavailablePlugins.push([plugin, 'Plugin is disabled by configuration']);
|
||||||
|
});
|
||||||
|
plugins.gatekeepedPlugins.forEach((plugin) => {
|
||||||
|
unavailablePlugins.push([
|
||||||
|
plugin,
|
||||||
|
`This plugin is only available to members of gatekeeper '${plugin.gatekeeper}'`,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
plugins.failedPlugins.forEach(([plugin, error]) => {
|
||||||
|
unavailablePlugins.push([
|
||||||
|
plugin,
|
||||||
|
`Flipper failed to load this plugin: '${error}'`,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// process all client plugins
|
||||||
|
if (device && client) {
|
||||||
|
const clientPlugins = Array.from(plugins.clientPlugins.values()).sort(
|
||||||
|
sortPluginsByName,
|
||||||
|
);
|
||||||
|
const favoritePlugins = getFavoritePlugins(
|
||||||
|
device,
|
||||||
|
client,
|
||||||
|
clientPlugins,
|
||||||
|
client && userStarredPlugins[client.query.app],
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
clientPlugins.forEach((plugin) => {
|
||||||
|
if (!client.supportsPlugin(plugin.id)) {
|
||||||
|
unavailablePlugins.push([
|
||||||
|
plugin.details,
|
||||||
|
`Plugin '${getPluginTitle(
|
||||||
|
plugin.details,
|
||||||
|
)}' is installed in Flipper, but not supported by the client application`,
|
||||||
|
]);
|
||||||
|
} else if (favoritePlugins.includes(plugin)) {
|
||||||
|
enabledPlugins.push(plugin);
|
||||||
|
} else {
|
||||||
|
disabledPlugins.push(plugin);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const uninstalledMarketplacePlugins = filterNewestVersionOfEachPlugin(
|
||||||
|
[...plugins.bundledPlugins.values()],
|
||||||
|
plugins.marketplacePlugins,
|
||||||
|
).filter((p) => !plugins.loadedPlugins.has(p.id));
|
||||||
|
uninstalledMarketplacePlugins.forEach((plugin) => {
|
||||||
|
if (client.supportsPlugin(plugin.id)) {
|
||||||
|
downloadablePlugins.push(plugin);
|
||||||
|
} else {
|
||||||
|
unavailablePlugins.push([
|
||||||
|
plugin,
|
||||||
|
`Plugin '${getPluginTitle(
|
||||||
|
plugin,
|
||||||
|
)}' is not installed in Flipper and not supported by the client application`,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
devicePlugins.sort(sortPluginsByName);
|
||||||
|
metroPlugins.sort(sortPluginsByName);
|
||||||
|
unavailablePlugins.sort(([a], [b]) => {
|
||||||
|
return getPluginTitle(a) > getPluginTitle(b) ? 1 : -1;
|
||||||
|
});
|
||||||
|
downloadablePlugins.sort((a, b) => {
|
||||||
|
return getPluginTitle(a) > getPluginTitle(b) ? 1 : -1;
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
devicePlugins,
|
||||||
|
metroPlugins,
|
||||||
|
enabledPlugins,
|
||||||
|
disabledPlugins,
|
||||||
|
unavailablePlugins,
|
||||||
|
downloadablePlugins,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFavoritePlugins(
|
||||||
|
device: BaseDevice,
|
||||||
|
client: Client,
|
||||||
|
allPlugins: PluginDefinition[],
|
||||||
|
starredPlugins: undefined | string[],
|
||||||
|
returnFavoredPlugins: boolean, // if false, unfavoried plugins are returned
|
||||||
|
): PluginDefinition[] {
|
||||||
|
if (device.isArchived) {
|
||||||
|
if (!returnFavoredPlugins) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
// for archived plugins, all stored plugins are enabled
|
||||||
|
return allPlugins.filter(
|
||||||
|
(plugin) => client.plugins.indexOf(plugin.id) !== -1,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (!starredPlugins || !starredPlugins.length) {
|
||||||
|
return returnFavoredPlugins ? [] : allPlugins;
|
||||||
|
}
|
||||||
|
return allPlugins.filter((plugin) => {
|
||||||
|
const idx = starredPlugins.indexOf(plugin.id);
|
||||||
|
return idx === -1 ? !returnFavoredPlugins : returnFavoredPlugins;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user