mirror of
https://github.com/status-im/spiff-arena.git
synced 2025-01-14 12:14:46 +00:00
Bug/various bugs (#499)
* The ErrorBoundary is super powerful and useful. There is a default implementation that seems to be recommended now, so dropping our homegrown one for the standard one. We can now render custom components when an error happens within an error boundary, and we can use error boundaries within sub-components as we now do in the reactFormBuilder which will capture form rendering errors, and allow you to fix the error and retry. The more global ErrorBoundary set in the "ContainerForExtensions" now users a the ErrorBoundaryFallack to render the error - which looks a little cleaner, and tries to offer a little more information about what went wrong.
This commit is contained in:
parent
db7f6a64a1
commit
ce130f4539
20
spiffworkflow-frontend/package-lock.json
generated
20
spiffworkflow-frontend/package-lock.json
generated
@ -53,6 +53,7 @@
|
||||
"react-datepicker": "^4.8.0",
|
||||
"react-devtools": "^4.27.1",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-error-boundary": "^4.0.11",
|
||||
"react-icons": "^4.4.0",
|
||||
"react-jsonschema-form": "^1.8.1",
|
||||
"react-router": "^6.3.0",
|
||||
@ -25438,6 +25439,17 @@
|
||||
"react": "^18.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-error-boundary": {
|
||||
"version": "4.0.11",
|
||||
"resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-4.0.11.tgz",
|
||||
"integrity": "sha512-U13ul67aP5DOSPNSCWQ/eO0AQEYzEFkVljULQIjMV0KlffTAhxuDoBKdO0pb/JZ8mDhMKFZ9NZi0BmLGUiNphw==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.12.5"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.13.1"
|
||||
}
|
||||
},
|
||||
"node_modules/react-error-overlay": {
|
||||
"version": "6.0.11",
|
||||
"resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz",
|
||||
@ -50937,6 +50949,14 @@
|
||||
"scheduler": "^0.23.0"
|
||||
}
|
||||
},
|
||||
"react-error-boundary": {
|
||||
"version": "4.0.11",
|
||||
"resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-4.0.11.tgz",
|
||||
"integrity": "sha512-U13ul67aP5DOSPNSCWQ/eO0AQEYzEFkVljULQIjMV0KlffTAhxuDoBKdO0pb/JZ8mDhMKFZ9NZi0BmLGUiNphw==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.12.5"
|
||||
}
|
||||
},
|
||||
"react-error-overlay": {
|
||||
"version": "6.0.11",
|
||||
"resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz",
|
||||
|
@ -57,7 +57,8 @@
|
||||
"timepicker": "^1.13.18",
|
||||
"typescript": "^4.7.4",
|
||||
"use-debounce": "^9.0.4",
|
||||
"web-vitals": "^3.0.2"
|
||||
"web-vitals": "^3.0.2",
|
||||
"react-error-boundary": "^4.0.11"
|
||||
},
|
||||
"overrides": {
|
||||
"postcss-preset-env": {
|
||||
|
@ -1,11 +1,11 @@
|
||||
import { Content } from '@carbon/react';
|
||||
import { Routes, Route } from 'react-router-dom';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { ErrorBoundary } from 'react-error-boundary';
|
||||
import NavigationBar from './components/NavigationBar';
|
||||
|
||||
import HomePageRoutes from './routes/HomePageRoutes';
|
||||
import About from './routes/About';
|
||||
import ErrorBoundary from './components/ErrorBoundary';
|
||||
import AdminRoutes from './routes/AdminRoutes';
|
||||
|
||||
import ScrollToTop from './components/ScrollToTop';
|
||||
@ -19,6 +19,7 @@ import {
|
||||
UiSchemaUxElement,
|
||||
} from './extension_ui_schema_interfaces';
|
||||
import HttpService from './services/HttpService';
|
||||
import { ErrorBoundaryFallback } from './ErrorBoundaryFallack';
|
||||
|
||||
export default function ContainerForExtensions() {
|
||||
const [extensionUxElements, setExtensionNavigationItems] = useState<
|
||||
@ -84,7 +85,7 @@ export default function ContainerForExtensions() {
|
||||
<NavigationBar extensionUxElements={extensionUxElements} />
|
||||
<Content className={contentClassName}>
|
||||
<ScrollToTop />
|
||||
<ErrorBoundary>
|
||||
<ErrorBoundary FallbackComponent={ErrorBoundaryFallback}>
|
||||
<Routes>
|
||||
<Route path="/*" element={<HomePageRoutes />} />
|
||||
<Route path="/about" element={<About />} />
|
||||
|
133
spiffworkflow-frontend/src/ErrorBoundaryFallack.tsx
Normal file
133
spiffworkflow-frontend/src/ErrorBoundaryFallack.tsx
Normal file
@ -0,0 +1,133 @@
|
||||
import { Button, Content } from '@carbon/react';
|
||||
import { Routes, Route } from 'react-router-dom';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { ErrorBoundary, useErrorBoundary } from 'react-error-boundary';
|
||||
import NavigationBar from './components/NavigationBar';
|
||||
|
||||
import HomePageRoutes from './routes/HomePageRoutes';
|
||||
import About from './routes/About';
|
||||
import AdminRoutes from './routes/AdminRoutes';
|
||||
|
||||
import ScrollToTop from './components/ScrollToTop';
|
||||
import EditorRoutes from './routes/EditorRoutes';
|
||||
import Extension from './routes/Extension';
|
||||
import { useUriListForPermissions } from './hooks/UriListForPermissions';
|
||||
import { PermissionsToCheck, ProcessFile, ProcessModel } from './interfaces';
|
||||
import { usePermissionFetcher } from './hooks/PermissionService';
|
||||
import {
|
||||
ExtensionUiSchema,
|
||||
UiSchemaUxElement,
|
||||
} from './extension_ui_schema_interfaces';
|
||||
import HttpService from './services/HttpService';
|
||||
import { Notification } from './components/Notification';
|
||||
|
||||
type ErrorProps = {
|
||||
error: Error;
|
||||
};
|
||||
|
||||
export function ErrorBoundaryFallback({ error }: ErrorProps) {
|
||||
// This is displayed if the ErrorBoundary catches an error when rendering the form.
|
||||
const { resetBoundary } = useErrorBoundary();
|
||||
|
||||
return (
|
||||
<Notification
|
||||
title="Something Went Wrong. "
|
||||
onClose={() => resetBoundary()}
|
||||
type="error"
|
||||
>
|
||||
<p>
|
||||
We encountered an unexpected error. Please try again. If the problem
|
||||
persists, please contact your administrator.
|
||||
</p>
|
||||
<p>{error.message}</p>
|
||||
<Button onClick={resetBoundary}>Try again</Button>
|
||||
</Notification>
|
||||
);
|
||||
}
|
||||
|
||||
export default function ContainerForExtensions() {
|
||||
const [extensionUxElements, setExtensionNavigationItems] = useState<
|
||||
UiSchemaUxElement[] | null
|
||||
>(null);
|
||||
|
||||
let contentClassName = 'main-site-body-centered';
|
||||
if (window.location.pathname.startsWith('/editor/')) {
|
||||
contentClassName = 'no-center-stuff';
|
||||
}
|
||||
const { targetUris } = useUriListForPermissions();
|
||||
const permissionRequestData: PermissionsToCheck = {
|
||||
[targetUris.extensionListPath]: ['GET'],
|
||||
};
|
||||
const { ability, permissionsLoaded } = usePermissionFetcher(
|
||||
permissionRequestData
|
||||
);
|
||||
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
useEffect(() => {
|
||||
if (!permissionsLoaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
const processExtensionResult = (processModels: ProcessModel[]) => {
|
||||
const eni: UiSchemaUxElement[] = processModels
|
||||
.map((processModel: ProcessModel) => {
|
||||
const extensionUiSchemaFile = processModel.files.find(
|
||||
(file: ProcessFile) => file.name === 'extension_uischema.json'
|
||||
);
|
||||
if (extensionUiSchemaFile && extensionUiSchemaFile.file_contents) {
|
||||
try {
|
||||
const extensionUiSchema: ExtensionUiSchema = JSON.parse(
|
||||
extensionUiSchemaFile.file_contents
|
||||
);
|
||||
if (extensionUiSchema.ux_elements) {
|
||||
return extensionUiSchema.ux_elements;
|
||||
}
|
||||
} catch (jsonParseError: any) {
|
||||
console.error(
|
||||
`Unable to get navigation items for ${processModel.id}`
|
||||
);
|
||||
}
|
||||
}
|
||||
return [] as UiSchemaUxElement[];
|
||||
})
|
||||
.flat();
|
||||
if (eni) {
|
||||
setExtensionNavigationItems(eni);
|
||||
}
|
||||
};
|
||||
|
||||
if (ability.can('GET', targetUris.extensionListPath)) {
|
||||
HttpService.makeCallToBackend({
|
||||
path: targetUris.extensionListPath,
|
||||
successCallback: processExtensionResult,
|
||||
});
|
||||
}
|
||||
}, [targetUris.extensionListPath, permissionsLoaded, ability]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<NavigationBar extensionUxElements={extensionUxElements} />
|
||||
<Content className={contentClassName}>
|
||||
<ScrollToTop />
|
||||
<ErrorBoundary fallback={<h1>Something went wrong.</h1>}>
|
||||
<Routes>
|
||||
<Route path="/*" element={<HomePageRoutes />} />
|
||||
<Route path="/about" element={<About />} />
|
||||
<Route path="/tasks/*" element={<HomePageRoutes />} />
|
||||
<Route
|
||||
path="/admin/*"
|
||||
element={
|
||||
<AdminRoutes extensionUxElements={extensionUxElements} />
|
||||
}
|
||||
/>
|
||||
<Route path="/editor/*" element={<EditorRoutes />} />
|
||||
<Route
|
||||
path="/extensions/:page_identifier"
|
||||
element={<Extension />}
|
||||
/>
|
||||
</Routes>
|
||||
</ErrorBoundary>
|
||||
</Content>
|
||||
</>
|
||||
);
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
type Props = {
|
||||
children?: React.ReactNode;
|
||||
};
|
||||
|
||||
type State = any;
|
||||
|
||||
class ErrorBoundary extends React.Component<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.state = { hasError: false };
|
||||
}
|
||||
|
||||
static getDerivedStateFromError(error: any) {
|
||||
return { hasError: true, error };
|
||||
}
|
||||
|
||||
componentDidCatch(error: any, errorInfo: any) {
|
||||
console.error(error, errorInfo);
|
||||
if (error.constructor.name === 'AggregateError') {
|
||||
console.error(error.message);
|
||||
console.error(error.name);
|
||||
console.error(error.errors);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { hasError } = this.state;
|
||||
const { children } = this.props;
|
||||
|
||||
if (hasError) {
|
||||
return <h1>Something went wrong.</h1>;
|
||||
}
|
||||
|
||||
return children;
|
||||
}
|
||||
}
|
||||
|
||||
export default ErrorBoundary;
|
@ -16,10 +16,35 @@ import {
|
||||
Loading,
|
||||
} from '@carbon/react';
|
||||
import { useDebounce } from 'use-debounce';
|
||||
import { ErrorBoundary, useErrorBoundary } from 'react-error-boundary';
|
||||
import HttpService from '../../services/HttpService';
|
||||
import ExamplesTable from './ExamplesTable';
|
||||
import CustomForm from '../CustomForm';
|
||||
import ErrorBoundary from '../ErrorBoundary';
|
||||
import { Notification } from '../Notification';
|
||||
|
||||
type ErrorProps = {
|
||||
error: Error;
|
||||
};
|
||||
|
||||
function FormErrorFallback({ error }: ErrorProps) {
|
||||
// This is displayed if the ErrorBoundary catches an error when rendering the form.
|
||||
const { resetBoundary } = useErrorBoundary();
|
||||
|
||||
return (
|
||||
<Notification
|
||||
title="Failed to render form. "
|
||||
onClose={() => resetBoundary()}
|
||||
type="error"
|
||||
>
|
||||
<p>
|
||||
The form could not be built with the current schema, UI and data. Please
|
||||
try to correct the issue and try again.
|
||||
</p>
|
||||
<p>{error.message}</p>
|
||||
<Button onClick={resetBoundary}>Try again</Button>
|
||||
</Notification>
|
||||
);
|
||||
}
|
||||
|
||||
type OwnProps = {
|
||||
processModelId: string;
|
||||
@ -246,6 +271,7 @@ export default function ReactFormBuilder({
|
||||
|
||||
function updateDataFromStr(newDataStr: string) {
|
||||
try {
|
||||
setStrFormData(newDataStr);
|
||||
const newData = JSON.parse(newDataStr);
|
||||
setFormData(newData);
|
||||
} catch (e) {
|
||||
@ -461,7 +487,7 @@ export default function ReactFormBuilder({
|
||||
<Column sm={4} md={5} lg={8}>
|
||||
<h2>Form Preview</h2>
|
||||
<div className="error_info_small">{errorMessage}</div>
|
||||
<ErrorBoundary>
|
||||
<ErrorBoundary FallbackComponent={FormErrorFallback}>
|
||||
<CustomForm
|
||||
id="custom_form"
|
||||
formData={formData}
|
||||
|
Loading…
x
Reference in New Issue
Block a user