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:
committed by
Facebook Github Bot
parent
d37fa7ba95
commit
fd022e3c73
@@ -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],
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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(),
|
||||
]),
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user