Improves Zapier response and payload (#3892)
* improve listBookings API call * improve payload for zapier call * use more readable location for integrations * remove cancellation reason * convert location to readbale string * code clean up * code clean up * updated expected webhook response for e2e tests * fix failing e2e * revert webhook response Co-authored-by: CarinaWolli <wollencarina@gmail.com> Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
This commit is contained in:
co-authored by
CarinaWolli
kodiakhq[bot]
parent
0b8eb3b8ec
commit
bb24539731
@@ -2,10 +2,55 @@ import { Webhook } from "@prisma/client";
|
||||
import { createHmac } from "crypto";
|
||||
import { compile } from "handlebars";
|
||||
|
||||
import { getHumanReadableLocationValue } from "@calcom/app-store/locations";
|
||||
import type { CalendarEvent } from "@calcom/types/Calendar";
|
||||
|
||||
import { getTranslation } from "@server/lib/i18n";
|
||||
|
||||
type ContentType = "application/json" | "application/x-www-form-urlencoded";
|
||||
|
||||
export type EventTypeInfo = {
|
||||
eventTitle?: string | null;
|
||||
eventDescription?: string | null;
|
||||
requiresConfirmation?: boolean | null;
|
||||
price?: number | null;
|
||||
currency?: string | null;
|
||||
length?: number | null;
|
||||
};
|
||||
|
||||
function getZapierPayload(data: CalendarEvent & EventTypeInfo & { status?: string }): string {
|
||||
const attendees = data.attendees.map((attendee) => {
|
||||
return {
|
||||
name: attendee.name,
|
||||
email: attendee.email,
|
||||
timeZone: attendee.timeZone,
|
||||
};
|
||||
});
|
||||
|
||||
const t = data.organizer.language.translate;
|
||||
const location = getHumanReadableLocationValue(data.location || "", t);
|
||||
|
||||
const body = {
|
||||
title: data.title,
|
||||
description: data.description,
|
||||
customInputs: data.customInputs,
|
||||
startTime: data.startTime,
|
||||
endTime: data.endTime,
|
||||
location: location,
|
||||
status: data.status,
|
||||
eventType: {
|
||||
title: data.eventTitle,
|
||||
description: data.eventDescription,
|
||||
requiresConfirmation: data.requiresConfirmation,
|
||||
price: data.price,
|
||||
currency: data.currency,
|
||||
length: data.length,
|
||||
},
|
||||
attendees: attendees,
|
||||
};
|
||||
return JSON.stringify(body);
|
||||
}
|
||||
|
||||
function applyTemplate(template: string, data: CalendarEvent, contentType: ContentType) {
|
||||
const compiled = compile(template)(data);
|
||||
if (contentType === "application/json") {
|
||||
@@ -28,11 +73,13 @@ const sendPayload = async (
|
||||
triggerEvent: string,
|
||||
createdAt: string,
|
||||
webhook: Pick<Webhook, "subscriberUrl" | "appId" | "payloadTemplate">,
|
||||
data: CalendarEvent & {
|
||||
metadata?: { [key: string]: string };
|
||||
rescheduleUid?: string;
|
||||
bookingId?: number;
|
||||
}
|
||||
data: CalendarEvent &
|
||||
EventTypeInfo & {
|
||||
metadata?: { [key: string]: string };
|
||||
rescheduleUid?: string;
|
||||
bookingId?: number;
|
||||
status?: string;
|
||||
}
|
||||
) => {
|
||||
const { appId, payloadTemplate: template } = webhook;
|
||||
|
||||
@@ -45,7 +92,7 @@ const sendPayload = async (
|
||||
|
||||
/* Zapier id is hardcoded in the DB, we send the raw data for this case */
|
||||
if (appId === "zapier") {
|
||||
body = JSON.stringify(data);
|
||||
body = getZapierPayload(data);
|
||||
} else if (template) {
|
||||
body = applyTemplate(template, data, contentType);
|
||||
} else {
|
||||
|
||||
@@ -37,7 +37,7 @@ import type { EventResult, PartialReference } from "@calcom/types/EventManager";
|
||||
|
||||
import { getSession } from "@lib/auth";
|
||||
import { HttpError } from "@lib/core/http/error";
|
||||
import sendPayload from "@lib/webhooks/sendPayload";
|
||||
import sendPayload, { EventTypeInfo } from "@lib/webhooks/sendPayload";
|
||||
|
||||
import { getTranslation } from "@server/lib/i18n";
|
||||
|
||||
@@ -845,13 +845,25 @@ async function handler(req: NextApiRequest) {
|
||||
metadata: reqBody.metadata,
|
||||
});
|
||||
const bookingId = booking?.id;
|
||||
|
||||
const eventTypeInfo: EventTypeInfo = {
|
||||
eventTitle: eventType.title,
|
||||
eventDescription: eventType.description,
|
||||
requiresConfirmation: eventType.requiresConfirmation || null,
|
||||
price: eventType.price,
|
||||
currency: eventType.currency,
|
||||
length: eventType.length,
|
||||
};
|
||||
|
||||
const promises = subscribers.map((sub) =>
|
||||
sendPayload(sub.secret, eventTrigger, new Date().toISOString(), sub, {
|
||||
...evt,
|
||||
...eventTypeInfo,
|
||||
bookingId,
|
||||
rescheduleUid,
|
||||
metadata: reqBody.metadata,
|
||||
eventTypeId,
|
||||
status: eventType.requiresConfirmation ? "PENDING" : "ACCEPTED",
|
||||
}).catch((e) => {
|
||||
console.error(`Error executing webhook for event: ${eventTrigger}, URL: ${sub.subscriberUrl}`, e);
|
||||
})
|
||||
|
||||
@@ -27,7 +27,7 @@ import prisma, { bookingMinimalSelect } from "@calcom/prisma";
|
||||
import type { CalendarEvent } from "@calcom/types/Calendar";
|
||||
|
||||
import { getSession } from "@lib/auth";
|
||||
import sendPayload from "@lib/webhooks/sendPayload";
|
||||
import sendPayload, { EventTypeInfo } from "@lib/webhooks/sendPayload";
|
||||
import getWebhooks from "@lib/webhooks/subscriptions";
|
||||
|
||||
import { getTranslation } from "@server/lib/i18n";
|
||||
@@ -75,6 +75,11 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
select: {
|
||||
recurringEvent: true,
|
||||
title: true,
|
||||
description: true,
|
||||
requiresConfirmation: true,
|
||||
price: true,
|
||||
currency: true,
|
||||
length: true,
|
||||
workflows: {
|
||||
include: {
|
||||
workflow: {
|
||||
@@ -165,9 +170,23 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
eventTypeId: (bookingToDelete.eventTypeId as number) || 0,
|
||||
triggerEvent: eventTrigger,
|
||||
};
|
||||
|
||||
const eventTypeInfo: EventTypeInfo = {
|
||||
eventTitle: bookingToDelete?.eventType?.title || null,
|
||||
eventDescription: bookingToDelete?.eventType?.description || null,
|
||||
requiresConfirmation: bookingToDelete?.eventType?.requiresConfirmation || null,
|
||||
price: bookingToDelete?.eventType?.price || null,
|
||||
currency: bookingToDelete?.eventType?.currency || null,
|
||||
length: bookingToDelete?.eventType?.length || null,
|
||||
};
|
||||
|
||||
const webhooks = await getWebhooks(subscriberOptions);
|
||||
const promises = webhooks.map((webhook) =>
|
||||
sendPayload(webhook.secret, eventTrigger, new Date().toISOString(), webhook, evt).catch((e) => {
|
||||
sendPayload(webhook.secret, eventTrigger, new Date().toISOString(), webhook, {
|
||||
...evt,
|
||||
...eventTypeInfo,
|
||||
status: "CANCELLED",
|
||||
}).catch((e) => {
|
||||
console.error(`Error executing webhook for event: ${eventTrigger}, URL: ${webhook.subscriberUrl}`, e);
|
||||
})
|
||||
);
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"triggerEvent":"BOOKING_CREATED","createdAt":"[redacted/dynamic]","payload":{"type":"30 min","title":"30 min between PRO and Test Testson","description":"","additionalNotes":"","customInputs":{},"startTime":"[redacted/dynamic]","endTime":"[redacted/dynamic]","organizer":{"name":"PRO","email":"[redacted/dynamic]","timeZone":"[redacted/dynamic]","language":"[redacted/dynamic]"},"attendees":[{"email":"test@example.com","name":"Test Testson","timeZone":"[redacted/dynamic]","language":"[redacted/dynamic]"}],"location":"[redacted/dynamic]","destinationCalendar":null,"hideCalendarNotes":false,"requiresConfirmation":"[redacted/dynamic]","eventTypeId":"[redacted/dynamic]","uid":"[redacted/dynamic]","bookingId":"[redacted/dynamic]","metadata":{},"additionalInformation":"[redacted/dynamic]"}}
|
||||
{"triggerEvent":"BOOKING_CREATED","createdAt":"[redacted/dynamic]","payload":{"type":"30 min","title":"30 min between PRO and Test Testson","description":"","additionalNotes":"","customInputs":{},"startTime":"[redacted/dynamic]","endTime":"[redacted/dynamic]","organizer":{"name":"PRO","email":"[redacted/dynamic]","timeZone":"[redacted/dynamic]","language":"[redacted/dynamic]"},"attendees":[{"email":"test@example.com","name":"Test Testson","timeZone":"[redacted/dynamic]","language":"[redacted/dynamic]"}],"location":"[redacted/dynamic]","destinationCalendar":null,"hideCalendarNotes":false,"requiresConfirmation":"[redacted/dynamic]","eventTypeId":"[redacted/dynamic]","uid":"[redacted/dynamic]","bookingId":"[redacted/dynamic]","metadata":{},"additionalInformation":"[redacted/dynamic]"}}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
import { getHumanReadableLocationValue } from "@calcom/core/location";
|
||||
import findValidApiKey from "@calcom/features/ee/api-keys/lib/findValidApiKey";
|
||||
import { defaultHandler, defaultResponder } from "@calcom/lib/server";
|
||||
import prisma, { bookingMinimalSelect } from "@calcom/prisma";
|
||||
import { defaultHandler, defaultResponder, getTranslation } from "@calcom/lib/server";
|
||||
import prisma from "@calcom/prisma";
|
||||
|
||||
async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
const apiKey = req.query.apiKey as string;
|
||||
@@ -24,8 +25,25 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
userId: validKey.userId,
|
||||
},
|
||||
select: {
|
||||
...bookingMinimalSelect,
|
||||
title: true,
|
||||
description: true,
|
||||
customInputs: true,
|
||||
startTime: true,
|
||||
endTime: true,
|
||||
location: true,
|
||||
cancellationReason: true,
|
||||
status: true,
|
||||
user: true,
|
||||
eventType: {
|
||||
select: {
|
||||
title: true,
|
||||
description: true,
|
||||
requiresConfirmation: true,
|
||||
price: true,
|
||||
currency: true,
|
||||
length: true,
|
||||
},
|
||||
},
|
||||
attendees: {
|
||||
select: {
|
||||
name: true,
|
||||
@@ -36,7 +54,13 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
},
|
||||
});
|
||||
|
||||
res.status(201).json(bookings);
|
||||
const t = await getTranslation(bookings[0].user?.locale ?? "en", "common");
|
||||
|
||||
const updatedBookings = bookings.map((booking) => {
|
||||
return { ...booking, location: getHumanReadableLocationValue(booking.location || "", t) };
|
||||
});
|
||||
|
||||
res.status(201).json(updatedBookings);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return res.status(500).json({ message: "Unable to get bookings." });
|
||||
|
||||
Reference in New Issue
Block a user