Skip to content

Commit 5b5a42e

Browse files
authored
refactor: v2 and atoms booking fields (#16685)
* refactor: define inputs and outputs by separating default and custom fields * refactor: event type api <-> internal transformers * refactor: v2 use refactored libraries to transform input and output booking fields * refactor: dont allow custom slugs with default reserved slugs * refactor: input service store only specific default system fields * refactor: atoms display only specific default system fields * refactor: delete unused system field constants * fix: tests * fix: old event types * fix: imports * fix: import * chore: release libraries and use v2 with them * fix: unit tests * refactor: push email in case of missing phone * refactor: satisfies check for event-type locations
1 parent 2b43d9a commit 5b5a42e

34 files changed

Lines changed: 7109 additions & 1188 deletions

apps/api/v2/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
"dependencies": {
2929
"@calcom/platform-constants": "*",
3030
"@calcom/platform-enums": "*",
31-
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.34",
31+
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.36",
3232
"@calcom/platform-libraries-0.0.2": "npm:@calcom/platform-libraries@0.0.2",
3333
"@calcom/platform-types": "*",
3434
"@calcom/platform-utils": "*",

apps/api/v2/src/ee/event-types/event-types_2024_04_15/services/event-types.service.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
updateEventType,
1616
EventTypesPublic,
1717
getEventTypesPublic,
18+
systemBeforeFieldEmail,
1819
} from "@calcom/platform-libraries";
1920
import { EventType } from "@calcom/prisma/client";
2021

@@ -127,8 +128,17 @@ export class EventTypesService_2024_04_15 {
127128
async updateEventType(eventTypeId: number, body: UpdateEventTypeInput_2024_04_15, user: UserWithProfile) {
128129
this.checkCanUpdateEventType(user.id, eventTypeId);
129130
const eventTypeUser = await this.getUserToUpdateEvent(user);
131+
const bookingFields = [...(body.bookingFields || [])];
132+
133+
if (
134+
!bookingFields.find((field) => field.type === "email") &&
135+
!bookingFields.find((field) => field.type === "phone")
136+
) {
137+
bookingFields.push(systemBeforeFieldEmail);
138+
}
139+
130140
await updateEventType({
131-
input: { id: eventTypeId, ...body },
141+
input: { id: eventTypeId, ...body, bookingFields },
132142
ctx: {
133143
user: eventTypeUser,
134144
// eslint-disable-next-line @typescript-eslint/ban-ts-comment

apps/api/v2/src/ee/event-types/event-types_2024_06_14/controllers/event-types.controller.e2e-spec.ts

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,6 @@ describe("Event types Endpoints", () => {
250250
expect(createdEventType.description).toEqual(body.description);
251251
expect(createdEventType.lengthInMinutes).toEqual(body.lengthInMinutes);
252252
expect(createdEventType.locations).toEqual(body.locations);
253-
expect(createdEventType.bookingFields).toEqual(body.bookingFields);
254253
expect(createdEventType.ownerId).toEqual(user.id);
255254
expect(createdEventType.scheduleId).toEqual(firstSchedule.id);
256255
expect(createdEventType.bookingLimitsCount).toEqual(body.bookingLimitsCount);
@@ -259,6 +258,16 @@ describe("Event types Endpoints", () => {
259258
expect(createdEventType.offsetStart).toEqual(body.offsetStart);
260259
expect(createdEventType.bookingWindow).toEqual(body.bookingWindow);
261260
expect(createdEventType.recurrence).toEqual(body.recurrence);
261+
262+
const responseBookingFields = body.bookingFields || [];
263+
const expectedBookingFields = [
264+
{ isDefault: true, required: true, slug: "name", type: "name" },
265+
{ isDefault: true, required: true, slug: "email", type: "email" },
266+
{ isDefault: true, required: false, slug: "rescheduleReason", type: "textarea" },
267+
...responseBookingFields.map((field) => ({ isDefault: false, ...field })),
268+
];
269+
270+
expect(createdEventType.bookingFields).toEqual(expectedBookingFields);
262271
eventType = responseBody.data;
263272
});
264273
});
@@ -476,6 +485,16 @@ describe("Event types Endpoints", () => {
476485
let legacyEventTypeId1: number;
477486
let legacyEventTypeId2: number;
478487

488+
const expectedReturnSystemFields = [
489+
{ isDefault: true, required: true, slug: "name", type: "name" },
490+
{ isDefault: true, required: true, slug: "email", type: "email" },
491+
{ isDefault: true, type: "radioInput", slug: "location", required: false },
492+
{ isDefault: true, required: true, slug: "title", type: "text" },
493+
{ isDefault: true, required: false, slug: "notes", type: "textarea" },
494+
{ isDefault: true, required: false, slug: "guests", type: "multiemail" },
495+
{ isDefault: true, required: false, slug: "rescheduleReason", type: "textarea" },
496+
];
497+
479498
beforeAll(async () => {
480499
const moduleRef = await withApiAuth(
481500
userEmail,
@@ -545,7 +564,7 @@ describe("Event types Endpoints", () => {
545564
.expect(400);
546565
});
547566

548-
it("should return empty bookingFields if system fields are the only one in database", async () => {
567+
it("should return system bookingFields stored in database", async () => {
549568
const legacyEventTypeInput = {
550569
title: "legacy event type",
551570
description: "legacy event type description",
@@ -638,11 +657,11 @@ describe("Event types Endpoints", () => {
638657
.then(async (response) => {
639658
const responseBody: ApiSuccessResponse<EventTypeOutput_2024_06_14> = response.body;
640659
const fetchedEventType = responseBody.data;
641-
expect(fetchedEventType.bookingFields).toEqual([]);
660+
expect(fetchedEventType.bookingFields).toEqual(expectedReturnSystemFields);
642661
});
643662
});
644663

645-
it("should return user created bookingFields among system fields in the database", async () => {
664+
it("should return user created bookingFields with system fields", async () => {
646665
const userDefinedBookingField = {
647666
name: "team",
648667
type: "textarea",
@@ -755,7 +774,9 @@ describe("Event types Endpoints", () => {
755774
const fetchedEventType = responseBody.data;
756775

757776
expect(fetchedEventType.bookingFields).toEqual([
777+
...expectedReturnSystemFields,
758778
{
779+
isDefault: false,
759780
type: userDefinedBookingField.type,
760781
slug: userDefinedBookingField.name,
761782
label: userDefinedBookingField.label,

apps/api/v2/src/ee/event-types/event-types_2024_06_14/event-types.repository.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ import { Injectable } from "@nestjs/common";
66

77
import {
88
getEventTypeById,
9-
transformApiEventTypeBookingFields,
10-
transformApiEventTypeLocations,
9+
transformBookingFieldsApiToInternal,
10+
transformLocationsApiToInternal,
1111
} from "@calcom/platform-libraries";
1212
import { CreateEventTypeInput_2024_06_14 } from "@calcom/platform-types";
1313
import type { PrismaClient } from "@calcom/prisma";
@@ -30,8 +30,8 @@ type InputEventTransformed = Omit<
3030
> & {
3131
length: number;
3232
slug: string;
33-
locations?: ReturnType<typeof transformApiEventTypeLocations>;
34-
bookingFields?: ReturnType<typeof transformApiEventTypeBookingFields>;
33+
locations?: ReturnType<typeof transformLocationsApiToInternal>;
34+
bookingFields?: ReturnType<typeof transformBookingFieldsApiToInternal>;
3535
};
3636

3737
@Injectable()

apps/api/v2/src/ee/event-types/event-types_2024_06_14/services/input-event-types.service.ts

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
import { Injectable } from "@nestjs/common";
22

33
import {
4-
transformApiEventTypeBookingFields,
5-
transformApiEventTypeLocations,
6-
transformApiEventTypeIntervalLimits,
7-
transformApiEventTypeFutureBookingLimits,
8-
transformApiEventTypeRecurrence,
4+
transformBookingFieldsApiToInternal,
5+
transformLocationsApiToInternal,
6+
transformIntervalLimitsApiToInternal,
7+
transformFutureBookingLimitsApiToInternal,
8+
transformRecurrenceApiToInternal,
9+
systemBeforeFieldName,
10+
systemBeforeFieldEmail,
11+
systemAfterFieldRescheduleReason,
912
} from "@calcom/platform-libraries";
13+
import { systemBeforeFieldLocation } from "@calcom/platform-libraries";
1014
import { CreateEventTypeInput_2024_06_14, UpdateEventTypeInput_2024_06_14 } from "@calcom/platform-types";
1115

1216
@Injectable()
@@ -30,11 +34,12 @@ export class InputEventTypesService_2024_06_14 {
3034
...rest
3135
} = inputEventType;
3236

37+
const hasMultipleLocations = (locations || defaultLocations).length > 1;
3338
const eventType = {
3439
...rest,
3540
length: lengthInMinutes,
3641
locations: this.transformInputLocations(locations || defaultLocations),
37-
bookingFields: this.transformInputBookingFields(bookingFields),
42+
bookingFields: this.transformInputBookingFields(bookingFields, hasMultipleLocations),
3843
bookingLimits: bookingLimitsCount ? this.transformInputIntervalLimits(bookingLimitsCount) : undefined,
3944
durationLimits: bookingLimitsDuration
4045
? this.transformInputIntervalLimits(bookingLimitsDuration)
@@ -59,11 +64,15 @@ export class InputEventTypesService_2024_06_14 {
5964
...rest
6065
} = inputEventType;
6166

67+
const hasMultipleLocations = !!(locations && locations?.length > 1);
68+
6269
const eventType = {
6370
...rest,
6471
length: lengthInMinutes,
6572
locations: locations ? this.transformInputLocations(locations) : undefined,
66-
bookingFields: bookingFields ? this.transformInputBookingFields(bookingFields) : undefined,
73+
bookingFields: bookingFields
74+
? this.transformInputBookingFields(bookingFields, hasMultipleLocations)
75+
: undefined,
6776
schedule: scheduleId,
6877
bookingLimits: bookingLimitsCount ? this.transformInputIntervalLimits(bookingLimitsCount) : undefined,
6978
durationLimits: bookingLimitsDuration
@@ -77,23 +86,35 @@ export class InputEventTypesService_2024_06_14 {
7786
}
7887

7988
transformInputLocations(inputLocations: CreateEventTypeInput_2024_06_14["locations"]) {
80-
return transformApiEventTypeLocations(inputLocations);
89+
return transformLocationsApiToInternal(inputLocations);
8190
}
8291

83-
transformInputBookingFields(inputBookingFields: CreateEventTypeInput_2024_06_14["bookingFields"]) {
84-
return transformApiEventTypeBookingFields(inputBookingFields);
92+
transformInputBookingFields(
93+
inputBookingFields: CreateEventTypeInput_2024_06_14["bookingFields"],
94+
hasMultipleLocations: boolean
95+
) {
96+
const defaultFieldsBefore = [systemBeforeFieldName, systemBeforeFieldEmail];
97+
// note(Lauris): if event type has multiple locations then a radio button booking field has to be displayed to allow booker to pick location
98+
if (hasMultipleLocations) {
99+
defaultFieldsBefore.push(systemBeforeFieldLocation);
100+
}
101+
102+
const customFields = transformBookingFieldsApiToInternal(inputBookingFields);
103+
const defaultFieldsAfter = [systemAfterFieldRescheduleReason];
104+
105+
return [...defaultFieldsBefore, ...customFields, ...defaultFieldsAfter];
85106
}
86107

87108
transformInputIntervalLimits(inputBookingFields: CreateEventTypeInput_2024_06_14["bookingLimitsCount"]) {
88-
return transformApiEventTypeIntervalLimits(inputBookingFields);
109+
return transformIntervalLimitsApiToInternal(inputBookingFields);
89110
}
90111

91112
transformInputBookingWindow(inputBookingWindow: CreateEventTypeInput_2024_06_14["bookingWindow"]) {
92-
const res = transformApiEventTypeFutureBookingLimits(inputBookingWindow);
113+
const res = transformFutureBookingLimitsApiToInternal(inputBookingWindow);
93114
return !!res ? res : {};
94115
}
95116

96117
transformInputRecurrignEvent(recurrence: CreateEventTypeInput_2024_06_14["recurrence"]) {
97-
return transformApiEventTypeRecurrence(recurrence);
118+
return transformRecurrenceApiToInternal(recurrence);
98119
}
99120
}

apps/api/v2/src/ee/event-types/event-types_2024_06_14/services/output-event-types.service.ts

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,17 @@ import type { EventType, User, Schedule } from "@prisma/client";
44
import {
55
EventTypeMetaDataSchema,
66
userMetadata,
7-
getResponseEventTypeLocations,
8-
getResponseEventTypeBookingFields,
7+
transformLocationsInternalToApi,
8+
transformBookingFieldsInternalToApi,
99
parseRecurringEvent,
1010
TransformedLocationsSchema,
1111
BookingFieldsSchema,
1212
SystemField,
13-
UserField,
13+
CustomField,
1414
parseBookingLimit,
15-
getResponseEventTypeIntervalLimits,
16-
getResponseEventTypeFutureBookingLimits,
17-
getResponseEventTypeRecurrence,
15+
transformIntervalLimitsInternalToApi,
16+
transformFutureBookingLimitsInternalToApi,
17+
transformRecurrenceInternalToApi,
1818
} from "@calcom/platform-libraries";
1919
import { TransformFutureBookingsLimitSchema_2024_06_14 } from "@calcom/platform-types";
2020

@@ -144,19 +144,20 @@ export class OutputEventTypesService_2024_06_14 {
144144

145145
transformLocations(locations: any) {
146146
if (!locations) return [];
147-
return getResponseEventTypeLocations(TransformedLocationsSchema.parse(locations));
147+
return transformLocationsInternalToApi(TransformedLocationsSchema.parse(locations));
148148
}
149149

150-
transformBookingFields(inputBookingFields: (SystemField | UserField)[] | null) {
151-
if (!inputBookingFields) return [];
152-
const userFields = inputBookingFields.filter((field) => field.editable === "user") as UserField[];
153-
return getResponseEventTypeBookingFields(userFields);
150+
transformBookingFields(bookingFields: (SystemField | CustomField)[] | null) {
151+
if (!bookingFields) return [];
152+
153+
return transformBookingFieldsInternalToApi(bookingFields);
154154
}
155155

156156
transformRecurringEvent(recurringEvent: any) {
157157
if (!recurringEvent) return null;
158158
const recurringEventParsed = parseRecurringEvent(recurringEvent);
159-
return getResponseEventTypeRecurrence(recurringEventParsed);
159+
if (!recurringEventParsed) return null;
160+
return transformRecurrenceInternalToApi(recurringEventParsed);
160161
}
161162

162163
transformMetadata(metadata: any) {
@@ -182,10 +183,10 @@ export class OutputEventTypesService_2024_06_14 {
182183

183184
transformIntervalLimits(bookingLimits: any) {
184185
const bookingLimitsParsed = parseBookingLimit(bookingLimits);
185-
return getResponseEventTypeIntervalLimits(bookingLimitsParsed);
186+
return transformIntervalLimitsInternalToApi(bookingLimitsParsed);
186187
}
187188

188189
transformBookingWindow(bookingLimits: TransformFutureBookingsLimitSchema_2024_06_14) {
189-
return getResponseEventTypeFutureBookingLimits(bookingLimits);
190+
return transformFutureBookingLimitsInternalToApi(bookingLimits);
190191
}
191192
}

apps/api/v2/test/setEnvVars.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,7 @@ const env: Partial<Omit<Environment, "NODE_ENV">> = {
2222
process.env = {
2323
...env,
2424
...process.env,
25+
NEXT_PUBLIC_VAPID_PUBLIC_KEY:
26+
"BIds0AQJ96xGBjTSMHTOqLBLutQE7Lu32KKdgSdy7A2cS4mKI2cgb3iGkhDJa5Siy-stezyuPm8qpbhmNxdNHMw",
27+
VAPID_PRIVATE_KEY: "6cJtkASCar5sZWguIAW7OjvyixpBw9p8zL8WDDwk9Jk",
2528
};

0 commit comments

Comments
 (0)