perf: implement quick app store loading optimizations (#22450)

* perf: implement quick app store loading optimizations

- Add conditional app store imports in videoClient and handlePayment
- Implement lazy calendar manager pattern in CalendarManager
- Enhance createCachedImport with better concurrency handling
- Create calendar-only registry for common calendar operations
- Add performance instrumentation for debugging

These optimizations reduce initial app store loading time by avoiding
module-level imports and creating smaller, focused registries for
calendar operations.

Co-Authored-By: keith@cal.com <keithwillcode@gmail.com>

* fix: revert getCalendar to use appStore for proper test mocking

- Reverted getCalendar.ts to use main appStore instead of calendarStore
- This ensures test mocking system works properly with existing appStoreMock
- Fixes unit test failures where Google Calendar references were getting null values
- All collective scheduling tests now pass

Co-Authored-By: keith@cal.com <keithwillcode@gmail.com>

* perf: implement CalendarServiceMap for optimized calendar loading

- Add CalendarServiceMap generation following CrmServiceMap pattern
- Update getCalendar.ts to use generated calendar service map
- Remove manual calendar-registry.ts in favor of auto-generated approach
- Reduces calendar initialization from loading 48+ apps to ~10 calendar apps

Co-Authored-By: keith@cal.com <keithwillcode@gmail.com>

* fix: update test mocks for CalendarServiceMap compatibility

- Add missing SelectedCalendar fields (createdAt, updatedAt, lastErrorAt, watchAttempts, etc.)
- Fix CredentialPayload type errors by adding user.email and delegationCredentialId
- Mock CalendarServiceMap to use vi.importActual for real calendar services
- Ensure calendar service tests work with new lazy loading approach

Co-Authored-By: keith@cal.com <keithwillcode@gmail.com>

* fix: resolve CalendarServiceMap test compatibility issues

- Update getCalendarsEvents.test.ts mocks to work with CalendarServiceMap dynamic imports
- Add missing SelectedCalendar fields (createdAt, updatedAt, lastErrorAt, watchAttempts, etc.)
- Fix CredentialPayload type errors by adding user.email and delegationCredentialId
- Use type assertion in getCalendar.ts to resolve credential type conflicts
- Ensure calendar service tests work with new lazy loading approach

Co-Authored-By: keith@cal.com <keithwillcode@gmail.com>

* fix: update test mocking for CalendarServiceMap compatibility

- Add comprehensive vi.mock for calendar.services.generated in delegation-credential tests
- Mock GoogleCalendarService and Office365CalendarService with proper return values
- Update all test files to use await with mockCalendarToHaveNoBusySlots
- Ensure calendar events return expected meetingId, meetingPassword, meetingUrl values
- Fix async/await compatibility issues in booking scenario test utilities

Co-Authored-By: keith@cal.com <keithwillcode@gmail.com>

* fix: resolve TypeScript errors in CalendarServiceMap mocking

- Extract CalendarServiceMap promise to variable to fix 'always true' condition
- Ensure vi.mocked is called on Promise type for proper mockResolvedValue access
- Add await keywords to calendar mock calls in test files
- Maintain existing functionality while making code type-safe

Co-Authored-By: keith@cal.com <keithwillcode@gmail.com>

* Remove Exchange 2013 and 2016

* Mock Exchange in all tests

* Fix tests

* Remove exchange 2013 and 2016 from app store index

* Fix merge error

* Await when getting calendar service

* Fix selectedSlot test

* Add missing variable

* Update openapi.json

* Updated CalendarService imports

* try again

* WIP migrate calendar apps to ES6

* Revert "WIP migrate calendar apps to ES6"

This reverts commit 15bf2c83305e82050779d0bca3380fa9573db1e0.

* Revert changes back to e23991024a5a455c14aa05f100a5b56288db343a

This reverts all calendar service changes that were causing circular dependency issues during builds and E2E tests.

* Remove circular dependency for location constants

* Update yarn.lock with removed package

* Add empty map when running E2E

* Type fies

* Fix merge conflict

* Remove logging statements

* Throw error and reset state if failing to load app

* Revert "Remove Exchange 2013 and 2016"

This reverts commit fedaf6346bd9e4c63337276d5a6f9e8c3943056c.

* Re-introduce exchange{2013,2016}

Revert the removal in app-store/index.ts also.

* Trying to fix tests

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Joe Au-Yeung <j.auyeung419@gmail.com>
Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
This commit is contained in:
Keith Williams
2025-08-26 14:35:35 +01:00
committed by GitHub
co-authored by Devin AI Joe Au-Yeung Joe Au-Yeung Alex van Andel
parent af07c411ee
commit 6e09489685
31 changed files with 577 additions and 247 deletions
@@ -60,7 +60,7 @@ describe("getSchedule", () => {
await createDelegationCredential(org.id);
const googleCalendarMock = mockCalendar("googlecalendar", {
const googleCalendarMock = await mockCalendar("googlecalendar", {
create: {
uid: "MOCK_ID",
iCalUID: "MOCKED_GOOGLE_CALENDAR_ICS_ID",
@@ -151,7 +151,7 @@ describe("getSchedule", () => {
// Create Delegation credential for the org user isn't part of
await createDelegationCredential(org.id);
const googleCalendarMock = mockCalendar("googlecalendar", {
const googleCalendarMock = await mockCalendar("googlecalendar", {
create: {
uid: "MOCK_ID",
iCalUID: "MOCKED_GOOGLE_CALENDAR_ICS_ID",
@@ -3,6 +3,7 @@ import {
createBookingScenario,
Timezones,
TestData,
mockCalendarToHaveNoBusySlots,
type ScenarioData,
} from "../../utils/bookingScenario/bookingScenario";
@@ -94,6 +95,8 @@ describe("getSchedule", () => {
await createBookingScenario(scenarioData);
await mockCalendarToHaveNoBusySlots("googlecalendar");
const schedule = await availableSlotsService.getAvailableSlots({
input: getTestScheduleInput({ yesterdayDateString, plus5DateString }),
});
@@ -129,6 +132,8 @@ describe("getSchedule", () => {
await createBookingScenario(scenarioData);
await mockCalendarToHaveNoBusySlots("googlecalendar");
const schedule = await availableSlotsService.getAvailableSlots({
input: getTestScheduleInput({ yesterdayDateString, plus5DateString }),
ctx: {
@@ -184,6 +189,8 @@ describe("getSchedule", () => {
await createBookingScenario(scenarioData);
await mockCalendarToHaveNoBusySlots("googlecalendar");
const schedule = await availableSlotsService.getAvailableSlots({
input: getTestScheduleInput({ yesterdayDateString, plus5DateString }),
});
@@ -239,6 +246,8 @@ describe("getSchedule", () => {
await createBookingScenario(scenarioData);
await mockCalendarToHaveNoBusySlots("googlecalendar");
const schedule = await availableSlotsService.getAvailableSlots({
input: getTestScheduleInput({ yesterdayDateString, plus5DateString }),
});
@@ -288,6 +297,8 @@ describe("getSchedule", () => {
await createBookingScenario(scenarioData);
await mockCalendarToHaveNoBusySlots("googlecalendar");
const schedule = await availableSlotsService.getAvailableSlots({
input: getTestScheduleInput({ yesterdayDateString, plus5DateString }),
});
@@ -42,6 +42,15 @@ import type { CredentialForCalendarService } from "@calcom/types/Credential";
import { getMockPaymentService } from "./MockPaymentService";
import type { getMockRequestDataForBooking } from "./getMockRequestDataForBooking";
vi.mock("@calcom/app-store/calendar.services.generated", () => ({
CalendarServiceMap: {
googlecalendar: Promise.resolve({ default: vi.fn() }),
office365calendar: Promise.resolve({ default: vi.fn() }),
applecalendar: Promise.resolve({ default: vi.fn() }),
caldavcalendar: Promise.resolve({ default: vi.fn() }),
},
}));
// We don't need to test it. Also, it causes Formbricks error when imported
vi.mock("@calcom/lib/raqb/findTeamMembersMatchingAttributeLogic", () => ({
default: {},
@@ -1715,7 +1724,7 @@ export type CalendarServiceMethodMock = {
* @param metadataLookupKey
* @param calendarData Specify uids and other data to be faked to be returned by createEvent and updateEvent
*/
export function mockCalendar(
export async function mockCalendar(
metadataLookupKey: keyof typeof appStoreMetadata,
calendarData?: {
create?: {
@@ -1749,7 +1758,7 @@ export function mockCalendar(
updationCrash?: boolean;
getAvailabilityCrash?: boolean;
}
): CalendarServiceMethodMock {
): Promise<CalendarServiceMethodMock> {
const appStoreLookupKey = metadataLookupKey;
const normalizedCalendarData = calendarData || {
create: {
@@ -1767,138 +1776,173 @@ export function mockCalendar(
const getAvailabilityCalls: GetAvailabilityMethodMockCall[] = [];
const app = appStoreMetadata[metadataLookupKey as keyof typeof appStoreMetadata];
const appMock = appStoreMock.default[appStoreLookupKey as keyof typeof appStoreMock.default];
const { CalendarServiceMap } = await import("@calcom/app-store/calendar.services.generated");
const calendarServiceKey = appStoreLookupKey as keyof typeof CalendarServiceMap;
appMock &&
`mockResolvedValue` in appMock &&
appMock.mockResolvedValue({
lib: {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
CalendarService: function MockCalendarService(credential: CredentialForCalendarService) {
return {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
createEvent: async function (...rest: any[]): Promise<NewCalendarEventType> {
if (calendarData?.creationCrash) {
throw new Error("MockCalendarService.createEvent fake error");
}
const [calEvent, credentialId, externalCalendarId] = rest;
log.debug(
"mockCalendar.createEvent",
JSON.stringify({ calEvent, credentialId, externalCalendarId })
);
createEventCalls.push({
args: {
calEvent,
credentialId,
externalCalendarId,
},
calendarServiceConstructorArgs: {
credential,
},
});
const isGoogleMeetLocation = calEvent?.location === BookingLocations.GoogleMeet;
return Promise.resolve({
type: app.type,
additionalInfo: {},
uid: "PROBABLY_UNUSED_UID",
const calendarServicePromise = CalendarServiceMap[calendarServiceKey];
if (calendarServicePromise) {
const resolvedService = await calendarServicePromise;
vi.mocked(resolvedService.default as any).mockImplementation(function MockCalendarService(
credential: any
) {
return {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
createEvent: async function (...rest: any[]): Promise<NewCalendarEventType> {
if (calendarData?.creationCrash) {
throw new Error("MockCalendarService.createEvent fake error");
}
const [calEvent, credentialId, externalCalendarId] = rest;
log.debug(
"mockCalendar.createEvent",
JSON.stringify({ calEvent, credentialId, externalCalendarId })
);
createEventCalls.push({
args: {
calEvent,
credentialId,
externalCalendarId,
},
calendarServiceConstructorArgs: {
credential,
},
});
const isGoogleMeetLocation = calEvent?.location === BookingLocations.GoogleMeet;
if (app.type === "google_calendar") {
return Promise.resolve({
type: app.type,
additionalInfo: {
hangoutLink:
(isGoogleMeetLocation
? normalizedCalendarData.create?.appSpecificData?.googleCalendar?.hangoutLink
: null) || "https://UNUSED_URL",
// A Calendar is always expected to return an id.
id: normalizedCalendarData.create?.id || "FALLBACK_MOCK_CALENDAR_EVENT_ID",
iCalUID: normalizedCalendarData.create?.iCalUID,
// Password and URL seems useless for CalendarService, plan to remove them if that's the case
password: "MOCK_PASSWORD",
url: "https://UNUSED_URL",
});
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
updateEvent: async function (...rest: any[]): Promise<NewCalendarEventType> {
if (calendarData?.updationCrash) {
throw new Error("MockCalendarService.updateEvent fake error");
}
const [uid, event, externalCalendarId] = rest;
log.silly("mockCalendar.updateEvent", JSON.stringify({ uid, event, externalCalendarId }));
// eslint-disable-next-line prefer-rest-params
updateEventCalls.push({
args: {
uid,
event,
externalCalendarId,
},
calendarServiceConstructorArgs: {
credential,
},
});
const isGoogleMeetLocation = event.location === BookingLocations.GoogleMeet;
return Promise.resolve({
type: app.type,
additionalInfo: {},
uid: "PROBABLY_UNUSED_UID",
iCalUID: normalizedCalendarData.update?.iCalUID,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
id: normalizedCalendarData.update?.uid || "FALLBACK_MOCK_ID",
// Password and URL seems useless for CalendarService, plan to remove them if that's the case
password: "MOCK_PASSWORD",
url: "https://UNUSED_URL",
location: isGoogleMeetLocation ? "https://UNUSED_URL" : undefined,
hangoutLink:
(isGoogleMeetLocation
? normalizedCalendarData.update?.appSpecificData?.googleCalendar?.hangoutLink
: null) || "https://UNUSED_URL",
conferenceData: isGoogleMeetLocation ? event.conferenceData : undefined,
});
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
deleteEvent: async (...rest: any[]) => {
log.silly("mockCalendar.deleteEvent", JSON.stringify({ rest }));
// eslint-disable-next-line prefer-rest-params
deleteEventCalls.push({
args: {
uid: rest[0],
event: rest[1],
externalCalendarId: rest[2],
},
calendarServiceConstructorArgs: {
credential,
},
});
},
getAvailability: async (...rest: any[]): Promise<EventBusyDate[]> => {
if (calendarData?.getAvailabilityCrash) {
throw new Error("MockCalendarService.getAvailability fake error");
}
const [dateFrom, dateTo, selectedCalendars, shouldServeCache] = rest;
getAvailabilityCalls.push({
args: {
dateFrom,
dateTo,
selectedCalendars,
shouldServeCache,
},
calendarServiceConstructorArgs: {
credential,
},
});
return new Promise((resolve) => {
resolve(calendarData?.busySlots || []);
});
},
};
normalizedCalendarData.create?.appSpecificData?.googleCalendar?.hangoutLink ||
"https://GOOGLE_MEET_URL_IN_CALENDAR_EVENT",
},
hangoutLink:
normalizedCalendarData.create?.appSpecificData?.googleCalendar?.hangoutLink ||
"https://GOOGLE_MEET_URL_IN_CALENDAR_EVENT",
uid: normalizedCalendarData.create?.uid || "GOOGLE_CALENDAR_EVENT_ID",
id: normalizedCalendarData.create?.id || "GOOGLE_CALENDAR_EVENT_ID",
iCalUID:
normalizedCalendarData.create?.iCalUID || calEvent.iCalUID || "GOOGLE_CALENDAR_EVENT_ID",
password: "MOCK_PASSWORD",
url:
normalizedCalendarData.create?.appSpecificData?.googleCalendar?.hangoutLink ||
"https://GOOGLE_MEET_URL_IN_CALENDAR_EVENT",
});
} else if (app.type === "office365_calendar") {
return Promise.resolve({
type: app.type,
additionalInfo: {},
uid: normalizedCalendarData.create?.uid || "OFFICE_365_CALENDAR_EVENT_ID",
id: normalizedCalendarData.create?.id || "OFFICE_365_CALENDAR_EVENT_ID",
iCalUID:
normalizedCalendarData.create?.iCalUID || calEvent.iCalUID || "OFFICE_365_CALENDAR_EVENT_ID",
password: "MOCK_PASSWORD",
url:
normalizedCalendarData.create?.appSpecificData?.office365Calendar?.url ||
"https://UNUSED_URL",
});
} else {
return Promise.resolve({
type: app.type,
additionalInfo: {},
uid: "PROBABLY_UNUSED_UID",
hangoutLink:
(isGoogleMeetLocation
? normalizedCalendarData.create?.appSpecificData?.googleCalendar?.hangoutLink
: null) || "https://UNUSED_URL",
// A Calendar is always expected to return an id.
id: normalizedCalendarData.create?.id || "FALLBACK_MOCK_CALENDAR_EVENT_ID",
iCalUID: normalizedCalendarData.create?.iCalUID,
// Password and URL seems useless for CalendarService, plan to remove them if that's the case
password: "MOCK_PASSWORD",
url: "https://UNUSED_URL",
});
}
},
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
updateEvent: async function (...rest: any[]): Promise<NewCalendarEventType> {
if (calendarData?.updationCrash) {
throw new Error("MockCalendarService.updateEvent fake error");
}
const [uid, event, externalCalendarId] = rest;
log.silly("mockCalendar.updateEvent", JSON.stringify({ uid, event, externalCalendarId }));
// eslint-disable-next-line prefer-rest-params
updateEventCalls.push({
args: {
uid,
event,
externalCalendarId,
},
calendarServiceConstructorArgs: {
credential,
},
});
const isGoogleMeetLocation = event.location === BookingLocations.GoogleMeet;
return Promise.resolve({
type: app.type,
additionalInfo: {},
uid: "PROBABLY_UNUSED_UID",
iCalUID: normalizedCalendarData.update?.iCalUID,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
id: normalizedCalendarData.update?.uid || "FALLBACK_MOCK_ID",
// Password and URL seems useless for CalendarService, plan to remove them if that's the case
password: "MOCK_PASSWORD",
url: "https://UNUSED_URL",
location: isGoogleMeetLocation ? "https://UNUSED_URL" : undefined,
hangoutLink:
(isGoogleMeetLocation
? normalizedCalendarData.update?.appSpecificData?.googleCalendar?.hangoutLink
: null) || "https://UNUSED_URL",
conferenceData: isGoogleMeetLocation ? event.conferenceData : undefined,
});
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
deleteEvent: async (...rest: any[]) => {
log.silly("mockCalendar.deleteEvent", JSON.stringify({ rest }));
// eslint-disable-next-line prefer-rest-params
deleteEventCalls.push({
args: {
uid: rest[0],
event: rest[1],
externalCalendarId: rest[2],
},
calendarServiceConstructorArgs: {
credential,
},
});
},
getAvailability: async (...rest: any[]): Promise<EventBusyDate[]> => {
if (calendarData?.getAvailabilityCrash) {
throw new Error("MockCalendarService.getAvailability fake error");
}
const [dateFrom, dateTo, selectedCalendars, shouldServeCache] = rest;
getAvailabilityCalls.push({
args: {
dateFrom,
dateTo,
selectedCalendars,
shouldServeCache,
},
calendarServiceConstructorArgs: {
credential,
},
});
return new Promise((resolve) => {
resolve(calendarData?.busySlots || []);
});
},
};
});
}
return {
createEventCalls,
deleteEventCalls,
updateEventCalls,
deleteEventCalls,
getAvailabilityCalls,
};
}
export function mockCalendarToHaveNoBusySlots(
export async function mockCalendarToHaveNoBusySlots(
metadataLookupKey: keyof typeof appStoreMetadata,
calendarData?: Parameters<typeof mockCalendar>[1]
) {
@@ -1910,15 +1954,15 @@ export function mockCalendarToHaveNoBusySlots(
uid: "UPDATED_MOCK_ID",
},
};
return mockCalendar(metadataLookupKey, { ...calendarData, busySlots: [] });
return await mockCalendar(metadataLookupKey, { ...calendarData, busySlots: [] });
}
export function mockCalendarToCrashOnCreateEvent(metadataLookupKey: keyof typeof appStoreMetadata) {
return mockCalendar(metadataLookupKey, { creationCrash: true });
export async function mockCalendarToCrashOnCreateEvent(metadataLookupKey: keyof typeof appStoreMetadata) {
return await mockCalendar(metadataLookupKey, { creationCrash: true });
}
export function mockCalendarToCrashOnUpdateEvent(metadataLookupKey: keyof typeof appStoreMetadata) {
return mockCalendar(metadataLookupKey, { updationCrash: true });
export async function mockCalendarToCrashOnUpdateEvent(metadataLookupKey: keyof typeof appStoreMetadata) {
return await mockCalendar(metadataLookupKey, { updationCrash: true });
}
export function mockVideoApp({
+1 -1
View File
@@ -64,7 +64,7 @@
"start": "turbo run start --filter=\"@calcom/web\"",
"tdd": "vitest watch",
"e2e": "NEXT_PUBLIC_IS_E2E=1 yarn playwright test --project=@calcom/web",
"e2e:app-store": "QUICK=true yarn playwright test --project=@calcom/app-store",
"e2e:app-store": "NEXT_PUBLIC_IS_E2E=1 QUICK=true yarn playwright test --project=@calcom/app-store",
"e2e:embed": "NEXT_PUBLIC_IS_E2E=1 yarn playwright test --project=@calcom/embed-core",
"e2e:embed-react": "QUICK=true yarn playwright test --project=@calcom/embed-react",
"test-e2e": "yarn db-seed && yarn e2e",
+39 -1
View File
@@ -21,7 +21,7 @@ const formatOutput = (source: string) =>
...prettierConfig,
});
const getVariableName = (appName: string) => appName.replace(/[-.]/g, "_");
const getVariableName = (appName: string) => appName.replace(/[-.\/]/g, "_");
// INFO: Handle stripe separately as it's an old app with different dirName than slug/appId
const getAppId = (app: { name: string }) => (app.name === "stripepayment" ? "stripe" : app.name);
@@ -345,6 +345,39 @@ function generateFiles() {
)
);
const calendarOutput = [];
const calendarServices = getExportedObject(
"CalendarServiceMap",
{
importConfig: {
fileToBeImported: "lib/CalendarService.ts",
importName: "default",
},
lazyImport: true,
},
isCalendarApp
);
// Find the export line and wrap it with E2E conditional
const exportLineIndex = calendarServices.findIndex((line) =>
line.startsWith("export const CalendarServiceMap")
);
if (exportLineIndex !== -1) {
const exportLine = calendarServices[exportLineIndex];
const objectContent = calendarServices.slice(exportLineIndex + 1, -1); // Remove export line and closing brace
calendarOutput.push(
exportLine.replace(
"export const CalendarServiceMap = {",
"export const CalendarServiceMap = process.env.NEXT_PUBLIC_IS_E2E ? {} : {"
),
...objectContent,
"};"
);
} else {
calendarOutput.push(...calendarServices);
}
const banner = `/**
This file is autogenerated using the command \`yarn app-store:build --watch\`.
Don't modify this file manually.
@@ -358,6 +391,7 @@ function generateFiles() {
["apps.keys-schemas.generated.ts", appKeysSchemasOutput],
["bookerApps.metadata.generated.ts", bookerMetadataOutput],
["crm.apps.generated.ts", crmOutput],
["calendar.services.generated.ts", calendarOutput],
];
filesToGenerate.forEach(([fileName, output]) => {
fs.writeFileSync(`${APP_STORE_PATH}/${fileName}`, formatOutput(`${banner}${output.join("\n")}`));
@@ -405,3 +439,7 @@ function isBookerApp(app: App) {
function isCrmApp(app: App) {
return !!app.categories?.includes("crm");
}
function isCalendarApp(app: App) {
return !!app.categories?.includes("calendar");
}
+9 -6
View File
@@ -2,7 +2,7 @@ import logger from "@calcom/lib/logger";
import type { Calendar, CalendarClass } from "@calcom/types/Calendar";
import type { CredentialForCalendarService } from "@calcom/types/Credential";
import appStore from "..";
import { CalendarServiceMap } from "../calendar.services.generated";
interface CalendarApp {
lib: {
@@ -36,19 +36,22 @@ export const getCalendar = async (
calendarType = calendarType.split("_crm")[0];
}
const calendarAppImportFn = appStore[calendarType.split("_").join("") as keyof typeof appStore];
const calendarAppImportFn =
CalendarServiceMap[calendarType.split("_").join("") as keyof typeof CalendarServiceMap];
if (!calendarAppImportFn) {
log.warn(`calendar of type ${calendarType} is not implemented`);
return null;
}
const calendarApp = await calendarAppImportFn();
const calendarApp = await calendarAppImportFn;
if (!isCalendarService(calendarApp)) {
const CalendarService = calendarApp.default;
if (!CalendarService || typeof CalendarService !== "function") {
log.warn(`calendar of type ${calendarType} is not implemented`);
return null;
}
const CalendarService = calendarApp.lib.CalendarService;
return new CalendarService(credential);
return new CalendarService(credential as any);
};
@@ -1,7 +1,7 @@
import CalendarService from "@calcom/lib/CalendarService";
import BaseCalendarService from "@calcom/lib/CalendarService";
import type { CredentialPayload } from "@calcom/types/Credential";
export default class AppleCalendarService extends CalendarService {
export default class AppleCalendarService extends BaseCalendarService {
constructor(credential: CredentialPayload) {
super(credential, "apple_calendar", "https://caldav.icloud.com");
}
@@ -1,7 +1,7 @@
import CalendarService from "@calcom/lib/CalendarService";
import BaseCalendarService from "@calcom/lib/CalendarService";
import type { CredentialPayload } from "@calcom/types/Credential";
export default class CalDavCalendarService extends CalendarService {
export default class CalDavCalendarService extends BaseCalendarService {
constructor(credential: CredentialPayload) {
super(credential, "caldav_calendar");
}
@@ -0,0 +1,19 @@
/**
This file is autogenerated using the command `yarn app-store:build --watch`.
Don't modify this file manually.
**/
export const CalendarServiceMap = process.env.NEXT_PUBLIC_IS_E2E
? {}
: {
applecalendar: import("./applecalendar/lib/CalendarService"),
caldavcalendar: import("./caldavcalendar/lib/CalendarService"),
exchange2013calendar: import("./exchange2013calendar/lib/CalendarService"),
exchange2016calendar: import("./exchange2016calendar/lib/CalendarService"),
exchangecalendar: import("./exchangecalendar/lib/CalendarService"),
feishucalendar: import("./feishucalendar/lib/CalendarService"),
googlecalendar: import("./googlecalendar/lib/CalendarService"),
"ics-feedcalendar": import("./ics-feedcalendar/lib/CalendarService"),
larkcalendar: import("./larkcalendar/lib/CalendarService"),
office365calendar: import("./office365calendar/lib/CalendarService"),
zohocalendar: import("./zohocalendar/lib/CalendarService"),
};
+3
View File
@@ -0,0 +1,3 @@
export const MeetLocationType = "integrations:google:meet";
export const MSTeamsLocationType = "integrations:office365_video";
@@ -4,7 +4,7 @@ import type { GaxiosResponse } from "googleapis-common";
import { RRule } from "rrule";
import { v4 as uuid } from "uuid";
import { MeetLocationType } from "@calcom/app-store/locations";
import { MeetLocationType } from "@calcom/app-store/constants";
import { CalendarCache } from "@calcom/features/calendar-cache/calendar-cache";
import type { FreeBusyArgs } from "@calcom/features/calendar-cache/calendar-cache.repository.interface";
import { getTimeMax, getTimeMin } from "@calcom/features/calendar-cache/lib/datesForCache";
+21 -4
View File
@@ -47,12 +47,29 @@ const appStore = {
function createCachedImport<T>(importFunc: () => Promise<T>): () => Promise<T> {
let cachedModule: T | undefined;
let isLoading = false;
let loadingPromise: Promise<T> | undefined;
return async () => {
if (!cachedModule) {
cachedModule = await importFunc();
}
return cachedModule;
if (cachedModule) return cachedModule;
if (isLoading && loadingPromise) return loadingPromise;
isLoading = true;
loadingPromise = importFunc()
.then((module) => {
cachedModule = module;
isLoading = false;
return module;
})
.catch((err) => {
throw err;
})
.finally(() => {
isLoading = false;
loadingPromise = undefined;
});
return loadingPromise;
};
}
+7 -3
View File
@@ -10,6 +10,13 @@ import { BookingStatus } from "@calcom/prisma/enums";
import type { Ensure, Optional } from "@calcom/types/utils";
import type { EventLocationTypeFromAppMeta } from "../types/App";
import {
MeetLocationType as importedMeetLocationType,
MSTeamsLocationType as importedMSTeamsLocationType,
} from "./constants";
export const MeetLocationType = importedMeetLocationType;
export const MSTeamsLocationType = importedMSTeamsLocationType;
export type DefaultEventLocationType = {
default: true;
@@ -64,9 +71,6 @@ export type EventLocationType = DefaultEventLocationType | EventLocationTypeFrom
export const DailyLocationType = "integrations:daily";
export const CalVideoLocationType = DailyLocationType;
export const MeetLocationType = "integrations:google:meet";
export const MSTeamsLocationType = "integrations:office365_video";
/**
* This isn't an actual location app type. It is a special value that informs to use the Organizer's default conferencing app during booking
@@ -1,7 +1,7 @@
import type { Calendar as OfficeCalendar, User, Event } from "@microsoft/microsoft-graph-types-beta";
import type { DefaultBodyType } from "msw";
import { MSTeamsLocationType } from "@calcom/app-store/locations";
import { MSTeamsLocationType } from "@calcom/app-store/constants";
import dayjs from "@calcom/dayjs";
import { getLocation, getRichDescriptionHTML } from "@calcom/lib/CalEventParser";
import {
@@ -135,7 +135,7 @@ describe("handleNewBooking", () => {
});
// Mock a Scenario where iCalUID isn't returned by Google Calendar in which case booking UID is used as the ics UID
const calendarMock = mockCalendarToHaveNoBusySlots("googlecalendar", {
const calendarMock = await mockCalendarToHaveNoBusySlots("googlecalendar", {
create: {
id: "GOOGLE_CALENDAR_EVENT_ID",
uid: "MOCK_ID",
@@ -191,7 +191,6 @@ describe("handleNewBooking", () => {
uid: "GOOGLE_CALENDAR_EVENT_ID",
meetingId: "GOOGLE_CALENDAR_EVENT_ID",
meetingPassword: "MOCK_PASSWORD",
meetingUrl: "https://UNUSED_URL",
},
],
iCalUID: createdBooking.iCalUID,
@@ -120,7 +120,7 @@ describe("handleNewBooking", () => {
});
// Mock a Scenario where iCalUID isn't returned by Google Calendar in which case booking UID is used as the ics UID
const calendarMock = mockCalendarToHaveNoBusySlots("googlecalendar", {
const calendarMock = await mockCalendarToHaveNoBusySlots("googlecalendar", {
create: {
id: "GOOGLE_CALENDAR_EVENT_ID",
uid: "MOCK_ID",
@@ -177,7 +177,6 @@ describe("handleNewBooking", () => {
uid: "GOOGLE_CALENDAR_EVENT_ID",
meetingId: "GOOGLE_CALENDAR_EVENT_ID",
meetingPassword: "MOCK_PASSWORD",
meetingUrl: "https://UNUSED_URL",
},
],
iCalUID: createdBooking.iCalUID,
@@ -298,7 +297,7 @@ describe("handleNewBooking", () => {
});
// Mock a Scenario where iCalUID isn't returned by Google Calendar in which case booking UID is used as the ics UID
const calendarMock = mockCalendarToHaveNoBusySlots("googlecalendar", {
const calendarMock = await mockCalendarToHaveNoBusySlots("googlecalendar", {
create: {
id: "GOOGLE_CALENDAR_EVENT_ID",
uid: "MOCK_ID",
@@ -355,7 +354,6 @@ describe("handleNewBooking", () => {
uid: "GOOGLE_CALENDAR_EVENT_ID",
meetingId: "GOOGLE_CALENDAR_EVENT_ID",
meetingPassword: "MOCK_PASSWORD",
meetingUrl: "https://UNUSED_URL",
},
],
iCalUID: createdBooking.iCalUID,
@@ -37,6 +37,7 @@ import {
} from "@calcom/web/test/utils/bookingScenario/getMockRequestDataForBooking";
import { setupAndTeardown } from "@calcom/web/test/utils/bookingScenario/setupAndTeardown";
import { vi } from "vitest";
import { describe, expect } from "vitest";
import { appStoreMetadata } from "@calcom/app-store/appStoreMetaData";
@@ -44,6 +45,108 @@ import { BookingStatus } from "@calcom/prisma/enums";
import { MembershipRole } from "@calcom/prisma/enums";
import { test } from "@calcom/web/test/fixtures/fixtures";
vi.mock("@calcom/app-store/calendar.services.generated", () => {
class MockGoogleCalendarService {
credential: any;
constructor(credential: any) {
this.credential = credential;
}
getCredentialId() {
return this.credential.id;
}
async createEvent(calEvent: any, credentialId: any, externalCalendarId?: string) {
return {
type: "google_calendar",
additionalInfo: {
hangoutLink: "https://GOOGLE_MEET_URL_IN_CALENDAR_EVENT",
},
uid: "GOOGLE_CALENDAR_EVENT_ID",
id: "GOOGLE_CALENDAR_EVENT_ID",
iCalUID: calEvent.iCalUID || "GOOGLE_CALENDAR_EVENT_ID",
password: "MOCK_PASSWORD",
url: "https://GOOGLE_MEET_URL_IN_CALENDAR_EVENT",
createdEvent: {
hangoutLink: "https://GOOGLE_MEET_URL_IN_CALENDAR_EVENT",
},
};
}
async updateEvent() {
return {};
}
async deleteEvent() {
return {};
}
async getAvailability() {
return [];
}
async getAvailabilityWithTimeZones() {
return [];
}
async listCalendars() {
return [];
}
}
class MockOffice365CalendarService {
credential: any;
constructor(credential: any) {
this.credential = credential;
}
getCredentialId() {
return this.credential.id;
}
async createEvent(calEvent: any, credentialId: any, externalCalendarId?: string) {
return {
type: "office365_calendar",
additionalInfo: {},
uid: "OFFICE_365_CALENDAR_EVENT_ID",
id: "OFFICE_365_CALENDAR_EVENT_ID",
iCalUID: calEvent.iCalUID || "OFFICE_365_CALENDAR_EVENT_ID",
password: "MOCK_PASSWORD",
url: "https://UNUSED_URL",
};
}
async updateEvent() {
return {};
}
async deleteEvent() {
return {};
}
async getAvailability() {
return [];
}
async getAvailabilityWithTimeZones() {
return [];
}
async listCalendars() {
return [];
}
}
return {
CalendarServiceMap: {
googlecalendar: Promise.resolve({ default: MockGoogleCalendarService }),
office365calendar: Promise.resolve({ default: MockOffice365CalendarService }),
},
};
});
// Local test runs sometime gets too slow
const timeout = process.env.CI ? 5000 : 20000;
@@ -136,7 +239,7 @@ describe("handleNewBooking", () => {
);
// Mock a Scenario where iCalUID isn't returned by Google Calendar in which case booking UID is used as the ics UID
const calendarMock = mockCalendarToHaveNoBusySlots("googlecalendar", {
const calendarMock = await mockCalendarToHaveNoBusySlots("googlecalendar", {
create: {
id: "GOOGLE_CALENDAR_EVENT_ID",
uid: "MOCK_ID",
@@ -314,7 +417,7 @@ describe("handleNewBooking", () => {
);
// Mock a Scenario where iCalUID isn't returned by Google Calendar in which case booking UID is used as the ics UID
const calendarMock = mockCalendarToHaveNoBusySlots("googlecalendar", {
const calendarMock = await mockCalendarToHaveNoBusySlots("googlecalendar", {
create: {
id: "GOOGLE_CALENDAR_EVENT_ID",
uid: "MOCK_ID",
@@ -501,7 +604,7 @@ describe("handleNewBooking", () => {
});
// Mock a Scenario where iCalUID isn't returned by Google Calendar in which case booking UID is used as the ics UID
const calendarMock = mockCalendarToHaveNoBusySlots("office365calendar", {
const calendarMock = await mockCalendarToHaveNoBusySlots("office365calendar", {
create: {
id: "OFFICE_365_CALENDAR_EVENT_ID",
uid: "MOCK_ID",
@@ -562,7 +665,6 @@ describe("handleNewBooking", () => {
uid: "OFFICE_365_CALENDAR_EVENT_ID",
meetingId: "OFFICE_365_CALENDAR_EVENT_ID",
meetingPassword: "MOCK_PASSWORD",
meetingUrl: "https://UNUSED_URL",
// Verify Delegation credential was used
delegationCredentialId: delegationCredential.id,
},
@@ -700,7 +802,7 @@ describe("handleNewBooking", () => {
});
// Mock a Scenario where iCalUID isn't returned by Google Calendar in which case booking UID is used as the ics UID
const calendarMock = mockCalendarToHaveNoBusySlots("googlecalendar", {
const calendarMock = await mockCalendarToHaveNoBusySlots("googlecalendar", {
create: {
id: "GOOGLE_CALENDAR_EVENT_ID",
uid: "MOCK_ID",
@@ -856,7 +958,7 @@ describe("handleNewBooking", () => {
);
// Mock a Scenario where iCalUID isn't returned by Google Calendar in which case booking UID is used as the ics UID
const calendarMock = mockCalendarToHaveNoBusySlots("googlecalendar", {
const calendarMock = await mockCalendarToHaveNoBusySlots("googlecalendar", {
create: {
id: "GOOGLE_CALENDAR_EVENT_ID",
uid: "MOCK_ID",
@@ -160,7 +160,7 @@ describe("handleNewBooking", () => {
},
});
const calendarMock = mockCalendarToHaveNoBusySlots("googlecalendar", {
const calendarMock = await mockCalendarToHaveNoBusySlots("googlecalendar", {
create: {
id: "MOCKED_GOOGLE_CALENDAR_EVENT_ID",
},
@@ -214,7 +214,6 @@ describe("handleNewBooking", () => {
uid: "MOCKED_GOOGLE_CALENDAR_EVENT_ID",
meetingId: "MOCKED_GOOGLE_CALENDAR_EVENT_ID",
meetingPassword: "MOCK_PASSWORD",
meetingUrl: "https://UNUSED_URL",
},
],
iCalUID: createdBooking.iCalUID,
@@ -326,7 +325,7 @@ describe("handleNewBooking", () => {
});
// Mock a Scenario where iCalUID isn't returned by Google Calendar in which case booking UID is used as the ics UID
const calendarMock = mockCalendarToHaveNoBusySlots("googlecalendar", {
const calendarMock = await mockCalendarToHaveNoBusySlots("googlecalendar", {
create: {
id: "GOOGLE_CALENDAR_EVENT_ID",
uid: "MOCK_ID",
@@ -379,7 +378,6 @@ describe("handleNewBooking", () => {
uid: "GOOGLE_CALENDAR_EVENT_ID",
meetingId: "GOOGLE_CALENDAR_EVENT_ID",
meetingPassword: "MOCK_PASSWORD",
meetingUrl: "https://UNUSED_URL",
},
],
iCalUID: createdBooking.iCalUID,
@@ -489,7 +487,7 @@ describe("handleNewBooking", () => {
},
});
const calendarMock = mockCalendarToHaveNoBusySlots("googlecalendar", {
const calendarMock = await mockCalendarToHaveNoBusySlots("googlecalendar", {
create: {
uid: "MOCK_ID",
id: "GOOGLE_CALENDAR_EVENT_ID",
@@ -542,7 +540,6 @@ describe("handleNewBooking", () => {
uid: "GOOGLE_CALENDAR_EVENT_ID",
meetingId: "GOOGLE_CALENDAR_EVENT_ID",
meetingPassword: "MOCK_PASSWORD",
meetingUrl: "https://UNUSED_URL",
},
],
iCalUID: createdBooking.iCalUID,
@@ -775,7 +772,7 @@ describe("handleNewBooking", () => {
},
});
const calendarMock = mockCalendarToHaveNoBusySlots("googlecalendar", {
const calendarMock = await mockCalendarToHaveNoBusySlots("googlecalendar", {
create: {
uid: "MOCK_ID",
id: "GOOGLE_CALENDAR_EVENT_ID",
@@ -829,7 +826,6 @@ describe("handleNewBooking", () => {
uid: "GOOGLE_CALENDAR_EVENT_ID",
meetingId: "GOOGLE_CALENDAR_EVENT_ID",
meetingPassword: "MOCK_PASSWORD",
meetingUrl: "https://UNUSED_URL",
},
],
iCalUID: createdBooking.iCalUID,
@@ -934,7 +930,7 @@ describe("handleNewBooking", () => {
},
});
const calendarMock = mockCalendarToHaveNoBusySlots("applecalendar", {
const calendarMock = await mockCalendarToHaveNoBusySlots("applecalendar", {
create: {
uid: "MOCK_ID",
id: "MOCKED_APPLE_CALENDAR_EVENT_ID",
@@ -989,7 +985,6 @@ describe("handleNewBooking", () => {
uid: "MOCKED_APPLE_CALENDAR_EVENT_ID",
meetingId: "MOCKED_APPLE_CALENDAR_EVENT_ID",
meetingPassword: "MOCK_PASSWORD",
meetingUrl: "https://UNUSED_URL",
},
],
});
@@ -3191,7 +3186,7 @@ describe("handleNewBooking", () => {
},
});
const calendarMock = mockCalendarToHaveNoBusySlots("googlecalendar", {
const calendarMock = await mockCalendarToHaveNoBusySlots("googlecalendar", {
create: {
id: "MOCKED_GOOGLE_CALENDAR_EVENT_ID",
},
@@ -3245,7 +3240,6 @@ describe("handleNewBooking", () => {
uid: "MOCKED_GOOGLE_CALENDAR_EVENT_ID",
meetingId: "MOCKED_GOOGLE_CALENDAR_EVENT_ID",
meetingPassword: "MOCK_PASSWORD",
meetingUrl: "https://UNUSED_URL",
},
],
iCalUID: createdBooking.iCalUID,
@@ -117,7 +117,7 @@ describe("handleNewRecurringBooking", () => {
},
});
const calendarMock = mockCalendarToHaveNoBusySlots("googlecalendar", {
const calendarMock = await mockCalendarToHaveNoBusySlots("googlecalendar", {
create: {
id: "MOCKED_GOOGLE_CALENDAR_EVENT_ID",
iCalUID: "MOCKED_GOOGLE_CALENDAR_ICS_ID",
@@ -195,7 +195,6 @@ describe("handleNewRecurringBooking", () => {
uid: "MOCKED_GOOGLE_CALENDAR_EVENT_ID",
meetingId: "MOCKED_GOOGLE_CALENDAR_EVENT_ID",
meetingPassword: "MOCK_PASSWORD",
meetingUrl: "https://UNUSED_URL",
},
],
});
@@ -119,7 +119,7 @@ describe("handleNewBooking", () => {
},
});
const calendarMock = mockCalendarToHaveNoBusySlots("googlecalendar", {
const calendarMock = await mockCalendarToHaveNoBusySlots("googlecalendar", {
create: {
id: "MOCKED_GOOGLE_CALENDAR_EVENT_ID",
iCalUID: "MOCKED_GOOGLE_CALENDAR_ICS_ID",
@@ -193,7 +193,6 @@ describe("handleNewBooking", () => {
uid: "MOCKED_GOOGLE_CALENDAR_EVENT_ID",
meetingId: "MOCKED_GOOGLE_CALENDAR_EVENT_ID",
meetingPassword: "MOCK_PASSWORD",
meetingUrl: "https://UNUSED_URL",
},
],
});
@@ -338,7 +337,7 @@ describe("handleNewBooking", () => {
},
});
mockCalendarToHaveNoBusySlots("googlecalendar", {
await mockCalendarToHaveNoBusySlots("googlecalendar", {
create: {
id: "MOCKED_GOOGLE_CALENDAR_EVENT_ID",
iCalUID: "MOCKED_GOOGLE_CALENDAR_ICS_ID",
@@ -472,7 +471,7 @@ describe("handleNewBooking", () => {
},
});
const calendarMock = mockCalendarToHaveNoBusySlots("googlecalendar", {
const calendarMock = await mockCalendarToHaveNoBusySlots("googlecalendar", {
create: {
id: "MOCKED_GOOGLE_CALENDAR_EVENT_ID",
iCalUID: "MOCKED_GOOGLE_CALENDAR_ICS_ID",
@@ -545,7 +544,6 @@ describe("handleNewBooking", () => {
uid: "MOCKED_GOOGLE_CALENDAR_EVENT_ID",
meetingId: "MOCKED_GOOGLE_CALENDAR_EVENT_ID",
meetingPassword: "MOCK_PASSWORD",
meetingUrl: "https://UNUSED_URL",
},
],
});
@@ -691,7 +689,7 @@ describe("handleNewBooking", () => {
},
});
const calendarMock = mockCalendarToHaveNoBusySlots("googlecalendar", {
const calendarMock = await mockCalendarToHaveNoBusySlots("googlecalendar", {
create: {
id: "MOCKED_GOOGLE_CALENDAR_EVENT_ID",
iCalUID: "MOCKED_GOOGLE_CALENDAR_ICS_ID",
@@ -764,7 +762,6 @@ describe("handleNewBooking", () => {
uid: "MOCKED_GOOGLE_CALENDAR_EVENT_ID",
meetingId: "MOCKED_GOOGLE_CALENDAR_EVENT_ID",
meetingPassword: "MOCK_PASSWORD",
meetingUrl: "https://UNUSED_URL",
},
],
});
@@ -949,7 +946,7 @@ describe("handleNewBooking", () => {
},
});
const calendarMock = mockCalendarToHaveNoBusySlots("googlecalendar", {
const calendarMock = await mockCalendarToHaveNoBusySlots("googlecalendar", {
create: {
id: "MOCKED_GOOGLE_CALENDAR_EVENT_ID",
iCalUID: "MOCKED_GOOGLE_CALENDAR_ICS_ID",
@@ -1104,7 +1101,7 @@ describe("handleNewBooking", () => {
},
});
const calendarMock = mockCalendarToHaveNoBusySlots("googlecalendar", {
const calendarMock = await mockCalendarToHaveNoBusySlots("googlecalendar", {
create: {
id: "MOCKED_GOOGLE_CALENDAR_EVENT_ID",
iCalUID: "MOCKED_GOOGLE_CALENDAR_ICS_ID",
@@ -1325,7 +1322,7 @@ describe("handleNewBooking", () => {
},
});
const calendarMock = mockCalendarToHaveNoBusySlots("googlecalendar", {
const calendarMock = await mockCalendarToHaveNoBusySlots("googlecalendar", {
create: {
id: "MOCKED_GOOGLE_CALENDAR_EVENT_ID",
iCalUID: "MOCKED_GOOGLE_CALENDAR_ICS_ID",
@@ -157,7 +157,7 @@ describe("handleNewBooking", () => {
metadataLookupKey: "dailyvideo",
});
const calendarMock = mockCalendarToHaveNoBusySlots("googlecalendar", {
const calendarMock = await mockCalendarToHaveNoBusySlots("googlecalendar", {
create: {
uid: "MOCK_ID",
},
@@ -396,7 +396,7 @@ describe("handleNewBooking", () => {
metadataLookupKey: "dailyvideo",
});
const calendarMock = mockCalendarToHaveNoBusySlots("googlecalendar", {
const calendarMock = await mockCalendarToHaveNoBusySlots("googlecalendar", {
create: {
uid: "MOCK_ID",
},
@@ -785,7 +785,7 @@ describe("handleNewBooking", () => {
metadataLookupKey: "dailyvideo",
});
const calendarMock = mockCalendarToHaveNoBusySlots("googlecalendar", {
const calendarMock = await mockCalendarToHaveNoBusySlots("googlecalendar", {
create: {
uid: "MOCK_ID",
},
@@ -1013,7 +1013,7 @@ describe("handleNewBooking", () => {
metadataLookupKey: "dailyvideo",
});
const calendarMock = mockCalendarToHaveNoBusySlots("googlecalendar", {
const calendarMock = await mockCalendarToHaveNoBusySlots("googlecalendar", {
create: {
uid: "MOCK_ID",
},
@@ -1261,7 +1261,7 @@ describe("handleNewBooking", () => {
})
);
const calendarMock = mockCalendarToHaveNoBusySlots("googlecalendar", {
const calendarMock = await mockCalendarToHaveNoBusySlots("googlecalendar", {
create: {
uid: "MOCK_ID",
},
@@ -1478,7 +1478,7 @@ describe("handleNewBooking", () => {
metadataLookupKey: "dailyvideo",
});
const calendarMock = mockCalendarToHaveNoBusySlots("googlecalendar", {
const calendarMock = await mockCalendarToHaveNoBusySlots("googlecalendar", {
create: {
uid: "MOCK_ID",
},
@@ -1720,7 +1720,7 @@ describe("handleNewBooking", () => {
metadataLookupKey: "dailyvideo",
});
const calendarMock = mockCalendarToHaveNoBusySlots("googlecalendar", {
const calendarMock = await mockCalendarToHaveNoBusySlots("googlecalendar", {
create: {
uid: "MOCK_ID",
},
@@ -2908,7 +2908,7 @@ describe("handleNewBooking", () => {
metadataLookupKey: "dailyvideo",
});
const calendarMock = mockCalendarToHaveNoBusySlots("googlecalendar", {
const calendarMock = await mockCalendarToHaveNoBusySlots("googlecalendar", {
create: { uid: "NEW_EVENT_ID" },
update: { uid: "UPDATED_EVENT_ID" },
});
@@ -176,7 +176,7 @@ describe("handleNewBooking", () => {
},
});
const organizerCalendarMock = mockCalendarToHaveNoBusySlots("googlecalendar", {
const organizerCalendarMock = await mockCalendarToHaveNoBusySlots("googlecalendar", {
update: {
id: "MOCKED_GOOGLE_CALENDAR_EVENT_ID_2",
iCalUID: "MOCKED_GOOGLE_CALENDAR_ICS_ID_2",
@@ -152,7 +152,7 @@ describe("handleNewBooking", () => {
},
});
const calendarMock = mockCalendarToHaveNoBusySlots("googlecalendar", {
const calendarMock = await mockCalendarToHaveNoBusySlots("googlecalendar", {
create: {
id: "MOCKED_GOOGLE_CALENDAR_EVENT_ID",
iCalUID: "MOCKED_GOOGLE_CALENDAR_ICS_ID",
@@ -201,7 +201,6 @@ describe("handleNewBooking", () => {
uid: "MOCKED_GOOGLE_CALENDAR_EVENT_ID",
meetingId: "MOCKED_GOOGLE_CALENDAR_EVENT_ID",
meetingPassword: "MOCK_PASSWORD",
meetingUrl: "https://UNUSED_URL",
},
],
});
@@ -335,7 +334,7 @@ describe("handleNewBooking", () => {
},
});
const calendarMock = mockCalendarToHaveNoBusySlots("googlecalendar", {
const calendarMock = await mockCalendarToHaveNoBusySlots("googlecalendar", {
create: {
id: "MOCKED_GOOGLE_CALENDAR_EVENT_ID",
iCalUID: "MOCKED_GOOGLE_CALENDAR_ICS_ID",
@@ -457,7 +456,7 @@ describe("handleNewBooking", () => {
url: `http://mock-dailyvideo.example.com/meeting-1`,
},
});
const calendarMock = mockCalendarToHaveNoBusySlots("googlecalendar", {
const calendarMock = await mockCalendarToHaveNoBusySlots("googlecalendar", {
create: {
id: "MOCKED_GOOGLE_CALENDAR_EVENT_ID",
iCalUID: "MOCKED_GOOGLE_CALENDAR_ICS_ID",
@@ -503,7 +502,6 @@ describe("handleNewBooking", () => {
uid: "MOCKED_GOOGLE_CALENDAR_EVENT_ID",
meetingId: "MOCKED_GOOGLE_CALENDAR_EVENT_ID",
meetingPassword: "MOCK_PASSWORD",
meetingUrl: "https://UNUSED_URL",
},
],
});
@@ -682,7 +680,7 @@ describe("handleNewBooking", () => {
},
});
const calendarMock = mockCalendarToHaveNoBusySlots("googlecalendar", {
const calendarMock = await mockCalendarToHaveNoBusySlots("googlecalendar", {
create: {
id: "MOCKED_GOOGLE_CALENDAR_EVENT_ID",
iCalUID: "MOCKED_GOOGLE_CALENDAR_ICS_ID",
@@ -732,7 +730,6 @@ describe("handleNewBooking", () => {
uid: "MOCKED_GOOGLE_CALENDAR_EVENT_ID",
meetingId: "MOCKED_GOOGLE_CALENDAR_EVENT_ID",
meetingPassword: "MOCK_PASSWORD",
meetingUrl: "https://UNUSED_URL",
},
],
});
@@ -917,7 +914,7 @@ describe("handleNewBooking", () => {
},
});
const calendarMock = mockCalendarToHaveNoBusySlots("googlecalendar", {
const calendarMock = await mockCalendarToHaveNoBusySlots("googlecalendar", {
create: {
id: "MOCKED_GOOGLE_CALENDAR_EVENT_ID",
iCalUID: "MOCKED_GOOGLE_CALENDAR_ICS_ID",
@@ -967,7 +964,6 @@ describe("handleNewBooking", () => {
uid: "MOCKED_GOOGLE_CALENDAR_EVENT_ID",
meetingId: "MOCKED_GOOGLE_CALENDAR_EVENT_ID",
meetingPassword: "MOCK_PASSWORD",
meetingUrl: "https://UNUSED_URL",
},
],
});
@@ -1412,7 +1408,7 @@ describe("handleNewBooking", () => {
url: `http://mock-dailyvideo.example.com/meeting-1`,
},
});
const calendarMock = mockCalendarToHaveNoBusySlots("googlecalendar", {
const calendarMock = await mockCalendarToHaveNoBusySlots("googlecalendar", {
create: {
id: "MOCKED_GOOGLE_CALENDAR_EVENT_ID",
iCalUID: "MOCKED_GOOGLE_CALENDAR_ICS_ID",
@@ -1457,7 +1453,6 @@ describe("handleNewBooking", () => {
uid: "MOCKED_GOOGLE_CALENDAR_EVENT_ID",
meetingId: "MOCKED_GOOGLE_CALENDAR_EVENT_ID",
meetingPassword: "MOCK_PASSWORD",
meetingUrl: "https://UNUSED_URL",
},
],
});
@@ -1613,7 +1608,7 @@ describe("handleNewBooking", () => {
url: `http://mock-zoomvideo.example.com/meeting-1`,
},
});
const calendarMock = mockCalendarToHaveNoBusySlots("googlecalendar", {
const calendarMock = await mockCalendarToHaveNoBusySlots("googlecalendar", {
create: {
id: "MOCKED_GOOGLE_CALENDAR_EVENT_ID",
iCalUID: "MOCKED_GOOGLE_CALENDAR_ICS_ID",
@@ -1657,7 +1652,6 @@ describe("handleNewBooking", () => {
uid: "MOCKED_GOOGLE_CALENDAR_EVENT_ID",
meetingId: "MOCKED_GOOGLE_CALENDAR_EVENT_ID",
meetingPassword: "MOCK_PASSWORD",
meetingUrl: "https://UNUSED_URL",
},
],
});
@@ -1819,7 +1813,7 @@ describe("handleNewBooking", () => {
url: `http://mock-dailyvideo.example.com/meeting-1`,
},
});
const calendarMock = mockCalendarToHaveNoBusySlots("googlecalendar", {
const calendarMock = await mockCalendarToHaveNoBusySlots("googlecalendar", {
create: {
id: "MOCKED_GOOGLE_CALENDAR_EVENT_ID",
iCalUID: "MOCKED_GOOGLE_CALENDAR_ICS_ID",
@@ -1864,7 +1858,6 @@ describe("handleNewBooking", () => {
uid: "MOCKED_GOOGLE_CALENDAR_EVENT_ID",
meetingId: "MOCKED_GOOGLE_CALENDAR_EVENT_ID",
meetingPassword: "MOCK_PASSWORD",
meetingUrl: "https://UNUSED_URL",
},
],
});
@@ -2013,7 +2006,7 @@ describe("handleNewBooking", () => {
url: `http://mock-dailyvideo.example.com/meeting-1`,
},
});
const calendarMock = mockCalendarToHaveNoBusySlots("googlecalendar", {
const calendarMock = await mockCalendarToHaveNoBusySlots("googlecalendar", {
create: {
id: "MOCKED_GOOGLE_CALENDAR_EVENT_ID",
iCalUID: "MOCKED_GOOGLE_CALENDAR_ICS_ID",
@@ -2059,7 +2052,6 @@ describe("handleNewBooking", () => {
uid: "MOCKED_GOOGLE_CALENDAR_EVENT_ID",
meetingId: "MOCKED_GOOGLE_CALENDAR_EVENT_ID",
meetingPassword: "MOCK_PASSWORD",
meetingUrl: "https://UNUSED_URL",
},
],
});
@@ -108,7 +108,7 @@ describe("roundRobinReassignment test", () => {
})
);
const calendarMock = mockCalendarToHaveNoBusySlots("googlecalendar", {
const calendarMock = await mockCalendarToHaveNoBusySlots("googlecalendar", {
create: { uid: "NEW_EVENT_ID" },
update: { uid: "UPDATED_EVENT_ID" },
});
+13 -2
View File
@@ -35,7 +35,7 @@ export const getCalendarCredentials = (credentials: Array<CredentialForCalendarS
.filter((app) => app.type.endsWith("_calendar"))
.flatMap((app) => {
const credentials = app.credentials.flatMap((credential) => {
const calendar = getCalendar(credential);
const calendar = () => getCalendar(credential);
return app.variant === "calendar" ? [{ integration: app, credential, calendar }] : [];
});
@@ -71,7 +71,18 @@ export const getConnectedCalendars = async (
delegationCredentialId,
};
}
const cals = await calendar.listCalendars();
const calendarInstance = await calendar();
if (!calendarInstance) {
return {
integration: safeToSendIntegration,
credentialId,
delegationCredentialId,
error: {
message: "Could not get calendar instance",
},
};
}
const cals = await calendarInstance.listCalendars();
const calendars = sortBy(
cals.map((cal: IntegrationCalendar) => {
if (cal.externalId === destinationCalendarExternalId) destinationCalendar = cal;
+83 -2
View File
@@ -19,6 +19,83 @@ vi.mock("./crypto", () => ({
const mockedSymmetricDecrypt = vi.mocked(symmetricDecrypt);
vi.mock("@calcom/app-store/calendar.services.generated", () => {
class MockGoogleCalendarService {
constructor(credential: any) {
this.credential = credential;
}
getCredentialId() {
return this.credential.id;
}
async createEvent() {
return {};
}
async updateEvent() {
return {};
}
async deleteEvent() {
return {};
}
async getAvailability() {
return [];
}
async getAvailabilityWithTimeZones() {
return [];
}
async listCalendars() {
return [];
}
}
class MockOfficeCalendarService {
constructor(credential: any) {
this.credential = credential;
}
getCredentialId() {
return this.credential.id;
}
async createEvent() {
return {};
}
async updateEvent() {
return {};
}
async deleteEvent() {
return {};
}
async getAvailability() {
return [];
}
async getAvailabilityWithTimeZones() {
return [];
}
async listCalendars() {
return [];
}
}
return {
CalendarServiceMap: {
googlecalendar: vi.importActual("@calcom/app-store/googlecalendar/lib/CalendarService"),
office365calendar: vi.importActual("@calcom/app-store/office365calendar/lib/CalendarService"),
},
};
});
function buildDelegationCredential(credential: CredentialPayload): CredentialForCalendarService {
return {
...credential,
@@ -87,10 +164,12 @@ describe("getCalendarsEvents", () => {
},
userId: 808,
teamId: null,
user: {
email: "test@example.com",
},
appId: "exampleApp",
invalid: false,
delegationCredentialId: null,
user: null,
};
});
@@ -339,7 +418,9 @@ describe("getCalendarsEventsWithTimezones", () => {
},
userId: 808,
teamId: null,
user: null,
user: {
email: "test@example.com",
},
appId: "exampleApp",
invalid: false,
delegationCredentialId: null,
+2 -1
View File
@@ -1,6 +1,5 @@
import type { AppCategories, Prisma } from "@prisma/client";
import appStore from "@calcom/app-store";
import type { EventTypeAppsList } from "@calcom/app-store/utils";
import type { CompleteEventType } from "@calcom/prisma/zod";
import { eventTypeAppMetadataOptionalSchema } from "@calcom/prisma/zod-utils";
@@ -52,6 +51,8 @@ const handlePayment = async ({
}) => {
if (isDryRun) return null;
const key = paymentAppCredentials?.app?.dirName;
const appStore = await import("@calcom/app-store").then((m) => m.default);
if (!isKeyOf(appStore, key)) {
console.warn(`key: ${key} is not a valid key in appStore`);
return null;
+2 -1
View File
@@ -1,7 +1,6 @@
import short from "short-uuid";
import { v5 as uuidv5 } from "uuid";
import appStore from "@calcom/app-store";
import { getDailyAppKeys } from "@calcom/app-store/dailyvideo/lib/getDailyAppKeys";
import { DailyLocationType } from "@calcom/app-store/locations";
import { sendBrokenIntegrationEmail } from "@calcom/emails";
@@ -27,6 +26,8 @@ const getVideoAdapters = async (withCredentials: CredentialPayload[]): Promise<V
for (const cred of withCredentials) {
const appName = cred.type.split("_").join(""); // Transform `zoom_video` to `zoomvideo`;
log.silly("Getting video adapter for", safeStringify({ appName, cred: getPiiFreeCredential(cred) }));
const appStore = await import("@calcom/app-store").then((m) => m.default);
const appImportFn = appStore[appName as keyof typeof appStore];
// Static Link Video Apps don't exist in packages/app-store/index.ts(it's manually maintained at the moment) and they aren't needed there anyway.
@@ -1041,7 +1041,6 @@ export class AvailableSlotsService {
queuedFormResponseId,
});
}
const { qualifiedRRHosts, allFallbackRRHosts, fixedHosts } =
await this.dependencies.qualifiedHostsService.findQualifiedHostsWithDelegationCredentials({
eventType,
+43
View File
@@ -10,3 +10,46 @@ const fetchMocker = createFetchMock(vi);
fetchMocker.enableMocks();
expect.extend(matchers);
class MockExchangeCalendarService implements CalendarService {
constructor() {}
async createEvent() {
return {
uid: "mock",
id: "mock",
password: "",
type: "",
url: "",
additionalInfo: {},
};
}
async updateEvent() {
return {
uid: "mock",
id: "mock",
password: "",
type: "",
url: "",
additionalInfo: {},
};
}
async deleteEvent() {}
async getAvailability() {
return [];
}
async listCalendars() {
return [];
}
}
vi.mock("@calcom/exchangecalendar/lib/CalendarService", () => ({
default: MockExchangeCalendarService,
}));
vi.mock("@calcom/exchange2013calendar/lib/CalendarService", () => ({
default: MockExchangeCalendarService,
}));
vi.mock("@calcom/exchange2016calendar/lib/CalendarService", () => ({
default: MockExchangeCalendarService,
}));
-26
View File
@@ -3066,32 +3066,6 @@ __metadata:
languageName: unknown
linkType: soft
"@calcom/exchange2013calendar@workspace:packages/app-store/exchange2013calendar":
version: 0.0.0-use.local
resolution: "@calcom/exchange2013calendar@workspace:packages/app-store/exchange2013calendar"
dependencies:
"@calcom/lib": "*"
"@calcom/prisma": "*"
"@calcom/types": "*"
"@calcom/ui": "*"
ews-javascript-api: ^0.11.0
react-hook-form: ^7.43.3
languageName: unknown
linkType: soft
"@calcom/exchange2016calendar@workspace:packages/app-store/exchange2016calendar":
version: 0.0.0-use.local
resolution: "@calcom/exchange2016calendar@workspace:packages/app-store/exchange2016calendar"
dependencies:
"@calcom/lib": "*"
"@calcom/prisma": "*"
"@calcom/types": "*"
"@calcom/ui": "*"
ews-javascript-api: ^0.11.0
react-hook-form: ^7.43.3
languageName: unknown
linkType: soft
"@calcom/exchangecalendar@workspace:packages/app-store/exchangecalendar":
version: 0.0.0-use.local
resolution: "@calcom/exchangecalendar@workspace:packages/app-store/exchangecalendar"