mirror of
https://github.com/FoxxMD/multi-scrobbler.git
synced 2026-07-09 17:28:28 +00:00
feat(ui): Add queue states to timeline
Some checks are pending
Publish Docker image to Dockerhub / test (push) Waiting to run
Publish Docker image to Dockerhub / Build OCI Images (push) Blocked by required conditions
Publish Docker image to Dockerhub / Build OCI Images-1 (push) Blocked by required conditions
Publish Docker image to Dockerhub / Merge OCI Images and Push (push) Blocked by required conditions
Some checks are pending
Publish Docker image to Dockerhub / test (push) Waiting to run
Publish Docker image to Dockerhub / Build OCI Images (push) Blocked by required conditions
Publish Docker image to Dockerhub / Build OCI Images-1 (push) Blocked by required conditions
Publish Docker image to Dockerhub / Merge OCI Images and Push (push) Blocked by required conditions
This commit is contained in:
parent
e6130eb5eb
commit
3d6e86f258
5 changed files with 146 additions and 37 deletions
|
|
@ -1,27 +1,26 @@
|
|||
import React, { ComponentProps, useState, Fragment } from "react"
|
||||
import { Accordion, Timeline, Icon, Span, Stack, Heading, Card, Box, Tabs, Skeleton, SkeletonCircle, SkeletonText, HTMLChakraProps } from '@chakra-ui/react';
|
||||
import { ComponentType, ErrorLike, JsonPlayObject, PlayActivity } from "../../core/Atomic";
|
||||
import React, { Fragment } from "react"
|
||||
import { Timeline, Icon, Span, Card, Tabs, SkeletonCircle, SkeletonText, HTMLChakraProps } from '@chakra-ui/react';
|
||||
import { CLIENT_DEAD_QUEUE, CLIENT_INGRESS_QUEUE, ComponentType, QUEUE_STATUS_COMPLETED, QUEUE_STATUS_FAILED, QUEUE_STATUS_QUEUED } from "../../core/Atomic";
|
||||
import { PlayData } from "./PlayData";
|
||||
import { ErrorAlert } from "./ErrorAlert";
|
||||
import { IoMdCodeDownload } from "react-icons/io";
|
||||
import { BiWrench } from "react-icons/bi";
|
||||
import { IoMusicalNoteOutline } from "react-icons/io5";
|
||||
import { HiMiniMagnifyingGlass } from "react-icons/hi2";
|
||||
import { TbDatabaseEdit } from "react-icons/tb";
|
||||
import { capitalize } from "../../core/StringUtils";
|
||||
import { shortTodayAwareFormat, todayAwareFormat } from "../../core/TimeUtils";
|
||||
import dayjs from "dayjs";
|
||||
import { ChakraCodeBlockShort, ChakraPlainBlockShort } from "./CodeBlock";
|
||||
import { shortTodayAwareFormat } from "../../core/TimeUtils";
|
||||
import dayjs, { Dayjs } from "dayjs";
|
||||
import { ChakraCodeBlockShort } from "./CodeBlock";
|
||||
import { TransformSteps } from "./TransformSteps";
|
||||
import { ScrobbleMatchResult } from "./ScrobbleMatchResult";
|
||||
import { ScrobbleActionResult } from "./ScrobbleActionResult";
|
||||
import { ExpandCollapse } from "./ExpandCollapse";
|
||||
import { MSCollapsible } from "./MSCollapsible";
|
||||
import { TimelineErrorIcon } from "./timeline/TimelineIcon";
|
||||
import { Muted } from "./Typography";
|
||||
import { PlayApiCommonDetailed } from "../../core/Api";
|
||||
import { PlayApiCommonDetailed, QueueStateApi } from "../../core/Api";
|
||||
import { MSErrorBoundary } from "./ErrorBoundary";
|
||||
import { activityTransformHasIssue, timelineTextFormatting } from "../utils/ComponentUtils";
|
||||
import { CheckIcon, TimelineIndicatorIconQueued } from "./icons/ChakraIcons";
|
||||
|
||||
|
||||
export interface ActivityDetailProps {
|
||||
|
|
@ -53,6 +52,97 @@ const TimelineLoading = () => {
|
|||
);
|
||||
}
|
||||
|
||||
const QueueTimelineItem = (props: {queueState: QueueStateApi, collapsibleOpen: boolean, /*isBefore?: Dayjs, isAfter?: Dayjs*/}) => {
|
||||
const {
|
||||
queueState,
|
||||
collapsibleOpen,
|
||||
} = props;
|
||||
if(queueState.queueStatus === QUEUE_STATUS_QUEUED) {
|
||||
return (
|
||||
<Timeline.Item>
|
||||
<Timeline.Connector>
|
||||
<Timeline.Separator />
|
||||
<Timeline.Indicator>
|
||||
<TimelineIndicatorIconQueued fontSize="lg"/>
|
||||
</Timeline.Indicator>
|
||||
</Timeline.Connector>
|
||||
<Timeline.Content gap="4">
|
||||
<Timeline.Title {...timelineTextFormatting}>
|
||||
{queueState.queueName === CLIENT_DEAD_QUEUE ? 'Dead ' : ''}Queued <Muted>at</Muted> {shortTodayAwareFormat(dayjs(queueState.updatedAt))}
|
||||
</Timeline.Title>
|
||||
</Timeline.Content>
|
||||
</Timeline.Item>
|
||||
);
|
||||
}
|
||||
|
||||
if(queueState.queueStatus === QUEUE_STATUS_COMPLETED) {
|
||||
return (
|
||||
<Timeline.Item>
|
||||
<Timeline.Connector>
|
||||
<Timeline.Separator />
|
||||
<Timeline.Indicator>
|
||||
<CheckIcon fontSize="lg"/>
|
||||
</Timeline.Indicator>
|
||||
</Timeline.Connector>
|
||||
<Timeline.Content gap="4">
|
||||
<Timeline.Title {...timelineTextFormatting}>
|
||||
{queueState.queueName === CLIENT_DEAD_QUEUE ? 'Dead ' : ''}Queue finished processing <Muted>at</Muted> {shortTodayAwareFormat(dayjs(queueState.updatedAt))}
|
||||
</Timeline.Title>
|
||||
</Timeline.Content>
|
||||
</Timeline.Item>
|
||||
);
|
||||
}
|
||||
|
||||
if(queueState.queueStatus === QUEUE_STATUS_FAILED) {
|
||||
let titleContent: React.JSX.Element;
|
||||
const titleProps: HTMLChakraProps<"span"> = queueState.error === undefined ? timelineTextFormatting : {};
|
||||
if(queueState.error === undefined) {
|
||||
titleContent = <>{queueState.queueName === CLIENT_DEAD_QUEUE ? 'Dead ' : ''}Queue failed <Muted>at</Muted> {shortTodayAwareFormat(dayjs(queueState.updatedAt))}</>;
|
||||
} else {
|
||||
titleContent = (
|
||||
<MSCollapsible indicator={<Fragment>{queueState.queueName === CLIENT_DEAD_QUEUE ? 'Dead ' : ''}Queue failed <Muted>at</Muted> {shortTodayAwareFormat(dayjs(queueState.updatedAt))}</Fragment>}
|
||||
defaultOpen={collapsibleOpen}
|
||||
disableUntil="md"
|
||||
timeline>
|
||||
<ErrorAlert error={queueState.error} />
|
||||
</MSCollapsible>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<Timeline.Item>
|
||||
<Timeline.Connector>
|
||||
<Timeline.Separator />
|
||||
<Timeline.Indicator>
|
||||
<CheckIcon fontSize="lg"/>
|
||||
</Timeline.Indicator>
|
||||
</Timeline.Connector>
|
||||
<Timeline.Content gap="4">
|
||||
<Timeline.Title {...titleProps}>
|
||||
{titleContent}
|
||||
</Timeline.Title>
|
||||
</Timeline.Content>
|
||||
</Timeline.Item>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const TimeFiltered = (props: {datetime: Dayjs, isBefore?: Dayjs, isAfter?: Dayjs, children: React.ReactNode}) => {
|
||||
const {
|
||||
isBefore,
|
||||
isAfter,
|
||||
datetime,
|
||||
children
|
||||
} = props;
|
||||
if(isBefore !== undefined && !datetime.isBefore(isBefore)) {
|
||||
return null;
|
||||
}
|
||||
if(isAfter !== undefined && !datetime.isAfter(isAfter)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return children;
|
||||
}
|
||||
|
||||
export const ActivityTimeline = (props: ActivityDetailProps) => {
|
||||
|
||||
if(props.activity === undefined) {
|
||||
|
|
@ -63,7 +153,8 @@ export const ActivityTimeline = (props: ActivityDetailProps) => {
|
|||
activity:{
|
||||
play,
|
||||
input,
|
||||
seenAt
|
||||
seenAt,
|
||||
queueStates,
|
||||
} = {},
|
||||
collapsibleOpen,
|
||||
componentType
|
||||
|
|
@ -82,15 +173,15 @@ export const ActivityTimeline = (props: ActivityDetailProps) => {
|
|||
error,
|
||||
warnings = []
|
||||
} = {},
|
||||
scrobble
|
||||
scrobble,
|
||||
} = play;
|
||||
const {
|
||||
play: original,
|
||||
data: ogInput
|
||||
} = input || {};
|
||||
|
||||
let scrobbleSummary: React.JSX.Element,
|
||||
scrobbleIconProps: Record<string, any> = {
|
||||
let scrobbleSummary: React.JSX.Element;
|
||||
const scrobbleIconProps: Record<string, any> = {
|
||||
color: 'green.focusRing'
|
||||
};
|
||||
if (payload !== undefined) {
|
||||
|
|
@ -116,6 +207,25 @@ export const ActivityTimeline = (props: ActivityDetailProps) => {
|
|||
transformResult = <Span> resulted in <Span color="orange.solid">warnings</Span></Span>;
|
||||
}
|
||||
|
||||
let queueItem: React.JSX.Element | undefined,
|
||||
queueDateTime: Dayjs | undefined,
|
||||
deadItem: React.JSX.Element | undefined,
|
||||
deadDateTime: Dayjs | undefined;
|
||||
const ingressQueue = queueStates.find(x => x.queueName === CLIENT_INGRESS_QUEUE);
|
||||
if(ingressQueue !== undefined) {
|
||||
queueDateTime = dayjs(ingressQueue.updatedAt);
|
||||
queueItem = <QueueTimelineItem queueState={ingressQueue} collapsibleOpen={collapsibleOpen}/>
|
||||
}
|
||||
const deadqueue = queueStates.find(x => x.queueName === CLIENT_DEAD_QUEUE);
|
||||
if(deadqueue !== undefined) {
|
||||
deadDateTime = dayjs(deadqueue.updatedAt);
|
||||
deadItem = <QueueTimelineItem queueState={deadqueue} collapsibleOpen={collapsibleOpen}/>
|
||||
}
|
||||
|
||||
const stepsDateTime = steps.length > 0 ? dayjs(steps[0].createdAt) : undefined;
|
||||
// TODO add actual timestamp to scrobble action so we can make this more accurate
|
||||
const scrobbleDateTime = match !== undefined ? dayjs(match.createdAt) : undefined;
|
||||
|
||||
return (
|
||||
<MSErrorBoundary>
|
||||
<Timeline.Root variant="subtle" size="lg">
|
||||
|
|
@ -157,6 +267,8 @@ export const ActivityTimeline = (props: ActivityDetailProps) => {
|
|||
</Timeline.Title>
|
||||
</Timeline.Content>
|
||||
</Timeline.Item>
|
||||
{queueItem && stepsDateTime && <TimeFiltered datetime={queueDateTime} isBefore={stepsDateTime}>{queueItem}</TimeFiltered>}
|
||||
{deadItem && stepsDateTime && <TimeFiltered datetime={deadDateTime} isBefore={stepsDateTime}>{deadItem}</TimeFiltered>}
|
||||
{steps.length > 0 ? (
|
||||
<Timeline.Item>
|
||||
<Timeline.Connector>
|
||||
|
|
@ -199,6 +311,8 @@ export const ActivityTimeline = (props: ActivityDetailProps) => {
|
|||
</Timeline.Content>
|
||||
</Timeline.Item>
|
||||
)}
|
||||
{queueItem && <TimeFiltered datetime={queueDateTime} isAfter={stepsDateTime} isBefore={scrobbleDateTime}>{queueItem}</TimeFiltered>}
|
||||
{deadItem && <TimeFiltered datetime={deadDateTime} isAfter={stepsDateTime} isBefore={scrobbleDateTime}>{deadItem}</TimeFiltered>}
|
||||
{match !== undefined ? (
|
||||
<Timeline.Item>
|
||||
<Timeline.Connector>
|
||||
|
|
@ -255,6 +369,8 @@ export const ActivityTimeline = (props: ActivityDetailProps) => {
|
|||
</Timeline.Content>
|
||||
</Timeline.Item>
|
||||
) : null}
|
||||
{queueItem && <TimeFiltered datetime={queueDateTime} isAfter={scrobbleDateTime}>{queueItem}</TimeFiltered>}
|
||||
{deadItem && <TimeFiltered datetime={deadDateTime} isAfter={scrobbleDateTime}>{deadItem}</TimeFiltered>}
|
||||
</Timeline.Root>
|
||||
</MSErrorBoundary>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,16 +1,12 @@
|
|||
import { ComponentProps, Fragment } from "react"
|
||||
import { Timeline, Icon, Span, Stack, Heading, Tabs, DataList, Alert, List, HStack } from '@chakra-ui/react';
|
||||
import { PlayData } from "./PlayData";
|
||||
import React, { Fragment } from "react"
|
||||
import { Timeline, Icon, Span, Stack, Alert, List, HStack } from '@chakra-ui/react';
|
||||
import { ErrorAlert } from "./ErrorAlert";
|
||||
import { BiWrench } from "react-icons/bi";
|
||||
import { IoMusicalNoteOutline } from "react-icons/io5";
|
||||
import { HiOutlineCloudUpload, HiOutlineCloudDownload } from "react-icons/hi";
|
||||
import { LuCheck, LuCircleX, LuX } from "react-icons/lu";
|
||||
import { ChakraCodeBlockShort, ChakraPlainBlock, ChakraPlainBlockShort } from "./CodeBlock";
|
||||
import { formatNumber, jdiff } from "../../core/DataUtils";
|
||||
import { ChakraCodeBlockShort } from "./CodeBlock";
|
||||
import { capitalize } from "../../core/StringUtils";
|
||||
import { MSCollapsible, MSCollapsibleExternalProps } from "./MSCollapsible";
|
||||
import { TimelineErrorIcon } from "./timeline/TimelineIcon";
|
||||
import { ScrobbleResult } from "../../core/Atomic";
|
||||
|
||||
export interface ScrobbleActionResultProps extends MSCollapsibleExternalProps {
|
||||
result: ScrobbleResult<string>,
|
||||
|
|
@ -25,7 +21,6 @@ export const ScrobbleActionResult = (props: ScrobbleActionResultProps) => {
|
|||
warnings = [],
|
||||
payload,
|
||||
response,
|
||||
mergedScrobble
|
||||
} = {},
|
||||
scrobbler,
|
||||
collapsibleOpen
|
||||
|
|
|
|||
|
|
@ -1,13 +1,8 @@
|
|||
import { ComponentProps, Fragment } from "react"
|
||||
import { Timeline, Icon, Span, Stack, Heading, Tabs, DataList, List } from '@chakra-ui/react';
|
||||
import { JsonPlayObject, LifecycleStep, PlayMatchResult } from "../../core/Atomic";
|
||||
import { Icon, Tabs, DataList, List } from '@chakra-ui/react';
|
||||
import { PlayMatchResult } from "../../core/Atomic";
|
||||
import { PlayData } from "./PlayData";
|
||||
import { ErrorAlert } from "./ErrorAlert";
|
||||
import { BiWrench } from "react-icons/bi";
|
||||
import { IoMusicalNoteOutline } from "react-icons/io5";
|
||||
import { LuCheck, LuCircleX, LuX } from "react-icons/lu";
|
||||
import { ChakraCodeBlockShort, ChakraPlainBlock, ChakraPlainBlockShort } from "./CodeBlock";
|
||||
import { formatNumber, jdiff } from "../../core/DataUtils";
|
||||
import { LuCheck, LuX } from "react-icons/lu";
|
||||
import { formatNumber } from "../../core/DataUtils";
|
||||
|
||||
export interface ScrobbleMatchResultProps {
|
||||
match: PlayMatchResult<string>
|
||||
|
|
@ -49,7 +44,7 @@ export const ScrobbleMatchResult = (props: ScrobbleMatchResultProps) => {
|
|||
<DataList.ItemValue>
|
||||
<List.Root>
|
||||
{match.breakdowns.map((x, index) => {
|
||||
if (x.includes('Time Detail')) {
|
||||
if (x !== null && x !== undefined && x.includes('Time Detail')) {
|
||||
const sub = x.substring(15).split('|');
|
||||
return (
|
||||
<List.Root ps="5">
|
||||
|
|
|
|||
|
|
@ -23,7 +23,8 @@ import {
|
|||
LuEyeClosed,
|
||||
LuCalendar,
|
||||
LuRefreshCw,
|
||||
LuCopy
|
||||
LuCopy,
|
||||
LuClock
|
||||
} from "react-icons/lu"
|
||||
import { VscDebugRestart } from 'react-icons/vsc';
|
||||
import { MdOutlineFiberNew } from "react-icons/md";
|
||||
|
|
@ -99,8 +100,8 @@ export const MenuButton = makeIconButton(MenuIcon);
|
|||
export const XIcon = LuX;
|
||||
export const XButton = makeIconButton(XIcon);
|
||||
|
||||
export const CheckIcon = LuCheck;
|
||||
export const CheckButton = makeIconButton(CheckIcon);
|
||||
export const CheckIcon = makeChakraIcon(LuCheck);
|
||||
export const CheckButton = makeIconButton(LuCheck);
|
||||
|
||||
export const ExternalLinkIcon = LuExternalLink;
|
||||
export const ExternalLinkButton = makeIconButton(ExternalLinkIcon);
|
||||
|
|
@ -175,4 +176,6 @@ export const CopyIconButton = makeIconButton(LuCopy);
|
|||
|
||||
export const ExclamationCircleIcon = makeChakraIcon(LuCircleAlert);
|
||||
|
||||
export const ExclamationTriangleIcon = makeChakraIcon(LuTriangleAlert);
|
||||
export const ExclamationTriangleIcon = makeChakraIcon(LuTriangleAlert);
|
||||
|
||||
export const TimelineIndicatorIconQueued = makeChakraIcon(LuClock);
|
||||
|
|
@ -5,7 +5,7 @@ import { TextMuted } from "../TextMuted.js";
|
|||
import { isClientType } from "../../../backend/common/infrastructure/Atomic.js";
|
||||
import { capitalize } from "../../../core/StringUtils.js";
|
||||
import { ShortDateDisplay } from "../DateDisplay.js";
|
||||
import { CheckIcon, ChevronLeftButton, ChevronRightButton, EllipsisButton, EyeButton, EyeClosedIcon, EyeIcon, IdleIcon, PowerButton, PowerIcon, PowerOffButton, PowerOffIcon, RetryIcon, XIcon } from "../icons/ChakraIcons.js";
|
||||
import { ChevronLeftButton, EllipsisButton, EyeButton, EyeClosedIcon, EyeIcon, IdleIcon, PowerButton, PowerIcon, PowerOffButton, PowerOffIcon, RetryIcon } from "../icons/ChakraIcons.js";
|
||||
import { ChakraPlayer, ChakraPlayerFetchable, PlayersContainer, PlayersContainerFetchable } from "../chakraPlayer/Player.js";
|
||||
import { InfoTip, ToggleTip, Tooltip } from "../ToggleTip.js";
|
||||
import { QueryFunctionContext, queryOptions, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue