chore: Fixing ICS file in workflows & ICS calendar descriptions [CAL-4409] (#16731)

* Remove title and subtitle from ICS calendar description

* Remove unused method

* Use generateIcsString function for workflows

* Type fixes

* Refactor typing of CalEventParser

* WIP

* Type fixes

* Fix test

* Fix tests

* type fixes

* Remove duplicate code before `scheduleEmailReminder`
This commit is contained in:
Joe Au-Yeung
2024-10-07 10:50:50 -04:00
committed by GitHub
parent 44f6f2e320
commit 0d757039f7
25 changed files with 173 additions and 223 deletions
-10
View File
@@ -12,20 +12,14 @@ export enum GenerateIcsRole {
export default function generateIcsFile({
calEvent,
title,
subtitle,
role,
status,
t,
isRequestReschedule,
}: {
calEvent: CalendarEvent;
title: string;
subtitle: string;
role: GenerateIcsRole;
status: EventStatus;
t?: TFunction;
isRequestReschedule?: boolean;
}) {
// O365 deletes emails if the calendar event is selected. Currently no option to disable this on the web
if (
@@ -39,12 +33,8 @@ export default function generateIcsFile({
filename: "event.ics",
content: generateIcsString({
event: calEvent,
title,
subtitle,
role,
status,
t,
isRequestReschedule,
}),
method: "REQUEST",
};
+22 -33
View File
@@ -1,16 +1,13 @@
import type { DateArray, ParticipationStatus, ParticipationRole, EventStatus } from "ics";
import type { DateArray, ParticipationRole, EventStatus, ParticipationStatus } from "ics";
import { createEvent } from "ics";
import type { TFunction } from "next-i18next";
import { RRule } from "rrule";
import dayjs from "@calcom/dayjs";
import { getRichDescription } from "@calcom/lib/CalEventParser";
import { getWhen } from "@calcom/lib/CalEventParser";
import { getVideoCallUrlFromCalEvent } from "@calcom/lib/CalEventParser";
import type { CalendarEvent, Person } from "@calcom/types/Calendar";
import { GenerateIcsRole } from "./generateIcsFile";
export enum BookingAction {
Create = "create",
Cancel = "cancel",
@@ -19,52 +16,44 @@ export enum BookingAction {
LocationChange = "location_change",
}
export type ICSCalendarEvent = Pick<
CalendarEvent,
| "uid"
| "iCalUID"
| "iCalSequence"
| "startTime"
| "endTime"
| "title"
| "organizer"
| "attendees"
| "location"
| "recurringEvent"
| "team"
| "type"
| "hideCalendarEventDetails"
>;
const generateIcsString = ({
event,
title,
subtitle,
status,
role,
isRequestReschedule,
partstat = "ACCEPTED",
t,
}: {
event: CalendarEvent;
title: string;
subtitle: string;
event: ICSCalendarEvent;
status: EventStatus;
role: GenerateIcsRole;
isRequestReschedule?: boolean;
partstat?: ParticipationStatus;
t?: TFunction;
}) => {
const location = getVideoCallUrlFromCalEvent(event) || event.location;
// Taking care of recurrence rule
let recurrenceRule: string | undefined = undefined;
const partstat: ParticipationStatus = "ACCEPTED";
const icsRole: ParticipationRole = "REQ-PARTICIPANT";
if (event.recurringEvent?.count) {
// ics appends "RRULE:" already, so removing it from RRule generated string
recurrenceRule = new RRule(event.recurringEvent).toString().replace("RRULE:", "");
}
const getTextBody = (title: string, subtitle: string): string => {
let body: string;
if (isRequestReschedule && role === GenerateIcsRole.ATTENDEE && t) {
body = `
${title}
${getWhen(event, t)}
${subtitle}`;
}
body = `
${title}
${subtitle}
${getRichDescription(event, t)}
`.trim();
return body;
};
const icsEvent = createEvent({
uid: event.iCalUID || event.uid!,
sequence: event.iCalSequence || 0,
@@ -76,7 +65,7 @@ const generateIcsString = ({
startInputType: "utc",
productId: "calcom/ics",
title: event.title,
description: getTextBody(title, subtitle),
description: getRichDescription(event, t),
duration: { minutes: dayjs(event.endTime).diff(dayjs(event.startTime), "minute") },
organizer: { name: event.organizer.name, email: event.organizer.email },
...{ recurrenceRule },
@@ -7,8 +7,6 @@ export default class AttendeeAddGuestsEmail extends AttendeeScheduledEmail {
return {
icalEvent: generateIcsFile({
calEvent: this.calEvent,
title: this.t("new_guests_added"),
subtitle: this.t("emailed_you_and_any_other_attendees"),
role: GenerateIcsRole.ATTENDEE,
status: "CONFIRMED",
}),
@@ -7,8 +7,6 @@ export default class AttendeeCancelledEmail extends AttendeeScheduledEmail {
return {
icalEvent: generateIcsFile({
calEvent: this.calEvent,
title: this.t("event_request_cancelled"),
subtitle: this.t("emailed_you_and_any_other_attendees"),
role: GenerateIcsRole.ATTENDEE,
status: "CANCELLED",
}),
@@ -7,8 +7,6 @@ export default class AttendeeLocationChangeEmail extends AttendeeScheduledEmail
return {
icalEvent: generateIcsFile({
calEvent: this.calEvent,
title: this.t("event_location_changed"),
subtitle: this.t("emailed_you_and_any_other_attendees"),
role: GenerateIcsRole.ATTENDEE,
status: "CONFIRMED",
}),
@@ -7,8 +7,6 @@ export default class AttendeeRescheduledEmail extends AttendeeScheduledEmail {
return {
icalEvent: generateIcsFile({
calEvent: this.calEvent,
title: this.t("event_type_has_been_rescheduled"),
subtitle: this.t("emailed_you_and_any_other_attendees"),
role: GenerateIcsRole.ATTENDEE,
status: "CONFIRMED",
}),
@@ -35,11 +35,7 @@ export default class AttendeeScheduledEmail extends BaseEmail {
return {
icalEvent: generateIcsFile({
calEvent: this.calEvent,
title: this.calEvent.recurringEvent?.count
? this.t("your_event_has_been_scheduled_recurring")
: this.t("your_event_has_been_scheduled"),
role: GenerateIcsRole.ATTENDEE,
subtitle: this.t("emailed_you_and_any_other_attendees"),
status: "CONFIRMED",
}),
to: `${this.attendee.name} <${this.attendee.email}>`,
@@ -19,10 +19,6 @@ export default class AttendeeWasRequestedToRescheduleEmail extends OrganizerSche
return {
icalEvent: generateIcsFile({
calEvent: this.calEvent,
title: this.t("request_reschedule_booking"),
subtitle: this.t("request_reschedule_subtitle", {
organizer: this.calEvent.organizer.name,
}),
role: GenerateIcsRole.ATTENDEE,
status: "CANCELLED",
}),
@@ -11,8 +11,6 @@ export default class OrganizerAddGuestsEmail extends OrganizerScheduledEmail {
return {
icalEvent: generateIcsFile({
calEvent: this.calEvent,
title: this.t("new_guests_added"),
subtitle: this.t("emailed_you_and_any_other_attendees"),
role: GenerateIcsRole.ORGANIZER,
status: "CONFIRMED",
}),
@@ -11,8 +11,6 @@ export default class OrganizerCancelledEmail extends OrganizerScheduledEmail {
return {
icalEvent: generateIcsFile({
calEvent: this.calEvent,
title: this.t("event_request_cancelled"),
subtitle: this.t("emailed_you_and_any_other_attendees"),
status: "CANCELLED",
role: GenerateIcsRole.ORGANIZER,
}),
@@ -11,8 +11,6 @@ export default class OrganizerLocationChangeEmail extends OrganizerScheduledEmai
return {
icalEvent: generateIcsFile({
calEvent: this.calEvent,
title: this.t("event_location_changed"),
subtitle: this.t("emailed_you_and_any_other_attendees"),
role: GenerateIcsRole.ORGANIZER,
status: "CONFIRMED",
}),
@@ -1,8 +1,3 @@
import type { DateArray, Person } from "ics";
import { createEvent } from "ics";
import dayjs from "@calcom/dayjs";
import { getRichDescription } from "@calcom/lib/CalEventParser";
import { EMAIL_FROM_NAME } from "@calcom/lib/constants";
import type { CalendarEvent } from "@calcom/types/Calendar";
@@ -22,12 +17,6 @@ export default class OrganizerRequestedToRescheduleEmail extends OrganizerSchedu
return {
icalEvent: generateIcsFile({
calEvent: this.calEvent,
title: this.t("request_reschedule_title_organizer", {
attendee: this.calEvent.attendees[0].name,
}),
subtitle: this.t("request_reschedule_subtitle_organizer", {
attendee: this.calEvent.attendees[0].name,
}),
role: GenerateIcsRole.ORGANIZER,
status: "CANCELLED",
}),
@@ -52,50 +41,4 @@ export default class OrganizerRequestedToRescheduleEmail extends OrganizerSchedu
),
};
}
// @OVERRIDE
protected getiCalEventAsString(): string | undefined {
const icsEvent = createEvent({
start: dayjs(this.calEvent.startTime)
.utc()
.toArray()
.slice(0, 6)
.map((v, i) => (i === 1 ? v + 1 : v)) as DateArray,
startInputType: "utc",
productId: "calcom/ics",
title: this.t("ics_event_title", {
eventType: this.calEvent.type,
name: this.calEvent.attendees[0].name,
}),
description: this.getTextBody(
this.t("request_reschedule_title_organizer", {
attendee: this.calEvent.attendees[0].name,
}),
this.t("request_reschedule_subtitle_organizer", {
attendee: this.calEvent.attendees[0].name,
})
),
duration: { minutes: dayjs(this.calEvent.endTime).diff(dayjs(this.calEvent.startTime), "minute") },
organizer: { name: this.calEvent.organizer.name, email: this.calEvent.organizer.email },
attendees: this.calEvent.attendees.map((attendee: Person) => ({
name: attendee.name,
email: attendee.email,
})),
status: "CANCELLED",
method: "CANCEL",
});
if (icsEvent.error) {
throw icsEvent.error;
}
return icsEvent.value;
}
// @OVERRIDE
protected getTextBody(title = "", subtitle = ""): string {
return `
${this.t(title)}
${this.t(subtitle)}
${getRichDescription(this.calEvent, this.t, true)}
`.trim();
}
}
@@ -11,8 +11,6 @@ export default class OrganizerRescheduledEmail extends OrganizerScheduledEmail {
return {
icalEvent: generateIcsFile({
calEvent: this.calEvent,
title: this.t("event_type_has_been_rescheduled"),
subtitle: this.t("emailed_you_and_any_other_attendees"),
role: GenerateIcsRole.ORGANIZER,
status: "CONFIRMED",
}),
@@ -34,10 +34,6 @@ export default class OrganizerScheduledEmail extends BaseEmail {
return {
icalEvent: generateIcsFile({
calEvent: this.calEvent,
title: this.calEvent.recurringEvent?.count
? this.t("new_event_scheduled_recurring")
: this.t("new_event_scheduled"),
subtitle: this.t("emailed_you_and_any_other_attendees"),
role: GenerateIcsRole.ORGANIZER,
status: "CONFIRMED",
}),
@@ -378,7 +378,7 @@ async function handler(req: CustomRequest) {
bookerUrl,
...{
eventType: {
slug: bookingToDelete.eventType?.slug,
slug: bookingToDelete.eventType?.slug as string,
schedulingType: bookingToDelete.eventType?.schedulingType,
hosts: bookingToDelete.eventType?.hosts,
},
@@ -3,10 +3,12 @@ import type { NextApiRequest, NextApiResponse } from "next";
import { v4 as uuidv4 } from "uuid";
import dayjs from "@calcom/dayjs";
import generateIcsString from "@calcom/emails/lib/generateIcsString";
import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventResponses";
import { getBookerBaseUrl } from "@calcom/lib/getBookerUrl/server";
import logger from "@calcom/lib/logger";
import { defaultHandler } from "@calcom/lib/server";
import { getTranslation } from "@calcom/lib/server/i18n";
import { getTimeFormatStringFromUserTimeFormat } from "@calcom/lib/timeFormat";
import prisma from "@calcom/prisma";
import { SchedulingType, WorkflowActions, WorkflowMethods, WorkflowTemplates } from "@calcom/prisma/enums";
@@ -18,7 +20,6 @@ import {
getAllRemindersToDelete,
getAllUnscheduledReminders,
} from "../lib/getWorkflowReminders";
import { getiCalEventAsString } from "../lib/getiCalEventAsString";
import {
cancelScheduledEmail,
deleteScheduledSend,
@@ -271,6 +272,37 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
if (emailContent.emailSubject.length > 0 && !emailBodyEmpty && sendTo) {
const batchId = await getBatchId();
const booking = reminder.booking;
const t = await getTranslation(booking.user?.locale ?? "en", "common");
const attendeePromises = [];
for (const attendee of booking.attendees) {
attendeePromises.push(
getTranslation(attendee.locale ?? "en", "common").then((tAttendee) => ({
...attendee,
language: { locale: attendee.locale ?? "en", translate: tAttendee },
}))
);
}
const attendees = await Promise.all(attendeePromises);
const event = {
...booking,
startTime: dayjs(booking.startTime).utc().format(),
endTime: dayjs(booking.endTime).utc().format(),
type: booking.eventType?.slug ?? "",
organizer: {
name: booking.user?.name ?? "",
email: booking.user?.email ?? "",
timeZone: booking.user?.timeZone ?? "",
language: { translate: t, locale: booking.user?.locale ?? "en" },
},
attendees,
};
sendEmailPromises.push(
sendSendgridMail(
{
@@ -283,7 +315,9 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
attachments: reminder.workflowStep.includeCalendarEvent
? [
{
content: Buffer.from(getiCalEventAsString(reminder.booking) || "").toString("base64"),
content: Buffer.from(
generateIcsString({ event, status: "CONFIRMED" }) || ""
).toString("base64"),
filename: "event.ics",
type: "text/calendar; method=REQUEST",
disposition: "attachment",
@@ -27,9 +27,11 @@ type PartialBooking =
| "attendees"
| "userPrimaryEmail"
| "smsReminderNumber"
| "title"
> & {
eventType:
| (Partial<EventType> & {
slug: string;
team: { parentId?: number };
hosts: { user: { email: string; destinationCalendar?: { primaryEmail: string } } }[] | undefined;
})
@@ -1,15 +1,13 @@
import type { MailData } from "@sendgrid/helpers/classes/mail";
import { createEvent } from "ics";
import type { ParticipationStatus } from "ics";
import type { DateArray } from "ics";
import { RRule } from "rrule";
import type { EventStatus } from "ics";
import { v4 as uuidv4 } from "uuid";
import { guessEventLocationType } from "@calcom/app-store/locations";
import dayjs from "@calcom/dayjs";
import generateIcsString from "@calcom/emails/lib/generateIcsString";
import { preprocessNameFieldDataWithVariant } from "@calcom/features/form-builder/utils";
import { WEBSITE_URL } from "@calcom/lib/constants";
import logger from "@calcom/lib/logger";
import { getTranslation } from "@calcom/lib/server/i18n";
import prisma from "@calcom/prisma";
import type { TimeUnit } from "@calcom/prisma/enums";
import {
@@ -29,53 +27,6 @@ import emailReminderTemplate from "./templates/emailReminderTemplate";
const log = logger.getSubLogger({ prefix: ["[emailReminderManager]"] });
function getiCalEventAsString(evt: BookingInfo, status?: ParticipationStatus) {
const uid = uuidv4();
let recurrenceRule: string | undefined = undefined;
if (evt.eventType.recurringEvent?.count) {
recurrenceRule = new RRule(evt.eventType.recurringEvent).toString().replace("RRULE:", "");
}
let location = bookingMetadataSchema.parse(evt.metadata || {})?.videoCallUrl;
if (!location) {
location = guessEventLocationType(location)?.label || evt.location || "";
}
const icsEvent = createEvent({
uid,
startInputType: "utc",
start: dayjs(evt.startTime)
.utc()
.toArray()
.slice(0, 6)
.map((v, i) => (i === 1 ? v + 1 : v)) as DateArray,
duration: { minutes: dayjs(evt.endTime).diff(dayjs(evt.startTime), "minute") },
title: evt.title,
description: evt.additionalNotes || "",
location,
organizer: { email: evt.organizer.email || "", name: evt.organizer.name },
attendees: [
{
name: preprocessNameFieldDataWithVariant("fullName", evt.attendees[0].name) as string,
email: evt.attendees[0].email,
partstat: status,
role: "REQ-PARTICIPANT",
rsvp: true,
},
],
method: "REQUEST",
...{ recurrenceRule },
status: "CONFIRMED",
});
if (icsEvent.error) {
throw icsEvent.error;
}
return icsEvent.value;
}
type ScheduleEmailReminderAction = Extract<
WorkflowActions,
"EMAIL_HOST" | "EMAIL_ATTENDEE" | "EMAIL_ADDRESS"
@@ -95,6 +46,7 @@ export interface ScheduleReminderArgs {
}
interface scheduleEmailReminderArgs extends ScheduleReminderArgs {
evt: BookingInfo;
sendTo: MailData["to"];
action: ScheduleEmailReminderAction;
emailSubject?: string;
@@ -260,13 +212,26 @@ export const scheduleEmailReminder = async (args: scheduleEmailReminderArgs) =>
const batchId = await getBatchId();
function sendEmail(data: Partial<MailData>, triggerEvent?: WorkflowTriggerEvents) {
const status: ParticipationStatus =
triggerEvent === WorkflowTriggerEvents.AFTER_EVENT
? "COMPLETED"
: triggerEvent === WorkflowTriggerEvents.EVENT_CANCELLED
? "DECLINED"
: "ACCEPTED";
async function sendEmail(data: Partial<MailData>, triggerEvent?: WorkflowTriggerEvents) {
const status: EventStatus =
triggerEvent === WorkflowTriggerEvents.EVENT_CANCELLED ? "CANCELLED" : "CONFIRMED";
const organizerT = await getTranslation(evt.organizer.language.locale || "en", "common");
const attendeeT = await getTranslation(evt.attendees[0].language.locale || "en", "common");
const attendee = {
...evt.attendees[0],
name: preprocessNameFieldDataWithVariant("fullName", evt.attendees[0].name) as string,
language: { ...evt.attendees[0].language, translate: attendeeT },
};
const emailEvent = {
...evt,
type: evt.eventType?.slug || "",
organizer: { ...evt.organizer, language: { ...evt.organizer.language, translate: organizerT } },
attendees: [attendee],
};
return sendSendgridMail(
{
@@ -278,7 +243,12 @@ export const scheduleEmailReminder = async (args: scheduleEmailReminderArgs) =>
attachments: includeCalendarEvent
? [
{
content: Buffer.from(getiCalEventAsString(evt, status) || "").toString("base64"),
content: Buffer.from(
generateIcsString({
event: emailEvent,
status,
}) || ""
).toString("base64"),
filename: "event.ics",
type: "text/calendar; method=REQUEST",
disposition: "attachment",
@@ -17,7 +17,7 @@ import { scheduleWhatsappReminder } from "./whatsappReminderManager";
export type ExtendedCalendarEvent = Omit<CalendarEvent, "bookerUrl"> & {
metadata?: { videoCallUrl: string | undefined };
eventType: {
slug?: string;
slug: string;
schedulingType?: SchedulingType | null;
hosts?: { user: { email: string; destinationCalendar?: { primaryEmail: string | null } | null } }[];
};
@@ -46,8 +46,8 @@ export type BookingInfo = {
timeFormat?: TimeFormat;
username?: string;
};
eventType: {
slug?: string;
eventType?: {
slug: string;
recurringEvent?: RecurringEvent | null;
};
startTime: string;
@@ -11,6 +11,7 @@ import {
expectSMSWorkflowToBeTriggered,
expectSMSWorkflowToBeNotTriggered,
} from "@calcom/web/test/utils/bookingScenario/expects";
import { setupAndTeardown } from "@calcom/web/test/utils/bookingScenario/setupAndTeardown";
import { describe, expect, beforeAll, vi } from "vitest";
@@ -87,7 +88,7 @@ const mockBookings = [
status: BookingStatus.ACCEPTED,
startTime: `2024-05-22T04:00:00.000Z`,
endTime: `2024-05-22T04:30:00.000Z`,
attendees: [{ email: "attendee@example.com" }],
attendees: [{ email: "attendee@example.com", locale: "en" }],
},
{
uid: "mL4Dx9jTkQbnWEu3pR7yNcF",
@@ -96,7 +97,7 @@ const mockBookings = [
status: BookingStatus.ACCEPTED,
startTime: `2024-05-23T04:00:00.000Z`,
endTime: `2024-05-23T04:30:00.000Z`,
attendees: [{ email: "attendee@example.com" }],
attendees: [{ email: "attendee@example.com", locale: "en" }],
},
{
uid: "Fd9Rf8iYsOpmQUw9hB1vKd8",
@@ -105,7 +106,7 @@ const mockBookings = [
status: BookingStatus.ACCEPTED,
startTime: `2024-06-01T04:30:00.000Z`,
endTime: `2024-06-01T05:00:00.000Z`,
attendees: [{ email: "attendee@example.com" }],
attendees: [{ email: "attendee@example.com", locale: "en" }],
},
{
uid: "Kd8Dx9jTkQbnWEu3pR7yKdl",
@@ -114,7 +115,7 @@ const mockBookings = [
status: BookingStatus.ACCEPTED,
startTime: `2024-06-02T04:30:00.000Z`,
endTime: `2024-06-02T05:00:00.000Z`,
attendees: [{ email: "attendee@example.com" }],
attendees: [{ email: "attendee@example.com", locale: "en" }],
},
];
@@ -366,6 +367,8 @@ describe("deleteRemindersOfActiveOnIds", () => {
});
describe("scheduleBookingReminders", () => {
setupAndTeardown();
test("schedules workflow notifications with before event trigger and email to host action", async ({}) => {
// organizer is part of org and two teams
const organizer = getOrganizer({
+65 -22
View File
@@ -12,14 +12,17 @@ const translator = short();
// The odd indentation in this file is necessary because otherwise the leading tabs will be applied into the event description.
export const getWhat = (calEvent: CalendarEvent, t: TFunction) => {
export const getWhat = (calEvent: Pick<CalendarEvent, "title">, t: TFunction) => {
return `
${t("what")}:
${calEvent.title}
`;
};
export const getWhen = (calEvent: CalendarEvent, t: TFunction) => {
export const getWhen = (
calEvent: Pick<CalendarEvent, "organizer" | "attendees" | "seatsPerTimeSlot">,
t: TFunction
) => {
return calEvent.seatsPerTimeSlot
? `
${t("organizer_timezone")}:
@@ -31,7 +34,13 @@ ${calEvent.attendees[0].timeZone}
`;
};
export const getWho = (calEvent: CalendarEvent, t: TFunction) => {
export const getWho = (
calEvent: Pick<
CalendarEvent,
"attendees" | "seatsPerTimeSlot" | "seatsShowAttendees" | "organizer" | "team"
>,
t: TFunction
) => {
let attendeesFromCalEvent = [...calEvent.attendees];
if (calEvent.seatsPerTimeSlot && !calEvent.seatsShowAttendees) {
attendeesFromCalEvent = [];
@@ -67,7 +76,7 @@ ${organizer + attendees + teamMembers.join("")}
`;
};
export const getAdditionalNotes = (calEvent: CalendarEvent, t: TFunction) => {
export const getAdditionalNotes = (calEvent: Pick<CalendarEvent, "additionalNotes">, t: TFunction) => {
if (!calEvent.additionalNotes) {
return "";
}
@@ -77,7 +86,7 @@ ${calEvent.additionalNotes}
`;
};
export const getUserFieldsResponses = (calEvent: CalendarEvent) => {
export const getUserFieldsResponses = (calEvent: Parameters<typeof getLabelValueMapFromResponses>[0]) => {
const labelValueMap = getLabelValueMapFromResponses(calEvent);
if (!labelValueMap) {
@@ -98,7 +107,7 @@ ${labelValueMap[key]}
return responsesString;
};
export const getAppsStatus = (calEvent: CalendarEvent, t: TFunction) => {
export const getAppsStatus = (calEvent: Pick<CalendarEvent, "appsStatus">, t: TFunction) => {
if (!calEvent.appsStatus) {
return "";
}
@@ -115,7 +124,7 @@ export const getAppsStatus = (calEvent: CalendarEvent, t: TFunction) => {
`;
};
export const getDescription = (calEvent: CalendarEvent, t: TFunction) => {
export const getDescription = (calEvent: Pick<CalendarEvent, "description">, t: TFunction) => {
if (!calEvent.description) {
return "";
}
@@ -123,7 +132,9 @@ export const getDescription = (calEvent: CalendarEvent, t: TFunction) => {
${calEvent.description}
`;
};
export const getLocation = (calEvent: CalendarEvent) => {
export const getLocation = (
calEvent: Parameters<typeof getVideoCallUrlFromCalEvent>[0] & Parameters<typeof getProviderName>[0]
) => {
const meetingUrl = getVideoCallUrlFromCalEvent(calEvent);
if (meetingUrl) {
return meetingUrl;
@@ -132,7 +143,7 @@ export const getLocation = (calEvent: CalendarEvent) => {
return providerName || calEvent.location || "";
};
export const getProviderName = (calEvent: CalendarEvent): string => {
export const getProviderName = (calEvent: Pick<CalendarEvent, "location">): string => {
// TODO: use getAppName from @calcom/app-store
if (calEvent.location && calEvent.location.includes("integrations:")) {
let location = calEvent.location.split(":")[1];
@@ -148,12 +159,12 @@ export const getProviderName = (calEvent: CalendarEvent): string => {
return "";
};
export const getUid = (calEvent: CalendarEvent): string => {
export const getUid = (calEvent: Pick<CalendarEvent, "uid">): string => {
const uid = calEvent.uid;
return uid ?? translator.fromUUID(uuidv5(JSON.stringify(calEvent), uuidv5.URL));
};
const getSeatReferenceId = (calEvent: CalendarEvent): string => {
const getSeatReferenceId = (calEvent: Pick<CalendarEvent, "attendeeSeatId">): string => {
return calEvent.attendeeSeatId ? calEvent.attendeeSeatId : "";
};
@@ -168,7 +179,12 @@ export const getBookingUrl = (calEvent: CalendarEvent) => {
return `${calEvent.bookerUrl ?? WEBAPP_URL}/booking/${getUid(calEvent)}?changes=true`;
};
export const getPlatformManageLink = (calEvent: CalendarEvent, t: TFunction) => {
export const getPlatformManageLink = (
calEvent: Parameters<typeof getCancelLink>[0] &
Parameters<typeof getRescheduleLink>[0]["calEvent"] &
Pick<CalendarEvent, "platformBookingUrl" | "platformRescheduleUrl" | "team">,
t: TFunction
) => {
const shouldDisplayReschedule = !calEvent.recurringEvent && calEvent.platformRescheduleUrl;
let res =
calEvent.platformBookingUrl || shouldDisplayReschedule || calEvent.platformCancelUrl
@@ -194,7 +210,11 @@ export const getPlatformManageLink = (calEvent: CalendarEvent, t: TFunction) =>
return res;
};
export const getManageLink = (calEvent: CalendarEvent, t: TFunction) => {
export const getManageLink = (
calEvent: Parameters<typeof getPlatformManageLink>[0] &
Pick<CalendarEvent, "platformClientId" | "bookerUrl">,
t: TFunction
) => {
if (calEvent.platformClientId) {
return getPlatformManageLink(calEvent, t);
}
@@ -205,7 +225,7 @@ export const getManageLink = (calEvent: CalendarEvent, t: TFunction) => {
};
export const getPlatformCancelLink = (
calEvent: CalendarEvent,
calEvent: Pick<CalendarEvent, "platformCancelUrl" | "type" | "organizer" | "recurringEvent" | "team">,
bookingUid: string,
seatUid?: string
): string => {
@@ -223,7 +243,13 @@ export const getPlatformCancelLink = (
return "";
};
export const getCancelLink = (calEvent: CalendarEvent, attendee?: Person): string => {
export const getCancelLink = (
calEvent: Parameters<typeof getUid>[0] &
Parameters<typeof getSeatReferenceId>[0] &
Parameters<typeof getPlatformCancelLink>[0] &
Pick<CalendarEvent, "platformClientId" | "bookerUrl">,
attendee?: Person
): string => {
const Uid = getUid(calEvent);
const seatReferenceUid = getSeatReferenceId(calEvent);
if (calEvent.platformClientId) {
@@ -241,7 +267,7 @@ export const getCancelLink = (calEvent: CalendarEvent, attendee?: Person): strin
};
export const getPlatformRescheduleLink = (
calEvent: CalendarEvent,
calEvent: Pick<CalendarEvent, "platformRescheduleUrl" | "type" | "organizer" | "team">,
bookingUid: string,
seatUid?: string
): string => {
@@ -264,7 +290,10 @@ export const getRescheduleLink = ({
allowRescheduleForCancelledBooking = false,
attendee,
}: {
calEvent: CalendarEvent;
calEvent: Parameters<typeof getUid>[0] &
Parameters<typeof getSeatReferenceId>[0] &
Parameters<typeof getPlatformRescheduleLink>[0] &
Pick<CalendarEvent, "bookerUrl" | "platformClientId">;
allowRescheduleForCancelledBooking?: boolean;
attendee?: Person;
}): string => {
@@ -286,8 +315,19 @@ export const getRescheduleLink = ({
return url.toString();
};
type RichDescriptionCalEvent = Parameters<typeof getCancellationReason>[0] &
Parameters<typeof getWhat>[0] &
Parameters<typeof getWhen>[0] &
Parameters<typeof getLocation>[0] &
Parameters<typeof getDescription>[0] &
Parameters<typeof getAdditionalNotes>[0] &
Parameters<typeof getUserFieldsResponses>[0] &
Parameters<typeof getAppsStatus>[0] &
Parameters<typeof getManageLink>[0] &
Pick<CalendarEvent, "organizer" | "paymentInfo">;
export const getRichDescription = (
calEvent: CalendarEvent,
calEvent: RichDescriptionCalEvent,
t_?: TFunction /*, attendee?: Person*/,
includeAppStatus = false
) => {
@@ -320,7 +360,7 @@ ${calEvent.paymentInfo.link}
`.trim();
};
export const getCancellationReason = (calEvent: CalendarEvent, t: TFunction) => {
export const getCancellationReason = (calEvent: Pick<CalendarEvent, "cancellationReason">, t: TFunction) => {
if (!calEvent.cancellationReason) return "";
return `
${t("cancellation_reason")}:
@@ -328,15 +368,18 @@ ${calEvent.cancellationReason}
`;
};
export const isDailyVideoCall = (calEvent: CalendarEvent): boolean => {
export const isDailyVideoCall = (calEvent: Pick<CalendarEvent, "videoCallData">): boolean => {
return calEvent?.videoCallData?.type === "daily_video";
};
export const getPublicVideoCallUrl = (calEvent: CalendarEvent): string => {
export const getPublicVideoCallUrl = (calEvent: Pick<CalendarEvent, "uid">): string => {
return `${WEBAPP_URL}/video/${getUid(calEvent)}`;
};
export const getVideoCallUrlFromCalEvent = (calEvent: CalendarEvent): string => {
export const getVideoCallUrlFromCalEvent = (
calEvent: Parameters<typeof getPublicVideoCallUrl>[0] &
Pick<CalendarEvent, "videoCallData" | "additionalInformation">
): string => {
if (calEvent.videoCallData) {
if (isDailyVideoCall(calEvent)) {
return getPublicVideoCallUrl(calEvent);
@@ -4,7 +4,10 @@ import { TITLE_FIELD } from "@calcom/features/bookings/lib/SystemField";
import type { bookingResponse } from "@calcom/features/bookings/lib/getBookingResponsesSchema";
import type { CalendarEvent } from "@calcom/types/Calendar";
export default function getLabelValueMapFromResponses(calEvent: CalendarEvent, isOrganizer = false) {
export default function getLabelValueMapFromResponses(
calEvent: Pick<CalendarEvent, "customInputs" | "userFieldsResponses" | "responses" | "eventTypeId">,
isOrganizer = false
) {
const { customInputs, userFieldsResponses, responses, eventTypeId } = calEvent;
const isDynamicEvent = !eventTypeId;
@@ -251,7 +251,7 @@ export const activateEventTypeHandler = async ({ ctx, input }: ActivateEventType
title: booking.title,
language: { locale: booking?.user?.locale || defaultLocale },
eventType: {
slug: booking.eventType?.slug,
slug: booking.eventType?.slug || "",
schedulingType: booking.eventType?.schedulingType,
hosts: booking.eventType?.hosts,
},
@@ -629,7 +629,7 @@ export async function scheduleBookingReminders(
title: booking.title,
language: { locale: booking?.user?.locale || defaultLocale },
eventType: {
slug: booking.eventType?.slug,
slug: booking.eventType?.slug || "",
schedulingType: booking.eventType?.schedulingType,
hosts: booking.eventType?.hosts,
},
@@ -664,6 +664,7 @@ export async function scheduleBookingReminders(
sendTo = [step.sendTo || ""];
break;
}
await scheduleEmailReminder({
evt: bookingInfo,
triggerEvent: trigger,