Improvise UI of crash reporter plugin

Summary:
- New improved UI
- Instead of sending the callstack as a string from android, now sending it as an array
- Deeplink to Logs support just for android. In iOS crash is not automatically logged in Logs plugin, atleast thats what happens in sample app

Reviewed By: jknoxville

Differential Revision: D13216477

fbshipit-source-id: d8b77549c83572d0442e431ce88a8f01f42c9565
This commit is contained in:
Pritesh Nandgaonkar
2018-11-30 05:26:46 -08:00
committed by Facebook Github Bot
parent d37fa7ba95
commit fd022e3c73
8 changed files with 168 additions and 13 deletions

View File

@@ -6,6 +6,9 @@
*/
import type {FlipperPlugin, FlipperBasePlugin} from './plugin.js';
import {FlipperDevicePlugin} from './plugin.js';
import type BaseDevice from './devices/BaseDevice.js';
import type {App} from './App.js';
import type Logger from './fb-stubs/Logger.js';
import type {Store} from './reducers/index.js';
@@ -67,6 +70,12 @@ export default class Client extends EventEmitter {
},
});
}
getDevice = (): ?BaseDevice =>
this.store
.getState()
.connections.devices.find(
(device: BaseDevice) => device.serial === this.query.device_id,
);
on: ((event: 'plugins-change', callback: () => void) => void) &
((event: 'close', callback: () => void) => void);
@@ -157,7 +166,12 @@ export default class Client extends EventEmitter {
this.store.getState().plugins.clientPlugins.get(params.api) ||
this.store.getState().plugins.devicePlugins.get(params.api);
if (persistingPlugin && persistingPlugin.persistedStateReducer) {
const pluginKey = `${this.id}#${params.api}`;
let pluginKey = `${this.id}#${params.api}`;
//$FlowFixMe
if (persistingPlugin.prototype instanceof FlipperDevicePlugin) {
// For device plugins, we are just using the device id instead of client id as the prefix.
pluginKey = `${this.getDevice()?.serial || ''}#${params.api}`;
}
const persistedState = {
...persistingPlugin.defaultPersistedState,
...this.store.getState().pluginStates[pluginKey],

View File

@@ -6,7 +6,7 @@
*/
import type {FlipperPlugin, FlipperDevicePlugin} from './plugin.js';
import type LogManager from './fb-stubs/Logger';
import type BaseDevice from './devices/BaseDevice.js';
import BaseDevice from './devices/BaseDevice.js';
import type {Props as PluginProps} from './plugin';
import Client from './Client.js';
@@ -128,7 +128,6 @@ class PluginContainer extends Component<Props, State> {
if (!activePlugin || !target) {
return null;
}
const props: PluginProps<Object> = {
key: pluginKey,
logger: this.props.logger,
@@ -150,6 +149,9 @@ class PluginContainer extends Component<Props, State> {
) {
this.props.selectPlugin({selectedPlugin: pluginID, deepLinkPayload});
return true;
} else if (target instanceof BaseDevice) {
this.props.selectPlugin({selectedPlugin: pluginID, deepLinkPayload});
return true;
} else {
return false;
}

View File

@@ -170,6 +170,7 @@ type MainSidebarProps = {|
selectPlugin: (payload: {
selectedPlugin: ?string,
selectedApp: ?string,
deepLinkPayload: ?string,
}) => void,
clients: Array<Client>,
uninitializedClients: Array<{
@@ -220,6 +221,7 @@ class MainSidebar extends Component<MainSidebarProps> {
selectPlugin({
selectedPlugin: 'notifications',
selectedApp: null,
deepLinkPayload: null,
})
}>
<PluginIcon
@@ -250,6 +252,7 @@ class MainSidebar extends Component<MainSidebarProps> {
selectPlugin({
selectedPlugin: plugin.id,
selectedApp: null,
deepLinkPayload: null,
})
}
plugin={plugin}
@@ -282,6 +285,7 @@ class MainSidebar extends Component<MainSidebarProps> {
selectPlugin({
selectedPlugin: plugin.id,
selectedApp: client.id,
deepLinkPayload: null,
})
}
plugin={plugin}

View File

@@ -30,7 +30,12 @@ export default (store: Store, logger: Logger) => {
});
store.dispatch({
type: 'CLEAR_PLUGIN_STATE',
payload: id,
payload: {
id,
devicePlugins: new Set([
...store.getState().plugins.devicePlugins.keys(),
]),
},
});
});

View File

@@ -14,8 +14,11 @@ export {
FlipperPlugin,
FlipperDevicePlugin,
} from './plugin.js';
export {clipboard} from 'electron';
export * from './fb-stubs/constants.js';
export * from './utils/createPaste.js';
export {connect} from 'react-redux';
export {selectPlugin} from './reducers/connections';
export {
default as SidebarExtensions,

View File

@@ -6,7 +6,17 @@
* @flow
*/
import {FlipperDevicePlugin, Device} from 'flipper';
import {
FlipperDevicePlugin,
Device,
View,
styled,
FlexColumn,
FlexRow,
ContextMenu,
clipboard,
Button,
} from 'flipper';
import type {Notification} from '../../plugin';
type Crash = {|
@@ -19,6 +29,34 @@ type PersistedState = {|
crashes: Array<Crash>,
|};
const Title = styled(View)({
fontWeight: 'bold',
fontSize: '100%',
color: 'red',
});
const Value = styled(View)({
paddingLeft: '8px',
fontSize: '100%',
fontFamily: 'Monospace',
});
const RootColumn = styled(FlexColumn)({
paddingLeft: '16px',
paddingRight: '16px',
paddingTop: '8px',
});
const CrashRow = styled(FlexRow)({
paddingTop: '8px',
});
const CallStack = styled('pre')({
fontFamily: 'Monospace',
fontSize: '100%',
paddingLeft: '8px',
});
export default class CrashReporterPlugin extends FlipperDevicePlugin {
static title = 'Crash Reporter';
static id = 'CrashReporter';
@@ -67,11 +105,70 @@ export default class CrashReporterPlugin extends FlipperDevicePlugin {
message: crash.callStack,
severity: 'error',
title: 'CRASH: ' + crash.name + ' ' + crash.reason,
action: 'Inspect',
};
});
};
convertCallstackToString(crash: Crash): string {
return crash.callStack.reduce((acc, val) => acc.concat('\n', val));
}
render() {
return 'Dedicated space to debug crashes. Look out for crash notifications.';
if (this.props.persistedState.crashes.length > 0) {
const crash = this.props.persistedState.crashes.slice(-1)[0];
const callStackString = this.convertCallstackToString(crash);
return (
<RootColumn>
<CrashRow>
<Title>Name</Title>
<Value>{crash.name}</Value>
</CrashRow>
<CrashRow>
<Title>Reason</Title>
<Value>{crash.reason}</Value>
</CrashRow>
<CrashRow>
<Title>CallStack</Title>
</CrashRow>
<CrashRow>
<ContextMenu
items={[
{
label: 'copy',
click: () => {
clipboard.writeText(callStackString);
},
},
]}>
<CallStack>{callStackString}</CallStack>
</ContextMenu>
</CrashRow>
{this.device.os == 'Android' && (
<CrashRow>
<Button
onClick={event => {
this.props.selectPlugin(
'DeviceLogs',
JSON.stringify({
...crash,
callStackString: callStackString,
}),
);
}}>
Deeplink to Logs
</Button>
</CrashRow>
)}
</RootColumn>
);
}
return (
<RootColumn>
<Title>
Dedicated space to debug crashes. Look out for crash notifications
</Title>
</RootColumn>
);
}
}

View File

@@ -280,6 +280,30 @@ export default class LogTable extends FlipperDevicePlugin<
}));
};
calculateHighlightedRows = (
deeplinkPayload: ?string,
rows: Array<TableBodyRow>,
): Array<string> => {
if (!deeplinkPayload) {
return [];
}
const crash = JSON.parse(deeplinkPayload);
let highlightedRows = rows.filter(x => {
//$FlowFixMe: x.filterValue is not undefined
let matched = x.filterValue.includes(crash.name);
if (!matched) {
return matched;
}
for (let i = 0; i < crash.callStack.length && matched; ++i) {
//$FlowFixMe: x.filterValue is not undefined
matched = x.filterValue.includes(crash.callStack[i]);
}
return matched;
});
highlightedRows = highlightedRows.map(x => x.key);
return [highlightedRows.pop()];
};
state = {
rows: [],
entries: [],
@@ -390,7 +414,6 @@ export default class LogTable extends FlipperDevicePlugin<
key: String(this.counter++),
},
};
// batch up logs to be processed every 250ms, if we have lots of log
// messages coming in, then calling an setState 200+ times is actually
// pretty expensive
@@ -425,11 +448,15 @@ export default class LogTable extends FlipperDevicePlugin<
this.addRowIfNeeded(rows, row, entry, previousEntry);
}
const highlightedRows = this.calculateHighlightedRows(
this.props.deepLinkPayload,
rows,
);
return {
entries,
rows,
key2entry,
highlightedRows: highlightedRows,
};
});
}, 100);
@@ -547,8 +574,7 @@ export default class LogTable extends FlipperDevicePlugin<
});
render() {
const {rows} = this.state;
const {rows, highlightedRows} = this.state;
const contextMenuItems = [
{
type: 'separator',
@@ -568,6 +594,9 @@ export default class LogTable extends FlipperDevicePlugin<
columnOrder={this.columnOrder}
columns={this.columns}
rows={rows}
highlightedRows={
highlightedRows ? new Set(highlightedRows) : new Set([])
}
onRowHighlighted={this.onRowHighlighted}
multiHighlight={true}
defaultFilters={DEFAULT_FILTERS}

View File

@@ -19,7 +19,7 @@ export type Action =
}
| {
type: 'CLEAR_PLUGIN_STATE',
payload: string,
payload: {id: string, devicePlugins: Set<string>},
};
const INITIAL_STATE: State = {};
@@ -41,7 +41,8 @@ export default function reducer(
return Object.keys(state).reduce((newState, pluginKey) => {
// Only add the pluginState, if its from a plugin other than the one that
// was removed. pluginKeys are in the form of ${clientID}#${pluginID}.
if (!pluginKey.startsWith(payload)) {
const pluginId = pluginKey.split('#').pop();
if (pluginId !== payload.id || payload.devicePlugins.has(pluginId)) {
newState[pluginKey] = state[pluginKey];
}
return newState;