docs: update insights chart dev guide for Prisma v6 (#23793)
This commit is contained in:
@@ -20,26 +20,26 @@ UI Component → tRPC Handler → Insights Service → Database Query → Respon
|
||||
```typescript
|
||||
// packages/features/insights/components/booking/MyNewChart.tsx
|
||||
import { LineChart, XAxis, YAxis, CartesianGrid, Tooltip, Line, ResponsiveContainer } from "recharts";
|
||||
|
||||
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { trpc } from "@calcom/trpc";
|
||||
|
||||
|
||||
import { useInsightsBookingParameters } from "../../hooks/useInsightsBookingParameters";
|
||||
import { ChartCard } from "../ChartCard";
|
||||
import { LoadingInsight } from "../LoadingInsights";
|
||||
|
||||
|
||||
export const MyNewChart = () => {
|
||||
const { t } = useLocale();
|
||||
const insightsBookingParams = useInsightsBookingParameters();
|
||||
|
||||
|
||||
const { data, isSuccess, isPending } = trpc.viewer.insights.myNewChartData.useQuery(insightsBookingParams, {
|
||||
staleTime: 180000, // 3 minutes
|
||||
refetchOnWindowFocus: false,
|
||||
trpc: { context: { skipBatch: true } },
|
||||
});
|
||||
|
||||
|
||||
if (isPending) return <LoadingInsight />;
|
||||
|
||||
|
||||
return (
|
||||
<ChartCard title={t("my_new_chart_title")}>
|
||||
{isSuccess && data?.length > 0 ? (
|
||||
@@ -66,7 +66,7 @@ UI Component → tRPC Handler → Insights Service → Database Query → Respon
|
||||
|
||||
<Step title="Add Component to Barrel Export">
|
||||
Update the booking components index file:
|
||||
|
||||
|
||||
```typescript
|
||||
// packages/features/insights/components/booking/index.ts
|
||||
export { AverageEventDurationChart } from "./AverageEventDurationChart";
|
||||
@@ -76,9 +76,9 @@ UI Component → tRPC Handler → Insights Service → Database Query → Respon
|
||||
```
|
||||
</Step>
|
||||
<Step title="Add Component to Insights View">
|
||||
|
||||
|
||||
Add your component to the main insights page:
|
||||
|
||||
|
||||
```typescript
|
||||
// apps/web/modules/insights/insights-view.tsx
|
||||
import {
|
||||
@@ -86,45 +86,45 @@ UI Component → tRPC Handler → Insights Service → Database Query → Respon
|
||||
BookingKPICards, // ... existing imports
|
||||
MyNewChart, // Add this import
|
||||
} from "@calcom/features/insights/components/booking";
|
||||
|
||||
|
||||
export default function InsightsPage() {
|
||||
// ... existing code
|
||||
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Existing components */}
|
||||
<BookingKPICards />
|
||||
<EventTrendsChart />
|
||||
|
||||
|
||||
{/* Add your new chart */}
|
||||
<MyNewChart />
|
||||
|
||||
|
||||
{/* Other existing components */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
</Step>
|
||||
</Step>
|
||||
<Step title="Create tRPC Handler">
|
||||
|
||||
Add the tRPC endpoint in the insights router using the `getInsightsBookingService()` DI container function:
|
||||
|
||||
|
||||
```typescript
|
||||
// packages/features/insights/server/trpc-router.ts
|
||||
import { bookingRepositoryBaseInputSchema } from "@calcom/features/insights/server/raw-data.schema";
|
||||
import { userBelongsToTeamProcedure } from "@calcom/trpc/server/procedures/authedProcedure";
|
||||
|
||||
|
||||
import { TRPCError } from "@trpc/server";
|
||||
|
||||
|
||||
export const insightsRouter = router({
|
||||
// ... existing procedures
|
||||
|
||||
|
||||
myNewChartData: userBelongsToTeamProcedure
|
||||
.input(bookingRepositoryBaseInputSchema)
|
||||
.query(async ({ ctx, input }) => {
|
||||
// `createInsightsBookingService` is defined at the root level in this file
|
||||
const insightsBookingService = createInsightsBookingService(ctx, input);
|
||||
|
||||
|
||||
try {
|
||||
return await insightsBookingService.getMyNewChartData();
|
||||
} catch (e) {
|
||||
@@ -134,24 +134,21 @@ UI Component → tRPC Handler → Insights Service → Database Query → Respon
|
||||
});
|
||||
```
|
||||
</Step>
|
||||
<Step title="Add Service Method to InsightsBookingBaseService">
|
||||
<Step title="Add Service Method to InsightsBookingBaseService">
|
||||
Add your new method to the `InsightsBookingBaseService` class:
|
||||
|
||||
|
||||
```typescript
|
||||
// packages/lib/server/service/InsightsBookingBaseService.ts
|
||||
export class InsightsBookingBaseService {
|
||||
// ... existing methods
|
||||
|
||||
|
||||
async getMyNewChartData() {
|
||||
const baseConditions = await this.getBaseConditions();
|
||||
|
||||
|
||||
// Example: Get booking counts by day using raw SQL for performance
|
||||
const data = await this.prisma.$queryRaw<
|
||||
Array<{
|
||||
date: Date;
|
||||
bookingsCount: number;
|
||||
}>
|
||||
>`
|
||||
// Note: Use Prisma.sql for the entire query (Prisma v6 requirement)
|
||||
// Prisma v6 no longer allows mixing template literals with Prisma.sql fragments
|
||||
const query = Prisma.sql`
|
||||
SELECT
|
||||
DATE("createdAt") as date,
|
||||
COUNT(*)::int as "bookingsCount"
|
||||
@@ -160,7 +157,14 @@ UI Component → tRPC Handler → Insights Service → Database Query → Respon
|
||||
GROUP BY DATE("createdAt")
|
||||
ORDER BY date ASC
|
||||
`;
|
||||
|
||||
|
||||
const data = await this.prisma.$queryRaw<
|
||||
Array<{
|
||||
date: Date;
|
||||
bookingsCount: number;
|
||||
}>
|
||||
>(query);
|
||||
|
||||
// Transform the data for the chart
|
||||
return data.map((item) => ({
|
||||
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
|
||||
}
|
||||
```
|
||||
</Step>
|
||||
|
||||
</Steps>
|
||||
|
||||
## Best Practices
|
||||
@@ -179,5 +184,6 @@ UI Component → tRPC Handler → Insights Service → Database Query → Respon
|
||||
3. **Base Conditions**: Always use `await this.getBaseConditions()` for proper filtering and permissions
|
||||
4. **Error Handling**: Wrap service calls in try-catch blocks with `TRPCError`
|
||||
5. **Loading States**: Always show loading indicators with `LoadingInsight`
|
||||
6. **Consistent Styling**: Use `recharts` for new charts.
|
||||
6. **Consistent Styling**: Use `recharts` for new charts
|
||||
7. **Date Handling**: Use `getDateRanges()` and `getTimeView()` for time-based charts
|
||||
8. **Prisma v6 Compatibility**: Use `Prisma.sql` for the entire query instead of mixing template literals with SQL fragments
|
||||
|
||||
@@ -143,12 +143,9 @@ export class InsightsBookingBaseService {
|
||||
const baseConditions = await this.getBaseConditions();
|
||||
|
||||
// Example: Get booking counts by day using raw SQL for performance
|
||||
const data = await this.prisma.$queryRaw<
|
||||
Array<{
|
||||
date: Date;
|
||||
bookingsCount: number;
|
||||
}>
|
||||
>`
|
||||
// Note: Use Prisma.sql for the entire query (Prisma v6 requirement)
|
||||
// Prisma v6 no longer allows mixing template literals with Prisma.sql fragments
|
||||
const query = Prisma.sql`
|
||||
SELECT
|
||||
DATE("createdAt") as date,
|
||||
COUNT(*)::int as "bookingsCount"
|
||||
@@ -158,6 +155,13 @@ export class InsightsBookingBaseService {
|
||||
ORDER BY date ASC
|
||||
`;
|
||||
|
||||
const data = await this.prisma.$queryRaw<
|
||||
Array<{
|
||||
date: Date;
|
||||
bookingsCount: number;
|
||||
}>
|
||||
>(query);
|
||||
|
||||
// Transform the data for the chart
|
||||
return data.map((item) => ({
|
||||
date: item.date.toISOString().split("T")[0], // Format as YYYY-MM-DD
|
||||
@@ -174,5 +178,6 @@ export class InsightsBookingBaseService {
|
||||
3. **Base Conditions**: Always use `await this.getBaseConditions()` for proper filtering and permissions
|
||||
4. **Error Handling**: Wrap service calls in try-catch blocks with `TRPCError`
|
||||
5. **Loading States**: Always show loading indicators with `LoadingInsight`
|
||||
6. **Consistent Styling**: Use `recharts` for new charts.
|
||||
6. **Consistent Styling**: Use `recharts` for new charts
|
||||
7. **Date Handling**: Use `getDateRanges()` and `getTimeView()` for time-based charts
|
||||
8. **Prisma v6 Compatibility**: Use `Prisma.sql` for the entire query instead of mixing template literals with SQL fragments
|
||||
|
||||
Reference in New Issue
Block a user