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
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"@trigger.dev/sdk": patch
|
||||
"@trigger.dev/react": patch
|
||||
---
|
||||
|
||||
Updated run, run statuses and event endpoints to v2 to get full run statuses
|
||||
@@ -0,0 +1,84 @@
|
||||
import type { LoaderFunctionArgs } from "@remix-run/server-runtime";
|
||||
import { json } from "@remix-run/server-runtime";
|
||||
import { GetEvent } from "@trigger.dev/core";
|
||||
import { z } from "zod";
|
||||
import { prisma } from "~/db.server";
|
||||
import { authenticateApiRequest } from "~/services/apiAuth.server";
|
||||
import { apiCors } from "~/utils/apiCors";
|
||||
|
||||
const ParamsSchema = z.object({
|
||||
eventId: z.string(),
|
||||
});
|
||||
|
||||
export async function loader({ request, params }: LoaderFunctionArgs) {
|
||||
if (request.method.toUpperCase() === "OPTIONS") {
|
||||
return apiCors(request, json({}));
|
||||
}
|
||||
|
||||
const authenticationResult = await authenticateApiRequest(request, {
|
||||
allowPublicKey: true,
|
||||
});
|
||||
if (!authenticationResult) {
|
||||
return apiCors(request, json({ error: "Invalid or Missing API key" }, { status: 401 }));
|
||||
}
|
||||
|
||||
const authenticatedEnv = authenticationResult.environment;
|
||||
|
||||
const parsed = ParamsSchema.safeParse(params);
|
||||
|
||||
if (!parsed.success) {
|
||||
return apiCors(request, json({ error: "Invalid or Missing eventId" }, { status: 400 }));
|
||||
}
|
||||
|
||||
const { eventId } = parsed.data;
|
||||
|
||||
const event = await findEventRecord(eventId, authenticatedEnv.id);
|
||||
|
||||
if (!event) {
|
||||
return apiCors(request, json({ error: "Event not found" }, { status: 404 }));
|
||||
}
|
||||
|
||||
return apiCors(request, json(toJSON(event)));
|
||||
}
|
||||
|
||||
function toJSON(eventRecord: FoundEventRecord): GetEvent {
|
||||
return {
|
||||
id: eventRecord.eventId,
|
||||
name: eventRecord.name,
|
||||
createdAt: eventRecord.createdAt,
|
||||
updatedAt: eventRecord.updatedAt,
|
||||
runs: eventRecord.runs.map((run) => ({
|
||||
id: run.id,
|
||||
status: run.status,
|
||||
startedAt: run.startedAt,
|
||||
completedAt: run.completedAt,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
type FoundEventRecord = NonNullable<Awaited<ReturnType<typeof findEventRecord>>>;
|
||||
|
||||
async function findEventRecord(eventId: string, environmentId: string) {
|
||||
return await prisma.eventRecord.findUnique({
|
||||
select: {
|
||||
eventId: true,
|
||||
name: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
runs: {
|
||||
select: {
|
||||
id: true,
|
||||
status: true,
|
||||
startedAt: true,
|
||||
completedAt: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
where: {
|
||||
eventId_environmentId: {
|
||||
eventId,
|
||||
environmentId,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
import type { LoaderFunctionArgs } from "@remix-run/server-runtime";
|
||||
import { json } from "@remix-run/server-runtime";
|
||||
import { JobRunStatusRecordSchema } from "@trigger.dev/core";
|
||||
import { z } from "zod";
|
||||
import { prisma } from "~/db.server";
|
||||
import { authenticateApiRequest } from "~/services/apiAuth.server";
|
||||
import { logger } from "~/services/logger.server";
|
||||
import { apiCors } from "~/utils/apiCors";
|
||||
|
||||
const ParamsSchema = z.object({
|
||||
runId: z.string(),
|
||||
});
|
||||
|
||||
const RecordsSchema = z.array(JobRunStatusRecordSchema);
|
||||
|
||||
export async function loader({ request, params }: LoaderFunctionArgs) {
|
||||
if (request.method.toUpperCase() === "OPTIONS") {
|
||||
return apiCors(request, json({}));
|
||||
}
|
||||
|
||||
// Next authenticate the request
|
||||
const authenticationResult = await authenticateApiRequest(request, { allowPublicKey: true });
|
||||
|
||||
if (!authenticationResult) {
|
||||
return apiCors(request, json({ error: "Invalid or Missing API key" }, { status: 401 }));
|
||||
}
|
||||
|
||||
const { runId } = ParamsSchema.parse(params);
|
||||
|
||||
logger.debug("Get run statuses", {
|
||||
runId,
|
||||
});
|
||||
|
||||
try {
|
||||
const run = await prisma.jobRun.findUnique({
|
||||
where: {
|
||||
id: runId,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
status: true,
|
||||
output: true,
|
||||
statuses: {
|
||||
orderBy: {
|
||||
createdAt: "asc",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!run) {
|
||||
return apiCors(request, json({ error: `No run found for id ${runId}` }, { status: 404 }));
|
||||
}
|
||||
|
||||
const parsedStatuses = RecordsSchema.parse(
|
||||
run.statuses.map((s) => ({
|
||||
...s,
|
||||
state: s.state ?? undefined,
|
||||
data: s.data ?? undefined,
|
||||
history: s.history ?? undefined,
|
||||
}))
|
||||
);
|
||||
|
||||
return apiCors(
|
||||
request,
|
||||
json({
|
||||
run: {
|
||||
id: run.id,
|
||||
status: run.status,
|
||||
output: run.output,
|
||||
},
|
||||
statuses: parsedStatuses,
|
||||
})
|
||||
);
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
return apiCors(request, json({ error: error.message }, { status: 400 }));
|
||||
}
|
||||
|
||||
return apiCors(request, json({ error: "Something went wrong" }, { status: 500 }));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
import type { LoaderFunctionArgs } from "@remix-run/server-runtime";
|
||||
import { json } from "@remix-run/server-runtime";
|
||||
import { z } from "zod";
|
||||
import { ApiRunPresenter } from "~/presenters/ApiRunPresenter.server";
|
||||
import { authenticateApiRequest } from "~/services/apiAuth.server";
|
||||
import { apiCors } from "~/utils/apiCors";
|
||||
import { taskListToTree } from "~/utils/taskListToTree";
|
||||
|
||||
const ParamsSchema = z.object({
|
||||
runId: z.string(),
|
||||
});
|
||||
|
||||
const SearchQuerySchema = z.object({
|
||||
cursor: z.string().optional(),
|
||||
take: z.coerce.number().default(20),
|
||||
subtasks: z.coerce.boolean().default(false),
|
||||
taskdetails: z.coerce.boolean().default(false),
|
||||
});
|
||||
|
||||
export async function loader({ request, params }: LoaderFunctionArgs) {
|
||||
if (request.method.toUpperCase() === "OPTIONS") {
|
||||
return apiCors(request, json({}));
|
||||
}
|
||||
|
||||
const authenticationResult = await authenticateApiRequest(request, {
|
||||
allowPublicKey: true,
|
||||
});
|
||||
if (!authenticationResult) {
|
||||
return apiCors(request, json({ error: "Invalid or Missing API key" }, { status: 401 }));
|
||||
}
|
||||
|
||||
const authenticatedEnv = authenticationResult.environment;
|
||||
|
||||
const parsed = ParamsSchema.safeParse(params);
|
||||
|
||||
if (!parsed.success) {
|
||||
return apiCors(request, json({ error: "Invalid or missing runId" }, { status: 400 }));
|
||||
}
|
||||
|
||||
const { runId } = parsed.data;
|
||||
|
||||
const url = new URL(request.url);
|
||||
const parsedQuery = SearchQuerySchema.safeParse(Object.fromEntries(url.searchParams));
|
||||
|
||||
if (!parsedQuery.success) {
|
||||
return apiCors(
|
||||
request,
|
||||
json({ error: "Invalid or missing query parameters" }, { status: 400 })
|
||||
);
|
||||
}
|
||||
|
||||
const query = parsedQuery.data;
|
||||
const showTaskDetails = query.taskdetails && authenticationResult.type === "PRIVATE";
|
||||
const take = Math.min(query.take, 50);
|
||||
|
||||
const presenter = new ApiRunPresenter();
|
||||
const jobRun = await presenter.call({
|
||||
runId: runId,
|
||||
maxTasks: take,
|
||||
taskDetails: showTaskDetails,
|
||||
subTasks: query.subtasks,
|
||||
cursor: query.cursor,
|
||||
});
|
||||
|
||||
if (!jobRun) {
|
||||
return apiCors(request, json({ message: "Run not found" }, { status: 404 }));
|
||||
}
|
||||
|
||||
if (jobRun.environmentId !== authenticatedEnv.id) {
|
||||
return apiCors(request, json({ message: "Run not found" }, { status: 404 }));
|
||||
}
|
||||
|
||||
const selectedTasks = jobRun.tasks.slice(0, take);
|
||||
|
||||
const tasks = taskListToTree(selectedTasks, query.subtasks);
|
||||
const nextTask = jobRun.tasks[take];
|
||||
|
||||
return apiCors(
|
||||
request,
|
||||
json({
|
||||
id: jobRun.id,
|
||||
status: jobRun.status,
|
||||
startedAt: jobRun.startedAt,
|
||||
updatedAt: jobRun.updatedAt,
|
||||
completedAt: jobRun.completedAt,
|
||||
output: jobRun.output,
|
||||
tasks: tasks.map((task) => {
|
||||
const { parentId, ...rest } = task;
|
||||
return { ...rest };
|
||||
}),
|
||||
statuses: jobRun.statuses.map((s) => ({
|
||||
...s,
|
||||
state: s.state ?? undefined,
|
||||
data: s.data ?? undefined,
|
||||
history: s.history ?? undefined,
|
||||
})),
|
||||
nextCursor: nextTask ? nextTask.id : undefined,
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -17,7 +17,7 @@ export function useEventDetails(eventId: string | undefined): UseEventDetailsRes
|
||||
{
|
||||
queryKey: [`triggerdotdev-event-${eventId}`],
|
||||
queryFn: async () => {
|
||||
return await zodfetch(GetEventSchema, `${apiUrl}/api/v1/events/${eventId}`, {
|
||||
return await zodfetch(GetEventSchema, `${apiUrl}/api/v2/events/${eventId}`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
Authorization: `Bearer ${publicApiKey}`,
|
||||
|
||||
@@ -24,7 +24,7 @@ export function useRunDetails(
|
||||
|
||||
const { refreshIntervalMs: refreshInterval, ...otherOptions } = options || {};
|
||||
|
||||
const url = urlWithSearchParams(`${apiUrl}/api/v1/runs/${runId}`, otherOptions);
|
||||
const url = urlWithSearchParams(`${apiUrl}/api/v2/runs/${runId}`, otherOptions);
|
||||
|
||||
return useQuery(
|
||||
{
|
||||
|
||||
@@ -49,7 +49,7 @@ export function useRunStatuses(
|
||||
{
|
||||
queryKey: [`triggerdotdev-run-${runId}`],
|
||||
queryFn: async () => {
|
||||
return await zodfetch(GetRunStatusesSchema, `${apiUrl}/api/v1/runs/${runId}/statuses`, {
|
||||
return await zodfetch(GetRunStatusesSchema, `${apiUrl}/api/v2/runs/${runId}/statuses`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
Authorization: `Bearer ${publicApiKey}`,
|
||||
|
||||
@@ -450,7 +450,7 @@ export class ApiClient {
|
||||
eventId,
|
||||
});
|
||||
|
||||
return await zodfetch(GetEventSchema, `${this.#apiUrl}/api/v1/events/${eventId}`, {
|
||||
return await zodfetch(GetEventSchema, `${this.#apiUrl}/api/v2/events/${eventId}`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
Authorization: `Bearer ${apiKey}`,
|
||||
@@ -467,7 +467,7 @@ export class ApiClient {
|
||||
|
||||
return await zodfetch(
|
||||
GetRunSchema,
|
||||
urlWithSearchParams(`${this.#apiUrl}/api/v1/runs/${runId}`, options),
|
||||
urlWithSearchParams(`${this.#apiUrl}/api/v2/runs/${runId}`, options),
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
@@ -500,7 +500,7 @@ export class ApiClient {
|
||||
runId,
|
||||
});
|
||||
|
||||
return await zodfetch(GetRunStatusesSchema, `${this.#apiUrl}/api/v1/runs/${runId}/statuses`, {
|
||||
return await zodfetch(GetRunStatusesSchema, `${this.#apiUrl}/api/v2/runs/${runId}/statuses`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
Authorization: `Bearer ${apiKey}`,
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint",
|
||||
"generate:types": "npx supabase gen types typescript --project-id axtbanoixaztvdntngew --schema public --schema public_2 > src/supabase.types.ts"
|
||||
"generate:types": "npx supabase gen types typescript --project-id axtbanoixaztvdntngew --schema public --schema public_2 > src/supabase.types.ts",
|
||||
"dev:trigger": "trigger-cli dev --port 3000"
|
||||
},
|
||||
"dependencies": {
|
||||
"@trigger.dev/eslint-plugin": "workspace:*",
|
||||
@@ -43,4 +44,4 @@
|
||||
"trigger.dev": {
|
||||
"endpointId": "nextjs-example"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,10 @@ client.defineJob({
|
||||
// state: "loading",
|
||||
});
|
||||
|
||||
await io.runTask("task-1", async () => {
|
||||
await io.wait("wait-subtask", 2);
|
||||
});
|
||||
|
||||
await io.wait("wait-input", 2);
|
||||
|
||||
await gettingInputData.update("input-data-complete", {
|
||||
@@ -54,7 +58,7 @@ client.defineJob({
|
||||
},
|
||||
});
|
||||
|
||||
await io.wait("wait-again", 4);
|
||||
await io.wait("wait-again-2", 4);
|
||||
|
||||
await generatingMemes.update("completed-generation", {
|
||||
label: "Generated memes",
|
||||
|
||||
Reference in New Issue
Block a user