Skip to content

Commit dea6ec5

Browse files
perf: handleChildrenEventTypes transaction (#25602)
* perf: handleChildrenEventTypes transaction * fixup! perf: handleChildrenEventTypes transaction * fix: e2e tests * test: update handleChildrenEventTypes tests for createMany API Co-Authored-By: morgan@cal.com <morgan@cal.com> * fix: tests --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
1 parent 8cace7f commit dea6ec5

File tree

2 files changed

+277
-116
lines changed

2 files changed

+277
-116
lines changed

apps/web/test/lib/handleChildrenEventTypes.test.ts

Lines changed: 193 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,18 @@ import type { EventType, User, WorkflowsOnEventTypes } from "@calcom/prisma/clie
99
import type { Prisma } from "@calcom/prisma/client";
1010
import { SchedulingType } from "@calcom/prisma/enums";
1111

12+
// Helper to setup transaction mock that executes the callback with the prisma mock
13+
const setupTransactionMock = () => {
14+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
15+
// @ts-ignore
16+
prismaMock.$transaction.mockImplementation(async (callback) => {
17+
if (typeof callback === "function") {
18+
return await callback(prismaMock);
19+
}
20+
return Promise.all(callback);
21+
});
22+
};
23+
1224
// create input does not allow ID
1325
const mockFindFirstEventType = (
1426
data?: Partial<EventType> & { workflows?: WorkflowsOnEventTypes[] } & { users?: User[] }
@@ -132,6 +144,13 @@ describe("handleChildrenEventTypes", () => {
132144
metadata: { managedEventConfig: {} },
133145
locations: [],
134146
});
147+
148+
// Setup transaction mock to execute the callback
149+
setupTransactionMock();
150+
151+
// Mock findMany to return empty array (no workflows to link)
152+
prismaMock.eventType.findMany.mockResolvedValue([]);
153+
135154
const result = await updateChildrenEventTypes({
136155
eventTypeId: 1,
137156
oldEventType: { children: [], team: { name: "" } },
@@ -143,25 +162,27 @@ describe("handleChildrenEventTypes", () => {
143162
updatedValues: {},
144163
});
145164
const { createdAt, updatedAt, ...expectedEvType } = evType;
146-
expect(prismaMock.eventType.create).toHaveBeenCalledWith({
147-
data: {
148-
...expectedEvType,
149-
parentId: 1,
150-
users: { connect: [{ id: 4 }] },
151-
lockTimeZoneToggleOnBookingPage: false,
152-
requiresBookerEmailVerification: false,
153-
bookingLimits: undefined,
154-
durationLimits: undefined,
155-
recurringEvent: undefined,
156-
eventTypeColor: undefined,
157-
customReplyToEmail: null,
158-
userId: 4,
159-
rrSegmentQueryValue: undefined,
160-
assignRRMembersUsingSegment: false,
161-
useBookerTimezone: false,
162-
restrictionScheduleId: null,
163-
allowReschedulingCancelledBookings: false,
164-
},
165+
expect(prismaMock.eventType.createMany).toHaveBeenCalledWith({
166+
data: [
167+
{
168+
...expectedEvType,
169+
parentId: 1,
170+
lockTimeZoneToggleOnBookingPage: false,
171+
requiresBookerEmailVerification: false,
172+
bookingLimits: undefined,
173+
durationLimits: undefined,
174+
recurringEvent: undefined,
175+
eventTypeColor: undefined,
176+
customReplyToEmail: null,
177+
userId: 4,
178+
rrSegmentQueryValue: undefined,
179+
assignRRMembersUsingSegment: false,
180+
useBookerTimezone: false,
181+
restrictionScheduleId: null,
182+
allowReschedulingCancelledBookings: false,
183+
},
184+
],
185+
skipDuplicates: true,
165186
});
166187
expect(result.newUserIds).toEqual([4]);
167188
expect(result.oldUserIds).toEqual([]);
@@ -304,6 +325,13 @@ describe("handleChildrenEventTypes", () => {
304325
metadata: { managedEventConfig: {} },
305326
locations: [],
306327
});
328+
329+
// Setup transaction mock to execute the callback
330+
setupTransactionMock();
331+
332+
// Mock findMany to return empty array (no workflows to link)
333+
prismaMock.eventType.findMany.mockResolvedValue([]);
334+
307335
prismaMock.eventType.deleteMany.mockResolvedValue([123] as unknown as Prisma.BatchPayload);
308336
const result = await updateChildrenEventTypes({
309337
eventTypeId: 1,
@@ -316,27 +344,28 @@ describe("handleChildrenEventTypes", () => {
316344
updatedValues: {},
317345
});
318346
const { createdAt, updatedAt, ...expectedEvType } = evType;
319-
expect(prismaMock.eventType.create).toHaveBeenCalledWith({
320-
data: {
321-
...expectedEvType,
322-
parentId: 1,
323-
users: { connect: [{ id: 4 }] },
324-
bookingLimits: undefined,
325-
durationLimits: undefined,
326-
recurringEvent: undefined,
327-
eventTypeColor: undefined,
328-
customReplyToEmail: null,
329-
instantMeetingScheduleId: undefined,
330-
lockTimeZoneToggleOnBookingPage: false,
331-
requiresBookerEmailVerification: false,
332-
userId: 4,
333-
workflows: undefined,
334-
rrSegmentQueryValue: undefined,
335-
assignRRMembersUsingSegment: false,
336-
useBookerTimezone: false,
337-
restrictionScheduleId: null,
338-
allowReschedulingCancelledBookings: false,
339-
},
347+
expect(prismaMock.eventType.createMany).toHaveBeenCalledWith({
348+
data: [
349+
{
350+
...expectedEvType,
351+
parentId: 1,
352+
bookingLimits: undefined,
353+
durationLimits: undefined,
354+
recurringEvent: undefined,
355+
eventTypeColor: undefined,
356+
customReplyToEmail: null,
357+
instantMeetingScheduleId: undefined,
358+
lockTimeZoneToggleOnBookingPage: false,
359+
requiresBookerEmailVerification: false,
360+
userId: 4,
361+
rrSegmentQueryValue: undefined,
362+
assignRRMembersUsingSegment: false,
363+
useBookerTimezone: false,
364+
restrictionScheduleId: null,
365+
allowReschedulingCancelledBookings: false,
366+
},
367+
],
368+
skipDuplicates: true,
340369
});
341370
expect(result.newUserIds).toEqual([4]);
342371
expect(result.oldUserIds).toEqual([]);
@@ -439,6 +468,96 @@ describe("handleChildrenEventTypes", () => {
439468
],
440469
});
441470

471+
// Setup transaction mock to execute the callback
472+
setupTransactionMock();
473+
474+
// Mock findMany to return the newly created event type for workflow linking
475+
// This simulates the event type created for user 5
476+
prismaMock.eventType.findMany.mockResolvedValue([
477+
{
478+
id: 3,
479+
title: "test",
480+
slug: "test",
481+
description: null,
482+
position: 0,
483+
locations: [],
484+
length: 30,
485+
offsetStart: 0,
486+
hidden: false,
487+
userId: 5,
488+
profileId: null,
489+
teamId: null,
490+
eventName: null,
491+
parentId: 1,
492+
bookingFields: null,
493+
timeZone: null,
494+
periodType: "UNLIMITED",
495+
periodStartDate: null,
496+
periodEndDate: null,
497+
periodDays: null,
498+
periodCountCalendarDays: null,
499+
lockTimeZoneToggleOnBookingPage: false,
500+
lockedTimeZone: null,
501+
requiresConfirmation: false,
502+
requiresConfirmationWillBlockSlot: false,
503+
requiresConfirmationForFreeEmail: false,
504+
requiresBookerEmailVerification: false,
505+
canSendCalVideoTranscriptionEmails: true,
506+
autoTranslateDescriptionEnabled: false,
507+
recurringEvent: null,
508+
disableGuests: false,
509+
hideCalendarNotes: false,
510+
hideCalendarEventDetails: false,
511+
minimumBookingNotice: 120,
512+
beforeEventBuffer: 0,
513+
afterEventBuffer: 0,
514+
seatsPerTimeSlot: null,
515+
onlyShowFirstAvailableSlot: false,
516+
showOptimizedSlots: false,
517+
disableCancelling: false,
518+
disableRescheduling: false,
519+
seatsShowAttendees: false,
520+
seatsShowAvailabilityCount: true,
521+
schedulingType: null,
522+
scheduleId: null,
523+
price: 0,
524+
currency: "usd",
525+
slotInterval: null,
526+
metadata: {},
527+
successRedirectUrl: null,
528+
forwardParamsSuccessRedirect: true,
529+
bookingLimits: null,
530+
durationLimits: null,
531+
isInstantEvent: false,
532+
instantMeetingExpiryTimeOffsetInSeconds: 90,
533+
instantMeetingScheduleId: null,
534+
assignAllTeamMembers: false,
535+
useEventTypeDestinationCalendarEmail: false,
536+
secondaryEmailId: null,
537+
eventTypeColor: null,
538+
rescheduleWithSameRoundRobinHost: false,
539+
rrSegmentQueryValue: null,
540+
assignRRMembersUsingSegment: false,
541+
useEventLevelSelectedCalendars: false,
542+
restrictionScheduleId: null,
543+
useBookerTimezone: false,
544+
allowReschedulingCancelledBookings: false,
545+
includeNoShowInRRCalculation: false,
546+
interfaceLanguage: null,
547+
customReplyToEmail: null,
548+
createdAt: new Date(),
549+
updatedAt: new Date(),
550+
instantMeetingParameters: [],
551+
isRRWeightsEnabled: false,
552+
maxLeadThreshold: null,
553+
allowReschedulingPastBookings: false,
554+
hideOrganizerEmail: false,
555+
maxActiveBookingsPerBooker: null,
556+
maxActiveBookingPerBookerOfferReschedule: false,
557+
bookingRequiresAuthentication: false,
558+
},
559+
]);
560+
442561
// Mock the event type that will be returned for existing users
443562
const mockUpdatedEventType = {
444563
id: 2,
@@ -481,33 +600,41 @@ describe("handleChildrenEventTypes", () => {
481600
});
482601

483602
const { createdAt, updatedAt, ...expectedEvType } = evType;
484-
expect(prismaMock.eventType.create).toHaveBeenCalledWith({
485-
data: {
486-
...expectedEvType,
487-
bookingLimits: undefined,
488-
durationLimits: undefined,
489-
recurringEvent: undefined,
490-
eventTypeColor: undefined,
491-
customReplyToEmail: null,
492-
locations: [],
493-
lockTimeZoneToggleOnBookingPage: false,
494-
requiresBookerEmailVerification: false,
495-
useBookerTimezone: false,
496-
restrictionScheduleId: null,
497-
parentId: 1,
498-
userId: 5,
499-
users: {
500-
connect: [{ id: 5 }],
501-
},
502-
workflows: {
503-
create: [{ workflowId: 11 }],
603+
if ("workflows" in expectedEvType) delete expectedEvType.workflows;
604+
// Verify createMany was called for new users (user 5)
605+
// Note: createMany doesn't support nested relations like workflows, so they're handled separately
606+
expect(prismaMock.eventType.createMany).toHaveBeenCalledWith({
607+
data: [
608+
{
609+
...expectedEvType,
610+
bookingLimits: undefined,
611+
durationLimits: undefined,
612+
recurringEvent: undefined,
613+
eventTypeColor: undefined,
614+
customReplyToEmail: null,
615+
locations: [],
616+
lockTimeZoneToggleOnBookingPage: false,
617+
requiresBookerEmailVerification: false,
618+
useBookerTimezone: false,
619+
restrictionScheduleId: null,
620+
instantMeetingScheduleId: undefined,
621+
parentId: 1,
622+
userId: 5,
623+
rrSegmentQueryValue: undefined,
624+
assignRRMembersUsingSegment: false,
625+
useEventLevelSelectedCalendars: false,
626+
allowReschedulingCancelledBookings: false,
504627
},
505-
rrSegmentQueryValue: undefined,
506-
assignRRMembersUsingSegment: false,
507-
useEventLevelSelectedCalendars: false,
508-
allowReschedulingCancelledBookings: false,
509-
},
628+
],
629+
skipDuplicates: true,
510630
});
631+
632+
// Verify workflowsOnEventTypes.createMany was called for new users' workflows
633+
expect(prismaMock.workflowsOnEventTypes.createMany).toHaveBeenCalledWith({
634+
data: [{ eventTypeId: 3, workflowId: 11 }],
635+
skipDuplicates: true,
636+
});
637+
511638
const { profileId, rrSegmentQueryValue, createdAt: _, updatedAt: __, ...rest } = evType;
512639
if ("workflows" in rest) delete rest.workflows;
513640
expect(prismaMock.eventType.update).toHaveBeenCalledWith({
@@ -531,6 +658,7 @@ describe("handleChildrenEventTypes", () => {
531658
},
532659
},
533660
});
661+
// Verify workflowsOnEventTypes.upsert was called for existing users' workflows
534662
expect(prismaMock.workflowsOnEventTypes.upsert).toHaveBeenCalledWith({
535663
create: {
536664
eventTypeId: 2,

0 commit comments

Comments
 (0)