fix: round robin re assignment google meet bug (#17378)

This commit is contained in:
Udit Takkar authored and GitHub committed 2024-10-29 15:27:37 -04:00
1 parent 8605fb4518
commit 04a49e74d4
5 files changed
+221 -42

No files matched your search

+5
View File
@@ -96,6 +96,11 @@ export type EventManagerUser = {
type createdEventSchema = z.infer<typeof createdEventSchema>;
export type EventManagerInitParams = {
user: EventManagerUser;
eventTypeAppMetadata?: z.infer<typeof EventTypeAppMetadataSchema>;
};
export default class EventManager {
calendarCredentials: CredentialPayload[];
videoCredentials: CredentialPayload[];
@@ -0,0 +1,174 @@
import type { DestinationCalendar } from "@prisma/client";
import type { Prisma } from "@prisma/client";
import { metadata as GoogleMeetMetadata } from "@calcom/app-store/googlevideo/_metadata";
import { MeetLocationType } from "@calcom/app-store/locations";
import EventManager from "@calcom/core/EventManager";
import type { EventManagerInitParams } from "@calcom/core/EventManager";
import { getVideoCallDetails } from "@calcom/features/bookings/lib/handleNewBooking/getVideoCallDetails";
import { getVideoCallUrlFromCalEvent } from "@calcom/lib/CalEventParser";
import logger from "@calcom/lib/logger";
import { getTranslation } from "@calcom/lib/server/i18n";
import { BookingReferenceRepository } from "@calcom/lib/server/repository/bookingReference";
import { prisma } from "@calcom/prisma";
import type { CalendarEvent, AdditionalInformation } from "@calcom/types/Calendar";
export const handleRescheduleEventManager = async ({
evt,
rescheduleUid,
newBookingId,
changedOrganizer,
previousHostDestinationCalendar,
initParams,
bookingLocation,
bookingId,
bookingICalUID,
bookingMetadata,
}: {
evt: CalendarEvent;
rescheduleUid: string;
newBookingId?: number;
changedOrganizer?: boolean;
previousHostDestinationCalendar?: DestinationCalendar[] | null;
initParams: EventManagerInitParams;
bookingLocation: string | null;
bookingId: number;
bookingICalUID?: string | null;
bookingMetadata?: Prisma.JsonValue;
}) => {
const handleRescheduleEventManager = logger.getSubLogger({
prefix: ["handleRescheduleEventManager", `${bookingId}`],
});
const eventManager = new EventManager(initParams.user, initParams?.eventTypeAppMetadata);
const updateManager = await eventManager.reschedule(
evt,
rescheduleUid,
newBookingId,
changedOrganizer,
previousHostDestinationCalendar
);
const results = updateManager.results ?? [];
const { metadata: videoMetadata, videoCallUrl: _videoCallUrl } = getVideoCallDetails({
results: results,
});
let videoCallUrl = _videoCallUrl;
let metadata: AdditionalInformation = {};
metadata = videoMetadata;
if (results.length) {
// Handle Google Meet results
if (bookingLocation === MeetLocationType) {
const googleMeetResult = {
appName: GoogleMeetMetadata.name,
type: "conferencing",
uid: results[0].uid,
originalEvent: results[0].originalEvent,
};
// Find index of google_calendar inside createManager.referencesToCreate
const googleCalIndex = updateManager.referencesToCreate.findIndex(
(ref) => ref.type === "google_calendar"
);
const googleCalResult = results[googleCalIndex];
const t = await getTranslation("en", "common");
if (!googleCalResult) {
handleRescheduleEventManager.warn("Google Calendar not installed but using Google Meet as location");
results.push({
...googleMeetResult,
success: false,
calWarnings: [t("google_meet_warning")],
});
}
const googleHangoutLink = Array.isArray(googleCalResult?.updatedEvent)
? googleCalResult.updatedEvent[0]?.hangoutLink
: googleCalResult?.updatedEvent?.hangoutLink ?? googleCalResult?.createdEvent?.hangoutLink;
if (googleHangoutLink) {
results.push({
...googleMeetResult,
success: true,
});
// Add google_meet to referencesToCreate in the same index as google_calendar
updateManager.referencesToCreate[googleCalIndex] = {
...updateManager.referencesToCreate[googleCalIndex],
meetingUrl: googleHangoutLink,
};
// Also create a new referenceToCreate with type video for google_meet
updateManager.referencesToCreate.push({
type: "google_meet_video",
meetingUrl: googleHangoutLink,
uid: googleCalResult.uid,
credentialId: updateManager.referencesToCreate[googleCalIndex].credentialId,
});
} else if (googleCalResult && !googleHangoutLink) {
results.push({
...googleMeetResult,
success: false,
});
}
}
const createdOrUpdatedEvent = Array.isArray(results[0]?.updatedEvent)
? results[0]?.updatedEvent[0]
: results[0]?.updatedEvent ?? results[0]?.createdEvent;
metadata.hangoutLink = createdOrUpdatedEvent?.hangoutLink;
metadata.conferenceData = createdOrUpdatedEvent?.conferenceData;
metadata.entryPoints = createdOrUpdatedEvent?.entryPoints;
videoCallUrl =
metadata.hangoutLink || createdOrUpdatedEvent?.url || getVideoCallUrlFromCalEvent(evt) || videoCallUrl;
const calendarResult = results.find((result) => result.type.includes("_calendar"));
evt.iCalUID = Array.isArray(calendarResult?.updatedEvent)
? calendarResult?.updatedEvent[0]?.iCalUID
: calendarResult?.updatedEvent?.iCalUID || undefined;
}
const newReferencesToCreate = structuredClone(updateManager.referencesToCreate);
await BookingReferenceRepository.replaceBookingReferences({
bookingId,
newReferencesToCreate,
});
try {
if (bookingLocation?.startsWith("http")) {
videoCallUrl = bookingLocation;
}
const newBookingMetaData = videoCallUrl
? {
videoCallUrl: getVideoCallUrlFromCalEvent(evt) || videoCallUrl,
}
: undefined;
await prisma.booking.update({
where: {
id: bookingId,
},
data: {
location: bookingLocation,
iCalUID: evt.iCalUID !== bookingICalUID ? evt.iCalUID : bookingICalUID,
metadata: { ...(typeof bookingMetadata === "object" && bookingMetadata), ...newBookingMetaData },
},
});
} catch (error) {
handleRescheduleEventManager.error("Error while updating booking metadata", JSON.stringify({ error }));
}
const evtWithAdditionalInfo = {
...evt,
additionalInformation: metadata,
};
return { evtWithAdditionalInfo };
};
@@ -2,7 +2,6 @@
import { cloneDeep } from "lodash";
import { OrganizerDefaultConferencingAppType, getLocationValueForDB } from "@calcom/app-store/locations";
import EventManager from "@calcom/core/EventManager";
import { getEventName } from "@calcom/core/event";
import dayjs from "@calcom/dayjs";
import { sendRoundRobinCancelledEmailsAndSMS, sendRoundRobinScheduledEmailsAndSMS } from "@calcom/emails";
@@ -20,7 +19,6 @@ import { SENDER_NAME } from "@calcom/lib/constants";
import { getBookerBaseUrl } from "@calcom/lib/getBookerUrl/server";
import logger from "@calcom/lib/logger";
import { getTranslation } from "@calcom/lib/server/i18n";
import { BookingReferenceRepository } from "@calcom/lib/server/repository/bookingReference";
import { getTimeFormatStringFromUserTimeFormat } from "@calcom/lib/timeFormat";
import { prisma } from "@calcom/prisma";
import { WorkflowActions, WorkflowMethods, WorkflowTriggerEvents } from "@calcom/prisma/enums";
@@ -28,6 +26,7 @@ import { userMetadata as userMetadataSchema } from "@calcom/prisma/zod-utils";
import type { EventTypeMetadata } from "@calcom/prisma/zod-utils";
import type { CalendarEvent } from "@calcom/types/Calendar";
import { handleRescheduleEventManager } from "./handleRescheduleEventManager";
import type { BookingSelectResult } from "./utils/bookingSelect";
import { bookingSelect } from "./utils/bookingSelect";
import { getDestinationCalendar } from "./utils/getDestinationCalendar";
@@ -124,6 +123,7 @@ export const roundRobinManualReassignment = async ({
})();
const previousRRHostT = await getTranslation(previousRRHost?.locale || "en", "common");
let bookingLocation = booking.location;
if (hasOrganizerChanged) {
const bookingResponses = booking.responses;
@@ -134,8 +134,7 @@ export const roundRobinManualReassignment = async ({
const responseSafeParse = await responseSchema.safeParseAsync(bookingResponses);
const responses = responseSafeParse.success ? responseSafeParse.data : undefined;
let bookingLocation = booking.location;
if (eventType.locations.includes({ type: OrganizerDefaultConferencingAppType })) {
if (eventType.locations.some((location) => location.type === OrganizerDefaultConferencingAppType)) {
const newUserMetadataSafeParse = userMetadataSchema.safeParse(newUser.metadata);
const defaultLocationUrl = newUserMetadataSafeParse.success
? newUserMetadataSafeParse?.data?.defaultConferencingApp?.appLink
@@ -248,6 +247,7 @@ export const roundRobinManualReassignment = async ({
bookingFields: eventType.bookingFields ?? null,
booking,
}),
location: bookingLocation,
};
const credentials = await prisma.credential.findMany({
@@ -255,29 +255,28 @@ export const roundRobinManualReassignment = async ({
include: { user: { select: { email: true } } },
});
const eventManager = new EventManager({ ...newUser, credentials });
const previousHostDestinationCalendar = hasOrganizerChanged
? await prisma.destinationCalendar.findFirst({
where: { userId: originalOrganizer.id },
})
: null;
const results = await eventManager.reschedule(
const { evtWithAdditionalInfo } = await handleRescheduleEventManager({
evt,
booking.uid,
undefined,
hasOrganizerChanged,
previousHostDestinationCalendar ? [previousHostDestinationCalendar] : []
);
const newReferencesToCreate = structuredClone(results.referencesToCreate);
await BookingReferenceRepository.replaceBookingReferences({
rescheduleUid: booking.uid,
newBookingId: undefined,
changedOrganizer: hasOrganizerChanged,
previousHostDestinationCalendar: previousHostDestinationCalendar ? [previousHostDestinationCalendar] : [],
initParams: {
user: { ...newUser, credentials },
},
bookingId,
newReferencesToCreate,
bookingLocation,
bookingICalUID: booking.iCalUID,
bookingMetadata: booking.metadata,
});
const { cancellationReason, ...evtWithoutCancellationReason } = evt;
const { cancellationReason, ...evtWithoutCancellationReason } = evtWithAdditionalInfo;
// Send emails
await sendRoundRobinScheduledEmailsAndSMS({
@@ -300,7 +299,7 @@ export const roundRobinManualReassignment = async ({
});
// Send cancellation email to previous RR host
const cancelledEvt = cloneDeep(evt);
const cancelledEvt = cloneDeep(evtWithAdditionalInfo);
cancelledEvt.organizer = {
email: originalOrganizer.email,
name: originalOrganizer.name || "",
@@ -330,7 +329,7 @@ export const roundRobinManualReassignment = async ({
await handleWorkflowsUpdate({
booking,
newUser,
evt,
evt: evtWithAdditionalInfo,
eventType,
orgId,
});
@@ -2,7 +2,6 @@
import { cloneDeep } from "lodash";
import { OrganizerDefaultConferencingAppType, getLocationValueForDB } from "@calcom/app-store/locations";
import EventManager from "@calcom/core/EventManager";
import { getEventName } from "@calcom/core/event";
import dayjs from "@calcom/dayjs";
import { sendRoundRobinCancelledEmailsAndSMS, sendRoundRobinScheduledEmailsAndSMS } from "@calcom/emails";
@@ -23,7 +22,6 @@ import { getBookerBaseUrl } from "@calcom/lib/getBookerUrl/server";
import logger from "@calcom/lib/logger";
import { getLuckyUser } from "@calcom/lib/server";
import { getTranslation } from "@calcom/lib/server/i18n";
import { BookingReferenceRepository } from "@calcom/lib/server/repository/bookingReference";
import { getTimeFormatStringFromUserTimeFormat } from "@calcom/lib/timeFormat";
import { prisma } from "@calcom/prisma";
import { WorkflowActions, WorkflowMethods, WorkflowTriggerEvents } from "@calcom/prisma/enums";
@@ -31,6 +29,7 @@ import { userMetadata as userMetadataSchema } from "@calcom/prisma/zod-utils";
import type { EventTypeMetadata } from "@calcom/prisma/zod-utils";
import type { CalendarEvent } from "@calcom/types/Calendar";
import { handleRescheduleEventManager } from "./handleRescheduleEventManager";
import { bookingSelect } from "./utils/bookingSelect";
import { getDestinationCalendar } from "./utils/getDestinationCalendar";
import { getTeamMembers } from "./utils/getTeamMembers";
@@ -174,6 +173,7 @@ export const roundRobinReassignment = async ({
}
const attendeeList = await Promise.all(attendeePromises);
let bookingLocation = booking.location;
if (hasOrganizerChanged) {
const bookingResponses = booking.responses;
@@ -187,9 +187,7 @@ export const roundRobinReassignment = async ({
const responses = responseSafeParse.success ? responseSafeParse.data : undefined;
let bookingLocation = booking.location;
if (eventType.locations.includes({ type: OrganizerDefaultConferencingAppType })) {
if (eventType.locations.some((location) => location.type === OrganizerDefaultConferencingAppType)) {
const organizerMetadataSafeParse = userMetadataSchema.safeParse(reassignedRRHost.metadata);
const defaultLocationUrl = organizerMetadataSafeParse.success
@@ -291,6 +289,7 @@ export const roundRobinReassignment = async ({
bookingFields: eventType?.bookingFields ?? null,
booking,
}),
location: bookingLocation,
};
const credentials = await prisma.credential.findMany({
@@ -305,27 +304,27 @@ export const roundRobinReassignment = async ({
},
},
});
const eventManager = new EventManager({ ...organizer, credentials: [...credentials] });
const results = await eventManager.reschedule(
const { evtWithAdditionalInfo } = await handleRescheduleEventManager({
evt,
booking.uid,
undefined,
hasOrganizerChanged,
previousHostDestinationCalendar ? [previousHostDestinationCalendar] : []
);
let newReferencesToCreate = [];
newReferencesToCreate = structuredClone(results.referencesToCreate);
await BookingReferenceRepository.replaceBookingReferences({
rescheduleUid: booking.uid,
newBookingId: undefined,
changedOrganizer: hasOrganizerChanged,
previousHostDestinationCalendar: previousHostDestinationCalendar ? [previousHostDestinationCalendar] : [],
initParams: {
user: { ...organizer, credentials: [...credentials] },
},
bookingId,
newReferencesToCreate,
bookingLocation,
bookingICalUID: booking.iCalUID,
bookingMetadata: booking.metadata,
});
const { cancellationReason, ...evtWithoutCancellationReason } = evtWithAdditionalInfo;
// Send to new RR host
await sendRoundRobinScheduledEmailsAndSMS({
calEvent: evt,
calEvent: evtWithoutCancellationReason,
members: [
{
...reassignedRRHost,
@@ -340,7 +339,7 @@ export const roundRobinReassignment = async ({
if (previousRRHost) {
// Send to cancelled RR host
// First we need to replace the new RR host with the old RR host in the evt object
const cancelledRRHostEvt = cloneDeep(evt);
const cancelledRRHostEvt = cloneDeep(evtWithAdditionalInfo);
cancelledRRHostEvt.title = currentBookingTitle;
if (hasOrganizerChanged) {
cancelledRRHostEvt.organizer = {
@@ -427,7 +426,7 @@ export const roundRobinReassignment = async ({
},
});
const workflowEventMetadata = { videoCallUrl: getVideoCallUrlFromCalEvent(evt) };
const workflowEventMetadata = { videoCallUrl: getVideoCallUrlFromCalEvent(evtWithAdditionalInfo) };
const bookerUrl = await getBookerBaseUrl(orgId);
@@ -438,7 +437,7 @@ export const roundRobinReassignment = async ({
if (workflowStep && workflow) {
await scheduleEmailReminder({
evt: {
...evt,
...evtWithAdditionalInfo,
metadata: workflowEventMetadata,
eventType,
bookerUrl,
@@ -512,7 +511,7 @@ export const roundRobinReassignment = async ({
workflows: newEventWorkflows,
smsReminderNumber: null,
calendarEvent: {
...evt,
...evtWithAdditionalInfo,
metadata: workflowEventMetadata,
eventType: { slug: eventType.slug },
bookerUrl,
@@ -19,6 +19,8 @@ export const bookingSelect = {
},
attendees: true,
references: true,
metadata: true,
iCalUID: true,
};
export type BookingSelectResult = Prisma.BookingGetPayload<{