import { Activity, ArrowUpCircle, Ban, ChevronLeft, ExternalLink, Info, Pause, Play, RotateCcw, Search, WifiOff, X, } from 'lucide-react' import Link from 'next/link' import { parseAsString, useQueryState } from 'nuqs' import { useEffect, useState } from 'react' import { toast } from 'sonner' import { useParams } from 'common' import Table from 'components/to-be-cleaned/Table' import AlertError from 'components/ui/AlertError' import { ButtonTooltip } from 'components/ui/ButtonTooltip' import { useReplicationPipelineByIdQuery } from 'data/replication/pipeline-by-id-query' import { useReplicationPipelineReplicationStatusQuery } from 'data/replication/pipeline-replication-status-query' import { useReplicationPipelineStatusQuery } from 'data/replication/pipeline-status-query' import { useReplicationPipelineVersionQuery } from 'data/replication/pipeline-version-query' import { useRestartPipelineHelper } from 'data/replication/restart-pipeline-helper' import { useStartPipelineMutation } from 'data/replication/start-pipeline-mutation' import { useStopPipelineMutation } from 'data/replication/stop-pipeline-mutation' import { PipelineStatusRequestStatus, usePipelineRequestStatus, } from 'state/replication-pipeline-request-status' import { Badge, Button, cn } from 'ui' import { GenericSkeletonLoader } from 'ui-patterns' import { Input } from 'ui-patterns/DataInputs/Input' import { ErroredTableDetails } from '../ErroredTableDetails' import { PIPELINE_ACTIONABLE_STATES, PIPELINE_ERROR_MESSAGES, getStatusName, } from '../Pipeline.utils' import { PipelineStatus } from '../PipelineStatus' import { PipelineStatusName, STATUS_REFRESH_FREQUENCY_MS } from '../Replication.constants' import { UpdateVersionModal } from '../UpdateVersionModal' import { SlotLagMetrics, TableState } from './ReplicationPipelineStatus.types' import { getDisabledStateConfig, getStatusConfig } from './ReplicationPipelineStatus.utils' import { SlotLagMetricsInline, SlotLagMetricsList } from './SlotLagMetrics' /** * Component for displaying replication pipeline status and table replication details. * Supports both legacy 'error' state and new 'errored' state with retry policies. */ export const ReplicationPipelineStatus = () => { const { ref: projectRef, pipelineId: _pipelineId } = useParams() const [searchString, setSearchString] = useQueryState('search', parseAsString.withDefault('')) const [showUpdateVersionModal, setShowUpdateVersionModal] = useState(false) const pipelineId = Number(_pipelineId) const { getRequestStatus, updatePipelineStatus, setRequestStatus } = usePipelineRequestStatus() const requestStatus = getRequestStatus(pipelineId) const { data: pipeline, error: pipelineError, isPending: isPipelineLoading, isError: isPipelineError, } = useReplicationPipelineByIdQuery({ projectRef, pipelineId, }) const { data: pipelineStatusData, error: pipelineStatusError, isLoading: isPipelineStatusLoading, isError: isPipelineStatusError, isSuccess: isPipelineStatusSuccess, } = useReplicationPipelineStatusQuery( { projectRef, pipelineId }, { enabled: !!pipelineId, refetchInterval: STATUS_REFRESH_FREQUENCY_MS, } ) const { data: replicationStatusData, isPending: isStatusLoading, isError: isStatusError, } = useReplicationPipelineReplicationStatusQuery( { projectRef, pipelineId }, { enabled: !!pipelineId, refetchInterval: STATUS_REFRESH_FREQUENCY_MS, } ) const { data: versionData } = useReplicationPipelineVersionQuery({ projectRef, pipelineId: pipeline?.id, }) const hasUpdate = Boolean(versionData?.new_version) const { mutateAsync: startPipeline, isPending: isStartingPipeline } = useStartPipelineMutation() const { mutateAsync: stopPipeline, isPending: isStoppingPipeline } = useStopPipelineMutation() const { restartPipeline } = useRestartPipelineHelper() const destinationName = pipeline?.destination_name const statusName = getStatusName(pipelineStatusData?.status) const config = getDisabledStateConfig({ requestStatus, statusName }) const tableStatuses = replicationStatusData?.table_statuses || [] const applyLagMetrics = replicationStatusData?.apply_lag const filteredTableStatuses = searchString.length === 0 ? tableStatuses : tableStatuses.filter((table) => table.table_name.toLowerCase().includes(searchString.toLowerCase()) ) const tablesWithLag = tableStatuses.filter((table) => Boolean(table.table_sync_lag)) const isPipelineRunning = statusName === 'started' const hasTableData = tableStatuses.length > 0 const isEnablingDisabling = requestStatus === PipelineStatusRequestStatus.StartRequested || requestStatus === PipelineStatusRequestStatus.StopRequested || requestStatus === PipelineStatusRequestStatus.RestartRequested const showDisabledState = !isPipelineRunning || isEnablingDisabling const refreshIntervalLabel = STATUS_REFRESH_FREQUENCY_MS >= 1000 ? `${Math.round(STATUS_REFRESH_FREQUENCY_MS / 1000)}s` : `${STATUS_REFRESH_FREQUENCY_MS}ms` const logsUrl = `/project/${projectRef}/logs/replication-logs${ pipelineId ? `?f=${encodeURIComponent(JSON.stringify({ pipeline_id: pipelineId }))}` : '' }` const label = statusName === 'stopped' ? 'Start' : statusName === 'started' ? 'Stop' : statusName === 'failed' ? 'Restart' : 'Action unavailable' const icon = statusName === 'stopped' ? ( ) : statusName === 'started' ? ( ) : statusName === 'failed' ? ( ) : ( ) const onPrimaryAction = async () => { if (!projectRef) return console.error('Project ref is required') if (!pipeline) return toast.error(PIPELINE_ERROR_MESSAGES.NO_PIPELINE_FOUND) try { if (statusName === 'stopped') { setRequestStatus(pipeline.id, PipelineStatusRequestStatus.StartRequested, statusName) await startPipeline({ projectRef, pipelineId: pipeline.id }) } else if (statusName === 'started') { setRequestStatus(pipeline.id, PipelineStatusRequestStatus.StopRequested, statusName) await stopPipeline({ projectRef, pipelineId: pipeline.id }) } else if (statusName === 'failed') { setRequestStatus(pipeline.id, PipelineStatusRequestStatus.RestartRequested, statusName) await restartPipeline({ projectRef, pipelineId: pipeline.id }) } } catch (error) { toast.error(PIPELINE_ERROR_MESSAGES.ENABLE_DESTINATION) } } useEffect(() => { updatePipelineStatus(pipelineId, statusName) }, [pipelineId, statusName, updatePipelineStatus]) return ( <>

{destinationName || 'Pipeline'}

{hasUpdate && ( )} } className="text-xs" placeholder="Search for tables" value={searchString} disabled={isPipelineError} onChange={(e) => setSearchString(e.target.value)} actions={ searchString.length > 0 ? [ setSearchString('')} />, ] : undefined } />
{isPipelineError && ( )} {isStatusError && (
Live updates paused Retrying automatically
)} {showDisabledState && (
{config.icon}

{config.title}

{config.message}

)} {(isPipelineLoading || isStatusLoading) && (
)} {applyLagMetrics && (

Replication lag

Snapshot of how far this pipeline is trailing behind right now.

Updates every {refreshIntervalLabel}

{isStatusError && (

Unable to refresh data. Showing the last values we received.

)} {tablesWithLag.length > 0 && ( <>
During initial sync, tables can copy and stream independently before reconciling with the overall pipeline.
    {tablesWithLag.map((table) => (
  • ))}
)}
)} {hasTableData && (
{/* [Joshen] Should update to use new Table components next time */} Table, Status, Details, ]} body={ <> {filteredTableStatuses.length === 0 && hasTableData && (

No results found

Your search for "{searchString}" did not return any results

)} {filteredTableStatuses.map((table, index) => { const statusConfig = getStatusConfig(table.state as TableState['state']) return (

{table.table_name}

} tooltip={{ content: { side: 'bottom', text: 'Open in Table Editor' }, }} >
{showDisabledState ? ( Not Available ) : ( statusConfig.badge )} {showDisabledState ? (

Status unavailable while pipeline is {config.badge.toLowerCase()}

) : (
{statusConfig.description}
{table.state.name === 'error' && ( )}
)}
) })} } /> )} {!isStatusLoading && tableStatuses.length === 0 && (

{showDisabledState ? 'Pipeline not running' : 'No table status information'}

{showDisabledState ? `The replication pipeline is currently ${statusName || 'not active'}. Table status information is not available while the pipeline is in this state.` : `This pipeline doesn't have any table replication status data available yet. The status will appear here once replication begins.`}

Data refreshes automatically every 2 seconds

)} setShowUpdateVersionModal(false)} confirmLabel={ statusName === PipelineStatusName.STARTED || statusName === PipelineStatusName.FAILED ? 'Update and restart' : 'Update version' } /> ) }