fix: exclude third-party video apps from areCalendarEventsEnabled setting (#27230)
* fix: exclude third-party video apps from areCalendarEventsEnabled setting When areCalendarEventsEnabled is false, video meeting creation for third-party video apps (like Daily.co) was being skipped, causing the meetingUrl field in webhook payloads to contain the location identifier (integrations:daily) instead of the actual video meeting URL. This fix adds a skipCalendarEvent option to EventManager.create() that: - Skips calendar event creation when true - Skips CRM event creation when true (CRM events are tied to calendar events) - Still creates video meetings for third-party video apps The calling code in handleConfirmation.ts and RegularBookingService.ts now uses this option when areCalendarEventsEnabled is false, ensuring video meetings are still created while respecting the platform customer's preference to manage their own calendar events. Co-Authored-By: morgan@cal.com <morgan@cal.com> * test: update E2E tests to verify skipCalendarEvent option is passed when areCalendarEventsEnabled is false Co-Authored-By: morgan@cal.com <morgan@cal.com> * refactor: address PR review feedback - remove comments and update E2E tests to spy on createAllCalendarEvents Co-Authored-By: morgan@cal.com <morgan@cal.com> * test: add spy for createAllCRMEvents in e2e tests when areCalendarEventsEnabled is false Co-Authored-By: lauris@cal.com <lauris.skraucis@gmail.com> * test: add missing spy for createAllCRMEvents in beforeEach hook Co-Authored-By: morgan@cal.com <morgan@cal.com> * fix: use videoCallUrl from metadata for webhook payload location Co-Authored-By: lauris@cal.com <lauris.skraucis@gmail.com> * fix: simplify webhook location to use metadata?.videoCallUrl || evt.location Co-Authored-By: lauris@cal.com <lauris.skraucis@gmail.com> * docs: add description about video call link limitations when calendar events disabled Co-Authored-By: lauris@cal.com <lauris.skraucis@gmail.com> * i18n: add translation key for calendar events disabled video limitation Co-Authored-By: lauris@cal.com <lauris.skraucis@gmail.com> * fix: update translation text to accurately reflect video app behavior Co-Authored-By: lauris@cal.com <lauris.skraucis@gmail.com> * Update common.json --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: Lauris Skraucis <lauris.skraucis@gmail.com>
This commit is contained in:
co-authored by
morgan@cal.com
lauris@cal.com
Devin AI
parent
b02470e780
commit
5b048dd868
@@ -2667,6 +2667,12 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
jest
|
||||
.spyOn(EventManager.prototype, "create")
|
||||
.mockImplementation(() => Promise.resolve({ results: [], referencesToCreate: [] }));
|
||||
jest
|
||||
.spyOn(EventManager.prototype, "createAllCalendarEvents")
|
||||
.mockImplementation(() => Promise.resolve([]));
|
||||
jest
|
||||
.spyOn(EventManager.prototype, "createAllCRMEvents")
|
||||
.mockImplementation(() => Promise.resolve([]));
|
||||
});
|
||||
|
||||
describe("platform oAuth client has calendar events enabled", () => {
|
||||
@@ -2837,7 +2843,8 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
const data: BookingOutput_2024_08_13 = responseBody.data;
|
||||
expect(data.id).toBeDefined();
|
||||
expect(data.uid).toBeDefined();
|
||||
expect(EventManager.prototype.create).toHaveBeenCalledTimes(0);
|
||||
expect(EventManager.prototype.createAllCalendarEvents).toHaveBeenCalledTimes(0);
|
||||
expect(EventManager.prototype.createAllCRMEvents).toHaveBeenCalledTimes(0);
|
||||
} else {
|
||||
throw new Error(
|
||||
"Invalid response data - expected booking but received array of possibly recurring bookings"
|
||||
@@ -2867,7 +2874,8 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
const responseBody: CreateBookingOutput_2024_08_13 = response.body;
|
||||
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||
expect(responseBody.data).toBeDefined();
|
||||
expect(EventManager.prototype.create).toHaveBeenCalledTimes(0);
|
||||
expect(EventManager.prototype.createAllCalendarEvents).toHaveBeenCalledTimes(0);
|
||||
expect(EventManager.prototype.createAllCRMEvents).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2901,7 +2909,8 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
const data: BookingOutput_2024_08_13 = responseBody.data;
|
||||
expect(data.id).toBeDefined();
|
||||
expect(data.uid).toBeDefined();
|
||||
expect(EventManager.prototype.create).toHaveBeenCalledTimes(0);
|
||||
expect(EventManager.prototype.createAllCalendarEvents).toHaveBeenCalledTimes(0);
|
||||
expect(EventManager.prototype.createAllCRMEvents).toHaveBeenCalledTimes(0);
|
||||
bookingThatRequiresConfirmationUid = data.uid;
|
||||
} else {
|
||||
throw new Error(
|
||||
@@ -2926,7 +2935,8 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
const data: BookingOutput_2024_08_13 = responseBody.data;
|
||||
expect(data.id).toBeDefined();
|
||||
expect(data.uid).toBeDefined();
|
||||
expect(EventManager.prototype.create).toHaveBeenCalledTimes(0);
|
||||
expect(EventManager.prototype.createAllCalendarEvents).toHaveBeenCalledTimes(0);
|
||||
expect(EventManager.prototype.createAllCRMEvents).toHaveBeenCalledTimes(0);
|
||||
} else {
|
||||
throw new Error(
|
||||
"Invalid response data - expected booking but received array of possibly recurring bookings"
|
||||
|
||||
@@ -231,6 +231,7 @@ export const OAuthClientForm = ({
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<p className="text-subtle mt-2 text-sm">{t("calendar_events_disabled_video_limitation")}</p>
|
||||
</div>
|
||||
<div className="mt-6">
|
||||
<div className="flex items-center">
|
||||
|
||||
@@ -4517,5 +4517,6 @@
|
||||
"error_enabling_feature": "Error enabling feature. Please try again.",
|
||||
"set_organizer_as_contact_owner": "Set booking organizer as contact owner",
|
||||
"overwrite_existing_contact_owner": "Overwrite existing contact owner",
|
||||
"calendar_events_disabled_video_limitation": "When calendar events are disabled, meeting links cannot be generated for apps whose link generation is tied to calendar event creation, for example, Google Meet and Microsoft Teams. Other video apps like Cal Video will continue to work.",
|
||||
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS":"↑↑↑↑↑↑↑↑↑↑↑↑↑ Add your new strings above here ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
|
||||
}
|
||||
|
||||
@@ -280,8 +280,15 @@ export default class EventManager {
|
||||
* event will be scheduled for it as well.
|
||||
*
|
||||
* @param event
|
||||
* @param options.skipCalendarEvent - When true, skips calendar event creation but still creates video meetings.
|
||||
* This is useful for platform customers who manage their own calendar events but still want Cal.com to create
|
||||
* video meetings for third-party video apps like Daily.co.
|
||||
*/
|
||||
public async create(event: CalendarEvent): Promise<CreateUpdateResult> {
|
||||
public async create(
|
||||
event: CalendarEvent,
|
||||
options?: { skipCalendarEvent?: boolean }
|
||||
): Promise<CreateUpdateResult> {
|
||||
const { skipCalendarEvent = false } = options ?? {};
|
||||
// TODO this method shouldn't be modifying the event object that's passed in
|
||||
const evt = processLocation(event);
|
||||
|
||||
@@ -357,7 +364,9 @@ export default class EventManager {
|
||||
// Some calendar libraries may edit the original event so let's clone it
|
||||
const clonedCalEvent = cloneDeep(event);
|
||||
// Create the calendar event with the proper video call data
|
||||
results.push(...(await this.createAllCalendarEvents(clonedCalEvent)));
|
||||
if (!skipCalendarEvent) {
|
||||
results.push(...(await this.createAllCalendarEvents(clonedCalEvent)));
|
||||
}
|
||||
|
||||
if (evt.location === MSTeamsLocationType) {
|
||||
this.updateMSTeamsVideoCallData(evt, results);
|
||||
@@ -371,7 +380,7 @@ export default class EventManager {
|
||||
return result.type.includes("_calendar");
|
||||
};
|
||||
|
||||
const createdCRMEvents = await this.createAllCRMEvents(evt);
|
||||
const createdCRMEvents = skipCalendarEvent ? [] : await this.createAllCRMEvents(evt);
|
||||
|
||||
results.push(...createdCRMEvents);
|
||||
|
||||
|
||||
@@ -151,7 +151,7 @@ export async function handleConfirmation(args: {
|
||||
const apps = eventTypeAppMetadataOptionalSchema.parse(eventTypeMetadata?.apps);
|
||||
const eventManager = new EventManager(user, apps);
|
||||
const areCalendarEventsEnabled = platformClientParams?.areCalendarEventsEnabled ?? true;
|
||||
const scheduleResult = areCalendarEventsEnabled ? await eventManager.create(evt) : placeholderCreatedEvent;
|
||||
const scheduleResult = await eventManager.create(evt, { skipCalendarEvent: !areCalendarEventsEnabled });
|
||||
const results = scheduleResult.results;
|
||||
const metadata: AdditionalInformation = {};
|
||||
const workflows = await getAllWorkflowsFromEventType(eventType, booking.userId);
|
||||
|
||||
@@ -2290,11 +2290,8 @@ async function handler(
|
||||
// If it's not a reschedule, doesn't require confirmation and there's no price,
|
||||
// Create a booking
|
||||
} else if (isConfirmedByDefault) {
|
||||
// Use EventManager to conditionally use all needed integrations.
|
||||
const createManager =
|
||||
areCalendarEventsEnabled && !skipCalendarSyncTaskCreation
|
||||
? await eventManager.create(evt)
|
||||
: placeholderCreatedEvent;
|
||||
const shouldSkipCalendarEvents = !areCalendarEventsEnabled || skipCalendarSyncTaskCreation;
|
||||
const createManager = await eventManager.create(evt, { skipCalendarEvent: shouldSkipCalendarEvents });
|
||||
if (evt.location) {
|
||||
booking.location = evt.location;
|
||||
}
|
||||
@@ -2479,6 +2476,8 @@ async function handler(
|
||||
tracingLogger,
|
||||
});
|
||||
|
||||
const webhookLocation = metadata?.videoCallUrl || evt.location;
|
||||
|
||||
const webhookData: EventPayloadType = {
|
||||
...evt,
|
||||
...eventTypeInfo,
|
||||
@@ -2496,6 +2495,7 @@ async function handler(
|
||||
status: "ACCEPTED",
|
||||
smsReminderNumber: booking?.smsReminderNumber || undefined,
|
||||
rescheduledBy: reqBody.rescheduledBy,
|
||||
location: webhookLocation,
|
||||
...(assignmentReason ? { assignmentReason: [assignmentReason] } : {}),
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user