From badd9a5698f50cb5156128ced087ab24cd1236bc Mon Sep 17 00:00:00 2001 From: Alex van Andel Date: Sat, 17 May 2025 01:55:57 +0100 Subject: [PATCH] perf: Better perf by going event type first w/o booking (#21369) * perf: Better perf by going event type first w/o booking * minor fixup * Use an IN with children instead of an OR with parent * Turns out we already had a variable with the right values * Make sure the right event type is used even for children * few fixes * Full select is needed to keep typings working * Fix last remaining issue --- .../workflows/activateEventType.handler.ts | 135 ++++++++++-------- 1 file changed, 79 insertions(+), 56 deletions(-) diff --git a/packages/trpc/server/routers/viewer/workflows/activateEventType.handler.ts b/packages/trpc/server/routers/viewer/workflows/activateEventType.handler.ts index d384d291a9..bbadb64406 100644 --- a/packages/trpc/server/routers/viewer/workflows/activateEventType.handler.ts +++ b/packages/trpc/server/routers/viewer/workflows/activateEventType.handler.ts @@ -5,7 +5,7 @@ import { getBookerBaseUrl } from "@calcom/lib/getBookerUrl/server"; import { WorkflowRepository } from "@calcom/lib/server/repository/workflow"; import { getTimeFormatStringFromUserTimeFormat } from "@calcom/lib/timeFormat"; import { prisma } from "@calcom/prisma"; -import { BookingStatus } from "@calcom/prisma/client"; +import { BookingStatus } from "@calcom/prisma/enums"; import { MembershipRole, SchedulingType, WorkflowActions, WorkflowTriggerEvents } from "@calcom/prisma/enums"; import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils"; import type { TrpcSessionUser } from "@calcom/trpc/server/types"; @@ -26,7 +26,7 @@ export const activateEventTypeHandler = async ({ ctx, input }: ActivateEventType const { eventTypeId, workflowId } = input; // Check that event type belong to the user or team - const userEventType = await prisma.eventType.findFirst({ + const eventType = await prisma.eventType.findFirst({ where: { id: eventTypeId, OR: [ @@ -47,30 +47,65 @@ export const activateEventTypeHandler = async ({ ctx, input }: ActivateEventType ], }, select: { + id: true, teamId: true, + hideOrganizerEmail: true, + customReplyToEmail: true, + schedulingType: true, + slug: true, + hosts: { + select: { + user: { + select: { + email: true, + destinationCalendar: { + select: { + primaryEmail: true, + }, + }, + }, + }, + }, + }, children: { select: { id: true, + teamId: true, + hideOrganizerEmail: true, + customReplyToEmail: true, + schedulingType: true, + slug: true, + hosts: { + select: { + user: { + select: { + email: true, + destinationCalendar: { + select: { + primaryEmail: true, + }, + }, + }, + }, + }, + }, }, }, }, }); - if (!userEventType) + if (!eventType) throw new TRPCError({ code: "UNAUTHORIZED", message: "Not authorized to edit this event type" }); + // at this point we know that the event type belongs to the user or team + // so we don't use OR, we use logic. + const whereClause = eventType.teamId ? { teamId: eventType.teamId } : { userId: ctx.user.id }; + // Check that the workflow belongs to the user or team const eventTypeWorkflow = await prisma.workflow.findFirst({ where: { id: workflowId, - OR: [ - { - userId: ctx.user.id, - }, - { - teamId: userEventType.teamId || undefined, - }, - ], + ...whereClause, }, select: { steps: { @@ -117,7 +152,12 @@ export const activateEventTypeHandler = async ({ ctx, input }: ActivateEventType const isOrg = eventTypeWorkflow.team?.isOrganization ?? false; - const activeOn = [eventTypeId].concat(userEventType.children.map((ch) => ch.id)); + const activeOnEventTypes = new Map([ + [eventType.id, eventType], + ...(eventType.children + ? eventType.children.map((child) => [child.id, child] as [number, typeof eventType]) + : []), + ]); if (isActive) { // disable workflow for this event type & delete all reminders @@ -146,7 +186,7 @@ export const activateEventTypeHandler = async ({ ctx, input }: ActivateEventType await prisma.workflowsOnEventTypes.deleteMany({ where: { workflowId, - eventTypeId: { in: [eventTypeId].concat(userEventType.children.map((ch) => ch.id)) }, + eventTypeId: { in: Array.from(activeOnEventTypes.keys()) }, }, }); @@ -207,7 +247,11 @@ export const activateEventTypeHandler = async ({ ctx, input }: ActivateEventType }); } } - await removeSmsReminderFieldForEventTypes({ activeOnToRemove: activeOn, workflowId, isOrg }); + await removeSmsReminderFieldForEventTypes({ + activeOnToRemove: Array.from(activeOnEventTypes.keys()), + workflowId, + isOrg, + }); } else { if ( eventTypeWorkflow.trigger == WorkflowTriggerEvents.BEFORE_EVENT || @@ -216,20 +260,16 @@ export const activateEventTypeHandler = async ({ ctx, input }: ActivateEventType // activate workflow and schedule reminders for existing bookings const bookingsForReminders = await prisma.booking.findMany({ where: { - OR: [ - { eventTypeId }, - { - eventType: { - parentId: eventTypeId, - }, - }, - ], + eventTypeId: { + in: Array.from(activeOnEventTypes.keys()), + }, status: BookingStatus.ACCEPTED, startTime: { gte: new Date(), }, }, select: { + eventTypeId: true, metadata: true, userId: true, smsReminderNumber: true, @@ -246,28 +286,6 @@ export const activateEventTypeHandler = async ({ ctx, input }: ActivateEventType locale: true, }, }, - eventType: { - select: { - schedulingType: true, - slug: true, - customReplyToEmail: true, - hosts: { - select: { - user: { - select: { - email: true, - destinationCalendar: { - select: { - primaryEmail: true, - }, - }, - }, - }, - }, - }, - hideOrganizerEmail: true, - }, - }, user: { select: { name: true, @@ -283,6 +301,8 @@ export const activateEventTypeHandler = async ({ ctx, input }: ActivateEventType const bookerUrl = await getBookerBaseUrl(ctx.user.organizationId ?? null); for (const booking of bookingsForReminders) { + // eventTypeId is technically nullable but we know it will be there + const bookingEventType = activeOnEventTypes.get(booking.eventTypeId!); const defaultLocale = "en"; const bookingInfo = { uid: booking.uid, @@ -308,14 +328,14 @@ export const activateEventTypeHandler = async ({ ctx, input }: ActivateEventType endTime: booking.endTime.toISOString(), title: booking.title, language: { locale: booking?.user?.locale || defaultLocale }, - hideOrganizerEmail: booking.eventType?.hideOrganizerEmail, + hideOrganizerEmail: bookingEventType?.hideOrganizerEmail, eventType: { - slug: booking.eventType?.slug || "", - schedulingType: booking.eventType?.schedulingType, - hosts: booking.eventType?.hosts, + slug: bookingEventType?.slug || "", + schedulingType: bookingEventType?.schedulingType, + hosts: bookingEventType?.hosts, }, metadata: booking.metadata, - customReplyToEmail: booking.eventType?.customReplyToEmail, + customReplyToEmail: bookingEventType?.customReplyToEmail, }; for (const step of eventTypeWorkflow.steps) { if ( @@ -449,12 +469,10 @@ export const activateEventTypeHandler = async ({ ctx, input }: ActivateEventType } await prisma.workflowsOnEventTypes.createMany({ - data: [ - { - workflowId, - eventTypeId, - }, - ].concat(userEventType.children.map((ch) => ({ workflowId, eventTypeId: ch.id }))), + data: Array.from(activeOnEventTypes).map(([eventTypeId]) => ({ + workflowId, + eventTypeId, + })), }); const requiresAttendeeNumber = (action: WorkflowActions) => action === WorkflowActions.SMS_ATTENDEE || action === WorkflowActions.WHATSAPP_ATTENDEE; @@ -464,7 +482,12 @@ export const activateEventTypeHandler = async ({ ctx, input }: ActivateEventType return requiresAttendeeNumber(step.action) && step.numberRequired; }); - await upsertSmsReminderFieldForEventTypes({ activeOn, workflowId, isSmsReminderNumberRequired, isOrg }); + await upsertSmsReminderFieldForEventTypes({ + activeOn: Array.from(activeOnEventTypes.keys()), + workflowId, + isSmsReminderNumberRequired, + isOrg, + }); } } };