Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions packages/features/insights/HOW_TO_ADD_BOOKING_CHARTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ export default function InsightsPage() {

## Step 4: Create tRPC Handler

Add the tRPC endpoint in the insights router using the `createInsightsBookingService()` helper:
Add the tRPC endpoint in the insights router using the `getInsightsBookingService()` DI container function:

```typescript
// packages/features/insights/server/trpc-router.ts
Expand All @@ -118,6 +118,7 @@ export const insightsRouter = router({
myNewChartData: userBelongsToTeamProcedure
.input(bookingRepositoryBaseInputSchema)
.query(async ({ ctx, input }) => {
// `createInsightsBookingService` is defined at the root level in this file
const insightsBookingService = createInsightsBookingService(ctx, input);

try {
Expand All @@ -129,13 +130,13 @@ export const insightsRouter = router({
});
```

## Step 5: Add Service Method to InsightsBookingService
## Step 5: Add Service Method to InsightsBookingBaseService

Add your new method to the `InsightsBookingService` class:
Add your new method to the `InsightsBookingBaseService` class:

```typescript
// packages/lib/server/service/insightsBooking.ts
export class InsightsBookingService {
// packages/lib/server/service/InsightsBookingBaseService.ts
export class InsightsBookingBaseService {
// ... existing methods

async getMyNewChartData() {
Expand Down Expand Up @@ -168,7 +169,7 @@ export class InsightsBookingService {

## Best Practices

1. **Use `createInsightsBookingService()`**: Always use the helper function for consistent service creation
1. **Use `getInsightsBookingService()`**: Always use the DI container function for consistent service creation
2. **Raw SQL for Performance**: Use `$queryRaw` for complex aggregations and better performance
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`
Expand Down
7 changes: 3 additions & 4 deletions packages/features/insights/server/trpc-router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import {
routingRepositoryBaseInputSchema,
bookingRepositoryBaseInputSchema,
} from "@calcom/features/insights/server/raw-data.schema";
import { getInsightsBookingService } from "@calcom/lib/di/containers/insights-booking";
import { getInsightsRoutingService } from "@calcom/lib/di/containers/insights-routing";
import { InsightsBookingService } from "@calcom/lib/server/service/insightsBooking";
import type { readonlyPrisma } from "@calcom/prisma";
import { BookingStatus } from "@calcom/prisma/enums";
import authedProcedure from "@calcom/trpc/server/procedures/authedProcedure";
Expand Down Expand Up @@ -316,14 +316,13 @@ const BATCH_SIZE = 1000; // Adjust based on your needs
* Helper function to create InsightsBookingService with standardized parameters
*/
function createInsightsBookingService(
ctx: { insightsDb: typeof readonlyPrisma; user: { id: number; organizationId: number | null } },
ctx: { user: { id: number; organizationId: number | null } },
input: z.infer<typeof bookingRepositoryBaseInputSchema>,
dateTarget: "createdAt" | "startTime" = "createdAt"
) {
const { scope, selectedTeamId, startDate, endDate, columnFilters } = input;

return new InsightsBookingService({
prisma: ctx.insightsDb,
return getInsightsBookingService({
options: {
scope,
userId: ctx.user.id,
Expand Down
27 changes: 27 additions & 0 deletions packages/lib/di/containers/insights-booking.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { createContainer } from "@evyweb/ioctopus";

import { DI_TOKENS } from "@calcom/lib/di/tokens";
import type {
InsightsBookingServicePublicOptions,
InsightsBookingServiceFilterOptions,
InsightsBookingBaseService,
} from "@calcom/lib/server/service/InsightsBookingBaseService";
import type { InsightsBookingService } from "@calcom/lib/server/service/InsightsBookingDIService";
import { prismaModule } from "@calcom/prisma/prisma.module";

import { insightsBookingModule } from "../modules/insights-booking";

export function getInsightsBookingService({
options,
filters,
}: {
options: InsightsBookingServicePublicOptions;
filters?: InsightsBookingServiceFilterOptions;
}): InsightsBookingBaseService {
const container = createContainer();
container.load(DI_TOKENS.READ_ONLY_PRISMA_CLIENT, prismaModule);
container.load(DI_TOKENS.INSIGHTS_BOOKING_SERVICE_MODULE, insightsBookingModule);

const diService = container.get<InsightsBookingService>(DI_TOKENS.INSIGHTS_BOOKING_SERVICE);
return diService.create({ options, filters });
}
11 changes: 11 additions & 0 deletions packages/lib/di/modules/insights-booking.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { createModule } from "@evyweb/ioctopus";

import type { IInsightsBookingService } from "@calcom/lib/server/service/InsightsBookingDIService";
import { InsightsBookingService } from "@calcom/lib/server/service/InsightsBookingDIService";

import { DI_TOKENS } from "../tokens";

export const insightsBookingModule = createModule();
insightsBookingModule.bind(DI_TOKENS.INSIGHTS_BOOKING_SERVICE).toClass(InsightsBookingService, {
prisma: DI_TOKENS.READ_ONLY_PRISMA_CLIENT,
} satisfies Record<keyof IInsightsBookingService, symbol>);
2 changes: 2 additions & 0 deletions packages/lib/di/tokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ export const DI_TOKENS = {
AVAILABLE_SLOTS_SERVICE_MODULE: Symbol("AvailableSlotsModule"),
INSIGHTS_ROUTING_SERVICE: Symbol("InsightsRoutingService"),
INSIGHTS_ROUTING_SERVICE_MODULE: Symbol("InsightsRoutingServiceModule"),
INSIGHTS_BOOKING_SERVICE: Symbol("InsightsBookingService"),
INSIGHTS_BOOKING_SERVICE_MODULE: Symbol("InsightsBookingServiceModule"),
FEATURES_REPOSITORY: Symbol("FeaturesRepository"),
FEATURES_REPOSITORY_MODULE: Symbol("FeaturesRepositoryModule"),
CACHE_SERVICE: Symbol("CacheService"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ const NOTHING_CONDITION = Prisma.sql`1=0`;

const bookingDataKeys = new Set(Object.keys(bookingDataSchema.shape));

export class InsightsBookingService {
export class InsightsBookingBaseService {
private prisma: typeof readonlyPrisma;
private options: InsightsBookingServiceOptions | null;
private filters: InsightsBookingServiceFilterOptions | null;
Expand Down
29 changes: 29 additions & 0 deletions packages/lib/server/service/InsightsBookingDIService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import type { readonlyPrisma } from "@calcom/prisma";

import {
InsightsBookingBaseService,
type InsightsBookingServicePublicOptions,
type InsightsBookingServiceFilterOptions,
} from "./InsightsBookingBaseService";

export interface IInsightsBookingService {
prisma: typeof readonlyPrisma;
}

export class InsightsBookingService {
constructor(private readonly dependencies: IInsightsBookingService) {}

create({
options,
filters,
}: {
options: InsightsBookingServicePublicOptions;
filters?: InsightsBookingServiceFilterOptions;
}): InsightsBookingBaseService {
return new InsightsBookingBaseService({
prisma: this.dependencies.prisma,
options,
filters,
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { ColumnFilterType } from "@calcom/features/data-table/lib/types";
import prisma from "@calcom/prisma";
import { BookingStatus, MembershipRole } from "@calcom/prisma/enums";

import { InsightsBookingService } from "../../service/insightsBooking";
import { InsightsBookingBaseService as InsightsBookingService } from "../InsightsBookingBaseService";

const NOTHING_CONDITION = Prisma.sql`1=0`;

Expand Down
Loading