fix: managed event type bug (#27047)

This commit is contained in:
Udit Takkar authored and GitHub committed 2026-01-20 17:29:12 +01:00
1 parent cb6b52b334
commit 6c3b10672c
3 files changed
+198

No files matched your search

@@ -240,6 +240,9 @@ describe("handleChildrenEventTypes", () => {
// @ts-expect-error - partial mock for test purposes
prismaMock.eventType.findMany.mockResolvedValue([{ userId: 4, metadata: {} }]);
// @ts-expect-error - partial mock for test purposes
prismaMock.eventType.update.mockResolvedValue({ id: 100 });
const result = await updateChildrenEventTypes({
eventTypeId: 1,
oldEventType: { children: [{ userId: 4 }], team: { name: "" } },
@@ -313,6 +316,9 @@ describe("handleChildrenEventTypes", () => {
// @ts-expect-error - partial mock for test purposes
prismaMock.eventType.findMany.mockResolvedValue([{ userId: 4, metadata: {} }]);
// @ts-expect-error - partial mock for test purposes
prismaMock.eventType.update.mockResolvedValue({ id: 101 });
const result = await updateChildrenEventTypes({
eventTypeId: 1,
oldEventType: { children: [{ userId: 4 }, { userId: 1 }], team: { name: "" } },
@@ -450,6 +456,9 @@ describe("handleChildrenEventTypes", () => {
// @ts-expect-error - partial mock for test purposes
prismaMock.eventType.findMany.mockResolvedValue([{ userId: 4, metadata: {} }]);
// @ts-expect-error - partial mock for test purposes
prismaMock.eventType.update.mockResolvedValue({ id: 102 });
prismaMock.eventType.deleteMany.mockResolvedValue([123] as unknown as Prisma.BatchPayload);
const result = await updateChildrenEventTypes({
eventTypeId: 1,
@@ -960,4 +969,98 @@ describe("handleChildrenEventTypes", () => {
});
});
});
describe("CalVideoSettings propagation", () => {
it("Creates CalVideoSettings for new children when parent has settings", async () => {
const { schedulingType, teamId, timeZone, ...evType } = mockFindFirstEventType({
id: 123,
metadata: { managedEventConfig: {} },
locations: [],
});
setupTransactionMock();
prismaMock.eventType.createManyAndReturn.mockResolvedValue([
{ ...evType, id: 456, userId: 4, schedulingType, teamId, timeZone } as unknown as EventType,
]);
await updateChildrenEventTypes({
eventTypeId: 1,
oldEventType: { children: [], team: { name: "" } },
children: [{ hidden: false, owner: { id: 4, name: "", email: "", eventTypeSlugs: [] } }],
updatedEventType: { schedulingType: "MANAGED", slug: "something" },
currentUserId: 1,
prisma: prismaMock,
profileId: null,
updatedValues: {},
calVideoSettings: {
enableAutomaticRecordingForOrganizer: true,
enableAutomaticTranscription: true,
},
});
expect(prismaMock.calVideoSettings.createMany).toHaveBeenCalledWith({
data: [
expect.objectContaining({
eventTypeId: 456,
enableAutomaticRecordingForOrganizer: true,
enableAutomaticTranscription: true,
}),
],
skipDuplicates: true,
});
});
it("Syncs/deletes CalVideoSettings for existing children based on parent settings", async () => {
const mockExistingChildUpdate = () => {
mockFindFirstEventType({ metadata: { managedEventConfig: {} }, locations: [] });
// @ts-expect-error - partial mock
prismaMock.eventType.findMany.mockResolvedValue([{ userId: 4, metadata: {} }]);
prismaMock.eventType.update.mockResolvedValue({
id: 789,
userId: 4,
schedulingType: SchedulingType.MANAGED,
} as unknown as EventType);
};
const baseParams = {
eventTypeId: 1,
oldEventType: { children: [{ userId: 4 }], team: { name: "" } },
children: [{ hidden: false, owner: { id: 4, name: "", email: "", eventTypeSlugs: [] } }],
updatedEventType: { schedulingType: "MANAGED" as const, slug: "something" },
currentUserId: 1,
prisma: prismaMock,
profileId: null,
updatedValues: {},
};
// Test upsert when calVideoSettings provided
mockExistingChildUpdate();
await updateChildrenEventTypes({
...baseParams,
calVideoSettings: { enableAutomaticRecordingForOrganizer: true },
});
expect(prismaMock.calVideoSettings.upsert).toHaveBeenCalledWith(
expect.objectContaining({
where: { eventTypeId: 789 },
update: expect.objectContaining({ enableAutomaticRecordingForOrganizer: true }),
create: expect.objectContaining({ eventTypeId: 789, enableAutomaticRecordingForOrganizer: true }),
})
);
// Test deleteMany when calVideoSettings is null
vi.clearAllMocks();
mockExistingChildUpdate();
await updateChildrenEventTypes({ ...baseParams, calVideoSettings: null });
expect(prismaMock.calVideoSettings.deleteMany).toHaveBeenCalledWith({
where: { eventTypeId: { in: [789] } },
});
// Test no-op when calVideoSettings is undefined
vi.clearAllMocks();
mockExistingChildUpdate();
await updateChildrenEventTypes({ ...baseParams, calVideoSettings: undefined });
expect(prismaMock.calVideoSettings.upsert).not.toHaveBeenCalled();
expect(prismaMock.calVideoSettings.deleteMany).not.toHaveBeenCalled();
});
});
});
@@ -40,6 +40,16 @@ interface handleChildrenEventTypesProps {
| undefined;
prisma: PrismaClient | DeepMockProxy<PrismaClient>;
updatedValues: Prisma.EventTypeUpdateInput;
calVideoSettings?: {
disableRecordingForGuests?: boolean | null;
disableRecordingForOrganizer?: boolean | null;
enableAutomaticTranscription?: boolean | null;
enableAutomaticRecordingForOrganizer?: boolean | null;
disableTranscriptionForGuests?: boolean | null;
disableTranscriptionForOrganizer?: boolean | null;
redirectUrlOnExit?: string | null;
requireEmailForGuests?: boolean | null;
} | null;
}
const sendAllSlugReplacementEmails = async (
@@ -138,6 +148,7 @@ export default async function handleChildrenEventTypes({
prisma,
profileId,
updatedValues: _updatedValues,
calVideoSettings,
}: handleChildrenEventTypesProps) {
// Check we are dealing with a managed event type
if (updatedEventType?.schedulingType !== SchedulingType.MANAGED)
@@ -269,6 +280,26 @@ export default async function handleChildrenEventTypes({
skipDuplicates: true,
});
}
// Create CalVideoSettings for new children if parent has settings
if (calVideoSettings && createdEvents.length > 0) {
const calVideoSettingsToCreate = createdEvents.map((event) => ({
eventTypeId: event.id,
disableRecordingForGuests: calVideoSettings.disableRecordingForGuests ?? false,
disableRecordingForOrganizer: calVideoSettings.disableRecordingForOrganizer ?? false,
enableAutomaticTranscription: calVideoSettings.enableAutomaticTranscription ?? false,
enableAutomaticRecordingForOrganizer: calVideoSettings.enableAutomaticRecordingForOrganizer ?? false,
disableTranscriptionForGuests: calVideoSettings.disableTranscriptionForGuests ?? false,
disableTranscriptionForOrganizer: calVideoSettings.disableTranscriptionForOrganizer ?? false,
redirectUrlOnExit: calVideoSettings.redirectUrlOnExit ?? null,
requireEmailForGuests: calVideoSettings.requireEmailForGuests ?? false,
}));
await tx.calVideoSettings.createMany({
data: calVideoSettingsToCreate,
skipDuplicates: true,
});
}
});
}
@@ -422,6 +453,58 @@ export default async function handleChildrenEventTypes({
}
});
}
// Sync CalVideoSettings to existing children
if (oldEventTypes.length > 0) {
const childEventTypeIds = oldEventTypes.map((e) => e.id);
const BATCH_SIZE = 50;
if (calVideoSettings) {
// Parent has CalVideoSettings - upsert for all children
for (let i = 0; i < childEventTypeIds.length; i += BATCH_SIZE) {
const batch = childEventTypeIds.slice(i, i + BATCH_SIZE);
await Promise.all(
batch.map((eventTypeId) =>
prisma.calVideoSettings.upsert({
where: { eventTypeId },
update: {
disableRecordingForGuests: calVideoSettings.disableRecordingForGuests ?? false,
disableRecordingForOrganizer: calVideoSettings.disableRecordingForOrganizer ?? false,
enableAutomaticTranscription: calVideoSettings.enableAutomaticTranscription ?? false,
enableAutomaticRecordingForOrganizer:
calVideoSettings.enableAutomaticRecordingForOrganizer ?? false,
disableTranscriptionForGuests: calVideoSettings.disableTranscriptionForGuests ?? false,
disableTranscriptionForOrganizer: calVideoSettings.disableTranscriptionForOrganizer ?? false,
redirectUrlOnExit: calVideoSettings.redirectUrlOnExit ?? null,
requireEmailForGuests: calVideoSettings.requireEmailForGuests ?? false,
updatedAt: new Date(),
},
create: {
eventTypeId,
disableRecordingForGuests: calVideoSettings.disableRecordingForGuests ?? false,
disableRecordingForOrganizer: calVideoSettings.disableRecordingForOrganizer ?? false,
enableAutomaticTranscription: calVideoSettings.enableAutomaticTranscription ?? false,
enableAutomaticRecordingForOrganizer:
calVideoSettings.enableAutomaticRecordingForOrganizer ?? false,
disableTranscriptionForGuests: calVideoSettings.disableTranscriptionForGuests ?? false,
disableTranscriptionForOrganizer: calVideoSettings.disableTranscriptionForOrganizer ?? false,
redirectUrlOnExit: calVideoSettings.redirectUrlOnExit ?? null,
requireEmailForGuests: calVideoSettings.requireEmailForGuests ?? false,
},
})
)
);
}
} else if (calVideoSettings === null) {
// Parent's CalVideoSettings were removed - delete from all children
await prisma.calVideoSettings.deleteMany({
where: {
eventTypeId: { in: childEventTypeIds },
},
});
}
// Note: If calVideoSettings is undefined, don't touch children's settings
}
}
// Old users deleted
@@ -716,6 +716,17 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => {
return acc;
}, {});
// Determine calVideoSettings to pass to children:
// - If calVideoSettings provided in input, sync to children
// - If Cal Video location removed, delete from children (pass null)
// - Otherwise, leave children's settings untouched (pass undefined)
let calVideoSettingsForChildren: typeof calVideoSettings | null | undefined = undefined;
if (calVideoSettings !== undefined) {
calVideoSettingsForChildren = calVideoSettings;
} else if (eventType.calVideoSettings && !isCalVideoLocationActive) {
calVideoSettingsForChildren = null;
}
// Handling updates to children event types (managed events types)
await updateChildrenEventTypes({
eventTypeId: id,
@@ -726,6 +737,7 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => {
profileId: ctx.user.profile.id,
prisma: ctx.prisma,
updatedValues,
calVideoSettings: calVideoSettingsForChildren,
});
// Clean up empty host groups