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:
authored and GitHub committed 2022-12-15 21:43:07 +00:00
1 parent 20402ac26c
commit 688541923b
9 files changed
+36 -12

No files matched your search

+1
View File
@@ -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,
+1
View File
@@ -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;
+2
View File
@@ -300,6 +300,8 @@ model Booking {
smsReminderNumber String?
workflowReminders WorkflowReminder[]
scheduledJobs String[]
/// @zod.custom(imports.bookingMetadataSchema)
metadata Json?
}
model Schedule {
+6
View File
@@ -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(),