fix: round robin re assignment google meet bug (#17378)
This commit is contained in:
1 parent
8605fb4518
commit
04a49e74d4
5 files changed
+221
-42
No files matched your search
@@ -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<{
|
||||
|
||||
Reference in new issue
Block a user