Improve graphql errors display in toast

This commit is contained in:
schroda
2025-07-27 03:42:29 +02:00
parent 20ca6d6cda
commit 56eabae9ae
3 changed files with 38 additions and 33 deletions

View File

@@ -7,6 +7,7 @@
*/
import { ApolloError } from '@apollo/client/errors';
import { ReactNode } from 'react';
export const jsonSaveParse = <T = any>(...args: Parameters<typeof JSON.parse>): T | null => {
try {
@@ -41,3 +42,30 @@ export const getValueFromObject = <T>(obj: Record<string, any>, key: string): T
export const coerceIn = (value: number, min: number, max: number): number => Math.max(Math.min(value, max), min);
export const noOp = () => {};
const GRAPHQL_EXCEPTION_MESSAGE_REGEX = /(.*Exception while fetching data \(.*\) : .*)\r\n\r\n(.*)/s;
export const extractGraphqlExceptionInfo = (
error: ReactNode | string,
): {
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,
};
};

View File

@@ -8,13 +8,14 @@
// adopted from: https://github.com/tachiyomiorg/tachiyomi/blob/master/app/src/main/java/eu/kanade/tachiyomi/widget/EmptyView.kt
import { useMemo, useState, type JSX } from 'react';
import { type JSX, useMemo, useState } from 'react';
import Typography from '@mui/material/Typography';
import { SxProps, Theme } from '@mui/material/styles';
import { useTranslation } from 'react-i18next';
import Button from '@mui/material/Button';
import Stack from '@mui/material/Stack';
import Collapse from '@mui/material/Collapse';
import { extractGraphqlExceptionInfo } from '@/lib/HelperFunctions.ts';
const ERROR_FACES = ['(・o・;)', 'Σ(ಠ_ಠ)', 'ಥ_ಥ', '(˘・_・˘)', '(; ̄Д ̄)', '(・Д・。'];
@@ -31,34 +32,6 @@ export interface EmptyViewProps {
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();

View File

@@ -17,6 +17,7 @@ import { awaitConfirmation } from '@/modules/core/utils/AwaitableDialog.tsx';
import { TranslationKey } from '@/Base.types.ts';
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
import { MediaQuery } from '@/modules/core/utils/MediaQuery.tsx';
import { extractGraphqlExceptionInfo } from '@/lib/HelperFunctions.ts';
const MAX_DESCRIPTION_LENGTH = 200;
@@ -49,10 +50,13 @@ export const SnackbarWithDescription = memo(
const severity = variant === 'default' ? 'info' : variant;
const finalAction = typeof action === 'function' ? action(id) : action;
const isDescriptionTooLong = (description?.length ?? 0) > MAX_DESCRIPTION_LENGTH;
const { isGraphqlException, graphqlError, graphqlStackTrace } = extractGraphqlExceptionInfo(description);
const finalDescription = isGraphqlException ? graphqlError : description;
const isDescriptionTooLong = (finalDescription?.length ?? 0) > MAX_DESCRIPTION_LENGTH;
const actualDescription = isDescriptionTooLong
? description?.slice(0, MAX_DESCRIPTION_LENGTH)
: description;
? finalDescription?.slice(0, MAX_DESCRIPTION_LENGTH)
: finalDescription;
const TitleComponent = actualDescription?.length ? AlertTitle : Fragment;
@@ -79,7 +83,7 @@ export const SnackbarWithDescription = memo(
>
<TitleComponent>{message}</TitleComponent>
{actualDescription}
{isDescriptionTooLong ? (
{isDescriptionTooLong || (isGraphqlException && graphqlStackTrace) ? (
<Button
onClick={() => {
awaitConfirmation({