fix: enabling weights on RR events breaks atoms (#20297)

* fix: enabling weights on RR events breaks atoms

* Update find-team-members-matching-attribute.input.ts

* refactor: Extract team member processing logic into separate hook

* Update find-team-members-matching-attribute.input.ts

* moved `TeamMemberDto` and `FindTeamMembersMatchingAttributeOutputDto`  to platform-types

* `team-members.output.ts` -> `team-members-attributes.output.ts`

* upgrade platform-libraries
This commit is contained in:
Somay Chauhan
2025-04-07 12:11:19 +05:30
committed by GitHub
parent 1fb4147940
commit d158a1e007
14 changed files with 327 additions and 67 deletions
+1 -1
View File
@@ -38,7 +38,7 @@
"@axiomhq/winston": "^1.2.0",
"@calcom/platform-constants": "*",
"@calcom/platform-enums": "*",
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.171",
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.172",
"@calcom/platform-libraries-0.0.2": "npm:@calcom/platform-libraries@0.0.2",
"@calcom/platform-types": "*",
"@calcom/platform-utils": "*",
@@ -2,6 +2,7 @@ import { EventTypesModule_2024_06_14 } from "@/ee/event-types/event-types_2024_0
import { SchedulesRepository_2024_06_11 } from "@/ee/schedules/schedules_2024_06_11/schedules.repository";
import { AtomsRepository } from "@/modules/atoms/atoms.repository";
import { AtomsController } from "@/modules/atoms/controllers/atoms.controller";
import { AttributesAtomsService } from "@/modules/atoms/services/attributes-atom.service";
import { ConferencingAtomsService } from "@/modules/atoms/services/conferencing-atom.service";
import { EventTypesAtomService } from "@/modules/atoms/services/event-types-atom.service";
import { CredentialsRepository } from "@/modules/credentials/credentials.repository";
@@ -19,6 +20,7 @@ import { Module } from "@nestjs/common";
providers: [
EventTypesAtomService,
ConferencingAtomsService,
AttributesAtomsService,
MembershipsRepository,
CredentialsRepository,
UsersRepository,
@@ -3,6 +3,8 @@ import {
BulkUpdateEventTypeToDefaultLocationDto,
EventTypesAppInput,
} from "@/modules/atoms/inputs/event-types-app.input";
import { FindTeamMembersMatchingAttributeQueryDto } from "@/modules/atoms/inputs/find-team-members-matching-attribute.input";
import { AttributesAtomsService } from "@/modules/atoms/services/attributes-atom.service";
import { ConferencingAtomsService } from "@/modules/atoms/services/conferencing-atom.service";
import { EventTypesAtomService } from "@/modules/atoms/services/event-types-atom.service";
import { PlatformPlan } from "@/modules/auth/decorators/billing/platform-plan.decorator";
@@ -34,6 +36,8 @@ import { ConnectedApps } from "@calcom/platform-libraries/app-store";
import type { UpdateEventTypeReturn } from "@calcom/platform-libraries/event-types";
import { ApiResponse } from "@calcom/platform-types";
import { FindTeamMembersMatchingAttributeResponseDto } from "../outputs/find-team-members-matching-attribute.output";
/*
Endpoints used only by platform atoms, reusing code from other modules, data is already formatted and ready to be used by frontend atoms
@@ -50,7 +54,8 @@ these endpoints should not be recommended for use by third party and are exclude
export class AtomsController {
constructor(
private readonly eventTypesService: EventTypesAtomService,
private readonly conferencingService: ConferencingAtomsService
private readonly conferencingService: ConferencingAtomsService,
private readonly attributesService: AttributesAtomsService
) {}
@Get("event-types/:eventTypeId")
@@ -222,4 +227,25 @@ export class AtomsController {
data: conferencingApps,
};
}
@Get("/organizations/:orgId/teams/:teamId/members-matching-attribute")
@Version(VERSION_NEUTRAL)
@UseGuards(ApiAuthGuard)
async findTeamMembersMatchingAttributes(
@GetUser() user: UserWithProfile,
@Param("teamId", ParseIntPipe) teamId: number,
@Param("orgId", ParseIntPipe) orgId: number,
@Query() query: FindTeamMembersMatchingAttributeQueryDto
): Promise<FindTeamMembersMatchingAttributeResponseDto> {
const result = await this.attributesService.findTeamMembersMatchingAttribute(teamId, orgId, {
attributesQueryValue: query.attributesQueryValue,
isPreview: query.isPreview,
enablePerf: query.enablePerf,
concurrency: query.concurrency,
});
return {
status: SUCCESS_STATUS,
data: result,
};
}
}
@@ -0,0 +1,25 @@
import { ApiPropertyOptional } from "@nestjs/swagger";
import type { TFindTeamMembersMatchingAttributeLogicInputSchema } from "@calcom/platform-libraries";
export class FindTeamMembersMatchingAttributeQueryDto {
@ApiPropertyOptional({
nullable: true,
})
attributesQueryValue: TFindTeamMembersMatchingAttributeLogicInputSchema["attributesQueryValue"];
@ApiPropertyOptional({
type: Boolean,
})
isPreview?: boolean;
@ApiPropertyOptional({
type: Boolean,
})
enablePerf?: boolean;
@ApiPropertyOptional({
type: Number,
})
concurrency?: number;
}
@@ -0,0 +1,19 @@
import { ApiProperty } from "@nestjs/swagger";
import { Expose, Type } from "class-transformer";
import { IsString, ValidateNested } from "class-validator";
import { SUCCESS_STATUS, ERROR_STATUS } from "@calcom/platform-constants";
import { FindTeamMembersMatchingAttributeOutputDto } from "@calcom/platform-types";
export class FindTeamMembersMatchingAttributeResponseDto {
@ApiProperty({ example: SUCCESS_STATUS, enum: [SUCCESS_STATUS, ERROR_STATUS] })
@IsString()
@Expose()
readonly status!: typeof SUCCESS_STATUS | typeof ERROR_STATUS;
@ValidateNested()
@Type(() => FindTeamMembersMatchingAttributeOutputDto)
@Expose()
@ApiProperty({ type: FindTeamMembersMatchingAttributeOutputDto })
readonly data!: FindTeamMembersMatchingAttributeOutputDto;
}
@@ -0,0 +1,64 @@
import { UsersRepository } from "@/modules/users/users.repository";
import { Logger } from "@nestjs/common";
import { Injectable } from "@nestjs/common";
import { findTeamMembersMatchingAttributeLogic } from "@calcom/platform-libraries";
import { FindTeamMembersMatchingAttributeQueryDto } from "../inputs/find-team-members-matching-attribute.input";
@Injectable()
export class AttributesAtomsService {
private logger = new Logger("AttributesAtomService");
constructor(private readonly usersRepository: UsersRepository) {}
async findTeamMembersMatchingAttribute(
teamId: number,
orgId: number,
input: FindTeamMembersMatchingAttributeQueryDto
) {
const {
teamMembersMatchingAttributeLogic: matchingTeamMembersWithResult,
mainAttributeLogicBuildingWarnings: mainWarnings,
fallbackAttributeLogicBuildingWarnings: fallbackWarnings,
troubleshooter,
} = await findTeamMembersMatchingAttributeLogic(
{
teamId,
orgId,
attributesQueryValue: input.attributesQueryValue,
},
{
enablePerf: input.enablePerf,
concurrency: input.concurrency,
enableTroubleshooter: input.enablePerf,
}
);
if (!matchingTeamMembersWithResult) {
return {
troubleshooter,
mainWarnings,
fallbackWarnings,
result: null,
};
}
const matchingTeamMembersIds = matchingTeamMembersWithResult.map(
(member: { userId: number }) => member.userId
);
const matchingTeamMembers = await this.usersRepository.findByIds(matchingTeamMembersIds);
return {
mainWarnings,
fallbackWarnings,
troubleshooter: troubleshooter,
result: matchingTeamMembers.map((user) => ({
id: user.id,
name: user.name,
email: user.email,
})),
};
}
}
@@ -12,8 +12,8 @@ import { formatToLocalizedDate, formatToLocalizedTime } from "@calcom/lib/date-f
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML";
import type { inferSSRProps } from "@calcom/types/inferSSRProps";
import { Icon } from "@calcom/ui/components/icon";
import classNames from "@calcom/ui/classNames";
import { Icon } from "@calcom/ui/components/icon";
import type { getServerSideProps } from "@lib/video/[uid]/getServerSideProps";
@@ -3,12 +3,12 @@
import Link from "next/link";
import { useState, useEffect, useRef, useMemo } from "react";
import { useIsPlatform } from "@calcom/atoms/hooks/useIsPlatform";
import type { Host, TeamMember } from "@calcom/features/eventtypes/lib/types";
import ServerTrans from "@calcom/lib/components/ServerTrans";
import { downloadAsCsv } from "@calcom/lib/csvUtils";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import type { AttributesQueryValue } from "@calcom/lib/raqb/types";
import { trpc } from "@calcom/trpc";
import { Avatar } from "@calcom/ui/components/avatar";
import { buttonClasses } from "@calcom/ui/components/button";
import { Button } from "@calcom/ui/components/button";
@@ -25,6 +25,11 @@ import {
} from "@calcom/ui/components/sheet";
import { showToast } from "@calcom/ui/components/toast";
import {
useTeamMembersWithSegment,
useTeamMembersWithSegmentPlatform,
} from "../../../platform/atoms/event-types/hooks/useTeamMembersWithSegment";
type TeamMemberItemProps = {
member: Omit<TeamMember, "defaultScheduleId"> & { weight?: number };
onWeightChange: (memberId: string, weight: number) => void;
@@ -93,62 +98,6 @@ const TeamMemberItem = ({ member, onWeightChange }: TeamMemberItemProps) => {
);
};
interface UseTeamMembersWithSegmentProps {
initialTeamMembers: TeamMember[];
assignRRMembersUsingSegment: boolean;
teamId?: number;
queryValue?: AttributesQueryValue | null;
value: Host[];
}
const useTeamMembersWithSegment = ({
initialTeamMembers,
assignRRMembersUsingSegment,
teamId,
queryValue,
value,
}: UseTeamMembersWithSegmentProps) => {
const { data: matchingTeamMembersWithResult, isPending } =
trpc.viewer.attributes.findTeamMembersMatchingAttributeLogic.useQuery(
{
teamId: teamId || 0,
attributesQueryValue: queryValue as AttributesQueryValue,
_enablePerf: true,
},
{
enabled: assignRRMembersUsingSegment && !!queryValue && !!teamId,
}
);
const teamMembers = useMemo(() => {
if (assignRRMembersUsingSegment && matchingTeamMembersWithResult?.result) {
return matchingTeamMembersWithResult.result.map((member) => ({
value: member.id.toString(),
label: member.name || member.email,
email: member.email,
avatar: "", // Add avatar with fallback to empty string
}));
}
return initialTeamMembers;
}, [assignRRMembersUsingSegment, matchingTeamMembersWithResult, initialTeamMembers]);
const localWeightsInitialValues = useMemo(
() =>
teamMembers.reduce<Record<string, number>>((acc, member) => {
const memberInValue = value.find((host) => host.userId === parseInt(member.value, 10));
acc[member.value] = memberInValue?.weight ?? 100;
return acc;
}, {}),
[teamMembers, value]
);
return {
teamMembers,
localWeightsInitialValues,
isPending,
};
};
interface Props {
teamMembers: TeamMember[];
value: Host[];
@@ -172,7 +121,11 @@ export const EditWeightsForAllTeamMembers = ({
const { t } = useLocale();
const [searchQuery, setSearchQuery] = useState("");
const { teamMembers, localWeightsInitialValues } = useTeamMembersWithSegment({
const isPlatform = useIsPlatform();
const useTeamMembersHook = isPlatform ? useTeamMembersWithSegmentPlatform : useTeamMembersWithSegment;
const { teamMembers, localWeightsInitialValues } = useTeamMembersHook({
initialTeamMembers,
assignRRMembersUsingSegment,
teamId,
@@ -12,8 +12,8 @@ import type { baseEventTypeSelect } from "@calcom/prisma";
import { SchedulingType } from "@calcom/prisma/enums";
import type { EventTypeModel } from "@calcom/prisma/zod";
import { eventTypeMetaDataSchemaWithTypedApps } from "@calcom/prisma/zod-utils";
import { Badge } from "@calcom/ui/components/badge";
import classNames from "@calcom/ui/classNames";
import { Badge } from "@calcom/ui/components/badge";
export type EventTypeDescriptionProps = {
eventType: Pick<
@@ -0,0 +1,141 @@
import { useQuery } from "@tanstack/react-query";
import { useMemo } from "react";
import type { Host, TeamMember } from "@calcom/features/eventtypes/lib/types";
import type { AttributesQueryValue } from "@calcom/lib/raqb/types";
import { SUCCESS_STATUS } from "@calcom/platform-constants";
import type {
ApiResponse,
ApiSuccessResponse,
TeamMemberDto,
FindTeamMembersMatchingAttributeOutputDto,
} from "@calcom/platform-types";
import { trpc } from "@calcom/trpc/react";
import { useAtomsContext } from "../../hooks/useAtomsContext";
import http from "../../lib/http";
interface UseTeamMembersWithSegmentProps {
initialTeamMembers: TeamMember[];
assignRRMembersUsingSegment: boolean;
teamId?: number;
orgId?: number;
queryValue?: AttributesQueryValue | null;
value: Host[];
}
const useProcessTeamMembersData = ({
initialTeamMembers,
assignRRMembersUsingSegment,
matchingTeamMembersWithResult,
value,
}: {
initialTeamMembers: TeamMember[];
assignRRMembersUsingSegment: boolean;
matchingTeamMembersWithResult?: { result: TeamMemberDto[] | null };
value: Host[];
}) => {
const teamMembers = useMemo(() => {
if (assignRRMembersUsingSegment && matchingTeamMembersWithResult?.result) {
return matchingTeamMembersWithResult.result.map((member) => ({
value: member.id.toString(),
label: member.name || member.email,
email: member.email,
avatar: "",
}));
}
return initialTeamMembers;
}, [assignRRMembersUsingSegment, matchingTeamMembersWithResult, initialTeamMembers]);
const localWeightsInitialValues = useMemo(
() =>
teamMembers.reduce<Record<string, number>>((acc, member) => {
const memberInValue = value.find((host) => host.userId === parseInt(member.value, 10));
acc[member.value] = memberInValue?.weight ?? 100;
return acc;
}, {}),
[teamMembers, value]
);
return {
teamMembers,
localWeightsInitialValues,
};
};
export const useTeamMembersWithSegmentPlatform = ({
initialTeamMembers,
assignRRMembersUsingSegment,
teamId,
orgId,
queryValue,
value,
}: UseTeamMembersWithSegmentProps) => {
const { isInit } = useAtomsContext();
const pathname = `/atoms/organizations/${orgId}/teams/${teamId}/members-matching-attribute?${new URLSearchParams(
{
attributesQueryValue: queryValue ? JSON.stringify(queryValue) : "",
enablePerf: "true",
}
)}`;
const { data: matchingTeamMembersWithResult, isPending } = useQuery({
queryKey: ["teamMembersMatchingAttribute", teamId, orgId, queryValue],
queryFn: async () => {
return http?.get<ApiResponse<FindTeamMembersMatchingAttributeOutputDto>>(pathname).then((res) => {
if (res.data.status === SUCCESS_STATUS) {
return (res.data as ApiSuccessResponse<FindTeamMembersMatchingAttributeOutputDto>).data;
}
throw new Error(res.data.error.message);
});
},
enabled: isInit && !!teamId && !!orgId,
});
const { teamMembers, localWeightsInitialValues } = useProcessTeamMembersData({
initialTeamMembers,
assignRRMembersUsingSegment,
matchingTeamMembersWithResult,
value,
});
return {
teamMembers,
localWeightsInitialValues,
isPending,
};
};
export const useTeamMembersWithSegment = ({
initialTeamMembers,
assignRRMembersUsingSegment,
teamId,
queryValue,
value,
}: UseTeamMembersWithSegmentProps) => {
const { data: matchingTeamMembersWithResult, isPending } =
trpc.viewer.attributes.findTeamMembersMatchingAttributeLogic.useQuery(
{
teamId: teamId || 0,
attributesQueryValue: queryValue as AttributesQueryValue,
_enablePerf: true,
},
{
enabled: assignRRMembersUsingSegment && !!queryValue && !!teamId,
}
);
const { teamMembers, localWeightsInitialValues } = useProcessTeamMembersData({
initialTeamMembers,
assignRRMembersUsingSegment,
matchingTeamMembersWithResult,
value,
});
return {
teamMembers,
localWeightsInitialValues,
isPending,
};
};
+3
View File
@@ -113,4 +113,7 @@ export { SelectedCalendarRepository } from "@calcom/lib/server/repository/select
export { encryptServiceAccountKey } from "@calcom/lib/server/serviceAccountKey";
export { createHandler as createApiKeyHandler } from "@calcom/trpc/server/routers/viewer/apiKeys/create.handler";
export { getCalendarLinks } from "@calcom/lib/bookings/getCalendarLinks";
export { findTeamMembersMatchingAttributeLogic } from "@calcom/lib/raqb/findTeamMembersMatchingAttributeLogic";
export type { TFindTeamMembersMatchingAttributeLogicInputSchema } from "@calcom/trpc/server/routers/viewer/attributes/findTeamMembersMatchingAttributeLogic.schema";
export { checkAdminOrOwner } from "@calcom/features/auth/lib/checkAdminOrOwner";
@@ -1 +1,2 @@
export * from "./team.output";
export * from "./team-members-attributes.output";
@@ -0,0 +1,26 @@
import { Expose, Type } from "class-transformer";
import { IsArray, IsInt, IsOptional, IsString, ValidateNested } from "class-validator";
export class TeamMemberDto {
@IsInt()
@Expose()
readonly id!: number;
@IsOptional()
@IsString()
@Expose()
readonly name!: string | null;
@IsString()
@Expose()
readonly email!: string;
}
export class FindTeamMembersMatchingAttributeOutputDto {
@IsOptional()
@IsArray()
@ValidateNested({ each: true })
@Type(() => TeamMemberDto)
@Expose()
readonly result!: TeamMemberDto[] | null;
}
+5 -5
View File
@@ -2297,7 +2297,7 @@ __metadata:
"@axiomhq/winston": ^1.2.0
"@calcom/platform-constants": "*"
"@calcom/platform-enums": "*"
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.171"
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.172"
"@calcom/platform-libraries-0.0.2": "npm:@calcom/platform-libraries@0.0.2"
"@calcom/platform-types": "*"
"@calcom/platform-utils": "*"
@@ -3345,13 +3345,13 @@ __metadata:
languageName: node
linkType: hard
"@calcom/platform-libraries@npm:@calcom/platform-libraries@0.0.171":
version: 0.0.171
resolution: "@calcom/platform-libraries@npm:0.0.171"
"@calcom/platform-libraries@npm:@calcom/platform-libraries@0.0.172":
version: 0.0.172
resolution: "@calcom/platform-libraries@npm:0.0.172"
dependencies:
"@calcom/features": "*"
"@calcom/lib": "*"
checksum: 8941ebb4436883d1472a82e9178410ead2d5d73101ce285c1d3acd2da6ab9829d787d24e92fe95561d404a862b28b098d5875fbe6b5c4a07666d2dabae84f288
checksum: b7086cff4367194a8495d3ae0081632d8b5afbde43633c2a290296f300f5a898dd13b194665efa2e11946a448d562153674f6922c36b595935c262ea7eb994b7
languageName: node
linkType: hard