Skip to content

Commit d152811

Browse files
fix: preserve seatsPerTimeSlot during partial event type updates (calcom#25450)
* fix: preserve seatsPerTimeSlot during partial event type updates When doing a partial update on the PATCH /v2/organizations/{orgId}/teams/{teamId}/event-types/{eventTypeId} endpoint, providing a partial body would reset seatsPerTimeSlot back to null because the transformSeatsApiToInternal function was always called even when seats was undefined. This fix ensures that the seat options are only transformed when the seats field is explicitly provided in the update request, preserving existing values during partial updates. Also adds e2e test to verify partial updates preserve seatsPerTimeSlot. Co-Authored-By: morgan@cal.com <morgan@cal.com> * fix: handle TypeScript union type narrowing for seatsPerTimeSlot When seats is not provided in a partial update, the transformed body may not have seatsPerTimeSlot property. This fix uses 'in' operator to safely check for the property before accessing it. Also fixes the e2e test to properly narrow the union type for seats using type guards. Co-Authored-By: morgan@cal.com <morgan@cal.com> * refactor: simplify seatsPerTimeSlot handling per review feedback Per supalarry's suggestion, instead of using 'in' operator checks in multiple validation calls, we now return {seatsPerTimeSlot: undefined} when seats is not provided. This keeps the validation code unchanged and only requires modifying one line in the transformation. Co-Authored-By: morgan@cal.com <morgan@cal.com> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
1 parent 8b6c627 commit d152811

2 files changed

Lines changed: 65 additions & 1 deletion

File tree

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,7 @@ export class InputEventTypesService_2024_06_14 {
252252
requiresConfirmationWillBlockSlot:
253253
confirmationPolicyTransformed?.requiresConfirmationWillBlockSlot ?? undefined,
254254
eventTypeColor: this.transformInputEventTypeColor(color),
255-
...this.transformInputSeatOptions(seats),
255+
...(seats ? this.transformInputSeatOptions(seats) : { seatsPerTimeSlot: undefined }),
256256
eventName: customName,
257257
useEventTypeDestinationCalendarEmail: useDestinationCalendarEmail,
258258
...maxActiveBookingsPerBooker,

apps/api/v2/src/modules/organizations/event-types/organizations-member-team-admin-event-types.e2e-spec.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1230,6 +1230,70 @@ describe("Organizations Event Types Endpoints", () => {
12301230
});
12311231
});
12321232

1233+
it("should preserve seatsPerTimeSlot when doing partial update", async () => {
1234+
const createBody: CreateTeamEventTypeInput_2024_06_14 = {
1235+
title: "Coding consultation with seats",
1236+
slug: `organizations-event-types-seats-${randomString()}`,
1237+
description: "Our team will review your codebase.",
1238+
lengthInMinutes: 60,
1239+
locations: [
1240+
{
1241+
type: "integration",
1242+
integration: "cal-video",
1243+
},
1244+
],
1245+
schedulingType: "COLLECTIVE",
1246+
hosts: [
1247+
{
1248+
userId: teammate1.id,
1249+
},
1250+
],
1251+
seats: {
1252+
seatsPerTimeSlot: 5,
1253+
showAttendeeInfo: true,
1254+
showAvailabilityCount: true,
1255+
},
1256+
};
1257+
1258+
const createResponse = await request(app.getHttpServer())
1259+
.post(`/v2/organizations/${org.id}/teams/${team.id}/event-types`)
1260+
.send(createBody)
1261+
.expect(201);
1262+
1263+
const createdEventType: TeamEventTypeOutput_2024_06_14 = createResponse.body.data;
1264+
const createdSeats = createdEventType.seats;
1265+
expect(createdSeats).toBeDefined();
1266+
expect(createdSeats && "seatsPerTimeSlot" in createdSeats).toBe(true);
1267+
if (createdSeats && "seatsPerTimeSlot" in createdSeats) {
1268+
expect(createdSeats.seatsPerTimeSlot).toEqual(5);
1269+
expect(createdSeats.showAttendeeInfo).toEqual(true);
1270+
expect(createdSeats.showAvailabilityCount).toEqual(true);
1271+
}
1272+
1273+
// Now do a partial update that only changes a different field (not seats)
1274+
const updateBody: UpdateTeamEventTypeInput_2024_06_14 = {
1275+
bookingRequiresAuthentication: false,
1276+
};
1277+
1278+
const updateResponse = await request(app.getHttpServer())
1279+
.patch(`/v2/organizations/${org.id}/teams/${team.id}/event-types/${createdEventType.id}`)
1280+
.send(updateBody)
1281+
.expect(200);
1282+
1283+
const updatedEventType: TeamEventTypeOutput_2024_06_14 = updateResponse.body.data;
1284+
1285+
// Verify that seatsPerTimeSlot is preserved and not reset to null
1286+
const updatedSeats = updatedEventType.seats;
1287+
expect(updatedSeats).toBeDefined();
1288+
expect(updatedSeats && "seatsPerTimeSlot" in updatedSeats).toBe(true);
1289+
if (updatedSeats && "seatsPerTimeSlot" in updatedSeats) {
1290+
expect(updatedSeats.seatsPerTimeSlot).toEqual(5);
1291+
expect(updatedSeats.showAttendeeInfo).toEqual(true);
1292+
expect(updatedSeats.showAvailabilityCount).toEqual(true);
1293+
}
1294+
expect(updatedEventType.bookingRequiresAuthentication).toEqual(false);
1295+
});
1296+
12331297
function evaluateHost(expected: Host, received: Host | undefined) {
12341298
expect(expected.userId).toEqual(received?.userId);
12351299
expect(expected.mandatory).toEqual(received?.mandatory);

0 commit comments

Comments
 (0)