diff --git a/packages/features/insights/components/RoutingFormResponsesTable.tsx b/packages/features/insights/components/RoutingFormResponsesTable.tsx
index cc62b9d828..acb7f46185 100644
--- a/packages/features/insights/components/RoutingFormResponsesTable.tsx
+++ b/packages/features/insights/components/RoutingFormResponsesTable.tsx
@@ -1,6 +1,5 @@
"use client";
-import { keepPreviousData } from "@tanstack/react-query";
import {
useReactTable,
getCoreRowModel,
@@ -55,34 +54,25 @@ export function RoutingFormResponsesTable() {
const getInsightsFacetedUniqueValues = useInsightsFacetedUniqueValues({ headers, userId, teamId, isAll });
- const { sorting, updateFilter } = useDataTable();
+ const { sorting, limit, offset, updateFilter } = useDataTable();
- const { data, fetchNextPage, isFetching, isPending, hasNextPage, isLoading } =
- trpc.viewer.insights.routingFormResponses.useInfiniteQuery(
- {
- teamId,
- startDate,
- endDate,
- userId,
- memberUserIds,
- isAll,
- routingFormId,
- columnFilters,
- sorting,
- limit: 30,
- },
- {
- getNextPageParam: (lastPage) => lastPage.nextCursor,
- placeholderData: keepPreviousData,
- trpc: {
- context: { skipBatch: true },
- },
- }
- );
+ const { data, isFetching, isPending, isLoading } = trpc.viewer.insights.routingFormResponses.useQuery({
+ teamId,
+ startDate,
+ endDate,
+ userId,
+ memberUserIds,
+ isAll,
+ routingFormId,
+ columnFilters,
+ sorting,
+ limit,
+ offset,
+ });
const processedData = useMemo(() => {
- if (!isHeadersSuccess) return [];
- return (data?.pages?.flatMap((page) => page.data) ?? []) as RoutingFormTableRow[];
+ if (!isHeadersSuccess || !data) return [];
+ return data.data as RoutingFormTableRow[];
}, [data, isHeadersSuccess]);
const columns = useInsightsColumns({ headers, isHeadersSuccess });
@@ -118,7 +108,7 @@ export function RoutingFormResponsesTable() {
}
}, [table, getInsightsFacetedUniqueValues, routingFormId]);
- if ((isHeadersLoading && !headers) || ((isFetching || isLoading) && !data)) {
+ if (isHeadersLoading && !headers) {
return ;
}
@@ -128,10 +118,9 @@ export function RoutingFormResponsesTable() {
}
ToolbarLeft={
<>
diff --git a/packages/features/insights/filters/Download/RoutingFormResponsesDownload.tsx b/packages/features/insights/filters/Download/RoutingFormResponsesDownload.tsx
index d5b6f29fd3..a54199be71 100644
--- a/packages/features/insights/filters/Download/RoutingFormResponsesDownload.tsx
+++ b/packages/features/insights/filters/Download/RoutingFormResponsesDownload.tsx
@@ -17,10 +17,7 @@ type Props = {
sorting: SortingState;
};
-type Batch = {
- data: RoutingData[];
- nextCursor: number | undefined;
-};
+const BATCH_SIZE = 100; // Increased batch size for downloads
export const RoutingFormResponsesDownload = ({ sorting }: Props) => {
const { t } = useLocale();
@@ -31,12 +28,12 @@ export const RoutingFormResponsesDownload = ({ sorting }: Props) => {
const utils = trpc.useUtils();
const fetchBatch = async (
- cursor: number | undefined = undefined
+ offset: number
): Promise<{
data: RoutingData[];
- nextCursor: number | undefined;
+ total: number;
}> => {
- const { data, nextCursor } = await utils.viewer.insights.routingFormResponsesForDownload.fetch({
+ const result = await utils.viewer.insights.routingFormResponsesForDownload.fetch({
teamId,
startDate,
endDate,
@@ -46,27 +43,28 @@ export const RoutingFormResponsesDownload = ({ sorting }: Props) => {
routingFormId,
columnFilters,
sorting,
- cursor,
+ limit: BATCH_SIZE,
+ offset,
});
- return {
- data,
- nextCursor,
- };
+ return result;
};
const handleDownloadClick = async () => {
try {
setIsDownloading(true);
let allData: RoutingData[] = [];
- let hasMore = true;
- let cursor: number | undefined = undefined;
+ let offset = 0;
- // Fetch data in batches until there's no more data
- while (hasMore) {
- const result: Batch = await fetchBatch(cursor);
+ // Get first batch to get total count
+ const firstBatch = await fetchBatch(0);
+ allData = [...firstBatch.data];
+ const totalRecords = firstBatch.total;
+
+ // Continue fetching remaining batches
+ while (allData.length < totalRecords) {
+ offset += BATCH_SIZE;
+ const result = await fetchBatch(offset);
allData = [...allData, ...result.data];
- hasMore = result.nextCursor !== undefined;
- cursor = result.nextCursor;
}
if (allData.length > 0) {
diff --git a/packages/features/insights/server/raw-data.schema.ts b/packages/features/insights/server/raw-data.schema.ts
index 9c720a332a..d0bbcdb3aa 100644
--- a/packages/features/insights/server/raw-data.schema.ts
+++ b/packages/features/insights/server/raw-data.schema.ts
@@ -23,7 +23,7 @@ export const rawDataInputSchema = z.object({
eventTypeId: z.coerce.number().optional().nullable(),
}) satisfies z.ZodType;
-export type RoutingFormResponsesInput = {
+export type RoutingFormStatsInput = {
startDate: string;
endDate: string;
teamId?: number;
@@ -31,13 +31,16 @@ export type RoutingFormResponsesInput = {
memberUserIds?: number[];
isAll: boolean;
routingFormId?: string;
- cursor?: number;
- limit?: number;
columnFilters: ColumnFilter[];
sorting?: Sorting[];
};
-export const routingFormResponsesInputSchema = z.object({
+export type RoutingFormResponsesInput = RoutingFormStatsInput & {
+ offset?: number;
+ limit?: number;
+};
+
+export const routingFormStatsInputSchema = z.object({
startDate: z.string(),
endDate: z.string(),
teamId: z.coerce.number().optional(),
@@ -45,8 +48,12 @@ export const routingFormResponsesInputSchema = z.object({
memberUserIds: z.number().array().optional(),
isAll: z.coerce.boolean(),
routingFormId: z.string().optional(),
- cursor: z.number().optional(),
- limit: z.number().optional(),
columnFilters: z.array(ZColumnFilter),
sorting: z.array(ZSorting).optional(),
+}) satisfies z.ZodType;
+
+export const routingFormResponsesInputSchema = z.object({
+ ...routingFormStatsInputSchema.shape,
+ offset: z.number().optional(),
+ limit: z.number().max(100).optional(),
}) satisfies z.ZodType;
diff --git a/packages/features/insights/server/routing-events.ts b/packages/features/insights/server/routing-events.ts
index 2e55e1ec97..4f7deb382f 100644
--- a/packages/features/insights/server/routing-events.ts
+++ b/packages/features/insights/server/routing-events.ts
@@ -12,10 +12,12 @@ import { zodFields as routingFormFieldsSchema } from "@calcom/app-store/routing-
import dayjs from "@calcom/dayjs";
import { ColumnFilterType } from "@calcom/features/data-table";
import { makeWhereClause, makeOrderBy } from "@calcom/features/data-table/lib/server";
-import type { ColumnFilter, TypedColumnFilter } from "@calcom/features/data-table/lib/types";
-import type { RoutingFormResponsesInput } from "@calcom/features/insights/server/raw-data.schema";
+import type { TypedColumnFilter } from "@calcom/features/data-table/lib/types";
+import type {
+ RoutingFormResponsesInput,
+ RoutingFormStatsInput,
+} from "@calcom/features/insights/server/raw-data.schema";
import { readonlyPrisma as prisma } from "@calcom/prisma";
-import type { BookingStatus } from "@calcom/prisma/enums";
import { type ResponseValue, ZResponse } from "../lib/types";
@@ -27,17 +29,8 @@ type RoutingFormInsightsTeamFilter = {
routingFormId?: string | null;
};
-type RoutingFormInsightsFilter = RoutingFormInsightsTeamFilter & {
- startDate?: string;
- endDate?: string;
- memberUserId?: number | null;
- searchQuery?: string | null;
- bookingStatus?: BookingStatus | "NO_BOOKING" | null;
- fieldFilter?: {
- fieldId: string;
- optionId: string;
- } | null;
- columnFilters: ColumnFilter[];
+type RoutingFormStatsFilter = RoutingFormStatsInput & {
+ organizationId: number | null;
};
type RoutingFormResponsesFilter = RoutingFormResponsesInput & {
@@ -114,13 +107,11 @@ class RoutingEventsInsights {
isAll,
organizationId,
routingFormId,
- cursor,
- limit,
userId,
memberUserIds,
columnFilters,
sorting,
- }: RoutingFormResponsesFilter) {
+ }: RoutingFormStatsFilter) {
const whereClause = await this.getWhereClauseForRoutingFormResponses({
teamId,
startDate,
@@ -128,8 +119,6 @@ class RoutingEventsInsights {
isAll,
organizationId,
routingFormId,
- cursor,
- limit,
userId,
memberUserIds,
columnFilters,
@@ -288,8 +277,8 @@ class RoutingEventsInsights {
isAll,
organizationId,
routingFormId,
- cursor,
limit,
+ offset,
userId,
memberUserIds,
columnFilters,
@@ -302,8 +291,6 @@ class RoutingEventsInsights {
isAll,
organizationId,
routingFormId,
- cursor,
- limit,
userId,
memberUserIds,
columnFilters,
@@ -336,16 +323,14 @@ class RoutingEventsInsights {
},
where: whereClause,
orderBy: sorting && sorting.length > 0 ? makeOrderBy(sorting) : { createdAt: "desc" },
- take: limit ? limit + 1 : undefined, // Get one extra item to check if there are more pages
- cursor: cursor ? { id: cursor } : undefined,
+ take: limit,
+ skip: offset,
});
const [totalResponses, responses] = await Promise.all([totalResponsePromise, responsesPromise]);
- const hasNextPage = responses.length > (limit ?? 0);
- const responsesToReturn = responses.slice(0, limit ? limit : responses.length);
type Response = Omit<
- (typeof responsesToReturn)[number],
+ (typeof responses)[number],
"response" | "responseLowercase" | "bookingAttendees"
> & {
response: Record;
@@ -355,8 +340,7 @@ class RoutingEventsInsights {
return {
total: totalResponses,
- data: responsesToReturn as Response[],
- nextCursor: hasNextPage ? responsesToReturn[responsesToReturn.length - 1].id : undefined,
+ data: responses as Response[],
};
}
@@ -367,8 +351,8 @@ class RoutingEventsInsights {
isAll,
organizationId,
routingFormId,
- cursor,
limit,
+ offset,
userId,
memberUserIds,
columnFilters,
@@ -388,10 +372,10 @@ class RoutingEventsInsights {
isAll,
organizationId,
routingFormId,
- cursor,
userId,
memberUserIds,
limit,
+ offset,
columnFilters,
sorting,
});
@@ -444,7 +428,7 @@ class RoutingEventsInsights {
return {
data: dataWithFlatResponse,
- nextCursor: data.nextCursor,
+ total: data.total,
};
}
diff --git a/packages/features/insights/server/trpc-router.ts b/packages/features/insights/server/trpc-router.ts
index 5f1280fa28..76d50b5d2b 100644
--- a/packages/features/insights/server/trpc-router.ts
+++ b/packages/features/insights/server/trpc-router.ts
@@ -6,6 +6,7 @@ import dayjs from "@calcom/dayjs";
import {
rawDataInputSchema,
routingFormResponsesInputSchema,
+ routingFormStatsInputSchema,
} from "@calcom/features/insights/server/raw-data.schema";
import { randomString } from "@calcom/lib/random";
import type { readonlyPrisma } from "@calcom/prisma";
@@ -1574,7 +1575,7 @@ export const insightsRouter = router({
});
}),
routingFormsByStatus: userBelongsToTeamProcedure
- .input(routingFormResponsesInputSchema)
+ .input(routingFormStatsInputSchema)
.query(async ({ ctx, input }) => {
return await RoutingEventsInsights.getRoutingFormStats({
teamId: input.teamId,
@@ -1583,10 +1584,8 @@ export const insightsRouter = router({
isAll: input.isAll,
organizationId: ctx.user.organizationId ?? null,
routingFormId: input.routingFormId,
- cursor: input.cursor,
userId: ctx.user.id,
memberUserIds: input.memberUserIds,
- limit: input.limit,
columnFilters: input.columnFilters,
sorting: input.sorting,
});
@@ -1601,10 +1600,10 @@ export const insightsRouter = router({
isAll: input.isAll,
organizationId: ctx.user.organizationId ?? null,
routingFormId: input.routingFormId,
- cursor: input.cursor,
userId: ctx.user.id,
memberUserIds: input.memberUserIds,
limit: input.limit,
+ offset: input.offset,
columnFilters: input.columnFilters,
sorting: input.sorting,
});
@@ -1619,10 +1618,10 @@ export const insightsRouter = router({
isAll: input.isAll,
organizationId: ctx.user.organizationId ?? null,
routingFormId: input.routingFormId,
- cursor: input.cursor,
userId: ctx.user.id,
memberUserIds: input.memberUserIds,
limit: input.limit ?? BATCH_SIZE,
+ offset: input.offset,
columnFilters: input.columnFilters,
sorting: input.sorting,
});