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}`],
|
queryKey: [`triggerdotdev-event-${eventId}`],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
return await zodfetch(GetEventSchema, `${apiUrl}/api/v1/events/${eventId}`, {
|
return await zodfetch(GetEventSchema, `${apiUrl}/api/v2/events/${eventId}`, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${publicApiKey}`,
|
Authorization: `Bearer ${publicApiKey}`,
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export function useRunDetails(
|
|||||||
|
|
||||||
const { refreshIntervalMs: refreshInterval, ...otherOptions } = options || {};
|
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(
|
return useQuery(
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ export function useRunStatuses(
|
|||||||
{
|
{
|
||||||
queryKey: [`triggerdotdev-run-${runId}`],
|
queryKey: [`triggerdotdev-run-${runId}`],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
return await zodfetch(GetRunStatusesSchema, `${apiUrl}/api/v1/runs/${runId}/statuses`, {
|
return await zodfetch(GetRunStatusesSchema, `${apiUrl}/api/v2/runs/${runId}/statuses`, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${publicApiKey}`,
|
Authorization: `Bearer ${publicApiKey}`,
|
||||||
|
|||||||
@@ -450,7 +450,7 @@ export class ApiClient {
|
|||||||
eventId,
|
eventId,
|
||||||
});
|
});
|
||||||
|
|
||||||
return await zodfetch(GetEventSchema, `${this.#apiUrl}/api/v1/events/${eventId}`, {
|
return await zodfetch(GetEventSchema, `${this.#apiUrl}/api/v2/events/${eventId}`, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${apiKey}`,
|
Authorization: `Bearer ${apiKey}`,
|
||||||
@@ -467,7 +467,7 @@ export class ApiClient {
|
|||||||
|
|
||||||
return await zodfetch(
|
return await zodfetch(
|
||||||
GetRunSchema,
|
GetRunSchema,
|
||||||
urlWithSearchParams(`${this.#apiUrl}/api/v1/runs/${runId}`, options),
|
urlWithSearchParams(`${this.#apiUrl}/api/v2/runs/${runId}`, options),
|
||||||
{
|
{
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
@@ -500,7 +500,7 @@ export class ApiClient {
|
|||||||
runId,
|
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",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${apiKey}`,
|
Authorization: `Bearer ${apiKey}`,
|
||||||
|
|||||||
@@ -7,7 +7,8 @@
|
|||||||
"build": "next build",
|
"build": "next build",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "next lint",
|
"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": {
|
"dependencies": {
|
||||||
"@trigger.dev/eslint-plugin": "workspace:*",
|
"@trigger.dev/eslint-plugin": "workspace:*",
|
||||||
@@ -43,4 +44,4 @@
|
|||||||
"trigger.dev": {
|
"trigger.dev": {
|
||||||
"endpointId": "nextjs-example"
|
"endpointId": "nextjs-example"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,10 @@ client.defineJob({
|
|||||||
// state: "loading",
|
// state: "loading",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await io.runTask("task-1", async () => {
|
||||||
|
await io.wait("wait-subtask", 2);
|
||||||
|
});
|
||||||
|
|
||||||
await io.wait("wait-input", 2);
|
await io.wait("wait-input", 2);
|
||||||
|
|
||||||
await gettingInputData.update("input-data-complete", {
|
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", {
|
await generatingMemes.update("completed-generation", {
|
||||||
label: "Generated memes",
|
label: "Generated memes",
|
||||||
|
|||||||
Reference in New Issue
Block a user