Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
ef57274
perf: improve `/apps/installed/calendar` page load speed.
zhyd1997 Aug 19, 2025
0ba2d04
Merge branch 'main' into perf/issue-23104-1755628640
zhyd1997 Aug 19, 2025
1c8aec9
feat: add analytics loaders.
zhyd1997 Aug 20, 2025
ba5d8db
feat: add payment loaders.
zhyd1997 Aug 20, 2025
ca94ac0
feat: add video loaders.
zhyd1997 Aug 20, 2025
f546618
chore: remove commented code.
zhyd1997 Aug 20, 2025
f20b1cb
chore: fix payment loader key type.
zhyd1997 Aug 20, 2025
c41afcd
test: revert app store data change for type check
zhyd1997 Aug 20, 2025
310ce65
feat: add `getCalendarApps` util fn.
zhyd1997 Aug 20, 2025
5e7b3f0
Merge branch 'main' into perf/issue-23104-1755628640
zhyd1997 Aug 20, 2025
8f48699
Merge branch 'main' into perf/issue-23104-1755628640
zhyd1997 Aug 21, 2025
24706da
refactor: add calendar apps metadata handling into separate file
zhyd1997 Aug 21, 2025
f04a8fa
test: add calendar and video apps mocks
zhyd1997 Aug 21, 2025
6b7f01c
test: add payment apps mock
zhyd1997 Aug 21, 2025
a13d1e9
Merge branch 'main' into perf/issue-23104-1755628640
zhyd1997 Aug 22, 2025
9445f49
test: correct import path for `calendarLoaders` mock
zhyd1997 Aug 22, 2025
2819c0f
feat: implement video apps handling and metadata generation
zhyd1997 Aug 22, 2025
19f9aa5
test: mock default export instead of named export.
zhyd1997 Aug 22, 2025
76bc398
test: update import to use namespace for `paymentLoaders` mock
zhyd1997 Aug 22, 2025
5c7df2f
chore: update `paymentAppsMock` implementation to use @ts-expect-erro…
zhyd1997 Aug 22, 2025
187ddcf
Merge branch 'main' into perf/issue-23104-1755628640
zhyd1997 Aug 22, 2025
ddb8365
refactor: add new utility functions for credential preparation and me…
zhyd1997 Aug 22, 2025
df97c69
Merge branch 'main' into perf/issue-23104-1755628640
zhyd1997 Aug 22, 2025
d079cb5
refactor: streamline mock implementations by introducing `mockDeepHel…
zhyd1997 Aug 23, 2025
b53f30f
refactor: rename mock implementations for calendar, payment, and vide…
zhyd1997 Aug 23, 2025
0f6ebbc
Merge branch 'main' into perf/issue-23104-1755628640
zhyd1997 Aug 23, 2025
02da408
refactor: replace payment setup logic with dedicated `setupPaymentSer…
zhyd1997 Aug 23, 2025
ffb0fcb
fix: update `setupPaymentService` to accept app and credential parame…
zhyd1997 Aug 23, 2025
fb71c07
refactor: update `setupPaymentService` to use `PreparedApp` type for …
zhyd1997 Aug 23, 2025
044634b
Merge branch 'main' into perf/issue-23104-1755628640
zhyd1997 Aug 25, 2025
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
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
} from "@nestjs/common";
import { Injectable } from "@nestjs/common";

