UpdateVersionModal.tsx 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. import { useParams } from 'common'
  2. import { ChevronDown } from 'lucide-react'
  3. import { toast } from 'sonner'
  4. import { Collapsible, CollapsibleContent, CollapsibleTrigger, DialogSectionSeparator } from 'ui'
  5. import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal'
  6. import { getStatusName } from './Pipeline.utils'
  7. import { PipelineStatusName, STATUS_REFRESH_FREQUENCY_MS } from './Replication.constants'
  8. import { useReplicationPipelineStatusQuery } from '@/data/replication/pipeline-status-query'
  9. import { useReplicationPipelineVersionQuery } from '@/data/replication/pipeline-version-query'
  10. import { Pipeline } from '@/data/replication/pipelines-query'
  11. import { useRestartPipelineHelper } from '@/data/replication/restart-pipeline-helper'
  12. import { useUpdatePipelineVersionMutation } from '@/data/replication/update-pipeline-version-mutation'
  13. import {
  14. PipelineStatusRequestStatus,
  15. usePipelineRequestStatus,
  16. } from '@/state/replication-pipeline-request-status'
  17. import { type ResponseError } from '@/types'
  18. interface UpdateVersionModalProps {
  19. visible: boolean
  20. pipeline?: Pipeline
  21. confirmLabel?: string
  22. confirmLabelLoading?: string
  23. onClose: () => void
  24. }
  25. export const UpdateVersionModal = ({
  26. visible,
  27. pipeline,
  28. confirmLabel,
  29. confirmLabelLoading = 'Updating',
  30. onClose,
  31. }: UpdateVersionModalProps) => {
  32. const { ref: projectRef } = useParams()
  33. const { setRequestStatus } = usePipelineRequestStatus()
  34. const { data: pipelineStatusData } = useReplicationPipelineStatusQuery(
  35. { projectRef, pipelineId: pipeline?.id },
  36. { refetchInterval: STATUS_REFRESH_FREQUENCY_MS }
  37. )
  38. const pipelineStatus = pipelineStatusData?.status
  39. const statusName = getStatusName(pipelineStatus)
  40. const isStopped = statusName === PipelineStatusName.STOPPED
  41. const { data: versionData, isPending: isLoadingVersion } = useReplicationPipelineVersionQuery({
  42. projectRef,
  43. pipelineId: pipeline?.id,
  44. })
  45. const currentVersionName = versionData?.version?.name
  46. const newVersionName = versionData?.new_version?.name
  47. const { mutateAsync: updatePipelineVersion } = useUpdatePipelineVersionMutation()
  48. const { restartPipeline } = useRestartPipelineHelper()
  49. const onConfirmUpdate = async () => {
  50. if (!projectRef || !pipeline?.id) return
  51. const versionId = versionData?.new_version?.id
  52. if (!versionId) return
  53. // Step 1: Update to the new version
  54. try {
  55. await updatePipelineVersion({ projectRef, pipelineId: pipeline.id, versionId })
  56. } catch (e) {
  57. // 404: default changed; version cache will refresh via mutation onError. Keep dialog open.
  58. if ((e as ResponseError)?.code === 404) return
  59. // Other errors are already toasted by the mutation; do not double-toast here.
  60. return
  61. }
  62. // Step 2: Reflect optimistic restart (only if not stopped) and close any panels
  63. if (!isStopped) {
  64. setRequestStatus(pipeline.id, PipelineStatusRequestStatus.RestartRequested, statusName)
  65. // Step 3: Restart the pipeline (stop + start)
  66. try {
  67. await restartPipeline({ projectRef, pipelineId: pipeline.id })
  68. toast.success('Pipeline successfully updated and is currently restarting')
  69. } catch (e) {
  70. // Clear optimistic state and surface a single concise error
  71. setRequestStatus(pipeline.id, PipelineStatusRequestStatus.None)
  72. toast.error(`Failed to restart pipeline: ${(e as ResponseError).message}`)
  73. }
  74. } else {
  75. toast.success('Pipeline successfully updated')
  76. }
  77. onClose()
  78. }
  79. return (
  80. <ConfirmationModal
  81. size="small"
  82. visible={visible}
  83. title="Update pipeline image"
  84. className="p-0!"
  85. confirmLabel={confirmLabel ?? (isStopped ? 'Update image' : 'Update and restart')}
  86. confirmLabelLoading={confirmLabelLoading}
  87. onCancel={onClose}
  88. onConfirm={onConfirmUpdate}
  89. >
  90. <div className="flex flex-col gap-y-3 py-4 px-5">
  91. <p className="text-sm text-foreground">
  92. A new pipeline image is available with improvements and bug fixes. Proceed to update?
  93. </p>
  94. {!isStopped && (
  95. <p className="text-sm text-foreground-light">
  96. The pipeline will automatically restart when updating. Replication will continue from
  97. where it left off.
  98. </p>
  99. )}
  100. </div>
  101. <DialogSectionSeparator />
  102. <Collapsible className="px-5 py-3 group">
  103. <CollapsibleTrigger className="w-full flex items-center justify-between text-sm text-foreground-light">
  104. <p>View version update details</p>
  105. <ChevronDown size={14} className="group-data-open:-rotate-180 transition" />
  106. </CollapsibleTrigger>
  107. <CollapsibleContent>
  108. <div className="flex flex-col gap-y-2 mt-2 pb-2">
  109. <div className="text-sm text-foreground prose max-w-full">
  110. <p className="text-foreground-light mb-1">Current version:</p>{' '}
  111. <code className="text-code-inline">
  112. {isLoadingVersion ? 'Loading...' : (currentVersionName ?? 'Unknown')}
  113. </code>
  114. </div>
  115. <div className="text-sm text-foreground prose max-w-full">
  116. <p className="text-foreground-light mb-1">New version:</p>{' '}
  117. <code className="text-code-inline">
  118. {isLoadingVersion ? 'Loading...' : (newVersionName ?? 'Unknown')}
  119. </code>
  120. </div>
  121. </div>
  122. </CollapsibleContent>
  123. </Collapsible>
  124. </ConfirmationModal>
  125. )
  126. }