From 9de621eb23b313281539a952abda20cdf40cbf13 Mon Sep 17 00:00:00 2001 From: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com> Date: Wed, 4 Feb 2026 12:43:16 -0500 Subject: [PATCH] perf: replace O(n*m) array lookups with O(1) Map lookups in attribute routing (#27599) * Replace n x m array functions with maps * test: add tests and benchmark for Map-based attribute lookup optimization Co-Authored-By: joe@cal.com * test: add real-world enterprise scenario (14 attrs x 6,530 opts) to benchmark Co-Authored-By: joe@cal.com * chore: remove benchmark file Co-Authored-By: joe@cal.com * Rename function * Pass built maps to functions --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../attributes/lib/getAttributes.test.ts | 327 +++++++++++++++++- .../features/attributes/lib/getAttributes.ts | 86 +++-- 2 files changed, 376 insertions(+), 37 deletions(-) diff --git a/packages/features/attributes/lib/getAttributes.test.ts b/packages/features/attributes/lib/getAttributes.test.ts index 2edb922243..db3281afa6 100644 --- a/packages/features/attributes/lib/getAttributes.test.ts +++ b/packages/features/attributes/lib/getAttributes.test.ts @@ -1,10 +1,8 @@ import prismock from "@calcom/testing/lib/__mocks__/prisma"; - -import { describe, expect, it, beforeEach } from "vitest"; - import type { Attribute } from "@calcom/app-store/routing-forms/types/types"; import type { AttributeOption } from "@calcom/prisma/client"; import { AttributeType, MembershipRole } from "@calcom/prisma/enums"; +import { beforeEach, describe, expect, it } from "vitest"; import { getAttributesForTeam, @@ -483,6 +481,329 @@ describe("getAttributes", () => { }); }); + describe("Map-based lookup optimization", () => { + it("should correctly resolve attribute options when multiple attributes have many options", async () => { + const team = await createMockTeam({ orgId }); + const { user, orgMembership } = await createMockUserHavingMembershipWithBothTeamAndOrg({ + orgId, + teamId: team.id, + }); + + // Create multiple attributes with multiple options to test the Map lookup + const attr1 = await createMockAttribute({ + orgId, + id: "attr1", + name: "Department", + slug: "department", + type: AttributeType.SINGLE_SELECT, + options: [ + { id: "dept-eng", value: "Engineering", slug: "engineering", isGroup: false, contains: [] }, + { id: "dept-sales", value: "Sales", slug: "sales", isGroup: false, contains: [] }, + { id: "dept-marketing", value: "Marketing", slug: "marketing", isGroup: false, contains: [] }, + ], + }); + + const attr2 = await createMockAttribute({ + orgId, + id: "attr2", + name: "Location", + slug: "location", + type: AttributeType.SINGLE_SELECT, + options: [ + { id: "loc-us", value: "United States", slug: "us", isGroup: false, contains: [] }, + { id: "loc-uk", value: "United Kingdom", slug: "uk", isGroup: false, contains: [] }, + { id: "loc-de", value: "Germany", slug: "de", isGroup: false, contains: [] }, + ], + }); + + const attr3 = await createMockAttribute({ + orgId, + id: "attr3", + name: "Skills", + slug: "skills", + type: AttributeType.MULTI_SELECT, + options: [ + { id: "skill-js", value: "JavaScript", slug: "javascript", isGroup: false, contains: [] }, + { id: "skill-py", value: "Python", slug: "python", isGroup: false, contains: [] }, + { id: "skill-go", value: "Go", slug: "go", isGroup: false, contains: [] }, + ], + }); + + // Assign options from different attributes + await createMockAttributeAssignment({ + orgMembershipId: orgMembership.id, + attributeOptionId: "dept-eng", + }); + + await createMockAttributeAssignment({ + orgMembershipId: orgMembership.id, + attributeOptionId: "loc-uk", + }); + + await createMockAttributeAssignment({ + orgMembershipId: orgMembership.id, + attributeOptionId: "skill-js", + }); + + await createMockAttributeAssignment({ + orgMembershipId: orgMembership.id, + attributeOptionId: "skill-py", + }); + + const { attributesOfTheOrg, attributesAssignedToTeamMembersWithOptions } = + await getAttributesAssignmentData({ teamId: team.id, orgId }); + + expect(attributesAssignedToTeamMembersWithOptions).toHaveLength(1); + const userAssignment = attributesAssignedToTeamMembersWithOptions[0]; + expect(userAssignment.userId).toBe(user.id); + + // Verify each attribute was correctly resolved via Map lookup + expect(userAssignment.attributes.attr1).toEqual({ + type: AttributeType.SINGLE_SELECT, + attributeOption: { isGroup: false, value: "Engineering", contains: [] }, + }); + + expect(userAssignment.attributes.attr2).toEqual({ + type: AttributeType.SINGLE_SELECT, + attributeOption: { isGroup: false, value: "United Kingdom", contains: [] }, + }); + + expect(userAssignment.attributes.attr3).toEqual({ + type: AttributeType.MULTI_SELECT, + attributeOption: [ + { isGroup: false, value: "JavaScript", contains: [] }, + { isGroup: false, value: "Python", contains: [] }, + ], + }); + + expectAttributesToMatch(attributesOfTheOrg, [attr1, attr2, attr3]); + }); + + it("should handle group options with nested contains correctly via Map lookup", async () => { + const team = await createMockTeam({ orgId }); + const { orgMembership } = await createMockUserHavingMembershipWithBothTeamAndOrg({ + orgId, + teamId: team.id, + }); + + // Create attribute with group option that contains other options + await createMockAttribute({ + orgId, + id: "attr1", + name: "Region", + slug: "region", + type: AttributeType.MULTI_SELECT, + options: [ + { id: "region-na", value: "North America", slug: "na", isGroup: false, contains: [] }, + { id: "region-eu", value: "Europe", slug: "eu", isGroup: false, contains: [] }, + { id: "region-apac", value: "APAC", slug: "apac", isGroup: false, contains: [] }, + { + id: "region-global", + value: "Global", + slug: "global", + isGroup: true, + contains: ["region-na", "region-eu", "region-apac"], + }, + ], + }); + + // Assign the group option + await createMockAttributeAssignment({ + orgMembershipId: orgMembership.id, + attributeOptionId: "region-global", + }); + + const { attributesAssignedToTeamMembersWithOptions } = await getAttributesAssignmentData({ + teamId: team.id, + orgId, + }); + + expect(attributesAssignedToTeamMembersWithOptions).toHaveLength(1); + const userAssignment = attributesAssignedToTeamMembersWithOptions[0]; + + // Verify the group option's contains array was correctly enriched via Map lookup + expect(userAssignment.attributes.attr1).toEqual({ + type: AttributeType.MULTI_SELECT, + attributeOption: { + isGroup: true, + value: "Global", + contains: [ + { id: "region-na", value: "North America", slug: "na" }, + { id: "region-eu", value: "Europe", slug: "eu" }, + { id: "region-apac", value: "APAC", slug: "apac" }, + ], + }, + }); + }); + + it("should handle multiple users with different attribute assignments efficiently", async () => { + const team = await createMockTeam({ orgId }); + + // Create first user + const user1 = await prismock.user.create({ + data: { name: "User 1", email: "user1@test.com" }, + }); + const orgMembership1 = await prismock.membership.create({ + data: { + role: MembershipRole.MEMBER, + disableImpersonation: false, + accepted: true, + teamId: orgId, + userId: user1.id, + }, + }); + await prismock.membership.create({ + data: { + role: MembershipRole.MEMBER, + disableImpersonation: false, + accepted: true, + teamId: team.id, + userId: user1.id, + }, + }); + + // Create second user + const user2 = await prismock.user.create({ + data: { name: "User 2", email: "user2@test.com" }, + }); + const orgMembership2 = await prismock.membership.create({ + data: { + role: MembershipRole.MEMBER, + disableImpersonation: false, + accepted: true, + teamId: orgId, + userId: user2.id, + }, + }); + await prismock.membership.create({ + data: { + role: MembershipRole.MEMBER, + disableImpersonation: false, + accepted: true, + teamId: team.id, + userId: user2.id, + }, + }); + + // Create attribute with options + await createMockAttribute({ + orgId, + id: "attr1", + name: "Level", + slug: "level", + type: AttributeType.SINGLE_SELECT, + options: [ + { id: "level-junior", value: "Junior", slug: "junior", isGroup: false, contains: [] }, + { id: "level-senior", value: "Senior", slug: "senior", isGroup: false, contains: [] }, + { id: "level-lead", value: "Lead", slug: "lead", isGroup: false, contains: [] }, + ], + }); + + // Assign different options to different users + await createMockAttributeAssignment({ + orgMembershipId: orgMembership1.id, + attributeOptionId: "level-junior", + }); + + await createMockAttributeAssignment({ + orgMembershipId: orgMembership2.id, + attributeOptionId: "level-senior", + }); + + const { attributesAssignedToTeamMembersWithOptions } = await getAttributesAssignmentData({ + teamId: team.id, + orgId, + }); + + expect(attributesAssignedToTeamMembersWithOptions).toHaveLength(2); + + // Find each user's assignment + const user1Assignment = attributesAssignedToTeamMembersWithOptions.find((a) => a.userId === user1.id); + const user2Assignment = attributesAssignedToTeamMembersWithOptions.find((a) => a.userId === user2.id); + + expect(user1Assignment?.attributes.attr1).toEqual({ + type: AttributeType.SINGLE_SELECT, + attributeOption: { isGroup: false, value: "Junior", contains: [] }, + }); + + expect(user2Assignment?.attributes.attr1).toEqual({ + type: AttributeType.SINGLE_SELECT, + attributeOption: { isGroup: false, value: "Senior", contains: [] }, + }); + }); + + it("should handle empty attributes gracefully", async () => { + const team = await createMockTeam({ orgId }); + await createMockUserHavingMembershipWithBothTeamAndOrg({ + orgId, + teamId: team.id, + }); + + // Create attribute with no options + await createMockAttribute({ + orgId, + id: "attr1", + name: "Empty Attribute", + slug: "empty-attribute", + type: AttributeType.SINGLE_SELECT, + options: [], + }); + + const { attributesOfTheOrg, attributesAssignedToTeamMembersWithOptions } = + await getAttributesAssignmentData({ teamId: team.id, orgId }); + + // No assignments since there are no options to assign + expect(attributesAssignedToTeamMembersWithOptions).toHaveLength(0); + expect(attributesOfTheOrg).toHaveLength(1); + }); + + it("should handle option IDs that don't exist in the org's attributes", async () => { + const team = await createMockTeam({ orgId }); + const { orgMembership } = await createMockUserHavingMembershipWithBothTeamAndOrg({ + orgId, + teamId: team.id, + }); + + await createMockAttribute({ + orgId, + id: "attr1", + name: "Department", + slug: "department", + type: AttributeType.SINGLE_SELECT, + options: [ + { id: "dept-eng", value: "Engineering", slug: "engineering", isGroup: false, contains: [] }, + ], + }); + + // Create an assignment with a valid option + await createMockAttributeAssignment({ + orgMembershipId: orgMembership.id, + attributeOptionId: "dept-eng", + }); + + // Create an assignment with an invalid option ID (simulating data inconsistency) + await prismock.attributeToUser.create({ + data: { + memberId: orgMembership.id, + attributeOptionId: "non-existent-option", + }, + }); + + // The function should handle this gracefully and only return valid assignments + const { attributesAssignedToTeamMembersWithOptions } = await getAttributesAssignmentData({ + teamId: team.id, + orgId, + }); + + // Should only have the valid assignment + expect(attributesAssignedToTeamMembersWithOptions).toHaveLength(1); + expect(attributesAssignedToTeamMembersWithOptions[0].attributes.attr1).toEqual({ + type: AttributeType.SINGLE_SELECT, + attributeOption: { isGroup: false, value: "Engineering", contains: [] }, + }); + }); + }); + describe("getAttributesAssignmentData with attributeIds filter", () => { it("should return only assignments for specified attributeIds when filter is provided", async () => { const team = await createMockTeam({ orgId }); diff --git a/packages/features/attributes/lib/getAttributes.ts b/packages/features/attributes/lib/getAttributes.ts index 8fd7df6492..40449f58d8 100644 --- a/packages/features/attributes/lib/getAttributes.ts +++ b/packages/features/attributes/lib/getAttributes.ts @@ -128,11 +128,13 @@ async function _findMembershipsForBothOrgAndTeam({ function _prepareAssignmentData({ assignmentsForTheTeam, - attributesOfTheOrg, + lookupMaps, }: { assignmentsForTheTeam: AssignmentForTheTeam[]; - attributesOfTheOrg: Attribute[]; + lookupMaps: AttributeLookupMaps; }) { + const { optionIdToOption, attributeIdToOptions } = lookupMaps; + const teamMembersThatHaveOptionAssigned = assignmentsForTheTeam.reduce( (acc, attributeToUser) => { const userId = attributeToUser.userId; @@ -212,11 +214,9 @@ function _prepareAssignmentData({ }) { return contains .map((optionId) => { - const allOptions = attributesOfTheOrg.find( - (_attribute) => _attribute.id === attribute.id - )?.options; - const option = allOptions?.find((option) => option.id === optionId); + const option = optionIdToOption.get(optionId); if (!option) { + const allOptions = attributeIdToOptions.get(attribute.id); console.error( `Enriching "contains" for attribute ${ attribute.name @@ -238,33 +238,49 @@ function _prepareAssignmentData({ } } -function _getAttributeFromAttributeOption({ - allAttributesOfTheOrg, - attributeOptionId, -}: { - allAttributesOfTheOrg: Attribute[]; - attributeOptionId: AttributeOptionId; -}) { - return allAttributesOfTheOrg.find((attribute) => - attribute.options.some((option) => option.id === attributeOptionId) - ); +/** + * Builds lookup maps for O(1) attribute and option lookups by option ID. + * This replaces O(n×m) linear scans with O(1) Map lookups. + */ +function _buildAttributeLookupMaps(attributesOfTheOrg: FullAttribute[]) { + const optionIdToAttribute = new Map(); + const optionIdToOption = new Map< + AttributeOptionId, + FullAttribute["options"][number] + >(); + const attributeIdToOptions = new Map(); + + for (const attribute of attributesOfTheOrg) { + attributeIdToOptions.set(attribute.id, attribute.options); + for (const option of attribute.options) { + optionIdToAttribute.set(option.id, attribute); + optionIdToOption.set(option.id, option); + } + } + + return { optionIdToAttribute, optionIdToOption, attributeIdToOptions }; } -function _getAttributeOptionFromAttributeOption({ - allAttributesOfTheOrg, +type AttributeLookupMaps = ReturnType; + +function _getAttributeFromAttributeOption({ + lookupMaps, attributeOptionId, }: { - allAttributesOfTheOrg: FullAttribute[]; + lookupMaps: AttributeLookupMaps; attributeOptionId: AttributeOptionId; }) { - const matchingOption = allAttributesOfTheOrg.reduce((found, attribute) => { - if (found) return found; - return ( - attribute.options.find((option) => option.id === attributeOptionId) || - null - ); - }, null as null | (typeof allAttributesOfTheOrg)[number]["options"][number]); - return matchingOption; + return lookupMaps.optionIdToAttribute.get(attributeOptionId); +} + +function _getAttributeOptionFromId({ + lookupMaps, + attributeOptionId, +}: { + lookupMaps: AttributeLookupMaps; + attributeOptionId: AttributeOptionId; +}) { + return lookupMaps.optionIdToOption.get(attributeOptionId); } async function _getOrgMembershipToUserIdForTeam({ @@ -417,11 +433,11 @@ async function getAttributesAssignedToMembersOfTeam({ function _buildAssignmentsForTeam({ attributesToUsersForTeam, orgMembershipToUserIdForTeamMembers, - attributesOfTheOrg, + lookupMaps, }: { attributesToUsersForTeam: AttributeToUser[]; orgMembershipToUserIdForTeamMembers: Map; - attributesOfTheOrg: FullAttribute[]; + lookupMaps: AttributeLookupMaps; }) { return attributesToUsersForTeam .map((attributeToUser) => { @@ -434,12 +450,12 @@ function _buildAssignmentsForTeam({ return null; } const attribute = _getAttributeFromAttributeOption({ - allAttributesOfTheOrg: attributesOfTheOrg, + lookupMaps, attributeOptionId: attributeToUser.attributeOptionId, }); - const attributeOption = _getAttributeOptionFromAttributeOption({ - allAttributesOfTheOrg: attributesOfTheOrg, + const attributeOption = _getAttributeOptionFromId({ + lookupMaps, attributeOptionId: attributeToUser.attributeOptionId, }); @@ -484,15 +500,17 @@ export async function getAttributesAssignmentData({ attributeIds, }); + const lookupMaps = _buildAttributeLookupMaps(attributesOfTheOrg); + const assignmentsForTheTeam = _buildAssignmentsForTeam({ attributesToUsersForTeam, orgMembershipToUserIdForTeamMembers, - attributesOfTheOrg, + lookupMaps, }); const attributesAssignedToTeamMembersWithOptions = _prepareAssignmentData({ - attributesOfTheOrg, assignmentsForTheTeam, + lookupMaps, }); return {