From 5ef970e5b57a8a10a938535368b9123a26fb5c1e Mon Sep 17 00:00:00 2001 From: Pritesh Nandgaonkar Date: Mon, 28 Jan 2019 15:33:56 -0800 Subject: [PATCH] Serialize Store Summary: This diff adds the capability to export the flipper data to a file. With this diff you can click on the "Export Flipper" option from the "Edit" menu in menubar. It will export it in the file at this location `~/.flipper/MessageLogs.json` We do not exactly export the store, but just the important part of it. We export in the following format ``` { fileVersion: '1.0', device: { os: 'iOS', title: 'iPhone 7', serial: '', deviceType: 'physical', }, clients: [ { query: { app: 'Facebook', }, d: '12345678' }, { query: { app: 'Instagram', }, id: '12345678' } ], store: { pluginState: {}, notifications: {} } } ``` In next diff I will add the capability to select the folder to export the file too. Reviewed By: danielbuechele Differential Revision: D13751963 fbshipit-source-id: 7d3d49c6adf8145b2181d2332c7dbd589155cec3 --- src/Client.js | 4 +- src/MenuBar.js | 29 ++++++++++++-- src/devices/BaseDevice.js | 7 +++- src/dispatcher/plugins.js | 11 ++++-- src/reducers/connections.js | 1 + src/reducers/index.js | 1 + src/utils/exportData.js | 76 +++++++++++++++++++++++++++++++++++++ 7 files changed, 118 insertions(+), 11 deletions(-) create mode 100644 src/utils/exportData.js diff --git a/src/Client.js b/src/Client.js index 97dd80a09..40a29eb8e 100644 --- a/src/Client.js +++ b/src/Client.js @@ -73,7 +73,6 @@ export default class Client extends EventEmitter { store: Store, ) { super(); - this.connected = true; this.plugins = []; this.connection = conn; @@ -104,6 +103,7 @@ export default class Client extends EventEmitter { }, }); } + getDevice = (): ?BaseDevice => this.store .getState() @@ -272,7 +272,7 @@ export default class Client extends EventEmitter { } toJSON() { - return ``; + return {id: this.id, query: this.query}; } subscribe( diff --git a/src/MenuBar.js b/src/MenuBar.js index 105680ddb..15227424c 100644 --- a/src/MenuBar.js +++ b/src/MenuBar.js @@ -6,7 +6,8 @@ */ import type {FlipperPlugin, FlipperDevicePlugin} from './plugin.js'; - +import {exportStoreToFile} from './utils/exportData.js'; +import type {Store} from './reducers/'; import electron from 'electron'; export type DefaultKeyboardAction = 'clear' | 'goToBottom' | 'createPaste'; @@ -64,9 +65,13 @@ function actionHandler(action: string) { export function setupMenuBar( plugins: Array | FlipperDevicePlugin<>>>, + store: Store, ) { - const template = getTemplate(electron.remote.app, electron.remote.shell); - + const template = getTemplate( + electron.remote.app, + electron.remote.shell, + store, + ); // collect all keyboard actions from all plugins const registeredActions: Set = new Set( plugins @@ -169,8 +174,24 @@ export function activateMenuItems( ); } -function getTemplate(app: Object, shell: Object): Array { +function getTemplate( + app: Object, + shell: Object, + store: Store, +): Array { const template = [ + { + label: 'File', + submenu: [ + { + label: 'Export Data', + role: 'export', + click: function(item: Object, focusedWindow: Object) { + exportStoreToFile(store); + }, + }, + ], + }, { label: 'Edit', submenu: [ diff --git a/src/devices/BaseDevice.js b/src/devices/BaseDevice.js index fba3b4a08..45c91ffba 100644 --- a/src/devices/BaseDevice.js +++ b/src/devices/BaseDevice.js @@ -68,7 +68,12 @@ export default class BaseDevice { } toJSON() { - return `<${this.constructor.name}#${this.title}#${this.serial}>`; + return { + os: this.os, + title: this.title, + deviceType: this.deviceType, + serial: this.serial, + }; } teardown() {} diff --git a/src/dispatcher/plugins.js b/src/dispatcher/plugins.js index b3a07b8b5..110ba384f 100644 --- a/src/dispatcher/plugins.js +++ b/src/dispatcher/plugins.js @@ -59,10 +59,13 @@ export default (store: Store, logger: Logger) => { store.subscribe(() => { const newState = store.getState().plugins; if (state !== newState) { - setupMenuBar([ - ...newState.devicePlugins.values(), - ...newState.clientPlugins.values(), - ]); + setupMenuBar( + [ + ...newState.devicePlugins.values(), + ...newState.clientPlugins.values(), + ], + store, + ); } state = newState; }); diff --git a/src/reducers/connections.js b/src/reducers/connections.js index 729c6ff46..b6afe84a0 100644 --- a/src/reducers/connections.js +++ b/src/reducers/connections.js @@ -354,6 +354,7 @@ export const selectPlugin = (payload: {| type: 'SELECT_PLUGIN', payload, }); + export const userPreferredPlugin = (payload: string): Action => ({ type: 'SELECT_USER_PREFERRED_PLUGIN', payload, diff --git a/src/reducers/index.js b/src/reducers/index.js index e6f89ee0f..3f06bfb19 100644 --- a/src/reducers/index.js +++ b/src/reducers/index.js @@ -11,6 +11,7 @@ import connections from './connections.js'; import pluginStates from './pluginStates.js'; import notifications from './notifications.js'; import plugins from './plugins.js'; + import {persistReducer} from 'redux-persist'; import storage from 'redux-persist/lib/storage/index.js'; diff --git a/src/utils/exportData.js b/src/utils/exportData.js new file mode 100644 index 000000000..9a970b949 --- /dev/null +++ b/src/utils/exportData.js @@ -0,0 +1,76 @@ +/** + * 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 fs from 'fs'; +import os from 'os'; +import path from 'path'; + +const exportFilePath = path.join( + os.homedir(), + '.flipper', + 'FlipperExport.json', +); + +export const exportStoreToFile = (data: Store): Promise => { + const state = data.getState(); + const json = { + fileVersion: '1.0.0', + device: {}, + clients: [], + store: { + pluginStates: {}, + activeNotifications: [], + }, + }; + + const device = state.connections.selectedDevice; + if (device) { + const {serial} = device; + json.device = device.toJSON(); + const clients = state.connections.clients + .filter(client => { + return client.query.device_id === serial; + }) + .map(client => { + return client.toJSON(); + }); + + json.clients = clients; + + const allPluginStates = state.pluginStates; + let pluginStates = {}; + for (let key in allPluginStates) { + const filteredClients = clients.filter(client => { + let keyArray = key.split('#'); + keyArray.pop(); // Remove the last entry related to plugin + return client.id.includes(keyArray.join('#')); + }); + if (filteredClients.length > 0) { + pluginStates = {...pluginStates, [key]: allPluginStates[key]}; + json.store.pluginStates = pluginStates; + } + } + + const allActiveNotifications = state.notifications.activeNotifications; + let activeNotifications = allActiveNotifications.filter(notif => { + const filteredClients = clients.filter(client => + client.id.includes(notif.client), + ); + return filteredClients.length > 0; + }); + json.store.activeNotifications = activeNotifications; + return new Promise((resolve, reject) => { + fs.writeFile(exportFilePath, JSON.stringify(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')); +};