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(
id: string,
query: ClientQuery,
conn: ReactiveSocket,
conn: ?ReactiveSocket,
logger: Logger,
store: Store,
plugins: ?Plugins,
) {
super();
this.connected = true;
this.plugins = [];
this.plugins = plugins ? plugins : [];
this.connection = conn;
this.id = id;
this.query = query;
this.messageIdCounter = 0;
this.logger = logger;
this.store = store;
this.broadcastCallbacks = new Map();
this.requestCallbacks = new Map();
@@ -104,16 +104,18 @@ export default class Client extends EventEmitter {
},
};
conn.connectionStatus().subscribe({
onNext(payload) {
if (payload.kind == 'ERROR' || payload.kind == 'CLOSED') {
client.connected = false;
}
},
onSubscribe(subscription) {
subscription.request(Number.MAX_SAFE_INTEGER);
},
});
if (conn) {
conn.connectionStatus().subscribe({
onNext(payload) {
if (payload.kind == 'ERROR' || payload.kind == 'CLOSED') {
client.connected = false;
}
},
onSubscribe(subscription) {
subscription.request(Number.MAX_SAFE_INTEGER);
},
});
}
}
getDevice = (): ?BaseDevice =>
@@ -132,7 +134,7 @@ export default class Client extends EventEmitter {
query: ClientQuery;
messageIdCounter: number;
plugins: Plugins;
connection: ReactiveSocket;
connection: ?ReactiveSocket;
responder: PartialResponder;
store: Store;
@@ -337,7 +339,9 @@ export default class Client extends EventEmitter {
console.debug(data, 'message:call');
this.startTimingRequestResponse({method, id, params});
this.connection.fireAndForget({data: JSON.stringify(data)});
if (this.connection) {
this.connection.fireAndForget({data: JSON.stringify(data)});
}
});
}
@@ -369,7 +373,9 @@ export default class Client extends EventEmitter {
params,
};
console.debug(data, 'message:send');
this.connection.fireAndForget({data: JSON.stringify(data)});
if (this.connection) {
this.connection.fireAndForget({data: JSON.stringify(data)});
}
}
call(api: string, method: string, params?: Object): Promise<Object> {

View File

@@ -6,10 +6,12 @@
*/
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 electron from 'electron';
import {GK} from 'flipper';
import {remote} from 'electron';
const {dialog} = remote;
export type DefaultKeyboardAction = 'clear' | 'goToBottom' | 'createPaste';
export type TopLevelMenu = 'Edit' | 'View' | 'Window' | 'Help';
@@ -315,12 +317,28 @@ function getTemplate(
label: 'File',
submenu: [
{
label: 'Export Data',
label: 'Export Data...',
role: 'export',
click: function(item: Object, focusedWindow: Object) {
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,
} = this.props;
let {clients, uninitializedClients} = this.props;
clients = clients
.filter(
(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']);
}
}

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() {}
supportedColumns(): Array<string> {
throw new Error('unimplemented');
return ['date', 'pid', 'tid', 'tag', 'message', 'type', 'time'];
}
addLogListener(callback: DeviceLogListener): Symbol {
@@ -117,7 +117,7 @@ export default class BaseDevice {
this.logListeners.delete(id);
}
spawnShell(): DeviceShell {
spawnShell(): ?DeviceShell {
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 {FlipperDevicePlugin} from '../plugin.js';
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 os from 'os';
@@ -150,3 +153,61 @@ export const exportStoreToFile = (store: Store): Promise<void> => {
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) => {
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],
},
});
});
});
};