fix: Re-instate new slot logic now it's A/B tested in production (#19605)
* fix: Re-instate new slot logic now it's well tested * Fixup getAggregatedAvailability * Changes to code-owners to reflect removed/moved code * Move more files around. * Fix failed type --------- Co-authored-by: Omar López <zomars@me.com>
This commit is contained in:
1 parent
4bab8c2c32
commit
1cfd94d293
13 files changed
+21
-881
No files matched your search
@@ -1,340 +0,0 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
|
||||
import type { WorkingHours } from "@calcom/types/schedule";
|
||||
|
||||
import { getAggregateWorkingHours } from "./getAggregateWorkingHours";
|
||||
|
||||
describe("getAggregateWorkingHours", () => {
|
||||
it("should return all schedules if no scheduling type", () => {
|
||||
const workingHours: WorkingHours[] = [
|
||||
{
|
||||
days: [1, 2, 3],
|
||||
startTime: 0,
|
||||
endTime: 720,
|
||||
},
|
||||
];
|
||||
const result = getAggregateWorkingHours(
|
||||
[
|
||||
{
|
||||
busy: [],
|
||||
timeZone: "Europe/London",
|
||||
workingHours,
|
||||
dateOverrides: [],
|
||||
datesOutOfOffice: {},
|
||||
},
|
||||
{
|
||||
busy: [],
|
||||
timeZone: "Europe/London",
|
||||
workingHours,
|
||||
dateOverrides: [],
|
||||
datesOutOfOffice: {},
|
||||
},
|
||||
],
|
||||
null
|
||||
);
|
||||
|
||||
expect(result).toEqual([...workingHours, ...workingHours]);
|
||||
});
|
||||
|
||||
it("should return all schedules if no fixed users exist", () => {
|
||||
const workingHours: WorkingHours[] = [
|
||||
{
|
||||
days: [1, 2, 3],
|
||||
startTime: 0,
|
||||
endTime: 720,
|
||||
},
|
||||
];
|
||||
const result = getAggregateWorkingHours(
|
||||
[
|
||||
{
|
||||
busy: [],
|
||||
timeZone: "Europe/London",
|
||||
workingHours,
|
||||
dateOverrides: [],
|
||||
datesOutOfOffice: {},
|
||||
},
|
||||
{
|
||||
busy: [],
|
||||
timeZone: "Europe/London",
|
||||
workingHours,
|
||||
dateOverrides: [],
|
||||
datesOutOfOffice: {},
|
||||
},
|
||||
],
|
||||
"MANAGED"
|
||||
);
|
||||
|
||||
expect(result).toEqual([...workingHours, ...workingHours]);
|
||||
});
|
||||
|
||||
it("should consider all schedules fixed if collective", () => {
|
||||
const workingHoursA: WorkingHours[] = [
|
||||
{
|
||||
days: [1, 2],
|
||||
startTime: 0,
|
||||
endTime: 200,
|
||||
},
|
||||
];
|
||||
const workingHoursB: WorkingHours[] = [
|
||||
{
|
||||
days: [2, 3],
|
||||
startTime: 100,
|
||||
endTime: 300,
|
||||
},
|
||||
];
|
||||
const result = getAggregateWorkingHours(
|
||||
[
|
||||
{
|
||||
busy: [],
|
||||
timeZone: "Europe/London",
|
||||
workingHours: workingHoursA,
|
||||
dateOverrides: [],
|
||||
datesOutOfOffice: {},
|
||||
user: {
|
||||
isFixed: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
busy: [],
|
||||
timeZone: "Europe/London",
|
||||
workingHours: workingHoursB,
|
||||
dateOverrides: [],
|
||||
datesOutOfOffice: {},
|
||||
user: {
|
||||
isFixed: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
"COLLECTIVE"
|
||||
);
|
||||
|
||||
expect(result).toEqual([
|
||||
{
|
||||
days: [2],
|
||||
startTime: 100,
|
||||
endTime: 200,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("should include loose host hours", () => {
|
||||
const workingHoursA: WorkingHours[] = [
|
||||
{
|
||||
days: [1, 2],
|
||||
startTime: 0,
|
||||
endTime: 200,
|
||||
},
|
||||
];
|
||||
const workingHoursB: WorkingHours[] = [
|
||||
{
|
||||
days: [2, 3],
|
||||
startTime: 100,
|
||||
endTime: 300,
|
||||
},
|
||||
];
|
||||
const result = getAggregateWorkingHours(
|
||||
[
|
||||
{
|
||||
busy: [],
|
||||
timeZone: "Europe/London",
|
||||
workingHours: workingHoursA,
|
||||
dateOverrides: [],
|
||||
datesOutOfOffice: {},
|
||||
user: {
|
||||
isFixed: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
busy: [],
|
||||
timeZone: "Europe/London",
|
||||
workingHours: workingHoursB,
|
||||
dateOverrides: [],
|
||||
datesOutOfOffice: {},
|
||||
user: {
|
||||
isFixed: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
"COLLECTIVE"
|
||||
);
|
||||
|
||||
expect(result).toEqual([
|
||||
{
|
||||
days: [2],
|
||||
startTime: 100,
|
||||
endTime: 200,
|
||||
userId: undefined,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("should return last user's hours if no intersection", () => {
|
||||
const workingHoursA: WorkingHours[] = [
|
||||
{
|
||||
days: [1],
|
||||
startTime: 0,
|
||||
endTime: 200,
|
||||
},
|
||||
];
|
||||
const workingHoursB: WorkingHours[] = [
|
||||
{
|
||||
days: [2],
|
||||
startTime: 100,
|
||||
endTime: 300,
|
||||
},
|
||||
];
|
||||
const result = getAggregateWorkingHours(
|
||||
[
|
||||
{
|
||||
busy: [],
|
||||
timeZone: "Europe/London",
|
||||
workingHours: workingHoursA,
|
||||
dateOverrides: [],
|
||||
datesOutOfOffice: {},
|
||||
user: {
|
||||
isFixed: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
busy: [],
|
||||
timeZone: "Europe/London",
|
||||
workingHours: workingHoursB,
|
||||
dateOverrides: [],
|
||||
datesOutOfOffice: {},
|
||||
user: {
|
||||
isFixed: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
"COLLECTIVE"
|
||||
);
|
||||
|
||||
expect(result).toEqual([...workingHoursB]);
|
||||
});
|
||||
|
||||
it("should include user IDs when not collective", () => {
|
||||
const workingHoursA: WorkingHours[] = [
|
||||
{
|
||||
days: [1, 2],
|
||||
startTime: 0,
|
||||
endTime: 200,
|
||||
userId: 1,
|
||||
},
|
||||
];
|
||||
const workingHoursB: WorkingHours[] = [
|
||||
{
|
||||
days: [2, 3],
|
||||
startTime: 100,
|
||||
endTime: 300,
|
||||
userId: 2,
|
||||
},
|
||||
];
|
||||
const result = getAggregateWorkingHours(
|
||||
[
|
||||
{
|
||||
busy: [],
|
||||
timeZone: "Europe/London",
|
||||
workingHours: workingHoursA,
|
||||
dateOverrides: [],
|
||||
datesOutOfOffice: {},
|
||||
user: {
|
||||
isFixed: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
busy: [],
|
||||
timeZone: "Europe/London",
|
||||
workingHours: workingHoursB,
|
||||
dateOverrides: [],
|
||||
datesOutOfOffice: {},
|
||||
user: {
|
||||
isFixed: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
"MANAGED"
|
||||
);
|
||||
|
||||
expect(result).toEqual([
|
||||
{
|
||||
userId: 1,
|
||||
days: [2],
|
||||
startTime: 100,
|
||||
endTime: 200,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("should handle multiple intersections", () => {
|
||||
const workingHoursA: WorkingHours[] = [
|
||||
{
|
||||
days: [1, 2],
|
||||
startTime: 0,
|
||||
endTime: 200,
|
||||
},
|
||||
{
|
||||
days: [3, 4],
|
||||
startTime: 100,
|
||||
endTime: 300,
|
||||
},
|
||||
];
|
||||
const workingHoursB: WorkingHours[] = [
|
||||
{
|
||||
days: [2, 3],
|
||||
startTime: 100,
|
||||
endTime: 300,
|
||||
},
|
||||
{
|
||||
days: [4, 5],
|
||||
startTime: 0,
|
||||
endTime: 200,
|
||||
},
|
||||
];
|
||||
const result = getAggregateWorkingHours(
|
||||
[
|
||||
{
|
||||
busy: [],
|
||||
timeZone: "Europe/London",
|
||||
workingHours: workingHoursA,
|
||||
dateOverrides: [],
|
||||
datesOutOfOffice: {},
|
||||
user: {
|
||||
isFixed: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
busy: [],
|
||||
timeZone: "Europe/London",
|
||||
workingHours: workingHoursB,
|
||||
dateOverrides: [],
|
||||
datesOutOfOffice: {},
|
||||
user: {
|
||||
isFixed: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
"COLLECTIVE"
|
||||
);
|
||||
|
||||
expect(result).toEqual([
|
||||
{
|
||||
days: [2],
|
||||
startTime: 100,
|
||||
endTime: 200,
|
||||
userId: undefined,
|
||||
},
|
||||
{
|
||||
days: [3],
|
||||
startTime: 100,
|
||||
endTime: 300,
|
||||
userId: undefined,
|
||||
},
|
||||
{
|
||||
days: [4],
|
||||
startTime: 100,
|
||||
endTime: 200,
|
||||
userId: undefined,
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -1,60 +0,0 @@
|
||||
import { SchedulingType } from "@calcom/prisma/enums";
|
||||
import type { WorkingHours } from "@calcom/types/schedule";
|
||||
|
||||
/**
|
||||
* This function gets team members working hours and busy slots,
|
||||
* offsets them to UTC and intersects them for collective events.
|
||||
**/
|
||||
export const getAggregateWorkingHours = (
|
||||
usersWorkingHoursAndBusySlots: (Omit<
|
||||
Awaited<ReturnType<Awaited<typeof import("./getUserAvailability")>["getUserAvailability"]>>,
|
||||
"currentSeats" | "dateRanges" | "oooExcludedDateRanges"
|
||||
> & { user?: { isFixed?: boolean } })[],
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
schedulingType: SchedulingType | null
|
||||
): WorkingHours[] => {
|
||||
// during personal events, just flatMap.
|
||||
if (!schedulingType) {
|
||||
return usersWorkingHoursAndBusySlots.flatMap((s) => s.workingHours);
|
||||
}
|
||||
const looseHostWorkingHours = usersWorkingHoursAndBusySlots
|
||||
.filter(({ user }) => schedulingType !== SchedulingType.COLLECTIVE && user?.isFixed !== true)
|
||||
.flatMap((s) => s.workingHours);
|
||||
|
||||
const fixedHostSchedules = usersWorkingHoursAndBusySlots.filter(
|
||||
({ user }) => schedulingType === SchedulingType.COLLECTIVE || user?.isFixed
|
||||
);
|
||||
// return early when there are no fixed hosts.
|
||||
if (!fixedHostSchedules.length) {
|
||||
return looseHostWorkingHours;
|
||||
}
|
||||
return fixedHostSchedules.reduce((currentWorkingHours: WorkingHours[], s) => {
|
||||
const updatedWorkingHours: typeof currentWorkingHours = [];
|
||||
|
||||
s.workingHours.forEach((workingHour) => {
|
||||
const sameDayWorkingHours = currentWorkingHours.filter((compare) =>
|
||||
compare.days.find((day) => workingHour.days.includes(day))
|
||||
);
|
||||
if (!sameDayWorkingHours.length) {
|
||||
updatedWorkingHours.push(workingHour); // the first day is always added.
|
||||
return;
|
||||
}
|
||||
// days are overlapping when different users are involved, instead of adding we now need to subtract
|
||||
updatedWorkingHours.push(
|
||||
...sameDayWorkingHours.map((compare) => {
|
||||
const intersect = workingHour.days.filter((day) => compare.days.includes(day));
|
||||
const retVal: WorkingHours & { userId?: number | null } = {
|
||||
days: intersect,
|
||||
startTime: Math.max(workingHour.startTime, compare.startTime),
|
||||
endTime: Math.min(workingHour.endTime, compare.endTime),
|
||||
};
|
||||
if (schedulingType !== SchedulingType.COLLECTIVE) {
|
||||
retVal.userId = compare.userId;
|
||||
}
|
||||
return retVal;
|
||||
})
|
||||
);
|
||||
});
|
||||
return updatedWorkingHours;
|
||||
}, looseHostWorkingHours);
|
||||
};
|
||||
+1
-1
@@ -3,7 +3,7 @@ import { describe, it, expect } from "vitest";
|
||||
import type { Dayjs } from "@calcom/dayjs";
|
||||
import dayjs from "@calcom/dayjs";
|
||||
|
||||
import { getAggregatedAvailabilityNew as getAggregatedAvailability } from ".";
|
||||
import { getAggregatedAvailability } from "./getAggregatedAvailability";
|
||||
|
||||
// Helper to check if a time range overlaps with availability
|
||||
const isAvailable = (availability: { start: Dayjs; end: Dayjs }[], range: { start: Dayjs; end: Dayjs }) => {
|
||||
+1
-1
@@ -2,7 +2,7 @@ import type { DateRange } from "@calcom/lib/date-ranges";
|
||||
import { intersect } from "@calcom/lib/date-ranges";
|
||||
import { SchedulingType } from "@calcom/prisma/enums";
|
||||
|
||||
import { mergeOverlappingDateRanges } from "./date-range-utils/mergeOverlappingDateRanges";
|
||||
import { mergeOverlappingDateRanges } from "./getAggregatedAvailability/date-range-utils/mergeOverlappingDateRanges";
|
||||
|
||||
function uniqueAndSortedDateRanges(ranges: DateRange[]): DateRange[] {
|
||||
const seen = new Set<string>();
|
||||
+1
-1
@@ -3,7 +3,7 @@ import { describe, it, expect } from "vitest";
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import type { DateRange } from "@calcom/lib/date-ranges";
|
||||
|
||||
import { mergeOverlappingDateRanges } from ".";
|
||||
import { mergeOverlappingDateRanges } from "./mergeOverlappingDateRanges";
|
||||
|
||||
const november2 = "2023-11-02";
|
||||
const november3 = "2023-11-03";
|
||||
File renamed without changes.
@@ -1,37 +0,0 @@
|
||||
import type { DateRange } from "@calcom/lib/date-ranges";
|
||||
import { intersect } from "@calcom/lib/date-ranges";
|
||||
import { SchedulingType } from "@calcom/prisma/enums";
|
||||
|
||||
import { mergeOverlappingDateRanges } from "./date-range-utils/mergeOverlappingDateRanges";
|
||||
|
||||
export const getAggregatedAvailability = (
|
||||
userAvailability: {
|
||||
dateRanges: DateRange[];
|
||||
oooExcludedDateRanges: DateRange[];
|
||||
user?: { isFixed?: boolean };
|
||||
}[],
|
||||
schedulingType: SchedulingType | null
|
||||
): DateRange[] => {
|
||||
const isTeamEvent =
|
||||
schedulingType === SchedulingType.COLLECTIVE ||
|
||||
schedulingType === SchedulingType.ROUND_ROBIN ||
|
||||
userAvailability.length > 1;
|
||||
const fixedHosts = userAvailability.filter(
|
||||
({ user }) => !schedulingType || schedulingType === SchedulingType.COLLECTIVE || user?.isFixed
|
||||
);
|
||||
|
||||
const dateRangesToIntersect = fixedHosts.map((s) =>
|
||||
!isTeamEvent ? s.dateRanges : s.oooExcludedDateRanges
|
||||
);
|
||||
|
||||
const unfixedHosts = userAvailability.filter(({ user }) => user?.isFixed !== true);
|
||||
if (unfixedHosts.length) {
|
||||
dateRangesToIntersect.push(
|
||||
unfixedHosts.flatMap((s) => (!isTeamEvent ? s.dateRanges : s.oooExcludedDateRanges))
|
||||
);
|
||||
}
|
||||
|
||||
const availability = intersect(dateRangesToIntersect);
|
||||
|
||||
return mergeOverlappingDateRanges(availability);
|
||||
};
|
||||
@@ -1,3 +0,0 @@
|
||||
export { getAggregatedAvailability as getAggregatedAvailabilityNew } from "./getAggregatedAvailability";
|
||||
|
||||
export { getAggregatedAvailability as getAggregatedAvailability } from "./getAggregatedAvailabilityOld";
|
||||
Reference in new issue
Block a user