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 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 {App} from './App.js';
|
||||||
import type Logger from './fb-stubs/Logger.js';
|
import type Logger from './fb-stubs/Logger.js';
|
||||||
import type {Store} from './reducers/index.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) &
|
on: ((event: 'plugins-change', callback: () => void) => void) &
|
||||||
((event: 'close', 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.clientPlugins.get(params.api) ||
|
||||||
this.store.getState().plugins.devicePlugins.get(params.api);
|
this.store.getState().plugins.devicePlugins.get(params.api);
|
||||||
if (persistingPlugin && persistingPlugin.persistedStateReducer) {
|
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 = {
|
const persistedState = {
|
||||||
...persistingPlugin.defaultPersistedState,
|
...persistingPlugin.defaultPersistedState,
|
||||||
...this.store.getState().pluginStates[pluginKey],
|
...this.store.getState().pluginStates[pluginKey],
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
*/
|
*/
|
||||||
import type {FlipperPlugin, FlipperDevicePlugin} from './plugin.js';
|
import type {FlipperPlugin, FlipperDevicePlugin} from './plugin.js';
|
||||||
import type LogManager from './fb-stubs/Logger';
|
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 type {Props as PluginProps} from './plugin';
|
||||||
|
|
||||||
import Client from './Client.js';
|
import Client from './Client.js';
|
||||||
@@ -128,7 +128,6 @@ class PluginContainer extends Component<Props, State> {
|
|||||||
if (!activePlugin || !target) {
|
if (!activePlugin || !target) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const props: PluginProps<Object> = {
|
const props: PluginProps<Object> = {
|
||||||
key: pluginKey,
|
key: pluginKey,
|
||||||
logger: this.props.logger,
|
logger: this.props.logger,
|
||||||
@@ -150,6 +149,9 @@ class PluginContainer extends Component<Props, State> {
|
|||||||
) {
|
) {
|
||||||
this.props.selectPlugin({selectedPlugin: pluginID, deepLinkPayload});
|
this.props.selectPlugin({selectedPlugin: pluginID, deepLinkPayload});
|
||||||
return true;
|
return true;
|
||||||
|
} else if (target instanceof BaseDevice) {
|
||||||
|
this.props.selectPlugin({selectedPlugin: pluginID, deepLinkPayload});
|
||||||
|
return true;
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -170,6 +170,7 @@ type MainSidebarProps = {|
|
|||||||
selectPlugin: (payload: {
|
selectPlugin: (payload: {
|
||||||
selectedPlugin: ?string,
|
selectedPlugin: ?string,
|
||||||
selectedApp: ?string,
|
selectedApp: ?string,
|
||||||
|
deepLinkPayload: ?string,
|
||||||
}) => void,
|
}) => void,
|
||||||
clients: Array<Client>,
|
clients: Array<Client>,
|
||||||
uninitializedClients: Array<{
|
uninitializedClients: Array<{
|
||||||
@@ -220,6 +221,7 @@ class MainSidebar extends Component<MainSidebarProps> {
|
|||||||
selectPlugin({
|
selectPlugin({
|
||||||
selectedPlugin: 'notifications',
|
selectedPlugin: 'notifications',
|
||||||
selectedApp: null,
|
selectedApp: null,
|
||||||
|
deepLinkPayload: null,
|
||||||
})
|
})
|
||||||
}>
|
}>
|
||||||
<PluginIcon
|
<PluginIcon
|
||||||
@@ -250,6 +252,7 @@ class MainSidebar extends Component<MainSidebarProps> {
|
|||||||
selectPlugin({
|
selectPlugin({
|
||||||
selectedPlugin: plugin.id,
|
selectedPlugin: plugin.id,
|
||||||
selectedApp: null,
|
selectedApp: null,
|
||||||
|
deepLinkPayload: null,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
plugin={plugin}
|
plugin={plugin}
|
||||||
@@ -282,6 +285,7 @@ class MainSidebar extends Component<MainSidebarProps> {
|
|||||||
selectPlugin({
|
selectPlugin({
|
||||||
selectedPlugin: plugin.id,
|
selectedPlugin: plugin.id,
|
||||||
selectedApp: client.id,
|
selectedApp: client.id,
|
||||||
|
deepLinkPayload: null,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
plugin={plugin}
|
plugin={plugin}
|
||||||
|
|||||||
@@ -30,7 +30,12 @@ export default (store: Store, logger: Logger) => {
|
|||||||
});
|
});
|
||||||
store.dispatch({
|
store.dispatch({
|
||||||
type: 'CLEAR_PLUGIN_STATE',
|
type: 'CLEAR_PLUGIN_STATE',
|
||||||
payload: id,
|
payload: {
|
||||||
|
id,
|
||||||
|
devicePlugins: new Set([
|
||||||
|
...store.getState().plugins.devicePlugins.keys(),
|
||||||
|
]),
|
||||||
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -14,8 +14,11 @@ export {
|
|||||||
FlipperPlugin,
|
FlipperPlugin,
|
||||||
FlipperDevicePlugin,
|
FlipperDevicePlugin,
|
||||||
} from './plugin.js';
|
} from './plugin.js';
|
||||||
|
export {clipboard} from 'electron';
|
||||||
export * from './fb-stubs/constants.js';
|
export * from './fb-stubs/constants.js';
|
||||||
|
export * from './utils/createPaste.js';
|
||||||
|
export {connect} from 'react-redux';
|
||||||
|
export {selectPlugin} from './reducers/connections';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
default as SidebarExtensions,
|
default as SidebarExtensions,
|
||||||
|
|||||||
@@ -6,7 +6,17 @@
|
|||||||
* @flow
|
* @flow
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {FlipperDevicePlugin, Device} from 'flipper';
|
import {
|
||||||
|
FlipperDevicePlugin,
|
||||||
|
Device,
|
||||||
|
View,
|
||||||
|
styled,
|
||||||
|
FlexColumn,
|
||||||
|
FlexRow,
|
||||||
|
ContextMenu,
|
||||||
|
clipboard,
|
||||||
|
Button,
|
||||||
|
} from 'flipper';
|
||||||
import type {Notification} from '../../plugin';
|
import type {Notification} from '../../plugin';
|
||||||
|
|
||||||
type Crash = {|
|
type Crash = {|
|
||||||
@@ -19,6 +29,34 @@ type PersistedState = {|
|
|||||||
crashes: Array<Crash>,
|
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 {
|
export default class CrashReporterPlugin extends FlipperDevicePlugin {
|
||||||
static title = 'Crash Reporter';
|
static title = 'Crash Reporter';
|
||||||
static id = 'CrashReporter';
|
static id = 'CrashReporter';
|
||||||
@@ -67,11 +105,70 @@ export default class CrashReporterPlugin extends FlipperDevicePlugin {
|
|||||||
message: crash.callStack,
|
message: crash.callStack,
|
||||||
severity: 'error',
|
severity: 'error',
|
||||||
title: 'CRASH: ' + crash.name + ' ' + crash.reason,
|
title: 'CRASH: ' + crash.name + ' ' + crash.reason,
|
||||||
|
action: 'Inspect',
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
convertCallstackToString(crash: Crash): string {
|
||||||
|
return crash.callStack.reduce((acc, val) => acc.concat('\n', val));
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
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 = {
|
state = {
|
||||||
rows: [],
|
rows: [],
|
||||||
entries: [],
|
entries: [],
|
||||||
@@ -390,7 +414,6 @@ export default class LogTable extends FlipperDevicePlugin<
|
|||||||
key: String(this.counter++),
|
key: String(this.counter++),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// batch up logs to be processed every 250ms, if we have lots of log
|
// 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
|
// messages coming in, then calling an setState 200+ times is actually
|
||||||
// pretty expensive
|
// pretty expensive
|
||||||
@@ -425,11 +448,15 @@ export default class LogTable extends FlipperDevicePlugin<
|
|||||||
|
|
||||||
this.addRowIfNeeded(rows, row, entry, previousEntry);
|
this.addRowIfNeeded(rows, row, entry, previousEntry);
|
||||||
}
|
}
|
||||||
|
const highlightedRows = this.calculateHighlightedRows(
|
||||||
|
this.props.deepLinkPayload,
|
||||||
|
rows,
|
||||||
|
);
|
||||||
return {
|
return {
|
||||||
entries,
|
entries,
|
||||||
rows,
|
rows,
|
||||||
key2entry,
|
key2entry,
|
||||||
|
highlightedRows: highlightedRows,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}, 100);
|
}, 100);
|
||||||
@@ -547,8 +574,7 @@ export default class LogTable extends FlipperDevicePlugin<
|
|||||||
});
|
});
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {rows} = this.state;
|
const {rows, highlightedRows} = this.state;
|
||||||
|
|
||||||
const contextMenuItems = [
|
const contextMenuItems = [
|
||||||
{
|
{
|
||||||
type: 'separator',
|
type: 'separator',
|
||||||
@@ -568,6 +594,9 @@ export default class LogTable extends FlipperDevicePlugin<
|
|||||||
columnOrder={this.columnOrder}
|
columnOrder={this.columnOrder}
|
||||||
columns={this.columns}
|
columns={this.columns}
|
||||||
rows={rows}
|
rows={rows}
|
||||||
|
highlightedRows={
|
||||||
|
highlightedRows ? new Set(highlightedRows) : new Set([])
|
||||||
|
}
|
||||||
onRowHighlighted={this.onRowHighlighted}
|
onRowHighlighted={this.onRowHighlighted}
|
||||||
multiHighlight={true}
|
multiHighlight={true}
|
||||||
defaultFilters={DEFAULT_FILTERS}
|
defaultFilters={DEFAULT_FILTERS}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export type Action =
|
|||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: 'CLEAR_PLUGIN_STATE',
|
type: 'CLEAR_PLUGIN_STATE',
|
||||||
payload: string,
|
payload: {id: string, devicePlugins: Set<string>},
|
||||||
};
|
};
|
||||||
|
|
||||||
const INITIAL_STATE: State = {};
|
const INITIAL_STATE: State = {};
|
||||||
@@ -41,7 +41,8 @@ export default function reducer(
|
|||||||
return Object.keys(state).reduce((newState, pluginKey) => {
|
return Object.keys(state).reduce((newState, pluginKey) => {
|
||||||
// Only add the pluginState, if its from a plugin other than the one that
|
// Only add the pluginState, if its from a plugin other than the one that
|
||||||
// was removed. pluginKeys are in the form of ${clientID}#${pluginID}.
|
// 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];
|
newState[pluginKey] = state[pluginKey];
|
||||||
}
|
}
|
||||||
return newState;
|
return newState;
|
||||||
|
|||||||
Reference in New Issue
Block a user