perf: batch user queries in schedules output service (#25901)
Replace N sequential queries with single batch query for fetching user default schedule IDs Changes: - Add getUsersScheduleDefaultIds batch method to UsersRepository - Add getResponseSchedules batch method to OutputSchedulesService - Extract transformScheduleToOutput for reuse - Simplify OrganizationsSchedulesService and TeamsSchedulesService - Remove dead code (formatInput no-op method) Reduces database round-trips from O(n) to O(1) for schedule lookups
This commit is contained in:
+23
-11
@@ -4,18 +4,34 @@ import { Injectable } from "@nestjs/common";
|
||||
import type { WeekDay } from "@calcom/platform-types";
|
||||
import type { Availability, Schedule } from "@calcom/prisma/client";
|
||||
|
||||
type DatabaseSchedule = Schedule & { availability: Availability[] };
|
||||
|
||||
@Injectable()
|
||||
export class OutputSchedulesService_2024_06_11 {
|
||||
constructor(private readonly usersRepository: UsersRepository) {}
|
||||
|
||||
async getResponseSchedule(databaseSchedule: Schedule & { availability: Availability[] }) {
|
||||
if (!databaseSchedule.timeZone) {
|
||||
databaseSchedule.timeZone = "Europe/London";
|
||||
}
|
||||
async getResponseSchedules(databaseSchedules: DatabaseSchedule[]) {
|
||||
if (databaseSchedules.length === 0) return [];
|
||||
const userIds = [...new Set(databaseSchedules.map((schedule) => schedule.userId))];
|
||||
const defaultScheduleIds = await this.usersRepository.getUsersScheduleDefaultIds(userIds);
|
||||
|
||||
return databaseSchedules.map((schedule) =>
|
||||
this.transformScheduleToOutput(schedule, defaultScheduleIds.get(schedule.userId) ?? null)
|
||||
);
|
||||
}
|
||||
|
||||
async getResponseSchedule(databaseSchedule: DatabaseSchedule) {
|
||||
const ownerDefaultScheduleId = await this.usersRepository.getUserScheduleDefaultId(
|
||||
databaseSchedule.userId
|
||||
);
|
||||
return this.transformScheduleToOutput(databaseSchedule, ownerDefaultScheduleId);
|
||||
}
|
||||
|
||||
private transformScheduleToOutput(
|
||||
databaseSchedule: DatabaseSchedule,
|
||||
ownerDefaultScheduleId: number | null
|
||||
) {
|
||||
const timeZone = databaseSchedule.timeZone || "Europe/London";
|
||||
|
||||
const createdScheduleAvailabilities = databaseSchedule.availability.filter(
|
||||
(availability) => !!availability.days.length
|
||||
@@ -26,7 +42,7 @@ export class OutputSchedulesService_2024_06_11 {
|
||||
id: databaseSchedule.id,
|
||||
ownerId: databaseSchedule.userId,
|
||||
name: databaseSchedule.name,
|
||||
timeZone: databaseSchedule.timeZone,
|
||||
timeZone,
|
||||
availability: createdScheduleAvailabilities.map((availability) => ({
|
||||
days: availability.days.map(transformNumberToDay),
|
||||
startTime: this.padHoursMinutesWithZeros(
|
||||
@@ -54,13 +70,9 @@ export class OutputSchedulesService_2024_06_11 {
|
||||
};
|
||||
}
|
||||
|
||||
padHoursMinutesWithZeros(hhMM: string) {
|
||||
private padHoursMinutesWithZeros(hhMM: string) {
|
||||
const [hours, minutes] = hhMM.split(":");
|
||||
|
||||
const formattedHours = hours.padStart(2, "0");
|
||||
const formattedMinutes = minutes.padStart(2, "0");
|
||||
|
||||
return `${formattedHours}:${formattedMinutes}`;
|
||||
return `${hours.padStart(2, "0")}:${minutes.padStart(2, "0")}`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-9
@@ -3,8 +3,6 @@ import { OutputSchedulesService_2024_06_11 } from "@/ee/schedules/schedules_2024
|
||||
import { UsersRepository } from "@/modules/users/users.repository";
|
||||
import { Injectable } from "@nestjs/common";
|
||||
|
||||
import { ScheduleOutput_2024_06_11 } from "@calcom/platform-types";
|
||||
|
||||
@Injectable()
|
||||
export class OrganizationsSchedulesService {
|
||||
constructor(
|
||||
@@ -19,12 +17,6 @@ export class OrganizationsSchedulesService {
|
||||
|
||||
const schedules = await this.schedulesRepository.getSchedulesByUserIds(usersIds, skip, take);
|
||||
|
||||
const responseSchedules: ScheduleOutput_2024_06_11[] = [];
|
||||
|
||||
for (const schedule of schedules) {
|
||||
responseSchedules.push(await this.outputSchedulesService.getResponseSchedule(schedule));
|
||||
}
|
||||
|
||||
return responseSchedules;
|
||||
return this.outputSchedulesService.getResponseSchedules(schedules);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,6 @@ import { OutputSchedulesService_2024_06_11 } from "@/ee/schedules/schedules_2024
|
||||
import { TeamsRepository } from "@/modules/teams/teams/teams.repository";
|
||||
import { Injectable } from "@nestjs/common";
|
||||
|
||||
import { ScheduleOutput_2024_06_11 } from "@calcom/platform-types";
|
||||
|
||||
@Injectable()
|
||||
export class TeamsSchedulesService {
|
||||
constructor(
|
||||
@@ -17,12 +15,6 @@ export class TeamsSchedulesService {
|
||||
const userIds = await this.teamsRepository.getTeamUsersIds(teamId);
|
||||
const schedules = await this.schedulesRepository.getSchedulesByUserIds(userIds, skip, take);
|
||||
|
||||
const responseSchedules: ScheduleOutput_2024_06_11[] = [];
|
||||
|
||||
for (const schedule of schedules) {
|
||||
responseSchedules.push(await this.outputSchedulesService.getResponseSchedule(schedule));
|
||||
}
|
||||
|
||||
return responseSchedules;
|
||||
return this.outputSchedulesService.getResponseSchedules(schedules);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,8 +22,6 @@ export class UsersRepository {
|
||||
oAuthClientId: string,
|
||||
isPlatformManaged: boolean
|
||||
) {
|
||||
this.formatInput(user);
|
||||
|
||||
return this.dbWrite.prisma.user.create({
|
||||
data: {
|
||||
...user,
|
||||
@@ -227,8 +225,6 @@ export class UsersRepository {
|
||||
}
|
||||
|
||||
async update(userId: number, updateData: UpdateManagedUserInput) {
|
||||
this.formatInput(updateData);
|
||||
|
||||
return this.dbWrite.prisma.user.update({
|
||||
where: { id: userId },
|
||||
data: updateData,
|
||||
@@ -257,12 +253,6 @@ export class UsersRepository {
|
||||
});
|
||||
}
|
||||
|
||||
formatInput(userInput: CreateManagedUserInput | UpdateManagedUserInput) {
|
||||
if (userInput.weekStart) {
|
||||
userInput.weekStart = userInput.weekStart;
|
||||
}
|
||||
}
|
||||
|
||||
setDefaultSchedule(userId: number, scheduleId: number) {
|
||||
return this.dbWrite.prisma.user.update({
|
||||
where: { id: userId },
|
||||
@@ -280,6 +270,14 @@ export class UsersRepository {
|
||||
return user?.defaultScheduleId;
|
||||
}
|
||||
|
||||
async getUsersScheduleDefaultIds(userIds: number[]): Promise<Map<number, number | null>> {
|
||||
const users = await this.dbRead.prisma.user.findMany({
|
||||
where: { id: { in: userIds } },
|
||||
select: { id: true, defaultScheduleId: true },
|
||||
});
|
||||
return new Map(users.map((user) => [user.id, user.defaultScheduleId]));
|
||||
}
|
||||
|
||||
async getOrganizationUsers(organizationId: number) {
|
||||
const profiles = await this.dbRead.prisma.profile.findMany({
|
||||
where: {
|
||||
|
||||
Reference in New Issue
Block a user