From 10a39018951d86b1a23402d6b1e4a24e3268a58c Mon Sep 17 00:00:00 2001 From: zklaschka Date: Fri, 12 Jan 2024 00:06:33 +0100 Subject: [PATCH] TELESTION-443: Error Boundaries --- frontend-react/package.json | 1 + frontend-react/pnpm-lock.yaml | 12 +++++++ frontend-react/src/app/index.tsx | 9 ++++-- .../app/widgets/error-widget/error-widget.tsx | 24 ++++++++++++++ .../src/app/widgets/error-widget/index.tsx | 14 +++++++++ frontend-react/src/lib/application/index.tsx | 7 +++-- .../component/error-fallback.module.css | 5 +++ .../lib/widget/component/error-fallback.tsx | 31 +++++++++++++++++++ .../lib/widget/component/widget-renderer.tsx | 6 +++- 9 files changed, 103 insertions(+), 6 deletions(-) create mode 100644 frontend-react/src/app/widgets/error-widget/error-widget.tsx create mode 100644 frontend-react/src/app/widgets/error-widget/index.tsx create mode 100644 frontend-react/src/lib/widget/component/error-fallback.module.css create mode 100644 frontend-react/src/lib/widget/component/error-fallback.tsx diff --git a/frontend-react/package.json b/frontend-react/package.json index e1f5d66f..4f8a0d66 100644 --- a/frontend-react/package.json +++ b/frontend-react/package.json @@ -68,6 +68,7 @@ "nats.ws": "^1.19.1", "react-bootstrap": "^2.9.1", "react-dom": "^18.2.0", + "react-error-boundary": "^4.0.12", "react-router-dom": "^6.19.0", "zod": "^3.22.4" }, diff --git a/frontend-react/pnpm-lock.yaml b/frontend-react/pnpm-lock.yaml index 73f77182..beb9728d 100644 --- a/frontend-react/pnpm-lock.yaml +++ b/frontend-react/pnpm-lock.yaml @@ -26,6 +26,9 @@ dependencies: react-dom: specifier: ^18.2.0 version: 18.2.0(react@18.2.0) + react-error-boundary: + specifier: ^4.0.12 + version: 4.0.12(react@18.2.0) react-router-dom: specifier: ^6.19.0 version: 6.19.0(react-dom@18.2.0)(react@18.2.0) @@ -3366,6 +3369,15 @@ packages: scheduler: 0.23.0 dev: false + /react-error-boundary@4.0.12(react@18.2.0): + resolution: {integrity: sha512-kJdxdEYlb7CPC1A0SeUY38cHpjuu6UkvzKiAmqmOFL21VRfMhOcWxTCBgLVCO0VEMh9JhFNcVaXlV4/BTpiwOA==} + peerDependencies: + react: '>=16.13.1' + dependencies: + '@babel/runtime': 7.23.5 + react: 18.2.0 + dev: false + /react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} diff --git a/frontend-react/src/app/index.tsx b/frontend-react/src/app/index.tsx index e0448edc..3f1d2716 100644 --- a/frontend-react/src/app/index.tsx +++ b/frontend-react/src/app/index.tsx @@ -1,5 +1,6 @@ import { initTelestion, registerWidgets, UserData } from '../lib'; import { simpleWidget } from './widgets/simple-widget'; +import { errorWidget } from './widgets/error-widget'; const defaultUserData: UserData = { version: '0.0.1', @@ -8,7 +9,7 @@ const defaultUserData: UserData = { title: 'Default Dashboard', layout: [ ['.', '8fj2o4', '.'], - ['.', '.', '.'] + ['.', '9fj2o4', '9fj2o4'] ] } }, @@ -18,11 +19,15 @@ const defaultUserData: UserData = { configuration: { text: 'Hello World!' } + }, + '9fj2o4': { + type: 'error-widget', + configuration: {} } } }; -registerWidgets(simpleWidget); +registerWidgets(simpleWidget, errorWidget); await initTelestion({ version: '0.0.1', diff --git a/frontend-react/src/app/widgets/error-widget/error-widget.tsx b/frontend-react/src/app/widgets/error-widget/error-widget.tsx new file mode 100644 index 00000000..37aabaea --- /dev/null +++ b/frontend-react/src/app/widgets/error-widget/error-widget.tsx @@ -0,0 +1,24 @@ +import { useEffect, useState } from 'react'; + +export function ErrorWidget() { + const [showError, setShowError] = useState(false); + useEffect(() => { + const timeout = setTimeout(() => { + setShowError(true); + }, 3_000); + return () => clearTimeout(timeout); + }, []); + + if (showError) { + throw new Error( + `Test error thrown by the error widget at ${new Date().toISOString()}` + ); + } + + return ( +
+

This widget will throw an error after three seconds

+

It is used to test the error handling of the dashboard.

+
+ ); +} diff --git a/frontend-react/src/app/widgets/error-widget/index.tsx b/frontend-react/src/app/widgets/error-widget/index.tsx new file mode 100644 index 00000000..660ff20a --- /dev/null +++ b/frontend-react/src/app/widgets/error-widget/index.tsx @@ -0,0 +1,14 @@ +import { ErrorWidget } from './error-widget.tsx'; +import { Widget } from '../../../lib'; + +export const errorWidget: Widget = { + id: 'error-widget', + label: 'Error Widget', + + createConfig() { + return {}; + }, + + element: , + configElement:
Config
+}; diff --git a/frontend-react/src/lib/application/index.tsx b/frontend-react/src/lib/application/index.tsx index a33d4e47..21524773 100644 --- a/frontend-react/src/lib/application/index.tsx +++ b/frontend-react/src/lib/application/index.tsx @@ -1,3 +1,7 @@ +// Import styles first to allow overriding bootstrap styles in CSS modules +import 'bootstrap-icons/font/bootstrap-icons.min.css'; +import './index.scss'; + import React from 'react'; import ReactDOM from 'react-dom/client'; import { RouterProvider } from 'react-router-dom'; @@ -6,9 +10,6 @@ import { registerWidgets } from '../widget'; import { TelestionOptions } from './model.ts'; import { createTelestionRouter } from './telestion-router.tsx'; -import 'bootstrap-icons/font/bootstrap-icons.min.css'; -import './index.scss'; - export * from './model.ts'; export * from './hooks'; diff --git a/frontend-react/src/lib/widget/component/error-fallback.module.css b/frontend-react/src/lib/widget/component/error-fallback.module.css new file mode 100644 index 00000000..bc3b6fe1 --- /dev/null +++ b/frontend-react/src/lib/widget/component/error-fallback.module.css @@ -0,0 +1,5 @@ +.alert { + height: 100%; + margin: 0; + overflow-y: auto; +} diff --git a/frontend-react/src/lib/widget/component/error-fallback.tsx b/frontend-react/src/lib/widget/component/error-fallback.tsx new file mode 100644 index 00000000..4e5ace92 --- /dev/null +++ b/frontend-react/src/lib/widget/component/error-fallback.tsx @@ -0,0 +1,31 @@ +import { Alert, AlertHeading, Button } from 'react-bootstrap'; +import styles from './error-fallback.module.css'; + +export function ErrorFallback({ + error, + resetErrorBoundary +}: { + error: Error; + resetErrorBoundary: () => void; +}) { + return ( + + Widget Error +

+ Unfortunately, the widget encountered an error and cannot be displayed. +

+

Please try again or contact your developer if the problem persists.

+
+ Details +
{error.stack}
+
+ +
+ ); +} diff --git a/frontend-react/src/lib/widget/component/widget-renderer.tsx b/frontend-react/src/lib/widget/component/widget-renderer.tsx index 1912c137..ac2d2fa5 100644 --- a/frontend-react/src/lib/widget/component/widget-renderer.tsx +++ b/frontend-react/src/lib/widget/component/widget-renderer.tsx @@ -3,6 +3,8 @@ import { getWidgetById } from '../state.ts'; import { getUserData } from '../../user-data'; import styles from './widget-renderer.module.css'; +import { ErrorBoundary } from 'react-error-boundary'; +import { ErrorFallback } from './error-fallback.tsx'; export interface WidgetRendererProps { widgetInstanceId: string; @@ -44,7 +46,9 @@ registerWidget({ key={`renderer-${widgetInstanceId}`} value={widgetInstance.configuration} > - {widget.element} + + {widget.element} + ) : (