Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 21 additions & 22 deletions apps/api/v2/src/ee/calendars/services/calendars.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,32 +83,31 @@ export class CalendarsService {
calendarsToLoad,
userId
);
try {
const calendarBusyTimes = await getBusyCalendarTimes(
this.buildNonDelegationCredentials(credentials),
dateFrom,
dateTo,
composedSelectedCalendars
);
const calendarBusyTimesConverted = calendarBusyTimes.map(
(busyTime: EventBusyDate & { timeZone?: string }) => {
const busyTimeStart = DateTime.fromJSDate(new Date(busyTime.start)).setZone(timezone);
const busyTimeEnd = DateTime.fromJSDate(new Date(busyTime.end)).setZone(timezone);
const busyTimeStartDate = busyTimeStart.toJSDate();
const busyTimeEndDate = busyTimeEnd.toJSDate();
return {
...busyTime,
start: busyTimeStartDate,
end: busyTimeEndDate,
};
}
);
return calendarBusyTimesConverted;
} catch (error) {
const calendarBusyTimesQuery = await getBusyCalendarTimes(
this.buildNonDelegationCredentials(credentials),
dateFrom,
dateTo,
composedSelectedCalendars
);
if (!calendarBusyTimesQuery.success) {
throw new InternalServerErrorException(
"Unable to fetch connected calendars events. Please try again later."
);
}
const calendarBusyTimesConverted = calendarBusyTimesQuery.data.map(
(busyTime: EventBusyDate & { timeZone?: string }) => {
const busyTimeStart = DateTime.fromJSDate(new Date(busyTime.start)).setZone(timezone);
const busyTimeEnd = DateTime.fromJSDate(new Date(busyTime.end)).setZone(timezone);
const busyTimeStartDate = busyTimeStart.toJSDate();
const busyTimeEndDate = busyTimeEnd.toJSDate();
return {
...busyTime,
start: busyTimeStartDate,
end: busyTimeEndDate,
};
}
);
return calendarBusyTimesConverted;
}

