Generate fake test data from custom and webhook event schemas
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@trigger.dev/sdk": patch
|
||||
---
|
||||
|
||||
Generate and send JSON Schema for custom and webhook events
|
||||
@@ -0,0 +1,87 @@
|
||||
import type { PrismaClient } from "~/db.server";
|
||||
import { prisma } from "~/db.server";
|
||||
import { JSONSchemaFaker } from "json-schema-faker";
|
||||
import type { Workflow, WorkflowRun } from ".prisma/client";
|
||||
|
||||
export class WorkflowTestPresenter {
|
||||
#prismaClient: PrismaClient;
|
||||
|
||||
constructor(prismaClient: PrismaClient = prisma) {
|
||||
this.#prismaClient = prismaClient;
|
||||
}
|
||||
|
||||
async data({
|
||||
organizationSlug,
|
||||
workflowSlug,
|
||||
environmentSlug,
|
||||
}: {
|
||||
organizationSlug: string;
|
||||
workflowSlug: string;
|
||||
environmentSlug: string;
|
||||
}) {
|
||||
const workflow = await this.#prismaClient.workflow.findFirst({
|
||||
where: {
|
||||
slug: workflowSlug,
|
||||
organization: {
|
||||
slug: organizationSlug,
|
||||
},
|
||||
},
|
||||
include: {
|
||||
runs: {
|
||||
where: {
|
||||
environment: {
|
||||
slug: environmentSlug,
|
||||
},
|
||||
},
|
||||
include: {
|
||||
event: true,
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: "desc",
|
||||
},
|
||||
take: 1,
|
||||
},
|
||||
externalSource: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!workflow) {
|
||||
throw new Error("Workflow not found");
|
||||
}
|
||||
|
||||
const payload = await this.#getPayload(workflow, workflow.runs[0]);
|
||||
|
||||
const status =
|
||||
workflow.status === "CREATED"
|
||||
? workflow.type === "WEBHOOK" &&
|
||||
workflow.externalSource?.manualRegistration
|
||||
? "TESTABLE"
|
||||
: "CREATED"
|
||||
: workflow.status;
|
||||
|
||||
return { payload, status };
|
||||
}
|
||||
|
||||
async #getPayload(
|
||||
workflow: Workflow,
|
||||
lastRun?: WorkflowRun & { event: { payload: any } }
|
||||
) {
|
||||
if (workflow.type === "SCHEDULE") {
|
||||
return {
|
||||
scheduledTime: new Date(),
|
||||
lastRunAt: lastRun?.startedAt,
|
||||
};
|
||||
}
|
||||
|
||||
if (lastRun) {
|
||||
return lastRun.event.payload;
|
||||
}
|
||||
|
||||
if (workflow.jsonSchema) {
|
||||
// @ts-ignore
|
||||
return JSONSchemaFaker.generate(workflow.jsonSchema);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
}
|
||||
+16
-19
@@ -13,7 +13,8 @@ import { Body } from "~/components/primitives/text/Body";
|
||||
import { Title } from "~/components/primitives/text/Title";
|
||||
import { useCurrentOrganization } from "~/hooks/useOrganizations";
|
||||
import { useCurrentWorkflow } from "~/hooks/useWorkflows";
|
||||
import { getMostRecentWorkflowRun } from "~/models/workflowRun.server";
|
||||
import { getRuntimeEnvironmentFromRequest } from "~/models/runtimeEnvironment.server";
|
||||
import { WorkflowTestPresenter } from "~/presenters/testPresenter.server";
|
||||
import { requireUserId } from "~/services/session.server";
|
||||
|
||||
export const loader = async ({ request, params }: LoaderArgs) => {
|
||||
@@ -22,12 +23,14 @@ export const loader = async ({ request, params }: LoaderArgs) => {
|
||||
invariant(workflowSlug, "workflowSlug is required");
|
||||
invariant(organizationSlug, "organizationSlug is required");
|
||||
|
||||
const environmentSlug = await getRuntimeEnvironmentFromRequest(request);
|
||||
|
||||
try {
|
||||
const latestRun = await getMostRecentWorkflowRun({
|
||||
workflowSlug,
|
||||
organizationSlug,
|
||||
});
|
||||
return typedjson({ latestRun });
|
||||
const presenter = new WorkflowTestPresenter();
|
||||
|
||||
return typedjson(
|
||||
await presenter.data({ workflowSlug, organizationSlug, environmentSlug })
|
||||
);
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
throw new Response("Error ", { status: 400 });
|
||||
@@ -35,24 +38,26 @@ export const loader = async ({ request, params }: LoaderArgs) => {
|
||||
};
|
||||
|
||||
export default function Page() {
|
||||
const { latestRun } = useTypedLoaderData<typeof loader>();
|
||||
const { payload, status } = useTypedLoaderData<typeof loader>();
|
||||
|
||||
const organization = useCurrentOrganization();
|
||||
invariant(organization, "Organization not found");
|
||||
const workflow = useCurrentWorkflow();
|
||||
invariant(workflow, "Workflow not found");
|
||||
|
||||
console.log("Workflow status is", { status, payload });
|
||||
|
||||
return (
|
||||
<>
|
||||
<Title>Test</Title>
|
||||
{workflow.status === "CREATED" && (
|
||||
{status === "CREATED" && (
|
||||
<>
|
||||
<PanelWarning className="mb-6">
|
||||
This workflow requires its APIs to be connected before it can run.
|
||||
</PanelWarning>
|
||||
</>
|
||||
)}
|
||||
{workflow.status === "DISABLED" && (
|
||||
{status === "DISABLED" ? (
|
||||
<PanelInfo className="mb-6">
|
||||
<Body className="flex grow items-center justify-between">
|
||||
This workflow is disabled. Runs cannot be triggered or tested while
|
||||
@@ -62,21 +67,13 @@ export default function Page() {
|
||||
Settings
|
||||
</TertiaryLink>
|
||||
</PanelInfo>
|
||||
)}
|
||||
|
||||
{workflow.status === "READY" && (
|
||||
) : (
|
||||
<Panel className="mt-4">
|
||||
<Tester
|
||||
organizationSlug={organization.slug}
|
||||
workflowSlug={workflow.slug}
|
||||
eventNames={workflow.eventNames}
|
||||
initialValue={
|
||||
workflow.type === "SCHEDULE"
|
||||
? JSON.stringify({ scheduledTime: new Date() }, null, 2)
|
||||
: latestRun == null
|
||||
? "{\n\n}"
|
||||
: JSON.stringify(latestRun.event.payload, null, 2)
|
||||
}
|
||||
initialValue={JSON.stringify(payload, null, 2)}
|
||||
/>
|
||||
</Panel>
|
||||
)}
|
||||
|
||||
@@ -201,6 +201,7 @@ export class DispatchWorkflowRun {
|
||||
});
|
||||
|
||||
if (
|
||||
!workflowRun.isTest &&
|
||||
workflowRun.workflow.externalSource &&
|
||||
workflowRun.workflow.externalSource.status === "CREATED"
|
||||
) {
|
||||
|
||||
@@ -136,6 +136,12 @@ export class RegisterWorkflow {
|
||||
service: payload.trigger.service,
|
||||
eventNames: payload.trigger.name,
|
||||
triggerTtlInSeconds: payload.triggerTTL,
|
||||
jsonSchema:
|
||||
"schema" in payload.trigger
|
||||
? payload.trigger.schema
|
||||
? payload.trigger.schema
|
||||
: undefined
|
||||
: undefined,
|
||||
},
|
||||
create: {
|
||||
organizationId: organization.id,
|
||||
@@ -147,6 +153,12 @@ export class RegisterWorkflow {
|
||||
service: payload.trigger.service,
|
||||
eventNames: payload.trigger.name,
|
||||
triggerTtlInSeconds: payload.triggerTTL,
|
||||
jsonSchema:
|
||||
"schema" in payload.trigger
|
||||
? payload.trigger.schema
|
||||
? payload.trigger.schema
|
||||
: undefined
|
||||
: undefined,
|
||||
},
|
||||
include: {
|
||||
externalSource: true,
|
||||
|
||||
@@ -90,6 +90,7 @@
|
||||
"isbot": "^3.6.5",
|
||||
"javascript-time-ago": "^2.5.7",
|
||||
"json-query": "^2.2.2",
|
||||
"json-schema-faker": "0.5.0-rcv.46",
|
||||
"jsonata": "^1.8.6",
|
||||
"jsonschema": "^1.4.1",
|
||||
"keyv": "^4.3.2",
|
||||
@@ -126,7 +127,7 @@
|
||||
"zod-error": "^1.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@faker-js/faker": "^7.5.0",
|
||||
"@faker-js/faker": "^7.6.0",
|
||||
"@remix-run/dev": "^1.7.2",
|
||||
"@remix-run/eslint-config": "^1.7.2",
|
||||
"@swc/core": "^1.3.4",
|
||||
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Workflow" ADD COLUMN "jsonSchema" JSONB;
|
||||
@@ -120,6 +120,8 @@ model Workflow {
|
||||
|
||||
packageJson Json?
|
||||
|
||||
jsonSchema Json?
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import { z } from "zod";
|
||||
import { Trigger, customEvent, sendEvent } from "@trigger.dev/sdk";
|
||||
import {
|
||||
Trigger,
|
||||
customEvent,
|
||||
sendEvent,
|
||||
scheduleEvent,
|
||||
webhookEvent,
|
||||
} from "@trigger.dev/sdk";
|
||||
import { ulid } from "ulid";
|
||||
|
||||
const userCreatedEvent = z.object({
|
||||
@@ -70,3 +76,127 @@ new Trigger({
|
||||
await ctx.logger.error("This is an error");
|
||||
},
|
||||
}).listen();
|
||||
|
||||
export const bookingPayloadSchema = z.object({
|
||||
triggerEvent: z.string(),
|
||||
createdAt: z.coerce.date(),
|
||||
payload: z.object({
|
||||
type: z.string(),
|
||||
title: z.string(),
|
||||
description: z.string(),
|
||||
additionalNotes: z.string(),
|
||||
customInputs: z.object({}),
|
||||
startTime: z.coerce.date(),
|
||||
endTime: z.coerce.date(),
|
||||
organizer: z.object({
|
||||
id: z.number(),
|
||||
name: z.string(),
|
||||
email: z.string(),
|
||||
timeZone: z.string(),
|
||||
language: z.object({ locale: z.string() }),
|
||||
}),
|
||||
attendees: z.array(
|
||||
z.object({
|
||||
email: z.string(),
|
||||
name: z.string(),
|
||||
timeZone: z.string(),
|
||||
language: z.object({ locale: z.string() }),
|
||||
})
|
||||
),
|
||||
location: z.string(),
|
||||
destinationCalendar: z.object({
|
||||
id: z.number(),
|
||||
integration: z.string(),
|
||||
externalId: z.string(),
|
||||
userId: z.number(),
|
||||
eventTypeId: z.null(),
|
||||
credentialId: z.number(),
|
||||
}),
|
||||
hideCalendarNotes: z.boolean(),
|
||||
requiresConfirmation: z.null(),
|
||||
eventTypeId: z.number(),
|
||||
seatsShowAttendees: z.boolean(),
|
||||
uid: z.string(),
|
||||
conferenceData: z.object({
|
||||
createRequest: z.object({ requestId: z.string() }),
|
||||
}),
|
||||
videoCallData: z.object({
|
||||
type: z.string(),
|
||||
id: z.string(),
|
||||
password: z.string(),
|
||||
url: z.string(),
|
||||
}),
|
||||
appsStatus: z.array(
|
||||
z.object({
|
||||
appName: z.string(),
|
||||
type: z.string(),
|
||||
success: z.number(),
|
||||
failures: z.number(),
|
||||
errors: z.array(z.any()).optional(),
|
||||
warnings: z.array(z.any()).optional(),
|
||||
})
|
||||
),
|
||||
eventTitle: z.string(),
|
||||
eventDescription: z.null(),
|
||||
price: z.number(),
|
||||
currency: z.string(),
|
||||
length: z.number(),
|
||||
bookingId: z.number(),
|
||||
metadata: z.object({ videoCallUrl: z.string() }),
|
||||
status: z.string(),
|
||||
}),
|
||||
});
|
||||
|
||||
new Trigger({
|
||||
id: "calcom-booking-custom-event",
|
||||
name: "Cal.com booking custom event",
|
||||
apiKey: "trigger_dev_zC25mKNn6c0q",
|
||||
endpoint: "ws://localhost:8889/ws",
|
||||
logLevel: "debug",
|
||||
triggerTTL: 60 * 60 * 24,
|
||||
on: customEvent({ name: "calcom.booking", schema: bookingPayloadSchema }),
|
||||
run: async (event, ctx) => {
|
||||
return event;
|
||||
},
|
||||
}).listen();
|
||||
|
||||
new Trigger({
|
||||
id: "testing-schedule-test-events",
|
||||
name: "Testing scheduled test payloads",
|
||||
apiKey: "trigger_dev_zC25mKNn6c0q",
|
||||
endpoint: "ws://localhost:8889/ws",
|
||||
logLevel: "debug",
|
||||
triggerTTL: 60 * 60 * 24,
|
||||
on: scheduleEvent({
|
||||
rateOf: { hours: 1 },
|
||||
}),
|
||||
run: async (event, ctx) => {
|
||||
return event;
|
||||
},
|
||||
}).listen();
|
||||
|
||||
new Trigger({
|
||||
id: "smoke-test-webhook-schema-test",
|
||||
name: "Smoke Test Webhook Schema Test",
|
||||
apiKey: "trigger_dev_zC25mKNn6c0q",
|
||||
endpoint: "ws://localhost:8889/ws",
|
||||
logLevel: "debug",
|
||||
on: webhookEvent({
|
||||
service: "cal.com",
|
||||
eventName: "BOOKING_CREATED",
|
||||
filter: {
|
||||
triggerEvent: ["BOOKING_CREATED"],
|
||||
},
|
||||
schema: bookingPayloadSchema,
|
||||
verifyPayload: {
|
||||
enabled: true,
|
||||
header: "X-Cal-Signature-256",
|
||||
},
|
||||
}),
|
||||
run: async (event, ctx) => {
|
||||
await ctx.logger.info("Received a cal.com booking", {
|
||||
event,
|
||||
wallTime: new Date(),
|
||||
});
|
||||
},
|
||||
}).listen();
|
||||
|
||||
@@ -11,6 +11,7 @@ export const CustomEventTriggerSchema = z.object({
|
||||
service: z.literal("trigger"),
|
||||
name: z.string(),
|
||||
filter: EventFilterSchema,
|
||||
schema: JsonSchema.optional(),
|
||||
});
|
||||
export type CustomEventTrigger = z.infer<typeof CustomEventTriggerSchema>;
|
||||
|
||||
@@ -21,6 +22,7 @@ export const WebhookEventTriggerSchema = z.object({
|
||||
filter: EventFilterSchema,
|
||||
source: JsonSchema.optional(),
|
||||
manualRegistration: z.boolean().default(false),
|
||||
schema: JsonSchema.optional(),
|
||||
});
|
||||
export type WebhookEventTrigger = z.infer<typeof WebhookEventTriggerSchema>;
|
||||
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
"uuid": "^9.0.0",
|
||||
"ws": "^8.11.0",
|
||||
"zod": "^3.20.2",
|
||||
"zod-error": "^1.1.0"
|
||||
"zod-error": "^1.1.0",
|
||||
"zod-to-json-schema": "^3.20.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,10 +3,15 @@ import {
|
||||
TriggerMetadataSchema,
|
||||
ScheduleSourceSchema,
|
||||
ScheduledEventPayloadSchema,
|
||||
} from "@trigger.dev/common-schemas";
|
||||
import type {
|
||||
CustomEventTrigger,
|
||||
EventFilter,
|
||||
WebhookEventTrigger,
|
||||
} from "@trigger.dev/common-schemas";
|
||||
import { z } from "zod";
|
||||
import slugify from "slug";
|
||||
import zodToJsonSchema from "zod-to-json-schema";
|
||||
|
||||
export type EventRule = z.infer<typeof EventFilterSchema>;
|
||||
|
||||
@@ -30,6 +35,7 @@ export function customEvent<TSchema extends z.ZodTypeAny>(
|
||||
service: "trigger",
|
||||
name: options.name,
|
||||
filter: { event: [options.name], payload: options.filter ?? {} },
|
||||
schema: zodToJsonSchema(options.schema) as CustomEventTrigger["schema"],
|
||||
},
|
||||
schema: options.schema,
|
||||
};
|
||||
@@ -80,6 +86,7 @@ export function webhookEvent<TSchema extends z.ZodTypeAny>(
|
||||
event: options.eventName,
|
||||
},
|
||||
manualRegistration: true,
|
||||
schema: zodToJsonSchema(options.schema) as WebhookEventTrigger["schema"],
|
||||
},
|
||||
schema: options.schema,
|
||||
};
|
||||
|
||||
Generated
+37
-1
@@ -48,7 +48,7 @@ importers:
|
||||
'@codemirror/search': ^6.2.3
|
||||
'@codemirror/state': ^6.1.3
|
||||
'@codemirror/view': ^6.5.0
|
||||
'@faker-js/faker': ^7.5.0
|
||||
'@faker-js/faker': ^7.6.0
|
||||
'@headlessui/react': ^1.6.4
|
||||
'@heroicons/react': ^2.0.12
|
||||
'@jsonhero/fetch-hero': ^0.2.2
|
||||
@@ -136,6 +136,7 @@ importers:
|
||||
isbot: ^3.6.5
|
||||
javascript-time-ago: ^2.5.7
|
||||
json-query: ^2.2.2
|
||||
json-schema-faker: 0.5.0-rcv.46
|
||||
jsonata: ^1.8.6
|
||||
jsonschema: ^1.4.1
|
||||
keyv: ^4.3.2
|
||||
@@ -242,6 +243,7 @@ importers:
|
||||
isbot: 3.6.5
|
||||
javascript-time-ago: 2.5.9
|
||||
json-query: 2.2.2
|
||||
json-schema-faker: 0.5.0-rcv.46
|
||||
jsonata: 1.8.6
|
||||
jsonschema: 1.4.1
|
||||
keyv: 4.5.2
|
||||
@@ -966,6 +968,7 @@ importers:
|
||||
ws: ^8.11.0
|
||||
zod: ^3.20.2
|
||||
zod-error: ^1.1.0
|
||||
zod-to-json-schema: ^3.20.2
|
||||
dependencies:
|
||||
debug: 4.3.4
|
||||
evt: 2.4.13
|
||||
@@ -976,6 +979,7 @@ importers:
|
||||
ws: 8.12.0
|
||||
zod: 3.20.2
|
||||
zod-error: 1.1.0
|
||||
zod-to-json-schema: 3.20.2_zod@3.20.2
|
||||
devDependencies:
|
||||
'@trigger.dev/common-schemas': link:../common-schemas
|
||||
'@trigger.dev/tsconfig': link:../../config-packages/tsconfig
|
||||
@@ -9749,6 +9753,10 @@ packages:
|
||||
combined-stream: 1.0.8
|
||||
mime-types: 2.1.35
|
||||
|
||||
/format-util/1.0.5:
|
||||
resolution: {integrity: sha512-varLbTj0e0yVyRpqQhuWV+8hlePAgaoFRhNFj50BNjEIrw1/DphHSObtqwskVCPWNgzwPoQrZAbfa/SBiicNeg==}
|
||||
dev: false
|
||||
|
||||
/format/0.2.2:
|
||||
resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==}
|
||||
engines: {node: '>=0.4.x'}
|
||||
@@ -11298,6 +11306,23 @@ packages:
|
||||
resolution: {integrity: sha512-y+IcVZSdqNmS4fO8t1uZF6RMMs0xh3SrTjJr9bp1X3+v0Q13+7Cyv12dSmKwDswp/H427BVtpkLWhGxYu3ZWRA==}
|
||||
dev: false
|
||||
|
||||
/json-schema-faker/0.5.0-rcv.46:
|
||||
resolution: {integrity: sha512-Q+sGrxptZfezwm7M9W9VmHT9E8s5fWPCaRC4J2zUjb3CmDsxokiCBdHdS/psu91Tafc/ITv+GtIztGzUVT2zIg==}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
json-schema-ref-parser: 6.1.0
|
||||
jsonpath-plus: 5.1.0
|
||||
dev: false
|
||||
|
||||
/json-schema-ref-parser/6.1.0:
|
||||
resolution: {integrity: sha512-pXe9H1m6IgIpXmE5JSb8epilNTGsmTb2iPohAXpOdhqGFbQjNeHHsZxU+C8w6T81GZxSPFLeUoqDJmzxx5IGuw==}
|
||||
deprecated: Please switch to @apidevtools/json-schema-ref-parser
|
||||
dependencies:
|
||||
call-me-maybe: 1.0.2
|
||||
js-yaml: 3.14.1
|
||||
ono: 4.0.11
|
||||
dev: false
|
||||
|
||||
/json-schema-traverse/0.4.1:
|
||||
resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
|
||||
dev: true
|
||||
@@ -11349,6 +11374,11 @@ packages:
|
||||
graceful-fs: 4.2.10
|
||||
dev: true
|
||||
|
||||
/jsonpath-plus/5.1.0:
|
||||
resolution: {integrity: sha512-890w2Pjtj0iswAxalRlt2kHthi6HKrXEfZcn+ZNZptv7F3rUGIeDuZo+C+h4vXBHLEsVjJrHeCm35nYeZLzSBQ==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
dev: false
|
||||
|
||||
/jsonschema/1.4.1:
|
||||
resolution: {integrity: sha512-S6cATIPVv1z0IlxdN+zUk5EPjkGCdnhN4wVSBlvoUO1tOLJootbo9CquNJmbIh4yikWHiUedhRYrNPn1arpEmQ==}
|
||||
dev: false
|
||||
@@ -13101,6 +13131,12 @@ packages:
|
||||
dependencies:
|
||||
mimic-fn: 2.1.0
|
||||
|
||||
/ono/4.0.11:
|
||||
resolution: {integrity: sha512-jQ31cORBFE6td25deYeD80wxKBMj+zBmHTrVxnc6CKhx8gho6ipmWM5zj/oeoqioZ99yqBls9Z/9Nss7J26G2g==}
|
||||
dependencies:
|
||||
format-util: 1.0.5
|
||||
dev: false
|
||||
|
||||
/open/8.4.0:
|
||||
resolution: {integrity: sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
Reference in New Issue
Block a user