feat: Make event duration a dynamic text variable (#15262)

* feat: Make event duration a dynamic text variable

* Update common.json

* final update

* update

* fix type error

* fix unit test

* refactor and update

* For now log the situation when duration isnt a number

---------

Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
This commit is contained in:
Anik Dhabal BabuandHariom Balhara authored and GitHub committed 2024-08-05 09:07:46 +00:00
1 parent 099d830e7e
commit 461d5e0535
10 files changed
+59 -1

No files matched your search

@@ -70,6 +70,10 @@ const CustomEventTypeModalForm: FC<CustomEventTypeModalFormProps> = (props) => {
<p className="text-subtle ml-1 mr-5 w-32">{`{Event type title}`}</p>
<p className="text-emphasis">{t("event_name_info")}</p>
</div>
<div className="mb-2.5 flex font-normal">
<p className="text-subtle ml-1 mr-5 w-32">{`{Event duration}`}</p>
<p className="text-emphasis">{t("event_duration_info")}</p>
</div>
<div className="mb-2.5 flex font-normal">
<p className="text-subtle ml-1 mr-5 w-32">{`{Organiser}`}</p>
<p className="text-emphasis">{t("your_full_name")}</p>
@@ -74,6 +74,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
eventName: formMethods.getValues("eventName"),
host: formMethods.getValues("users")[0]?.name || "Nameless",
bookingFields: bookingFields,
eventDuration: formMethods.getValues("length"),
t,
};
@@ -233,6 +233,7 @@ export default function Success(props: PageProps) {
host: props.profile.name || "Nameless",
location: location,
bookingFields: bookingInfo.responses,
eventDuration: eventType.length,
t,
};
@@ -1444,6 +1444,7 @@
"add_dynamic_variables": "Add dynamic text variables",
"event_name_info": "The event type name",
"event_date_info": "The event date",
"event_duration_info": "The event duration",
"event_time_info": "The event start time",
"event_type_not_found": "EventType not Found",
"location_variable": "Location",
+34
View File
@@ -12,6 +12,7 @@ describe("event tests", () => {
attendeeName: "example attendee",
eventType: "example event type",
host: "example host",
eventDuration: 15,
t: tFunc as TFunction,
});
@@ -39,6 +40,7 @@ describe("event tests", () => {
eventType: "example event type",
host: "example host",
teamName: "example team name",
eventDuration: 15,
t: tFunc as TFunction,
});
@@ -66,6 +68,7 @@ describe("event tests", () => {
eventType: "example event type",
host: "example host",
eventName: "example event name",
eventDuration: 15,
t: tFunc as TFunction,
});
@@ -81,6 +84,7 @@ describe("event tests", () => {
eventType: "example event type",
host: "example host",
eventName: "event type: {Event type title}",
eventDuration: 15,
t: tFunc as TFunction,
});
@@ -96,6 +100,7 @@ describe("event tests", () => {
eventType: "example event type",
host: "example host",
eventName: "scheduler: {Scheduler}",
eventDuration: 15,
t: tFunc as TFunction,
});
@@ -111,6 +116,7 @@ describe("event tests", () => {
eventType: "example event type",
host: "example host",
eventName: "organiser: {Organiser}",
eventDuration: 15,
t: tFunc as TFunction,
});
@@ -126,6 +132,7 @@ describe("event tests", () => {
eventType: "example event type",
host: "example host",
eventName: "user: {USER}",
eventDuration: 15,
t: tFunc as TFunction,
});
@@ -141,6 +148,7 @@ describe("event tests", () => {
eventType: "example event type",
host: "example host",
eventName: "attendee: {ATTENDEE}",
eventDuration: 15,
t: tFunc as TFunction,
});
@@ -156,6 +164,7 @@ describe("event tests", () => {
eventType: "example event type",
host: "example host",
eventName: "host: {HOST}",
eventDuration: 15,
t: tFunc as TFunction,
});
@@ -171,6 +180,7 @@ describe("event tests", () => {
eventType: "example event type",
host: "example host",
eventName: "host or attendee: {HOST/ATTENDEE}",
eventDuration: 15,
t: tFunc as TFunction,
});
@@ -187,6 +197,7 @@ describe("event tests", () => {
eventType: "example event type",
host: "example host",
eventName: "host or attendee: {HOST/ATTENDEE}",
eventDuration: 15,
t: tFunc as TFunction,
},
true
@@ -222,6 +233,7 @@ describe("event tests", () => {
eventType: "example event type",
host: "example host",
eventName: "custom field: {customField}",
eventDuration: 15,
bookingFields: {
customField: {
value: "example custom field",
@@ -242,6 +254,7 @@ describe("event tests", () => {
eventType: "example event type",
host: "example host",
eventName: "custom field: {customField}",
eventDuration: 15,
bookingFields: {
customField: {
value: 808,
@@ -262,6 +275,7 @@ describe("event tests", () => {
eventType: "example event type",
host: "example host",
eventName: "custom field: {customField}",
eventDuration: 15,
bookingFields: {
customField: {
value: undefined,
@@ -283,6 +297,7 @@ describe("event tests", () => {
host: "example host",
location: "attendeeInPerson",
eventName: "location: {Location}",
eventDuration: 15,
t: tFunc as TFunction,
});
@@ -299,6 +314,7 @@ describe("event tests", () => {
host: "example host",
location: "attendeeInPerson",
eventName: "location: {LOCATION}",
eventDuration: 15,
t: tFunc as TFunction,
});
@@ -314,6 +330,7 @@ describe("event tests", () => {
eventType: "example event type",
host: "example host",
eventName: "location: {Location}",
eventDuration: 15,
t: tFunc as TFunction,
});
@@ -330,6 +347,7 @@ describe("event tests", () => {
host: "example host",
location: "",
eventName: "location: {Location}",
eventDuration: 15,
t: tFunc as TFunction,
});
@@ -346,6 +364,7 @@ describe("event tests", () => {
host: "example host",
location: "unknownNonsense",
eventName: "location: {Location}",
eventDuration: 15,
t: tFunc as TFunction,
});
@@ -354,6 +373,20 @@ describe("event tests", () => {
});
});
it("should support templating of event duration", () => {
const tFunc = vi.fn(() => "foo");
const result = event.getEventName({
attendeeName: "example attendee",
eventType: "example event type",
host: "example host",
eventName: "event duration: {Event duration}",
eventDuration: 15,
t: tFunc as TFunction,
});
expect(result).toBe("event duration: 15 mins");
});
describe("fn: validateCustomEventName", () => {
it("should be valid when no variables used", () => {
expect(event.validateCustomEventName("foo")).toBe(true);
@@ -369,6 +402,7 @@ describe("event tests", () => {
"HOST",
"ATTENDEE",
"USER",
"Event duration",
].forEach((value) => {
it(`should support {${value}} variable`, () => {
expect(event.validateCustomEventName(`foo {${value}} bar`)).toBe(true);
+4 -1
View File
@@ -23,6 +23,7 @@ export type EventNameObjectType = {
teamName?: string | null;
host: string;
location?: string;
eventDuration: number;
bookingFields?: Prisma.JsonObject;
t: TFunction;
};
@@ -61,7 +62,8 @@ export function getEventName(eventNameObj: EventNameObjectType, forAttendeeView
.replaceAll("{USER}", attendeeName)
.replaceAll("{ATTENDEE}", attendeeName)
.replaceAll("{HOST}", eventNameObj.host)
.replaceAll("{HOST/ATTENDEE}", forAttendeeView ? eventNameObj.host : attendeeName);
.replaceAll("{HOST/ATTENDEE}", forAttendeeView ? eventNameObj.host : attendeeName)
.replaceAll("{Event duration}", `${String(eventNameObj.eventDuration)} mins`);
const { bookingFields } = eventNameObj || {};
const { name } = bookingFields || {};
@@ -116,6 +118,7 @@ export const validateCustomEventName = (value: string, bookingFields?: Prisma.Js
"{Organiser first name}",
"{Scheduler first name}",
"{Scheduler last name}",
"{Event duration}",
//allowed for fallback reasons
"{LOCATION}",
"{HOST/ATTENDEE}",
+11
View File
@@ -6,6 +6,8 @@ import type { EventNameObjectType } from "@calcom/core/event";
import { getEventName } from "@calcom/core/event";
import type BaseEmail from "@calcom/emails/templates/_base-email";
import { formatCalEvent } from "@calcom/lib/formatCalendarEvent";
import logger from "@calcom/lib/logger";
import { safeStringify } from "@calcom/lib/safeStringify";
import type { CalendarEvent, Person } from "@calcom/types/Calendar";
import type { MonthlyDigestEmailData } from "./src/templates/MonthlyDigestEmail";
@@ -280,6 +282,14 @@ export const sendCancelledEmails = async (
const emailsToSend: Promise<unknown>[] = [];
emailsToSend.push(sendEmail(() => new OrganizerCancelledEmail({ calEvent: calendarEvent })));
const calEventLength = calendarEvent.length;
if (typeof calEventLength !== "number") {
logger.error(
"`calEventLength` is not a number",
safeStringify({ calEventLength, calEventTitle: calEvent.title, bookingId: calEvent.bookingId })
);
}
const eventDuration = calEventLength as number;
if (calendarEvent.team?.members) {
for (const teamMember of calendarEvent.team.members) {
@@ -302,6 +312,7 @@ export const sendCancelledEmails = async (
attendeeName: attendee.name,
host: calendarEvent.organizer.name,
eventType: calendarEvent.type,
eventDuration,
...(calendarEvent.responses && { bookingFields: calendarEvent.responses }),
...(calendarEvent.location && { location: calendarEvent.location }),
}),
@@ -270,6 +270,7 @@ async function handler(req: CustomRequest) {
const evt: CalendarEvent = {
title: bookingToDelete?.title,
length: bookingToDelete?.eventType?.length,
type: bookingToDelete?.eventType?.slug as string,
description: bookingToDelete?.description || "",
customInputs: isPrismaObjOrUndefined(bookingToDelete.customInputs),
@@ -802,6 +802,7 @@ async function handler(
// TODO: Can we have an unnamed organizer? If not, I would really like to throw an error here.
host: organizerUser.name || "Nameless",
location: bookingLocation,
eventDuration: eventType.length,
bookingFields: { ...responses },
t: tOrganizer,
};
+1
View File
@@ -164,6 +164,7 @@ export interface CalendarEvent {
endTime: string;
organizer: Person;
attendees: Person[];
length?: number | null;
additionalNotes?: string | null;
customInputs?: Prisma.JsonObject | null;
description?: string | null;