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:
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",
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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}",
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
Vendored
+1
@@ -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;
|
||||
|
||||
Reference in new issue
Block a user