Skip to content

Commit

Permalink
DevTools error boundary: Search for pre-existing GH issues
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Brian Vaughn committed Apr 15, 2021
1 parent 96d00b9 commit b9b6de4
Show file tree
Hide file tree
Showing 13 changed files with 651 additions and 172 deletions.
1 change: 1 addition & 0 deletions packages/react-devtools-shared/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
},
"dependencies": {
"@babel/runtime": "^7.11.2",
"@octokit/rest": "^18.5.2",
"@reach/menu-button": "^0.1.17",
"@reach/tooltip": "^0.2.2",
"clipboard-js": "^0.3.6",
Expand Down
154 changes: 0 additions & 154 deletions packages/react-devtools-shared/src/devtools/views/ErrorBoundary.js

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/**
* 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.
*
* @flow
*/

import * as React from 'react';
import {Component, Suspense} from 'react';
import ErrorView from './ErrorView';
import SearchingGitHubIssues from './SearchingGitHubIssues';
import SuspendingErrorView from './SuspendingErrorView';

type Props = {|
children: React$Node,
|};

type State = {|
callStack: string | null,
componentStack: string | null,
errorMessage: string | null,
hasError: boolean,
|};

const InitialState: State = {
callStack: null,
componentStack: null,
errorMessage: null,
hasError: false,
};

export default class ErrorBoundary extends Component<Props, State> {
state: State = InitialState;

static getDerivedStateFromError(error: any) {
const errorMessage =
typeof error === 'object' &&
error !== null &&
error.hasOwnProperty('message')
? error.message
: '' + error;

return {
errorMessage,
hasError: true,
};
}

componentDidCatch(error: any, {componentStack}: any) {
const callStack =
typeof error === 'object' &&
error !== null &&
error.hasOwnProperty('stack')
? error.stack
.split('\n')
.slice(1)
.join('\n')
: null;

this.setState({
callStack,
componentStack,
});
}

render() {
const {children} = this.props;
const {callStack, componentStack, errorMessage, hasError} = this.state;

if (hasError) {
return (
<ErrorView
callStack={callStack}
componentStack={componentStack}
errorMessage={errorMessage}>
<Suspense fallback={<SearchingGitHubIssues />}>
<SuspendingErrorView
callStack={callStack}
componentStack={componentStack}
errorMessage={errorMessage}
/>
</Suspense>
</ErrorView>
);
}

return children;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/**
* 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.
*
* @flow
*/

import * as React from 'react';
import styles from './shared.css';

type Props = {|
callStack: string | null,
children: React$Node,
componentStack: string | null,
errorMessage: string | null,
|};

export default function ErrorView({
callStack,
children,
componentStack,
errorMessage,
}: Props) {
return (
<div className={styles.ErrorBoundary}>
{children}
<div className={styles.ErrorInfo}>
<div className={styles.Header}>
Uncaught Error: {errorMessage || ''}
</div>
{!!callStack && (
<div className={styles.Stack}>
The error was thrown {callStack.trim()}
</div>
)}
{!!componentStack && (
<div className={styles.Stack}>
The error occurred {componentStack.trim()}
</div>
)}
</div>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/**
* 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.
*
* @flow
*/

import * as React from 'react';
import Icon from '../Icon';
import styles from './shared.css';

type Props = {|
callStack: string | null,
componentStack: string | null,
errorMessage: string | null,
|};

export default function ReportNewIssue({
callStack,
componentStack,
errorMessage,
}: Props) {
let bugURL = process.env.GITHUB_URL;
if (!bugURL) {
return null;
}

const title = `Error: "${errorMessage || ''}"`;
const label = 'Component: Developer Tools';

let body = 'Describe what you were doing when the bug occurred:';
body += '\n1. ';
body += '\n2. ';
body += '\n3. ';
body += '\n\n---------------------------------------------';
body += '\nPlease do not remove the text below this line';
body += '\n---------------------------------------------';
body += `\n\nDevTools version: ${process.env.DEVTOOLS_VERSION || ''}`;
if (callStack) {
body += `\n\nCall stack: ${callStack.trim()}`;
}
if (componentStack) {
body += `\n\nComponent stack: ${componentStack.trim()}`;
}

bugURL += `/issues/new?labels=${encodeURI(label)}&title=${encodeURI(
title,
)}&body=${encodeURI(body)}`;

return (
<div className={styles.GitHubLinkRow}>
<Icon className={styles.ReportIcon} type="bug" />
<a
className={styles.ReportLink}
href={bugURL}
rel="noopener noreferrer"
target="_blank"
title="Report bug">
Report this issue
</a>
<div className={styles.ReproSteps}>
(Please include steps on how to reproduce it and the components used.)
</div>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
* 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.
*
* @flow
*/

import * as React from 'react';
import LoadingAnimation from 'react-devtools-shared/src/devtools/views/Components/LoadingAnimation';
import styles from './shared.css';

export default function SearchingGitHubIssues() {
return (
<div className={styles.GitHubLinkRow}>
<LoadingAnimation className={styles.LoadingIcon} />
Searching GitHub for reports of this error...
</div>
);
}
Loading

0 comments on commit b9b6de4

Please sign in to comment.