'use client'; import { useState, useEffect } from 'react'; import { useParams } from 'next/navigation'; import Link from 'next/link'; import { ArrowLeft, CheckCircle, XCircle, Clock, Loader, RefreshCw, AlertCircle, ChevronDown, ChevronRight, ExternalLink, Timer, ArrowRightLeft, BarChart3, } from 'lucide-react'; import type { ExecutionStatus, StageMetrics } from '@/lib/pipeline-types'; import { getExecution } from '@/lib/pipeline-api'; // Status badge component function StatusBadge({ status }: { status: ExecutionStatus['status'] }) { const config = { pending: { icon: Clock, color: 'bg-gray-100 text-gray-600', label: 'Pending', }, running: { icon: Loader, color: 'bg-blue-100 text-blue-600', label: 'Running', }, completed: { icon: CheckCircle, color: 'bg-green-100 text-green-600', label: 'Completed', }, failed: { icon: XCircle, color: 'bg-red-100 text-red-600', label: 'Failed', }, cancelled: { icon: AlertCircle, color: 'bg-yellow-100 text-yellow-600', label: 'Cancelled', }, }; const { icon: Icon, color, label } = config[status] || config.pending; return ( {label} ); } // Format date string function formatDate(dateStr: string | undefined): string { if (!dateStr) return '-'; const date = new Date(dateStr); return date.toLocaleDateString() + ' ' + date.toLocaleTimeString(); } // Format duration from milliseconds function formatDurationMs(ms: number | undefined): string { if (!ms) return '-'; if (ms < 1000) return `${ms}ms`; if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`; const minutes = Math.floor(ms / 60000); const seconds = Math.floor((ms % 60000) / 1000); return `${minutes}m ${seconds}s`; } // Calculate duration from timestamps function formatDuration(start?: string, end?: string): string { if (!start) return '-'; const startDate = new Date(start); const endDate = end ? new Date(end) : new Date(); const ms = endDate.getTime() - startDate.getTime(); return formatDurationMs(ms); } // Stage metrics display component function StageMetricsTable({ stages, metrics, completed, }: { stages: string[]; metrics: Record | undefined; completed: string[]; }) { if (!metrics || Object.keys(metrics).length === 0) { return (
No stage metrics available yet
); } // Calculate total duration const totalDuration = Object.values(metrics).reduce( (sum, m) => sum + (m.duration_ms || 0), 0 ); return (
{stages.map((stage, index) => { const stageMetrics = metrics[stage]; const isCompleted = completed.includes(stage); const percentage = totalDuration > 0 && stageMetrics ? ((stageMetrics.duration_ms / totalDuration) * 100).toFixed(1) : '0'; return ( ); })}
Stage Status Duration % of Total Records In Records Out
{index + 1} {stage}
{stageMetrics ? ( stageMetrics.success ? ( Success ) : ( Failed ) ) : isCompleted ? ( - ) : ( Pending )} {stageMetrics ? formatDurationMs(stageMetrics.duration_ms) : '-'} {stageMetrics && totalDuration > 0 ? (
{percentage}%
) : ( - )}
{stageMetrics?.records_in?.toLocaleString() || '-'} {stageMetrics?.records_out?.toLocaleString() || '-'}
Total {formatDurationMs(totalDuration)} 100%
); } // JSON viewer component function JsonViewer({ data, title }: { data: unknown; title: string }) { const [expanded, setExpanded] = useState(false); if (!data) return null; return (
{expanded && (
            {JSON.stringify(data, null, 2)}
          
)}
); } /** * Execution detail page - shows results, status, and stage metrics. */ export default function ExecutionDetailPage() { const params = useParams(); const pipelineId = params.pipelineId as string; const executionId = params.executionId as string; const [execution, setExecution] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [autoRefresh, setAutoRefresh] = useState(true); const fetchExecution = async () => { try { const data = await getExecution(pipelineId, executionId); setExecution(data); setError(null); // Stop auto-refresh if execution is complete if (data.status === 'completed' || data.status === 'failed' || data.status === 'cancelled') { setAutoRefresh(false); } } catch (e) { setError(e instanceof Error ? e.message : 'Failed to load execution'); } finally { setLoading(false); } }; useEffect(() => { fetchExecution(); }, [pipelineId, executionId]); // Auto-refresh while running useEffect(() => { if (!autoRefresh) return; const interval = setInterval(fetchExecution, 3000); return () => clearInterval(interval); }, [autoRefresh, pipelineId, executionId]); if (loading && !execution) { return (
); } if (error && !execution) { return (
Back to Pipeline

{error}

); } const isRunning = execution?.status === 'running' || execution?.status === 'pending'; return (
{/* Navigation */}
Back to Pipeline
{isRunning && ( Auto-refreshing... )}
{/* Header */}

Execution Details

{execution?.id}
{/* Info Grid */}
Started

{formatDate(execution?.started_at)}

Completed

{formatDate(execution?.completed_at)}

Total Duration

{execution?.total_duration_ms ? formatDurationMs(execution.total_duration_ms) : formatDuration(execution?.started_at, execution?.completed_at)}

Stages

{execution?.stages_completed.length} / {execution?.stages_requested.length}

{/* Job/Business Link */} {execution?.job_id && (
Job: {execution.job_id.slice(0, 12)}...
)} {execution?.business_id && (
Business: {execution.business_id}
)} {/* View Results Dashboard Button */} {execution?.status === 'completed' && execution?.job_id && (
View Results Dashboard

See classification results, sentiment analysis, and identified issues

)}
{/* Error Message */} {execution?.error_message && (

Execution Failed

{execution.error_message}

)} {/* Stage Metrics / Profiling */}

Stage Performance

{/* Raw Data */}

Execution Data

); }