@@ -24,6 +24,7 @@ import {
2424 Table ,
2525 TableBody ,
2626 TableCell ,
27+ TableCellMenu ,
2728 TableHeader ,
2829 TableHeaderCell ,
2930 TableRow ,
@@ -55,6 +56,8 @@ import { type RuntimeEnvironmentType } from "@trigger.dev/database";
5556import { environmentFullTitle } from "~/components/environments/EnvironmentLabel" ;
5657import { Callout } from "~/components/primitives/Callout" ;
5758import upgradeForQueuesPath from "~/assets/images/queues-dashboard.png" ;
59+ import { PauseQueueService } from "~/v3/services/pauseQueue.server" ;
60+ import { Badge } from "~/components/primitives/Badge" ;
5861
5962const SearchParamsSchema = z . object ( {
6063 page : z . coerce . number ( ) . min ( 1 ) . default ( 1 ) ,
@@ -161,8 +164,40 @@ export const action = async ({ request, params }: ActionFunctionArgs) => {
161164 request ,
162165 "Environment resumed"
163166 ) ;
167+ case "queue-pause" :
168+ case "queue-resume" : {
169+ const friendlyId = formData . get ( "friendlyId" ) ;
170+ if ( ! friendlyId ) {
171+ return redirectWithErrorMessage (
172+ `/orgs/${ organizationSlug } /projects/${ projectParam } /env/${ envParam } /queues` ,
173+ request ,
174+ "Queue ID is required"
175+ ) ;
176+ }
177+
178+ const queueService = new PauseQueueService ( ) ;
179+ const result = await queueService . call (
180+ environment ,
181+ friendlyId . toString ( ) ,
182+ action === "queue-pause" ? "paused" : "resumed"
183+ ) ;
184+
185+ if ( ! result . success ) {
186+ return redirectWithErrorMessage (
187+ `/orgs/${ organizationSlug } /projects/${ projectParam } /env/${ envParam } /queues` ,
188+ request ,
189+ result . error ?? `Failed to ${ action === "queue-pause" ? "pause" : "resume" } queue`
190+ ) ;
191+ }
192+
193+ return redirectWithSuccessMessage (
194+ `/orgs/${ organizationSlug } /projects/${ projectParam } /env/${ envParam } /queues` ,
195+ request ,
196+ `Queue ${ action === "queue-pause" ? "paused" : "resumed" } `
197+ ) ;
198+ }
164199 default :
165- redirectWithErrorMessage (
200+ return redirectWithErrorMessage (
166201 `/orgs/${ organizationSlug } /projects/${ projectParam } /env/${ envParam } /queues` ,
167202 request ,
168203 "Something went wrong"
@@ -266,7 +301,7 @@ export default function Page() {
266301 < TableHeaderCell alignment = "right" > Queued</ TableHeaderCell >
267302 < TableHeaderCell alignment = "right" > Running</ TableHeaderCell >
268303 < TableHeaderCell alignment = "right" > Concurrency limit</ TableHeaderCell >
269- < TableHeaderCell alignment = "right ">
304+ < TableHeaderCell className = "w-[1%] pl-24 ">
270305 < span className = "sr-only" > Pause/resume</ span >
271306 </ TableHeaderCell >
272307 </ TableRow >
@@ -287,37 +322,78 @@ export default function Page() {
287322 resolve = { Promise . all ( [ queues , environment ] ) }
288323 errorElement = { < Paragraph variant = "small" > Error loading queues</ Paragraph > }
289324 >
290- { ( [ q , environment ] ) => {
325+ { ( [ q , env ] ) => {
291326 return q . length > 0 ? (
292327 q . map ( ( queue ) => (
293328 < TableRow key = { queue . name } >
294329 < TableCell >
295330 < span className = "flex items-center gap-2" >
296331 { queue . type === "task" ? (
297332 < SimpleTooltip
298- button = { < TaskIcon className = "size-4 text-blue-500" /> }
333+ button = {
334+ < TaskIcon
335+ className = { cn (
336+ "size-4 text-blue-500" ,
337+ queue . paused && "opacity-50"
338+ ) }
339+ />
340+ }
299341 content = { `This queue was automatically created from your "${ queue . name } " task` }
300342 />
301343 ) : (
302344 < SimpleTooltip
303345 button = {
304- < RectangleStackIcon className = "size-4 text-purple-500" />
346+ < RectangleStackIcon
347+ className = { cn (
348+ "size-4 text-purple-500" ,
349+ queue . paused && "opacity-50"
350+ ) }
351+ />
305352 }
306353 content = { `This is a custom queue you added in your code.` }
307354 />
308355 ) }
309- < span > { queue . name } </ span >
356+ < span className = { queue . paused ? "opacity-50" : undefined } >
357+ { queue . name }
358+ </ span >
359+ { queue . paused ? (
360+ < Badge variant = "extra-small" className = "text-warning" >
361+ Paused
362+ </ Badge >
363+ ) : null }
310364 </ span >
311365 </ TableCell >
312- < TableCell alignment = "right" > { queue . queued } </ TableCell >
313- < TableCell alignment = "right" > { queue . running } </ TableCell >
314- < TableCell alignment = "right" >
366+ < TableCell
367+ alignment = "right"
368+ className = { queue . paused ? "opacity-50" : undefined }
369+ >
370+ { queue . queued }
371+ </ TableCell >
372+ < TableCell
373+ alignment = "right"
374+ className = { queue . paused ? "opacity-50" : undefined }
375+ >
376+ { queue . running }
377+ </ TableCell >
378+ < TableCell
379+ alignment = "right"
380+ className = { queue . paused ? "opacity-50" : undefined }
381+ >
315382 { queue . concurrencyLimit ?? (
316383 < span className = "text-text-dimmed" >
317- Max ({ environment . concurrencyLimit } )
384+ Max ({ env . concurrencyLimit } )
318385 </ span >
319386 ) }
320387 </ TableCell >
388+ < TableCellMenu
389+ isSticky
390+ visibleButtons = {
391+ queue . paused && < QueuePauseResumeButton queue = { queue } />
392+ }
393+ hiddenButtons = {
394+ ! queue . paused && < QueuePauseResumeButton queue = { queue } />
395+ }
396+ />
321397 </ TableRow >
322398 ) )
323399 ) : (
@@ -401,7 +477,7 @@ function EnvironmentPauseResumeButton({
401477 LeadingIcon = { env . paused ? PlayIcon : PauseIcon }
402478 leadingIconClassName = { env . paused ? "text-success" : "text-amber-500" }
403479 >
404- { env . paused ? "Resume" : "Pause environment" }
480+ { env . paused ? "Resume... " : "Pause environment... " }
405481 </ Button >
406482 </ DialogTrigger >
407483 </ div >
@@ -437,6 +513,7 @@ function EnvironmentPauseResumeButton({
437513 disabled = { isLoading }
438514 variant = { env . paused ? "primary/medium" : "danger/medium" }
439515 LeadingIcon = { isLoading ? < Spinner /> : env . paused ? PlayIcon : PauseIcon }
516+ shortcut = { { modifiers : [ "mod" ] , key : "enter" } }
440517 >
441518 { env . paused ? "Resume environment" : "Pause environment" }
442519 </ Button >
@@ -456,6 +533,83 @@ function EnvironmentPauseResumeButton({
456533 ) ;
457534}
458535
536+ function QueuePauseResumeButton ( {
537+ queue,
538+ } : {
539+ /** The "id" here is a friendlyId */
540+ queue : { id : string ; name : string ; paused : boolean } ;
541+ } ) {
542+ const navigation = useNavigation ( ) ;
543+ const [ isOpen , setIsOpen ] = useState ( false ) ;
544+
545+ return (
546+ < Dialog open = { isOpen } onOpenChange = { setIsOpen } >
547+ < div >
548+ < TooltipProvider disableHoverableContent = { true } >
549+ < Tooltip >
550+ < TooltipTrigger asChild >
551+ < div >
552+ < DialogTrigger asChild >
553+ < Button
554+ type = "button"
555+ variant = "tertiary/small"
556+ LeadingIcon = { queue . paused ? PlayIcon : PauseIcon }
557+ leadingIconClassName = { queue . paused ? "text-success" : "text-amber-500" }
558+ >
559+ { queue . paused ? "Resume..." : "Pause..." }
560+ </ Button >
561+ </ DialogTrigger >
562+ </ div >
563+ </ TooltipTrigger >
564+ < TooltipContent side = "right" className = { "text-xs" } >
565+ { queue . paused
566+ ? `Resume processing runs in queue "${ queue . name } "`
567+ : `Pause processing runs in queue "${ queue . name } "` }
568+ </ TooltipContent >
569+ </ Tooltip >
570+ </ TooltipProvider >
571+ </ div >
572+ < DialogContent >
573+ < DialogHeader > { queue . paused ? "Resume queue?" : "Pause queue?" } </ DialogHeader >
574+ < div className = "flex flex-col gap-3 pt-3" >
575+ < Paragraph >
576+ { queue . paused
577+ ? `This will allow runs to be dequeued in the "${ queue . name } " queue again.`
578+ : `This will pause all runs from being dequeued in the "${ queue . name } " queue. Any executing runs will continue to run.` }
579+ </ Paragraph >
580+ < Form method = "post" onSubmit = { ( ) => setIsOpen ( false ) } >
581+ < input
582+ type = "hidden"
583+ name = "action"
584+ value = { queue . paused ? "queue-resume" : "queue-pause" }
585+ />
586+ < input type = "hidden" name = "friendlyId" value = { queue . id } />
587+ < FormButtons
588+ confirmButton = {
589+ < Button
590+ type = "submit"
591+ shortcut = { { modifiers : [ "mod" ] , key : "enter" } }
592+ variant = { queue . paused ? "primary/medium" : "danger/medium" }
593+ LeadingIcon = { queue . paused ? PlayIcon : PauseIcon }
594+ >
595+ { queue . paused ? "Resume queue" : "Pause queue" }
596+ </ Button >
597+ }
598+ cancelButton = {
599+ < DialogClose asChild >
600+ < Button type = "button" variant = "tertiary/medium" >
601+ Cancel
602+ </ Button >
603+ </ DialogClose >
604+ }
605+ />
606+ </ Form >
607+ </ div >
608+ </ DialogContent >
609+ </ Dialog >
610+ ) ;
611+ }
612+
459613function EngineVersionUpgradeCallout ( ) {
460614 return (
461615 < div className = "mt-4 flex max-w-lg flex-col gap-4 rounded-sm border border-grid-bright bg-background-bright px-4" >
0 commit comments