feat: allow inserting webhook variables into custom payload template (#22835)
* feat: allow inserting webhook variables into custom payload template * localize UI and apply design system styling * add translations and minor UI fixes * add translations --------- Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>
This commit is contained in:
co-authored by
Carina Wollendorfer
parent
5a350efb40
commit
1fa8a5ef25
@@ -3441,6 +3441,35 @@
|
||||
"usage_based_expiration_description": "This link can be used for {{count}} booking",
|
||||
"usage_based_generic_expiration_description": "This link can be configured to expire after a set number of bookings",
|
||||
"usage_based_expiration_description_plural": "This link can be used for {{count}} bookings",
|
||||
"webhook_trigger_event": "The name of the trigger event (e.g., BOOKING_CREATED, BOOKING_CANCELLED)",
|
||||
"webhook_created_at": "The time of the webhook",
|
||||
"webhook_type": "The event type slug",
|
||||
"webhook_title": "The event type name",
|
||||
"webhook_start_time": "The event's start time",
|
||||
"webhook_end_time": "The event's end time",
|
||||
"webhook_description": "The event's description as described in the event type settings",
|
||||
"webhook_location": "Location of the event",
|
||||
"webhook_uid": "The UID of the booking",
|
||||
"webhook_reschedule_uid": "The UID for rescheduling",
|
||||
"webhook_cancellation_reason": "Reason for cancellation",
|
||||
"webhook_rejection_reason": "Reason for rejection",
|
||||
"webhook_organizer_name": "Name of the organizer",
|
||||
"webhook_organizer_email": "Email of the organizer",
|
||||
"webhook_organizer_timezone": "Timezone of the organizer (e.g., 'America/New_York', 'Asia/Kolkata')",
|
||||
"webhook_organizer_locale": "Locale of the organizer (e.g., 'en', 'fr')",
|
||||
"webhook_attendee_name": "Name of the first attendee",
|
||||
"webhook_attendee_email": "Email of the first attendee",
|
||||
"webhook_attendee_timezone": "Timezone of the first attendee",
|
||||
"webhook_attendee_locale": "Locale of the first attendee",
|
||||
"webhook_team_name": "Name of the team booked",
|
||||
"webhook_team_members": "Members of the team booked",
|
||||
"webhook_video_call_url": "Video call URL for the meeting",
|
||||
"webhook_hide_variables": "Hide variables",
|
||||
"webhook_show_variable": "Show available variables",
|
||||
"webhook_event_and_booking": "Event and Booking",
|
||||
"webhook_people": "People",
|
||||
"webhook_teams": "Teams",
|
||||
"webhook_metadata": "Metadata",
|
||||
"stats": "Stats",
|
||||
"booking_status": "Booking status",
|
||||
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Add your new strings above here ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
|
||||
|
||||
@@ -73,6 +73,154 @@ const WEBHOOK_TRIGGER_EVENTS_GROUPED_BY_APP_V2: Record<string, WebhookTriggerEve
|
||||
],
|
||||
} as const;
|
||||
|
||||
function getWebhookVariables(t: (key: string) => string) {
|
||||
return [
|
||||
{
|
||||
category: t("webhook_event_and_booking"),
|
||||
variables: [
|
||||
{
|
||||
name: "triggerEvent",
|
||||
variable: "{{triggerEvent}}",
|
||||
type: "String",
|
||||
description: t("webhook_trigger_event"),
|
||||
},
|
||||
{
|
||||
name: "createdAt",
|
||||
variable: "{{createdAt}}",
|
||||
type: "Datetime",
|
||||
description: t("webhook_created_at"),
|
||||
},
|
||||
{ name: "type", variable: "{{type}}", type: "String", description: t("webhook_type") },
|
||||
{ name: "title", variable: "{{title}}", type: "String", description: t("webhook_title") },
|
||||
{
|
||||
name: "startTime",
|
||||
variable: "{{startTime}}",
|
||||
type: "Datetime",
|
||||
description: t("webhook_start_time"),
|
||||
},
|
||||
{
|
||||
name: "endTime",
|
||||
variable: "{{endTime}}",
|
||||
type: "Datetime",
|
||||
description: t("webhook_end_time"),
|
||||
},
|
||||
{
|
||||
name: "description",
|
||||
variable: "{{description}}",
|
||||
type: "String",
|
||||
description: t("webhook_description"),
|
||||
},
|
||||
{
|
||||
name: "location",
|
||||
variable: "{{location}}",
|
||||
type: "String",
|
||||
description: t("webhook_location"),
|
||||
},
|
||||
{ name: "uid", variable: "{{uid}}", type: "String", description: t("webhook_uid") },
|
||||
{
|
||||
name: "rescheduleUid",
|
||||
variable: "{{rescheduleUid}}",
|
||||
type: "String",
|
||||
description: t("webhook_reschedule_uid"),
|
||||
},
|
||||
{
|
||||
name: "cancellationReason",
|
||||
variable: "{{cancellationReason}}",
|
||||
type: "String",
|
||||
description: t("webhook_cancellation_reason"),
|
||||
},
|
||||
{
|
||||
name: "rejectionReason",
|
||||
variable: "{{rejectionReason}}",
|
||||
type: "String",
|
||||
description: t("webhook_rejection_reason"),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
category: t("webhook_people"),
|
||||
variables: [
|
||||
{
|
||||
name: "organizer.name",
|
||||
variable: "{{organizer.name}}",
|
||||
type: "String",
|
||||
description: t("webhook_organizer_name"),
|
||||
},
|
||||
{
|
||||
name: "organizer.email",
|
||||
variable: "{{organizer.email}}",
|
||||
type: "String",
|
||||
description: t("webhook_organizer_email"),
|
||||
},
|
||||
{
|
||||
name: "organizer.timezone",
|
||||
variable: "{{organizer.timezone}}",
|
||||
type: "String",
|
||||
description: t("webhook_organizer_timezone"),
|
||||
},
|
||||
{
|
||||
name: "organizer.language.locale",
|
||||
variable: "{{organizer.language.locale}}",
|
||||
type: "String",
|
||||
description: t("webhook_organizer_locale"),
|
||||
},
|
||||
{
|
||||
name: "attendees.0.name",
|
||||
variable: "{{attendees.0.name}}",
|
||||
type: "String",
|
||||
description: t("webhook_attendee_name"),
|
||||
},
|
||||
{
|
||||
name: "attendees.0.email",
|
||||
variable: "{{attendees.0.email}}",
|
||||
type: "String",
|
||||
description: t("webhook_attendee_email"),
|
||||
},
|
||||
{
|
||||
name: "attendees.0.timezone",
|
||||
variable: "{{attendees.0.timezone}}",
|
||||
type: "String",
|
||||
description: t("webhook_attendee_timezone"),
|
||||
},
|
||||
{
|
||||
name: "attendees.0.language.locale",
|
||||
variable: "{{attendees.0.language.locale}}",
|
||||
type: "String",
|
||||
description: t("webhook_attendee_locale"),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
category: t("webhook_teams"),
|
||||
variables: [
|
||||
{
|
||||
name: "team.name",
|
||||
variable: "{{team.name}}",
|
||||
type: "String",
|
||||
description: t("webhook_team_name"),
|
||||
},
|
||||
{
|
||||
name: "team.members",
|
||||
variable: "{{team.members}}",
|
||||
type: "String[]",
|
||||
description: t("webhook_team_members"),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
category: t("webhook_metadata"),
|
||||
variables: [
|
||||
{
|
||||
name: "metadata.videoCallUrl",
|
||||
variable: "{{metadata.videoCallUrl}}",
|
||||
type: "String",
|
||||
description: t("webhook_video_call_url"),
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export type WebhookFormValues = {
|
||||
subscriberUrl: string;
|
||||
active: boolean;
|
||||
@@ -94,6 +242,7 @@ const WebhookForm = (props: {
|
||||
}) => {
|
||||
const { apps = [], selectOnlyInstantMeetingOption = false, overrideTriggerOptions } = props;
|
||||
const { t } = useLocale();
|
||||
const webhookVariables = getWebhookVariables(t);
|
||||
|
||||
const triggerOptions = overrideTriggerOptions
|
||||
? [...overrideTriggerOptions]
|
||||
@@ -141,6 +290,29 @@ const WebhookForm = (props: {
|
||||
const [useCustomTemplate, setUseCustomTemplate] = useState(
|
||||
props?.webhook?.payloadTemplate !== undefined && props?.webhook?.payloadTemplate !== null
|
||||
);
|
||||
|
||||
function insertVariableIntoTemplate(current: string, name: string, value: string): string {
|
||||
try {
|
||||
const parsed = JSON.parse(current || "{}");
|
||||
parsed[name] = value;
|
||||
return JSON.stringify(parsed, null, 2);
|
||||
} catch {
|
||||
const trimmed = current.trim();
|
||||
if (trimmed === "{}" || trimmed === "") {
|
||||
return `{\n "${name}": "${value}"\n}`;
|
||||
}
|
||||
|
||||
if (trimmed.endsWith("}")) {
|
||||
const withoutClosing = trimmed.slice(0, -1);
|
||||
const needsComma = withoutClosing.trim().endsWith('"') || withoutClosing.trim().endsWith("}");
|
||||
return `${withoutClosing}${needsComma ? "," : ""}\n "${name}": "${value}"\n}`;
|
||||
}
|
||||
|
||||
return `${current}\n"${name}": "${value}"`;
|
||||
}
|
||||
}
|
||||
|
||||
const [showVariables, setShowVariables] = useState(false);
|
||||
const [newSecret, setNewSecret] = useState("");
|
||||
const [changeSecret, setChangeSecret] = useState<boolean>(false);
|
||||
const hasSecretKey = !!props?.webhook?.secret;
|
||||
@@ -339,14 +511,52 @@ const WebhookForm = (props: {
|
||||
/>
|
||||
</div>
|
||||
{useCustomTemplate && (
|
||||
<TextArea
|
||||
name="customPayloadTemplate"
|
||||
rows={3}
|
||||
value={value}
|
||||
onChange={(e) => {
|
||||
formMethods.setValue("payloadTemplate", e?.target.value, { shouldDirty: true });
|
||||
}}
|
||||
/>
|
||||
<div className="space-y-3">
|
||||
<TextArea
|
||||
name="customPayloadTemplate"
|
||||
rows={8}
|
||||
value={value || ""}
|
||||
placeholder={`{\n\n}`}
|
||||
onChange={(e) =>
|
||||
formMethods.setValue("payloadTemplate", e?.target.value, { shouldDirty: true })
|
||||
}
|
||||
/>
|
||||
|
||||
<Button type="button" color="secondary" onClick={() => setShowVariables(!showVariables)}>
|
||||
{showVariables ? t("webhook_hide_variables") : t("webhook_show_variable")}
|
||||
</Button>
|
||||
|
||||
{showVariables && (
|
||||
<div className="border-muted max-h-80 overflow-y-auto rounded-md border p-3">
|
||||
{webhookVariables.map(({ category, variables }) => (
|
||||
<div key={category} className="mb-4">
|
||||
<h4 className="mb-2 text-sm font-medium">{category}</h4>
|
||||
<div className="space-y-2">
|
||||
{variables.map(({ name, variable, description }) => (
|
||||
<div
|
||||
key={name}
|
||||
className="hover:bg-muted cursor-pointer rounded p-2 text-sm transition-colors"
|
||||
onClick={() => {
|
||||
const currentValue = formMethods.getValues("payloadTemplate") || "{}";
|
||||
const updatedValue = insertVariableIntoTemplate(
|
||||
currentValue,
|
||||
name,
|
||||
variable
|
||||
);
|
||||
formMethods.setValue("payloadTemplate", updatedValue, {
|
||||
shouldDirty: true,
|
||||
});
|
||||
}}>
|
||||
<div className="text-emphasis font-mono">{variable}</div>
|
||||
<div className="text-muted mt-1 text-xs">{description}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user