From 54116a1f6d465c9aa86c648e13d45e916315c4e8 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Tue, 19 Sep 2023 15:07:30 +0100 Subject: [PATCH 01/24] Added stripInternal to SDK tsconfig --- packages/trigger-sdk/tsconfig.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/trigger-sdk/tsconfig.json b/packages/trigger-sdk/tsconfig.json index e5b64c11cd..7b13d85e2b 100644 --- a/packages/trigger-sdk/tsconfig.json +++ b/packages/trigger-sdk/tsconfig.json @@ -10,7 +10,8 @@ }, "lib": ["DOM", "DOM.Iterable"], "declaration": false, - "declarationMap": false + "declarationMap": false, + "stripInternal": true }, "exclude": ["node_modules"] } From 22fd21e2c5f1a0a57072d9aea0969ba237774d21 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Tue, 19 Sep 2023 16:02:41 +0100 Subject: [PATCH 02/24] Statuses can now be set from a run, and are stored in the database --- .../routes/api.v1.runs.$runId.statuses.$id.ts | 147 ++++++++++++++++++ packages/core/src/schemas/api.ts | 27 ++++ .../migration.sql | 20 +++ packages/database/prisma/schema.prisma | 20 +++ packages/trigger-sdk/src/apiClient.ts | 24 +++ packages/trigger-sdk/src/io.ts | 57 ++++++- packages/trigger-sdk/src/status.ts | 44 ++++++ packages/trigger-sdk/src/triggerClient.ts | 5 + references/job-catalog/package.json | 3 +- references/job-catalog/src/status.ts | 43 +++++ 10 files changed, 387 insertions(+), 3 deletions(-) create mode 100644 apps/webapp/app/routes/api.v1.runs.$runId.statuses.$id.ts create mode 100644 packages/database/prisma/migrations/20230919141017_added_job_run_status_record_table/migration.sql create mode 100644 packages/trigger-sdk/src/status.ts create mode 100644 references/job-catalog/src/status.ts diff --git a/apps/webapp/app/routes/api.v1.runs.$runId.statuses.$id.ts b/apps/webapp/app/routes/api.v1.runs.$runId.statuses.$id.ts new file mode 100644 index 0000000000..6078dab800 --- /dev/null +++ b/apps/webapp/app/routes/api.v1.runs.$runId.statuses.$id.ts @@ -0,0 +1,147 @@ +import type { ActionArgs } from "@remix-run/server-runtime"; +import { json } from "@remix-run/server-runtime"; +import { JobRunStatusRecord, TaskStatus } from "@trigger.dev/database"; +import { + RunTaskBodyOutput, + RunTaskBodyOutputSchema, + ServerTask, + StatusHistory, + StatusHistorySchema, + StatusUpdate, + StatusUpdateData, + StatusUpdateSchema, + StatusUpdateState, +} from "@trigger.dev/core"; +import { z } from "zod"; +import { $transaction, PrismaClient, prisma } from "~/db.server"; +import { taskWithAttemptsToServerTask } from "~/models/task.server"; +import { authenticateApiRequest } from "~/services/apiAuth.server"; +import { logger } from "~/services/logger.server"; +import { ulid } from "~/services/ulid.server"; +import { workerQueue } from "~/services/worker.server"; + +const ParamsSchema = z.object({ + runId: z.string(), + id: z.string(), +}); + +export async function action({ request, params }: ActionArgs) { + // Ensure this is a POST request + if (request.method.toUpperCase() !== "PUT") { + return { status: 405, body: "Method Not Allowed" }; + } + + // Next authenticate the request + const authenticationResult = await authenticateApiRequest(request); + + if (!authenticationResult) { + return json({ error: "Invalid or Missing API key" }, { status: 401 }); + } + + const { runId, id } = ParamsSchema.parse(params); + + // Now parse the request body + const anyBody = await request.json(); + + logger.debug("SetStatusService.call() request body", { + body: anyBody, + runId, + id, + }); + + const body = StatusUpdateSchema.safeParse(anyBody); + + if (!body.success) { + return json({ error: "Invalid request body" }, { status: 400 }); + } + + const service = new SetStatusService(); + + try { + const status = await service.call(runId, id, body.data); + + logger.debug("SetStatusService.call() response body", { + runId, + id, + status, + }); + + if (!status) { + return json({ error: "Something went wrong" }, { status: 500 }); + } + + return json(status); + } catch (error) { + if (error instanceof Error) { + return json({ error: error.message }, { status: 400 }); + } + + return json({ error: "Something went wrong" }, { status: 500 }); + } +} + +export class SetStatusService { + #prismaClient: PrismaClient; + + constructor(prismaClient: PrismaClient = prisma) { + this.#prismaClient = prismaClient; + } + + public async call( + runId: string, + id: string, + status: StatusUpdate + ): Promise { + const statusRecord = await $transaction(this.#prismaClient, async (tx) => { + const existingStatus = await tx.jobRunStatusRecord.findUnique({ + where: { + runId_key: { + runId, + key: id, + }, + }, + }); + + const history: StatusHistory = []; + const historyResult = StatusHistorySchema.safeParse(existingStatus?.history); + if (historyResult.success) { + history.push(...historyResult.data); + } + if (existingStatus) { + history.push({ + label: existingStatus.label, + state: existingStatus.state as StatusUpdateState, + data: existingStatus.data as StatusUpdateData, + }); + } + + const updatedStatus = await tx.jobRunStatusRecord.upsert({ + where: { + runId_key: { + runId, + key: id, + }, + }, + create: { + key: id, + runId, + //this shouldn't ever use the id in reality, as the SDK makess it compulsory on the first call + label: status.label ?? id, + state: status.state, + data: status.data as any, + history: [], + }, + update: { + label: status.label, + state: status.state, + data: status.data as any, + history: history as any[], + }, + }); + + return updatedStatus; + }); + + return statusRecord; + } +} diff --git a/packages/core/src/schemas/api.ts b/packages/core/src/schemas/api.ts index 112c33191a..00626f8dc5 100644 --- a/packages/core/src/schemas/api.ts +++ b/packages/core/src/schemas/api.ts @@ -729,3 +729,30 @@ export const CreateExternalConnectionBodySchema = z.object({ }); export type CreateExternalConnectionBody = z.infer; + +const StatusUpdateStateSchema = z.union([ + z.literal("loading"), + z.literal("success"), + z.literal("failure"), +]); +export type StatusUpdateState = z.infer; + +const StatusUpdateDataSchema = z.record(SerializableJsonSchema); +export type StatusUpdateData = z.infer; + +export const StatusUpdateSchema = z.object({ + label: z.string().optional(), + state: StatusUpdateStateSchema.optional(), + data: StatusUpdateDataSchema.optional(), +}); +export type StatusUpdate = z.infer; + +const InitalStatusUpdateSchema = StatusUpdateSchema.required({ label: true }); +export type InitialStatusUpdate = z.infer; + +export const StatusHistorySchema = z.array(StatusUpdateSchema); +export type StatusHistory = z.infer; + +export const JobRunStatusRecord = InitalStatusUpdateSchema.extend({ + history: StatusHistorySchema.default([]), +}); diff --git a/packages/database/prisma/migrations/20230919141017_added_job_run_status_record_table/migration.sql b/packages/database/prisma/migrations/20230919141017_added_job_run_status_record_table/migration.sql new file mode 100644 index 0000000000..dd1eb9b312 --- /dev/null +++ b/packages/database/prisma/migrations/20230919141017_added_job_run_status_record_table/migration.sql @@ -0,0 +1,20 @@ +-- CreateTable +CREATE TABLE "JobRunStatusRecord" ( + "id" TEXT NOT NULL, + "key" TEXT NOT NULL, + "runId" TEXT NOT NULL, + "label" TEXT NOT NULL, + "state" TEXT, + "data" JSONB, + "history" JSONB, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "JobRunStatusRecord_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "JobRunStatusRecord_runId_key_key" ON "JobRunStatusRecord"("runId", "key"); + +-- AddForeignKey +ALTER TABLE "JobRunStatusRecord" ADD CONSTRAINT "JobRunStatusRecord_runId_fkey" FOREIGN KEY ("runId") REFERENCES "JobRun"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/packages/database/prisma/schema.prisma b/packages/database/prisma/schema.prisma index 2878bc638c..7982ea4d9f 100644 --- a/packages/database/prisma/schema.prisma +++ b/packages/database/prisma/schema.prisma @@ -716,6 +716,7 @@ model JobRun { runConnections RunConnection[] missingConnections MissingConnection[] executions JobRunExecution[] + JobRunStatusRecord JobRunStatusRecord[] } enum JobRunStatus { @@ -850,6 +851,25 @@ enum TaskAttemptStatus { ERRORED } +model JobRunStatusRecord { + id String @id @default(cuid()) + key String + + run JobRun @relation(fields: [runId], references: [id], onDelete: Cascade, onUpdate: Cascade) + runId String + + label String + state String? + data Json? + + history Json? + + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + @@unique([runId, key]) +} + model SecretReference { id String @id @default(cuid()) key String @unique diff --git a/packages/trigger-sdk/src/apiClient.ts b/packages/trigger-sdk/src/apiClient.ts index 394284d1fa..2d561201ea 100644 --- a/packages/trigger-sdk/src/apiClient.ts +++ b/packages/trigger-sdk/src/apiClient.ts @@ -24,6 +24,8 @@ import { urlWithSearchParams, UpdateTriggerSourceBodyV2, RegisterTriggerBodyV2, + StatusUpdate, + JobRunStatusRecord, } from "@trigger.dev/core"; import fetch, { type RequestInit } from "node-fetch"; @@ -198,6 +200,28 @@ export class ApiClient { }); } + async updateStatus(runId: string, id: string, status: StatusUpdate) { + const apiKey = await this.#apiKey(); + + this.#logger.debug("Update status", { + id, + status, + }); + + return await zodfetch( + JobRunStatusRecord, + `${this.#apiUrl}/api/v1/runs/${runId}/statuses/${id}`, + { + method: "PUT", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${apiKey}`, + }, + body: JSON.stringify(status), + } + ); + } + async updateSource( client: string, key: string, diff --git a/packages/trigger-sdk/src/io.ts b/packages/trigger-sdk/src/io.ts index 1e3574e0d1..5e0101528f 100644 --- a/packages/trigger-sdk/src/io.ts +++ b/packages/trigger-sdk/src/io.ts @@ -5,13 +5,13 @@ import { ErrorWithStackSchema, FetchRequestInit, FetchRetryOptions, + InitialStatusUpdate, IntervalOptions, LogLevel, Logger, RunTaskOptions, SendEvent, SendEventOptions, - SerializableJson, SerializableJsonSchema, ServerTask, UpdateTriggerSourceBodyV2, @@ -25,13 +25,14 @@ import { RetryWithTaskError, isTriggerError, } from "./errors"; -import { createIOWithIntegrations } from "./ioWithIntegrations"; import { calculateRetryAt } from "./retry"; import { TriggerClient } from "./triggerClient"; import { DynamicTrigger } from "./triggers/dynamic"; import { ExternalSource, ExternalSourceParams } from "./triggers/externalSource"; import { DynamicSchedule } from "./triggers/scheduled"; import { EventSpecification, TaskLogger, TriggerContext } from "./types"; +import { IntegrationTaskKey } from "./integrations"; +import { TriggerStatus } from "./status"; export type IOTask = ServerTask; @@ -88,6 +89,16 @@ export class IO { this._context = options.context; } + /** @internal */ + get runId() { + return this._id; + } + + /** @internal */ + get triggerClient() { + return this._triggerClient; + } + /** Used to send log messages to the [Run log](https://trigger.dev/docs/documentation/guides/viewing-runs). */ get logger() { return new IOLogger(async (level, message, data) => { @@ -150,6 +161,48 @@ export class IO { }); } + /** `io.createStatus()` allows you to set a status with associated data during the Run. Statuses can be used by your UI using the react package + * @param key Should be a stable and unique key inside the `run()`. See [resumability](https://trigger.dev/docs/documentation/concepts/resumability) for more information. + * @param initialStatus The initial status you want this status to have. You can update it during the rub using the returned object. + * @returns a TriggerStatus object that you can call `update()` on, to update the status. + * @example + * ```ts + * client.defineJob( + //... + run: async (payload, io, ctx) => { + const generatingImages = await io.createStatus("generating-images", { + label: "Generating Images", + state: "loading", + data: { + progress: 0.1, + }, + }); + + //...do stuff + + await generatingImages.update("completed-generation", { + label: "Generated images", + state: "success", + data: { + progress: 1.0, + urls: ["http://..."] + }, + }); + + //... + }); + * ``` + */ + async createStatus( + key: IntegrationTaskKey, + initialStatus: InitialStatusUpdate + ): Promise { + const id = typeof key === "string" ? key : key.join("-"); + const status = new TriggerStatus(id, this); + await status.update(key, initialStatus); + return status; + } + /** `io.backgroundFetch()` fetches data from a URL that can take longer that the serverless timeout. The actual `fetch` request is performed on the Trigger.dev platform, and the response is sent back to you. * @param key Should be a stable and unique key inside the `run()`. See [resumability](https://trigger.dev/docs/documentation/concepts/resumability) for more information. * @param url The URL to fetch from. diff --git a/packages/trigger-sdk/src/status.ts b/packages/trigger-sdk/src/status.ts new file mode 100644 index 0000000000..ff249604a5 --- /dev/null +++ b/packages/trigger-sdk/src/status.ts @@ -0,0 +1,44 @@ +import { DisplayProperty, StatusUpdate } from "@trigger.dev/core/schemas"; +import { IntegrationTaskKey } from "./integrations"; +import { IO } from "./io"; +import { TriggerClient } from "./triggerClient"; + +export class TriggerStatus { + constructor( + private id: string, + private io: IO + ) {} + + async update(key: IntegrationTaskKey, status: StatusUpdate) { + const properties: DisplayProperty[] = []; + + if (status.label) { + properties.push({ + label: "Label", + text: status.label, + }); + } + + if (status.state) { + properties.push({ + label: "State", + text: status.state, + }); + } + + return await this.io.runTask( + key, + async (task) => { + return await this.io.triggerClient.updateStatus(this.io.runId, this.id, status); + }, + { + name: status.label ?? `Status update`, + icon: "clock", + params: { + ...status, + }, + properties, + } + ); + } +} diff --git a/packages/trigger-sdk/src/triggerClient.ts b/packages/trigger-sdk/src/triggerClient.ts index 6c2ffaf3df..f95470d353 100644 --- a/packages/trigger-sdk/src/triggerClient.ts +++ b/packages/trigger-sdk/src/triggerClient.ts @@ -26,6 +26,7 @@ import { SendEvent, SendEventOptions, SourceMetadataV2, + StatusUpdate, } from "@trigger.dev/core"; import { ApiClient } from "./apiClient"; import { CanceledWithTaskError, ResumeWithTaskError, RetryWithTaskError } from "./errors"; @@ -567,6 +568,10 @@ export class TriggerClient { return this.#client.cancelEvent(eventId); } + async updateStatus(runId: string, id: string, status: StatusUpdate) { + return this.#client.updateStatus(runId, id, status); + } + async registerSchedule(id: string, key: string, schedule: ScheduleMetadata) { return this.#client.registerSchedule(this.id, id, key, schedule); } diff --git a/references/job-catalog/package.json b/references/job-catalog/package.json index 37e18f34be..7d77bf5952 100644 --- a/references/job-catalog/package.json +++ b/references/job-catalog/package.json @@ -21,6 +21,7 @@ "dynamic-schedule": "nodemon --watch src/dynamic-schedule.ts -r tsconfig-paths/register -r dotenv/config src/dynamic-schedule.ts", "dynamic-triggers": "nodemon --watch src/dynamic-triggers.ts -r tsconfig-paths/register -r dotenv/config src/dynamic-triggers.ts", "background-fetch": "nodemon --watch src/background-fetch.ts -r tsconfig-paths/register -r dotenv/config src/background-fetch.ts", + "status": "nodemon --watch src/status.ts -r tsconfig-paths/register -r dotenv/config src/status.ts", "dev:trigger": "trigger-cli dev --port 8080" }, "dependencies": { @@ -52,4 +53,4 @@ "ts-node": "^10.9.1", "tsconfig-paths": "^3.14.1" } -} \ No newline at end of file +} diff --git a/references/job-catalog/src/status.ts b/references/job-catalog/src/status.ts new file mode 100644 index 0000000000..5c8135f705 --- /dev/null +++ b/references/job-catalog/src/status.ts @@ -0,0 +1,43 @@ +import { createExpressServer } from "@trigger.dev/express"; +import { TriggerClient, eventTrigger } from "@trigger.dev/sdk"; +import { z } from "zod"; + +export const client = new TriggerClient({ + id: "job-catalog", + apiKey: process.env["TRIGGER_API_KEY"], + apiUrl: process.env["TRIGGER_API_URL"], + verbose: false, + ioLogLocalEnabled: true, +}); + +client.defineJob({ + id: "status", + name: "Status: updating status", + version: "0.0.2", + trigger: eventTrigger({ + name: "status", + }), + run: async (payload, io, ctx) => { + const generatingText = await io.createStatus("generating-images", { + label: "Generating Images", + state: "loading", + data: { + progress: 0.1, + }, + }); + + await io.wait("wait", 5); + + //...do stuff + await generatingText.update("completed-generation", { + label: "Generated images", + state: "success", + data: { + progress: 1.0, + urls: ["http://www."], + }, + }); + }, +}); + +createExpressServer(client); From e456d4ab1a172f058ba549e8ecaaaeb494581183 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Tue, 19 Sep 2023 16:15:20 +0100 Subject: [PATCH 03/24] Added the key to the returned status --- packages/core/src/schemas/api.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/core/src/schemas/api.ts b/packages/core/src/schemas/api.ts index 00626f8dc5..8812cbfb0a 100644 --- a/packages/core/src/schemas/api.ts +++ b/packages/core/src/schemas/api.ts @@ -754,5 +754,6 @@ export const StatusHistorySchema = z.array(StatusUpdateSchema); export type StatusHistory = z.infer; export const JobRunStatusRecord = InitalStatusUpdateSchema.extend({ + key: z.string(), history: StatusHistorySchema.default([]), }); From 790a689c3a0fc8ee9383c851a1c0d83e988a62d4 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Tue, 19 Sep 2023 16:48:36 +0100 Subject: [PATCH 04/24] Made the test job have an extra step and only pass in some of the options --- packages/core/src/schemas/api.ts | 2 +- references/job-catalog/src/status.ts | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/packages/core/src/schemas/api.ts b/packages/core/src/schemas/api.ts index 8812cbfb0a..ccf901c07f 100644 --- a/packages/core/src/schemas/api.ts +++ b/packages/core/src/schemas/api.ts @@ -755,5 +755,5 @@ export type StatusHistory = z.infer; export const JobRunStatusRecord = InitalStatusUpdateSchema.extend({ key: z.string(), - history: StatusHistorySchema.default([]), + history: StatusHistorySchema, }); diff --git a/references/job-catalog/src/status.ts b/references/job-catalog/src/status.ts index 5c8135f705..63fd41d8a5 100644 --- a/references/job-catalog/src/status.ts +++ b/references/job-catalog/src/status.ts @@ -26,7 +26,17 @@ client.defineJob({ }, }); - await io.wait("wait", 5); + await io.wait("wait", 2); + + //...do stuff + await generatingText.update("middle-generation", { + data: { + progress: 0.5, + urls: ["http://www."], + }, + }); + + await io.wait("wait-again", 2); //...do stuff await generatingText.update("completed-generation", { @@ -34,7 +44,7 @@ client.defineJob({ state: "success", data: { progress: 1.0, - urls: ["http://www."], + urls: ["http://www.", "http://www.", "http://www."], }, }); }, From b1e44e3598d09df044c8f495bab324bcb6e9969a Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Tue, 19 Sep 2023 17:21:31 +0100 Subject: [PATCH 05/24] client.getRunStatuses() and the corresponding endpoint --- .../app/routes/api.v1.runs.$runId.statuses.ts | 63 +++++++++++++++++++ packages/core/src/schemas/api.ts | 2 + packages/database/prisma/schema.prisma | 2 +- packages/trigger-sdk/src/apiClient.ts | 16 +++++ packages/trigger-sdk/src/triggerClient.ts | 4 ++ references/job-catalog/src/status.ts | 4 ++ 6 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 apps/webapp/app/routes/api.v1.runs.$runId.statuses.ts diff --git a/apps/webapp/app/routes/api.v1.runs.$runId.statuses.ts b/apps/webapp/app/routes/api.v1.runs.$runId.statuses.ts new file mode 100644 index 0000000000..e92d74d3f8 --- /dev/null +++ b/apps/webapp/app/routes/api.v1.runs.$runId.statuses.ts @@ -0,0 +1,63 @@ +import type { LoaderArgs } from "@remix-run/server-runtime"; +import { json } from "@remix-run/server-runtime"; +import { + JobRunStatusRecord, + StatusHistory, + StatusHistorySchema, + StatusUpdate, + StatusUpdateData, + StatusUpdateState, +} from "@trigger.dev/core"; + +import { z } from "zod"; +import { $transaction, PrismaClient, prisma } from "~/db.server"; +import { authenticateApiRequest } from "~/services/apiAuth.server"; +import { logger } from "~/services/logger.server"; +import { apiCors } from "~/utils/apiCors"; + +const ParamsSchema = z.object({ + runId: z.string(), +}); + +const RecordsSchema = z.array(JobRunStatusRecord); + +export async function loader({ request, params }: LoaderArgs) { + // Next authenticate the request + const authenticationResult = await authenticateApiRequest(request, { allowPublicKey: true }); + + if (!authenticationResult) { + return json({ error: "Invalid or Missing API key" }, { status: 401 }); + } + + const { runId } = ParamsSchema.parse(params); + + logger.debug("Get run statuses", { + runId, + }); + + try { + const statuses = await prisma.jobRunStatusRecord.findMany({ + where: { + runId, + }, + orderBy: { + createdAt: "desc", + }, + }); + + const parsedStatuses = RecordsSchema.parse(statuses); + + return apiCors( + request, + json({ + statuses: parsedStatuses, + }) + ); + } catch (error) { + if (error instanceof Error) { + return apiCors(request, json({ error: error.message }, { status: 400 })); + } + + return apiCors(request, json({ error: "Something went wrong" }, { status: 500 })); + } +} diff --git a/packages/core/src/schemas/api.ts b/packages/core/src/schemas/api.ts index ccf901c07f..463a2b962b 100644 --- a/packages/core/src/schemas/api.ts +++ b/packages/core/src/schemas/api.ts @@ -757,3 +757,5 @@ export const JobRunStatusRecord = InitalStatusUpdateSchema.extend({ key: z.string(), history: StatusHistorySchema, }); + +export const GetRunStatusesSchema = z.object({ statuses: z.array(JobRunStatusRecord) }); diff --git a/packages/database/prisma/schema.prisma b/packages/database/prisma/schema.prisma index 7982ea4d9f..8e36acef9a 100644 --- a/packages/database/prisma/schema.prisma +++ b/packages/database/prisma/schema.prisma @@ -716,7 +716,7 @@ model JobRun { runConnections RunConnection[] missingConnections MissingConnection[] executions JobRunExecution[] - JobRunStatusRecord JobRunStatusRecord[] + statuses JobRunStatusRecord[] } enum JobRunStatus { diff --git a/packages/trigger-sdk/src/apiClient.ts b/packages/trigger-sdk/src/apiClient.ts index 2d561201ea..8ef761f9cf 100644 --- a/packages/trigger-sdk/src/apiClient.ts +++ b/packages/trigger-sdk/src/apiClient.ts @@ -26,6 +26,7 @@ import { RegisterTriggerBodyV2, StatusUpdate, JobRunStatusRecord, + GetRunStatusesSchema, } from "@trigger.dev/core"; import fetch, { type RequestInit } from "node-fetch"; @@ -383,6 +384,21 @@ export class ApiClient { ); } + async getRunStatuses(runId: string) { + const apiKey = await this.#apiKey(); + + this.#logger.debug("Getting Run statuses", { + runId, + }); + + return await zodfetch(GetRunStatusesSchema, `${this.#apiUrl}/api/v1/runs/${runId}/statuses`, { + method: "GET", + headers: { + Authorization: `Bearer ${apiKey}`, + }, + }); + } + async getRuns(jobSlug: string, options?: GetRunsOptions) { const apiKey = await this.#apiKey(); diff --git a/packages/trigger-sdk/src/triggerClient.ts b/packages/trigger-sdk/src/triggerClient.ts index f95470d353..d193c53d3f 100644 --- a/packages/trigger-sdk/src/triggerClient.ts +++ b/packages/trigger-sdk/src/triggerClient.ts @@ -592,6 +592,10 @@ export class TriggerClient { return this.#client.getRuns(jobSlug, options); } + async getRunStatuses(runId: string) { + return this.#client.getRunStatuses(runId); + } + authorized( apiKey?: string | null ): "authorized" | "unauthorized" | "missing-client" | "missing-header" { diff --git a/references/job-catalog/src/status.ts b/references/job-catalog/src/status.ts index 63fd41d8a5..fcd2aa4f8d 100644 --- a/references/job-catalog/src/status.ts +++ b/references/job-catalog/src/status.ts @@ -47,6 +47,10 @@ client.defineJob({ urls: ["http://www.", "http://www.", "http://www."], }, }); + + await io.runTask("get statuses", async () => { + return await client.getRunStatuses(ctx.run.id); + }); }, }); From 08932b75506d22a3750cbb3e1cdb5b9f41d12c9f Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Tue, 19 Sep 2023 17:21:47 +0100 Subject: [PATCH 06/24] client.getRun() now includes status info --- apps/webapp/app/routes/api.v1.runs.$runId.ts | 4 ++++ packages/core/src/schemas/runs.ts | 3 +++ references/job-catalog/src/status.ts | 4 ++++ 3 files changed, 11 insertions(+) diff --git a/apps/webapp/app/routes/api.v1.runs.$runId.ts b/apps/webapp/app/routes/api.v1.runs.$runId.ts index 973d71f787..905c40e672 100644 --- a/apps/webapp/app/routes/api.v1.runs.$runId.ts +++ b/apps/webapp/app/routes/api.v1.runs.$runId.ts @@ -93,6 +93,9 @@ export async function loader({ request, params }: LoaderArgs) { } : undefined, }, + statuses: { + select: { key: true, label: true, state: true, data: true, history: true }, + }, }, }); @@ -122,6 +125,7 @@ export async function loader({ request, params }: LoaderArgs) { const { parentId, ...rest } = task; return { ...rest }; }), + statuses: jobRun.statuses, nextCursor: nextTask ? nextTask.id : undefined, }) ); diff --git a/packages/core/src/schemas/runs.ts b/packages/core/src/schemas/runs.ts index 31f9bde8b1..8072f9f292 100644 --- a/packages/core/src/schemas/runs.ts +++ b/packages/core/src/schemas/runs.ts @@ -1,5 +1,6 @@ import { ZodObject, z } from "zod"; import { TaskStatusSchema } from "./tasks"; +import { JobRunStatusRecord } from "./api"; export const RunStatusSchema = z.union([ z.literal("PENDING"), @@ -78,6 +79,8 @@ export const GetRunSchema = RunSchema.extend({ output: z.any().optional(), /** The tasks from the run */ tasks: z.array(RunTaskWithSubtasksSchema), + /** Any status updates that were published from the run */ + statuses: z.array(JobRunStatusRecord), /** If there are more tasks, you can use this to get them */ nextCursor: z.string().optional(), }); diff --git a/references/job-catalog/src/status.ts b/references/job-catalog/src/status.ts index fcd2aa4f8d..bf70a63c5f 100644 --- a/references/job-catalog/src/status.ts +++ b/references/job-catalog/src/status.ts @@ -51,6 +51,10 @@ client.defineJob({ await io.runTask("get statuses", async () => { return await client.getRunStatuses(ctx.run.id); }); + + await io.runTask("get run", async () => { + return await client.getRun(ctx.run.id); + }); }, }); From 4f2b91df0e335d9f6de0f77a952c29144b1ead4d Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Tue, 19 Sep 2023 17:54:42 +0100 Subject: [PATCH 07/24] Fixed circular dependency schema --- .../app/routes/api.v1.runs.$runId.statuses.ts | 36 +++++++++++-------- apps/webapp/app/routes/api.v1.runs.$runId.ts | 3 +- packages/core/src/schemas/api.ts | 33 +++-------------- packages/core/src/schemas/index.ts | 1 + packages/core/src/schemas/runs.ts | 2 +- packages/core/src/schemas/statuses.ts | 31 ++++++++++++++++ packages/trigger-sdk/src/apiClient.ts | 4 +-- 7 files changed, 62 insertions(+), 48 deletions(-) create mode 100644 packages/core/src/schemas/statuses.ts diff --git a/apps/webapp/app/routes/api.v1.runs.$runId.statuses.ts b/apps/webapp/app/routes/api.v1.runs.$runId.statuses.ts index e92d74d3f8..e185ca1993 100644 --- a/apps/webapp/app/routes/api.v1.runs.$runId.statuses.ts +++ b/apps/webapp/app/routes/api.v1.runs.$runId.statuses.ts @@ -1,16 +1,8 @@ import type { LoaderArgs } from "@remix-run/server-runtime"; import { json } from "@remix-run/server-runtime"; -import { - JobRunStatusRecord, - StatusHistory, - StatusHistorySchema, - StatusUpdate, - StatusUpdateData, - StatusUpdateState, -} from "@trigger.dev/core"; - +import { JobRunStatusRecord } from "@trigger.dev/core"; import { z } from "zod"; -import { $transaction, PrismaClient, prisma } from "~/db.server"; +import { prisma } from "~/db.server"; import { authenticateApiRequest } from "~/services/apiAuth.server"; import { logger } from "~/services/logger.server"; import { apiCors } from "~/utils/apiCors"; @@ -36,20 +28,34 @@ export async function loader({ request, params }: LoaderArgs) { }); try { - const statuses = await prisma.jobRunStatusRecord.findMany({ + const run = await prisma.jobRun.findUnique({ where: { - runId, + id: runId, }, - orderBy: { - createdAt: "desc", + select: { + id: true, + status: true, + statuses: { + orderBy: { + createdAt: "desc", + }, + }, }, }); - const parsedStatuses = RecordsSchema.parse(statuses); + if (!run) { + return apiCors(request, json({ error: `No run found for id ${runId}` }, { status: 404 })); + } + + const parsedStatuses = RecordsSchema.parse(run.statuses); return apiCors( request, json({ + run: { + id: run.id, + status: run.status, + }, statuses: parsedStatuses, }) ); diff --git a/apps/webapp/app/routes/api.v1.runs.$runId.ts b/apps/webapp/app/routes/api.v1.runs.$runId.ts index 905c40e672..55049bc9a1 100644 --- a/apps/webapp/app/routes/api.v1.runs.$runId.ts +++ b/apps/webapp/app/routes/api.v1.runs.$runId.ts @@ -1,9 +1,8 @@ import type { LoaderArgs } from "@remix-run/server-runtime"; import { json } from "@remix-run/server-runtime"; -import { cors } from "remix-utils"; import { z } from "zod"; import { prisma } from "~/db.server"; -import { authenticateApiRequest, getApiKeyFromRequest } from "~/services/apiAuth.server"; +import { authenticateApiRequest } from "~/services/apiAuth.server"; import { apiCors } from "~/utils/apiCors"; import { taskListToTree } from "~/utils/taskListToTree"; diff --git a/packages/core/src/schemas/api.ts b/packages/core/src/schemas/api.ts index 463a2b962b..9db3366b5e 100644 --- a/packages/core/src/schemas/api.ts +++ b/packages/core/src/schemas/api.ts @@ -15,6 +15,8 @@ import { } from "./schedules"; import { CachedTaskSchema, TaskSchema } from "./tasks"; import { EventSpecificationSchema, TriggerMetadataSchema } from "./triggers"; +import { RunStatusSchema } from "./runs"; +import { JobRunStatusRecord } from "./statuses"; export const UpdateTriggerSourceBodyV1Schema = z.object({ registeredEvents: z.array(z.string()), @@ -730,32 +732,7 @@ export const CreateExternalConnectionBodySchema = z.object({ export type CreateExternalConnectionBody = z.infer; -const StatusUpdateStateSchema = z.union([ - z.literal("loading"), - z.literal("success"), - z.literal("failure"), -]); -export type StatusUpdateState = z.infer; - -const StatusUpdateDataSchema = z.record(SerializableJsonSchema); -export type StatusUpdateData = z.infer; - -export const StatusUpdateSchema = z.object({ - label: z.string().optional(), - state: StatusUpdateStateSchema.optional(), - data: StatusUpdateDataSchema.optional(), +export const GetRunStatusesSchema = z.object({ + run: z.object({ id: z.string(), status: RunStatusSchema }), + statuses: z.array(JobRunStatusRecord), }); -export type StatusUpdate = z.infer; - -const InitalStatusUpdateSchema = StatusUpdateSchema.required({ label: true }); -export type InitialStatusUpdate = z.infer; - -export const StatusHistorySchema = z.array(StatusUpdateSchema); -export type StatusHistory = z.infer; - -export const JobRunStatusRecord = InitalStatusUpdateSchema.extend({ - key: z.string(), - history: StatusHistorySchema, -}); - -export const GetRunStatusesSchema = z.object({ statuses: z.array(JobRunStatusRecord) }); diff --git a/packages/core/src/schemas/index.ts b/packages/core/src/schemas/index.ts index 3ad2573181..09f3a15c05 100644 --- a/packages/core/src/schemas/index.ts +++ b/packages/core/src/schemas/index.ts @@ -12,3 +12,4 @@ export * from "./fetch"; export * from "./events"; export * from "./runs"; export * from "./addMissingVersionField"; +export * from "./statuses"; diff --git a/packages/core/src/schemas/runs.ts b/packages/core/src/schemas/runs.ts index 8072f9f292..45e97bd0f2 100644 --- a/packages/core/src/schemas/runs.ts +++ b/packages/core/src/schemas/runs.ts @@ -1,6 +1,6 @@ import { ZodObject, z } from "zod"; import { TaskStatusSchema } from "./tasks"; -import { JobRunStatusRecord } from "./api"; +import { JobRunStatusRecord } from "./statuses"; export const RunStatusSchema = z.union([ z.literal("PENDING"), diff --git a/packages/core/src/schemas/statuses.ts b/packages/core/src/schemas/statuses.ts new file mode 100644 index 0000000000..2291914147 --- /dev/null +++ b/packages/core/src/schemas/statuses.ts @@ -0,0 +1,31 @@ +import { z } from "zod"; +import { SerializableJsonSchema } from "./json"; +import { RunStatusSchema } from "./runs"; + +const StatusUpdateStateSchema = z.union([ + z.literal("loading"), + z.literal("success"), + z.literal("failure"), +]); +export type StatusUpdateState = z.infer; + +const StatusUpdateDataSchema = z.record(SerializableJsonSchema); +export type StatusUpdateData = z.infer; + +export const StatusUpdateSchema = z.object({ + label: z.string().optional(), + state: StatusUpdateStateSchema.optional(), + data: StatusUpdateDataSchema.optional(), +}); +export type StatusUpdate = z.infer; + +const InitalStatusUpdateSchema = StatusUpdateSchema.required({ label: true }); +export type InitialStatusUpdate = z.infer; + +export const StatusHistorySchema = z.array(StatusUpdateSchema); +export type StatusHistory = z.infer; + +export const JobRunStatusRecord = InitalStatusUpdateSchema.extend({ + key: z.string(), + history: StatusHistorySchema, +}); diff --git a/packages/trigger-sdk/src/apiClient.ts b/packages/trigger-sdk/src/apiClient.ts index 8ef761f9cf..d7fbed76f3 100644 --- a/packages/trigger-sdk/src/apiClient.ts +++ b/packages/trigger-sdk/src/apiClient.ts @@ -24,9 +24,9 @@ import { urlWithSearchParams, UpdateTriggerSourceBodyV2, RegisterTriggerBodyV2, - StatusUpdate, - JobRunStatusRecord, GetRunStatusesSchema, + JobRunStatusRecord, + StatusUpdate, } from "@trigger.dev/core"; import fetch, { type RequestInit } from "node-fetch"; From ed144ba59531c3dad451436d992e12bddbed0481 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Tue, 19 Sep 2023 20:23:59 +0100 Subject: [PATCH 08/24] Translate null to undefined --- .../routes/api.v1.runs.$runId.statuses.$id.ts | 26 +++++++++++-------- .../app/routes/api.v1.runs.$runId.statuses.ts | 6 +++-- packages/core/src/schemas/api.ts | 7 ++--- packages/core/src/schemas/runs.ts | 4 +-- packages/core/src/schemas/statuses.ts | 4 +-- packages/trigger-sdk/src/apiClient.ts | 4 +-- 6 files changed, 29 insertions(+), 22 deletions(-) diff --git a/apps/webapp/app/routes/api.v1.runs.$runId.statuses.$id.ts b/apps/webapp/app/routes/api.v1.runs.$runId.statuses.$id.ts index 6078dab800..e547534231 100644 --- a/apps/webapp/app/routes/api.v1.runs.$runId.statuses.$id.ts +++ b/apps/webapp/app/routes/api.v1.runs.$runId.statuses.$id.ts @@ -1,6 +1,6 @@ import type { ActionArgs } from "@remix-run/server-runtime"; import { json } from "@remix-run/server-runtime"; -import { JobRunStatusRecord, TaskStatus } from "@trigger.dev/database"; +import { TaskStatus } from "@trigger.dev/database"; import { RunTaskBodyOutput, RunTaskBodyOutputSchema, @@ -19,6 +19,7 @@ import { authenticateApiRequest } from "~/services/apiAuth.server"; import { logger } from "~/services/logger.server"; import { ulid } from "~/services/ulid.server"; import { workerQueue } from "~/services/worker.server"; +import { JobRunStatusRecordSchema } from "@trigger.dev/core"; const ParamsSchema = z.object({ runId: z.string(), @@ -58,18 +59,25 @@ export async function action({ request, params }: ActionArgs) { const service = new SetStatusService(); try { - const status = await service.call(runId, id, body.data); + const statusRecord = await service.call(runId, id, body.data); logger.debug("SetStatusService.call() response body", { runId, id, - status, + statusRecord, }); - if (!status) { + if (!statusRecord) { return json({ error: "Something went wrong" }, { status: 500 }); } + const status = JobRunStatusRecordSchema.parse({ + ...statusRecord, + state: statusRecord.state ?? undefined, + history: statusRecord.history ?? undefined, + data: statusRecord.data ?? undefined, + }); + return json(status); } catch (error) { if (error instanceof Error) { @@ -87,11 +95,7 @@ export class SetStatusService { this.#prismaClient = prismaClient; } - public async call( - runId: string, - id: string, - status: StatusUpdate - ): Promise { + public async call(runId: string, id: string, status: StatusUpdate) { const statusRecord = await $transaction(this.#prismaClient, async (tx) => { const existingStatus = await tx.jobRunStatusRecord.findUnique({ where: { @@ -110,8 +114,8 @@ export class SetStatusService { if (existingStatus) { history.push({ label: existingStatus.label, - state: existingStatus.state as StatusUpdateState, - data: existingStatus.data as StatusUpdateData, + state: (existingStatus.state ?? undefined) as StatusUpdateState, + data: (existingStatus.data ?? undefined) as StatusUpdateData, }); } diff --git a/apps/webapp/app/routes/api.v1.runs.$runId.statuses.ts b/apps/webapp/app/routes/api.v1.runs.$runId.statuses.ts index e185ca1993..ff8d827e8e 100644 --- a/apps/webapp/app/routes/api.v1.runs.$runId.statuses.ts +++ b/apps/webapp/app/routes/api.v1.runs.$runId.statuses.ts @@ -1,6 +1,6 @@ import type { LoaderArgs } from "@remix-run/server-runtime"; import { json } from "@remix-run/server-runtime"; -import { JobRunStatusRecord } from "@trigger.dev/core"; +import { JobRunStatusRecordSchema } from "@trigger.dev/core"; import { z } from "zod"; import { prisma } from "~/db.server"; import { authenticateApiRequest } from "~/services/apiAuth.server"; @@ -11,7 +11,7 @@ const ParamsSchema = z.object({ runId: z.string(), }); -const RecordsSchema = z.array(JobRunStatusRecord); +const RecordsSchema = z.array(JobRunStatusRecordSchema); export async function loader({ request, params }: LoaderArgs) { // Next authenticate the request @@ -35,6 +35,7 @@ export async function loader({ request, params }: LoaderArgs) { select: { id: true, status: true, + output: true, statuses: { orderBy: { createdAt: "desc", @@ -55,6 +56,7 @@ export async function loader({ request, params }: LoaderArgs) { run: { id: run.id, status: run.status, + output: run.output, }, statuses: parsedStatuses, }) diff --git a/packages/core/src/schemas/api.ts b/packages/core/src/schemas/api.ts index 9db3366b5e..b59f23be76 100644 --- a/packages/core/src/schemas/api.ts +++ b/packages/core/src/schemas/api.ts @@ -16,7 +16,7 @@ import { import { CachedTaskSchema, TaskSchema } from "./tasks"; import { EventSpecificationSchema, TriggerMetadataSchema } from "./triggers"; import { RunStatusSchema } from "./runs"; -import { JobRunStatusRecord } from "./statuses"; +import { JobRunStatusRecordSchema } from "./statuses"; export const UpdateTriggerSourceBodyV1Schema = z.object({ registeredEvents: z.array(z.string()), @@ -733,6 +733,7 @@ export const CreateExternalConnectionBodySchema = z.object({ export type CreateExternalConnectionBody = z.infer; export const GetRunStatusesSchema = z.object({ - run: z.object({ id: z.string(), status: RunStatusSchema }), - statuses: z.array(JobRunStatusRecord), + run: z.object({ id: z.string(), status: RunStatusSchema, output: z.any().optional() }), + statuses: z.array(JobRunStatusRecordSchema), }); +export type GetRunStatuses = z.infer; diff --git a/packages/core/src/schemas/runs.ts b/packages/core/src/schemas/runs.ts index 45e97bd0f2..4804b2cfce 100644 --- a/packages/core/src/schemas/runs.ts +++ b/packages/core/src/schemas/runs.ts @@ -1,6 +1,6 @@ import { ZodObject, z } from "zod"; import { TaskStatusSchema } from "./tasks"; -import { JobRunStatusRecord } from "./statuses"; +import { JobRunStatusRecordSchema } from "./statuses"; export const RunStatusSchema = z.union([ z.literal("PENDING"), @@ -80,7 +80,7 @@ export const GetRunSchema = RunSchema.extend({ /** The tasks from the run */ tasks: z.array(RunTaskWithSubtasksSchema), /** Any status updates that were published from the run */ - statuses: z.array(JobRunStatusRecord), + statuses: z.array(JobRunStatusRecordSchema), /** If there are more tasks, you can use this to get them */ nextCursor: z.string().optional(), }); diff --git a/packages/core/src/schemas/statuses.ts b/packages/core/src/schemas/statuses.ts index 2291914147..2216161e16 100644 --- a/packages/core/src/schemas/statuses.ts +++ b/packages/core/src/schemas/statuses.ts @@ -2,7 +2,7 @@ import { z } from "zod"; import { SerializableJsonSchema } from "./json"; import { RunStatusSchema } from "./runs"; -const StatusUpdateStateSchema = z.union([ +export const StatusUpdateStateSchema = z.union([ z.literal("loading"), z.literal("success"), z.literal("failure"), @@ -25,7 +25,7 @@ export type InitialStatusUpdate = z.infer; export const StatusHistorySchema = z.array(StatusUpdateSchema); export type StatusHistory = z.infer; -export const JobRunStatusRecord = InitalStatusUpdateSchema.extend({ +export const JobRunStatusRecordSchema = InitalStatusUpdateSchema.extend({ key: z.string(), history: StatusHistorySchema, }); diff --git a/packages/trigger-sdk/src/apiClient.ts b/packages/trigger-sdk/src/apiClient.ts index d7fbed76f3..f7a4bcb399 100644 --- a/packages/trigger-sdk/src/apiClient.ts +++ b/packages/trigger-sdk/src/apiClient.ts @@ -25,7 +25,7 @@ import { UpdateTriggerSourceBodyV2, RegisterTriggerBodyV2, GetRunStatusesSchema, - JobRunStatusRecord, + JobRunStatusRecordSchema, StatusUpdate, } from "@trigger.dev/core"; @@ -210,7 +210,7 @@ export class ApiClient { }); return await zodfetch( - JobRunStatusRecord, + JobRunStatusRecordSchema, `${this.#apiUrl}/api/v1/runs/${runId}/statuses/${id}`, { method: "PUT", From 5ecb369eac320f648e52714afa6bdf8781de780a Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Tue, 19 Sep 2023 20:24:13 +0100 Subject: [PATCH 09/24] Added the react package to the nextjs-reference tsconfig --- references/nextjs-reference/tsconfig.json | 129 ++++++---------------- 1 file changed, 33 insertions(+), 96 deletions(-) diff --git a/references/nextjs-reference/tsconfig.json b/references/nextjs-reference/tsconfig.json index 28ad34a2cf..acd8c76025 100644 --- a/references/nextjs-reference/tsconfig.json +++ b/references/nextjs-reference/tsconfig.json @@ -1,11 +1,7 @@ { "compilerOptions": { "target": "ES2015", - "lib": [ - "dom", - "dom.iterable", - "ES2015" - ], + "lib": ["dom", "dom.iterable", "ES2015"], "allowJs": true, "skipLibCheck": true, "strict": true, @@ -19,87 +15,35 @@ "jsx": "preserve", "incremental": true, "paths": { - "@/*": [ - "./src/*" - ], - "@trigger.dev/sdk": [ - "../../packages/trigger-sdk/src/index" - ], - "@trigger.dev/sdk/*": [ - "../../packages/trigger-sdk/src/*" - ], - "@trigger.dev/nextjs": [ - "../../packages/nextjs/src/index" - ], - "@trigger.dev/nextjs/*": [ - "../../packages/nextjs/src/*" - ], - "@trigger.dev/core": [ - "../../packages/core/src/index" - ], - "@trigger.dev/core/*": [ - "../../packages/core/src/*" - ], - "@trigger.dev/integration-kit": [ - "../../packages/integration-kit/src/index" - ], - "@trigger.dev/integration-kit/*": [ - "../../packages/integration-kit/src/*" - ], - "@trigger.dev/github": [ - "../../integrations/github/src/index" - ], - "@trigger.dev/github/*": [ - "../../integrations/github/src/*" - ], - "@trigger.dev/slack": [ - "../../integrations/slack/src/index" - ], - "@trigger.dev/slack/*": [ - "../../integrations/slack/src/*" - ], - "@trigger.dev/openai": [ - "../../integrations/openai/src/index" - ], - "@trigger.dev/openai/*": [ - "../../integrations/openai/src/*" - ], - "@trigger.dev/resend": [ - "../../integrations/resend/src/index" - ], - "@trigger.dev/resend/*": [ - "../../integrations/resend/src/*" - ], - "@trigger.dev/typeform": [ - "../../integrations/typeform/src/index" - ], - "@trigger.dev/typeform/*": [ - "../../integrations/typeform/src/*" - ], - "@trigger.dev/plain": [ - "../../integrations/plain/src/index" - ], - "@trigger.dev/plain/*": [ - "../../integrations/plain/src/*" - ], - "@trigger.dev/supabase": [ - "../../integrations/supabase/src/index" - ], - "@trigger.dev/supabase/*": [ - "../../integrations/supabase/src/*" - ], - "@trigger.dev/stripe": [ - "../../integrations/stripe/src/index" - ], - "@trigger.dev/stripe/*": [ - "../../integrations/stripe/src/*" - ], - "@trigger.dev/sendgrid": [ - "../../integrations/sendgrid/src/index" - ], - "@trigger.dev/sendgrid/*": [ - "../../integrations/sendgrid/src/*" - ] + "@/*": ["./src/*"], + "@trigger.dev/sdk": ["../../packages/trigger-sdk/src/index"], + "@trigger.dev/sdk/*": ["../../packages/trigger-sdk/src/*"], + "@trigger.dev/react": ["../../packages/react/src/index"], + "@trigger.dev/react/*": ["../../packages/react/src/*"], + "@trigger.dev/nextjs": ["../../packages/nextjs/src/index"], + "@trigger.dev/nextjs/*": ["../../packages/nextjs/src/*"], + "@trigger.dev/core": ["../../packages/core/src/index"], + "@trigger.dev/core/*": ["../../packages/core/src/*"], + "@trigger.dev/integration-kit": ["../../packages/integration-kit/src/index"], + "@trigger.dev/integration-kit/*": ["../../packages/integration-kit/src/*"], + "@trigger.dev/github": ["../../integrations/github/src/index"], + "@trigger.dev/github/*": ["../../integrations/github/src/*"], + "@trigger.dev/slack": ["../../integrations/slack/src/index"], + "@trigger.dev/slack/*": ["../../integrations/slack/src/*"], + "@trigger.dev/openai": ["../../integrations/openai/src/index"], + "@trigger.dev/openai/*": ["../../integrations/openai/src/*"], + "@trigger.dev/resend": ["../../integrations/resend/src/index"], + "@trigger.dev/resend/*": ["../../integrations/resend/src/*"], + "@trigger.dev/typeform": ["../../integrations/typeform/src/index"], + "@trigger.dev/typeform/*": ["../../integrations/typeform/src/*"], + "@trigger.dev/plain": ["../../integrations/plain/src/index"], + "@trigger.dev/plain/*": ["../../integrations/plain/src/*"], + "@trigger.dev/supabase": ["../../integrations/supabase/src/index"], + "@trigger.dev/supabase/*": ["../../integrations/supabase/src/*"], + "@trigger.dev/stripe": ["../../integrations/stripe/src/index"], + "@trigger.dev/stripe/*": ["../../integrations/stripe/src/*"], + "@trigger.dev/sendgrid": ["../../integrations/sendgrid/src/index"], + "@trigger.dev/sendgrid/*": ["../../integrations/sendgrid/src/*"] }, "plugins": [ { @@ -107,13 +51,6 @@ } ] }, - "include": [ - "next-env.d.ts", - "**/*.ts", - "**/*.tsx", - ".next/types/**/*.ts" - ], - "exclude": [ - "node_modules" - ] -} \ No newline at end of file + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules"] +} From 6cabf8cfae24736220b9e073ee001b054b97e20d Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Tue, 19 Sep 2023 20:24:32 +0100 Subject: [PATCH 10/24] Removed unused OpenAI integration from nextjs-reference project --- references/nextjs-reference/src/trigger.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/references/nextjs-reference/src/trigger.ts b/references/nextjs-reference/src/trigger.ts index 86184b71e2..e52fb84d8f 100644 --- a/references/nextjs-reference/src/trigger.ts +++ b/references/nextjs-reference/src/trigger.ts @@ -19,11 +19,6 @@ export const client = new TriggerClient({ ioLogLocalEnabled: true, }); -export const openai = new OpenAI({ - id: "openai", - apiKey: process.env["OPENAI_API_KEY"]!, -}); - export const github = new Github({ id: "github", octokitRequest: { fetch }, From 25267d0efe0391ce9947e790f94dc14bf670a2b2 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Tue, 19 Sep 2023 20:24:51 +0100 Subject: [PATCH 11/24] New hooks for getting the statuses --- packages/react/src/statuses.ts | 73 ++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 packages/react/src/statuses.ts diff --git a/packages/react/src/statuses.ts b/packages/react/src/statuses.ts new file mode 100644 index 0000000000..099cf12f86 --- /dev/null +++ b/packages/react/src/statuses.ts @@ -0,0 +1,73 @@ +"use client"; + +import { UseQueryResult, useQuery } from "@tanstack/react-query"; +import { + GetRun, + GetRunOptions, + GetRunSchema, + GetRunStatuses, + GetRunStatusesSchema, + urlWithSearchParams, +} from "@trigger.dev/core"; +import { useTriggerProvider } from "./TriggerProvider"; +import { zodfetch } from "./fetch"; +import { useEventDetails } from "./events"; + +export const runResolvedStatuses = ["SUCCESS", "FAILURE", "CANCELED", "TIMED_OUT", "ABORTED"]; + +const defaultRefreshInterval = 1000; + +export type RunStatusesOptions = { + /** How often you want to refresh, the default is 1000. Min is 500 */ + refreshIntervalMs?: number; +}; + +export type UseRunStatusesResult = GetRunStatuses & { isFetching: boolean; error?: Error }; + +export function useRunStatuses( + runId: string | undefined, + options?: RunStatusesOptions +): UseRunStatusesResult { + const { apiUrl, publicApiKey, queryClient } = useTriggerProvider(); + + const queryResult = useQuery( + { + queryKey: [`triggerdotdev-run-${runId}`], + queryFn: async () => { + return await zodfetch(GetRunStatusesSchema, `${apiUrl}/api/v1/runs/${runId}/statuses`, { + method: "GET", + headers: { + Authorization: `Bearer ${publicApiKey}`, + }, + }); + }, + enabled: !!runId, + refetchInterval: (data) => { + if (data?.run.status && runResolvedStatuses.includes(data.run.status)) { + return false; + } + if (options.refreshIntervalMs !== undefined) { + return Math.max(options.refreshIntervalMs, 500); + } + + return defaultRefreshInterval; + }, + }, + queryClient + ); + + return { + isFetching: queryResult.isLoading, + error: queryResult.error, + run: queryResult.data.run, + statuses: queryResult.data.statuses, + }; +} + +export function useEventRunStatuses( + eventId: string | undefined, + options?: RunStatusesOptions +): UseRunStatusesResult { + const event = useEventDetails(eventId); + return useRunStatuses(event.data?.runs[0]?.id, options); +} From 6e9f3c540498a9c6f140a4ecc2a8559df0d0e649 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Tue, 19 Sep 2023 20:25:34 +0100 Subject: [PATCH 12/24] Disabled most of the nextjs-reference jobs --- .../src/app/api/trigger/route.ts | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/references/nextjs-reference/src/app/api/trigger/route.ts b/references/nextjs-reference/src/app/api/trigger/route.ts index f72f543aed..af1c70c9ea 100644 --- a/references/nextjs-reference/src/app/api/trigger/route.ts +++ b/references/nextjs-reference/src/app/api/trigger/route.ts @@ -1,19 +1,18 @@ import { createAppRoute } from "@trigger.dev/nextjs"; import { client } from "@/trigger"; -import "@/jobs/events"; -import "@/jobs/general"; -import "@/jobs/github"; -import "@/jobs/logging"; -import "@/jobs/openai"; -import "@/jobs/plain"; -import "@/jobs/resend"; -import "@/jobs/schedules"; -import "@/jobs/slack"; -import "@/jobs/typeform"; -import "@/jobs/edgeCases"; import "@/jobs/hooks"; -import "@/jobs/supabase"; -import "@/jobs/stripe"; +// import "@/jobs/events"; +// import "@/jobs/general"; +// import "@/jobs/github"; +// import "@/jobs/logging"; +// import "@/jobs/openai"; +// import "@/jobs/plain"; +// import "@/jobs/schedules"; +// import "@/jobs/slack"; +// import "@/jobs/typeform"; +// import "@/jobs/edgeCases"; +// import "@/jobs/supabase"; +// import "@/jobs/stripe"; export const { POST, dynamic } = createAppRoute(client); From 1ef4f583907ee4701136f9d2cf8deba0c8c94af2 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Tue, 19 Sep 2023 20:25:50 +0100 Subject: [PATCH 13/24] Updated the hooks UI --- .../src/components/RunDetails.tsx | 7 ++- .../nextjs-reference/src/jobs/general.ts | 2 +- references/nextjs-reference/src/jobs/hooks.ts | 54 +++++++++++++++---- 3 files changed, 52 insertions(+), 11 deletions(-) diff --git a/references/nextjs-reference/src/components/RunDetails.tsx b/references/nextjs-reference/src/components/RunDetails.tsx index d6a83dbf1f..f20ad96909 100644 --- a/references/nextjs-reference/src/components/RunDetails.tsx +++ b/references/nextjs-reference/src/components/RunDetails.tsx @@ -62,7 +62,12 @@ export function EventRunData({ id }: { id: string }) { } if (isError) { - return

Error

; + return ( +
+

{error.name}

+

{error.message}

+
+ ); } if (!data) { diff --git a/references/nextjs-reference/src/jobs/general.ts b/references/nextjs-reference/src/jobs/general.ts index a35786bf9f..685d7c4467 100644 --- a/references/nextjs-reference/src/jobs/general.ts +++ b/references/nextjs-reference/src/jobs/general.ts @@ -1,4 +1,4 @@ -import { client, github, githubUser, openai, slack } from "@/trigger"; +import { client, github, githubUser, slack } from "@/trigger"; import { events } from "@trigger.dev/github"; import { Job, diff --git a/references/nextjs-reference/src/jobs/hooks.ts b/references/nextjs-reference/src/jobs/hooks.ts index f7a9fd425d..dc6d418481 100644 --- a/references/nextjs-reference/src/jobs/hooks.ts +++ b/references/nextjs-reference/src/jobs/hooks.ts @@ -9,14 +9,50 @@ client.defineJob({ name: "test-event", }), run: async (payload, io, ctx) => { - await io.logger.log("This Job is triggered from a button in the frontend"); - await io.wait("wait", 5); - await io.logger.log("It runs for a while to test the React hooks"); - await io.wait("wait 2", 5); - await io.logger.log("This is the end of the job"); - - return { - myMessage: "This is the output of the job", - }; + const gettingInputData = await io.createStatus("getting-input-data", { + label: "Getting input data", + // state: "loading", + }); + + await io.wait("wait-input", 2); + + await gettingInputData.update("input-data-complete", { + label: "Input data complete", + state: "success", + }); + + const generatingMemes = await io.createStatus("generating-memes", { + label: "Generating memes", + state: "loading", + data: { + progress: 0.1, + }, + }); + + await io.wait("wait", 2); + + //...do stuff + await generatingMemes.update("middle-generation", { + data: { + progress: 0.5, + urls: ["http://www."], + }, + }); + + await io.wait("wait-again", 2); + + //...do stuff + await generatingMemes.update("completed-generation", { + label: "Generated memes", + state: "success", + data: { + progress: 1.0, + urls: [ + "https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExZnZoMndsdWh0MmhvY2kyaDF6YjZjZzg1ZGsxdnhhYm13a3Q1Y3lkbyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/13HgwGsXF0aiGY/giphy.gif", + "https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExbXdhNGhjaXVoZzFrMWJ0dmYyM2ZuOTIxN2J3aWYwY3J1OHI4eW13cCZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/scZPhLqaVOM1qG4lT9/giphy.gif", + "https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExdHJhdXJ2Nnl6YnR3bXZuejZ3Y3Q5a2w3Mng2ZXZmMmJjeWdtZWhibCZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/yYSSBtDgbbRzq/giphy-downsized.gif", + ], + }, + }); }, }); From f95a2367b726a463535834a7c87e1c5284bff221 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Tue, 19 Sep 2023 20:36:47 +0100 Subject: [PATCH 14/24] Updated the endpoints to deal with null statuses values --- apps/webapp/app/routes/api.v1.runs.$runId.statuses.ts | 9 ++++++++- apps/webapp/app/routes/api.v1.runs.$runId.ts | 7 ++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/apps/webapp/app/routes/api.v1.runs.$runId.statuses.ts b/apps/webapp/app/routes/api.v1.runs.$runId.statuses.ts index ff8d827e8e..4845854612 100644 --- a/apps/webapp/app/routes/api.v1.runs.$runId.statuses.ts +++ b/apps/webapp/app/routes/api.v1.runs.$runId.statuses.ts @@ -48,7 +48,14 @@ export async function loader({ request, params }: LoaderArgs) { return apiCors(request, json({ error: `No run found for id ${runId}` }, { status: 404 })); } - const parsedStatuses = RecordsSchema.parse(run.statuses); + const parsedStatuses = RecordsSchema.parse( + run.statuses.map((s) => ({ + ...s, + state: s.state ?? undefined, + data: s.data ?? undefined, + history: s.history ?? undefined, + })) + ); return apiCors( request, diff --git a/apps/webapp/app/routes/api.v1.runs.$runId.ts b/apps/webapp/app/routes/api.v1.runs.$runId.ts index 55049bc9a1..e1273654f8 100644 --- a/apps/webapp/app/routes/api.v1.runs.$runId.ts +++ b/apps/webapp/app/routes/api.v1.runs.$runId.ts @@ -124,7 +124,12 @@ export async function loader({ request, params }: LoaderArgs) { const { parentId, ...rest } = task; return { ...rest }; }), - statuses: jobRun.statuses, + statuses: jobRun.statuses.map((s) => ({ + ...s, + state: s.state ?? undefined, + data: s.data ?? undefined, + history: s.history ?? undefined, + })), nextCursor: nextTask ? nextTask.id : undefined, }) ); From f0257cdc90f1271f09d4a0ce718ab2054c22dc24 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Tue, 19 Sep 2023 21:46:20 +0100 Subject: [PATCH 15/24] The hook is working, with an example --- .../app/routes/api.v1.runs.$runId.statuses.ts | 8 +- packages/react/src/index.ts | 1 + packages/react/src/statuses.ts | 55 ++- packages/react/tsconfig.json | 1 + .../src/app/api/trigger/route.ts | 12 - .../src/components/RunDetails.tsx | 97 ++--- .../nextjs-reference/src/jobs/edgeCases.ts | 25 -- .../nextjs-reference/src/jobs/events.ts | 60 --- .../nextjs-reference/src/jobs/general.ts | 269 ------------- .../nextjs-reference/src/jobs/github.ts | 371 ------------------ references/nextjs-reference/src/jobs/hooks.ts | 20 +- .../nextjs-reference/src/jobs/logging.ts | 19 - .../nextjs-reference/src/jobs/openai.ts | 157 -------- references/nextjs-reference/src/jobs/plain.ts | 88 ----- .../nextjs-reference/src/jobs/schedules.ts | 18 - references/nextjs-reference/src/jobs/slack.ts | 92 ----- .../nextjs-reference/src/jobs/stripe.ts | 160 -------- .../nextjs-reference/src/jobs/supabase.ts | 341 ---------------- .../nextjs-reference/src/jobs/typeform.ts | 84 ---- 19 files changed, 97 insertions(+), 1781 deletions(-) delete mode 100644 references/nextjs-reference/src/jobs/edgeCases.ts delete mode 100644 references/nextjs-reference/src/jobs/events.ts delete mode 100644 references/nextjs-reference/src/jobs/general.ts delete mode 100644 references/nextjs-reference/src/jobs/github.ts delete mode 100644 references/nextjs-reference/src/jobs/logging.ts delete mode 100644 references/nextjs-reference/src/jobs/openai.ts delete mode 100644 references/nextjs-reference/src/jobs/plain.ts delete mode 100644 references/nextjs-reference/src/jobs/schedules.ts delete mode 100644 references/nextjs-reference/src/jobs/slack.ts delete mode 100644 references/nextjs-reference/src/jobs/stripe.ts delete mode 100644 references/nextjs-reference/src/jobs/supabase.ts delete mode 100644 references/nextjs-reference/src/jobs/typeform.ts diff --git a/apps/webapp/app/routes/api.v1.runs.$runId.statuses.ts b/apps/webapp/app/routes/api.v1.runs.$runId.statuses.ts index 4845854612..f7e6351de2 100644 --- a/apps/webapp/app/routes/api.v1.runs.$runId.statuses.ts +++ b/apps/webapp/app/routes/api.v1.runs.$runId.statuses.ts @@ -14,11 +14,15 @@ const ParamsSchema = z.object({ const RecordsSchema = z.array(JobRunStatusRecordSchema); export async function loader({ request, params }: LoaderArgs) { + if (request.method.toUpperCase() === "OPTIONS") { + return apiCors(request, json({})); + } + // Next authenticate the request const authenticationResult = await authenticateApiRequest(request, { allowPublicKey: true }); if (!authenticationResult) { - return json({ error: "Invalid or Missing API key" }, { status: 401 }); + return apiCors(request, json({ error: "Invalid or Missing API key" }, { status: 401 })); } const { runId } = ParamsSchema.parse(params); @@ -38,7 +42,7 @@ export async function loader({ request, params }: LoaderArgs) { output: true, statuses: { orderBy: { - createdAt: "desc", + createdAt: "asc", }, }, }, diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index 46718c7f49..47223c19b4 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -1,3 +1,4 @@ export * from "./TriggerProvider"; export * from "./events"; export * from "./runs"; +export * from "./statuses"; diff --git a/packages/react/src/statuses.ts b/packages/react/src/statuses.ts index 099cf12f86..ec722d6640 100644 --- a/packages/react/src/statuses.ts +++ b/packages/react/src/statuses.ts @@ -12,8 +12,7 @@ import { import { useTriggerProvider } from "./TriggerProvider"; import { zodfetch } from "./fetch"; import { useEventDetails } from "./events"; - -export const runResolvedStatuses = ["SUCCESS", "FAILURE", "CANCELED", "TIMED_OUT", "ABORTED"]; +import { runResolvedStatuses } from "./runs"; const defaultRefreshInterval = 1000; @@ -22,7 +21,23 @@ export type RunStatusesOptions = { refreshIntervalMs?: number; }; -export type UseRunStatusesResult = GetRunStatuses & { isFetching: boolean; error?: Error }; +export type UseRunStatusesResult = + | { + fetchStatus: "loading"; + error: undefined; + statuses: undefined; + run: undefined; + } + | { + fetchStatus: "error"; + error: Error; + statuses: undefined; + run: undefined; + } + | ({ + fetchStatus: "success"; + error: undefined; + } & GetRunStatuses); export function useRunStatuses( runId: string | undefined, @@ -46,7 +61,7 @@ export function useRunStatuses( if (data?.run.status && runResolvedStatuses.includes(data.run.status)) { return false; } - if (options.refreshIntervalMs !== undefined) { + if (options?.refreshIntervalMs !== undefined) { return Math.max(options.refreshIntervalMs, 500); } @@ -56,12 +71,32 @@ export function useRunStatuses( queryClient ); - return { - isFetching: queryResult.isLoading, - error: queryResult.error, - run: queryResult.data.run, - statuses: queryResult.data.statuses, - }; + switch (queryResult.status) { + case "pending": { + return { + fetchStatus: "loading", + error: undefined, + statuses: undefined, + run: undefined, + }; + } + case "error": { + return { + fetchStatus: "error", + error: queryResult.error, + statuses: undefined, + run: undefined, + }; + } + case "success": { + return { + fetchStatus: "success", + error: undefined, + run: queryResult.data.run, + statuses: queryResult.data.statuses, + }; + } + } } export function useEventRunStatuses( diff --git a/packages/react/tsconfig.json b/packages/react/tsconfig.json index 887ec9b5b4..4ab3ef959a 100644 --- a/packages/react/tsconfig.json +++ b/packages/react/tsconfig.json @@ -4,6 +4,7 @@ "jsx": "react", "moduleResolution": "node", "esModuleInterop": true, + "strict": true, "lib": ["es2015", "dom"], "paths": { "@trigger.dev/core": ["../core/src/index"], diff --git a/references/nextjs-reference/src/app/api/trigger/route.ts b/references/nextjs-reference/src/app/api/trigger/route.ts index af1c70c9ea..acc3ef2863 100644 --- a/references/nextjs-reference/src/app/api/trigger/route.ts +++ b/references/nextjs-reference/src/app/api/trigger/route.ts @@ -2,17 +2,5 @@ import { createAppRoute } from "@trigger.dev/nextjs"; import { client } from "@/trigger"; import "@/jobs/hooks"; -// import "@/jobs/events"; -// import "@/jobs/general"; -// import "@/jobs/github"; -// import "@/jobs/logging"; -// import "@/jobs/openai"; -// import "@/jobs/plain"; -// import "@/jobs/schedules"; -// import "@/jobs/slack"; -// import "@/jobs/typeform"; -// import "@/jobs/edgeCases"; -// import "@/jobs/supabase"; -// import "@/jobs/stripe"; export const { POST, dynamic } = createAppRoute(client); diff --git a/references/nextjs-reference/src/components/RunDetails.tsx b/references/nextjs-reference/src/components/RunDetails.tsx index f20ad96909..a0f85bf444 100644 --- a/references/nextjs-reference/src/components/RunDetails.tsx +++ b/references/nextjs-reference/src/components/RunDetails.tsx @@ -1,67 +1,15 @@ "use client"; -import styles from "@/styles/Home.module.css"; -import { useEventDetails, useEventRunDetails, useRunDetails } from "@trigger.dev/react"; - -export function EventData({ id }: { id: string }) { - const { isLoading, data, error } = useEventDetails(id); - - return ( - <> -

Event

- {isLoading ? ( -

Loading

- ) : error ? ( - JSON.stringify(error, null, 2) - ) : data ? ( -

- Event ID: {data.id} -

- ) : ( -

- )} - - ); -} - -export function RunData({ id }: { id: string }) { - const { isLoading, isError, data, error } = useRunDetails(id); - - if (isLoading) { - return

Loading...

; - } - - if (isError) { - return

Error

; - } - - if (!data) { - return

Loading...

; - } - - return ( - <> -
Run status: {data.status}
-
- {data.tasks?.map((task) => ( -
-

{task.name}

-

Status: {task.status}

-
- ))} -
- - ); -} +import { useEventRunStatuses } from "@trigger.dev/react"; export function EventRunData({ id }: { id: string }) { - const { isLoading, isError, data, error } = useEventRunDetails(id); + const { fetchStatus, error, statuses, run } = useEventRunStatuses(id); - if (isLoading) { + if (fetchStatus === "loading") { return

Loading...

; } - if (isError) { + if (fetchStatus === "error") { return (

{error.name}

@@ -70,25 +18,34 @@ export function EventRunData({ id }: { id: string }) { ); } - if (!data) { - return

Loading...

; - } - return ( <> -
Run status: {data.status}
+
Run status: {run.status}
- {data.tasks?.map((task) => ( -
-

{task.displayKey ?? task.name}

-

{task.icon}

-

Status: {task.status}

-
- ))} + {statuses.map((status) => { + switch (status.key) { + case "getting-input-data": { + return ( +
+

{status.label}

+

Status: {status.state}

+
+ ); + } + case "generating-memes": { + const urls = status.data?.urls as string[] | undefined; + return ( +
+ {urls?.map((url) => )} +
+ ); + } + } + })}
- {data.output && ( + {run.output && ( -
{JSON.stringify(data.output, null, 2)}
+
{JSON.stringify(run.output, null, 2)}
)} diff --git a/references/nextjs-reference/src/jobs/edgeCases.ts b/references/nextjs-reference/src/jobs/edgeCases.ts deleted file mode 100644 index cd7c60b23e..0000000000 --- a/references/nextjs-reference/src/jobs/edgeCases.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { client } from "@/trigger"; -import { Job, eventTrigger } from "@trigger.dev/sdk"; -import { z } from "zod"; - -client.defineJob({ - id: "test-long-running-cpu", - name: "Test long running CPU", - version: "0.0.1", - trigger: eventTrigger({ - name: "test.cpu", - schema: z.object({ - iterations: z.number(), - sleepDuration: z.number(), - }), - }), - run: async (payload, io, ctx) => { - console.log(`Running run ${ctx.run.id} at ${new Date().toISOString()}`); - - for (let i = 0; i < payload.iterations ?? 1; i++) { - await new Promise((resolve) => setTimeout(resolve, payload.sleepDuration ?? 1000)); - } - - console.log(`Finishing run ${ctx.run.id} at ${new Date().toISOString()}`); - }, -}); diff --git a/references/nextjs-reference/src/jobs/events.ts b/references/nextjs-reference/src/jobs/events.ts deleted file mode 100644 index 4ef4071864..0000000000 --- a/references/nextjs-reference/src/jobs/events.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { client } from "@/trigger"; -import { Job, eventTrigger } from "@trigger.dev/sdk"; -import { z } from "zod"; - -client.defineJob({ - id: "test-event-trigger-1", - name: "Test Event Trigger 1", - version: "0.0.1", - logLevel: "debug", - trigger: eventTrigger({ - name: "test-event-trigger-1", - schema: z.object({ - name: z.string(), - payload: z.any(), - }), - }), - run: async (payload, io, ctx) => { - await io.sendEvent( - "send", - { - name: payload.name, - payload: payload.payload, - timestamp: new Date(), - }, - { deliverAt: new Date(Date.now() + 1000 * 30) } - ); - }, -}); - -client.defineJob({ - id: "test-event-trigger-2", - name: "Test Event Trigger 2", - version: "0.0.1", - logLevel: "debug", - trigger: eventTrigger({ - name: "test-event-trigger-2", - }), - run: async (payload, io, ctx) => { - for (let index = 0; index < 100; index++) { - await io.sendEvent(`send-${index}`, { - name: "test-event-trigger-1", - payload: { name: "whatever", payload: { index } }, - }); - } - }, -}); - -client.defineJob({ - id: "test-multiple-events", - name: "Test Multiple Events", - version: "0.0.1", - logLevel: "debug", - trigger: eventTrigger({ - name: ["test.event.1", "test.event.2"], - examples: [{ id: "test", name: "Test", payload: { name: "test" } }], - }), - run: async (payload, io, ctx) => { - await io.logger.log(`Triggered by the ${ctx.event.name} event`, { ctx }); - }, -}); diff --git a/references/nextjs-reference/src/jobs/general.ts b/references/nextjs-reference/src/jobs/general.ts deleted file mode 100644 index 685d7c4467..0000000000 --- a/references/nextjs-reference/src/jobs/general.ts +++ /dev/null @@ -1,269 +0,0 @@ -import { client, github, githubUser, slack } from "@/trigger"; -import { events } from "@trigger.dev/github"; -import { - Job, - cronTrigger, - eventTrigger, - intervalTrigger, - isTriggerError, - missingConnectionNotification, - missingConnectionResolvedNotification, -} from "@trigger.dev/sdk"; -import { z } from "zod"; - -const enabled = true; - -client.defineJob({ - id: "on-missing-auth-connection", - name: "On missing auth connection", - version: "0.1.1", - enabled, - trigger: missingConnectionNotification([githubUser]), - integrations: { - slack, - }, - run: async (payload, io, ctx) => { - switch (payload.type) { - case "DEVELOPER": { - return await io.slack.postMessage("message", { - text: `Missing developer connection: ${JSON.stringify(payload)}`, - channel: "C04GWUTDC3W", - }); - } - case "EXTERNAL": { - return await io.slack.postMessage("message", { - text: `Missing external connection: account: ${JSON.stringify( - payload.account - )}, payload: ${JSON.stringify(payload)}`, - channel: "C04GWUTDC3W", - }); - } - } - }, -}); - -client.defineJob({ - id: "on-missing-auth-connection-resolved", - name: "On missing auth connection-resolved", - version: "0.1.1", - enabled, - trigger: missingConnectionResolvedNotification([githubUser]), - integrations: { - slack, - }, - run: async (payload, io, ctx) => { - switch (payload.type) { - case "DEVELOPER": { - return await io.slack.postMessage("message", { - text: `Missing developer connection resolved: ${JSON.stringify(payload)}`, - channel: "C04GWUTDC3W", - }); - } - case "EXTERNAL": { - return await io.slack.postMessage("message", { - text: `Missing external connection resolved: ${JSON.stringify(payload)}`, - channel: "C04GWUTDC3W", - }); - } - } - }, -}); - -client.defineJob({ - id: "get-user-repo", - name: "Get User Repo", - version: "0.1.1", - enabled, - trigger: eventTrigger({ - name: "get.repo", - schema: z.object({ - owner: z.string(), - repo: z.string(), - }), - }), - integrations: { - github: githubUser, - }, - run: async (payload, io, ctx) => { - return await io.github.getRepo("get.repo", payload); - }, -}); - -client.defineJob({ - id: "event-1", - name: "Run when the foo.bar event happens", - version: "0.0.1", - enabled: true, - trigger: eventTrigger({ - name: "foo.bar", - }), - run: async (payload, io, ctx) => { - await io.try( - async () => { - return await io.runTask( - "task-1", - async (task) => { - if (task.attempts > 2) { - return { - bar: "foo", - }; - } - - throw new Error(`Task failed on ${task.attempts} attempt(s)`); - }, - { name: "task-1", retry: { limit: 3 } } - ); - }, - async (error) => { - // These should never be reached - await io.wait("wait-after-error", 5); - - await io.logger.error("This is a log error message", { - payload, - error, - }); - - return { - foo: "bar", - }; - } - ); - - try { - await io.runTask( - "task-2", - async (task) => { - throw new Error(`Task failed on ${task.attempts} attempt(s)`); - }, - { name: "task-2", retry: { limit: 5 } } - ); - } catch (error) { - if (isTriggerError(error)) { - throw error; - } - - await io.wait("wait-after-error", 5); - - await io.logger.error("This is a log error message", { - payload, - error, - }); - } - - return { - payload, - }; - }, -}); - -client.defineJob({ - id: "scheduled-job-1", - name: "Scheduled Job 1", - version: "0.1.1", - enabled: true, - trigger: intervalTrigger({ - seconds: 60, - }), - run: async (payload, io, ctx) => { - await io.wait("wait", 5); // wait for 5 seconds - - await io.logger.info("This is a log info message", { - payload, - }); - - await io.sendEvent("send-event", { - name: "custom.event", - payload, - context: ctx, - }); - - await io.runTask("level 1", async () => { - await io.runTask("level 2", async () => { - await io.runTask("level 3", async () => { - await io.runTask("level 4", async () => { - await io.runTask("level 5", async () => {}); - }); - }); - }); - }); - - await io.wait("5 minutes", 5 * 60); - - await io.runTask("Fingers crossed", async () => { - throw new Error("You messed up buddy!"); - }); - }, -}); - -client.defineJob({ - id: "scheduled-job-2", - name: "Scheduled Job 2", - version: "0.1.1", - enabled, - trigger: cronTrigger({ - cron: "*/5 * * * *", // every 5 minutes - }), - run: async (payload, io, ctx) => { - await io.wait("wait", 5); // wait for 5 seconds - await io.logger.info("This is a log info message", { - payload, - ctx, - }); - - return { - message: "Hello from scheduled job 1", - }; - }, -}); - -client.defineJob({ - id: "test-io-functions", - name: "Test IO functions", - version: "0.1.1", - enabled, - trigger: eventTrigger({ - name: "test.io", - }), - run: async (payload, io, ctx) => { - await io.wait("wait", 5); // wait for 5 seconds - await io.logger.info("This is a log info message", { - payload, - }); - await io.sendEvent("send-event", { - name: "custom.event", - payload, - context: ctx, - }); - }, -}); - -client.defineJob({ - id: "alert-on-new-github-issues-3", - name: "Alert on new GitHub issues", - version: "0.1.1", - enabled, - integrations: { - slack, - }, - trigger: github.triggers.repo({ - event: events.onIssueOpened, - owner: "ericallam", - repo: "basic-starter-12k", - }), - run: async (payload, io, ctx) => { - await io.runTask("slow task", async () => { - await new Promise((resolve) => setTimeout(resolve, 5000)); - }); - - await io.logger.info("This is a simple log info message"); - - await io.wait("wait", 5); // wait for 5 seconds - - const response = await io.slack.postMessage("Slack 📝", { - text: `New Issue opened: ${payload.issue.html_url}`, - channel: "C04GWUTDC3W", - }); - - return response; - }, -}); diff --git a/references/nextjs-reference/src/jobs/github.ts b/references/nextjs-reference/src/jobs/github.ts deleted file mode 100644 index c4aa833b4e..0000000000 --- a/references/nextjs-reference/src/jobs/github.ts +++ /dev/null @@ -1,371 +0,0 @@ -import { client, github, slack } from "@/trigger"; -import { Github } from "@trigger.dev/github"; -import { events } from "@trigger.dev/github"; -import { Job } from "@trigger.dev/sdk"; - -const githubApiKey = new Github({ - id: "github-api-key", - token: process.env["GITHUB_API_KEY"]!, -}); - -client.defineJob({ - id: "github-integration-on-issue", - name: "GitHub Integration - On Issue", - version: "0.1.0", - trigger: githubApiKey.triggers.repo({ - event: events.onIssue, - owner: "triggerdotdev", - repo: "empty", - }), - run: async (payload, io, ctx) => { - await io.logger.info("This is a simple log info message"); - return { payload, ctx }; - }, -}); - -client.defineJob({ - id: "github-integration-on-issue-opened", - name: "GitHub Integration - On Issue Opened", - version: "0.1.0", - integrations: { github: githubApiKey }, - trigger: githubApiKey.triggers.repo({ - event: events.onIssueOpened, - owner: "triggerdotdev", - repo: "empty", - }), - run: async (payload, io, ctx) => { - await io.github.addIssueAssignees("add assignee", { - owner: payload.repository.owner.login, - repo: payload.repository.name, - issueNumber: payload.issue.number, - assignees: ["matt-aitken"], - }); - - await io.github.addIssueLabels("add label", { - owner: payload.repository.owner.login, - repo: payload.repository.name, - issueNumber: payload.issue.number, - labels: ["bug"], - }); - - return { payload, ctx }; - }, -}); - -client.defineJob({ - id: "new-github-issue-reminder", - name: "New GitHub issue reminder", - version: "0.1.0", - integrations: { github, slack }, - trigger: github.triggers.repo({ - event: events.onIssueOpened, - owner: "triggerdotdev", - repo: "empty", - }), - run: async (payload, io, ctx) => { - //delay for 24 hours (or 60 seconds in development) - const delayDuration = ctx.environment.type === "DEVELOPMENT" ? 60 : 60 * 60 * 24; - await io.wait("wait 24 hours", delayDuration); - - const issue = await io.github.getIssue("get issue", { - owner: payload.repository.owner.login, - repo: payload.repository.name, - issueNumber: payload.issue.number, - }); - - //if the issue has had no activity - if (issue.updated_at === payload.issue.updated_at) { - await io.slack.postMessage("Slack reminder", { - text: `New issue needs attention: <${issue.html_url}|${issue.title}>`, - channel: "C04GWUTDC3W", - }); - - //assign it to someone, in this case… me - await io.github.addIssueAssignees("add assignee", { - owner: payload.repository.owner.login, - repo: payload.repository.name, - issueNumber: payload.issue.number, - assignees: ["ericallam"], - }); - } - }, -}); - -client.defineJob({ - id: "github-integration-on-issue-assigned", - name: "GitHub Integration - On Issue assigned", - version: "0.1.0", - trigger: githubApiKey.triggers.repo({ - event: events.onIssueAssigned, - owner: "triggerdotdev", - repo: "empty", - }), - run: async (payload, io, ctx) => { - await io.logger.info("This is a simple log info message"); - return { payload, ctx }; - }, -}); - -client.defineJob({ - id: "github-integration-on-issue-commented", - name: "GitHub Integration - On Issue commented", - version: "0.1.0", - trigger: githubApiKey.triggers.repo({ - event: events.onIssueComment, - owner: "triggerdotdev", - repo: "empty", - }), - run: async (payload, io, ctx) => { - await io.logger.info("This is a simple log info message"); - return { payload, ctx }; - }, -}); - -client.defineJob({ - id: "star-slack-notification", - name: "New Star Slack Notification", - version: "0.1.0", - integrations: { slack }, - trigger: githubApiKey.triggers.repo({ - event: events.onNewStar, - owner: "triggerdotdev", - repo: "empty", - }), - run: async (payload, io, ctx) => { - const response = await io.slack.postMessage("Slack star", { - text: `${payload.sender.login} starred ${payload.repository.full_name}.\nTotal: ${payload.repository.stargazers_count}⭐️`, - channel: "C04GWUTDC3W", - }); - }, -}); - -client.defineJob({ - id: "github-integration-on-new-star", - name: "GitHub Integration - On New Star", - version: "0.1.0", - trigger: githubApiKey.triggers.repo({ - event: events.onNewStar, - owner: "triggerdotdev", - repo: "empty", - }), - run: async (payload, io, ctx) => { - await io.logger.info("This is a simple log info message"); - return { payload, ctx }; - }, -}); - -client.defineJob({ - id: "github-integration-on-new-repo", - name: "GitHub Integration - On New Repository", - version: "0.1.0", - trigger: githubApiKey.triggers.repo({ - event: events.onNewRepository, - owner: "triggerdotdev", - repo: "empty", - }), - run: async (payload, io, ctx) => { - await io.logger.info("This is a simple log info message"); - return { payload, ctx }; - }, -}); - -client.defineJob({ - id: "github-integration-on-new-branch-or-tag", - name: "GitHub Integration - On New Branch or Tag", - version: "0.1.0", - trigger: githubApiKey.triggers.repo({ - event: events.onNewBranchOrTag, - owner: "triggerdotdev", - repo: "empty", - }), - run: async (payload, io, ctx) => { - await io.logger.info("This is a simple log info message"); - return { payload, ctx }; - }, -}); - -client.defineJob({ - id: "github-integration-on-new-branch", - name: "GitHub Integration - On New Branch", - version: "0.1.0", - trigger: githubApiKey.triggers.repo({ - event: events.onNewBranch, - owner: "triggerdotdev", - repo: "empty", - }), - run: async (payload, io, ctx) => { - await io.logger.info("This is a simple log info message"); - return { payload, ctx }; - }, -}); - -client.defineJob({ - id: "github-integration-on-push", - name: "GitHub Integration - On Push", - version: "0.1.0", - trigger: githubApiKey.triggers.repo({ - event: events.onPush, - owner: "triggerdotdev", - repo: "empty", - }), - run: async (payload, io, ctx) => { - await io.logger.info("This is a simple log info message"); - return { payload, ctx }; - }, -}); - -client.defineJob({ - id: "github-integration-on-pull-request", - name: "GitHub Integration - On Pull Request", - version: "0.1.0", - trigger: githubApiKey.triggers.repo({ - event: events.onPullRequest, - owner: "triggerdotdev", - repo: "empty", - }), - run: async (payload, io, ctx) => { - await io.logger.info("This is a simple log info message"); - return { payload, ctx }; - }, -}); - -client.defineJob({ - id: "github-integration-on-pull-request-review", - name: "GitHub Integration - On Pull Request Review", - version: "0.1.0", - trigger: githubApiKey.triggers.repo({ - event: events.onPullRequestReview, - owner: "triggerdotdev", - repo: "empty", - }), - run: async (payload, io, ctx) => { - await io.logger.info("This is a simple log info message"); - return { payload, ctx }; - }, -}); - -client.defineJob({ - id: "github-integration-on-pull-request-merge-commit", - name: "GitHub Integration - on Pull Request Merge Commit", - version: "0.1.0", - integrations: { github }, - trigger: githubApiKey.triggers.repo({ - event: events.onPullRequest, - owner: "triggerdotdev", - repo: "empty", - }), - run: async (payload, io, ctx) => { - await io.logger.info("This is a simple log info message"); - - if (payload.pull_request.merged && payload.pull_request.merge_commit_sha) { - const commit = await io.github.getCommit("get merge commit", { - owner: payload.repository.owner.login, - repo: payload.repository.name, - commitSHA: payload.pull_request.merge_commit_sha, - }); - await io.logger.info("Merge Commit Details", commit); - } - - return { payload, ctx }; - }, -}); - -client.defineJob({ - id: "github-integration-get-tree", - name: "GitHub Integration - Get Tree", - version: "0.1.0", - integrations: { github }, - trigger: githubApiKey.triggers.repo({ - event: events.onPullRequest, - owner: "triggerdotdev", - repo: "empty", - }), - run: async (payload, io, ctx) => { - await io.logger.info("This is a simple log info message"); - - if (payload.pull_request.merged && payload.pull_request.merge_commit_sha) { - const tree = await io.github.getTree("get merge commit", { - owner: payload.repository.owner.login, - repo: payload.repository.name, - treeSHA: payload.pull_request.merge_commit_sha, - }); - await io.logger.info("Tree ", tree); - } - - return { payload, ctx }; - }, -}); - -client.defineJob({ - id: "github-integration-get-reference", - name: "GitHub Integration - Get Reference", - integrations: { github }, - version: "0.1.0", - trigger: githubApiKey.triggers.repo({ - event: events.onNewBranch, - owner: "triggerdotdev", - repo: "empty", - }), - run: async (payload, io, ctx) => { - await io.logger.info("This is a simple log info message"); - - const ref = await io.github.getReference("Get reference", { - owner: payload.repository.owner.login, - repo: payload.repository.name, - ref: payload.ref, - }); - - await io.logger.info("Reference ", ref); - - return { payload, ctx }; - }, -}); - -client.defineJob({ - id: "github-integration-list-matching-references", - name: "GitHub Integration - List Matching References", - integrations: { github }, - version: "0.1.0", - trigger: githubApiKey.triggers.repo({ - event: events.onNewBranch, - owner: "triggerdotdev", - repo: "empty", - }), - run: async (payload, io, ctx) => { - await io.logger.info("This is a simple log info message"); - - const ref = await io.github.listMatchingReferences("List Matching References", { - owner: payload.repository.owner.login, - repo: payload.repository.name, - ref: payload.ref, - }); - - await io.logger.info("Reference ", ref); - - return { payload, ctx }; - }, -}); - -client.defineJob({ - id: "github-integration-get-tag", - name: "GitHub Integration - Get Tag", - version: "0.1.0", - integrations: { github }, - trigger: githubApiKey.triggers.repo({ - event: events.onNewBranchOrTag, - owner: "triggerdotdev", - repo: "empty", - }), - run: async (payload, io, ctx) => { - await io.logger.info("This is a simple log info message"); - if (payload.ref_type === "tag") { - const tag = io.github.getTag("Get Tag", { - owner: payload.repository.owner.login, - repo: payload.repository.name, - tagSHA: payload.ref, - }); - await io.logger.info("Tag ", tag); - } - return { payload, ctx }; - }, -}); diff --git a/references/nextjs-reference/src/jobs/hooks.ts b/references/nextjs-reference/src/jobs/hooks.ts index dc6d418481..09ed9412ce 100644 --- a/references/nextjs-reference/src/jobs/hooks.ts +++ b/references/nextjs-reference/src/jobs/hooks.ts @@ -34,14 +34,28 @@ client.defineJob({ //...do stuff await generatingMemes.update("middle-generation", { data: { - progress: 0.5, - urls: ["http://www."], + progress: 0.3, + urls: [ + "https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExZnZoMndsdWh0MmhvY2kyaDF6YjZjZzg1ZGsxdnhhYm13a3Q1Y3lkbyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/13HgwGsXF0aiGY/giphy.gif", + ], }, }); - await io.wait("wait-again", 2); + await io.wait("wait-again", 4); //...do stuff + await generatingMemes.update("generating-more-memes", { + data: { + progress: 0.6, + urls: [ + "https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExZnZoMndsdWh0MmhvY2kyaDF6YjZjZzg1ZGsxdnhhYm13a3Q1Y3lkbyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/13HgwGsXF0aiGY/giphy.gif", + "https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExbXdhNGhjaXVoZzFrMWJ0dmYyM2ZuOTIxN2J3aWYwY3J1OHI4eW13cCZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/scZPhLqaVOM1qG4lT9/giphy.gif", + ], + }, + }); + + await io.wait("wait-again", 4); + await generatingMemes.update("completed-generation", { label: "Generated memes", state: "success", diff --git a/references/nextjs-reference/src/jobs/logging.ts b/references/nextjs-reference/src/jobs/logging.ts deleted file mode 100644 index 3ebd97c5b8..0000000000 --- a/references/nextjs-reference/src/jobs/logging.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { client } from "@/trigger"; -import { Job, eventTrigger } from "@trigger.dev/sdk"; - -client.defineJob({ - id: "test-logging", - name: "Test logging", - version: "0.0.1", - logLevel: "debug", - trigger: eventTrigger({ - name: "test.logging", - }), - run: async (payload, io, ctx) => { - await io.logger.log("Hello log level", { payload }); - await io.logger.error("Hello error level", { payload }); - await io.logger.warn("Hello warn level", { payload }); - await io.logger.info("Hello info level", { payload }); - await io.logger.debug("Hello debug level", { payload }); - }, -}); diff --git a/references/nextjs-reference/src/jobs/openai.ts b/references/nextjs-reference/src/jobs/openai.ts deleted file mode 100644 index cd3219fa5d..0000000000 --- a/references/nextjs-reference/src/jobs/openai.ts +++ /dev/null @@ -1,157 +0,0 @@ -import { client } from "@/trigger"; -import { OpenAI } from "@trigger.dev/openai"; -import { Job, eventTrigger } from "@trigger.dev/sdk"; -import { z } from "zod"; - -const openai = new OpenAI({ - id: "openai", - apiKey: process.env["OPENAI_API_KEY"]!, -}); - -client.defineJob({ - id: "openai-tasks", - name: "OpenAI Tasks", - version: "0.0.1", - trigger: eventTrigger({ - name: "openai.tasks", - schema: z.object({}), - }), - integrations: { - openai, - }, - run: async (payload, io, ctx) => { - const models = await io.openai.listModels("list-models"); - - if (models.data.length > 0) { - await io.openai.retrieveModel("get-model", { - model: models.data[0].id, - }); - } - - await io.openai.backgroundCreateChatCompletion("background-chat-completion", { - model: "gpt-3.5-turbo", - messages: [ - { - role: "user", - content: "Create a good programming joke about background jobs", - }, - ], - }); - - await io.openai.createChatCompletion("chat-completion", { - model: "gpt-3.5-turbo", - messages: [ - { - role: "user", - content: "Create a good programming joke about background jobs", - }, - ], - }); - - await io.openai.backgroundCreateCompletion("background-completion", { - model: "text-davinci-003", - prompt: "Create a good programming joke about Tasks", - }); - - await io.openai.createCompletion("completion", { - model: "text-davinci-003", - prompt: "Create a good programming joke about Tasks", - }); - - await io.openai.createEdit("edit", { - model: "text-davinci-edit-001", - input: "Thsi is ridddled with erors", - instruction: "Fix the spelling errors", - }); - - await io.openai.createEmbedding("embedding", { - model: "text-embedding-ada-002", - input: "The food was delicious and the waiter...", - }); - }, -}); - -client.defineJob({ - id: "openai-images", - name: "OpenAI Images", - version: "0.0.1", - trigger: eventTrigger({ - name: "openai.images", - schema: z.object({}), - }), - integrations: { - openai, - }, - run: async (payload, io, ctx) => { - await io.openai.createImage("image", { - prompt: "A hedgehog wearing a party hat", - n: 2, - size: "256x256", - response_format: "url", - }); - }, -}); - -client.defineJob({ - id: "openai-files", - name: "OpenAI Files", - version: "0.0.1", - trigger: eventTrigger({ - name: "openai.files", - schema: z.object({}), - }), - integrations: { - openai, - }, - run: async (payload, io, ctx) => { - // jsonl string - await io.openai.createFile("file-string", { - file: `{ "prompt": "Tell me a joke", "completion": "Something funny" }\n{ "prompt": "Tell me another joke", "completion": "Something also funny" }`, - fileName: "cool-file.jsonl", - purpose: "fine-tune", - }); - - // fine tune file - const fineTuneFile = await io.openai.createFineTuneFile("file-fine-tune", { - fileName: "fine-tune.jsonl", - examples: [ - { - prompt: "Tell me a joke", - completion: "Why did the chicken cross the road? No one knows", - }, - { - prompt: "Tell me another joke", - completion: "Why did the chicken cross the road? To get to the other side", - }, - ], - }); - - const model = await io.openai.createFineTune("fine-tune", { - model: "davinci", - training_file: fineTuneFile.id, - }); - - const fineTunes = await io.openai.listFineTunes("list-fine-tunes"); - - const fineTune = await io.openai.retrieveFineTune("get-fine-tune", { - fineTuneId: model.id, - }); - - const events = await io.openai.listFineTuneEvents("list-fine-tune-events", { - fineTuneId: model.id, - }); - - const cancelFineTune = await io.openai.cancelFineTune("cancel-fine-tune", { - fineTuneId: model.id, - }); - - const files = await io.openai.listFiles("list-files"); - await io.logger.info("files", files); - - //this will fail because the fine tune didn't complete - await io.logger.info("This next task will fail because the model never completed"); - const deleteFineTune = await io.openai.deleteFineTune("delete-fine-tune", { - fineTunedModelId: model.id, - }); - }, -}); diff --git a/references/nextjs-reference/src/jobs/plain.ts b/references/nextjs-reference/src/jobs/plain.ts deleted file mode 100644 index 7eb51b1f8d..0000000000 --- a/references/nextjs-reference/src/jobs/plain.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { client } from "@/trigger"; -import { - ComponentDividerSpacingSize, - ComponentTextColor, - ComponentTextSize, - Plain, -} from "@trigger.dev/plain"; -import { Job, eventTrigger } from "@trigger.dev/sdk"; - -export const plain = new Plain({ - id: "plain-1", - apiKey: process.env["PLAIN_API_KEY"]!, -}); - -client.defineJob({ - id: "plain-playground", - name: "Plain Playground", - version: "0.1.1", - integrations: { - plain, - }, - trigger: eventTrigger({ - name: "plain.playground", - }), - run: async (payload, io, ctx) => { - const { customer } = await io.plain.upsertCustomer("upsert-customer", { - identifier: { - emailAddress: "eric@trigger.dev", - }, - onCreate: { - email: { - email: "eric@trigger.dev", - isVerified: true, - }, - fullName: "Eric Allam", - externalId: "123", - }, - onUpdate: { - fullName: { - value: "Eric Allam", - }, - externalId: { - value: "123", - }, - }, - }); - - const result = await io.plain.runTask("create-issue", async (client) => - client.createIssue({ - customerId: "abcdefghij", - issueTypeId: "123456", - }) - ); - - const foundCustomer = await io.plain.getCustomerById("get-customer", { - customerId: customer.id, - }); - - const timelineEntry = await io.plain.upsertCustomTimelineEntry("upsert-timeline-entry", { - customerId: customer.id, - title: "My timeline entry", - components: [ - { - componentText: { - text: `This is a nice title`, - }, - }, - { - componentDivider: { - dividerSpacingSize: ComponentDividerSpacingSize.M, - }, - }, - { - componentText: { - textSize: ComponentTextSize.S, - textColor: ComponentTextColor.Muted, - text: "External id", - }, - }, - { - componentText: { - text: foundCustomer?.externalId ?? "", - }, - }, - ], - }); - }, -}); diff --git a/references/nextjs-reference/src/jobs/schedules.ts b/references/nextjs-reference/src/jobs/schedules.ts deleted file mode 100644 index d5dc061ebf..0000000000 --- a/references/nextjs-reference/src/jobs/schedules.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { client } from "@/trigger"; -import { Job, cronTrigger } from "@trigger.dev/sdk"; - -client.defineJob({ - id: "test-cron-schedule-5", - name: "Test Cron Schedule 5", - version: "0.0.1", - logLevel: "debug", - trigger: cronTrigger({ - cron: "*/1 * * * *", - }), - run: async (payload, io, ctx) => { - await io.logger.debug("Hello cron schedule 2a", { - payload, - payload2: payload, - }); - }, -}); diff --git a/references/nextjs-reference/src/jobs/slack.ts b/references/nextjs-reference/src/jobs/slack.ts deleted file mode 100644 index 4df5200607..0000000000 --- a/references/nextjs-reference/src/jobs/slack.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { client } from "@/trigger"; -import { Slack } from "@trigger.dev/slack"; -import { Job, cronTrigger, eventTrigger } from "@trigger.dev/sdk"; - -const db = { - getKpiSummary: async (date: Date) => { - return { - revenue: 23_269, - orders: 1_234, - }; - }, -}; - -export const slack = new Slack({ id: "slack-6" }); -export const slackMissing = new Slack({ id: "slack-7" }); - -client.defineJob({ - id: "slack-kpi-summary", - name: "Slack kpi summary", - version: "0.1.1", - integrations: { - slack, - }, - trigger: cronTrigger({ - cron: "0 9 * * *", // 9am every day (UTC) - }), - run: async (payload, io, ctx) => { - const { revenue } = await db.getKpiSummary(payload.ts); - const response = await io.slack.postMessage("Slack 📝", { - text: `Yesterday's revenue was $${revenue}`, - channel: "C04GWUTDC3W", - }); - - return response; - }, -}); - -client.defineJob({ - id: "slack-auto-join", - name: "Slack Auto Join", - version: "0.1.1", - integrations: { - slack, - }, - trigger: eventTrigger({ - name: "slack.auto_join", - }), - run: async (payload, io, ctx) => { - const response = await io.slack.postMessage("Slack 📝", { - channel: "C05G130TH4G", - text: "Welcome to the team, Eric!", - blocks: [ - { - type: "section", - text: { - type: "mrkdwn", - text: `Welcome to the team, Eric!`, - }, - }, - { - type: "section", - text: { - type: "mrkdwn", - text: `I'm here to help you get started with Trigger!`, - }, - }, - ], - }); - - return response; - }, -}); - -client.defineJob({ - id: "slack-missing-integration", - name: "Slack with missing integration", - version: "0.1.1", - integrations: { - slack: slackMissing, - }, - trigger: eventTrigger({ - name: "missing.integration", - }), - run: async (payload, io, ctx) => { - const response = await io.slack.postMessage("message", { - text: `There's no Slack connection, or is there?`, - channel: "C04GWUTDC3W", - }); - - return response; - }, -}); diff --git a/references/nextjs-reference/src/jobs/stripe.ts b/references/nextjs-reference/src/jobs/stripe.ts deleted file mode 100644 index df834b82a3..0000000000 --- a/references/nextjs-reference/src/jobs/stripe.ts +++ /dev/null @@ -1,160 +0,0 @@ -import { Stripe } from "@trigger.dev/stripe"; -import { client } from "@/trigger"; -import { eventTrigger } from "@trigger.dev/sdk"; -import { z } from "zod"; - -const stripe = new Stripe({ - id: "stripe", - apiKey: process.env["STRIPE_API_KEY"]!, -}); - -client.defineJob({ - id: "stripe-example-1", - name: "Stripe Example 1", - version: "0.1.0", - trigger: eventTrigger({ - name: "stripe.example", - schema: z.object({ - customerId: z.string(), - source: z.string(), - }), - }), - integrations: { - stripe, - }, - run: async (payload, io, ctx) => { - await io.stripe.createCharge("create-charge", { - amount: 100, - currency: "usd", - source: payload.source, - customer: payload.customerId, - }); - }, -}); - -client.defineJob({ - id: "stripe-example-1", - name: "Stripe Example 1", - version: "0.1.0", - trigger: eventTrigger({ - name: "stripe.example", - schema: z.object({ - customerId: z.string(), - source: z.string(), - }), - }), - integrations: { - stripe, - }, - run: async (payload, io, ctx) => { - await io.stripe.createCharge("create-charge", { - amount: 100, - currency: "usd", - source: payload.source, - customer: payload.customerId, - }); - }, -}); - -client.defineJob({ - id: "stripe-create-customer", - name: "Stripe Create Customer", - version: "0.1.0", - trigger: eventTrigger({ - name: "stripe.new.customer", - schema: z.object({ - email: z.string(), - name: z.string(), - }), - }), - integrations: { - stripe, - }, - run: async (payload, io, ctx) => { - await io.stripe.createCustomer("create-customer", { - email: payload.email, - name: payload.name, - }); - }, -}); - -client.defineJob({ - id: "stripe-update-customer", - name: "Stripe Update Customer", - version: "0.1.0", - trigger: eventTrigger({ - name: "stripe.update.customer", - schema: z.object({ - customerId: z.string(), - name: z.string(), - }), - }), - integrations: { - stripe, - }, - run: async (payload, io, ctx) => { - await io.stripe.updateCustomer("update-customer", { - id: payload.customerId, - name: payload.name, - }); - }, -}); - -client.defineJob({ - id: "stripe-retrieve-subscription", - name: "Stripe Retrieve Subscription", - version: "0.1.0", - trigger: eventTrigger({ - name: "stripe.retrieve.subscription", - schema: z.object({ - id: z.string(), - }), - }), - integrations: { - stripe, - }, - run: async (payload, io, ctx) => { - const subscription = await io.stripe.retrieveSubscription("get", { - id: payload.id, - expand: ["customer"], - }); - }, -}); - -client.defineJob({ - id: "stripe-on-price", - name: "Stripe On Price", - version: "0.1.0", - trigger: stripe.onPrice({ events: ["price.created", "price.updated"] }), - run: async (payload, io, ctx) => { - if (ctx.event.name === "price.created") { - await io.logger.info("price created!", { ctx }); - } else { - await io.logger.info("price updated!", { ctx }); - } - }, -}); - -client.defineJob({ - id: "stripe-on-price-created", - name: "Stripe On Price Created", - version: "0.1.0", - trigger: stripe.onPriceCreated(), - run: async (payload, io, ctx) => { - await io.logger.info("ctx", { ctx }); - }, -}); - -client.defineJob({ - id: "stripe-on-subscription-created", - name: "Stripe On Subscription Created", - version: "0.1.0", - trigger: stripe.onCustomerSubscriptionCreated({ - filter: { - currency: ["usd"], - }, - }), - run: async (payload, io, ctx) => { - await io.logger.info("ctx", { ctx }); - }, -}); diff --git a/references/nextjs-reference/src/jobs/supabase.ts b/references/nextjs-reference/src/jobs/supabase.ts deleted file mode 100644 index 0b93b3c37a..0000000000 --- a/references/nextjs-reference/src/jobs/supabase.ts +++ /dev/null @@ -1,341 +0,0 @@ -import { Database } from "@/supabase.types"; -import { client } from "@/trigger"; -import { - type IntegrationIO, - Job, - eventTrigger, - type JobPayload, - type JobIO, - type TriggerPayload, - type IOWithIntegrations, -} from "@trigger.dev/sdk"; -import { SupabaseManagement, Supabase } from "@trigger.dev/supabase"; -import { z } from "zod"; - -const supabase = new SupabaseManagement({ - id: "supabase", -}); - -const db = supabase.db(process.env["SUPABASE_ID"]!); - -const dbNoTypes = supabase.db(process.env["SUPABASE_ID"]!); - -const supabaseManagementKey = new SupabaseManagement({ - id: "supabase-management-key", - apiKey: process.env["SUPABASE_API_KEY"]!, -}); - -const dbKey = supabase.db(process.env["SUPABASE_ID"]!); - -const supabaseDB = new Supabase({ - id: "supabase-db", - supabaseUrl: `https://${process.env["SUPABASE_ID"]}.supabase.co`, - supabaseKey: process.env["SUPABASE_KEY"]!, -}); - -async function doPlaygroundStuff(io: IntegrationIO, ref: string) { - await io.getPGConfig("get-pg-config", { - ref, - }); -} - -new Job(client, { - id: "supabase-playground", - name: "Supabase Playground", - version: "0.1.1", - trigger: eventTrigger({ - name: "supabase.playground", - }), - integrations: { - supabase, - supabaseDB, - supabaseManagementKey, - }, - run: async (payload, io, ctx) => { - await io.supabaseManagementKey.getPGConfig("get-pg-config", { - ref: payload.ref, - }); - - await io.supabase.getOrganizations("get-orgs"); - await io.supabase.getProjects("get-projects"); - - await io.supabase.listFunctions("list-functions", { - ref: payload.ref, - }); - - await io.supabase.runQuery("run-query", { - ref: payload.ref, - query: "SELECT * FROM users", - }); - - await io.supabase.getTypescriptTypes("get-typescript-types", { - ref: payload.ref, - }); - - const users = await io.supabaseDB.runTask( - "fetch-users", - async (db) => { - const { data, error } = await db.from("users").select("*"); - - if (error) throw error; - - return data; - }, - { name: "Fetch Users" } - ); - - const newUser = await io.supabaseDB.runTask( - "create-user", - async (db) => { - return await db - .from("users") - .insert({ - first_name: "John", - last_name: "Doe", - email_address: "john@trigger.dev", - }) - .select(); - }, - { name: "New Users" } - ); - }, -}); - -const createTodoJob = new Job(client, { - id: "supabase-create-todo", - name: "Supabase Create Todo", - version: "0.1.1", - trigger: eventTrigger({ - name: "supabase.create-todo", - schema: z.object({ - contents: z.string(), - user_id: z.number(), - }), - }), - integrations: { - supabaseDB, - }, - run: async (payload, io, ctx) => { - const newTodo = await io.supabaseDB.runTask( - "create-todo", - async (db) => { - const { data, error } = await db - .from("todos") - .insert({ - contents: payload.contents, - user_id: payload.user_id, - is_complete: false, - }) - .select(); - - if (error) throw error; - - return data; - }, - { - name: "Create Todo", - properties: [{ label: "Contents", text: payload.contents }], - } - ); - }, -}); - -type CreateTodo = JobPayload; -type CreateTodoIO = JobIO; - -async function runCreateTodo(payload: CreateTodo, io: CreateTodoIO) { - const newTodo = await io.supabaseDB.runTask( - "create-todo", - async (db) => { - const { data, error } = await db - .from("todos") - .insert({ - contents: payload.contents, - user_id: payload.user_id, - is_complete: false, - }) - .select(); - - if (error) throw error; - - return data; - }, - { - name: "Create Todo", - properties: [{ label: "Contents", text: payload.contents }], - } - ); -} - -const createProjectTrigger = eventTrigger({ - name: "supabase.create", - schema: z.object({ - name: z.string(), - organization_id: z.string(), - plan: z.enum(["free", "pro"]), - region: z.enum(["us-east-1", "us-west-1"]), - password: z.string(), - }), -}); - -async function doRun( - payload: TriggerPayload, - io: IOWithIntegrations<{ supabase: typeof supabase }> -) { - await io.supabase.createProject("create-project", { - name: payload.name, - organization_id: payload.organization_id, - plan: payload.plan, - region: payload.region, - kps_enabled: true, - db_pass: payload.password, - }); -} - -new Job(client, { - id: "supabase-create-project", - name: "Supabase Create Project", - version: "0.1.1", - trigger: createProjectTrigger, - integrations: { - supabase, - }, - run: async (payload, io, ctx) => { - await io.supabase.createProject("create-project", { - name: payload.name, - organization_id: payload.organization_id, - plan: payload.plan, - region: payload.region, - kps_enabled: true, - db_pass: payload.password, - }); - }, -}); - -new Job(client, { - id: "supabase-on-user-insert", - name: "Supabase On User Insert", - version: "0.1.1", - trigger: db.onInserted({ - table: "users", - }), - integrations: { - supabase, - }, - run: async (payload, io, ctx) => {}, -}); - -new Job(client, { - id: "supabase-on-user-insert-2", - name: "Supabase On User Insert 2", - version: "0.1.1", - trigger: db.onInserted({ - table: "users", - }), - integrations: { - supabase, - }, - run: async (payload, io, ctx) => {}, -}); - -new Job(client, { - id: "supabase-on-user-email-changed", - name: "Supabase On User Email Changed", - version: "0.1.1", - trigger: db.onUpdated({ - table: "users", - }), - integrations: { - supabase, - }, - run: async (payload, io, ctx) => {}, -}); - -new Job(client, { - id: "supabase-on-user-deleted", - name: "Supabase On User Deleted", - version: "0.1.1", - trigger: db.onDeleted({ - table: "users", - }), - integrations: { - supabase, - }, - run: async (payload, io, ctx) => {}, -}); - -new Job(client, { - id: "supabase-on-todo-created", - name: "Supabase On TODO created", - version: "0.1.1", - trigger: dbKey.onInserted({ - table: "todos", - }), - integrations: { - supabase, - }, - run: async (payload, io, ctx) => {}, -}); - -new Job(client, { - id: "supabase-on-todo-created", - name: "Supabase On TODO created", - version: "0.1.1", - trigger: dbKey.onInserted({ - table: "todos", - }), - integrations: { - supabase, - }, - run: async (payload, io, ctx) => {}, -}); - -new Job(client, { - id: "supabase-on-todo-completed", - name: "Supabase On TODO completed", - version: "0.1.1", - trigger: dbKey.onUpdated({ - table: "todos", - filter: { - old_record: { - is_complete: [false], - }, - record: { - is_complete: [true], - }, - }, - }), - integrations: { - supabase, - }, - run: async (payload, io, ctx) => { - await io.logger.log("Todo Completed", { payload }); - }, -}); - -new Job(client, { - id: "supabase-on-tweet-created-or-deleted", - name: "Supabase On Tweet Created or Deleted", - version: "0.1.1", - trigger: dbKey.onDeleted({ - schema: "public_2", - table: "tweets", - }), - integrations: { - supabase, - }, - run: async (payload, io, ctx) => {}, -}); - -new Job(client, { - id: "supabase-on-todo-created-no-types", - name: "Supabase On TODO created", - version: "0.1.1", - trigger: dbNoTypes.onInserted({ - table: "todos", - }), - integrations: { - supabase, - }, - run: async (payload, io, ctx) => {}, -}); diff --git a/references/nextjs-reference/src/jobs/typeform.ts b/references/nextjs-reference/src/jobs/typeform.ts deleted file mode 100644 index a2b03e0821..0000000000 --- a/references/nextjs-reference/src/jobs/typeform.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { client } from "@/trigger"; -import { Typeform, events } from "@trigger.dev/typeform"; -import { Job, eventTrigger } from "@trigger.dev/sdk"; -import { DynamicTrigger } from "@trigger.dev/sdk"; -import { z } from "zod"; - -export const typeform = new Typeform({ - id: "typeform-1", - token: process.env["TYPEFORM_API_KEY"]!, -}); - -client.defineJob({ - id: "typeform-playground", - name: "Typeform Playground", - version: "0.1.1", - integrations: { - typeform, - }, - trigger: eventTrigger({ - name: "typeform.playground", - schema: z.object({ - formId: z.string().optional(), - }), - }), - run: async (payload, io, ctx) => { - await io.typeform.listForms("list-forms"); - - if (payload.formId) { - const form = await io.typeform.getForm("get-form", { - uid: payload.formId, - }); - - const listResponses = await io.typeform.listResponses("list-responses", { - uid: payload.formId, - pageSize: 50, - }); - - const allResponses = await io.typeform.getAllResponses("get-all-responses", { - uid: payload.formId, - }); - } - - await io.typeform.runTask( - "create-form", - async (client) => { - return client.forms.create({ - data: { - title: "My Form", - fields: [ - { - title: "What is your name?", - type: "short_text", - ref: "name", - }, - { - title: "What is your email?", - type: "email", - ref: "email", - }, - ], - }, - }); - }, - { name: "Create Form" } - ); - }, -}); - -client.defineJob({ - id: "typeform-webhook-2", - name: "Typeform Webhook 2", - version: "0.1.1", - trigger: typeform.onFormResponse({ - uid: "QQnotGJM", - tag: "tag4", - }), - run: async (payload, io, ctx) => {}, -}); - -const dynamicTrigger = new DynamicTrigger(client, { - id: "typeform-dynamic-trigger", - source: typeform.source, - event: events.onFormResponse, -}); From a88018893faae7452231f3f4658d3a8daf4654cb Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Tue, 19 Sep 2023 18:01:40 -0700 Subject: [PATCH 16/24] =?UTF-8?q?Changeset:=20=E2=80=9CYou=20can=20create?= =?UTF-8?q?=20statuses=20in=20your=20Jobs=20that=20can=20then=20be=20read?= =?UTF-8?q?=20using=20React=20hooks=E2=80=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .changeset/kind-penguins-try.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changeset/kind-penguins-try.md diff --git a/.changeset/kind-penguins-try.md b/.changeset/kind-penguins-try.md new file mode 100644 index 0000000000..763013956e --- /dev/null +++ b/.changeset/kind-penguins-try.md @@ -0,0 +1,7 @@ +--- +"@trigger.dev/sdk": patch +"@trigger.dev/react": patch +"@trigger.dev/core": patch +--- + +You can create statuses in your Jobs that can then be read using React hooks From 79670f15d285755c79df47f3c2392f92b3a61fb7 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Tue, 19 Sep 2023 18:11:27 -0700 Subject: [PATCH 17/24] Changeset config is back to the old changelog style --- .changeset/config.json | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/.changeset/config.json b/.changeset/config.json index 64bfe8e522..5e8716336b 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -1,11 +1,6 @@ { "$schema": "https://unpkg.com/@changesets/config@2.2.0/schema.json", - "changelog": [ - "@remix-run/changelog-github", - { - "repo": "triggerdotdev/trigger.dev" - } - ], + "changelog": "@changesets/cli/changelog", "commit": false, "fixed": [ [ From 0a6462eff0bab1d2fb30f621c6876ad926b7dc60 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Wed, 20 Sep 2023 11:16:51 -0700 Subject: [PATCH 18/24] WIP on new React hooks guide --- docs/_snippets/react-hook-cards.mdx | 8 + docs/_snippets/react-hook-types.mdx | 24 ++ .../guides/react-hooks-automatic.mdx | 144 ++++++++++++ .../guides/react-hooks-setup.mdx | 83 +++++++ docs/documentation/guides/react-hooks.mdx | 212 +----------------- docs/mint.json | 10 +- 6 files changed, 277 insertions(+), 204 deletions(-) create mode 100644 docs/_snippets/react-hook-cards.mdx create mode 100644 docs/_snippets/react-hook-types.mdx create mode 100644 docs/documentation/guides/react-hooks-automatic.mdx create mode 100644 docs/documentation/guides/react-hooks-setup.mdx diff --git a/docs/_snippets/react-hook-cards.mdx b/docs/_snippets/react-hook-cards.mdx new file mode 100644 index 0000000000..4a947988df --- /dev/null +++ b/docs/_snippets/react-hook-cards.mdx @@ -0,0 +1,8 @@ + + + Receive coarse updates without writing additional Job code + + + Add statuses to your Job code for fine-grained UI updates + + diff --git a/docs/_snippets/react-hook-types.mdx b/docs/_snippets/react-hook-types.mdx new file mode 100644 index 0000000000..03608f73d1 --- /dev/null +++ b/docs/_snippets/react-hook-types.mdx @@ -0,0 +1,24 @@ +## The two types of Run progress you can use + +1. Automatic updates of Run and Task progress (no extra Job code required) +2. Explicitly created and updated `statuses` (more flexible and powerful) + +### Automatic updates + +These require no changes inside your Job code. You can receive: + +- Info about an event you sent, including the Runs it triggered. +- The overall status of the Run (in progress, success and fail statuses). +- Metadata like start and completed times. +- The Run output (what is returned or an error that failed the Job) +- Information about the Tasks that have completed/failed/are running. + +### Explicit `statuses` + +You can create `statuses` in your Job code. This gives you fine grained control over what you want to expose. + +It allows you to: + +- Show exactly what you want in your UI (with as many statuses as you want). +- Pass arbitrary data to your UI, which you can use to render elements. +- Update existing elements in your UI as the progress of the run continues. diff --git a/docs/documentation/guides/react-hooks-automatic.mdx b/docs/documentation/guides/react-hooks-automatic.mdx new file mode 100644 index 0000000000..89d6edf55e --- /dev/null +++ b/docs/documentation/guides/react-hooks-automatic.mdx @@ -0,0 +1,144 @@ +--- +title: "Automatic React hooks" +description: "These allow you to show Run and Task progress without adding code to your Jobs" +--- + +## The data you can receive + +- Info about an event you sent, including the Runs it triggered. +- The overall status of the Run (in progress, success and fail statuses). +- Metadata like start and completed times. +- The Run output (what is returned or an error that failed the Job) +- Information about the Tasks that have completed/failed/are running. + +## The hooks + +- [useEventDetails](/sdk/react/useeventdetails): get the details of a specific event +- [useRunDetails](/sdk/react/userundetails): get the details of a specific Run +- [useEventRunDetails](/sdk/react/useeventrundetails): get the details of a Run triggered from a specific event + +All of these hooks will automatically refresh your components as the state of your Runs or events change. + +#### 5.a useEventDetails + +The `useEventDetails` hook will get the details of a specific event. You can use this to show the status of a specific event. + + + +This component will show the details of an event and the overall status of Runs that were triggered by the event: + +```tsx +import { useEventDetails } from "@trigger.dev/react"; + +export default function EventDetails({ eventId }: { eventId: string }) { + const { data, error } = useEventDetails(eventId); + + if (error) { + return
Error: {error.message}
; + } + + if (!data) { + return
Loading...
; + } + + return ( +
+

{data.name}

+

Runs: {data.runs?.length}

+
+ {data.runs?.map((run) => ( +
+

+ Run {run.id}: {run.status} +

+
+ ))} +
+
+ ); +} +``` + +#### 5.b useRunDetails + +The `useRunDetails` hook will get the details of a specific Run. You can use this to show the status of a specific Run. + + + You can call [client.getRuns()](/sdk/triggerclient/instancemethods/getruns) with a Job id to get a + list of the most recent Runs for that Job. You can then pass that run id to your frontend to use + in the hook. + + +This component will show the details of a Run and the status of each task in the Run: + +```tsx +import { useRunDetails } from "@trigger.dev/react"; + +export default function RunDetails({ runId }: { runId: string }) { + const { data, error } = useRunDetails(runId); + + if (error) { + return
Error: {error.message}
; + } + + if (!data) { + return
Loading...
; + } + + return ( +
+

Run {data.id}

+

Status: {data.status}

+
+ {data.tasks?.map((task) => ( +
+

+ Task {task.id}: {task.status} +

+
+ ))} +
+
+ ); +} +``` + +#### 5.c useEventRunDetails + +The `useEventRunDetails` hook will get the details of a specific Run that was triggered by a specific event. You can use this to show the status of a specific Run. + + + +This component will show the details of a Run and the status of each task in the Run: + +```tsx +import { useEventRunDetails } from "@trigger.dev/react"; + +export default function EventRunDetails({ eventId }: { eventId: string }) { + const { data, error } = useEventRunDetails(eventId); + + if (error) { + return
Error: {error.message}
; + } + + if (!data) { + return
Loading...
; + } + + return ( +
+

Run {data.id}

+

Status: {data.status}

+
+ {data.tasks?.map((task) => ( +
+

+ Task {task.id}: {task.status} +

+
+ ))} +
+
+ ); +} +``` diff --git a/docs/documentation/guides/react-hooks-setup.mdx b/docs/documentation/guides/react-hooks-setup.mdx new file mode 100644 index 0000000000..740dcf6e65 --- /dev/null +++ b/docs/documentation/guides/react-hooks-setup.mdx @@ -0,0 +1,83 @@ +--- +title: "React hooks: setup" +description: "Add React hooks to your project including the TriggerProvider" +--- + +## Setting up your project for hooks + +This guide assumes that your project is already setup and you have a Job running. If not, you should follow the [quick start guide](/documentation/quickstart) first. + + + + Add the `@trigger.dev/react` package to your project: + + + + ```bash npm + npm install @trigger.dev/react@latest + ``` + + ```bash pnpm + pnpm install @trigger.dev/react@latest + ``` + + ```bash yarn + yarn add @trigger.dev/react@latest + ``` + + + + + + In the Trigger.dev dashboard you should go to your Project and then the "Environments & API Keys" page. + + ![Get your public API Key](/images/environments-public-apikey.png) + + You should copy the `PUBLIC` API key for the dev environment. + + + A public API key is a key that can be used in the browser. It can only be used to read certain + data from the API and can not write data. This means that it can be used to get the status of a + Job Run, but not to start a new Job Run. + + + + + Add the `NEXT_PUBLIC_TRIGGER_API_KEY` environment variable to your project. This will be used by the `TriggerProvider` component to connect to the Trigger API. + + ```sh .env.local + #... + TRIGGER_API_KEY=[your_private_api_key] + NEXT_PUBLIC_TRIGGER_API_KEY=[your_public_api_key] + #... + ``` + + Your private API key should already be in there. + + + + + The [TriggerProvider](/sdk/react/triggerprovider) component is a React Context Provider that will make the Trigger API client available to all child components. + + Generally you'll want to add this to the root of your app, so that it's available everywhere. However, you can add it lower in the hierarchy but it must be above any of the hooks. + + ```tsx app/layout.tsx + export default function RootLayout({ children }: { children: React.ReactNode }) { + return ( + + + + {children} + + + + ); + } + ``` + + + + +## Now add your hooks + + diff --git a/docs/documentation/guides/react-hooks.mdx b/docs/documentation/guides/react-hooks.mdx index 5df366aa87..0527e2b383 100644 --- a/docs/documentation/guides/react-hooks.mdx +++ b/docs/documentation/guides/react-hooks.mdx @@ -1,5 +1,5 @@ --- -title: "React hooks" +title: "Overview" description: "How to show the live status of Job Runs in your React app" --- @@ -9,207 +9,13 @@ You can display the live progress of a Job Run to your users, including the stat -## Steps + -This guide assumes that your project is already setup and you have a Job running. If not, you should follow the [quick start guide](/documentation/quickstart) first. +## Get started -### 1. Install the package - -Add the `@trigger.dev/react` package to your project: - - - -```bash npm -npm install @trigger.dev/react@latest -``` - -```bash pnpm -pnpm install @trigger.dev/react@latest -``` - -```bash yarn -yarn add @trigger.dev/react@latest -``` - - - -### 2. Get your public API key - -In the Trigger.dev dashboard you should go to your Project and then the "Environments & API Keys" page. - -![Get your public API Key](/images/environments-public-apikey.png) - -You should copy the `PUBLIC` API key for the dev environment. - - - A public API key is a key that can be used in the browser. It can only be used to read certain - data from the API and can not write data. This means that it can be used to get the status of a - Job Run, but not to start a new Job Run. - - -### 3. Add the env var to your project - -Add the `NEXT_PUBLIC_TRIGGER_API_KEY` environment variable to your project. This will be used by the `TriggerProvider` component to connect to the Trigger API. - -```sh .env.local -#... -TRIGGER_API_KEY=[your_private_api_key] -NEXT_PUBLIC_TRIGGER_API_KEY=[your_public_api_key] -#... -``` - -Your private API key should already be in there. - -### 4. Add the `TriggerProvider` component - -The [TriggerProvider](/sdk/react/triggerprovider) component is a React Context Provider that will make the Trigger API client available to all child components. - -Generally you'll want to add this to the root of your app, so that it's available everywhere. However, you can add it lower in the hierarchy but it must be above any of the hooks. - -```tsx app/layout.tsx -export default function RootLayout({ children }: { children: React.ReactNode }) { - return ( - - - - {children} - - - - ); -} -``` - -### 5. Add hooks to your components - -There are three hooks that you can use to show statuses: - -- [useEventDetails](/sdk/react/useeventdetails): get the details of a specific event -- [useRunDetails](/sdk/react/userundetails): get the details of a specific Run -- [useEventRunDetails](/sdk/react/useeventrundetails): get the details of a Run triggered from a specific event - -All of these hooks will automatically refresh your components as the state of your Runs or events change. - -#### 5.a useEventDetails - -The `useEventDetails` hook will get the details of a specific event. You can use this to show the status of a specific event. - - - -This component will show the details of an event and the overall status of Runs that were triggered by the event: - -```tsx -import { useEventDetails } from "@trigger.dev/react"; - -export default function EventDetails({ eventId }: { eventId: string }) { - const { data, error } = useEventDetails(eventId); - - if (error) { - return
Error: {error.message}
; - } - - if (!data) { - return
Loading...
; - } - - return ( -
-

{data.name}

-

Runs: {data.runs?.length}

-
- {data.runs?.map((run) => ( -
-

- Run {run.id}: {run.status} -

-
- ))} -
-
- ); -} -``` - -#### 5.b useRunDetails - -The `useRunDetails` hook will get the details of a specific Run. You can use this to show the status of a specific Run. - - - You can call [client.getRuns()](/sdk/triggerclient/instancemethods/getruns) with a Job id to get a - list of the most recent Runs for that Job. You can then pass that run id to your frontend to use - in the hook. - - -This component will show the details of a Run and the status of each task in the Run: - -```tsx -import { useRunDetails } from "@trigger.dev/react"; - -export default function RunDetails({ runId }: { runId: string }) { - const { data, error } = useRunDetails(runId); - - if (error) { - return
Error: {error.message}
; - } - - if (!data) { - return
Loading...
; - } - - return ( -
-

Run {data.id}

-

Status: {data.status}

-
- {data.tasks?.map((task) => ( -
-

- Task {task.id}: {task.status} -

-
- ))} -
-
- ); -} -``` - -#### 5.c useEventRunDetails - -The `useEventRunDetails` hook will get the details of a specific Run that was triggered by a specific event. You can use this to show the status of a specific Run. - - - -This component will show the details of a Run and the status of each task in the Run: - -```tsx -import { useEventRunDetails } from "@trigger.dev/react"; - -export default function EventRunDetails({ eventId }: { eventId: string }) { - const { data, error } = useEventRunDetails(eventId); - - if (error) { - return
Error: {error.message}
; - } - - if (!data) { - return
Loading...
; - } - - return ( -
-

Run {data.id}

-

Status: {data.status}

-
- {data.tasks?.map((task) => ( -
-

- Task {task.id}: {task.status} -

-
- ))} -
-
- ); -} -``` + + + First you need to add React hooks to your project including a TriggerProvider + + + diff --git a/docs/mint.json b/docs/mint.json index f877bcf3d6..cd69f6d906 100644 --- a/docs/mint.json +++ b/docs/mint.json @@ -171,7 +171,15 @@ "documentation/guides/using-integrations-oauth" ] }, - "documentation/guides/react-hooks", + { + "group": "React hooks", + "pages": [ + "documentation/guides/react-hooks", + "documentation/guides/react-hooks-setup", + "documentation/guides/react-hooks-automatic", + "documentation/guides/react-hooks-statuses" + ] + }, { "group": "Deployment", "pages": [ From e7f53aaed09e9985c25e565d9c9100cb98666cf6 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Wed, 20 Sep 2023 14:38:42 -0700 Subject: [PATCH 19/24] Guide docs for the new hooks --- docs/_snippets/how-to-get-run-id.mdx | 5 + .../guides/react-hooks-automatic.mdx | 12 +- .../guides/react-hooks-statuses.mdx | 183 ++++++++++++++++++ 3 files changed, 192 insertions(+), 8 deletions(-) create mode 100644 docs/_snippets/how-to-get-run-id.mdx create mode 100644 docs/documentation/guides/react-hooks-statuses.mdx diff --git a/docs/_snippets/how-to-get-run-id.mdx b/docs/_snippets/how-to-get-run-id.mdx new file mode 100644 index 0000000000..8d5d372431 --- /dev/null +++ b/docs/_snippets/how-to-get-run-id.mdx @@ -0,0 +1,5 @@ + + You can call [client.getRuns()](/sdk/triggerclient/instancemethods/getruns) with a Job id to get a + list of the most recent Runs for that Job. You can then pass that run id to your frontend to use + in the hook. + diff --git a/docs/documentation/guides/react-hooks-automatic.mdx b/docs/documentation/guides/react-hooks-automatic.mdx index 89d6edf55e..be65cd4e26 100644 --- a/docs/documentation/guides/react-hooks-automatic.mdx +++ b/docs/documentation/guides/react-hooks-automatic.mdx @@ -19,7 +19,7 @@ description: "These allow you to show Run and Task progress without adding code All of these hooks will automatically refresh your components as the state of your Runs or events change. -#### 5.a useEventDetails +#### useEventDetails The `useEventDetails` hook will get the details of a specific event. You can use this to show the status of a specific event. @@ -59,15 +59,11 @@ export default function EventDetails({ eventId }: { eventId: string }) { } ``` -#### 5.b useRunDetails +#### useRunDetails The `useRunDetails` hook will get the details of a specific Run. You can use this to show the status of a specific Run. - - You can call [client.getRuns()](/sdk/triggerclient/instancemethods/getruns) with a Job id to get a - list of the most recent Runs for that Job. You can then pass that run id to your frontend to use - in the hook. - + This component will show the details of a Run and the status of each task in the Run: @@ -103,7 +99,7 @@ export default function RunDetails({ runId }: { runId: string }) { } ``` -#### 5.c useEventRunDetails +#### useEventRunDetails The `useEventRunDetails` hook will get the details of a specific Run that was triggered by a specific event. You can use this to show the status of a specific Run. diff --git a/docs/documentation/guides/react-hooks-statuses.mdx b/docs/documentation/guides/react-hooks-statuses.mdx new file mode 100644 index 0000000000..2d49dab2a8 --- /dev/null +++ b/docs/documentation/guides/react-hooks-statuses.mdx @@ -0,0 +1,183 @@ +--- +title: "Statuses hooks" +description: "How to add `statuses` to your Job code and then subscribe using the hooks" +--- + +## In your Job code + +You should create a "status", which you can then update throughout the Job if you'd like to. You can create more than one status. Each of these will come through to the hook with the latest status and the history for each. + +```ts jobs/yourjob.ts +//your job +client.defineJob({ + id: "meme-generator", + name: "Generate memes", + version: "0.1.1", + trigger: eventTrigger({ + name: "generate-memes", + }), + run: async (payload, io, ctx) => { + //create a status "generating-memes" + //you give it the starting state. Only label is required + const generatingMemes = await io.createStatus("generating-memes", { + //the label is compulsory on this first call + label: "Generating memes", + //state is optional + state: "loading", + //data is an optional object. the values can be any type that is JSON serializable + data: { + progress: 0.1, + }, + }); + + //...do stuff, like generate memes + + //update the generatingMemes status. + //anything set here will override the previous values, but you'll be able to view the full history with hooks + await generatingMemes.update("middle-generation", { + //label and state aren't specified so will remain the same + //set data, this overrides the previous value + state: "success", + data: { + progress: 1, + urls: [ + "https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExZnZoMndsdWh0MmhvY2kyaDF6YjZjZzg1ZGsxdnhhYm13a3Q1Y3lkbyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/13HgwGsXF0aiGY/giphy.gif", + ], + }, + }); + }, +}); +``` + +In this case we created just a single status `generating-memes` and then updated it. It's worth noting that you can create as many statuses as you'd like in a single Job and you can update them as many times as you want. + +This allows you to fine-grained control over how you report progress and output data from your Job. + +## Using the React hooks + +There are two hooks you can use in your UI to display the Run statuses. + +- [useRunStatuses](/sdk/react/userunstatuses): get the statuses of the specified run +- [useEventRunStatuses](/sdk/react/useeventrunstatuses): get the statuses of a run that was triggered by a specified event + +## useEventRunStatuses + +The `useEventRunStatuses` hook will give you the statuses and overview data of **the first run** that is triggered by an event. + + + +This component will show the details of a Run and the status of each task in the Run: + +```ts +import { useEventRunStatuses } from "@trigger.dev/react"; + +export function EventRunData({ id }: { id: string }) { + const { fetchStatus, error, statuses, run } = useEventRunStatuses(id); + + if (fetchStatus === "loading") { + return

Loading...

; + } + + if (fetchStatus === "error") { + return ( +
+

{error.name}

+

{error.message}

+
+ ); + } + + return ( + <> + //you receive the overall status of the run, e.g. SUCCESS, FAIL +
Run status: {run.status}
+
+ {statuses.map((status) => { + switch (status.key) { + case "generating-memes": { + const urls = status.data?.urls as string[] | undefined; + return ( +
+ // Will display: "Generating memes: loading" +

+ {status.label}: {status.state} +

+ //will render the memes as images + {urls?.map((url) => )} +
+ ); + } + } + })} +
+ //this is what's returned from the run function + {run.output && ( + +
{JSON.stringify(run.output, null, 2)}
+
+ )} + + ); +} +``` + +## useRunStatuses + +The `useRunStatuses` hook will give you the statuses and overview data of a specific Run. + + + +This component will show the details of a Run and the status of each task in the Run: + +```ts +import { useRunStatuses } from "@trigger.dev/react"; + +export function RunData({ id }: { id: string }) { + const { fetchStatus, error, statuses, run } = useRunStatuses(id); + + if (fetchStatus === "loading") { + return

Loading...

; + } + + if (fetchStatus === "error") { + return ( +
+

{error.name}

+

{error.message}

+
+ ); + } + + return ( + <> + //you receive the overall status of the run, e.g. SUCCESS, FAIL +
Run status: {run.status}
+
+ {statuses.map((status) => { + switch (status.key) { + case "generating-memes": { + const urls = status.data?.urls as string[] | undefined; + return ( +
+ // Will display: "Generating memes: loading" +

+ {status.label}: {status.state} +

+ //will render the memes as images + {urls?.map((url) => )} +
+ ); + } + } + })} +
+ //this is what's returned from the run function + {run.output && ( + +
{JSON.stringify(run.output, null, 2)}
+
+ )} + + ); +} +``` From 543a38a23d0f7b787ad982c083ebb2fa8c27ea9f Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Wed, 20 Sep 2023 17:07:46 -0700 Subject: [PATCH 20/24] Added the status hooks to the React hooks guide --- docs/_snippets/react-hook-cards.mdx | 8 -- .../guides/react-hooks-automatic.mdx | 2 +- .../guides/react-hooks-setup.mdx | 83 ---------------- .../guides/react-hooks-statuses.mdx | 10 +- docs/documentation/guides/react-hooks.mdx | 95 +++++++++++++++++-- docs/mint.json | 1 - 6 files changed, 99 insertions(+), 100 deletions(-) delete mode 100644 docs/_snippets/react-hook-cards.mdx delete mode 100644 docs/documentation/guides/react-hooks-setup.mdx diff --git a/docs/_snippets/react-hook-cards.mdx b/docs/_snippets/react-hook-cards.mdx deleted file mode 100644 index 4a947988df..0000000000 --- a/docs/_snippets/react-hook-cards.mdx +++ /dev/null @@ -1,8 +0,0 @@ - - - Receive coarse updates without writing additional Job code - - - Add statuses to your Job code for fine-grained UI updates - - diff --git a/docs/documentation/guides/react-hooks-automatic.mdx b/docs/documentation/guides/react-hooks-automatic.mdx index be65cd4e26..d097442556 100644 --- a/docs/documentation/guides/react-hooks-automatic.mdx +++ b/docs/documentation/guides/react-hooks-automatic.mdx @@ -1,6 +1,6 @@ --- title: "Automatic React hooks" -description: "These allow you to show Run and Task progress without adding code to your Jobs" +description: "These allow you to show Run and Task progress without adding extra code to your Jobs." --- ## The data you can receive diff --git a/docs/documentation/guides/react-hooks-setup.mdx b/docs/documentation/guides/react-hooks-setup.mdx deleted file mode 100644 index 740dcf6e65..0000000000 --- a/docs/documentation/guides/react-hooks-setup.mdx +++ /dev/null @@ -1,83 +0,0 @@ ---- -title: "React hooks: setup" -description: "Add React hooks to your project including the TriggerProvider" ---- - -## Setting up your project for hooks - -This guide assumes that your project is already setup and you have a Job running. If not, you should follow the [quick start guide](/documentation/quickstart) first. - - - - Add the `@trigger.dev/react` package to your project: - - - - ```bash npm - npm install @trigger.dev/react@latest - ``` - - ```bash pnpm - pnpm install @trigger.dev/react@latest - ``` - - ```bash yarn - yarn add @trigger.dev/react@latest - ``` - - - - - - In the Trigger.dev dashboard you should go to your Project and then the "Environments & API Keys" page. - - ![Get your public API Key](/images/environments-public-apikey.png) - - You should copy the `PUBLIC` API key for the dev environment. - - - A public API key is a key that can be used in the browser. It can only be used to read certain - data from the API and can not write data. This means that it can be used to get the status of a - Job Run, but not to start a new Job Run. - - - - - Add the `NEXT_PUBLIC_TRIGGER_API_KEY` environment variable to your project. This will be used by the `TriggerProvider` component to connect to the Trigger API. - - ```sh .env.local - #... - TRIGGER_API_KEY=[your_private_api_key] - NEXT_PUBLIC_TRIGGER_API_KEY=[your_public_api_key] - #... - ``` - - Your private API key should already be in there. - - - - - The [TriggerProvider](/sdk/react/triggerprovider) component is a React Context Provider that will make the Trigger API client available to all child components. - - Generally you'll want to add this to the root of your app, so that it's available everywhere. However, you can add it lower in the hierarchy but it must be above any of the hooks. - - ```tsx app/layout.tsx - export default function RootLayout({ children }: { children: React.ReactNode }) { - return ( - - - - {children} - - - - ); - } - ``` - - - - -## Now add your hooks - - diff --git a/docs/documentation/guides/react-hooks-statuses.mdx b/docs/documentation/guides/react-hooks-statuses.mdx index 2d49dab2a8..193af7edb6 100644 --- a/docs/documentation/guides/react-hooks-statuses.mdx +++ b/docs/documentation/guides/react-hooks-statuses.mdx @@ -1,8 +1,16 @@ --- -title: "Statuses hooks" +title: "Explicit status hooks" description: "How to add `statuses` to your Job code and then subscribe using the hooks" --- +You can create `statuses` in your Job code. This gives you fine grained control over what you want to expose. + +It allows you to: + +- Show exactly what you want in your UI (with as many statuses as you want). +- Pass arbitrary data to your UI, which you can use to render elements. +- Update existing elements in your UI as the progress of the run continues. + ## In your Job code You should create a "status", which you can then update throughout the Job if you'd like to. You can create more than one status. Each of these will come through to the hook with the latest status and the history for each. diff --git a/docs/documentation/guides/react-hooks.mdx b/docs/documentation/guides/react-hooks.mdx index 0527e2b383..cebb7e7a0d 100644 --- a/docs/documentation/guides/react-hooks.mdx +++ b/docs/documentation/guides/react-hooks.mdx @@ -9,13 +9,96 @@ You can display the live progress of a Job Run to your users, including the stat - +## Setting up your project for hooks -## Get started +This guide assumes that your project is already setup and you have a Job running. If not, you should follow the [quick start guide](/documentation/quickstart) first. - - - First you need to add React hooks to your project including a TriggerProvider + + + Add the `@trigger.dev/react` package to your project: + + + + ```bash npm + npm install @trigger.dev/react@latest + ``` + + ```bash pnpm + pnpm install @trigger.dev/react@latest + ``` + + ```bash yarn + yarn add @trigger.dev/react@latest + ``` + + + + + + In the Trigger.dev dashboard you should go to your Project and then the "Environments & API Keys" page. + + ![Get your public API Key](/images/environments-public-apikey.png) + + You should copy the `PUBLIC` API key for the dev environment. + + + A public API key is a key that can be used in the browser. It can only be used to read certain + data from the API and can not write data. This means that it can be used to get the status of a + Job Run, but not to start a new Job Run. + + + + + Add the `NEXT_PUBLIC_TRIGGER_API_KEY` environment variable to your project. This will be used by the `TriggerProvider` component to connect to the Trigger API. + + ```sh .env.local + #... + TRIGGER_API_KEY=[your_private_api_key] + NEXT_PUBLIC_TRIGGER_API_KEY=[your_public_api_key] + #... + ``` + + Your private API key should already be in there. + + + + + The [TriggerProvider](/sdk/react/triggerprovider) component is a React Context Provider that will make the Trigger API client available to all child components. + + Generally you'll want to add this to the root of your app, so that it's available everywhere. However, you can add it lower in the hierarchy but it must be above any of the hooks. + + ```tsx app/layout.tsx + export default function RootLayout({ children }: { children: React.ReactNode }) { + return ( + + + + {children} + + + + ); + } + ``` + + + + +## Two ways to report Run progress + +**Automatic progress** – without writing additional code in your Job you can get updates on the overall run status and individual tasks inside the run. + +**Explicit status** – add code to your Job that reports the status of the things you're doing. This gives you full flexibility for displaying progress in your UI. + + + + Receive coarse updates without writing additional Job code + + + Add statuses to your Job code for fine-grained UI updates - diff --git a/docs/mint.json b/docs/mint.json index cd69f6d906..f8f2b7cf85 100644 --- a/docs/mint.json +++ b/docs/mint.json @@ -175,7 +175,6 @@ "group": "React hooks", "pages": [ "documentation/guides/react-hooks", - "documentation/guides/react-hooks-setup", "documentation/guides/react-hooks-automatic", "documentation/guides/react-hooks-statuses" ] From 4a18f2012e749834f1d15d8e198b9c811e8233a4 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Wed, 20 Sep 2023 17:08:25 -0700 Subject: [PATCH 21/24] Removed the links to the status hooks reference for now --- docs/documentation/guides/react-hooks-statuses.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/documentation/guides/react-hooks-statuses.mdx b/docs/documentation/guides/react-hooks-statuses.mdx index 193af7edb6..4b07f22474 100644 --- a/docs/documentation/guides/react-hooks-statuses.mdx +++ b/docs/documentation/guides/react-hooks-statuses.mdx @@ -65,8 +65,8 @@ This allows you to fine-grained control over how you report progress and output There are two hooks you can use in your UI to display the Run statuses. -- [useRunStatuses](/sdk/react/userunstatuses): get the statuses of the specified run -- [useEventRunStatuses](/sdk/react/useeventrunstatuses): get the statuses of a run that was triggered by a specified event +- **useRunStatuses**: get the statuses of the specified run +- **useEventRunStatuses**: get the statuses of a run that was triggered by a specified event ## useEventRunStatuses From 149a741e9658d01a8cab6b5f2f08c8903650a97a Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Wed, 20 Sep 2023 17:59:11 -0700 Subject: [PATCH 22/24] Re-ordered the hooks --- docs/documentation/guides/react-hooks-statuses.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/documentation/guides/react-hooks-statuses.mdx b/docs/documentation/guides/react-hooks-statuses.mdx index 4b07f22474..b6f9732ebb 100644 --- a/docs/documentation/guides/react-hooks-statuses.mdx +++ b/docs/documentation/guides/react-hooks-statuses.mdx @@ -65,8 +65,8 @@ This allows you to fine-grained control over how you report progress and output There are two hooks you can use in your UI to display the Run statuses. -- **useRunStatuses**: get the statuses of the specified run - **useEventRunStatuses**: get the statuses of a run that was triggered by a specified event +- **useRunStatuses**: get the statuses of the specified run ## useEventRunStatuses From 7d5cd20893062d1fc81ab8d25a80b950dd911190 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Thu, 21 Sep 2023 09:08:06 -0700 Subject: [PATCH 23/24] Fix for an error in the docs --- docs/documentation/guides/react-hooks-statuses.mdx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/documentation/guides/react-hooks-statuses.mdx b/docs/documentation/guides/react-hooks-statuses.mdx index b6f9732ebb..eb0bb08f34 100644 --- a/docs/documentation/guides/react-hooks-statuses.mdx +++ b/docs/documentation/guides/react-hooks-statuses.mdx @@ -43,9 +43,10 @@ client.defineJob({ //update the generatingMemes status. //anything set here will override the previous values, but you'll be able to view the full history with hooks await generatingMemes.update("middle-generation", { - //label and state aren't specified so will remain the same - //set data, this overrides the previous value + //label isn't specified so will remain the same + //state will be updated to "success" state: "success", + //set data, this overrides the previous value data: { progress: 1, urls: [ From 460f1c4c9b6e8e3b8cc606c84f260c42bd2b505b Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Thu, 21 Sep 2023 10:22:16 -0700 Subject: [PATCH 24/24] Set a default of a blank array for the GetRunSchema --- packages/core/src/schemas/runs.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/schemas/runs.ts b/packages/core/src/schemas/runs.ts index 4804b2cfce..e22b964535 100644 --- a/packages/core/src/schemas/runs.ts +++ b/packages/core/src/schemas/runs.ts @@ -80,7 +80,7 @@ export const GetRunSchema = RunSchema.extend({ /** The tasks from the run */ tasks: z.array(RunTaskWithSubtasksSchema), /** Any status updates that were published from the run */ - statuses: z.array(JobRunStatusRecordSchema), + statuses: z.array(JobRunStatusRecordSchema).default([]), /** If there are more tasks, you can use this to get them */ nextCursor: z.string().optional(), });