refactor: convert lucky user to service class (#23329)
* refactor: lucky user to service class with DI * fix: unit test filterHostsByLeadThreshold.test.ts * fixup! fix: unit test filterHostsByLeadThreshold.test.ts * fix: get lucky user test missing mocks * fix: get lucky user hosts not within interval * chore: bump platform library * chore: bump platform library * chore: add DAILY_API_KEY to turborepo.json db seed * chore: add DAILY_API_KEY to cache-db action * fixup! chore: add DAILY_API_KEY to cache-db action * chore: remove log in fixture * chore: bump platform library * chore: refactor test team-emails.e2e * only failing test * run all api v2 tests * chore: bump platform library * revert change to turbo.json and action in cache-db * chore: use inPerson location in team-emails.e2e
This commit is contained in:
@@ -38,7 +38,7 @@
|
||||
"@axiomhq/winston": "^1.2.0",
|
||||
"@calcom/platform-constants": "*",
|
||||
"@calcom/platform-enums": "*",
|
||||
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.321",
|
||||
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.333",
|
||||
"@calcom/platform-types": "*",
|
||||
"@calcom/platform-utils": "*",
|
||||
"@calcom/prisma": "*",
|
||||
|
||||
+362
-816
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,12 @@
|
||||
import { PrismaWriteService } from "@/modules/prisma/prisma-write.service";
|
||||
import { Injectable } from "@nestjs/common";
|
||||
|
||||
import { PrismaAttributeRepository as PrismaAttributeRepositoryLib } from "@calcom/platform-libraries/repositories";
|
||||
import { PrismaClient } from "@calcom/prisma";
|
||||
|
||||
@Injectable()
|
||||
export class PrismaAttributeRepository extends PrismaAttributeRepositoryLib {
|
||||
constructor(private readonly dbWrite: PrismaWriteService) {
|
||||
super(dbWrite.prisma as unknown as PrismaClient);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { PrismaWriteService } from "@/modules/prisma/prisma-write.service";
|
||||
import { Injectable } from "@nestjs/common";
|
||||
|
||||
import { PrismaHostRepository as PrismaHostRepositoryLib } from "@calcom/platform-libraries/repositories";
|
||||
import { PrismaClient } from "@calcom/prisma";
|
||||
|
||||
@Injectable()
|
||||
export class PrismaHostRepository extends PrismaHostRepositoryLib {
|
||||
constructor(private readonly dbWrite: PrismaWriteService) {
|
||||
super(dbWrite.prisma as unknown as PrismaClient);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { PrismaAttributeRepository } from "@/lib/repositories/prisma-attribute.repository";
|
||||
import { PrismaBookingRepository } from "@/lib/repositories/prisma-booking.repository";
|
||||
import { PrismaHostRepository } from "@/lib/repositories/prisma-host.repository";
|
||||
import { PrismaOOORepository } from "@/lib/repositories/prisma-ooo.repository";
|
||||
import { PrismaUserRepository } from "@/lib/repositories/prisma-user.repository";
|
||||
import { Injectable } from "@nestjs/common";
|
||||
|
||||
import { LuckyUserService as BaseLuckyUserService } from "@calcom/platform-libraries/bookings";
|
||||
|
||||
@Injectable()
|
||||
export class LuckyUserService extends BaseLuckyUserService {
|
||||
constructor(
|
||||
bookingRepository: PrismaBookingRepository,
|
||||
hostRepository: PrismaHostRepository,
|
||||
oooRepository: PrismaOOORepository,
|
||||
userRepository: PrismaUserRepository,
|
||||
attributeRepository: PrismaAttributeRepository
|
||||
) {
|
||||
super({
|
||||
bookingRepository: bookingRepository,
|
||||
hostRepository: hostRepository,
|
||||
oooRepository: oooRepository,
|
||||
userRepository: userRepository,
|
||||
attributeRepository: attributeRepository,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,6 @@ export class ApiKeysRepositoryFixture {
|
||||
async createApiKey(userId: number, expiresAt: Date | null, teamId?: number) {
|
||||
const keyString = randomBytes(16).toString("hex");
|
||||
const hashedKey = createHash("sha256").update(keyString).digest("hex");
|
||||
console.log("createApiKey Fixutre", { hashedKey, keyString });
|
||||
const apiKey = await this.prismaWriteClient.apiKey.create({
|
||||
data: {
|
||||
userId,
|
||||
|
||||
@@ -54,6 +54,7 @@ import {
|
||||
} from "@calcom/lib/delegationCredential/server";
|
||||
import { getCheckBookingAndDurationLimitsService } from "@calcom/lib/di/containers/BookingLimits";
|
||||
import { getCacheService } from "@calcom/lib/di/containers/Cache";
|
||||
import { getLuckyUserService } from "@calcom/lib/di/containers/LuckyUser";
|
||||
import { ErrorCode } from "@calcom/lib/errorCodes";
|
||||
import { getErrorFromUnknown } from "@calcom/lib/errors";
|
||||
import { getEventName, updateHostInEventName } from "@calcom/lib/event";
|
||||
@@ -67,7 +68,6 @@ import logger from "@calcom/lib/logger";
|
||||
import { handlePayment } from "@calcom/lib/payment/handlePayment";
|
||||
import { getPiiFreeCalendarEvent, getPiiFreeEventType } from "@calcom/lib/piiFreeData";
|
||||
import { safeStringify } from "@calcom/lib/safeStringify";
|
||||
import { getLuckyUser } from "@calcom/lib/server/getLuckyUser";
|
||||
import { getTranslation } from "@calcom/lib/server/i18n";
|
||||
import { BookingRepository } from "@calcom/lib/server/repository/booking";
|
||||
import { WorkflowRepository } from "@calcom/lib/server/repository/workflow";
|
||||
@@ -903,7 +903,8 @@ async function handler(
|
||||
memberId: eventTypeWithUsers.users[0].id ?? null,
|
||||
teamId: eventType.teamId,
|
||||
});
|
||||
const newLuckyUser = await getLuckyUser({
|
||||
const luckyUserService = getLuckyUserService();
|
||||
const newLuckyUser = await luckyUserService.getLuckyUser({
|
||||
// find a lucky user that is not already in the luckyUsers array
|
||||
availableUsers: freeUsers,
|
||||
// only hosts from the same group
|
||||
|
||||
@@ -20,12 +20,12 @@ import {
|
||||
enrichHostsWithDelegationCredentials,
|
||||
enrichUserWithDelegationCredentialsIncludeServiceAccountKey,
|
||||
} from "@calcom/lib/delegationCredential/server";
|
||||
import { getLuckyUserService } from "@calcom/lib/di/containers/LuckyUser";
|
||||
import { ErrorCode } from "@calcom/lib/errorCodes";
|
||||
import { getEventName } from "@calcom/lib/event";
|
||||
import { IdempotencyKeyService } from "@calcom/lib/idempotencyKey/idempotencyKeyService";
|
||||
import { isPrismaObjOrUndefined } from "@calcom/lib/isPrismaObj";
|
||||
import logger from "@calcom/lib/logger";
|
||||
import { getLuckyUser } from "@calcom/lib/server/getLuckyUser";
|
||||
import { getTranslation } from "@calcom/lib/server/i18n";
|
||||
import { getTimeFormatStringFromUserTimeFormat } from "@calcom/lib/timeFormat";
|
||||
import { prisma } from "@calcom/prisma";
|
||||
@@ -153,8 +153,8 @@ export const roundRobinReassignment = async ({
|
||||
},
|
||||
roundRobinReassignLogger
|
||||
);
|
||||
|
||||
const reassignedRRHost = await getLuckyUser({
|
||||
const luckyUserService = getLuckyUserService();
|
||||
const reassignedRRHost = await luckyUserService.getLuckyUser({
|
||||
availableUsers,
|
||||
eventType,
|
||||
allRRHosts: eventTypeHosts.filter((host) => !host.isFixed), // todo: only use hosts from virtual queue
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { describe, expect, it, vi, afterEach } from "vitest";
|
||||
import type { Mock } from "vitest";
|
||||
import { describe, expect, it, vi, afterEach, beforeEach } from "vitest";
|
||||
|
||||
import { getLuckyUserService } from "@calcom/lib/di/containers/LuckyUser";
|
||||
import prisma from "@calcom/prisma";
|
||||
import { RRResetInterval } from "@calcom/prisma/client";
|
||||
|
||||
import { getOrderedListOfLuckyUsers } from "../server/getLuckyUser";
|
||||
import { filterHostsByLeadThreshold, errorCodes } from "./filterHostsByLeadThreshold";
|
||||
|
||||
// Import the original Prisma client
|
||||
const luckyUserService = getLuckyUserService();
|
||||
|
||||
// Mocking setup
|
||||
const prismaMock = {
|
||||
@@ -18,13 +19,19 @@ const prismaMock = {
|
||||
// Use `vi.spyOn` to make `prisma.booking.groupBy` call the mock instead
|
||||
vi.spyOn(prisma.booking, "groupBy").mockImplementation(prismaMock.booking.groupBy);
|
||||
|
||||
vi.mock("../server/getLuckyUser", () => ({
|
||||
getOrderedListOfLuckyUsers: vi.fn(),
|
||||
}));
|
||||
// This variable will hold our mock function
|
||||
let getOrderedListOfLuckyUsersMock: Mock;
|
||||
|
||||
beforeEach(() => {
|
||||
// Clear all mocks and spies before each test
|
||||
vi.clearAllMocks();
|
||||
// Spy on the real method and explicitly cast it as a Mock
|
||||
getOrderedListOfLuckyUsersMock = vi.spyOn(luckyUserService, "getOrderedListOfLuckyUsers") as Mock;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// Clear call history before each test to avoid cross-test interference
|
||||
prismaMock.booking.groupBy.mockClear();
|
||||
// Restore all spies to their original implementation
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe("filterHostByLeadThreshold", () => {
|
||||
@@ -100,7 +107,7 @@ describe("filterHostByLeadThreshold", () => {
|
||||
},
|
||||
];
|
||||
|
||||
getOrderedListOfLuckyUsers.mockResolvedValue({
|
||||
getOrderedListOfLuckyUsersMock.mockResolvedValue({
|
||||
perUserData: {
|
||||
bookingsCount: { 1: 10, 2: 6 },
|
||||
},
|
||||
@@ -157,7 +164,7 @@ describe("filterHostByLeadThreshold", () => {
|
||||
},
|
||||
];
|
||||
|
||||
getOrderedListOfLuckyUsers.mockResolvedValue({
|
||||
getOrderedListOfLuckyUsersMock.mockResolvedValue({
|
||||
perUserData: {
|
||||
bookingsCount: { 1: 7, 2: 5, 3: 0 },
|
||||
weights: { 1: 100, 2: 50, 3: 20 },
|
||||
|
||||
@@ -3,8 +3,8 @@ import type { RRResetInterval, SelectedCalendar } from "@calcom/prisma/client";
|
||||
import { RRTimestampBasis } from "@calcom/prisma/enums";
|
||||
import type { CredentialForCalendarService } from "@calcom/types/Credential";
|
||||
|
||||
import type { RoutingFormResponse } from "../server/getLuckyUser";
|
||||
import { getOrderedListOfLuckyUsers } from "../server/getLuckyUser";
|
||||
import { getLuckyUserService } from "../di/containers/LuckyUser";
|
||||
import type { LuckyUserService, RoutingFormResponse } from "../server/getLuckyUser";
|
||||
|
||||
export const errorCodes = {
|
||||
MAX_LEAD_THRESHOLD_FALSY: "Max lead threshold should be null or > 1, not 0.",
|
||||
@@ -26,7 +26,9 @@ type BaseHost<User extends BaseUser> = {
|
||||
user: User;
|
||||
};
|
||||
|
||||
type PerUserData = Awaited<ReturnType<typeof getOrderedListOfLuckyUsers>>["perUserData"];
|
||||
type PerUserData = Awaited<
|
||||
ReturnType<(typeof LuckyUserService.prototype)["getOrderedListOfLuckyUsers"]>
|
||||
>["perUserData"];
|
||||
type WeightedPerUserData = Omit<PerUserData, "weights" | "calibrations" | "bookingShortfalls"> & {
|
||||
weights: NonNullable<PerUserData["weights"]>;
|
||||
calibrations: NonNullable<PerUserData["calibrations"]>;
|
||||
@@ -116,7 +118,8 @@ export const filterHostsByLeadThreshold = async <T extends BaseHost<BaseUser>>({
|
||||
}
|
||||
|
||||
// this needs the routing forms response too, because it needs to know what queue we are in
|
||||
const orderedLuckyUsers = await getOrderedListOfLuckyUsers({
|
||||
const luckyUserService = getLuckyUserService();
|
||||
const orderedLuckyUsers = await luckyUserService.getOrderedListOfLuckyUsers({
|
||||
availableUsers: [
|
||||
{
|
||||
...hosts[0].user,
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import { createContainer } from "@evyweb/ioctopus";
|
||||
|
||||
import { DI_TOKENS } from "@calcom/lib/di/tokens";
|
||||
import type { LuckyUserService } from "@calcom/lib/server/getLuckyUser";
|
||||
import { prismaModule } from "@calcom/prisma/prisma.module";
|
||||
|
||||
import { attributeRepositoryModule } from "../modules/Attribute";
|
||||
import { bookingRepositoryModule } from "../modules/Booking";
|
||||
import { hostRepositoryModule } from "../modules/Host";
|
||||
import { luckyUserServiceModule } from "../modules/LuckyUser";
|
||||
import { oooRepositoryModule } from "../modules/Ooo";
|
||||
import { userRepositoryModule } from "../modules/User";
|
||||
|
||||
const container = createContainer();
|
||||
container.load(DI_TOKENS.PRISMA_MODULE, prismaModule);
|
||||
container.load(DI_TOKENS.BOOKING_REPOSITORY_MODULE, bookingRepositoryModule);
|
||||
container.load(DI_TOKENS.HOST_REPOSITORY_MODULE, hostRepositoryModule);
|
||||
container.load(DI_TOKENS.OOO_REPOSITORY_MODULE, oooRepositoryModule);
|
||||
container.load(DI_TOKENS.USER_REPOSITORY_MODULE, userRepositoryModule);
|
||||
container.load(DI_TOKENS.ATTRIBUTE_REPOSITORY_MODULE, attributeRepositoryModule);
|
||||
container.load(DI_TOKENS.LUCKY_USER_SERVICE_MODULE, luckyUserServiceModule);
|
||||
|
||||
export function getLuckyUserService() {
|
||||
return container.get<LuckyUserService>(DI_TOKENS.LUCKY_USER_SERVICE);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { createModule } from "@evyweb/ioctopus";
|
||||
|
||||
import { DI_TOKENS } from "@calcom/lib/di/tokens";
|
||||
import { PrismaAttributeRepository } from "@calcom/lib/server/repository/PrismaAttributeRepository";
|
||||
|
||||
export const attributeRepositoryModule = createModule();
|
||||
attributeRepositoryModule
|
||||
.bind(DI_TOKENS.ATTRIBUTE_REPOSITORY)
|
||||
.toClass(PrismaAttributeRepository, [DI_TOKENS.PRISMA_CLIENT]);
|
||||
@@ -0,0 +1,7 @@
|
||||
import { createModule } from "@evyweb/ioctopus";
|
||||
|
||||
import { DI_TOKENS } from "@calcom/lib/di/tokens";
|
||||
import { HostRepository } from "@calcom/lib/server/repository/host";
|
||||
|
||||
export const hostRepositoryModule = createModule();
|
||||
hostRepositoryModule.bind(DI_TOKENS.HOST_REPOSITORY).toClass(HostRepository, [DI_TOKENS.PRISMA_CLIENT]);
|
||||
@@ -0,0 +1,13 @@
|
||||
import { createModule } from "@evyweb/ioctopus";
|
||||
|
||||
import { DI_TOKENS } from "@calcom/lib/di/tokens";
|
||||
import { LuckyUserService } from "@calcom/lib/server/getLuckyUser";
|
||||
|
||||
export const luckyUserServiceModule = createModule();
|
||||
luckyUserServiceModule.bind(DI_TOKENS.LUCKY_USER_SERVICE).toClass(LuckyUserService, {
|
||||
bookingRepository: DI_TOKENS.BOOKING_REPOSITORY,
|
||||
hostRepository: DI_TOKENS.HOST_REPOSITORY,
|
||||
oooRepository: DI_TOKENS.OOO_REPOSITORY,
|
||||
userRepository: DI_TOKENS.USER_REPOSITORY,
|
||||
attributeRepository: DI_TOKENS.ATTRIBUTE_REPOSITORY,
|
||||
});
|
||||
@@ -45,4 +45,10 @@ export const DI_TOKENS = {
|
||||
NO_SLOTS_NOTIFICATION_SERVICE_MODULE: Symbol("NoSlotsNotificationServiceModule"),
|
||||
MEMBERSHIP_REPOSITORY: Symbol("MembershipRepository"),
|
||||
MEMBERSHIP_REPOSITORY_MODULE: Symbol("MembershipRepositoryModule"),
|
||||
LUCKY_USER_SERVICE: Symbol("LuckyUserService"),
|
||||
LUCKY_USER_SERVICE_MODULE: Symbol("LuckyUserServiceModule"),
|
||||
HOST_REPOSITORY: Symbol("HostRepository"),
|
||||
HOST_REPOSITORY_MODULE: Symbol("HostRepositoryModule"),
|
||||
ATTRIBUTE_REPOSITORY: Symbol("AttributeRepository"),
|
||||
ATTRIBUTE_REPOSITORY_MODULE: Symbol("AttributeRepositoryModule"),
|
||||
};
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { describe, it, vi, expect, afterEach, beforeEach, beforeAll, afterAll } from "vitest";
|
||||
|
||||
import { getLuckyUserService } from "@calcom/lib/di/containers/LuckyUser";
|
||||
import prisma from "@calcom/prisma";
|
||||
|
||||
import { getLuckyUser, getOrderedListOfLuckyUsers } from "./getLuckyUser";
|
||||
|
||||
const luckyUserService = getLuckyUserService();
|
||||
let commonEventTypeId: number;
|
||||
const userIds: number[] = [];
|
||||
|
||||
@@ -202,7 +202,7 @@ describe("getLuckyUser Integration tests", () => {
|
||||
const organizerThatShowedUp = organizerHostThatShowedUp.user;
|
||||
const organizerThatDidntShowUp = organizerHostThatDidntShowUp.user;
|
||||
|
||||
const luckyUser = await getLuckyUser({
|
||||
const luckyUser = await luckyUserService.getLuckyUser({
|
||||
availableUsers: [organizerThatShowedUp, organizerThatDidntShowUp],
|
||||
eventType: {
|
||||
id: commonEventTypeId,
|
||||
@@ -262,7 +262,7 @@ describe("getLuckyUser Integration tests", () => {
|
||||
const organizerWhoseAttendeeDidntShowUp = organizerHostWhoseAttendeeDidntShowUp.user;
|
||||
|
||||
expect(
|
||||
getLuckyUser({
|
||||
luckyUserService.getLuckyUser({
|
||||
availableUsers: [organizerWhoseAttendeeShowedUp, organizerWhoseAttendeeDidntShowUp],
|
||||
eventType: {
|
||||
id: commonEventTypeId,
|
||||
@@ -359,7 +359,7 @@ describe("getLuckyUser Integration tests", () => {
|
||||
const organizerWhoWasAttendeeAndDidntShowUp = organizerHostWhoWasAttendeeAndDidntShowUp.user;
|
||||
|
||||
expect(
|
||||
getLuckyUser({
|
||||
luckyUserService.getLuckyUser({
|
||||
availableUsers: [
|
||||
organizerWhoseAttendeeShowedUp,
|
||||
fixedHostOrganizerWhoseAttendeeDidNotShowUp,
|
||||
@@ -426,7 +426,7 @@ describe("getLuckyUser Integration tests", () => {
|
||||
const userWithBookingThatHappenedEarlier = hostWithBookingThatHappenedEarlier.user;
|
||||
|
||||
expect(
|
||||
getLuckyUser({
|
||||
luckyUserService.getLuckyUser({
|
||||
availableUsers: [userWithBookingThatHappenedLater, userWithBookingThatHappenedEarlier],
|
||||
eventType: {
|
||||
id: commonEventTypeId,
|
||||
@@ -469,7 +469,7 @@ describe("getOrderedListOfLuckyUsers Integration tests", () => {
|
||||
const user2 = host2.user;
|
||||
const user3 = host3.user;
|
||||
|
||||
const { users: luckyUsers } = await getOrderedListOfLuckyUsers({
|
||||
const { users: luckyUsers } = await luckyUserService.getOrderedListOfLuckyUsers({
|
||||
availableUsers: [user2, user1, user3],
|
||||
eventType: {
|
||||
id: commonEventTypeId,
|
||||
@@ -482,7 +482,7 @@ describe("getOrderedListOfLuckyUsers Integration tests", () => {
|
||||
|
||||
expectLuckyUsers(luckyUsers, [user2, user1, user3]);
|
||||
|
||||
const { users: luckyUsers2 } = await getOrderedListOfLuckyUsers({
|
||||
const { users: luckyUsers2 } = await luckyUserService.getOrderedListOfLuckyUsers({
|
||||
availableUsers: [user3, user1, user2],
|
||||
eventType: {
|
||||
id: commonEventTypeId,
|
||||
@@ -524,7 +524,7 @@ describe("getOrderedListOfLuckyUsers Integration tests", () => {
|
||||
const user2WithWeight100 = host3WithWeight100.user;
|
||||
|
||||
const allRRHosts = [host1WithWeight100, host2WithWeight200, host3WithWeight100];
|
||||
const { users: luckyUsers } = await getOrderedListOfLuckyUsers({
|
||||
const { users: luckyUsers } = await luckyUserService.getOrderedListOfLuckyUsers({
|
||||
availableUsers: [userWithHighestWeight, user1WithWeight100, user2WithWeight100],
|
||||
eventType: {
|
||||
id: commonEventTypeId,
|
||||
@@ -544,7 +544,7 @@ describe("getOrderedListOfLuckyUsers Integration tests", () => {
|
||||
user2WithWeight100,
|
||||
]);
|
||||
|
||||
const { users: luckyUsers2 } = await getOrderedListOfLuckyUsers({
|
||||
const { users: luckyUsers2 } = await luckyUserService.getOrderedListOfLuckyUsers({
|
||||
availableUsers: [user2WithWeight100, userWithHighestWeight, user1WithWeight100],
|
||||
eventType: {
|
||||
id: commonEventTypeId,
|
||||
@@ -622,7 +622,7 @@ describe("getOrderedListOfLuckyUsers Integration tests", () => {
|
||||
routingFormResponse: null,
|
||||
};
|
||||
|
||||
const { users: luckyUsers, perUserData } = await getOrderedListOfLuckyUsers({
|
||||
const { users: luckyUsers, perUserData } = await luckyUserService.getOrderedListOfLuckyUsers({
|
||||
...getLuckUserParams,
|
||||
availableUsers: [getLuckUserParams.availableUsers[0], ...getLuckUserParams.availableUsers.slice(1)],
|
||||
});
|
||||
@@ -705,7 +705,7 @@ describe("getOrderedListOfLuckyUsers Integration tests", () => {
|
||||
routingFormResponse: null,
|
||||
};
|
||||
|
||||
const { users: luckyUsers, perUserData } = await getOrderedListOfLuckyUsers({
|
||||
const { users: luckyUsers, perUserData } = await luckyUserService.getOrderedListOfLuckyUsers({
|
||||
...getLuckUserParams,
|
||||
availableUsers: [getLuckUserParams.availableUsers[0], ...getLuckUserParams.availableUsers.slice(1)],
|
||||
});
|
||||
@@ -781,7 +781,7 @@ describe("getOrderedListOfLuckyUsers Integration tests", () => {
|
||||
routingFormResponse: null,
|
||||
};
|
||||
|
||||
const { users: luckyUsers, perUserData } = await getOrderedListOfLuckyUsers({
|
||||
const { users: luckyUsers, perUserData } = await luckyUserService.getOrderedListOfLuckyUsers({
|
||||
...getLuckyUserParams,
|
||||
availableUsers: [getLuckyUserParams.availableUsers[0], ...getLuckyUserParams.availableUsers.slice(1)],
|
||||
});
|
||||
|
||||
@@ -5,15 +5,13 @@ import { v4 as uuid } from "uuid";
|
||||
import { expect, it, describe, vi, beforeAll } from "vitest";
|
||||
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import { getLuckyUserService } from "@calcom/lib/di/containers/LuckyUser";
|
||||
import { buildUser, buildBooking } from "@calcom/lib/test/builder";
|
||||
import { AttributeType, RRResetInterval, RRTimestampBasis } from "@calcom/prisma/enums";
|
||||
|
||||
import {
|
||||
getLuckyUser,
|
||||
prepareQueuesAndAttributesData,
|
||||
getIntervalStartDate,
|
||||
getIntervalEndDate,
|
||||
} from "./getLuckyUser";
|
||||
import { getIntervalStartDate, getIntervalEndDate } from "./getLuckyUser";
|
||||
|
||||
const luckyUserService = getLuckyUserService();
|
||||
|
||||
type NonEmptyArray<T> = [T, ...T[]];
|
||||
type GetLuckyUserAvailableUsersType = NonEmptyArray<ReturnType<typeof buildUser>>;
|
||||
@@ -58,18 +56,18 @@ it("can find lucky user with maximize availability", async () => {
|
||||
CalendarManagerMock.getBusyCalendarTimes.mockResolvedValue({ success: true, data: [] });
|
||||
prismaMock.outOfOfficeEntry.findMany.mockResolvedValue([]);
|
||||
|
||||
// TODO: we may be able to use native prisma generics somehow?
|
||||
prismaMock.user.findMany.mockResolvedValue(users);
|
||||
prismaMock.host.findMany.mockResolvedValue([]);
|
||||
prismaMock.booking.findMany.mockResolvedValue([]);
|
||||
|
||||
await expect(
|
||||
getLuckyUser({
|
||||
luckyUserService.getLuckyUser({
|
||||
availableUsers: users,
|
||||
eventType: {
|
||||
id: 1,
|
||||
isRRWeightsEnabled: false,
|
||||
team: { rrResetInterval: RRResetInterval.MONTH, rrTimestampBasis: RRTimestampBasis.CREATED_AT },
|
||||
includeNoShowInRRCalculation: false,
|
||||
},
|
||||
allRRHosts: [],
|
||||
routingFormResponse: null,
|
||||
@@ -110,22 +108,22 @@ it("can find lucky user with maximize availability and priority ranking", async
|
||||
CalendarManagerMock.getBusyCalendarTimes.mockResolvedValue({ success: true, data: [] });
|
||||
prismaMock.outOfOfficeEntry.findMany.mockResolvedValue([]);
|
||||
|
||||
// TODO: we may be able to use native prisma generics somehow?
|
||||
prismaMock.user.findMany.mockResolvedValue(users);
|
||||
prismaMock.host.findMany.mockResolvedValue([]);
|
||||
prismaMock.booking.findMany.mockResolvedValue([]);
|
||||
|
||||
// both users have medium priority (one user has no priority set, default to medium) so pick least recently booked
|
||||
await expect(
|
||||
getLuckyUser({
|
||||
luckyUserService.getLuckyUser({
|
||||
availableUsers: users,
|
||||
eventType: {
|
||||
id: 1,
|
||||
isRRWeightsEnabled: false,
|
||||
team: {
|
||||
rrResetInterval: RRResetInterval.MONTH,
|
||||
team: { rrResetInterval: RRResetInterval.MONTH, rrTimestampBasis: RRTimestampBasis.CREATED_AT },
|
||||
rrTimestampBasis: RRTimestampBasis.CREATED_AT,
|
||||
},
|
||||
includeNoShowInRRCalculation: false,
|
||||
},
|
||||
allRRHosts: [],
|
||||
routingFormResponse: null,
|
||||
@@ -171,18 +169,18 @@ it("can find lucky user with maximize availability and priority ranking", async
|
||||
});
|
||||
|
||||
const usersWithPriorities: GetLuckyUserAvailableUsersType = [userLowest, userMedium, userHighest];
|
||||
// TODO: we may be able to use native prisma generics somehow?
|
||||
prismaMock.user.findMany.mockResolvedValue(usersWithPriorities);
|
||||
prismaMock.booking.findMany.mockResolvedValue([]);
|
||||
prismaMock.host.findMany.mockResolvedValue([]);
|
||||
// pick the user with the highest priority
|
||||
await expect(
|
||||
getLuckyUser({
|
||||
luckyUserService.getLuckyUser({
|
||||
availableUsers: usersWithPriorities,
|
||||
eventType: {
|
||||
id: 1,
|
||||
isRRWeightsEnabled: false,
|
||||
team: { rrResetInterval: RRResetInterval.MONTH, rrTimestampBasis: RRTimestampBasis.CREATED_AT },
|
||||
includeNoShowInRRCalculation: false,
|
||||
},
|
||||
allRRHosts: [],
|
||||
routingFormResponse: null,
|
||||
@@ -232,19 +230,19 @@ it("can find lucky user with maximize availability and priority ranking", async
|
||||
userHighLeastRecentBooking,
|
||||
userHighRecentBooking,
|
||||
];
|
||||
// TODO: we may be able to use native prisma generics somehow?
|
||||
prismaMock.user.findMany.mockResolvedValue(usersWithSamePriorities);
|
||||
prismaMock.booking.findMany.mockResolvedValue([]);
|
||||
prismaMock.host.findMany.mockResolvedValue([]);
|
||||
|
||||
// pick the least recently booked user of the two with the highest priority
|
||||
await expect(
|
||||
getLuckyUser({
|
||||
luckyUserService.getLuckyUser({
|
||||
availableUsers: usersWithSamePriorities,
|
||||
eventType: {
|
||||
id: 1,
|
||||
isRRWeightsEnabled: false,
|
||||
team: { rrResetInterval: RRResetInterval.MONTH, rrTimestampBasis: RRTimestampBasis.CREATED_AT },
|
||||
includeNoShowInRRCalculation: false,
|
||||
},
|
||||
allRRHosts: [],
|
||||
routingFormResponse: null,
|
||||
@@ -318,24 +316,35 @@ describe("maximize availability and weights", () => {
|
||||
|
||||
const allRRHosts = [
|
||||
{
|
||||
user: { id: users[0].id, email: users[0].email, credentials: [], selectedCalendars: [] },
|
||||
user: {
|
||||
id: users[0].id,
|
||||
email: users[0].email,
|
||||
credentials: [],
|
||||
userLevelSelectedCalendars: [],
|
||||
},
|
||||
weight: users[0].weight,
|
||||
createdAt: new Date(0),
|
||||
},
|
||||
{
|
||||
user: { id: users[1].id, email: users[1].email, credentials: [], selectedCalendars: [] },
|
||||
user: {
|
||||
id: users[1].id,
|
||||
email: users[1].email,
|
||||
credentials: [],
|
||||
userLevelSelectedCalendars: [],
|
||||
},
|
||||
weight: users[1].weight,
|
||||
createdAt: new Date(0),
|
||||
},
|
||||
];
|
||||
|
||||
await expect(
|
||||
getLuckyUser({
|
||||
luckyUserService.getLuckyUser({
|
||||
availableUsers: users,
|
||||
eventType: {
|
||||
id: 1,
|
||||
isRRWeightsEnabled: true,
|
||||
team: { rrResetInterval: RRResetInterval.MONTH, rrTimestampBasis: RRTimestampBasis.CREATED_AT },
|
||||
includeNoShowInRRCalculation: false,
|
||||
},
|
||||
allRRHosts,
|
||||
routingFormResponse: null,
|
||||
@@ -426,24 +435,35 @@ describe("maximize availability and weights", () => {
|
||||
|
||||
const allRRHosts = [
|
||||
{
|
||||
user: { id: users[0].id, email: users[0].email, credentials: [], selectedCalendars: [] },
|
||||
user: {
|
||||
id: users[0].id,
|
||||
email: users[0].email,
|
||||
credentials: [],
|
||||
userLevelSelectedCalendars: [],
|
||||
},
|
||||
weight: users[0].weight,
|
||||
createdAt: new Date(0),
|
||||
},
|
||||
{
|
||||
user: { id: users[1].id, email: users[1].email, credentials: [], selectedCalendars: [] },
|
||||
user: {
|
||||
id: users[1].id,
|
||||
email: users[1].email,
|
||||
credentials: [],
|
||||
userLevelSelectedCalendars: [],
|
||||
},
|
||||
weight: users[1].weight,
|
||||
createdAt: new Date(0),
|
||||
},
|
||||
];
|
||||
|
||||
await expect(
|
||||
getLuckyUser({
|
||||
luckyUserService.getLuckyUser({
|
||||
availableUsers: users,
|
||||
eventType: {
|
||||
id: 1,
|
||||
isRRWeightsEnabled: true,
|
||||
team: { rrResetInterval: RRResetInterval.DAY, rrTimestampBasis: RRTimestampBasis.CREATED_AT },
|
||||
includeNoShowInRRCalculation: false,
|
||||
},
|
||||
allRRHosts,
|
||||
routingFormResponse: null,
|
||||
@@ -534,24 +554,35 @@ describe("maximize availability and weights", () => {
|
||||
|
||||
const allRRHosts = [
|
||||
{
|
||||
user: { id: users[0].id, email: users[0].email, credentials: [], selectedCalendars: [] },
|
||||
user: {
|
||||
id: users[0].id,
|
||||
email: users[0].email,
|
||||
credentials: [],
|
||||
userLevelSelectedCalendars: [],
|
||||
},
|
||||
weight: users[0].weight,
|
||||
createdAt: new Date(0),
|
||||
},
|
||||
{
|
||||
user: { id: users[1].id, email: users[1].email, credentials: [], selectedCalendars: [] },
|
||||
user: {
|
||||
id: users[1].id,
|
||||
email: users[1].email,
|
||||
credentials: [],
|
||||
userLevelSelectedCalendars: [],
|
||||
},
|
||||
weight: users[1].weight,
|
||||
createdAt: new Date(0),
|
||||
},
|
||||
];
|
||||
|
||||
await expect(
|
||||
getLuckyUser({
|
||||
luckyUserService.getLuckyUser({
|
||||
availableUsers: users,
|
||||
eventType: {
|
||||
id: 1,
|
||||
isRRWeightsEnabled: true,
|
||||
team: { rrResetInterval: RRResetInterval.DAY, rrTimestampBasis: RRTimestampBasis.CREATED_AT },
|
||||
includeNoShowInRRCalculation: false,
|
||||
},
|
||||
allRRHosts,
|
||||
routingFormResponse: null,
|
||||
@@ -589,12 +620,22 @@ describe("maximize availability and weights", () => {
|
||||
|
||||
const allRRHosts = [
|
||||
{
|
||||
user: { id: users[0].id, email: users[0].email, credentials: [], selectedCalendars: [] },
|
||||
user: {
|
||||
id: users[0].id,
|
||||
email: users[0].email,
|
||||
credentials: [],
|
||||
userLevelSelectedCalendars: [],
|
||||
},
|
||||
weight: users[0].weight,
|
||||
createdAt: new Date(0),
|
||||
},
|
||||
{
|
||||
user: { id: users[1].id, email: users[1].email, credentials: [], selectedCalendars: [] },
|
||||
user: {
|
||||
id: users[1].id,
|
||||
email: users[1].email,
|
||||
credentials: [],
|
||||
userLevelSelectedCalendars: [],
|
||||
},
|
||||
weight: users[1].weight,
|
||||
createdAt: new Date(0),
|
||||
},
|
||||
@@ -641,17 +682,18 @@ describe("maximize availability and weights", () => {
|
||||
]);
|
||||
|
||||
await expect(
|
||||
getLuckyUser({
|
||||
luckyUserService.getLuckyUser({
|
||||
availableUsers: users,
|
||||
eventType: {
|
||||
id: 1,
|
||||
isRRWeightsEnabled: true,
|
||||
team: { rrResetInterval: RRResetInterval.MONTH, rrTimestampBasis: RRTimestampBasis.CREATED_AT },
|
||||
includeNoShowInRRCalculation: false,
|
||||
},
|
||||
allRRHosts,
|
||||
routingFormResponse: null,
|
||||
})
|
||||
).resolves.toStrictEqual(users[1]); // user[1] has one more bookings, but user[0] has calibration 2
|
||||
).resolves.toStrictEqual(users[1]);
|
||||
|
||||
const queryArgs = prismaMock.booking.findMany.mock.calls[0][0];
|
||||
|
||||
@@ -695,12 +737,22 @@ describe("maximize availability and weights", () => {
|
||||
|
||||
const allRRHosts = [
|
||||
{
|
||||
user: { id: users[0].id, email: users[0].email, credentials: [], selectedCalendars: [] },
|
||||
user: {
|
||||
id: users[0].id,
|
||||
email: users[0].email,
|
||||
credentials: [],
|
||||
userLevelSelectedCalendars: [],
|
||||
},
|
||||
weight: users[0].weight,
|
||||
createdAt: new Date(0),
|
||||
},
|
||||
{
|
||||
user: { id: users[1].id, email: users[1].email, credentials: [], selectedCalendars: [] },
|
||||
user: {
|
||||
id: users[1].id,
|
||||
email: users[1].email,
|
||||
credentials: [],
|
||||
userLevelSelectedCalendars: [],
|
||||
},
|
||||
weight: users[1].weight,
|
||||
createdAt: new Date(0),
|
||||
},
|
||||
@@ -751,17 +803,18 @@ describe("maximize availability and weights", () => {
|
||||
]);
|
||||
|
||||
await expect(
|
||||
getLuckyUser({
|
||||
luckyUserService.getLuckyUser({
|
||||
availableUsers: users,
|
||||
eventType: {
|
||||
id: 1,
|
||||
isRRWeightsEnabled: true,
|
||||
team: { rrResetInterval: RRResetInterval.MONTH, rrTimestampBasis: RRTimestampBasis.CREATED_AT },
|
||||
includeNoShowInRRCalculation: false,
|
||||
},
|
||||
allRRHosts,
|
||||
routingFormResponse: null,
|
||||
})
|
||||
).resolves.toStrictEqual(users[1]); // user[1] has one more booking, but user[0] has calibration 2
|
||||
).resolves.toStrictEqual(users[1]);
|
||||
|
||||
const queryArgs = prismaMock.booking.findMany.mock.calls[0][0];
|
||||
|
||||
@@ -809,12 +862,22 @@ describe("maximize availability and weights", () => {
|
||||
|
||||
const allRRHosts = [
|
||||
{
|
||||
user: { id: users[0].id, email: users[0].email, credentials: [], selectedCalendars: [] },
|
||||
user: {
|
||||
id: users[0].id,
|
||||
email: users[0].email,
|
||||
credentials: [],
|
||||
userLevelSelectedCalendars: [],
|
||||
},
|
||||
weight: users[0].weight,
|
||||
createdAt: middleOfMonth,
|
||||
},
|
||||
{
|
||||
user: { id: users[1].id, email: users[1].email, credentials: [], selectedCalendars: [] },
|
||||
user: {
|
||||
id: users[1].id,
|
||||
email: users[1].email,
|
||||
credentials: [],
|
||||
userLevelSelectedCalendars: [],
|
||||
},
|
||||
weight: users[1].weight,
|
||||
createdAt: new Date(0),
|
||||
},
|
||||
@@ -823,7 +886,6 @@ describe("maximize availability and weights", () => {
|
||||
CalendarManagerMock.getBusyCalendarTimes.mockResolvedValue({ success: true, data: [] });
|
||||
prismaMock.outOfOfficeEntry.findMany.mockResolvedValue([]);
|
||||
|
||||
// TODO: we may be able to use native prisma generics somehow?
|
||||
prismaMock.user.findMany.mockResolvedValue(users);
|
||||
prismaMock.host.findMany.mockResolvedValue([
|
||||
{
|
||||
@@ -832,8 +894,10 @@ describe("maximize availability and weights", () => {
|
||||
createdAt: allRRHosts[0].createdAt,
|
||||
},
|
||||
]);
|
||||
// findMany bookings are BEFORE the new host (user 1) was added, calibration=2.
|
||||
prismaMock.booking.findMany.mockResolvedValue([
|
||||
prismaMock.booking.findMany.mockResolvedValueOnce([]);
|
||||
prismaMock.booking.findMany.mockResolvedValueOnce([]);
|
||||
prismaMock.booking.findMany.mockResolvedValueOnce([]);
|
||||
prismaMock.booking.findMany.mockResolvedValueOnce([
|
||||
buildBooking({
|
||||
id: 4,
|
||||
userId: 2,
|
||||
@@ -846,19 +910,24 @@ describe("maximize availability and weights", () => {
|
||||
}),
|
||||
]);
|
||||
await expect(
|
||||
getLuckyUser({
|
||||
luckyUserService.getLuckyUser({
|
||||
availableUsers: users,
|
||||
eventType: {
|
||||
id: 1,
|
||||
isRRWeightsEnabled: true,
|
||||
team: { rrResetInterval: RRResetInterval.MONTH, rrTimestampBasis: RRTimestampBasis.CREATED_AT },
|
||||
includeNoShowInRRCalculation: false,
|
||||
},
|
||||
allRRHosts,
|
||||
routingFormResponse: null,
|
||||
})
|
||||
).resolves.toStrictEqual(users[1]);
|
||||
// findMany bookings are AFTER the new host (user 1) was added, calibration=0.
|
||||
prismaMock.booking.findMany.mockResolvedValue([
|
||||
|
||||
prismaMock.booking.findMany.mockResolvedValueOnce([]);
|
||||
prismaMock.booking.findMany.mockResolvedValueOnce([]);
|
||||
prismaMock.booking.findMany.mockResolvedValueOnce([]);
|
||||
prismaMock.booking.findMany.mockResolvedValueOnce([
|
||||
// Mock 6: All hosts
|
||||
buildBooking({
|
||||
id: 4,
|
||||
userId: 2,
|
||||
@@ -871,12 +940,13 @@ describe("maximize availability and weights", () => {
|
||||
}),
|
||||
]);
|
||||
await expect(
|
||||
getLuckyUser({
|
||||
luckyUserService.getLuckyUser({
|
||||
availableUsers: users,
|
||||
eventType: {
|
||||
id: 1,
|
||||
isRRWeightsEnabled: true,
|
||||
team: { rrResetInterval: RRResetInterval.MONTH, rrTimestampBasis: RRTimestampBasis.CREATED_AT },
|
||||
includeNoShowInRRCalculation: false,
|
||||
},
|
||||
allRRHosts,
|
||||
routingFormResponse: null,
|
||||
@@ -1019,11 +1089,16 @@ describe("attribute weights and virtual queues", () => {
|
||||
],
|
||||
});
|
||||
|
||||
const queuesAndAttributesData = await prepareQueuesAndAttributesData({
|
||||
const queuesAndAttributesData = await luckyUserService.prepareQueuesAndAttributesData({
|
||||
eventType: {
|
||||
id: 1,
|
||||
isRRWeightsEnabled: true,
|
||||
team: { parentId: 1, rrResetInterval: RRResetInterval.DAY },
|
||||
team: {
|
||||
parentId: 1,
|
||||
rrResetInterval: RRResetInterval.DAY,
|
||||
rrTimestampBasis: RRTimestampBasis.CREATED_AT,
|
||||
},
|
||||
includeNoShowInRRCalculation: false,
|
||||
},
|
||||
routingFormResponse,
|
||||
allRRHosts: [
|
||||
@@ -1032,7 +1107,7 @@ describe("attribute weights and virtual queues", () => {
|
||||
id: 1,
|
||||
email: "test1@example.com",
|
||||
credentials: [],
|
||||
selectedCalendars: [],
|
||||
userLevelSelectedCalendars: [],
|
||||
},
|
||||
createdAt: new Date(),
|
||||
weight: 10,
|
||||
@@ -1042,7 +1117,7 @@ describe("attribute weights and virtual queues", () => {
|
||||
id: 2,
|
||||
email: "test2@example.com",
|
||||
credentials: [],
|
||||
selectedCalendars: [],
|
||||
userLevelSelectedCalendars: [],
|
||||
},
|
||||
createdAt: new Date(),
|
||||
weight: 150,
|
||||
@@ -1158,11 +1233,16 @@ describe("attribute weights and virtual queues", () => {
|
||||
],
|
||||
});
|
||||
|
||||
const queuesAndAttributesData = await prepareQueuesAndAttributesData({
|
||||
const queuesAndAttributesData = await luckyUserService.prepareQueuesAndAttributesData({
|
||||
eventType: {
|
||||
id: 1,
|
||||
isRRWeightsEnabled: true,
|
||||
team: { parentId: 1, rrResetInterval: RRResetInterval.DAY },
|
||||
team: {
|
||||
parentId: 1,
|
||||
rrResetInterval: RRResetInterval.DAY,
|
||||
rrTimestampBasis: RRTimestampBasis.CREATED_AT,
|
||||
},
|
||||
includeNoShowInRRCalculation: false,
|
||||
},
|
||||
routingFormResponse,
|
||||
allRRHosts: [
|
||||
@@ -1171,7 +1251,7 @@ describe("attribute weights and virtual queues", () => {
|
||||
id: 1,
|
||||
email: "test1@example.com",
|
||||
credentials: [],
|
||||
selectedCalendars: [],
|
||||
userLevelSelectedCalendars: [],
|
||||
},
|
||||
createdAt: new Date(),
|
||||
weight: 10,
|
||||
@@ -1181,7 +1261,7 @@ describe("attribute weights and virtual queues", () => {
|
||||
id: 2,
|
||||
email: "test2@example.com",
|
||||
credentials: [],
|
||||
selectedCalendars: [],
|
||||
userLevelSelectedCalendars: [],
|
||||
},
|
||||
createdAt: new Date(),
|
||||
weight: 150,
|
||||
@@ -1300,7 +1380,7 @@ describe("attribute weights and virtual queues", () => {
|
||||
response: {
|
||||
[fieldId]: {
|
||||
label: "company_size",
|
||||
value: attributeOptionIdFirst, // booking part of virtual queue
|
||||
value: attributeOptionIdFirst,
|
||||
},
|
||||
},
|
||||
createdAt: new Date("2022-01-25T06:30:00.000Z"),
|
||||
@@ -1319,7 +1399,7 @@ describe("attribute weights and virtual queues", () => {
|
||||
response: {
|
||||
[fieldId]: {
|
||||
label: "company_size",
|
||||
value: attributeOptionIdFirst, // booking part of virtual queue
|
||||
value: attributeOptionIdFirst,
|
||||
},
|
||||
},
|
||||
createdAt: new Date("2022-01-25T05:30:00.000Z"),
|
||||
@@ -1338,7 +1418,7 @@ describe("attribute weights and virtual queues", () => {
|
||||
response: {
|
||||
[fieldId]: {
|
||||
label: "company_size",
|
||||
value: attributeOptionIdSecond, // different queue, booking doesn't count
|
||||
value: attributeOptionIdSecond,
|
||||
},
|
||||
},
|
||||
createdAt: new Date("2022-01-25T05:30:00.000Z"),
|
||||
@@ -1377,19 +1457,29 @@ describe("attribute weights and virtual queues", () => {
|
||||
|
||||
const allRRHosts = [
|
||||
{
|
||||
user: { id: users[0].id, email: users[0].email, credentials: [], selectedCalendars: [] },
|
||||
user: {
|
||||
id: users[0].id,
|
||||
email: users[0].email,
|
||||
credentials: [],
|
||||
userLevelSelectedCalendars: [],
|
||||
},
|
||||
weight: users[0].weight,
|
||||
createdAt: new Date(0),
|
||||
},
|
||||
{
|
||||
user: { id: users[1].id, email: users[1].email, credentials: [], selectedCalendars: [] },
|
||||
user: {
|
||||
id: users[1].id,
|
||||
email: users[1].email,
|
||||
credentials: [],
|
||||
userLevelSelectedCalendars: [],
|
||||
},
|
||||
weight: users[1].weight,
|
||||
createdAt: new Date(0),
|
||||
},
|
||||
];
|
||||
|
||||
await expect(
|
||||
getLuckyUser({
|
||||
luckyUserService.getLuckyUser({
|
||||
availableUsers: users,
|
||||
eventType: {
|
||||
id: 1,
|
||||
@@ -1399,6 +1489,7 @@ describe("attribute weights and virtual queues", () => {
|
||||
rrResetInterval: RRResetInterval.DAY,
|
||||
rrTimestampBasis: RRTimestampBasis.CREATED_AT,
|
||||
},
|
||||
includeNoShowInRRCalculation: false,
|
||||
},
|
||||
allRRHosts,
|
||||
routingFormResponse,
|
||||
|
||||
+936
-1027
File diff suppressed because it is too large
Load Diff
@@ -55,4 +55,45 @@ export class PrismaAttributeRepository {
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async findUniqueWithWeights({
|
||||
teamId,
|
||||
attributeId,
|
||||
isWeightsEnabled = true,
|
||||
}: {
|
||||
teamId: number;
|
||||
attributeId: string;
|
||||
isWeightsEnabled?: boolean;
|
||||
}) {
|
||||
return await this.prismaClient.attribute.findUnique({
|
||||
where: {
|
||||
id: attributeId,
|
||||
teamId,
|
||||
isWeightsEnabled,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
slug: true,
|
||||
type: true,
|
||||
options: {
|
||||
select: {
|
||||
id: true,
|
||||
value: true,
|
||||
slug: true,
|
||||
assignedUsers: {
|
||||
select: {
|
||||
member: {
|
||||
select: {
|
||||
userId: true,
|
||||
},
|
||||
},
|
||||
weight: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,4 +14,27 @@ export class HostRepository {
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async findHostsCreatedInInterval({
|
||||
eventTypeId,
|
||||
userIds,
|
||||
startDate,
|
||||
}: {
|
||||
eventTypeId: number;
|
||||
userIds: number[];
|
||||
startDate: Date;
|
||||
}) {
|
||||
return await this.prismaClient.host.findMany({
|
||||
where: {
|
||||
userId: {
|
||||
in: userIds,
|
||||
},
|
||||
eventTypeId,
|
||||
isFixed: false,
|
||||
createdAt: {
|
||||
gte: startDate,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,70 +80,99 @@ export class PrismaOOORepository {
|
||||
});
|
||||
}
|
||||
|
||||
async findUserOOODays({userId, dateTo, dateFrom}: {userId: number, dateTo: string, dateFrom: string}) {
|
||||
async findUserOOODays({ userId, dateTo, dateFrom }: { userId: number; dateTo: string; dateFrom: string }) {
|
||||
return this.prismaClient.outOfOfficeEntry.findMany({
|
||||
where: {
|
||||
userId,
|
||||
OR: [
|
||||
// outside of range
|
||||
// (start <= 'dateTo' AND end >= 'dateFrom')
|
||||
{
|
||||
start: {
|
||||
lte: dateTo,
|
||||
},
|
||||
end: {
|
||||
gte: dateFrom,
|
||||
},
|
||||
},
|
||||
// start is between dateFrom and dateTo but end is outside of range
|
||||
// (start <= 'dateTo' AND end >= 'dateTo')
|
||||
{
|
||||
start: {
|
||||
lte: dateTo,
|
||||
},
|
||||
|
||||
end: {
|
||||
gte: dateTo,
|
||||
},
|
||||
},
|
||||
// end is between dateFrom and dateTo but start is outside of range
|
||||
// (start <= 'dateFrom' OR end <= 'dateTo')
|
||||
{
|
||||
start: {
|
||||
lte: dateFrom,
|
||||
},
|
||||
|
||||
end: {
|
||||
lte: dateTo,
|
||||
},
|
||||
},
|
||||
],
|
||||
where: {
|
||||
userId,
|
||||
OR: [
|
||||
// outside of range
|
||||
// (start <= 'dateTo' AND end >= 'dateFrom')
|
||||
{
|
||||
start: {
|
||||
lte: dateTo,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
start: true,
|
||||
end: true,
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
toUser: {
|
||||
select: {
|
||||
id: true,
|
||||
username: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
reason: {
|
||||
select: {
|
||||
id: true,
|
||||
emoji: true,
|
||||
reason: true,
|
||||
},
|
||||
},
|
||||
end: {
|
||||
gte: dateFrom,
|
||||
},
|
||||
});
|
||||
},
|
||||
// start is between dateFrom and dateTo but end is outside of range
|
||||
// (start <= 'dateTo' AND end >= 'dateTo')
|
||||
{
|
||||
start: {
|
||||
lte: dateTo,
|
||||
},
|
||||
|
||||
end: {
|
||||
gte: dateTo,
|
||||
},
|
||||
},
|
||||
// end is between dateFrom and dateTo but start is outside of range
|
||||
// (start <= 'dateFrom' OR end <= 'dateTo')
|
||||
{
|
||||
start: {
|
||||
lte: dateFrom,
|
||||
},
|
||||
|
||||
end: {
|
||||
lte: dateTo,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
start: true,
|
||||
end: true,
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
toUser: {
|
||||
select: {
|
||||
id: true,
|
||||
username: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
reason: {
|
||||
select: {
|
||||
id: true,
|
||||
emoji: true,
|
||||
reason: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async findOOOEntriesInInterval({
|
||||
userIds,
|
||||
startDate,
|
||||
endDate,
|
||||
}: {
|
||||
userIds: number[];
|
||||
startDate: Date;
|
||||
endDate: Date;
|
||||
}) {
|
||||
return this.prismaClient.outOfOfficeEntry.findMany({
|
||||
where: {
|
||||
userId: {
|
||||
in: userIds,
|
||||
},
|
||||
start: {
|
||||
lte: endDate,
|
||||
},
|
||||
end: {
|
||||
gte: startDate,
|
||||
},
|
||||
},
|
||||
select: {
|
||||
start: true,
|
||||
end: true,
|
||||
userId: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import { availabilityUserSelect } from "@calcom/prisma";
|
||||
import type { PrismaClient } from "@calcom/prisma";
|
||||
import type { Prisma, User as UserType } from "@calcom/prisma/client";
|
||||
import type { CreationSource } from "@calcom/prisma/enums";
|
||||
import { MembershipRole } from "@calcom/prisma/enums";
|
||||
import { MembershipRole, BookingStatus } from "@calcom/prisma/enums";
|
||||
import { credentialForCalendarServiceSelect } from "@calcom/prisma/selects/credential";
|
||||
import { userMetadata } from "@calcom/prisma/zod-utils";
|
||||
import type { UpId, UserProfile } from "@calcom/types/UserProfile";
|
||||
@@ -1028,4 +1028,43 @@ export class UserRepository {
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async findUsersWithLastBooking({ userIds, eventTypeId }: { userIds: number[]; eventTypeId: number }) {
|
||||
return this.prismaClient.user.findMany({
|
||||
where: {
|
||||
id: {
|
||||
in: userIds,
|
||||
},
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
bookings: {
|
||||
select: {
|
||||
createdAt: true,
|
||||
},
|
||||
where: {
|
||||
eventTypeId,
|
||||
status: BookingStatus.ACCEPTED,
|
||||
attendees: {
|
||||
some: {
|
||||
noShow: false,
|
||||
},
|
||||
},
|
||||
OR: [
|
||||
{
|
||||
noShowHost: false,
|
||||
},
|
||||
{
|
||||
noShowHost: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: "desc",
|
||||
},
|
||||
take: 1,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,2 +1,4 @@
|
||||
export { LuckyUserService } from "@calcom/lib/server/getLuckyUser";
|
||||
|
||||
export { CheckBookingLimitsService } from "@calcom/lib/intervalLimits/server/checkBookingLimits";
|
||||
export { CheckBookingAndDurationLimitsService } from "@calcom/features/bookings/lib/handleNewBooking/checkBookingAndDurationLimits";
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export { PrismaAttributeRepository } from "@calcom/lib/server/repository/PrismaAttributeRepository";
|
||||
export { PrismaOOORepository } from "@calcom/lib/server/repository/ooo";
|
||||
export { ScheduleRepository as PrismaScheduleRepository } from "@calcom/lib/server/repository/schedule";
|
||||
export { BookingRepository as PrismaBookingRepository } from "@calcom/lib/server/repository/booking";
|
||||
@@ -8,3 +9,4 @@ export { TeamRepository as PrismaTeamRepository } from "@calcom/lib/server/repos
|
||||
export { UserRepository as PrismaUserRepository } from "@calcom/lib/server/repository/user";
|
||||
export { FeaturesRepository as PrismaFeaturesRepository } from "@calcom/features/flags/features.repository";
|
||||
export { MembershipRepository as PrismaMembershipRepository } from "@calcom/lib/server/repository/membership";
|
||||
export { HostRepository as PrismaHostRepository } from "@calcom/lib/server/repository/host";
|
||||
|
||||
+3
-2
@@ -10,10 +10,10 @@ import type { NextApiResponse } from "next";
|
||||
import { enrichFormWithMigrationData } from "@calcom/app-store/routing-forms/enrichFormWithMigrationData";
|
||||
import { getUrlSearchParamsToForwardForTestPreview } from "@calcom/app-store/routing-forms/pages/routing-link/getUrlSearchParamsToForward";
|
||||
import { enrichHostsWithDelegationCredentials } from "@calcom/lib/delegationCredential/server";
|
||||
import { getLuckyUserService } from "@calcom/lib/di/containers/LuckyUser";
|
||||
import { entityPrismaWhereClause } from "@calcom/lib/entityPermissionUtils.server";
|
||||
import { fromEntriesWithDuplicateKeys } from "@calcom/lib/fromEntriesWithDuplicateKeys";
|
||||
import { findTeamMembersMatchingAttributeLogic } from "@calcom/lib/raqb/findTeamMembersMatchingAttributeLogic";
|
||||
import { getOrderedListOfLuckyUsers } from "@calcom/lib/server/getLuckyUser";
|
||||
import { EventTypeRepository } from "@calcom/lib/server/repository/eventTypeRepository";
|
||||
import { UserRepository } from "@calcom/lib/server/repository/user";
|
||||
import type { PrismaClient } from "@calcom/prisma";
|
||||
@@ -270,12 +270,13 @@ export const findTeamMembersMatchingAttributeLogicOfRouteHandler = async ({
|
||||
}
|
||||
|
||||
const timeBeforeGetOrderedLuckyUsers = performance.now();
|
||||
const luckyUserService = getLuckyUserService();
|
||||
const {
|
||||
users: orderedLuckyUsers,
|
||||
perUserData,
|
||||
isUsingAttributeWeights,
|
||||
} = matchingHosts.length
|
||||
? await getOrderedListOfLuckyUsers({
|
||||
? await luckyUserService.getOrderedListOfLuckyUsers({
|
||||
// Assuming all are available
|
||||
availableUsers: [
|
||||
{
|
||||
|
||||
@@ -2493,7 +2493,7 @@ __metadata:
|
||||
"@axiomhq/winston": ^1.2.0
|
||||
"@calcom/platform-constants": "*"
|
||||
"@calcom/platform-enums": "*"
|
||||
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.321"
|
||||
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.333"
|
||||
"@calcom/platform-types": "*"
|
||||
"@calcom/platform-utils": "*"
|
||||
"@calcom/prisma": "*"
|
||||
@@ -3553,13 +3553,13 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@calcom/platform-libraries@npm:@calcom/platform-libraries@0.0.321":
|
||||
version: 0.0.321
|
||||
resolution: "@calcom/platform-libraries@npm:0.0.321"
|
||||
"@calcom/platform-libraries@npm:@calcom/platform-libraries@0.0.333":
|
||||
version: 0.0.333
|
||||
resolution: "@calcom/platform-libraries@npm:0.0.333"
|
||||
dependencies:
|
||||
"@calcom/features": "*"
|
||||
"@calcom/lib": "*"
|
||||
checksum: 634b092311d9952d6ce1eec3a55fc18ab314d282d087ca7d9e9baf3f53f1e718e81d2b2cb6613f377d129c864290eb14f89770cfdd0c14e8968a34db093163f5
|
||||
checksum: 321c792aeb36ec69b7a02756ac550736bd9a71e4544fcebd9ab625bc2132edd5bf3e86295af05d797ff82de0ec35f3954c7409543e4325b7cbbbb55ce786ef60
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
||||
Reference in New Issue
Block a user