fix: use app name instead of app slug in email (#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
This commit is contained in:
Dhairyashil Shinde
2025-12-23 03:41:08 +05:30
committed by GitHub
parent f598a8ebdd
commit 9909ac53fc
7 changed files with 132 additions and 17 deletions
@@ -2368,7 +2368,7 @@ const getMockAppStatus = ({
}
const foundApp = foundEntry[1];
return {
appName: overrideName ?? foundApp.slug,
appName: overrideName ?? foundApp.name,
type: foundApp.type,
failures,
success,
@@ -1906,5 +1906,111 @@ describe("CalendarEventBuilder", () => {
expect(builtFromBooking.bookerUrl).toBe("https://cal.com");
});
it("should resolve app type to human-readable app name in appsStatus", async () => {
const mockBooking = {
uid: "booking-app-name-test",
metadata: null,
title: "App Name Test Event",
startTime: new Date(mockStartTime),
endTime: new Date(mockEndTime),
description: null,
location: "integrations:google:meet",
responses: null,
customInputs: null,
iCalUID: null,
iCalSequence: 0,
oneTimePassword: null,
attendees: [
{
name: "Test Attendee",
email: "attendee@example.com",
timeZone: "UTC",
locale: "en",
phoneNumber: null,
bookingSeat: null,
},
],
user: {
id: 1,
name: "Test Host",
email: "host@example.com",
username: "testhost",
timeZone: "UTC",
locale: "en",
timeFormat: 24,
destinationCalendar: null,
profiles: [],
},
destinationCalendar: null,
eventType: {
id: 1,
title: "Test Event Type",
slug: "test-event",
description: null,
hideCalendarNotes: false,
hideCalendarEventDetails: false,
hideOrganizerEmail: false,
schedulingType: null,
seatsPerTimeSlot: null,
seatsShowAttendees: false,
seatsShowAvailabilityCount: false,
customReplyToEmail: null,
disableRescheduling: false,
disableCancelling: false,
requiresConfirmation: false,
recurringEvent: null,
bookingFields: [],
metadata: null,
eventName: null,
team: null,
users: [],
hosts: [],
workflows: [],
},
references: [
{
type: "google_calendar",
uid: "google-cal-uid-123",
meetingId: null,
meetingPassword: null,
meetingUrl: null,
},
{
type: "google_video",
uid: "google-meet-uid-456",
meetingId: "meet-123",
meetingPassword: null,
meetingUrl: "https://meet.google.com/abc-defg-hij",
},
],
seatsReferences: [],
} satisfies BookingForCalEventBuilder;
const eventFromBooking = await CalendarEventBuilder.fromBooking(mockBooking);
const builtEvent = eventFromBooking.build();
expect(builtEvent).not.toBeNull();
if (builtEvent) {
expect(builtEvent.appsStatus).toBeDefined();
expect(builtEvent.appsStatus).toHaveLength(2);
// Verify Google Calendar uses human-readable name, not the type slug
const googleCalendarStatus = builtEvent.appsStatus?.find((app) => app.type === "google_calendar");
expect(googleCalendarStatus).toBeDefined();
expect(googleCalendarStatus?.appName).toBe("Google Calendar");
// Should NOT be the raw type like "google_calendar" or "google-calendar"
expect(googleCalendarStatus?.appName).not.toBe("google_calendar");
expect(googleCalendarStatus?.appName).not.toBe("google-calendar");
// Verify Google Meet uses human-readable name
const googleMeetStatus = builtEvent.appsStatus?.find((app) => app.type === "google_video");
expect(googleMeetStatus).toBeDefined();
expect(googleMeetStatus?.appName).toBe("Google Meet");
// Should NOT be the raw type
expect(googleMeetStatus?.appName).not.toBe("google_video");
expect(googleMeetStatus?.appName).not.toBe("google-video");
}
});
});
});
+6 -4
View File
@@ -1,5 +1,6 @@
import type { TFunction } from "i18next";
import { ALL_APPS } from "@calcom/app-store/utils";
import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventResponses";
import type { BookingRepository } from "@calcom/features/bookings/repositories/BookingRepository";
import { getBookerBaseUrl } from "@calcom/features/ee/organizations/lib/getBookerUrlServer";
@@ -12,6 +13,8 @@ import { bookingResponses as bookingResponsesSchema } from "@calcom/prisma/zod-u
import type { CalendarEvent, Person, CalEventResponses, AppsStatus } from "@calcom/types/Calendar";
import type { VideoCallData } from "@calcom/types/VideoApiAdapter";
const APP_TYPE_TO_NAME_MAP = new Map<string, string>(ALL_APPS.map((app) => [app.type, app.name]));
export type BookingForCalEventBuilder = NonNullable<
Awaited<ReturnType<BookingRepository["getBookingForCalEventBuilder"]>>
>;
@@ -211,8 +214,9 @@ export class CalendarEventBuilder {
references
.filter((r) => r && r.type)
.forEach((ref) => {
const appName = APP_TYPE_TO_NAME_MAP.get(ref.type) || ref.type.replace("_", "-");
appsStatus.push({
appName: ref.type.replace("_", "-"),
appName,
type: ref.type,
success: ref.uid ? 1 : 0,
failures: ref.uid ? 0 : 1,
@@ -234,9 +238,7 @@ export class CalendarEventBuilder {
bookingAttendees.some((attendee) => attendee.email === host.user.email)
);
const hostsWithoutOrganizerData = hostsToInclude.filter(
(host) => host.user.email !== user.email
);
const hostsWithoutOrganizerData = hostsToInclude.filter((host) => host.user.email !== user.email);
const hostsWithoutOrganizer = await Promise.all(
hostsWithoutOrganizerData.map((host) => _buildPersonFromUser(host.user))
@@ -1174,7 +1174,7 @@ export default class EventManager {
if (!calendarReference) {
return {
appName: cred.appId || "",
appName: cred.appName || "",
type: cred.type,
success: false,
uid: "",
@@ -1267,7 +1267,7 @@ export default class EventManager {
if (createdEvent) {
createdEvents.push({
type: credential.type,
appName: credential.appId || "",
appName: credential.appName || "",
uid,
success,
createdEvent: {
@@ -1300,7 +1300,7 @@ export default class EventManager {
updatedEvents.push({
type: credential.type,
appName: credential.appId || "",
appName: credential.appName || "",
success,
uid: updatedEvent?.id || "",
originalEvent: event,
@@ -365,7 +365,7 @@ export const createEvent = async (
})
);
return {
appName: credential.appId || "",
appName: credential.appName || "",
type: credential.type,
success,
uid,
@@ -450,7 +450,7 @@ export const updateEvent = async (
}
return {
appName: credential.appId || "",
appName: credential.appName || "",
type: credential.type,
success,
uid,
@@ -13,7 +13,7 @@ import { safeStringify } from "@calcom/lib/safeStringify";
import { prisma } from "@calcom/prisma";
import type { GetRecordingsResponseSchema, GetAccessLinkResponseSchema } from "@calcom/prisma/zod-utils";
import type { CalendarEvent, EventBusyDate } from "@calcom/types/Calendar";
import type { CredentialPayload } from "@calcom/types/Credential";
import type { CredentialPayload, CredentialForCalendarService } from "@calcom/types/Credential";
import type { EventResult, PartialReference } from "@calcom/types/EventManager";
import type { VideoCallData } from "@calcom/types/VideoApiAdapter";
@@ -26,7 +26,10 @@ const getBusyVideoTimes = async (withCredentials: CredentialPayload[]) =>
results.reduce((acc, availability) => acc.concat(availability), [] as (EventBusyDate | undefined)[])
);
const createMeeting = async (credential: CredentialPayload, calEvent: CalendarEvent) => {
const createMeeting = async (
credential: CredentialPayload | CredentialForCalendarService,
calEvent: CalendarEvent
) => {
const uid: string = getUid(calEvent);
log.debug(
"createMeeting",
@@ -54,7 +57,7 @@ const createMeeting = async (credential: CredentialPayload, calEvent: CalendarEv
createdEvent: VideoCallData | undefined;
credentialId: number;
} = {
appName: credential.appId || "",
appName: credential.appName || credential.appId || "",
type: credential.type,
uid,
originalEvent: calEvent,
@@ -100,7 +103,7 @@ const createMeeting = async (credential: CredentialPayload, calEvent: CalendarEv
};
const updateMeeting = async (
credential: CredentialPayload,
credential: CredentialPayload | CredentialForCalendarService,
calEvent: CalendarEvent,
bookingRef: PartialReference | null
): Promise<EventResult<VideoCallData>> => {
@@ -123,7 +126,7 @@ const updateMeeting = async (
safeStringify({ bookingRef, canCallUpdateMeeting, calEvent, credential })
);
return {
appName: credential.appId || "",
appName: credential.appName || credential.appId || "",
type: credential.type,
success,
uid,
@@ -132,7 +135,7 @@ const updateMeeting = async (
}
return {
appName: credential.appId || "",
appName: credential.appName || credential.appId || "",
type: credential.type,
success,
uid,
@@ -141,7 +144,10 @@ const updateMeeting = async (
};
};
const deleteMeeting = async (credential: CredentialPayload | null, uid: string): Promise<unknown> => {
const deleteMeeting = async (
credential: CredentialPayload | CredentialForCalendarService | null,
uid: string
): Promise<unknown> => {
if (credential) {
const videoAdapter = (await getVideoAdapters([credential]))[0];
log.debug(
+1
View File
@@ -9,6 +9,7 @@ export type CredentialPayload = Prisma.CredentialGetPayload<{
select: typeof import("@calcom/prisma/selects/credential").credentialForCalendarServiceSelect;
}> & {
delegatedToId?: string | null;
appName?: string;
};
export type CredentialForCalendarService = CredentialPayload & {