feat: allow cancelling jobs from trigger-client sdk (#562)

* feat: allow cancelling jobs from trigger-client sdk

* use presenter instead of service for non-mutating logic
This commit is contained in:
Hemachandar
2023-10-05 21:21:59 +05:30
committed by GitHub
parent 914745f642
commit 2e9452ab7c
6 changed files with 176 additions and 44 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"@trigger.dev/sdk": patch
---
allow cancelling jobs from trigger-client
@@ -0,0 +1,72 @@
import { Job } from "@trigger.dev/database";
import { PrismaClient, prisma } from "~/db.server";
type ApiRunOptions = {
runId: Job["id"];
maxTasks?: number;
taskDetails?: boolean;
subTasks?: boolean;
cursor?: string;
};
export class ApiRunPresenter {
#prismaClient: PrismaClient;
constructor(prismaClient: PrismaClient = prisma) {
this.#prismaClient = prismaClient;
}
public async call({
runId,
maxTasks = 20,
taskDetails = false,
subTasks = false,
cursor,
}: ApiRunOptions) {
const take = Math.min(maxTasks, 50);
return await prisma.jobRun.findUnique({
where: {
id: runId,
},
select: {
id: true,
status: true,
startedAt: true,
updatedAt: true,
completedAt: true,
environmentId: true,
output: true,
tasks: {
select: {
id: true,
parentId: true,
displayKey: true,
status: true,
name: true,
icon: true,
startedAt: true,
completedAt: true,
params: taskDetails,
output: taskDetails,
},
where: {
parentId: subTasks ? undefined : null,
},
orderBy: {
id: "asc",
},
take: take + 1,
cursor: cursor
? {
id: cursor,
}
: undefined,
},
statuses: {
select: { key: true, label: true, state: true, data: true, history: true },
},
},
});
}
}
@@ -0,0 +1,71 @@
import type { ActionArgs } from "@remix-run/server-runtime";
import { json } from "@remix-run/server-runtime";
import { PrismaErrorSchema } from "~/db.server";
import { z } from "zod";
import { authenticateApiRequest } from "~/services/apiAuth.server";
import { CancelRunService } from "~/services/runs/cancelRun.server";
import { ApiRunPresenter } from "~/presenters/ApiRunPresenter.server";
const ParamsSchema = z.object({
runId: z.string(),
});
export async function action({ request, params }: ActionArgs) {
// Ensure this is a POST request
if (request.method.toUpperCase() !== "POST") {
return { status: 405, body: "Method Not Allowed" };
}
// Authenticate the request
const authenticationResult = await authenticateApiRequest(request);
if (!authenticationResult) {
return json({ error: "Invalid or Missing API Key" }, { status: 401 });
}
const parsed = ParamsSchema.safeParse(params);
if (!parsed.success) {
return json({ error: "Invalid or Missing runId" }, { status: 400 });
}
const { runId } = parsed.data;
const service = new CancelRunService();
try {
await service.call({ runId });
} catch (error) {
const prismaError = PrismaErrorSchema.safeParse(error);
// Record not found in the database
if (prismaError.success && prismaError.data.code === "P2005") {
return json({ error: "Run not found" }, { status: 404 });
} else {
return json({ error: "Internal Server Error" }, { status: 500 });
}
}
const presenter = new ApiRunPresenter();
const jobRun = await presenter.call({
runId: runId,
});
if (!jobRun) {
return json({ message: "Run not found" }, { status: 404 });
}
return json({
id: jobRun.id,
status: jobRun.status,
startedAt: jobRun.startedAt,
updatedAt: jobRun.updatedAt,
completedAt: jobRun.completedAt,
output: jobRun.output,
tasks: jobRun.tasks,
statuses: jobRun.statuses.map((s) => ({
...s,
state: s.state ?? undefined,
data: s.data ?? undefined,
history: s.history ?? undefined,
})),
});
}
+8 -44
View File
@@ -1,7 +1,7 @@
import type { LoaderArgs } from "@remix-run/server-runtime";
import { json } from "@remix-run/server-runtime";
import { z } from "zod";
import { prisma } from "~/db.server";
import { ApiRunPresenter } from "~/presenters/ApiRunPresenter.server";
import { authenticateApiRequest } from "~/services/apiAuth.server";
import { apiCors } from "~/utils/apiCors";
import { taskListToTree } from "~/utils/taskListToTree";
@@ -51,51 +51,15 @@ export async function loader({ request, params }: LoaderArgs) {
const query = parsedQuery.data;
const showTaskDetails = query.taskdetails && authenticationResult.type === "PRIVATE";
const take = Math.min(query.take, 50);
const jobRun = await prisma.jobRun.findUnique({
where: {
id: runId,
},
select: {
id: true,
status: true,
startedAt: true,
updatedAt: true,
completedAt: true,
environmentId: true,
output: true,
tasks: {
select: {
id: true,
parentId: true,
displayKey: true,
status: true,
name: true,
icon: true,
startedAt: true,
completedAt: true,
params: showTaskDetails,
output: showTaskDetails,
},
where: {
parentId: query.subtasks ? undefined : null,
},
orderBy: {
id: "asc",
},
take: take + 1,
cursor: query.cursor
? {
id: query.cursor,
}
: undefined,
},
statuses: {
select: { key: true, label: true, state: true, data: true, history: true },
},
},
const presenter = new ApiRunPresenter();
const jobRun = await presenter.call({
runId: runId,
maxTasks: take,
taskDetails: showTaskDetails,
subTasks: query.subtasks,
cursor: query.cursor,
});
if (!jobRun) {
+16
View File
@@ -399,6 +399,22 @@ export class ApiClient {
);
}
async cancelRun(runId: string) {
const apiKey = await this.#apiKey();
this.#logger.debug("Cancelling Run", {
runId,
});
return await zodfetch(GetRunSchema, `${this.#apiUrl}/api/v1/runs/${runId}/cancel`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${apiKey}`,
},
});
}
async getRunStatuses(runId: string) {
const apiKey = await this.#apiKey();
@@ -642,6 +642,10 @@ export class TriggerClient {
return this.#client.getRun(runId, options);
}
async cancelRun(runId: string) {
return this.#client.cancelRun(runId);
}
async getRuns(jobSlug: string, options?: GetRunsOptions) {
return this.#client.getRuns(jobSlug, options);
}