Add listeners for unhandled errors to web views

This commit is contained in:
Robert
2023-03-06 10:56:27 +00:00
parent 2217d3f21f
commit 7176f690f3
9 changed files with 124 additions and 8 deletions

View File

@@ -20,6 +20,8 @@ import { assertNever, getErrorMessage } from "../pure/helpers-pure";
import { HistoryItemLabelProvider } from "../query-history/history-item-label-provider";
import { AbstractWebview, WebviewPanelConfig } from "../abstract-webview";
import { telemetryListener } from "../telemetry";
import { redactableError } from "../pure/errors";
import { showAndLogExceptionWithTelemetry } from "../helpers";
interface ComparePair {
from: CompletedLocalQueryInfo;
@@ -139,6 +141,14 @@ export class CompareView extends AbstractWebview<
telemetryListener?.sendUIInteraction(msg.action);
break;
case "unhandledError":
void showAndLogExceptionWithTelemetry(
redactableError(
msg.error,
)`Unhandled error in result comparison view: ${msg.error.message}`,
);
break;
default:
assertNever(msg);
}

View File

@@ -295,6 +295,13 @@ export class ResultsView extends AbstractWebview<
case "telemetry":
telemetryListener?.sendUIInteraction(msg.action);
break;
case "unhandledError":
void showAndLogExceptionWithTelemetry(
redactableError(
msg.error,
)`Unhandled error in results view: ${msg.error.message}`,
);
break;
default:
assertNever(msg);
}

View File

