@samitouri / QOS-React-2 / commits / f3337aa544

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"