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:
Joe Au-Yeung
2024-09-26 17:08:25 +00:00
committed by GitHub
co-authored by Alex van Andel Somay Chauhan Alex van Andel
parent b6c6e41603
commit ae7fae7da4
29 changed files with 93 additions and 5 deletions
+1 -1
View File
@@ -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": "*",
@@ -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
);
@@ -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,
};
}
@@ -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()
+7
View File
@@ -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"
},
+1
View File
@@ -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;
+1
View File
@@ -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 } }) => (
+1
View File
@@ -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)
+1
View File
@@ -85,6 +85,7 @@ const commons = {
onlyShowFirstAvailableSlot: false,
id: 0,
hideCalendarNotes: false,
hideCalendarEventDetails: false,
recurringEvent: null,
destinationCalendar: null,
team: null,
+1
View File
@@ -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,
+1
View File
@@ -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,
@@ -290,6 +290,10 @@ export class CreateEventTypeInput_2024_06_14 {
@IsOptional()
@IsBoolean()
useDestinationCalendarEmail?: boolean;
@IsOptional()
@IsBoolean()
hideCalendarEventDetails?: boolean;
}
export enum HostPriority {
@@ -282,6 +282,10 @@ export class UpdateEventTypeInput_2024_06_14 {
@IsOptional()
@IsBoolean()
useDestinationCalendarEmail?: boolean;
@IsOptional()
@IsBoolean()
hideCalendarEventDetails?: boolean;
}
@ApiExtraModels(
@@ -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;
}
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "EventType" ADD COLUMN "hideCalendarEventDetails" BOOLEAN NOT NULL DEFAULT false;
+1
View File
@@ -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)
+1
View File
@@ -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,
+1
View File
@@ -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;