BatchRestartDialog.tsx 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. import { useParams } from 'common'
  2. import { useMemo } from 'react'
  3. import { toast } from 'sonner'
  4. import {
  5. AlertDialog,
  6. AlertDialogAction,
  7. AlertDialogCancel,
  8. AlertDialogContent,
  9. AlertDialogDescription,
  10. AlertDialogFooter,
  11. AlertDialogHeader,
  12. AlertDialogTitle,
  13. } from 'ui'
  14. import { PipelineStatusName } from './Replication.constants'
  15. import { ReplicationPipelineTableStatus } from '@/data/replication/pipeline-replication-status-query'
  16. import { useRollbackTablesMutation } from '@/data/replication/rollback-tables-mutation'
  17. interface BatchRestartDialogProps {
  18. open: boolean
  19. onOpenChange: (open: boolean) => void
  20. mode: 'all' | 'errored'
  21. totalTables: number
  22. erroredTablesCount: number
  23. tables: ReplicationPipelineTableStatus[]
  24. pipelineStatusName?: PipelineStatusName
  25. onRestartStart?: (tableIds: number[]) => void
  26. onRestartComplete?: (tableIds: number[]) => void
  27. }
  28. export const BatchRestartDialog = ({
  29. open,
  30. onOpenChange,
  31. mode,
  32. totalTables,
  33. erroredTablesCount,
  34. tables,
  35. pipelineStatusName,
  36. onRestartStart,
  37. onRestartComplete,
  38. }: BatchRestartDialogProps) => {
  39. const { ref: projectRef, pipelineId: _pipelineId } = useParams()
  40. const pipelineId = Number(_pipelineId)
  41. // Calculate which table IDs will be restarted based on mode (memoized)
  42. const affectedTableIds = useMemo(() => {
  43. if (mode === 'all') {
  44. return tables.map((t) => t.table_id)
  45. } else {
  46. return tables
  47. .filter(
  48. (t) =>
  49. t.state.name === 'error' &&
  50. 'retry_policy' in t.state &&
  51. t.state.retry_policy?.policy === 'manual_retry'
  52. )
  53. .map((t) => t.table_id)
  54. }
  55. }, [mode, tables])
  56. const { mutate: rollbackTables, isPending: isResetting } = useRollbackTablesMutation({
  57. onSuccess: (data) => {
  58. const count = data.tables.length
  59. toast.success(
  60. `Restarting replication for ${count} table${count > 1 ? 's' : ''}. Pipeline will restart automatically.`
  61. )
  62. },
  63. onSettled: () => {
  64. onRestartComplete?.(affectedTableIds)
  65. onOpenChange(false)
  66. },
  67. onError: (error) => {
  68. toast.error(`Failed to restart replication: ${error.message}`)
  69. },
  70. })
  71. const handleReset = () => {
  72. if (!projectRef) return toast.error('Project ref is required')
  73. onRestartStart?.(affectedTableIds)
  74. rollbackTables({
  75. projectRef,
  76. pipelineId,
  77. target: mode === 'all' ? { type: 'all_tables' } : { type: 'all_errored_tables' },
  78. rollbackType: 'full',
  79. pipelineStatusName,
  80. })
  81. }
  82. const dialogContent =
  83. mode === 'all'
  84. ? {
  85. title: 'Restart all tables',
  86. description: (
  87. <div className="space-y-3 text-sm">
  88. <p>
  89. This will restart replication for all
  90. {totalTables === 0 ? '' : totalTables} table{totalTables > 1 ? 's' : ''} in this
  91. pipeline from scratch:
  92. </p>
  93. <ul className="list-disc list-inside space-y-1.5 pl-2">
  94. <li>
  95. <strong>All table copies will be re-initialized.</strong> Every table will be
  96. copied again from the source.
  97. </li>
  98. <li>
  99. <strong>All downstream data will be deleted.</strong> All replicated data will be
  100. removed.
  101. </li>
  102. <li>
  103. <strong>The pipeline will restart automatically.</strong> This is required to
  104. apply this change.
  105. </li>
  106. </ul>
  107. </div>
  108. ),
  109. action: 'Restart all tables',
  110. }
  111. : {
  112. title: 'Restart failed tables',
  113. description: (
  114. <div className="space-y-3 text-sm">
  115. <p>
  116. This will restart replication for{' '}
  117. <strong>all {erroredTablesCount} failed tables</strong> from scratch:
  118. </p>
  119. <ul className="list-disc list-inside space-y-1.5 pl-2">
  120. <li>
  121. <strong>Failed table copies will be re-initialized.</strong> These tables will be
  122. copied again from the source.
  123. </li>
  124. <li>
  125. <strong>Existing downstream data will be deleted.</strong> Replicated data for
  126. these tables will be removed.
  127. </li>
  128. <li>
  129. <strong>All other tables remain untouched.</strong> Only failed tables are
  130. affected.
  131. </li>
  132. <li>
  133. <strong>The pipeline will restart automatically.</strong> This is required to
  134. apply this change.
  135. </li>
  136. </ul>
  137. </div>
  138. ),
  139. action: 'Restart failed tables',
  140. }
  141. return (
  142. <AlertDialog open={open} onOpenChange={onOpenChange}>
  143. <AlertDialogContent>
  144. <AlertDialogHeader>
  145. <AlertDialogTitle>{dialogContent.title}</AlertDialogTitle>
  146. <AlertDialogDescription asChild>{dialogContent.description}</AlertDialogDescription>
  147. </AlertDialogHeader>
  148. <AlertDialogFooter>
  149. <AlertDialogCancel disabled={isResetting}>Cancel</AlertDialogCancel>
  150. <AlertDialogAction disabled={isResetting} onClick={handleReset} variant="warning">
  151. {isResetting ? 'Restarting replication...' : dialogContent.action}
  152. </AlertDialogAction>
  153. </AlertDialogFooter>
  154. </AlertDialogContent>
  155. </AlertDialog>
  156. )
  157. }