import { getVideoApps } from "@calcom/app-store/_utils/videos/getVideoApps";
import {
CONFERENCING_APPS,
CAL_VIDEO,
Expand All @@ -23,7 +24,7 @@ import {
} from "@calcom/platform-constants";
import { userMetadata } from "@calcom/platform-libraries";
import { getUsersCredentialsIncludeServiceAccountKey } from "@calcom/platform-libraries/app-store";
import { getApps, handleDeleteCredential } from "@calcom/platform-libraries/app-store";
import { handleDeleteCredential } from "@calcom/platform-libraries/app-store";

@Injectable()
export class ConferencingService {
Expand Down Expand Up @@ -93,7 +94,7 @@ export class ConferencingService {
}
const credentials = await getUsersCredentialsIncludeServiceAccountKey(user);

const foundApp = getApps(credentials, true).filter((app) => app.slug === appSlug)[0];
const foundApp = getVideoApps(credentials, true).filter((app) => app.slug === appSlug)[0];

const appLocation = foundApp?.appData?.location;

Expand Down
166 changes: 87 additions & 79 deletions apps/web/test/utils/bookingScenario/bookingScenario.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import appStoreMock from "../../../../../tests/libs/__mocks__/app-store";
import calendarLoadersMock from "../../../../../tests/libs/__mocks__/calendarLoadersMock";
import i18nMock from "../../../../../tests/libs/__mocks__/libServerI18n";
import paymentLoadersMock from "../../../../../tests/libs/__mocks__/paymentLoadersMock";
import prismock from "../../../../../tests/libs/__mocks__/prisma";
import videoLoadersMock from "../../../../../tests/libs/__mocks__/videoLoadersMock";

import type { BookingReference, Attendee, Booking, Membership } from "@prisma/client";
import type { Prisma } from "@prisma/client";
Expand All @@ -11,6 +14,7 @@ import { vi } from "vitest";
import "vitest-fetch-mock";
import type { z } from "zod";

import { calendarAppsMetaData } from "@calcom/app-store/_utils/calendars/calendarAppsMetaData";
import { appStoreMetadata } from "@calcom/app-store/appStoreMetaData";
import { handleStripePaymentSuccess } from "@calcom/features/ee/payments/api/webhook";
import { weekdayToWeekIndex, type WeekDays } from "@calcom/lib/dayjs";
Expand Down Expand Up @@ -1716,7 +1720,7 @@ export type CalendarServiceMethodMock = {
* @param calendarData Specify uids and other data to be faked to be returned by createEvent and updateEvent
*/
export function mockCalendar(
metadataLookupKey: keyof typeof appStoreMetadata,
metadataLookupKey: keyof typeof calendarAppsMetaData,
calendarData?: {
create?: {
id?: string;
Expand Down Expand Up @@ -1759,15 +1763,15 @@ export function mockCalendar(
uid: "UPDATED_MOCK_ID",
},
};
log.silly(`Mocking ${appStoreLookupKey} on appStoreMock`);
log.silly(`Mocking ${appStoreLookupKey} on calendarAppsMock`);

const createEventCalls: CreateEventMethodMockCall[] = [];
const updateEventCalls: UpdateEventMethodMockCall[] = [];
const deleteEventCalls: DeleteEventMethodMockCall[] = [];
const getAvailabilityCalls: GetAvailabilityMethodMockCall[] = [];
const app = appStoreMetadata[metadataLookupKey as keyof typeof appStoreMetadata];
const app = calendarAppsMetaData[metadataLookupKey as keyof typeof calendarAppsMetaData];

const appMock = appStoreMock.default[appStoreLookupKey as keyof typeof appStoreMock.default];
const appMock = calendarLoadersMock.default[appStoreLookupKey as keyof typeof calendarLoadersMock.default];

appMock &&
`mockResolvedValue` in appMock &&
Expand Down Expand Up @@ -1899,7 +1903,7 @@ export function mockCalendar(
}

export function mockCalendarToHaveNoBusySlots(
metadataLookupKey: keyof typeof appStoreMetadata,
metadataLookupKey: keyof typeof calendarAppsMetaData,
calendarData?: Parameters<typeof mockCalendar>[1]
) {
calendarData = calendarData || {
Expand All @@ -1913,11 +1917,11 @@ export function mockCalendarToHaveNoBusySlots(
return mockCalendar(metadataLookupKey, { ...calendarData, busySlots: [] });
}

export function mockCalendarToCrashOnCreateEvent(metadataLookupKey: keyof typeof appStoreMetadata) {
export function mockCalendarToCrashOnCreateEvent(metadataLookupKey: keyof typeof calendarAppsMetaData) {
return mockCalendar(metadataLookupKey, { creationCrash: true });
}

export function mockCalendarToCrashOnUpdateEvent(metadataLookupKey: keyof typeof appStoreMetadata) {
export function mockCalendarToCrashOnUpdateEvent(metadataLookupKey: keyof typeof calendarAppsMetaData) {
return mockCalendar(metadataLookupKey, { updationCrash: true });
}

Expand Down Expand Up @@ -1951,67 +1955,69 @@ export function mockVideoApp({
const updateMeetingCalls: any[] = [];
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const deleteMeetingCalls: any[] = [];
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
appStoreMock.default[appStoreLookupKey as keyof typeof appStoreMock.default].mockImplementation(() => {
return new Promise((resolve) => {
resolve({
lib: {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
VideoApiAdapter: (credential) => {
return {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
createMeeting: (...rest: any[]) => {
if (creationCrash) {
throw new Error("MockVideoApiAdapter.createMeeting fake error");
}
createMeetingCalls.push({
credential,
args: rest,
});

return Promise.resolve({
type: appStoreMetadata[metadataLookupKey as keyof typeof appStoreMetadata].type,
...videoMeetingData,
});
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
updateMeeting: async (...rest: any[]) => {
if (updationCrash) {
throw new Error("MockVideoApiAdapter.updateMeeting fake error");
}
const [bookingRef, calEvent] = rest;
updateMeetingCalls.push({
credential,
args: rest,
});
if (!bookingRef.type) {
throw new Error("bookingRef.type is not defined");
}
if (!calEvent.organizer) {
throw new Error("calEvent.organizer is not defined");
}
log.silly("MockVideoApiAdapter.updateMeeting", JSON.stringify({ bookingRef, calEvent }));
return Promise.resolve({
type: appStoreMetadata[metadataLookupKey as keyof typeof appStoreMetadata].type,
...videoMeetingData,
});
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
deleteMeeting: async (...rest: any[]) => {
log.silly("MockVideoApiAdapter.deleteMeeting", JSON.stringify(rest));
deleteMeetingCalls.push({
credential,
args: rest,
});
},
};
videoLoadersMock.default[appStoreLookupKey as keyof typeof videoLoadersMock.default].mockImplementation(
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error FIXME
() => {
return new Promise((resolve) => {
resolve({
lib: {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error FIXME
VideoApiAdapter: (credential) => {
return {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
createMeeting: (...rest: any[]) => {
if (creationCrash) {
throw new Error("MockVideoApiAdapter.createMeeting fake error");
}
createMeetingCalls.push({
credential,
args: rest,
});

return Promise.resolve({
type: appStoreMetadata[metadataLookupKey as keyof typeof appStoreMetadata].type,
...videoMeetingData,
});
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
updateMeeting: async (...rest: any[]) => {
if (updationCrash) {
throw new Error("MockVideoApiAdapter.updateMeeting fake error");
}
const [bookingRef, calEvent] = rest;
updateMeetingCalls.push({
credential,
args: rest,
});
if (!bookingRef.type) {
throw new Error("bookingRef.type is not defined");
}
if (!calEvent.organizer) {
throw new Error("calEvent.organizer is not defined");
}
log.silly("MockVideoApiAdapter.updateMeeting", JSON.stringify({ bookingRef, calEvent }));
return Promise.resolve({
type: appStoreMetadata[metadataLookupKey as keyof typeof appStoreMetadata].type,
...videoMeetingData,
});
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
deleteMeeting: async (...rest: any[]) => {
log.silly("MockVideoApiAdapter.deleteMeeting", JSON.stringify(rest));
deleteMeetingCalls.push({
credential,
args: rest,
});
},
};
},
},
},
});
});
});
});
}
);
return {
createMeetingCalls,
updateMeetingCalls,
Expand Down Expand Up @@ -2062,17 +2068,21 @@ export function mockPaymentApp({
}) {
appStoreLookupKey = appStoreLookupKey || metadataLookupKey;
const { paymentUid, externalId, MockPaymentService } = getMockPaymentService();
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
appStoreMock.default[appStoreLookupKey as keyof typeof appStoreMock.default].mockImplementation(() => {
return new Promise((resolve) => {
resolve({
lib: {
PaymentService: MockPaymentService,
},
paymentLoadersMock.default[appStoreLookupKey as keyof typeof paymentLoadersMock.default].mockImplementation(
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error FIXME
() => {
return new Promise((resolve) => {
resolve({
lib: {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error FIXME
PaymentService: MockPaymentService,
},
});
});
});
});
}
);

return {
paymentUid,
Expand All @@ -2090,12 +2100,10 @@ export function mockErrorOnVideoMeetingCreation({
appStoreLookupKey = appStoreLookupKey || metadataLookupKey;
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
appStoreMock.default[appStoreLookupKey].mockImplementation(() => {
videoLoadersMock.default[appStoreLookupKey].mockImplementation(() => {
return new Promise((resolve) => {
resolve({
lib: {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
VideoApiAdapter: () => ({
createMeeting: () => {
throw new MockError("Error creating Video meeting");
Expand Down
52 changes: 52 additions & 0 deletions packages/app-store-cli/src/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ function generateFiles() {
const appKeysSchemasOutput = [];
const serverOutput = [];
const crmOutput = [];
const calendarMetadataOutput = [];
const videoAppsMetadataOutput = [];
const appDirs: { name: string; path: string }[] = [];

fs.readdirSync(`${APP_STORE_PATH}`).forEach(function (dir) {
Expand Down Expand Up @@ -345,6 +347,46 @@ function generateFiles() {
)
);

calendarMetadataOutput.push(
...getExportedObject(
"calendarAppsMetadata",
{
// Try looking for config.json and if it's not found use _metadata.ts to generate appStoreMetadata
importConfig: [
{
fileToBeImported: "config.json",
importName: "default",
},
{
fileToBeImported: "_metadata.ts",
importName: "metadata",
},
],
},
isCalendarApp
)
);

videoAppsMetadataOutput.push(
...getExportedObject(
"videoAppsMetadata",
{
// Try looking for config.json and if it's not found use _metadata.ts to generate appStoreMetadata
importConfig: [
{
fileToBeImported: "config.json",
importName: "default",
},
{
fileToBeImported: "_metadata.ts",
importName: "metadata",
},
],
},
isVideoApp
)
);

const banner = `/**
This file is autogenerated using the command \`yarn app-store:build --watch\`.
Don't modify this file manually.
Expand All @@ -358,6 +400,8 @@ function generateFiles() {
["apps.keys-schemas.generated.ts", appKeysSchemasOutput],
["bookerApps.metadata.generated.ts", bookerMetadataOutput],
["crm.apps.generated.ts", crmOutput],
["calendarApps.metadata.generated.ts", calendarMetadataOutput],
["videoApps.metadata.generated.ts", videoAppsMetadataOutput],
];
filesToGenerate.forEach(([fileName, output]) => {
fs.writeFileSync(`${APP_STORE_PATH}/${fileName}`, formatOutput(`${banner}${output.join("\n")}`));
Expand Down Expand Up @@ -405,3 +449,11 @@ function isBookerApp(app: App) {
function isCrmApp(app: App) {
return !!app.categories?.includes("crm");
}

function isCalendarApp(app: App) {
return !!app.categories?.includes("calendar");
}

function isVideoApp(app: App) {
return !!app.categories?.includes("video") || !!app.categories?.includes("conferencing");
}
8 changes: 8 additions & 0 deletions packages/app-store/_utils/analytics/analyticsLoaders.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { createCachedImport } from "../createCachedImport";

export const analyticsLoaders = {
dub: createCachedImport(() => import("../../dub")),
plausible: createCachedImport(() => import("../../plausible")),
};

export type AnalyticsLoadersKey = keyof typeof analyticsLoaders;
14 changes: 14 additions & 0 deletions packages/app-store/_utils/calendars/calendarAppsMetaData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type { AppMeta } from "@calcom/types/App";

import { calendarAppsMetadata as rawCalendarAppsMetadata } from "../../calendarApps.metadata.generated";
import { getNormalizedAppMetadata } from "../../getNormalizedAppMetadata";

type RawCalendarAppsMetaData = typeof rawCalendarAppsMetadata;
type CalendarAppsMetaData = {
[key in keyof RawCalendarAppsMetaData]: Omit<AppMeta, "dirName"> & { dirName: string };
};

export const calendarAppsMetaData = {} as CalendarAppsMetaData;
for (const [key, value] of Object.entries(rawCalendarAppsMetadata)) {
calendarAppsMetaData[key as keyof typeof calendarAppsMetaData] = getNormalizedAppMetadata(value);
}
18 changes: 18 additions & 0 deletions packages/app-store/_utils/calendars/calendarLoaders.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { createCachedImport } from "../createCachedImport";

const calendarLoaders = {
applecalendar: createCachedImport(() => import("../../applecalendar")),
caldavcalendar: createCachedImport(() => import("../../caldavcalendar")),
googlecalendar: createCachedImport(() => import("../../googlecalendar")),
"ics-feedcalendar": createCachedImport(() => import("../../ics-feedcalendar")),
larkcalendar: createCachedImport(() => import("../../larkcalendar")),
office365calendar: createCachedImport(() => import("../../office365calendar")),
exchange2013calendar: createCachedImport(() => import("../../exchange2013calendar")),
exchange2016calendar: createCachedImport(() => import("../../exchange2016calendar")),
exchangecalendar: createCachedImport(() => import("../../exchangecalendar")),
zohocalendar: createCachedImport(() => import("../../zohocalendar")),
};

export type CalendarLoaderKey = keyof typeof calendarLoaders;

export default calendarLoaders;
Loading
Loading