Convert UI to Sandy
Summary: With proper notification, components and code clean up in place, time for the reward and giving the plugin a fresh look. Changelog: CrashReporter plugin got a fresh look and several navigation issues were addressed. Reviewed By: passy Differential Revision: D28102398 fbshipit-source-id: 5721634e45c5b1fc5fba3fb0c0b8970635b80b46
This commit is contained in:
committed by
Facebook GitHub Bot
parent
01ea822341
commit
e707fcc9f9
@@ -25,12 +25,17 @@ export function Toolbar({
|
|||||||
children,
|
children,
|
||||||
style,
|
style,
|
||||||
wash,
|
wash,
|
||||||
|
right,
|
||||||
}: {
|
}: {
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
position?: 'bottom' | 'top';
|
position?: 'bottom' | 'top';
|
||||||
compact?: boolean;
|
compact?: boolean;
|
||||||
wash?: boolean;
|
wash?: boolean;
|
||||||
style?: React.CSSProperties;
|
style?: React.CSSProperties;
|
||||||
|
/**
|
||||||
|
* Additional children that are always right-aligned
|
||||||
|
*/
|
||||||
|
right?: React.ReactNode;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<SandyToolbarContainer
|
<SandyToolbarContainer
|
||||||
@@ -39,6 +44,12 @@ export function Toolbar({
|
|||||||
center
|
center
|
||||||
wash={wash}>
|
wash={wash}>
|
||||||
{children}
|
{children}
|
||||||
|
{right ? (
|
||||||
|
<>
|
||||||
|
<div style={{flexGrow: 1}}></div>
|
||||||
|
{right}
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
</SandyToolbarContainer>
|
</SandyToolbarContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -321,7 +321,7 @@ export const DataSourceRenderer: <T extends object, C>(
|
|||||||
}) as any;
|
}) as any;
|
||||||
|
|
||||||
const TableContainer = styled.div({
|
const TableContainer = styled.div({
|
||||||
overflowY: 'scroll',
|
overflowY: 'auto',
|
||||||
overflowX: 'hidden',
|
overflowX: 'hidden',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flex: 1,
|
flex: 1,
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ export const TableRow = memo(function TableRow<T>({
|
|||||||
.filter((col) => col.visible)
|
.filter((col) => col.visible)
|
||||||
.map((col) => {
|
.map((col) => {
|
||||||
const value = col.onRender
|
const value = col.onRender
|
||||||
? (col as any).onRender(record, highlighted, itemIndex) // TODO: ever used?
|
? (col as any).onRender(record, highlighted, itemIndex)
|
||||||
: DataFormatter.format((record as any)[col.key], col.formatters);
|
: DataFormatter.format((record as any)[col.key], col.formatters);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
113
desktop/plugins/public/crash_reporter/Crashes.tsx
Normal file
113
desktop/plugins/public/crash_reporter/Crashes.tsx
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*
|
||||||
|
* @format
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import {Button, Typography} from 'antd';
|
||||||
|
import {CoffeeOutlined, CopyOutlined, DeleteOutlined} from '@ant-design/icons';
|
||||||
|
import {
|
||||||
|
usePlugin,
|
||||||
|
useValue,
|
||||||
|
DataList,
|
||||||
|
Layout,
|
||||||
|
CodeBlock,
|
||||||
|
Toolbar,
|
||||||
|
} from 'flipper-plugin';
|
||||||
|
import {Crash, devicePlugin} from './index';
|
||||||
|
|
||||||
|
const {Text} = Typography;
|
||||||
|
export function Crashes() {
|
||||||
|
const plugin = usePlugin(devicePlugin);
|
||||||
|
const crashes = useValue(plugin.crashes);
|
||||||
|
const selectedCrashId = useValue(plugin.selectedCrash);
|
||||||
|
const selectedCrash = crashes.find(
|
||||||
|
(c) => c.notificationID === selectedCrashId,
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Layout.Left resizable width={400}>
|
||||||
|
<DataList
|
||||||
|
items={crashes.map((crash) => ({
|
||||||
|
id: crash.notificationID,
|
||||||
|
title: crash.reason ?? crash.name,
|
||||||
|
description: `${crash.date.toLocaleString()} - ${crash.name}`,
|
||||||
|
}))}
|
||||||
|
selection={plugin.selectedCrash}
|
||||||
|
onRenderEmpty={null}
|
||||||
|
/>
|
||||||
|
{selectedCrash ? (
|
||||||
|
<CrashDetails crash={selectedCrash} />
|
||||||
|
) : (
|
||||||
|
<Layout.Horizontal center grow>
|
||||||
|
<Layout.Container center grow gap>
|
||||||
|
<CoffeeOutlined />
|
||||||
|
<Text type="secondary">
|
||||||
|
{crashes.length === 0
|
||||||
|
? 'No crashes detected so far!'
|
||||||
|
: 'No crash selected'}
|
||||||
|
</Text>
|
||||||
|
</Layout.Container>
|
||||||
|
</Layout.Horizontal>
|
||||||
|
)}
|
||||||
|
</Layout.Left>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function CrashDetails({crash}: {crash: Crash}) {
|
||||||
|
const plugin = usePlugin(devicePlugin);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Layout.Top>
|
||||||
|
<Toolbar
|
||||||
|
wash
|
||||||
|
right={
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
plugin.clearCrashes();
|
||||||
|
}}
|
||||||
|
title="Clear all crashes"
|
||||||
|
danger>
|
||||||
|
<DeleteOutlined />
|
||||||
|
</Button>
|
||||||
|
}>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
plugin.copyCrashToClipboard(crash.callstack!);
|
||||||
|
}}>
|
||||||
|
<CopyOutlined />
|
||||||
|
</Button>
|
||||||
|
{plugin.isFB ? (
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
plugin.createPaste(crash.callstack!);
|
||||||
|
}}>
|
||||||
|
Create paste
|
||||||
|
</Button>
|
||||||
|
) : null}
|
||||||
|
<Button
|
||||||
|
disabled={!crash.callstack}
|
||||||
|
onClick={() => {
|
||||||
|
plugin.openInLogs(crash.callstack!);
|
||||||
|
}}>
|
||||||
|
Open In Logs
|
||||||
|
</Button>
|
||||||
|
</Toolbar>
|
||||||
|
<Layout.ScrollContainer pad vertical>
|
||||||
|
<CodeBlock>
|
||||||
|
<Text strong>{crash.name}</Text>
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<Text strong>{crash.reason}</Text>
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
{crash.callstack}
|
||||||
|
</CodeBlock>
|
||||||
|
</Layout.ScrollContainer>
|
||||||
|
</Layout.Top>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -12,8 +12,12 @@ import {Crash} from '../index';
|
|||||||
import {TestUtils} from 'flipper-plugin';
|
import {TestUtils} from 'flipper-plugin';
|
||||||
import {getPluginKey} from 'flipper';
|
import {getPluginKey} from 'flipper';
|
||||||
import * as CrashReporterPlugin from '../index';
|
import * as CrashReporterPlugin from '../index';
|
||||||
import {parseCrashLog} from '../crash-utils';
|
import {
|
||||||
import {parsePath, shouldShowiOSCrashNotification} from '../ios-crash-utils';
|
parseIosCrash,
|
||||||
|
parsePath,
|
||||||
|
shouldShowiOSCrashNotification,
|
||||||
|
} from '../ios-crash-utils';
|
||||||
|
import {parseAndroidCrash} from '../android-crash-utils';
|
||||||
|
|
||||||
function getCrash(
|
function getCrash(
|
||||||
id: number,
|
id: number,
|
||||||
@@ -42,7 +46,7 @@ function assertCrash(crash: Crash, expectedCrash: Crash) {
|
|||||||
test('test the parsing of the date and crash info for the log which matches the predefined regex', () => {
|
test('test the parsing of the date and crash info for the log which matches the predefined regex', () => {
|
||||||
const log =
|
const log =
|
||||||
'Blaa Blaaa \n Blaa Blaaa \n Exception Type: SIGSEGV \n Blaa Blaa \n Blaa Blaa Date/Time: 2019-03-21 12:07:00.861 +0000 \n Blaa balaaa';
|
'Blaa Blaaa \n Blaa Blaaa \n Exception Type: SIGSEGV \n Blaa Blaa \n Blaa Blaa Date/Time: 2019-03-21 12:07:00.861 +0000 \n Blaa balaaa';
|
||||||
const crash = parseCrashLog(log, 'iOS', undefined);
|
const crash = parseIosCrash(log);
|
||||||
expect(crash.callstack).toEqual(log);
|
expect(crash.callstack).toEqual(log);
|
||||||
expect(crash.reason).toEqual('SIGSEGV');
|
expect(crash.reason).toEqual('SIGSEGV');
|
||||||
expect(crash.name).toEqual('SIGSEGV');
|
expect(crash.name).toEqual('SIGSEGV');
|
||||||
@@ -52,16 +56,15 @@ test('test the parsing of the date and crash info for the log which matches the
|
|||||||
test('test the parsing of the reason for crash when log matches the crash regex, but there is no mention of date', () => {
|
test('test the parsing of the reason for crash when log matches the crash regex, but there is no mention of date', () => {
|
||||||
const log =
|
const log =
|
||||||
'Blaa Blaaa \n Blaa Blaaa \n Exception Type: SIGSEGV \n Blaa Blaa \n Blaa Blaa';
|
'Blaa Blaaa \n Blaa Blaaa \n Exception Type: SIGSEGV \n Blaa Blaa \n Blaa Blaa';
|
||||||
const crash = parseCrashLog(log, 'iOS', undefined);
|
const crash = parseIosCrash(log);
|
||||||
expect(crash.callstack).toEqual(log);
|
expect(crash.callstack).toEqual(log);
|
||||||
expect(crash.reason).toEqual('SIGSEGV');
|
expect(crash.reason).toEqual('SIGSEGV');
|
||||||
expect(crash.name).toEqual('SIGSEGV');
|
expect(crash.name).toEqual('SIGSEGV');
|
||||||
expect(crash.date).toBeUndefined();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('test the parsing of the crash log when log does not match the predefined regex but is alphanumeric', () => {
|
test('test the parsing of the crash log when log does not match the predefined regex but is alphanumeric', () => {
|
||||||
const log = 'Blaa Blaaa \n Blaa Blaaa \n Blaa Blaaa';
|
const log = 'Blaa Blaaa \n Blaa Blaaa \n Blaa Blaaa';
|
||||||
const crash = parseCrashLog(log, 'iOS', undefined);
|
const crash = parseIosCrash(log);
|
||||||
expect(crash.callstack).toEqual(log);
|
expect(crash.callstack).toEqual(log);
|
||||||
expect(crash.reason).toEqual('Cannot figure out the cause');
|
expect(crash.reason).toEqual('Cannot figure out the cause');
|
||||||
expect(crash.name).toEqual('Cannot figure out the cause');
|
expect(crash.name).toEqual('Cannot figure out the cause');
|
||||||
@@ -70,25 +73,23 @@ test('test the parsing of the crash log when log does not match the predefined r
|
|||||||
test('test the parsing of the reason for crash when log does not match the predefined regex contains unicode character', () => {
|
test('test the parsing of the reason for crash when log does not match the predefined regex contains unicode character', () => {
|
||||||
const log =
|
const log =
|
||||||
'Blaa Blaaa \n Blaa Blaaa \n Exception Type: 🍕🐬 \n Blaa Blaa \n Blaa Blaa';
|
'Blaa Blaaa \n Blaa Blaaa \n Exception Type: 🍕🐬 \n Blaa Blaa \n Blaa Blaa';
|
||||||
const crash = parseCrashLog(log, 'iOS', undefined);
|
const crash = parseIosCrash(log);
|
||||||
expect(crash.callstack).toEqual(log);
|
expect(crash.callstack).toEqual(log);
|
||||||
expect(crash.reason).toEqual('Cannot figure out the cause');
|
expect(crash.reason).toEqual('Cannot figure out the cause');
|
||||||
expect(crash.name).toEqual('Cannot figure out the cause');
|
expect(crash.name).toEqual('Cannot figure out the cause');
|
||||||
expect(crash.date).toBeUndefined();
|
|
||||||
});
|
});
|
||||||
test('test the parsing of the reason for crash when log is empty', () => {
|
test('test the parsing of the reason for crash when log is empty', () => {
|
||||||
const log = '';
|
const log = '';
|
||||||
const crash = parseCrashLog(log, 'iOS', undefined);
|
const crash = parseIosCrash(log);
|
||||||
expect(crash.callstack).toEqual(log);
|
expect(crash.callstack).toEqual(log);
|
||||||
expect(crash.reason).toEqual('Cannot figure out the cause');
|
expect(crash.reason).toEqual('Cannot figure out the cause');
|
||||||
expect(crash.name).toEqual('Cannot figure out the cause');
|
expect(crash.name).toEqual('Cannot figure out the cause');
|
||||||
expect(crash.date).toBeUndefined();
|
|
||||||
});
|
});
|
||||||
test('test the parsing of the Android crash log for the proper android crash format', () => {
|
test('test the parsing of the Android crash log for the proper android crash format', () => {
|
||||||
const log =
|
const log =
|
||||||
'FATAL EXCEPTION: main\nProcess: com.facebook.flipper.sample, PID: 27026\njava.lang.IndexOutOfBoundsException: Index: 190, Size: 0\n\tat java.util.ArrayList.get(ArrayList.java:437)\n\tat com.facebook.flipper.sample.RootComponentSpec.hitGetRequest(RootComponentSpec.java:72)\n\tat com.facebook.flipper.sample.RootComponent.hitGetRequest(RootComponent.java:46)\n';
|
'FATAL EXCEPTION: main\nProcess: com.facebook.flipper.sample, PID: 27026\njava.lang.IndexOutOfBoundsException: Index: 190, Size: 0\n\tat java.util.ArrayList.get(ArrayList.java:437)\n\tat com.facebook.flipper.sample.RootComponentSpec.hitGetRequest(RootComponentSpec.java:72)\n\tat com.facebook.flipper.sample.RootComponent.hitGetRequest(RootComponent.java:46)\n';
|
||||||
const date = new Date();
|
const date = new Date();
|
||||||
const crash = parseCrashLog(log, 'Android', date);
|
const crash = parseAndroidCrash(log, date);
|
||||||
expect(crash.callstack).toEqual(log);
|
expect(crash.callstack).toEqual(log);
|
||||||
expect(crash.reason).toEqual(
|
expect(crash.reason).toEqual(
|
||||||
'java.lang.IndexOutOfBoundsException: Index: 190, Size: 0',
|
'java.lang.IndexOutOfBoundsException: Index: 190, Size: 0',
|
||||||
@@ -98,7 +99,7 @@ test('test the parsing of the Android crash log for the proper android crash for
|
|||||||
});
|
});
|
||||||
test('test the parsing of the Android crash log for the unknown crash format and no date', () => {
|
test('test the parsing of the Android crash log for the unknown crash format and no date', () => {
|
||||||
const log = 'Blaa Blaa Blaa';
|
const log = 'Blaa Blaa Blaa';
|
||||||
const crash = parseCrashLog(log, 'Android', undefined);
|
const crash = parseAndroidCrash(log, undefined);
|
||||||
expect(crash.callstack).toEqual(log);
|
expect(crash.callstack).toEqual(log);
|
||||||
expect(crash.reason).toEqual('Cannot figure out the cause');
|
expect(crash.reason).toEqual('Cannot figure out the cause');
|
||||||
expect(crash.name).toEqual('Cannot figure out the cause');
|
expect(crash.name).toEqual('Cannot figure out the cause');
|
||||||
@@ -106,7 +107,7 @@ test('test the parsing of the Android crash log for the unknown crash format and
|
|||||||
});
|
});
|
||||||
test('test the parsing of the Android crash log for the partial format matching the crash format', () => {
|
test('test the parsing of the Android crash log for the partial format matching the crash format', () => {
|
||||||
const log = 'First Line Break \n Blaa Blaa \n Blaa Blaa ';
|
const log = 'First Line Break \n Blaa Blaa \n Blaa Blaa ';
|
||||||
const crash = parseCrashLog(log, 'Android', undefined);
|
const crash = parseAndroidCrash(log, undefined);
|
||||||
expect(crash.callstack).toEqual(log);
|
expect(crash.callstack).toEqual(log);
|
||||||
expect(crash.reason).toEqual('Cannot figure out the cause');
|
expect(crash.reason).toEqual('Cannot figure out the cause');
|
||||||
expect(crash.name).toEqual('First Line Break ');
|
expect(crash.name).toEqual('First Line Break ');
|
||||||
@@ -114,7 +115,7 @@ test('test the parsing of the Android crash log for the partial format matching
|
|||||||
test('test the parsing of the Android crash log with os being iOS', () => {
|
test('test the parsing of the Android crash log with os being iOS', () => {
|
||||||
const log =
|
const log =
|
||||||
'FATAL EXCEPTION: main\nProcess: com.facebook.flipper.sample, PID: 27026\njava.lang.IndexOutOfBoundsException: Index: 190, Size: 0\n\tat java.util.ArrayList.get(ArrayList.java:437)\n\tat com.facebook.flipper.sample.RootComponentSpec.hitGetRequest(RootComponentSpec.java:72)\n\tat com.facebook.flipper.sample.RootComponent.hitGetRequest(RootComponent.java:46)\n';
|
'FATAL EXCEPTION: main\nProcess: com.facebook.flipper.sample, PID: 27026\njava.lang.IndexOutOfBoundsException: Index: 190, Size: 0\n\tat java.util.ArrayList.get(ArrayList.java:437)\n\tat com.facebook.flipper.sample.RootComponentSpec.hitGetRequest(RootComponentSpec.java:72)\n\tat com.facebook.flipper.sample.RootComponent.hitGetRequest(RootComponent.java:46)\n';
|
||||||
const crash = parseCrashLog(log, 'iOS', undefined);
|
const crash = parseIosCrash(log);
|
||||||
expect(crash.callstack).toEqual(log);
|
expect(crash.callstack).toEqual(log);
|
||||||
expect(crash.reason).toEqual('Cannot figure out the cause');
|
expect(crash.reason).toEqual('Cannot figure out the cause');
|
||||||
expect(crash.name).toEqual('Cannot figure out the cause');
|
expect(crash.name).toEqual('Cannot figure out the cause');
|
||||||
@@ -165,7 +166,7 @@ test('test getNewPersistedStateFromCrashLog for non-empty defaultPersistedState
|
|||||||
const pluginStateCrash = getCrash(0, 'callstack', 'crash1', 'crash1');
|
const pluginStateCrash = getCrash(0, 'callstack', 'crash1', 'crash1');
|
||||||
plugin.instance.reportCrash(pluginStateCrash);
|
plugin.instance.reportCrash(pluginStateCrash);
|
||||||
const content = 'Blaa Blaaa \n Blaa Blaaa';
|
const content = 'Blaa Blaaa \n Blaa Blaaa';
|
||||||
plugin.instance.reportCrash(parseCrashLog(content, 'iOS', undefined));
|
plugin.instance.reportCrash(parseIosCrash(content));
|
||||||
const crashes = plugin.instance.crashes.get();
|
const crashes = plugin.instance.crashes.get();
|
||||||
expect(crashes.length).toEqual(2);
|
expect(crashes.length).toEqual(2);
|
||||||
assertCrash(crashes[0], pluginStateCrash);
|
assertCrash(crashes[0], pluginStateCrash);
|
||||||
@@ -180,18 +181,6 @@ test('test getNewPersistedStateFromCrashLog for non-empty defaultPersistedState
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('test getNewPersistedStateFromCrashLog when os is undefined', () => {
|
|
||||||
const plugin = TestUtils.startDevicePlugin(CrashReporterPlugin);
|
|
||||||
const content = 'Blaa Blaaa \n Blaa Blaaa';
|
|
||||||
expect(() => {
|
|
||||||
plugin.instance.reportCrash(
|
|
||||||
parseCrashLog(content, undefined as any, undefined),
|
|
||||||
);
|
|
||||||
}).toThrowErrorMatchingInlineSnapshot(`"Unsupported OS"`);
|
|
||||||
const crashes = plugin.instance.crashes.get();
|
|
||||||
expect(crashes.length).toEqual(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('test parsing of path when inputs are correct', () => {
|
test('test parsing of path when inputs are correct', () => {
|
||||||
const content =
|
const content =
|
||||||
'Blaa Blaaa \n Blaa Blaaa \n Path: path/to/simulator/TH1S-15DEV1CE-1D/AppName.app/AppName \n Blaa Blaa \n Blaa Blaa';
|
'Blaa Blaaa \n Blaa Blaaa \n Path: path/to/simulator/TH1S-15DEV1CE-1D/AppName.app/AppName \n Blaa Blaa \n Blaa Blaa';
|
||||||
|
|||||||
@@ -7,17 +7,14 @@
|
|||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type {DeviceLogEntry} from 'flipper-plugin';
|
import type {DeviceLogEntry, DevicePluginClient} from 'flipper-plugin';
|
||||||
import type {CrashLog} from './index';
|
import {UNKNOWN_CRASH_REASON} from './crash-utils';
|
||||||
|
import type {Crash, CrashLog} from './index';
|
||||||
|
|
||||||
export function parseAndroidCrash(
|
export function parseAndroidCrash(content: string, logDate?: Date) {
|
||||||
content: string,
|
|
||||||
fallbackReason: string,
|
|
||||||
logDate?: Date,
|
|
||||||
) {
|
|
||||||
const regForName = /.*\n/;
|
const regForName = /.*\n/;
|
||||||
const nameRegArr = regForName.exec(content);
|
const nameRegArr = regForName.exec(content);
|
||||||
let name = nameRegArr ? nameRegArr[0] : fallbackReason;
|
let name = nameRegArr ? nameRegArr[0] : UNKNOWN_CRASH_REASON;
|
||||||
const regForCallStack = /\tat[\w\s\n\.$&+,:;=?@#|'<>.^*()%!-]*$/;
|
const regForCallStack = /\tat[\w\s\n\.$&+,:;=?@#|'<>.^*()%!-]*$/;
|
||||||
const callStackArray = regForCallStack.exec(content);
|
const callStackArray = regForCallStack.exec(content);
|
||||||
const callStack = callStackArray ? callStackArray[0] : '';
|
const callStack = callStackArray ? callStackArray[0] : '';
|
||||||
@@ -29,8 +26,8 @@ export function parseAndroidCrash(
|
|||||||
const reasonText =
|
const reasonText =
|
||||||
remainingString.length > 0
|
remainingString.length > 0
|
||||||
? remainingString.split('\n').pop()
|
? remainingString.split('\n').pop()
|
||||||
: fallbackReason;
|
: UNKNOWN_CRASH_REASON;
|
||||||
const reason = reasonText ? reasonText : fallbackReason;
|
const reason = reasonText ? reasonText : UNKNOWN_CRASH_REASON;
|
||||||
if (name[name.length - 1] === '\n') {
|
if (name[name.length - 1] === '\n') {
|
||||||
name = name.slice(0, -1);
|
name = name.slice(0, -1);
|
||||||
}
|
}
|
||||||
@@ -53,3 +50,34 @@ export function shouldParseAndroidLog(
|
|||||||
entry.type === 'fatal')
|
entry.type === 'fatal')
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function startAndroidCrashWatcher(
|
||||||
|
client: DevicePluginClient,
|
||||||
|
reportCrash: (payload: CrashLog | Crash) => void,
|
||||||
|
) {
|
||||||
|
const referenceDate = new Date();
|
||||||
|
let androidLog: string = '';
|
||||||
|
let androidLogUnderProcess = false;
|
||||||
|
let timer: null | NodeJS.Timeout = null;
|
||||||
|
client.device.onLogEntry((entry: DeviceLogEntry) => {
|
||||||
|
if (shouldParseAndroidLog(entry, referenceDate)) {
|
||||||
|
if (androidLogUnderProcess) {
|
||||||
|
androidLog += '\n' + entry.message;
|
||||||
|
androidLog = androidLog.trim();
|
||||||
|
if (timer) {
|
||||||
|
clearTimeout(timer);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
androidLog = entry.message;
|
||||||
|
androidLogUnderProcess = true;
|
||||||
|
}
|
||||||
|
timer = setTimeout(() => {
|
||||||
|
if (androidLog.length > 0) {
|
||||||
|
reportCrash(parseAndroidCrash(androidLog, entry.date));
|
||||||
|
}
|
||||||
|
androidLogUnderProcess = false;
|
||||||
|
androidLog = '';
|
||||||
|
}, 50);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,32 +8,12 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import unicodeSubstring from 'unicode-substring';
|
import unicodeSubstring from 'unicode-substring';
|
||||||
import type {CrashLog} from './index';
|
import type {Crash} from './index';
|
||||||
import {parseAndroidCrash} from './android-crash-utils';
|
import {DevicePluginClient} from 'flipper-plugin';
|
||||||
import {parseIosCrash} from './ios-crash-utils';
|
|
||||||
|
|
||||||
export const UNKNOWN_CRASH_REASON = 'Cannot figure out the cause';
|
export const UNKNOWN_CRASH_REASON = 'Cannot figure out the cause';
|
||||||
|
|
||||||
export function parseCrashLog(
|
function truncate(baseString: string, numOfChars: number): string {
|
||||||
content: string,
|
|
||||||
os: string,
|
|
||||||
logDate?: Date,
|
|
||||||
): CrashLog {
|
|
||||||
const fallbackReason = UNKNOWN_CRASH_REASON;
|
|
||||||
switch (os) {
|
|
||||||
case 'iOS': {
|
|
||||||
return parseIosCrash(content, fallbackReason, logDate);
|
|
||||||
}
|
|
||||||
case 'Android': {
|
|
||||||
return parseAndroidCrash(content, fallbackReason, logDate);
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
throw new Error('Unsupported OS');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function truncate(baseString: string, numOfChars: number): string {
|
|
||||||
if (baseString.length <= numOfChars) {
|
if (baseString.length <= numOfChars) {
|
||||||
return baseString;
|
return baseString;
|
||||||
}
|
}
|
||||||
@@ -41,8 +21,40 @@ export function truncate(baseString: string, numOfChars: number): string {
|
|||||||
return truncated_string + '\u2026';
|
return truncated_string + '\u2026';
|
||||||
}
|
}
|
||||||
|
|
||||||
export function trimCallStackIfPossible(callstack: string): string {
|
function trimCallStackIfPossible(callstack: string): string {
|
||||||
const regex = /Application Specific Information:/;
|
const regex = /Application Specific Information:/;
|
||||||
const query = regex.exec(callstack);
|
const query = regex.exec(callstack);
|
||||||
return query ? callstack.substring(0, query.index) : callstack;
|
return query ? callstack.substring(0, query.index) : callstack;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function showCrashNotification(
|
||||||
|
client: DevicePluginClient,
|
||||||
|
crash: Crash,
|
||||||
|
) {
|
||||||
|
const ignore = !crash.name && !crash.reason;
|
||||||
|
const unknownCrashCause = crash.reason === UNKNOWN_CRASH_REASON;
|
||||||
|
if (ignore || unknownCrashCause) {
|
||||||
|
console.warn('Ignored the notification for the crash', crash);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let title: string = 'CRASH: ' + truncate(crash.name || crash.reason, 50);
|
||||||
|
title = `${
|
||||||
|
crash.name == crash.reason
|
||||||
|
? title
|
||||||
|
: title + 'Reason: ' + truncate(crash.reason, 50)
|
||||||
|
}`;
|
||||||
|
const callstack = crash.callstack
|
||||||
|
? trimCallStackIfPossible(crash.callstack)
|
||||||
|
: 'No callstack available';
|
||||||
|
const msg = `Callstack: ${truncate(callstack, 200)}`;
|
||||||
|
// TODO: fix client id
|
||||||
|
client.showNotification({
|
||||||
|
id: crash.notificationID,
|
||||||
|
message: msg,
|
||||||
|
severity: 'error',
|
||||||
|
title: title,
|
||||||
|
action: crash.notificationID,
|
||||||
|
category: crash.reason || 'Unknown reason',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,57 +7,11 @@
|
|||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
|
||||||
View,
|
|
||||||
styled,
|
|
||||||
FlexColumn,
|
|
||||||
FlexRow,
|
|
||||||
ContextMenu,
|
|
||||||
clipboard,
|
|
||||||
Button,
|
|
||||||
Text,
|
|
||||||
colors,
|
|
||||||
Toolbar,
|
|
||||||
Spacer,
|
|
||||||
Select,
|
|
||||||
} from 'flipper';
|
|
||||||
import React from 'react';
|
|
||||||
import {
|
|
||||||
createState,
|
|
||||||
DeviceLogEntry,
|
|
||||||
DevicePluginClient,
|
|
||||||
usePlugin,
|
|
||||||
useValue,
|
|
||||||
} from 'flipper-plugin';
|
|
||||||
import type {FSWatcher} from 'fs';
|
import type {FSWatcher} from 'fs';
|
||||||
import {
|
import {createState, DevicePluginClient} from 'flipper-plugin';
|
||||||
parseCrashLog,
|
import {showCrashNotification} from './crash-utils';
|
||||||
trimCallStackIfPossible,
|
|
||||||
truncate,
|
|
||||||
UNKNOWN_CRASH_REASON,
|
|
||||||
} from './crash-utils';
|
|
||||||
import {addFileWatcherForiOSCrashLogs} from './ios-crash-utils';
|
import {addFileWatcherForiOSCrashLogs} from './ios-crash-utils';
|
||||||
import {shouldParseAndroidLog} from './android-crash-utils';
|
import {startAndroidCrashWatcher} from './android-crash-utils';
|
||||||
|
|
||||||
type Maybe<T> = T | null | undefined;
|
|
||||||
|
|
||||||
type HeaderRowProps = {
|
|
||||||
title: string;
|
|
||||||
value: string;
|
|
||||||
};
|
|
||||||
type openLogsCallbackType = () => void;
|
|
||||||
|
|
||||||
type CrashReporterBarProps = {
|
|
||||||
openLogsCallback?: openLogsCallbackType;
|
|
||||||
crashSelector: CrashSelectorProps;
|
|
||||||
};
|
|
||||||
|
|
||||||
type CrashSelectorProps = {
|
|
||||||
crashes?: {[key: string]: string};
|
|
||||||
orderedIDs?: Array<string>;
|
|
||||||
selectedCrashID?: string;
|
|
||||||
onCrashChange: (name: Maybe<string>) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type Crash = {
|
export type Crash = {
|
||||||
notificationID: string;
|
notificationID: string;
|
||||||
@@ -71,235 +25,9 @@ export type CrashLog = {
|
|||||||
callstack: string;
|
callstack: string;
|
||||||
reason: string;
|
reason: string;
|
||||||
name: string;
|
name: string;
|
||||||
date: Maybe<Date>;
|
date?: Date | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Padder = styled.div<{
|
|
||||||
paddingLeft?: number;
|
|
||||||
paddingRight?: number;
|
|
||||||
paddingBottom?: number;
|
|
||||||
paddingTop?: number;
|
|
||||||
}>(({paddingLeft, paddingRight, paddingBottom, paddingTop}) => ({
|
|
||||||
paddingLeft: paddingLeft || 0,
|
|
||||||
paddingRight: paddingRight || 0,
|
|
||||||
paddingBottom: paddingBottom || 0,
|
|
||||||
paddingTop: paddingTop || 0,
|
|
||||||
}));
|
|
||||||
|
|
||||||
const Title = styled(Text)({
|
|
||||||
fontWeight: 'bold',
|
|
||||||
color: colors.greyTint3,
|
|
||||||
height: 'auto',
|
|
||||||
width: 200,
|
|
||||||
textOverflow: 'ellipsis',
|
|
||||||
});
|
|
||||||
|
|
||||||
const Line = styled(View)({
|
|
||||||
backgroundColor: colors.greyTint2,
|
|
||||||
height: 1,
|
|
||||||
width: 'auto',
|
|
||||||
marginTop: 2,
|
|
||||||
flexShrink: 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
const Container = styled(FlexColumn)({
|
|
||||||
overflow: 'auto',
|
|
||||||
flexShrink: 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
const Value = styled(Text)({
|
|
||||||
fontWeight: 'bold',
|
|
||||||
color: colors.greyTint3,
|
|
||||||
height: 'auto',
|
|
||||||
maxHeight: 200,
|
|
||||||
flexGrow: 1,
|
|
||||||
textOverflow: 'ellipsis',
|
|
||||||
whiteSpace: 'normal',
|
|
||||||
wordWrap: 'break-word',
|
|
||||||
lineHeight: 2,
|
|
||||||
marginLeft: 8,
|
|
||||||
marginRight: 8,
|
|
||||||
overflow: 'hidden',
|
|
||||||
});
|
|
||||||
|
|
||||||
const FlexGrowColumn = styled(FlexColumn)({
|
|
||||||
flexGrow: 1,
|
|
||||||
});
|
|
||||||
|
|
||||||
const PluginRootContainer = styled(FlexColumn)({
|
|
||||||
height: '100%',
|
|
||||||
});
|
|
||||||
|
|
||||||
const ScrollableColumn = styled(FlexGrowColumn)({
|
|
||||||
overflow: 'auto',
|
|
||||||
height: 'auto',
|
|
||||||
});
|
|
||||||
|
|
||||||
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,
|
|
||||||
});
|
|
||||||
|
|
||||||
const StackTraceContainer = styled(FlexColumn)({
|
|
||||||
backgroundColor: colors.greyStackTraceTint,
|
|
||||||
flexShrink: 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
class CrashSelector extends React.Component<CrashSelectorProps> {
|
|
||||||
render() {
|
|
||||||
const {crashes, selectedCrashID, orderedIDs, onCrashChange} = this.props;
|
|
||||||
return (
|
|
||||||
<StyledFlexRowColumn>
|
|
||||||
<ButtonGroupContainer>
|
|
||||||
<MatchParentHeightComponent>
|
|
||||||
<Button
|
|
||||||
disabled={Boolean(!orderedIDs || orderedIDs.length <= 1)}
|
|
||||||
compact={true}
|
|
||||||
onClick={() => {
|
|
||||||
if (orderedIDs && selectedCrashID) {
|
|
||||||
const index = orderedIDs.indexOf(selectedCrashID as string);
|
|
||||||
const nextIndex =
|
|
||||||
index < 1 ? orderedIDs.length - 1 : index - 1;
|
|
||||||
const nextID = orderedIDs[nextIndex];
|
|
||||||
onCrashChange(nextID);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
icon="chevron-left"
|
|
||||||
iconSize={12}
|
|
||||||
title="Previous Crash"
|
|
||||||
/>
|
|
||||||
</MatchParentHeightComponent>
|
|
||||||
<MatchParentHeightComponent>
|
|
||||||
<Button
|
|
||||||
disabled={Boolean(!orderedIDs || orderedIDs.length <= 1)}
|
|
||||||
compact={true}
|
|
||||||
onClick={() => {
|
|
||||||
if (orderedIDs && selectedCrashID) {
|
|
||||||
const index = orderedIDs.indexOf(selectedCrashID as string);
|
|
||||||
const nextIndex =
|
|
||||||
index >= orderedIDs.length - 1 ? 0 : index + 1;
|
|
||||||
const nextID = orderedIDs[nextIndex];
|
|
||||||
onCrashChange(nextID);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
icon="chevron-right"
|
|
||||||
iconSize={12}
|
|
||||||
title="Next Crash"
|
|
||||||
/>
|
|
||||||
</MatchParentHeightComponent>
|
|
||||||
</ButtonGroupContainer>
|
|
||||||
<StyledSelectContainer>
|
|
||||||
<StyledSelect
|
|
||||||
grow={true}
|
|
||||||
selected={selectedCrashID || 'NoCrashID'}
|
|
||||||
options={crashes || {NoCrashID: 'No Crash'}}
|
|
||||||
onChangeWithKey={(key: string) => {
|
|
||||||
if (onCrashChange) {
|
|
||||||
onCrashChange(key);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</StyledSelectContainer>
|
|
||||||
</StyledFlexRowColumn>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class CrashReporterBar extends React.Component<CrashReporterBarProps> {
|
|
||||||
render() {
|
|
||||||
const {openLogsCallback, crashSelector} = this.props;
|
|
||||||
return (
|
|
||||||
<Toolbar>
|
|
||||||
<CrashSelector {...crashSelector} />
|
|
||||||
<Spacer />
|
|
||||||
<Button
|
|
||||||
disabled={Boolean(!openLogsCallback)}
|
|
||||||
onClick={openLogsCallback}>
|
|
||||||
Open In Logs
|
|
||||||
</Button>
|
|
||||||
</Toolbar>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class HeaderRow extends React.Component<HeaderRowProps> {
|
|
||||||
render() {
|
|
||||||
const {title, value} = this.props;
|
|
||||||
return (
|
|
||||||
<Padder paddingTop={8} paddingBottom={2} paddingLeft={8}>
|
|
||||||
<Container>
|
|
||||||
<FlexRow>
|
|
||||||
<Title>{title}</Title>
|
|
||||||
<ContextMenu
|
|
||||||
items={[
|
|
||||||
{
|
|
||||||
label: 'copy',
|
|
||||||
click: () => {
|
|
||||||
clipboard.writeText(value);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]}>
|
|
||||||
<Value code={true}>{value}</Value>
|
|
||||||
</ContextMenu>
|
|
||||||
</FlexRow>
|
|
||||||
<Line />
|
|
||||||
</Container>
|
|
||||||
</Padder>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type StackTraceComponentProps = {
|
|
||||||
stacktrace: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
class StackTraceComponent extends React.Component<StackTraceComponentProps> {
|
|
||||||
render() {
|
|
||||||
const {stacktrace} = this.props;
|
|
||||||
return (
|
|
||||||
<StackTraceContainer>
|
|
||||||
<Padder paddingTop={8} paddingBottom={2} paddingLeft={8}>
|
|
||||||
<Value code={true}>{stacktrace}</Value>
|
|
||||||
</Padder>
|
|
||||||
<Line />
|
|
||||||
</StackTraceContainer>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function devicePlugin(client: DevicePluginClient) {
|
export function devicePlugin(client: DevicePluginClient) {
|
||||||
let notificationID = -1;
|
let notificationID = -1;
|
||||||
let watcher: FSWatcher | undefined;
|
let watcher: FSWatcher | undefined;
|
||||||
@@ -326,70 +54,18 @@ export function devicePlugin(client: DevicePluginClient) {
|
|||||||
draft.push(crash);
|
draft.push(crash);
|
||||||
});
|
});
|
||||||
|
|
||||||
// show notification?
|
showCrashNotification(client, crash);
|
||||||
const ignore = !crash.name && !crash.reason;
|
|
||||||
const unknownCrashCause = crash.reason === UNKNOWN_CRASH_REASON;
|
|
||||||
if (ignore || unknownCrashCause) {
|
|
||||||
console.warn('Ignored the notification for the crash', crash);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let title: string = 'CRASH: ' + truncate(crash.name || crash.reason, 50);
|
|
||||||
title = `${
|
|
||||||
crash.name == crash.reason
|
|
||||||
? title
|
|
||||||
: title + 'Reason: ' + truncate(crash.reason, 50)
|
|
||||||
}`;
|
|
||||||
const callstack = crash.callstack
|
|
||||||
? trimCallStackIfPossible(crash.callstack)
|
|
||||||
: 'No callstack available';
|
|
||||||
const msg = `Callstack: ${truncate(callstack, 200)}`;
|
|
||||||
client.showNotification({
|
|
||||||
id: crash.notificationID,
|
|
||||||
message: msg,
|
|
||||||
severity: 'error',
|
|
||||||
title: title,
|
|
||||||
action: crash.notificationID,
|
|
||||||
category: crash.reason || 'Unknown reason',
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Startup logic to establish log monitoring
|
// Startup logic to establish log monitoring
|
||||||
if (client.device.isConnected) {
|
if (client.device.isConnected) {
|
||||||
if (client.device.os.includes('iOS')) {
|
if (client.device.os.includes('iOS')) {
|
||||||
watcher = addFileWatcherForiOSCrashLogs(
|
watcher = addFileWatcherForiOSCrashLogs(
|
||||||
client.device.os,
|
|
||||||
client.device.serial,
|
client.device.serial,
|
||||||
reportCrash,
|
reportCrash,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
const referenceDate = new Date();
|
startAndroidCrashWatcher(client, reportCrash);
|
||||||
let androidLog: string = '';
|
|
||||||
let androidLogUnderProcess = false;
|
|
||||||
let timer: Maybe<NodeJS.Timeout> = null;
|
|
||||||
client.device.onLogEntry((entry: DeviceLogEntry) => {
|
|
||||||
if (shouldParseAndroidLog(entry, referenceDate)) {
|
|
||||||
if (androidLogUnderProcess) {
|
|
||||||
androidLog += '\n' + entry.message;
|
|
||||||
androidLog = androidLog.trim();
|
|
||||||
if (timer) {
|
|
||||||
clearTimeout(timer);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
androidLog = entry.message;
|
|
||||||
androidLogUnderProcess = true;
|
|
||||||
}
|
|
||||||
timer = setTimeout(() => {
|
|
||||||
if (androidLog.length > 0) {
|
|
||||||
reportCrash(
|
|
||||||
parseCrashLog(androidLog, client.device.os, entry.date),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
androidLogUnderProcess = false;
|
|
||||||
androidLog = '';
|
|
||||||
}, 50);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -408,106 +84,15 @@ export function devicePlugin(client: DevicePluginClient) {
|
|||||||
copyCrashToClipboard(callstack: string) {
|
copyCrashToClipboard(callstack: string) {
|
||||||
client.writeTextToClipboard(callstack);
|
client.writeTextToClipboard(callstack);
|
||||||
},
|
},
|
||||||
};
|
createPaste(callstack: string) {
|
||||||
}
|
client.createPaste(callstack);
|
||||||
|
|
||||||
export function Component() {
|
|
||||||
const plugin = usePlugin(devicePlugin);
|
|
||||||
const selectedCrash = useValue(plugin.selectedCrash);
|
|
||||||
const crashes = useValue(plugin.crashes);
|
|
||||||
const crash =
|
|
||||||
crashes.find((c) => c.notificationID === selectedCrash) ??
|
|
||||||
crashes[crashes.length - 1] ??
|
|
||||||
undefined;
|
|
||||||
|
|
||||||
if (crash) {
|
|
||||||
const crashMap = crashes.reduce(
|
|
||||||
(acc: {[key: string]: string}, persistedCrash: Crash) => {
|
|
||||||
const {notificationID, date} = persistedCrash;
|
|
||||||
const name = 'Crash at ' + date.toLocaleString();
|
|
||||||
acc[notificationID] = name;
|
|
||||||
return acc;
|
|
||||||
},
|
},
|
||||||
{},
|
isFB: client.isFB,
|
||||||
);
|
clearCrashes() {
|
||||||
|
crashes.set([]);
|
||||||
const orderedIDs = crashes.map(
|
selectedCrash.set(undefined);
|
||||||
(persistedCrash) => persistedCrash.notificationID,
|
|
||||||
);
|
|
||||||
const selectedCrashID = crash.notificationID;
|
|
||||||
const onCrashChange = (id: Maybe<string>) => {
|
|
||||||
if (id) {
|
|
||||||
plugin.selectedCrash.set(id);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const callstackString = crash.callstack || '';
|
|
||||||
const children = callstackString.split('\n').map((str) => {
|
|
||||||
return {message: str};
|
|
||||||
});
|
|
||||||
const crashSelector: CrashSelectorProps = {
|
|
||||||
crashes: crashMap,
|
|
||||||
orderedIDs,
|
|
||||||
selectedCrashID,
|
|
||||||
onCrashChange,
|
|
||||||
};
|
|
||||||
const showReason = crash.reason !== UNKNOWN_CRASH_REASON;
|
|
||||||
return (
|
|
||||||
<PluginRootContainer>
|
|
||||||
{plugin.os == 'Android' ? (
|
|
||||||
<CrashReporterBar
|
|
||||||
crashSelector={crashSelector}
|
|
||||||
openLogsCallback={() => {
|
|
||||||
if (crash.callstack) {
|
|
||||||
plugin.openInLogs(crash.callstack);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<CrashReporterBar crashSelector={crashSelector} />
|
|
||||||
)}
|
|
||||||
<ScrollableColumn>
|
|
||||||
<HeaderRow title="Name" value={crash.name} />
|
|
||||||
{showReason ? (
|
|
||||||
<HeaderRow title="Reason" value={crash.reason} />
|
|
||||||
) : null}
|
|
||||||
<Padder paddingLeft={8} paddingTop={4} paddingBottom={2}>
|
|
||||||
<Title> Stacktrace </Title>
|
|
||||||
</Padder>
|
|
||||||
<ContextMenu
|
|
||||||
items={[
|
|
||||||
{
|
|
||||||
label: 'copy',
|
|
||||||
click: () => {
|
|
||||||
plugin.copyCrashToClipboard(callstackString);
|
|
||||||
},
|
},
|
||||||
},
|
|
||||||
]}>
|
|
||||||
<Line />
|
|
||||||
{children.map((child, index) => {
|
|
||||||
return (
|
|
||||||
<StackTraceComponent key={index} stacktrace={child.message} />
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</ContextMenu>
|
|
||||||
</ScrollableColumn>
|
|
||||||
</PluginRootContainer>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const crashSelector = {
|
|
||||||
crashes: undefined,
|
|
||||||
orderedIDs: undefined,
|
|
||||||
selectedCrashID: undefined,
|
|
||||||
onCrashChange: () => void {},
|
|
||||||
};
|
};
|
||||||
return (
|
|
||||||
<StyledFlexGrowColumn>
|
|
||||||
<CrashReporterBar crashSelector={crashSelector} />
|
|
||||||
<StyledFlexColumn>
|
|
||||||
<Padder paddingBottom={8}>
|
|
||||||
<Title>No Crashes Logged</Title>
|
|
||||||
</Padder>
|
|
||||||
</StyledFlexColumn>
|
|
||||||
</StyledFlexGrowColumn>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export {Crashes as Component} from './Crashes';
|
||||||
|
|||||||
@@ -8,26 +8,20 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import type {Crash, CrashLog} from './index';
|
import type {Crash, CrashLog} from './index';
|
||||||
import {parseCrashLog} from './crash-utils';
|
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import {promisify} from 'util';
|
import {promisify} from 'util';
|
||||||
|
import {UNKNOWN_CRASH_REASON} from './crash-utils';
|
||||||
|
|
||||||
export function parseIosCrash(
|
export function parseIosCrash(content: string) {
|
||||||
content: string,
|
|
||||||
fallbackReason: string,
|
|
||||||
logDate?: Date,
|
|
||||||
) {
|
|
||||||
const regex = /Exception Type: *\w*/;
|
const regex = /Exception Type: *\w*/;
|
||||||
const arr = regex.exec(content);
|
const arr = regex.exec(content);
|
||||||
const exceptionString = arr ? arr[0] : '';
|
const exceptionString = arr ? arr[0] : '';
|
||||||
const exceptionRegex = /\w*$/;
|
const exceptionRegex = /\w*$/;
|
||||||
const tmp = exceptionRegex.exec(exceptionString);
|
const tmp = exceptionRegex.exec(exceptionString);
|
||||||
const exception = tmp && tmp[0].length ? tmp[0] : fallbackReason;
|
const exception = tmp && tmp[0].length ? tmp[0] : UNKNOWN_CRASH_REASON;
|
||||||
|
|
||||||
let date = logDate;
|
|
||||||
if (!date) {
|
|
||||||
const dateRegex = /Date\/Time: *[\w\s\.:-]*/;
|
const dateRegex = /Date\/Time: *[\w\s\.:-]*/;
|
||||||
const dateArr = dateRegex.exec(content);
|
const dateArr = dateRegex.exec(content);
|
||||||
const dateString = dateArr ? dateArr[0] : '';
|
const dateString = dateArr ? dateArr[0] : '';
|
||||||
@@ -35,8 +29,7 @@ export function parseIosCrash(
|
|||||||
const tmp1 = dateRegex2.exec(dateString);
|
const tmp1 = dateRegex2.exec(dateString);
|
||||||
const extractedDateString: string | null =
|
const extractedDateString: string | null =
|
||||||
tmp1 && tmp1[0].length ? tmp1[0] : null;
|
tmp1 && tmp1[0].length ? tmp1[0] : null;
|
||||||
date = extractedDateString ? new Date(extractedDateString) : logDate;
|
const date = extractedDateString ? new Date(extractedDateString) : new Date();
|
||||||
}
|
|
||||||
|
|
||||||
const crash: CrashLog = {
|
const crash: CrashLog = {
|
||||||
callstack: content,
|
callstack: content,
|
||||||
@@ -70,7 +63,6 @@ export function parsePath(content: string): string | null {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function addFileWatcherForiOSCrashLogs(
|
export function addFileWatcherForiOSCrashLogs(
|
||||||
deviceOs: string,
|
|
||||||
serial: string,
|
serial: string,
|
||||||
reportCrash: (payload: CrashLog | Crash) => void,
|
reportCrash: (payload: CrashLog | Crash) => void,
|
||||||
) {
|
) {
|
||||||
@@ -96,7 +88,7 @@ export function addFileWatcherForiOSCrashLogs(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (shouldShowiOSCrashNotification(serial, data)) {
|
if (shouldShowiOSCrashNotification(serial, data)) {
|
||||||
reportCrash(parseCrashLog(data, deviceOs, undefined));
|
reportCrash(parseIosCrash(data));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -35,6 +35,8 @@
|
|||||||
"unicode-substring": "^1.0.0"
|
"unicode-substring": "^1.0.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"flipper-plugin": "0.0.0"
|
"@ant-design/icons": "*",
|
||||||
|
"ant-design": "*",
|
||||||
|
"flipper-plugin": "*"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user