Files
triggerdotdev--trigger.dev/apps/webapp/app/presenters/RunPresenter.server.ts
2024-08-15 15:34:57 +01:00

243 lines
6.4 KiB
TypeScript

import {
ErrorWithStack,
ErrorWithStackSchema,
EventSpecificationSchema,
StyleSchema,
} from "@trigger.dev/core";
import { $replica, PrismaClient, prisma } from "~/db.server";
import { isRunCompleted, runBasicStatus } from "~/models/jobRun.server";
import { mergeProperties } from "~/utils/mergeProperties.server";
import { taskListToTree } from "~/utils/taskListToTree";
import { getUsername } from "~/utils/username";
type RunOptions = {
id: string;
userId: string;
};
export type ViewRun = NonNullable<Awaited<ReturnType<RunPresenter["call"]>>>;
export type ViewTask = NonNullable<Awaited<ReturnType<RunPresenter["call"]>>>["tasks"][number];
export type ViewEvent = NonNullable<Awaited<ReturnType<RunPresenter["call"]>>>["event"];
type QueryEvent = NonNullable<Awaited<ReturnType<RunPresenter["query"]>>>["event"];
type QueryTask = NonNullable<Awaited<ReturnType<RunPresenter["query"]>>>["tasks"][number];
export class RunPresenter {
#prismaClient: PrismaClient;
constructor(prismaClient: PrismaClient = prisma) {
this.#prismaClient = prismaClient;
}
public async call({ id, userId }: RunOptions) {
const run = await this.query({ id, userId });
if (!run) {
return undefined;
}
const eventSpecification = EventSpecificationSchema.parse(run.version.eventSpecification);
const runProperties = mergeProperties(
run.version.properties,
run.properties,
eventSpecification.properties
);
//enrich tasks then group subtasks under their parents
const enrichedTasks = this.enrichTasks(run.tasks);
const tasks = taskListToTree(enrichedTasks);
let runError: ErrorWithStack | undefined = undefined;
let runOutput: string | null | undefined = run.output
? JSON.stringify(run.output, null, 2)
: null;
if (run.status === "FAILURE") {
const error = ErrorWithStackSchema.safeParse(run.output);
if (error.success) {
runError = error.data;
runOutput = null;
} else {
runError = { message: "Unknown error" };
runOutput = null;
}
}
return {
id: run.id,
number: run.number,
status: run.status,
basicStatus: runBasicStatus(run.status),
isFinished: isRunCompleted(run.status),
startedAt: run.startedAt,
completedAt: run.completedAt,
isTest: run.isTest,
version: run.version.version,
output: runOutput,
properties: runProperties,
environment: {
type: run.environment.type,
slug: run.environment.slug,
userId: run.environment.orgMember?.user.id,
userName: getUsername(run.environment.orgMember?.user),
},
event: this.#prepareEventData(run.event),
tasks,
runConnections: run.runConnections,
missingConnections: run.missingConnections,
error: runError,
executionDuration: run.executionDuration,
executionCount: run.executionCount,
};
}
#prepareEventData(event: QueryEvent) {
return {
id: event.eventId,
name: event.name,
payload: JSON.stringify(event.payload),
context: JSON.stringify(event.context),
timestamp: event.timestamp,
deliveredAt: event.deliveredAt,
externalAccount: event.externalAccount
? {
identifier: event.externalAccount.identifier,
}
: undefined,
};
}
query({ id, userId }: RunOptions) {
return $replica.jobRun.findFirst({
select: {
id: true,
number: true,
status: true,
startedAt: true,
completedAt: true,
isTest: true,
properties: true,
output: true,
executionCount: true,
executionDuration: true,
version: {
select: {
version: true,
properties: true,
eventSpecification: true,
},
},
environment: {
select: {
type: true,
slug: true,
orgMember: {
select: {
user: {
select: {
id: true,
name: true,
displayName: true,
},
},
},
},
},
},
event: {
select: {
eventId: true,
name: true,
payload: true,
context: true,
timestamp: true,
deliveredAt: true,
externalAccount: {
select: {
identifier: true,
},
},
},
},
tasks: {
select: {
id: true,
displayKey: true,
name: true,
icon: true,
status: true,
delayUntil: true,
description: true,
properties: true,
outputProperties: true,
error: true,
startedAt: true,
completedAt: true,
style: true,
parentId: true,
noop: true,
runConnection: {
select: {
integration: {
select: {
definitionId: true,
title: true,
slug: true,
definition: {
select: {
icon: true,
},
},
},
},
},
},
},
orderBy: {
createdAt: "asc",
},
take: 1000,
},
runConnections: {
select: {
id: true,
key: true,
integration: {
select: {
title: true,
slug: true,
description: true,
scopes: true,
definition: true,
},
},
},
},
missingConnections: true,
},
where: {
id,
organization: {
members: {
some: {
userId,
},
},
},
},
});
}
enrichTasks(tasks: QueryTask[]) {
return tasks.map((task) => ({
...task,
error: task.error ? ErrorWithStackSchema.parse(task.error) : undefined,
connection: task.runConnection,
properties: mergeProperties(task.properties, task.outputProperties),
style: task.style ? StyleSchema.parse(task.style) : undefined,
}));
}
}