diff --git a/apps/studio/components/interfaces/Database/Replication/BatchRestartDialog.test.tsx b/apps/studio/components/interfaces/Database/Replication/BatchRestartDialog.test.tsx
index cae640ff9a7..19b7ccbf2a0 100644
--- a/apps/studio/components/interfaces/Database/Replication/BatchRestartDialog.test.tsx
+++ b/apps/studio/components/interfaces/Database/Replication/BatchRestartDialog.test.tsx
@@ -2,6 +2,7 @@ import { act, fireEvent, render, screen } from '@testing-library/react'
import { describe, expect, it, vi } from 'vitest'
import { BatchRestartDialog } from './BatchRestartDialog'
+import { PipelineStatusName } from './Replication.constants'
import type { ReplicationPipelineTableStatus } from '@/data/replication/pipeline-replication-status-query'
const mocks = vi.hoisted(() => ({
@@ -58,15 +59,21 @@ describe('BatchRestartDialog', () => {
mode="errored"
tables={tables}
tableSyncCopy={{ type: 'include_tables', table_ids: [1, 2] }}
+ pipelineStatusName={PipelineStatusName.STARTED}
onRestartStart={onRestartStart}
/>
)
- expect(screen.getByText(/3 currently failed tables/)).toBeInTheDocument()
+ expect(screen.getByText(/This resets 3 failed tables/)).toBeInTheDocument()
+ expect(
+ screen.getByText(
+ /2 of 3 tables will sync existing rows again. The remaining 1 table will skip initial sync/
+ )
+ ).toBeInTheDocument()
expect(screen.getByTestId('copy-targets')).toHaveTextContent('public.table_1,public.table_2')
await act(async () => {
- fireEvent.click(screen.getByRole('button', { name: 'Restart failed tables' }))
+ fireEvent.click(screen.getByRole('button', { name: 'Reset failed tables' }))
})
expect(onRestartStart).toHaveBeenCalledWith([1, 2, 3])
@@ -78,4 +85,17 @@ describe('BatchRestartDialog', () => {
})
)
})
+
+ it('prevents a reset when the pipeline status is unavailable', () => {
+ render(
+
+ )
+
+ expect(screen.getByRole('button', { name: 'Reset all tables' })).toBeDisabled()
+ })
})
diff --git a/apps/studio/components/interfaces/Database/Replication/BatchRestartDialog.tsx b/apps/studio/components/interfaces/Database/Replication/BatchRestartDialog.tsx
index 765b929e4c7..ad806e1f167 100644
--- a/apps/studio/components/interfaces/Database/Replication/BatchRestartDialog.tsx
+++ b/apps/studio/components/interfaces/Database/Replication/BatchRestartDialog.tsx
@@ -54,43 +54,19 @@ export const BatchRestartDialog = ({
}
}, [mode, tables])
const affectedTableIds = useMemo(() => affectedTables.map((table) => table.id), [affectedTables])
+ const isPipelineStatusUnavailable = pipelineStatusName === undefined
const copiedTables = useMemo(
() => getTableCopyTargets(affectedTables, tableSyncCopy),
[affectedTables, tableSyncCopy]
)
-
- const initialSyncDescription =
- copiedTables.length === 0 ? (
-
- No table will run an initial sync. Replication will resume with new changes
- only, without syncing existing source rows. There is no additional initial sync charge.
-
- ) : copiedTables.length === affectedTables.length ? (
-
-
- {copiedTables.length === 1
- ? 'The table will run its initial sync again.'
- : `All ${copiedTables.length} tables will run initial sync again.`}
- {' '}
- Existing source rows will be synced again. Data successfully processed during this initial
- sync is billed again.
-
- ) : (
-
-
- {copiedTables.length} of {affectedTables.length} tables will run initial sync again.
- {' '}
- Existing source rows for those tables will be synced again and billed again. The remaining
- tables will resume replication with new changes only.
-
- )
+ const pipelineAction = pipelineStatusName === PipelineStatusName.STOPPED ? 'start' : 'restart'
const { mutateAsync: rollbackTables, isPending: isResetting } = useRollbackTablesMutation({
onSuccess: (data) => {
const count = data.tables.length
toast.success(
- `Restarting replication for ${count} table${count > 1 ? 's' : ''}. Pipeline will restart automatically.`
+ `Resetting ${count} table${count > 1 ? 's' : ''}. Pipeline will ${pipelineAction} automatically.`
)
},
onSettled: () => {
@@ -98,12 +74,13 @@ export const BatchRestartDialog = ({
onOpenChange(false)
},
onError: (error) => {
- toast.error(`Failed to restart replication: ${error.message}`)
+ toast.error(`Failed to reset tables: ${error.message}`)
},
})
const handleReset = async () => {
if (!projectRef) return toast.error('Project ref is required')
+ if (isPipelineStatusUnavailable) return
onRestartStart?.(affectedTableIds)
@@ -118,57 +95,28 @@ export const BatchRestartDialog = ({
} catch (error) {}
}
+ const count = affectedTables.length
+ const tableWord = count === 1 ? 'table' : 'tables'
+ const remainingTableCount = count - copiedTables.length
+ const remainingTableWord = remainingTableCount === 1 ? 'table' : 'tables'
+ const initialSyncDescription =
+ copiedTables.length === 0
+ ? 'Initial sync is skipped, so replication resumes with new changes only.'
+ : copiedTables.length === affectedTables.length
+ ? 'Existing rows will sync again.'
+ : `${copiedTables.length} of ${count} ${tableWord} will sync existing rows again. The remaining ${remainingTableCount} ${remainingTableWord} will skip initial sync and resume with new changes only.`
+
const dialogContent =
mode === 'all'
? {
- title: 'Restart all tables',
- description: (
-
-
- This will restart replication for all {affectedTables.length} table
- {affectedTables.length === 1 ? '' : 's'} in this pipeline from scratch:
-
-
- {initialSyncDescription}
- -
- All downstream data will be deleted. All replicated data will be
- removed.
-
- -
- The pipeline will restart automatically. This is required to
- apply this change.
-
-
-
- ),
- action: 'Restart all tables',
+ title: 'Reset all tables',
+ description: `This resets all ${count} ${tableWord}. Destination data will be deleted. ${initialSyncDescription} The pipeline will ${pipelineAction} automatically.`,
+ action: 'Reset all tables',
}
: {
- title: 'Restart failed tables',
- description: (
-
-
- This will restart replication for all{' '}
- {affectedTables.length} currently failed tables from scratch:
-
-
- {initialSyncDescription}
- -
- Existing downstream data will be deleted. Replicated data for
- these tables will be removed.
-
- -
- Tables that are not failed remain untouched. The request resets
- every table that is failed when it runs.
-
- -
- The pipeline will restart automatically. This is required to
- apply this change.
-
-
-
- ),
- action: 'Restart failed tables',
+ title: 'Reset failed tables',
+ description: `This resets ${count} failed ${tableWord}. Destination data for those tables will be deleted. ${initialSyncDescription} The pipeline will ${pipelineAction} automatically. Other tables stay as they are.`,
+ action: 'Reset failed tables',
}
return (
@@ -176,7 +124,7 @@ export const BatchRestartDialog = ({
{dialogContent.title}
- {dialogContent.description}
+ {dialogContent.description}
Cancel
-
- {isResetting ? 'Restarting replication...' : dialogContent.action}
+
+ {isResetting ? 'Resetting…' : dialogContent.action}
diff --git a/apps/studio/components/interfaces/Database/Replication/RestartCostEstimate.tsx b/apps/studio/components/interfaces/Database/Replication/RestartCostEstimate.tsx
index 55bae36ad55..131b47a9162 100644
--- a/apps/studio/components/interfaces/Database/Replication/RestartCostEstimate.tsx
+++ b/apps/studio/components/interfaces/Database/Replication/RestartCostEstimate.tsx
@@ -41,13 +41,13 @@ export const RestartCostEstimate = ({
[estimate, tables]
)
return (
-
+
{tables.length === 0 ? (
No additional initial sync charge
- This restart will skip initial sync based on the pipeline's settings.
+ This reset will skip initial sync based on the pipeline’s settings.
@@ -71,18 +71,18 @@ export const RestartCostEstimate = ({
) : (
-
- A cost estimate is unavailable. You can still restart the{' '}
+
+ A cost estimate is unavailable. You can still reset the{' '}
{tables.length === 1 ? 'table' : 'tables'}.
)}
{restartEstimate?.isComplete && restartEstimate.hasRowFilteredTables && (
-
+
*Row filters can reduce the data processed compared with this estimate.
)}
{restartEstimate?.isComplete && (
-
+
Quick planning estimate; the final charge is based on successfully processed initial sync
data, which is billed again.
diff --git a/apps/studio/components/interfaces/Database/Replication/RestartTableDialog.tsx b/apps/studio/components/interfaces/Database/Replication/RestartTableDialog.tsx
index c9cb1f8b4c5..01441eb6ca9 100644
--- a/apps/studio/components/interfaces/Database/Replication/RestartTableDialog.tsx
+++ b/apps/studio/components/interfaces/Database/Replication/RestartTableDialog.tsx
@@ -44,19 +44,18 @@ export const RestartTableDialog = ({
const pipelineId = Number(_pipelineId)
const tableName = `${table.schema}.${table.name}`
const willCopyTable = shouldCopyTable(tableSyncCopy, table.id)
+ const pipelineAction = pipelineStatusName === PipelineStatusName.STOPPED ? 'start' : 'restart'
const { mutate: rollbackTables, isPending: isResetting } = useRollbackTablesMutation({
onSuccess: () => {
- toast.success(
- `Restarting replication for "${tableName}". Pipeline will ${pipelineStatusName === PipelineStatusName.STOPPED ? 'start' : 'restart'} automatically.`
- )
+ toast.success(`Resetting "${tableName}". Pipeline will ${pipelineAction} automatically.`)
},
onSettled: () => {
onRestartComplete?.()
onOpenChange(false)
},
onError: (error) => {
- toast.error(`Failed to restart replication: ${error.message}`)
+ toast.error(`Failed to reset table: ${error.message}`)
},
})
@@ -74,47 +73,16 @@ export const RestartTableDialog = ({
})
}
+ const consequence = willCopyTable
+ ? `Destination data for this table will be deleted, existing rows will sync again, and the pipeline will ${pipelineAction} automatically.`
+ : `Destination data for this table will be deleted. Initial sync is skipped for this table, so replication resumes with new changes only. The pipeline will ${pipelineAction} automatically.`
+
return (
-
- Restart replication for {tableName}
-
-
-
-
- This will restart replication for{' '}
- {tableName} from scratch:
-
-
- {willCopyTable ? (
- -
- The table's initial sync will restart. Existing source rows
- will be synced again. Data successfully processed during this initial sync is
- billed again.
-
- ) : (
- -
- The table will skip initial sync. Replication will resume with
- new changes only, without syncing existing source rows. There is no additional
- initial sync charge.
-
- )}
- -
- Existing downstream data will be deleted. Any replicated data for
- this table will be removed.
-
- -
- All other tables remain untouched. Only this table is affected.
-
- -
- The pipeline will restart automatically. This is required to
- apply this change.
-
-
-
-
+ Reset {tableName}
+ {consequence}
Cancel
- {isResetting ? 'Restarting replication...' : 'Restart replication'}
+ {isResetting ? 'Resetting…' : 'Reset table'}