feat: Add hideCalendarEventDetails option to event types (#16817)
* Add `hideCalendarEventDetails` to DB Co-authored-by: Alex van Andel <emrysal@users.noreply.github.com> * Add option to hide calendar event details * Pass `hideCalendarEventDetails` to event object * Add to API * Add to CalendarEvent class * Pass `hideCalendarEventDetails` to calendar services * Adjust test * fix: move `hideCalendarEventDetails` to `api/v2/event-types_2024_06_14` and remove from older versions - Added `hideCalendarEventDetails` to `api/v2/event-types_2024_06_14` - Removed `hideCalendarEventDetails` from `api/v1` and `api/v2/event-types_2024_04_15` * fix: calEventRaw is undefined (use event instead) * chore: Remove debug artifact * fix: description id + update to copy * fix: Attempt at fixing type error * Add hideCalendarEventDetails to test builder --------- Co-authored-by: Alex van Andel <emrysal@users.noreply.github.com> Co-authored-by: Somay Chauhan <somaychauhan98@gmail.com> Co-authored-by: Alex van Andel <me@alexvanandel.com>
This commit is contained in:
co-authored by
Alex van Andel
Somay Chauhan
Alex van Andel
parent
b6c6e41603
commit
ae7fae7da4
@@ -28,7 +28,7 @@
|
||||
"dependencies": {
|
||||
"@calcom/platform-constants": "*",
|
||||
"@calcom/platform-enums": "*",
|
||||
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.41",
|
||||
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.43",
|
||||
"@calcom/platform-libraries-0.0.2": "npm:@calcom/platform-libraries@0.0.2",
|
||||
"@calcom/platform-types": "*",
|
||||
"@calcom/platform-utils": "*",
|
||||
|
||||
+8
@@ -260,6 +260,7 @@ describe("Event types Endpoints", () => {
|
||||
},
|
||||
requiresBookerEmailVerification: false,
|
||||
hideCalendarNotes: false,
|
||||
hideCalendarEventDetails: false,
|
||||
lockTimeZoneToggleOnBookingPage: true,
|
||||
color: {
|
||||
darkThemeHex: "#292929",
|
||||
@@ -297,6 +298,7 @@ describe("Event types Endpoints", () => {
|
||||
);
|
||||
|
||||
expect(createdEventType.hideCalendarNotes).toEqual(body.hideCalendarNotes);
|
||||
expect(createdEventType.hideCalendarEventDetails).toEqual(body.hideCalendarEventDetails);
|
||||
expect(createdEventType.lockTimeZoneToggleOnBookingPage).toEqual(
|
||||
body.lockTimeZoneToggleOnBookingPage
|
||||
);
|
||||
@@ -351,6 +353,7 @@ describe("Event types Endpoints", () => {
|
||||
eventType.requiresBookerEmailVerification
|
||||
);
|
||||
expect(fetchedEventType.hideCalendarNotes).toEqual(eventType.hideCalendarNotes);
|
||||
expect(fetchedEventType.hideCalendarEventDetails).toEqual(eventType.hideCalendarEventDetails);
|
||||
expect(fetchedEventType.lockTimeZoneToggleOnBookingPage).toEqual(
|
||||
eventType.lockTimeZoneToggleOnBookingPage
|
||||
);
|
||||
@@ -667,6 +670,7 @@ describe("Event types Endpoints", () => {
|
||||
},
|
||||
requiresBookerEmailVerification: true,
|
||||
hideCalendarNotes: true,
|
||||
hideCalendarEventDetails: true,
|
||||
lockTimeZoneToggleOnBookingPage: true,
|
||||
color: {
|
||||
darkThemeHex: "#292929",
|
||||
@@ -706,6 +710,7 @@ describe("Event types Endpoints", () => {
|
||||
body.requiresBookerEmailVerification
|
||||
);
|
||||
expect(updatedEventType.hideCalendarNotes).toEqual(body.hideCalendarNotes);
|
||||
expect(updatedEventType.hideCalendarEventDetails).toEqual(body.hideCalendarEventDetails);
|
||||
expect(updatedEventType.lockTimeZoneToggleOnBookingPage).toEqual(
|
||||
body.lockTimeZoneToggleOnBookingPage
|
||||
);
|
||||
@@ -724,6 +729,7 @@ describe("Event types Endpoints", () => {
|
||||
eventType.customName = updatedEventType.customName;
|
||||
eventType.requiresBookerEmailVerification = updatedEventType.requiresBookerEmailVerification;
|
||||
eventType.hideCalendarNotes = updatedEventType.hideCalendarNotes;
|
||||
eventType.hideCalendarEventDetails = updatedEventType.hideCalendarEventDetails;
|
||||
eventType.lockTimeZoneToggleOnBookingPage = updatedEventType.lockTimeZoneToggleOnBookingPage;
|
||||
eventType.color = updatedEventType.color;
|
||||
});
|
||||
@@ -774,6 +780,7 @@ describe("Event types Endpoints", () => {
|
||||
eventType.requiresBookerEmailVerification
|
||||
);
|
||||
expect(fetchedEventType.hideCalendarNotes).toEqual(eventType.hideCalendarNotes);
|
||||
expect(fetchedEventType.hideCalendarEventDetails).toEqual(eventType.hideCalendarEventDetails);
|
||||
expect(fetchedEventType.lockTimeZoneToggleOnBookingPage).toEqual(
|
||||
eventType.lockTimeZoneToggleOnBookingPage
|
||||
);
|
||||
@@ -811,6 +818,7 @@ describe("Event types Endpoints", () => {
|
||||
eventType.requiresBookerEmailVerification
|
||||
);
|
||||
expect(fetchedEventType.hideCalendarNotes).toEqual(eventType.hideCalendarNotes);
|
||||
expect(fetchedEventType.hideCalendarEventDetails).toEqual(eventType.hideCalendarEventDetails);
|
||||
expect(fetchedEventType.lockTimeZoneToggleOnBookingPage).toEqual(
|
||||
eventType.lockTimeZoneToggleOnBookingPage
|
||||
);
|
||||
|
||||
+3
@@ -80,6 +80,7 @@ type Input = Pick<
|
||||
| "eventName"
|
||||
| "destinationCalendar"
|
||||
| "useEventTypeDestinationCalendarEmail"
|
||||
| "hideCalendarEventDetails"
|
||||
>;
|
||||
|
||||
@Injectable()
|
||||
@@ -111,6 +112,7 @@ export class OutputEventTypesService_2024_06_14 {
|
||||
hideCalendarNotes,
|
||||
seatsShowAttendees,
|
||||
useEventTypeDestinationCalendarEmail,
|
||||
hideCalendarEventDetails,
|
||||
} = databaseEventType;
|
||||
|
||||
const locations = this.transformLocations(databaseEventType.locations);
|
||||
@@ -182,6 +184,7 @@ export class OutputEventTypesService_2024_06_14 {
|
||||
customName,
|
||||
destinationCalendar,
|
||||
useDestinationCalendarEmail: useEventTypeDestinationCalendarEmail,
|
||||
hideCalendarEventDetails,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
+2
@@ -300,6 +300,7 @@ describe("Organizations Event Types Endpoints", () => {
|
||||
},
|
||||
requiresBookerEmailVerification: true,
|
||||
hideCalendarNotes: true,
|
||||
hideCalendarEventDetails: true,
|
||||
lockTimeZoneToggleOnBookingPage: true,
|
||||
color: {
|
||||
darkThemeHex: "#292929",
|
||||
@@ -330,6 +331,7 @@ describe("Organizations Event Types Endpoints", () => {
|
||||
expect(data.confirmationPolicy).toEqual(body.confirmationPolicy);
|
||||
expect(data.requiresBookerEmailVerification).toEqual(body.requiresBookerEmailVerification);
|
||||
expect(data.hideCalendarNotes).toEqual(body.hideCalendarNotes);
|
||||
expect(data.hideCalendarEventDetails).toEqual(body.hideCalendarEventDetails);
|
||||
expect(data.lockTimeZoneToggleOnBookingPage).toEqual(body.lockTimeZoneToggleOnBookingPage);
|
||||
expect(data.color).toEqual(body.color);
|
||||
|
||||
|
||||
@@ -65,6 +65,7 @@ type Input = Pick<
|
||||
| "requiresConfirmationWillBlockSlot"
|
||||
| "eventName"
|
||||
| "useEventTypeDestinationCalendarEmail"
|
||||
| "hideCalendarEventDetails"
|
||||
>;
|
||||
|
||||
@Injectable()
|
||||
|
||||
@@ -7504,6 +7504,9 @@
|
||||
"hideCalendarNotes": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"hideCalendarEventDetails": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"color": {
|
||||
"$ref": "#/components/schemas/EventTypeColor_2024_06_14"
|
||||
},
|
||||
@@ -7565,6 +7568,7 @@
|
||||
"confirmationPolicy",
|
||||
"requiresBookerEmailVerification",
|
||||
"hideCalendarNotes",
|
||||
"hideCalendarEventDetails",
|
||||
"color",
|
||||
"seats",
|
||||
"offsetStart",
|
||||
@@ -9509,6 +9513,9 @@
|
||||
"hideCalendarNotes": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"hideCalendarEventDetails": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"color": {
|
||||
"$ref": "#/components/schemas/EventTypeColor_2024_06_14"
|
||||
},
|
||||
|
||||
@@ -106,6 +106,7 @@ test.describe("BOOKING_CREATED", async () => {
|
||||
location: "[redacted/dynamic]",
|
||||
destinationCalendar: null,
|
||||
hideCalendarNotes: false,
|
||||
hideCalendarEventDetails: false,
|
||||
requiresConfirmation: "[redacted/dynamic]",
|
||||
eventTypeId: "[redacted/dynamic]",
|
||||
seatsShowAttendees: true,
|
||||
|
||||
@@ -2613,5 +2613,7 @@
|
||||
"limit_team_booking_frequency_description": "Limit how many times members can be booked across all team event types (collective and round-robin)",
|
||||
"booking_limits_updated_successfully": "Booking limits updated successfully",
|
||||
"you_are_unauthorized_to_make_this_change_to_the_booking": "You are unauthorized to make this change to the booking",
|
||||
"hide_calendar_event_details": "Hide calendar event details on shared calendars",
|
||||
"description_hide_calendar_event_details": "When a calendar is shared, events are visible to readers but their details are hidden from those without write access.",
|
||||
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Add your new strings above here ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
|
||||
}
|
||||
|
||||
@@ -256,6 +256,9 @@ export default class GoogleCalendarService implements Calendar {
|
||||
: true,
|
||||
iCalUID: formattedCalEvent.iCalUID,
|
||||
};
|
||||
if (calEventRaw.hideCalendarEventDetails) {
|
||||
payload.visibility = "private";
|
||||
}
|
||||
|
||||
if (formattedCalEvent.location) {
|
||||
payload["location"] = getLocation(formattedCalEvent);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Calendar as OfficeCalendar, User } from "@microsoft/microsoft-graph-types-beta";
|
||||
import type { Calendar as OfficeCalendar, User, Event } from "@microsoft/microsoft-graph-types-beta";
|
||||
import type { DefaultBodyType } from "msw";
|
||||
|
||||
import dayjs from "@calcom/dayjs";
|
||||
@@ -266,7 +266,7 @@ export default class Office365CalendarService implements Calendar {
|
||||
}
|
||||
|
||||
private translateEvent = (event: CalendarEvent) => {
|
||||
return {
|
||||
const office365Event: Event = {
|
||||
subject: event.title,
|
||||
body: {
|
||||
contentType: "text",
|
||||
@@ -280,6 +280,7 @@ export default class Office365CalendarService implements Calendar {
|
||||
dateTime: dayjs(event.endTime).tz(event.organizer.timeZone).format("YYYY-MM-DDTHH:mm:ss"),
|
||||
timeZone: event.organizer.timeZone,
|
||||
},
|
||||
hideAttendees: !event.seatsPerTimeSlot ? false : !event.seatsShowAttendees,
|
||||
organizer: {
|
||||
emailAddress: {
|
||||
address: event.destinationCalendar
|
||||
@@ -295,7 +296,7 @@ export default class Office365CalendarService implements Calendar {
|
||||
address: attendee.email,
|
||||
name: attendee.name,
|
||||
},
|
||||
type: "required",
|
||||
type: "required" as const,
|
||||
})),
|
||||
...(event.team?.members
|
||||
? event.team?.members
|
||||
@@ -309,13 +310,17 @@ export default class Office365CalendarService implements Calendar {
|
||||
address: destinationCalendar?.externalId ?? member.email,
|
||||
name: member.name,
|
||||
},
|
||||
type: "required",
|
||||
type: "required" as const,
|
||||
};
|
||||
})
|
||||
: []),
|
||||
],
|
||||
location: event.location ? { displayName: getLocation(event) } : undefined,
|
||||
};
|
||||
if (event.hideCalendarEventDetails) {
|
||||
office365Event.sensitivity = "private";
|
||||
}
|
||||
return office365Event;
|
||||
};
|
||||
|
||||
private fetcher = async (endpoint: string, init?: RequestInit | undefined) => {
|
||||
|
||||
@@ -146,6 +146,7 @@ export class CalendarEventBuilder implements ICalendarEventBuilder {
|
||||
metadata: true,
|
||||
destinationCalendar: true,
|
||||
hideCalendarNotes: true,
|
||||
hideCalendarEventDetails: true,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
@@ -206,6 +207,12 @@ export class CalendarEventBuilder implements ICalendarEventBuilder {
|
||||
this.calendarEvent.hideCalendarNotes = hideCalendarNotes;
|
||||
}
|
||||
|
||||
public setHideCalendarEventDetails(
|
||||
hideCalendarEventDetails: CalendarEventClass["hideCalendarEventDetails"]
|
||||
) {
|
||||
this.calendarEvent.hideCalendarEventDetails = hideCalendarEventDetails;
|
||||
}
|
||||
|
||||
public setDescription(description: CalendarEventClass["description"]) {
|
||||
this.calendarEvent.description = description;
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ class CalendarEventClass implements CalendarEvent {
|
||||
cancellationReason?: string | null;
|
||||
rejectionReason?: string | null;
|
||||
hideCalendarNotes?: boolean;
|
||||
hideCalendarEventDetails?: boolean;
|
||||
additionalNotes?: string | null | undefined;
|
||||
recurrence?: string;
|
||||
iCalUID?: string | null;
|
||||
|
||||
@@ -101,6 +101,7 @@ const generateIcsString = ({
|
||||
location: location ?? undefined,
|
||||
method: "REQUEST",
|
||||
status,
|
||||
...(event.hideCalendarEventDetails ? { classification: "PRIVATE" } : {}),
|
||||
});
|
||||
if (icsEvent.error) {
|
||||
throw icsEvent.error;
|
||||
|
||||
@@ -898,6 +898,7 @@ async function handler(
|
||||
conferenceCredentialId,
|
||||
destinationCalendar,
|
||||
hideCalendarNotes: eventType.hideCalendarNotes,
|
||||
hideCalendarEventDetails: eventType.hideCalendarEventDetails,
|
||||
requiresConfirmation: !isConfirmedByDefault,
|
||||
eventTypeId: eventType.id,
|
||||
// if seats are not enabled we should default true
|
||||
|
||||
@@ -59,6 +59,7 @@ export const getEventTypesFromDB = async (eventTypeId: number) => {
|
||||
metadata: true,
|
||||
destinationCalendar: true,
|
||||
hideCalendarNotes: true,
|
||||
hideCalendarEventDetails: true,
|
||||
seatsPerTimeSlot: true,
|
||||
recurringEvent: true,
|
||||
seatsShowAttendees: true,
|
||||
|
||||
@@ -138,6 +138,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
||||
const seatsLocked = shouldLockDisableProps("seatsPerTimeSlotEnabled");
|
||||
const requiresBookerEmailVerificationProps = shouldLockDisableProps("requiresBookerEmailVerification");
|
||||
const hideCalendarNotesLocked = shouldLockDisableProps("hideCalendarNotes");
|
||||
const hideCalendarEventDetailsLocked = shouldLockDisableProps("hideCalendarEventDetails");
|
||||
const eventTypeColorLocked = shouldLockDisableProps("eventTypeColor");
|
||||
const lockTimeZoneToggleOnBookingPageLocked = shouldLockDisableProps("lockTimeZoneToggleOnBookingPage");
|
||||
|
||||
@@ -323,6 +324,21 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="hideCalendarEventDetails"
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<SettingsToggle
|
||||
labelClassName="text-sm"
|
||||
toggleSwitchAtTheEnd={true}
|
||||
switchContainerClassName="border-subtle rounded-lg border py-6 px-4 sm:px-6"
|
||||
title={t("hide_calendar_event_details")}
|
||||
{...hideCalendarEventDetailsLocked}
|
||||
description={t("description_hide_calendar_event_details")}
|
||||
checked={value}
|
||||
onCheckedChange={(e) => onChange(e)}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="successRedirectUrl"
|
||||
render={({ field: { value, onChange } }) => (
|
||||
|
||||
@@ -162,6 +162,7 @@ export default abstract class BaseCalendarService implements Calendar {
|
||||
* [UPDATE]: Since we're not using the PUBLISH method to publish the iCalendar event and creating the event directly on iCal,
|
||||
* this shouldn't be an issue and we should be able to add attendees to the event right here.
|
||||
*/
|
||||
...(event.hideCalendarEventDetails ? { classification: "PRIVATE" } : {}),
|
||||
});
|
||||
|
||||
if (error || !iCalString)
|
||||
|
||||
@@ -85,6 +85,7 @@ const commons = {
|
||||
onlyShowFirstAvailableSlot: false,
|
||||
id: 0,
|
||||
hideCalendarNotes: false,
|
||||
hideCalendarEventDetails: false,
|
||||
recurringEvent: null,
|
||||
destinationCalendar: null,
|
||||
team: null,
|
||||
|
||||
@@ -55,4 +55,5 @@ export const eventTypeSelect = Prisma.validator<Prisma.EventTypeSelect>()({
|
||||
bookingLimits: true,
|
||||
durationLimits: true,
|
||||
eventTypeColor: true,
|
||||
hideCalendarEventDetails: true,
|
||||
});
|
||||
|
||||
@@ -447,6 +447,7 @@ export class EventTypeRepository {
|
||||
requiresBookerEmailVerification: true,
|
||||
recurringEvent: true,
|
||||
hideCalendarNotes: true,
|
||||
hideCalendarEventDetails: true,
|
||||
disableGuests: true,
|
||||
minimumBookingNotice: true,
|
||||
beforeEventBuffer: true,
|
||||
|
||||
@@ -109,6 +109,7 @@ export const buildEventType = (eventType?: Partial<EventType>): EventType => {
|
||||
requiresConfirmationWillBlockSlot: false,
|
||||
disableGuests: false,
|
||||
hideCalendarNotes: false,
|
||||
hideCalendarEventDetails: false,
|
||||
minimumBookingNotice: 120,
|
||||
beforeEventBuffer: 0,
|
||||
afterEventBuffer: 0,
|
||||
|
||||
@@ -79,6 +79,7 @@ export const useEventTypeForm = ({
|
||||
endDate: periodDates.endDate,
|
||||
},
|
||||
hideCalendarNotes: eventType.hideCalendarNotes,
|
||||
hideCalendarEventDetails: eventType.hideCalendarEventDetails,
|
||||
offsetStart: eventType.offsetStart,
|
||||
bookingFields: eventType.bookingFields,
|
||||
periodType: eventType.periodType,
|
||||
|
||||
+4
@@ -290,6 +290,10 @@ export class CreateEventTypeInput_2024_06_14 {
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
useDestinationCalendarEmail?: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
hideCalendarEventDetails?: boolean;
|
||||
}
|
||||
|
||||
export enum HostPriority {
|
||||
|
||||
+4
@@ -282,6 +282,10 @@ export class UpdateEventTypeInput_2024_06_14 {
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
useDestinationCalendarEmail?: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
hideCalendarEventDetails?: boolean;
|
||||
}
|
||||
|
||||
@ApiExtraModels(
|
||||
|
||||
+10
@@ -333,6 +333,11 @@ class BaseEventTypeOutput_2024_06_14 {
|
||||
@IsBoolean()
|
||||
@DocsProperty()
|
||||
useDestinationCalendarEmail?: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
@DocsProperty()
|
||||
hideCalendarEventDetails?: boolean;
|
||||
}
|
||||
|
||||
export class TeamEventTypeResponseHost extends TeamEventTypeHostInput {
|
||||
@@ -385,4 +390,9 @@ export class TeamEventTypeOutput_2024_06_14 extends BaseEventTypeOutput_2024_06_
|
||||
@IsEnum(SchedulingTypeEnum)
|
||||
@DocsProperty({ enum: SchedulingTypeEnum })
|
||||
schedulingType!: EventTypesOutputSchedulingType | null;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
@DocsProperty()
|
||||
hideCalendarEventDetails?: boolean;
|
||||
}
|
||||
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "EventType" ADD COLUMN "hideCalendarEventDetails" BOOLEAN NOT NULL DEFAULT false;
|
||||
@@ -110,6 +110,7 @@ model EventType {
|
||||
recurringEvent Json?
|
||||
disableGuests Boolean @default(false)
|
||||
hideCalendarNotes Boolean @default(false)
|
||||
hideCalendarEventDetails Boolean @default(false)
|
||||
/// @zod.min(0)
|
||||
minimumBookingNotice Int @default(120)
|
||||
beforeEventBuffer Int @default(0)
|
||||
|
||||
@@ -648,6 +648,7 @@ export const allManagedEventTypeProps: { [k in keyof Omit<Prisma.EventTypeSelect
|
||||
metadata: true,
|
||||
children: true,
|
||||
hideCalendarNotes: true,
|
||||
hideCalendarEventDetails: true,
|
||||
minimumBookingNotice: true,
|
||||
beforeEventBuffer: true,
|
||||
afterEventBuffer: true,
|
||||
|
||||
Vendored
+1
@@ -189,6 +189,7 @@ export interface CalendarEvent {
|
||||
cancellationReason?: string | null;
|
||||
rejectionReason?: string | null;
|
||||
hideCalendarNotes?: boolean;
|
||||
hideCalendarEventDetails?: boolean;
|
||||
recurrence?: string;
|
||||
recurringEvent?: RecurringEvent | null;
|
||||
eventTypeId?: number | null;
|
||||
|
||||
Reference in New Issue
Block a user