import { useEffect, useState } from "react"; import { ReloadIcon, CopyIcon, CheckIcon } from "@radix-ui/react-icons"; import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger, } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Collapsible, CollapsibleContent, CollapsibleTrigger, } from "@/components/ui/collapsible"; import { CodeEditor } from "@/routes/workflows/components/CodeEditor"; import { getClient } from "@/api/AxiosClient"; import { useCredentialGetter } from "@/hooks/useCredentialGetter"; import { toast } from "@/components/ui/use-toast"; import { copyText } from "@/util/copyText"; type TestWebhookRequest = { webhook_url: string; run_type: "task" | "workflow_run"; run_id: string | null; }; type TestWebhookResponse = { status_code: number | null; latency_ms: number; response_body: string; headers_sent: Record; error: string | null; }; type TestWebhookDialogProps = { runType: "task" | "workflow_run"; runId?: string | null; initialWebhookUrl?: string; trigger?: React.ReactNode; }; function TestWebhookDialog({ runType, runId, initialWebhookUrl, trigger, }: TestWebhookDialogProps) { const [open, setOpen] = useState(false); const [targetUrl, setTargetUrl] = useState(initialWebhookUrl || ""); const [loading, setLoading] = useState(false); const [result, setResult] = useState(null); const [signatureOpen, setSignatureOpen] = useState(false); const [responseOpen, setResponseOpen] = useState(false); const [copiedResponse, setCopiedResponse] = useState(false); const credentialGetter = useCredentialGetter(); const runTest = async (url: string) => { setTargetUrl(url); if (!url.trim()) { toast({ variant: "destructive", title: "Error", description: "Enter a webhook URL before testing.", }); setOpen(false); return; } setLoading(true); setResult(null); setSignatureOpen(false); setResponseOpen(false); setCopiedResponse(false); try { const client = await getClient(credentialGetter); const response = await client.post( "/internal/test-webhook", { webhook_url: url, run_type: runType, run_id: runId ?? null, } satisfies TestWebhookRequest, ); setResult(response.data); if (response.data.error) { toast({ variant: "destructive", title: "Webhook Test Failed", description: response.data.error, }); } else if ( response.data.status_code && response.data.status_code >= 200 && response.data.status_code < 300 ) { toast({ variant: "success", title: "Webhook Test Successful", description: `Received ${response.data.status_code} response in ${response.data.latency_ms}ms`, }); } else if (response.data.status_code) { toast({ variant: "destructive", title: "Webhook Test Failed", description: `Received ${response.data.status_code} response`, }); } } catch (error) { toast({ variant: "destructive", title: "Error", description: error instanceof Error ? error.message : "Failed to test webhook", }); } finally { setLoading(false); } }; useEffect(() => { if (!open) { return; } const nextUrl = initialWebhookUrl || ""; setTargetUrl(nextUrl); void runTest(nextUrl); // eslint-disable-next-line react-hooks/exhaustive-deps }, [open, initialWebhookUrl]); const handleCopyResponse = async () => { if (!result?.response_body) { return; } try { await copyText(result.response_body); setCopiedResponse(true); setTimeout(() => setCopiedResponse(false), 2000); } catch (error) { toast({ variant: "destructive", title: "Failed to copy response", description: error instanceof Error ? error.message : "Clipboard permissions are required.", }); } }; const getStatusBadgeClass = (statusCode: number | null) => { if (!statusCode) return "bg-slate-500"; if (statusCode >= 200 && statusCode < 300) return "bg-green-600"; if (statusCode >= 400 && statusCode < 500) return "bg-orange-600"; if (statusCode >= 500) return "bg-red-600"; return "bg-blue-600"; }; return ( {trigger || ( )} Test Webhook URL
setTargetUrl(event.target.value)} placeholder="https://your-endpoint.com/webhook" />
{loading && !result ? (
Sending test webhook…
) : null} {result && (
{result.error ? (

Error

{result.error}

) : ( <>
{result.status_code || "N/A"}

{result.latency_ms}ms

{Object.entries(result.headers_sent).map( ([key, value]) => (
{key}: {" "} {value}
), )}
)}
)}
); } export { TestWebhookDialog };