Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ function BookingListInner({
}

export function BookingListContainer(props: BookingListContainerProps) {
const { limit, offset, setPageIndex } = useDataTable();
const { limit, offset, setPageIndex, isValidatorPending } = useDataTable();
const { eventTypeIds, teamIds, userIds, dateRange, attendeeName, attendeeEmail, bookingUid } =
useBookingFilters();

Expand Down Expand Up @@ -272,6 +272,7 @@ export function BookingListContainer(props: BookingListContainerProps) {
const query = trpc.viewer.bookings.get.useQuery(queryInput, {
staleTime: 5 * 60 * 1000, // 5 minutes - data is considered fresh
gcTime: 30 * 60 * 1000, // 30 minutes - cache retention time
enabled: !isValidatorPending, // Wait for validator to be ready before fetching
});

const bookings = useMemo(() => query.data?.bookings ?? [], [query.data?.bookings]);
Expand Down
374 changes: 374 additions & 0 deletions apps/web/modules/bookings/hooks/useActiveFiltersValidator.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,374 @@
import { describe, expect, it } from "vitest";

import { ColumnFilterType } from "@calcom/features/data-table";
import type { ActiveFilters } from "@calcom/features/data-table";

import { createActiveFiltersValidator, type AccessibleResources } from "./useActiveFiltersValidator";

describe("createActiveFiltersValidator", () => {
const defaultAccessibleResources: AccessibleResources = {
userIds: [1, 2, 3],
eventTypeIds: [10, 20, 30],
teamIds: [100, 200, 300],
};

describe("userId filter validation", () => {
it("keeps valid user IDs in multi-select filter", () => {
const validator = createActiveFiltersValidator(defaultAccessibleResources);
const filters: ActiveFilters = [
{
f: "userId",
v: {
type: ColumnFilterType.MULTI_SELECT,
data: [1, 2, 999],
},
},
];

const result = validator(filters);

expect(result).toHaveLength(1);
expect(result[0].f).toBe("userId");
expect(result[0].v).toEqual({
type: ColumnFilterType.MULTI_SELECT,
data: [1, 2],
});
});

it("removes filter when all user IDs are invalid in multi-select", () => {
const validator = createActiveFiltersValidator(defaultAccessibleResources);
const filters: ActiveFilters = [
{
f: "userId",
v: {
type: ColumnFilterType.MULTI_SELECT,
data: [999, 888],
},
},
];

const result = validator(filters);

expect(result).toHaveLength(0);
});

it("keeps valid user ID in single-select filter", () => {
const validator = createActiveFiltersValidator(defaultAccessibleResources);
const filters: ActiveFilters = [
{
f: "userId",
v: {
type: ColumnFilterType.SINGLE_SELECT,
data: 1,
},
},
];

const result = validator(filters);

expect(result).toHaveLength(1);
expect(result[0].f).toBe("userId");
});

it("removes filter when user ID is invalid in single-select", () => {
const validator = createActiveFiltersValidator(defaultAccessibleResources);
const filters: ActiveFilters = [
{
f: "userId",
v: {
type: ColumnFilterType.SINGLE_SELECT,
data: 999,
},
},
];

const result = validator(filters);

expect(result).toHaveLength(0);
});
});

describe("eventTypeId filter validation", () => {
it("keeps valid event type IDs in multi-select filter", () => {
const validator = createActiveFiltersValidator(defaultAccessibleResources);
const filters: ActiveFilters = [
{
f: "eventTypeId",
v: {
type: ColumnFilterType.MULTI_SELECT,
data: [10, 20, 999],
},
},
];

const result = validator(filters);

expect(result).toHaveLength(1);
expect(result[0].f).toBe("eventTypeId");
expect(result[0].v).toEqual({
type: ColumnFilterType.MULTI_SELECT,
data: [10, 20],
});
});

it("removes filter when all event type IDs are invalid in multi-select", () => {
const validator = createActiveFiltersValidator(defaultAccessibleResources);
const filters: ActiveFilters = [
{
f: "eventTypeId",
v: {
type: ColumnFilterType.MULTI_SELECT,
data: [999, 888],
},
},
];

const result = validator(filters);

expect(result).toHaveLength(0);
});

it("keeps valid event type ID in single-select filter", () => {
const validator = createActiveFiltersValidator(defaultAccessibleResources);
const filters: ActiveFilters = [
{
f: "eventTypeId",
v: {
type: ColumnFilterType.SINGLE_SELECT,
data: 10,
},
},
];

const result = validator(filters);

expect(result).toHaveLength(1);
expect(result[0].f).toBe("eventTypeId");
});

it("removes filter when event type ID is invalid in single-select", () => {
const validator = createActiveFiltersValidator(defaultAccessibleResources);
const filters: ActiveFilters = [
{
f: "eventTypeId",
v: {
type: ColumnFilterType.SINGLE_SELECT,
data: 999,
},
},
];

const result = validator(filters);

expect(result).toHaveLength(0);
});
});

describe("teamId filter validation", () => {
it("keeps valid team IDs in multi-select filter", () => {
const validator = createActiveFiltersValidator(defaultAccessibleResources);
const filters: ActiveFilters = [
{
f: "teamId",
v: {
type: ColumnFilterType.MULTI_SELECT,
data: [100, 200, 999],
},
},
];

const result = validator(filters);

expect(result).toHaveLength(1);
expect(result[0].f).toBe("teamId");
expect(result[0].v).toEqual({
type: ColumnFilterType.MULTI_SELECT,
data: [100, 200],
});
});

it("removes filter when all team IDs are invalid in multi-select", () => {
const validator = createActiveFiltersValidator(defaultAccessibleResources);
const filters: ActiveFilters = [
{
f: "teamId",
v: {
type: ColumnFilterType.MULTI_SELECT,
data: [999, 888],
},
},
];

const result = validator(filters);

expect(result).toHaveLength(0);
});

it("keeps valid team ID in single-select filter", () => {
const validator = createActiveFiltersValidator(defaultAccessibleResources);
const filters: ActiveFilters = [
{
f: "teamId",
v: {
type: ColumnFilterType.SINGLE_SELECT,
data: 100,
},
},
];

const result = validator(filters);

expect(result).toHaveLength(1);
expect(result[0].f).toBe("teamId");
});

it("removes filter when team ID is invalid in single-select", () => {
const validator = createActiveFiltersValidator(defaultAccessibleResources);
const filters: ActiveFilters = [
{
f: "teamId",
v: {
type: ColumnFilterType.SINGLE_SELECT,
data: 999,
},
},
];

const result = validator(filters);

expect(result).toHaveLength(0);
});
});

describe("other filters", () => {
it("preserves filters that are not userId, eventTypeId, or teamId", () => {
const validator = createActiveFiltersValidator(defaultAccessibleResources);
const filters: ActiveFilters = [
{
f: "status",
v: {
type: ColumnFilterType.SINGLE_SELECT,
data: "confirmed",
},
},
{
f: "attendeeName",
v: {
type: ColumnFilterType.TEXT,
data: {
operator: "contains",
operand: "John",
},
},
},
];

const result = validator(filters);

expect(result).toHaveLength(2);
expect(result[0].f).toBe("status");
expect(result[1].f).toBe("attendeeName");
});

it("preserves filters without a value", () => {
const validator = createActiveFiltersValidator(defaultAccessibleResources);
const filters: ActiveFilters = [
{
f: "userId",
v: undefined,
},
];

const result = validator(filters);

expect(result).toHaveLength(1);
expect(result[0].f).toBe("userId");
expect(result[0].v).toBeUndefined();
});
});

describe("mixed filters", () => {
it("validates multiple filters correctly", () => {
const validator = createActiveFiltersValidator(defaultAccessibleResources);
const filters: ActiveFilters = [
{
f: "userId",
v: {
type: ColumnFilterType.MULTI_SELECT,
data: [1, 999],
},
},
{
f: "eventTypeId",
v: {
type: ColumnFilterType.MULTI_SELECT,
data: [888],
},
},
{
f: "teamId",
v: {
type: ColumnFilterType.SINGLE_SELECT,
data: 100,
},
},
{
f: "status",
v: {
type: ColumnFilterType.SINGLE_SELECT,
data: "confirmed",
},
},
];

const result = validator(filters);

expect(result).toHaveLength(3);
expect(result[0].f).toBe("userId");
expect(result[0].v).toEqual({
type: ColumnFilterType.MULTI_SELECT,
data: [1],
});
expect(result[1].f).toBe("teamId");
expect(result[2].f).toBe("status");
});
});

describe("empty accessible resources", () => {
it("removes all ID-based filters when no resources are accessible", () => {
const validator = createActiveFiltersValidator({
userIds: [],
eventTypeIds: [],
teamIds: [],
});
const filters: ActiveFilters = [
{
f: "userId",
v: {
type: ColumnFilterType.MULTI_SELECT,
data: [1, 2],
},
},
{
f: "eventTypeId",
v: {
type: ColumnFilterType.SINGLE_SELECT,
data: 10,
},
},
{
f: "status",
v: {
type: ColumnFilterType.SINGLE_SELECT,
data: "confirmed",
},
},
];

const result = validator(filters);

expect(result).toHaveLength(1);
expect(result[0].f).toBe("status");
});
});
});
Loading
Loading