Import from exported file

Summary:
This diff adds the feature to import the exported flipper data. It has the following features

- Dialog to select the file
- Merges the data with an existing store.

Reviewed By: danielbuechele

Differential Revision: D13901944

fbshipit-source-id: 1b9755735419732a34254bdc39d911bcb51ad8fe
This commit is contained in:
Pritesh Nandgaonkar
2019-02-05 09:26:43 -08:00
committed by Facebook Github Bot
parent 259ae35284
commit 9bc54597cf
8 changed files with 129 additions and 22 deletions

View File

@@ -80,20 +80,20 @@ export default class Client extends EventEmitter {
constructor( constructor(
id: string, id: string,
query: ClientQuery, query: ClientQuery,
conn: ReactiveSocket, conn: ?ReactiveSocket,
logger: Logger, logger: Logger,
store: Store, store: Store,
plugins: ?Plugins,
) { ) {
super(); super();
this.connected = true; this.connected = true;
this.plugins = []; this.plugins = plugins ? plugins : [];
this.connection = conn; this.connection = conn;
this.id = id; this.id = id;
this.query = query; this.query = query;
this.messageIdCounter = 0; this.messageIdCounter = 0;
this.logger = logger; this.logger = logger;
this.store = store; this.store = store;
this.broadcastCallbacks = new Map(); this.broadcastCallbacks = new Map();
this.requestCallbacks = new Map(); this.requestCallbacks = new Map();
@@ -104,6 +104,7 @@ export default class Client extends EventEmitter {
}, },
}; };
if (conn) {
conn.connectionStatus().subscribe({ conn.connectionStatus().subscribe({
onNext(payload) { onNext(payload) {
if (payload.kind == 'ERROR' || payload.kind == 'CLOSED') { if (payload.kind == 'ERROR' || payload.kind == 'CLOSED') {
@@ -115,6 +116,7 @@ export default class Client extends EventEmitter {
}, },
}); });
} }
}
getDevice = (): ?BaseDevice => getDevice = (): ?BaseDevice =>
this.store this.store
@@ -132,7 +134,7 @@ export default class Client extends EventEmitter {
query: ClientQuery; query: ClientQuery;
messageIdCounter: number; messageIdCounter: number;
plugins: Plugins; plugins: Plugins;
connection: ReactiveSocket; connection: ?ReactiveSocket;
responder: PartialResponder; responder: PartialResponder;
store: Store; store: Store;
@@ -337,7 +339,9 @@ export default class Client extends EventEmitter {
console.debug(data, 'message:call'); console.debug(data, 'message:call');
this.startTimingRequestResponse({method, id, params}); this.startTimingRequestResponse({method, id, params});
if (this.connection) {
this.connection.fireAndForget({data: JSON.stringify(data)}); this.connection.fireAndForget({data: JSON.stringify(data)});
}
}); });
} }
@@ -369,8 +373,10 @@ export default class Client extends EventEmitter {
params, params,
}; };
console.debug(data, 'message:send'); console.debug(data, 'message:send');
if (this.connection) {
this.connection.fireAndForget({data: JSON.stringify(data)}); this.connection.fireAndForget({data: JSON.stringify(data)});
} }
}
call(api: string, method: string, params?: Object): Promise<Object> { call(api: string, method: string, params?: Object): Promise<Object> {
return reportPluginFailures( return reportPluginFailures(

View File

@@ -6,10 +6,12 @@
*/ */
import type {FlipperPlugin, FlipperDevicePlugin} from './plugin.js'; import type {FlipperPlugin, FlipperDevicePlugin} from './plugin.js';
import {exportStoreToFile} from './utils/exportData.js'; import {exportStoreToFile, importFileToStore} from './utils/exportData.js';
import type {Store} from './reducers/'; import type {Store} from './reducers/';
import electron from 'electron'; import electron from 'electron';
import {GK} from 'flipper'; import {GK} from 'flipper';
import {remote} from 'electron';
const {dialog} = remote;
export type DefaultKeyboardAction = 'clear' | 'goToBottom' | 'createPaste'; export type DefaultKeyboardAction = 'clear' | 'goToBottom' | 'createPaste';
export type TopLevelMenu = 'Edit' | 'View' | 'Window' | 'Help'; export type TopLevelMenu = 'Edit' | 'View' | 'Window' | 'Help';
@@ -315,12 +317,28 @@ function getTemplate(
label: 'File', label: 'File',
submenu: [ submenu: [
{ {
label: 'Export Data', label: 'Export Data...',
role: 'export', role: 'export',
click: function(item: Object, focusedWindow: Object) { click: function(item: Object, focusedWindow: Object) {
exportStoreToFile(store); exportStoreToFile(store);
}, },
}, },
{
label: 'Import Data...',
role: 'import',
click: function(item: Object, focusedWindow: Object) {
dialog.showOpenDialog(
{
properties: ['openFile'],
},
(files: Array<string>) => {
if (files !== undefined && files.length > 0) {
importFileToStore(files[0], store);
}
},
);
},
},
], ],
}); });
} }

