fix: including Prisma.sql for Prisma v6 upgrade (#23775)

This commit is contained in:
Eunjae Lee
2025-09-11 23:16:12 +01:00
committed by GitHub
parent c28eb90928
commit c3985e3efb
2 changed files with 172 additions and 127 deletions
@@ -12,8 +12,8 @@ import {
isNumberFilterValue,
} from "@calcom/features/data-table/lib/utils";
import type { DateRange } from "@calcom/features/insights/server/insightsDateUtils";
import { Prisma } from "@calcom/prisma/client";
import type { PrismaClient } from "@calcom/prisma";
import { Prisma } from "@calcom/prisma/client";
import { MembershipRole } from "@calcom/prisma/enums";
import { MembershipRepository } from "../repository/membership";
@@ -168,12 +168,7 @@ export class InsightsBookingBaseService {
async getBookingsByHourStats({ timeZone }: { timeZone: string }) {
const baseConditions = await this.getBaseConditions();
const results = await this.prisma.$queryRaw<
Array<{
hour: string;
count: number;
}>
>`
const query = Prisma.sql`
SELECT
EXTRACT(HOUR FROM ("startTime" AT TIME ZONE 'UTC' AT TIME ZONE ${timeZone}))::int as "hour",
COUNT(*)::int as "count"
@@ -184,6 +179,13 @@ export class InsightsBookingBaseService {
ORDER BY 1
`;
const results = await this.prisma.$queryRaw<
Array<{
hour: string;
count: number;
}>
>(query);
// Create a map of results by hour for easy lookup
const resultsMap = new Map(results.map((row) => [Number(row.hour), row.count]));
@@ -216,11 +218,13 @@ export class InsightsBookingBaseService {
}
}
return await this.prisma.$queryRaw<Array<SelectedFields<TSelect>>>`
const query = Prisma.sql`
SELECT ${selectFields}
FROM "BookingTimeStatusDenormalized"
WHERE ${baseConditions}
`;
return await this.prisma.$queryRaw<Array<SelectedFields<TSelect>>>(query);
}
async getBaseConditions(): Promise<Prisma.Sql> {
@@ -449,33 +453,17 @@ export class InsightsBookingBaseService {
const baseConditions = await this.getBaseConditions();
// Get total count first
const totalCountResult = await this.prisma.$queryRaw<[{ count: number }]>`
const totalCountQuery = Prisma.sql`
SELECT COUNT(*)::int as count
FROM "BookingTimeStatusDenormalized"
WHERE ${baseConditions}
`;
const totalCountResult = await this.prisma.$queryRaw<[{ count: number }]>(totalCountQuery);
const totalCount = totalCountResult[0]?.count || 0;
// 1. Get booking data from BookingTimeStatusDenormalized
const csvData = await this.prisma.$queryRaw<
Array<{
id: number;
uid: string | null;
title: string;
createdAt: Date;
timeStatus: string;
eventTypeId: number | null;
eventLength: number;
startTime: Date;
endTime: Date;
paid: boolean;
userEmail: string;
userUsername: string;
rating: number | null;
ratingFeedback: string | null;
noShowHost: boolean;
}>
>`
const csvDataQuery = Prisma.sql`
SELECT
"id",
"uid",
@@ -499,6 +487,26 @@ export class InsightsBookingBaseService {
OFFSET ${offset}
`;
const csvData = await this.prisma.$queryRaw<
Array<{
id: number;
uid: string | null;
title: string;
createdAt: Date;
timeStatus: string;
eventTypeId: number | null;
eventLength: number;
startTime: Date;
endTime: Date;
paid: boolean;
userEmail: string;
userUsername: string;
rating: number | null;
ratingFeedback: string | null;
noShowHost: boolean;
}>
>(csvDataQuery);
if (csvData.length === 0) {
return { data: csvData, total: totalCount };
}
@@ -641,15 +649,7 @@ export class InsightsBookingBaseService {
const baseConditions = await this.getBaseConditions();
const data = await this.prisma.$queryRaw<
{
date: Date;
bookingsCount: number;
timeStatus: string;
noShowHost: boolean;
noShowGuests: number;
}[]
>`
const query = Prisma.sql`
WITH booking_stats AS (
SELECT
DATE("createdAt" AT TIME ZONE ${timeZone}) as "date",
@@ -688,6 +688,16 @@ export class InsightsBookingBaseService {
ORDER BY bs."date"
`;
const data = await this.prisma.$queryRaw<
{
date: Date;
bookingsCount: number;
timeStatus: string;
noShowHost: boolean;
noShowGuests: number;
}[]
>(query);
// Initialize aggregate object with zero counts for all date ranges
const aggregate: {
[date: string]: {
@@ -777,12 +787,7 @@ export class InsightsBookingBaseService {
async getPopularEventsStats() {
const baseConditions = await this.getBaseConditions();
const bookingsFromSelected = await this.prisma.$queryRaw<
Array<{
eventTypeId: number;
count: number;
}>
>`
const query = Prisma.sql`
SELECT
"eventTypeId",
COUNT(id)::int as count
@@ -793,6 +798,13 @@ export class InsightsBookingBaseService {
LIMIT 10
`;
const bookingsFromSelected = await this.prisma.$queryRaw<
Array<{
eventTypeId: number;
count: number;
}>
>(query);
const eventTypeIds = bookingsFromSelected.map((booking) => booking.eventTypeId);
if (eventTypeIds.length === 0) {
@@ -866,12 +878,7 @@ export class InsightsBookingBaseService {
additionalCondition = Prisma.sql`AND status = 'accepted'`;
}
const bookingsFromTeam = await this.prisma.$queryRaw<
Array<{
userId: number;
count: number;
}>
>`
const query = Prisma.sql`
SELECT
"userId",
COUNT(id)::int as count
@@ -882,6 +889,13 @@ export class InsightsBookingBaseService {
LIMIT 10
`;
const bookingsFromTeam = await this.prisma.$queryRaw<
Array<{
userId: number;
count: number;
}>
>(query);
if (bookingsFromTeam.length === 0) {
return [];
}
@@ -927,12 +941,7 @@ export class InsightsBookingBaseService {
async getMembersRatingStats(sortOrder: "ASC" | "DESC" = "DESC"): Promise<UserStatsData> {
const baseConditions = await this.getBaseConditions();
const bookingsFromTeam = await this.prisma.$queryRaw<
Array<{
userId: number;
count: number;
}>
>`
const query = Prisma.sql`
SELECT
"userId",
AVG("rating")::float as "count"
@@ -943,6 +952,13 @@ export class InsightsBookingBaseService {
LIMIT 10
`;
const bookingsFromTeam = await this.prisma.$queryRaw<
Array<{
userId: number;
count: number;
}>
>(query);
if (bookingsFromTeam.length === 0) {
return [];
}
@@ -988,13 +1004,7 @@ export class InsightsBookingBaseService {
async getRecentRatingsStats() {
const baseConditions = await this.getBaseConditions();
const bookingsFromTeam = await this.prisma.$queryRaw<
Array<{
userId: number | null;
rating: number | null;
ratingFeedback: string | null;
}>
>`
const query = Prisma.sql`
SELECT
"userId",
"rating",
@@ -1005,6 +1015,14 @@ export class InsightsBookingBaseService {
LIMIT 10
`;
const bookingsFromTeam = await this.prisma.$queryRaw<
Array<{
userId: number | null;
rating: number | null;
ratingFeedback: string | null;
}>
>(query);
if (bookingsFromTeam.length === 0) {
return [];
}
@@ -1062,19 +1080,7 @@ export class InsightsBookingBaseService {
async getBookingStats() {
const baseConditions = await this.getBaseConditions();
const stats = await this.prisma.$queryRaw<
Array<{
total_bookings: bigint;
completed_bookings: bigint;
rescheduled_bookings: bigint;
cancelled_bookings: bigint;
no_show_host_bookings: bigint;
avg_rating: number | null;
total_ratings: bigint;
ratings_above_3: bigint;
no_show_guests: bigint;
}>
>`
const query = Prisma.sql`
WITH booking_stats AS (
SELECT
COUNT(*) as total_bookings,
@@ -1107,6 +1113,20 @@ export class InsightsBookingBaseService {
FROM booking_stats bs, guest_stats gs
`;
const stats = await this.prisma.$queryRaw<
Array<{
total_bookings: bigint;
completed_bookings: bigint;
rescheduled_bookings: bigint;
cancelled_bookings: bigint;
no_show_host_bookings: bigint;
avg_rating: number | null;
total_ratings: bigint;
ratings_above_3: bigint;
no_show_guests: bigint;
}>
>(query);
const rawStats = stats[0];
return rawStats
? {
@@ -1136,15 +1156,7 @@ export class InsightsBookingBaseService {
async getRecentNoShowGuests() {
const baseConditions = await this.getBaseConditions();
const recentNoShowBookings = await this.prisma.$queryRaw<
Array<{
bookingId: number;
startTime: Date;
eventTypeName: string;
guestName: string;
guestEmail: string;
}>
>`
const query = Prisma.sql`
WITH booking_attendee_stats AS (
SELECT
b.id as booking_id,
@@ -1182,6 +1194,16 @@ export class InsightsBookingBaseService {
LIMIT 10
`;
const recentNoShowBookings = await this.prisma.$queryRaw<
Array<{
bookingId: number;
startTime: Date;
eventTypeName: string;
guestName: string;
guestEmail: string;
}>
>(query);
return recentNoShowBookings;
}
@@ -183,14 +183,7 @@ export class InsightsRoutingBaseService {
.reduce((acc, curr) => Prisma.sql`${acc} ${curr}`);
// Single query to get all data grouped by date ranges using CTE
const results = await this.prisma.$queryRaw<
Array<{
dateRange: string | null;
totalSubmissions: bigint;
successfulRoutings: bigint;
acceptedBookings: bigint;
}>
>`
const query = Prisma.sql`
WITH date_ranged_data AS (
SELECT
CASE ${caseStatements}
@@ -212,6 +205,15 @@ export class InsightsRoutingBaseService {
ORDER BY "dateRange"
`;
const results = await this.prisma.$queryRaw<
Array<{
dateRange: string | null;
totalSubmissions: bigint;
successfulRoutings: bigint;
acceptedBookings: bigint;
}>
>(query);
// Create a map of results by dateRange for easy lookup
const resultsMap = new Map(
results
@@ -265,16 +267,18 @@ export class InsightsRoutingBaseService {
const orderByClause = this.buildOrderByClause(sorting);
// Get total count
const totalCountResult = await this.prisma.$queryRaw<Array<{ count: bigint }>>`
const totalCountQuery = Prisma.sql`
SELECT COUNT(*) as count
FROM "RoutingFormResponseDenormalized" rfrd
WHERE ${baseConditions}
`;
const totalCountResult = await this.prisma.$queryRaw<Array<{ count: bigint }>>(totalCountQuery);
const totalCount = Number(totalCountResult[0]?.count || 0);
// Get paginated data with JSON aggregation for attendees and fields
const data = await this.prisma.$queryRaw<Array<InsightsRoutingTableItem>>`
const dataQuery = Prisma.sql`
SELECT
rfrd."id",
rfrd."uuid",
@@ -341,6 +345,8 @@ export class InsightsRoutingBaseService {
OFFSET ${offset}
`;
const data = await this.prisma.$queryRaw<Array<InsightsRoutingTableItem>>(dataQuery);
return {
total: totalCount,
data,
@@ -359,19 +365,25 @@ export class InsightsRoutingBaseService {
}
// Get total count
const totalResult = await this.prisma.$queryRaw<Array<{ count: bigint }>>`
const totalQuery = Prisma.sql`
SELECT COUNT(*) as count
FROM "RoutingFormResponseDenormalized" rfrd
WHERE ${baseConditions}
`;
const totalResult = await this.prisma.$queryRaw<Array<{ count: bigint }>>(totalQuery);
// Get total without booking count
const totalWithoutBookingResult = await this.prisma.$queryRaw<Array<{ count: bigint }>>`
const totalWithoutBookingQuery = Prisma.sql`
SELECT COUNT(*) as count
FROM "RoutingFormResponseDenormalized" rfrd
WHERE (${baseConditions}) AND ("bookingUid" IS NULL)
`;
const totalWithoutBookingResult = await this.prisma.$queryRaw<Array<{ count: bigint }>>(
totalWithoutBookingQuery
);
const total = Number(totalResult[0]?.count || 0);
const totalWithoutBooking = Number(totalWithoutBookingResult[0]?.count || 0);
@@ -437,14 +449,7 @@ export class InsightsRoutingBaseService {
: Prisma.sql`1 = 1`;
// Get users who have been routed to during the period
const usersQuery = await this.prisma.$queryRaw<
Array<{
id: number;
name: string | null;
email: string | null;
avatarUrl: string | null;
}>
>`
const usersQuerySql = Prisma.sql`
SELECT DISTINCT ON ("bookingUserId")
"bookingUserId" as id,
"bookingUserName" as name,
@@ -461,6 +466,15 @@ export class InsightsRoutingBaseService {
${limit ? Prisma.sql`LIMIT ${limit}` : Prisma.empty}
`;
const usersQuery = await this.prisma.$queryRaw<
Array<{
id: number;
name: string | null;
email: string | null;
avatarUrl: string | null;
}>
>(usersQuerySql);
const users = usersQuery;
// Return early if no users found
@@ -478,13 +492,7 @@ export class InsightsRoutingBaseService {
}
// Get periods with pagination
const periodStats = await this.prisma.$queryRaw<
Array<{
userId: number;
period_start: Date;
total: number;
}>
>`
const periodStatsQuery = Prisma.sql`
WITH RECURSIVE date_range AS (
SELECT date_trunc(${dayjsPeriod}, ${startDate}::timestamp) as date
UNION ALL
@@ -545,13 +553,16 @@ export class InsightsRoutingBaseService {
ORDER BY c.period_start ASC, c."userId" ASC
`;
// Get statistics for the entire period for comparison
const statsQuery = await this.prisma.$queryRaw<
const periodStats = await this.prisma.$queryRaw<
Array<{
userId: number;
total_bookings: number;
period_start: Date;
total: number;
}>
>`
>(periodStatsQuery);
// Get statistics for the entire period for comparison
const statsQuerySql = Prisma.sql`
SELECT
"bookingUserId" as "userId",
COUNT(DISTINCT "bookingId")::integer as total_bookings
@@ -564,6 +575,13 @@ export class InsightsRoutingBaseService {
ORDER BY total_bookings ASC
`;
const statsQuery = await this.prisma.$queryRaw<
Array<{
userId: number;
total_bookings: number;
}>
>(statsQuerySql);
// Calculate average and median
const average =
statsQuery.reduce((sum, stat) => sum + Number(stat.total_bookings), 0) / statsQuery.length || 0;
@@ -625,7 +643,10 @@ export class InsightsRoutingBaseService {
if (!userStatsMap.has(userId)) {
userStatsMap.set(userId, {});
}
userStatsMap.get(userId)![stat.period_start.toISOString()] = stat.total;
const userStat = userStatsMap.get(userId);
if (userStat) {
userStat[stat.period_start.toISOString()] = stat.total;
}
});
return data.users.data.map((user) => {
@@ -940,17 +961,7 @@ export class InsightsRoutingBaseService {
const baseConditions = await this.getBaseConditions();
// Get failed bookings (responses without a successful booking) grouped by form, field, and option
const result = await this.prisma.$queryRaw<
{
formId: string;
formName: string;
fieldId: string;
fieldLabel: string;
optionId: string;
optionLabel: string;
count: number;
}[]
>`
const query = Prisma.sql`
WITH form_fields AS (
SELECT DISTINCT
rfrd."formId" as form_id,
@@ -997,6 +1008,18 @@ export class InsightsRoutingBaseService {
ORDER BY count DESC
`;
const result = await this.prisma.$queryRaw<
{
formId: string;
formName: string;
fieldId: string;
fieldLabel: string;
optionId: string;
optionLabel: string;
count: number;
}[]
>(query);
// First group by form and field
const groupedByFormAndField = result.reduce((acc, curr) => {
const formKey = curr.formName;