Fix workflows on seated events (#8034)

* Fix workflows on seated events

* Fix new args required in handleConfirmation

* Update function import to new name

* added email to override for workflows
This commit is contained in:
alannnc 2023-04-13 12:03:08 -07:00 committed by GitHub
parent 7fde30edf9
commit 5c763389f7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 275 additions and 219 deletions

View File

@ -1,7 +1,7 @@
import type { TFunction } from "next-i18next";
import { guessEventLocationType } from "@calcom/app-store/locations";
import { getVideoCallUrl } from "@calcom/lib/CalEventParser";
import { getVideoCallUrlFromCalEvent } from "@calcom/lib/CalEventParser";
import logger from "@calcom/lib/logger";
import type { CalendarEvent } from "@calcom/types/Calendar";
@ -19,7 +19,7 @@ export function LocationInfo(props: { calEvent: CalendarEvent; t: TFunction }) {
let meetingUrl = location?.search(/^https?:/) !== -1 ? location : undefined;
if (props.calEvent) {
meetingUrl = getVideoCallUrl(props.calEvent) || meetingUrl;
meetingUrl = getVideoCallUrlFromCalEvent(props.calEvent) || meetingUrl;
}
const isPhone = location?.startsWith("+");

View File

@ -331,11 +331,11 @@ async function handler(req: CustomRequest) {
//Workflows - schedule reminders
if (bookingToDelete.eventType?.workflows) {
await sendCancelledReminders(
bookingToDelete.eventType?.workflows,
bookingToDelete.smsReminderNumber,
evt
);
await sendCancelledReminders({
workflows: bookingToDelete.eventType?.workflows,
smsReminderNumber: bookingToDelete.smsReminderNumber,
evt,
});
}
let updatedBookings: {

View File

@ -175,14 +175,12 @@ export async function handleConfirmation(args: {
const isFirstBooking = index === 0;
await scheduleWorkflowReminders(
updatedBookings[index]?.eventType?.workflows || [],
updatedBookings[index].smsReminderNumber,
evtOfBooking,
false,
false,
isFirstBooking
);
await scheduleWorkflowReminders({
workflows: updatedBookings[index]?.eventType?.workflows || [],
smsReminderNumber: updatedBookings[index].smsReminderNumber,
calendarEvent: evtOfBooking,
isFirstRecurringEvent: isFirstBooking,
});
}
}
} catch (error) {

View File

@ -38,7 +38,7 @@ import { scheduleWorkflowReminders } from "@calcom/features/ee/workflows/lib/rem
import { deleteScheduledSMSReminder } from "@calcom/features/ee/workflows/lib/reminders/smsReminderManager";
import getWebhooks from "@calcom/features/webhooks/lib/getWebhooks";
import { isPrismaObjOrUndefined, parseRecurringEvent } from "@calcom/lib";
import { getVideoCallUrl } from "@calcom/lib/CalEventParser";
import { getVideoCallUrlFromCalEvent } from "@calcom/lib/CalEventParser";
import { getDefaultEvent, getGroupName, getUsernameList } from "@calcom/lib/defaultEvents";
import { getErrorFromUnknown } from "@calcom/lib/errors";
import getPaymentAppData from "@calcom/lib/getPaymentAppData";
@ -911,15 +911,15 @@ async function handler(
return deletedReferences;
};
const handleSeats = async (): Promise<
| (Partial<Booking> & {
appsStatus?: AppsStatus[];
seatReferenceUid?: string;
paymentUid?: string;
message?: string;
})
| null
> => {
const handleSeats = async () => {
let resultBooking:
| (Partial<Booking> & {
appsStatus?: AppsStatus[];
seatReferenceUid?: string;
paymentUid?: string;
message?: string;
})
| null = null;
const booking = await prisma.booking.findUniqueOrThrow({
where: {
uid: rescheduleUid || reqBody.bookingUid,
@ -1093,144 +1093,151 @@ async function handler(
cancellationReason: "$RCH$" + (rescheduleReason ? rescheduleReason : ""), // Removable code prefix to differentiate cancellation from rescheduling for email
});
}
const resultBooking = await resultBookingQuery(newBooking.id);
const foundBooking = await findBookingQuery(newBooking.id);
return { ...resultBooking, appsStatus: newBooking.appsStatus };
}
resultBooking = { ...foundBooking, appsStatus: newBooking.appsStatus };
} else {
// Merge two bookings together
const attendeesToMove = [],
attendeesToDelete = [];
// Merge two bookings together
const attendeesToMove = [],
attendeesToDelete = [];
for (const attendee of booking.attendees) {
// If the attendee already exists on the new booking then delete the attendee record of the old booking
if (
newTimeSlotBooking.attendees.some(
(newBookingAttendee) => newBookingAttendee.email === attendee.email
)
) {
attendeesToDelete.push(attendee.id);
// If the attendee does not exist on the new booking then move that attendee record to the new booking
} else {
attendeesToMove.push({ id: attendee.id, seatReferenceId: attendee.bookingSeat?.id });
for (const attendee of booking.attendees) {
// If the attendee already exists on the new booking then delete the attendee record of the old booking
if (
newTimeSlotBooking.attendees.some(
(newBookingAttendee) => newBookingAttendee.email === attendee.email
)
) {
attendeesToDelete.push(attendee.id);
// If the attendee does not exist on the new booking then move that attendee record to the new booking
} else {
attendeesToMove.push({ id: attendee.id, seatReferenceId: attendee.bookingSeat?.id });
}
}
}
// Confirm that the new event will have enough available seats
if (
!eventType.seatsPerTimeSlot ||
attendeesToMove.length +
newTimeSlotBooking.attendees.filter((attendee) => attendee.bookingSeat).length >
eventType.seatsPerTimeSlot
) {
throw new HttpError({ statusCode: 409, message: "Booking does not have enough available seats" });
}
// Confirm that the new event will have enough available seats
if (
!eventType.seatsPerTimeSlot ||
attendeesToMove.length +
newTimeSlotBooking.attendees.filter((attendee) => attendee.bookingSeat).length >
eventType.seatsPerTimeSlot
) {
throw new HttpError({ statusCode: 409, message: "Booking does not have enough available seats" });
}
const moveAttendeeCalls = [];
for (const attendeeToMove of attendeesToMove) {
moveAttendeeCalls.push(
prisma.attendee.update({
where: {
id: attendeeToMove.id,
},
data: {
bookingId: newTimeSlotBooking.id,
bookingSeat: {
upsert: {
create: {
referenceUid: uuid(),
bookingId: newTimeSlotBooking.id,
},
update: {
bookingId: newTimeSlotBooking.id,
const moveAttendeeCalls = [];
for (const attendeeToMove of attendeesToMove) {
moveAttendeeCalls.push(
prisma.attendee.update({
where: {
id: attendeeToMove.id,
},
data: {
bookingId: newTimeSlotBooking.id,
bookingSeat: {
upsert: {
create: {
referenceUid: uuid(),
bookingId: newTimeSlotBooking.id,
},
update: {
bookingId: newTimeSlotBooking.id,
},
},
},
},
},
})
);
}
})
);
}
await Promise.all([
...moveAttendeeCalls,
// Delete any attendees that are already a part of that new time slot booking
prisma.attendee.deleteMany({
await Promise.all([
...moveAttendeeCalls,
// Delete any attendees that are already a part of that new time slot booking
prisma.attendee.deleteMany({
where: {
id: {
in: attendeesToDelete,
},
},
}),
]);
const updatedNewBooking = await prisma.booking.findUnique({
where: {
id: {
in: attendeesToDelete,
},
id: newTimeSlotBooking.id,
},
}),
]);
include: {
attendees: true,
references: true,
},
});
const updatedNewBooking = await prisma.booking.findUnique({
where: {
id: newTimeSlotBooking.id,
},
include: {
attendees: true,
references: true,
},
});
if (!updatedNewBooking) {
throw new HttpError({ statusCode: 404, message: "Updated booking not found" });
}
if (!updatedNewBooking) {
throw new HttpError({ statusCode: 404, message: "Updated booking not found" });
// Update the evt object with the new attendees
const updatedBookingAttendees = updatedNewBooking.attendees.map((attendee) => {
const evtAttendee = {
...attendee,
language: { translate: tAttendees, locale: language ?? "en" },
};
return evtAttendee;
});
evt.attendees = updatedBookingAttendees;
addVideoCallDataToEvt(updatedNewBooking.references);
const copyEvent = cloneDeep(evt);
const updateManager = await eventManager.reschedule(
copyEvent,
rescheduleUid,
newTimeSlotBooking.id
);
const results = updateManager.results;
const calendarResult = results.find((result) => result.type.includes("_calendar"));
evt.iCalUID = Array.isArray(calendarResult?.updatedEvent)
? calendarResult?.updatedEvent[0]?.iCalUID
: calendarResult?.updatedEvent?.iCalUID || undefined;
// TODO send reschedule emails to attendees of the old booking
await sendRescheduledEmails({
...copyEvent,
additionalNotes, // Resets back to the additionalNote input and not the override value
cancellationReason: "$RCH$" + (rescheduleReason ? rescheduleReason : ""), // Removable code prefix to differentiate cancellation from rescheduling for email
});
// Update the old booking with the cancelled status
await prisma.booking.update({
where: {
id: booking.id,
},
data: {
status: BookingStatus.CANCELLED,
},
});
const foundBooking = await findBookingQuery(newTimeSlotBooking.id);
resultBooking = { ...foundBooking };
}
// Update the evt object with the new attendees
const updatedBookingAttendees = updatedNewBooking.attendees.map((attendee) => {
const evtAttendee = { ...attendee, language: { translate: tAttendees, locale: language ?? "en" } };
return evtAttendee;
});
evt.attendees = updatedBookingAttendees;
addVideoCallDataToEvt(updatedNewBooking.references);
const copyEvent = cloneDeep(evt);
const updateManager = await eventManager.reschedule(copyEvent, rescheduleUid, newTimeSlotBooking.id);
const results = updateManager.results;
const calendarResult = results.find((result) => result.type.includes("_calendar"));
evt.iCalUID = Array.isArray(calendarResult?.updatedEvent)
? calendarResult?.updatedEvent[0]?.iCalUID
: calendarResult?.updatedEvent?.iCalUID || undefined;
// TODO send reschedule emails to attendees of the old booking
await sendRescheduledEmails({
...copyEvent,
additionalNotes, // Resets back to the additionalNote input and not the override value
cancellationReason: "$RCH$" + (rescheduleReason ? rescheduleReason : ""), // Removable code prefix to differentiate cancellation from rescheduling for email
});
// Update the old booking with the cancelled status
await prisma.booking.update({
where: {
id: booking.id,
},
data: {
status: BookingStatus.CANCELLED,
},
});
const resultBooking = await resultBookingQuery(newTimeSlotBooking.id);
return { ...resultBooking };
}
// seatAttendee is null when the organizer is rescheduling.
const seatAttendee: Partial<Person> | null = bookingSeat?.attendee || null;
seatAttendee.language = { translate: tAttendees, locale: bookingSeat?.attendee.locale ?? "en" };
if (seatAttendee) {
seatAttendee["language"] = { translate: tAttendees, locale: bookingSeat?.attendee.locale ?? "en" };
}
// If there is no booking then remove the attendee from the old booking and create a new one
if (!newTimeSlotBooking) {
await prisma.attendee.delete({
where: {
id: seatAttendee.id,
id: seatAttendee?.id,
},
});
@ -1259,24 +1266,26 @@ async function handler(
// Need to change the new seat reference and attendee record to remove it from the old booking and add it to the new booking
// https://stackoverflow.com/questions/4980963/database-insert-new-rows-or-update-existing-ones
await Promise.all([
await prisma.attendee.update({
where: {
id: seatAttendee.id,
},
data: {
bookingId: newTimeSlotBooking.id,
},
}),
await prisma.bookingSeat.update({
where: {
id: bookingSeat.id,
},
data: {
bookingId: newTimeSlotBooking.id,
},
}),
]);
if (seatAttendee?.id && bookingSeat?.id) {
await Promise.all([
await prisma.attendee.update({
where: {
id: seatAttendee.id,
},
data: {
bookingId: newTimeSlotBooking.id,
},
}),
await prisma.bookingSeat.update({
where: {
id: bookingSeat.id,
},
data: {
bookingId: newTimeSlotBooking.id,
},
}),
]);
}
const copyEvent = cloneDeep(evt);
@ -1296,9 +1305,9 @@ async function handler(
});
await lastAttendeeDeleteBooking(originalRescheduledBooking, filteredAttendees, originalBookingEvt);
const resultBooking = await resultBookingQuery(newTimeSlotBooking.id);
const foundBooking = await findBookingQuery(newTimeSlotBooking.id);
return { ...resultBooking, seatReferenceUid: bookingSeat.referenceUid };
resultBooking = { ...foundBooking, seatReferenceUid: bookingSeat?.referenceUid };
} else {
// Need to add translation for attendees to pass type checks. Since these values are never written to the db we can just use the new attendee language
const bookingAttendees = booking.attendees.map((attendee) => {
@ -1324,7 +1333,7 @@ async function handler(
const attendeeUniqueId = uuid();
const bookingUpdated = await prisma.booking.update({
await prisma.booking.update({
where: {
uid: reqBody.bookingUid,
},
@ -1373,7 +1382,7 @@ async function handler(
const eventManager = new EventManager({ ...organizerUser, credentials });
await eventManager.updateCalendarAttendees(evt, booking);
const resultBooking = await resultBookingQuery(booking.id);
const foundBooking = await findBookingQuery(booking.id);
if (!Number.isNaN(paymentAppData.price) && paymentAppData.price > 0 && !!booking) {
const credentialPaymentAppCategories = await prisma.credential.findMany({
@ -1416,16 +1425,33 @@ async function handler(
bookerEmail
);
return {
...resultBooking,
message: "Payment required",
paymentUid: payment?.uid,
seatReferenceUid: bookingSeat?.referenceUid,
};
resultBooking = { ...foundBooking };
resultBooking["message"] = "Payment required";
resultBooking["paymentUid"] = payment?.uid;
}
return { ...resultBooking, seatReferenceUid: evt.attendeeSeatId };
resultBooking = { ...foundBooking, seatReferenceUid: evt.attendeeSeatId };
}
// Here we should handle every after action that needs to be done after booking creation
// Obtain event metadata that includes videoCallUrl
const metadata = { videoCallUrl: evt.videoCallData?.url };
try {
await scheduleWorkflowReminders({
workflows: eventType.workflows,
smsReminderNumber: smsReminderNumber || null,
calendarEvent: { ...evt, responses, ...{ metadata } },
requiresConfirmation: evt.requiresConfirmation || false,
isRescheduleEvent: !!rescheduleUid,
isFirstRecurringEvent: true,
emailAttendeeSendToOverride: bookerEmail,
});
} catch (error) {
log.error("Error while scheduling workflow reminders", error);
}
return resultBooking;
};
// For seats, if the booking already exists then we want to add the new attendee to the existing booking
if (eventType.seatsPerTimeSlot && (reqBody.bookingUid || rescheduleUid)) {
@ -1779,7 +1805,6 @@ async function handler(
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)) {
@ -1907,7 +1932,11 @@ async function handler(
videoCallUrl = booking.location;
}
const metadata = videoCallUrl ? { videoCallUrl: getVideoCallUrl(evt) || videoCallUrl } : undefined;
const metadata = videoCallUrl
? {
videoCallUrl: getVideoCallUrlFromCalEvent(evt),
}
: undefined;
if (isConfirmedByDefault) {
const eventTrigger: WebhookTriggerEvents = rescheduleUid
? WebhookTriggerEvents.BOOKING_RESCHEDULED
@ -2023,15 +2052,17 @@ async function handler(
log.error("Error while creating booking references", error);
}
const metadataFromEvent = { videoCallUrl };
try {
await scheduleWorkflowReminders(
eventType.workflows,
smsReminderNumber || null,
{ ...evt, responses, ...{ metadata } },
evt.requiresConfirmation || false,
rescheduleUid ? true : false,
true
);
await scheduleWorkflowReminders({
workflows: eventType.workflows,
smsReminderNumber: smsReminderNumber || null,
calendarEvent: { ...evt, responses, ...{ metadata: metadataFromEvent } },
requiresConfirmation: evt.requiresConfirmation || false,
isRescheduleEvent: !!rescheduleUid,
isFirstRecurringEvent: true,
});
} catch (error) {
log.error("Error while scheduling workflow reminders", error);
}
@ -2082,7 +2113,7 @@ function handleCustomInputs(
});
}
const resultBookingQuery = async (bookingId: number) => {
const findBookingQuery = async (bookingId: number) => {
const foundBooking = await prisma.booking.findUnique({
where: {
id: bookingId,

View File

@ -1,6 +1,7 @@
import type { TimeUnit } from "@prisma/client";
import { WorkflowTriggerEvents, WorkflowTemplates, WorkflowActions, WorkflowMethods } from "@prisma/client";
import client from "@sendgrid/client";
import type { MailData } from "@sendgrid/helpers/classes/mail";
import sgMail from "@sendgrid/mail";
import dayjs from "@calcom/dayjs";
@ -30,7 +31,7 @@ export const scheduleEmailReminder = async (
time: number | null;
timeUnit: TimeUnit | null;
},
sendTo: string,
sendTo: MailData["to"],
emailSubject: string,
emailBody: string,
workflowStepId: number,

View File

@ -1,5 +1,6 @@
import type { Workflow, WorkflowsOnEventTypes, WorkflowStep } from "@prisma/client";
import { WorkflowActions, WorkflowTriggerEvents } from "@prisma/client";
import type { MailData } from "@sendgrid/helpers/classes/mail";
import { SENDER_ID, SENDER_NAME } from "@calcom/lib/constants";
import type { CalendarEvent } from "@calcom/types/Calendar";
@ -7,21 +8,33 @@ import type { CalendarEvent } from "@calcom/types/Calendar";
import { scheduleEmailReminder } from "./emailReminderManager";
import { scheduleSMSReminder } from "./smsReminderManager";
export const scheduleWorkflowReminders = async (
export interface ScheduleWorkflowRemindersArgs {
workflows: (WorkflowsOnEventTypes & {
workflow: Workflow & {
steps: WorkflowStep[];
};
})[],
smsReminderNumber: string | null,
evt: CalendarEvent & { metadata?: { videoCallUrl: string } },
needsConfirmation: boolean,
isRescheduleEvent: boolean,
isFirstRecurringEvent: boolean
) => {
if (workflows.length > 0 && !needsConfirmation) {
workflows.forEach((workflowReference) => {
if (workflowReference.workflow.steps.length === 0) return;
})[];
smsReminderNumber: string | null;
calendarEvent: CalendarEvent & { metadata?: { videoCallUrl: string | undefined } };
requiresConfirmation?: boolean;
isRescheduleEvent?: boolean;
isFirstRecurringEvent?: boolean;
emailAttendeeSendToOverride?: string;
}
export const scheduleWorkflowReminders = async (args: ScheduleWorkflowRemindersArgs) => {
const {
workflows,
smsReminderNumber,
calendarEvent: evt,
requiresConfirmation = false,
isRescheduleEvent = false,
isFirstRecurringEvent = false,
emailAttendeeSendToOverride = "",
} = args;
if (workflows.length > 0 && !requiresConfirmation) {
for (const workflowReference of workflows) {
if (workflowReference.workflow.steps.length === 0) continue;
const workflow = workflowReference.workflow;
if (
@ -32,7 +45,7 @@ export const scheduleWorkflowReminders = async (
(workflow.trigger === WorkflowTriggerEvents.RESCHEDULE_EVENT && isRescheduleEvent) ||
workflow.trigger === WorkflowTriggerEvents.AFTER_EVENT
) {
workflow.steps.forEach(async (step) => {
for (const step of workflow.steps) {
if (step.action === WorkflowActions.SMS_ATTENDEE || step.action === WorkflowActions.SMS_NUMBER) {
const sendTo = step.action === WorkflowActions.SMS_ATTENDEE ? smsReminderNumber : step.sendTo;
await scheduleSMSReminder(
@ -60,14 +73,16 @@ export const scheduleWorkflowReminders = async (
switch (step.action) {
case WorkflowActions.EMAIL_HOST:
sendTo = evt.organizer.email;
sendTo = evt.organizer?.email || "";
break;
case WorkflowActions.EMAIL_ATTENDEE:
sendTo = evt.attendees[0].email;
sendTo = !!emailAttendeeSendToOverride
? emailAttendeeSendToOverride
: evt.attendees?.[0]?.email || "";
break;
}
scheduleEmailReminder(
await scheduleEmailReminder(
evt,
workflow.trigger,
step.action,
@ -83,27 +98,31 @@ export const scheduleWorkflowReminders = async (
step.sender || SENDER_NAME
);
}
});
}
}
});
}
}
};
export const sendCancelledReminders = async (
export interface SendCancelledRemindersArgs {
workflows: (WorkflowsOnEventTypes & {
workflow: Workflow & {
steps: WorkflowStep[];
};
})[],
smsReminderNumber: string | null,
evt: CalendarEvent
) => {
})[];
smsReminderNumber: string | null;
evt: CalendarEvent;
}
export const sendCancelledReminders = async (args: SendCancelledRemindersArgs) => {
const { workflows, smsReminderNumber, evt } = args;
if (workflows.length > 0) {
workflows
.filter((workflowRef) => workflowRef.workflow.trigger === WorkflowTriggerEvents.EVENT_CANCELLED)
.forEach((workflowRef) => {
const workflow = workflowRef.workflow;
workflow.steps.forEach(async (step) => {
for (const workflowRef of workflows) {
const { workflow } = workflowRef;
if (workflow.trigger === WorkflowTriggerEvents.EVENT_CANCELLED) {
for (const step of workflow.steps) {
if (step.action === WorkflowActions.SMS_ATTENDEE || step.action === WorkflowActions.SMS_NUMBER) {
const sendTo = step.action === WorkflowActions.SMS_ATTENDEE ? smsReminderNumber : step.sendTo;
await scheduleSMSReminder(
@ -127,17 +146,18 @@ export const sendCancelledReminders = async (
step.action === WorkflowActions.EMAIL_ATTENDEE ||
step.action === WorkflowActions.EMAIL_HOST
) {
let sendTo = "";
let sendTo: MailData["to"] = "";
switch (step.action) {
case WorkflowActions.EMAIL_HOST:
sendTo = evt.organizer.email;
break;
case WorkflowActions.EMAIL_ATTENDEE:
sendTo = evt.attendees[0].email;
sendTo = evt.attendees.map((a) => a.email);
break;
}
scheduleEmailReminder(
await scheduleEmailReminder(
evt,
workflow.trigger,
step.action,
@ -153,7 +173,8 @@ export const sendCancelledReminders = async (
step.sender || SENDER_NAME
);
}
});
});
}
}
}
}
};

View File

@ -120,7 +120,7 @@ export const getDescription = (calEvent: CalendarEvent) => {
`;
};
export const getLocation = (calEvent: CalendarEvent) => {
const meetingUrl = getVideoCallUrl(calEvent);
const meetingUrl = getVideoCallUrlFromCalEvent(calEvent);
if (meetingUrl) {
return meetingUrl;
}
@ -220,7 +220,7 @@ export const getPublicVideoCallUrl = (calEvent: CalendarEvent): string => {
return WEBAPP_URL + "/video/" + getUid(calEvent);
};
export const getVideoCallUrl = (calEvent: CalendarEvent): string => {
export const getVideoCallUrlFromCalEvent = (calEvent: CalendarEvent): string => {
if (calEvent.videoCallData) {
if (isDailyVideoCall(calEvent)) {
return getPublicVideoCallUrl(calEvent);

View File

@ -1,6 +1,11 @@
import { faker } from "@faker-js/faker";
import { getLocation, getPublicVideoCallUrl, getVideoCallPassword, getVideoCallUrl } from "../CalEventParser";
import {
getLocation,
getPublicVideoCallUrl,
getVideoCallPassword,
getVideoCallUrlFromCalEvent,
} from "../CalEventParser";
import { buildCalendarEvent, buildVideoCallData } from "./builder";
jest.mock("@calcom/lib/constants", () => ({
@ -20,7 +25,7 @@ describe("getLocation", () => {
}),
});
expect(getLocation(calEvent)).toEqual(getVideoCallUrl(calEvent));
expect(getLocation(calEvent)).toEqual(getVideoCallUrlFromCalEvent(calEvent));
});
it("should return an integration provider name from event", () => {
const provideName = "Cal.com";
@ -49,7 +54,7 @@ describe("getVideoCallUrl", () => {
}),
});
expect(getVideoCallUrl(calEvent)).toEqual(getPublicVideoCallUrl(calEvent));
expect(getVideoCallUrlFromCalEvent(calEvent)).toEqual(getPublicVideoCallUrl(calEvent));
});
});