diff --git a/packages/app-store/routing-forms/TODO.md b/packages/app-store/routing-forms/TODO.md index 0fee3b24fc..1df208c830 100644 --- a/packages/app-store/routing-forms/TODO.md +++ b/packages/app-store/routing-forms/TODO.md @@ -8,7 +8,7 @@ - [x] Connect Form Response to Booking - [x] Test with non option based attributes and form field input - [ ] Switching from Select to MultiSelect in attribute - - [x] It immediately makes the Routing logic to not work. Fix it. + - [ ] It immediately makes the Routing logic to not work. Fix it. - [ ] It removes the selected option from Routing. Fix it. - [ ] Warnings are needed with recommendation to create new attribute instead in unsupported scenarios. We need to test for more such changes to Attribute types. - [ ] When choosing 'Value of Field' option in RAQB, show a warning somewhere if all options of the Form field aren't present in the attribute option. diff --git a/packages/app-store/routing-forms/trpc/__tests__/utils.test.ts b/packages/app-store/routing-forms/trpc/__tests__/utils.test.ts index 093eaa8dae..87bbdb9ff3 100644 --- a/packages/app-store/routing-forms/trpc/__tests__/utils.test.ts +++ b/packages/app-store/routing-forms/trpc/__tests__/utils.test.ts @@ -1,7 +1,6 @@ import type { BaseWidget } from "react-awesome-query-builder"; import { describe, it, expect, vi, beforeEach } from "vitest"; -import logger from "@calcom/lib/logger"; import type { AttributeType } from "@calcom/prisma/enums"; import { RoutingFormFieldType } from "../../lib/FieldTypes"; @@ -86,7 +85,7 @@ function buildQueryValue({ }: { rules: { raqbFieldId: string; - value: string | number | string[]; + value: string | number | string[] | [string[]]; operator: string; valueSrc: NonNullable[]; valueType: string[]; @@ -118,8 +117,9 @@ function buildSelectTypeFieldQueryValue({ }: { rules: { raqbFieldId: string; - value: string | number | string[]; + value: string | number | string[] | [string[]]; operator: string; + valueType?: string[]; }[]; }) { return buildQueryValue({ @@ -128,7 +128,7 @@ function buildSelectTypeFieldQueryValue({ value: rule.value, operator: rule.operator, valueSrc: ["value"], - valueType: ["select"], + valueType: rule.valueType ?? ["select"], })), }) as AttributesQueryValue; } @@ -336,6 +336,125 @@ describe("findTeamMembersMatchingAttributeLogicOfRoute", () => { ]); }); + it("should return matching team members with a SINGLE_SELECT attribute when 'Any in' option is selected", async () => { + const Option1OfAttribute1HumanReadableValue = "Option 1"; + const Option1OfField1HumanReadableValue = Option1OfAttribute1HumanReadableValue; + const Field1Id = "field-1"; + + const Option1OfAttribute1 = { + id: "attr-1-opt-1", + value: Option1OfAttribute1HumanReadableValue, + slug: "option-1", + }; + + const Option2OfAttribute1 = { + id: "attr-1-opt-2", + value: "Option 2", + slug: "option-2", + }; + + const Option3OfAttribute1 = { + id: "attr-1-opt-3", + value: "Option 3", + slug: "option-3", + }; + + const Attribute1 = { + id: "attr1", + name: "Attribute 1", + type: "SINGLE_SELECT" as const, + slug: "attribute-1", + options: [Option1OfAttribute1, Option2OfAttribute1, Option3OfAttribute1], + }; + + mockAttributesScenario({ + attributes: [Attribute1], + teamMembersWithAttributeOptionValuePerAttribute: [ + { userId: 1, attributes: { [Attribute1.id]: Option1OfAttribute1.value } }, + ], + }); + + const attributesQueryValue = buildSelectTypeFieldQueryValue({ + rules: [ + { + raqbFieldId: Attribute1.id, + value: [[Option1OfAttribute1.id, Option2OfAttribute1.id]], + operator: "select_any_in", + valueType: ["multiselect"], + }, + ], + }) as AttributesQueryValue; + + const { teamMembersMatchingAttributeLogic: result } = await findTeamMembersMatchingAttributeLogicOfRoute({ + form: { + routes: [ + buildDefaultCustomPageRoute({ + id: "test-route", + attributesQueryValue: attributesQueryValue, + }), + ], + fields: [], + }, + response: {}, + routeId: "test-route", + teamId: 1, + }); + + expect(result).toEqual([ + { + userId: 1, + result: RaqbLogicResult.MATCH, + }, + ]); + }); + + describe("Error handling", () => { + it("should throw an error if the attribute type is not supported", async () => { + const Option1OfAttribute1 = { id: "opt1", value: "Option 1", slug: "option-1" }; + const Attribute1 = { + id: "attr1", + name: "Attribute 1", + type: "UNSUPPORTED_ATTRIBUTE_TYPE" as unknown as AttributeType, + slug: "attribute-1", + options: [Option1OfAttribute1], + }; + mockAttributesScenario({ + attributes: [Attribute1], + teamMembersWithAttributeOptionValuePerAttribute: [ + { + userId: 1, + attributes: { [Attribute1.id]: Option1OfAttribute1.value }, + }, + ], + }); + + await expect( + findTeamMembersMatchingAttributeLogicOfRoute({ + form: { + routes: [ + buildDefaultCustomPageRoute({ + id: "test-route", + attributesQueryValue: buildSelectTypeFieldQueryValue({ + rules: [ + { + raqbFieldId: Attribute1.id, + value: [Option1OfAttribute1.id], + operator: "select_equals", + }, + ], + }) as AttributesQueryValue, + }), + ], + fields: [], + }, + response: {}, + routeId: "test-route", + teamId: 1, + }) + ).rejects.toThrow("Unsupported attribute type"); + }); + }); + describe("Performance testing", () => { describe("20 attributes, 4000 team members", async () => { // In tests, the performance is actually really bad than real world. So, skipping this test for now @@ -346,7 +465,6 @@ describe("findTeamMembersMatchingAttributeLogicOfRoute", () => { numTeamMembers: 4000, numAttributesUsedPerTeamMember: 10, }); - const attributesQueryValue = buildSelectTypeFieldQueryValue({ rules: [ { diff --git a/packages/app-store/routing-forms/trpc/raqbUtils.ts b/packages/app-store/routing-forms/trpc/raqbUtils.ts index b3f7859385..45575ce093 100644 --- a/packages/app-store/routing-forms/trpc/raqbUtils.ts +++ b/packages/app-store/routing-forms/trpc/raqbUtils.ts @@ -138,6 +138,10 @@ const replaceFieldTemplateVariableWithOptionLabel = ({ * Utilities to handle compatiblity when attribute's type changes */ const attributeChangeCompatibility = { + /** + * FIXME: It isn't able to handle a case where for SINGLE_SELECT attribute, the queryValue->valueType is ["multiselect"]. It happens for select_any_in operator + * So, don't use it till that is fixed. + */ getRaqbFieldTypeCompatibleWithQueryValue: function getRaqbFieldTypeCompatibleWithQueryValue({ attributesQueryValue, raqbField, @@ -163,6 +167,8 @@ const attributeChangeCompatibility = { }, /** * Ensure the attribute value if of type same as the valueType in queryValue + * FIXME: It isn't able to handle a case where for SINGLE_SELECT attribute, the queryValue->valueType is ["multiselect"]. It happens for select_any_in operator + * So, don't use it till that is fixed. */ ensureAttributeValueToBeOfRaqbFieldValueType: function ensureAttributeValueToBeOfRaqbFieldValueType({ attributeValue, @@ -197,11 +203,14 @@ function getAttributesData({ const compatibleValueForAttributeAndFormFieldMatching = compatibleForAttributeAndFormFieldMatch(value); // We do this to ensure that correct jsonLogic is generated for an existing route even if the attribute's type changes - acc[attributeId] = attributeChangeCompatibility.ensureAttributeValueToBeOfRaqbFieldValueType({ - attributeValue: compatibleValueForAttributeAndFormFieldMatching, - attributesQueryValue, - attributeId, - }); + // acc[attributeId] = attributeChangeCompatibility.ensureAttributeValueToBeOfRaqbFieldValueType({ + // attributeValue: compatibleValueForAttributeAndFormFieldMatching, + // attributesQueryValue, + // attributeId, + // }); + + // Right now we can't trust ensureAttributeValueToBeOfRaqbFieldValueType to give us the correct value + acc[attributeId] = compatibleValueForAttributeAndFormFieldMatching; return acc; }, {} as Record); @@ -262,11 +271,14 @@ function getAttributesQueryBuilderConfig({ const attributesQueryBuilderConfigFieldsWithCompatibleListValues = Object.fromEntries( Object.entries(attributesQueryBuilderConfig.fields).map(([raqbFieldId, raqbField]) => { - const raqbFieldType = attributeChangeCompatibility.getRaqbFieldTypeCompatibleWithQueryValue({ - attributesQueryValue, - raqbField, - raqbFieldId, - }); + // const raqbFieldType = attributeChangeCompatibility.getRaqbFieldTypeCompatibleWithQueryValue({ + // attributesQueryValue, + // raqbField, + // raqbFieldId, + // }); + + // Right now we can't trust getRaqbFieldTypeCompatibleWithQueryValue to give us the correct type + const raqbFieldType = raqbField.type; return [ raqbFieldId,