1- import { AdjustmentsHorizontalIcon , PauseIcon , PlayIcon } from "@heroicons/react/20/solid" ;
1+ import {
2+ AdjustmentsHorizontalIcon ,
3+ ArchiveBoxArrowDownIcon ,
4+ ArchiveBoxXMarkIcon ,
5+ PauseIcon ,
6+ PlayIcon ,
7+ } from "@heroicons/react/20/solid" ;
28import { DialogClose } from "@radix-ui/react-dialog" ;
3- import { Form , useNavigation } from "@remix-run/react" ;
9+ import { Form , useFetcher , useNavigation } from "@remix-run/react" ;
410import { useEffect , useState } from "react" ;
511import { cn } from "~/utils/cn" ;
612import type { QueueLimits } from "~/components/queues/queue-limits" ;
7- import { Button , type ButtonVariant } from "~/components/primitives/Buttons" ;
13+ import { Button , type ButtonVariant , LinkButton } from "~/components/primitives/Buttons" ;
14+ import { useEnvironment } from "~/hooks/useEnvironment" ;
15+ import { useOrganization } from "~/hooks/useOrganizations" ;
16+ import { useProject } from "~/hooks/useProject" ;
17+ import type { ArchiveCheckResult } from "~/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.queues.$queueParam.archive-check" ;
18+ import {
19+ type EnvironmentForPath ,
20+ type OrgForPath ,
21+ type ProjectForPath ,
22+ v3RunsPath ,
23+ } from "~/utils/pathBuilder" ;
824import { Dialog , DialogContent , DialogHeader , DialogTrigger } from "~/components/primitives/Dialog" ;
925import { FormButtons } from "~/components/primitives/FormButtons" ;
1026import { FormError } from "~/components/primitives/FormError" ;
@@ -24,9 +40,10 @@ import {
2440} from "~/components/primitives/Tooltip" ;
2541
2642// Per-queue action controls. Extracted from the Queues list route so the queue detail page can
27- // reuse them. Both submit a `<Form method="post">` to the current route, so whichever route renders
28- // them must handle the `queue-pause` / `queue-resume` / `queue-override` / `queue-remove-override`
29- // actions (see `handleQueueMutationAction` in `~/models/queueMutation.server`).
43+ // reuse them. They submit a `<Form method="post">` to the current route, so whichever route renders
44+ // them must handle the `queue-pause` / `queue-resume` / `queue-override` / `queue-remove-override` /
45+ // `queue-archive` / `queue-unarchive` actions (see `handleQueueMutationAction` in
46+ // `~/models/queueMutation.server`).
3047
3148export function QueuePauseResumeButton ( {
3249 queue,
@@ -594,3 +611,185 @@ export function QueueOverrideConcurrencyButton({
594611 </ Dialog >
595612 ) ;
596613}
614+
615+ function archiveCheckPath (
616+ organization : OrgForPath ,
617+ project : ProjectForPath ,
618+ environment : EnvironmentForPath ,
619+ queueFriendlyId : string
620+ ) {
621+ return `/resources/orgs/${ organization . slug } /projects/${ project . slug } /env/${ environment . slug } /queues/${ queueFriendlyId } /archive-check` ;
622+ }
623+
624+ export function QueueArchiveButton ( {
625+ queue,
626+ trigger = "menu" ,
627+ disabled = false ,
628+ lastOnPage = false ,
629+ onDone,
630+ } : {
631+ /** The "id" here is a friendlyId */
632+ queue : { id : string ; name : string ; type : "task" | "custom" ; archivedAt : Date | null } ;
633+ /** The only row on a later page, so archiving it should go back a page. */
634+ lastOnPage ?: boolean ;
635+ /** "menu" renders a row-menu item; "button" a standalone button for the detail page. */
636+ trigger ?: "menu" | "button" ;
637+ disabled ?: boolean ;
638+ /** Called after submitting, e.g. to close the row menu that hosts this control. */
639+ onDone ?: ( ) => void ;
640+ } ) {
641+ const [ isOpen , setIsOpen ] = useState ( false ) ;
642+ const organization = useOrganization ( ) ;
643+ const project = useProject ( ) ;
644+ const environment = useEnvironment ( ) ;
645+ const check = useFetcher < ArchiveCheckResult > ( ) ;
646+ const noPermission = "You don't have permission to manage queues" ;
647+
648+ if ( queue . archivedAt ) {
649+ return (
650+ < Form method = "post" onSubmit = { ( ) => onDone ?.( ) } >
651+ < input type = "hidden" name = "action" value = "queue-unarchive" />
652+ < input type = "hidden" name = "friendlyId" value = { queue . id } />
653+ { trigger === "button" ? (
654+ < Button
655+ type = "submit"
656+ variant = "secondary/small"
657+ LeadingIcon = { ArchiveBoxXMarkIcon }
658+ disabled = { disabled }
659+ tooltip = { disabled ? noPermission : undefined }
660+ >
661+ Unarchive
662+ </ Button >
663+ ) : (
664+ < PopoverMenuItem
665+ type = "submit"
666+ icon = { ArchiveBoxXMarkIcon }
667+ leadingIconClassName = "text-text-dimmed"
668+ title = { disabled ? noPermission : "Unarchive" }
669+ disabled = { disabled }
670+ />
671+ ) }
672+ </ Form >
673+ ) ;
674+ }
675+
676+ const openDialog = ( open : boolean ) => {
677+ setIsOpen ( open ) ;
678+ if ( open ) {
679+ check . load ( archiveCheckPath ( organization , project , environment , queue . id ) ) ;
680+ }
681+ } ;
682+
683+ const result = check . state === "idle" ? check . data : undefined ;
684+ const queueFilterName = queue . type === "task" ? `task/${ queue . name } ` : queue . name ;
685+
686+ return (
687+ < Dialog open = { isOpen } onOpenChange = { openDialog } >
688+ < DialogTrigger asChild >
689+ { trigger === "button" ? (
690+ < Button
691+ type = "button"
692+ variant = "secondary/small"
693+ LeadingIcon = { ArchiveBoxArrowDownIcon }
694+ disabled = { disabled }
695+ tooltip = { disabled ? noPermission : undefined }
696+ >
697+ Archive…
698+ </ Button >
699+ ) : (
700+ < PopoverMenuItem
701+ icon = { ArchiveBoxArrowDownIcon }
702+ leadingIconClassName = "text-text-dimmed"
703+ title = { disabled ? noPermission : "Archive..." }
704+ disabled = { disabled }
705+ />
706+ ) }
707+ </ DialogTrigger >
708+ < DialogContent >
709+ { result === undefined ? (
710+ < >
711+ < DialogHeader > Archive queue?</ DialogHeader >
712+ < div className = "flex items-center gap-2 pt-3" >
713+ < Spinner className = "size-4" />
714+ < Paragraph > Checking whether this queue can be archived…</ Paragraph >
715+ </ div >
716+ </ >
717+ ) : result . archivable ? (
718+ < >
719+ < DialogHeader > Archive queue?</ DialogHeader >
720+ < div className = "flex flex-col gap-3 pt-3" >
721+ < Paragraph >
722+ { `This hides the "${ queue . name } " queue from this list. It doesn't affect any runs, and its limits and settings are kept.` }
723+ </ Paragraph >
724+ < Paragraph >
725+ If a future deploy declares this queue again, it will be unarchived automatically.
726+ You can also find it with the "Show archived" toggle.
727+ </ Paragraph >
728+ < Form
729+ method = "post"
730+ onSubmit = { ( ) => {
731+ setIsOpen ( false ) ;
732+ onDone ?.( ) ;
733+ } }
734+ >
735+ < input type = "hidden" name = "action" value = "queue-archive" />
736+ < input type = "hidden" name = "friendlyId" value = { queue . id } />
737+ { lastOnPage ? < input type = "hidden" name = "lastOnPage" value = "true" /> : null }
738+ < FormButtons
739+ confirmButton = {
740+ < Button
741+ type = "submit"
742+ shortcut = { { modifiers : [ "mod" ] , key : "enter" } }
743+ variant = "primary/medium"
744+ LeadingIcon = { ArchiveBoxArrowDownIcon }
745+ >
746+ Archive queue
747+ </ Button >
748+ }
749+ cancelButton = {
750+ < DialogClose asChild >
751+ < Button type = "button" variant = "secondary/medium" >
752+ Cancel
753+ </ Button >
754+ </ DialogClose >
755+ }
756+ />
757+ </ Form >
758+ </ div >
759+ </ >
760+ ) : (
761+ < >
762+ < DialogHeader > Can't archive this queue yet</ DialogHeader >
763+ < div className = "flex flex-col gap-3 pt-3" >
764+ < Paragraph > { result . reason } </ Paragraph >
765+ < FormButtons
766+ confirmButton = {
767+ result . activeRuns ? (
768+ < LinkButton
769+ variant = "secondary/medium"
770+ to = { v3RunsPath ( organization , project , environment , {
771+ queues : [ queueFilterName ] ,
772+ statuses : [ "PENDING" , "DEQUEUED" , "EXECUTING" ] ,
773+ period : "30d" ,
774+ rootOnly : false ,
775+ } ) }
776+ >
777+ View runs
778+ </ LinkButton >
779+ ) : undefined
780+ }
781+ cancelButton = {
782+ < DialogClose asChild >
783+ < Button type = "button" variant = "secondary/medium" >
784+ Close
785+ </ Button >
786+ </ DialogClose >
787+ }
788+ />
789+ </ div >
790+ </ >
791+ ) }
792+ </ DialogContent >
793+ </ Dialog >
794+ ) ;
795+ }
0 commit comments