Fixed invalid busy times query logic (#7243)

* Fixed invalid busy times query logic

* Adjusted collective test
This commit is contained in:
Alex van Andel
2023-02-21 02:45:10 +00:00
committed by GitHub
parent 831506447a
commit 51664bf733
2 changed files with 97 additions and 90 deletions
+32 -15
View File
@@ -185,6 +185,7 @@ type InputEventType = {
slotInterval?: number;
minimumBookingNotice?: number;
users?: { id: number }[];
hosts?: { id: number }[];
schedulingType?: SchedulingType;
beforeEventBuffer?: number;
afterEventBuffer?: number;
@@ -197,6 +198,7 @@ type InputBooking = {
endTime: string;
title?: string;
status: BookingStatus;
attendees?: { email: string }[];
};
type InputHost = {
@@ -789,6 +791,14 @@ describe("getSchedule", () => {
id: 1,
slotInterval: 45,
schedulingType: "COLLECTIVE",
hosts: [
{
id: 101,
},
{
id: 102,
},
],
},
// A default Event Type which this user owns
{
@@ -803,11 +813,21 @@ describe("getSchedule", () => {
id: 101,
schedules: [TestData.schedules.IstWorkHours],
},
{
...TestData.users.example,
id: 102,
schedules: [TestData.schedules.IstWorkHours],
},
],
bookings: [
// Create a booking on our Collective Event Type
{
// userId: XX, <- No owner since this is a Collective Event Type
userId: 101,
attendees: [
{
email: "IntegrationTestUser102@example.com",
},
],
eventTypeId: 1,
status: "ACCEPTED",
startTime: `${plus2DateString}T04:00:00.000Z`,
@@ -1215,15 +1235,8 @@ async function addBookings(bookings: InputBooking[], eventTypes: InputEventType[
// @ts-ignore
statusIn.includes(booking.status) && booking.userId === where.OR[0].userId;
// ~~ SECOND CONDITION checks whether this user is a host of this Event Type
// and that booking.status is a match for the returned query
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
let secondConditionMatches = where.OR[1].eventTypeId.in.includes(booking.eventTypeId);
secondConditionMatches = secondConditionMatches && statusIn.includes(booking.status);
// We return this booking if either condition is met
return firstConditionMatches || secondConditionMatches;
return firstConditionMatches;
})
.map((booking) => ({
uid: uuidv4(),
@@ -1236,11 +1249,17 @@ async function addBookings(bookings: InputBooking[], eventTypes: InputEventType[
});
}
function addHosts(hosts: InputHost[]) {
prismaMock.host.findMany.mockResolvedValue(hosts);
}
function addUsers(users: InputUser[]) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
prismaMock.user.findUniqueOrThrow.mockImplementation((findUniqueArgs) => {
return new Promise((resolve) => {
resolve({
email: `IntegrationTestUser${findUniqueArgs?.where.id}@example.com`,
} as unknown as PrismaUser);
});
});
prismaMock.user.findMany.mockResolvedValue(
users.map((user) => {
return {
@@ -1271,8 +1290,6 @@ function createBookingScenario(data: ScenarioData) {
addUsers(data.users);
addHosts(data.hosts);
const eventType = addEventTypes(data.eventTypes, data.users);
if (data.apps) {
prismaMock.app.findMany.mockResolvedValue(data.apps as PrismaApp[]);
+65 -75
View File
@@ -1,4 +1,5 @@
import { BookingStatus, Credential } from "@prisma/client";
import type { Credential } from "@prisma/client";
import { BookingStatus } from "@prisma/client";
import { getBusyCalendarTimes } from "@calcom/core/CalendarManager";
import dayjs from "@calcom/dayjs";
@@ -34,16 +35,23 @@ export async function getBusyTimes(params: {
status: BookingStatus.ACCEPTED,
})}`
);
// get user email for attendee checking.
const user = await prisma.user.findUniqueOrThrow({
where: {
id: userId,
},
select: {
email: true,
},
});
/**
* A user is considered busy within a given time period if there
* is a booking they own OR host.
* is a booking they own OR attend.
*
* Therefore this query does the following:
* - Performs a query for all EventType id's where this user is a host
* - Performs a query for all bookings where:
* Performs a query for all bookings where:
* - The given booking is owned by this user, or..
* - The given booking's EventType is hosted by this user
* - The current user has a different booking at this time he/she attends
*
* See further discussion within this GH issue:
* https://github.com/calcom/cal.com/issues/6374
@@ -52,79 +60,61 @@ export async function getBusyTimes(params: {
* logic within getSchedule.test.ts:addBookings
*/
performance.mark("prismaBookingGetStart");
const busyTimes: EventBusyDetails[] =
// Getting all EventTypes ID's hosted by this user
await prisma.host
.findMany({
where: {
userId: {
equals: userId,
const sharedQuery = {
startTime: { gte: new Date(startTime) },
endTime: { lte: new Date(endTime) },
status: {
in: [BookingStatus.ACCEPTED],
},
};
// Find bookings that block this user from hosting further bookings.
const busyTimes: EventBusyDetails[] = await prisma.booking
.findMany({
where: {
OR: [
// User is primary host (individual events, or primary organizer)
{
...sharedQuery,
userId,
},
},
select: {
eventTypeId: true,
},
})
// Converting the response object into an array
.then((thisUserHostedEvents) => thisUserHostedEvents.map((e) => e.eventTypeId))
// Finding all bookings owned OR hosted by this user
.then((thisUserHostedEventIds) => {
// This gets applied to both conditions
const sharedQuery = {
startTime: { gte: new Date(startTime) },
endTime: { lte: new Date(endTime) },
status: {
in: [BookingStatus.ACCEPTED],
},
};
return prisma.booking.findMany({
where: {
OR: [
// Bookings owned by this user
{
...sharedQuery,
userId,
},
// Bookings with an EventType ID that's hosted by this user
{
...sharedQuery,
eventTypeId: {
in: thisUserHostedEventIds,
},
},
],
},
select: {
id: true,
startTime: true,
endTime: true,
title: true,
eventType: {
select: {
id: true,
afterEventBuffer: true,
beforeEventBuffer: true,
// The current user has a different booking at this time he/she attends
{
...sharedQuery,
attendees: {
some: {
email: user.email,
},
},
},
});
})
.then((bookings) =>
bookings.map(({ startTime, endTime, title, id, eventType }) => ({
start: dayjs(startTime)
.subtract((eventType?.beforeEventBuffer || 0) + (afterEventBuffer || 0), "minute")
.toDate(),
end: dayjs(endTime)
.add((eventType?.afterEventBuffer || 0) + (beforeEventBuffer || 0), "minute")
.toDate(),
title,
source: `eventType-${eventType?.id}-booking-${id}`,
}))
);
],
},
select: {
id: true,
startTime: true,
endTime: true,
title: true,
eventType: {
select: {
id: true,
afterEventBuffer: true,
beforeEventBuffer: true,
},
},
},
})
.then((bookings) =>
bookings.map(({ startTime, endTime, title, id, eventType }) => ({
start: dayjs(startTime)
.subtract((eventType?.beforeEventBuffer || 0) + (afterEventBuffer || 0), "minute")
.toDate(),
end: dayjs(endTime)
.add((eventType?.afterEventBuffer || 0) + (beforeEventBuffer || 0), "minute")
.toDate(),
title,
source: `eventType-${eventType?.id}-booking-${id}`,
}))
);
logger.silly(`Busy Time from Cal Bookings ${JSON.stringify(busyTimes)}`);
performance.mark("prismaBookingGetEnd");
performance.measure(`prisma booking get took $1'`, "prismaBookingGetStart", "prismaBookingGetEnd");