From 97b6042768108392c44568e998edd139492d7cc6 Mon Sep 17 00:00:00 2001 From: FoxxMD Date: Tue, 7 Jul 2026 03:07:29 +0000 Subject: [PATCH] feat(ui): Implement basic error summary detection --- src/client/components/ActivityDetail.tsx | 75 +++++++++++++++++-- src/client/components/chakraPlayer/Player.tsx | 1 + src/core/tests/utils/fixtures.ts | 3 +- 3 files changed, 70 insertions(+), 9 deletions(-) diff --git a/src/client/components/ActivityDetail.tsx b/src/client/components/ActivityDetail.tsx index d33384eb..27d541dd 100644 --- a/src/client/components/ActivityDetail.tsx +++ b/src/client/components/ActivityDetail.tsx @@ -1,5 +1,5 @@ import React, { ComponentProps, useState, Fragment, useEffect } from "react" -import { Accordion, For, Span, Stack, Text, Box, AbsoluteCenter, Button, Clipboard, Separator, HStack, Flex, Badge, IconButton, Container, Icon, useAccordionItemContext, Skeleton, SkeletonText, Collapsible, BadgeProps } from '@chakra-ui/react'; +import { Accordion, For, Span, Code, Alert, Stack, Text, Box, AbsoluteCenter, Button, Clipboard, Separator, HStack, Flex, Badge, IconButton, Container, Icon, useAccordionItemContext, Skeleton, SkeletonText, Collapsible, BadgeProps } from '@chakra-ui/react'; import { CLIENT_DEAD_QUEUE, ComponentType, QUEUE_NAMES, Second } from "../../core/Atomic"; import { PlayData } from "./PlayData"; import { ErrorAlert } from "./ErrorAlert"; @@ -152,6 +152,71 @@ export const ActivitySummaryFetchable = (props: MarkOptional } +export const ActivityErrorSummary = (props: {activity: ActivityDetailProps['activity']}) => { + const { + activity: { + queueStates = [], + play: { + lifecycle = [], + scrobble, + } = {}, + error, + } + } = props; + if(error !== undefined && error !== null) { + return ; + } + const lifecycleError = lifecycle.find(x => x.error !== undefined && x.error !== null && Object.keys(x.error).length > 0); + if(lifecycleError !== undefined) { + return ( + + + + Error occurred during Play Transform in Stage {lifecycleError.stageType}-{lifecycleError.stageName} in Hook {lifecycleError.hook} from {lifecycleError.source} + + + {lifecycleError.error.message} + Open the Timeline to find the error specifics. + + + + + ); + } + if(scrobble?.error !== undefined) { + return ( + + + + Error occurred during while trying to scrobble + + + {scrobble.error.message} + Open the Timeline to find the error specifics. + + + + + ); + } + if(scrobble?.warnings !== undefined) { + return ( + + + + There were warnings while scrobbling + + + Open the Timeline to find warning specifics. + + + + + ) + } + return null; +} + export const ActivityDetails = (props: ActivityDetailProps) => { const { activity, @@ -175,13 +240,7 @@ export const ActivityDetails = (props: ActivityDetailProps) => { return ( - {/* - - - - - */} - {error !== undefined && error !== null ? : null} + diff --git a/src/client/components/chakraPlayer/Player.tsx b/src/client/components/chakraPlayer/Player.tsx index f959623d..331533d4 100644 --- a/src/client/components/chakraPlayer/Player.tsx +++ b/src/client/components/chakraPlayer/Player.tsx @@ -96,6 +96,7 @@ export const ChakraPlayer = (props: PlayerProps) => { // so clear inside the set state function (bad) using the previous data argument, before returning new value let interval; if(!isNowPlaying && data.status?.calculated === 'playing' && data.position !== undefined && !data.status?.stale && !data.status?.orphaned) { + // eslint-disable-next-line react-hooks/set-state-in-effect setProgressBuffer(data.position); interval = setInterval(() => { setProgressBuffer((oldPosition) => { diff --git a/src/core/tests/utils/fixtures.ts b/src/core/tests/utils/fixtures.ts index 0acd7b42..f021978f 100644 --- a/src/core/tests/utils/fixtures.ts +++ b/src/core/tests/utils/fixtures.ts @@ -12,6 +12,7 @@ import { playToListenPayload } from '../../../backend/common/vendor/listenbrainz import { mergeSimpleError, SimpleError, SkipTransformStageError, StagePrerequisiteError } from '../../../backend/common/errors/MSErrors.js'; import dayjs, { Dayjs } from 'dayjs'; import { TransformHook } from '../../../backend/common/infrastructure/Transform.js'; +import { serializeError } from 'serialize-error'; export interface ScrobbleMatchOptions { match?: boolean @@ -131,7 +132,7 @@ export const playWithLifecycleScrobble = async (play: PlayObject, opts: Scrobble scrobbleRes.payload = playToListenPayload(play); if(error) { - scrobbleRes.error = new Error('Failed to scrobble to client', {cause: new UpstreamError('Client returned a 400 or something')}); + scrobbleRes.error = serializeError(new Error('Failed to scrobble to client', {cause: new UpstreamError('Client returned a 400 or something')})); play.scrobble = scrobbleRes; return play; } -- 2.51.2