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,
device: string,
listPlugins: boolean,
selectPlugins: Array<string>,
|};
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() : ''};

View File

@@ -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),
);

View File

@@ -15,6 +15,7 @@ export type State = {
gatekeepedPlugins: Array<PluginDefinition>,
disabledPlugins: Array<PluginDefinition>,
failedPlugins: Array<[PluginDefinition, string]>,
selectedPlugins: Array<string>,
};
type P = Class<FlipperPlugin<> | FlipperDevicePlugin<>>;
@@ -35,6 +36,10 @@ export type Action =
| {
type: 'FAILED_PLUGINS',
payload: Array<[PluginDefinition, string]>,
}
| {
type: 'SELECTED_PLUGINS',
payload: Array<string>,
};
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;
}

View File

@@ -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([]);
});

View File

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

View File

@@ -73,6 +73,7 @@ export function processPluginStates(
serial: string,
allPluginStates: PluginStatesState,
devicePlugins: Map<string, Class<FlipperDevicePlugin<>>>,
selectedPlugins: Array<string>,
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<ClientExport>,
devicePlugins: Map<string, Class<FlipperDevicePlugin<>>>,
salt: string,
selectedPlugins: Array<string>,
statusUpdate?: (msg: string) => void,
): Promise<?ExportType> => {
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};

View File

@@ -20,12 +20,16 @@ export type ExportMetricType = {[clientID: string]: MetricPluginType};
async function exportMetrics(
pluginStates: PluginStatesState,
pluginsMap: Map<string, Class<FlipperDevicePlugin<> | FlipperPlugin<>>>,
selectedPlugins: Array<string>,
): Promise<string> {
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<string, Class<FlipperDevicePlugin<> | FlipperPlugin<>>>,
selectedPlugins: Array<string>,
): Promise<string> {
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);
}