Files
whyrating-engine-legacy/web/app/pipelines/[pipelineId]/page.tsx
Alejandro Gutiérrez 824634aa76 feat: Add extensible multi-pipeline integration system
This commit implements a plugin-like pipeline architecture with:

Pipeline Core Package (packages/pipeline-core/):
- BasePipeline abstract class all pipelines implement
- PipelineRegistry for database-backed discovery/management
- PipelineRunner for execution with status tracking
- DashboardConfig contracts for dynamic widget definitions

Database Migration (006_pipeline_registry.sql):
- pipeline.registry table for registered pipelines
- pipeline.executions table for execution history
- Views for execution stats and monitoring

ReviewIQ Pipeline Refactor:
- Implements BasePipeline interface
- Adds get_dashboard_config() with widget definitions
- Adds get_widget_data() methods for all dashboard widgets
- Maintains backward compatibility with Pipeline alias

Generic Pipeline API (api/routes/pipelines.py):
- GET /api/pipelines - List all registered pipelines
- GET /api/pipelines/{id} - Pipeline details
- POST /api/pipelines/{id}/execute - Execute pipeline
- GET /api/pipelines/{id}/dashboard - Dashboard config
- GET /api/pipelines/{id}/widgets/{w} - Widget data
- GET /api/pipelines/{id}/executions - Execution history

Frontend Dynamic Dashboard System:
- DynamicDashboard component renders from config
- WidgetRegistry maps types to components
- Widget components: StatCard, LineChart, BarChart,
  PieChart, DataTable, Heatmap
- Pipeline API client library

Frontend Pipeline Pages:
- /pipelines - List all registered pipelines
- /pipelines/[id] - Dynamic dashboard for pipeline
- /pipelines/[id]/executions - Execution history
- Pipelines nav item in Sidebar

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-24 19:05:38 +00:00

169 lines
5.7 KiB
TypeScript

'use client';
import { useState, useEffect } from 'react';
import { useParams } from 'next/navigation';
import Link from 'next/link';
import { AlertCircle, ArrowLeft, History, Settings } from 'lucide-react';
import type { DashboardConfig, PipelineDetail } from '@/lib/pipeline-types';
import { getPipeline, getDashboardConfig } from '@/lib/pipeline-api';
import { DynamicDashboard } from '@/components/dashboard';
/**
* Pipeline dashboard page.
*
* Displays the dynamic dashboard for a specific pipeline.
*/
export default function PipelineDashboardPage() {
const params = useParams();
const pipelineId = params.pipelineId as string;
const [pipeline, setPipeline] = useState<PipelineDetail | null>(null);
const [dashboardConfig, setDashboardConfig] = useState<DashboardConfig | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const fetchData = async () => {
setLoading(true);
setError(null);
try {
const [pipelineData, configData] = await Promise.all([
getPipeline(pipelineId),
getDashboardConfig(pipelineId),
]);
setPipeline(pipelineData);
setDashboardConfig(configData);
} catch (e) {
setError(e instanceof Error ? e.message : 'Failed to load pipeline');
} finally {
setLoading(false);
}
};
if (pipelineId) {
fetchData();
}
}, [pipelineId]);
if (loading) {
return (
<div className="p-6">
{/* Header skeleton */}
<div className="flex items-center mb-6 animate-pulse">
<div className="w-8 h-8 bg-gray-200 dark:bg-gray-700 rounded" />
<div className="ml-4 flex-1">
<div className="h-6 w-48 bg-gray-200 dark:bg-gray-700 rounded" />
<div className="h-4 w-64 bg-gray-200 dark:bg-gray-700 rounded mt-2" />
</div>
</div>
{/* Dashboard skeleton */}
<div className="space-y-6">
<div className="grid grid-cols-4 gap-4">
{[1, 2, 3, 4].map((i) => (
<div
key={i}
className="h-24 bg-gray-200 dark:bg-gray-700 rounded-lg animate-pulse"
/>
))}
</div>
<div className="h-64 bg-gray-200 dark:bg-gray-700 rounded-lg animate-pulse" />
</div>
</div>
);
}
if (error || !pipeline || !dashboardConfig) {
return (
<div className="p-6">
<Link
href="/pipelines"
className="inline-flex items-center text-sm text-gray-500 hover:text-gray-700 dark:hover:text-gray-300 mb-6"
>
<ArrowLeft className="w-4 h-4 mr-1" />
Back to Pipelines
</Link>
<div className="text-center py-12">
<AlertCircle className="w-12 h-12 text-red-500 mx-auto mb-4" />
<p className="text-red-600 dark:text-red-400">
{error || 'Pipeline not found'}
</p>
<Link
href="/pipelines"
className="mt-4 inline-block px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700"
>
Back to Pipelines
</Link>
</div>
</div>
);
}
return (
<div className="p-6">
{/* Navigation */}
<div className="flex items-center justify-between mb-6">
<Link
href="/pipelines"
className="inline-flex items-center text-sm text-gray-500 hover:text-gray-700 dark:hover:text-gray-300"
>
<ArrowLeft className="w-4 h-4 mr-1" />
Back to Pipelines
</Link>
<div className="flex items-center space-x-3">
<Link
href={`/pipelines/${pipelineId}/executions`}
className="inline-flex items-center px-3 py-2 text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-md"
>
<History className="w-4 h-4 mr-2" />
Execution History
</Link>
</div>
</div>
{/* Pipeline Info */}
<div className="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 p-4 mb-6">
<div className="flex items-center justify-between">
<div>
<h2 className="text-lg font-semibold text-gray-900 dark:text-gray-100">
{pipeline.name}
</h2>
<p className="text-sm text-gray-500 mt-1">{pipeline.description}</p>
</div>
<div className="text-right">
<p className="text-sm text-gray-500">
Version: <span className="font-medium">{pipeline.version}</span>
</p>
<p className="text-sm text-gray-500">
Input: <code className="text-xs bg-gray-100 dark:bg-gray-700 px-1 py-0.5 rounded">{pipeline.input_type}</code>
</p>
</div>
</div>
<div className="mt-3 pt-3 border-t border-gray-200 dark:border-gray-700">
<p className="text-sm text-gray-500 mb-2">Stages:</p>
<div className="flex flex-wrap gap-2">
{pipeline.stages.map((stage, index) => (
<span
key={stage}
className="inline-flex items-center px-2 py-1 bg-blue-50 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300 text-sm rounded"
>
<span className="w-5 h-5 flex items-center justify-center bg-blue-100 dark:bg-blue-800 rounded-full text-xs font-medium mr-2">
{index + 1}
</span>
{stage}
</span>
))}
</div>
</div>
</div>
{/* Dynamic Dashboard */}
<DynamicDashboard pipelineId={pipelineId} config={dashboardConfig} />
</div>
);
}