From 07dbaa5b5085388414768e8a5a1994daf8a9ebfb Mon Sep 17 00:00:00 2001 From: Riccardo Busetti Date: Tue, 22 Jul 2025 15:44:46 +0200 Subject: [PATCH] Fix --- .../Database/Replication/DestinationRow.tsx | 31 ++----- .../Database/Replication/Pipeline.utils.ts | 10 ++ .../PipelineRequestStatusContext.tsx | 18 +++- .../Replication/ReplicationPipelineStatus.tsx | 92 ++++++++++--------- 4 files changed, 86 insertions(+), 65 deletions(-) create mode 100644 apps/studio/components/interfaces/Database/Replication/Pipeline.utils.ts diff --git a/apps/studio/components/interfaces/Database/Replication/DestinationRow.tsx b/apps/studio/components/interfaces/Database/Replication/DestinationRow.tsx index f82e78f397a..7f413039943 100644 --- a/apps/studio/components/interfaces/Database/Replication/DestinationRow.tsx +++ b/apps/studio/components/interfaces/Database/Replication/DestinationRow.tsx @@ -8,12 +8,13 @@ import ShimmeringLoader from 'ui-patterns/ShimmeringLoader' import RowMenu from './RowMenu' import PipelineStatus, { PipelineStatusName } from './PipelineStatus' import { PipelineStatusRequestStatus, usePipelineRequestStatus } from './PipelineRequestStatusContext' +import { getStatusName } from './Pipeline.utils' import { useParams } from 'common' import { ReplicationPipelineStatusData, useReplicationPipelineStatusQuery, } from 'data/replication/pipeline-status-query' -import { useState } from 'react' +import { useState, useEffect } from 'react' import { toast } from 'sonner' import { useStartPipelineMutation } from 'data/replication/start-pipeline-mutation' import { useStopPipelineMutation } from 'data/replication/stop-pipeline-mutation' @@ -67,31 +68,19 @@ const DestinationRow = ({ }, { refetchInterval: refreshFrequencyMs } ) - const { getRequestStatus, setRequestStatus: setGlobalRequestStatus } = usePipelineRequestStatus() + const { getRequestStatus, setRequestStatus: setGlobalRequestStatus, updatePipelineStatus } = usePipelineRequestStatus() const requestStatus = pipeline?.id ? getRequestStatus(pipeline.id) : PipelineStatusRequestStatus.None const { mutateAsync: startPipeline } = useStartPipelineMutation() const { mutateAsync: stopPipeline } = useStopPipelineMutation() const pipelineStatus = pipelineStatusData?.status - const getStatusName = ( - status: ReplicationPipelineStatusData['status'] | undefined - ): string | undefined => { - if (status && typeof status === 'object' && 'name' in status) { - return status.name - } - - return undefined - } - const statusName = getStatusName(pipelineStatus) - if ( - pipeline?.id && - ((requestStatus === PipelineStatusRequestStatus.EnableRequested && - (statusName === PipelineStatusName.STARTED || statusName === PipelineStatusName.FAILED)) || - (requestStatus === PipelineStatusRequestStatus.DisableRequested && - (statusName === PipelineStatusName.STOPPED || statusName === PipelineStatusName.FAILED))) - ) { - setGlobalRequestStatus(pipeline.id, PipelineStatusRequestStatus.None) - } + + // Update request status based on backend status using centralized logic + useEffect(() => { + if (pipeline?.id) { + updatePipelineStatus(pipeline.id, statusName) + } + }, [pipeline?.id, statusName, updatePipelineStatus]) const onEnableClick = async () => { if (!projectRef) { diff --git a/apps/studio/components/interfaces/Database/Replication/Pipeline.utils.ts b/apps/studio/components/interfaces/Database/Replication/Pipeline.utils.ts new file mode 100644 index 00000000000..0c61249722e --- /dev/null +++ b/apps/studio/components/interfaces/Database/Replication/Pipeline.utils.ts @@ -0,0 +1,10 @@ +import { ReplicationPipelineStatusData } from 'data/replication/pipeline-status-query' + +export const getStatusName = ( + status: ReplicationPipelineStatusData['status'] | undefined +): string | undefined => { + if (status && typeof status === 'object' && 'name' in status) { + return status.name + } + return undefined +} \ No newline at end of file diff --git a/apps/studio/components/interfaces/Database/Replication/PipelineRequestStatusContext.tsx b/apps/studio/components/interfaces/Database/Replication/PipelineRequestStatusContext.tsx index d7b99db8415..d1a80ea2ecf 100644 --- a/apps/studio/components/interfaces/Database/Replication/PipelineRequestStatusContext.tsx +++ b/apps/studio/components/interfaces/Database/Replication/PipelineRequestStatusContext.tsx @@ -1,4 +1,4 @@ -import { createContext, useContext, useState, ReactNode } from 'react' +import { createContext, useContext, useState, ReactNode, useEffect } from 'react' export enum PipelineStatusRequestStatus { None = 'None', @@ -10,6 +10,7 @@ interface PipelineRequestStatusContextType { requestStatus: Record setRequestStatus: (pipelineId: number, status: PipelineStatusRequestStatus) => void getRequestStatus: (pipelineId: number) => PipelineStatusRequestStatus + updatePipelineStatus: (pipelineId: number, backendStatus: string | undefined) => void } const PipelineRequestStatusContext = createContext(undefined) @@ -32,12 +33,27 @@ export const PipelineRequestStatusProvider = ({ children }: PipelineRequestStatu return requestStatus[pipelineId] || PipelineStatusRequestStatus.None } + // Centralized logic to update request status based on backend status + const updatePipelineStatus = (pipelineId: number, backendStatus: string | undefined) => { + const currentRequestStatus = getRequestStatus(pipelineId) + + if ( + (currentRequestStatus === PipelineStatusRequestStatus.EnableRequested && + (backendStatus === 'started' || backendStatus === 'failed')) || + (currentRequestStatus === PipelineStatusRequestStatus.DisableRequested && + (backendStatus === 'stopped' || backendStatus === 'failed')) + ) { + setRequestStatus(pipelineId, PipelineStatusRequestStatus.None) + } + } + return ( {children} diff --git a/apps/studio/components/interfaces/Database/Replication/ReplicationPipelineStatus.tsx b/apps/studio/components/interfaces/Database/Replication/ReplicationPipelineStatus.tsx index da92d0e80c0..e88c2ff53ec 100644 --- a/apps/studio/components/interfaces/Database/Replication/ReplicationPipelineStatus.tsx +++ b/apps/studio/components/interfaces/Database/Replication/ReplicationPipelineStatus.tsx @@ -4,9 +4,10 @@ import AlertError from 'components/ui/AlertError' import { useReplicationPipelineReplicationStatusQuery } from 'data/replication/pipeline-replication-status-query' import { useReplicationPipelineByIdQuery } from 'data/replication/pipeline-by-id-query' import { useReplicationPipelineStatusQuery } from 'data/replication/pipeline-status-query' -import PipelineStatus, { PipelineStatusName } from './PipelineStatus' -import { PipelineStatusRequestStatus, usePipelineRequestStatus } from './PipelineRequestStatusContext' -import { useState } from 'react' +import PipelineStatus from './PipelineStatus' +import { usePipelineRequestStatus } from './PipelineRequestStatusContext' +import { getStatusName } from './Pipeline.utils' +import { useState, useEffect } from 'react' import { Button, Badge, @@ -53,7 +54,7 @@ const ReplicationPipelineStatus = ({ }: ReplicationPipelineStatusProps) => { const { ref: projectRef } = useParams() const [filterString, setFilterString] = useState('') - const { getRequestStatus, setRequestStatus } = usePipelineRequestStatus() + const { getRequestStatus, updatePipelineStatus } = usePipelineRequestStatus() const requestStatus = getRequestStatus(pipelineId) const { @@ -93,25 +94,12 @@ const ReplicationPipelineStatus = ({ } ) - // Reset request status when backend status changes appropriately - const getStatusName = ( - status: typeof pipelineStatusData.status | undefined - ): string | undefined => { - if (status && typeof status === 'object' && 'name' in status) { - return status.name - } - return undefined - } - + // Update request status based on backend status using centralized logic const statusName = getStatusName(pipelineStatusData?.status) - if ( - (requestStatus === PipelineStatusRequestStatus.EnableRequested && - (statusName === PipelineStatusName.STARTED || statusName === PipelineStatusName.FAILED)) || - (requestStatus === PipelineStatusRequestStatus.DisableRequested && - (statusName === PipelineStatusName.STOPPED || statusName === PipelineStatusName.FAILED)) - ) { - setRequestStatus(pipelineId, PipelineStatusRequestStatus.None) - } + + useEffect(() => { + updatePipelineStatus(pipelineId, statusName) + }, [pipelineId, statusName, updatePipelineStatus]) const handleCopyTableStatus = async (tableName: string, state: TableState['state']) => { @@ -177,6 +165,33 @@ const ReplicationPipelineStatus = ({ } } + const getDisabledStateTitle = (status: string | undefined) => { + switch (status) { + case 'failed': return 'Pipeline Failed' + case 'stopped': return 'Pipeline Stopped' + case 'starting': return 'Pipeline Starting' + default: return 'Pipeline Not Running' + } + } + + const getDisabledStateMessage = (status: string | undefined) => { + switch (status) { + case 'failed': return 'Replication has failed. Check the logs for details. Table status is disabled.' + case 'stopped': return 'Replication is paused. Enable the pipeline to resume data synchronization.' + case 'starting': return 'Pipeline is initializing. Table status will be available once started.' + default: return 'Pipeline is not actively running. Table status information is disabled.' + } + } + + const getDisabledStateBadge = (status: string | undefined) => { + switch (status) { + case 'failed': return 'Failed' + case 'stopped': return 'Stopped' + case 'starting': return 'Starting' + default: return 'Disabled' + } + } + const tableStatuses = replicationStatusData?.table_statuses || [] const filteredTableStatuses = filterString.length === 0 ? tableStatuses @@ -186,8 +201,8 @@ const ReplicationPipelineStatus = ({ const errorTables = tableStatuses.filter((table: TableState) => table.state.name === 'error') const hasErrors = errorTables.length > 0 - const isPipelineRunning = pipelineStatusData?.status?.name === 'started' - const isPipelineNotRunning = !isPipelineRunning // Any state other than 'started' is not running + const isPipelineRunning = statusName === 'started' + const hasTableData = tableStatuses.length > 0 // Any state other than 'started' is not running @@ -289,7 +304,7 @@ const ReplicationPipelineStatus = ({ )} {/* Pipeline not running state - generic disabled state */} - {isPipelineNotRunning && tableStatuses.length > 0 && ( + {!isPipelineRunning && hasTableData && (
@@ -298,16 +313,10 @@ const ReplicationPipelineStatus = ({

- {pipelineStatusData?.status?.name === 'failed' ? 'Pipeline Failed' : - pipelineStatusData?.status?.name === 'stopped' ? 'Pipeline Stopped' : - pipelineStatusData?.status?.name === 'starting' ? 'Pipeline Starting' : - 'Pipeline Not Running'} + {getDisabledStateTitle(statusName)}

- {pipelineStatusData?.status?.name === 'failed' ? 'Replication has failed. Check the logs for details. Table status is disabled.' : - pipelineStatusData?.status?.name === 'stopped' ? 'Replication is paused. Enable the pipeline to resume data synchronization.' : - pipelineStatusData?.status?.name === 'starting' ? 'Pipeline is initializing. Table status will be available once started.' : - 'Pipeline is not actively running. Table status information is disabled.'} + {getDisabledStateMessage(statusName)}

@@ -334,15 +343,12 @@ const ReplicationPipelineStatus = ({ - {pipelineStatusData?.status?.name === 'failed' ? 'Failed' : - pipelineStatusData?.status?.name === 'stopped' ? 'Stopped' : - pipelineStatusData?.status?.name === 'starting' ? 'Starting' : - 'Disabled'} + {getDisabledStateBadge(statusName)}
- Status unavailable while pipeline is {pipelineStatusData?.status?.name || 'not running'} + Status unavailable while pipeline is {statusName || 'not running'}
@@ -372,7 +378,7 @@ const ReplicationPipelineStatus = ({ )} {/* Tables list - active/running pipeline */} - {isPipelineRunning && !isStatusLoading && tableStatuses.length > 0 && ( + {isPipelineRunning && !isStatusLoading && hasTableData && (
0 && ( + {isPipelineRunning && !isStatusLoading && filteredTableStatuses.length === 0 && hasTableData && (

No tables match "{filterString}"

)} {/* No results - not running pipeline */} - {isPipelineNotRunning && !isStatusLoading && filteredTableStatuses.length === 0 && tableStatuses.length > 0 && ( + {!isPipelineRunning && !isStatusLoading && filteredTableStatuses.length === 0 && hasTableData && (

No tables match "{filterString}"

@@ -470,7 +476,7 @@ const ReplicationPipelineStatus = ({ )} {/* Empty state - not running pipeline */} - {isPipelineNotRunning && !isStatusLoading && tableStatuses.length === 0 && ( + {!isPipelineRunning && !isStatusLoading && tableStatuses.length === 0 && (
@@ -479,7 +485,7 @@ const ReplicationPipelineStatus = ({

Pipeline Not Running

- The replication pipeline is currently {pipelineStatusData?.status?.name || 'not active'}. + The replication pipeline is currently {statusName || 'not active'}. Table status information is not available while the pipeline is in this state.