diff --git a/apps/web/playwright/bookings-list.e2e.ts b/apps/web/playwright/bookings-list.e2e.ts index 4c132ff30c..b2d0fa3f86 100644 --- a/apps/web/playwright/bookings-list.e2e.ts +++ b/apps/web/playwright/bookings-list.e2e.ts @@ -2,7 +2,7 @@ import { expect } from "@playwright/test"; import prisma from "@calcom/prisma"; import { BookingStatus } from "@calcom/prisma/client"; -import { MembershipRole } from "@calcom/prisma/enums"; +import { MembershipRole, SchedulingType } from "@calcom/prisma/enums"; import { createTeamEventType } from "./fixtures/users"; import type { Fixtures } from "./lib/fixtures"; @@ -378,6 +378,80 @@ test.describe("Bookings", () => { thirdUpcomingBooking.locator(`text=${thirdUserAttendeeTeamEvent!.title}`) ).toBeVisible(); }); + + test("Does not show booking from another user from collective event type when a member is filtered", async ({ + page, + users, + bookings, + }) => { + const teamMatesObj = [ + { name: "teammate-1" }, + { name: "teammate-2" }, + { name: "teammate-3" }, + { name: "teammate-4" }, + ]; + const owner = await users.create( + { username: "pro-user", name: "pro-user" }, + { + hasTeam: true, + teammates: teamMatesObj, + schedulingType: SchedulingType.COLLECTIVE, + } + ); + + const { team } = await owner.getFirstTeamMembership(); + const eventType = await owner.getFirstTeamEvent(team.id); + const { id: eventTypeId, title: teamEventTitle, slug: teamEventSlug } = eventType; + + // remove myself from host of this event type + await prisma.host.delete({ + where: { + userId_eventTypeId: { + userId: owner.id, + eventTypeId, + }, + }, + }); + + // teammate-1 + const host = await prisma.membership.findFirstOrThrow({ + where: { + teamId: team.id, + userId: { + not: owner.id, + }, + }, + include: { + user: true, + }, + }); + + await createBooking({ + bookingsFixture: bookings, + organizer: host.user, + organizerEventType: eventType, + attendees: [{ name: "test user", email: "test@example.com", timeZone: "Europe/Paris" }], + relativeDate: 0, + title: "Booking from test user", + }); + + // teammate-2 + const anotherUser = teamMatesObj.find((m) => m.name !== host.user.name)?.name; + + await owner.apiLogin(); + await page.goto("/bookings/upcoming"); + await page.waitForResponse((response) => /\/api\/trpc\/bookings\/get.*/.test(response.url())); + + await page.locator('[data-testid="add-filter-button"]').click(); + await page.locator('[data-testid="add-filter-item-userId"]').click(); + await page.locator('[data-testid="filter-popover-trigger-userId"]').click(); + await page + .locator(`[data-testid="multi-select-options-userId"] [role="option"]:has-text("${anotherUser}")`) + .click(); + await page.waitForResponse((response) => /\/api\/trpc\/bookings\/get.*/.test(response.url())); + + await expect(page.locator('[data-testid="booking-item"]')).toHaveCount(0); + }); }); async function createBooking({ diff --git a/packages/trpc/server/routers/viewer/bookings/get.handler.ts b/packages/trpc/server/routers/viewer/bookings/get.handler.ts index 235b996174..cd8bc207be 100644 --- a/packages/trpc/server/routers/viewer/bookings/get.handler.ts +++ b/packages/trpc/server/routers/viewer/bookings/get.handler.ts @@ -170,13 +170,13 @@ export async function getBookings({ const [ eventTypeIdsFromTeamIdsFilter, - { attendeeEmailsFromUserIdsFilter, eventTypeIdsFromUserIdsFilter }, + attendeeEmailsFromUserIdsFilter, eventTypeIdsFromEventTypeIdsFilter, eventTypeIdsWhereUserIsAdminOrOwener, userIdsWhereUserIsOrgAdminOrOwener, ] = await Promise.all([ getEventTypeIdsFromTeamIdsFilter(prisma, filters?.teamIds), - getIdsFromUserIdsFilter(prisma, user.email, filters?.userIds), + getAttendeeEmailsFromUserIdsFilter(prisma, user.email, filters?.userIds), getEventTypeIdsFromEventTypeIdsFilter(prisma, filters?.eventTypeIds), getEventTypeIdsWhereUserIsAdminOrOwner(prisma, membershipConditionWhereUserIsAdminOwner), getUserIdsWhereUserIsOrgAdminOrOwner(prisma, membershipConditionWhereUserIsAdminOwner), @@ -226,7 +226,7 @@ export async function getBookings({ }, ] : []), - ...(filters?.userIds && filters.userIds.length > 0 && eventTypeIdsFromUserIdsFilter + ...(filters?.userIds && filters.userIds.length > 0 ? [ { OR: [ @@ -235,15 +235,6 @@ export async function getBookings({ in: filters.userIds, }, }, - ...(eventTypeIdsFromUserIdsFilter?.length - ? [ - { - eventTypeId: { - in: eventTypeIdsFromUserIdsFilter, - }, - }, - ] - : []), ...(attendeeEmailsFromUserIdsFilter?.length ? [ { @@ -491,71 +482,30 @@ async function getEventTypeIdsFromTeamIdsFilter(prisma: PrismaClient, teamIds?: return Array.from(new Set([...directTeamEventTypeIds, ...parentTeamEventTypeIds])); } -async function getIdsFromUserIdsFilter(prisma: PrismaClient, userEmail: string, userIds?: number[]) { +async function getAttendeeEmailsFromUserIdsFilter( + prisma: PrismaClient, + userEmail: string, + userIds?: number[] +) { if (!userIds || userIds.length === 0) { - return { - eventTypeIdsFromUserIdsFilter: undefined, - attendeeEmailsFromUserIdsFilter: undefined, - }; + return; } - const [attendeeEmailsFromUserIdsFilter, eventTypeIdsFromHostsFilter, eventTypeIdsFromUsersFilter] = - await Promise.all([ - prisma.user - .findMany({ - where: { - id: { - in: userIds, - }, - }, - select: { - email: true, - }, - }) - // Include booking if current user is an attendee, regardless of user ID filter - .then((users) => users.map((user) => user.email).concat([userEmail])), + const attendeeEmailsFromUserIdsFilter = await prisma.user + .findMany({ + where: { + id: { + in: userIds, + }, + }, + select: { + email: true, + }, + }) + // Include booking if current user is an attendee, regardless of user ID filter + .then((users) => users.map((user) => user.email).concat([userEmail])); - prisma.eventType - .findMany({ - where: { - hosts: { - some: { - userId: { - in: userIds, - }, - isFixed: true, - }, - }, - }, - select: { - id: true, - }, - }) - .then((eventTypes) => eventTypes.map((eventType) => eventType.id)), - - prisma.eventType - .findMany({ - where: { - users: { - some: { - id: { - in: userIds, - }, - }, - }, - }, - select: { - id: true, - }, - }) - .then((eventTypes) => eventTypes.map((eventType) => eventType.id)), - ]); - - const eventTypeIdsFromUserIdsFilter = Array.from( - new Set([...eventTypeIdsFromHostsFilter, ...eventTypeIdsFromUsersFilter]) - ); - - return { attendeeEmailsFromUserIdsFilter, eventTypeIdsFromUserIdsFilter }; + return attendeeEmailsFromUserIdsFilter; } async function getEventTypeIdsFromEventTypeIdsFilter(prisma: PrismaClient, eventTypeIds?: number[]) {