diff --git a/frontend/src/api/hooks.ts b/frontend/src/api/hooks.ts index 27ba7c3..f44a707 100644 --- a/frontend/src/api/hooks.ts +++ b/frontend/src/api/hooks.ts @@ -525,6 +525,14 @@ export function usePipelineHealth(hours = 24) { return useGet>(['pipeline-health', hours], 'query', `/api/ops/pipeline/health?hours=${hours}`); } +export function useRetryFailedExtractions() { + const qc = useQueryClient(); + return useMutation({ + mutationFn: () => apiPost<{ retried: number; message: string }>('query', '/api/ops/pipeline/retry-failed', {}), + onSuccess: () => qc.invalidateQueries({ queryKey: ['pipeline-health'] }), + }); +} + export function useIngestionSummary(hours = 24) { return useGet>(['ingestion-summary', hours], 'query', `/api/ops/ingestion/summary?hours=${hours}`); } diff --git a/frontend/src/pages/OpsPipeline.tsx b/frontend/src/pages/OpsPipeline.tsx index 724898b..10f673b 100644 --- a/frontend/src/pages/OpsPipeline.tsx +++ b/frontend/src/pages/OpsPipeline.tsx @@ -1,5 +1,5 @@ import { useState, useEffect } from 'react'; -import { usePipelineHealth } from '../api/hooks'; +import { usePipelineHealth, useRetryFailedExtractions } from '../api/hooks'; import { LoadingSpinner, DateRangeSelector, Card } from '../components/ui'; const QUEUE_LABELS: Record = { @@ -53,6 +53,7 @@ export function OpsPipelinePage() { const [hours, setHours] = useState(24); const { data, isLoading } = usePipelineHealth(hours); const stream = usePipelineStream(); + const retryMutation = useRetryFailedExtractions(); if (isLoading) return ; @@ -70,6 +71,8 @@ export function OpsPipelinePage() { .map((s) => [s.status, s.doc_count]), ); + const failedCount = docStages['extraction_failed'] ?? 0; + // Separate DLQ entries from regular queues const dlqEntries = Object.entries(queueDepths).filter(([k]) => k.startsWith('dlq:')); const regularQueues = Object.entries(QUEUE_LABELS); @@ -79,6 +82,22 @@ export function OpsPipelinePage() {

Pipeline Health

+ {failedCount > 0 && ( + + )} + {retryMutation.isSuccess && ( + {retryMutation.data.message} + )} + {retryMutation.isError && ( + Retry failed + )} {stream && (