Skip to content

Commit 7176f69

Browse files
Add listeners for unhandled errors to web views
1 parent 2217d3f commit 7176f69

9 files changed

Lines changed: 124 additions & 8 deletions

File tree

extensions/ql-vscode/src/compare/compare-view.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import { assertNever, getErrorMessage } from "../pure/helpers-pure";
2020
import { HistoryItemLabelProvider } from "../query-history/history-item-label-provider";
2121
import { AbstractWebview, WebviewPanelConfig } from "../abstract-webview";
2222
import { telemetryListener } from "../telemetry";
23+
import { redactableError } from "../pure/errors";
24+
import { showAndLogExceptionWithTelemetry } from "../helpers";
2325

2426
interface ComparePair {
2527
from: CompletedLocalQueryInfo;
@@ -139,6 +141,14 @@ export class CompareView extends AbstractWebview<
139141
telemetryListener?.sendUIInteraction(msg.action);
140142
break;
141143

144+
case "unhandledError":
145+
void showAndLogExceptionWithTelemetry(
146+
redactableError(
147+
msg.error,
148+
)`Unhandled error in result comparison view: ${msg.error.message}`,
149+
);
150+
break;
151+
142152
default:
143153
assertNever(msg);
144154
}

extensions/ql-vscode/src/interface.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,13 @@ export class ResultsView extends AbstractWebview<
295295
case "telemetry":
296296
telemetryListener?.sendUIInteraction(msg.action);
297297
break;
298+
case "unhandledError":
299+
void showAndLogExceptionWithTelemetry(
300+
redactableError(
301+
msg.error,
302+
)`Unhandled error in results view: ${msg.error.message}`,
303+
);
304+
break;
298305
default:
299306
assertNever(msg);
300307
}

