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) {