Support export data

Summary:
Made Sandy plugins part of the export / import logic

Note that in the export format there is now a `pluginStates2` field. It could have been put in `pluginStates` as well, but I started with that and it felt very weird, as the `pluginStates` are supposed to reflect what is living in the Redux stores, where sandy state doesn't live in the Redux store (not directly at least). Trying to store it in the same field made things overcomplicated and confusing.

Reviewed By: jknoxville

Differential Revision: D22432769

fbshipit-source-id: d9858e561fad4fadbf0f90858874ed444e3a836c
This commit is contained in:
Michel Weststrate
2020-07-14 09:04:59 -07:00
committed by Facebook GitHub Bot
parent 0e4a6d659b
commit 44f99eb304
8 changed files with 315 additions and 93 deletions

View File

@@ -291,6 +291,20 @@ export default class Client extends EventEmitter {
}); });
} }
initFromImport(initialStates: Record<string, Record<string, any>>): this {
this.plugins.forEach((pluginId) => {
const plugin = this.getPlugin(pluginId);
if (isSandyPlugin(plugin)) {
// TODO: needs to be wrapped in error tracking T68955280
this.sandyPluginStates.set(
plugin.id,
new SandyPluginInstance(this, plugin, initialStates[pluginId]),
);
}
});
return this;
}
// get the supported plugins // get the supported plugins
async loadPlugins(): Promise<Plugins> { async loadPlugins(): Promise<Plugins> {
const plugins = await this.rawCall<{plugins: Plugins}>( const plugins = await this.rawCall<{plugins: Plugins}>(
@@ -327,7 +341,6 @@ export default class Client extends EventEmitter {
!this.sandyPluginStates.has(plugin.id) !this.sandyPluginStates.has(plugin.id)
) { ) {
// TODO: needs to be wrapped in error tracking T68955280 // TODO: needs to be wrapped in error tracking T68955280
// TODO: pick up any existing persisted state T68683449
this.sandyPluginStates.set( this.sandyPluginStates.set(
plugin.id, plugin.id,
new SandyPluginInstance(this, plugin), new SandyPluginInstance(this, plugin),
@@ -345,7 +358,6 @@ export default class Client extends EventEmitter {
const instance = this.sandyPluginStates.get(pluginId); const instance = this.sandyPluginStates.get(pluginId);
if (instance) { if (instance) {
instance.destroy(); instance.destroy();
// TODO: make sure persisted state is writtenT68683449
this.sandyPluginStates.delete(pluginId); this.sandyPluginStates.delete(pluginId);
} }
} }

View File

@@ -38,7 +38,7 @@ export type ClientPluginMap = Map<string, ClientPluginDefinition>;
export type DevicePluginMap = Map<string, DevicePluginDefinition>; export type DevicePluginMap = Map<string, DevicePluginDefinition>;
export function isSandyPlugin( export function isSandyPlugin(
plugin?: PluginDefinition, plugin?: PluginDefinition | null,
): plugin is SandyPluginDefinition { ): plugin is SandyPluginDefinition {
return plugin instanceof SandyPluginDefinition; return plugin instanceof SandyPluginDefinition;
} }

View File

@@ -70,7 +70,6 @@ export function rootReducer(
client.deinitPlugin(selectedPlugin); client.deinitPlugin(selectedPlugin);
} }
// stop sandy plugins // stop sandy plugins
// TODO: forget any persisted state as well T68683449
client.stopPluginIfNeeded(plugin.id); client.stopPluginIfNeeded(plugin.id);
delete draft.pluginMessageQueue[ delete draft.pluginMessageQueue[
getPluginKey(client.id, {serial: client.query.device_id}, plugin.id) getPluginKey(client.id, {serial: client.query.device_id}, plugin.id)

View File

@@ -11,11 +11,24 @@ import {State} from '../../reducers/index';
import configureStore from 'redux-mock-store'; import configureStore from 'redux-mock-store';
import {default as BaseDevice} from '../../devices/BaseDevice'; import {default as BaseDevice} from '../../devices/BaseDevice';
import {default as ArchivedDevice} from '../../devices/ArchivedDevice'; import {default as ArchivedDevice} from '../../devices/ArchivedDevice';
import {processStore, determinePluginsToProcess} from '../exportData'; import {
processStore,
determinePluginsToProcess,
exportStore,
importDataToStore,
} from '../exportData';
import {FlipperPlugin, FlipperDevicePlugin} from '../../plugin'; import {FlipperPlugin, FlipperDevicePlugin} from '../../plugin';
import {Notification} from '../../plugin'; import {Notification} from '../../plugin';
import {default as Client, ClientExport} from '../../Client'; import {default as Client, ClientExport} from '../../Client';
import {State as PluginsState} from '../../reducers/plugins'; import {State as PluginsState} from '../../reducers/plugins';
import {renderMockFlipperWithPlugin} from '../../test-utils/createMockFlipperWithPlugin';
import {
TestUtils,
SandyPluginDefinition,
createState,
FlipperClient,
} from 'flipper-plugin';
import {selectPlugin} from '../../reducers/connections';
class TestPlugin extends FlipperPlugin<any, any, any> {} class TestPlugin extends FlipperPlugin<any, any, any> {}
TestPlugin.title = 'TestPlugin'; TestPlugin.title = 'TestPlugin';
@@ -170,18 +183,20 @@ test('test generateNotifications helper function', () => {
}); });
}); });
test('test processStore function for empty state', () => { test('test processStore function for empty state', async () => {
const json = processStore({ expect(
activeNotifications: [], processStore({
device: null, activeNotifications: [],
pluginStates: {}, device: null,
clients: [], pluginStates: {},
devicePlugins: new Map(), clients: [],
clientPlugins: new Map(), devicePlugins: new Map(),
salt: 'salt', clientPlugins: new Map(),
selectedPlugins: [], salt: 'salt',
}); selectedPlugins: [],
expect(json).rejects.toMatchInlineSnapshot( pluginStates2: {},
}),
).rejects.toMatchInlineSnapshot(
`[Error: Selected device is null, please select a device]`, `[Error: Selected device is null, please select a device]`,
); );
}); });
@@ -198,6 +213,7 @@ test('test processStore function for an iOS device connected', async () => {
screenshotHandle: null, screenshotHandle: null,
}), }),
pluginStates: {}, pluginStates: {},
pluginStates2: {},
clients: [], clients: [],
devicePlugins: new Map(), devicePlugins: new Map(),
clientPlugins: new Map(), clientPlugins: new Map(),
@@ -232,6 +248,7 @@ test('test processStore function for an iOS device connected with client plugin
logEntries: [], logEntries: [],
screenshotHandle: null, screenshotHandle: null,
}); });
const client = generateClientFromDevice(device, 'testapp');
const clientIdentifier = generateClientIdentifier(device, 'testapp'); const clientIdentifier = generateClientIdentifier(device, 'testapp');
const json = await processStore({ const json = await processStore({
activeNotifications: [], activeNotifications: [],
@@ -239,7 +256,10 @@ test('test processStore function for an iOS device connected with client plugin
pluginStates: { pluginStates: {
[`${clientIdentifier}#TestPlugin`]: {msg: 'Test plugin'}, [`${clientIdentifier}#TestPlugin`]: {msg: 'Test plugin'},
}, },
clients: [generateClientFromDevice(device, 'testapp')], pluginStates2: {
[`${clientIdentifier}`]: {TestPlugin2: [{msg: 'Test plugin2'}]},
},
clients: [client],
devicePlugins: new Map(), devicePlugins: new Map(),
clientPlugins: new Map([['TestPlugin', TestPlugin]]), clientPlugins: new Map([['TestPlugin', TestPlugin]]),
salt: 'salt', salt: 'salt',
@@ -257,7 +277,17 @@ test('test processStore function for an iOS device connected with client plugin
msg: 'Test plugin', msg: 'Test plugin',
}), }),
}; };
const expectedPluginState2 = {
[`${generateClientIdentifierWithSalt(clientIdentifier, 'salt')}`]: {
TestPlugin2: [
{
msg: 'Test plugin2',
},
],
},
};
expect(pluginStates).toEqual(expectedPluginState); expect(pluginStates).toEqual(expectedPluginState);
expect(json.pluginStates2).toEqual(expectedPluginState2);
}); });
test('test processStore function to have only the client for the selected device', async () => { test('test processStore function to have only the client for the selected device', async () => {
@@ -301,6 +331,7 @@ test('test processStore function to have only the client for the selected device
msg: 'Test plugin selected device', msg: 'Test plugin selected device',
}, },
}, },
pluginStates2: {},
clients: [ clients: [
selectedDeviceClient, selectedDeviceClient,
generateClientFromDevice(unselectedDevice, 'testapp'), generateClientFromDevice(unselectedDevice, 'testapp'),
@@ -361,6 +392,7 @@ test('test processStore function to have multiple clients for the selected devic
msg: 'Test plugin App2', msg: 'Test plugin App2',
}, },
}, },
pluginStates2: {},
clients: [ clients: [
generateClientFromDevice(selectedDevice, 'testapp1'), generateClientFromDevice(selectedDevice, 'testapp1'),
generateClientFromDevice(selectedDevice, 'testapp2'), generateClientFromDevice(selectedDevice, 'testapp2'),
@@ -411,6 +443,7 @@ test('test processStore function for device plugin state and no clients', async
msg: 'Test Device plugin', msg: 'Test Device plugin',
}, },
}, },
pluginStates2: {},
clients: [], clients: [],
devicePlugins: new Map([['TestDevicePlugin', TestDevicePlugin]]), devicePlugins: new Map([['TestDevicePlugin', TestDevicePlugin]]),
clientPlugins: new Map(), clientPlugins: new Map(),
@@ -448,6 +481,7 @@ test('test processStore function for unselected device plugin state and no clien
msg: 'Test Device plugin', msg: 'Test Device plugin',
}, },
}, },
pluginStates2: {},
clients: [], clients: [],
devicePlugins: new Map([['TestDevicePlugin', TestDevicePlugin]]), devicePlugins: new Map([['TestDevicePlugin', TestDevicePlugin]]),
clientPlugins: new Map(), clientPlugins: new Map(),
@@ -489,6 +523,7 @@ test('test processStore function for notifications for selected device', async (
activeNotifications: [activeNotification], activeNotifications: [activeNotification],
device: selectedDevice, device: selectedDevice,
pluginStates: {}, pluginStates: {},
pluginStates2: {},
clients: [client], clients: [client],
devicePlugins: new Map([['TestDevicePlugin', TestDevicePlugin]]), devicePlugins: new Map([['TestDevicePlugin', TestDevicePlugin]]),
clientPlugins: new Map(), clientPlugins: new Map(),
@@ -551,6 +586,7 @@ test('test processStore function for notifications for unselected device', async
activeNotifications: [activeNotification], activeNotifications: [activeNotification],
device: selectedDevice, device: selectedDevice,
pluginStates: {}, pluginStates: {},
pluginStates2: {},
clients: [client, unselectedclient], clients: [client, unselectedclient],
devicePlugins: new Map(), devicePlugins: new Map(),
clientPlugins: new Map(), clientPlugins: new Map(),
@@ -591,6 +627,7 @@ test('test processStore function for selected plugins', async () => {
activeNotifications: [], activeNotifications: [],
device: selectedDevice, device: selectedDevice,
pluginStates: pluginstates, pluginStates: pluginstates,
pluginStates2: {},
clients: [client], clients: [client],
devicePlugins: new Map([ devicePlugins: new Map([
['TestDevicePlugin1', TestDevicePlugin], ['TestDevicePlugin1', TestDevicePlugin],
@@ -640,6 +677,7 @@ test('test processStore function for no selected plugins', async () => {
activeNotifications: [], activeNotifications: [],
device: selectedDevice, device: selectedDevice,
pluginStates: pluginstates, pluginStates: pluginstates,
pluginStates2: {},
clients: [client], clients: [client],
devicePlugins: new Map([ devicePlugins: new Map([
['TestDevicePlugin1', TestDevicePlugin], ['TestDevicePlugin1', TestDevicePlugin],
@@ -935,3 +973,128 @@ test('test determinePluginsToProcess to ignore archived clients', async () => {
}, },
]); ]);
}); });
const sandyTestPlugin = new SandyPluginDefinition(
TestUtils.createMockPluginDetails(),
{
plugin(
client: FlipperClient<{
inc: {};
}>,
) {
const counter = createState(0, {persist: 'counter'});
const _somethingElse = createState(0);
const anotherState = createState({testCount: 0}, {persist: 'otherState'});
client.onMessage('inc', () => {
counter.set(counter.get() + 1);
anotherState.update((draft) => {
draft.testCount -= 1;
});
});
return {};
},
Component() {
return null;
},
},
);
test('Sandy plugins are exported properly', async () => {
const {client, sendMessage, store} = await renderMockFlipperWithPlugin(
sandyTestPlugin,
);
// We do select another plugin, to verify that pending message queues are indeed processed before exporting
store.dispatch(
selectPlugin({
selectedPlugin: 'DeviceLogs',
selectedApp: client.id,
deepLinkPayload: null,
}),
);
// Deliberately not using 'act' here, to verify that exportStore itself makes sure buffers are flushed first
sendMessage('inc', {});
sendMessage('inc', {});
sendMessage('inc', {});
const storeExport = await exportStore(store);
const serial = storeExport.exportStoreData.device!.serial;
expect(serial).not.toBeFalsy();
expect(storeExport.exportStoreData.pluginStates2).toEqual({
[`TestApp#Android#MockAndroidDevice#${serial}`]: {
TestPlugin: {counter: 3, otherState: {testCount: -3}},
},
});
});
test('Sandy plugins are imported properly', async () => {
const data = {
clients: [
{
id:
'TestApp#Android#MockAndroidDevice#2e52cea6-94b0-4ea1-b9a8-c9135ede14ca-serial',
query: {
app: 'TestApp',
device: 'MockAndroidDevice',
device_id: '2e52cea6-94b0-4ea1-b9a8-c9135ede14ca-serial',
os: 'Android',
sdk_version: 4,
},
},
],
device: {
deviceType: 'archivedPhysical',
logs: [],
os: 'Android',
serial: '2e52cea6-94b0-4ea1-b9a8-c9135ede14ca-serial',
title: 'MockAndroidDevice',
},
deviceScreenshot: null,
fileVersion: '0.9.99',
flipperReleaseRevision: undefined,
pluginStates2: {
'TestApp#Android#MockAndroidDevice#2e52cea6-94b0-4ea1-b9a8-c9135ede14ca-serial': {
TestPlugin: {
otherState: {
testCount: -3,
},
counter: 3,
},
},
},
store: {
activeNotifications: [],
pluginStates: {},
},
};
const {client, store} = await renderMockFlipperWithPlugin(sandyTestPlugin);
await importDataToStore('unittest.json', JSON.stringify(data), store);
const client2 = store.getState().connections.clients[1];
expect(client2).not.toBeFalsy();
expect(client2).not.toBe(client);
expect(client2.plugins).toEqual([TestPlugin.id]);
expect(client.sandyPluginStates.get(TestPlugin.id)!.exportState())
.toMatchInlineSnapshot(`
Object {
"counter": 0,
"otherState": Object {
"testCount": 0,
},
}
`);
expect(client2.sandyPluginStates.get(TestPlugin.id)!.exportState())
.toMatchInlineSnapshot(`
Object {
"counter": 3,
"otherState": Object {
"testCount": -3,
},
}
`);
});

View File

@@ -22,7 +22,6 @@ import {
FlipperDevicePlugin, FlipperDevicePlugin,
callClient, callClient,
supportsMethod, supportsMethod,
FlipperBasePlugin,
PluginDefinition, PluginDefinition,
DevicePluginMap, DevicePluginMap,
ClientPluginMap, ClientPluginMap,
@@ -55,9 +54,16 @@ export const IMPORT_FLIPPER_TRACE_EVENT = 'import-flipper-trace';
export const EXPORT_FLIPPER_TRACE_EVENT = 'export-flipper-trace'; export const EXPORT_FLIPPER_TRACE_EVENT = 'export-flipper-trace';
export const EXPORT_FLIPPER_TRACE_TIME_SERIALIZATION_EVENT = `${EXPORT_FLIPPER_TRACE_EVENT}:serialization`; export const EXPORT_FLIPPER_TRACE_TIME_SERIALIZATION_EVENT = `${EXPORT_FLIPPER_TRACE_EVENT}:serialization`;
// maps clientId -> pluginId -> persistence key -> state
export type SandyPluginStates = Record<
string,
Record<string, Record<string, any>>
>;
export type PluginStatesExportState = { export type PluginStatesExportState = {
[pluginKey: string]: string; [pluginKey: string]: string;
}; };
export type ExportType = { export type ExportType = {
fileVersion: string; fileVersion: string;
flipperReleaseRevision: string | undefined; flipperReleaseRevision: string | undefined;
@@ -68,6 +74,7 @@ export type ExportType = {
pluginStates: PluginStatesExportState; pluginStates: PluginStatesExportState;
activeNotifications: Array<PluginNotification>; activeNotifications: Array<PluginNotification>;
}; };
pluginStates2: SandyPluginStates;
supportRequestDetails?: SupportFormRequestDetailsState; supportRequestDetails?: SupportFormRequestDetailsState;
}; };
@@ -106,6 +113,7 @@ type AddSaltToDeviceSerialOptions = {
deviceScreenshot: string | null; deviceScreenshot: string | null;
clients: Array<ClientExport>; clients: Array<ClientExport>;
pluginStates: PluginStatesExportState; pluginStates: PluginStatesExportState;
pluginStates2: SandyPluginStates;
pluginNotification: Array<PluginNotification>; pluginNotification: Array<PluginNotification>;
selectedPlugins: Array<string>; selectedPlugins: Array<string>;
statusUpdate?: (msg: string) => void; statusUpdate?: (msg: string) => void;
@@ -220,16 +228,10 @@ const serializePluginStates = async (
statusUpdate?: (msg: string) => void, statusUpdate?: (msg: string) => void,
idler?: Idler, idler?: Idler,
): Promise<PluginStatesExportState> => { ): Promise<PluginStatesExportState> => {
const pluginsMap: Map<string, typeof FlipperBasePlugin> = new Map([]); const pluginsMap = new Map<string, PluginDefinition>([
clientPlugins.forEach((val, key) => { ...clientPlugins.entries(),
// TODO: Support Sandy T68683449 and use ClientPluginsMap ...devicePlugins.entries(),
if (!isSandyPlugin(val)) { ]);
pluginsMap.set(key, val);
}
});
devicePlugins.forEach((val, key) => {
pluginsMap.set(key, val);
});
const pluginExportState: PluginStatesExportState = {}; const pluginExportState: PluginStatesExportState = {};
for (const key in pluginStates) { for (const key in pluginStates) {
const pluginName = deconstructPluginKey(key).pluginName; const pluginName = deconstructPluginKey(key).pluginName;
@@ -237,7 +239,9 @@ const serializePluginStates = async (
const serializationMarker = `${EXPORT_FLIPPER_TRACE_EVENT}:serialization-per-plugin`; const serializationMarker = `${EXPORT_FLIPPER_TRACE_EVENT}:serialization-per-plugin`;
performance.mark(serializationMarker); performance.mark(serializationMarker);
const pluginClass = pluginName ? pluginsMap.get(pluginName) : null; const pluginClass = pluginName ? pluginsMap.get(pluginName) : null;
if (pluginClass) { if (isSandyPlugin(pluginClass)) {
continue; // Those are already processed by `exportSandyPluginStates`
} else if (pluginClass) {
pluginExportState[key] = await pluginClass.serializePersistedState( pluginExportState[key] = await pluginClass.serializePersistedState(
pluginStates[key], pluginStates[key],
statusUpdate, statusUpdate,
@@ -252,19 +256,32 @@ const serializePluginStates = async (
return pluginExportState; return pluginExportState;
}; };
function exportSandyPluginStates(
pluginsToProcess: PluginsToProcess,
): SandyPluginStates {
const res: SandyPluginStates = {};
pluginsToProcess.forEach(({pluginId, client, pluginClass}) => {
if (isSandyPlugin(pluginClass) && client.sandyPluginStates.has(pluginId)) {
if (!res[client.id]) {
res[client.id] = {};
}
res[client.id][pluginId] = client.sandyPluginStates
.get(pluginId)!
.exportState();
}
});
return res;
}
const deserializePluginStates = ( const deserializePluginStates = (
pluginStatesExportState: PluginStatesExportState, pluginStatesExportState: PluginStatesExportState,
clientPlugins: ClientPluginMap, clientPlugins: ClientPluginMap,
devicePlugins: DevicePluginMap, devicePlugins: DevicePluginMap,
): PluginStatesState => { ): PluginStatesState => {
const pluginsMap: Map<string, typeof FlipperBasePlugin> = new Map([]); const pluginsMap = new Map<string, PluginDefinition>([
clientPlugins.forEach((val, key) => { ...clientPlugins.entries(),
// TODO: Support Sandy T68683449 ...devicePlugins.entries(),
if (!isSandyPlugin(val)) pluginsMap.set(key, val); ]);
});
devicePlugins.forEach((val, key) => {
pluginsMap.set(key, val);
});
const pluginsState: PluginStatesState = {}; const pluginsState: PluginStatesState = {};
for (const key in pluginStatesExportState) { for (const key in pluginStatesExportState) {
const pluginName = deconstructPluginKey(key).pluginName; const pluginName = deconstructPluginKey(key).pluginName;
@@ -272,7 +289,9 @@ const deserializePluginStates = (
continue; continue;
} }
const pluginClass = pluginsMap.get(pluginName); const pluginClass = pluginsMap.get(pluginName);
if (pluginClass) { if (isSandyPlugin(pluginClass)) {
pluginsState[key] = pluginStatesExportState[key];
} else if (pluginClass) {
pluginsState[key] = pluginClass.deserializePersistedState( pluginsState[key] = pluginClass.deserializePersistedState(
pluginStatesExportState[key], pluginStatesExportState[key],
); );
@@ -281,19 +300,34 @@ const deserializePluginStates = (
return pluginsState; return pluginsState;
}; };
const addSaltToDeviceSerial = async ( function replaceSerialsInKeys<T extends Record<string, any>>(
options: AddSaltToDeviceSerialOptions, collection: T,
): Promise<ExportType> => { baseSerial: string,
const { newSerial: string,
salt, ): T {
device, const result: Record<string, any> = {};
deviceScreenshot, for (const key in collection) {
clients, if (!key.includes(baseSerial)) {
pluginStates, throw new Error(
pluginNotification, `Error while exporting, plugin state (${key}) does not have ${baseSerial} in its key`,
statusUpdate, );
selectedPlugins, }
} = options; result[key.replace(baseSerial, newSerial)] = collection[key];
}
return result as T;
}
async function addSaltToDeviceSerial({
salt,
device,
deviceScreenshot,
clients,
pluginStates,
pluginNotification,
statusUpdate,
selectedPlugins,
pluginStates2,
}: AddSaltToDeviceSerialOptions): Promise<ExportType> {
const {serial} = device; const {serial} = device;
const newSerial = salt + '-' + serial; const newSerial = salt + '-' + serial;
const newDevice = new ArchivedDevice({ const newDevice = new ArchivedDevice({
@@ -322,17 +356,16 @@ const addSaltToDeviceSerial = async (
statusUpdate( statusUpdate(
'Adding salt to the selected device id in the plugin states...', 'Adding salt to the selected device id in the plugin states...',
); );
const updatedPluginStates: PluginStatesExportState = {}; const updatedPluginStates = replaceSerialsInKeys(
for (let key in pluginStates) { pluginStates,
if (!key.includes(serial)) { serial,
throw new Error( newSerial,
`Error while exporting, plugin state (${key}) does not have ${serial} in its key`, );
); const updatedPluginStates2 = replaceSerialsInKeys(
} pluginStates2,
const pluginData = pluginStates[key]; serial,
key = key.replace(serial, newSerial); newSerial,
updatedPluginStates[key] = pluginData; );
}
statusUpdate && statusUpdate &&
statusUpdate( statusUpdate(
@@ -357,13 +390,15 @@ const addSaltToDeviceSerial = async (
pluginStates: updatedPluginStates, pluginStates: updatedPluginStates,
activeNotifications: updatedPluginNotifications, activeNotifications: updatedPluginNotifications,
}, },
pluginStates2: updatedPluginStates2,
}; };
}; }
type ProcessStoreOptions = { type ProcessStoreOptions = {
activeNotifications: Array<PluginNotification>; activeNotifications: Array<PluginNotification>;
device: BaseDevice | null; device: BaseDevice | null;
pluginStates: PluginStatesState; pluginStates: PluginStatesState;
pluginStates2: SandyPluginStates;
clients: Array<ClientExport>; clients: Array<ClientExport>;
devicePlugins: DevicePluginMap; devicePlugins: DevicePluginMap;
clientPlugins: ClientPluginMap; clientPlugins: ClientPluginMap;
@@ -372,22 +407,21 @@ type ProcessStoreOptions = {
statusUpdate?: (msg: string) => void; statusUpdate?: (msg: string) => void;
}; };
export const processStore = async ( export async function processStore(
options: ProcessStoreOptions, {
idler?: Idler,
): Promise<ExportType> => {
const {
activeNotifications, activeNotifications,
device, device,
pluginStates, pluginStates,
pluginStates2,
clients, clients,
devicePlugins, devicePlugins,
clientPlugins, clientPlugins,
salt, salt,
selectedPlugins, selectedPlugins,
statusUpdate, statusUpdate,
} = options; }: ProcessStoreOptions,
idler?: Idler,
): Promise<ExportType> {
if (device) { if (device) {
const {serial} = device; const {serial} = device;
statusUpdate && statusUpdate('Capturing screenshot...'); statusUpdate && statusUpdate('Capturing screenshot...');
@@ -437,12 +471,13 @@ export const processStore = async (
pluginNotification: processedActiveNotifications, pluginNotification: processedActiveNotifications,
statusUpdate, statusUpdate,
selectedPlugins, selectedPlugins,
pluginStates2,
}); });
return exportFlipperData; return exportFlipperData;
} }
throw new Error('Selected device is null, please select a device'); throw new Error('Selected device is null, please select a device');
}; }
export async function fetchMetadata( export async function fetchMetadata(
pluginsToProcess: PluginsToProcess, pluginsToProcess: PluginsToProcess,
@@ -520,14 +555,18 @@ async function processQueues(
pluginId, pluginId,
pluginKey, pluginKey,
pluginClass, pluginClass,
client,
} of pluginsToProcess) { } of pluginsToProcess) {
// TODO: Support Sandy T68683449 if (isSandyPlugin(pluginClass) || pluginClass.persistedStateReducer) {
if (!isSandyPlugin(pluginClass) && pluginClass.persistedStateReducer) { client.flushMessageBuffer();
const processQueueMarker = `${EXPORT_FLIPPER_TRACE_EVENT}:process-queue-per-plugin`; const processQueueMarker = `${EXPORT_FLIPPER_TRACE_EVENT}:process-queue-per-plugin`;
performance.mark(processQueueMarker); performance.mark(processQueueMarker);
const plugin = isSandyPlugin(pluginClass)
? client.sandyPluginStates.get(pluginId)
: pluginClass;
if (!plugin) continue;
await processMessageQueue( await processMessageQueue(
pluginClass, plugin,
pluginKey, pluginKey,
store, store,
({current, total}) => { ({current, total}) => {
@@ -590,7 +629,7 @@ export function determinePluginsToProcess(
return pluginsToProcess; return pluginsToProcess;
} }
export async function getStoreExport( async function getStoreExport(
store: MiddlewareAPI, store: MiddlewareAPI,
statusUpdate?: (msg: string) => void, statusUpdate?: (msg: string) => void,
idler?: Idler, idler?: Idler,
@@ -621,12 +660,18 @@ export async function getStoreExport(
statusUpdate, statusUpdate,
idler, idler,
); );
const newPluginState = metadata.pluginStates;
// TODO: support async export like fetchMetaData T68683476
// TODO: support device plugins T68738317
const pluginStates2 = pluginsToProcess
? exportSandyPluginStates(pluginsToProcess)
: {};
getLogger().trackTimeSince(fetchMetaDataMarker, fetchMetaDataMarker, { getLogger().trackTimeSince(fetchMetaDataMarker, fetchMetaDataMarker, {
plugins: state.plugins.selectedPlugins, plugins: state.plugins.selectedPlugins,
}); });
const {errors} = metadata; const {errors} = metadata;
const newPluginState = metadata.pluginStates;
const {activeNotifications} = state.notifications; const {activeNotifications} = state.notifications;
const {devicePlugins, clientPlugins} = state.plugins; const {devicePlugins, clientPlugins} = state.plugins;
@@ -635,6 +680,7 @@ export async function getStoreExport(
activeNotifications, activeNotifications,
device: selectedDevice, device: selectedDevice,
pluginStates: newPluginState, pluginStates: newPluginState,
pluginStates2,
clients: client ? [client.toJSON()] : [], clients: client ? [client.toJSON()] : [],
devicePlugins, devicePlugins,
clientPlugins, clientPlugins,
@@ -776,13 +822,18 @@ export function importDataToStore(source: string, data: string, store: Store) {
}, },
}); });
}); });
clients.forEach((client: {id: string; query: ClientQuery}) => { clients.forEach((client: {id: string; query: ClientQuery}) => {
const clientPlugins: Array<string> = keys const sandyPluginStates = json.pluginStates2[client.id] || {};
.filter((key) => { const clientPlugins: Array<string> = [
const plugin = deconstructPluginKey(key); ...keys
return plugin.type === 'client' && client.id === plugin.client; .filter((key) => {
}) const plugin = deconstructPluginKey(key);
.map((pluginKey) => deconstructPluginKey(pluginKey).pluginName); return plugin.type === 'client' && client.id === plugin.client;
})
.map((pluginKey) => deconstructPluginKey(pluginKey).pluginName),
...Object.keys(sandyPluginStates),
];
store.dispatch({ store.dispatch({
type: 'NEW_CLIENT', type: 'NEW_CLIENT',
payload: new Client( payload: new Client(
@@ -793,7 +844,7 @@ export function importDataToStore(source: string, data: string, store: Store) {
store, store,
clientPlugins, clientPlugins,
archivedDevice, archivedDevice,
), ).initFromImport(sandyPluginStates),
}); });
}); });
if (supportRequestDetails) { if (supportRequestDetails) {

View File

@@ -47,7 +47,7 @@ async function exportMetrics(
const metricsReducer: const metricsReducer:
| (<U>(persistedState: U) => Promise<MetricType>) | (<U>(persistedState: U) => Promise<MetricType>)
| undefined = | undefined =
pluginClass && !isSandyPlugin(pluginClass) pluginClass && !isSandyPlugin(pluginClass) // This feature doesn't seem to be used at all, so let's add it when needed for Sandy
? pluginClass.metricsReducer ? pluginClass.metricsReducer
: undefined; : undefined;
if (pluginsMap.has(pluginName) && metricsReducer) { if (pluginsMap.has(pluginName) && metricsReducer) {
@@ -135,6 +135,5 @@ export async function exportMetricsFromTrace(
), ),
); );
} }
// TODO: Support Sandy T68683449 and use ClientPluginsMap, or kill feature
return await exportMetrics(pluginStates, pluginsMap, selectedPlugins); return await exportMetrics(pluginStates, pluginsMap, selectedPlugins);
} }

View File

@@ -203,11 +203,9 @@ export function getPersistentPlugins(plugins: PluginsState): Array<string> {
const pluginClass = pluginsMap.get(plugin); const pluginClass = pluginsMap.get(plugin);
return ( return (
plugin == 'DeviceLogs' || plugin == 'DeviceLogs' ||
(pluginClass && isSandyPlugin(pluginClass) ||
// TODO: support Sandy plugin T68683449 pluginClass?.defaultPersistedState ||
!isSandyPlugin(pluginClass) && pluginClass?.exportPersistedState
(pluginClass.defaultPersistedState != undefined ||
pluginClass.exportPersistedState != undefined))
); );
}); });
} }

View File

@@ -35,7 +35,7 @@ export class SandyPluginDefinition {
module: FlipperPluginModule<any>; module: FlipperPluginModule<any>;
details: PluginDetails; details: PluginDetails;
// TODO: Implement T68683449 // TODO: Implement T68683476
exportPersistedState: exportPersistedState:
| (( | ((
callClient: (method: string, params?: any) => Promise<any>, callClient: (method: string, params?: any) => Promise<any>,