async getUniqCalendarCredentials(calendarsToLoad: Calendar[], userId: User["id"]) {
Expand Down
32 changes: 19 additions & 13 deletions apps/web/test/lib/getSchedule.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1281,12 +1281,15 @@ describe("getSchedule", () => {
const { dateString: plus2DateString } = getDate({ dateIncrement: 2 });
const { dateString: plus3DateString } = getDate({ dateIncrement: 3 });

CalendarManagerMock.getBusyCalendarTimes.mockResolvedValue([
{
start: `${plus3DateString}T04:00:00.000Z`,
end: `${plus3DateString}T05:59:59.000Z`,
},
]);
CalendarManagerMock.getBusyCalendarTimes.mockResolvedValue({
success: true,
data: [
{
start: `${plus3DateString}T04:00:00.000Z`,
end: `${plus3DateString}T05:59:59.000Z`,
},
],
});

const scenarioData = {
eventTypes: [
Expand Down Expand Up @@ -1347,12 +1350,15 @@ describe("getSchedule", () => {
const { dateString: plus2DateString } = getDate({ dateIncrement: 2 });
const { dateString: plus3DateString } = getDate({ dateIncrement: 3 });

CalendarManagerMock.getBusyCalendarTimes.mockResolvedValue([
{
start: `${plus3DateString}T04:00:00.000Z`,
end: `${plus3DateString}T05:59:59.000Z`,
},
]);
CalendarManagerMock.getBusyCalendarTimes.mockResolvedValue({
success: true,
data: [
{
start: `${plus3DateString}T04:00:00.000Z`,
end: `${plus3DateString}T05:59:59.000Z`,
},
],
});

const scenarioData = {
eventTypes: [
Expand Down Expand Up @@ -1421,7 +1427,7 @@ describe("getSchedule", () => {
const { dateString: plus1DateString } = getDate({ dateIncrement: 1 });
const { dateString: plus2DateString } = getDate({ dateIncrement: 2 });

CalendarManagerMock.getBusyCalendarTimes.mockResolvedValue([]);
CalendarManagerMock.getBusyCalendarTimes.mockResolvedValue({ success: true, data: [] });

const scenarioData = {
eventTypes: [
Expand Down
21 changes: 6 additions & 15 deletions packages/lib/CalendarManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -245,21 +245,11 @@ export const getBusyCalendarTimes = async (
}

// const months = getMonths(dateFrom, dateTo);
// Subtract 11 hours from the start date to avoid problems in UTC- time zones.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move the startDate and endDate out of the try block so it can be accessed inside of the catch block

const startDate = dayjs(dateFrom).subtract(11, "hours").format();
// Add 14 hours from the start date to avoid problems in UTC+ time zones.
const endDate = dayjs(dateTo).add(14, "hours").format();
try {
// Subtract 11 hours from the start date to avoid problems in UTC- time zones.
const startDate = dayjs(dateFrom).subtract(11, "hours").format();
// Add 14 hours from the start date to avoid problems in UTC+ time zones.
const endDate = dayjs(dateTo).add(14, "hours").format();

log.debug(
"getBusyCalendarTimes manipulated dates",
safeStringify({
newStartDate: startDate,
newEndDate: endDate,
oldStartDate: dateFrom,
oldEndDate: dateTo,
})
);
if (includeTimeZone) {
results = await getCalendarsEventsWithTimezones(
deduplicatedCredentials,
Expand All @@ -281,8 +271,9 @@ export const getBusyCalendarTimes = async (
selectedCalendarIds: selectedCalendars.map((calendar) => calendar.externalId),
error: safeStringify(e),
});
return { success: false, data: [{ start: startDate, end: endDate, source: "error-placeholder" }] };
}
return results.reduce((acc, availability) => acc.concat(availability), []);
return { success: true, data: results.reduce((acc, availability) => acc.concat(availability), []) };
};

export const createEvent = async (
Expand Down
13 changes: 12 additions & 1 deletion packages/lib/getBusyTimes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,13 +179,24 @@ const _getBusyTimes = async (params: {
performance.measure(`prisma booking get took $1'`, "prismaBookingGetStart", "prismaBookingGetEnd");
if (credentials?.length > 0 && !bypassBusyCalendarTimes) {
const startConnectedCalendarsGet = performance.now();
const calendarBusyTimes = await getBusyCalendarTimes(

const calendarBusyTimesQuery = await getBusyCalendarTimes(
credentials,
startTime,
endTime,
selectedCalendars,
shouldServeCache
);

if (!calendarBusyTimesQuery.success) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We throw an error when failing to get the calendar event times because it's not a true representation of a user's busy times.

throw new Error(
`Failed to fetch busy calendar times for selected calendars ${selectedCalendars.map(
(calendar) => calendar.id
)}`
);
}

const calendarBusyTimes = calendarBusyTimesQuery.data;
const endConnectedCalendarsGet = performance.now();
logger.debug(
`Connected Calendars get took ${
Expand Down
51 changes: 33 additions & 18 deletions packages/lib/getUserAvailability.ts
Original file line number Diff line number Diff line change
Expand Up @@ -411,24 +411,39 @@ const _getUserAvailability = async function getUsersWorkingHoursLifeTheUniverseA
? EventTypeRepository.getSelectedCalendarsFromUser({ user, eventTypeId: eventType.id })
: user.userLevelSelectedCalendars;

const busyTimes = await getBusyTimes({
credentials: user.credentials,
startTime: getBusyTimesStart,
endTime: getBusyTimesEnd,
eventTypeId,
userId: user.id,
userEmail: user.email,
username: `${user.username}`,
beforeEventBuffer,
afterEventBuffer,
selectedCalendars,
seatedEvent: !!eventType?.seatsPerTimeSlot,
rescheduleUid: initialData?.rescheduleUid || null,
duration,
currentBookings: initialData?.currentBookings,
bypassBusyCalendarTimes,
shouldServeCache,
});
let busyTimes = [];
try {
busyTimes = await getBusyTimes({
credentials: user.credentials,
startTime: getBusyTimesStart,
endTime: getBusyTimesEnd,
eventTypeId,
userId: user.id,
userEmail: user.email,
username: `${user.username}`,
beforeEventBuffer,
afterEventBuffer,
selectedCalendars,
seatedEvent: !!eventType?.seatsPerTimeSlot,
rescheduleUid: initialData?.rescheduleUid || null,
duration,
currentBookings: initialData?.currentBookings,
bypassBusyCalendarTimes,
shouldServeCache,
});
} catch (error) {
log.error(`Error fetching busy times for user ${username}:`, error);
return {
busy: [],
timeZone,
dateRanges: [],
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there is an error getting busy times, then return an array with no available days.

oooExcludedDateRanges: [],
workingHours: [],
dateOverrides: [],
currentSeats: [],
datesOutOfOffice: undefined,
};
}

const detailedBusyTimes: EventBusyDetails[] = [
...busyTimes.map((a) => ({
Expand Down
33 changes: 18 additions & 15 deletions packages/lib/server/getLuckyUser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ it("can find lucky user with maximize availability", async () => {
}),
];

CalendarManagerMock.getBusyCalendarTimes.mockResolvedValue([]);
CalendarManagerMock.getBusyCalendarTimes.mockResolvedValue({ success: true, data: [] });
prismaMock.outOfOfficeEntry.findMany.mockResolvedValue([]);

// TODO: we may be able to use native prisma generics somehow?
Expand Down Expand Up @@ -107,7 +107,7 @@ it("can find lucky user with maximize availability and priority ranking", async
}),
];

CalendarManagerMock.getBusyCalendarTimes.mockResolvedValue([]);
CalendarManagerMock.getBusyCalendarTimes.mockResolvedValue({ success: true, data: [] });
prismaMock.outOfOfficeEntry.findMany.mockResolvedValue([]);

// TODO: we may be able to use native prisma generics somehow?
Expand Down Expand Up @@ -289,7 +289,7 @@ describe("maximize availability and weights", () => {
}),
];

CalendarManagerMock.getBusyCalendarTimes.mockResolvedValue([]);
CalendarManagerMock.getBusyCalendarTimes.mockResolvedValue({ success: true, data: [] });
prismaMock.outOfOfficeEntry.findMany.mockResolvedValue([]);
prismaMock.user.findMany.mockResolvedValue(users);
prismaMock.host.findMany.mockResolvedValue([]);
Expand Down Expand Up @@ -392,7 +392,7 @@ describe("maximize availability and weights", () => {
}),
];

CalendarManagerMock.getBusyCalendarTimes.mockResolvedValue([]);
CalendarManagerMock.getBusyCalendarTimes.mockResolvedValue({ success: true, data: [] });
prismaMock.outOfOfficeEntry.findMany.mockResolvedValue([]);
prismaMock.user.findMany.mockResolvedValue(users);
prismaMock.host.findMany.mockResolvedValue([]);
Expand Down Expand Up @@ -500,7 +500,7 @@ describe("maximize availability and weights", () => {
}),
];

CalendarManagerMock.getBusyCalendarTimes.mockResolvedValue([]);
CalendarManagerMock.getBusyCalendarTimes.mockResolvedValue({ success: true, data: [] });
prismaMock.outOfOfficeEntry.findMany.mockResolvedValue([]);
prismaMock.user.findMany.mockResolvedValue(users);
prismaMock.host.findMany.mockResolvedValue([]);
Expand Down Expand Up @@ -600,7 +600,7 @@ describe("maximize availability and weights", () => {
},
];

CalendarManagerMock.getBusyCalendarTimes.mockResolvedValue([]);
CalendarManagerMock.getBusyCalendarTimes.mockResolvedValue({ success: true, data: [] });

prismaMock.outOfOfficeEntry.findMany.mockResolvedValue([
{
Expand Down Expand Up @@ -707,13 +707,16 @@ describe("maximize availability and weights", () => {
];

CalendarManagerMock.getBusyCalendarTimes
.mockResolvedValueOnce([
{
start: dayjs().utc().startOf("month").toDate(),
end: dayjs().utc().startOf("month").add(3, "day").toDate(),
timeZone: "UTC",
},
])
.mockResolvedValueOnce({
success: true,
data: [
{
start: dayjs().utc().startOf("month").toDate(),
end: dayjs().utc().startOf("month").add(3, "day").toDate(),
timeZone: "UTC",
},
],
})
.mockResolvedValue([]);

prismaMock.outOfOfficeEntry.findMany.mockResolvedValue([]);
Expand Down Expand Up @@ -817,7 +820,7 @@ describe("maximize availability and weights", () => {
},
];

CalendarManagerMock.getBusyCalendarTimes.mockResolvedValue([]);
CalendarManagerMock.getBusyCalendarTimes.mockResolvedValue({ success: true, data: [] });
prismaMock.outOfOfficeEntry.findMany.mockResolvedValue([]);

// TODO: we may be able to use native prisma generics somehow?
Expand Down Expand Up @@ -1279,7 +1282,7 @@ describe("attribute weights and virtual queues", () => {
chosenRouteId: routeId,
};

CalendarManagerMock.getBusyCalendarTimes.mockResolvedValue([]);
CalendarManagerMock.getBusyCalendarTimes.mockResolvedValue({ success: true, data: [] });
prismaMock.outOfOfficeEntry.findMany.mockResolvedValue([]);

prismaMock.user.findMany.mockResolvedValue(users);
Expand Down
17 changes: 12 additions & 5 deletions packages/lib/server/getLuckyUser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -456,7 +456,7 @@ async function getCalendarBusyTimesOfInterval(
rrTimestampBasis: RRTimestampBasis,
meetingStartTime?: Date
): Promise<{ userId: number; busyTimes: (EventBusyDate & { timeZone?: string })[] }[]> {
return Promise.all(
const usersBusyTimesQuery = await Promise.all(
usersWithCredentials.map((user) =>
getBusyCalendarTimes(
user.credentials,
Expand All @@ -465,12 +465,19 @@ async function getCalendarBusyTimesOfInterval(
user.userLevelSelectedCalendars,
true,
true
).then((busyTimes) => ({
userId: user.id,
busyTimes,
}))
)
)
);

return usersBusyTimesQuery.reduce((usersBusyTime, userBusyTimeQuery, index) => {
if (userBusyTimeQuery.success) {
usersBusyTime.push({
userId: usersWithCredentials[index].id,
busyTimes: userBusyTimeQuery.data,
});
}
return usersBusyTime;
}, [] as { userId: number; busyTimes: Awaited<ReturnType<typeof getBusyCalendarTimes>>["data"] }[]);
}

async function getBookingsOfInterval({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,13 +84,22 @@ export const calendarOverlayHandler = async ({ ctx, input }: ListOptions) => {
});

// get all clanedar services
const calendarBusyTimes = await getBusyCalendarTimes(
const calendarBusyTimesQuery = await getBusyCalendarTimes(
credentials,
dateFrom,
dateTo,
composedSelectedCalendars
);

if (!calendarBusyTimesQuery.success) {
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: "Failed to fetch busy calendar times",
});
}

const calendarBusyTimes = calendarBusyTimesQuery.data;

// Convert to users timezone

const userTimeZone = input.loggedInUsersTz;
Expand Down
Loading