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:
@@ -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),
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user