243 lines
6.4 KiB
TypeScript
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,
|
|
}));
|
|
}
|
|
}
|