From 76607460688749c05f0ee558dfb2d1e8ef385d26 Mon Sep 17 00:00:00 2001 From: Joe Au-Yeung Date: Tue, 11 Feb 2025 23:51:49 -0500 Subject: [PATCH 01/11] Add `rerouting` to `AssignmentReasonRecorder` --- .../AssignmentReasonRecorder.ts | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/packages/features/ee/round-robin/assignmentReason/AssignmentReasonRecorder.ts b/packages/features/ee/round-robin/assignmentReason/AssignmentReasonRecorder.ts index 65e39761295694..8273371e1a80b7 100644 --- a/packages/features/ee/round-robin/assignmentReason/AssignmentReasonRecorder.ts +++ b/packages/features/ee/round-robin/assignmentReason/AssignmentReasonRecorder.ts @@ -169,4 +169,25 @@ export default class AssignmentReasonRecorder { }, }); } + + static async rerouting({ bookingId, reroutedByEmail }: { bookingId: number; reroutedByEmail: string }) { + const reroutedBy = await prisma.user.findFirst({ + where: { + email: reroutedByEmail, + }, + select: { + id: true, + }, + }); + + const reasonString = `Rerouted by: ${reroutedBy?.id || "team member"}. `; + + await prisma.assignmentReason.create({ + data: { + bookingId: bookingId, + reasonEnum: AssignmentReasonEnum.REROUTED, + reasonString, + }, + }); + } } From 293d379fd2a2a368a74efdbbe71aa7087727ddc0 Mon Sep 17 00:00:00 2001 From: Joe Au-Yeung Date: Tue, 11 Feb 2025 23:52:16 -0500 Subject: [PATCH 02/11] Pass session email as `rescheduledBy` --- apps/web/components/dialog/RerouteDialog.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/web/components/dialog/RerouteDialog.tsx b/apps/web/components/dialog/RerouteDialog.tsx index ca72e40dc83ad2..58e03c148e723a 100644 --- a/apps/web/components/dialog/RerouteDialog.tsx +++ b/apps/web/components/dialog/RerouteDialog.tsx @@ -1,4 +1,5 @@ import { useMutation } from "@tanstack/react-query"; +import { useSession } from "next-auth/react"; import Link from "next/link"; import { useRouter } from "next/navigation"; import { useState } from "react"; @@ -277,6 +278,7 @@ const NewRoutingManager = ({ const { t } = useLocale(); const router = useRouter(); const bookerUrl = useBookerUrl(); + const session = useSession(); const teamMemberIdsMatchingAttributeLogic = teamMembersMatchingAttributeLogic?.data ?.map((member) => member.id) @@ -467,6 +469,7 @@ const NewRoutingManager = ({ // TODO: Long term, we should refactor handleNewBooking and use a different route specific for this purpose, createBookingMutation.mutate({ rescheduleUid: booking.uid, + rescheduledBy: session?.data?.user?.email, // rescheduleReason, reroutingFormResponses: reroutingFormResponses, ...getTimeslotFields(), From ff2c83c726be274a9457d9b59a864f612da68681 Mon Sep 17 00:00:00 2001 From: Joe Au-Yeung Date: Tue, 11 Feb 2025 23:53:13 -0500 Subject: [PATCH 03/11] Write reassign reason --- packages/features/bookings/lib/handleNewBooking.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/features/bookings/lib/handleNewBooking.ts b/packages/features/bookings/lib/handleNewBooking.ts index a30ad16c143dc2..8277c63c04afce 100644 --- a/packages/features/bookings/lib/handleNewBooking.ts +++ b/packages/features/bookings/lib/handleNewBooking.ts @@ -1206,7 +1206,17 @@ async function handler( // If it's a round robin event, record the reason for the host assignment if (eventType.schedulingType === SchedulingType.ROUND_ROBIN) { - if (reqBody.crmOwnerRecordType && reqBody.crmAppSlug && contactOwnerEmail && routingFormResponseId) { + if (isReroutingCase) { + await AssignmentReasonRecorder.rerouting({ + bookingId: booking.id, + reroutedBy: reqBody.rescheduledBy, + }); + } else if ( + reqBody.crmOwnerRecordType && + reqBody.crmAppSlug && + contactOwnerEmail && + routingFormResponseId + ) { await monitorCallbackAsync(AssignmentReasonRecorder.CRMOwnership, { bookingId: booking.id, crmAppSlug: reqBody.crmAppSlug, From 9b76d155ef9c2b7ba1865643e10a2a1bb3e84ab9 Mon Sep 17 00:00:00 2001 From: Joe Au-Yeung Date: Wed, 12 Feb 2025 00:00:26 -0500 Subject: [PATCH 04/11] Edit message --- .../ee/round-robin/assignmentReason/AssignmentReasonRecorder.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/features/ee/round-robin/assignmentReason/AssignmentReasonRecorder.ts b/packages/features/ee/round-robin/assignmentReason/AssignmentReasonRecorder.ts index 8273371e1a80b7..8281af65d7a276 100644 --- a/packages/features/ee/round-robin/assignmentReason/AssignmentReasonRecorder.ts +++ b/packages/features/ee/round-robin/assignmentReason/AssignmentReasonRecorder.ts @@ -180,7 +180,7 @@ export default class AssignmentReasonRecorder { }, }); - const reasonString = `Rerouted by: ${reroutedBy?.id || "team member"}. `; + const reasonString = `Rerouted by user: ${reroutedBy?.id || "team member"}`; await prisma.assignmentReason.create({ data: { From 362ec40ee0237e91b6edf153577dbf0accc2d329 Mon Sep 17 00:00:00 2001 From: Joe Au-Yeung Date: Wed, 12 Feb 2025 00:47:18 -0500 Subject: [PATCH 05/11] Use `routingFormRoute` when rerouting --- .../AssignmentReasonRecorder.ts | 47 ++++++++++--------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/packages/features/ee/round-robin/assignmentReason/AssignmentReasonRecorder.ts b/packages/features/ee/round-robin/assignmentReason/AssignmentReasonRecorder.ts index 8281af65d7a276..6a856e59c92a54 100644 --- a/packages/features/ee/round-robin/assignmentReason/AssignmentReasonRecorder.ts +++ b/packages/features/ee/round-robin/assignmentReason/AssignmentReasonRecorder.ts @@ -13,11 +13,15 @@ export default class AssignmentReasonRecorder { routingFormResponseId, organizerId, teamId, + isRerouting, + reroutedByEmail, }: { bookingId: number; routingFormResponseId: number; organizerId: number; teamId: number; + isRerouting: boolean; + reroutedByEmail: string | null; }) { // Get the routing form data const routingFormResponse = await prisma.app_RoutingForms_FormResponse.findFirst({ @@ -98,11 +102,29 @@ export default class AssignmentReasonRecorder { } } + let reroutedByUserId: number | null = null; + if (isRerouting && reroutedByEmail) { + const userQuery = await prisma.user.findFirst({ + where: { + email: reroutedByEmail, + }, + select: { + id: true, + }, + }); + + if (userQuery) { + reroutedByUserId = userQuery.id; + } + } + await prisma.assignmentReason.create({ data: { bookingId: bookingId, - reasonEnum: AssignmentReasonEnum.ROUTING_FORM_ROUTING, - reasonString: attributeValues.join(", "), + reasonEnum: isRerouting ? AssignmentReasonEnum.REROUTED : AssignmentReasonEnum.ROUTING_FORM_ROUTING, + reasonString: `${ + reroutedByUserId ? `Rerouted by user: ${reroutedByUserId}` : "" + } ${attributeValues.join(", ")}`, }, }); } @@ -169,25 +191,4 @@ export default class AssignmentReasonRecorder { }, }); } - - static async rerouting({ bookingId, reroutedByEmail }: { bookingId: number; reroutedByEmail: string }) { - const reroutedBy = await prisma.user.findFirst({ - where: { - email: reroutedByEmail, - }, - select: { - id: true, - }, - }); - - const reasonString = `Rerouted by user: ${reroutedBy?.id || "team member"}`; - - await prisma.assignmentReason.create({ - data: { - bookingId: bookingId, - reasonEnum: AssignmentReasonEnum.REROUTED, - reasonString, - }, - }); - } } From 48198c3612668e1538846866a76438c7a3e5d6f0 Mon Sep 17 00:00:00 2001 From: Joe Au-Yeung Date: Wed, 12 Feb 2025 00:49:12 -0500 Subject: [PATCH 06/11] Pass isRerouting & reroutedByEmail to `routingFormRoute` method --- packages/features/bookings/lib/handleNewBooking.ts | 14 +++----------- .../assignmentReason/AssignmentReasonRecorder.ts | 2 +- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/packages/features/bookings/lib/handleNewBooking.ts b/packages/features/bookings/lib/handleNewBooking.ts index 8277c63c04afce..194158086b479d 100644 --- a/packages/features/bookings/lib/handleNewBooking.ts +++ b/packages/features/bookings/lib/handleNewBooking.ts @@ -1206,17 +1206,7 @@ async function handler( // If it's a round robin event, record the reason for the host assignment if (eventType.schedulingType === SchedulingType.ROUND_ROBIN) { - if (isReroutingCase) { - await AssignmentReasonRecorder.rerouting({ - bookingId: booking.id, - reroutedBy: reqBody.rescheduledBy, - }); - } else if ( - reqBody.crmOwnerRecordType && - reqBody.crmAppSlug && - contactOwnerEmail && - routingFormResponseId - ) { + if (reqBody.crmOwnerRecordType && reqBody.crmAppSlug && contactOwnerEmail && routingFormResponseId) { await monitorCallbackAsync(AssignmentReasonRecorder.CRMOwnership, { bookingId: booking.id, crmAppSlug: reqBody.crmAppSlug, @@ -1230,6 +1220,8 @@ async function handler( routingFormResponseId, organizerId: organizerUser.id, teamId, + isRerouting: isReroutingCase, + reroutedByEmail: reqBody.rescheduledBy, }); } } diff --git a/packages/features/ee/round-robin/assignmentReason/AssignmentReasonRecorder.ts b/packages/features/ee/round-robin/assignmentReason/AssignmentReasonRecorder.ts index 6a856e59c92a54..0810464f4f057f 100644 --- a/packages/features/ee/round-robin/assignmentReason/AssignmentReasonRecorder.ts +++ b/packages/features/ee/round-robin/assignmentReason/AssignmentReasonRecorder.ts @@ -21,7 +21,7 @@ export default class AssignmentReasonRecorder { organizerId: number; teamId: number; isRerouting: boolean; - reroutedByEmail: string | null; + reroutedByEmail?: string | null; }) { // Get the routing form data const routingFormResponse = await prisma.app_RoutingForms_FormResponse.findFirst({ From 63d507dd184eef2f1683fdf622b9cc46a3834fe1 Mon Sep 17 00:00:00 2001 From: Joe Au-Yeung Date: Wed, 12 Feb 2025 10:23:05 -0500 Subject: [PATCH 07/11] Type fix --- apps/web/components/dialog/RerouteDialog.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/components/dialog/RerouteDialog.tsx b/apps/web/components/dialog/RerouteDialog.tsx index 58e03c148e723a..d5e8e0d5528082 100644 --- a/apps/web/components/dialog/RerouteDialog.tsx +++ b/apps/web/components/dialog/RerouteDialog.tsx @@ -469,7 +469,7 @@ const NewRoutingManager = ({ // TODO: Long term, we should refactor handleNewBooking and use a different route specific for this purpose, createBookingMutation.mutate({ rescheduleUid: booking.uid, - rescheduledBy: session?.data?.user?.email, + rescheduledBy: session?.data?.user?.email ?? undefined, // rescheduleReason, reroutingFormResponses: reroutingFormResponses, ...getTimeslotFields(), From 0e5a21055a5eeb8aa8728c2bb5a04df63478173e Mon Sep 17 00:00:00 2001 From: Joe Au-Yeung Date: Thu, 13 Feb 2025 20:49:18 -0500 Subject: [PATCH 08/11] Fix test --- apps/web/components/dialog/__tests__/RerouteDialog.test.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/web/components/dialog/__tests__/RerouteDialog.test.tsx b/apps/web/components/dialog/__tests__/RerouteDialog.test.tsx index e5c68d786e9a73..21c7a2629b285d 100644 --- a/apps/web/components/dialog/__tests__/RerouteDialog.test.tsx +++ b/apps/web/components/dialog/__tests__/RerouteDialog.test.tsx @@ -1,4 +1,5 @@ import { act, fireEvent, render, screen } from "@testing-library/react"; +import { SessionProvider } from "next-auth/react"; import { vi } from "vitest"; import { RouteActionType } from "@calcom/app-store/routing-forms/zod"; @@ -383,7 +384,9 @@ describe("RerouteDialog", () => { describe("New Routing tests", () => { test("when verify_new_route is clicked, the form is submitted", async () => { render( - + + + ); fireEvent.click(screen.getByText("verify_new_route")); From abcaf7444cc399c0573c7acfb923f72290398de7 Mon Sep 17 00:00:00 2001 From: Joe Au-Yeung Date: Thu, 13 Feb 2025 20:57:41 -0500 Subject: [PATCH 09/11] Fix test --- .../components/dialog/__tests__/RerouteDialog.test.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/web/components/dialog/__tests__/RerouteDialog.test.tsx b/apps/web/components/dialog/__tests__/RerouteDialog.test.tsx index 21c7a2629b285d..d6da6a647b036b 100644 --- a/apps/web/components/dialog/__tests__/RerouteDialog.test.tsx +++ b/apps/web/components/dialog/__tests__/RerouteDialog.test.tsx @@ -405,7 +405,9 @@ describe("RerouteDialog", () => { describe("New tab rescheduling", () => { test("new tab is closed when new booking is rerouted", async () => { render( - + + + ); clickVerifyNewRouteButton(); clickRescheduleToTheNewEventWithDifferentTimeslotButton(); @@ -444,7 +446,9 @@ describe("RerouteDialog", () => { test("Rescheduling with same timeslot works", async () => { render( - + + + ); clickVerifyNewRouteButton(); clickRescheduleWithSameTimeslotOfChosenEventButton(); From 276b7cde14ef91efb8be630453a8279ed498757d Mon Sep 17 00:00:00 2001 From: Joe Au-Yeung Date: Thu, 13 Feb 2025 21:13:40 -0500 Subject: [PATCH 10/11] Fix test --- .../dialog/__tests__/RerouteDialog.test.tsx | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/apps/web/components/dialog/__tests__/RerouteDialog.test.tsx b/apps/web/components/dialog/__tests__/RerouteDialog.test.tsx index d6da6a647b036b..a2ba1a3b904da9 100644 --- a/apps/web/components/dialog/__tests__/RerouteDialog.test.tsx +++ b/apps/web/components/dialog/__tests__/RerouteDialog.test.tsx @@ -37,6 +37,11 @@ const mockOpen = vi.fn((_url: string) => { vi.stubGlobal("open", mockOpen); +const mockSession = { + expires: new Date(Date.now() + 2 * 86400).toISOString(), + user: userWhoBooked, +}; + vi.mock("@calcom/app-store/routing-forms/components/FormInputFields", () => ({ default: vi.fn(({ response, form, setResponse, disabledFields }) => { return ( @@ -384,7 +389,7 @@ describe("RerouteDialog", () => { describe("New Routing tests", () => { test("when verify_new_route is clicked, the form is submitted", async () => { render( - + ); @@ -405,7 +410,7 @@ describe("RerouteDialog", () => { describe("New tab rescheduling", () => { test("new tab is closed when new booking is rerouted", async () => { render( - + ); @@ -446,7 +451,7 @@ describe("RerouteDialog", () => { test("Rescheduling with same timeslot works", async () => { render( - + ); From 66e07feb04f6bf1278c88ef6a8e1b85ad2ad701a Mon Sep 17 00:00:00 2001 From: Joe Au-Yeung Date: Thu, 13 Feb 2025 21:56:43 -0500 Subject: [PATCH 11/11] Type fix --- .../components/dialog/__tests__/RerouteDialog.test.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/apps/web/components/dialog/__tests__/RerouteDialog.test.tsx b/apps/web/components/dialog/__tests__/RerouteDialog.test.tsx index a2ba1a3b904da9..368141a76180d3 100644 --- a/apps/web/components/dialog/__tests__/RerouteDialog.test.tsx +++ b/apps/web/components/dialog/__tests__/RerouteDialog.test.tsx @@ -1,4 +1,5 @@ import { act, fireEvent, render, screen } from "@testing-library/react"; +import type { Session } from "next-auth"; import { SessionProvider } from "next-auth/react"; import { vi } from "vitest"; @@ -39,8 +40,12 @@ vi.stubGlobal("open", mockOpen); const mockSession = { expires: new Date(Date.now() + 2 * 86400).toISOString(), - user: userWhoBooked, -}; + user: { + id: 1, + name: "Test User", + email: "user@example.com", + }, +} as Session; vi.mock("@calcom/app-store/routing-forms/components/FormInputFields", () => ({ default: vi.fn(({ response, form, setResponse, disabledFields }) => {