diff --git a/apps/api/v2/package.json b/apps/api/v2/package.json index 31d1d68336..6fba137189 100644 --- a/apps/api/v2/package.json +++ b/apps/api/v2/package.json @@ -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": "*", diff --git a/apps/api/v2/src/ee/event-types/event-types_2024_06_14/controllers/event-types.controller.e2e-spec.ts b/apps/api/v2/src/ee/event-types/event-types_2024_06_14/controllers/event-types.controller.e2e-spec.ts index f2c18566ff..b55123bb0f 100644 --- a/apps/api/v2/src/ee/event-types/event-types_2024_06_14/controllers/event-types.controller.e2e-spec.ts +++ b/apps/api/v2/src/ee/event-types/event-types_2024_06_14/controllers/event-types.controller.e2e-spec.ts @@ -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 ); diff --git a/apps/api/v2/src/ee/event-types/event-types_2024_06_14/services/output-event-types.service.ts b/apps/api/v2/src/ee/event-types/event-types_2024_06_14/services/output-event-types.service.ts index 9ab61ccd67..27570801c6 100644 --- a/apps/api/v2/src/ee/event-types/event-types_2024_06_14/services/output-event-types.service.ts +++ b/apps/api/v2/src/ee/event-types/event-types_2024_06_14/services/output-event-types.service.ts @@ -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, }; } diff --git a/apps/api/v2/src/modules/organizations/controllers/event-types/organizations-event-types.e2e-spec.ts b/apps/api/v2/src/modules/organizations/controllers/event-types/organizations-event-types.e2e-spec.ts index 0075dab763..5a8e4c950b 100644 --- a/apps/api/v2/src/modules/organizations/controllers/event-types/organizations-event-types.e2e-spec.ts +++ b/apps/api/v2/src/modules/organizations/controllers/event-types/organizations-event-types.e2e-spec.ts @@ -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); diff --git a/apps/api/v2/src/modules/organizations/services/event-types/output.service.ts b/apps/api/v2/src/modules/organizations/services/event-types/output.service.ts index bcbdea637f..0d0c8f4f3a 100644 --- a/apps/api/v2/src/modules/organizations/services/event-types/output.service.ts +++ b/apps/api/v2/src/modules/organizations/services/event-types/output.service.ts @@ -65,6 +65,7 @@ type Input = Pick< | "requiresConfirmationWillBlockSlot" | "eventName" | "useEventTypeDestinationCalendarEmail" + | "hideCalendarEventDetails" >; @Injectable() diff --git a/apps/api/v2/swagger/documentation.json b/apps/api/v2/swagger/documentation.json index 82f389dcdb..26aae1e2aa 100644 --- a/apps/api/v2/swagger/documentation.json +++ b/apps/api/v2/swagger/documentation.json @@ -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" }, diff --git a/apps/web/playwright/webhook.e2e.ts b/apps/web/playwright/webhook.e2e.ts index b3481f4b43..f710dd2c9b 100644 --- a/apps/web/playwright/webhook.e2e.ts +++ b/apps/web/playwright/webhook.e2e.ts @@ -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, diff --git a/apps/web/public/static/locales/en/common.json b/apps/web/public/static/locales/en/common.json index 89c6361cae..b2f7b3864d 100644 --- a/apps/web/public/static/locales/en/common.json +++ b/apps/web/public/static/locales/en/common.json @@ -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 ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑" } diff --git a/packages/app-store/googlecalendar/lib/CalendarService.ts b/packages/app-store/googlecalendar/lib/CalendarService.ts index e8d6ddf5cc..20ed7ce00f 100644 --- a/packages/app-store/googlecalendar/lib/CalendarService.ts +++ b/packages/app-store/googlecalendar/lib/CalendarService.ts @@ -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); diff --git a/packages/app-store/office365calendar/lib/CalendarService.ts b/packages/app-store/office365calendar/lib/CalendarService.ts index 47e6ed5b0a..ff3951f1aa 100644 --- a/packages/app-store/office365calendar/lib/CalendarService.ts +++ b/packages/app-store/office365calendar/lib/CalendarService.ts @@ -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) => { diff --git a/packages/core/builders/CalendarEvent/builder.ts b/packages/core/builders/CalendarEvent/builder.ts index 08daeadd7c..a3f30b5d0a 100644 --- a/packages/core/builders/CalendarEvent/builder.ts +++ b/packages/core/builders/CalendarEvent/builder.ts @@ -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; } diff --git a/packages/core/builders/CalendarEvent/class.ts b/packages/core/builders/CalendarEvent/class.ts index 2c774a2414..412d39e8ff 100644 --- a/packages/core/builders/CalendarEvent/class.ts +++ b/packages/core/builders/CalendarEvent/class.ts @@ -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; diff --git a/packages/emails/lib/generateIcsString.ts b/packages/emails/lib/generateIcsString.ts index 59d2d35198..df2fa51875 100644 --- a/packages/emails/lib/generateIcsString.ts +++ b/packages/emails/lib/generateIcsString.ts @@ -101,6 +101,7 @@ const generateIcsString = ({ location: location ?? undefined, method: "REQUEST", status, + ...(event.hideCalendarEventDetails ? { classification: "PRIVATE" } : {}), }); if (icsEvent.error) { throw icsEvent.error; diff --git a/packages/features/bookings/lib/handleNewBooking.ts b/packages/features/bookings/lib/handleNewBooking.ts index 6f4be8fc25..99b91b2bec 100644 --- a/packages/features/bookings/lib/handleNewBooking.ts +++ b/packages/features/bookings/lib/handleNewBooking.ts @@ -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 diff --git a/packages/features/bookings/lib/handleNewBooking/getEventTypesFromDB.ts b/packages/features/bookings/lib/handleNewBooking/getEventTypesFromDB.ts index 3204e82c7b..246b6febd0 100644 --- a/packages/features/bookings/lib/handleNewBooking/getEventTypesFromDB.ts +++ b/packages/features/bookings/lib/handleNewBooking/getEventTypesFromDB.ts @@ -59,6 +59,7 @@ export const getEventTypesFromDB = async (eventTypeId: number) => { metadata: true, destinationCalendar: true, hideCalendarNotes: true, + hideCalendarEventDetails: true, seatsPerTimeSlot: true, recurringEvent: true, seatsShowAttendees: true, diff --git a/packages/features/eventtypes/components/tabs/advanced/EventAdvancedTab.tsx b/packages/features/eventtypes/components/tabs/advanced/EventAdvancedTab.tsx index 88c006574c..dfb92e8d21 100644 --- a/packages/features/eventtypes/components/tabs/advanced/EventAdvancedTab.tsx +++ b/packages/features/eventtypes/components/tabs/advanced/EventAdvancedTab.tsx @@ -138,6 +138,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick )} /> + ( + onChange(e)} + /> + )} + /> ( diff --git a/packages/lib/CalendarService.ts b/packages/lib/CalendarService.ts index d32c98af9b..957a8e7958 100644 --- a/packages/lib/CalendarService.ts +++ b/packages/lib/CalendarService.ts @@ -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) diff --git a/packages/lib/defaultEvents.ts b/packages/lib/defaultEvents.ts index f28cebcaf1..45fae3256f 100644 --- a/packages/lib/defaultEvents.ts +++ b/packages/lib/defaultEvents.ts @@ -85,6 +85,7 @@ const commons = { onlyShowFirstAvailableSlot: false, id: 0, hideCalendarNotes: false, + hideCalendarEventDetails: false, recurringEvent: null, destinationCalendar: null, team: null, diff --git a/packages/lib/server/eventTypeSelect.ts b/packages/lib/server/eventTypeSelect.ts index 52032041c9..898c26e9d5 100644 --- a/packages/lib/server/eventTypeSelect.ts +++ b/packages/lib/server/eventTypeSelect.ts @@ -55,4 +55,5 @@ export const eventTypeSelect = Prisma.validator()({ bookingLimits: true, durationLimits: true, eventTypeColor: true, + hideCalendarEventDetails: true, }); diff --git a/packages/lib/server/repository/eventType.ts b/packages/lib/server/repository/eventType.ts index 24ceec5a7d..46da72ae6a 100644 --- a/packages/lib/server/repository/eventType.ts +++ b/packages/lib/server/repository/eventType.ts @@ -447,6 +447,7 @@ export class EventTypeRepository { requiresBookerEmailVerification: true, recurringEvent: true, hideCalendarNotes: true, + hideCalendarEventDetails: true, disableGuests: true, minimumBookingNotice: true, beforeEventBuffer: true, diff --git a/packages/lib/test/builder.ts b/packages/lib/test/builder.ts index 26c8f5785a..b4c30bb3c8 100644 --- a/packages/lib/test/builder.ts +++ b/packages/lib/test/builder.ts @@ -109,6 +109,7 @@ export const buildEventType = (eventType?: Partial): EventType => { requiresConfirmationWillBlockSlot: false, disableGuests: false, hideCalendarNotes: false, + hideCalendarEventDetails: false, minimumBookingNotice: 120, beforeEventBuffer: 0, afterEventBuffer: 0, diff --git a/packages/platform/atoms/event-types/hooks/useEventTypeForm.ts b/packages/platform/atoms/event-types/hooks/useEventTypeForm.ts index 0784510d4d..f4e036ce7a 100644 --- a/packages/platform/atoms/event-types/hooks/useEventTypeForm.ts +++ b/packages/platform/atoms/event-types/hooks/useEventTypeForm.ts @@ -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, diff --git a/packages/platform/types/event-types/event-types_2024_06_14/inputs/create-event-type.input.ts b/packages/platform/types/event-types/event-types_2024_06_14/inputs/create-event-type.input.ts index 31a073c9cf..c0128a8ba1 100644 --- a/packages/platform/types/event-types/event-types_2024_06_14/inputs/create-event-type.input.ts +++ b/packages/platform/types/event-types/event-types_2024_06_14/inputs/create-event-type.input.ts @@ -290,6 +290,10 @@ export class CreateEventTypeInput_2024_06_14 { @IsOptional() @IsBoolean() useDestinationCalendarEmail?: boolean; + + @IsOptional() + @IsBoolean() + hideCalendarEventDetails?: boolean; } export enum HostPriority { diff --git a/packages/platform/types/event-types/event-types_2024_06_14/inputs/update-event-type.input.ts b/packages/platform/types/event-types/event-types_2024_06_14/inputs/update-event-type.input.ts index 7653baa9a5..4bc857b178 100644 --- a/packages/platform/types/event-types/event-types_2024_06_14/inputs/update-event-type.input.ts +++ b/packages/platform/types/event-types/event-types_2024_06_14/inputs/update-event-type.input.ts @@ -282,6 +282,10 @@ export class UpdateEventTypeInput_2024_06_14 { @IsOptional() @IsBoolean() useDestinationCalendarEmail?: boolean; + + @IsOptional() + @IsBoolean() + hideCalendarEventDetails?: boolean; } @ApiExtraModels( diff --git a/packages/platform/types/event-types/event-types_2024_06_14/outputs/event-type.output.ts b/packages/platform/types/event-types/event-types_2024_06_14/outputs/event-type.output.ts index d4b4719bc0..8a6dcda9a0 100644 --- a/packages/platform/types/event-types/event-types_2024_06_14/outputs/event-type.output.ts +++ b/packages/platform/types/event-types/event-types_2024_06_14/outputs/event-type.output.ts @@ -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; } diff --git a/packages/prisma/migrations/20240925153154_add_hide_calendar_event_details/migration.sql b/packages/prisma/migrations/20240925153154_add_hide_calendar_event_details/migration.sql new file mode 100644 index 0000000000..3edfe03c6a --- /dev/null +++ b/packages/prisma/migrations/20240925153154_add_hide_calendar_event_details/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "EventType" ADD COLUMN "hideCalendarEventDetails" BOOLEAN NOT NULL DEFAULT false; diff --git a/packages/prisma/schema.prisma b/packages/prisma/schema.prisma index 6421340b3e..0fa9000395 100644 --- a/packages/prisma/schema.prisma +++ b/packages/prisma/schema.prisma @@ -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) diff --git a/packages/prisma/zod-utils.ts b/packages/prisma/zod-utils.ts index e212a94556..65a5c0b526 100644 --- a/packages/prisma/zod-utils.ts +++ b/packages/prisma/zod-utils.ts @@ -648,6 +648,7 @@ export const allManagedEventTypeProps: { [k in keyof Omit