add duration to workflow block timeline (#3107)

This commit is contained in:
Jonathan Dobson
2025-08-05 16:56:29 -04:00
committed by GitHub
parent 9c8ed3a701
commit 9fb6eb942f
3 changed files with 58 additions and 14 deletions

View File

@@ -50,6 +50,7 @@ export type WorkflowRunBlock = {
wait_sec?: number | null; wait_sec?: number | null;
created_at: string; created_at: string;
modified_at: string; modified_at: string;
duration: number | null;
// for loop block itself // for loop block itself
loop_values: Array<unknown> | null; loop_values: Array<unknown> | null;

View File

@@ -47,3 +47,33 @@ export const getInitialValues = (
return iv as Record<string, unknown>; return iv as Record<string, unknown>;
}; };
export interface Duration {
hour: number;
minute: number;
second: number;
}
export const toDuration = (seconds: number): Duration => {
let minutes = Math.floor(seconds / 60);
let hours = Math.floor(minutes / 60);
seconds = seconds % 60;
minutes = minutes % 60;
hours = hours % 24;
return {
hour: Math.floor(hours),
minute: Math.floor(minutes),
second: Math.floor(seconds),
};
};
export const formatDuration = (duration: Duration): string => {
if (duration.hour) {
return `${duration.hour}h ${duration.minute}m ${duration.second}s`;
} else if (duration.minute) {
return `${duration.minute}m ${duration.second}s`;
} else {
return `${duration.second}s`;
}
};

View File

@@ -25,6 +25,7 @@ import { isTaskVariantBlock } from "../types/workflowTypes";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { useCallback } from "react"; import { useCallback } from "react";
import { Status } from "@/api/types"; import { Status } from "@/api/types";
import { formatDuration, toDuration } from "@/routes/workflows/utils";
import { ThoughtCard } from "./ThoughtCard"; import { ThoughtCard } from "./ThoughtCard";
import { ObserverThought } from "../types/workflowRunTypes"; import { ObserverThought } from "../types/workflowRunTypes";
type Props = { type Props = {
@@ -85,6 +86,9 @@ function WorkflowRunTimelineBlockItem({
block.status === Status.TimedOut || block.status === Status.TimedOut ||
block.status === Status.Canceled); block.status === Status.Canceled);
const duration =
block.duration !== null ? formatDuration(toDuration(block.duration)) : null;
return ( return (
<div <div
className={cn( className={cn(
@@ -115,7 +119,9 @@ function WorkflowRunTimelineBlockItem({
<span className="text-sm"> <span className="text-sm">
{workflowBlockTitle[block.block_type]} {workflowBlockTitle[block.block_type]}
</span> </span>
<span className="text-xs text-slate-400">{block.label}</span> <span className="flex gap-2 text-xs text-slate-400">
{block.label}
</span>
</div> </div>
</div> </div>
<div className="flex gap-2"> <div className="flex gap-2">
@@ -129,19 +135,26 @@ function WorkflowRunTimelineBlockItem({
<CheckCircledIcon className="size-4 text-success" /> <CheckCircledIcon className="size-4 text-success" />
</div> </div>
)} )}
<div className="flex gap-1 self-start rounded bg-slate-elevation5 px-2 py-1"> <div className="flex flex-col items-end gap-[1px]">
{showDiagnosticLink ? ( <div className="flex gap-1 self-start rounded bg-slate-elevation5 px-2 py-1">
<Link to={`/tasks/${block.task_id}/diagnostics`}> {showDiagnosticLink ? (
<div className="flex gap-1"> <Link to={`/tasks/${block.task_id}/diagnostics`}>
<ExternalLinkIcon className="size-4" /> <div className="flex gap-1">
<span className="text-xs">Diagnostics</span> <ExternalLinkIcon className="size-4" />
</div> <span className="text-xs">Diagnostics</span>
</Link> </div>
) : ( </Link>
<> ) : (
<CubeIcon className="size-4" /> <>
<span className="text-xs">Block</span> <CubeIcon className="size-4" />
</> <span className="text-xs">Block</span>
</>
)}
</div>
{duration && (
<div className="pr-[5px] text-xs text-[#00ecff]">
{duration}
</div>
)} )}
</div> </div>
</div> </div>