Skip to content

Commit 429b542

Browse files
author
Brian Vaughn
committed
DevTools error boundary: Search for pre-existing GH issues
Previously the error boundary UI in DevTools showed a link to report the error on GitHub. This was helpful but resulted in a lot of duplicate issues. This commit adds an intermediate step of searching GitHub issues (using the public API) to find a pre-existing match and linking to it instead if one is found. Hopefully this will encourage people to add info to existing issues rather than report duplicates.
1 parent 96d00b9 commit 429b542

13 files changed

+648
-172
lines changed

packages/react-devtools-shared/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
},
99
"dependencies": {
1010
"@babel/runtime": "^7.11.2",
11+
"@octokit/rest": "^18.5.2",
1112
"@reach/menu-button": "^0.1.17",
1213
"@reach/tooltip": "^0.2.2",
1314
"clipboard-js": "^0.3.6",

packages/react-devtools-shared/src/devtools/views/ErrorBoundary.js

-154
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow
8+
*/
9+
10+
import * as React from 'react';
11+
import {Component, Suspense} from 'react';
12+
import ErrorView from './ErrorView';
13+
import SearchingGitHubIssues from './SearchingGitHubIssues';
14+
import SuspendingErrorView from './SuspendingErrorView';
15+
16+
type Props = {|
17+
children: React$Node,
18+
|};
19+
20+
type State = {|
21+
callStack: string | null,
22+
componentStack: string | null,
23+
errorMessage: string | null,
24+
hasError: boolean,
25+
|};
26+
27+
const InitialState: State = {
28+
callStack: null,
29+
componentStack: null,
30+
errorMessage: null,
31+
hasError: false,
32+
};
33+
34+
export default class ErrorBoundary extends Component<Props, State> {
35+
state: State = InitialState;
36+
37+
static getDerivedStateFromError(error: any) {
38+
const errorMessage =
39+
typeof error === 'object' &&
40+
error !== null &&
41+
error.hasOwnProperty('message')
42+
? error.message
43+
: error;
44+
45+
return {
46+
errorMessage,
47+
hasError: true,
48+
};
49+
}
50+
51+
componentDidCatch(error: any, {componentStack}: any) {
52+
const callStack =
53+
typeof error === 'object' &&
54+
error !== null &&
55+
error.hasOwnProperty('stack')
56+
? error.stack
57+
.split('\n')
58+
.slice(1)
59+
.join('\n')
60+
: null;
61+
62+
this.setState({
63+
callStack,
64+
componentStack,
65+
});
66+
}
67+
68+
render() {
69+
const {children} = this.props;
70+
const {callStack, componentStack, errorMessage, hasError} = this.state;
71+
72+
if (hasError) {
73+
return (
74+
<ErrorView
75+
callStack={callStack}
76+
componentStack={componentStack}
77+
errorMessage={errorMessage}>
78+
<Suspense fallback={<SearchingGitHubIssues />}>
79+
<SuspendingErrorView
80+
callStack={callStack}
81+
componentStack={componentStack}
82+
errorMessage={errorMessage}
83+
/>
84+
</Suspense>
85+
</ErrorView>
86+
);
87+
}
88+
89+
return children;
90+
}
91+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow
8+
*/
9+
10+
import * as React from 'react';
11+
import styles from './shared.css';
12+
13+
type Props = {|
14+
callStack: string | null,
15+
children: React$Node,
16+
componentStack: string | null,
17+
errorMessage: string | null,
18+
|};
19+
20+
export default function ErrorView({
21+
callStack,
22+
children,
23+
componentStack,
24+
errorMessage,
25+
}: Props) {
26+
return (
27+
<div className={styles.ErrorBoundary}>
28+
{children}
29+
<div className={styles.ErrorInfo}>
30+
<div className={styles.Header}>
31+
Uncaught Error: {errorMessage || ''}
32+
</div>
33+
{!!callStack && (
34+
<div className={styles.Stack}>
35+
The error was thrown {callStack.trim()}
36+
</div>
37+
)}
38+
{!!componentStack && (
39+
<div className={styles.Stack}>
40+
The error occurred {componentStack.trim()}
41+
</div>
42+
)}
43+
</div>
44+
</div>
45+
);
46+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow
8+
*/
9+
10+
import * as React from 'react';
11+
import Icon from '../Icon';
12+
import styles from './shared.css';
13+
14+
type Props = {|
15+
callStack: string | null,
16+
componentStack: string | null,
17+
errorMessage: string | null,
18+
|};
19+
20+
export default function ReportNewIssue({
21+
callStack,
22+
componentStack,
23+
errorMessage,
24+
}: Props) {
25+
let bugURL = process.env.GITHUB_URL;
26+
if (!bugURL) {
27+
return null;
28+
}
29+
30+
const title = `Error: "${errorMessage || ''}"`;
31+
const label = 'Component: Developer Tools';
32+
33+
let body = 'Describe what you were doing when the bug occurred:';
34+
body += '\n1. ';
35+
body += '\n2. ';
36+
body += '\n3. ';
37+
body += '\n\n---------------------------------------------';
38+
body += '\nPlease do not remove the text below this line';
39+
body += '\n---------------------------------------------';
40+
body += `\n\nDevTools version: ${process.env.DEVTOOLS_VERSION || ''}`;
41+
if (callStack) {
42+
body += `\n\nCall stack: ${callStack.trim()}`;
43+
}
44+
if (componentStack) {
45+
body += `\n\nComponent stack: ${componentStack.trim()}`;
46+
}
47+
48+
bugURL += `/issues/new?labels=${encodeURI(label)}&title=${encodeURI(
49+
title,
50+
)}&body=${encodeURI(body)}`;
51+
52+
return (
53+
<div className={styles.GitHubLinkRow}>
54+
<Icon className={styles.ReportIcon} type="bug" />
55+
<a
56+
className={styles.ReportLink}
57+
href={bugURL}
58+
rel="noopener noreferrer"
59+
target="_blank"
60+
title="Report bug">
61+
Report this issue
62+
</a>
63+
<div className={styles.ReproSteps}>
64+
(Please include steps on how to reproduce it.)
65+
</div>
66+
</div>
67+
);
68+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow
8+
*/
9+
10+
import * as React from 'react';
11+
import LoadingAnimation from 'react-devtools-shared/src/devtools/views/Components/LoadingAnimation';
12+
import styles from './shared.css';
13+
14+
export default function SearchingGitHubIssues() {
15+
return (
16+
<div className={styles.GitHubLinkRow}>
17+
<LoadingAnimation className={styles.LoadingIcon} />
18+
Searching GitHub for reports of this error...
19+
</div>
20+
);
21+
}

0 commit comments

Comments
 (0)