Skip to content

Commit acbcd59

Browse files
perf: Attributes Query - loadTree only once (calcom#17172)
* chore: increase pagination managed users endpoint (calcom#17147) * perf: Build tree once per call --------- Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>
1 parent 05c1ad8 commit acbcd59

File tree

2 files changed

+79
-27
lines changed

2 files changed

+79
-27
lines changed

packages/app-store/routing-forms/trpc/__tests__/utils.test.ts

+19-2
Original file line numberDiff line numberDiff line change
@@ -458,7 +458,7 @@ describe("findTeamMembersMatchingAttributeLogicOfRoute", () => {
458458
describe("Performance testing", () => {
459459
describe("20 attributes, 4000 team members", async () => {
460460
// In tests, the performance is actually really bad than real world. So, skipping this test for now
461-
it.skip("should return matching team members with a SINGLE_SELECT attribute when 'all in' option is selected", async () => {
461+
it("should return matching team members with a SINGLE_SELECT attribute when 'all in' option is selected", async () => {
462462
const { attributes } = mockHugeAttributesOfTypeSingleSelect({
463463
numAttributes: 20,
464464
numOptionsPerAttribute: 30,
@@ -503,13 +503,30 @@ describe("findTeamMembersMatchingAttributeLogicOfRoute", () => {
503503
userId: 1,
504504
result: RaqbLogicResult.MATCH,
505505
},
506+
{
507+
userId: 2,
508+
result: RaqbLogicResult.MATCH,
509+
},
510+
{
511+
userId: 3,
512+
result: RaqbLogicResult.MATCH,
513+
},
514+
{
515+
userId: 2000,
516+
result: RaqbLogicResult.MATCH,
517+
},
518+
// Last Item
519+
{
520+
userId: 4000,
521+
result: RaqbLogicResult.MATCH,
522+
},
506523
])
507524
);
508525

509526
if (!timeTaken) {
510527
throw new Error("Looks like performance testing is not enabled");
511528
}
512-
const totalTimeTaken = Object.values(timeTaken).reduce((sum, time) => sum ?? 0 + (time || 0), 0);
529+
const totalTimeTaken = Object.values(timeTaken).reduce((sum, time) => (sum ?? 0) + (time ?? 0), 0);
513530
console.log("Total time taken", totalTimeTaken, {
514531
timeTaken,
515532
});

packages/app-store/routing-forms/trpc/utils.ts

+60-25
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { App_RoutingForms_Form, User } from "@prisma/client";
22
import async from "async";
3-
import os from "os";
3+
import { Config, Utils as QbUtils } from "react-awesome-query-builder";
44

55
import getWebhooks from "@calcom/features/webhooks/lib/getWebhooks";
66
import { sendGenericWebhookPayload } from "@calcom/features/webhooks/lib/sendPayload";
@@ -10,13 +10,14 @@ import { safeStringify } from "@calcom/lib/safeStringify";
1010
import { WebhookTriggerEvents } from "@calcom/prisma/client";
1111
import type { Ensure } from "@calcom/types/utils";
1212

13-
import { evaluateRaqbLogic, RaqbLogicResult } from "../lib/evaluateRaqbLogic";
13+
import { RaqbLogicResult } from "../lib/evaluateRaqbLogic";
1414
import {
1515
getTeamMembersWithAttributeOptionValuePerAttribute,
1616
getAttributesForTeam,
1717
} from "../lib/getAttributes";
1818
import isRouter from "../lib/isRouter";
19-
import type { SerializableField, OrderedResponses } from "../types/types";
19+
import jsonLogic from "../lib/jsonLogic";
20+
import type { SerializableField, OrderedResponses, AttributesQueryValue } from "../types/types";
2021
import type { FormResponse, SerializableForm } from "../types/types";
2122
import { acrossQueryValueCompatiblity } from "./raqbUtils";
2223

@@ -117,6 +118,37 @@ function perf<ReturnValue>(fn: () => ReturnValue): [ReturnValue, number | null]
117118
return [result, end - start];
118119
}
119120

121+
function getJsonLogic({
122+
attributesQueryValue,
123+
attributesQueryBuilderConfig,
124+
}: {
125+
attributesQueryValue: AttributesQueryValue;
126+
attributesQueryBuilderConfig: Config;
127+
}) {
128+
const state = {
129+
tree: QbUtils.checkTree(
130+
QbUtils.loadTree(attributesQueryValue),
131+
// We know that attributesQueryBuilderConfig is a Config because getAttributesQueryBuilderConfig returns a Config. So, asserting it.
132+
attributesQueryBuilderConfig as unknown as Config
133+
),
134+
config: attributesQueryBuilderConfig as unknown as Config,
135+
};
136+
137+
const jsonLogicQuery = QbUtils.jsonLogicFormat(state.tree, state.config);
138+
const logic = jsonLogicQuery.logic;
139+
if (!logic) {
140+
if (attributesQueryValue.children1 && Object.keys(attributesQueryValue.children1).length > 0) {
141+
throw new Error("Couldn't build the logic from the query value");
142+
}
143+
console.log(
144+
"No logic found",
145+
safeStringify({ attributesQueryValue, queryBuilderConfigFields: attributesQueryBuilderConfig.fields })
146+
);
147+
}
148+
console.log("Using LOGIC", safeStringify(logic));
149+
return logic;
150+
}
151+
120152
export async function findTeamMembersMatchingAttributeLogicOfRoute(
121153
{
122154
form,
@@ -206,6 +238,24 @@ export async function findTeamMembersMatchingAttributeLogicOfRoute(
206238
getTeamMembersWithAttributeOptionValuePerAttributeTimeTaken,
207239
] = await aPf(() => getTeamMembersWithAttributeOptionValuePerAttribute({ teamId: teamId }));
208240

241+
const logic = getJsonLogic({
242+
attributesQueryValue,
243+
attributesQueryBuilderConfig: attributesQueryBuilderConfig as unknown as Config,
244+
});
245+
246+
if (!logic) {
247+
return {
248+
teamMembersMatchingAttributeLogic: [],
249+
timeTaken: {
250+
gAtr: getAttributesForTeamTimeTaken,
251+
gQryCnfg: getAttributesQueryBuilderConfigTimeTaken,
252+
gMbrWtAtr: getTeamMembersWithAttributeOptionValuePerAttributeTimeTaken,
253+
lgcFrMbrs: null,
254+
gQryVal: getAttributesQueryValueTimeTaken,
255+
},
256+
};
257+
}
258+
209259
const [_, teamMembersMatchingAttributeLogicTimeTaken] = await aPf(async () => {
210260
return await async.mapLimit<TeamMemberWithAttributeOptionValuePerAttribute, Promise<void>>(
211261
teamMembersWithAttributeOptionValuePerAttribute,
@@ -215,30 +265,15 @@ export async function findTeamMembersMatchingAttributeLogicOfRoute(
215265
attributesData: member.attributes,
216266
attributesQueryValue,
217267
});
218-
moduleLogger.debug(
219-
`Checking team member ${member.userId} with attributes logic`,
220-
safeStringify({ attributes: attributesData, attributesQueryValue })
221-
);
222-
const result = evaluateRaqbLogic(
223-
{
224-
queryValue: attributesQueryValue,
225-
queryBuilderConfig: attributesQueryBuilderConfig,
226-
data: attributesData,
227-
beStrictWithEmptyLogic: true,
228-
},
229-
{
230-
// 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
231-
logLevel: 2,
232-
}
233-
);
234-
235-
if (result === RaqbLogicResult.MATCH || result === RaqbLogicResult.LOGIC_NOT_FOUND_SO_MATCHED) {
236-
moduleLogger.debug(`Team member ${member.userId} matches attributes logic`);
237-
teamMembersMatchingAttributeLogicMap.set(member.userId, result);
238-
} else {
239-
moduleLogger.debug(`Team member ${member.userId} does not match attributes logic`);
268+
269+
const result = !!jsonLogic.apply(logic as any, attributesData)
270+
? RaqbLogicResult.MATCH
271+
: RaqbLogicResult.NO_MATCH;
272+
273+
if (result !== RaqbLogicResult.MATCH) {
240274
return;
241275
}
276+
teamMembersMatchingAttributeLogicMap.set(member.userId, result);
242277
}
243278
);
244279
});

0 commit comments

Comments
 (0)