Export and import all the nodes

Summary:
This diff does the following

- Support to export the entire view hierarchy for iOS
- Android is not supported yet
- This diff adds a call `getAllNodes` to the client side of iOS, which returns the entire view hierarchy
- Currently the search doesn't work on the imported layout plugin data. Also the imported layout plugin data doesn't expand the way it does when component is mounted, reason being the client passed to the plugin is not functional for the archived case

I will work on fixing the last points in the next diffs stacked on the current one.

For Android:
- Currently the export function will export whatever is currently displayed on the Flipper app, not the entire view hierarchy

Support for Android will also come up in later diffs.

Reviewed By: jknoxville

Differential Revision: D14209157

fbshipit-source-id: 3ad3e39edfd994913dc19cc239bfbbe011a9757c
This commit is contained in:
Pritesh Nandgaonkar
2019-02-28 09:38:03 -08:00
committed by Facebook Github Bot
parent 99ea11b8e6
commit af317eed2b
9 changed files with 146 additions and 41 deletions

View File

@@ -10,8 +10,8 @@ import type {State as PluginStates} from '../reducers/pluginStates';
import type {PluginNotification} from '../reducers/notifications.js';
import type {ClientExport} from '../Client.js';
import type {State as PluginStatesState} from '../reducers/pluginStates';
import type {State} from '../reducers/index';
import {FlipperDevicePlugin} from '../plugin.js';
import {pluginKey} from '../reducers/pluginStates';
import {FlipperDevicePlugin, FlipperPlugin, callClient} from '../plugin.js';
import {default as BaseDevice} from '../devices/BaseDevice';
import {default as ArchivedDevice} from '../devices/ArchivedDevice';
import {default as Client} from '../Client';
@@ -174,17 +174,51 @@ export const processStore = (
return null;
};
export function serializeStore(state: State): ?ExportType {
const {activeNotifications} = state.notifications;
const {selectedDevice, clients} = state.connections;
export async function serializeStore(store: Store): Promise<?ExportType> {
const state = store.getState();
const {clients} = state.connections;
const {pluginStates} = state;
const {devicePlugins} = state.plugins;
const {plugins} = state;
const newPluginState = {...pluginStates};
// TODO: T39612653 Make Client mockable. Currently rsocket logic is tightly coupled.
// Not passing the entire state as currently Client is not mockable.
const pluginsMap: Map<
string,
Class<FlipperDevicePlugin<> | FlipperPlugin<>>,
> = new Map([]);
plugins.clientPlugins.forEach((val, key) => {
pluginsMap.set(key, val);
});
plugins.devicePlugins.forEach((val, key) => {
pluginsMap.set(key, val);
});
for (let client of clients) {
for (let plugin of client.plugins) {
const pluginClass: ?Class<
FlipperDevicePlugin<> | FlipperPlugin<>,
> = plugin ? pluginsMap.get(plugin) : null;
const exportState = pluginClass ? pluginClass.exportPersistedState : null;
if (exportState) {
const key = pluginKey(client.id, plugin);
const data = await exportState(
callClient(client, plugin),
newPluginState[key],
store,
);
newPluginState[key] = data;
}
}
}
const {activeNotifications} = store.getState().notifications;
const {selectedDevice} = store.getState().connections;
const {devicePlugins} = store.getState().plugins;
return processStore(
activeNotifications,
selectedDevice,
pluginStates,
newPluginState,
clients.map(client => client.toJSON()),
devicePlugins,
uuid.v4(),
@@ -193,21 +227,21 @@ export function serializeStore(state: State): ?ExportType {
export const exportStoreToFile = (
exportFilePath: string,
data: Store,
store: Store,
): Promise<void> => {
const json = serializeStore(data.getState());
if (json) {
return new Promise((resolve, reject) => {
fs.writeFile(exportFilePath, serialize(json), err => {
if (err) {
reject(err);
}
resolve();
});
return new Promise(async (resolve, reject) => {
const json = await serializeStore(store);
if (!json) {
console.error('Make sure a device is connected');
reject('No device is selected');
}
fs.writeFile(exportFilePath, serialize(json), err => {
if (err) {
reject(err);
}
resolve();
});
}
console.error('Make sure a device is connected');
return new Promise.reject(new Error('No device is selected'));
});
};
export const importFileToStore = (file: string, store: Store) => {