From e8a8f87086bcdec15fc8f69ba3723dcb3ac976a1 Mon Sep 17 00:00:00 2001 From: Pritesh Nandgaonkar Date: Fri, 19 Jul 2019 06:58:45 -0700 Subject: [PATCH] Adds capability to select plugins for which the data would be exported Summary: This diff adds `selectedPlugins` property in the redux store. With this diff now you can pass the list of plugins to headless so that it exports only the selected plugin's data Reviewed By: passy Differential Revision: D16280167 fbshipit-source-id: b03a1c49a7f51547470e0bcfa43083e52efabdd0 --- headless/index.js | 23 +++++ src/reducers/__tests__/plugins.node.js | 5 + src/reducers/plugins.js | 12 +++ src/utils/__tests__/exportData.electron.js | 103 ++++++++++++++++++++- src/utils/__tests__/pluginUtils.node.js | 1 + src/utils/exportData.js | 14 ++- src/utils/exportMetrics.js | 13 ++- 7 files changed, 167 insertions(+), 4 deletions(-) diff --git a/headless/index.js b/headless/index.js index 4135664de..291427bc9 100644 --- a/headless/index.js +++ b/headless/index.js @@ -39,6 +39,7 @@ type UserArguments = {| listDevices: boolean, device: string, listPlugins: boolean, + selectPlugins: Array, |}; yargs @@ -89,6 +90,12 @@ yargs describe: 'Will print the list of supported plugins in the terminal', type: 'boolean', }); + yargs.option('select-plugins', { + default: [], + describe: + 'The data/metrics would be exported only for the selected plugins', + type: 'array', + }); yargs.option('device', { default: undefined, describe: @@ -327,6 +334,21 @@ async function startFlipper(userArguments: UserArguments) { exit: false, }); }, + (userArguments: UserArguments, store: Store) => { + const {selectPlugins} = userArguments; + const selectedPlugins = selectPlugins.filter(selectPlugin => { + return selectPlugin != undefined; + }); + if (selectedPlugins) { + store.dispatch({ + type: 'SELECTED_PLUGINS', + payload: selectedPlugins, + }); + } + return Promise.resolve({ + exit: false, + }); + }, ]; const exitActionClosures: Array< @@ -352,6 +374,7 @@ async function startFlipper(userArguments: UserArguments) { return exportMetricsFromTrace( metrics, pluginsClassMap(store.getState().plugins), + store.getState().plugins.selectedPlugins, ) .then(payload => { return {exit: true, result: payload ? payload.toString() : ''}; diff --git a/src/reducers/__tests__/plugins.node.js b/src/reducers/__tests__/plugins.node.js index c73ceadee..0766aeab9 100644 --- a/src/reducers/__tests__/plugins.node.js +++ b/src/reducers/__tests__/plugins.node.js @@ -36,6 +36,7 @@ test('add clientPlugin', () => { gatekeepedPlugins: [], failedPlugins: [], disabledPlugins: [], + selectedPlugins: [], }, registerPlugins([testPlugin]), ); @@ -50,6 +51,7 @@ test('add devicePlugin', () => { gatekeepedPlugins: [], failedPlugins: [], disabledPlugins: [], + selectedPlugins: [], }, registerPlugins([testDevicePlugin]), ); @@ -64,6 +66,7 @@ test('do not add plugin twice', () => { gatekeepedPlugins: [], failedPlugins: [], disabledPlugins: [], + selectedPlugins: [], }, registerPlugins([testPlugin, testPlugin]), ); @@ -78,6 +81,7 @@ test('do not add other classes', () => { gatekeepedPlugins: [], failedPlugins: [], disabledPlugins: [], + selectedPlugins: [], }, // $FlowFixMe testing wrong classes on purpose here registerPlugins([testBasePlugin]), @@ -95,6 +99,7 @@ test('add gatekeeped plugin', () => { gatekeepedPlugins: [], failedPlugins: [], disabledPlugins: [], + selectedPlugins: [], }, addGatekeepedPlugins(gatekeepedPlugins), ); diff --git a/src/reducers/plugins.js b/src/reducers/plugins.js index cbb88d83f..2a161497c 100644 --- a/src/reducers/plugins.js +++ b/src/reducers/plugins.js @@ -15,6 +15,7 @@ export type State = { gatekeepedPlugins: Array, disabledPlugins: Array, failedPlugins: Array<[PluginDefinition, string]>, + selectedPlugins: Array, }; type P = Class | FlipperDevicePlugin<>>; @@ -35,6 +36,10 @@ export type Action = | { type: 'FAILED_PLUGINS', payload: Array<[PluginDefinition, string]>, + } + | { + type: 'SELECTED_PLUGINS', + payload: Array, }; const INITIAL_STATE: State = { @@ -43,6 +48,7 @@ const INITIAL_STATE: State = { gatekeepedPlugins: [], disabledPlugins: [], failedPlugins: [], + selectedPlugins: [], }; export default function reducer( @@ -87,6 +93,12 @@ export default function reducer( ...state, failedPlugins: state.failedPlugins.concat(action.payload), }; + } else if (action.type === 'SELECTED_PLUGINS') { + const {selectedPlugins} = state; + return { + ...state, + selectedPlugins: selectedPlugins.concat(action.payload), + }; } else { return state; } diff --git a/src/utils/__tests__/exportData.electron.js b/src/utils/__tests__/exportData.electron.js index c06c7bddb..9609f5532 100644 --- a/src/utils/__tests__/exportData.electron.js +++ b/src/utils/__tests__/exportData.electron.js @@ -150,7 +150,7 @@ test('test generateNotifications helper function', () => { }); test('test processStore function for empty state', () => { - const json = processStore([], null, {}, [], new Map(), 'salt'); + const json = processStore([], null, {}, [], new Map(), 'salt', []); expect(json).resolves.toBeNull(); }); @@ -162,6 +162,7 @@ test('test processStore function for an iOS device connected', async () => { [], new Map(), 'salt', + [], ); expect(json).toBeDefined(); // $FlowFixMe Flow doesn't that its a test and the assertion for null is already done @@ -194,6 +195,7 @@ test('test processStore function for an iOS device connected with client plugin [generateClientFromDevice(device, 'testapp')], new Map(), 'salt', + [], ); expect(json).toBeDefined(); //$FlowFixMe Flow doesn't that its a test and the assertion for null is already done @@ -252,6 +254,7 @@ test('test processStore function to have only the client for the selected device ], new Map(), 'salt', + [], ); expect(json).toBeDefined(); @@ -308,6 +311,7 @@ test('test processStore function to have multiple clients for the selected devic ], new Map(), 'salt', + [], ); expect(json).toBeDefined(); //$FlowFixMe Flow doesn't that its a test and the assertion for null is already added @@ -350,6 +354,7 @@ test('test processStore function for device plugin state and no clients', async [], new Map([['TestDevicePlugin', TestDevicePlugin]]), 'salt', + [], ); expect(json).toBeDefined(); //$FlowFixMe Flow doesn't that its a test and the assertion for null is already done @@ -382,6 +387,7 @@ test('test processStore function for unselected device plugin state and no clien [], new Map([['TestDevicePlugin', TestDevicePlugin]]), 'salt', + [], ); expect(json).toBeDefined(); //$FlowFixMe Flow doesn't that its a test and the assertion for null is already done @@ -420,6 +426,7 @@ test('test processStore function for notifications for selected device', async ( [client], new Map([['TestDevicePlugin', TestDevicePlugin]]), 'salt', + [], ); expect(json).toBeDefined(); @@ -477,6 +484,7 @@ test('test processStore function for notifications for unselected device', async [client, unselectedclient], new Map(), 'salt', + [], ); expect(json).toBeDefined(); //$FlowFixMe Flow doesn't that its a test and the assertion for null is already done @@ -487,3 +495,96 @@ test('test processStore function for notifications for unselected device', async const {activeNotifications} = json.store; expect(activeNotifications).toEqual([]); }); + +test('test processStore function for selected plugins', async () => { + const selectedDevice = new ArchivedDevice( + 'serial', + 'emulator', + 'TestiPhone', + 'iOS', + [], + ); + + const client = generateClientFromDevice(selectedDevice, 'app'); + const pluginstates = { + [generateClientIdentifier(selectedDevice, 'app') + '#plugin1']: { + msg: 'Test plugin1', + }, + [generateClientIdentifier(selectedDevice, 'app') + '#plugin2']: { + msg: 'Test plugin2', + }, + }; + const json = await processStore( + [], + selectedDevice, + pluginstates, + [client], + new Map(), + 'salt', + ['plugin2'], + ); + expect(json).toBeDefined(); + //$FlowFixMe Flow doesn't that its a test and the assertion for null is already done + const {pluginStates} = json.store; + const {clients} = json; + expect(pluginStates).toEqual({ + [generateClientIdentifierWithSalt( + generateClientIdentifier(selectedDevice, 'app'), + 'salt', + ) + '#plugin2']: { + msg: 'Test plugin2', + }, + }); + expect(clients).toEqual([generateClientFromClientWithSalt(client, 'salt')]); + const {activeNotifications} = json.store; + expect(activeNotifications).toEqual([]); +}); + +test('test processStore function for no selected plugins', async () => { + const selectedDevice = new ArchivedDevice( + 'serial', + 'emulator', + 'TestiPhone', + 'iOS', + [], + ); + const client = generateClientFromDevice(selectedDevice, 'app'); + const pluginstates = { + [generateClientIdentifier(selectedDevice, 'app') + '#plugin1']: { + msg: 'Test plugin1', + }, + [generateClientIdentifier(selectedDevice, 'app') + '#plugin2']: { + msg: 'Test plugin2', + }, + }; + const json = await processStore( + [], + selectedDevice, + pluginstates, + [client], + new Map(), + 'salt', + [], + ); + expect(json).toBeDefined(); + //$FlowFixMe Flow doesn't that its a test and the assertion for null is already done + const {pluginStates} = json.store; + const {clients} = json; + expect(pluginStates).toEqual({ + [generateClientIdentifierWithSalt( + generateClientIdentifier(selectedDevice, 'app'), + 'salt', + ) + '#plugin2']: { + msg: 'Test plugin2', + }, + [generateClientIdentifierWithSalt( + generateClientIdentifier(selectedDevice, 'app'), + 'salt', + ) + '#plugin1']: { + msg: 'Test plugin1', + }, + }); + expect(clients).toEqual([generateClientFromClientWithSalt(client, 'salt')]); + const {activeNotifications} = json.store; + expect(activeNotifications).toEqual([]); +}); diff --git a/src/utils/__tests__/pluginUtils.node.js b/src/utils/__tests__/pluginUtils.node.js index 4b7383758..27625c932 100644 --- a/src/utils/__tests__/pluginUtils.node.js +++ b/src/utils/__tests__/pluginUtils.node.js @@ -30,6 +30,7 @@ function mockPluginState( gatekeepedPlugins, disabledPlugins, failedPlugins, + selectedPlugins: [], }; } diff --git a/src/utils/exportData.js b/src/utils/exportData.js index 8bfc00deb..06240cfe6 100644 --- a/src/utils/exportData.js +++ b/src/utils/exportData.js @@ -73,6 +73,7 @@ export function processPluginStates( serial: string, allPluginStates: PluginStatesState, devicePlugins: Map>>, + selectedPlugins: Array, statusUpdate?: (msg: string) => void, ): PluginStatesState { let pluginStates = {}; @@ -81,6 +82,9 @@ export function processPluginStates( for (const key in allPluginStates) { const keyArray = key.split('#'); const pluginName = keyArray.pop(); + if (selectedPlugins.length > 0 && !selectedPlugins.includes(pluginName)) { + continue; + } const filteredClients = clients.filter(client => { // Remove the last entry related to plugin return client.id.includes(keyArray.join('#')); @@ -194,6 +198,7 @@ export const processStore = async ( clients: Array, devicePlugins: Map>>, salt: string, + selectedPlugins: Array, statusUpdate?: (msg: string) => void, ): Promise => { if (device) { @@ -204,6 +209,7 @@ export const processStore = async ( serial, pluginStates, devicePlugins, + selectedPlugins, statusUpdate, ); const processedActiveNotifications = processNotificationStates( @@ -245,7 +251,12 @@ export async function fetchMetadata( ) { continue; } - for (const plugin of client.plugins) { + const selectedPlugins = store.getState().plugins.selectedPlugins; + const selectedFilteredPlugins = + selectedPlugins.length > 0 + ? client.plugins.filter(plugin => selectedPlugins.includes(plugin)) + : client.plugins; + for (const plugin of selectedFilteredPlugins) { const pluginClass: ?Class< FlipperDevicePlugin<> | FlipperPlugin<>, > = plugin ? pluginsMap.get(plugin) : null; @@ -315,6 +326,7 @@ export async function getStoreExport( clients.map(client => client.toJSON()), devicePlugins, uuid.v4(), + store.getState().plugins.selectedPlugins, statusUpdate, ); return {exportData, errorArray}; diff --git a/src/utils/exportMetrics.js b/src/utils/exportMetrics.js index 51c7606ee..d6472a8d1 100644 --- a/src/utils/exportMetrics.js +++ b/src/utils/exportMetrics.js @@ -20,12 +20,16 @@ export type ExportMetricType = {[clientID: string]: MetricPluginType}; async function exportMetrics( pluginStates: PluginStatesState, pluginsMap: Map | FlipperPlugin<>>>, + selectedPlugins: Array, ): Promise { const metrics: ExportMetricType = {}; for (const key in pluginStates) { const pluginStateData = pluginStates[key]; const arr = key.split('#'); const pluginName = arr.pop(); + if (!selectedPlugins.includes(pluginName)) { + continue; + } const clientID = arr.join('#'); const metricsReducer: ?( persistedState: any, @@ -60,7 +64,11 @@ export async function exportMetricsWithoutTrace( console.error(errorArray); } - const metrics = await exportMetrics(newPluginStates, pluginsMap); + const metrics = await exportMetrics( + newPluginStates, + pluginsMap, + store.getState().plugins.selectedPlugins, + ); return metrics; } @@ -76,6 +84,7 @@ function parseJSON(str: string): ?any { export async function exportMetricsFromTrace( trace: string, pluginsMap: Map | FlipperPlugin<>>>, + selectedPlugins: Array, ): Promise { const data = fs.readFileSync(trace, 'utf8'); const parsedJSONData = parseJSON(data); @@ -103,5 +112,5 @@ export async function exportMetricsFromTrace( ), ); } - return await exportMetrics(pluginStates, pluginsMap); + return await exportMetrics(pluginStates, pluginsMap, selectedPlugins); }