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:
@@ -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");
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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(
|
||||
|
||||
Vendored
+1
@@ -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 & {
|
||||
|
||||
Reference in New Issue
Block a user