From b2763a627481e66047e858a14a3ce2f8d815cab3 Mon Sep 17 00:00:00 2001 From: Vijay Date: Fri, 14 Feb 2025 14:38:57 +0530 Subject: [PATCH] fix: Default people filter in bookings list for admin (#18035) * default people filter for admin * chore * type check * added test case * made test concise to reduce flakiness * add timeout to wait for second get response before counting * update timeout --------- Co-authored-by: Peer Richelsen --- .../apps/[slug]/setup/page.tsx | 2 +- .../bookings/views/bookings-listing-view.tsx | 11 ++- apps/web/playwright/bookings-list.e2e.ts | 93 ++++++++++++++++++- .../bookings/components/FiltersContainer.tsx | 1 + .../routers/loggedInViewer/me.handler.ts | 15 ++- 5 files changed, 117 insertions(+), 5 deletions(-) diff --git a/apps/web/app/(use-page-wrapper)/apps/[slug]/setup/page.tsx b/apps/web/app/(use-page-wrapper)/apps/[slug]/setup/page.tsx index 6ac89e6455..4410b8cc71 100644 --- a/apps/web/app/(use-page-wrapper)/apps/[slug]/setup/page.tsx +++ b/apps/web/app/(use-page-wrapper)/apps/[slug]/setup/page.tsx @@ -32,4 +32,4 @@ const ServerPage = async ({ params, searchParams }: ServerPageProps) => { return ; }; -export default ServerPage; \ No newline at end of file +export default ServerPage; diff --git a/apps/web/modules/bookings/views/bookings-listing-view.tsx b/apps/web/modules/bookings/views/bookings-listing-view.tsx index 17ae45d8bb..70d8458f56 100644 --- a/apps/web/modules/bookings/views/bookings-listing-view.tsx +++ b/apps/web/modules/bookings/views/bookings-listing-view.tsx @@ -7,7 +7,7 @@ import { getSortedRowModel, createColumnHelper, } from "@tanstack/react-table"; -import { useMemo, useState } from "react"; +import { useMemo, useState, useEffect } from "react"; import type { z } from "zod"; import { WipeMyCalActionButton } from "@calcom/app-store/wipemycalother/components"; @@ -95,12 +95,19 @@ type RowData = }; function BookingsContent({ status }: BookingsProps) { - const { data: filterQuery } = useFilterQuery(); + const { data: filterQuery, pushItemToKey } = useFilterQuery(); const { t } = useLocale(); const user = useMeQuery().data; const [isFiltersVisible, setIsFiltersVisible] = useState(false); + useEffect(() => { + if (user?.isTeamAdminOrOwner && !filterQuery.userIds?.length) { + setIsFiltersVisible(true); + pushItemToKey("userIds", user?.id); + } + }, [user, filterQuery.status]); + const query = trpc.viewer.bookings.get.useInfiniteQuery( { limit: 10, diff --git a/apps/web/playwright/bookings-list.e2e.ts b/apps/web/playwright/bookings-list.e2e.ts index 6ee5ff75e4..1b25d303de 100644 --- a/apps/web/playwright/bookings-list.e2e.ts +++ b/apps/web/playwright/bookings-list.e2e.ts @@ -1,10 +1,12 @@ import { expect } from "@playwright/test"; +import { prisma } from "@calcom/prisma"; import { BookingStatus } from "@calcom/prisma/client"; +import { MembershipRole } from "@calcom/prisma/enums"; import type { Fixtures } from "./lib/fixtures"; import { test } from "./lib/fixtures"; -import { setupManagedEvent } from "./lib/testUtils"; +import { localize, setupManagedEvent } from "./lib/testUtils"; test.afterEach(({ users }) => users.deleteAll()); @@ -207,6 +209,95 @@ test.describe("Bookings", () => { webhookReceiver.close(); }); }); + test("Admin bookings filtered by default", async ({ page, users, bookings }) => { + const t = await localize("en"); + const firstUser = await users.create( + { name: "First", email: "first@cal.com" }, + { + hasTeam: true, + teamRole: MembershipRole.ADMIN, + } + ); + const teamId = (await firstUser.getFirstTeamMembership()).teamId; + const secondUser = await users.create({ name: "Second", email: "second@cal.com" }); + const thirdUser = await users.create({ name: "Third", email: "third@cal.com" }); + // Add teammates to the team + await prisma.membership.createMany({ + data: [ + { + teamId: teamId, + userId: secondUser.id, + role: MembershipRole.MEMBER, + accepted: true, + }, + { + teamId: teamId, + userId: thirdUser.id, + role: MembershipRole.MEMBER, + accepted: true, + }, + ], + }); + + //Create a single booking for FirstUser(admin) + const firstUserBookingFixture = await createBooking({ + title: "FirstUser as Organizer Meeting", + bookingsFixture: bookings, + relativeDate: 3, + organizer: firstUser, + organizerEventType: firstUser.eventTypes[0], + attendees: [ + { name: "Second", email: secondUser.email, timeZone: "Europe/Berlin" }, + { name: "Third", email: thirdUser.email, timeZone: "Europe/Berlin" }, + ], + }); + const firstUserBooking = await firstUserBookingFixture.self(); + + //Create 2 bookings for SecondUser + await createBooking({ + title: "SecondUser as Organizer Meeting 1", + bookingsFixture: bookings, + organizer: secondUser, + relativeDate: 2, + organizerEventType: secondUser.eventTypes[0], + attendees: [ + { name: "First", email: firstUser.email, timeZone: "Europe/Berlin" }, + { name: "Third", email: thirdUser.email, timeZone: "Europe/Berlin" }, + ], + }); + await createBooking({ + title: "SecondUser as Organizer Meeting 2", + bookingsFixture: bookings, + organizer: secondUser, + relativeDate: 4, + organizerEventType: secondUser.eventTypes[0], + attendees: [ + { name: "First", email: firstUser.email, timeZone: "Europe/Berlin" }, + { name: "Third", email: thirdUser.email, timeZone: "Europe/Berlin" }, + ], + }); + + //admin login + await firstUser.apiLogin(); + await Promise.all([ + page.waitForResponse((response) => /\/api\/trpc\/bookings\/get.*/.test(response.url())), + page.waitForResponse((response) => /\/api\/trpc\/bookings\/get.*/.test(response.url())), + page.goto(`/bookings/upcoming`), + page.waitForTimeout(10000), + page.waitForURL(`**\/upcoming?status=upcoming&userIds=${firstUser.id}`), + ]); + + //expect only 1 booking (of admin) to be shown in list due to default filtering for admin + const upcomingBookingsTable = page.locator('[data-testid="upcoming-bookings"]'); + const bookingListItems = upcomingBookingsTable.locator('[data-testid="booking-item"]'); + const bookingListCount = await bookingListItems.count(); + expect(bookingListCount).toBe(1); + const firstUpcomingBooking = bookingListItems.nth(0); + await expect( + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + firstUpcomingBooking.locator(`text=${firstUserBooking!.title}`) + ).toBeVisible(); + }); }); async function createBooking({ diff --git a/packages/features/bookings/components/FiltersContainer.tsx b/packages/features/bookings/components/FiltersContainer.tsx index 373d61f54a..54fbf20b1d 100644 --- a/packages/features/bookings/components/FiltersContainer.tsx +++ b/packages/features/bookings/components/FiltersContainer.tsx @@ -35,6 +35,7 @@ export function FiltersContainer({ isFiltersVisible }: FiltersContainerProps) {