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 87bbdb9ff3..da67d79b4e 100644 --- a/packages/app-store/routing-forms/trpc/__tests__/utils.test.ts +++ b/packages/app-store/routing-forms/trpc/__tests__/utils.test.ts @@ -458,7 +458,7 @@ describe("findTeamMembersMatchingAttributeLogicOfRoute", () => { 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 - it.skip("should return matching team members with a SINGLE_SELECT attribute when 'all in' option is selected", async () => { + it("should return matching team members with a SINGLE_SELECT attribute when 'all in' option is selected", async () => { const { attributes } = mockHugeAttributesOfTypeSingleSelect({ numAttributes: 20, numOptionsPerAttribute: 30, @@ -503,13 +503,30 @@ describe("findTeamMembersMatchingAttributeLogicOfRoute", () => { userId: 1, result: RaqbLogicResult.MATCH, }, + { + userId: 2, + result: RaqbLogicResult.MATCH, + }, + { + userId: 3, + result: RaqbLogicResult.MATCH, + }, + { + userId: 2000, + result: RaqbLogicResult.MATCH, + }, + // Last Item + { + userId: 4000, + result: RaqbLogicResult.MATCH, + }, ]) ); if (!timeTaken) { throw new Error("Looks like performance testing is not enabled"); } - const totalTimeTaken = Object.values(timeTaken).reduce((sum, time) => sum ?? 0 + (time || 0), 0); + const totalTimeTaken = Object.values(timeTaken).reduce((sum, time) => (sum ?? 0) + (time ?? 0), 0); console.log("Total time taken", totalTimeTaken, { timeTaken, }); diff --git a/packages/app-store/routing-forms/trpc/utils.ts b/packages/app-store/routing-forms/trpc/utils.ts index a499e116f5..cddbfeb554 100644 --- a/packages/app-store/routing-forms/trpc/utils.ts +++ b/packages/app-store/routing-forms/trpc/utils.ts @@ -1,6 +1,6 @@ import type { App_RoutingForms_Form, User } from "@prisma/client"; import async from "async"; -import os from "os"; +import { Config, Utils as QbUtils } from "react-awesome-query-builder"; import getWebhooks from "@calcom/features/webhooks/lib/getWebhooks"; import { sendGenericWebhookPayload } from "@calcom/features/webhooks/lib/sendPayload"; @@ -10,13 +10,14 @@ import { safeStringify } from "@calcom/lib/safeStringify"; import { WebhookTriggerEvents } from "@calcom/prisma/client"; import type { Ensure } from "@calcom/types/utils"; -import { evaluateRaqbLogic, RaqbLogicResult } from "../lib/evaluateRaqbLogic"; +import { RaqbLogicResult } from "../lib/evaluateRaqbLogic"; import { getTeamMembersWithAttributeOptionValuePerAttribute, getAttributesForTeam, } from "../lib/getAttributes"; import isRouter from "../lib/isRouter"; -import type { SerializableField, OrderedResponses } from "../types/types"; +import jsonLogic from "../lib/jsonLogic"; +import type { SerializableField, OrderedResponses, AttributesQueryValue } from "../types/types"; import type { FormResponse, SerializableForm } from "../types/types"; import { acrossQueryValueCompatiblity } from "./raqbUtils"; @@ -117,6 +118,37 @@ function perf(fn: () => ReturnValue): [ReturnValue, number | null] return [result, end - start]; } +function getJsonLogic({ + attributesQueryValue, + attributesQueryBuilderConfig, +}: { + attributesQueryValue: AttributesQueryValue; + attributesQueryBuilderConfig: Config; +}) { + const state = { + tree: QbUtils.checkTree( + QbUtils.loadTree(attributesQueryValue), + // We know that attributesQueryBuilderConfig is a Config because getAttributesQueryBuilderConfig returns a Config. So, asserting it. + attributesQueryBuilderConfig as unknown as Config + ), + config: attributesQueryBuilderConfig as unknown as Config, + }; + + const jsonLogicQuery = QbUtils.jsonLogicFormat(state.tree, state.config); + const logic = jsonLogicQuery.logic; + if (!logic) { + if (attributesQueryValue.children1 && Object.keys(attributesQueryValue.children1).length > 0) { + throw new Error("Couldn't build the logic from the query value"); + } + console.log( + "No logic found", + safeStringify({ attributesQueryValue, queryBuilderConfigFields: attributesQueryBuilderConfig.fields }) + ); + } + console.log("Using LOGIC", safeStringify(logic)); + return logic; +} + export async function findTeamMembersMatchingAttributeLogicOfRoute( { form, @@ -206,6 +238,24 @@ export async function findTeamMembersMatchingAttributeLogicOfRoute( getTeamMembersWithAttributeOptionValuePerAttributeTimeTaken, ] = await aPf(() => getTeamMembersWithAttributeOptionValuePerAttribute({ teamId: teamId })); + const logic = getJsonLogic({ + attributesQueryValue, + attributesQueryBuilderConfig: attributesQueryBuilderConfig as unknown as Config, + }); + + if (!logic) { + return { + teamMembersMatchingAttributeLogic: [], + timeTaken: { + gAtr: getAttributesForTeamTimeTaken, + gQryCnfg: getAttributesQueryBuilderConfigTimeTaken, + gMbrWtAtr: getTeamMembersWithAttributeOptionValuePerAttributeTimeTaken, + lgcFrMbrs: null, + gQryVal: getAttributesQueryValueTimeTaken, + }, + }; + } + const [_, teamMembersMatchingAttributeLogicTimeTaken] = await aPf(async () => { return await async.mapLimit>( teamMembersWithAttributeOptionValuePerAttribute, @@ -215,30 +265,15 @@ export async function findTeamMembersMatchingAttributeLogicOfRoute( attributesData: member.attributes, attributesQueryValue, }); - moduleLogger.debug( - `Checking team member ${member.userId} with attributes logic`, - safeStringify({ attributes: attributesData, attributesQueryValue }) - ); - const result = evaluateRaqbLogic( - { - queryValue: attributesQueryValue, - queryBuilderConfig: attributesQueryBuilderConfig, - data: attributesData, - beStrictWithEmptyLogic: true, - }, - { - // This logic runs too many times as it is per team member and we don't want to spam the console with logs. It might also take a performance hit otherwise - logLevel: 2, - } - ); - if (result === RaqbLogicResult.MATCH || result === RaqbLogicResult.LOGIC_NOT_FOUND_SO_MATCHED) { - moduleLogger.debug(`Team member ${member.userId} matches attributes logic`); - teamMembersMatchingAttributeLogicMap.set(member.userId, result); - } else { - moduleLogger.debug(`Team member ${member.userId} does not match attributes logic`); + const result = !!jsonLogic.apply(logic as any, attributesData) + ? RaqbLogicResult.MATCH + : RaqbLogicResult.NO_MATCH; + + if (result !== RaqbLogicResult.MATCH) { return; } + teamMembersMatchingAttributeLogicMap.set(member.userId, result); } ); });