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