Fix the bug where flipper tried to fetch meta data from archived device after importing from same device

Summary:
This diff fixes an issue where when one imports an archived device and tries to export again through the same device with which the initial import was made then the export functionality tries to also fetch metadata from the archived device, it waits for the metadata till it timeouts.

Also this diff fixes an issue where if the selected plugin is not present in the `selectedClient` then it will also ignore that plugin from the other clients even if it was supported by those clients.

Unit test cases for all the above cases are added.

Bug:

{F231519408}

Reviewed By: mweststrate

Differential Revision: D20459179

fbshipit-source-id: 4f0d8c40bec875e3cc43cd6aa70061c8b8da7b05
This commit is contained in:
Pritesh Nandgaonkar
2020-03-16 05:20:11 -07:00
committed by Facebook GitHub Bot
parent d75be90522
commit 18915ba43c
3 changed files with 315 additions and 33 deletions

View File

@@ -12,16 +12,31 @@ try {
} catch (e) {
jest.mock('../../fb-stubs/Logger');
}
import {State} from '../../reducers/index';
import configureStore from 'redux-mock-store';
import {default as BaseDevice} from '../../devices/BaseDevice';
import {default as ArchivedDevice} from '../../devices/ArchivedDevice';
import {processStore} from '../exportData';
import {processStore, determinePluginsToProcess} from '../exportData';
import {FlipperPlugin, FlipperDevicePlugin} from '../../plugin';
import {Notification} from '../../plugin';
import {ClientExport} from '../../Client';
import {default as Client, ClientExport} from '../../Client';
import {State as PluginsState} from '../../reducers/plugins';
class TestPlugin extends FlipperPlugin<any, any, any> {}
TestPlugin.title = 'TestPlugin';
TestPlugin.id = 'TestPlugin';
class TestDevicePlugin extends FlipperDevicePlugin<any, any, any> {}
TestDevicePlugin.title = 'TestDevicePlugin';
TestDevicePlugin.id = 'TestDevicePlugin';
const logger = {
track: () => {},
info: () => {},
warn: () => {},
error: () => {},
debug: () => {},
trackTimeSince: () => {},
};
const mockStore = configureStore<State, {}>([])();
function generateNotifications(
id: string,
@@ -672,3 +687,265 @@ test('test processStore function for no selected plugins', async () => {
const {activeNotifications} = json.store;
expect(activeNotifications).toEqual([]);
});
test('test determinePluginsToProcess for mutilple clients having plugins present', async () => {
const device1 = new BaseDevice('serial1', 'emulator', 'TestiPhone', 'iOS');
const client1 = new Client(
generateClientIdentifier(device1, 'app'),
{app: 'app', os: 'iOS', device: 'TestiPhone', device_id: 'serial1'},
null,
logger,
mockStore,
['TestPlugin', 'TestDevicePlugin'],
);
const client2 = new Client(
generateClientIdentifier(device1, 'app2'),
{app: 'app2', os: 'iOS', device: 'TestiPhone', device_id: 'serial1'},
null,
logger,
mockStore,
['TestDevicePlugin'],
);
const client3 = new Client(
generateClientIdentifier(device1, 'app3'),
{app: 'app3', os: 'iOS', device: 'TestiPhone', device_id: 'serial1'},
null,
logger,
mockStore,
['TestPlugin', 'TestDevicePlugin'],
);
const plugins: PluginsState = {
clientPlugins: new Map([
['TestPlugin', TestPlugin],
['RandomPlugin', TestPlugin],
]),
devicePlugins: new Map([['TestDevicePlugin', TestDevicePlugin]]),
gatekeepedPlugins: [],
disabledPlugins: [],
failedPlugins: [],
selectedPlugins: ['TestPlugin'],
};
const op = determinePluginsToProcess(
[client1, client2, client3],
device1,
plugins,
);
expect(op).toBeDefined();
expect(op).toEqual([
{
pluginKey: `${client1.id}#TestPlugin`,
pluginId: 'TestPlugin',
pluginName: 'TestPlugin',
pluginClass: TestPlugin,
client: client1,
},
{
pluginKey: `${client3.id}#TestPlugin`,
pluginId: 'TestPlugin',
pluginName: 'TestPlugin',
pluginClass: TestPlugin,
client: client3,
},
]);
});
test('test determinePluginsToProcess for no selected plugin present in any clients', async () => {
const device1 = new BaseDevice('serial1', 'emulator', 'TestiPhone', 'iOS');
const client1 = new Client(
generateClientIdentifier(device1, 'app'),
{app: 'app', os: 'iOS', device: 'TestiPhone', device_id: 'serial1'},
null,
logger,
mockStore,
['TestPlugin', 'TestDevicePlugin'],
);
const client2 = new Client(
generateClientIdentifier(device1, 'app2'),
{app: 'app2', os: 'iOS', device: 'TestiPhone', device_id: 'serial1'},
null,
logger,
mockStore,
['TestDevicePlugin'],
);
const plugins: PluginsState = {
clientPlugins: new Map([
['TestPlugin', TestPlugin],
['RandomPlugin', TestPlugin],
]),
devicePlugins: new Map([['TestDevicePlugin', TestDevicePlugin]]),
gatekeepedPlugins: [],
disabledPlugins: [],
failedPlugins: [],
selectedPlugins: ['RandomPlugin'],
};
const op = determinePluginsToProcess([client1, client2], device1, plugins);
expect(op).toBeDefined();
expect(op).toEqual([]);
});
test('test determinePluginsToProcess for multiple clients on same device', async () => {
const device1 = new BaseDevice('serial1', 'emulator', 'TestiPhone', 'iOS');
const client1 = new Client(
generateClientIdentifier(device1, 'app'),
{app: 'app', os: 'iOS', device: 'TestiPhone', device_id: 'serial1'},
null,
logger,
mockStore,
['TestPlugin', 'TestDevicePlugin'],
);
const client2 = new Client(
generateClientIdentifier(device1, 'app2'),
{app: 'app2', os: 'iOS', device: 'TestiPhone', device_id: 'serial1'},
null,
logger,
mockStore,
['TestDevicePlugin'],
);
const plugins: PluginsState = {
clientPlugins: new Map([['TestPlugin', TestPlugin]]),
devicePlugins: new Map([['TestDevicePlugin', TestDevicePlugin]]),
gatekeepedPlugins: [],
disabledPlugins: [],
failedPlugins: [],
selectedPlugins: ['TestPlugin'],
};
const op = determinePluginsToProcess([client1, client2], device1, plugins);
expect(op).toBeDefined();
expect(op.length).toEqual(1);
expect(op).toEqual([
{
pluginKey: `${client1.id}#TestPlugin`,
pluginId: 'TestPlugin',
pluginName: 'TestPlugin',
pluginClass: TestPlugin,
client: client1,
},
]);
});
test('test determinePluginsToProcess for multiple clients on different device', async () => {
const device1 = new BaseDevice('serial1', 'emulator', 'TestiPhone', 'iOS');
const device2 = new BaseDevice('serial2', 'emulator', 'TestiPhone', 'iOS');
const client1Device1 = new Client(
generateClientIdentifier(device1, 'app'),
{app: 'app', os: 'iOS', device: 'TestiPhone', device_id: 'serial1'},
null,
logger,
mockStore,
['TestPlugin', 'TestDevicePlugin'],
);
const client2Device1 = new Client(
generateClientIdentifier(device1, 'app2'),
{app: 'app1', os: 'iOS', device: 'TestiPhone', device_id: 'serial1'},
null,
logger,
mockStore,
['TestDevicePlugin'],
);
const client1Device2 = new Client(
generateClientIdentifier(device2, 'app'),
{app: 'app', os: 'iOS', device: 'TestiPhone', device_id: 'serial2'},
null,
logger,
mockStore,
['TestPlugin', 'TestDevicePlugin'],
);
const client2Device2 = new Client(
generateClientIdentifier(device2, 'app2'),
{app: 'app1', os: 'iOS', device: 'TestiPhone', device_id: 'serial2'},
null,
logger,
mockStore,
['TestDevicePlugin'],
);
const plugins: PluginsState = {
clientPlugins: new Map([['TestPlugin', TestPlugin]]),
devicePlugins: new Map([['TestDevicePlugin', TestDevicePlugin]]),
gatekeepedPlugins: [],
disabledPlugins: [],
failedPlugins: [],
selectedPlugins: ['TestPlugin'],
};
const op = determinePluginsToProcess(
[client1Device1, client2Device1, client1Device2, client2Device2],
device2,
plugins,
);
expect(op).toBeDefined();
expect(op.length).toEqual(1);
expect(op).toEqual([
{
pluginKey: `${client1Device2.id}#TestPlugin`,
pluginId: 'TestPlugin',
pluginName: 'TestPlugin',
pluginClass: TestPlugin,
client: client1Device2,
},
]);
});
test('test determinePluginsToProcess to ignore archived clients', async () => {
const selectedDevice = new BaseDevice(
'serial',
'emulator',
'TestiPhone',
'iOS',
);
const archivedDevice = new ArchivedDevice({
serial: 'serial-archived',
deviceType: 'emulator',
title: 'TestiPhone',
os: 'iOS',
logEntries: [],
screenshotHandle: null,
});
const logger = {
track: () => {},
info: () => {},
warn: () => {},
error: () => {},
debug: () => {},
trackTimeSince: () => {},
};
const mockStore = configureStore<State, {}>([])();
const client = new Client(
generateClientIdentifier(selectedDevice, 'app'),
{app: 'app', os: 'iOS', device: 'TestiPhone', device_id: 'serial'},
null,
logger,
mockStore,
['TestPlugin', 'TestDevicePlugin'],
);
const archivedClient = new Client(
generateClientIdentifier(archivedDevice, 'app'),
{app: 'app', os: 'iOS', device: 'TestiPhone', device_id: 'serial-archived'},
null,
logger,
mockStore,
['TestPlugin', 'TestDevicePlugin'],
);
const plugins: PluginsState = {
clientPlugins: new Map([['TestPlugin', TestPlugin]]),
devicePlugins: new Map([['TestDevicePlugin', TestDevicePlugin]]),
gatekeepedPlugins: [],
disabledPlugins: [],
failedPlugins: [],
selectedPlugins: ['TestPlugin'],
};
const op = determinePluginsToProcess(
[client, archivedClient],
selectedDevice,
plugins,
);
expect(op).toBeDefined();
expect(op.length).toEqual(1);
expect(op).toEqual([
{
pluginKey: `${client.id}#TestPlugin`,
pluginId: 'TestPlugin',
pluginName: 'TestPlugin',
pluginClass: TestPlugin,
client: client,
},
]);
});

View File

@@ -14,6 +14,7 @@ import {getInstance as getLogger} from '../fb-stubs/Logger';
import {Store, State as ReduxState, MiddlewareAPI} from '../reducers';
import {DeviceExport} from '../devices/BaseDevice';
import {State as PluginStatesState} from '../reducers/pluginStates';
import {State as PluginsState} from '../reducers/plugins';
import {PluginNotification} from '../reducers/notifications';
import Client, {ClientExport, ClientQuery} from '../Client';
import {pluginKey} from '../reducers/pluginStates';
@@ -502,38 +503,32 @@ async function processQueues(
}
}
function getSelection(
store: MiddlewareAPI,
): {client: Client | null; device: BaseDevice | null} {
const state = store.getState();
const {clients} = state.connections;
const client = clients.find(
client => client.id === state.connections.selectedApp,
);
const {selectedDevice} = state.connections;
return {client: client ?? null, device: selectedDevice};
}
export function determinePluginsToProcess(
store: MiddlewareAPI,
clients: Array<Client>,
selectedDevice: null | BaseDevice,
plugins: PluginsState,
): PluginsToProcess {
const state = store.getState();
const {plugins} = state;
const {clients} = state.connections;
const {client, device} = getSelection(store);
const pluginsToProcess: PluginsToProcess = [];
const selectedPlugins = state.plugins.selectedPlugins;
const selectedPlugins = plugins.selectedPlugins;
for (const client of clients) {
if (
!selectedDevice ||
selectedDevice.isArchived ||
client.query.device_id !== selectedDevice.serial
) {
continue;
}
const selectedFilteredPlugins = client
? selectedPlugins.length > 0
? client.plugins.filter(plugin => selectedPlugins.includes(plugin))
: client.plugins
: [];
for (const client of clients) {
if (!device || device.isArchived || !client.id.includes(device.serial)) {
for (const plugin of selectedFilteredPlugins) {
if (!client.plugins.includes(plugin)) {
// Ignore clients which doesn't support the selected plugins.
continue;
}
for (const plugin of selectedFilteredPlugins) {
const pluginClass =
plugins.clientPlugins.get(plugin) || plugins.devicePlugins.get(plugin);
if (pluginClass) {
@@ -556,7 +551,13 @@ export async function getStoreExport(
statusUpdate?: (msg: string) => void,
idler?: Idler,
): Promise<{exportData: ExportType | null; errorArray: Array<Error>}> {
const pluginsToProcess = determinePluginsToProcess(store);
const state = store.getState();
const {clients, selectedApp, selectedDevice} = state.connections;
const pluginsToProcess = determinePluginsToProcess(
clients,
selectedDevice,
state.plugins,
);
statusUpdate?.('Preparing to process data queues for plugins...');
await processQueues(store, pluginsToProcess, statusUpdate, idler);
@@ -565,8 +566,7 @@ export async function getStoreExport(
const fetchMetaDataMarker = `${EXPORT_FLIPPER_TRACE_EVENT}:fetch-meta-data`;
performance.mark(fetchMetaDataMarker);
const {device, client} = getSelection(store);
const state = store.getState();
const client = clients.find(client => client.id === selectedApp);
const metadata = await fetchMetadata(
pluginsToProcess,
state.pluginStates,
@@ -586,7 +586,7 @@ export async function getStoreExport(
const exportData = await processStore(
{
activeNotifications,
device,
device: selectedDevice,
pluginStates: newPluginState,
clients: client ? [client.toJSON()] : [],
devicePlugins,

View File

@@ -71,7 +71,12 @@ export async function exportMetricsWithoutTrace(
string,
typeof FlipperDevicePlugin | typeof FlipperPlugin
> = pluginsClassMap(store.getState().plugins);
const pluginsToProcess = determinePluginsToProcess(store);
const {clients, selectedDevice} = store.getState().connections;
const pluginsToProcess = determinePluginsToProcess(
clients,
selectedDevice,
store.getState().plugins,
);
const metadata = await fetchMetadata(
pluginsToProcess,
pluginStates,