perf: improve checkIsAvailable in getAvailable slots (#18352)
* refactor: Update booking query to use userId and optimize availability check logic - Changed the booking query to use userId instead of nested user object for filtering active bookings. - Refactored availability check logic to improve readability and performance by using valueOf() for time comparisons and consolidating multiple checks into a single return statement. * fix: update tests * chore: use toDate() * tests: add checkavailable unit tests
This commit is contained in:
@@ -1,7 +1,10 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import type { GetAvailabilityUser } from "@calcom/core/getUserAvailability";
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import type { EventBusyDate } from "@calcom/types/Calendar";
|
||||
|
||||
import { checkIfIsAvailable } from "./util";
|
||||
import { getUsersWithCredentialsConsideringContactOwner } from "./util";
|
||||
|
||||
describe("getUsersWithCredentialsConsideringContactOwner", () => {
|
||||
@@ -88,3 +91,163 @@ describe("getUsersWithCredentialsConsideringContactOwner", () => {
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("checkIfIsAvailable", () => {
|
||||
const createTestData = (time: string) => ({
|
||||
time: dayjs(time),
|
||||
eventLength: 30,
|
||||
busy: [] as EventBusyDate[],
|
||||
});
|
||||
|
||||
describe("currentSeats handling", () => {
|
||||
it("should return true if slot exists in currentSeats", () => {
|
||||
const currentSeats: CurrentSeats = [
|
||||
{
|
||||
uid: "123",
|
||||
startTime: dayjs.utc("2023-01-01T09:00:00Z").toDate(),
|
||||
_count: { attendees: 1 },
|
||||
},
|
||||
];
|
||||
|
||||
const result = checkIfIsAvailable({
|
||||
...createTestData("2023-01-01T09:00:00Z"),
|
||||
currentSeats,
|
||||
});
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("busy time overlap scenarios", () => {
|
||||
it("should return true when no busy periods", () => {
|
||||
const result = checkIfIsAvailable(createTestData("2023-01-01T09:00:00Z"));
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it("should return true when busy period ends before slot starts", () => {
|
||||
const result = checkIfIsAvailable({
|
||||
...createTestData("2023-01-01T09:00:00Z"),
|
||||
busy: [
|
||||
{
|
||||
start: dayjs.utc("2023-01-01T08:00:00Z").toDate(),
|
||||
end: dayjs.utc("2023-01-01T08:30:00Z").toDate(),
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it("should return true when busy period starts after slot ends", () => {
|
||||
const result = checkIfIsAvailable({
|
||||
...createTestData("2023-01-01T09:00:00Z"),
|
||||
busy: [
|
||||
{
|
||||
start: dayjs.utc("2023-01-01T10:00:00Z").toDate(),
|
||||
end: dayjs.utc("2023-01-01T10:30:00Z").toDate(),
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it("should return false when slot start falls within busy period", () => {
|
||||
const result = checkIfIsAvailable({
|
||||
...createTestData("2023-01-01T09:15:00Z"),
|
||||
busy: [
|
||||
{
|
||||
start: dayjs.utc("2023-01-01T09:00:00Z").toDate(),
|
||||
end: dayjs.utc("2023-01-01T09:30:00Z").toDate(),
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it("should return false when slot end falls within busy period", () => {
|
||||
const result = checkIfIsAvailable({
|
||||
...createTestData("2023-01-01T08:45:00Z"),
|
||||
busy: [
|
||||
{
|
||||
start: dayjs.utc("2023-01-01T09:00:00Z").toDate(),
|
||||
end: dayjs.utc("2023-01-01T09:30:00Z").toDate(),
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it("should return false when busy period is completely contained within slot", () => {
|
||||
const result = checkIfIsAvailable({
|
||||
...createTestData("2023-01-01T09:00:00Z"),
|
||||
eventLength: 60,
|
||||
busy: [
|
||||
{
|
||||
start: dayjs.utc("2023-01-01T09:15:00Z").toDate(),
|
||||
end: dayjs.utc("2023-01-01T09:45:00Z").toDate(),
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it("should return false when slot is completely contained within busy period", () => {
|
||||
const result = checkIfIsAvailable({
|
||||
...createTestData("2023-01-01T09:15:00Z"),
|
||||
busy: [
|
||||
{
|
||||
start: dayjs.utc("2023-01-01T09:00:00Z").toDate(),
|
||||
end: dayjs.utc("2023-01-01T10:00:00Z").toDate(),
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it("should return true when multiple non-overlapping busy periods", () => {
|
||||
const result = checkIfIsAvailable({
|
||||
...createTestData("2023-01-01T09:30:00Z"),
|
||||
busy: [
|
||||
{
|
||||
start: dayjs.utc("2023-01-01T08:00:00Z").toDate(),
|
||||
end: dayjs.utc("2023-01-01T09:00:00Z").toDate(),
|
||||
},
|
||||
{
|
||||
start: dayjs.utc("2023-01-01T10:00:00Z").toDate(),
|
||||
end: dayjs.utc("2023-01-01T11:00:00Z").toDate(),
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it("should return false if any busy period overlaps", () => {
|
||||
const result = checkIfIsAvailable({
|
||||
...createTestData("2023-01-01T09:30:00Z"),
|
||||
busy: [
|
||||
{
|
||||
start: dayjs.utc("2023-01-01T08:00:00Z").toDate(),
|
||||
end: dayjs.utc("2023-01-01T09:00:00Z").toDate(),
|
||||
},
|
||||
{
|
||||
start: dayjs.utc("2023-01-01T09:45:00Z").toDate(),
|
||||
end: dayjs.utc("2023-01-01T10:00:00Z").toDate(),
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it("should handle exact boundary conditions", () => {
|
||||
const result = checkIfIsAvailable({
|
||||
...createTestData("2023-01-01T09:30:00Z"),
|
||||
busy: [
|
||||
{
|
||||
start: dayjs.utc("2023-01-01T09:00:00Z").toDate(),
|
||||
end: dayjs.utc("2023-01-01T09:30:00Z").toDate(),
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -62,33 +62,38 @@ export const checkIfIsAvailable = ({
|
||||
return true;
|
||||
}
|
||||
|
||||
const slotEndTime = time.add(eventLength, "minutes").utc();
|
||||
const slotStartTime = time.utc();
|
||||
const slotStartDate = time.utc().toDate();
|
||||
const slotEndDate = time.add(eventLength, "minutes").utc().toDate();
|
||||
|
||||
return busy.every((busyTime) => {
|
||||
const startTime = dayjs.utc(busyTime.start).utc();
|
||||
const endTime = dayjs.utc(busyTime.end);
|
||||
const busyStartDate = dayjs.utc(busyTime.start).toDate();
|
||||
const busyEndDate = dayjs.utc(busyTime.end).toDate();
|
||||
|
||||
if (endTime.isBefore(slotStartTime) || startTime.isAfter(slotEndTime)) {
|
||||
// First check if there's any overlap at all
|
||||
// If busy period ends before slot starts or starts after slot ends, there's no overlap
|
||||
if (busyEndDate <= slotStartDate || busyStartDate >= slotEndDate) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (slotStartTime.isBetween(startTime, endTime, null, "[)")) {
|
||||
return false;
|
||||
} else if (slotEndTime.isBetween(startTime, endTime, null, "(]")) {
|
||||
// Now check all possible overlap scenarios:
|
||||
|
||||
// 1. Slot start falls within busy period (inclusive start, exclusive end)
|
||||
if (slotStartDate >= busyStartDate && slotStartDate < busyEndDate) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if start times are the same
|
||||
if (time.utc().isBetween(startTime, endTime, null, "[)")) {
|
||||
// 2. Slot end falls within busy period (exclusive start, inclusive end)
|
||||
if (slotEndDate > busyStartDate && slotEndDate <= busyEndDate) {
|
||||
return false;
|
||||
}
|
||||
// Check if slot end time is between start and end time
|
||||
else if (slotEndTime.isBetween(startTime, endTime)) {
|
||||
|
||||
// 3. Busy period completely contained within slot
|
||||
if (busyStartDate >= slotStartDate && busyEndDate <= slotEndDate) {
|
||||
return false;
|
||||
}
|
||||
// Check if startTime is between slot
|
||||
else if (startTime.isBetween(time, slotEndTime)) {
|
||||
|
||||
// 4. Slot completely contained within busy period
|
||||
if (busyStartDate <= slotStartDate && busyEndDate >= slotEndDate) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user