fix: fixes for round robin reassignment (#17233)

* fixes scheduled email for new host

* fixes emails and calendar event

* type fixes

* add test to check if caneclation email is called

---------

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: sean <sean@brydon.io>
This commit is contained in:
Carina Wollendorfer
2024-10-22 10:08:25 -03:00
committed by GitHub
co-authored by CarinaWolli sean
parent 1fc376c51a
commit 1ee4fa0906
3 changed files with 140 additions and 41 deletions
@@ -247,4 +247,78 @@ describe("roundRobinManualReassignment test", () => {
expect(attendees.some((attendee) => attendee.email === currentRRHost.email)).toBe(false);
expect(attendees.some((attendee) => attendee.email === newHost.email)).toBe(true);
});
test("sends cancellation email to previous RR host when reassigning", async ({ emails }) => {
const roundRobinManualReassignment = (await import("./roundRobinManualReassignment")).default;
const EventManager = (await import("@calcom/core/EventManager")).default;
const eventManagerSpy = vi.spyOn(EventManager.prototype as any, "reschedule");
eventManagerSpy.mockResolvedValue({ referencesToCreate: [] });
const sendRoundRobinCancelledEmailsAndSMSSpy = vi.spyOn(
await import("@calcom/emails"),
"sendRoundRobinCancelledEmailsAndSMS"
);
const users = testUsers;
const originalHost = users[0];
const previousRRHost = users[1];
const newHost = users[2];
const { dateString: dateStringPlusOne } = getDate({ dateIncrement: 1 });
const bookingToReassignUid = "booking-to-reassign-with-previous-rr-host";
await createBookingScenario(
getScenarioData({
eventTypes: [
{
id: 1,
slug: "round-robin-event",
schedulingType: SchedulingType.ROUND_ROBIN,
length: 45,
users: users.map((user) => ({ id: user.id })),
hosts: users.map((user) => ({ userId: user.id, isFixed: false })),
},
],
bookings: [
{
id: 124,
eventTypeId: 1,
userId: originalHost.id,
uid: bookingToReassignUid,
status: BookingStatus.ACCEPTED,
startTime: `${dateStringPlusOne}T05:00:00.000Z`,
endTime: `${dateStringPlusOne}T05:15:00.000Z`,
attendees: [
getMockBookingAttendee({
id: 2,
name: "attendee",
email: "attendee@test.com",
locale: "en",
timeZone: "Asia/Kolkata",
}),
getMockBookingAttendee({
id: previousRRHost.id,
name: previousRRHost.name,
email: previousRRHost.email,
locale: "en",
timeZone: previousRRHost.timeZone,
}),
],
},
],
organizer: originalHost,
usersApartFromOrganizer: users.slice(1),
})
);
await roundRobinManualReassignment({
bookingId: 124,
newUserId: newHost.id,
orgId: null,
});
expect(sendRoundRobinCancelledEmailsAndSMSSpy).toHaveBeenCalledTimes(1);
});
});
@@ -104,6 +104,24 @@ export const roundRobinManualReassignment = async ({
const newUserT = await getTranslation(newUser.locale || "en", "common");
const originalOrganizerT = await getTranslation(originalOrganizer.locale || "en", "common");
const roundRobinHosts = eventType.hosts.filter((host) => !host.isFixed);
const attendeeEmailsSet = new Set(booking.attendees.map((attendee) => attendee.email));
// Find the current round robin host assigned
const previousRRHost = (() => {
for (const host of roundRobinHosts) {
if (host.user.id === booking.userId) {
return host.user;
}
if (attendeeEmailsSet.has(host.user.email)) {
return host.user;
}
}
})();
const previousRRHostT = await getTranslation(previousRRHost?.locale || "en", "common");
if (hasOrganizerChanged) {
const bookingResponses = booking.responses;
const responseSchema = getBookingResponsesSchema({
@@ -166,24 +184,38 @@ export const roundRobinManualReassignment = async ({
hasOrganizerChanged,
});
const organizer = hasOrganizerChanged ? newUser : booking.user ?? newUser;
const organizerT = await getTranslation(organizer?.locale || "en", "common");
const teamMembers = await getTeamMembers({
eventTypeHosts,
attendees: booking.attendees,
organizer: newUser,
previousHost: originalOrganizer,
organizer,
previousHost: previousRRHost || null,
reassignedHost: newUser,
});
const attendeePromises = booking.attendees.map(async (attendee) => ({
email: attendee.email,
name: attendee.name,
timeZone: attendee.timeZone,
language: {
translate: await getTranslation(attendee.locale ?? "en", "common"),
locale: attendee.locale ?? "en",
},
phoneNumber: attendee.phoneNumber || undefined,
}));
const attendeePromises = [];
for (const attendee of booking.attendees) {
if (
attendee.email === newUser.email ||
attendee.email === previousRRHost?.email ||
teamMembers.some((member) => member.email === attendee.email)
) {
continue;
}
attendeePromises.push(
getTranslation(attendee.locale ?? "en", "common").then((tAttendee) => ({
email: attendee.email,
name: attendee.name,
timeZone: attendee.timeZone,
language: { translate: tAttendee, locale: attendee.locale ?? "en" },
phoneNumber: attendee.phoneNumber || undefined,
}))
);
}
const attendeeList = await Promise.all(attendeePromises);
@@ -194,10 +226,10 @@ export const roundRobinManualReassignment = async ({
startTime: dayjs(booking.startTime).utc().format(),
endTime: dayjs(booking.endTime).utc().format(),
organizer: {
email: newUser.email,
name: newUser.name || "",
timeZone: newUser.timeZone,
language: { translate: newUserT, locale: newUser.locale || "en" },
email: organizer.email,
name: organizer.name || "",
timeZone: organizer.timeZone,
language: { translate: organizerT, locale: organizer.locale || "en" },
},
attendees: attendeeList,
uid: booking.uid,
@@ -242,8 +274,10 @@ export const roundRobinManualReassignment = async ({
newReferencesToCreate,
});
const { cancellationReason, ...evtWithoutCancellationReason } = evt;
// Send emails
await sendRoundRobinScheduledEmailsAndSMS(evt, [
await sendRoundRobinScheduledEmailsAndSMS(evtWithoutCancellationReason, [
{
...newUser,
name: newUser.name || "",
@@ -262,19 +296,21 @@ export const roundRobinManualReassignment = async ({
language: { translate: originalOrganizerT, locale: originalOrganizer.locale || "en" },
};
await sendRoundRobinCancelledEmailsAndSMS(
cancelledEvt,
[
{
...originalOrganizer,
name: originalOrganizer.name || "",
username: originalOrganizer.username || "",
timeFormat: getTimeFormatStringFromUserTimeFormat(originalOrganizer.timeFormat),
language: { translate: originalOrganizerT, locale: originalOrganizer.locale || "en" },
},
],
eventType?.metadata as EventTypeMetadata
);
if (previousRRHost) {
await sendRoundRobinCancelledEmailsAndSMS(
cancelledEvt,
[
{
...previousRRHost,
name: previousRRHost.name || "",
username: previousRRHost.username || "",
timeFormat: getTimeFormatStringFromUserTimeFormat(previousRRHost.timeFormat),
language: { translate: previousRRHostT, locale: previousRRHost.locale || "en" },
},
],
eventType?.metadata as EventTypeMetadata
);
}
if (hasOrganizerChanged) {
// Handle changing workflows with organizer
@@ -151,17 +151,6 @@ export const roundRobinReassignment = async ({
reassignedHost: reassignedRRHost,
});
// Assume the RR host was labelled as a team member
if (reassignedRRHost.email !== organizer.email) {
teamMembers.push({
id: reassignedRRHost.id,
email: reassignedRRHost.email,
name: reassignedRRHost.name || "",
timeZone: reassignedRRHost.timeZone,
language: { translate: reassignedRRHostT, locale: reassignedRRHost.locale ?? "en" },
});
}
const attendeePromises = [];
for (const attendee of booking.attendees) {
if (