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
This commit is contained in:
Keith Williams
2024-10-23 14:25:10 +00:00
committed by GitHub
parent 058d58a6f3
commit 596dc3fbd7
2 changed files with 164 additions and 93 deletions
+96 -93
View File
@@ -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<ReturnType<typeof getUser>>;
type GetUser = Awaited<ReturnType<typeof getUser>>;
export const getCurrentSeats = async (
...args: Parameters<typeof _getCurrentSeats>
@@ -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<OutOfOfficeEntry, "id" | "start" | "end"> & {
user: Pick<User, "id" | "name">;
toUser: Pick<User, "id" | "username" | "name"> | null;
reason: Pick<OutOfOfficeReason, "id" | "emoji" | "reason"> | 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<typeof _getOutOfOfficeDays>
): Promise<ReturnType<typeof _getOutOfOfficeDays>> => {
return monitorCallbackAsync(_getOutOfOfficeDays, ...args);
};
const _getOutOfOfficeDays = async ({
userId,
dateFrom,
dateTo,
availability,
}: GetUserAvailabilityParamsDTO): Promise<IOutOfOfficeData> => {
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<GetUserAvailabilityQuery, "userId" | "username">;
initialData?: Omit<GetUserAvailabilityInitialData, "user">;
@@ -690,6 +692,7 @@ const _getUsersAvailability = async ({
...initialData,
user,
currentBookings: user.currentBookings,
outOfOfficeDays: user.outOfOfficeDays,
}
: undefined
)
@@ -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<ReturnType<typeof getBusyTimesForLimitChecks>> = [];
@@ -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),
};
});