Save meeting url to metadata of bookings and add it to webhook payload (#5773)
* small design fix for time unit select * save video cal url to booking in metaData * save video url when bookings are confirmed * fix e2e tests * remove not needed waitForNavigation * add metadata to booking created webhook payload * add migration * fix type error * adjust e2e tests * add bookingMetadataSchema * add missing change from merge * add new line to expected test result * update snapshot * fix e2e tests * Update confirm.ts Co-authored-by: CarinaWolli <wollencarina@gmail.com> Co-authored-by: zomars <zomars@me.com>
This commit is contained in:
9 files changed
+36
-12
No files matched your search
@@ -67,6 +67,7 @@ test("add webhook & test that creating an event triggers a webhook call", async
|
||||
body.payload.eventTypeId = dynamic;
|
||||
body.payload.videoCallData = dynamic;
|
||||
body.payload.appsStatus = dynamic;
|
||||
body.payload.metadata.videoCallUrl = dynamic;
|
||||
|
||||
// if we change the shape of our webhooks, we can simply update this by clicking `u`
|
||||
// console.log("BODY", body);
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"triggerEvent":"BOOKING_CREATED","createdAt":"[redacted/dynamic]","payload":{"type":"30 min","title":"30 min between Nameless and Test Testson","description":"","additionalNotes":"","customInputs":{},"startTime":"[redacted/dynamic]","endTime":"[redacted/dynamic]","organizer":{"name":"Nameless","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]","seatsShowAttendees":false,"uid":"[redacted/dynamic]","videoCallData":"[redacted/dynamic]","appsStatus":"[redacted/dynamic]","eventTitle":"30 min","eventDescription":null,"price":0,"currency":"usd","length":30,"bookingId":"[redacted/dynamic]","metadata":{},"status":"ACCEPTED","additionalInformation":"[redacted/dynamic]"}}
|
||||
{"triggerEvent":"BOOKING_CREATED","createdAt":"[redacted/dynamic]","payload":{"type":"30 min","title":"30 min between Nameless and Test Testson","description":"","additionalNotes":"","customInputs":{},"startTime":"[redacted/dynamic]","endTime":"[redacted/dynamic]","organizer":{"name":"Nameless","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]","seatsShowAttendees":false,"uid":"[redacted/dynamic]","videoCallData":"[redacted/dynamic]","appsStatus":"[redacted/dynamic]","eventTitle":"30 min","eventDescription":null,"price":0,"currency":"usd","length":30,"bookingId":"[redacted/dynamic]","metadata":{"videoCallUrl":"[redacted/dynamic]"},"status":"ACCEPTED","additionalInformation":"[redacted/dynamic]"}}
|
||||
@@ -835,6 +835,8 @@ async function handler(req: NextApiRequest & { userId?: number | undefined }) {
|
||||
evt.appsStatus = Object.values(calcAppsStatus);
|
||||
}
|
||||
|
||||
let videoCallUrl;
|
||||
|
||||
if (originalRescheduledBooking?.uid) {
|
||||
// Use EventManager to conditionally use all needed integrations.
|
||||
const updateManager = await eventManager.reschedule(
|
||||
@@ -869,9 +871,9 @@ async function handler(req: NextApiRequest & { userId?: number | undefined }) {
|
||||
metadata.conferenceData = updatedEvent.conferenceData;
|
||||
metadata.entryPoints = updatedEvent.entryPoints;
|
||||
handleAppsStatus(results, booking);
|
||||
videoCallUrl = metadata.hangoutLink || videoCallUrl;
|
||||
}
|
||||
}
|
||||
|
||||
if (noEmail !== true) {
|
||||
await sendRescheduledEmails({
|
||||
...evt,
|
||||
@@ -893,6 +895,9 @@ async function handler(req: NextApiRequest & { userId?: number | undefined }) {
|
||||
|
||||
results = createManager.results;
|
||||
referencesToCreate = createManager.referencesToCreate;
|
||||
|
||||
videoCallUrl = evt.videoCallData && evt.videoCallData.url ? evt.videoCallData.url : null;
|
||||
|
||||
if (results.length > 0 && results.every((res) => !res.success)) {
|
||||
const error = {
|
||||
errorCode: "BookingCreatingMeetingFailed",
|
||||
@@ -909,6 +914,7 @@ async function handler(req: NextApiRequest & { userId?: number | undefined }) {
|
||||
metadata.conferenceData = results[0].createdEvent?.conferenceData;
|
||||
metadata.entryPoints = results[0].createdEvent?.entryPoints;
|
||||
handleAppsStatus(results, booking);
|
||||
videoCallUrl = metadata.hangoutLink || videoCallUrl;
|
||||
}
|
||||
if (noEmail !== true) {
|
||||
await sendScheduledEmails({
|
||||
@@ -945,7 +951,7 @@ async function handler(req: NextApiRequest & { userId?: number | undefined }) {
|
||||
}
|
||||
|
||||
log.debug(`Booking ${organizerUser.username} completed`);
|
||||
|
||||
const metadata = videoCallUrl ? { videoCallUrl } : undefined;
|
||||
if (isConfirmedByDefault) {
|
||||
const eventTrigger: WebhookTriggerEvents = rescheduleUid
|
||||
? WebhookTriggerEvents.BOOKING_RESCHEDULED
|
||||
@@ -1001,7 +1007,7 @@ async function handler(req: NextApiRequest & { userId?: number | undefined }) {
|
||||
...eventTypeInfo,
|
||||
bookingId,
|
||||
rescheduleUid,
|
||||
metadata: reqBody.metadata,
|
||||
metadata: { ...metadata, ...reqBody.metadata },
|
||||
eventTypeId,
|
||||
status: "ACCEPTED",
|
||||
smsReminderNumber: booking?.smsReminderNumber || undefined,
|
||||
@@ -1043,6 +1049,7 @@ async function handler(req: NextApiRequest & { userId?: number | undefined }) {
|
||||
uid: booking.uid,
|
||||
},
|
||||
data: {
|
||||
metadata,
|
||||
references: {
|
||||
createMany: {
|
||||
data: referencesToCreate,
|
||||
|
||||
@@ -42,7 +42,7 @@ export const TimeTimeUnitInput = (props: Props) => {
|
||||
<DropdownMenuTrigger asChild>
|
||||
<button className="-ml-1 h-9 w-24 rounded-none rounded-r-md border border-gray-300 bg-gray-50 px-3 py-1 text-sm">
|
||||
<div className="flex">
|
||||
<div className="w-3/4">
|
||||
<div className="mr-1 w-3/4">
|
||||
{timeUnit ? t(`${timeUnit.toLowerCase()}_timeUnit`) : "undefined"}{" "}
|
||||
</div>
|
||||
<div className="w-1/4 pt-1">
|
||||
|
||||
@@ -193,6 +193,7 @@ export async function processBookingConfirmation(
|
||||
// count changed, parsing again to get the new value in
|
||||
evt.recurringEvent = parseRecurringEvent(recurringEvent);
|
||||
}
|
||||
let videoCallUrl;
|
||||
|
||||
if (confirmed) {
|
||||
const eventManager = new EventManager(user);
|
||||
@@ -200,6 +201,8 @@ export async function processBookingConfirmation(
|
||||
|
||||
const results = scheduleResult.results;
|
||||
|
||||
videoCallUrl = evt.videoCallData && evt.videoCallData.url ? evt.videoCallData.url : null;
|
||||
|
||||
if (results.length > 0 && results.every((res) => !res.success)) {
|
||||
const error = {
|
||||
errorCode: "BookingCreatingMeetingFailed",
|
||||
@@ -215,6 +218,7 @@ export async function processBookingConfirmation(
|
||||
metadata.hangoutLink = results[0].createdEvent?.hangoutLink;
|
||||
metadata.conferenceData = results[0].createdEvent?.conferenceData;
|
||||
metadata.entryPoints = results[0].createdEvent?.entryPoints;
|
||||
videoCallUrl = metadata.hangoutLink || videoCallUrl;
|
||||
}
|
||||
try {
|
||||
await sendScheduledEmails({ ...evt, additionalInformation: metadata });
|
||||
@@ -237,6 +241,7 @@ export async function processBookingConfirmation(
|
||||
})[];
|
||||
} | null;
|
||||
}[] = [];
|
||||
const metadata = videoCallUrl ? { videoCallUrl } : undefined;
|
||||
|
||||
if (recurringEventId) {
|
||||
// The booking to confirm is a recurring event and comes from /booking/recurring, proceeding to mark all related
|
||||
@@ -255,6 +260,7 @@ export async function processBookingConfirmation(
|
||||
},
|
||||
data: {
|
||||
status: BookingStatus.ACCEPTED,
|
||||
metadata,
|
||||
references: {
|
||||
create: scheduleResult.referencesToCreate,
|
||||
},
|
||||
@@ -293,6 +299,7 @@ export async function processBookingConfirmation(
|
||||
},
|
||||
data: {
|
||||
status: BookingStatus.ACCEPTED,
|
||||
metadata,
|
||||
references: {
|
||||
create: scheduleResult.referencesToCreate,
|
||||
},
|
||||
@@ -366,13 +373,11 @@ export async function processBookingConfirmation(
|
||||
|
||||
const subscribersMeetingEnded = await getWebhooks(subscriberOptionsMeetingEnded);
|
||||
|
||||
subscribersMeetingEnded.forEach(
|
||||
(subscriber: { subscriberUrl: string; id: string; appId: string | null }) => {
|
||||
updatedBookings.forEach((booking) => {
|
||||
scheduleTrigger(booking, subscriber.subscriberUrl, subscriber);
|
||||
});
|
||||
}
|
||||
);
|
||||
subscribersMeetingEnded.forEach((subscriber) => {
|
||||
updatedBookings.forEach((booking) => {
|
||||
scheduleTrigger(booking, subscriber.subscriberUrl, subscriber);
|
||||
});
|
||||
});
|
||||
|
||||
const eventTypeInfo: EventTypeInfo = {
|
||||
eventTitle: booking.eventType?.title,
|
||||
|
||||
@@ -54,6 +54,7 @@ export const buildBooking = (booking?: Partial<Booking>): Booking => {
|
||||
recurringEventId: null,
|
||||
smsReminderNumber: null,
|
||||
scheduledJobs: [],
|
||||
metadata: null,
|
||||
...booking,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Booking" ADD COLUMN "metadata" JSONB;
|
||||
@@ -300,6 +300,8 @@ model Booking {
|
||||
smsReminderNumber String?
|
||||
workflowReminders WorkflowReminder[]
|
||||
scheduledJobs String[]
|
||||
/// @zod.custom(imports.bookingMetadataSchema)
|
||||
metadata Json?
|
||||
}
|
||||
|
||||
model Schedule {
|
||||
|
||||
@@ -220,6 +220,12 @@ export const teamMetadataSchema = z
|
||||
.partial()
|
||||
.nullable();
|
||||
|
||||
export const bookingMetadataSchema = z
|
||||
.object({
|
||||
videoCallUrl: z.string().optional(),
|
||||
})
|
||||
.nullable();
|
||||
|
||||
export const customInputOptionSchema = z.array(
|
||||
z.object({
|
||||
label: z.string(),
|
||||
|
||||
Reference in new issue
Block a user