From c472bce54f6f6ece0d3d2c26d87203eb30a669df Mon Sep 17 00:00:00 2001 From: Lauris Skraucis Date: Thu, 17 Jul 2025 11:02:12 +0200 Subject: [PATCH] fix: v2 managed event type creation hosts (#22584) * fix: v2 managed event type creation hosts * refactor * fix: test * remove uncecessary call --- .../organizations-event-types.e2e-spec.ts | 50 ++++++++++++++ .../event-types/services/input.service.ts | 69 ++++++++++++++----- 2 files changed, 103 insertions(+), 16 deletions(-) diff --git a/apps/api/v2/src/modules/organizations/event-types/organizations-event-types.e2e-spec.ts b/apps/api/v2/src/modules/organizations/event-types/organizations-event-types.e2e-spec.ts index 32fbd4a924..0f1a017f76 100644 --- a/apps/api/v2/src/modules/organizations/event-types/organizations-event-types.e2e-spec.ts +++ b/apps/api/v2/src/modules/organizations/event-types/organizations-event-types.e2e-spec.ts @@ -1158,6 +1158,56 @@ describe("Organizations Event Types Endpoints", () => { }); }); + it("should create a managed team event-type without hosts", async () => { + const body: CreateTeamEventTypeInput_2024_06_14 = { + title: "Coding consultation managed without hosts", + slug: "coding-consultation-managed-without-hosts", + description: "Our team will review your codebase.", + lengthInMinutes: 60, + locations: [ + { + type: "integration", + integration: "cal-video", + }, + ], + schedulingType: "MANAGED", + }; + + return request(app.getHttpServer()) + .post(`/v2/organizations/${org.id}/teams/${team.id}/event-types`) + .send(body) + .expect(201) + .then(async (response) => { + const responseBody: ApiSuccessResponse = response.body; + expect(responseBody.status).toEqual(SUCCESS_STATUS); + + const data = responseBody.data; + expect(data.length).toEqual(1); + + const teammate1EventTypes = await eventTypesRepositoryFixture.getAllUserEventTypes(teammate1.id); + const teammate2EventTypes = await eventTypesRepositoryFixture.getAllUserEventTypes(teammate2.id); + const teamEventTypes = await eventTypesRepositoryFixture.getAllTeamEventTypes(team.id); + + const teammate1HasThisEvent = teammate1EventTypes.some((eventType) => eventType.slug === body.slug); + const teammate2HasThisEvent = teammate2EventTypes.some((eventType) => eventType.slug === body.slug); + expect(teammate1HasThisEvent).toBe(false); + expect(teammate2HasThisEvent).toBe(false); + expect( + teamEventTypes.filter( + (eventType) => eventType.schedulingType === "MANAGED" && eventType.slug === body.slug + ).length + ).toEqual(1); + + const responseTeamEvent = responseBody.data.find((event) => event.teamId === team.id); + expect(responseTeamEvent).toBeDefined(); + expect(responseTeamEvent?.hosts).toEqual([]); + + if (!responseTeamEvent) { + throw new Error("Team event not found"); + } + }); + }); + function evaluateHost(expected: Host, received: Host | undefined) { expect(expected.userId).toEqual(received?.userId); expect(expected.mandatory).toEqual(received?.mandatory); diff --git a/apps/api/v2/src/modules/organizations/event-types/services/input.service.ts b/apps/api/v2/src/modules/organizations/event-types/services/input.service.ts index d7dc77a3f8..7883cab28f 100644 --- a/apps/api/v2/src/modules/organizations/event-types/services/input.service.ts +++ b/apps/api/v2/src/modules/organizations/event-types/services/input.service.ts @@ -121,7 +121,7 @@ export class InputOrganizationsEventTypesService { }, ]; - const children = await this.getChildEventTypesForManagedEventType(null, inputEventType, teamId); + const children = await this.getChildEventTypesForManagedEventTypeCreate(inputEventType, teamId); const metadata = rest.schedulingType === "MANAGED" @@ -159,7 +159,11 @@ export class InputOrganizationsEventTypesService { throw new BadRequestException("Event type to update not found"); } - const children = await this.getChildEventTypesForManagedEventType(eventTypeId, inputEventType, teamId); + const children = + dbEventType.schedulingType === "MANAGED" + ? await this.getChildEventTypesForManagedEventTypeUpdate(eventTypeId, inputEventType, teamId) + : undefined; + const teamEventType = { ...eventType, // note(Lauris): we don't populate hosts for managed event-types because they are handled by the children @@ -176,20 +180,17 @@ export class InputOrganizationsEventTypesService { return teamEventType; } - async getChildEventTypesForManagedEventType( - eventTypeId: number | null, + async getChildEventTypesForManagedEventTypeUpdate( + eventTypeId: number, inputEventType: UpdateTeamEventTypeInput_2024_06_14, teamId: number ) { - let eventType = null; - if (eventTypeId) { - eventType = await this.teamsEventTypesRepository.getEventTypeByIdWithChildren(eventTypeId); - if (!eventType || eventType.schedulingType !== "MANAGED") { - return undefined; - } + const eventType = await this.teamsEventTypesRepository.getEventTypeByIdWithChildren(eventTypeId); + if (!eventType || eventType.schedulingType !== "MANAGED") { + return undefined; } - const ownersIds = await this.getOwnersIdsForManagedEventType(teamId, inputEventType, eventType); + const ownersIds = await this.getOwnersIdsForManagedEventTypeUpdate(teamId, inputEventType, eventType); const owners = await this.getOwnersForManagedEventType(ownersIds); return owners.map((owner) => { @@ -200,22 +201,58 @@ export class InputOrganizationsEventTypesService { }); } - async getOwnersIdsForManagedEventType( + async getOwnersIdsForManagedEventTypeUpdate( teamId: number, inputEventType: UpdateTeamEventTypeInput_2024_06_14, - eventType: { children: { userId: number | null }[] } | null + eventType: { children: { userId: number | null }[] } ) { if (inputEventType.assignAllTeamMembers) { return await this.getTeamUsersIds(teamId); } - // note(Lauris): when API user updates managed event type users if (inputEventType.hosts) { return inputEventType.hosts.map((host) => host.userId); } // note(Lauris): when API user DOES NOT update managed event type users, but we still need existing managed event type users to know which event-types to update - return eventType?.children.map((child) => child.userId).filter((id) => !!id) as number[]; + // e.g if managed event type title is changed then all children managed event types should be updated as well. + const childrenOwnersIds: number[] = []; + for (const child of eventType.children) { + if (child.userId) { + childrenOwnersIds.push(child.userId); + } + } + return childrenOwnersIds; + } + + async getChildEventTypesForManagedEventTypeCreate( + inputEventType: UpdateTeamEventTypeInput_2024_06_14, + teamId: number + ) { + const ownersIds = await this.getOwnersIdsForManagedEventTypeCreate(teamId, inputEventType); + const owners = await this.getOwnersForManagedEventType(ownersIds); + + return owners.map((owner) => { + return { + hidden: false, + owner, + }; + }); + } + + async getOwnersIdsForManagedEventTypeCreate( + teamId: number, + inputEventType: UpdateTeamEventTypeInput_2024_06_14 + ) { + if (inputEventType.assignAllTeamMembers) { + return await this.getTeamUsersIds(teamId); + } + + if (inputEventType.hosts) { + return inputEventType.hosts.map((host) => host.userId); + } + + return []; } async getTeamUsersIds(teamId: number) { @@ -234,7 +271,7 @@ export class InputOrganizationsEventTypesService { } async getOwnersForManagedEventType(userIds: number[]) { - const users = await this.usersRepository.findByIdsWithEventTypes(userIds); + const users = userIds.length ? await this.usersRepository.findByIdsWithEventTypes(userIds) : []; return users.map((user) => { const nonManagedEventTypes = user.eventTypes.filter((eventType) => !eventType.parentId);