split date ranges for calling /freebusy endpoint (#11962)

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
pull/12013/head^2
Carina Wollendorfer 2023-10-26 10:29:08 -04:00 committed by GitHub
parent 52386e08f2
commit c2a57fd72b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 97 additions and 56 deletions

View File

@ -4,6 +4,7 @@ import type { calendar_v3 } from "googleapis";
import { google } from "googleapis"; import { google } from "googleapis";
import { MeetLocationType } from "@calcom/app-store/locations"; import { MeetLocationType } from "@calcom/app-store/locations";
import dayjs from "@calcom/dayjs";
import { getFeatureFlagMap } from "@calcom/features/flags/server/utils"; import { getFeatureFlagMap } from "@calcom/features/flags/server/utils";
import { getLocation, getRichDescription } from "@calcom/lib/CalEventParser"; import { getLocation, getRichDescription } from "@calcom/lib/CalEventParser";
import type CalendarService from "@calcom/lib/CalendarService"; import type CalendarService from "@calcom/lib/CalendarService";
@ -369,57 +370,75 @@ export default class GoogleCalendarService implements Calendar {
timeMin: string; timeMin: string;
timeMax: string; timeMax: string;
items: { id: string }[]; items: { id: string }[];
}): Promise<calendar_v3.Schema$FreeBusyResponse> { }): Promise<EventBusyDate[] | null> {
const calendar = await this.authedCalendar(); const calendar = await this.authedCalendar();
const flags = await getFeatureFlagMap(prisma); const flags = await getFeatureFlagMap(prisma);
let freeBusyResult: calendar_v3.Schema$FreeBusyResponse = {};
if (!flags["calendar-cache"]) { if (!flags["calendar-cache"]) {
this.log.warn("Calendar Cache is disabled - Skipping"); this.log.warn("Calendar Cache is disabled - Skipping");
const { timeMin, timeMax, items } = args; const { timeMin, timeMax, items } = args;
const apires = await calendar.freebusy.query({ const apires = await calendar.freebusy.query({
requestBody: { timeMin, timeMax, items }, requestBody: { timeMin, timeMax, items },
}); });
return apires.data;
freeBusyResult = apires.data;
} else {
const { timeMin: _timeMin, timeMax: _timeMax, items } = args;
const { timeMin, timeMax } = handleMinMax(_timeMin, _timeMax);
const key = JSON.stringify({ timeMin, timeMax, items });
const cached = await prisma.calendarCache.findUnique({
where: {
credentialId_key: {
credentialId: this.credential.id,
key,
},
expiresAt: { gte: new Date(Date.now()) },
},
});
if (cached) {
freeBusyResult = cached.value as unknown as calendar_v3.Schema$FreeBusyResponse;
} else {
const apires = await calendar.freebusy.query({
requestBody: { timeMin, timeMax, items },
});
// Skipping await to respond faster
await prisma.calendarCache.upsert({
where: {
credentialId_key: {
credentialId: this.credential.id,
key,
},
},
update: {
value: JSON.parse(JSON.stringify(apires.data)),
expiresAt: new Date(Date.now() + CACHING_TIME),
},
create: {
value: JSON.parse(JSON.stringify(apires.data)),
credentialId: this.credential.id,
key,
expiresAt: new Date(Date.now() + CACHING_TIME),
},
});
freeBusyResult = apires.data;
}
} }
const { timeMin: _timeMin, timeMax: _timeMax, items } = args; if (!freeBusyResult.calendars) return null;
const { timeMin, timeMax } = handleMinMax(_timeMin, _timeMax);
const key = JSON.stringify({ timeMin, timeMax, items });
const cached = await prisma.calendarCache.findUnique({
where: {
credentialId_key: {
credentialId: this.credential.id,
key,
},
expiresAt: { gte: new Date(Date.now()) },
},
});
if (cached) return cached.value as unknown as calendar_v3.Schema$FreeBusyResponse; const result = Object.values(freeBusyResult.calendars).reduce((c, i) => {
i.busy?.forEach((busyTime) => {
const apires = await calendar.freebusy.query({ c.push({
requestBody: { timeMin, timeMax, items }, start: busyTime.start || "",
}); end: busyTime.end || "",
});
// Skipping await to respond faster });
await prisma.calendarCache.upsert({ return c;
where: { }, [] as Prisma.PromiseReturnType<CalendarService["getAvailability"]>);
credentialId_key: { return result;
credentialId: this.credential.id,
key,
},
},
update: {
value: JSON.parse(JSON.stringify(apires.data)),
expiresAt: new Date(Date.now() + CACHING_TIME),
},
create: {
value: JSON.parse(JSON.stringify(apires.data)),
credentialId: this.credential.id,
key,
expiresAt: new Date(Date.now() + CACHING_TIME),
},
});
return apires.data;
} }
async getAvailability( async getAvailability(
@ -444,22 +463,44 @@ export default class GoogleCalendarService implements Calendar {
try { try {
const calsIds = await getCalIds(); const calsIds = await getCalIds();
const freeBusyData = await this.getCacheOrFetchAvailability({ const originalStartDate = dayjs(dateFrom);
timeMin: dateFrom, const originalEndDate = dayjs(dateTo);
timeMax: dateTo, const diff = originalEndDate.diff(originalStartDate, "days");
items: calsIds.map((id) => ({ id })),
}); // /freebusy from google api only allows a date range of 90 days
if (!freeBusyData?.calendars) throw new Error("No response from google calendar"); if (diff <= 90) {
const result = Object.values(freeBusyData.calendars).reduce((c, i) => { const freeBusyData = await this.getCacheOrFetchAvailability({
i.busy?.forEach((busyTime) => { timeMin: dateFrom,
c.push({ timeMax: dateTo,
start: busyTime.start || "", items: calsIds.map((id) => ({ id })),
end: busyTime.end || "",
});
}); });
return c; if (!freeBusyData) throw new Error("No response from google calendar");
}, [] as Prisma.PromiseReturnType<CalendarService["getAvailability"]>);
return result; return freeBusyData;
} else {
const busyData = [];
const loopsNumber = Math.ceil(diff / 90);
let startDate = originalStartDate;
let endDate = originalStartDate.add(90, "days");
for (let i = 0; i < loopsNumber; i++) {
if (endDate.isAfter(originalEndDate)) endDate = originalEndDate;
busyData.push(
...((await this.getCacheOrFetchAvailability({
timeMin: startDate.format(),
timeMax: endDate.format(),
items: calsIds.map((id) => ({ id })),
})) || [])
);
startDate = endDate.add(1, "minutes");
endDate = startDate.add(90, "days");
}
return busyData;
}
} catch (error) { } catch (error) {
this.log.error("There was an error contacting google calendar service: ", error); this.log.error("There was an error contacting google calendar service: ", error);
throw error; throw error;