extensions/ql-vscode/src/pure/errors.ts

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
export class RedactableError extends Error {
22
constructor(
3-
cause: Error | undefined,
3+
cause: ErrorLike | undefined,
44
private readonly strings: TemplateStringsArray,
55
private readonly values: unknown[],
66
) {
@@ -54,19 +54,35 @@ export function redactableError(
5454
...values: unknown[]
5555
): RedactableError;
5656
export function redactableError(
57-
error: Error,
57+
error: ErrorLike,
5858
): (strings: TemplateStringsArray, ...values: unknown[]) => RedactableError;
5959

6060
export function redactableError(
61-
errorOrStrings: Error | TemplateStringsArray,
61+
errorOrStrings: ErrorLike | TemplateStringsArray,
6262
...values: unknown[]
6363
):
6464
| ((strings: TemplateStringsArray, ...values: unknown[]) => RedactableError)
6565
| RedactableError {
66-
if (errorOrStrings instanceof Error) {
66+
if (isErrorLike(errorOrStrings)) {
6767
return (strings: TemplateStringsArray, ...values: unknown[]) =>
6868
new RedactableError(errorOrStrings, strings, values);
6969
} else {
7070
return new RedactableError(undefined, errorOrStrings, values);
7171
}
7272
}
73+
74+
export interface ErrorLike {
75+
message: string;
76+
stack?: string;
77+
}
78+
79+
function isErrorLike(error: any): error is ErrorLike {
80+
if (
81+
error.message !== undefined &&
82+
typeof error.message === "string" &&
83+
(error.stack === undefined || typeof error.stack === "string")
84+
) {
85+
return true;
86+
}
87+
return false;
88+
}

extensions/ql-vscode/src/pure/interface-types.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
VariantAnalysisScannedRepositoryState,
1313
} from "../variant-analysis/shared/variant-analysis";
1414
import { RepositoriesFilterSortStateWithIds } from "./variant-analysis-filter-sort";
15+
import { ErrorLike } from "./errors";
1516

1617
/**
1718
* This module contains types and code that are shared between
@@ -189,7 +190,8 @@ export type FromResultsViewMsg =
189190
| ViewLoadedMsg
190191
| ChangePage
191192
| OpenFileMsg
192-
| TelemetryMessage;
193+
| TelemetryMessage
194+
| UnhandledErrorMessage;
193195

194196
/**
195197
* Message from the results view to open a database source
@@ -291,7 +293,8 @@ export type FromCompareViewMessage =
291293
| ChangeCompareMessage
292294
| ViewSourceFileMsg
293295
| OpenQueryMessage
294-
| TelemetryMessage;
296+
| TelemetryMessage
297+
| UnhandledErrorMessage;
295298

296299
/**
297300
* Message from the compare view to request opening a query.
@@ -439,6 +442,11 @@ export interface TelemetryMessage {
439442
action: string;
440443
}
441444

445+
export interface UnhandledErrorMessage {
446+
t: "unhandledError";
447+
error: ErrorLike;
448+
}
449+
442450
export type ToVariantAnalysisMessage =
443451
| SetVariantAnalysisMessage
444452
| SetRepoResultsMessage
@@ -453,4 +461,5 @@ export type FromVariantAnalysisMessage =
453461
| ExportResultsMessage
454462
| OpenLogsMessage
455463
| CancelVariantAnalysisMessage
456-
| TelemetryMessage;
464+
| TelemetryMessage
465+
| UnhandledErrorMessage;

extensions/ql-vscode/src/variant-analysis/variant-analysis-view.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,12 @@ import {
1515
VariantAnalysisViewInterface,
1616
VariantAnalysisViewManager,
1717
} from "./variant-analysis-view-manager";
18-
import { showAndLogWarningMessage } from "../helpers";
18+
import {
19+
showAndLogExceptionWithTelemetry,
20+
showAndLogWarningMessage,
21+
} from "../helpers";
1922
import { telemetryListener } from "../telemetry";
23+
import { redactableError } from "../pure/errors";
2024

2125
export class VariantAnalysisView
2226
extends AbstractWebview<ToVariantAnalysisMessage, FromVariantAnalysisMessage>
@@ -153,6 +157,13 @@ export class VariantAnalysisView
153157
case "telemetry":
154158
telemetryListener?.sendUIInteraction(msg.action);
155159
break;
160+
case "unhandledError":
161+
void showAndLogExceptionWithTelemetry(
162+
redactableError(
163+
msg.error,
164+
)`Unhandled error in variant analysis results view: ${msg.error.message}`,
165+
);
166+
break;
156167
default:
157168
assertNever(msg);
158169
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { useEffect } from "react";
2+
import { getErrorMessage, getErrorStack } from "../../pure/helpers-pure";
3+
import { vscode } from "../vscode-api";
4+
5+
const unhandledErrorListener = (event: ErrorEvent) => {
6+
vscode.postMessage({
7+
t: "unhandledError",
8+
error: {
9+
message: getErrorMessage(event.error),
10+
stack: getErrorStack(event.error),
11+
},
12+
});
13+
};
14+
15+
const unhandledRejectionListener = (event: PromiseRejectionEvent) => {
16+
vscode.postMessage({
17+
t: "unhandledError",
18+
error: {
19+
message: getErrorMessage(event.reason),
20+
stack: getErrorStack(event.reason),
21+
},
22+
});
23+
};
24+
25+
/**
26+
* A react effect that handles adding listeners for unhandled errors / rejected promises.
27+
* When an error is detected a "unhandledError" message is posted to the view.
28+
*/
29+
export function useUnhandledErrorListener() {
30+
useEffect(() => {
31+
registerUnhandledErrorListener();
32+
return unregisterUnhandledErrorListener;
33+
}, []);
34+
}
35+
36+
/**
37+
* Adds listeners for unhandled errors / rejected promises.
38+
* When an error is detected a "unhandledError" message is posted to the view.
39+
*/
40+
export function registerUnhandledErrorListener() {
41+
window.addEventListener("error", unhandledErrorListener);
42+
window.addEventListener("unhandledrejection", unhandledRejectionListener);
43+
}
44+
45+
/**
46+
* Remove listeners for unhandled errors / rejected promises.
47+
*/
48+
export function unregisterUnhandledErrorListener() {
49+
window.removeEventListener("error", unhandledErrorListener);
50+
window.removeEventListener("unhandledrejection", unhandledRejectionListener);
51+
}

extensions/ql-vscode/src/view/compare/Compare.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { vscode } from "../vscode-api";
1010
import CompareTable from "./CompareTable";
1111

1212
import "../results/resultsView.css";
13+
import { useUnhandledErrorListener } from "../common/errors";
1314

1415
const emptyComparison: SetComparisonsMessage = {
1516
t: "setComparisons",
@@ -23,6 +24,8 @@ const emptyComparison: SetComparisonsMessage = {
2324
};
2425

2526
export function Compare(_: Record<string, never>): JSX.Element {
27+
useUnhandledErrorListener();
28+
2629
const [comparison, setComparison] =
2730
useState<SetComparisonsMessage>(emptyComparison);
2831

extensions/ql-vscode/src/view/results/results.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ import {
1414
NavigateMsg,
1515
ResultSet,
1616
} from "../../pure/interface-types";
17+
import {
18+
registerUnhandledErrorListener,
19+
unregisterUnhandledErrorListener,
20+
} from "../common/errors";
1721
import { EventHandlers as EventHandlerList } from "./event-handler-list";
1822
import { ResultTables } from "./result-tables";
1923

@@ -307,12 +311,14 @@ export class ResultsApp extends React.Component<
307311
componentDidMount(): void {
308312
this.vscodeMessageHandler = this.vscodeMessageHandler.bind(this);
309313
window.addEventListener("message", this.vscodeMessageHandler);
314+
registerUnhandledErrorListener();
310315
}
311316

312317
componentWillUnmount(): void {
313318
if (this.vscodeMessageHandler) {
314319
window.removeEventListener("message", this.vscodeMessageHandler);
315320
}
321+
unregisterUnhandledErrorListener();
316322
}
317323

318324
private vscodeMessageHandler(evt: MessageEvent) {

extensions/ql-vscode/src/view/variant-analysis/VariantAnalysis.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { ToVariantAnalysisMessage } from "../../pure/interface-types";
1414
import { vscode } from "../vscode-api";
1515
import { defaultFilterSortState } from "../../pure/variant-analysis-filter-sort";
1616
import { useTelemetryOnChange } from "../common/telemetry";
17+
import { useUnhandledErrorListener } from "../common/errors";
1718

1819
export type VariantAnalysisProps = {
1920
variantAnalysis?: VariantAnalysisDomainModel;
@@ -50,6 +51,8 @@ export function VariantAnalysis({
5051
repoStates: initialRepoStates = [],
5152
repoResults: initialRepoResults = [],
5253
}: VariantAnalysisProps): JSX.Element {
54+
useUnhandledErrorListener();
55+
5356
const [variantAnalysis, setVariantAnalysis] = useState<
5457
VariantAnalysisDomainModel | undefined
5558
>(initialVariantAnalysis);

0 commit comments

Comments
 (0)