diff --git a/src/client/components/ActivityTimeline.tsx b/src/client/components/ActivityTimeline.tsx
index 3576a523..28bbf6d4 100644
--- a/src/client/components/ActivityTimeline.tsx
+++ b/src/client/components/ActivityTimeline.tsx
@@ -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 (
+
+
+
+
+
+
+
+
+
+ {queueState.queueName === CLIENT_DEAD_QUEUE ? 'Dead ' : ''}Queued at {shortTodayAwareFormat(dayjs(queueState.updatedAt))}
+
+
+
+ );
+ }
+
+ if(queueState.queueStatus === QUEUE_STATUS_COMPLETED) {
+ return (
+
+
+
+
+
+
+
+
+
+ {queueState.queueName === CLIENT_DEAD_QUEUE ? 'Dead ' : ''}Queue finished processing at {shortTodayAwareFormat(dayjs(queueState.updatedAt))}
+
+
+
+ );
+ }
+
+ 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 at {shortTodayAwareFormat(dayjs(queueState.updatedAt))}>;
+ } else {
+ titleContent = (
+ {queueState.queueName === CLIENT_DEAD_QUEUE ? 'Dead ' : ''}Queue failed at {shortTodayAwareFormat(dayjs(queueState.updatedAt))}}
+ defaultOpen={collapsibleOpen}
+ disableUntil="md"
+ timeline>
+
+
+ )
+ }
+ return (
+
+
+
+
+
+
+
+
+
+ {titleContent}
+
+
+
+ );
+ }
+}
+
+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 = {
+ let scrobbleSummary: React.JSX.Element;
+ const scrobbleIconProps: Record = {
color: 'green.focusRing'
};
if (payload !== undefined) {
@@ -116,6 +207,25 @@ export const ActivityTimeline = (props: ActivityDetailProps) => {
transformResult = resulted in warnings;
}
+ 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 =
+ }
+ const deadqueue = queueStates.find(x => x.queueName === CLIENT_DEAD_QUEUE);
+ if(deadqueue !== undefined) {
+ deadDateTime = dayjs(deadqueue.updatedAt);
+ deadItem =
+ }
+
+ 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 (
@@ -157,6 +267,8 @@ export const ActivityTimeline = (props: ActivityDetailProps) => {
+ {queueItem && stepsDateTime && {queueItem}}
+ {deadItem && stepsDateTime && {deadItem}}
{steps.length > 0 ? (
@@ -199,6 +311,8 @@ export const ActivityTimeline = (props: ActivityDetailProps) => {
)}
+ {queueItem && {queueItem}}
+ {deadItem && {deadItem}}
{match !== undefined ? (
@@ -255,6 +369,8 @@ export const ActivityTimeline = (props: ActivityDetailProps) => {
) : null}
+ {queueItem && {queueItem}}
+ {deadItem && {deadItem}}
)
diff --git a/src/client/components/ScrobbleActionResult.tsx b/src/client/components/ScrobbleActionResult.tsx
index 88b1c0e7..c7074ee0 100644
--- a/src/client/components/ScrobbleActionResult.tsx
+++ b/src/client/components/ScrobbleActionResult.tsx
@@ -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,
@@ -25,7 +21,6 @@ export const ScrobbleActionResult = (props: ScrobbleActionResultProps) => {
warnings = [],
payload,
response,
- mergedScrobble
} = {},
scrobbler,
collapsibleOpen
diff --git a/src/client/components/ScrobbleMatchResult.tsx b/src/client/components/ScrobbleMatchResult.tsx
index e455df46..81efdc2d 100644
--- a/src/client/components/ScrobbleMatchResult.tsx
+++ b/src/client/components/ScrobbleMatchResult.tsx
@@ -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
@@ -49,7 +44,7 @@ export const ScrobbleMatchResult = (props: ScrobbleMatchResultProps) => {
{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 (
diff --git a/src/client/components/icons/ChakraIcons.tsx b/src/client/components/icons/ChakraIcons.tsx
index 04a2d388..16f5192c 100644
--- a/src/client/components/icons/ChakraIcons.tsx
+++ b/src/client/components/icons/ChakraIcons.tsx
@@ -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);
\ No newline at end of file
+export const ExclamationTriangleIcon = makeChakraIcon(LuTriangleAlert);
+
+export const TimelineIndicatorIconQueued = makeChakraIcon(LuClock);
\ No newline at end of file
diff --git a/src/client/components/msComponent/MSComponentDetailed.tsx b/src/client/components/msComponent/MSComponentDetailed.tsx
index 127f7798..7ec5d09e 100644
--- a/src/client/components/msComponent/MSComponentDetailed.tsx
+++ b/src/client/components/msComponent/MSComponentDetailed.tsx
@@ -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';