Files
whyrating-engine-legacy/web/app/pipelines/[pipelineId]/executions/page.tsx
Alejandro Gutiérrez 4d48437b21 feat: Add TanStack table for pipeline executions with debug modal
- Create ExecutionsView component with TanStack Table
- Add status filter buttons with count badges
- Add action buttons: Analytics, Metrics, Debug
- Add debug modal with AI copy-paste button for failed executions
- Generate detailed debug report with stage metrics and error context
- Update executions page to use new component

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-24 21:16:58 +00:00

111 lines
3.5 KiB
TypeScript

'use client';
import { useState, useEffect } from 'react';
import { useParams } from 'next/navigation';
import Link from 'next/link';
import { ArrowLeft, PlayCircle, AlertCircle } from 'lucide-react';
import type { ExecutionStatus, PipelineDetail } from '@/lib/pipeline-types';
import { getPipeline, listExecutions } from '@/lib/pipeline-api';
import ExecutionsView from '@/components/ExecutionsView';
/**
* Execution history page for a pipeline.
* Uses TanStack Table for filtering, sorting, and pagination.
*/
export default function ExecutionsPage() {
const params = useParams();
const pipelineId = params.pipelineId as string;
const [pipeline, setPipeline] = useState<PipelineDetail | null>(null);
const [executions, setExecutions] = useState<ExecutionStatus[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const fetchData = async () => {
setLoading(true);
setError(null);
try {
const [pipelineData, executionsData] = await Promise.all([
getPipeline(pipelineId),
listExecutions(pipelineId, { limit: 100 }),
]);
setPipeline(pipelineData);
setExecutions(executionsData);
} catch (e) {
setError(e instanceof Error ? e.message : 'Failed to load data');
} finally {
setLoading(false);
}
};
useEffect(() => {
if (pipelineId) {
fetchData();
}
}, [pipelineId]);
return (
<div className="h-full overflow-y-auto p-6">
{/* Navigation */}
<div className="flex items-center justify-between mb-6">
<Link
href={`/pipelines/${pipelineId}`}
className="inline-flex items-center text-sm text-gray-500 hover:text-gray-700"
>
<ArrowLeft className="w-4 h-4 mr-1" />
Back to Pipeline
</Link>
<Link
href={`/pipelines/${pipelineId}/run`}
className="inline-flex items-center px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 text-sm font-medium"
>
<PlayCircle className="w-4 h-4 mr-2" />
Run Pipeline
</Link>
</div>
{/* Header */}
<div className="mb-6">
<h1 className="text-2xl font-bold text-gray-900">
Execution History
</h1>
<p className="text-gray-500 mt-1">
{pipeline?.name || pipelineId} - View and analyze pipeline executions
</p>
</div>
{/* Content */}
{error ? (
<div className="text-center py-12">
<AlertCircle className="w-12 h-12 text-red-500 mx-auto mb-4" />
<p className="text-red-600">{error}</p>
<button
onClick={fetchData}
className="mt-4 px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700"
>
Retry
</button>
</div>
) : loading ? (
<div className="bg-white rounded-xl shadow-sm border border-gray-200 p-8">
<div className="animate-pulse space-y-4">
<div className="h-10 bg-gray-200 rounded w-full" />
<div className="h-12 bg-gray-100 rounded w-full" />
<div className="h-12 bg-gray-100 rounded w-full" />
<div className="h-12 bg-gray-100 rounded w-full" />
<div className="h-12 bg-gray-100 rounded w-full" />
</div>
</div>
) : (
<ExecutionsView
executions={executions}
pipelineId={pipelineId}
onRefresh={fetchData}
/>
)}
</div>
);
}