diff --git a/ui/src/AppBoot.js b/ui/src/AppBoot.js
index 70817997c..6d867a5a7 100644
--- a/ui/src/AppBoot.js
+++ b/ui/src/AppBoot.js
@@ -1,6 +1,6 @@
// helpers used to bootstrap App instance and environment for it
-import * as Sentry from "@sentry/browser";
+import { init } from "@sentry/browser";
const SettingsElement = () => document.getElementById("settings");
@@ -19,7 +19,7 @@ const SetupSentry = (settingsElement) => {
}
try {
- Sentry.init({
+ init({
dsn: settingsElement.dataset.sentryDsn,
release: version,
});
diff --git a/ui/src/ErrorBoundary.test.js b/ui/src/ErrorBoundary.test.js
index c0e298435..c38c0ac74 100644
--- a/ui/src/ErrorBoundary.test.js
+++ b/ui/src/ErrorBoundary.test.js
@@ -60,11 +60,25 @@ describe("", () => {
);
- const instance = tree.instance();
- instance.componentDidCatch("foo", { foo: "bar" });
+ tree.instance().componentDidCatch("foo", { foo: "bar" });
expect(sentrySpy).toHaveBeenCalled();
});
+ it("componentDidCatch is only called once", () => {
+ const sentrySpy = jest.spyOn(Sentry, "captureException");
+ Sentry.init({ dsn: "https://foobar@localhost/123456" });
+
+ const tree = mount(
+
+
+
+ );
+ tree.instance().componentDidCatch("foo", { foo: "bar" });
+ tree.instance().componentDidCatch("foo", { foo: "bar" });
+ tree.instance().componentDidCatch("foo", { foo: "bar" });
+ expect(sentrySpy).toHaveBeenCalledTimes(1);
+ });
+
it("calls window.location.reload after 60s", () => {
const reloadSpy = jest.spyOn(global.window.location, "reload");
MountedFailingComponent();
@@ -75,15 +89,14 @@ describe("", () => {
it("reloadSeconds is 40 after 20s with multiple exceptions", () => {
const tree = MountedFailingComponent();
- const instance = tree.instance();
- instance.componentDidCatch("foo", { foo: "bar" });
+ tree.instance().componentDidCatch("foo", { foo: "bar" });
jest.runTimersToTime(1000 * 10);
- instance.componentDidCatch("foo", { foo: "bar" });
+ tree.instance().componentDidCatch("foo", { foo: "bar" });
jest.runTimersToTime(1000 * 5);
- instance.componentDidCatch("foo", { foo: "bar" });
+ tree.instance().componentDidCatch("foo", { foo: "bar" });
jest.runTimersToTime(1000 * 5);
- instance.componentDidCatch("foo", { foo: "bar" });
+ tree.instance().componentDidCatch("foo", { foo: "bar" });
expect(tree.instance().state.reloadSeconds).toBe(40);
});
});
diff --git a/ui/src/ErrorBoundary.tsx b/ui/src/ErrorBoundary.tsx
index 900d6cc3b..69a573ca7 100644
--- a/ui/src/ErrorBoundary.tsx
+++ b/ui/src/ErrorBoundary.tsx
@@ -5,7 +5,7 @@ import React, {
ErrorInfo,
} from "react";
-import * as Sentry from "@sentry/browser";
+import { captureException } from "@sentry/browser";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faBomb } from "@fortawesome/free-solid-svg-icons/faBomb";
@@ -75,12 +75,11 @@ class ErrorBoundary extends Component {
}
};
- componentDidCatch(error: Error | null, errorInfo: ErrorInfo) {
- this.setState({ cachedError: error });
- Sentry.withScope((scope) => {
- scope.setExtras(errorInfo);
- Sentry.captureException(error);
- });
+ componentDidCatch(error: Error, { componentStack }: ErrorInfo) {
+ if (this.state.cachedError === null) {
+ this.setState({ cachedError: error });
+ captureException(error, { contexts: { react: { componentStack } } });
+ }
// reload after 60s, this is to fix wall monitors automatically
// but only if the timer isn't set yet
if (this.timer === null) {