refactor: calEventParser CalendarEvent ISP (#25890)

* refactor: calEventParser CalendarEvent ISP

* update

* fix: complete CalEventParser ISP refactoring - update call sites and fix type errors

- Update getUid call sites to pass only uid instead of whole CalendarEvent
- Update getLocation call sites to pass narrow shape with videoCallData, additionalInformation, location, uid
- Update narrow input shapes to accept null for location (matching CalendarEvent type)
- Update recurringEvent type to accept RecurringEvent | null instead of boolean
- Update videoCallData type to be more flexible ({ type?: string; url?: string })
- Fix ManageLink.tsx to pass recurringEvent directly instead of converting to boolean

Co-Authored-By: anik@cal.com <adhabal2002@gmail.com>

* test: update CalEventParser tests to use narrow input shapes

- Update getPublicVideoCallUrl test to pass uid instead of calEvent
- Update getVideoCallPassword tests to pass videoCallData instead of calEvent

Co-Authored-By: anik@cal.com <adhabal2002@gmail.com>

* few fix

* fix type error

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>
This commit is contained in:
Anik Dhabal Babu
2026-01-17 10:40:01 +05:30
committed by GitHub
co-authored by anik@cal.com Devin AI Morgan
parent c42d60256d
commit 9fcddbf27b
17 changed files with 426 additions and 136 deletions
@@ -392,7 +392,14 @@ class FeishuCalendarService implements Calendar {
],
};
if (event.location) {
feishuEvent.location = { name: getLocation(event) };
feishuEvent.location = {
name: getLocation({
videoCallData: event.videoCallData,
additionalInformation: event.additionalInformation,
location: event.location,
uid: event.uid,
}),
};
}
return feishuEvent;
};
@@ -189,7 +189,12 @@ class GoogleCalendarService implements Calendar {
}
if (calEvent.location) {
payload["location"] = getLocation(calEvent);
payload["location"] = getLocation({
videoCallData: calEvent.videoCallData,
additionalInformation: calEvent.additionalInformation,
location: calEvent.location,
uid: calEvent.uid,
});
}
if (calEvent.recurringEvent) {
@@ -246,7 +251,12 @@ class GoogleCalendarService implements Calendar {
calendarId: selectedCalendar,
eventId: event.id || "",
requestBody: {
location: getLocation(calEvent),
location: getLocation({
videoCallData: calEvent.videoCallData,
additionalInformation: calEvent.additionalInformation,
location: calEvent.location,
uid: calEvent.uid,
}),
description: calEvent.calendarDescription,
},
});
@@ -345,7 +355,12 @@ class GoogleCalendarService implements Calendar {
};
if (event.location) {
payload["location"] = getLocation(event);
payload["location"] = getLocation({
videoCallData: event.videoCallData,
additionalInformation: event.additionalInformation,
location: event.location,
uid: event.uid,
});
}
if (event.conferenceData && event.location === MeetLocationType) {
+18 -3
View File
@@ -55,7 +55,12 @@ class HubspotCalendarService implements CRM {
private getHubspotMeetingBody = (event: CalendarEvent): string => {
const userFields = getLabelValueMapFromResponses(event);
const plainText = event?.description?.replace(/<\/?[^>]+(>|$)/g, "").replace(/_/g, " ");
const location = getLocation(event);
const location = getLocation({
videoCallData: event.videoCallData,
additionalInformation: event.additionalInformation,
location: event.location,
uid: event.uid,
});
const userFieldsHtml = Object.entries(userFields)
.map(([key, value]) => {
const formattedValue = typeof value === "boolean" ? (value ? "Yes" : "No") : value || "-";
@@ -283,7 +288,12 @@ class HubspotCalendarService implements CRM {
hs_timestamp: Date.now().toString(),
hs_meeting_title: event.title,
hs_meeting_body: this.getHubspotMeetingBody(event),
hs_meeting_location: getLocation(event),
hs_meeting_location: getLocation({
videoCallData: event.videoCallData,
additionalInformation: event.additionalInformation,
location: event.location,
uid: event.uid,
}),
hs_meeting_start_time: new Date(event.startTime).toISOString(),
hs_meeting_end_time: new Date(event.endTime).toISOString(),
hs_meeting_outcome: "SCHEDULED",
@@ -323,7 +333,12 @@ class HubspotCalendarService implements CRM {
hs_timestamp: Date.now().toString(),
hs_meeting_title: event.title,
hs_meeting_body: this.getHubspotMeetingBody(event),
hs_meeting_location: getLocation(event),
hs_meeting_location: getLocation({
videoCallData: event.videoCallData,
additionalInformation: event.additionalInformation,
location: event.location,
uid: event.uid,
}),
hs_meeting_start_time: new Date(event.startTime).toISOString(),
hs_meeting_end_time: new Date(event.endTime).toISOString(),
hs_meeting_outcome: "RESCHEDULED",
@@ -392,7 +392,14 @@ class LarkCalendarService implements Calendar {
],
};
if (event.location) {
larkEvent.location = { name: getLocation(event) };
larkEvent.location = {
name: getLocation({
videoCallData: event.videoCallData,
additionalInformation: event.additionalInformation,
location: event.location,
uid: event.uid,
}),
};
}
return larkEvent;
};
@@ -121,7 +121,12 @@ class PipedriveCrmService implements CRM {
startDateTime: event.startTime,
endDateTime: event.endTime,
description: this.getMeetingBody(event),
location: getLocation(event),
location: getLocation({
videoCallData: event.videoCallData,
additionalInformation: event.additionalInformation,
location: event.location,
uid: event.uid,
}),
associations: {
contactId: String(contacts[0].id),
},
@@ -147,7 +152,12 @@ class PipedriveCrmService implements CRM {
startDateTime: event.startTime,
endDateTime: event.endTime,
description: this.getMeetingBody(event),
location: getLocation(event),
location: getLocation({
videoCallData: event.videoCallData,
additionalInformation: event.additionalInformation,
location: event.location,
uid: event.uid,
}),
};
const headers = new Headers();
headers.append("x-revert-api-token", this.revertApiKey);
@@ -226,7 +226,12 @@ class SalesforceCRMService implements CRM {
EndDateTime: new Date(event.endTime).toISOString(),
Subject: event.title,
Description: this.getSalesforceEventBody(event),
Location: getLocation(event),
Location: getLocation({
videoCallData: event.videoCallData,
additionalInformation: event.additionalInformation,
location: event.location,
uid: event.uid,
}),
...options,
...(event.recurringEvent && {
IsRecurrence2: true,
@@ -334,7 +339,12 @@ class SalesforceCRMService implements CRM {
EndDateTime: new Date(event.endTime).toISOString(),
Subject: event.title,
Description: this.getSalesforceEventBody(event),
Location: getLocation(event),
Location: getLocation({
videoCallData: event.videoCallData,
additionalInformation: event.additionalInformation,
location: event.location,
uid: event.uid,
}),
...(event.recurringEvent && {
IsRecurrence2: true,
Recurrence2PatternText: new RRule(event.recurringEvent).toString(),
@@ -189,7 +189,12 @@ class BiginCrmService implements CRM {
Start_DateTime: toISO8601String(new Date(event.startTime)),
End_DateTime: toISO8601String(new Date(event.endTime)),
Description: event.additionalNotes,
Venue: getLocation(event),
Venue: getLocation({
videoCallData: event.videoCallData,
additionalInformation: event.additionalInformation,
location: event.location,
uid: event.uid,
}),
Who_Id: contacts[0].id,
};
@@ -249,7 +254,12 @@ class BiginCrmService implements CRM {
Start_DateTime: toISO8601String(new Date(event.startTime)),
End_DateTime: toISO8601String(new Date(event.endTime)),
Description: event.additionalNotes,
Location: getLocation(event),
Location: getLocation({
videoCallData: event.videoCallData,
additionalInformation: event.additionalInformation,
location: event.location,
uid: event.uid,
}),
};
return axios
@@ -473,7 +473,14 @@ class ZohoCalendarService implements Calendar {
action: "popup",
},
],
location: event.location ? getLocation(event) : undefined,
location: event.location
? getLocation({
videoCallData: event.videoCallData,
additionalInformation: event.additionalInformation,
location: event.location,
uid: event.uid,
})
: undefined,
};
return zohoEvent;
+12 -2
View File
@@ -134,7 +134,12 @@ class ZohoCrmCrmService implements CRM {
Start_DateTime: toISO8601String(new Date(event.startTime)),
End_DateTime: toISO8601String(new Date(event.endTime)),
Description: this.getMeetingBody(event),
Venue: getLocation(event),
Venue: getLocation({
videoCallData: event.videoCallData,
additionalInformation: event.additionalInformation,
location: event.location,
uid: event.uid,
}),
Who_Id: contacts[0].id, // Link the first attendee as the primary Who_Id
};
@@ -158,7 +163,12 @@ class ZohoCrmCrmService implements CRM {
Start_DateTime: toISO8601String(new Date(event.startTime)),
End_DateTime: toISO8601String(new Date(event.endTime)),
Description: this.getMeetingBody(event),
Venue: getLocation(event),
Venue: getLocation({
videoCallData: event.videoCallData,
additionalInformation: event.additionalInformation,
location: event.location,
uid: event.uid,
}),
};
return axios({
method: "put",
+23 -2
View File
@@ -5,9 +5,30 @@ export function ManageLink(props: { calEvent: CalendarEvent; attendee: Person })
// Only the original attendee can make changes to the event
// Guests cannot
const t = props.attendee.language.translate;
const cancelLink = getCancelLink(props.calEvent, props.attendee);
const cancelLink = getCancelLink(
{
platformClientId: props.calEvent.platformClientId,
platformCancelUrl: props.calEvent.platformCancelUrl,
type: props.calEvent.type,
organizer: props.calEvent.organizer,
recurringEvent: props.calEvent.recurringEvent,
bookerUrl: props.calEvent.bookerUrl,
uid: props.calEvent.uid,
attendeeSeatId: props.calEvent.attendeeSeatId,
team: props.calEvent.team,
},
props.attendee
);
const rescheduleLink = getRescheduleLink({ calEvent: props.calEvent, attendee: props.attendee });
const bookingLink = getBookingUrl(props.calEvent);
const bookingLink = getBookingUrl({
platformClientId: props.calEvent.platformClientId,
platformBookingUrl: props.calEvent.platformBookingUrl,
bookerUrl: props.calEvent.bookerUrl,
type: props.calEvent.type,
uid: props.calEvent.uid,
organizer: props.calEvent.organizer,
attendeeSeatId: props.calEvent.attendeeSeatId,
});
const isOriginalAttendee = props.attendee.email === props.calEvent.attendees[0]?.email;
const isOrganizer = props.calEvent.organizer.email === props.attendee.email;
@@ -1249,7 +1249,7 @@ export default class EventManager {
? await featureRepo.checkIfTeamHasFeature(event.team.id, "salesforce-crm-tasker")
: false;
const uid = getUid(event);
const uid = getUid(event.uid);
for (const credential of this.crmCredentials) {
if (isTaskerEnabledForSalesforceCrm) {
if (!event.uid) {
@@ -295,7 +295,7 @@ export const createEvent = async (
): Promise<EventResult<NewCalendarEventType>> => {
// Some calendar libraries may edit the original event so let's clone it
const formattedEvent = formatCalEvent(originalEvent);
const uid: string = getUid(formattedEvent);
const uid: string = getUid(formattedEvent.uid);
const calendar = await getCalendar(credential, "booking");
let success = true;
let calError: string | undefined = undefined;
@@ -394,7 +394,7 @@ export const updateEvent = async (
}
const calEvent = processEvent(formattedEvent);
const uid = getUid(calEvent);
const uid = getUid(calEvent.uid);
const calendar = await getCalendar(credential, "booking");
let success = false;
let calError: string | undefined = undefined;
@@ -30,7 +30,7 @@ const createMeeting = async (
credential: CredentialPayload | CredentialForCalendarService,
calEvent: CalendarEvent
) => {
const uid: string = getUid(calEvent);
const uid: string = getUid(calEvent.uid);
log.debug(
"createMeeting",
safeStringify({
@@ -438,7 +438,7 @@ export class EmailWorkflowService {
sendToEmail: sendTo[0],
});
const meetingUrl =
getVideoCallUrlFromCalEvent(evt) || bookingMetadataSchema.parse(evt.metadata || {})?.videoCallUrl;
getVideoCallUrlFromCalEvent({ videoCallData: evt.videoCallData }) || bookingMetadataSchema.parse(evt.metadata || {})?.videoCallUrl;
const variables: VariablesType = {
eventName: evt.title || "",
organizerName: evt.organizer.name,
+279 -107
View File
@@ -3,7 +3,17 @@ import short from "short-uuid";
import { v5 as uuidv5 } from "uuid";
import getLabelValueMapFromResponses from "@calcom/lib/bookings/getLabelValueMapFromResponses";
import type { CalendarEvent, Person } from "@calcom/types/Calendar";
import { Prisma } from "@calcom/prisma/client";
import type {
AdditionalInformation,
AppsStatus,
CalendarEvent,
CalEventResponses,
Person,
RecurringEvent,
TeamMember,
VideoCallData,
} from "@calcom/types/Calendar";
import { WEBAPP_URL } from "./constants";
import isSmsCalEmail from "./isSmsCalEmail";
@@ -12,12 +22,16 @@ 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: Pick<CalendarEvent, "title">, t: TFunction) => {
return `${t("what")}:\n${calEvent.title}`;
export const getWhat = (title: string, t: TFunction) => {
return `${t("what")}:\n${title}`;
};
export const getWhen = (
calEvent: Pick<CalendarEvent, "organizer" | "attendees" | "seatsPerTimeSlot">,
calEvent: {
organizer: Person;
attendees?: Person[];
seatsPerTimeSlot?: number | null;
},
t: TFunction
) => {
const organizerTimezone = calEvent.organizer?.timeZone ?? "UTC";
@@ -30,10 +44,17 @@ export const getWhen = (
};
export const getWho = (
calEvent: Pick<
CalendarEvent,
"attendees" | "seatsPerTimeSlot" | "seatsShowAttendees" | "organizer" | "team" | "hideOrganizerEmail"
>,
calEvent: {
attendees: Person[];
seatsPerTimeSlot?: number | null;
seatsShowAttendees?: boolean | null;
organizer: Person;
team?: {
id: number;
members?: TeamMember[];
};
hideOrganizerEmail?: boolean;
},
t: TFunction
) => {
let attendeesFromCalEvent = [...calEvent.attendees];
@@ -68,15 +89,20 @@ export const getWho = (
}`;
};
export const getAdditionalNotes = (calEvent: Pick<CalendarEvent, "additionalNotes">, t: TFunction) => {
if (!calEvent.additionalNotes) {
export const getAdditionalNotes = (t: TFunction, additionalNotes?: string | null) => {
if (!additionalNotes) {
return "";
}
return `${t("additional_notes")}:\n${calEvent.additionalNotes}`;
return `${t("additional_notes")}:\n${additionalNotes}`;
};
export const getUserFieldsResponses = (
calEvent: Parameters<typeof getLabelValueMapFromResponses>[0],
calEvent: {
customInputs?: Prisma.JsonObject | null;
userFieldsResponses?: CalEventResponses | null;
responses?: CalEventResponses | null;
eventTypeId?: number | null;
},
t: TFunction
) => {
const labelValueMap = getLabelValueMapFromResponses(calEvent);
@@ -99,12 +125,12 @@ ${labelValueMap[key]}
return responsesString;
};
export const getAppsStatus = (calEvent: Pick<CalendarEvent, "appsStatus">, t: TFunction) => {
if (!calEvent.appsStatus) {
export const getAppsStatus = (t: TFunction, appsStatus?: AppsStatus[] | null) => {
if (!appsStatus) {
return "";
}
return `\n${t("apps_status")}
${calEvent.appsStatus.map((app) => {
${appsStatus.map((app) => {
return `\n- ${app.appName} ${
app.success >= 1 ? `✅ ${app.success > 1 ? `(x${app.success})` : ""}` : ""
}${
@@ -145,79 +171,103 @@ const htmlToPlainText = (html: string): string => {
);
};
export const getDescription = (calEvent: Pick<CalendarEvent, "description">, t: TFunction) => {
if (!calEvent.description) {
export const getDescription = (t: TFunction, description?: string | null) => {
if (!description) {
return "";
}
const plainText = htmlToPlainText(calEvent.description);
const plainText = htmlToPlainText(description);
return `${t("description")}\n${plainText}`;
};
export const getLocation = (
calEvent: Parameters<typeof getVideoCallUrlFromCalEvent>[0] & Parameters<typeof getProviderName>[0]
) => {
export const getLocation = (calEvent: {
videoCallData?: { type?: string; url?: string };
additionalInformation?: AdditionalInformation;
location?: string | null;
uid?: string | null;
}) => {
const meetingUrl = getVideoCallUrlFromCalEvent(calEvent);
if (meetingUrl) {
return meetingUrl;
}
const providerName = getProviderName(calEvent);
const providerName = getProviderName(calEvent.location);
return providerName || calEvent.location || "";
};
export const getProviderName = (calEvent: Pick<CalendarEvent, "location">): string => {
export const getProviderName = (location?: string | null): string => {
// TODO: use getAppName from @calcom/app-store
if (calEvent.location && calEvent.location.includes("integrations:")) {
let location = calEvent.location.split(":")[1];
if (location === "daily") {
location = "Cal Video";
if (location && location.includes("integrations:")) {
let locationName = location.split(":")[1];
if (locationName === "daily") {
locationName = "Cal Video";
}
return location[0].toUpperCase() + location.slice(1);
return locationName[0].toUpperCase() + locationName.slice(1);
}
// If location its a url, probably we should be validating it with a custom library
if (calEvent.location && /^https?:\/\//.test(calEvent.location)) {
return calEvent.location;
if (location && /^https?:\/\//.test(location)) {
return location;
}
return "";
};
export const getUid = (calEvent: Pick<CalendarEvent, "uid">): string => {
const uid = calEvent.uid;
return uid ?? translator.fromUUID(uuidv5(JSON.stringify(calEvent), uuidv5.URL));
export const getUid = (uid?: string | null): string => {
// If uid is provided, return it. Otherwise generate a random short UUID as fallback.
// Note: The original implementation used JSON.stringify(calEvent) for deterministic fallback,
// but with narrow inputs we can't do that. In practice, uid should always be set.
return uid ?? translator.new();
};
const getSeatReferenceId = (calEvent: Pick<CalendarEvent, "attendeeSeatId">): string => {
return calEvent.attendeeSeatId ? calEvent.attendeeSeatId : "";
const getSeatReferenceId = (attendeeSeatId?: string): string => {
return attendeeSeatId ? attendeeSeatId : "";
};
export const getBookingUrl = (calEvent: CalendarEvent) => {
const seatReferenceUid = getSeatReferenceId(calEvent);
export const getBookingUrl = (calEvent: {
platformClientId?: string | null;
platformBookingUrl?: string | null;
bookerUrl?: string;
type: string;
uid?: string | null;
organizer: Person;
attendeeSeatId?: string;
}) => {
const seatReferenceUid = getSeatReferenceId(calEvent.attendeeSeatId);
if (calEvent.platformClientId) {
if (!calEvent.platformBookingUrl) return "";
return `${calEvent.platformBookingUrl}/${getUid(calEvent)}?slug=${calEvent.type}&username=${
return `${calEvent.platformBookingUrl}/${getUid(calEvent.uid)}?slug=${calEvent.type}&username=${
calEvent.organizer.username
}${seatReferenceUid ? `&seatReferenceUid=${seatReferenceUid}` : ""}&changes=true`;
}
return `${calEvent.bookerUrl ?? WEBAPP_URL}/booking/${getUid(calEvent)}?changes=true`;
return `${calEvent.bookerUrl ?? WEBAPP_URL}/booking/${getUid(calEvent.uid)}?changes=true`;
};
export const getPlatformManageLink = (
calEvent: Parameters<typeof getCancelLink>[0] &
Parameters<typeof getRescheduleLink>[0]["calEvent"] &
Pick<CalendarEvent, "platformBookingUrl" | "platformRescheduleUrl" | "team">,
calEvent: {
platformBookingUrl?: string | null;
platformRescheduleUrl?: string | null;
platformCancelUrl?: string | null;
type: string;
organizer: Person;
recurringEvent?: RecurringEvent | null;
bookerUrl?: string | null;
uid?: string | null;
attendeeSeatId?: string;
team?: {
id: number;
};
},
t: TFunction
) => {
const shouldDisplayReschedule = !calEvent.recurringEvent && calEvent.platformRescheduleUrl;
const seatUid = getSeatReferenceId(calEvent);
const seatUid = getSeatReferenceId(calEvent.attendeeSeatId);
let res =
calEvent.platformBookingUrl || shouldDisplayReschedule || calEvent.platformCancelUrl
? `${t("need_to_reschedule_or_cancel")} `
: ``;
if (calEvent.platformBookingUrl) {
res += `Check Here: ${calEvent.platformBookingUrl}/${getUid(calEvent)}?slug=${calEvent.type}&username=${
calEvent.organizer.username
}${calEvent?.team ? `&teamId=${calEvent.team.id}` : ""}${
res += `Check Here: ${calEvent.platformBookingUrl}/${getUid(calEvent.uid)}?slug=${
calEvent.type
}&username=${calEvent.organizer.username}${calEvent?.team ? `&teamId=${calEvent.team.id}` : ""}${
seatUid ? `&seatReferenceUid=${seatUid}` : ""
}&changes=true${calEvent.platformCancelUrl || shouldDisplayReschedule ? ` ${t("or_lowercase")} ` : ""}`;
}
@@ -235,8 +285,21 @@ export const getPlatformManageLink = (
};
export const getManageLink = (
calEvent: Parameters<typeof getPlatformManageLink>[0] &
Pick<CalendarEvent, "platformClientId" | "bookerUrl">,
calEvent: {
platformClientId?: string | null;
platformBookingUrl?: string | null;
platformCancelUrl?: string | null;
platformRescheduleUrl?: string | null;
type: string;
organizer: Person;
recurringEvent?: RecurringEvent | null;
bookerUrl?: string | null;
uid?: string | null;
attendeeSeatId?: string;
team?: {
id: number;
};
},
t: TFunction
) => {
if (calEvent.platformClientId) {
@@ -244,12 +307,23 @@ export const getManageLink = (
}
return `${t("need_to_reschedule_or_cancel")} ${calEvent.bookerUrl ?? WEBAPP_URL}/booking/${getUid(
calEvent
calEvent.uid
)}?changes=true`;
};
export const getPlatformCancelLink = (
calEvent: Pick<CalendarEvent, "platformCancelUrl" | "type" | "organizer" | "recurringEvent" | "team">,
calEvent: {
platformCancelUrl?: string | null;
type: string;
organizer: Person;
recurringEvent?: RecurringEvent | null;
bookerUrl?: string | null;
uid?: string | null;
attendeeSeatId?: string;
team?: {
id: number;
};
},
bookingUid: string,
seatUid?: string
): string => {
@@ -269,14 +343,23 @@ export const getPlatformCancelLink = (
};
export const getCancelLink = (
calEvent: Parameters<typeof getUid>[0] &
Parameters<typeof getSeatReferenceId>[0] &
Parameters<typeof getPlatformCancelLink>[0] &
Pick<CalendarEvent, "platformClientId" | "bookerUrl">,
calEvent: {
platformClientId?: string | null;
platformCancelUrl?: string | null;
type: string;
organizer: Person;
recurringEvent?: RecurringEvent | null;
bookerUrl?: string | null;
uid?: string | null;
attendeeSeatId?: string;
team?: {
id: number;
};
},
attendee?: Person
): string => {
const Uid = getUid(calEvent);
const seatReferenceUid = getSeatReferenceId(calEvent);
const Uid = getUid(calEvent.uid);
const seatReferenceUid = getSeatReferenceId(calEvent.attendeeSeatId);
if (calEvent.platformClientId) {
return getPlatformCancelLink(calEvent, Uid, seatReferenceUid);
}
@@ -292,7 +375,14 @@ export const getCancelLink = (
};
export const getPlatformRescheduleLink = (
calEvent: Pick<CalendarEvent, "platformRescheduleUrl" | "type" | "organizer" | "team">,
calEvent: {
platformRescheduleUrl?: string | null;
type: string;
organizer: Person;
team?: {
id: number;
};
},
bookingUid: string,
seatUid?: string
): string => {
@@ -316,15 +406,23 @@ export const getRescheduleLink = ({
allowRescheduleForCancelledBooking = false,
attendee,
}: {
calEvent: Parameters<typeof getUid>[0] &
Parameters<typeof getSeatReferenceId>[0] &
Parameters<typeof getPlatformRescheduleLink>[0] &
Pick<CalendarEvent, "bookerUrl" | "platformClientId">;
calEvent: {
platformClientId?: string | null;
platformRescheduleUrl?: string | null;
type: string;
organizer: Person;
team?: {
id: number;
};
uid?: string | null;
attendeeSeatId?: string;
bookerUrl?: string | null;
};
allowRescheduleForCancelledBooking?: boolean;
attendee?: Person;
}): string => {
const Uid = getUid(calEvent);
const seatUid = getSeatReferenceId(calEvent);
const Uid = getUid(calEvent.uid);
const seatUid = getSeatReferenceId(calEvent.attendeeSeatId);
if (calEvent.platformClientId) {
return getPlatformRescheduleLink(calEvent, Uid, seatUid);
@@ -341,16 +439,35 @@ 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">;
type RichDescriptionCalEvent = {
organizer: Person;
paymentInfo?: {
link?: string | null;
} | null;
type: string;
cancellationReason?: string | null;
title: string;
attendees: Person[];
seatsPerTimeSlot?: number | null;
seatsShowAttendees?: boolean | null;
team?: {
id: number;
members?: TeamMember[];
};
additionalNotes?: string | null;
customInputs?: Prisma.JsonObject | null;
description?: string | null;
userFieldsResponses?: CalEventResponses | null;
responses?: CalEventResponses | null;
eventTypeId?: number | null;
appsStatus?: AppsStatus[] | null;
manageLink?: string | null;
hideOrganizerEmail?: boolean;
videoCallData?: { type?: string; url?: string };
additionalInformation?: AdditionalInformation;
location?: string | null;
uid?: string | null;
}
export const getRichDescriptionHTML = (
calEvent: RichDescriptionCalEvent,
@@ -389,14 +506,45 @@ export const getRichDescriptionHTML = (
// Build the HTML content for each section
const parts = [
textToHtml(getCancellationReason(calEvent, t)),
textToHtml(getWhat(calEvent, t)),
textToHtml(getWhen(calEvent, t)),
textToHtml(getWho(calEvent, t)),
textToHtml(getDescription(calEvent, t)),
textToHtml(getAdditionalNotes(calEvent, t)),
textToHtml(getUserFieldsResponses(calEvent, t)),
includeAppStatus ? textToHtml(getAppsStatus(calEvent, t)) : "",
textToHtml(getCancellationReason(t, calEvent.cancellationReason)),
textToHtml(getWhat(calEvent.title, t)),
textToHtml(
getWhen(
{
organizer: calEvent.organizer,
attendees: calEvent.attendees,
seatsPerTimeSlot: calEvent.seatsPerTimeSlot,
},
t
)
),
textToHtml(
getWho(
{
attendees: calEvent.attendees,
seatsPerTimeSlot: calEvent.seatsPerTimeSlot,
seatsShowAttendees: calEvent.seatsShowAttendees,
organizer: calEvent.organizer,
team: calEvent.team,
hideOrganizerEmail: calEvent.hideOrganizerEmail,
},
t
)
),
textToHtml(getDescription(t, calEvent.description)),
textToHtml(getAdditionalNotes(t, calEvent.additionalNotes)),
textToHtml(
getUserFieldsResponses(
{
customInputs: calEvent.customInputs,
userFieldsResponses: calEvent.userFieldsResponses,
responses: calEvent.responses,
eventTypeId: calEvent.eventTypeId,
},
t
)
),
includeAppStatus ? textToHtml(getAppsStatus(t, calEvent.appsStatus)) : "",
manageLinkHtml,
calEvent.paymentInfo
? `<p><strong>${t("pay_now")}:</strong> <a href="${calEvent.paymentInfo.link}">${
@@ -419,15 +567,37 @@ export const getRichDescription = (
// Join all parts with single newlines and remove extra whitespace
const parts = [
getCancellationReason(calEvent, t),
getWhat(calEvent, t),
getWhen(calEvent, t),
getWho(calEvent, t),
`${t("where")}:\n${getLocation(calEvent)}`,
getDescription(calEvent, t),
getAdditionalNotes(calEvent, t),
getCancellationReason(t, calEvent.cancellationReason),
getWhat(calEvent.title, t),
getWhen(
{
organizer: calEvent.organizer,
attendees: calEvent.attendees,
seatsPerTimeSlot: calEvent.seatsPerTimeSlot,
},
t
),
getWho(
{
attendees: calEvent.attendees,
seatsPerTimeSlot: calEvent.seatsPerTimeSlot,
seatsShowAttendees: calEvent.seatsShowAttendees,
organizer: calEvent.organizer,
team: calEvent.team,
hideOrganizerEmail: calEvent.hideOrganizerEmail,
},
t
),
`${t("where")}:\n${getLocation({
videoCallData: calEvent.videoCallData,
additionalInformation: calEvent.additionalInformation,
location: calEvent.location,
uid: calEvent.uid,
})}`,
getDescription(t, calEvent.description),
getAdditionalNotes(t, calEvent.additionalNotes),
getUserFieldsResponses(calEvent, t),
includeAppStatus ? getAppsStatus(calEvent, t) : "",
includeAppStatus ? getAppsStatus(t, calEvent.appsStatus) : "",
// TODO: Only the original attendee can make changes to the event
// Guests cannot
calEvent.seatsPerTimeSlot ? "" : getManageLink(calEvent, t),
@@ -441,31 +611,33 @@ export const getRichDescription = (
return parts;
};
export const getCancellationReason = (calEvent: Pick<CalendarEvent, "cancellationReason">, t: TFunction) => {
if (!calEvent.cancellationReason) return "";
const sanitized = calEvent.cancellationReason.startsWith("$RCH$")
? calEvent.cancellationReason.substring(5).trim()
: calEvent.cancellationReason.trim();
export const getCancellationReason = (t: TFunction, cancellationReason?: string | null) => {
if (!cancellationReason) return "";
const sanitized = cancellationReason.startsWith("$RCH$")
? cancellationReason.substring(5).trim()
: cancellationReason.trim();
return `${t("cancellation_reason")}:\n${sanitized}`;
};
export const isDailyVideoCall = (calEvent: Pick<CalendarEvent, "videoCallData">): boolean => {
return calEvent?.videoCallData?.type === "daily_video";
export const isDailyVideoCall = (videoCallData?: VideoCallData): boolean => {
return videoCallData?.type === "daily_video";
};
export const getPublicVideoCallUrl = (calEvent: Pick<CalendarEvent, "uid">): string => {
return `${WEBAPP_URL}/video/${getUid(calEvent)}`;
export const getPublicVideoCallUrl = (uid?: string | null): string => {
return `${WEBAPP_URL}/video/${getUid(uid)}`;
};
export const getVideoCallUrlFromCalEvent = (
calEvent: Parameters<typeof getPublicVideoCallUrl>[0] &
Pick<CalendarEvent, "videoCallData" | "additionalInformation" | "location">
): string => {
export const getVideoCallUrlFromCalEvent = (calEvent: {
videoCallData?: { type?: string; url?: string };
additionalInformation?: AdditionalInformation;
location?: string | null;
uid?: string | null;
}): string => {
if (calEvent.videoCallData) {
if (isDailyVideoCall(calEvent)) {
return getPublicVideoCallUrl(calEvent);
if (calEvent.videoCallData.type === "daily_video") {
return getPublicVideoCallUrl(calEvent.uid);
}
return calEvent.videoCallData.url;
return calEvent.videoCallData.url ?? "";
}
if (calEvent.additionalInformation?.hangoutLink) {
return calEvent.additionalInformation.hangoutLink;
@@ -476,6 +648,6 @@ export const getVideoCallUrlFromCalEvent = (
return "";
};
export const getVideoCallPassword = (calEvent: CalendarEvent): string => {
return isDailyVideoCall(calEvent) ? "" : calEvent?.videoCallData?.password ?? "";
export const getVideoCallPassword = (videoCallData?: VideoCallData): string => {
return isDailyVideoCall(videoCallData) ? "" : videoCallData?.password ?? "";
};
@@ -2,10 +2,16 @@ import type z from "zod";
import { TITLE_FIELD, SMS_REMINDER_NUMBER_FIELD } from "@calcom/lib/bookings/SystemField";
import type { dbReadResponseSchema as bookingResponse } from "@calcom/lib/dbReadResponseSchema";
import type { CalendarEvent } from "@calcom/types/Calendar";
import type { CalEventResponses } from "@calcom/types/Calendar";
import type { Prisma } from "@calcom/prisma/client";
export default function getLabelValueMapFromResponses(
calEvent: Pick<CalendarEvent, "customInputs" | "userFieldsResponses" | "responses" | "eventTypeId">,
calEvent: {
customInputs?: Prisma.JsonObject | null;
userFieldsResponses?: CalEventResponses | null;
responses?: CalEventResponses | null;
eventTypeId?: number | null;
},
isOrganizer = false
) {
const { customInputs, userFieldsResponses, responses, eventTypeId } = calEvent;
+3 -3
View File
@@ -56,7 +56,7 @@ describe("getVideoCallUrl", () => {
}),
});
expect(getVideoCallUrlFromCalEvent(calEvent)).toEqual(getPublicVideoCallUrl(calEvent));
expect(getVideoCallUrlFromCalEvent(calEvent)).toEqual(getPublicVideoCallUrl(calEvent.uid));
});
});
@@ -68,12 +68,12 @@ describe("getVideoCallPassword", () => {
}),
});
expect(getVideoCallPassword(calEvent)).toEqual("");
expect(getVideoCallPassword(calEvent.videoCallData)).toEqual("");
});
it("should return original password for other video call meetings", () => {
const calEvent = buildCalendarEvent();
expect(calEvent?.videoCallData?.type).not.toBe("daily_video");
expect(getVideoCallPassword(calEvent)).toEqual(calEvent?.videoCallData.password);
expect(getVideoCallPassword(calEvent.videoCallData)).toEqual(calEvent?.videoCallData?.password);
});
});