fix: add UID filter to /insights/routing (#20356)
This commit is contained in:
@@ -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)
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
Reference in New Issue
Block a user