From 9fb6eb942f554eafdb9d02f539f8488c11badd8f Mon Sep 17 00:00:00 2001 From: Jonathan Dobson Date: Tue, 5 Aug 2025 16:56:29 -0400 Subject: [PATCH] add duration to workflow block timeline (#3107) --- .../workflows/types/workflowRunTypes.ts | 1 + .../src/routes/workflows/utils.ts | 30 ++++++++++++++ .../WorkflowRunTimelineBlockItem.tsx | 41 ++++++++++++------- 3 files changed, 58 insertions(+), 14 deletions(-) diff --git a/skyvern-frontend/src/routes/workflows/types/workflowRunTypes.ts b/skyvern-frontend/src/routes/workflows/types/workflowRunTypes.ts index 3eaf897d..b1df28b0 100644 --- a/skyvern-frontend/src/routes/workflows/types/workflowRunTypes.ts +++ b/skyvern-frontend/src/routes/workflows/types/workflowRunTypes.ts @@ -50,6 +50,7 @@ export type WorkflowRunBlock = { wait_sec?: number | null; created_at: string; modified_at: string; + duration: number | null; // for loop block itself loop_values: Array | null; diff --git a/skyvern-frontend/src/routes/workflows/utils.ts b/skyvern-frontend/src/routes/workflows/utils.ts index 1ce27ff3..456eec52 100644 --- a/skyvern-frontend/src/routes/workflows/utils.ts +++ b/skyvern-frontend/src/routes/workflows/utils.ts @@ -47,3 +47,33 @@ export const getInitialValues = ( return iv as Record; }; + +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`; + } +}; diff --git a/skyvern-frontend/src/routes/workflows/workflowRun/WorkflowRunTimelineBlockItem.tsx b/skyvern-frontend/src/routes/workflows/workflowRun/WorkflowRunTimelineBlockItem.tsx index 2ec233f2..d64f81b0 100644 --- a/skyvern-frontend/src/routes/workflows/workflowRun/WorkflowRunTimelineBlockItem.tsx +++ b/skyvern-frontend/src/routes/workflows/workflowRun/WorkflowRunTimelineBlockItem.tsx @@ -25,6 +25,7 @@ import { isTaskVariantBlock } from "../types/workflowTypes"; import { Link } from "react-router-dom"; import { useCallback } from "react"; import { Status } from "@/api/types"; +import { formatDuration, toDuration } from "@/routes/workflows/utils"; import { ThoughtCard } from "./ThoughtCard"; import { ObserverThought } from "../types/workflowRunTypes"; type Props = { @@ -85,6 +86,9 @@ function WorkflowRunTimelineBlockItem({ block.status === Status.TimedOut || block.status === Status.Canceled); + const duration = + block.duration !== null ? formatDuration(toDuration(block.duration)) : null; + return (
{workflowBlockTitle[block.block_type]} - {block.label} + + {block.label} +
@@ -129,19 +135,26 @@ function WorkflowRunTimelineBlockItem({
)} -
- {showDiagnosticLink ? ( - -
- - Diagnostics -
- - ) : ( - <> - - Block - +
+
+ {showDiagnosticLink ? ( + +
+ + Diagnostics +
+ + ) : ( + <> + + Block + + )} +
+ {duration && ( +
+ {duration} +
)}