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
This commit is contained in:
Pritesh Nandgaonkar
2019-07-19 06:58:45 -07:00
committed by Facebook Github Bot
parent 9d813dce80
commit e8a8f87086
7 changed files with 167 additions and 4 deletions

View File

@@ -39,6 +39,7 @@ type UserArguments = {|
listDevices: boolean, listDevices: boolean,
device: string, device: string,
listPlugins: boolean, listPlugins: boolean,
selectPlugins: Array<string>,
|}; |};
yargs yargs
@@ -89,6 +90,12 @@ yargs
describe: 'Will print the list of supported plugins in the terminal', describe: 'Will print the list of supported plugins in the terminal',
type: 'boolean', type: 'boolean',
}); });
yargs.option('select-plugins', {
default: [],
describe:
'The data/metrics would be exported only for the selected plugins',
type: 'array',
});
yargs.option('device', { yargs.option('device', {
default: undefined, default: undefined,
describe: describe:
@@ -327,6 +334,21 @@ async function startFlipper(userArguments: UserArguments) {
exit: false, 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< const exitActionClosures: Array<
@@ -352,6 +374,7 @@ async function startFlipper(userArguments: UserArguments) {
return exportMetricsFromTrace( return exportMetricsFromTrace(
metrics, metrics,
pluginsClassMap(store.getState().plugins), pluginsClassMap(store.getState().plugins),
store.getState().plugins.selectedPlugins,
) )
.then(payload => { .then(payload => {
return {exit: true, result: payload ? payload.toString() : ''}; return {exit: true, result: payload ? payload.toString() : ''};

View File

@@ -36,6 +36,7 @@ test('add clientPlugin', () => {
gatekeepedPlugins: [], gatekeepedPlugins: [],
failedPlugins: [], failedPlugins: [],
disabledPlugins: [], disabledPlugins: [],
selectedPlugins: [],
}, },
registerPlugins([testPlugin]), registerPlugins([testPlugin]),
); );
@@ -50,6 +51,7 @@ test('add devicePlugin', () => {
gatekeepedPlugins: [], gatekeepedPlugins: [],
failedPlugins: [], failedPlugins: [],
disabledPlugins: [], disabledPlugins: [],
selectedPlugins: [],
}, },
registerPlugins([testDevicePlugin]), registerPlugins([testDevicePlugin]),
); );
@@ -64,6 +66,7 @@ test('do not add plugin twice', () => {
gatekeepedPlugins: [], gatekeepedPlugins: [],
failedPlugins: [], failedPlugins: [],
disabledPlugins: [], disabledPlugins: [],
selectedPlugins: [],
}, },
registerPlugins([testPlugin, testPlugin]), registerPlugins([testPlugin, testPlugin]),
); );
@@ -78,6 +81,7 @@ test('do not add other classes', () => {
gatekeepedPlugins: [], gatekeepedPlugins: [],
failedPlugins: [], failedPlugins: [],
disabledPlugins: [], disabledPlugins: [],
selectedPlugins: [],
}, },
// $FlowFixMe testing wrong classes on purpose here // $FlowFixMe testing wrong classes on purpose here
registerPlugins([testBasePlugin]), registerPlugins([testBasePlugin]),
@@ -95,6 +99,7 @@ test('add gatekeeped plugin', () => {
gatekeepedPlugins: [], gatekeepedPlugins: [],
failedPlugins: [], failedPlugins: [],
disabledPlugins: [], disabledPlugins: [],
selectedPlugins: [],
}, },
addGatekeepedPlugins(gatekeepedPlugins), addGatekeepedPlugins(gatekeepedPlugins),
); );

View File

@@ -15,6 +15,7 @@ export type State = {
gatekeepedPlugins: Array<PluginDefinition>, gatekeepedPlugins: Array<PluginDefinition>,
disabledPlugins: Array<PluginDefinition>, disabledPlugins: Array<PluginDefinition>,
failedPlugins: Array<[PluginDefinition, string]>, failedPlugins: Array<[PluginDefinition, string]>,
selectedPlugins: Array<string>,
}; };
type P = Class<FlipperPlugin<> | FlipperDevicePlugin<>>; type P = Class<FlipperPlugin<> | FlipperDevicePlugin<>>;
@@ -35,6 +36,10 @@ export type Action =
| { | {
type: 'FAILED_PLUGINS', type: 'FAILED_PLUGINS',
payload: Array<[PluginDefinition, string]>, payload: Array<[PluginDefinition, string]>,
}
| {
type: 'SELECTED_PLUGINS',
payload: Array<string>,
}; };
const INITIAL_STATE: State = { const INITIAL_STATE: State = {
@@ -43,6 +48,7 @@ const INITIAL_STATE: State = {
gatekeepedPlugins: [], gatekeepedPlugins: [],
disabledPlugins: [], disabledPlugins: [],
failedPlugins: [], failedPlugins: [],
selectedPlugins: [],
}; };
export default function reducer( export default function reducer(
@@ -87,6 +93,12 @@ export default function reducer(
...state, ...state,
failedPlugins: state.failedPlugins.concat(action.payload), failedPlugins: state.failedPlugins.concat(action.payload),
}; };
} else if (action.type === 'SELECTED_PLUGINS') {
const {selectedPlugins} = state;
return {
...state,
selectedPlugins: selectedPlugins.concat(action.payload),
};
} else { } else {
return state; return state;
} }

View File

@@ -150,7 +150,7 @@ test('test generateNotifications helper function', () => {
}); });
test('test processStore function for empty state', () => { 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(); expect(json).resolves.toBeNull();
}); });
@@ -162,6 +162,7 @@ test('test processStore function for an iOS device connected', async () => {
[], [],
new Map(), new Map(),
'salt', 'salt',
[],
); );
expect(json).toBeDefined(); expect(json).toBeDefined();
// $FlowFixMe Flow doesn't that its a test and the assertion for null is already done // $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')], [generateClientFromDevice(device, 'testapp')],
new Map(), new Map(),
'salt', 'salt',
[],
); );
expect(json).toBeDefined(); expect(json).toBeDefined();
//$FlowFixMe Flow doesn't that its a test and the assertion for null is already done //$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(), new Map(),
'salt', 'salt',
[],
); );
expect(json).toBeDefined(); expect(json).toBeDefined();
@@ -308,6 +311,7 @@ test('test processStore function to have multiple clients for the selected devic
], ],
new Map(), new Map(),
'salt', 'salt',
[],
); );
expect(json).toBeDefined(); expect(json).toBeDefined();
//$FlowFixMe Flow doesn't that its a test and the assertion for null is already added //$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]]), new Map([['TestDevicePlugin', TestDevicePlugin]]),
'salt', 'salt',
[],
); );
expect(json).toBeDefined(); expect(json).toBeDefined();
//$FlowFixMe Flow doesn't that its a test and the assertion for null is already done //$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]]), new Map([['TestDevicePlugin', TestDevicePlugin]]),
'salt', 'salt',
[],
); );
expect(json).toBeDefined(); expect(json).toBeDefined();
//$FlowFixMe Flow doesn't that its a test and the assertion for null is already done //$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], [client],
new Map([['TestDevicePlugin', TestDevicePlugin]]), new Map([['TestDevicePlugin', TestDevicePlugin]]),
'salt', 'salt',
[],
); );
expect(json).toBeDefined(); expect(json).toBeDefined();
@@ -477,6 +484,7 @@ test('test processStore function for notifications for unselected device', async
[client, unselectedclient], [client, unselectedclient],
new Map(), new Map(),
'salt', 'salt',
[],
); );
expect(json).toBeDefined(); expect(json).toBeDefined();
//$FlowFixMe Flow doesn't that its a test and the assertion for null is already done //$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; const {activeNotifications} = json.store;
expect(activeNotifications).toEqual([]); 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([]);
});

