chore: remove dead code regarding insights (#19022)

This commit is contained in:
Eunjae Lee
2025-01-31 19:23:40 +01:00
committed by GitHub
parent b7df700771
commit 7bbacca32a
6 changed files with 14 additions and 407 deletions
@@ -1,88 +0,0 @@
import { useState } from "react";
import { useFilterContext } from "@calcom/features/insights/context/provider";
import { downloadAsCsv } from "@calcom/lib/csvUtils";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc";
import type { RouterOutputs } from "@calcom/trpc/react";
import { Button, Dropdown, DropdownItem, DropdownMenuContent, DropdownMenuTrigger } from "@calcom/ui";
import { showToast } from "@calcom/ui";
type RawRoutingData = RouterOutputs["viewer"]["insights"]["rawRoutingData"]["data"][number] | undefined;
interface BatchResult {
data: RawRoutingData[];
nextCursor: number | null;
hasMore: boolean;
}
const RoutingDownload = () => {
const { filter } = useFilterContext();
const { t } = useLocale();
const [isDownloading, setIsDownloading] = useState(false);
const utils = trpc.useUtils();
const fetchBatch = async (cursor: number | null = null) => {
const result = await utils.viewer.insights.rawRoutingData.fetch({
startDate: filter.dateRange[0].toISOString(),
endDate: filter.dateRange[1].toISOString(),
teamId: filter.selectedTeamId ?? undefined,
userId: filter.selectedUserId ?? undefined,
memberUserId: filter.selectedMemberUserId ?? undefined,
routingFormId: filter.selectedRoutingFormId ?? undefined,
bookingStatus: filter.selectedBookingStatus ?? undefined,
fieldFilter: filter.selectedRoutingFormFilter ?? undefined,
isAll: !!filter.isAll,
cursor: cursor ?? undefined,
});
return result;
};
const handleDownloadClick = async () => {
try {
setIsDownloading(true);
let allData: RawRoutingData[] = [];
let hasMore = true;
let cursor: number | null = null;
// Fetch data in batches until there's no more data
while (hasMore) {
const result: BatchResult = await fetchBatch(cursor);
allData = [...allData, ...result.data];
hasMore = result.hasMore;
cursor = result.nextCursor;
}
if (allData.length > 0) {
const filename = `RoutingInsights-${filter.dateRange[0].format(
"YYYY-MM-DD"
)}-${filter.dateRange[1].format("YYYY-MM-DD")}.csv`;
downloadAsCsv(allData as Record<string, unknown>[], filename);
}
} catch (error) {
showToast(t("error_downloading_data"), "error");
} finally {
setIsDownloading(false);
}
};
return (
<Dropdown modal={false}>
<DropdownMenuTrigger asChild>
<Button
EndIcon="file-down"
color="secondary"
className="self-end sm:self-baseline"
loading={isDownloading}>
{t("download")}
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownItem onClick={handleDownloadClick}>{t("as_csv")}</DropdownItem>
</DropdownMenuContent>
</Dropdown>
);
};
export { RoutingDownload };
@@ -1,3 +1,2 @@
export { Download } from "./Download";
export { RoutingDownload } from "./RoutingDownload";
export { RoutingFormResponsesDownload } from "./RoutingFormResponsesDownload";
@@ -1,7 +1,6 @@
import { useMemo } from "react";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc";
import type { IconName } from "@calcom/ui";
import {
Dropdown,
@@ -23,31 +22,10 @@ type Option = {
StartIcon: IconName;
};
export const FilterType = ({ showRoutingFilters = false }: { showRoutingFilters?: boolean }) => {
export const FilterType = () => {
const { t } = useLocale();
const { filter, setConfigFilters } = useFilterContext();
const { selectedFilter, selectedUserId, selectedTeamId, selectedRoutingFormId, isAll, initialConfig } =
filter;
const initialConfigIsReady = !!(initialConfig?.teamId || initialConfig?.userId || initialConfig?.isAll);
// Dynamically load filters if showRoutingFilters is set to true
// Query routing form field options when showRoutingFilters is true
const { data: routingFormFieldOptions } = trpc.viewer.insights.getRoutingFormFieldOptions.useQuery(
{
userId: selectedUserId ?? -1,
teamId: selectedTeamId ?? -1,
isAll: !!isAll,
routingFormId: selectedRoutingFormId ?? undefined,
},
{
enabled: showRoutingFilters && initialConfigIsReady,
trpc: {
context: {
skipBatch: true,
},
},
}
);
const { selectedFilter, selectedUserId } = filter;
const filterOptions = useMemo(() => {
let options: Option[] = [
@@ -58,38 +36,11 @@ export const FilterType = ({ showRoutingFilters = false }: { showRoutingFilters?
},
];
// Add routing forms filter options
if (showRoutingFilters) {
options.push({
label: t("routing_forms"),
value: "routing_forms" as FilterType,
StartIcon: "calendar-check-2" as IconName,
});
options.push({
label: t("booking_status"),
value: "booking_status" as FilterType,
StartIcon: "circle" as IconName,
});
// Add dynamic routing form field options
if (routingFormFieldOptions?.length) {
options = [
...options,
...routingFormFieldOptions.map((option) => ({
label: option.label,
value: `rf_${option.id}` as FilterType,
StartIcon: "layers" as IconName,
})),
];
}
} else {
options.push({
label: t("event_type"),
value: "event-type",
StartIcon: "link",
});
}
options.push({
label: t("event_type"),
value: "event-type",
StartIcon: "link",
});
if (selectedUserId) {
// remove user option from filterOptions
@@ -97,7 +48,7 @@ export const FilterType = ({ showRoutingFilters = false }: { showRoutingFilters?
}
return options;
}, [t, showRoutingFilters, routingFormFieldOptions, selectedUserId]);
}, [t, selectedUserId]);
return (
<Dropdown>
+5 -18
View File
@@ -2,13 +2,10 @@ import { useFilterContext } from "@calcom/features/insights/context/provider";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Button, Icon, Tooltip } from "@calcom/ui";
import { BookingStatusFilter } from "./BookingStatusFilter";
import { DateSelect } from "./DateSelect";
import { Download, RoutingDownload } from "./Download";
import { Download } from "./Download";
import { EventTypeList } from "./EventTypeList";
import { FilterType } from "./FilterType";
import { RoutingFormFieldFilter } from "./RoutingFormFieldFilter";
import { RoutingFormFilterList } from "./RoutingFormFilterList";
import { TeamAndSelfList } from "./TeamAndSelfList";
import { UserListInTeam } from "./UserListInTeam";
@@ -37,7 +34,7 @@ const ClearFilters = () => {
);
};
export const Filters = ({ showRoutingFilters = false }: { showRoutingFilters?: boolean }) => {
export const Filters = () => {
const { filter } = useFilterContext();
const { selectedFilter } = filter;
@@ -51,23 +48,13 @@ export const Filters = ({ showRoutingFilters = false }: { showRoutingFilters?: b
return (
<div className="ml-auto mt-6 flex flex-col gap-2 sm:flex-row sm:flex-wrap sm:justify-between">
<div className="flex flex-col gap-2 sm:flex-row sm:flex-nowrap sm:justify-start">
<TeamAndSelfList omitOrg={showRoutingFilters} />
<TeamAndSelfList omitOrg={false} />
<UserListInTeam />
<EventTypeList />
{showRoutingFilters ? (
<>
<RoutingFormFilterList />
<BookingStatusFilter />
{routingFormFieldIds.map((fieldId) => {
if (fieldId) return <RoutingFormFieldFilter fieldId={fieldId} />;
})}
</>
) : null}
<FilterType showRoutingFilters={showRoutingFilters} />
<FilterType />
<ClearFilters />
</div>
@@ -96,7 +83,7 @@ export const Filters = ({ showRoutingFilters = false }: { showRoutingFilters?: b
</Tooltip>
</ButtonGroup> */}
<div className="flex flex-col-reverse sm:flex-row sm:flex-nowrap sm:justify-between">
{showRoutingFilters ? <RoutingDownload /> : <Download />}
<Download />
<DateSelect className="me-2 ms-2" />
</div>
</div>
@@ -4,10 +4,7 @@ import mapKeys from "lodash/mapKeys";
// eslint-disable-next-line no-restricted-imports
import startCase from "lodash/startCase";
import {
zodFields as routingFormFieldsSchema,
routingFormResponseInDbSchema,
} from "@calcom/app-store/routing-forms/zod";
import { zodFields as routingFormFieldsSchema } from "@calcom/app-store/routing-forms/zod";
import dayjs from "@calcom/dayjs";
import type { ColumnFilter, TypedColumnFilter } from "@calcom/features/data-table";
import { ColumnFilterType } from "@calcom/features/data-table";
@@ -632,185 +629,6 @@ class RoutingEventsInsights {
return headers;
}
static async getRawData({
teamId,
startDate,
endDate,
isAll,
organizationId,
routingFormId,
userId,
memberUserId,
bookingStatus,
fieldFilter,
take,
skip,
}: Omit<RoutingFormInsightsFilter, "columnFilters"> & { take?: number; skip?: number }) {
const formsWhereCondition = await this.getWhereForTeamOrAllTeams({
userId,
teamId,
isAll,
organizationId,
routingFormId,
});
// First get all forms and their fields to build a mapping
const forms = await prisma.app_RoutingForms_Form.findMany({
where: formsWhereCondition,
select: {
id: true,
name: true,
fields: true,
},
});
// Create a mapping of form ID to fields
type FormFieldOption = {
label: string;
type: string;
options: Record<string, string>;
};
type FormFieldsMap = Record<string, Record<string, FormFieldOption>>;
const formFieldsMap = forms.reduce((acc, form) => {
const fields = routingFormFieldsSchema.parse(form.fields);
acc[form.id] =
fields?.reduce((fieldMap: Record<string, FormFieldOption>, field) => {
fieldMap[field.id] = {
label: field.label,
type: field.type,
options:
field.options?.reduce((optMap, opt) => {
if (opt.id !== null) {
optMap[opt.id] = opt.label;
}
return optMap;
}, {} as Record<string, string>) ?? {},
};
return fieldMap;
}, {}) || {};
return acc;
}, {} as FormFieldsMap);
const responsesWhereCondition: Prisma.App_RoutingForms_FormResponseWhereInput = {
...(startDate &&
endDate && {
createdAt: {
gte: dayjs(startDate).startOf("day").toDate(),
lte: dayjs(endDate).endOf("day").toDate(),
},
}),
...(memberUserId || bookingStatus
? {
...(bookingStatus === "NO_BOOKING"
? { routedToBooking: null }
: {
routedToBooking: {
...(memberUserId && { userId: memberUserId }),
...(bookingStatus && { status: bookingStatus }),
},
}),
}
: {}),
...(fieldFilter && {
response: {
path: [fieldFilter.fieldId, "value"],
array_contains: [fieldFilter.optionId],
},
}),
form: formsWhereCondition,
};
const responses = await prisma.app_RoutingForms_FormResponse.findMany({
select: {
id: true,
response: true,
createdAt: true,
form: {
select: {
id: true,
name: true,
},
},
routedToBooking: {
select: {
uid: true,
status: true,
createdAt: true,
startTime: true,
endTime: true,
attendees: {
select: {
email: true,
name: true,
timeZone: true,
},
},
user: {
select: {
name: true,
email: true,
},
},
assignmentReason: {
select: {
reasonString: true,
},
},
},
},
},
where: responsesWhereCondition,
orderBy: {
createdAt: "desc",
},
take: take,
skip: skip,
});
// Transform the data into a flat structure suitable for CSV
return responses.map((response) => {
const parsedResponse = routingFormResponseInDbSchema.parse(response.response);
const formFields = formFieldsMap[response.form.id];
const flatResponse: Record<string, unknown> = {
"Response ID": response.id,
"Form Name": response.form.name,
"Submitted At": response.createdAt.toISOString(),
"Has Booking": !!response.routedToBooking,
"Booking Status": response.routedToBooking?.status || "NO_BOOKING",
"Booking Created At": response.routedToBooking?.createdAt?.toISOString() || "",
"Booking Start Time": response.routedToBooking?.startTime?.toISOString() || "",
"Booking End Time": response.routedToBooking?.endTime?.toISOString() || "",
"Attendee Name": response.routedToBooking?.attendees[0]?.name || "",
"Attendee Email": response.routedToBooking?.attendees[0]?.email || "",
"Attendee Timezone": response.routedToBooking?.attendees[0]?.timeZone || "",
"Assignment Reason": response.routedToBooking?.assignmentReason[0].reasonString || "",
"Routed To Name": response.routedToBooking?.user?.name || "",
"Routed To Email": response.routedToBooking?.user?.email || "",
};
// Add form fields as columns with their labels
Object.entries(parsedResponse).forEach(([fieldId, field]) => {
const fieldInfo = formFields[fieldId];
if (fieldInfo) {
const fieldLabel = fieldInfo.label;
if (Array.isArray(field.value)) {
// For multi-select fields, map the IDs to labels
const values = field.value.map((val) => fieldInfo.options[val] || val);
flatResponse[fieldLabel] = values.join(", ");
} else {
// For single-select fields, map the ID to label
flatResponse[fieldLabel] = fieldInfo.options[field.value] || field.value;
}
}
});
return flatResponse;
});
}
static async routedToPerPeriod({
userId,
teamId,
@@ -1663,66 +1663,6 @@ export const insightsRouter = router({
return headers || [];
}),
rawRoutingData: userBelongsToTeamProcedure
.input(
rawDataInputSchema.extend({
routingFormId: z.string().optional(),
bookingStatus: bookingStatusSchema,
fieldFilter: z
.object({
fieldId: z.string(),
optionId: z.string(),
})
.optional(),
cursor: z.number().optional(),
})
)
.query(async ({ ctx, input }) => {
const {
teamId,
startDate,
endDate,
userId,
memberUserId,
isAll,
routingFormId,
bookingStatus,
fieldFilter,
cursor,
} = input;
if (!teamId && !userId) {
return { data: [], hasMore: false, nextCursor: null };
}
try {
const csvData = await RoutingEventsInsights.getRawData({
teamId,
startDate,
endDate,
userId,
memberUserId,
isAll: isAll ?? false,
organizationId: ctx.user.organizationId,
routingFormId,
bookingStatus,
fieldFilter,
take: BATCH_SIZE,
skip: cursor || 0,
});
const hasMore = csvData.length === BATCH_SIZE;
const nextCursor = hasMore ? (cursor || 0) + BATCH_SIZE : null;
return {
data: csvData,
hasMore,
nextCursor,
};
} catch (e) {
throw new TRPCError({ code: "INTERNAL_SERVER_ERROR" });
}
}),
routedToPerPeriod: userBelongsToTeamProcedure
.input(
rawDataInputSchema.extend({