Skip to content

Commit 91538c9

Browse files
committed
Added skip timeout, reworked the UI
1 parent 632881f commit 91538c9

File tree

14 files changed

+324
-254
lines changed

14 files changed

+324
-254
lines changed

apps/webapp/app/presenters/v3/SpanPresenter.server.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {
2+
isWaitpointOutputTimeout,
23
MachinePresetName,
34
parsePacket,
45
prettyPrintPacket,
@@ -468,6 +469,13 @@ export class SpanPresenter extends BasePresenter {
468469
? await prettyPrintPacket(waitpoint.output, waitpoint.outputType ?? undefined)
469470
: undefined;
470471

472+
let isTimeout = false;
473+
if (waitpoint.outputIsError && output) {
474+
if (isWaitpointOutputTimeout(output)) {
475+
isTimeout = true;
476+
}
477+
}
478+
471479
return {
472480
...data,
473481
entity: {
@@ -483,6 +491,7 @@ export class SpanPresenter extends BasePresenter {
483491
outputType: waitpoint.outputType,
484492
outputIsError: waitpoint.outputIsError,
485493
completedAfter: waitpoint.completedAfter,
494+
isTimeout,
486495
},
487496
},
488497
};

apps/webapp/app/routes/engine.v1.runs.$runFriendlyId.waitpoints.tokens.$waitpointFriendlyId.wait.ts

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,10 @@
11
import { json } from "@remix-run/server-runtime";
2-
import {
3-
CompleteWaitpointTokenRequestBody,
4-
CompleteWaitpointTokenResponseBody,
5-
conditionallyExportPacket,
6-
CreateWaitpointTokenResponseBody,
7-
stringifyIO,
8-
WaitForWaitpointTokenRequestBody,
9-
WaitForWaitpointTokenResponseBody,
10-
} from "@trigger.dev/core/v3";
2+
import { WaitForWaitpointTokenResponseBody } from "@trigger.dev/core/v3";
113
import { RunId, WaitpointId } from "@trigger.dev/core/v3/apps";
124
import { z } from "zod";
135
import { $replica } from "~/db.server";
146
import { logger } from "~/services/logger.server";
157
import { createActionApiRoute } from "~/services/routeBuilders/apiBuilder.server";
16-
import { parseDelay } from "~/utils/delays";
17-
import { resolveIdempotencyKeyTTL } from "~/utils/idempotencyKeys.server";
188
import { engine } from "~/v3/runEngine.server";
199

2010
const { action } = createActionApiRoute(
@@ -23,7 +13,6 @@ const { action } = createActionApiRoute(
2313
runFriendlyId: z.string(),
2414
waitpointFriendlyId: z.string(),
2515
}),
26-
body: WaitForWaitpointTokenRequestBody,
2716
maxContentLength: 1024 * 10, // 10KB
2817
method: "POST",
2918
},
@@ -32,8 +21,6 @@ const { action } = createActionApiRoute(
3221
const waitpointId = WaitpointId.toId(params.waitpointFriendlyId);
3322
const runId = RunId.toId(params.runFriendlyId);
3423

35-
const timeout = await parseDelay(body.timeout);
36-
3724
try {
3825
//check permissions
3926
const waitpoint = await $replica.waitpoint.findFirst({
@@ -53,7 +40,6 @@ const { action } = createActionApiRoute(
5340
environmentId: authentication.environment.id,
5441
projectId: authentication.environment.project.id,
5542
organizationId: authentication.environment.organization.id,
56-
failAfter: timeout,
5743
});
5844

5945
return json<WaitForWaitpointTokenResponseBody>(

apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.waitpoints.$waitpointFriendlyId.complete/route.tsx

Lines changed: 115 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,22 @@
11
import { parse } from "@conform-to/zod";
2-
import { InformationCircleIcon } from "@heroicons/react/20/solid";
32
import { Form, useLocation, useNavigation, useSubmit } from "@remix-run/react";
43
import { 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";
610
import { WaitpointId } from "@trigger.dev/core/v3/apps";
711
import { Waitpoint } from "@trigger.dev/database";
812
import { useCallback, useRef } from "react";
913
import { z } from "zod";
1014
import { AnimatedHourglassIcon } from "~/assets/icons/AnimatedHourglassIcon";
11-
import { CodeBlock } from "~/components/code/CodeBlock";
1215
import { JSONEditor } from "~/components/code/JSONEditor";
1316
import { Button } from "~/components/primitives/Buttons";
1417
import { DateTime } from "~/components/primitives/DateTime";
1518
import { Paragraph } from "~/components/primitives/Paragraph";
19+
import { InfoIconTooltip } from "~/components/primitives/Tooltip";
1620
import { LiveCountdown } from "~/components/runs/v3/LiveTimer";
1721
import { $replica } from "~/db.server";
1822
import { useOrganization } from "~/hooks/useOrganizations";
@@ -26,7 +30,8 @@ import { engine } from "~/v3/runEngine.server";
2630
const 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

Comments
 (0)