Command processing (5/n): Star plugin

Summary:
*Stack summary*: this stack refactors plugin management actions to perform them in a dispatcher rather than in the root reducer (store.tsx) as all of these actions has side effects. To do that, we store requested plugin management actions (install/update/uninstall, star/unstar) in a queue which is then handled by pluginManager dispatcher. This dispatcher then dispatches all required state updates.

*Diff summary*: refactored "star plugin" operation to perform it in pluginManager dispatcher.

Reviewed By: mweststrate

Differential Revision: D26305576

fbshipit-source-id: 90516af4e9ba8504720ddfa587f691f53e71b702
This commit is contained in:
Anton Nikolaev
2021-02-16 10:46:11 -08:00
committed by Facebook GitHub Bot
parent 0b803f810e
commit 19f2fccc79
15 changed files with 156 additions and 131 deletions

View File

@@ -9,13 +9,18 @@
jest.mock('../plugins');
jest.mock('../../utils/electronModuleCache');
import {loadPlugin, uninstallPlugin} from '../../reducers/pluginManager';
import {
loadPlugin,
starPlugin,
uninstallPlugin,
} from '../../reducers/pluginManager';
import {requirePlugin} from '../plugins';
import {mocked} from 'ts-jest/utils';
import {TestUtils} from 'flipper-plugin';
import * as TestPlugin from '../../test-utils/TestPlugin';
import {_SandyPluginDefinition as SandyPluginDefinition} from 'flipper-plugin';
import MockFlipper from '../../test-utils/MockFlipper';
import Client from '../../Client';
const pluginDetails1 = TestUtils.createMockPluginDetails({
id: 'plugin1',
@@ -43,6 +48,7 @@ const pluginDefinition2 = new SandyPluginDefinition(pluginDetails2, TestPlugin);
const mockedRequirePlugin = mocked(requirePlugin);
let mockFlipper: MockFlipper;
let mockClient: Client;
beforeEach(async () => {
mockedRequirePlugin.mockImplementation(
@@ -56,9 +62,10 @@ beforeEach(async () => {
: undefined)!,
);
mockFlipper = new MockFlipper();
await mockFlipper.initWithDeviceAndClient({
const initResult = await mockFlipper.initWithDeviceAndClient({
clientOptions: {supportedPlugins: ['plugin1', 'plugin2']},
});
mockClient = initResult.client;
});
afterEach(async () => {
@@ -76,7 +83,7 @@ test('load plugin when no other version loaded', async () => {
expect(mockFlipper.getState().plugins.loadedPlugins.get('plugin1')).toBe(
pluginDetails1,
);
expect(mockFlipper.clients[0].sandyPluginStates.has('plugin1')).toBeFalsy();
expect(mockClient.sandyPluginStates.has('plugin1')).toBeFalsy();
});
test('load plugin when other version loaded', async () => {
@@ -96,7 +103,7 @@ test('load plugin when other version loaded', async () => {
expect(mockFlipper.getState().plugins.loadedPlugins.get('plugin1')).toBe(
pluginDetails1V2,
);
expect(mockFlipper.clients[0].sandyPluginStates.has('plugin1')).toBeFalsy();
expect(mockClient.sandyPluginStates.has('plugin1')).toBeFalsy();
});
test('load and enable Sandy plugin', async () => {
@@ -109,7 +116,7 @@ test('load and enable Sandy plugin', async () => {
expect(mockFlipper.getState().plugins.loadedPlugins.get('plugin1')).toBe(
pluginDetails1,
);
expect(mockFlipper.clients[0].sandyPluginStates.has('plugin1')).toBeTruthy();
expect(mockClient.sandyPluginStates.has('plugin1')).toBeTruthy();
});
test('uninstall plugin', async () => {
@@ -126,7 +133,7 @@ test('uninstall plugin', async () => {
expect(
mockFlipper.getState().plugins.uninstalledPlugins.has('flipper-plugin1'),
).toBeTruthy();
expect(mockFlipper.clients[0].sandyPluginStates.has('plugin1')).toBeFalsy();
expect(mockClient.sandyPluginStates.has('plugin1')).toBeFalsy();
});
test('uninstall bundled plugin', async () => {
@@ -152,7 +159,43 @@ test('uninstall bundled plugin', async () => {
.getState()
.plugins.uninstalledPlugins.has('flipper-bundled-plugin'),
).toBeTruthy();
expect(
mockFlipper.clients[0].sandyPluginStates.has('bundled-plugin'),
).toBeFalsy();
expect(mockClient.sandyPluginStates.has('bundled-plugin')).toBeFalsy();
});
test('star plugin', async () => {
mockFlipper.dispatch(
loadPlugin({plugin: pluginDetails1, enable: false, notifyIfFailed: false}),
);
mockFlipper.dispatch(
starPlugin({
plugin: pluginDefinition1,
selectedApp: mockClient.query.app,
}),
);
expect(
mockFlipper.getState().connections.userStarredPlugins[mockClient.query.app],
).toContain('plugin1');
expect(mockClient.sandyPluginStates.has('plugin1')).toBeTruthy();
});
test('unstar plugin', async () => {
mockFlipper.dispatch(
loadPlugin({plugin: pluginDetails1, enable: false, notifyIfFailed: false}),
);
mockFlipper.dispatch(
starPlugin({
plugin: pluginDefinition1,
selectedApp: mockClient.query.app,
}),
);
mockFlipper.dispatch(
starPlugin({
plugin: pluginDefinition1,
selectedApp: mockClient.query.app,
}),
);
expect(
mockFlipper.getState().connections.userStarredPlugins[mockClient.query.app],
).not.toContain('plugin1');
expect(mockClient.sandyPluginStates.has('plugin1')).toBeFalsy();
});

View File

@@ -16,6 +16,7 @@ import {
UninstallPluginActionPayload,
UpdatePluginActionPayload,
pluginCommandsProcessed,
StarPluginActionPayload,
} from '../reducers/pluginManager';
import {
getInstalledPlugins,
@@ -41,8 +42,13 @@ import {
} from '../reducers/plugins';
import {_SandyPluginDefinition} from 'flipper-plugin';
import type BaseDevice from '../devices/BaseDevice';
import {pluginStarred} from '../reducers/connections';
import {defaultEnabledBackgroundPlugins} from '../utils/pluginUtils';
import {pluginStarred, pluginUnstarred} from '../reducers/connections';
import {deconstructClientId} from '../utils/clientUtils';
import {clearMessageQueue} from '../reducers/pluginMessageQueue';
import {
getPluginKey,
defaultEnabledBackgroundPlugins,
} from '../utils/pluginUtils';
const maxInstalledPluginVersionsToKeep = 2;
@@ -101,6 +107,9 @@ export function processPluginCommandsQueue(
case 'UPDATE_PLUGIN':
updatePlugin(store, command.payload);
break;
case 'STAR_PLUGIN':
starPlugin(store, command.payload);
break;
default:
console.error('Unexpected plugin command', command);
break;
@@ -159,6 +168,39 @@ function updatePlugin(store: Store, payload: UpdatePluginActionPayload) {
}
}
function getSelectedAppId(store: Store) {
const {connections} = store.getState();
const selectedApp = connections.selectedApp
? deconstructClientId(connections.selectedApp).app
: undefined;
return selectedApp;
}
function starPlugin(store: Store, payload: StarPluginActionPayload) {
const {plugin, selectedApp} = payload;
const {connections} = store.getState();
const clients = connections.clients.filter(
(client) => client.query.app === selectedApp,
);
if (connections.userStarredPlugins[selectedApp]?.includes(plugin.id)) {
clients.forEach((client) => {
stopPlugin(client, plugin.id);
const pluginKey = getPluginKey(
client.id,
{serial: client.query.device_id},
plugin.id,
);
store.dispatch(clearMessageQueue(pluginKey));
});
store.dispatch(pluginUnstarred(plugin, selectedApp));
} else {
clients.forEach((client) => {
startPlugin(client, plugin);
});
store.dispatch(pluginStarred(plugin, selectedApp));
}
}
function updateClientPlugin(
store: Store,
plugin: typeof FlipperPlugin,
@@ -166,7 +208,10 @@ function updateClientPlugin(
) {
const clients = store.getState().connections.clients;
if (enable) {
store.dispatch(pluginStarred(plugin));
const selectedApp = getSelectedAppId(store);
if (selectedApp) {
store.dispatch(pluginStarred(plugin, selectedApp));
}
}
const clientsWithEnabledPlugin = clients.filter((c) => {
return (