From 22aa083883594a94132daeb6a9f8b03fc4bcbc83 Mon Sep 17 00:00:00 2001 From: Alex van Andel Date: Fri, 3 Dec 2021 17:18:31 +0100 Subject: [PATCH] Adds eventTypeId as a parameter (#1217) --- components/booking/pages/BookingPage.tsx | 12 +-- jest.config.ts | 1 + lib/ensureArray.ts | 9 ++ lib/types/booking.ts | 3 +- pages/api/book/event.ts | 120 ++++++++++++++--------- test/lib/team-event-types.test.ts | 30 ++++++ 6 files changed, 116 insertions(+), 59 deletions(-) create mode 100644 lib/ensureArray.ts create mode 100644 test/lib/team-event-types.test.ts diff --git a/components/booking/pages/BookingPage.tsx b/components/booking/pages/BookingPage.tsx index e4405f2c4c..b7239f472e 100644 --- a/components/booking/pages/BookingPage.tsx +++ b/components/booking/pages/BookingPage.tsx @@ -19,6 +19,7 @@ import { createPaymentLink } from "@ee/lib/stripe/client"; import { asStringOrNull } from "@lib/asStringOrNull"; import { timeZone } from "@lib/clock"; +import { ensureArray } from "@lib/ensureArray"; import { useLocale } from "@lib/hooks/useLocale"; import useTheme from "@lib/hooks/useTheme"; import { LocationType } from "@lib/location"; @@ -137,19 +138,12 @@ const BookingPage = (props: BookingPageProps) => { }; }; - // can be shortened using .filter(), except TypeScript doesn't know what to make of the types. - const guests = router.query.guest - ? Array.isArray(router.query.guest) - ? router.query.guest - : [router.query.guest] - : []; - const bookingForm = useForm({ defaultValues: { name: (router.query.name as string) || "", email: (router.query.email as string) || "", notes: (router.query.notes as string) || "", - guests, + guests: ensureArray(router.query.guest), customInputs: props.eventType.customInputs.reduce( (customInputs, input) => ({ ...customInputs, @@ -213,7 +207,7 @@ const BookingPage = (props: BookingPageProps) => { timeZone: timeZone(), language: i18n.language, rescheduleUid, - user: router.query.user as string, + user: router.query.user, location: getLocationValue(booking), metadata, customInputs: Object.keys(booking.customInputs || {}).map((inputId) => ({ diff --git a/jest.config.ts b/jest.config.ts index 13aeb6d619..2f719b82db 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -14,6 +14,7 @@ const config: Config.InitialOptions = { "^@components(.*)$": "/components$1", "^@lib(.*)$": "/lib$1", "^@server(.*)$": "/server$1", + "^@ee(.*)$": "/ee$1", }, }; diff --git a/lib/ensureArray.ts b/lib/ensureArray.ts new file mode 100644 index 0000000000..538bdcfaf3 --- /dev/null +++ b/lib/ensureArray.ts @@ -0,0 +1,9 @@ +export function ensureArray(val: unknown): T[] { + if (Array.isArray(val)) { + return val; + } + if (typeof val === "undefined") { + return []; + } + return [val] as T[]; +} diff --git a/lib/types/booking.ts b/lib/types/booking.ts index 3917d45e3f..eac385b3a6 100644 --- a/lib/types/booking.ts +++ b/lib/types/booking.ts @@ -16,8 +16,7 @@ export type BookingCreateBody = { rescheduleUid?: string; start: string; timeZone: string; - users?: string[]; - user?: string; + user?: string | string[]; language: string; customInputs: { label: string; value: string }[]; metadata: { diff --git a/pages/api/book/event.ts b/pages/api/book/event.ts index 8ba9552eb7..c0d89cbfd2 100644 --- a/pages/api/book/event.ts +++ b/pages/api/book/event.ts @@ -17,6 +17,7 @@ import { sendRescheduledEmails, sendOrganizerRequestEmail, } from "@lib/emails/email-manager"; +import { ensureArray } from "@lib/ensureArray"; import { getErrorFromUnknown } from "@lib/errors"; import { getEventName } from "@lib/event"; import EventManager, { EventResult, PartialReference } from "@lib/events/EventManager"; @@ -123,6 +124,59 @@ function isOutOfBounds( } } +const userSelect = Prisma.validator()({ + select: { + id: true, + email: true, + name: true, + username: true, + timeZone: true, + credentials: true, + bufferTime: true, + }, +}); + +const getUserNameWithBookingCounts = async (eventTypeId: number, selectedUserNames: string[]) => { + const users = await prisma.user.findMany({ + where: { + username: { in: selectedUserNames }, + bookings: { + some: { + startTime: { + gt: new Date(), + }, + eventTypeId, + }, + }, + }, + select: { + id: true, + username: true, + }, + }); + + const userNamesWithBookingCounts = await Promise.all( + users.map(async (user) => ({ + username: user.username, + bookingCount: await prisma.booking.count({ + where: { + user: { + id: user.id, + }, + startTime: { + gt: new Date(), + }, + eventTypeId, + }, + }), + })) + ); + + return userNamesWithBookingCounts; +}; + +type User = Prisma.UserGetPayload; + export default async function handler(req: NextApiRequest, res: NextApiResponse) { const reqBody = req.body as BookingCreateBody; const eventTypeId = reqBody.eventTypeId; @@ -144,28 +198,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) return res.status(400).json(error); } - const userSelect = Prisma.validator()({ - id: true, - email: true, - name: true, - username: true, - timeZone: true, - credentials: true, - bufferTime: true, - }); - - const userData = Prisma.validator()({ - select: userSelect, - }); - const eventType = await prisma.eventType.findUnique({ where: { id: eventTypeId, }, select: { - users: { - select: userSelect, - }, + users: userSelect, team: { select: { id: true, @@ -198,43 +236,19 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) where: { id: eventType.userId, }, - select: userSelect, + ...userSelect, }); if (!eventTypeUser) return res.status(404).json({ message: "eventTypeUser.notFound" }); users.push(eventTypeUser); } if (eventType.schedulingType === SchedulingType.ROUND_ROBIN) { - const selectedUsers = reqBody.users || []; - const selectedUsersDataWithBookingsCount = await prisma.user.findMany({ - where: { - username: { in: selectedUsers }, - bookings: { - every: { - startTime: { - gt: new Date(), - }, - }, - }, - }, - select: { - username: true, - _count: { - select: { bookings: true }, - }, - }, - }); + const bookingCounts = await getUserNameWithBookingCounts( + eventTypeId, + ensureArray(reqBody.user) || users.map((user) => user.username) + ); - const bookingCounts = selectedUsersDataWithBookingsCount.map((userData) => ({ - username: userData.username, - bookingCount: userData._count?.bookings || 0, - })); - - if (!bookingCounts.length) users.slice(0, 1); - - const [firstMostAvailableUser] = bookingCounts.sort((a, b) => (a.bookingCount > b.bookingCount ? 1 : -1)); - const luckyUser = users.find((user) => user.username === firstMostAvailableUser?.username); - users = luckyUser ? [luckyUser] : users; + users = getLuckyUsers(users, bookingCounts); } const invitee = [{ email: reqBody.email, name: reqBody.name, timeZone: reqBody.timeZone }]; @@ -354,7 +368,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) let results: EventResult[] = []; let referencesToCreate: PartialReference[] = []; - type User = Prisma.UserGetPayload; let user: User | null = null; for (const currentUser of users) { @@ -555,3 +568,14 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) // booking successful return res.status(201).json(booking); } + +export function getLuckyUsers( + users: User[], + bookingCounts: Prisma.PromiseReturnType +) { + if (!bookingCounts.length) users.slice(0, 1); + + const [firstMostAvailableUser] = bookingCounts.sort((a, b) => (a.bookingCount > b.bookingCount ? 1 : -1)); + const luckyUser = users.find((user) => user.username === firstMostAvailableUser?.username); + return luckyUser ? [luckyUser] : users; +} diff --git a/test/lib/team-event-types.test.ts b/test/lib/team-event-types.test.ts new file mode 100644 index 0000000000..4e2fdff28f --- /dev/null +++ b/test/lib/team-event-types.test.ts @@ -0,0 +1,30 @@ +import { getLuckyUsers } from "../../pages/api/book/event"; + +it("can find lucky users", async () => { + const users = [ + { + id: 1, + username: "test", + name: "Test User", + credentials: [], + timeZone: "GMT", + bufferTime: 0, + email: "test@example.com", + }, + { + id: 2, + username: "test2", + name: "Test 2 User", + credentials: [], + timeZone: "GMT", + bufferTime: 0, + email: "test2@example.com", + }, + ]; + expect( + getLuckyUsers(users, [ + { username: "test", bookingCount: 2 }, + { username: "test2", bookingCount: 0 }, + ]) + ).toStrictEqual([users[1]]); +});