diff --git a/apps/studio/components/interfaces/Database/Replication/DestinationRow.tsx b/apps/studio/components/interfaces/Database/Replication/DestinationRow.tsx index 71a8894a17d..64a67c980db 100644 --- a/apps/studio/components/interfaces/Database/Replication/DestinationRow.tsx +++ b/apps/studio/components/interfaces/Database/Replication/DestinationRow.tsx @@ -11,12 +11,9 @@ import { PipelineStatusRequestStatus, usePipelineRequestStatus, } from 'state/replication-pipeline-request-status' -import { getStatusName } from './Pipeline.utils' +import { getStatusName, PIPELINE_ERROR_MESSAGES } from './Pipeline.utils' import { useParams } from 'common' -import { - ReplicationPipelineStatusData, - useReplicationPipelineStatusQuery, -} from 'data/replication/pipeline-status-query' +import { useReplicationPipelineStatusQuery } from 'data/replication/pipeline-status-query' import { useState, useEffect } from 'react' import { toast } from 'sonner' import { useStartPipelineMutation } from 'data/replication/start-pipeline-mutation' @@ -96,14 +93,14 @@ const DestinationRow = ({ return } if (!pipeline) { - toast.error('No pipeline found') + toast.error(PIPELINE_ERROR_MESSAGES.NO_PIPELINE_FOUND) return } try { await startPipeline({ projectRef, pipelineId: pipeline.id }) } catch (error) { - toast.error('Failed to enable destination') + toast.error(PIPELINE_ERROR_MESSAGES.ENABLE_DESTINATION) } setGlobalRequestStatus(pipeline.id, PipelineStatusRequestStatus.EnableRequested) } @@ -113,14 +110,14 @@ const DestinationRow = ({ return } if (!pipeline) { - toast.error('No pipeline found') + toast.error(PIPELINE_ERROR_MESSAGES.NO_PIPELINE_FOUND) return } try { await stopPipeline({ projectRef, pipelineId: pipeline.id }) } catch (error) { - toast.error('Failed to disable destination') + toast.error(PIPELINE_ERROR_MESSAGES.DISABLE_DESTINATION) } setGlobalRequestStatus(pipeline.id, PipelineStatusRequestStatus.DisableRequested) } @@ -132,7 +129,7 @@ const DestinationRow = ({ return } if (!pipeline) { - toast.error('No pipeline found') + toast.error(PIPELINE_ERROR_MESSAGES.NO_PIPELINE_FOUND) return } @@ -142,14 +139,14 @@ const DestinationRow = ({ // so we don't need to call deletePipeline explicitly await deleteDestination({ projectRef, destinationId: destinationId }) } catch (error) { - toast.error('Failed to delete destination') + toast.error(PIPELINE_ERROR_MESSAGES.DELETE_DESTINATION) } } return ( <> {isPipelineError && ( - + )} {isPipelineSuccess && ( diff --git a/apps/studio/components/interfaces/Database/Replication/Destinations.tsx b/apps/studio/components/interfaces/Database/Replication/Destinations.tsx index 0ee44a0ba7a..09de2bfae34 100644 --- a/apps/studio/components/interfaces/Database/Replication/Destinations.tsx +++ b/apps/studio/components/interfaces/Database/Replication/Destinations.tsx @@ -11,6 +11,7 @@ import { useState } from 'react' import NewDestinationPanel from './DestinationPanel' import { useReplicationSourcesQuery } from 'data/replication/sources-query' import { noop } from 'lodash' +import { PIPELINE_ERROR_MESSAGES } from './Pipeline.utils' interface DestinationsProps { onSelectPipeline?: (pipelineId: number, destinationName: string) => void @@ -92,7 +93,7 @@ const Destinations = ({ onSelectPipeline = noop }: DestinationsProps) => { {(isSourcesError || isDestinationsError) && ( )} diff --git a/apps/studio/components/interfaces/Database/Replication/Pipeline.utils.ts b/apps/studio/components/interfaces/Database/Replication/Pipeline.utils.ts index d63da196471..89e15496e19 100644 --- a/apps/studio/components/interfaces/Database/Replication/Pipeline.utils.ts +++ b/apps/studio/components/interfaces/Database/Replication/Pipeline.utils.ts @@ -1,6 +1,19 @@ import { ReplicationPipelineStatusData } from 'data/replication/pipeline-status-query' import { PipelineStatusRequestStatus } from 'state/replication-pipeline-request-status' +// Shared error messages for consistency +export const PIPELINE_ERROR_MESSAGES = { + RETRIEVE_PIPELINE: 'Failed to retrieve pipeline information', + RETRIEVE_PIPELINE_STATUS: 'Failed to retrieve pipeline status', + RETRIEVE_REPLICATION_STATUS: 'Failed to retrieve table replication status', + RETRIEVE_DESTINATIONS: 'Failed to retrieve destinations', + ENABLE_DESTINATION: 'Failed to enable destination', + DISABLE_DESTINATION: 'Failed to disable destination', + DELETE_DESTINATION: 'Failed to delete destination', + NO_PIPELINE_FOUND: 'No pipeline found', + COPY_TABLE_STATUS: 'Failed to copy table status', +} as const + export const getStatusName = ( status: ReplicationPipelineStatusData['status'] | undefined ): string | undefined => { @@ -37,6 +50,11 @@ export const PIPELINE_STATE_MESSAGES = { message: 'Initializing replication. Table status will be available once running.', badge: 'Starting', }, + running: { + title: 'Pipeline Running', + message: 'Replication is active and processing data', + badge: 'Running', + }, unknown: { title: 'Pipeline Status Unknown', message: 'Unable to determine replication status. Check the logs for more information.', @@ -49,7 +67,6 @@ export const PIPELINE_STATE_MESSAGES = { }, } as const -// Helper to get consistent state messages based on request status and pipeline status export const getPipelineStateMessages = ( requestStatus: PipelineStatusRequestStatus | undefined, statusName: string | undefined @@ -71,6 +88,8 @@ export const getPipelineStateMessages = ( return PIPELINE_STATE_MESSAGES.stopped case 'starting': return PIPELINE_STATE_MESSAGES.starting + case 'started': + return PIPELINE_STATE_MESSAGES.running case 'unknown': return PIPELINE_STATE_MESSAGES.unknown default: diff --git a/apps/studio/components/interfaces/Database/Replication/PipelineStatus.tsx b/apps/studio/components/interfaces/Database/Replication/PipelineStatus.tsx index 66629a919c8..3181a5abace 100644 --- a/apps/studio/components/interfaces/Database/Replication/PipelineStatus.tsx +++ b/apps/studio/components/interfaces/Database/Replication/PipelineStatus.tsx @@ -6,7 +6,11 @@ import { Loader2, AlertTriangle } from 'lucide-react' import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from 'ui' import { ReplicationPipelineStatusData } from 'data/replication/pipeline-status-query' import { PipelineStatusRequestStatus } from 'state/replication-pipeline-request-status' -import { PIPELINE_STATE_MESSAGES } from './Pipeline.utils' +import { + getPipelineStateMessages, + PIPELINE_STATE_MESSAGES, + PIPELINE_ERROR_MESSAGES, +} from './Pipeline.utils' export enum PipelineStatusName { FAILED = 'failed', @@ -58,7 +62,7 @@ const PipelineStatus = ({ -

{PIPELINE_STATE_MESSAGES.failed.message}

+

{getPipelineStateMessages(requestStatus, 'failed').message}

@@ -66,12 +70,20 @@ const PipelineStatus = ({ } // Map backend statuses to UX-friendly display const getStatusConfig = () => { + const statusName = + pipelineStatus && typeof pipelineStatus === 'object' && 'name' in pipelineStatus + ? pipelineStatus.name + : undefined + + // Get consistent tooltip message using the same logic as other components + const stateMessages = getPipelineStateMessages(requestStatus, statusName) + if (requestStatus === PipelineStatusRequestStatus.EnableRequested) { return { label: 'Enabling...', dot: , color: 'text-brand-600', - tooltip: PIPELINE_STATE_MESSAGES.enabling.message, + tooltip: stateMessages.message, } } @@ -80,7 +92,7 @@ const PipelineStatus = ({ label: 'Disabling...', dot: , color: 'text-warning-600', - tooltip: PIPELINE_STATE_MESSAGES.disabling.message, + tooltip: stateMessages.message, } } @@ -98,35 +110,35 @@ const PipelineStatus = ({ label: 'Starting', dot: , color: 'text-warning-600', - tooltip: PIPELINE_STATE_MESSAGES.starting.message, + tooltip: stateMessages.message, } case PipelineStatusName.STARTED: return { label: 'Running', dot:
, color: 'text-brand-600', - tooltip: 'Replication is active and processing data', + tooltip: stateMessages.message, } case PipelineStatusName.STOPPED: return { label: 'Stopped', dot:
, color: 'text-foreground-light', - tooltip: PIPELINE_STATE_MESSAGES.stopped.message, + tooltip: stateMessages.message, } case PipelineStatusName.UNKNOWN: return { label: 'Unknown', dot:
, color: 'text-warning-600', - tooltip: PIPELINE_STATE_MESSAGES.unknown.message, + tooltip: stateMessages.message, } default: return { label: 'Unknown', dot:
, color: 'text-destructive-600', - tooltip: PIPELINE_STATE_MESSAGES.unknown.message, + tooltip: stateMessages.message, } } } @@ -145,7 +157,9 @@ const PipelineStatus = ({ return ( <> {isLoading && } - {isError && } + {isError && ( + + )} {isSuccess && ( <> {statusConfig.isFailedStatus ? ( diff --git a/apps/studio/components/interfaces/Database/Replication/ReplicationPipelineStatus.tsx b/apps/studio/components/interfaces/Database/Replication/ReplicationPipelineStatus.tsx index 98bade929f8..495a1945934 100644 --- a/apps/studio/components/interfaces/Database/Replication/ReplicationPipelineStatus.tsx +++ b/apps/studio/components/interfaces/Database/Replication/ReplicationPipelineStatus.tsx @@ -9,7 +9,7 @@ import { usePipelineRequestStatus, PipelineStatusRequestStatus, } from 'state/replication-pipeline-request-status' -import { getStatusName, getPipelineStateMessages } from './Pipeline.utils' +import { getStatusName, getPipelineStateMessages, PIPELINE_ERROR_MESSAGES } from './Pipeline.utils' import { useState, useEffect } from 'react' import { Button, @@ -116,7 +116,7 @@ const ReplicationPipelineStatus = ({ await copyToClipboard(statusText) toast.success('Table status copied to clipboard') } catch { - toast.error('Failed to copy table status') + toast.error(PIPELINE_ERROR_MESSAGES.COPY_TABLE_STATUS) } } @@ -255,7 +255,7 @@ const ReplicationPipelineStatus = ({

Pipeline Status

- +
) } @@ -305,7 +305,10 @@ const ReplicationPipelineStatus = ({ {(isPipelineLoading || isStatusLoading) && } {isStatusError && ( - + )} {/* Error alert */} diff --git a/apps/studio/components/interfaces/Database/Replication/RowMenu.tsx b/apps/studio/components/interfaces/Database/Replication/RowMenu.tsx index f09cc703093..f2467075c43 100644 --- a/apps/studio/components/interfaces/Database/Replication/RowMenu.tsx +++ b/apps/studio/components/interfaces/Database/Replication/RowMenu.tsx @@ -1,4 +1,5 @@ import AlertError from 'components/ui/AlertError' +import { PIPELINE_ERROR_MESSAGES } from './Pipeline.utils' import { Edit, MoreVertical, Pause, Play, Trash } from 'lucide-react' import { ResponseError } from 'types' import { @@ -46,7 +47,9 @@ const RowMenu = ({ return (
{isLoading && } - {isError && } + {isError && ( + + )}