Skip to content

Commit 9909ac5

Browse files
authored
fix: use app name instead of app slug in email (calcom#25285)
* fix: google calender name in email * fix: google calender name in email * cleaner code * test: add test for human-readable app name in appsStatus
1 parent f598a8e commit 9909ac5

7 files changed

Lines changed: 132 additions & 17 deletions

File tree

apps/web/test/utils/bookingScenario/bookingScenario.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2368,7 +2368,7 @@ const getMockAppStatus = ({
23682368
}
23692369
const foundApp = foundEntry[1];
23702370
return {
2371-
appName: overrideName ?? foundApp.slug,
2371+
appName: overrideName ?? foundApp.name,
23722372
type: foundApp.type,
23732373
failures,
23742374
success,

packages/features/CalendarEventBuilder.test.ts

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1906,5 +1906,111 @@ describe("CalendarEventBuilder", () => {
19061906

19071907
expect(builtFromBooking.bookerUrl).toBe("https://cal.com");
19081908
});
1909+
1910+
it("should resolve app type to human-readable app name in appsStatus", async () => {
1911+
const mockBooking = {
1912+
uid: "booking-app-name-test",
1913+
metadata: null,
1914+
title: "App Name Test Event",
1915+
startTime: new Date(mockStartTime),
1916+
endTime: new Date(mockEndTime),
1917+
description: null,
1918+
location: "integrations:google:meet",
1919+
responses: null,
1920+
customInputs: null,
1921+
iCalUID: null,
1922+
iCalSequence: 0,
1923+
oneTimePassword: null,
1924+
attendees: [
1925+
{
1926+
name: "Test Attendee",
1927+
email: "attendee@example.com",
1928+
timeZone: "UTC",
1929+
locale: "en",
1930+
phoneNumber: null,
1931+
bookingSeat: null,
1932+
},
1933+
],
1934+
user: {
1935+
id: 1,
1936+
name: "Test Host",
1937+
email: "host@example.com",
1938+
username: "testhost",
1939+
timeZone: "UTC",
1940+
locale: "en",
1941+
timeFormat: 24,
1942+
destinationCalendar: null,
1943+
profiles: [],
1944+
},
1945+
destinationCalendar: null,
1946+
eventType: {
1947+
id: 1,
1948+
title: "Test Event Type",
1949+
slug: "test-event",
1950+
description: null,
1951+
hideCalendarNotes: false,
1952+
hideCalendarEventDetails: false,
1953+
hideOrganizerEmail: false,
1954+
schedulingType: null,
1955+
seatsPerTimeSlot: null,
1956+
seatsShowAttendees: false,
1957+
seatsShowAvailabilityCount: false,
1958+
customReplyToEmail: null,
1959+
disableRescheduling: false,
1960+
disableCancelling: false,
1961+
requiresConfirmation: false,
1962+
recurringEvent: null,
1963+
bookingFields: [],
1964+
metadata: null,
1965+
eventName: null,
1966+
team: null,
1967+
users: [],
1968+
hosts: [],
1969+
workflows: [],
1970+
},
1971+
references: [
1972+
{
1973+
type: "google_calendar",
1974+
uid: "google-cal-uid-123",
1975+
meetingId: null,
1976+
meetingPassword: null,
1977+
meetingUrl: null,
1978+
},
1979+
{
1980+
type: "google_video",
1981+
uid: "google-meet-uid-456",
1982+
meetingId: "meet-123",
1983+
meetingPassword: null,
1984+
meetingUrl: "https://meet.google.com/abc-defg-hij",
1985+
},
1986+
],
1987+
seatsReferences: [],
1988+
} satisfies BookingForCalEventBuilder;
1989+
1990+
const eventFromBooking = await CalendarEventBuilder.fromBooking(mockBooking);
1991+
const builtEvent = eventFromBooking.build();
1992+
1993+
expect(builtEvent).not.toBeNull();
1994+
if (builtEvent) {
1995+
expect(builtEvent.appsStatus).toBeDefined();
1996+
expect(builtEvent.appsStatus).toHaveLength(2);
1997+
1998+
// Verify Google Calendar uses human-readable name, not the type slug
1999+
const googleCalendarStatus = builtEvent.appsStatus?.find((app) => app.type === "google_calendar");
2000+
expect(googleCalendarStatus).toBeDefined();
2001+
expect(googleCalendarStatus?.appName).toBe("Google Calendar");
2002+
// Should NOT be the raw type like "google_calendar" or "google-calendar"
2003+
expect(googleCalendarStatus?.appName).not.toBe("google_calendar");
2004+
expect(googleCalendarStatus?.appName).not.toBe("google-calendar");
2005+
2006+
// Verify Google Meet uses human-readable name
2007+
const googleMeetStatus = builtEvent.appsStatus?.find((app) => app.type === "google_video");
2008+
expect(googleMeetStatus).toBeDefined();
2009+
expect(googleMeetStatus?.appName).toBe("Google Meet");
2010+
// Should NOT be the raw type
2011+
expect(googleMeetStatus?.appName).not.toBe("google_video");
2012+
expect(googleMeetStatus?.appName).not.toBe("google-video");
2013+
}
2014+
});
19092015
});
19102016
});