View File

@@ -219,7 +219,6 @@ class MainSidebar extends PureComponent<MainSidebarProps> {
numNotifications, numNotifications,
} = this.props; } = this.props;
let {clients, uninitializedClients} = this.props; let {clients, uninitializedClients} = this.props;
clients = clients clients = clients
.filter( .filter(
(client: Client) => (client: Client) =>

View File

@@ -78,7 +78,7 @@ export default class AndroidDevice extends BaseDevice {
}); });
} }
spawnShell(): DeviceShell { spawnShell(): ?DeviceShell {
return child_process.spawn('adb', ['-s', this.serial, 'shell', '-t', '-t']); return child_process.spawn('adb', ['-s', this.serial, 'shell', '-t', '-t']);
} }
} }

View File

@@ -0,0 +1,23 @@
/**
* Copyright 2018-present Facebook.
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
* @format
*/
import BaseDevice from './BaseDevice.js';
import type {DeviceType, OS, DeviceShell} from './BaseDevice.js';
export default class ArchivedDevice extends BaseDevice {
constructor(serial: string, deviceType: DeviceType, title: string, os: OS) {
super(serial, deviceType, title);
this.os = os;
}
getLogs() {
return [];
}
spawnShell(): ?DeviceShell {
return null;
}
}

View File

@@ -86,7 +86,7 @@ export default class BaseDevice {
teardown() {} teardown() {}
supportedColumns(): Array<string> { supportedColumns(): Array<string> {
throw new Error('unimplemented'); return ['date', 'pid', 'tid', 'tag', 'message', 'type', 'time'];
} }
addLogListener(callback: DeviceLogListener): Symbol { addLogListener(callback: DeviceLogListener): Symbol {
@@ -117,7 +117,7 @@ export default class BaseDevice {
this.logListeners.delete(id); this.logListeners.delete(id);
} }
spawnShell(): DeviceShell { spawnShell(): ?DeviceShell {
throw new Error('unimplemented'); throw new Error('unimplemented');
} }
} }

View File

@@ -13,6 +13,9 @@ import type {State as PluginStatesState} from '../reducers/pluginStates';
import type {State} from '../reducers/index'; import type {State} from '../reducers/index';
import {FlipperDevicePlugin} from '../plugin.js'; import {FlipperDevicePlugin} from '../plugin.js';
import {default as BaseDevice} from '../devices/BaseDevice'; import {default as BaseDevice} from '../devices/BaseDevice';
import {default as ArchivedDevice} from '../devices/ArchivedDevice';
import {default as Client} from '../Client';
import {getInstance} from '../fb-stubs/Logger.js';
import fs from 'fs'; import fs from 'fs';
import os from 'os'; import os from 'os';
@@ -150,3 +153,61 @@ export const exportStoreToFile = (store: Store): Promise<void> => {
console.error('Make sure a device is connected'); console.error('Make sure a device is connected');
return new Promise.reject(new Error('No device is selected')); return new Promise.reject(new Error('No device is selected'));
}; };
export const importFileToStore = (file: string, store: Store) => {
fs.readFile(file, 'utf8', (err, data) => {
if (err) {
console.error(err);
return;
}
const json = JSON.parse(data);
const {device, clients} = json;
const archivedDevice = new ArchivedDevice(
device.serial,
device.deviceType,
device.title,
device.os,
);
store.dispatch({
type: 'REGISTER_DEVICE',
payload: archivedDevice,
});
store.dispatch({
type: 'SELECT_DEVICE',
payload: archivedDevice,
});
const {pluginStates} = json.store;
const keys = Object.keys(pluginStates);
clients.forEach(client => {
const clientPlugins = keys
.filter(key => {
const arr = key.split('#');
arr.pop();
const clientPlugin = arr.join('#');
return client.id === clientPlugin;
})
.map(client => client.split('#').pop());
store.dispatch({
type: 'NEW_CLIENT',
payload: new Client(
client.id,
client.query,
null,
getInstance(),
store,
clientPlugins,
),
});
});
keys.forEach(key => {
store.dispatch({
type: 'SET_PLUGIN_STATE',
payload: {
pluginKey: key,
state: pluginStates[key],
},
});
});
});
};