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 <peeroke@gmail.com>
This commit is contained in:
@@ -32,4 +32,4 @@ const ServerPage = async ({ params, searchParams }: ServerPageProps) => {
|
||||
return <SetupView {...props} />;
|
||||
};
|
||||
|
||||
export default ServerPage;
|
||||
export default ServerPage;
|
||||
|
||||
@@ -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<boolean>(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,
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -35,6 +35,7 @@ export function FiltersContainer({ isFiltersVisible }: FiltersContainerProps) {
|
||||
<StartTimeFilters />
|
||||
<Tooltip content={t("remove_filters")}>
|
||||
<Button
|
||||
data-testid="btn_bookings_list_remove_filters"
|
||||
disabled={!hasValidQueryParams}
|
||||
color="secondary"
|
||||
type="button"
|
||||
|
||||
@@ -4,7 +4,7 @@ import { getUserAvatarUrl } from "@calcom/lib/getAvatarUrl";
|
||||
import { ProfileRepository } from "@calcom/lib/server/repository/profile";
|
||||
import { UserRepository } from "@calcom/lib/server/repository/user";
|
||||
import prisma from "@calcom/prisma";
|
||||
import { IdentityProvider } from "@calcom/prisma/enums";
|
||||
import { IdentityProvider, MembershipRole } from "@calcom/prisma/enums";
|
||||
import { userMetadata } from "@calcom/prisma/zod-utils";
|
||||
import type { TrpcSessionUser } from "@calcom/trpc/server/trpc";
|
||||
|
||||
@@ -94,6 +94,18 @@ export const meHandler = async ({ ctx, input }: MeOptions) => {
|
||||
organizationSettings: user?.profile?.organization?.organizationSettings,
|
||||
};
|
||||
|
||||
const isTeamAdminOrOwner =
|
||||
(await prisma.membership.findFirst({
|
||||
where: {
|
||||
userId: user.id,
|
||||
accepted: true,
|
||||
role: { in: [MembershipRole.ADMIN, MembershipRole.OWNER] },
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
})) !== null;
|
||||
|
||||
return {
|
||||
id: user.id,
|
||||
name: user.name,
|
||||
@@ -132,5 +144,6 @@ export const meHandler = async ({ ctx, input }: MeOptions) => {
|
||||
secondaryEmails,
|
||||
isPremium: userMetadataPrased?.isPremium,
|
||||
...(passwordAdded ? { passwordAdded } : {}),
|
||||
isTeamAdminOrOwner,
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user