Improve graphql errors display in toast
This commit is contained in:
@@ -7,6 +7,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { ApolloError } from '@apollo/client/errors';
|
import { ApolloError } from '@apollo/client/errors';
|
||||||
|
import { ReactNode } from 'react';
|
||||||
|
|
||||||
export const jsonSaveParse = <T = any>(...args: Parameters<typeof JSON.parse>): T | null => {
|
export const jsonSaveParse = <T = any>(...args: Parameters<typeof JSON.parse>): T | null => {
|
||||||
try {
|
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 coerceIn = (value: number, min: number, max: number): number => Math.max(Math.min(value, max), min);
|
||||||
|
|
||||||
export const noOp = () => {};
|
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,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|||||||
@@ -8,13 +8,14 @@
|
|||||||
|
|
||||||
// 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, useState, type JSX } from 'react';
|
import { type JSX, 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';
|
import Collapse from '@mui/material/Collapse';
|
||||||
|
import { extractGraphqlExceptionInfo } from '@/lib/HelperFunctions.ts';
|
||||||
|
|
||||||
const ERROR_FACES = ['(・o・;)', 'Σ(ಠ_ಠ)', 'ಥ_ಥ', '(˘・_・˘)', '(; ̄Д ̄)', '(・Д・。'];
|
const ERROR_FACES = ['(・o・;)', 'Σ(ಠ_ಠ)', 'ಥ_ಥ', '(˘・_・˘)', '(; ̄Д ̄)', '(・Д・。'];
|
||||||
|
|
||||||
@@ -31,34 +32,6 @@ 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 ExtraMessage = ({ messageExtra }: Pick<EmptyViewProps, 'messageExtra'>) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import { awaitConfirmation } from '@/modules/core/utils/AwaitableDialog.tsx';
|
|||||||
import { TranslationKey } from '@/Base.types.ts';
|
import { TranslationKey } from '@/Base.types.ts';
|
||||||
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
|
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
|
||||||
import { MediaQuery } from '@/modules/core/utils/MediaQuery.tsx';
|
import { MediaQuery } from '@/modules/core/utils/MediaQuery.tsx';
|
||||||
|
import { extractGraphqlExceptionInfo } from '@/lib/HelperFunctions.ts';
|
||||||
|
|
||||||
const MAX_DESCRIPTION_LENGTH = 200;
|
const MAX_DESCRIPTION_LENGTH = 200;
|
||||||
|
|
||||||
@@ -49,10 +50,13 @@ export const SnackbarWithDescription = memo(
|
|||||||
const severity = variant === 'default' ? 'info' : variant;
|
const severity = variant === 'default' ? 'info' : variant;
|
||||||
const finalAction = typeof action === 'function' ? action(id) : action;
|
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
|
const actualDescription = isDescriptionTooLong
|
||||||
? description?.slice(0, MAX_DESCRIPTION_LENGTH)
|
? finalDescription?.slice(0, MAX_DESCRIPTION_LENGTH)
|
||||||
: description;
|
: finalDescription;
|
||||||
|
|
||||||
const TitleComponent = actualDescription?.length ? AlertTitle : Fragment;
|
const TitleComponent = actualDescription?.length ? AlertTitle : Fragment;
|
||||||
|
|
||||||
@@ -79,7 +83,7 @@ export const SnackbarWithDescription = memo(
|
|||||||
>
|
>
|
||||||
<TitleComponent>{message}</TitleComponent>
|
<TitleComponent>{message}</TitleComponent>
|
||||||
{actualDescription}
|
{actualDescription}
|
||||||
{isDescriptionTooLong ? (
|
{isDescriptionTooLong || (isGraphqlException && graphqlStackTrace) ? (
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
awaitConfirmation({
|
awaitConfirmation({
|
||||||
|
|||||||
Reference in New Issue
Block a user