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
This commit is contained in:
committed by
Facebook Github Bot
parent
fc1d32a3f9
commit
5ef970e5b5
@@ -73,7 +73,6 @@ export default class Client extends EventEmitter {
|
|||||||
store: Store,
|
store: Store,
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.connected = true;
|
this.connected = true;
|
||||||
this.plugins = [];
|
this.plugins = [];
|
||||||
this.connection = conn;
|
this.connection = conn;
|
||||||
@@ -104,6 +103,7 @@ export default class Client extends EventEmitter {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getDevice = (): ?BaseDevice =>
|
getDevice = (): ?BaseDevice =>
|
||||||
this.store
|
this.store
|
||||||
.getState()
|
.getState()
|
||||||
@@ -272,7 +272,7 @@ export default class Client extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
toJSON() {
|
toJSON() {
|
||||||
return `<Client#${this.id}>`;
|
return {id: this.id, query: this.query};
|
||||||
}
|
}
|
||||||
|
|
||||||
subscribe(
|
subscribe(
|
||||||
|
|||||||
@@ -6,7 +6,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import type {FlipperPlugin, FlipperDevicePlugin} from './plugin.js';
|
import type {FlipperPlugin, FlipperDevicePlugin} from './plugin.js';
|
||||||
|
import {exportStoreToFile} from './utils/exportData.js';
|
||||||
|
import type {Store} from './reducers/';
|
||||||
import electron from 'electron';
|
import electron from 'electron';
|
||||||
|
|
||||||
export type DefaultKeyboardAction = 'clear' | 'goToBottom' | 'createPaste';
|
export type DefaultKeyboardAction = 'clear' | 'goToBottom' | 'createPaste';
|
||||||
@@ -64,9 +65,13 @@ function actionHandler(action: string) {
|
|||||||
|
|
||||||
export function setupMenuBar(
|
export function setupMenuBar(
|
||||||
plugins: Array<Class<FlipperPlugin<> | FlipperDevicePlugin<>>>,
|
plugins: Array<Class<FlipperPlugin<> | 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
|
// collect all keyboard actions from all plugins
|
||||||
const registeredActions: Set<?KeyboardAction> = new Set(
|
const registeredActions: Set<?KeyboardAction> = new Set(
|
||||||
plugins
|
plugins
|
||||||
@@ -169,8 +174,24 @@ export function activateMenuItems(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTemplate(app: Object, shell: Object): Array<MenuItem> {
|
function getTemplate(
|
||||||
|
app: Object,
|
||||||
|
shell: Object,
|
||||||
|
store: Store,
|
||||||
|
): Array<MenuItem> {
|
||||||
const template = [
|
const template = [
|
||||||
|
{
|
||||||
|
label: 'File',
|
||||||
|
submenu: [
|
||||||
|
{
|
||||||
|
label: 'Export Data',
|
||||||
|
role: 'export',
|
||||||
|
click: function(item: Object, focusedWindow: Object) {
|
||||||
|
exportStoreToFile(store);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: 'Edit',
|
label: 'Edit',
|
||||||
submenu: [
|
submenu: [
|
||||||
|
|||||||
@@ -68,7 +68,12 @@ export default class BaseDevice {
|
|||||||
}
|
}
|
||||||
|
|
||||||
toJSON() {
|
toJSON() {
|
||||||
return `<${this.constructor.name}#${this.title}#${this.serial}>`;
|
return {
|
||||||
|
os: this.os,
|
||||||
|
title: this.title,
|
||||||
|
deviceType: this.deviceType,
|
||||||
|
serial: this.serial,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
teardown() {}
|
teardown() {}
|
||||||
|
|||||||
@@ -59,10 +59,13 @@ export default (store: Store, logger: Logger) => {
|
|||||||
store.subscribe(() => {
|
store.subscribe(() => {
|
||||||
const newState = store.getState().plugins;
|
const newState = store.getState().plugins;
|
||||||
if (state !== newState) {
|
if (state !== newState) {
|
||||||
setupMenuBar([
|
setupMenuBar(
|
||||||
|
[
|
||||||
...newState.devicePlugins.values(),
|
...newState.devicePlugins.values(),
|
||||||
...newState.clientPlugins.values(),
|
...newState.clientPlugins.values(),
|
||||||
]);
|
],
|
||||||
|
store,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
state = newState;
|
state = newState;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -354,6 +354,7 @@ export const selectPlugin = (payload: {|
|
|||||||
type: 'SELECT_PLUGIN',
|
type: 'SELECT_PLUGIN',
|
||||||
payload,
|
payload,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const userPreferredPlugin = (payload: string): Action => ({
|
export const userPreferredPlugin = (payload: string): Action => ({
|
||||||
type: 'SELECT_USER_PREFERRED_PLUGIN',
|
type: 'SELECT_USER_PREFERRED_PLUGIN',
|
||||||
payload,
|
payload,
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import connections from './connections.js';
|
|||||||
import pluginStates from './pluginStates.js';
|
import pluginStates from './pluginStates.js';
|
||||||
import notifications from './notifications.js';
|
import notifications from './notifications.js';
|
||||||
import plugins from './plugins.js';
|
import plugins from './plugins.js';
|
||||||
|
|
||||||
import {persistReducer} from 'redux-persist';
|
import {persistReducer} from 'redux-persist';
|
||||||
import storage from 'redux-persist/lib/storage/index.js';
|
import storage from 'redux-persist/lib/storage/index.js';
|
||||||
|
|
||||||
|
|||||||
76
src/utils/exportData.js
Normal file
76
src/utils/exportData.js
Normal file
@@ -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<void> => {
|
||||||
|
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'));
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user