Handle graphql exception errors

Extract stack trace from error message and show it as an information that is collapsed by default
This commit is contained in:
schroda
2025-03-22 20:37:43 +01:00
parent 8fac3ce352
commit 072c71c116

View File

@@ -8,12 +8,13 @@
// adopted from: https://github.com/tachiyomiorg/tachiyomi/blob/master/app/src/main/java/eu/kanade/tachiyomi/widget/EmptyView.kt // adopted from: https://github.com/tachiyomiorg/tachiyomi/blob/master/app/src/main/java/eu/kanade/tachiyomi/widget/EmptyView.kt
import { useMemo } from 'react'; import { useMemo, useState } from 'react';
import Typography from '@mui/material/Typography'; import Typography from '@mui/material/Typography';
import { SxProps, Theme } from '@mui/material/styles'; import { SxProps, Theme } from '@mui/material/styles';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import Button from '@mui/material/Button'; import Button from '@mui/material/Button';
import Stack from '@mui/material/Stack'; import Stack from '@mui/material/Stack';
import Collapse from '@mui/material/Collapse';
const ERROR_FACES = ['(・o・;)', 'Σ(ಠ_ಠ)', 'ಥ_ಥ', '(˘・_・˘)', '(; ̄Д ̄)', '(・Д・。']; const ERROR_FACES = ['(・o・;)', 'Σ(ಠ_ಠ)', 'ಥ_ಥ', '(˘・_・˘)', '(; ̄Д ̄)', '(・Д・。'];
@@ -30,6 +31,82 @@ export interface EmptyViewProps {
sx?: SxProps<Theme>; sx?: SxProps<Theme>;
} }
const GRAPHQL_EXCEPTION_MESSAGE_REGEX = /(.*Exception while fetching data \(.*\) : .*)\r\n\r\n(.*)/s;
const extractGraphqlExceptionInfo = (
error: EmptyViewProps['messageExtra'],
): {
isGraphqlException: boolean;
graphqlError?: string;
graphqlStackTrace?: string;
} => {
if (typeof error !== 'string') {
return { isGraphqlException: false };
}
const regexMatch = error.match(GRAPHQL_EXCEPTION_MESSAGE_REGEX);
const isGraphqlException = !!regexMatch;
if (!isGraphqlException) {
return { isGraphqlException: false };
}
const [, message, stackTrace] = regexMatch;
return {
isGraphqlException: true,
graphqlError: message,
graphqlStackTrace: stackTrace,
};
};
const ExtraMessage = ({ messageExtra }: Pick<EmptyViewProps, 'messageExtra'>) => {
const { t } = useTranslation();
const [showFullError, setShowFullError] = useState(false);
const { isGraphqlException, graphqlError, graphqlStackTrace } = extractGraphqlExceptionInfo(messageExtra);
if (!isGraphqlException) {
return (
<Typography variant="body1" sx={{ wordBreak: 'break-word', whiteSpace: 'pre-line', pointerEvents: 'all' }}>
{messageExtra}
</Typography>
);
}
return (
<>
<Stack
sx={{
flexDirection: 'row',
flexWrap: 'wrap',
gap: 1,
justifyContent: 'center',
alignItems: 'center',
}}
>
<Typography
variant="body1"
sx={{ wordBreak: 'break-word', whiteSpace: 'pre-line', pointerEvents: 'all' }}
>
{graphqlError}
</Typography>
<Button variant="text" onClick={() => setShowFullError(!showFullError)} sx={{ pointerEvents: 'all' }}>
{t(showFullError ? 'global.button.show_less' : 'global.button.show_more')}
</Button>
</Stack>
<Collapse in={showFullError}>
<Typography
variant="body1"
sx={{ wordBreak: 'break-word', whiteSpace: 'pre-line', pointerEvents: 'all' }}
>
{graphqlStackTrace}
</Typography>
</Collapse>
</>
);
};
export function EmptyView({ message, messageExtra, retry, noFaces, sx }: EmptyViewProps) { export function EmptyView({ message, messageExtra, retry, noFaces, sx }: EmptyViewProps) {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -57,9 +134,7 @@ export function EmptyView({ message, messageExtra, retry, noFaces, sx }: EmptyVi
<Typography variant="h5" sx={{ wordBreak: 'break-word', whiteSpace: 'pre-line', pointerEvents: 'all' }}> <Typography variant="h5" sx={{ wordBreak: 'break-word', whiteSpace: 'pre-line', pointerEvents: 'all' }}>
{message} {message}
</Typography> </Typography>
<Typography variant="body1" sx={{ wordBreak: 'break-word', whiteSpace: 'pre-line', pointerEvents: 'all' }}> <ExtraMessage messageExtra={messageExtra} />
{messageExtra}
</Typography>
{retry && ( {retry && (
<Button onClick={retry} sx={{ pointerEvents: 'all' }}> <Button onClick={retry} sx={{ pointerEvents: 'all' }}>
{t('global.button.retry')} {t('global.button.retry')}