Skip to content

Commit

Permalink
feat: allow cancelling jobs from trigger-client sdk (#562)
Browse files Browse the repository at this point in the history
* feat: allow cancelling jobs from trigger-client sdk

* use presenter instead of service for non-mutating logic
  • Loading branch information
hmacr authored Oct 5, 2023
1 parent 914745f commit 2e9452a
Show file tree
Hide file tree
Showing 6 changed files with 176 additions and 44 deletions.
5 changes: 5 additions & 0 deletions .changeset/orange-falcons-smile.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@trigger.dev/sdk": patch
---

allow cancelling jobs from trigger-client
72 changes: 72 additions & 0 deletions apps/webapp/app/presenters/ApiRunPresenter.server.ts
Original file line number Diff line number Diff line change
@@ -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 },
},
},
});
}
}
71 changes: 71 additions & 0 deletions apps/webapp/app/routes/api.v1.runs.$runId.cancel.ts
Original file line number Diff line number Diff line change
@@ -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,
})),
});
}
52 changes: 8 additions & 44 deletions apps/webapp/app/routes/api.v1.runs.$runId.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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) {
Expand Down
16 changes: 16 additions & 0 deletions packages/trigger-sdk/src/apiClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down
4 changes: 4 additions & 0 deletions packages/trigger-sdk/src/triggerClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down

0 comments on commit 2e9452a

Please sign in to comment.