fix: preserve seatsPerTimeSlot during partial event type updates (#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>
This commit is contained in:
co-authored by
Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
parent
8b6c6273ee
commit
d15281160f
+1
-1
@@ -252,7 +252,7 @@ export class InputEventTypesService_2024_06_14 {
|
||||
requiresConfirmationWillBlockSlot:
|
||||
confirmationPolicyTransformed?.requiresConfirmationWillBlockSlot ?? undefined,
|
||||
eventTypeColor: this.transformInputEventTypeColor(color),
|
||||
...this.transformInputSeatOptions(seats),
|
||||
...(seats ? this.transformInputSeatOptions(seats) : { seatsPerTimeSlot: undefined }),
|
||||
eventName: customName,
|
||||
useEventTypeDestinationCalendarEmail: useDestinationCalendarEmail,
|
||||
...maxActiveBookingsPerBooker,
|
||||
|
||||
+64
@@ -1230,6 +1230,70 @@ describe("Organizations Event Types Endpoints", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should preserve seatsPerTimeSlot when doing partial update", async () => {
|
||||
const createBody: CreateTeamEventTypeInput_2024_06_14 = {
|
||||
title: "Coding consultation with seats",
|
||||
slug: `organizations-event-types-seats-${randomString()}`,
|
||||
description: "Our team will review your codebase.",
|
||||
lengthInMinutes: 60,
|
||||
locations: [
|
||||
{
|
||||
type: "integration",
|
||||
integration: "cal-video",
|
||||
},
|
||||
],
|
||||
schedulingType: "COLLECTIVE",
|
||||
hosts: [
|
||||
{
|
||||
userId: teammate1.id,
|
||||
},
|
||||
],
|
||||
seats: {
|
||||
seatsPerTimeSlot: 5,
|
||||
showAttendeeInfo: true,
|
||||
showAvailabilityCount: true,
|
||||
},
|
||||
};
|
||||
|
||||
const createResponse = await request(app.getHttpServer())
|
||||
.post(`/v2/organizations/${org.id}/teams/${team.id}/event-types`)
|
||||
.send(createBody)
|
||||
.expect(201);
|
||||
|
||||
const createdEventType: TeamEventTypeOutput_2024_06_14 = createResponse.body.data;
|
||||
const createdSeats = createdEventType.seats;
|
||||
expect(createdSeats).toBeDefined();
|
||||
expect(createdSeats && "seatsPerTimeSlot" in createdSeats).toBe(true);
|
||||
if (createdSeats && "seatsPerTimeSlot" in createdSeats) {
|
||||
expect(createdSeats.seatsPerTimeSlot).toEqual(5);
|
||||
expect(createdSeats.showAttendeeInfo).toEqual(true);
|
||||
expect(createdSeats.showAvailabilityCount).toEqual(true);
|
||||
}
|
||||
|
||||
// Now do a partial update that only changes a different field (not seats)
|
||||
const updateBody: UpdateTeamEventTypeInput_2024_06_14 = {
|
||||
bookingRequiresAuthentication: false,
|
||||
};
|
||||
|
||||
const updateResponse = await request(app.getHttpServer())
|
||||
.patch(`/v2/organizations/${org.id}/teams/${team.id}/event-types/${createdEventType.id}`)
|
||||
.send(updateBody)
|
||||
.expect(200);
|
||||
|
||||
const updatedEventType: TeamEventTypeOutput_2024_06_14 = updateResponse.body.data;
|
||||
|
||||
// Verify that seatsPerTimeSlot is preserved and not reset to null
|
||||
const updatedSeats = updatedEventType.seats;
|
||||
expect(updatedSeats).toBeDefined();
|
||||
expect(updatedSeats && "seatsPerTimeSlot" in updatedSeats).toBe(true);
|
||||
if (updatedSeats && "seatsPerTimeSlot" in updatedSeats) {
|
||||
expect(updatedSeats.seatsPerTimeSlot).toEqual(5);
|
||||
expect(updatedSeats.showAttendeeInfo).toEqual(true);
|
||||
expect(updatedSeats.showAvailabilityCount).toEqual(true);
|
||||
}
|
||||
expect(updatedEventType.bookingRequiresAuthentication).toEqual(false);
|
||||
});
|
||||
|
||||
function evaluateHost(expected: Host, received: Host | undefined) {
|
||||
expect(expected.userId).toEqual(received?.userId);
|
||||
expect(expected.mandatory).toEqual(received?.mandatory);
|
||||
|
||||
Reference in New Issue
Block a user