Share some inspector timeline components

This commit is contained in:
Matt Aitken
2024-09-02 09:58:55 +01:00
parent f3a012715c
commit 537f706e92
3 changed files with 76 additions and 241 deletions
@@ -0,0 +1,63 @@
import { ReactNode } from "react";
import { cn } from "~/utils/cn";
type RunTimelineItemProps = {
title: ReactNode;
subtitle?: ReactNode;
state: "complete" | "error";
};
export function RunTimelineEvent({ title, subtitle, state }: RunTimelineItemProps) {
return (
<div className="grid h-5 grid-cols-[1.125rem_1fr] text-sm">
<div className="flex items-center justify-center">
<div
className={cn(
"size-[0.3125rem] rounded-full",
state === "complete" ? "bg-success" : "bg-error"
)}
></div>
</div>
<div className="flex items-baseline justify-between gap-3">
<span className="font-medium text-text-bright">{title}</span>
{subtitle ? <span className="text-xs text-text-dimmed">{subtitle}</span> : null}
</div>
</div>
);
}
type RunTimelineLineProps = {
title: ReactNode;
state: "complete" | "delayed" | "inprogress";
};
export function RunTimelineLine({ title, state }: RunTimelineLineProps) {
return (
<div className="grid h-6 grid-cols-[1.125rem_1fr] text-xs">
<div className="flex items-stretch justify-center">
<div
className={cn(
"w-px",
state === "complete" ? "bg-success" : state === "delayed" ? "bg-text-dimmed" : ""
)}
style={
state === "inprogress"
? {
width: "1px",
height: "100%",
background:
"repeating-linear-gradient(to bottom, #3B82F6 0%, #3B82F6 50%, transparent 50%, transparent 100%)",
backgroundSize: "1px 6px",
maskImage: "linear-gradient(to bottom, black 50%, transparent 100%)",
WebkitMaskImage: "linear-gradient(to bottom, black 50%, transparent 100%)",
}
: undefined
}
></div>
</div>
<div className="flex items-center justify-between gap-3">
<span className="text-text-dimmed">{title}</span>
</div>
</div>
);
}
@@ -1,11 +1,8 @@
import { CheckIcon, ClockIcon, CloudArrowDownIcon, QueueListIcon } from "@heroicons/react/20/solid";
import {
formatDuration,
formatDurationMilliseconds,
nanosecondsToMilliseconds,
TaskRunError,
} from "@trigger.dev/core/v3";
import { ReactNode, useEffect } from "react";
import { Link } from "@remix-run/react";
import { formatDuration, formatDurationMilliseconds, TaskRunError } from "@trigger.dev/core/v3";
import { useEffect } from "react";
import { useTypedFetcher } from "remix-typedjson";
import { ExitIcon } from "~/assets/icons/ExitIcon";
import { CodeBlock } from "~/components/code/CodeBlock";
import { EnvironmentLabel } from "~/components/environments/EnvironmentLabel";
@@ -25,6 +22,7 @@ import { useOrganization } from "~/hooks/useOrganizations";
import { useProject } from "~/hooks/useProject";
import { useSearchParams } from "~/hooks/useSearchParam";
import { RawRun } from "~/hooks/useSyncTraceRuns";
import { loader } from "~/routes/resources.runs.$runParam";
import { cn } from "~/utils/cn";
import { formatCurrencyAccurate } from "~/utils/numberFormatter";
import {
@@ -35,15 +33,12 @@ import {
v3SchedulePath,
v3TraceSpanPath,
} from "~/utils/pathBuilder";
import { TraceSpan } from "~/utils/taskEvent";
import { SpanLink } from "~/v3/eventRepository.server";
import { isFinalRunStatus } from "~/v3/taskStatus";
import { TaskRunStatusCombo } from "./TaskRunStatus";
import { useTypedFetcher } from "remix-typedjson";
import { RunInspectorData } from "~/routes/resources.runs.$runParam";
import { loader } from "~/routes/resources.runs.$runParam";
import { Link } from "@remix-run/react";
import { RunTimelineEvent, RunTimelineLine } from "./InspectorTimeline";
import { RunTag } from "./RunTag";
import { TraceSpan } from "~/utils/taskEvent";
import { TaskRunStatusCombo } from "./TaskRunStatus";
/**
* The RunInspector displays live information about a run.
@@ -529,67 +524,6 @@ function RunTimeline({ run }: { run: RawRun }) {
);
}
type RunTimelineItemProps = {
title: ReactNode;
subtitle?: ReactNode;
state: "complete" | "error";
};
function RunTimelineEvent({ title, subtitle, state }: RunTimelineItemProps) {
return (
<div className="grid h-5 grid-cols-[1.125rem_1fr] text-sm">
<div className="flex items-center justify-center">
<div
className={cn(
"size-[0.3125rem] rounded-full",
state === "complete" ? "bg-success" : "bg-error"
)}
></div>
</div>
<div className="flex items-baseline justify-between gap-3">
<span className="font-medium text-text-bright">{title}</span>
{subtitle ? <span className="text-xs text-text-dimmed">{subtitle}</span> : null}
</div>
</div>
);
}
type RunTimelineLineProps = {
title: ReactNode;
state: "complete" | "delayed" | "inprogress";
};
function RunTimelineLine({ title, state }: RunTimelineLineProps) {
return (
<div className="grid h-6 grid-cols-[1.125rem_1fr] text-xs">
<div className="flex items-stretch justify-center">
<div
className={cn(
"w-px",
state === "complete" ? "bg-success" : state === "delayed" ? "bg-text-dimmed" : ""
)}
style={
state === "inprogress"
? {
width: "1px",
height: "100%",
background:
"repeating-linear-gradient(to bottom, #3B82F6 0%, #3B82F6 50%, transparent 50%, transparent 100%)",
backgroundSize: "1px 6px",
maskImage: "linear-gradient(to bottom, black 50%, transparent 100%)",
WebkitMaskImage: "linear-gradient(to bottom, black 50%, transparent 100%)",
}
: undefined
}
></div>
</div>
<div className="flex items-center justify-between gap-3">
<span className="text-text-dimmed">{title}</span>
</div>
</div>
);
}
function RunError({ error }: { error: TaskRunError }) {
switch (error.type) {
case "STRING_ERROR":
@@ -673,91 +607,6 @@ function PacketDisplay({
}
}
type TimelineProps = {
startTime: Date;
duration: number;
inProgress: boolean;
isError: boolean;
};
type TimelineState = "error" | "pending" | "complete";
function SpanTimeline({ startTime, duration, inProgress, isError }: TimelineProps) {
const state = isError ? "error" : inProgress ? "pending" : "complete";
return (
<>
<div className="min-w-fit max-w-80">
<RunTimelineEvent
title="Started"
subtitle={<DateTimeAccurate date={startTime} />}
state="complete"
/>
{state === "pending" ? (
<RunTimelineLine
title={
<span className="flex items-center gap-1">
<Spinner className="size-4" />
<span>
<LiveTimer startTime={startTime} />
</span>
</span>
}
state={"inprogress"}
/>
) : (
<>
<RunTimelineLine
title={formatDuration(
startTime,
new Date(startTime.getTime() + nanosecondsToMilliseconds(duration))
)}
state={"complete"}
/>
<RunTimelineEvent
title="Finished"
subtitle={
<DateTimeAccurate
date={new Date(startTime.getTime() + nanosecondsToMilliseconds(duration))}
/>
}
state={isError ? "error" : "complete"}
/>
</>
)}
</div>
</>
);
}
function VerticalBar({ state }: { state: TimelineState }) {
return <div className={cn("h-3 w-0.75 rounded-full", classNameForState(state))}></div>;
}
function DottedLine() {
return (
<div className="flex h-0.75 flex-1 items-center justify-evenly">
<div className="h-0.75 w-0.75 bg-pending" />
<div className="h-0.75 w-0.75 bg-pending" />
<div className="h-0.75 w-0.75 bg-pending" />
<div className="h-0.75 w-0.75 bg-pending" />
</div>
);
}
function classNameForState(state: TimelineState) {
switch (state) {
case "pending": {
return "bg-pending";
}
case "complete": {
return "bg-success";
}
case "error": {
return "bg-error";
}
}
}
function SpanLinkElement({ link }: { link: SpanLink }) {
const organization = useOrganization();
const project = useProject();
@@ -1,16 +1,12 @@
import { formatDuration, nanosecondsToMilliseconds } from "@trigger.dev/core/v3";
import { ReactNode } from "react";
import { ExitIcon } from "~/assets/icons/ExitIcon";
import { CodeBlock } from "~/components/code/CodeBlock";
import { Button } from "~/components/primitives/Buttons";
import { DateTimeAccurate } from "~/components/primitives/DateTime";
import { Header2 } from "~/components/primitives/Headers";
import * as Property from "~/components/primitives/PropertyTable";
import { Spinner } from "~/components/primitives/Spinner";
import { TabButton, TabContainer } from "~/components/primitives/Tabs";
import { TextLink } from "~/components/primitives/TextLink";
import { InfoIconTooltip, SimpleTooltip } from "~/components/primitives/Tooltip";
import { LiveTimer } from "~/components/runs/v3/LiveTimer";
import { RunIcon } from "~/components/runs/v3/RunIcon";
import { SpanEvents } from "~/components/runs/v3/SpanEvents";
import { SpanTitle } from "~/components/runs/v3/SpanTitle";
@@ -22,6 +18,10 @@ import { cn } from "~/utils/cn";
import { v3RunPath, v3RunsPath, v3TraceSpanPath } from "~/utils/pathBuilder";
import { TraceSpan } from "~/utils/taskEvent";
import { SpanLink } from "~/v3/eventRepository.server";
import { RunTimelineEvent, RunTimelineLine } from "./InspectorTimeline";
import { Spinner } from "~/components/primitives/Spinner";
import { LiveTimer } from "./LiveTimer";
import { formatDuration, nanosecondsToMilliseconds } from "@trigger.dev/core/v3";
export function SpanInspector({
span,
@@ -234,67 +234,6 @@ export function SpanInspector({
);
}
type RunTimelineItemProps = {
title: ReactNode;
subtitle?: ReactNode;
state: "complete" | "error";
};
function RunTimelineEvent({ title, subtitle, state }: RunTimelineItemProps) {
return (
<div className="grid h-5 grid-cols-[1.125rem_1fr] text-sm">
<div className="flex items-center justify-center">
<div
className={cn(
"size-[0.3125rem] rounded-full",
state === "complete" ? "bg-success" : "bg-error"
)}
></div>
</div>
<div className="flex items-baseline justify-between gap-3">
<span className="font-medium text-text-bright">{title}</span>
{subtitle ? <span className="text-xs text-text-dimmed">{subtitle}</span> : null}
</div>
</div>
);
}
type RunTimelineLineProps = {
title: ReactNode;
state: "complete" | "delayed" | "inprogress";
};
function RunTimelineLine({ title, state }: RunTimelineLineProps) {
return (
<div className="grid h-6 grid-cols-[1.125rem_1fr] text-xs">
<div className="flex items-stretch justify-center">
<div
className={cn(
"w-px",
state === "complete" ? "bg-success" : state === "delayed" ? "bg-text-dimmed" : ""
)}
style={
state === "inprogress"
? {
width: "1px",
height: "100%",
background:
"repeating-linear-gradient(to bottom, #3B82F6 0%, #3B82F6 50%, transparent 50%, transparent 100%)",
backgroundSize: "1px 6px",
maskImage: "linear-gradient(to bottom, black 50%, transparent 100%)",
WebkitMaskImage: "linear-gradient(to bottom, black 50%, transparent 100%)",
}
: undefined
}
></div>
</div>
<div className="flex items-center justify-between gap-3">
<span className="text-text-dimmed">{title}</span>
</div>
</div>
);
}
type TimelineProps = {
startTime: Date;
duration: number;
@@ -302,9 +241,7 @@ type TimelineProps = {
isError: boolean;
};
type TimelineState = "error" | "pending" | "complete";
function SpanTimeline({ startTime, duration, inProgress, isError }: TimelineProps) {
export function SpanTimeline({ startTime, duration, inProgress, isError }: TimelineProps) {
const state = isError ? "error" : inProgress ? "pending" : "complete";
return (
<>
@@ -351,20 +288,6 @@ function SpanTimeline({ startTime, duration, inProgress, isError }: TimelineProp
);
}
function classNameForState(state: TimelineState) {
switch (state) {
case "pending": {
return "bg-pending";
}
case "complete": {
return "bg-success";
}
case "error": {
return "bg-error";
}
}
}
function SpanLinkElement({ link }: { link: SpanLink }) {
const organization = useOrganization();
const project = useProject();