Fixed invalid busy times query logic (#7243)
* Fixed invalid busy times query logic * Adjusted collective test
This commit is contained in:
@@ -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[]);
|
||||
|
||||
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user