From 0adc2ef52e81fa69e7c2b845a64617b7afac41fe Mon Sep 17 00:00:00 2001 From: Pritesh Nandgaonkar Date: Mon, 25 Mar 2019 04:50:07 -0700 Subject: [PATCH] Multiple crash support Summary: This diff adds support to show multiple crashes in the crash reporter plugin. You can also select crashes from the list of the dropdown. Reviewed By: danielbuechele Differential Revision: D14513401 fbshipit-source-id: 621d32c5971519e5046daec76ec2f9b32ba4d8ce --- src/index.js | 2 +- .../testCrashReporterPlugin.electron.js | 66 +++++-- src/plugins/crash_reporter/index.js | 186 ++++++++++++++++-- 3 files changed, 222 insertions(+), 32 deletions(-) diff --git a/src/index.js b/src/index.js index f054e260c..13c71eb00 100644 --- a/src/index.js +++ b/src/index.js @@ -16,7 +16,7 @@ export { FlipperDevicePlugin, callClient, } from './plugin.js'; -export type {PluginClient} from './plugin.js'; +export type {PluginClient, Props} from './plugin.js'; export {default as Client} from './Client.js'; export {clipboard} from 'electron'; export * from './fb-stubs/constants.js'; diff --git a/src/plugins/crash_reporter/__tests__/testCrashReporterPlugin.electron.js b/src/plugins/crash_reporter/__tests__/testCrashReporterPlugin.electron.js index 6f640d50d..1667c7ca8 100644 --- a/src/plugins/crash_reporter/__tests__/testCrashReporterPlugin.electron.js +++ b/src/plugins/crash_reporter/__tests__/testCrashReporterPlugin.electron.js @@ -38,9 +38,19 @@ function getCrash( callstack: callstack, reason: reason, name: name, + date: new Date(), }; } +function assertCrash(crash: Crash, expectedCrash: Crash) { + const {notificationID, callstack, reason, name, date} = crash; + expect(notificationID).toEqual(expectedCrash.notificationID); + expect(callstack).toEqual(expectedCrash.callstack); + expect(reason).toEqual(expectedCrash.reason); + expect(name).toEqual(expectedCrash.name); + expect(date.toDateString()).toEqual(expectedCrash.date.toDateString()); +} + beforeEach(() => { setNotificationID(0); // Resets notificationID to 0 setDefaultPersistedState({crashes: []}); // Resets defaultpersistedstate @@ -180,16 +190,28 @@ test('test getNewPersisitedStateFromCrashLog for non-empty defaultPersistedState ); const content = 'Blaa Blaaa \n Blaa Blaaa \n Exception Type: SIGSEGV \n Blaa Blaa \n Blaa Blaa'; - expect(perisistedState).toEqual({crashes: [pluginStateCrash]}); + expect(perisistedState).toBeDefined(); + // $FlowFixMe: Checked if perisistedState is defined or not + const {crashes} = perisistedState; + expect(crashes).toBeDefined(); + expect(crashes.length).toEqual(1); + expect(crashes[0]).toEqual(pluginStateCrash); const newPersistedState = getNewPersisitedStateFromCrashLog( perisistedState, CrashReporterPlugin, content, 'iOS', ); - expect(newPersistedState).toEqual({ - crashes: [pluginStateCrash, getCrash(1, content, 'SIGSEGV', 'SIGSEGV')], - }); + expect(newPersistedState).toBeDefined(); + // $FlowFixMe: Checked if perisistedState is defined or not + const newPersistedStateCrashes = newPersistedState.crashes; + expect(newPersistedStateCrashes).toBeDefined(); + expect(newPersistedStateCrashes.length).toEqual(2); + assertCrash(newPersistedStateCrashes[0], pluginStateCrash); + assertCrash( + newPersistedStateCrashes[1], + getCrash(1, content, 'SIGSEGV', 'SIGSEGV'), + ); }); test('test getNewPersisitedStateFromCrashLog for non-empty defaultPersistedState and undefined pluginState', () => { setNotificationID(0); @@ -210,9 +232,13 @@ test('test getNewPersisitedStateFromCrashLog for non-empty defaultPersistedState content, 'iOS', ); - expect(newPersistedState).toEqual({ - crashes: [crash, getCrash(1, content, 'SIGSEGV', 'SIGSEGV')], - }); + expect(newPersistedState).toBeDefined(); + // $FlowFixMe: Checked if perisistedState is defined or not + const {crashes} = newPersistedState; + expect(crashes).toBeDefined(); + expect(crashes.length).toEqual(2); + assertCrash(crashes[0], crash); + assertCrash(crashes[1], getCrash(1, content, 'SIGSEGV', 'SIGSEGV')); }); test('test getNewPersisitedStateFromCrashLog for non-empty defaultPersistedState and defined pluginState and improper crash log', () => { setNotificationID(0); @@ -234,17 +260,21 @@ test('test getNewPersisitedStateFromCrashLog for non-empty defaultPersistedState content, 'iOS', ); - expect(newPersistedState).toEqual({ - crashes: [ - pluginStateCrash, - getCrash( - 1, - content, - 'Cannot figure out the cause', - 'Cannot figure out the cause', - ), - ], - }); + expect(newPersistedState).toBeDefined(); + // $FlowFixMe: Checked if perisistedState is defined or not + const {crashes} = newPersistedState; + expect(crashes).toBeDefined(); + expect(crashes.length).toEqual(2); + assertCrash(crashes[0], pluginStateCrash); + assertCrash( + crashes[1], + getCrash( + 1, + content, + 'Cannot figure out the cause', + 'Cannot figure out the cause', + ), + ); }); test('test getNewPersisitedStateFromCrashLog when os is undefined', () => { setNotificationID(0); diff --git a/src/plugins/crash_reporter/index.js b/src/plugins/crash_reporter/index.js index 6588ea7be..81a93c6ce 100644 --- a/src/plugins/crash_reporter/index.js +++ b/src/plugins/crash_reporter/index.js @@ -26,13 +26,14 @@ import { colors, Toolbar, Spacer, + Select, } from 'flipper'; import fs from 'fs'; import os from 'os'; import util from 'util'; import path from 'path'; import type {Notification} from '../../plugin'; -import type {Store, DeviceLogEntry, OS} from 'flipper'; +import type {Store, DeviceLogEntry, OS, Props} from 'flipper'; import {Component} from 'react'; type HeaderRowProps = { @@ -43,6 +44,14 @@ type openLogsCallbackType = () => void; type CrashReporterBarProps = {| openLogsCallback?: openLogsCallbackType, + crashSelector: CrashSelectorProps, +|}; + +type CrashSelectorProps = {| + crashes: ?{[key: string]: string}, + orderedIDs: ?Array, + selectedCrashID: ?string, + onCrashChange: ?(string) => void, |}; export type Crash = {| @@ -50,6 +59,7 @@ export type Crash = {| callstack: string, reason: string, name: string, + date: Date, |}; export type CrashLog = {| @@ -62,6 +72,10 @@ export type PersistedState = { crashes: Array, }; +type State = { + crash: ?Crash, +}; + const Padder = styled('div')( ({paddingLeft, paddingRight, paddingBottom, paddingTop}) => ({ paddingLeft: paddingLeft || 0, @@ -111,11 +125,40 @@ const StyledFlexGrowColumn = styled(FlexColumn)({ flexGrow: 1, }); +const StyledFlexRowColumn = styled(FlexRow)({ + aligItems: 'center', + justifyContent: 'center', + height: '100%', +}); + const StyledFlexColumn = styled(StyledFlexGrowColumn)({ justifyContent: 'center', alignItems: 'center', }); +const MatchParentHeightComponent = styled(FlexRow)({ + height: '100%', +}); + +const ButtonGroupContainer = styled(FlexRow)({ + paddingLeft: 4, + paddingTop: 2, + paddingBottom: 2, + height: '100%', +}); + +const StyledSelectContainer = styled(FlexRow)({ + paddingLeft: 8, + paddingTop: 2, + paddingBottom: 2, + height: '100%', +}); + +const StyledSelect = styled(Select)({ + height: '100%', + maxWidth: 200, +}); + export function getNewPersisitedStateFromCrashLog( persistedState: ?PersistedState, persistingPlugin: Class | FlipperPlugin<>>, @@ -295,11 +338,75 @@ function addFileWatcherForiOSCrashLogs( }); } +class CrashSelector extends Component { + render() { + const {crashes, selectedCrashID, orderedIDs, onCrashChange} = this.props; + return ( + + + +