DevTools error boundary: Search for pre-existing GH issues (#21279)
Brian Vaughn committed
Apr 15, 2021 at 13:34 UTC
f3337aa544998be249c63ad3accc5008dbb938ed
20 files changed
+764
-209
packages/react-devtools-shared/package.json
+1
@@ -8,6 +8,7 @@
8
},
9
"dependencies": {
10
"@babel/runtime": "^7.11.2",
11
+ "@octokit/rest": "^18.5.2",
12
"@reach/menu-button": "^0.1.17",
13
"@reach/tooltip": "^0.2.2",
14
"clipboard-js": "^0.3.6",
packages/react-devtools-shared/src/config/DevToolsFeatureFlags.default.js
+1
@@ -14,3 +14,4 @@
14
************************************************************************/
15
16
export const enableProfilerChangedHookIndices = false;
17
+export const isInternalFacebookBuild = false;
packages/react-devtools-shared/src/config/DevToolsFeatureFlags.extension-fb.js
+1
@@ -14,6 +14,7 @@
14
************************************************************************/
15
16
export const enableProfilerChangedHookIndices = true;
17
+export const isInternalFacebookBuild = true;
18
19
/************************************************************************
20
* Do not edit the code below.
packages/react-devtools-shared/src/config/DevToolsFeatureFlags.extension-oss.js
+1
@@ -14,6 +14,7 @@
14
************************************************************************/
15
16
export const enableProfilerChangedHookIndices = false;
17
+export const isInternalFacebookBuild = false;
18
19
/************************************************************************
20
* Do not edit the code below.
packages/react-devtools-shared/src/constants.js
+3
@@ -49,6 +49,9 @@ export const CHANGE_LOG_URL =
49
export const UNSUPPORTED_VERSION_URL =
50
'https://reactjs.org/blog/2019/08/15/new-react-devtools.html#how-do-i-get-the-old-version-back';
51
52
+export const REACT_DEVTOOLS_WORKPLACE_URL =
53
+ 'https://fburl.com/react-devtools-workplace-group';
54
+
55
// HACK
56
//
57
// Extracting during build time avoids a temporarily invalid state for the inline target.
packages/react-devtools-shared/src/devtools/views/ErrorBoundary.css
deleted
-54
@@ -1,54 +0,0 @@
1
-.ErrorBoundary {
2
- height: 100%;
3
- width: 100%;
4
- background-color: var(--color-background);
5
- padding: 0.5rem;
6
- overflow: auto;
7
-}
8
-
9
-.Header {
10
- font-size: var(--font-size-sans-large);
11
- font-weight: bold;
12
- color: var(--color-error-text);
13
-}
14
-
15
-.Stack {
16
- margin-top: 0.5rem;
17
- white-space: pre-wrap;
18
- font-family: var(--font-family-monospace);
19
- font-size: var(--font-size-monospace-normal);
20
- -webkit-font-smoothing: initial;
21
- background-color: var(--color-error-background);
22
- border: 1px solid var(--color-error-border);
23
- color: var(--color-error-text);
24
- border-radius: 0.25rem;
25
- padding: 0.5rem;
26
-}
27
-
28
-.IconAndLinkRow {
29
- display: flex;
30
- align-items: center;
31
- margin-top: 0.5rem;
32
- color: var(--color-text);
33
-}
34
-
35
-.RetryIcon {
36
- margin-right: 0.25rem;
37
- color: var(--color-button-active);
38
-}
39
-
40
-.RetryButton {
41
- margin-right: 0.25rem;
42
- color: var(--color-text);
43
-}
44
-.RetryButton:hover {
45
- color: var(--color-button-hover);
46
-}
47
-
48
-.ReportIcon {
49
- margin-right: 0.25rem;
50
-}
51
-
52
-.ReportLink {
53
- color: var(--color-link);
54
-}
\ No newline at end of file
packages/react-devtools-shared/src/devtools/views/ErrorBoundary.js
deleted
-154
@@ -1,154 +0,0 @@
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} from 'react';
12
-import Button from './Button';
13
-import ButtonIcon from './ButtonIcon';
14
-import Icon from './Icon';
15
-import styles from './ErrorBoundary.css';
16
-import Store from 'react-devtools-shared/src/devtools/store';
17
-
18
-type Props = {|
19
- children: React$Node,
20
- onRetry?: (store: Store) => void,
21
- store: Store,
22
-|};
23
-
24
-type State = {|
25
- callStack: string | null,
26
- componentStack: string | null,
27
- errorMessage: string | null,
28
- hasError: boolean,
29
-|};
30
-
31
-const InitialState: State = {
32
- callStack: null,
33
- componentStack: null,
34
- errorMessage: null,
35
- hasError: false,
36
-};
37
-
38
-export default class ErrorBoundary extends Component<Props, State> {
39
- state: State = InitialState;
40
-
41
- static getDerivedStateFromError(error: any) {
42
- const errorMessage =
43
- typeof error === 'object' &&
44
- error !== null &&
45
- error.hasOwnProperty('message')
46
- ? error.message
47
- : error;
48
-
49
- return {
50
- errorMessage,
51
- hasError: true,
52
- };
53
- }
54
-
55
- componentDidCatch(error: any, {componentStack}: any) {
56
- const callStack =
57
- typeof error === 'object' &&
58
- error !== null &&
59
- error.hasOwnProperty('stack')
60
- ? error.stack
61
- .split('\n')
62
- .slice(1)
63
- .join('\n')
64
- : null;
65
-
66
- this.setState({
67
- callStack,
68
- componentStack,
69
- });
70
- }
71
-
72
- render() {
73
- const {children} = this.props;
74
- const {callStack, componentStack, errorMessage, hasError} = this.state;
75
-
76
- let bugURL = process.env.GITHUB_URL;
77
- if (bugURL) {
78
- const title = `Error: "${errorMessage || ''}"`;
79
- const label = 'Component: Developer Tools';
80
-
81
- let body = 'Describe what you were doing when the bug occurred:';
82
- body += '\n1. ';
83
- body += '\n2. ';
84
- body += '\n3. ';
85
- body += '\n\n---------------------------------------------';
86
- body += '\nPlease do not remove the text below this line';
87
- body += '\n---------------------------------------------';
88
- body += `\n\nDevTools version: ${process.env.DEVTOOLS_VERSION || ''}`;
89
- if (callStack) {
90
- body += `\n\nCall stack: ${callStack.trim()}`;
91
- }
92
- if (componentStack) {
93
- body += `\n\nComponent stack: ${componentStack.trim()}`;
94
- }
95
-
96
- bugURL += `/issues/new?labels=${encodeURI(label)}&title=${encodeURI(
97
- title,
98
- )}&body=${encodeURI(body)}`;
99
- }
100
-
101
- if (hasError) {
102
- return (
103
- <div className={styles.ErrorBoundary}>
104
- <div className={styles.Header}>
105
- Uncaught Error: {errorMessage || ''}
106
- </div>
107
- <div className={styles.IconAndLinkRow}>
108
- <Button
109
- className={styles.RetryButton}
110
- title="Retry"
111
- onClick={this.handleRetry}>
112
- <ButtonIcon className={styles.RetryIcon} type="reload" />
113
- Retry
114
- </Button>
115
- {bugURL && (
116
- <>
117
- <Icon className={styles.ReportIcon} type="bug" />
118
- <a
119
- className={styles.ReportLink}
120
- href={bugURL}
121
- rel="noopener noreferrer"
122
- target="_blank"
123
- title="Report bug">
124
- Report this issue
125
- </a>
126
- </>
127
- )}
128
- </div>
129
- {!!callStack && (
130
- <div className={styles.Stack}>
131
- The error was thrown {callStack.trim()}
132
- </div>
133
- )}
134
- {!!componentStack && (
135
- <div className={styles.Stack}>
136
- The error occurred {componentStack.trim()}
137
- </div>
138
- )}
139
- </div>
140
- );
141
- }
142
-
143
- return children;
144
- }
145
-
146
- handleRetry = () => {
147
- const {onRetry, store} = this.props;
148
- if (typeof onRetry === 'function') {
149
- onRetry(store);
150
- }
151
-
152
- this.setState(InitialState);
153
- };
154
-}
packages/react-devtools-shared/src/devtools/views/ErrorBoundary/ErrorBoundary.js
new
+91
@@ -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
+}
packages/react-devtools-shared/src/devtools/views/ErrorBoundary/ErrorView.js
new
+46
@@ -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
+}
packages/react-devtools-shared/src/devtools/views/ErrorBoundary/ReportNewIssue.js
new
+68
@@ -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 and the components used.)
65
+ </div>
66
+ </div>
67
+ );
68
+}
packages/react-devtools-shared/src/devtools/views/ErrorBoundary/SearchingGitHubIssues.js
new
+21
@@ -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
+}
packages/react-devtools-shared/src/devtools/views/ErrorBoundary/SuspendingErrorView.js
new
+49
@@ -0,0 +1,49 @@
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 {findGitHubIssue} from './cache';
12
+import UpdateExistingIssue from './UpdateExistingIssue';
13
+import ReportNewIssue from './ReportNewIssue';
14
+import WorkplaceGroup from './WorkplaceGroup';
15
+
16
+type Props = {|
17
+ callStack: string | null,
18
+ componentStack: string | null,
19
+ errorMessage: string | null,
20
+|};
21
+
22
+export default function SuspendingErrorView({
23
+ callStack,
24
+ componentStack,
25
+ errorMessage,
26
+}: Props) {
27
+ const maybeItem =
28
+ errorMessage !== null ? findGitHubIssue(errorMessage) : null;
29
+
30
+ let GitHubUI;
31
+ if (maybeItem != null) {
32
+ GitHubUI = <UpdateExistingIssue gitHubIssue={maybeItem} />;
33
+ } else {
34
+ GitHubUI = (
35
+ <ReportNewIssue
36
+ callStack={callStack}
37
+ componentStack={componentStack}
38
+ errorMessage={errorMessage}
39
+ />
40
+ );
41
+ }
42
+
43
+ return (
44
+ <>
45
+ {GitHubUI}
46
+ <WorkplaceGroup />
47
+ </>
48
+ );
49
+}
packages/react-devtools-shared/src/devtools/views/ErrorBoundary/UpdateExistingIssue.js
new
+38
@@ -0,0 +1,38 @@
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 type {GitHubIssue} from './githubAPI';
11
+
12
+import * as React from 'react';
13
+import Icon from '../Icon';
14
+import styles from './shared.css';
15
+
16
+export default function UpdateExistingIssue({
17
+ gitHubIssue,
18
+}: {|
19
+ gitHubIssue: GitHubIssue,
20
+|}) {
21
+ const {title, url} = gitHubIssue;
22
+ return (
23
+ <div className={styles.GitHubLinkRow}>
24
+ <Icon className={styles.ReportIcon} type="bug" />
25
+ <div className={styles.UpdateExistingIssuePrompt}>
26
+ Update existing issue:
27
+ </div>
28
+ <a
29
+ className={styles.ReportLink}
30
+ href={url}
31
+ rel="noopener noreferrer"
32
+ target="_blank"
33
+ title="Report bug">
34
+ {title}
35
+ </a>
36
+ </div>
37
+ );
38
+}
packages/react-devtools-shared/src/devtools/views/ErrorBoundary/WorkplaceGroup.js
new
+35
@@ -0,0 +1,35 @@
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 {isInternalFacebookBuild} from 'react-devtools-feature-flags';
12
+import {REACT_DEVTOOLS_WORKPLACE_URL} from 'react-devtools-shared/src/constants';
13
+import Icon from '../Icon';
14
+import styles from './shared.css';
15
+
16
+export default function WorkplaceGroup() {
17
+ if (!isInternalFacebookBuild) {
18
+ return null;
19
+ }
20
+
21
+ return (
22
+ <div className={styles.WorkplaceGroupRow}>
23
+ <Icon className={styles.ReportIcon} type="facebook" />
24
+ <a
25
+ className={styles.ReportLink}
26
+ href={REACT_DEVTOOLS_WORKPLACE_URL}
27
+ rel="noopener noreferrer"
28
+ target="_blank"
29
+ title="Report bug">
30
+ Report this on Workplace
31
+ </a>
32
+ <div className={styles.FacebookOnly}>(Facebook employees only.)</div>
33
+ </div>
34
+ );
35
+}
packages/react-devtools-shared/src/devtools/views/ErrorBoundary/cache.js
new
+134
@@ -0,0 +1,134 @@
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 type {Wakeable} from 'shared/ReactTypes';
11
+import type {GitHubIssue} from './githubAPI';
12
+
13
+import {unstable_getCacheForType} from 'react';
14
+import {searchGitHubIssues} from './githubAPI';
15
+
16
+const API_TIMEOUT = 3000;
17
+
18
+const Pending = 0;
19
+const Resolved = 1;
20
+const Rejected = 2;
21
+
22
+type PendingRecord = {|
23
+ status: 0,
24
+ value: Wakeable,
25
+|};
26
+
27
+type ResolvedRecord<T> = {|
28
+ status: 1,
29
+ value: T,
30
+|};
31
+
32
+type RejectedRecord = {|
33
+ status: 2,
34
+ value: null,
35
+|};
36
+
37
+type Record<T> = PendingRecord | ResolvedRecord<T> | RejectedRecord;
38
+
39
+function readRecord<T>(record: Record<T>): ResolvedRecord<T> | RejectedRecord {
40
+ if (record.status === Resolved) {
41
+ // This is just a type refinement.
42
+ return record;
43
+ } else if (record.status === Rejected) {
44
+ // This is just a type refinement.
45
+ return record;
46
+ } else {
47
+ throw record.value;
48
+ }
49
+}
50
+
51
+type GitHubIssueMap = Map<string, Record<GitHubIssue>>;
52
+
53
+function createMap(): GitHubIssueMap {
54
+ return new Map();
55
+}
56
+
57
+function getRecordMap(): Map<string, Record<GitHubIssue>> {
58
+ return unstable_getCacheForType(createMap);
59
+}
60
+
61
+export function findGitHubIssue(errorMessage: string): GitHubIssue | null {
62
+ errorMessage = normalizeErrorMessage(errorMessage);
63
+
64
+ const map = getRecordMap();
65
+ let record = map.get(errorMessage);
66
+
67
+ if (!record) {
68
+ const callbacks = new Set();
69
+ const wakeable: Wakeable = {
70
+ then(callback) {
71
+ callbacks.add(callback);
72
+ },
73
+ };
74
+ const wake = () => {
75
+ // This assumes they won't throw.
76
+ callbacks.forEach(callback => callback());
77
+ callbacks.clear();
78
+ };
79
+ const newRecord: Record<GitHubIssue> = (record = {
80
+ status: Pending,
81
+ value: wakeable,
82
+ });
83
+
84
+ let didTimeout = false;
85
+
86
+ searchGitHubIssues(errorMessage)
87
+ .then(maybeItem => {
88
+ if (didTimeout) {
89
+ return;
90
+ }
91
+
92
+ if (maybeItem) {
93
+ const resolvedRecord = ((newRecord: any): ResolvedRecord<GitHubIssue>);
94
+ resolvedRecord.status = Resolved;
95
+ resolvedRecord.value = maybeItem;
96
+ } else {
97
+ const notFoundRecord = ((newRecord: any): RejectedRecord);
98
+ notFoundRecord.status = Rejected;
99
+ notFoundRecord.value = null;
100
+ }
101
+
102
+ wake();
103
+ })
104
+ .catch(error => {
105
+ const thrownRecord = ((newRecord: any): RejectedRecord);
106
+ thrownRecord.status = Rejected;
107
+ thrownRecord.value = null;
108
+
109
+ wake();
110
+ });
111
+
112
+ // Only wait a little while for GitHub results before showing a fallback.
113
+ setTimeout(() => {
114
+ didTimeout = true;
115
+
116
+ const timedoutRecord = ((newRecord: any): RejectedRecord);
117
+ timedoutRecord.status = Rejected;
118
+ timedoutRecord.value = null;
119
+
120
+ wake();
121
+ }, API_TIMEOUT);
122
+
123
+ map.set(errorMessage, record);
124
+ }
125
+
126
+ const response = readRecord(record).value;
127
+ return response;
128
+}
129
+
130
+function normalizeErrorMessage(errorMessage: string): string {
131
+ // Remove Fiber IDs from error message (as those will be unique).
132
+ errorMessage = errorMessage.replace(/"[0-9]+"/, '');
133
+ return errorMessage;
134
+}
packages/react-devtools-shared/src/devtools/views/ErrorBoundary/githubAPI.js
new
+50
@@ -0,0 +1,50 @@
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 {Octokit} from '@octokit/rest';
11
+
12
+export type GitHubIssue = {|
13
+ title: string,
14
+ url: string,
15
+|};
16
+
17
+export async function searchGitHubIssues(
18
+ message: string,
19
+): Promise<GitHubIssue | null> {
20
+ // Remove Fiber IDs from error message (as those will be unique).
21
+ message = message.replace(/"[0-9]+"/, '');
22
+
23
+ const filters = [
24
+ // Unfortunately "repo" and "org" filters don't work
25
+ // Hopefully the label filter will be sufficient.
26
+ 'in:title',
27
+ 'is:issue',
28
+ 'is:open',
29
+ 'is:public',
30
+ 'label:"Component: Developer Tools"',
31
+ ];
32
+
33
+ const octokit = new Octokit();
34
+ const {data} = await octokit.search.issuesAndPullRequests({
35
+ q: message + ' ' + filters.join(' '),
36
+ });
37
+
38
+ const maybeItem = data.items.find(item =>
39
+ item.html_url.startsWith('https://github.com/facebook/react/'),
40
+ );
41
+
42
+ if (maybeItem) {
43
+ return {
44
+ title: maybeItem.title,
45
+ url: maybeItem.html_url,
46
+ };
47
+ } else {
48
+ return null;
49
+ }
50
+}
packages/react-devtools-shared/src/devtools/views/ErrorBoundary/index.js
new
+12
@@ -0,0 +1,12 @@
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 ErrorBoundary from './ErrorBoundary';
11
+
12
+export default ErrorBoundary;
packages/react-devtools-shared/src/devtools/views/ErrorBoundary/shared.css
new
+83
@@ -0,0 +1,83 @@
1
+.GitHubLinkRow {
2
+ flex: 0 0 auto;
3
+ display: flex;
4
+ align-items: center;
5
+ text-overflow: ellipsis;
6
+ white-space: nowrap;
7
+ overflow: auto;
8
+ padding: 0.25rem 0.5rem;
9
+ background: var(--color-console-warning-background);
10
+ color: var(--color-text);
11
+ border-bottom: 1px solid var(--color-console-warning-border);
12
+ border-top: 1px solid var(--color-console-warning-border);
13
+}
14
+
15
+.WorkplaceGroupRow {
16
+ flex: 0 0 auto;
17
+ display: flex;
18
+ align-items: center;
19
+ text-overflow: ellipsis;
20
+ white-space: nowrap;
21
+ overflow: auto;
22
+ padding: 0.25rem 0.5rem;
23
+ background: var(--color-background-hover);
24
+ border-bottom: 1px solid var(--color-border);
25
+}
26
+
27
+.ErrorBoundary {
28
+ height: 100%;
29
+ width: 100%;
30
+ background-color: var(--color-background);
31
+ display: flex;
32
+ flex-direction: column;
33
+}
34
+
35
+.ErrorInfo {
36
+ padding: 0.5rem;
37
+ overflow: auto;
38
+}
39
+
40
+.Header {
41
+ font-size: var(--font-size-sans-large);
42
+ font-weight: bold;
43
+ color: var(--color-error-text);
44
+}
45
+
46
+.Stack {
47
+ margin-top: 0.5rem;
48
+ white-space: pre-wrap;
49
+ font-family: var(--font-family-monospace);
50
+ font-size: var(--font-size-monospace-normal);
51
+ -webkit-font-smoothing: initial;
52
+ background-color: var(--color-error-background);
53
+ border: 1px solid var(--color-error-border);
54
+ color: var(--color-error-text);
55
+ border-radius: 0.25rem;
56
+ padding: 0.5rem;
57
+}
58
+
59
+.LoadingIcon {
60
+ margin-right: 0.25rem;
61
+}
62
+
63
+.ReportIcon {
64
+ margin-right: 0.25rem;
65
+}
66
+
67
+.ReportLink {
68
+ color: var(--color-link);
69
+}
70
+
71
+.FacebookOnly {
72
+ margin-left: 0.25rem;
73
+}
74
+
75
+.ReproSteps {
76
+ margin-left: 0.25rem;
77
+ color: var(--color-console-warning-text);
78
+}
79
+
80
+.UpdateExistingIssuePrompt {
81
+ margin-right: 0.25rem;
82
+ color: var(--color-console-warning-text);
83
+}
\ No newline at end of file
packages/react-devtools-shared/src/devtools/views/Icon.js
+8
@@ -17,6 +17,7 @@ export type IconType =
17
| 'components'
18
| 'copy'
19
| 'error'
20
+ | 'facebook'
21
| 'flame-chart'
22
| 'interactions'
23
| 'profiler'
@@ -52,6 +53,9 @@ export default function Icon({className = '', type}: Props) {
53
case 'error':
54
pathData = PATH_ERROR;
55
break;
56
+ case 'facebook':
57
+ pathData = PATH_FACEBOOK;
58
+ break;
59
case 'flame-chart':
60
pathData = PATH_FLAME_CHART;
61
break;
@@ -117,6 +121,10 @@ const PATH_COPY = `
121
122
const PATH_ERROR = `M16.971 0h-9.942l-7.029 7.029v9.941l7.029 7.03h9.941l7.03-7.029v-9.942l-7.029-7.029zm-1.402 16.945l-3.554-3.521-3.518 3.568-1.418-1.418 3.507-3.566-3.586-3.472 1.418-1.417 3.581 3.458 3.539-3.583 1.431 1.431-3.535 3.568 3.566 3.522-1.431 1.43z`;
123
124
+const PATH_FACEBOOK = `
125
+ M22,12c0-5.52-4.48-10-10-10S2,6.48,2,12c0,4.84,3.44,8.87,8,9.8V15H8v-3h2V9.5C10,7.57,11.57,6,13.5,6H16v3h-2 c-0.55,0-1,0.45-1,1v2h3v3h-3v6.95C18.05,21.45,22,17.19,22,12z
126
+`;
127
+
128
const PATH_FLAME_CHART = `
129
M10.0650893,21.5040462 C7.14020814,20.6850349 5,18.0558698 5,14.9390244 C5,14.017627
130
5,9.81707317 7.83333333,7.37804878 C7.83333333,7.37804878 7.58333333,11.199187 10,
yarn.lock
+122
-1
@@ -1814,6 +1814,26 @@
1814
read-package-json-fast "^1.1.1"
1815
readdir-scoped-modules "^1.1.0"
1816
1817
+"@octokit/auth-token@^2.4.4":
1818
+ version "2.4.5"
1819
+ resolved "https://registry.yarnpkg.com/@octokit/auth-token/-/auth-token-2.4.5.tgz#568ccfb8cb46f36441fac094ce34f7a875b197f3"
1820
+ integrity sha512-BpGYsPgJt05M7/L/5FoE1PiAbdxXFZkX/3kDYcsvd1v6UhlnE5e96dTDr0ezX/EFwciQxf3cNV0loipsURU+WA==
1821
+ dependencies:
1822
+ "@octokit/types" "^6.0.3"
1823
+
1824
+"@octokit/core@^3.2.3":
1825
+ version "3.4.0"
1826
+ resolved "https://registry.yarnpkg.com/@octokit/core/-/core-3.4.0.tgz#b48aa27d755b339fe7550548b340dcc2b513b742"
1827
+ integrity sha512-6/vlKPP8NF17cgYXqucdshWqmMZGXkuvtcrWCgU5NOI0Pl2GjlmZyWgBMrU8zJ3v2MJlM6++CiB45VKYmhiWWg==
1828
+ dependencies:
1829
+ "@octokit/auth-token" "^2.4.4"
1830
+ "@octokit/graphql" "^4.5.8"
1831
+ "@octokit/request" "^5.4.12"
1832
+ "@octokit/request-error" "^2.0.5"
1833
+ "@octokit/types" "^6.0.3"
1834
+ before-after-hook "^2.2.0"
1835
+ universal-user-agent "^6.0.0"
1836
+
1837
"@octokit/endpoint@^5.1.0":
1838
version "5.3.2"
1839
resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-5.3.2.tgz#2deda2d869cac9ba7f370287d55667be2a808d4b"
@@ -1824,6 +1844,49 @@
1844
universal-user-agent "^3.0.0"
1845
url-template "^2.0.8"
1846
1847
+"@octokit/endpoint@^6.0.1":
1848
+ version "6.0.11"
1849
+ resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-6.0.11.tgz#082adc2aebca6dcefa1fb383f5efb3ed081949d1"
1850
+ integrity sha512-fUIPpx+pZyoLW4GCs3yMnlj2LfoXTWDUVPTC4V3MUEKZm48W+XYpeWSZCv+vYF1ZABUm2CqnDVf1sFtIYrj7KQ==
1851
+ dependencies:
1852
+ "@octokit/types" "^6.0.3"
1853
+ is-plain-object "^5.0.0"
1854
+ universal-user-agent "^6.0.0"
1855
+
1856
+"@octokit/graphql@^4.5.8":
1857
+ version "4.6.1"
1858
+ resolved "https://registry.yarnpkg.com/@octokit/graphql/-/graphql-4.6.1.tgz#f975486a46c94b7dbe58a0ca751935edc7e32cc9"
1859
+ integrity sha512-2lYlvf4YTDgZCTXTW4+OX+9WTLFtEUc6hGm4qM1nlZjzxj+arizM4aHWzBVBCxY9glh7GIs0WEuiSgbVzv8cmA==
1860
+ dependencies:
1861
+ "@octokit/request" "^5.3.0"
1862
+ "@octokit/types" "^6.0.3"
1863
+ universal-user-agent "^6.0.0"
1864
+
1865
+"@octokit/openapi-types@^6.0.0":
1866
+ version "6.0.0"
1867
+ resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-6.0.0.tgz#7da8d7d5a72d3282c1a3ff9f951c8133a707480d"
1868
+ integrity sha512-CnDdK7ivHkBtJYzWzZm7gEkanA7gKH6a09Eguz7flHw//GacPJLmkHA3f3N++MJmlxD1Fl+mB7B32EEpSCwztQ==
1869
+
1870
+"@octokit/plugin-paginate-rest@^2.6.2":
1871
+ version "2.13.3"
1872
+ resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.13.3.tgz#f0f1792230805108762d87906fb02d573b9e070a"
1873
+ integrity sha512-46lptzM9lTeSmIBt/sVP/FLSTPGx6DCzAdSX3PfeJ3mTf4h9sGC26WpaQzMEq/Z44cOcmx8VsOhO+uEgE3cjYg==
1874
+ dependencies:
1875
+ "@octokit/types" "^6.11.0"
1876
+
1877
+"@octokit/plugin-request-log@^1.0.2":
1878
+ version "1.0.3"
1879
+ resolved "https://registry.yarnpkg.com/@octokit/plugin-request-log/-/plugin-request-log-1.0.3.tgz#70a62be213e1edc04bb8897ee48c311482f9700d"
1880
+ integrity sha512-4RFU4li238jMJAzLgAwkBAw+4Loile5haQMQr+uhFq27BmyJXcXSKvoQKqh0agsZEiUlW6iSv3FAgvmGkur7OQ==
1881
+
1882
+"@octokit/plugin-rest-endpoint-methods@5.0.0":
1883
+ version "5.0.0"
1884
+ resolved "https://registry.yarnpkg.com/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.0.0.tgz#cf2cdeb24ea829c31688216a5b165010b61f9a98"
1885
+ integrity sha512-Jc7CLNUueIshXT+HWt6T+M0sySPjF32mSFQAK7UfAg8qGeRI6OM1GSBxDLwbXjkqy2NVdnqCedJcP1nC785JYg==
1886
+ dependencies:
1887
+ "@octokit/types" "^6.13.0"
1888
+ deprecation "^2.3.1"
1889
+
1890
"@octokit/request-error@^1.0.1", "@octokit/request-error@^1.0.2":
1891
version "1.0.4"
1892
resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-1.0.4.tgz#15e1dc22123ba4a9a4391914d80ec1e5303a23be"
@@ -1832,6 +1895,15 @@
1895
deprecation "^2.0.0"
1896
once "^1.4.0"
1897
1898
+"@octokit/request-error@^2.0.0", "@octokit/request-error@^2.0.5":
1899
+ version "2.0.5"
1900
+ resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-2.0.5.tgz#72cc91edc870281ad583a42619256b380c600143"
1901
+ integrity sha512-T/2wcCFyM7SkXzNoyVNWjyVlUwBvW3igM3Btr/eKYiPmucXTtkxt2RBsf6gn3LTzaLSLTQtNmvg+dGsOxQrjZg==
1902
+ dependencies:
1903
+ "@octokit/types" "^6.0.3"
1904
+ deprecation "^2.0.0"
1905
+ once "^1.4.0"
1906
+
1907
"@octokit/request@^5.0.0":
1908
version "5.0.2"
1909
resolved "https://registry.yarnpkg.com/@octokit/request/-/request-5.0.2.tgz#59a920451f24811c016ddc507adcc41aafb2dca5"
@@ -1845,6 +1917,18 @@
1917
once "^1.4.0"
1918
universal-user-agent "^3.0.0"
1919
1920
+"@octokit/request@^5.3.0", "@octokit/request@^5.4.12":
1921
+ version "5.4.15"
1922
+ resolved "https://registry.yarnpkg.com/@octokit/request/-/request-5.4.15.tgz#829da413dc7dd3aa5e2cdbb1c7d0ebe1f146a128"
1923
+ integrity sha512-6UnZfZzLwNhdLRreOtTkT9n57ZwulCve8q3IT/Z477vThu6snfdkBuhxnChpOKNGxcQ71ow561Qoa6uqLdPtag==
1924
+ dependencies:
1925
+ "@octokit/endpoint" "^6.0.1"
1926
+ "@octokit/request-error" "^2.0.0"
1927
+ "@octokit/types" "^6.7.1"
1928
+ is-plain-object "^5.0.0"
1929
+ node-fetch "^2.6.1"
1930
+ universal-user-agent "^6.0.0"
1931
+
1932
"@octokit/rest@^16.14.1":
1933
version "16.28.7"
1934
resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-16.28.7.tgz#a2c2db5b318da84144beba82d19c1a9dbdb1a1fa"
@@ -1864,6 +1948,23 @@
1948
universal-user-agent "^3.0.0"
1949
url-template "^2.0.8"
1950
1951
+"@octokit/rest@^18.5.2":
1952
+ version "18.5.2"
1953
+ resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-18.5.2.tgz#0369e554b7076e3749005147be94c661c7a5a74b"
1954
+ integrity sha512-Kz03XYfKS0yYdi61BkL9/aJ0pP2A/WK5vF/syhu9/kY30J8He3P68hv9GRpn8bULFx2K0A9MEErn4v3QEdbZcw==
1955
+ dependencies:
1956
+ "@octokit/core" "^3.2.3"
1957
+ "@octokit/plugin-paginate-rest" "^2.6.2"
1958
+ "@octokit/plugin-request-log" "^1.0.2"
1959
+ "@octokit/plugin-rest-endpoint-methods" "5.0.0"
1960
+
1961
+"@octokit/types@^6.0.3", "@octokit/types@^6.11.0", "@octokit/types@^6.13.0", "@octokit/types@^6.7.1":
1962
+ version "6.13.0"
1963
+ resolved "https://registry.yarnpkg.com/@octokit/types/-/types-6.13.0.tgz#779e5b7566c8dde68f2f6273861dd2f0409480d0"
1964
+ integrity sha512-W2J9qlVIU11jMwKHUp5/rbVUeErqelCsO5vW5PKNb7wAXQVUz87Rc+imjlEvpvbH8yUb+KHmv8NEjVZdsdpyxA==
1965
+ dependencies:
1966
+ "@octokit/openapi-types" "^6.0.0"
1967
+
1968
"@pmmmwh/react-refresh-webpack-plugin@^0.4.1":
1969
version "0.4.1"
1970
resolved "https://registry.yarnpkg.com/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.4.1.tgz#a4db0ed8e58c2f8566161c9a8cdf1d095c9a891b"
@@ -3478,6 +3579,11 @@ before-after-hook@^2.0.0:
3579
resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.1.0.tgz#b6c03487f44e24200dd30ca5e6a1979c5d2fb635"
3580
integrity sha512-IWIbu7pMqyw3EAJHzzHbWa85b6oud/yfKYg5rqB5hNE8CeMi3nX+2C2sj0HswfblST86hpVEOAb9x34NZd6P7A==
3581
3582
+before-after-hook@^2.2.0:
3583
+ version "2.2.1"
3584
+ resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.2.1.tgz#73540563558687586b52ed217dad6a802ab1549c"
3585
+ integrity sha512-/6FKxSTWoJdbsLDF8tdIjaRiFXiE6UHsEHE3OPI/cwPURCVi1ukP0gmLn7XWEiFk5TcwQjjY5PWsU+j+tgXgmw==
3586
+
3587
big.js@^5.2.2:
3588
version "5.2.2"
3589
resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328"
@@ -5140,7 +5246,7 @@ depd@^1.1.2, depd@~1.1.2:
5246
resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
5247
integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=
5248
5143
-deprecation@^2.0.0:
5249
+deprecation@^2.0.0, deprecation@^2.3.1:
5250
version "2.3.1"
5251
resolved "https://registry.yarnpkg.com/deprecation/-/deprecation-2.3.1.tgz#6368cbdb40abf3373b525ac87e4a260c3a700919"
5252
integrity sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==
@@ -8128,6 +8234,11 @@ is-plain-object@^3.0.0:
8234
dependencies:
8235
isobject "^4.0.0"
8236
8237
+is-plain-object@^5.0.0:
8238
+ version "5.0.0"
8239
+ resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344"
8240
+ integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==
8241
+
8242
is-posix-bracket@^0.1.0:
8243
version "0.1.1"
8244
resolved "https://registry.yarnpkg.com/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz#3334dc79774368e92f016e6fbc0a88f5cd6e6bc4"
@@ -10042,6 +10153,11 @@ node-fetch@^2.3.0, node-fetch@^2.6.0:
10153
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd"
10154
integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==
10155
10156
+node-fetch@^2.6.1:
10157
+ version "2.6.1"
10158
+ resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052"
10159
+ integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==
10160
+
10161
node-forge@0.9.0:
10162
version "0.9.0"
10163
resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.9.0.tgz#d624050edbb44874adca12bb9a52ec63cb782579"
@@ -13801,6 +13917,11 @@ universal-user-agent@^3.0.0:
13917
dependencies:
13918
os-name "^3.0.0"
13919
13920
+universal-user-agent@^6.0.0:
13921
+ version "6.0.0"
13922
+ resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-6.0.0.tgz#3381f8503b251c0d9cd21bc1de939ec9df5480ee"
13923
+ integrity sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==
13924
+
13925
universalify@^0.1.0:
13926
version "0.1.2"
13927
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"