From 596dc3fbd72c3eb7e35a924651ad47bfe4082ad4 Mon Sep 17 00:00:00 2001 From: Keith Williams Date: Wed, 23 Oct 2024 11:25:10 -0300 Subject: [PATCH] perf: Load OOO in bulk for getSlots (#17270) * perf: Load OOO in bulk for getSlots * Added back the failsafe of running the query when no initial data is passed in * Changed length check to be more explicit --- packages/core/getUserAvailability.ts | 189 +++++++++--------- .../trpc/server/routers/viewer/slots/util.ts | 68 +++++++ 2 files changed, 164 insertions(+), 93 deletions(-) diff --git a/packages/core/getUserAvailability.ts b/packages/core/getUserAvailability.ts index 7639e580fa..a4172e58cb 100644 --- a/packages/core/getUserAvailability.ts +++ b/packages/core/getUserAvailability.ts @@ -1,4 +1,11 @@ -import type { Booking, Prisma, EventType as PrismaEventType } from "@prisma/client"; +import type { + Booking, + Prisma, + OutOfOfficeEntry, + OutOfOfficeReason, + User, + EventType as PrismaEventType, +} from "@prisma/client"; import { z } from "zod"; import type { Dayjs } from "@calcom/dayjs"; @@ -152,7 +159,7 @@ const _getUser = async (where: Prisma.UserWhereInput) => { }); }; -type User = Awaited>; +type GetUser = Awaited>; export const getCurrentSeats = async ( ...args: Parameters @@ -238,7 +245,7 @@ const _getUserAvailability = async function getUsersWorkingHoursLifeTheUniverseA returnDateOverrides: boolean; }, initialData?: { - user?: User; + user?: GetUser; eventType?: EventType; currentSeats?: CurrentSeats; rescheduleUid?: string | null; @@ -251,6 +258,11 @@ const _getUserAvailability = async function getUsersWorkingHoursLifeTheUniverseA seatsReferences: number; }; })[]; + outOfOfficeDays?: (Pick & { + user: Pick; + toUser: Pick | null; + reason: Pick | null; + })[]; busyTimesFromLimitsBookings: EventBusyDetails[]; } ) { @@ -297,10 +309,10 @@ const _getUserAvailability = async function getUsersWorkingHoursLifeTheUniverseA )[0]; const hostSchedule = eventType?.hosts?.find((host) => host.user.id === user.id)?.schedule; - + // TODO: It uses default timezone of user. Should we use timezone of team ? const fallbackTimezoneIfScheduleIsMissing = eventType?.timeZone || user.timeZone; - + const fallbackSchedule = { availability: [ { @@ -312,10 +324,12 @@ const _getUserAvailability = async function getUsersWorkingHoursLifeTheUniverseA ], id: 0, - timeZone: fallbackTimezoneIfScheduleIsMissing + timeZone: fallbackTimezoneIfScheduleIsMissing, }; - const schedule = (eventType?.schedule ? eventType.schedule : hostSchedule ? hostSchedule : userSchedule) ?? fallbackSchedule + const schedule = + (eventType?.schedule ? eventType.schedule : hostSchedule ? hostSchedule : userSchedule) ?? + fallbackSchedule; const timeZone = schedule?.timeZone || fallbackTimezoneIfScheduleIsMissing; const bookingLimits = parseBookingLimit(eventType?.bookingLimits); @@ -445,12 +459,74 @@ const _getUserAvailability = async function getUsersWorkingHoursLifeTheUniverseA } } - const datesOutOfOffice = await getOutOfOfficeDays({ - userId: user.id, - dateFrom, - dateTo, - availability, - }); + const outOfOfficeDays = + initialData?.outOfOfficeDays ?? + (await prisma.outOfOfficeEntry.findMany({ + where: { + userId: user.id, + OR: [ + // outside of range + // (start <= 'dateTo' AND end >= 'dateFrom') + { + start: { + lte: dateTo.toISOString(), + }, + end: { + gte: dateFrom.toISOString(), + }, + }, + // start is between dateFrom and dateTo but end is outside of range + // (start <= 'dateTo' AND end >= 'dateTo') + { + start: { + lte: dateTo.toISOString(), + }, + + end: { + gte: dateTo.toISOString(), + }, + }, + // end is between dateFrom and dateTo but start is outside of range + // (start <= 'dateFrom' OR end <= 'dateTo') + { + start: { + lte: dateFrom.toISOString(), + }, + + end: { + lte: dateTo.toISOString(), + }, + }, + ], + }, + select: { + id: true, + start: true, + end: true, + user: { + select: { + id: true, + name: true, + }, + }, + toUser: { + select: { + id: true, + username: true, + name: true, + }, + }, + reason: { + select: { + id: true, + emoji: true, + reason: true, + }, + }, + }, + })); + + const datesOutOfOffice: IOutOfOfficeData = calculateOutOfOfficeRanges(outOfOfficeDays, availability); const { dateRanges, oooExcludedDateRanges } = buildDateRanges({ dateFrom, @@ -520,9 +596,6 @@ const _getPeriodStartDatesBetween = (dateFrom: Dayjs, dateTo: Dayjs, period: Int }; interface GetUserAvailabilityParamsDTO { - userId: number; - dateFrom: Dayjs; - dateTo: Dayjs; availability: (DateOverride | WorkingHours)[]; } @@ -546,83 +619,11 @@ export interface IOutOfOfficeData { }; } -const getOutOfOfficeDays = async ( - ...args: Parameters -): Promise> => { - return monitorCallbackAsync(_getOutOfOfficeDays, ...args); -}; - -const _getOutOfOfficeDays = async ({ - userId, - dateFrom, - dateTo, - availability, -}: GetUserAvailabilityParamsDTO): Promise => { - const outOfOfficeDays = await prisma.outOfOfficeEntry.findMany({ - where: { - userId, - OR: [ - // outside of range - // (start <= 'dateTo' AND end >= 'dateFrom') - { - start: { - lte: dateTo.toISOString(), - }, - end: { - gte: dateFrom.toISOString(), - }, - }, - // start is between dateFrom and dateTo but end is outside of range - // (start <= 'dateTo' AND end >= 'dateTo') - { - start: { - lte: dateTo.toISOString(), - }, - - end: { - gte: dateTo.toISOString(), - }, - }, - // end is between dateFrom and dateTo but start is outside of range - // (start <= 'dateFrom' OR end <= 'dateTo') - { - start: { - lte: dateFrom.toISOString(), - }, - - end: { - lte: dateTo.toISOString(), - }, - }, - ], - }, - select: { - id: true, - start: true, - end: true, - user: { - select: { - id: true, - name: true, - }, - }, - toUser: { - select: { - id: true, - username: true, - name: true, - }, - }, - reason: { - select: { - id: true, - emoji: true, - reason: true, - }, - }, - }, - }); - if (!outOfOfficeDays.length) { +const calculateOutOfOfficeRanges = ( + outOfOfficeDays: GetUserAvailabilityInitialData["outOfOfficeDays"], + availability: GetUserAvailabilityParamsDTO["availability"] +): IOutOfOfficeData => { + if (!outOfOfficeDays || outOfOfficeDays.length === 0) { return {}; } @@ -673,6 +674,7 @@ const _getUsersAvailability = async ({ }: { users: (GetAvailabilityUser & { currentBookings?: GetUserAvailabilityInitialData["currentBookings"]; + outOfOfficeDays?: GetUserAvailabilityInitialData["outOfOfficeDays"]; })[]; query: Omit; initialData?: Omit; @@ -690,6 +692,7 @@ const _getUsersAvailability = async ({ ...initialData, user, currentBookings: user.currentBookings, + outOfOfficeDays: user.outOfOfficeDays, } : undefined ) diff --git a/packages/trpc/server/routers/viewer/slots/util.ts b/packages/trpc/server/routers/viewer/slots/util.ts index 3cce8a2ceb..4929813cca 100644 --- a/packages/trpc/server/routers/viewer/slots/util.ts +++ b/packages/trpc/server/routers/viewer/slots/util.ts @@ -614,6 +614,73 @@ export async function getAvailableSlots({ input, ctx }: GetScheduleOptions): Pro const currentBookingsAllUsers = [...resultOne, ...resultTwo, ...resultThree]; + const outOfOfficeDaysAllUsers = await prisma.outOfOfficeEntry.findMany({ + where: { + userId: { + in: allUserIds, + }, + OR: [ + // outside of range + // (start <= 'dateTo' AND end >= 'dateFrom') + { + start: { + lte: endTimeDate, + }, + end: { + gte: startTimeDate, + }, + }, + // start is between dateFrom and dateTo but end is outside of range + // (start <= 'dateTo' AND end >= 'dateTo') + { + start: { + lte: endTimeDate, + }, + + end: { + gte: endTimeDate, + }, + }, + // end is between dateFrom and dateTo but start is outside of range + // (start <= 'dateFrom' OR end <= 'dateTo') + { + start: { + lte: startTimeDate, + }, + + end: { + lte: endTimeDate, + }, + }, + ], + }, + select: { + id: true, + start: true, + end: true, + user: { + select: { + id: true, + name: true, + }, + }, + toUser: { + select: { + id: true, + username: true, + name: true, + }, + }, + reason: { + select: { + id: true, + emoji: true, + reason: true, + }, + }, + }, + }); + const bookingLimits = parseBookingLimit(eventType?.bookingLimits); const durationLimits = parseDurationLimit(eventType?.durationLimits); let busyTimesFromLimitsBookingsAllUsers: Awaited> = []; @@ -639,6 +706,7 @@ export async function getAvailableSlots({ input, ctx }: GetScheduleOptions): Pro const { attendees: _attendees, ...bookingWithoutAttendees } = bookings; return bookingWithoutAttendees; }), + outOfOfficeDays: outOfOfficeDaysAllUsers.filter((o) => o.user.id === currentUser.id), }; });