@@ -1,6 +1,6 @@
export class RedactableError extends Error {
constructor(
cause: Error | undefined,
cause: ErrorLike | undefined,
private readonly strings: TemplateStringsArray,
private readonly values: unknown[],
) {
@@ -54,19 +54,35 @@ export function redactableError(
...values: unknown[]
): RedactableError;
export function redactableError(
error: Error,
error: ErrorLike,
): (strings: TemplateStringsArray, ...values: unknown[]) => RedactableError;
export function redactableError(
errorOrStrings: Error | TemplateStringsArray,
errorOrStrings: ErrorLike | TemplateStringsArray,
...values: unknown[]
):
| ((strings: TemplateStringsArray, ...values: unknown[]) => RedactableError)
| RedactableError {
if (errorOrStrings instanceof Error) {
if (isErrorLike(errorOrStrings)) {
return (strings: TemplateStringsArray, ...values: unknown[]) =>
new RedactableError(errorOrStrings, strings, values);
} else {
return new RedactableError(undefined, errorOrStrings, values);
}
}
export interface ErrorLike {
message: string;
stack?: string;
}
function isErrorLike(error: any): error is ErrorLike {
if (
error.message !== undefined &&
typeof error.message === "string" &&
(error.stack === undefined || typeof error.stack === "string")
) {
return true;
}
return false;
}

View File

@@ -12,6 +12,7 @@ import {
VariantAnalysisScannedRepositoryState,
} from "../variant-analysis/shared/variant-analysis";
import { RepositoriesFilterSortStateWithIds } from "./variant-analysis-filter-sort";
import { ErrorLike } from "./errors";
/**
* This module contains types and code that are shared between
@@ -189,7 +190,8 @@ export type FromResultsViewMsg =
| ViewLoadedMsg
| ChangePage
| OpenFileMsg
| TelemetryMessage;
| TelemetryMessage
| UnhandledErrorMessage;
/**
* Message from the results view to open a database source
@@ -291,7 +293,8 @@ export type FromCompareViewMessage =
| ChangeCompareMessage
| ViewSourceFileMsg
| OpenQueryMessage
| TelemetryMessage;
| TelemetryMessage
| UnhandledErrorMessage;
/**
* Message from the compare view to request opening a query.
@@ -439,6 +442,11 @@ export interface TelemetryMessage {
action: string;
}
export interface UnhandledErrorMessage {
t: "unhandledError";
error: ErrorLike;
}
export type ToVariantAnalysisMessage =
| SetVariantAnalysisMessage
| SetRepoResultsMessage
@@ -453,4 +461,5 @@ export type FromVariantAnalysisMessage =
| ExportResultsMessage
| OpenLogsMessage
| CancelVariantAnalysisMessage
| TelemetryMessage;
| TelemetryMessage
| UnhandledErrorMessage;

View File

@@ -15,8 +15,12 @@ import {
VariantAnalysisViewInterface,
VariantAnalysisViewManager,
} from "./variant-analysis-view-manager";
import { showAndLogWarningMessage } from "../helpers";
import {
showAndLogExceptionWithTelemetry,
showAndLogWarningMessage,
} from "../helpers";
import { telemetryListener } from "../telemetry";
import { redactableError } from "../pure/errors";
export class VariantAnalysisView
extends AbstractWebview<ToVariantAnalysisMessage, FromVariantAnalysisMessage>
@@ -153,6 +157,13 @@ export class VariantAnalysisView
case "telemetry":
telemetryListener?.sendUIInteraction(msg.action);
break;
case "unhandledError":
void showAndLogExceptionWithTelemetry(
redactableError(
msg.error,
)`Unhandled error in variant analysis results view: ${msg.error.message}`,
);
break;
default:
assertNever(msg);
}

View File

@@ -0,0 +1,51 @@
import { useEffect } from "react";
import { getErrorMessage, getErrorStack } from "../../pure/helpers-pure";
import { vscode } from "../vscode-api";
const unhandledErrorListener = (event: ErrorEvent) => {
vscode.postMessage({
t: "unhandledError",
error: {
message: getErrorMessage(event.error),
stack: getErrorStack(event.error),
},
});
};
const unhandledRejectionListener = (event: PromiseRejectionEvent) => {
vscode.postMessage({
t: "unhandledError",
error: {
message: getErrorMessage(event.reason),
stack: getErrorStack(event.reason),
},
});
};
/**
* A react effect that handles adding listeners for unhandled errors / rejected promises.
* When an error is detected a "unhandledError" message is posted to the view.
*/
export function useUnhandledErrorListener() {
useEffect(() => {
registerUnhandledErrorListener();
return unregisterUnhandledErrorListener;
}, []);
}
/**
* Adds listeners for unhandled errors / rejected promises.
* When an error is detected a "unhandledError" message is posted to the view.
*/
export function registerUnhandledErrorListener() {
window.addEventListener("error", unhandledErrorListener);
window.addEventListener("unhandledrejection", unhandledRejectionListener);
}
/**
* Remove listeners for unhandled errors / rejected promises.
*/
export function unregisterUnhandledErrorListener() {
window.removeEventListener("error", unhandledErrorListener);
window.removeEventListener("unhandledrejection", unhandledRejectionListener);
}

View File

@@ -10,6 +10,7 @@ import { vscode } from "../vscode-api";
import CompareTable from "./CompareTable";
import "../results/resultsView.css";
import { useUnhandledErrorListener } from "../common/errors";
const emptyComparison: SetComparisonsMessage = {
t: "setComparisons",
@@ -23,6 +24,8 @@ const emptyComparison: SetComparisonsMessage = {
};
export function Compare(_: Record<string, never>): JSX.Element {
useUnhandledErrorListener();
const [comparison, setComparison] =
useState<SetComparisonsMessage>(emptyComparison);

View File

@@ -14,6 +14,10 @@ import {
NavigateMsg,
ResultSet,
} from "../../pure/interface-types";
import {
registerUnhandledErrorListener,
unregisterUnhandledErrorListener,
} from "../common/errors";
import { EventHandlers as EventHandlerList } from "./event-handler-list";
import { ResultTables } from "./result-tables";
@@ -307,12 +311,14 @@ export class ResultsApp extends React.Component<
componentDidMount(): void {
this.vscodeMessageHandler = this.vscodeMessageHandler.bind(this);
window.addEventListener("message", this.vscodeMessageHandler);
registerUnhandledErrorListener();
}
componentWillUnmount(): void {
if (this.vscodeMessageHandler) {
window.removeEventListener("message", this.vscodeMessageHandler);
}
unregisterUnhandledErrorListener();
}
private vscodeMessageHandler(evt: MessageEvent) {

View File

@@ -14,6 +14,7 @@ import { ToVariantAnalysisMessage } from "../../pure/interface-types";
import { vscode } from "../vscode-api";
import { defaultFilterSortState } from "../../pure/variant-analysis-filter-sort";
import { useTelemetryOnChange } from "../common/telemetry";
import { useUnhandledErrorListener } from "../common/errors";
export type VariantAnalysisProps = {
variantAnalysis?: VariantAnalysisDomainModel;
@@ -50,6 +51,8 @@ export function VariantAnalysis({
repoStates: initialRepoStates = [],
repoResults: initialRepoResults = [],
}: VariantAnalysisProps): JSX.Element {
useUnhandledErrorListener();
const [variantAnalysis, setVariantAnalysis] = useState<
VariantAnalysisDomainModel | undefined
>(initialVariantAnalysis);