c405ae7117
* Added maximumScheduleInstancesLimit column to Org, default to 20 * Docs on the schedule limits and improved soft-limit communication * Added limit info to the schedules list page * Created a task that creates schedules, useful for testing * Make deduplicationKey required when creating/updating a schedule using the SDK * New schedule button shows an alert if you’re over the limit * Added timezone to the form and db * WIP on the timezone dropdown for the create/edit schedule form * Use the new filter search for timezones * Made the timezone dropdown faster by fixing the virtualization * The preview table is working and added a nice message about daylight savings * Created a page where you can view the full list of timezones The URL is included in the error message if you send an invalid time using the SDK * Creating tasks with the timezone * Added timezone support the the scheduler and the schedules list * Added timezone support to more of the schedules UI * The timezone comes through to scheduled runs with nice JSDocs * Allow setting the timezone from the SDK * Always have a timezone on a schedule * Updated jsdocs * Updated catalog example * Changed the column to be a string, not null. Added the timezone across the SDK * API endpoint for getting the timezones * Added an SDK function to get the list of timezones * Added timezones to the docs * Changeset: Added timezone support to schedules * Added support for testing timezone * Tidied up imports * Imports * Imports * Update limits.mdx * Fixed a couple type issues and use the already exported zodfetch --------- Co-authored-by: Eric Allam <eallam@icloud.com>
209 lines
5.1 KiB
TypeScript
209 lines
5.1 KiB
TypeScript
import { ScheduledTaskPayload, parsePacket, prettyPrintPacket } from "@trigger.dev/core/v3";
|
|
import {
|
|
RuntimeEnvironmentType,
|
|
TaskRunAttemptStatus,
|
|
TaskRunStatus,
|
|
TaskTriggerSource,
|
|
} from "@trigger.dev/database";
|
|
import { sqlDatabaseSchema, PrismaClient, prisma } from "~/db.server";
|
|
import { getTimezones } from "~/utils/timezones.server";
|
|
import { getUsername } from "~/utils/username";
|
|
|
|
type TestTaskOptions = {
|
|
userId: string;
|
|
projectSlug: string;
|
|
taskFriendlyId: string;
|
|
};
|
|
|
|
type Task = {
|
|
id: string;
|
|
taskIdentifier: string;
|
|
filePath: string;
|
|
exportName: string;
|
|
friendlyId: string;
|
|
environment: {
|
|
id: string;
|
|
type: RuntimeEnvironmentType;
|
|
userId?: string;
|
|
userName?: string;
|
|
};
|
|
};
|
|
|
|
export type TestTask =
|
|
| {
|
|
triggerSource: "STANDARD";
|
|
task: Task;
|
|
runs: StandardRun[];
|
|
}
|
|
| {
|
|
triggerSource: "SCHEDULED";
|
|
task: Task;
|
|
possibleTimezones: string[];
|
|
runs: ScheduledRun[];
|
|
};
|
|
|
|
type RawRun = {
|
|
id: string;
|
|
number: BigInt;
|
|
friendlyId: string;
|
|
createdAt: Date;
|
|
status: TaskRunStatus;
|
|
payload: string;
|
|
payloadType: string;
|
|
runtimeEnvironmentId: string;
|
|
};
|
|
|
|
export type StandardRun = Omit<RawRun, "number"> & {
|
|
number: number;
|
|
};
|
|
|
|
export type ScheduledRun = Omit<RawRun, "number" | "payload"> & {
|
|
number: number;
|
|
payload: {
|
|
timestamp: Date;
|
|
lastTimestamp?: Date;
|
|
externalId?: string;
|
|
timezone: string;
|
|
};
|
|
};
|
|
|
|
export class TestTaskPresenter {
|
|
#prismaClient: PrismaClient;
|
|
|
|
constructor(prismaClient: PrismaClient = prisma) {
|
|
this.#prismaClient = prismaClient;
|
|
}
|
|
|
|
public async call({ userId, projectSlug, taskFriendlyId }: TestTaskOptions): Promise<TestTask> {
|
|
const task = await this.#prismaClient.backgroundWorkerTask.findFirstOrThrow({
|
|
select: {
|
|
id: true,
|
|
filePath: true,
|
|
exportName: true,
|
|
slug: true,
|
|
triggerSource: true,
|
|
runtimeEnvironment: {
|
|
select: {
|
|
id: true,
|
|
type: true,
|
|
orgMember: {
|
|
select: {
|
|
user: {
|
|
select: {
|
|
id: true,
|
|
name: true,
|
|
displayName: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
where: {
|
|
friendlyId: taskFriendlyId,
|
|
},
|
|
});
|
|
|
|
const latestRuns = await this.#prismaClient.$queryRaw<RawRun[]>`
|
|
WITH taskruns AS (
|
|
SELECT
|
|
tr.*
|
|
FROM
|
|
${sqlDatabaseSchema}."TaskRun" as tr
|
|
JOIN
|
|
${sqlDatabaseSchema}."BackgroundWorkerTask" as bwt
|
|
ON
|
|
tr."taskIdentifier" = bwt.slug
|
|
WHERE
|
|
bwt."friendlyId" = ${taskFriendlyId} AND
|
|
tr."runtimeEnvironmentId" = ${task.runtimeEnvironment.id}
|
|
ORDER BY
|
|
tr."createdAt" DESC
|
|
LIMIT 5
|
|
)
|
|
SELECT
|
|
taskr.id,
|
|
taskr.number,
|
|
taskr."friendlyId",
|
|
taskr."taskIdentifier",
|
|
taskr."createdAt",
|
|
taskr.status,
|
|
taskr.payload,
|
|
taskr."payloadType",
|
|
taskr."runtimeEnvironmentId"
|
|
FROM
|
|
taskruns AS taskr
|
|
WHERE
|
|
taskr."payloadType" = 'application/json' OR taskr."payloadType" = 'application/super+json'
|
|
ORDER BY
|
|
taskr."createdAt" DESC;`;
|
|
|
|
const taskWithEnvironment = {
|
|
id: task.id,
|
|
taskIdentifier: task.slug,
|
|
filePath: task.filePath,
|
|
exportName: task.exportName,
|
|
friendlyId: taskFriendlyId,
|
|
environment: {
|
|
id: task.runtimeEnvironment.id,
|
|
type: task.runtimeEnvironment.type,
|
|
userId: task.runtimeEnvironment.orgMember?.user.id,
|
|
userName: getUsername(task.runtimeEnvironment.orgMember?.user),
|
|
},
|
|
};
|
|
|
|
switch (task.triggerSource) {
|
|
case "STANDARD":
|
|
return {
|
|
triggerSource: "STANDARD",
|
|
task: taskWithEnvironment,
|
|
runs: await Promise.all(
|
|
latestRuns.map(async (r) => {
|
|
const number = Number(r.number);
|
|
|
|
return {
|
|
...r,
|
|
number,
|
|
payload: await prettyPrintPacket(r.payload, r.payloadType),
|
|
};
|
|
})
|
|
),
|
|
};
|
|
case "SCHEDULED":
|
|
const possibleTimezones = getTimezones();
|
|
return {
|
|
triggerSource: "SCHEDULED",
|
|
task: taskWithEnvironment,
|
|
possibleTimezones,
|
|
runs: (
|
|
await Promise.all(
|
|
latestRuns.map(async (r) => {
|
|
const number = Number(r.number);
|
|
|
|
const payload = await getScheduleTaskRunPayload(r);
|
|
|
|
if (payload.success) {
|
|
return {
|
|
...r,
|
|
number,
|
|
payload: payload.data,
|
|
};
|
|
}
|
|
})
|
|
)
|
|
).filter(Boolean),
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
async function getScheduleTaskRunPayload(run: RawRun) {
|
|
const payload = await parsePacket({ data: run.payload, dataType: run.payloadType });
|
|
if (!payload.timezone) {
|
|
payload.timezone = "UTC";
|
|
}
|
|
const parsed = ScheduledTaskPayload.safeParse(payload);
|
|
return parsed;
|
|
}
|