/** * Shared status presentation for the execution monitor: NodeStatus / * RunStatus → StatusBadge config, plus a human duration formatter. * * Kept as a tiny pure module so both ExecutionTimeline and * WorkflowRunViewer/ExecNodeDetails render statuses identically and the * mapping stays test-covered in one place. */ import type { StatusBadgeConfig } from '../badge/StatusBadge'; import type { NodeStatus, RunStatus } from './types'; /** NodeStatus → badge label + tailwind color classes. */ export const NODE_STATUS_BADGE: Record = { idle: { label: 'Idle', className: 'bg-gray-100 text-gray-600 border border-gray-200', }, pending: { label: 'Pending', className: 'bg-amber-100 text-amber-800 border border-amber-200', }, running: { label: 'Running', className: 'bg-blue-100 text-blue-800 border border-blue-200', }, success: { label: 'Success', className: 'bg-green-100 text-green-800 border border-green-200', }, error: { label: 'Error', className: 'bg-red-100 text-red-800 border border-red-200', }, skipped: { label: 'Skipped', className: 'bg-gray-100 text-gray-500 border border-gray-200', }, }; /** RunStatus → badge label + tailwind color classes. */ export const RUN_STATUS_BADGE: Record = { pending: { label: 'Pending', className: 'bg-amber-100 text-amber-800 border border-amber-200', }, running: { label: 'Running', className: 'bg-blue-100 text-blue-800 border border-blue-200', }, completed: { label: 'Completed', className: 'bg-green-100 text-green-800 border border-green-200', }, failed: { label: 'Failed', className: 'bg-red-100 text-red-800 border border-red-200', }, cancelled: { label: 'Cancelled', className: 'bg-gray-100 text-gray-600 border border-gray-200', }, waiting: { label: 'Waiting', className: 'bg-purple-100 text-purple-800 border border-purple-200', }, }; /** Statuses that are still in flight (warrant a live indicator). */ const NON_TERMINAL: ReadonlySet = new Set(['running']); export function isLiveStatus(status: NodeStatus): boolean { return NON_TERMINAL.has(status); } /** * Format a millisecond duration as a compact human string: * `42ms`, `1.23s`, `1m 5s`. Returns `null` for nullish/negative input. */ export function formatDuration(ms?: number | null): string | null { if (ms == null || ms < 0 || Number.isNaN(ms)) return null; if (ms < 1000) return `${Math.round(ms)}ms`; const totalSeconds = ms / 1000; if (totalSeconds < 60) { // up to 2 decimal places, trimming trailing zeros return `${parseFloat(totalSeconds.toFixed(2))}s`; } const minutes = Math.floor(totalSeconds / 60); const seconds = Math.round(totalSeconds % 60); return seconds > 0 ? `${minutes}m ${seconds}s` : `${minutes}m`; }