Device plugin management (2/n): enable/disable, install/uninstall

Summary:
*Stack summary*: this stack adds ability to manage device plugins in the same way as client plugins: install, update, uninstall, enable (star) and disable (unstar) them.

*Diff summary*: implemented all plugin management actions for device plugins.

Changelog: it is now possible to enable/disable and install/uninstall device plugins

Reviewed By: mweststrate

Differential Revision: D26337377

fbshipit-source-id: 7d1ed61a8dc5f3339e5e548c613b67bca0c27f4f
This commit is contained in:
Anton Nikolaev
2021-02-16 10:46:11 -08:00
committed by Facebook GitHub Bot
parent 899fcd0783
commit 4541cdc23b
14 changed files with 281 additions and 62 deletions

View File

@@ -21,6 +21,8 @@ import * as TestPlugin from '../../test-utils/TestPlugin';
import {_SandyPluginDefinition as SandyPluginDefinition} from 'flipper-plugin';
import MockFlipper from '../../test-utils/MockFlipper';
import Client from '../../Client';
import React from 'react';
import BaseDevice from '../../devices/BaseDevice';
const pluginDetails1 = TestUtils.createMockPluginDetails({
id: 'plugin1',
@@ -45,10 +47,27 @@ const pluginDetails2 = TestUtils.createMockPluginDetails({
});
const pluginDefinition2 = new SandyPluginDefinition(pluginDetails2, TestPlugin);
const devicePluginDetails = TestUtils.createMockPluginDetails({
id: 'device',
name: 'flipper-device',
});
const devicePluginDefinition = new SandyPluginDefinition(devicePluginDetails, {
supportsDevice() {
return true;
},
devicePlugin() {
return {};
},
Component() {
return <h1>Plugin3</h1>;
},
});
const mockedRequirePlugin = mocked(requirePlugin);
let mockFlipper: MockFlipper;
let mockClient: Client;
let mockDevice: BaseDevice;
beforeEach(async () => {
mockedRequirePlugin.mockImplementation(
@@ -59,6 +78,8 @@ beforeEach(async () => {
? pluginDefinition2
: details === pluginDetails1V2
? pluginDefinition1V2
: details === devicePluginDetails
? devicePluginDefinition
: undefined)!,
);
mockFlipper = new MockFlipper();
@@ -66,6 +87,7 @@ beforeEach(async () => {
clientOptions: {supportedPlugins: ['plugin1', 'plugin2']},
});
mockClient = initResult.client;
mockDevice = initResult.device;
});
afterEach(async () => {
@@ -199,3 +221,46 @@ test('unstar plugin', async () => {
).not.toContain('plugin1');
expect(mockClient.sandyPluginStates.has('plugin1')).toBeFalsy();
});
test('star device plugin', async () => {
mockFlipper.dispatch(
loadPlugin({
plugin: devicePluginDetails,
enable: false,
notifyIfFailed: false,
}),
);
mockFlipper.dispatch(
starPlugin({
plugin: devicePluginDefinition,
}),
);
expect(
mockFlipper.getState().connections.userStarredDevicePlugins.has('device'),
).toBeTruthy();
expect(mockDevice.sandyPluginStates.has('device')).toBeTruthy();
});
test('unstar device plugin', async () => {
mockFlipper.dispatch(
loadPlugin({
plugin: devicePluginDetails,
enable: false,
notifyIfFailed: false,
}),
);
mockFlipper.dispatch(
starPlugin({
plugin: devicePluginDefinition,
}),
);
mockFlipper.dispatch(
starPlugin({
plugin: devicePluginDefinition,
}),
);
expect(
mockFlipper.getState().connections.userStarredDevicePlugins.has('device'),
).toBeFalsy();
expect(mockDevice.sandyPluginStates.has('device')).toBeFalsy();
});

View File

@@ -12,11 +12,11 @@ import {clearPluginState} from '../reducers/pluginStates';
import type {Logger} from '../fb-interfaces/Logger';
import {
LoadPluginActionPayload,
PluginCommand,
UninstallPluginActionPayload,
UpdatePluginActionPayload,
pluginCommandsProcessed,
StarPluginActionPayload,
PluginCommand,
} from '../reducers/pluginManager';
import {
getInstalledPlugins,
@@ -28,8 +28,8 @@ import {sideEffect} from '../utils/sideEffect';
import {requirePlugin} from './plugins';
import {showErrorNotification} from '../utils/notifications';
import {
ClientPluginDefinition,
DevicePluginDefinition,
FlipperDevicePlugin,
FlipperPlugin,
PluginDefinition,
} from '../plugin';
@@ -41,11 +41,17 @@ import {
registerInstalledPlugins,
} from '../reducers/plugins';
import {_SandyPluginDefinition} from 'flipper-plugin';
import {pluginStarred, pluginUnstarred} from '../reducers/connections';
import {
devicePluginStarred,
devicePluginUnstarred,
pluginStarred,
pluginUnstarred,
} from '../reducers/connections';
import {deconstructClientId} from '../utils/clientUtils';
import {clearMessageQueue} from '../reducers/pluginMessageQueue';
import {
getPluginKey,
isDevicePluginDefinition,
defaultEnabledBackgroundPlugins,
} from '../utils/pluginUtils';
@@ -161,7 +167,7 @@ function uninstallPlugin(store: Store, {plugin}: UninstallPluginActionPayload) {
function updatePlugin(store: Store, payload: UpdatePluginActionPayload) {
const {plugin, enablePlugin} = payload;
if (isDevicePluginDefinition(plugin)) {
return updateDevicePlugin(store, plugin);
return updateDevicePlugin(store, plugin, enablePlugin);
} else {
return updateClientPlugin(store, plugin, enablePlugin);
}
@@ -175,8 +181,26 @@ function getSelectedAppId(store: Store) {
return selectedApp;
}
function starPlugin(store: Store, payload: StarPluginActionPayload) {
const {plugin, selectedApp} = payload;
function starPlugin(
store: Store,
{plugin, selectedApp}: StarPluginActionPayload,
) {
if (isDevicePluginDefinition(plugin)) {
starDevicePlugin(store, plugin);
} else {
starClientPlugin(store, plugin, selectedApp);
}
}
function starClientPlugin(
store: Store,
plugin: ClientPluginDefinition,
selectedApp: string | undefined,
) {
selectedApp = selectedApp ?? getSelectedAppId(store);
if (!selectedApp) {
return;
}
const {connections} = store.getState();
const clients = connections.clients.filter(
(client) => client.query.app === selectedApp,
@@ -200,6 +224,24 @@ function starPlugin(store: Store, payload: StarPluginActionPayload) {
}
}
function starDevicePlugin(store: Store, plugin: DevicePluginDefinition) {
const {connections} = store.getState();
const devicesWithPlugin = connections.devices.filter((d) =>
d.supportsPlugin(plugin.details),
);
if (connections.userStarredDevicePlugins.has(plugin.id)) {
devicesWithPlugin.forEach((d) => {
d.unloadDevicePlugin(plugin.id);
});
store.dispatch(devicePluginUnstarred(plugin));
} else {
devicesWithPlugin.forEach((d) => {
d.loadDevicePlugin(plugin);
});
store.dispatch(devicePluginStarred(plugin));
}
}
function updateClientPlugin(
store: Store,
plugin: typeof FlipperPlugin,
@@ -235,9 +277,16 @@ function updateClientPlugin(
}
}
function updateDevicePlugin(store: Store, plugin: DevicePluginDefinition) {
const devices = store.getState().connections.devices;
const devicesWithEnabledPlugin = devices.filter((d) =>
function updateDevicePlugin(
store: Store,
plugin: DevicePluginDefinition,
enable: boolean,
) {
if (enable) {
store.dispatch(devicePluginStarred(plugin));
}
const connections = store.getState().connections;
const devicesWithEnabledPlugin = connections.devices.filter((d) =>
d.supportsPlugin(plugin),
);
devicesWithEnabledPlugin.forEach((d) => {
@@ -295,12 +344,3 @@ function unloadPluginModule(plugin: ActivatablePluginDetails) {
}
unloadModule(plugin.entry);
}
export function isDevicePluginDefinition(
definition: PluginDefinition,
): definition is DevicePluginDefinition {
return (
(definition as any).prototype instanceof FlipperDevicePlugin ||
(definition instanceof _SandyPluginDefinition && definition.isDevicePlugin)
);
}