mirror of
https://github.com/inventree/InvenTree.git
synced 2026-11-10 02:47:49 +00:00
[UI] Copy error button (#13168)
Adds button to copy UI error information
This commit is contained in:
1 parent
2310b54e26
commit
c9ba1169f4
2 files changed
+74
-25
No files matched your search
@@ -1,13 +1,57 @@
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { Alert, Stack, Text } from '@mantine/core';
|
||||
import { Alert, Group, Stack, Text } from '@mantine/core';
|
||||
import { ErrorBoundary, type FallbackRender } from '@sentry/react';
|
||||
import { IconExclamationCircle, IconInfoCircle } from '@tabler/icons-react';
|
||||
import { type ReactNode, useCallback, useState } from 'react';
|
||||
import { CopyButton } from './CopyButton';
|
||||
|
||||
/**
|
||||
* Construct a plain-text error report, suitable for copying into a bug report
|
||||
*/
|
||||
export function formatErrorReport({
|
||||
label,
|
||||
error,
|
||||
componentStack
|
||||
}: {
|
||||
label: string;
|
||||
error: unknown;
|
||||
componentStack?: string;
|
||||
}): string {
|
||||
const lines: string[] = [
|
||||
`Error rendering component: ${label}`,
|
||||
`Timestamp: ${new Date().toISOString()}`,
|
||||
`URL: ${window.location.href}`,
|
||||
`User Agent: ${navigator.userAgent}`,
|
||||
''
|
||||
];
|
||||
|
||||
if (error instanceof Error) {
|
||||
lines.push(`${error.name}: ${error.message}`);
|
||||
if (error.stack) {
|
||||
lines.push('', 'Stack trace:', error.stack);
|
||||
}
|
||||
} else {
|
||||
lines.push(String(error));
|
||||
}
|
||||
|
||||
if (componentStack) {
|
||||
lines.push('', 'Component stack:', componentStack.trim());
|
||||
}
|
||||
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
export function DefaultFallback({
|
||||
title,
|
||||
error
|
||||
}: Readonly<{ title: string; error: string | null }>): ReactNode {
|
||||
error,
|
||||
details
|
||||
}: Readonly<{
|
||||
title: string;
|
||||
error: string | null;
|
||||
details?: string | null;
|
||||
}>): ReactNode {
|
||||
const copyValue = details ?? error;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Alert
|
||||
@@ -26,7 +70,20 @@ export function DefaultFallback({
|
||||
</Alert>
|
||||
{error && (
|
||||
<Alert color='red' icon={<IconInfoCircle />} title={t`Error Details`}>
|
||||
<Text size='sm'>{error}</Text>
|
||||
<Stack gap='xs'>
|
||||
<Text size='sm'>{error}</Text>
|
||||
{copyValue && (
|
||||
<Group justify='flex-start'>
|
||||
<CopyButton
|
||||
value={copyValue}
|
||||
label={t`Copy error information`}
|
||||
tooltip={t`Copy full error information to the clipboard`}
|
||||
color='red'
|
||||
variant='light'
|
||||
/>
|
||||
</Group>
|
||||
)}
|
||||
</Stack>
|
||||
</Alert>
|
||||
)}
|
||||
</>
|
||||
@@ -43,20 +100,28 @@ export function Boundary({
|
||||
fallback?: React.ReactElement<any> | FallbackRender;
|
||||
}>): ReactNode {
|
||||
const [errorMessage, setErrorMessage] = useState<string | null>(null);
|
||||
const [errorDetails, setErrorDetails] = useState<string | null>(null);
|
||||
|
||||
const onError = useCallback(
|
||||
(error: unknown, componentStack: string | undefined, eventId: string) => {
|
||||
console.error(`ERR: Error rendering component: ${label}`);
|
||||
console.error(error);
|
||||
setErrorMessage(error instanceof Error ? error.message : String(error));
|
||||
setErrorDetails(formatErrorReport({ label, error, componentStack }));
|
||||
},
|
||||
[]
|
||||
[label]
|
||||
);
|
||||
|
||||
return (
|
||||
<ErrorBoundary
|
||||
fallback={
|
||||
fallback ?? <DefaultFallback title={label} error={errorMessage} />
|
||||
fallback ?? (
|
||||
<DefaultFallback
|
||||
title={label}
|
||||
error={errorMessage}
|
||||
details={errorDetails}
|
||||
/>
|
||||
)
|
||||
}
|
||||
onError={onError}
|
||||
>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { DefaultFallback } from '@lib/components/Boundary';
|
||||
import { Boundary } from '@lib/components/Boundary';
|
||||
import { StylishText } from '@lib/components/StylishText';
|
||||
import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
|
||||
import { apiUrl } from '@lib/functions/Api';
|
||||
@@ -22,7 +22,6 @@ import {
|
||||
TextInput
|
||||
} from '@mantine/core';
|
||||
import { hideNotification, showNotification } from '@mantine/notifications';
|
||||
import { ErrorBoundary } from '@sentry/react';
|
||||
import {
|
||||
IconAlertCircle,
|
||||
IconAt,
|
||||
@@ -45,16 +44,6 @@ export function SecurityContent() {
|
||||
|
||||
const user = useUserState();
|
||||
|
||||
const [errorMessage, setErrorMessage] = useState<string | null>(null);
|
||||
|
||||
const onError = useCallback(
|
||||
(error: unknown, componentStack: string | undefined, eventId: string) => {
|
||||
console.error(`ERR: Error rendering component: ${error}`);
|
||||
setErrorMessage(error instanceof Error ? error.message : String(error));
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
<Accordion multiple defaultValue={['email']}>
|
||||
@@ -97,14 +86,9 @@ export function SecurityContent() {
|
||||
<StylishText size='lg'>{t`Access Tokens`}</StylishText>
|
||||
</Accordion.Control>
|
||||
<Accordion.Panel>
|
||||
<ErrorBoundary
|
||||
fallback={
|
||||
<DefaultFallback title={'API Table'} error={errorMessage} />
|
||||
}
|
||||
onError={onError}
|
||||
>
|
||||
<Boundary label='API Table'>
|
||||
<ApiTokenTable only_myself />
|
||||
</ErrorBoundary>
|
||||
</Boundary>
|
||||
</Accordion.Panel>
|
||||
</Accordion.Item>
|
||||
{user.isSuperuser() && (
|
||||
|
||||
Reference in new issue
Block a user