fix: double end-date parsing when saving availability (#20963)
* fix: double end-date parsing when saving availablity Signed-off-by: Omar López <zomars@me.com> * fix: cache hits in current and next month * Update CalendarService.test.ts --------- Signed-off-by: Omar López <zomars@me.com>
This commit is contained in:
1 parent
ce2d3a4b00
commit
ca8ee749a2
6 files changed
+54
-35
No files matched your search
@@ -7,6 +7,7 @@ import { expect, test, beforeEach, vi, describe } from "vitest";
|
||||
import "vitest-fetch-mock";
|
||||
|
||||
import { CalendarCache } from "@calcom/features/calendar-cache/calendar-cache";
|
||||
import { getTimeMin, getTimeMax } from "@calcom/features/calendar-cache/lib/datesForCache";
|
||||
import { SelectedCalendarRepository } from "@calcom/lib/server/repository/selectedCalendar";
|
||||
import type { CredentialForCalendarServiceWithEmail } from "@calcom/types/Credential";
|
||||
|
||||
@@ -187,8 +188,8 @@ test("Calendar Cache is being read on cache HIT", async () => {
|
||||
await calendarCache.upsertCachedAvailability(
|
||||
credentialInDb1.id,
|
||||
{
|
||||
timeMin: dateFrom1,
|
||||
timeMax: dateTo1,
|
||||
timeMin: getTimeMin(dateFrom1),
|
||||
timeMax: getTimeMax(dateTo1),
|
||||
items: [{ id: testSelectedCalendar.externalId }],
|
||||
},
|
||||
JSON.parse(
|
||||
|
||||
@@ -663,7 +663,13 @@ export default class GoogleCalendarService implements Calendar {
|
||||
): Promise<calendar_v3.Schema$FreeBusyResponse> {
|
||||
if (shouldServeCache === false) return await this.fetchAvailability(args);
|
||||
const calendarCache = await CalendarCache.init(null);
|
||||
const cached = await calendarCache.getCachedAvailability(this.credential.id, args);
|
||||
const cached = await calendarCache.getCachedAvailability(this.credential.id, {
|
||||
// Expand the start date to the start of the month to increase cache hits
|
||||
timeMin: getTimeMin(args.timeMin),
|
||||
// Expand the end date to the end of the month to increase cache hits
|
||||
timeMax: getTimeMax(args.timeMax),
|
||||
items: args.items,
|
||||
});
|
||||
if (cached) return cached.value as unknown as calendar_v3.Schema$FreeBusyResponse;
|
||||
return await this.fetchAvailability(args);
|
||||
}
|
||||
@@ -1055,9 +1061,9 @@ export default class GoogleCalendarService implements Calendar {
|
||||
|
||||
for (const [_eventTypeId, selectedCalendars] of Array.from(selectedCalendarsPerEventType.entries())) {
|
||||
const parsedArgs = {
|
||||
/** Expand the start date to the start of the month */
|
||||
/** Expand the start date to the start of the month to increase cache hits */
|
||||
timeMin: getTimeMin(),
|
||||
/** Expand the end date to the end of the month */
|
||||
/** Expand the end date to the end of the month to increase cache hits */
|
||||
timeMax: getTimeMax(),
|
||||
// Dont use eventTypeId in key because it can be used by any eventType
|
||||
// The only reason we are building it per eventType is because there can be different groups of calendars to lookup the availability for
|
||||
|
||||
@@ -7,7 +7,6 @@ import prisma from "@calcom/prisma";
|
||||
import type { Calendar, SelectedCalendarEventTypeIds } from "@calcom/types/Calendar";
|
||||
|
||||
import type { ICalendarCacheRepository } from "./calendar-cache.repository.interface";
|
||||
import { getTimeMax, getTimeMin } from "./lib/datesForCache";
|
||||
|
||||
const log = logger.getSubLogger({ prefix: ["CalendarCacheRepository"] });
|
||||
|
||||
@@ -19,8 +18,8 @@ function parseKeyForCache(args: FreeBusyArgs): string {
|
||||
// Ensure that calendarIds are unique
|
||||
const uniqueItems = uniqueBy(args.items, ["id"]);
|
||||
const key = JSON.stringify({
|
||||
timeMin: getTimeMin(args.timeMin),
|
||||
timeMax: getTimeMax(args.timeMax),
|
||||
timeMin: args.timeMin,
|
||||
timeMax: args.timeMax,
|
||||
items: uniqueItems,
|
||||
});
|
||||
return key;
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { getTimeMin, getTimeMax } from "./datesForCache";
|
||||
|
||||
describe("getTimeMin", () => {
|
||||
// Tested on multiple dates
|
||||
// vi.setSystemTime("2030-04-21T00:00:13Z");
|
||||
vi.setSystemTime("2025-04-24T00:00:13Z");
|
||||
|
||||
it("should return start of current month when no date is passed", () => {
|
||||
const result = getTimeMin();
|
||||
const expected = new Date();
|
||||
@@ -74,4 +75,23 @@ describe("getTimeMax", () => {
|
||||
const result = getTimeMax(testDate);
|
||||
expect(result).toMatchInlineSnapshot(`"2024-11-01T00:00:00.000Z"`);
|
||||
});
|
||||
|
||||
it("should handle year changes", () => {
|
||||
const testDate = "2024-12-24T23:59:59Z";
|
||||
const result = getTimeMax(testDate);
|
||||
expect(result).toMatchInlineSnapshot(`"2025-02-01T00:00:00.000Z"`);
|
||||
});
|
||||
|
||||
it("should handle next month", () => {
|
||||
const testDate = "2025-05-01T02:59:59.999Z";
|
||||
const result = getTimeMax(testDate);
|
||||
expect(result).toMatchInlineSnapshot(`"2025-06-01T00:00:00.000Z"`);
|
||||
});
|
||||
|
||||
it("should handle special case where timeMax is more than 2 months from now but less than 3 months", () => {
|
||||
vi.setSystemTime("2025-04-24T00:00:13Z");
|
||||
const testDate = "2025-06-02T23:59:59.999Z";
|
||||
const result = getTimeMax(testDate);
|
||||
expect(result).toMatchInlineSnapshot(`"2025-06-01T00:00:00.000Z"`);
|
||||
});
|
||||
});
|
||||
@@ -23,44 +23,37 @@ export function getTimeMax(timeMax?: string) {
|
||||
const currentMonth = now.getUTCMonth();
|
||||
const currentYear = now.getUTCFullYear();
|
||||
|
||||
// If no date is passed, return start of the month two months from *now*.
|
||||
if (!timeMax) {
|
||||
// If no date passed, return start of the overnext month from current date
|
||||
const result = new Date(Date.UTC(currentYear, currentMonth + 2, 1));
|
||||
result.setUTCHours(0, 0, 0, 0);
|
||||
return result.toISOString();
|
||||
}
|
||||
|
||||
// If a date is passed, determine the base month/year for calculation.
|
||||
const date = new Date(timeMax);
|
||||
const dateMonth = date.getUTCMonth();
|
||||
const dateYear = date.getUTCFullYear();
|
||||
|
||||
// If the date is within current month or next month, return start of overnext month
|
||||
// Otherwise, return start of overnext month from the date
|
||||
let targetYear = dateYear;
|
||||
let targetMonth = dateMonth;
|
||||
let baseYear = currentYear;
|
||||
let baseMonth = currentMonth;
|
||||
|
||||
// Check if date is within current month or next month
|
||||
const isWithinCurrentOrNextMonth =
|
||||
(dateYear === currentYear && dateMonth <= currentMonth + 1) ||
|
||||
(dateYear === currentYear + 1 && currentMonth === 11 && dateMonth === 0);
|
||||
// Check if date is within the current month or the next two months relative to *now*.
|
||||
const isWithinCurrentOrNextTwoMonths =
|
||||
(dateYear === currentYear && dateMonth <= currentMonth + 2) ||
|
||||
(dateYear === currentYear + 1 &&
|
||||
((currentMonth === 10 && dateMonth === 0) || (currentMonth === 11 && dateMonth <= 1)));
|
||||
|
||||
if (isWithinCurrentOrNextMonth) {
|
||||
// For dates within current month or next month, return start of overnext month
|
||||
targetMonth = dateMonth + 2;
|
||||
if (targetMonth > 11) {
|
||||
targetMonth = 0;
|
||||
targetYear++;
|
||||
}
|
||||
} else {
|
||||
// For dates beyond overnext month, return start of overnext month from the date
|
||||
targetMonth = dateMonth + 2;
|
||||
if (targetMonth > 11) {
|
||||
targetMonth = 0;
|
||||
targetYear++;
|
||||
}
|
||||
// If the input date is beyond the next two months relative to *now*,
|
||||
// use the *input date's* month/year as the base.
|
||||
if (!isWithinCurrentOrNextTwoMonths) {
|
||||
baseYear = dateYear;
|
||||
baseMonth = dateMonth;
|
||||
}
|
||||
// Otherwise, the base remains the current year/month.
|
||||
|
||||
const result = new Date(Date.UTC(targetYear, targetMonth, 1));
|
||||
// Calculate the start of the month two months after the determined base date.
|
||||
const result = new Date(Date.UTC(baseYear, baseMonth + 2, 1));
|
||||
result.setUTCHours(0, 0, 0, 0);
|
||||
return result.toISOString();
|
||||
}
|
||||
@@ -169,7 +169,7 @@ export const getBusyCalendarTimes = async (
|
||||
// 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).endOf("month").add(14, "hours").format();
|
||||
const endDate = dayjs(dateTo).add(14, "hours").format();
|
||||
|
||||
if (includeTimeZone) {
|
||||
results = await getCalendarsEventsWithTimezones(withCredentials, startDate, endDate, selectedCalendars);
|
||||
|
||||
Reference in new issue
Block a user