fix: add UID filter to /insights/routing (#20356)

This commit is contained in:
Eunjae Lee
2025-03-26 09:52:40 +00:00
committed by GitHub
parent a9ddea9184
commit f699fd1732
8 changed files with 78 additions and 14 deletions
@@ -3037,5 +3037,6 @@
"no_team_members": "You don't have team members yet",
"could_not_find_slug_to_publish_org": "Could not find slug to publish the organization",
"picklist": "Picklist",
"name_or_email": "Name or Email",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Add your new strings above here ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
}
@@ -3,8 +3,8 @@
import { useForm, Controller } from "react-hook-form";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Select } from "@calcom/ui/components/form";
import { Button } from "@calcom/ui/components/button";
import { Select } from "@calcom/ui/components/form";
import { Form } from "@calcom/ui/components/form";
import { Input } from "@calcom/ui/components/form";
@@ -19,7 +19,7 @@ export type TextFilterOptionsProps = {
export function TextFilterOptions({ column }: TextFilterOptionsProps) {
const { t } = useLocale();
const textFilterOperatorOptions = useTextFilterOperatorOptions();
const textFilterOperatorOptions = useTextFilterOperatorOptions(column.textOptions?.allowedOperators);
const filterValue = useFilterValue(column.id, ZTextFilterValue);
const { updateFilter, removeFilter } = useDataTable();
@@ -63,7 +63,13 @@ export function TextFilterOptions({ column }: TextFilterOptionsProps) {
}
}}
/>
{value?.requiresOperand && <Input className="mt-2" {...form.register("operand")} />}
{value?.requiresOperand && (
<Input
className="mt-2"
{...form.register("operand")}
placeholder={column.textOptions?.placeholder}
/>
)}
</>
)}
/>
@@ -1,5 +1,6 @@
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { type TextFilterOperator, textFilterOperators } from "../../lib/types";
import type { TextFilterOperatorOption, NumberFilterOperatorOption } from "./types";
export const numberFilterOperatorOptions: NumberFilterOperatorOption[] = [
@@ -11,9 +12,12 @@ export const numberFilterOperatorOptions: NumberFilterOperatorOption[] = [
{ value: "lte", label: "≤" },
];
export const useTextFilterOperatorOptions = (): TextFilterOperatorOption[] => {
export const useTextFilterOperatorOptions = (
allowedOperators?: TextFilterOperator[]
): TextFilterOperatorOption[] => {
const { t } = useLocale();
return [
const operators = allowedOperators ?? textFilterOperators;
const options = [
{ value: "equals", label: t("filter_operator_is"), requiresOperand: true },
{ value: "notEquals", label: t("filter_operator_is_not"), requiresOperand: true },
{ value: "contains", label: t("filter_operator_contains"), requiresOperand: true },
@@ -23,4 +27,7 @@ export const useTextFilterOperatorOptions = (): TextFilterOperatorOption[] => {
{ value: "isEmpty", label: t("filter_operator_is_empty"), requiresOperand: false },
{ value: "isNotEmpty", label: t("filter_operator_not_empty"), requiresOperand: false },
];
return options.filter((opt): opt is TextFilterOperatorOption =>
operators.includes(opt.value as TextFilterOperator)
);
};
+2 -2
View File
@@ -6,7 +6,7 @@ import {
isNumberFilterValue,
} from "./utils";
type makeWhereClauseProps = {
type MakeWhereClauseProps = {
columnName: string;
filterValue: FilterValue;
json?: true | { path: string[] };
@@ -20,7 +20,7 @@ export function makeOrderBy(sorting: SortingState) {
}));
}
export function makeWhereClause(props: makeWhereClauseProps) {
export function makeWhereClause(props: MakeWhereClauseProps) {
const { columnName, filterValue } = props;
const isJson = props.json === true || (typeof props.json === "object" && props.json.path?.length > 0);
const jsonPath = isJson && typeof props.json === "object" ? props.json.path : undefined;
+14 -3
View File
@@ -10,7 +10,7 @@ export enum ColumnFilterType {
DATE_RANGE = "dr",
}
const textFilterOperators = [
export const textFilterOperators = [
"equals",
"notEquals",
"contains",
@@ -113,14 +113,24 @@ export type DateRangeFilterOptions = {
range: "past" | "custom";
};
export type TextFilterOptions = {
allowedOperators?: TextFilterOperator[];
placeholder?: string;
};
export type ColumnFilterMeta =
| {
type: ColumnFilterType.DATE_RANGE;
icon?: IconName;
dateRangeOptions: DateRangeFilterOptions;
dateRangeOptions?: DateRangeFilterOptions;
}
| {
type?: Exclude<ColumnFilterType, ColumnFilterType.DATE_RANGE>;
type: ColumnFilterType.TEXT;
icon?: IconName;
textOptions?: TextFilterOptions;
}
| {
type?: Exclude<ColumnFilterType, ColumnFilterType.DATE_RANGE | ColumnFilterType.TEXT>;
icon?: IconName;
};
@@ -139,6 +149,7 @@ export type FilterableColumn = {
}
| {
type: ColumnFilterType.TEXT;
textOptions?: TextFilterOptions;
}
| {
type: ColumnFilterType.NUMBER;
@@ -50,10 +50,14 @@ export const RoutingKPICards = () => {
},
];
if (isPending || !data) {
if (isPending) {
return <LoadingKPICards categories={categories} />;
}
if (!data) {
return null;
}
return (
<>
<Grid numColsSm={1} numColsLg={3} className="mt-4 gap-x-4 gap-y-4">
@@ -67,8 +67,16 @@ export const useInsightsColumns = ({
id: "bookingUid",
header: t("uid"),
size: 100,
enableColumnFilter: false,
enableColumnFilter: true,
enableSorting: false,
meta: {
filter: {
type: ColumnFilterType.TEXT,
textOptions: {
allowedOperators: ["equals"],
},
},
},
cell: (info) => {
const bookingUid = info.getValue();
if (!bookingUid) return null;
@@ -95,6 +103,14 @@ export const useInsightsColumns = ({
size: 200,
enableColumnFilter: false,
enableSorting: false,
meta: {
filter: {
type: ColumnFilterType.TEXT,
textOptions: {
placeholder: t("name_or_email"),
},
},
},
cell: (info) => {
return <BookedByCell attendees={info.getValue()} rowId={info.row.original.id} />;
},
@@ -126,6 +126,12 @@ class RoutingEventsInsights {
sorting,
});
if (whereClause.bookingUid) {
// If bookingUid filter is applied, total count should be either 0 or 1.
// So this metrics doesn't provide any value.
return null;
}
const totalPromise = prisma.routingFormResponse.count({
where: whereClause,
});
@@ -217,9 +223,15 @@ class RoutingEventsInsights {
const assignmentReasonValue = bookingAssignmentReason
? getLowercasedFilterValue(bookingAssignmentReason)
: undefined;
const bookingUid = columnFilters.find((filter) => filter.id === "bookingUid") as
| TypedColumnFilter<ColumnFilterType.TEXT>
| undefined;
const responseFilters = columnFilters.filter(
(filter) => filter.id !== "bookingStatusOrder" && filter.id !== "bookingAssignmentReason"
(filter) =>
filter.id !== "bookingStatusOrder" &&
filter.id !== "bookingAssignmentReason" &&
filter.id !== "bookingUid"
);
const whereClause: Prisma.RoutingFormResponseWhereInput = {
@@ -256,6 +268,13 @@ class RoutingEventsInsights {
},
}),
// bookingUid
...(bookingUid &&
makeWhereClause({
columnName: "bookingUid",
filterValue: bookingUid.value,
})),
// AND clause
...(responseFilters.length > 0 && {
AND: responseFilters.map((fieldFilter) => {