|
| 1 | +import type { WorkspaceEngine } from "@ctrlplane/workspace-engine-sdk"; |
| 2 | +import { useState } from "react"; |
| 3 | + |
| 4 | +import type { JobStatus } from "../types"; |
| 5 | +import { trpc } from "~/api/trpc"; |
| 6 | +import { Button } from "~/components/ui/button"; |
| 7 | +import { |
| 8 | + Dialog, |
| 9 | + DialogClose, |
| 10 | + DialogContent, |
| 11 | + DialogDescription, |
| 12 | + DialogFooter, |
| 13 | + DialogHeader, |
| 14 | + DialogTitle, |
| 15 | + DialogTrigger, |
| 16 | +} from "~/components/ui/dialog"; |
| 17 | +import { |
| 18 | + HoverCard, |
| 19 | + HoverCardContent, |
| 20 | + HoverCardTrigger, |
| 21 | +} from "~/components/ui/hover-card"; |
| 22 | +import { |
| 23 | + Select, |
| 24 | + SelectContent, |
| 25 | + SelectItem, |
| 26 | + SelectTrigger, |
| 27 | +} from "~/components/ui/select"; |
| 28 | +import { useWorkspace } from "~/components/WorkspaceProvider"; |
| 29 | +import { cn } from "~/lib/utils"; |
| 30 | +import { JobStatusDisplayName } from "../../../_components/JobStatusBadge"; |
| 31 | + |
| 32 | +type ReleaseTarget = WorkspaceEngine["schemas"]["ReleaseTargetWithState"]; |
| 33 | + |
| 34 | +function useRedeployAll(releaseTargets: ReleaseTarget[]) { |
| 35 | + const { workspace } = useWorkspace(); |
| 36 | + const redeployAll = trpc.redeploy.releaseTargets.useMutation(); |
| 37 | + |
| 38 | + const handleRedeployAll = () => { |
| 39 | + redeployAll.mutateAsync({ |
| 40 | + workspaceId: workspace.id, |
| 41 | + releaseTargets: releaseTargets.map((rt) => ({ |
| 42 | + ...rt.releaseTarget, |
| 43 | + })), |
| 44 | + }); |
| 45 | + }; |
| 46 | + |
| 47 | + return { handleRedeployAll, isPending: redeployAll.isPending }; |
| 48 | +} |
| 49 | + |
| 50 | +function StatusSelector({ |
| 51 | + status, |
| 52 | + setStatus, |
| 53 | +}: { |
| 54 | + status: JobStatus | "all"; |
| 55 | + setStatus: (status: JobStatus | "all") => void; |
| 56 | +}) { |
| 57 | + return ( |
| 58 | + <Select |
| 59 | + value={String(status)} |
| 60 | + onValueChange={(value) => setStatus(value as JobStatus | "all")} |
| 61 | + > |
| 62 | + <SelectTrigger> |
| 63 | + {status === "all" ? "All statuses" : JobStatusDisplayName[status]} |
| 64 | + </SelectTrigger> |
| 65 | + <SelectContent> |
| 66 | + <SelectItem value="all">All statuses</SelectItem> |
| 67 | + {Object.keys(JobStatusDisplayName).map((status) => ( |
| 68 | + <SelectItem key={status} value={status}> |
| 69 | + {JobStatusDisplayName[status as keyof typeof JobStatusDisplayName]} |
| 70 | + </SelectItem> |
| 71 | + ))} |
| 72 | + </SelectContent> |
| 73 | + </Select> |
| 74 | + ); |
| 75 | +} |
| 76 | + |
| 77 | +function SelectedTargetsHover({ |
| 78 | + selectedTargets, |
| 79 | +}: { |
| 80 | + selectedTargets: ReleaseTarget[]; |
| 81 | +}) { |
| 82 | + return ( |
| 83 | + <HoverCard> |
| 84 | + <HoverCardTrigger asChild> |
| 85 | + <Button |
| 86 | + size="sm" |
| 87 | + variant="ghost" |
| 88 | + className="cursor-pointer text-sm text-green-500 hover:text-green-400" |
| 89 | + > |
| 90 | + {selectedTargets.length} resources selected |
| 91 | + </Button> |
| 92 | + </HoverCardTrigger> |
| 93 | + <HoverCardContent className="p-2"> |
| 94 | + <div className="flex flex-col gap-1 text-sm"> |
| 95 | + {selectedTargets.map((rt) => ( |
| 96 | + <div key={rt.releaseTarget.resourceId}> |
| 97 | + <span>{rt.resource.name}</span> |
| 98 | + </div> |
| 99 | + ))} |
| 100 | + </div> |
| 101 | + </HoverCardContent> |
| 102 | + </HoverCard> |
| 103 | + ); |
| 104 | +} |
| 105 | + |
| 106 | +export function RedeployAllDialog({ |
| 107 | + releaseTargets, |
| 108 | +}: { |
| 109 | + releaseTargets: ReleaseTarget[]; |
| 110 | +}) { |
| 111 | + const [open, setOpen] = useState(false); |
| 112 | + |
| 113 | + const environmentName = releaseTargets[0]?.environment.name ?? ""; |
| 114 | + const [status, setStatus] = useState<JobStatus | "all">("all"); |
| 115 | + const selectedTargets = releaseTargets.filter( |
| 116 | + (rt) => status === "all" || rt.state.latestJob?.status === status, |
| 117 | + ); |
| 118 | + const { handleRedeployAll, isPending } = useRedeployAll(selectedTargets); |
| 119 | + |
| 120 | + return ( |
| 121 | + <Dialog open={open} onOpenChange={setOpen}> |
| 122 | + <DialogTrigger asChild> |
| 123 | + <Button |
| 124 | + size="sm" |
| 125 | + variant="secondary" |
| 126 | + className="h-6 w-fit rounded-sm py-3" |
| 127 | + > |
| 128 | + Redeploy {environmentName} |
| 129 | + </Button> |
| 130 | + </DialogTrigger> |
| 131 | + <DialogContent> |
| 132 | + <DialogHeader> |
| 133 | + <DialogTitle> |
| 134 | + {" "} |
| 135 | + Redeploy all release targets in {environmentName}{" "} |
| 136 | + </DialogTitle> |
| 137 | + <DialogDescription> |
| 138 | + Are you sure you want to redeploy all release targets? |
| 139 | + </DialogDescription> |
| 140 | + </DialogHeader> |
| 141 | + |
| 142 | + <div className="flex items-center gap-2"> |
| 143 | + <StatusSelector status={status} setStatus={setStatus} /> |
| 144 | + {selectedTargets.length > 0 && ( |
| 145 | + <SelectedTargetsHover selectedTargets={selectedTargets} /> |
| 146 | + )} |
| 147 | + {selectedTargets.length === 0 && ( |
| 148 | + <span className="text-sm text-muted-foreground"> |
| 149 | + No resources selected |
| 150 | + </span> |
| 151 | + )} |
| 152 | + </div> |
| 153 | + |
| 154 | + <DialogFooter> |
| 155 | + <DialogClose asChild> |
| 156 | + <Button variant="outline">Cancel</Button> |
| 157 | + </DialogClose> |
| 158 | + <Button |
| 159 | + onClick={handleRedeployAll} |
| 160 | + disabled={isPending || selectedTargets.length === 0} |
| 161 | + > |
| 162 | + Redeploy |
| 163 | + </Button> |
| 164 | + </DialogFooter> |
| 165 | + </DialogContent> |
| 166 | + </Dialog> |
| 167 | + ); |
| 168 | +} |
0 commit comments