Skip to content

Commit 06fcea4

Browse files
authored
docs: update insights chart dev guide for Prisma v6 (calcom#23793)
1 parent a7fecaf commit 06fcea4

2 files changed

Lines changed: 49 additions & 38 deletions

File tree

docs/developing/guides/insights/add-new-booking-charts.mdx

Lines changed: 37 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -20,26 +20,26 @@ UI Component → tRPC Handler → Insights Service → Database Query → Respon
2020
```typescript
2121
// packages/features/insights/components/booking/MyNewChart.tsx
2222
import { LineChart, XAxis, YAxis, CartesianGrid, Tooltip, Line, ResponsiveContainer } from "recharts";
23-
23+
2424
import { useLocale } from "@calcom/lib/hooks/useLocale";
2525
import { trpc } from "@calcom/trpc";
26-
26+
2727
import { useInsightsBookingParameters } from "../../hooks/useInsightsBookingParameters";
2828
import { ChartCard } from "../ChartCard";
2929
import { LoadingInsight } from "../LoadingInsights";
30-
30+
3131
export const MyNewChart = () => {
3232
const { t } = useLocale();
3333
const insightsBookingParams = useInsightsBookingParameters();
34-
34+
3535
const { data, isSuccess, isPending } = trpc.viewer.insights.myNewChartData.useQuery(insightsBookingParams, {
3636
staleTime: 180000, // 3 minutes
3737
refetchOnWindowFocus: false,
3838
trpc: { context: { skipBatch: true } },
3939
});
40-
40+
4141
if (isPending) return <LoadingInsight />;
42-
42+
4343
return (
4444
<ChartCard title={t("my_new_chart_title")}>
4545
{isSuccess && data?.length > 0 ? (
@@ -66,7 +66,7 @@ UI Component → tRPC Handler → Insights Service → Database Query → Respon
6666

6767
<Step title="Add Component to Barrel Export">
6868
Update the booking components index file:
69-
69+
7070
```typescript
7171
// packages/features/insights/components/booking/index.ts
7272
export { AverageEventDurationChart } from "./AverageEventDurationChart";
@@ -76,55 +76,55 @@ UI Component → tRPC Handler → Insights Service → Database Query → Respon
7676
```
7777
</Step>
7878
<Step title="Add Component to Insights View">
79-
79+
8080
Add your component to the main insights page:
81-
81+
8282
```typescript
8383
// apps/web/modules/insights/insights-view.tsx
8484
import {
8585
AverageEventDurationChart,
8686
BookingKPICards, // ... existing imports
8787
MyNewChart, // Add this import
8888
} from "@calcom/features/insights/components/booking";
89-
89+
9090
export default function InsightsPage() {
9191
// ... existing code
92-
92+
9393
return (
9494
<div className="space-y-6">
9595
{/* Existing components */}
9696
<BookingKPICards />
9797
<EventTrendsChart />
98-
98+
9999
{/* Add your new chart */}
100100
<MyNewChart />
101-
101+
102102
{/* Other existing components */}
103103
</div>
104104
);
105105
}
106106
```
107-
</Step>
107+
</Step>
108108
<Step title="Create tRPC Handler">
109109

110110
Add the tRPC endpoint in the insights router using the `getInsightsBookingService()` DI container function:
111-
111+
112112
```typescript
113113
// packages/features/insights/server/trpc-router.ts
114114
import { bookingRepositoryBaseInputSchema } from "@calcom/features/insights/server/raw-data.schema";
115115
import { userBelongsToTeamProcedure } from "@calcom/trpc/server/procedures/authedProcedure";
116-
116+
117117
import { TRPCError } from "@trpc/server";
118-
118+
119119
export const insightsRouter = router({
120120
// ... existing procedures
121-
121+
122122
myNewChartData: userBelongsToTeamProcedure
123123
.input(bookingRepositoryBaseInputSchema)
124124
.query(async ({ ctx, input }) => {
125125
// `createInsightsBookingService` is defined at the root level in this file
126126
const insightsBookingService = createInsightsBookingService(ctx, input);
127-
127+
128128
try {
129129
return await insightsBookingService.getMyNewChartData();
130130
} catch (e) {
@@ -134,24 +134,21 @@ UI Component → tRPC Handler → Insights Service → Database Query → Respon
134134
});
135135
```
136136
</Step>
137-
<Step title="Add Service Method to InsightsBookingBaseService">
137+
<Step title="Add Service Method to InsightsBookingBaseService">
138138
Add your new method to the `InsightsBookingBaseService` class:
139-
139+
140140
```typescript
141141
// packages/lib/server/service/InsightsBookingBaseService.ts
142142
export class InsightsBookingBaseService {
143143
// ... existing methods
144-
144+
145145
async getMyNewChartData() {
146146
const baseConditions = await this.getBaseConditions();
147-
147+
148148
// Example: Get booking counts by day using raw SQL for performance
149-
const data = await this.prisma.$queryRaw<
150-
Array<{
151-
date: Date;
152-
bookingsCount: number;
153-
}>
154-
>`
149+
// Note: Use Prisma.sql for the entire query (Prisma v6 requirement)
150+
// Prisma v6 no longer allows mixing template literals with Prisma.sql fragments
151+
const query = Prisma.sql`
155152
SELECT
156153
DATE("createdAt") as date,
157154
COUNT(*)::int as "bookingsCount"
@@ -160,7 +157,14 @@ UI Component → tRPC Handler → Insights Service → Database Query → Respon
160157
GROUP BY DATE("createdAt")
161158
ORDER BY date ASC
162159
`;
163-
160+
161+
const data = await this.prisma.$queryRaw<
162+
Array<{
163+
date: Date;
164+
bookingsCount: number;
165+
}>
166+
>(query);
167+
164168
// Transform the data for the chart
165169
return data.map((item) => ({
166170
date: item.date.toISOString().split("T")[0], // Format as YYYY-MM-DD
@@ -170,6 +174,7 @@ UI Component → tRPC Handler → Insights Service → Database Query → Respon
170174
}
171175
```
172176
</Step>
177+
173178
</Steps>
174179

175180
## Best Practices
@@ -179,5 +184,6 @@ UI Component → tRPC Handler → Insights Service → Database Query → Respon
179184
3. **Base Conditions**: Always use `await this.getBaseConditions()` for proper filtering and permissions
180185
4. **Error Handling**: Wrap service calls in try-catch blocks with `TRPCError`
181186
5. **Loading States**: Always show loading indicators with `LoadingInsight`
182-
6. **Consistent Styling**: Use `recharts` for new charts.
187+
6. **Consistent Styling**: Use `recharts` for new charts
183188
7. **Date Handling**: Use `getDateRanges()` and `getTimeView()` for time-based charts
189+
8. **Prisma v6 Compatibility**: Use `Prisma.sql` for the entire query instead of mixing template literals with SQL fragments

packages/features/insights/HOW_TO_ADD_BOOKING_CHARTS.md

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -143,12 +143,9 @@ export class InsightsBookingBaseService {
143143
const baseConditions = await this.getBaseConditions();
144144

145145
// Example: Get booking counts by day using raw SQL for performance
146-
const data = await this.prisma.$queryRaw<
147-
Array<{
148-
date: Date;
149-
bookingsCount: number;
150-
}>
151-
>`
146+
// Note: Use Prisma.sql for the entire query (Prisma v6 requirement)
147+
// Prisma v6 no longer allows mixing template literals with Prisma.sql fragments
148+
const query = Prisma.sql`
152149
SELECT
153150
DATE("createdAt") as date,
154151
COUNT(*)::int as "bookingsCount"
@@ -158,6 +155,13 @@ export class InsightsBookingBaseService {
158155
ORDER BY date ASC
159156
`;
160157

158+
const data = await this.prisma.$queryRaw<
159+
Array<{
160+
date: Date;
161+
bookingsCount: number;
162+
}>
163+
>(query);
164+
161165
// Transform the data for the chart
162166
return data.map((item) => ({
163167
date: item.date.toISOString().split("T")[0], // Format as YYYY-MM-DD
@@ -174,5 +178,6 @@ export class InsightsBookingBaseService {
174178
3. **Base Conditions**: Always use `await this.getBaseConditions()` for proper filtering and permissions
175179
4. **Error Handling**: Wrap service calls in try-catch blocks with `TRPCError`
176180
5. **Loading States**: Always show loading indicators with `LoadingInsight`
177-
6. **Consistent Styling**: Use `recharts` for new charts.
181+
6. **Consistent Styling**: Use `recharts` for new charts
178182
7. **Date Handling**: Use `getDateRanges()` and `getTimeView()` for time-based charts
183+
8. **Prisma v6 Compatibility**: Use `Prisma.sql` for the entire query instead of mixing template literals with SQL fragments

0 commit comments

Comments
 (0)