fix: hide cal branding on platform workflows (#27385)
* fix: hide cal branding on platform workflows * refactor: rely on existing with platform variables code * revert: comment * fix e2e * chore: remove unit results
This commit is contained in:
@@ -611,6 +611,10 @@ export class BookingsController_2024_04_15 {
|
||||
...clone.body,
|
||||
noEmail: oAuthParams === undefined ? false : !oAuthParams.arePlatformEmailsEnabled,
|
||||
creationSource: CreationSource.API_V2,
|
||||
metadata: {
|
||||
...(clone.body.metadata || {}),
|
||||
...(oAuthClientId && { platformClientId: oAuthClientId }),
|
||||
},
|
||||
};
|
||||
if (oAuthClientId) {
|
||||
await this.setPlatformAttendeesEmails(clone.body, oAuthClientId);
|
||||
|
||||
@@ -515,7 +515,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
expect(updatedAtDate?.getTime()).toBeGreaterThanOrEqual(beforeCreate.getTime());
|
||||
expect(updatedAtDate?.getTime()).toBeLessThanOrEqual(afterCreate.getTime());
|
||||
|
||||
expect(data.metadata).toEqual(body.metadata);
|
||||
expect(data.metadata).toEqual({...body.metadata, platformClientId: oAuthClient.id});
|
||||
createdBooking = data;
|
||||
} else {
|
||||
throw new Error(
|
||||
@@ -2115,7 +2115,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
expect(updatedAtDate?.getTime()).toBeGreaterThanOrEqual(beforeCreate.getTime());
|
||||
expect(updatedAtDate?.getTime()).toBeLessThanOrEqual(afterCreate.getTime());
|
||||
|
||||
expect(data.metadata).toEqual(body.metadata);
|
||||
expect(data.metadata).toEqual({...body.metadata, platformClientId: oAuthClient.id});
|
||||
createdBooking = data;
|
||||
} else {
|
||||
throw new Error(
|
||||
@@ -2662,16 +2662,22 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
});
|
||||
|
||||
describe("calendar events", () => {
|
||||
// Type assertion to access private methods for test spying
|
||||
const eventManagerProto = EventManager.prototype as unknown as Record<
|
||||
"createAllCalendarEvents" | "createAllCRMEvents",
|
||||
(...args: unknown[]) => Promise<unknown[]>
|
||||
>;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
jest
|
||||
.spyOn(EventManager.prototype, "create")
|
||||
.mockImplementation(() => Promise.resolve({ results: [], referencesToCreate: [] }));
|
||||
jest
|
||||
.spyOn(EventManager.prototype, "createAllCalendarEvents")
|
||||
.spyOn(eventManagerProto, "createAllCalendarEvents")
|
||||
.mockImplementation(() => Promise.resolve([]));
|
||||
jest
|
||||
.spyOn(EventManager.prototype, "createAllCRMEvents")
|
||||
.spyOn(eventManagerProto, "createAllCRMEvents")
|
||||
.mockImplementation(() => Promise.resolve([]));
|
||||
});
|
||||
|
||||
@@ -2843,8 +2849,8 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
const data: BookingOutput_2024_08_13 = responseBody.data;
|
||||
expect(data.id).toBeDefined();
|
||||
expect(data.uid).toBeDefined();
|
||||
expect(EventManager.prototype.createAllCalendarEvents).toHaveBeenCalledTimes(0);
|
||||
expect(EventManager.prototype.createAllCRMEvents).toHaveBeenCalledTimes(0);
|
||||
expect(eventManagerProto.createAllCalendarEvents).toHaveBeenCalledTimes(0);
|
||||
expect(eventManagerProto.createAllCRMEvents).toHaveBeenCalledTimes(0);
|
||||
} else {
|
||||
throw new Error(
|
||||
"Invalid response data - expected booking but received array of possibly recurring bookings"
|
||||
@@ -2874,8 +2880,8 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
const responseBody: CreateBookingOutput_2024_08_13 = response.body;
|
||||
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||
expect(responseBody.data).toBeDefined();
|
||||
expect(EventManager.prototype.createAllCalendarEvents).toHaveBeenCalledTimes(0);
|
||||
expect(EventManager.prototype.createAllCRMEvents).toHaveBeenCalledTimes(0);
|
||||
expect(eventManagerProto.createAllCalendarEvents).toHaveBeenCalledTimes(0);
|
||||
expect(eventManagerProto.createAllCRMEvents).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2909,8 +2915,8 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
const data: BookingOutput_2024_08_13 = responseBody.data;
|
||||
expect(data.id).toBeDefined();
|
||||
expect(data.uid).toBeDefined();
|
||||
expect(EventManager.prototype.createAllCalendarEvents).toHaveBeenCalledTimes(0);
|
||||
expect(EventManager.prototype.createAllCRMEvents).toHaveBeenCalledTimes(0);
|
||||
expect(eventManagerProto.createAllCalendarEvents).toHaveBeenCalledTimes(0);
|
||||
expect(eventManagerProto.createAllCRMEvents).toHaveBeenCalledTimes(0);
|
||||
bookingThatRequiresConfirmationUid = data.uid;
|
||||
} else {
|
||||
throw new Error(
|
||||
@@ -2935,8 +2941,8 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
const data: BookingOutput_2024_08_13 = responseBody.data;
|
||||
expect(data.id).toBeDefined();
|
||||
expect(data.uid).toBeDefined();
|
||||
expect(EventManager.prototype.createAllCalendarEvents).toHaveBeenCalledTimes(0);
|
||||
expect(EventManager.prototype.createAllCRMEvents).toHaveBeenCalledTimes(0);
|
||||
expect(eventManagerProto.createAllCalendarEvents).toHaveBeenCalledTimes(0);
|
||||
expect(eventManagerProto.createAllCRMEvents).toHaveBeenCalledTimes(0);
|
||||
} else {
|
||||
throw new Error(
|
||||
"Invalid response data - expected booking but received array of possibly recurring bookings"
|
||||
|
||||
@@ -190,7 +190,10 @@ export class InputBookingsService_2024_08_13 {
|
||||
eventTypeId: inputBooking.eventTypeId,
|
||||
timeZone: inputBooking.attendee.timeZone,
|
||||
language: inputBooking.attendee.language || "en",
|
||||
metadata: inputBooking.metadata || {},
|
||||
metadata: {
|
||||
...(inputBooking.metadata || {}),
|
||||
...(platformClientId && { platformClientId }),
|
||||
},
|
||||
hasHashedBookingLink: false,
|
||||
guests,
|
||||
verificationCode: inputBooking.emailVerificationCode,
|
||||
|
||||
@@ -2615,7 +2615,7 @@ async function handler(
|
||||
workflows,
|
||||
smsReminderNumber: smsReminderNumber || null,
|
||||
calendarEvent: calendarEventForWorkflow,
|
||||
hideBranding: !!eventType.owner?.hideBranding,
|
||||
hideBranding: !!eventType.owner?.hideBranding || !!platformClientId,
|
||||
seatReferenceUid: evt.attendeeSeatId,
|
||||
isDryRun,
|
||||
triggers: [WorkflowTriggerEvents.BOOKING_PAYMENT_INITIATED],
|
||||
@@ -2790,7 +2790,7 @@ async function handler(
|
||||
evt: evtWithMetadata,
|
||||
workflows,
|
||||
requiresConfirmation: !isConfirmedByDefault,
|
||||
hideBranding: !!eventType.owner?.hideBranding,
|
||||
hideBranding: !!eventType.owner?.hideBranding || !!platformClientId,
|
||||
seatReferenceUid: evt.attendeeSeatId,
|
||||
isPlatformNoEmail: noEmail && Boolean(platformClientId),
|
||||
isDryRun,
|
||||
@@ -2805,7 +2805,7 @@ async function handler(
|
||||
workflows,
|
||||
smsReminderNumber: smsReminderNumber || null,
|
||||
calendarEvent: evtWithMetadata,
|
||||
hideBranding: !!eventType.owner?.hideBranding,
|
||||
hideBranding: !!eventType.owner?.hideBranding || !!platformClientId,
|
||||
seatReferenceUid: evt.attendeeSeatId,
|
||||
isDryRun,
|
||||
isConfirmedByDefault,
|
||||
|
||||
@@ -36,7 +36,7 @@ import customTemplate from "../lib/reminders/templates/customTemplate";
|
||||
import emailRatingTemplate from "../lib/reminders/templates/emailRatingTemplate";
|
||||
import emailReminderTemplate from "../lib/reminders/templates/emailReminderTemplate";
|
||||
|
||||
export async function handler(req: NextRequest) {
|
||||
async function handler(req: NextRequest) {
|
||||
const apiKey = req.headers.get("authorization") || req.nextUrl.searchParams.get("apiKey");
|
||||
|
||||
if (process.env.CRON_API_KEY !== apiKey) {
|
||||
@@ -243,9 +243,7 @@ export async function handler(req: NextRequest) {
|
||||
eventEndTimeInAttendeeTimezone: dayjs(reminder.booking?.endTime).tz(targetAttendee?.timeZone),
|
||||
};
|
||||
const emailLocale = locale || "en";
|
||||
const brandingDisabled = reminder.booking.eventType?.team
|
||||
? !!reminder.booking.eventType?.team?.hideBranding
|
||||
: !!reminder.booking.user?.hideBranding;
|
||||
const brandingDisabled = shouldHideBranding(reminder.booking);
|
||||
|
||||
const emailSubject = customTemplate(
|
||||
reminder.workflowStep.emailSubject || "",
|
||||
@@ -271,9 +269,7 @@ export async function handler(req: NextRequest) {
|
||||
getTimeFormatStringFromUserTimeFormat(reminder.booking.user?.timeFormat)
|
||||
).text.length === 0;
|
||||
} else if (reminder.workflowStep.template === WorkflowTemplates.REMINDER) {
|
||||
const brandingDisabled = reminder.booking.eventType?.team
|
||||
? !!reminder.booking.eventType?.team?.hideBranding
|
||||
: !!reminder.booking.user?.hideBranding;
|
||||
const brandingDisabled = shouldHideBranding(reminder.booking);
|
||||
emailContent = emailReminderTemplate({
|
||||
isEditingMode: false,
|
||||
locale: reminder.booking.user?.locale || "en",
|
||||
@@ -301,6 +297,7 @@ export async function handler(req: NextRequest) {
|
||||
const bookerUrl = await getBookerBaseUrl(
|
||||
reminder.booking.eventType?.team?.parentId ?? organizerOrganizationId ?? null
|
||||
);
|
||||
const brandingDisabled = shouldHideBranding(reminder.booking);
|
||||
emailContent = emailRatingTemplate({
|
||||
isEditingMode: true,
|
||||
locale: reminder.booking.user?.locale || "en",
|
||||
@@ -313,6 +310,7 @@ export async function handler(req: NextRequest) {
|
||||
timeZone: timeZone || "",
|
||||
organizer: reminder.booking.user?.name || "",
|
||||
name: name || "",
|
||||
isBrandingDisabled: brandingDisabled,
|
||||
ratingUrl: `${bookerUrl}/booking/${reminder.booking.uid}?rating`,
|
||||
noShowUrl: `${bookerUrl}/booking/${reminder.booking.uid}?noShow=true`,
|
||||
});
|
||||
@@ -430,9 +428,7 @@ export async function handler(req: NextRequest) {
|
||||
|
||||
const emailBodyEmpty = false;
|
||||
|
||||
const brandingDisabled = reminder.booking.eventType?.team
|
||||
? !!reminder.booking.eventType?.team?.hideBranding
|
||||
: !!reminder.booking.user?.hideBranding;
|
||||
const brandingDisabled = shouldHideBranding(reminder.booking);
|
||||
|
||||
emailContent = emailReminderTemplate({
|
||||
isEditingMode: false,
|
||||
@@ -516,3 +512,23 @@ export async function handler(req: NextRequest) {
|
||||
|
||||
return NextResponse.json({ message: `${unscheduledReminders.length} Emails to schedule` }, { status: 200 });
|
||||
}
|
||||
|
||||
function shouldHideBranding(booking: {
|
||||
metadata: unknown;
|
||||
eventType?: { team?: { hideBranding?: boolean } | null } | null;
|
||||
user?: { hideBranding?: boolean } | null;
|
||||
}): boolean {
|
||||
const bookingMetadata = bookingMetadataSchema.parse(booking.metadata || {});
|
||||
|
||||
if (bookingMetadata?.platformClientId) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (booking.eventType?.team) {
|
||||
return !!booking.eventType.team.hideBranding;
|
||||
}
|
||||
|
||||
return !!booking.user?.hideBranding;
|
||||
}
|
||||
|
||||
export {handler};
|
||||
@@ -87,9 +87,10 @@ export class EmailWorkflowService {
|
||||
creditCheckFn,
|
||||
});
|
||||
|
||||
const hideBranding = await getHideBranding({
|
||||
userId: workflow.userId ?? undefined,
|
||||
teamId: workflow.teamId ?? undefined,
|
||||
const hideBranding = await this.shouldHideBranding({
|
||||
platformClientId: evt.platformClientId,
|
||||
userId: workflow.userId,
|
||||
teamId: workflow.teamId,
|
||||
});
|
||||
|
||||
const emailWorkflowContentParams = await this.generateParametersToBuildEmailWorkflowContent({
|
||||
@@ -237,6 +238,27 @@ export class EmailWorkflowService {
|
||||
} as const;
|
||||
}
|
||||
|
||||
private async shouldHideBranding({
|
||||
platformClientId,
|
||||
userId,
|
||||
teamId,
|
||||
}: {
|
||||
platformClientId?: string | null;
|
||||
userId?: number | null;
|
||||
teamId?: number | null;
|
||||
}): Promise<boolean> {
|
||||
if (platformClientId) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const hideBranding = await getHideBranding({
|
||||
userId: userId ?? undefined,
|
||||
teamId: teamId ?? undefined,
|
||||
});
|
||||
|
||||
return hideBranding;
|
||||
}
|
||||
|
||||
async generateEmailPayloadForEvtWorkflow({
|
||||
evt,
|
||||
sendTo,
|
||||
@@ -412,6 +434,7 @@ export class EmailWorkflowService {
|
||||
meetingUrl,
|
||||
otherPerson: attendeeName,
|
||||
name,
|
||||
isBrandingDisabled: hideBranding,
|
||||
});
|
||||
} else if (matchedTemplate === WorkflowTemplates.RATING) {
|
||||
emailContent = emailRatingTemplate({
|
||||
@@ -426,6 +449,7 @@ export class EmailWorkflowService {
|
||||
timeZone,
|
||||
organizer: evt.organizer.name,
|
||||
name,
|
||||
isBrandingDisabled: hideBranding,
|
||||
ratingUrl: `${bookerUrl}/booking/${evt.uid}?rating`,
|
||||
noShowUrl: `${bookerUrl}/booking/${evt.uid}?noShow=true`,
|
||||
});
|
||||
@@ -554,4 +578,4 @@ export class EmailWorkflowService {
|
||||
sender,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -89,6 +89,7 @@ export type BookingInfo = {
|
||||
videoCallData?: {
|
||||
url?: string;
|
||||
};
|
||||
platformClientId?: string | null;
|
||||
};
|
||||
|
||||
export type WorkflowContextData =
|
||||
|
||||
@@ -49,14 +49,17 @@ export async function sendWorkflowEmails(payload: string): Promise<void> {
|
||||
throw new Error("Booking not found");
|
||||
}
|
||||
|
||||
const calendarEvent = (await CalendarEventBuilder.fromBooking(booking, {})).build();
|
||||
const bookingMetadata = bookingMetadataSchema.parse(booking.metadata || {});
|
||||
|
||||
const calendarEvent = (await CalendarEventBuilder.fromBooking(booking, {
|
||||
platformClientId: bookingMetadata?.platformClientId,
|
||||
})).build();
|
||||
|
||||
if (!calendarEvent) {
|
||||
throw new Error("Calendar event could not be built");
|
||||
}
|
||||
|
||||
// Check if videoCallUrl exists in booking metadata and add it to evt.metadata
|
||||
const bookingMetadata = bookingMetadataSchema.parse(booking.metadata || {});
|
||||
const metadata = bookingMetadata?.videoCallUrl
|
||||
? {
|
||||
videoCallUrl: bookingMetadata.videoCallUrl,
|
||||
|
||||
@@ -523,6 +523,7 @@ export const teamMetadataStrictSchema = baseTeamMetadataSchema
|
||||
export const bookingMetadataSchema = z
|
||||
.object({
|
||||
videoCallUrl: z.string().optional(),
|
||||
platformClientId: z.string().optional(),
|
||||
})
|
||||
.and(z.record(z.string()))
|
||||
.nullable()
|
||||
|
||||
Reference in New Issue
Block a user