View File

@@ -30,6 +30,7 @@ function mockPluginState(
gatekeepedPlugins, gatekeepedPlugins,
disabledPlugins, disabledPlugins,
failedPlugins, failedPlugins,
selectedPlugins: [],
}; };
} }

View File

@@ -73,6 +73,7 @@ export function processPluginStates(
serial: string, serial: string,
allPluginStates: PluginStatesState, allPluginStates: PluginStatesState,
devicePlugins: Map<string, Class<FlipperDevicePlugin<>>>, devicePlugins: Map<string, Class<FlipperDevicePlugin<>>>,
selectedPlugins: Array<string>,
statusUpdate?: (msg: string) => void, statusUpdate?: (msg: string) => void,
): PluginStatesState { ): PluginStatesState {
let pluginStates = {}; let pluginStates = {};
@@ -81,6 +82,9 @@ export function processPluginStates(
for (const key in allPluginStates) { for (const key in allPluginStates) {
const keyArray = key.split('#'); const keyArray = key.split('#');
const pluginName = keyArray.pop(); const pluginName = keyArray.pop();
if (selectedPlugins.length > 0 && !selectedPlugins.includes(pluginName)) {
continue;
}
const filteredClients = clients.filter(client => { const filteredClients = clients.filter(client => {
// Remove the last entry related to plugin // Remove the last entry related to plugin
return client.id.includes(keyArray.join('#')); return client.id.includes(keyArray.join('#'));
@@ -194,6 +198,7 @@ export const processStore = async (
clients: Array<ClientExport>, clients: Array<ClientExport>,
devicePlugins: Map<string, Class<FlipperDevicePlugin<>>>, devicePlugins: Map<string, Class<FlipperDevicePlugin<>>>,
salt: string, salt: string,
selectedPlugins: Array<string>,
statusUpdate?: (msg: string) => void, statusUpdate?: (msg: string) => void,
): Promise<?ExportType> => { ): Promise<?ExportType> => {
if (device) { if (device) {
@@ -204,6 +209,7 @@ export const processStore = async (
serial, serial,
pluginStates, pluginStates,
devicePlugins, devicePlugins,
selectedPlugins,
statusUpdate, statusUpdate,
); );
const processedActiveNotifications = processNotificationStates( const processedActiveNotifications = processNotificationStates(
@@ -245,7 +251,12 @@ export async function fetchMetadata(
) { ) {
continue; 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< const pluginClass: ?Class<
FlipperDevicePlugin<> | FlipperPlugin<>, FlipperDevicePlugin<> | FlipperPlugin<>,
> = plugin ? pluginsMap.get(plugin) : null; > = plugin ? pluginsMap.get(plugin) : null;
@@ -315,6 +326,7 @@ export async function getStoreExport(
clients.map(client => client.toJSON()), clients.map(client => client.toJSON()),
devicePlugins, devicePlugins,
uuid.v4(), uuid.v4(),
store.getState().plugins.selectedPlugins,
statusUpdate, statusUpdate,
); );
return {exportData, errorArray}; return {exportData, errorArray};

View File

@@ -20,12 +20,16 @@ export type ExportMetricType = {[clientID: string]: MetricPluginType};
async function exportMetrics( async function exportMetrics(
pluginStates: PluginStatesState, pluginStates: PluginStatesState,
pluginsMap: Map<string, Class<FlipperDevicePlugin<> | FlipperPlugin<>>>, pluginsMap: Map<string, Class<FlipperDevicePlugin<> | FlipperPlugin<>>>,
selectedPlugins: Array<string>,
): Promise<string> { ): Promise<string> {
const metrics: ExportMetricType = {}; const metrics: ExportMetricType = {};
for (const key in pluginStates) { for (const key in pluginStates) {
const pluginStateData = pluginStates[key]; const pluginStateData = pluginStates[key];
const arr = key.split('#'); const arr = key.split('#');
const pluginName = arr.pop(); const pluginName = arr.pop();
if (!selectedPlugins.includes(pluginName)) {
continue;
}
const clientID = arr.join('#'); const clientID = arr.join('#');
const metricsReducer: ?( const metricsReducer: ?(
persistedState: any, persistedState: any,
@@ -60,7 +64,11 @@ export async function exportMetricsWithoutTrace(
console.error(errorArray); console.error(errorArray);
} }
const metrics = await exportMetrics(newPluginStates, pluginsMap); const metrics = await exportMetrics(
newPluginStates,
pluginsMap,
store.getState().plugins.selectedPlugins,
);
return metrics; return metrics;
} }
@@ -76,6 +84,7 @@ function parseJSON(str: string): ?any {
export async function exportMetricsFromTrace( export async function exportMetricsFromTrace(
trace: string, trace: string,
pluginsMap: Map<string, Class<FlipperDevicePlugin<> | FlipperPlugin<>>>, pluginsMap: Map<string, Class<FlipperDevicePlugin<> | FlipperPlugin<>>>,
selectedPlugins: Array<string>,
): Promise<string> { ): Promise<string> {
const data = fs.readFileSync(trace, 'utf8'); const data = fs.readFileSync(trace, 'utf8');
const parsedJSONData = parseJSON(data); const parsedJSONData = parseJSON(data);
@@ -103,5 +112,5 @@ export async function exportMetricsFromTrace(
), ),
); );
} }
return await exportMetrics(pluginStates, pluginsMap); return await exportMetrics(pluginStates, pluginsMap, selectedPlugins);
} }