11import { parse } from "@conform-to/zod" ;
2- import { InformationCircleIcon } from "@heroicons/react/20/solid" ;
32import { Form , useLocation , useNavigation , useSubmit } from "@remix-run/react" ;
43import { ActionFunctionArgs , json } from "@remix-run/server-runtime" ;
5- import { conditionallyExportPacket , stringifyIO } from "@trigger.dev/core/v3" ;
4+ import {
5+ conditionallyExportPacket ,
6+ IOPacket ,
7+ stringifyIO ,
8+ timeoutError ,
9+ } from "@trigger.dev/core/v3" ;
610import { WaitpointId } from "@trigger.dev/core/v3/apps" ;
711import { Waitpoint } from "@trigger.dev/database" ;
812import { useCallback , useRef } from "react" ;
913import { z } from "zod" ;
1014import { AnimatedHourglassIcon } from "~/assets/icons/AnimatedHourglassIcon" ;
11- import { CodeBlock } from "~/components/code/CodeBlock" ;
1215import { JSONEditor } from "~/components/code/JSONEditor" ;
1316import { Button } from "~/components/primitives/Buttons" ;
1417import { DateTime } from "~/components/primitives/DateTime" ;
1518import { Paragraph } from "~/components/primitives/Paragraph" ;
19+ import { InfoIconTooltip } from "~/components/primitives/Tooltip" ;
1620import { LiveCountdown } from "~/components/runs/v3/LiveTimer" ;
1721import { $replica } from "~/db.server" ;
1822import { useOrganization } from "~/hooks/useOrganizations" ;
@@ -26,7 +30,8 @@ import { engine } from "~/v3/runEngine.server";
2630const CompleteWaitpointFormData = z . discriminatedUnion ( "type" , [
2731 z . object ( {
2832 type : z . literal ( "MANUAL" ) ,
29- payload : z . string ( ) ,
33+ payload : z . string ( ) . optional ( ) ,
34+ isTimeout : z . string ( ) . optional ( ) ,
3035 successRedirect : z . string ( ) ,
3136 failureRedirect : z . string ( ) ,
3237 } ) ,
@@ -104,34 +109,58 @@ export const action = async ({ request, params }: ActionFunctionArgs) => {
104109 ) ;
105110 }
106111 case "MANUAL" : {
107- let data : any ;
112+ if ( submission . value . isTimeout ) {
113+ try {
114+ const result = await engine . completeWaitpoint ( {
115+ id : waitpointId ,
116+ output : {
117+ type : "application/json" ,
118+ value : JSON . stringify ( timeoutError ( new Date ( ) ) ) ,
119+ isError : true ,
120+ } ,
121+ } ) ;
122+
123+ return redirectWithSuccessMessage (
124+ submission . value . successRedirect ,
125+ request ,
126+ "Waitpoint timed out"
127+ ) ;
128+ } catch ( e ) {
129+ return redirectWithErrorMessage (
130+ submission . value . failureRedirect ,
131+ request ,
132+ "Invalid payload, must be valid JSON"
133+ ) ;
134+ }
135+ }
136+
108137 try {
109- data = JSON . parse ( submission . value . payload ) ;
138+ const data = submission . value . payload ? JSON . parse ( submission . value . payload ) : { } ;
139+ const stringifiedData = await stringifyIO ( data ) ;
140+ const finalData = await conditionallyExportPacket (
141+ stringifiedData ,
142+ `${ waitpointId } /waitpoint/token`
143+ ) ;
144+
145+ const result = await engine . completeWaitpoint ( {
146+ id : waitpointId ,
147+ output : finalData . data
148+ ? { type : finalData . dataType , value : finalData . data , isError : false }
149+ : undefined ,
150+ } ) ;
151+
152+ return redirectWithSuccessMessage (
153+ submission . value . successRedirect ,
154+ request ,
155+ "Waitpoint completed"
156+ ) ;
110157 } catch ( e ) {
111158 return redirectWithErrorMessage (
112159 submission . value . failureRedirect ,
113160 request ,
114161 "Invalid payload, must be valid JSON"
115162 ) ;
116163 }
117- const stringifiedData = await stringifyIO ( data ) ;
118- const finalData = await conditionallyExportPacket (
119- stringifiedData ,
120- `${ waitpointId } /waitpoint/token`
121- ) ;
122-
123- const result = await engine . completeWaitpoint ( {
124- id : waitpointId ,
125- output : finalData . data
126- ? { type : finalData . dataType , value : finalData . data , isError : false }
127- : undefined ,
128- } ) ;
129-
130- return redirectWithSuccessMessage (
131- submission . value . successRedirect ,
132- request ,
133- "Waitpoint completed"
134- ) ;
135164 }
136165 }
137166 } catch ( error : any ) {
@@ -199,7 +228,7 @@ function CompleteDateTimeWaitpointForm({
199228 < Form
200229 action = { `/resources/orgs/${ organization . slug } /projects/${ project . slug } /waitpoints/${ waitpoint . friendlyId } /complete` }
201230 method = "post"
202- className = "grid h-full max-h-full grid-rows-[2.5rem_1fr_2.5rem ] overflow-hidden rounded-md border border-grid-bright"
231+ className = "grid h-full max-h-full grid-rows-[2.5rem_1fr_3.25rem ] overflow-hidden border-t border-grid-bright"
203232 >
204233 < div className = "mx-3 flex items-center" >
205234 < Paragraph variant = "small/bright" > Manually skip this waitpoint</ Paragraph >
@@ -229,17 +258,15 @@ function CompleteDateTimeWaitpointForm({
229258 < DateTime date = { waitpoint . completedAfter } />
230259 </ div >
231260 </ div >
232- < div className = "px-2" >
233- < div className = "mb-2 flex items-center justify-end gap-2 border-t border-grid-dimmed pt-2" >
234- < Button
235- variant = "secondary/small"
236- type = "submit"
237- disabled = { isLoading }
238- LeadingIcon = { isLoading ? "spinner" : undefined }
239- >
240- { isLoading ? "Completing…" : "Skip waitpoint" }
241- </ Button >
242- </ div >
261+ < div className = "flex items-center justify-end border-t border-grid-dimmed bg-background-dimmed px-2" >
262+ < Button
263+ variant = "secondary/medium"
264+ type = "submit"
265+ disabled = { isLoading }
266+ LeadingIcon = { isLoading ? "spinner" : undefined }
267+ >
268+ { isLoading ? "Completing…" : "Skip waitpoint" }
269+ </ Button >
243270 </ div >
244271 </ Form >
245272 ) ;
@@ -281,7 +308,7 @@ function CompleteManualWaitpointForm({ waitpoint }: { waitpoint: { friendlyId: s
281308 action = { formAction }
282309 method = "post"
283310 onSubmit = { ( e ) => submitForm ( e ) }
284- className = "grid h-full max-h-full grid-rows-[2.5rem_1fr_2.5rem ] overflow-hidden rounded-md border border-grid-bright"
311+ className = "grid h-full max-h-full grid-rows-[2.5rem_1fr_3.25rem ] overflow-hidden border-t border-grid-bright"
285312 >
286313 < input type = "hidden" name = "type" value = { "MANUAL" } />
287314 < input
@@ -294,10 +321,16 @@ function CompleteManualWaitpointForm({ waitpoint }: { waitpoint: { friendlyId: s
294321 name = "failureRedirect"
295322 value = { `${ location . pathname } ${ location . search } ` }
296323 />
297- < div className = "mx-3 flex items-center" >
324+ < div className = "mx-3 flex items-center gap-1 " >
298325 < Paragraph variant = "small/bright" > Manually complete this waitpoint</ Paragraph >
326+ < InfoIconTooltip
327+ content = {
328+ "This is will immediately complete this waitpoint with the payload you specify. This is useful during development for testing."
329+ }
330+ contentClassName = "normal-case tracking-normal max-w-xs"
331+ />
299332 </ div >
300- < div className = "overflow-y-auto border-t border-grid-dimmed bg-charcoal-900 scrollbar-thin scrollbar-track-transparent scrollbar-thumb-charcoal-600" >
333+ < div className = "overflow-y-auto bg-charcoal-900 scrollbar-thin scrollbar-track-transparent scrollbar-thumb-charcoal-600" >
301334 < div className = "max-h-[70vh] min-h-40 overflow-y-auto bg-charcoal-900 scrollbar-thin scrollbar-track-transparent scrollbar-thumb-charcoal-600" >
302335 < JSONEditor
303336 autoFocus
@@ -315,32 +348,51 @@ function CompleteManualWaitpointForm({ waitpoint }: { waitpoint: { friendlyId: s
315348 />
316349 </ div >
317350 </ div >
318- < div className = "bg-charcoal-900 px-2" >
319- < div className = "mb-2 flex items-center justify-end gap-2 border-t border-grid-dimmed pt-2" >
320- < Button
321- variant = "secondary/small"
322- type = "submit"
323- disabled = { isLoading }
324- LeadingIcon = { isLoading ? "spinner" : undefined }
325- >
326- { isLoading ? "Completing…" : "Complete waitpoint" }
327- </ Button >
328- </ div >
351+ < div className = "flex items-center justify-end gap-2 border-t border-grid-dimmed bg-background-dimmed px-2" >
352+ < Button
353+ variant = "secondary/medium"
354+ type = "submit"
355+ disabled = { isLoading }
356+ LeadingIcon = { isLoading ? "spinner" : undefined }
357+ >
358+ { isLoading ? "Completing…" : "Complete waitpoint" }
359+ </ Button >
329360 </ div >
330361 </ Form >
331- < CodeBlock
332- rowTitle = {
333- < span className = "-ml-1 flex items-center gap-1 text-text-dimmed" >
334- < InformationCircleIcon className = "size-5 shrink-0 text-text-dimmed" />
335- To complete this waitpoint in your code use:
336- </ span >
337- }
338- code = { `
339- await wait.completeToken<YourType>(tokenId,
340- output
341- );` }
342- showLineNumbers = { false }
343- />
344362 </ >
345363 ) ;
346364}
365+
366+ export function ForceTimeout ( { waitpoint } : { waitpoint : { friendlyId : string } } ) {
367+ const location = useLocation ( ) ;
368+ const navigation = useNavigation ( ) ;
369+ const isLoading = navigation . state !== "idle" ;
370+ const organization = useOrganization ( ) ;
371+ const project = useProject ( ) ;
372+ const formAction = `/resources/orgs/${ organization . slug } /projects/${ project . slug } /waitpoints/${ waitpoint . friendlyId } /complete` ;
373+
374+ return (
375+ < Form action = { formAction } method = "post" >
376+ < input type = "hidden" name = "type" value = { "MANUAL" } />
377+ < input type = "hidden" name = "isTimeout" value = { "1" } />
378+ < input
379+ type = "hidden"
380+ name = "successRedirect"
381+ value = { `${ location . pathname } ${ location . search } ` }
382+ />
383+ < input
384+ type = "hidden"
385+ name = "failureRedirect"
386+ value = { `${ location . pathname } ${ location . search } ` }
387+ />
388+ < Button
389+ variant = "tertiary/small"
390+ type = "submit"
391+ disabled = { isLoading }
392+ LeadingIcon = { isLoading ? "spinner" : undefined }
393+ >
394+ { isLoading ? "Forcing timeout…" : "Force timeout" }
395+ </ Button >
396+ </ Form >
397+ ) ;
398+ }
0 commit comments