| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194 |
- import { useParams } from 'common'
- import { ArrowUpCircle, Edit, MoreVertical, Pause, Play, RotateCcw, Trash } from 'lucide-react'
- import { parseAsInteger, useQueryState } from 'nuqs'
- import { toast } from 'sonner'
- import {
- Button,
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuItem,
- DropdownMenuSeparator,
- DropdownMenuTrigger,
- } from 'ui'
- import { ShimmeringLoader } from 'ui-patterns/ShimmeringLoader'
- import {
- getStatusName,
- PIPELINE_DISABLE_ALLOWED_FROM,
- PIPELINE_ENABLE_ALLOWED_FROM,
- } from './Pipeline.utils'
- import { PipelineStatusName } from './Replication.constants'
- import AlertError from '@/components/ui/AlertError'
- import { ReplicationPipelineStatusData } from '@/data/replication/pipeline-status-query'
- import { Pipeline } from '@/data/replication/pipelines-query'
- import { useRestartPipelineHelper } from '@/data/replication/restart-pipeline-helper'
- import { useStartPipelineMutation } from '@/data/replication/start-pipeline-mutation'
- import { useStopPipelineMutation } from '@/data/replication/stop-pipeline-mutation'
- import {
- PipelineStatusRequestStatus,
- usePipelineRequestStatus,
- } from '@/state/replication-pipeline-request-status'
- import type { ResponseError } from '@/types'
- interface RowMenuProps {
- destinationId: number
- pipeline: Pipeline | undefined
- pipelineStatus?: ReplicationPipelineStatusData['status']
- error: ResponseError | null
- isLoading: boolean
- isError: boolean
- hasUpdate?: boolean
- onDeleteClick: () => void
- onUpdateClick?: () => void
- }
- export const RowMenu = ({
- destinationId,
- pipeline,
- pipelineStatus,
- error,
- isLoading,
- isError,
- hasUpdate = false,
- onDeleteClick,
- onUpdateClick,
- }: RowMenuProps) => {
- const { ref: projectRef } = useParams()
- const statusName = getStatusName(pipelineStatus)
- const [, setEdit] = useQueryState(
- 'edit',
- parseAsInteger.withOptions({ history: 'push', clearOnDefault: true })
- )
- const { mutateAsync: startPipeline } = useStartPipelineMutation()
- const { mutateAsync: stopPipeline } = useStopPipelineMutation()
- const { restartPipeline } = useRestartPipelineHelper()
- const { getRequestStatus, setRequestStatus: setGlobalRequestStatus } = usePipelineRequestStatus()
- const requestStatus = pipeline?.id
- ? getRequestStatus(pipeline.id)
- : PipelineStatusRequestStatus.None
- // Show actions when not in a transitional state
- const canPerformActions =
- requestStatus === PipelineStatusRequestStatus.None &&
- statusName !== PipelineStatusName.STARTING &&
- [PipelineStatusName.STOPPED, PipelineStatusName.STARTED, PipelineStatusName.FAILED].includes(
- statusName as PipelineStatusName
- )
- // Show both stop and restart for started/failed states
- const showStopAndRestart =
- canPerformActions &&
- (statusName === PipelineStatusName.STARTED || statusName === PipelineStatusName.FAILED)
- // Show only start for stopped state
- const showStart = canPerformActions && statusName === PipelineStatusName.STOPPED
- const onEnablePipeline = async () => {
- if (!projectRef) return console.error('Project ref is required')
- if (!pipeline) return toast.error('No pipeline found')
- try {
- // Only show 'enabling' when transitioning from allowed states
- if (PIPELINE_ENABLE_ALLOWED_FROM.includes(statusName as PipelineStatusName)) {
- setGlobalRequestStatus(pipeline.id, PipelineStatusRequestStatus.StartRequested, statusName)
- }
- await startPipeline({ projectRef, pipelineId: pipeline.id })
- } catch (error) {
- setGlobalRequestStatus(pipeline.id, PipelineStatusRequestStatus.None)
- toast.error(`Failed to start pipeline: ${(error as ResponseError).message}`)
- }
- }
- const onDisablePipeline = async () => {
- if (!projectRef) return console.error('Project ref is required')
- if (!pipeline) return toast.error('No pipeline found')
- try {
- // Only show 'disabling' when transitioning from allowed states
- if (PIPELINE_DISABLE_ALLOWED_FROM.includes(statusName as PipelineStatusName)) {
- setGlobalRequestStatus(pipeline.id, PipelineStatusRequestStatus.StopRequested, statusName)
- }
- await stopPipeline({ projectRef, pipelineId: pipeline.id })
- } catch (error) {
- setGlobalRequestStatus(pipeline.id, PipelineStatusRequestStatus.None)
- toast.error(`Failed to stop pipeline: ${(error as ResponseError).message}`)
- }
- }
- const onRestartPipeline = async () => {
- if (!projectRef) return console.error('Project ref is required')
- if (!pipeline) return toast.error('No pipeline found')
- try {
- setGlobalRequestStatus(pipeline.id, PipelineStatusRequestStatus.RestartRequested, statusName)
- await restartPipeline({ projectRef, pipelineId: pipeline.id })
- } catch (error) {
- setGlobalRequestStatus(pipeline.id, PipelineStatusRequestStatus.None)
- toast.error(`Failed to restart pipeline: ${(error as ResponseError).message}`)
- }
- }
- return (
- <div className="flex justify-end items-center space-x-2">
- {isLoading && <ShimmeringLoader />}
- {isError && <AlertError error={error} subject="Failed to retrieve pipeline status" />}
- <DropdownMenu>
- <DropdownMenuTrigger asChild>
- <div className="relative">
- <Button type="default" className="px-1.5" icon={<MoreVertical />} />
- {hasUpdate && (
- <span className="absolute -top-0.5 -right-0.5 h-2 w-2 bg-brand rounded-full" />
- )}
- </div>
- </DropdownMenuTrigger>
- <DropdownMenuContent side="bottom" align="end" className="w-52">
- {hasUpdate && (
- <>
- <DropdownMenuItem className="space-x-2" onClick={() => onUpdateClick?.()}>
- <ArrowUpCircle size={14} />
- <p>Update available</p>
- </DropdownMenuItem>
- <DropdownMenuSeparator />
- </>
- )}
- {showStart && (
- <>
- <DropdownMenuItem className="space-x-2" onClick={onEnablePipeline}>
- <Play size={14} />
- <p>Start pipeline</p>
- </DropdownMenuItem>
- <DropdownMenuSeparator />
- </>
- )}
- {showStopAndRestart && (
- <>
- <DropdownMenuItem className="space-x-2" onClick={onRestartPipeline}>
- <RotateCcw size={14} />
- <p>Restart pipeline</p>
- </DropdownMenuItem>
- <DropdownMenuItem className="space-x-2" onClick={onDisablePipeline}>
- <Pause size={14} />
- <p>Stop pipeline</p>
- </DropdownMenuItem>
- <DropdownMenuSeparator />
- </>
- )}
- <DropdownMenuItem className="space-x-2" onClick={() => setEdit(destinationId)}>
- <Edit size={14} />
- <p>Edit destination</p>
- </DropdownMenuItem>
- <DropdownMenuItem className="space-x-2" onClick={onDeleteClick}>
- <Trash size={14} />
- <p>Delete destination</p>
- </DropdownMenuItem>
- </DropdownMenuContent>
- </DropdownMenu>
- </div>
- )
- }
|