diff --git a/components/workflow/config/trigger-config.tsx b/components/workflow/config/trigger-config.tsx index ca8e6740..c1df4b7f 100644 --- a/components/workflow/config/trigger-config.tsx +++ b/components/workflow/config/trigger-config.tsx @@ -1,9 +1,15 @@ "use client"; -import { Clock, Copy, Play, Webhook } from "lucide-react"; +import { Clock, Copy, MoreVertical, Play, Webhook } from "lucide-react"; import { toast } from "sonner"; import { Button } from "@/components/ui/button"; import { CodeEditor } from "@/components/ui/code-editor"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { @@ -14,6 +20,7 @@ import { SelectValue, } from "@/components/ui/select"; import { TimezoneSelect } from "@/components/ui/timezone-select"; +import { inferSchemaFromJSON } from "../utils/json-parser"; import { SchemaBuilder, type SchemaField } from "./schema-builder"; type TriggerConfigProps = { @@ -40,6 +47,12 @@ export function TriggerConfig({ } }; + const handleInferSchema = (mockRequest: string) => { + const inferredSchema = inferSchemaFromJSON(mockRequest); + onUpdateConfig("webhookSchema", JSON.stringify(inferredSchema)); + toast.success("Schema inferred from mock payload"); + }; + return ( <>
@@ -118,7 +131,28 @@ export function TriggerConfig({

- +
+ + + + + + + { + handleInferSchema(config.webhookMockRequest as string); + }} + > + Infer Schema + + + +
; + +type ArrayStructure = { + type: "array"; + itemType: ValidFieldType | FieldsStructure; +}; + +type FieldsStructure = { + [key: string]: ValidFieldType | FieldsStructure | ArrayStructure; +}; + +const detectType = (value: unknown): ValidFieldType => { + if (value === null) { + return "string"; + } + if (Array.isArray(value)) { + return "array"; + } + const t = typeof value; + if (t === "object") { + return "object"; + } + if (t === "string") { + return "string"; + } + if (t === "number") { + return "number"; + } + if (t === "boolean") { + return "boolean"; + } + return "string"; +}; + +const processArray = (arr: unknown[]): ArrayStructure => { + if (arr.length === 0) { + return { type: "array", itemType: "string" }; + } + + const firstElement = arr[0]; + + if (Array.isArray(firstElement)) { + return { type: "array", itemType: "object" }; + } + + if ( + typeof firstElement === "object" && + firstElement !== null && + !Array.isArray(firstElement) + ) { + return { type: "array", itemType: extractFields(firstElement) }; + } + + const detectedType = detectType(firstElement); + if (detectedType === "array") { + return { type: "array", itemType: "object" }; + } + return { type: "array", itemType: detectedType }; +}; + +const extractFields = (obj: unknown): FieldsStructure => { + if (obj === null || typeof obj !== "object" || Array.isArray(obj)) { + return {}; + } + + const result: FieldsStructure = {}; + + for (const key in obj as Record) { + if (Object.hasOwn(obj, key)) { + const value = (obj as Record)[key]; + const valueType = detectType(value); + + if (valueType === "object") { + result[key] = extractFields(value); + } else if (valueType === "array") { + result[key] = processArray(value as unknown[]); + } else { + result[key] = valueType; + } + } + } + return result; +}; + +const createPrimitiveField = ( + key: string, + type: ValidFieldType +): SchemaField => ({ + id: nanoid(), + name: key, + type, +}); + +const createArrayField = ( + key: string, + arrayStructure: ArrayStructure +): SchemaField => { + const field: SchemaField = { + id: nanoid(), + name: key, + type: "array", + }; + + if (typeof arrayStructure.itemType === "string") { + if (arrayStructure.itemType === "array") { + field.itemType = "object"; + field.fields = []; + } else { + field.itemType = arrayStructure.itemType as ValidItemType; + } + } else if (typeof arrayStructure.itemType === "object") { + field.itemType = "object"; + field.fields = convertToSchemaFields(arrayStructure.itemType); + } + + return field; +}; + +const createObjectField = ( + key: string, + fieldsStructure: FieldsStructure +): SchemaField => ({ + id: nanoid(), + name: key, + type: "object", + fields: convertToSchemaFields(fieldsStructure), +}); + +const convertToSchemaFields = ( + fieldsStructure: FieldsStructure +): SchemaField[] => { + const result: SchemaField[] = []; + + for (const [key, value] of Object.entries(fieldsStructure)) { + if (typeof value === "string") { + result.push(createPrimitiveField(key, value)); + } else if (typeof value === "object" && value !== null) { + if ("type" in value && value.type === "array") { + result.push(createArrayField(key, value as ArrayStructure)); + } else { + result.push(createObjectField(key, value as FieldsStructure)); + } + } + } + + return result; +}; + +export const inferSchemaFromJSON = (jsonString: string): SchemaField[] => { + const parsed = JSON.parse(jsonString); + const fieldsStructure = extractFields(parsed); + return convertToSchemaFields(fieldsStructure); +};