mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
[PUI] Error boundary (#7176)
* Create error boundary component - Ref: https://docs.sentry.io/platforms/javascript/guides/react/features/error-boundary/ - Keeps errors container to local components - Will be critical for plugin support * Add boundary to API forms
This commit is contained in:
parent
c7351c4064
commit
108bd28102
44
src/frontend/src/components/Boundary.tsx
Normal file
44
src/frontend/src/components/Boundary.tsx
Normal file
@ -0,0 +1,44 @@
|
||||
import { t } from '@lingui/macro';
|
||||
import { Alert } from '@mantine/core';
|
||||
import { ErrorBoundary, FallbackRender } from '@sentry/react';
|
||||
import { IconExclamationCircle } from '@tabler/icons-react';
|
||||
import { ReactNode, useCallback } from 'react';
|
||||
|
||||
function DefaultFallback({ title }: { title: String }): ReactNode {
|
||||
return (
|
||||
<Alert
|
||||
color="red"
|
||||
icon={<IconExclamationCircle />}
|
||||
title={t`Error rendering component` + `: ${title}`}
|
||||
>
|
||||
{t`An error occurred while rendering this component. Refer to the console for more information.`}
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
|
||||
export function Boundary({
|
||||
children,
|
||||
label,
|
||||
fallback
|
||||
}: {
|
||||
children: ReactNode;
|
||||
label: string;
|
||||
fallback?: React.ReactElement | FallbackRender | undefined;
|
||||
}): ReactNode {
|
||||
const onError = useCallback(
|
||||
(error: Error, componentStack: string, eventId: string) => {
|
||||
console.error(`Error rendering component: ${label}`);
|
||||
console.error(error, componentStack);
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
<ErrorBoundary
|
||||
fallback={fallback ?? <DefaultFallback title={label} />}
|
||||
onError={onError}
|
||||
>
|
||||
{children}
|
||||
</ErrorBoundary>
|
||||
);
|
||||
}
|
@ -36,6 +36,7 @@ import {
|
||||
import { invalidResponse } from '../../functions/notifications';
|
||||
import { getDetailUrl } from '../../functions/urls';
|
||||
import { PathParams } from '../../states/ApiState';
|
||||
import { Boundary } from '../Boundary';
|
||||
import {
|
||||
ApiFormField,
|
||||
ApiFormFieldSet,
|
||||
@ -472,6 +473,7 @@ export function ApiForm({
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
<Boundary label={`ApiForm-${id}`}>
|
||||
{/* Show loading overlay while fetching fields */}
|
||||
{/* zIndex used to force overlay on top of modal header bar */}
|
||||
<LoadingOverlay visible={isLoading} zIndex={1010} />
|
||||
@ -492,6 +494,7 @@ export function ApiForm({
|
||||
)}
|
||||
</Alert>
|
||||
)}
|
||||
<Boundary label={`ApiForm-${id}-PreFormContent`}>
|
||||
{props.preFormContent}
|
||||
{props.preFormSuccess && (
|
||||
<Alert color="green" radius="sm">
|
||||
@ -503,6 +506,8 @@ export function ApiForm({
|
||||
{props.preFormWarning}
|
||||
</Alert>
|
||||
)}
|
||||
</Boundary>
|
||||
<Boundary label={`ApiForm-${id}-FormContent`}>
|
||||
<FormProvider {...form}>
|
||||
<Stack spacing="xs">
|
||||
{!optionsLoading &&
|
||||
@ -516,7 +521,10 @@ export function ApiForm({
|
||||
))}
|
||||
</Stack>
|
||||
</FormProvider>
|
||||
</Boundary>
|
||||
<Boundary label={`ApiForm-${id}-PostFormContent`}>
|
||||
{props.postFormContent}
|
||||
</Boundary>
|
||||
</Stack>
|
||||
</div>
|
||||
</Paper>
|
||||
@ -547,6 +555,7 @@ export function ApiForm({
|
||||
</Button>
|
||||
</Group>
|
||||
</div>
|
||||
</Boundary>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import { Navigate, Outlet, useLocation, useNavigate } from 'react-router-dom';
|
||||
import { getActions } from '../../defaults/actions';
|
||||
import { InvenTreeStyle } from '../../globalStyle';
|
||||
import { useUserState } from '../../states/UserState';
|
||||
import { Boundary } from '../Boundary';
|
||||
import { Footer } from './Footer';
|
||||
import { Header } from './Header';
|
||||
|
||||
@ -29,17 +30,17 @@ export default function LayoutComponent() {
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
|
||||
const defaultactions = getActions(navigate);
|
||||
const [actions, setActions] = useState(defaultactions);
|
||||
const defaultActions = getActions(navigate);
|
||||
const [actions, setActions] = useState(defaultActions);
|
||||
const [customActions, setCustomActions] = useState<boolean>(false);
|
||||
|
||||
function actionsAreChanging(change: []) {
|
||||
if (change.length > defaultactions.length) setCustomActions(true);
|
||||
if (change.length > defaultActions.length) setCustomActions(true);
|
||||
setActions(change);
|
||||
}
|
||||
useEffect(() => {
|
||||
if (customActions) {
|
||||
setActions(defaultactions);
|
||||
setActions(defaultActions);
|
||||
setCustomActions(false);
|
||||
}
|
||||
}, [location]);
|
||||
@ -57,7 +58,10 @@ export default function LayoutComponent() {
|
||||
<Flex direction="column" mih="100vh">
|
||||
<Header />
|
||||
<Container className={classes.layoutContent} size="100%">
|
||||
<Boundary label={'layout'}>
|
||||
<Outlet />
|
||||
</Boundary>
|
||||
{/* </ErrorBoundary> */}
|
||||
</Container>
|
||||
<Space h="xl" />
|
||||
<Footer />
|
||||
|
@ -20,6 +20,7 @@ import {
|
||||
} from 'react-router-dom';
|
||||
|
||||
import { useLocalState } from '../../states/LocalState';
|
||||
import { Boundary } from '../Boundary';
|
||||
import { PlaceholderPanel } from '../items/Placeholder';
|
||||
import { StylishText } from '../items/StylishText';
|
||||
|
||||
@ -103,6 +104,7 @@ function BasePanelGroup({
|
||||
const [expanded, setExpanded] = useState<boolean>(true);
|
||||
|
||||
return (
|
||||
<Boundary label={`PanelGroup-${pageKey}`}>
|
||||
<Paper p="sm" radius="xs" shadow="xs">
|
||||
<Tabs
|
||||
value={panel}
|
||||
@ -167,13 +169,16 @@ function BasePanelGroup({
|
||||
<Divider />
|
||||
</>
|
||||
)}
|
||||
<Boundary label={`PanelContent-${panel.name}`}>
|
||||
{panel.content ?? <PlaceholderPanel />}
|
||||
</Boundary>
|
||||
</Stack>
|
||||
</Tabs.Panel>
|
||||
)
|
||||
)}
|
||||
</Tabs>
|
||||
</Paper>
|
||||
</Boundary>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -36,6 +36,7 @@ import { UserRoles } from '../../enums/Roles';
|
||||
import { apiUrl } from '../../states/ApiState';
|
||||
import { useUserSettingsState } from '../../states/SettingsState';
|
||||
import { useUserState } from '../../states/UserState';
|
||||
import { Boundary } from '../Boundary';
|
||||
import { RenderInstance } from '../render/Instance';
|
||||
import { ModelInformationDict } from '../render/ModelType';
|
||||
|
||||
@ -386,6 +387,7 @@ export function SearchDrawer({
|
||||
</Group>
|
||||
}
|
||||
>
|
||||
<Boundary label="SearchDrawer">
|
||||
{searchQuery.isFetching && (
|
||||
<Center>
|
||||
<Loader />
|
||||
@ -428,6 +430,7 @@ export function SearchDrawer({
|
||||
<Trans>No results available for search query</Trans>
|
||||
</Alert>
|
||||
)}
|
||||
</Boundary>
|
||||
</Drawer>
|
||||
);
|
||||
}
|
||||
|
@ -29,6 +29,7 @@ import { Fragment, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { api } from '../App';
|
||||
import { Boundary } from '../components/Boundary';
|
||||
import { ActionButton } from '../components/buttons/ActionButton';
|
||||
import { ButtonMenu } from '../components/buttons/ButtonMenu';
|
||||
import { ApiFormFieldSet } from '../components/forms/fields/ApiFormField';
|
||||
@ -557,6 +558,7 @@ export function InvenTreeTable<T = any>({
|
||||
onClose={() => setFiltersVisible(false)}
|
||||
/>
|
||||
)}
|
||||
<Boundary label="inventreetable">
|
||||
<Stack spacing="sm">
|
||||
<Group position="apart">
|
||||
<Group position="left" key="custom-actions" spacing={5}>
|
||||
@ -638,7 +640,9 @@ export function InvenTreeTable<T = any>({
|
||||
</Group>
|
||||
<Box pos="relative">
|
||||
<LoadingOverlay
|
||||
visible={tableOptionQuery.isLoading || tableOptionQuery.isFetching}
|
||||
visible={
|
||||
tableOptionQuery.isLoading || tableOptionQuery.isFetching
|
||||
}
|
||||
/>
|
||||
|
||||
<DataTable
|
||||
@ -682,6 +686,7 @@ export function InvenTreeTable<T = any>({
|
||||
/>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Boundary>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user