packages/features/CalendarEventBuilder.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { TFunction } from "i18next";
22

3+
import { ALL_APPS } from "@calcom/app-store/utils";
34
import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventResponses";
45
import type { BookingRepository } from "@calcom/features/bookings/repositories/BookingRepository";
56
import { getBookerBaseUrl } from "@calcom/features/ee/organizations/lib/getBookerUrlServer";
@@ -12,6 +13,8 @@ import { bookingResponses as bookingResponsesSchema } from "@calcom/prisma/zod-u
1213
import type { CalendarEvent, Person, CalEventResponses, AppsStatus } from "@calcom/types/Calendar";
1314
import type { VideoCallData } from "@calcom/types/VideoApiAdapter";
1415

16+
const APP_TYPE_TO_NAME_MAP = new Map<string, string>(ALL_APPS.map((app) => [app.type, app.name]));
17+
1518
export type BookingForCalEventBuilder = NonNullable<
1619
Awaited<ReturnType<BookingRepository["getBookingForCalEventBuilder"]>>
1720
>;
@@ -211,8 +214,9 @@ export class CalendarEventBuilder {
211214
references
212215
.filter((r) => r && r.type)
213216
.forEach((ref) => {
217+
const appName = APP_TYPE_TO_NAME_MAP.get(ref.type) || ref.type.replace("_", "-");
214218
appsStatus.push({
215-
appName: ref.type.replace("_", "-"),
219+
appName,
216220
type: ref.type,
217221
success: ref.uid ? 1 : 0,
218222
failures: ref.uid ? 0 : 1,
@@ -234,9 +238,7 @@ export class CalendarEventBuilder {
234238
bookingAttendees.some((attendee) => attendee.email === host.user.email)
235239
);
236240

237-
const hostsWithoutOrganizerData = hostsToInclude.filter(
238-
(host) => host.user.email !== user.email
239-
);
241+
const hostsWithoutOrganizerData = hostsToInclude.filter((host) => host.user.email !== user.email);
240242

241243
const hostsWithoutOrganizer = await Promise.all(
242244
hostsWithoutOrganizerData.map((host) => _buildPersonFromUser(host.user))

packages/features/bookings/lib/EventManager.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1174,7 +1174,7 @@ export default class EventManager {
11741174

11751175
if (!calendarReference) {
11761176
return {
1177-
appName: cred.appId || "",
1177+
appName: cred.appName || "",
11781178
type: cred.type,
11791179
success: false,
11801180
uid: "",
@@ -1267,7 +1267,7 @@ export default class EventManager {
12671267
if (createdEvent) {
12681268
createdEvents.push({
12691269
type: credential.type,
1270-
appName: credential.appId || "",
1270+
appName: credential.appName || "",
12711271
uid,
12721272
success,
12731273
createdEvent: {
@@ -1300,7 +1300,7 @@ export default class EventManager {
13001300

13011301
updatedEvents.push({
13021302
type: credential.type,
1303-
appName: credential.appId || "",
1303+
appName: credential.appName || "",
13041304
success,
13051305
uid: updatedEvent?.id || "",
13061306
originalEvent: event,

packages/features/calendars/lib/CalendarManager.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -365,7 +365,7 @@ export const createEvent = async (
365365
})
366366
);
367367
return {
368-
appName: credential.appId || "",
368+
appName: credential.appName || "",
369369
type: credential.type,
370370
success,
371371
uid,
@@ -450,7 +450,7 @@ export const updateEvent = async (
450450
}
451451

452452
return {
453-
appName: credential.appId || "",
453+
appName: credential.appName || "",
454454
type: credential.type,
455455
success,
456456
uid,

packages/features/conferencing/lib/videoClient.ts

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { safeStringify } from "@calcom/lib/safeStringify";
1313
import { prisma } from "@calcom/prisma";
1414
import type { GetRecordingsResponseSchema, GetAccessLinkResponseSchema } from "@calcom/prisma/zod-utils";
1515
import type { CalendarEvent, EventBusyDate } from "@calcom/types/Calendar";
16-
import type { CredentialPayload } from "@calcom/types/Credential";
16+
import type { CredentialPayload, CredentialForCalendarService } from "@calcom/types/Credential";
1717
import type { EventResult, PartialReference } from "@calcom/types/EventManager";
1818
import type { VideoCallData } from "@calcom/types/VideoApiAdapter";
1919

@@ -26,7 +26,10 @@ const getBusyVideoTimes = async (withCredentials: CredentialPayload[]) =>
2626
results.reduce((acc, availability) => acc.concat(availability), [] as (EventBusyDate | undefined)[])
2727
);
2828

29-
const createMeeting = async (credential: CredentialPayload, calEvent: CalendarEvent) => {
29+
const createMeeting = async (
30+
credential: CredentialPayload | CredentialForCalendarService,
31+
calEvent: CalendarEvent
32+
) => {
3033
const uid: string = getUid(calEvent);
3134
log.debug(
3235
"createMeeting",
@@ -54,7 +57,7 @@ const createMeeting = async (credential: CredentialPayload, calEvent: CalendarEv
5457
createdEvent: VideoCallData | undefined;
5558
credentialId: number;
5659
} = {
57-
appName: credential.appId || "",
60+
appName: credential.appName || credential.appId || "",
5861
type: credential.type,
5962
uid,
6063
originalEvent: calEvent,
@@ -100,7 +103,7 @@ const createMeeting = async (credential: CredentialPayload, calEvent: CalendarEv
100103
};
101104

102105
const updateMeeting = async (
103-
credential: CredentialPayload,
106+
credential: CredentialPayload | CredentialForCalendarService,
104107
calEvent: CalendarEvent,
105108
bookingRef: PartialReference | null
106109
): Promise<EventResult<VideoCallData>> => {
@@ -123,7 +126,7 @@ const updateMeeting = async (
123126
safeStringify({ bookingRef, canCallUpdateMeeting, calEvent, credential })
124127
);
125128
return {
126-
appName: credential.appId || "",
129+
appName: credential.appName || credential.appId || "",
127130
type: credential.type,
128131
success,
129132
uid,
@@ -132,7 +135,7 @@ const updateMeeting = async (
132135
}
133136

134137
return {
135-
appName: credential.appId || "",
138+
appName: credential.appName || credential.appId || "",
136139
type: credential.type,
137140
success,
138141
uid,
@@ -141,7 +144,10 @@ const updateMeeting = async (
141144
};
142145
};
143146

144-
const deleteMeeting = async (credential: CredentialPayload | null, uid: string): Promise<unknown> => {
147+
const deleteMeeting = async (
148+
credential: CredentialPayload | CredentialForCalendarService | null,
149+
uid: string
150+
): Promise<unknown> => {
145151
if (credential) {
146152
const videoAdapter = (await getVideoAdapters([credential]))[0];
147153
log.debug(

packages/types/Credential.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export type CredentialPayload = Prisma.CredentialGetPayload<{
99
select: typeof import("@calcom/prisma/selects/credential").credentialForCalendarServiceSelect;
1010
}> & {
1111
delegatedToId?: string | null;
12+
appName?: string;
1213
};
1314

1415
export type CredentialForCalendarService = CredentialPayload & {

0 commit comments

Comments
 (0)