-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Webhook payload dialog #722
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
254298a
5fa2b68
f5ff8be
5786fa1
f44cdcb
f601d29
ad8beeb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,164 @@ | ||
| "use client"; | ||
|
|
||
| import { useState } from "react"; | ||
| import { Copy, Check } from "lucide-react"; | ||
| import { Button } from "@/components/ui/button"; | ||
| import { | ||
| Dialog, | ||
| DialogContent, | ||
| DialogHeader, | ||
| DialogTitle, | ||
| DialogTrigger, | ||
| } from "@/components/ui/dialog"; | ||
|
|
||
| export function WebhookDocumentationDialog({ | ||
| children, | ||
| }: { | ||
| children: React.ReactNode; | ||
| }) { | ||
| return ( | ||
| <Dialog> | ||
| <DialogTrigger asChild>{children}</DialogTrigger> | ||
| <DialogContent className="max-w-2xl max-h-[80vh] overflow-y-auto"> | ||
| <DialogHeader> | ||
| <DialogTitle>Webhook Payload</DialogTitle> | ||
| </DialogHeader> | ||
| <WebhookPayloadDocumentation /> | ||
| </DialogContent> | ||
| </Dialog> | ||
| ); | ||
| } | ||
|
|
||
| export function WebhookPayloadDocumentation() { | ||
| const [copied, setCopied] = useState(false); | ||
|
|
||
| const copyToClipboard = async (text: string) => { | ||
| try { | ||
| await navigator.clipboard.writeText(text); | ||
| setCopied(true); | ||
| setTimeout(() => setCopied(false), 2000); | ||
| } catch (err) { | ||
| console.error("Failed to copy text: ", err); | ||
| } | ||
| }; | ||
|
|
||
| const payloadExample = { | ||
| email: { | ||
| threadId: "thread_abc123", | ||
| messageId: "message_xyz789", | ||
| subject: "Important Contract Document", | ||
| from: "client@company.com", | ||
| cc: "team@company.com", | ||
| bcc: "archive@company.com", | ||
| headerMessageId: "<CAF=4sK9...@mail.gmail.com>", | ||
| }, | ||
| executedRule: { | ||
| id: "exec_rule_123", | ||
| ruleId: "rule_456", | ||
| reason: "Email matched rule: Archive contracts", | ||
| automated: true, | ||
| createdAt: "2024-01-15T10:30:00.000Z", | ||
| }, | ||
| }; | ||
|
|
||
| const payloadJson = JSON.stringify(payloadExample, null, 2); | ||
|
|
||
| return ( | ||
| <div className="space-y-4"> | ||
| <div className="text-sm text-muted-foreground"> | ||
| When a rule with a webhook action is triggered, we'll send a POST | ||
| request to your URL with the following payload: | ||
| </div> | ||
|
|
||
| <div className="space-y-4"> | ||
| <div className="flex items-center justify-between"> | ||
| <h4 className="font-medium">Webhook Payload Structure</h4> | ||
| <Button | ||
| variant="ghost" | ||
| size="sm" | ||
| onClick={() => copyToClipboard(payloadJson)} | ||
| > | ||
| {copied ? ( | ||
| <Check className="h-4 w-4" /> | ||
| ) : ( | ||
| <Copy className="h-4 w-4" /> | ||
| )} | ||
| </Button> | ||
| </div> | ||
|
|
||
| <pre className="bg-muted p-4 rounded-md text-sm overflow-x-auto"> | ||
| <code>{payloadJson}</code> | ||
| </pre> | ||
|
|
||
| <div className="grid gap-4 md:grid-cols-2"> | ||
| <div> | ||
| <h5 className="font-medium mb-2">Email Fields</h5> | ||
| <div className="space-y-1 text-sm text-muted-foreground"> | ||
| <div> | ||
| <code>threadId</code> - Gmail/Outlook thread ID | ||
| </div> | ||
| <div> | ||
| <code>messageId</code> - Unique message ID | ||
| </div> | ||
| <div> | ||
| <code>subject</code> - Email subject line | ||
| </div> | ||
| <div> | ||
| <code>from</code> - Sender's email address | ||
| </div> | ||
| <div> | ||
| <code>cc/bcc</code> - Optional CC/BCC recipients | ||
| </div> | ||
| <div> | ||
| <code>headerMessageId</code> - Email Message-ID header | ||
| </div> | ||
| </div> | ||
| </div> | ||
|
|
||
| <div> | ||
| <h5 className="font-medium mb-2">Rule Execution Fields</h5> | ||
| <div className="space-y-1 text-sm text-muted-foreground"> | ||
| <div> | ||
| <code>id</code> - Execution ID | ||
| </div> | ||
| <div> | ||
| <code>ruleId</code> - Rule that was triggered | ||
| </div> | ||
| <div> | ||
| <code>reason</code> - Why the rule was triggered | ||
| </div> | ||
| <div> | ||
| <code>automated</code> - Whether rule ran automatically | ||
| </div> | ||
| <div> | ||
| <code>createdAt</code> - When the rule was executed (ISO 8601) | ||
| </div> | ||
| </div> | ||
| </div> | ||
| </div> | ||
|
|
||
| <div className="p-3 bg-blue-50 dark:bg-blue-950/30 rounded-md"> | ||
| <div className="text-sm text-blue-600 dark:text-blue-400"> | ||
| <strong>Authentication:</strong> Each request includes an{" "} | ||
| <code>X-Webhook-Secret</code> header with your webhook secret for | ||
| verification. | ||
| </div> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| export function WebhookDocumentationLink() { | ||
| return ( | ||
| <WebhookDocumentationDialog> | ||
| <Button | ||
| variant="link" | ||
| size="xs" | ||
| className="h-auto p-0 text-xs text-blue-600 hover:text-blue-800" | ||
| > | ||
| View payload structure | ||
| </Button> | ||
| </WebhookDocumentationDialog> | ||
| ); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -22,6 +22,7 @@ export const actionInputs: Record< | |
| label: string; | ||
| textArea?: boolean; | ||
| expandable?: boolean; | ||
| placeholder?: string; | ||
| }[]; | ||
|
Comment on lines
+25
to
26
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chainVerify placeholder is actually rendered and URL is validated. 🏁 Script executed: #!/bin/bash
# 1) Confirm placeholder is wired into RuleForm inputs
rg -nP -C3 'placeholder\s*=' apps/web/app/\(app\)/\[emailAccountId]/assistant/RuleForm.tsx
# 2) Locate rule.validation and check CALL_WEBHOOK url uses z.string().url()
fd -t f 'rule.validation.*\.ts' apps/web | xargs -I {} rg -nP -C3 'CALL_WEBHOOK|url\s*:\s*z\.string\(\)\.url\(\)' {}Length of output: 2072 Enforce URL format in Zod schema for CALL_WEBHOOK 🤖 Prompt for AI Agents |
||
| } | ||
| > = { | ||
|
|
@@ -139,6 +140,7 @@ export const actionInputs: Record< | |
| { | ||
| name: "url", | ||
| label: "URL", | ||
| placeholder: "https://example.com/webhook", | ||
| }, | ||
| ], | ||
| }, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1 @@ | ||
| v2.6.12 | ||
| v2.6.13 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove console usage; add clipboard fallback + user feedback
Project guideline forbids console usage. Add a graceful fallback when navigator.clipboard is unavailable/blocked and surface an error toast.
Apply this diff:
import { useState } from "react"; import { Copy, Check } from "lucide-react"; import { Button } from "@/components/ui/button"; +import { toastError } from "@/components/Toast"; @@ const copyToClipboard = async (text: string) => { try { await navigator.clipboard.writeText(text); setCopied(true); setTimeout(() => setCopied(false), 2000); - } catch (err) { - console.error("Failed to copy text: ", err); + } catch { + // Fallback for insecure contexts or denied permissions + try { + const el = document.createElement("textarea"); + el.value = text; + el.setAttribute("readonly", ""); + el.style.position = "absolute"; + el.style.left = "-9999px"; + document.body.appendChild(el); + el.select(); + document.execCommand("copy"); + document.body.removeChild(el); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + } catch { + toastError({ description: "Failed to copy to clipboard." }); + } } };📝 Committable suggestion
🤖 Prompt for AI Agents