diff --git a/packages/features/insights/filters/Download/RoutingDownload.tsx b/packages/features/insights/filters/Download/RoutingDownload.tsx deleted file mode 100644 index 4d570db647426f..00000000000000 --- a/packages/features/insights/filters/Download/RoutingDownload.tsx +++ /dev/null @@ -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[], filename); - } - } catch (error) { - showToast(t("error_downloading_data"), "error"); - } finally { - setIsDownloading(false); - } - }; - - return ( - - - - - - {t("as_csv")} - - - ); -}; - -export { RoutingDownload }; diff --git a/packages/features/insights/filters/Download/index.tsx b/packages/features/insights/filters/Download/index.tsx index f5bf0b4d79b308..ed5a52511a41a1 100644 --- a/packages/features/insights/filters/Download/index.tsx +++ b/packages/features/insights/filters/Download/index.tsx @@ -1,3 +1,2 @@ export { Download } from "./Download"; -export { RoutingDownload } from "./RoutingDownload"; export { RoutingFormResponsesDownload } from "./RoutingFormResponsesDownload"; diff --git a/packages/features/insights/filters/FilterType.tsx b/packages/features/insights/filters/FilterType.tsx index ecac07c6770922..44eeed106a7aac 100644 --- a/packages/features/insights/filters/FilterType.tsx +++ b/packages/features/insights/filters/FilterType.tsx @@ -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 ( diff --git a/packages/features/insights/filters/index.tsx b/packages/features/insights/filters/index.tsx index 9d277e5b03edc5..291183d5bf4d56 100644 --- a/packages/features/insights/filters/index.tsx +++ b/packages/features/insights/filters/index.tsx @@ -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 (
- + - {showRoutingFilters ? ( - <> - - - {routingFormFieldIds.map((fieldId) => { - if (fieldId) return ; - })} - - ) : null} - - +
@@ -96,7 +83,7 @@ export const Filters = ({ showRoutingFilters = false }: { showRoutingFilters?: b */}
- {showRoutingFilters ? : } +
diff --git a/packages/features/insights/server/routing-events.ts b/packages/features/insights/server/routing-events.ts index c262acd08e8962..49fe8620cc32c2 100644 --- a/packages/features/insights/server/routing-events.ts +++ b/packages/features/insights/server/routing-events.ts @@ -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 & { 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; - }; - - type FormFieldsMap = Record>; - - const formFieldsMap = forms.reduce((acc, form) => { - const fields = routingFormFieldsSchema.parse(form.fields); - acc[form.id] = - fields?.reduce((fieldMap: Record, 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) ?? {}, - }; - 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 = { - "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, diff --git a/packages/features/insights/server/trpc-router.ts b/packages/features/insights/server/trpc-router.ts index 7a3d553adc8643..d403dbcd4aa97f 100644 --- a/packages/features/insights/server/trpc-router.ts +++ b/packages/features/insights/server/trpc-router.ts @@ -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({