Files
calendar/packages/features/attributes/repositories/PrismaAttributeToUserRepository.ts
T
Joe Au-YeungGitHubClaude Opus 4.5joe@cal.com <j.auyeung419@gmail.com>joe@cal.com <j.auyeung419@gmail.com>Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
8d79209e74 perf: Optmize DB queries around attribute routing (#27600)
* Replace n x m array functions with maps

* feat: add attributeIds filter to findManyByOrgMembershipIds

Allow filtering attribute-to-user assignments by specific attribute IDs.
This enables querying only the attributes needed for a routing rule
instead of fetching all attributes for all team members.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* feat: extract attribute IDs from routing rules and filter queries

Add extractAttributeIdsFromQueryValue helper to parse routing rules
and identify which attributes are referenced. Pass these IDs through
getAttributesAssignmentData to _queryAllData to only fetch the
attribute assignments that are actually needed for evaluation.

For a team with 785 members and 14 attributes, if a routing rule
only references 2-3 attributes, this reduces the query from ~3500
rows to ~500 rows.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* feat: use attribute filtering in findTeamMembersMatchingAttributeLogic

Extract attribute IDs from the routing rules (main + fallback) and
pass them to getAttributesForLogic to only fetch necessary data.

This completes the optimization to reduce database load when
evaluating routing rules that only reference a subset of attributes.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* feat: filter attributes query by attribute IDs

Also filter the attributes query (not just attributeToUser assignments)
by the attribute IDs referenced in routing rules.

For a routing rule that references 2 out of 14 attributes with 6524
total options, this reduces:
- Attributes fetched: 14 → 2
- Options fetched: 6524 → ~930 (proportional reduction)
- Fewer regex replacements in replaceAttributeOptionIdsWithOptionLabel

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* test: add comprehensive test coverage for attribute routing query optimization

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>

* Revert "Replace n x m array functions with maps"

This reverts commit 312eeb03bd1aba19760f093c798861a23b5fc66f.

* fix: re-establish extractAttributeIdsFromQueryValue mock after resetAllMocks

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-02-04 11:28:46 -05:00

63 lines
1.7 KiB
TypeScript

import type { PrismaClient } from "@calcom/prisma";
import type { Prisma } from "@calcom/prisma/generated/prisma/client";
export class PrismaAttributeToUserRepository {
constructor(private prismaClient: PrismaClient) {}
async createManySkipDuplicates(data: Prisma.AttributeToUserCreateManyInput[]) {
return await this.prismaClient.attributeToUser.createMany({ data, skipDuplicates: true });
}
async deleteMany(where: Prisma.AttributeToUserWhereInput) {
if (Object.keys(where).length === 0) {
throw new Error("Empty where clause provided to deleteMany. Potential data loss risk.");
}
return await this.prismaClient.attributeToUser.deleteMany({ where });
}
async findManyIncludeAttribute(where: Prisma.AttributeToUserWhereInput) {
return await this.prismaClient.attributeToUser.findMany({
where,
include: {
attributeOption: {
select: {
attribute: true,
value: true,
slug: true,
},
},
},
});
}
async findManyByOrgMembershipIds({
orgMembershipIds,
attributeIds,
}: {
orgMembershipIds: number[];
attributeIds?: string[];
}) {
if (!orgMembershipIds.length) {
return [];
}
const attributesAssignedToTeamMembers = await this.prismaClient.attributeToUser.findMany({
where: {
memberId: {
in: orgMembershipIds,
},
// Only filter by attribute IDs if provided
...(attributeIds?.length && {
attributeOption: {
attributeId: {
in: attributeIds,
},
},
}),
},
});
return attributesAssignedToTeamMembers;
}
}