Skip to content

Commit dd879c8

Browse files
authored
Fix api run statuses (#874)
* Added a subtask for testing * Make it easier to run the CLI from the nextjs reference project * Copies of the run and statuses endpoints, but without simplifying the run statuses * Use the new v2 endpoints that give the full run statuses * Removed unused import * v2 events endpoint with the full run status info * Changeset
1 parent af485b9 commit dd879c8

File tree

10 files changed

+286
-9
lines changed

10 files changed

+286
-9
lines changed

.changeset/orange-eggs-fold.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@trigger.dev/sdk": patch
3+
"@trigger.dev/react": patch
4+
---
5+
6+
Updated run, run statuses and event endpoints to v2 to get full run statuses
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import type { LoaderFunctionArgs } from "@remix-run/server-runtime";
2+
import { json } from "@remix-run/server-runtime";
3+
import { GetEvent } from "@trigger.dev/core";
4+
import { z } from "zod";
5+
import { prisma } from "~/db.server";
6+
import { authenticateApiRequest } from "~/services/apiAuth.server";
7+
import { apiCors } from "~/utils/apiCors";
8+
9+
const ParamsSchema = z.object({
10+
eventId: z.string(),
11+
});
12+
13+
export async function loader({ request, params }: LoaderFunctionArgs) {
14+
if (request.method.toUpperCase() === "OPTIONS") {
15+
return apiCors(request, json({}));
16+
}
17+
18+
const authenticationResult = await authenticateApiRequest(request, {
19+
allowPublicKey: true,
20+
});
21+
if (!authenticationResult) {
22+
return apiCors(request, json({ error: "Invalid or Missing API key" }, { status: 401 }));
23+
}
24+
25+
const authenticatedEnv = authenticationResult.environment;
26+
27+
const parsed = ParamsSchema.safeParse(params);
28+
29+
if (!parsed.success) {
30+
return apiCors(request, json({ error: "Invalid or Missing eventId" }, { status: 400 }));
31+
}
32+
33+
const { eventId } = parsed.data;
34+
35+
const event = await findEventRecord(eventId, authenticatedEnv.id);
36+
37+
if (!event) {
38+
return apiCors(request, json({ error: "Event not found" }, { status: 404 }));
39+
}
40+
41+
return apiCors(request, json(toJSON(event)));
42+
}
43+
44+
function toJSON(eventRecord: FoundEventRecord): GetEvent {
45+
return {
46+
id: eventRecord.eventId,
47+
name: eventRecord.name,
48+
createdAt: eventRecord.createdAt,
49+
updatedAt: eventRecord.updatedAt,
50+
runs: eventRecord.runs.map((run) => ({
51+
id: run.id,
52+
status: run.status,
53+
startedAt: run.startedAt,
54+
completedAt: run.completedAt,
55+
})),
56+
};
57+
}
58+
59+
type FoundEventRecord = NonNullable<Awaited<ReturnType<typeof findEventRecord>>>;
60+
61+
async function findEventRecord(eventId: string, environmentId: string) {
62+
return await prisma.eventRecord.findUnique({
63+
select: {
64+
eventId: true,
65+
name: true,
66+
createdAt: true,
67+
updatedAt: true,
68+
runs: {
69+
select: {
70+
id: true,
71+
status: true,
72+
startedAt: true,
73+
completedAt: true,
74+
},
75+
},
76+
},
77+
where: {
78+
eventId_environmentId: {
79+
eventId,
80+
environmentId,
81+
},
82+
},
83+
});
84+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import type { LoaderFunctionArgs } from "@remix-run/server-runtime";
2+
import { json } from "@remix-run/server-runtime";
3+
import { JobRunStatusRecordSchema } from "@trigger.dev/core";
4+
import { z } from "zod";
5+
import { prisma } from "~/db.server";
6+
import { authenticateApiRequest } from "~/services/apiAuth.server";
7+
import { logger } from "~/services/logger.server";
8+
import { apiCors } from "~/utils/apiCors";
9+
10+
const ParamsSchema = z.object({
11+
runId: z.string(),
12+
});
13+
14+
const RecordsSchema = z.array(JobRunStatusRecordSchema);
15+
16+
export async function loader({ request, params }: LoaderFunctionArgs) {
17+
if (request.method.toUpperCase() === "OPTIONS") {
18+
return apiCors(request, json({}));
19+
}
20+
21+
// Next authenticate the request
22+
const authenticationResult = await authenticateApiRequest(request, { allowPublicKey: true });
23+
24+
if (!authenticationResult) {
25+
return apiCors(request, json({ error: "Invalid or Missing API key" }, { status: 401 }));
26+
}
27+
28+
const { runId } = ParamsSchema.parse(params);
29+
30+
logger.debug("Get run statuses", {
31+
runId,
32+
});
33+
34+
try {
35+
const run = await prisma.jobRun.findUnique({
36+
where: {
37+
id: runId,
38+
},
39+
select: {
40+
id: true,
41+
status: true,
42+
output: true,
43+
statuses: {
44+
orderBy: {
45+
createdAt: "asc",
46+
},
47+
},
48+
},
49+
});
50+
51+
if (!run) {
52+
return apiCors(request, json({ error: `No run found for id ${runId}` }, { status: 404 }));
53+
}
54+
55+
const parsedStatuses = RecordsSchema.parse(
56+
run.statuses.map((s) => ({
57+
...s,
58+
state: s.state ?? undefined,
59+
data: s.data ?? undefined,
60+
history: s.history ?? undefined,
61+
}))
62+
);
63+
64+
return apiCors(
65+
request,
66+
json({
67+
run: {
68+
id: run.id,
69+
status: run.status,
70+
output: run.output,
71+
},
72+
statuses: parsedStatuses,
73+
})
74+
);
75+
} catch (error) {
76+
if (error instanceof Error) {
77+
return apiCors(request, json({ error: error.message }, { status: 400 }));
78+
}
79+
80+
return apiCors(request, json({ error: "Something went wrong" }, { status: 500 }));
81+
}
82+
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import type { LoaderFunctionArgs } from "@remix-run/server-runtime";
2+
import { json } from "@remix-run/server-runtime";
3+
import { z } from "zod";
4+
import { ApiRunPresenter } from "~/presenters/ApiRunPresenter.server";
5+
import { authenticateApiRequest } from "~/services/apiAuth.server";
6+
import { apiCors } from "~/utils/apiCors";
7+
import { taskListToTree } from "~/utils/taskListToTree";
8+
9+
const ParamsSchema = z.object({
10+
runId: z.string(),
11+
});
12+
13+
const SearchQuerySchema = z.object({
14+
cursor: z.string().optional(),
15+
take: z.coerce.number().default(20),
16+
subtasks: z.coerce.boolean().default(false),
17+
taskdetails: z.coerce.boolean().default(false),
18+
});
19+
20+
export async function loader({ request, params }: LoaderFunctionArgs) {
21+
if (request.method.toUpperCase() === "OPTIONS") {
22+
return apiCors(request, json({}));
23+
}
24+
25+
const authenticationResult = await authenticateApiRequest(request, {
26+
allowPublicKey: true,
27+
});
28+
if (!authenticationResult) {
29+
return apiCors(request, json({ error: "Invalid or Missing API key" }, { status: 401 }));
30+
}
31+
32+
const authenticatedEnv = authenticationResult.environment;
33+
34+
const parsed = ParamsSchema.safeParse(params);
35+
36+
if (!parsed.success) {
37+
return apiCors(request, json({ error: "Invalid or missing runId" }, { status: 400 }));
38+
}
39+
40+
const { runId } = parsed.data;
41+
42+
const url = new URL(request.url);
43+
const parsedQuery = SearchQuerySchema.safeParse(Object.fromEntries(url.searchParams));
44+
45+
if (!parsedQuery.success) {
46+
return apiCors(
47+
request,
48+
json({ error: "Invalid or missing query parameters" }, { status: 400 })
49+
);
50+
}
51+
52+
const query = parsedQuery.data;
53+
const showTaskDetails = query.taskdetails && authenticationResult.type === "PRIVATE";
54+
const take = Math.min(query.take, 50);
55+
56+
const presenter = new ApiRunPresenter();
57+
const jobRun = await presenter.call({
58+
runId: runId,
59+
maxTasks: take,
60+
taskDetails: showTaskDetails,
61+
subTasks: query.subtasks,
62+
cursor: query.cursor,
63+
});
64+
65+
if (!jobRun) {
66+
return apiCors(request, json({ message: "Run not found" }, { status: 404 }));
67+
}
68+
69+
if (jobRun.environmentId !== authenticatedEnv.id) {
70+
return apiCors(request, json({ message: "Run not found" }, { status: 404 }));
71+
}
72+
73+
const selectedTasks = jobRun.tasks.slice(0, take);
74+
75+
const tasks = taskListToTree(selectedTasks, query.subtasks);
76+
const nextTask = jobRun.tasks[take];
77+
78+
return apiCors(
79+
request,
80+
json({
81+
id: jobRun.id,
82+
status: jobRun.status,
83+
startedAt: jobRun.startedAt,
84+
updatedAt: jobRun.updatedAt,
85+
completedAt: jobRun.completedAt,
86+
output: jobRun.output,
87+
tasks: tasks.map((task) => {
88+
const { parentId, ...rest } = task;
89+
return { ...rest };
90+
}),
91+
statuses: jobRun.statuses.map((s) => ({
92+
...s,
93+
state: s.state ?? undefined,
94+
data: s.data ?? undefined,
95+
history: s.history ?? undefined,
96+
})),
97+
nextCursor: nextTask ? nextTask.id : undefined,
98+
})
99+
);
100+
}

packages/react/src/events.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export function useEventDetails(eventId: string | undefined): UseEventDetailsRes
1717
{
1818
queryKey: [`triggerdotdev-event-${eventId}`],
1919
queryFn: async () => {
20-
return await zodfetch(GetEventSchema, `${apiUrl}/api/v1/events/${eventId}`, {
20+
return await zodfetch(GetEventSchema, `${apiUrl}/api/v2/events/${eventId}`, {
2121
method: "GET",
2222
headers: {
2323
Authorization: `Bearer ${publicApiKey}`,

packages/react/src/runs.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export function useRunDetails(
2424

2525
const { refreshIntervalMs: refreshInterval, ...otherOptions } = options || {};
2626

27-
const url = urlWithSearchParams(`${apiUrl}/api/v1/runs/${runId}`, otherOptions);
27+
const url = urlWithSearchParams(`${apiUrl}/api/v2/runs/${runId}`, otherOptions);
2828

2929
return useQuery(
3030
{

packages/react/src/statuses.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ export function useRunStatuses(
4949
{
5050
queryKey: [`triggerdotdev-run-${runId}`],
5151
queryFn: async () => {
52-
return await zodfetch(GetRunStatusesSchema, `${apiUrl}/api/v1/runs/${runId}/statuses`, {
52+
return await zodfetch(GetRunStatusesSchema, `${apiUrl}/api/v2/runs/${runId}/statuses`, {
5353
method: "GET",
5454
headers: {
5555
Authorization: `Bearer ${publicApiKey}`,

packages/trigger-sdk/src/apiClient.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -450,7 +450,7 @@ export class ApiClient {
450450
eventId,
451451
});
452452

453-
return await zodfetch(GetEventSchema, `${this.#apiUrl}/api/v1/events/${eventId}`, {
453+
return await zodfetch(GetEventSchema, `${this.#apiUrl}/api/v2/events/${eventId}`, {
454454
method: "GET",
455455
headers: {
456456
Authorization: `Bearer ${apiKey}`,
@@ -467,7 +467,7 @@ export class ApiClient {
467467

468468
return await zodfetch(
469469
GetRunSchema,
470-
urlWithSearchParams(`${this.#apiUrl}/api/v1/runs/${runId}`, options),
470+
urlWithSearchParams(`${this.#apiUrl}/api/v2/runs/${runId}`, options),
471471
{
472472
method: "GET",
473473
headers: {
@@ -500,7 +500,7 @@ export class ApiClient {
500500
runId,
501501
});
502502

503-
return await zodfetch(GetRunStatusesSchema, `${this.#apiUrl}/api/v1/runs/${runId}/statuses`, {
503+
return await zodfetch(GetRunStatusesSchema, `${this.#apiUrl}/api/v2/runs/${runId}/statuses`, {
504504
method: "GET",
505505
headers: {
506506
Authorization: `Bearer ${apiKey}`,

references/nextjs-reference/package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
"build": "next build",
88
"start": "next start",
99
"lint": "next lint",
10-
"generate:types": "npx supabase gen types typescript --project-id axtbanoixaztvdntngew --schema public --schema public_2 > src/supabase.types.ts"
10+
"generate:types": "npx supabase gen types typescript --project-id axtbanoixaztvdntngew --schema public --schema public_2 > src/supabase.types.ts",
11+
"dev:trigger": "trigger-cli dev --port 3000"
1112
},
1213
"dependencies": {
1314
"@trigger.dev/eslint-plugin": "workspace:*",
@@ -43,4 +44,4 @@
4344
"trigger.dev": {
4445
"endpointId": "nextjs-example"
4546
}
46-
}
47+
}

references/nextjs-reference/src/jobs/hooks.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ client.defineJob({
1414
// state: "loading",
1515
});
1616

17+
await io.runTask("task-1", async () => {
18+
await io.wait("wait-subtask", 2);
19+
});
20+
1721
await io.wait("wait-input", 2);
1822

1923
await gettingInputData.update("input-data-complete", {
@@ -54,7 +58,7 @@ client.defineJob({
5458
},
5559
});
5660

57-
await io.wait("wait-again", 4);
61+
await io.wait("wait-again-2", 4);
5862

5963
await generatingMemes.update("completed-generation", {
6064
label: "Generated memes",

0 commit comments

Comments
 (0)