diff --git a/pkg/ui/workspaces/db-console/assets/sleepy-moon.svg b/pkg/ui/workspaces/db-console/assets/sleepy-moon.svg new file mode 100644 index 000000000000..1c60daf68c27 --- /dev/null +++ b/pkg/ui/workspaces/db-console/assets/sleepy-moon.svg @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pkg/ui/workspaces/db-console/src/app.spec.tsx b/pkg/ui/workspaces/db-console/src/app.spec.tsx index 00930530a3f9..b5e6d5d66402 100644 --- a/pkg/ui/workspaces/db-console/src/app.spec.tsx +++ b/pkg/ui/workspaces/db-console/src/app.spec.tsx @@ -38,7 +38,7 @@ import { EnqueueRange } from "src/views/reports/containers/enqueueRange"; import { RangesMain } from "src/views/devtools/containers/raftRanges"; import { RaftMessages } from "src/views/devtools/containers/raftMessages"; import Raft from "src/views/devtools/containers/raft"; -import NotFound from "src/views/app/components/NotFound"; +import NotFound from "src/views/app/components/errorMessage/notFound"; import { ProblemRanges } from "src/views/reports/containers/problemRanges"; import { Localities } from "src/views/reports/containers/localities"; import { Nodes } from "src/views/reports/containers/nodes"; @@ -580,7 +580,7 @@ describe("Routing to", () => { }); describe("'/unknown-url' path", () => { - it("routes to component", () => { + it("routes to component", () => { navigateToPath("/some-random-ulr"); assert.lengthOf(appWrapper.find(NotFound), 1); }); diff --git a/pkg/ui/workspaces/db-console/src/app.tsx b/pkg/ui/workspaces/db-console/src/app.tsx index 97a2c7c19b07..9a232faf84c0 100644 --- a/pkg/ui/workspaces/db-console/src/app.tsx +++ b/pkg/ui/workspaces/db-console/src/app.tsx @@ -32,7 +32,7 @@ import { tabAttr, tableNameAttr, } from "src/util/constants"; -import NotFound from "src/views/app/components/NotFound"; +import NotFound from "src/views/app/components/errorMessage/notFound"; import Layout from "src/views/app/containers/layout"; import DataDistributionPage from "src/views/cluster/containers/dataDistribution"; import { EventPage } from "src/views/cluster/containers/events"; diff --git a/pkg/ui/workspaces/db-console/src/views/app/components/errorMessage/errorBoundary.tsx b/pkg/ui/workspaces/db-console/src/views/app/components/errorMessage/errorBoundary.tsx new file mode 100644 index 000000000000..3d23256e908d --- /dev/null +++ b/pkg/ui/workspaces/db-console/src/views/app/components/errorMessage/errorBoundary.tsx @@ -0,0 +1,70 @@ +// Copyright 2021 The Cockroach Authors. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +import React, { ErrorInfo } from "react"; +import Helmet from "react-helmet"; +import "./errorMessage.styl"; +import SleepyMoonImg from "assets/sleepy-moon.svg"; + +interface ErrorBoundaryProps { + onCatch?: (error: Error, errorInfo: ErrorInfo) => void; +} + +interface ErrorBoundaryState { + hasError: boolean; + error: Error | undefined; +} + +// ErrorBoundary with image and text message. +export default class ErrorBoundary extends React.Component< + ErrorBoundaryProps, + ErrorBoundaryState +> { + constructor(props: ErrorBoundaryProps) { + super(props); + this.state = { + hasError: false, + error: undefined, + }; + } + + static getDerivedStateFromError(error: Error) { + return { hasError: true, error }; + } + + componentDidCatch(error: Error, errorInfo: ErrorInfo) { + // Console.error for developer visibility. + console.error(error); + this.props.onCatch && this.props.onCatch(error, errorInfo); + } + + render() { + if (!this.state.hasError) { + return this.props.children; + } + return ( +
+ +
+ +
+
+ Something went wrong. +
+

+ There is a problem loading the component of this page. Try + refreshing the page. +

+
+
+
+ ); + } +} diff --git a/pkg/ui/workspaces/db-console/src/views/app/components/NotFound/notFound.styl b/pkg/ui/workspaces/db-console/src/views/app/components/errorMessage/errorMessage.styl similarity index 97% rename from pkg/ui/workspaces/db-console/src/views/app/components/NotFound/notFound.styl rename to pkg/ui/workspaces/db-console/src/views/app/components/errorMessage/errorMessage.styl index 16c8464ac8a9..72421bb4f6cb 100644 --- a/pkg/ui/workspaces/db-console/src/views/app/components/NotFound/notFound.styl +++ b/pkg/ui/workspaces/db-console/src/views/app/components/errorMessage/errorMessage.styl @@ -10,7 +10,7 @@ @require '~src/components/core/index' -.not-found-page +.error-message-page min-width 320px &__content display flex diff --git a/pkg/ui/workspaces/db-console/src/views/app/components/NotFound/index.tsx b/pkg/ui/workspaces/db-console/src/views/app/components/errorMessage/notFound.tsx similarity index 74% rename from pkg/ui/workspaces/db-console/src/views/app/components/NotFound/index.tsx rename to pkg/ui/workspaces/db-console/src/views/app/components/errorMessage/notFound.tsx index e22d4c64da73..91950e843fcc 100644 --- a/pkg/ui/workspaces/db-console/src/views/app/components/NotFound/index.tsx +++ b/pkg/ui/workspaces/db-console/src/views/app/components/errorMessage/notFound.tsx @@ -10,21 +10,21 @@ import React from "react"; import Helmet from "react-helmet"; -import "./notFound.styl"; +import "./errorMessage.styl"; import NotFoundImg from "assets/not-found.svg"; function NotFound() { return ( -
+
-
+
404 Error -
-
Whoops!
+
+
Whoops!

We can't find the page you are looking for. You may have typed the wrong address or found a broken link. diff --git a/pkg/ui/workspaces/db-console/src/views/app/containers/layout/index.tsx b/pkg/ui/workspaces/db-console/src/views/app/containers/layout/index.tsx index 61da39b8a640..7cf797c863f6 100644 --- a/pkg/ui/workspaces/db-console/src/views/app/containers/layout/index.tsx +++ b/pkg/ui/workspaces/db-console/src/views/app/containers/layout/index.tsx @@ -14,6 +14,7 @@ import { RouteComponentProps, withRouter } from "react-router-dom"; import { connect } from "react-redux"; import NavigationBar from "src/views/app/components/layoutSidebar"; +import ErrorBoundary from "src/views/app/components/errorMessage/errorBoundary"; import TimeWindowManager from "src/views/app/containers/timewindow"; import AlertBanner from "src/views/app/containers/alertBanner"; import RequireLogin from "src/views/login/requireLogin"; @@ -99,7 +100,7 @@ class Layout extends React.Component {

- {this.props.children} + {this.props.children}