fix: Add troubleshooting data and ensure no errors in live mode of router. (#17188)

This commit is contained in:
Hariom Balhara
2024-10-19 07:22:04 -07:00
committed by GitHub
parent 474f81f08e
commit 731f97ebe4
8 changed files with 432 additions and 86 deletions
+3 -4
View File
@@ -27,7 +27,6 @@ import type { Ensure } from "@calcom/types/utils";
import { Dialog, DialogContent, DialogFooter, DialogHeader } from "@calcom/ui";
import { Button, Tooltip } from "@calcom/ui";
import { showToast } from "@calcom/ui/components/toast";
import useRouterQuery from "@calcom/web/lib/hooks/useRouterQuery";
const enum ReroutingStatusEnum {
REROUTING_NOT_INITIATED = "not_initiated",
@@ -599,9 +598,9 @@ const NewRoutingManager = ({
}
function reroutingCTAs() {
if(chosenRoute.action.type !== RouteActionType.EventTypeRedirectUrl) {
if (chosenRoute.action.type !== RouteActionType.EventTypeRedirectUrl) {
// There are no actions to perform if the new chosen route isn't an event type redirect
return null
return null;
}
const shouldDisableCTAs = teamMembersMatchingAttributeLogic.isPending || createBookingMutation.isPending;
return (
@@ -766,7 +765,7 @@ const RerouteDialogContentAndFooterWithFormResponse = ({
const findTeamMembersMatchingAttributeLogicMutation =
trpc.viewer.appRoutingForms.findTeamMembersMatchingAttributeLogic.useMutation({
onSuccess(data) {
setTeamMembersMatchingAttributeLogic(data);
setTeamMembersMatchingAttributeLogic(data.result);
},
});
@@ -168,13 +168,15 @@ vi.mock("@calcom/trpc/react", () => ({
useMutation: vi.fn(({ onSuccess }) => {
return {
mutate: vi.fn(() => {
onSuccess([
{
id: 1,
name: "Matching User 1",
email: "matching-user-1@example.com",
},
]);
onSuccess({
result: [
{
id: 1,
name: "Matching User 1",
email: "matching-user-1@example.com",
},
],
});
}),
};
}),
@@ -8,8 +8,9 @@ import LicenseRequired from "@calcom/features/ee/common/components/LicenseRequir
import AddMembersWithSwitch from "@calcom/features/eventtypes/components/AddMembersWithSwitch";
import { ShellMain } from "@calcom/features/shell/Shell";
import useApp from "@calcom/lib/hooks/useApp";
import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import { trpc, TRPCClientError } from "@calcom/trpc/react";
import type { inferSSRProps } from "@calcom/types/inferSSRProps";
import {
Alert,
@@ -242,6 +243,7 @@ function SingleForm({ form, appUrl, Page, enrichedWithUserProfileForm }: SingleF
const [chosenRoute, setChosenRoute] = useState<NonRouterRoute | null>(null);
const [skipFirstUpdate, setSkipFirstUpdate] = useState(true);
const [eventTypeUrl, setEventTypeUrl] = useState("");
const searchParams = useCompatSearchParams();
const [teamMembersMatchingAttributeLogic, setTeamMembersMatchingAttributeLogic] = useState<
| {
id: number;
@@ -253,7 +255,14 @@ function SingleForm({ form, appUrl, Page, enrichedWithUserProfileForm }: SingleF
const findTeamMembersMatchingAttributeLogicMutation =
trpc.viewer.appRoutingForms.findTeamMembersMatchingAttributeLogic.useMutation({
onSuccess(data) {
setTeamMembersMatchingAttributeLogic(data);
setTeamMembersMatchingAttributeLogic(data.result);
},
onError(e) {
if (e instanceof TRPCClientError) {
showToast(e.message, "error");
} else {
showToast(t("something_went_wrong"), "error");
}
},
});
@@ -280,6 +289,8 @@ function SingleForm({ form, appUrl, Page, enrichedWithUserProfileForm }: SingleF
formId: form.id,
response,
routeId: route.id,
isPreview: true,
_enablePerf: searchParams.get("enablePerf") === "true",
});
}
@@ -54,7 +54,7 @@ function getSettings(_configFor: ConfigFor) {
groupActionsPosition: "bottomCenter",
// TODO: Test it and then enable it. It might allow us to show better error messages.
// But it doesn't detect every kind of error like an operator gone missing e.g. what happened in https://github.com/calcom/cal.com/pull/17102
// showErrorMessage: true,
showErrorMessage: true,
// Disable groups
maxNesting: 1,
};
@@ -1,8 +1,8 @@
import type { BaseWidget } from "react-awesome-query-builder";
import { describe, it, expect, vi, beforeEach } from "vitest";
import type { AttributeType } from "@calcom/prisma/enums";
import { RouteActionType } from "@calcom/app-store/routing-forms/zod";
import type { AttributeType } from "@calcom/prisma/enums";
import { RoutingFormFieldType } from "../../lib/FieldTypes";
import { RaqbLogicResult } from "../../lib/evaluateRaqbLogic";
@@ -176,15 +176,17 @@ describe("findTeamMembersMatchingAttributeLogicOfRoute", () => {
vi.resetAllMocks();
});
it("should return null if route is not found", async () => {
const { teamMembersMatchingAttributeLogic: result } = await findTeamMembersMatchingAttributeLogicOfRoute({
form: { routes: [], fields: [] },
response: {},
routeId: "non-existent-route",
teamId: 1,
});
it("should return null if route is not found and troubleshooter should also be null by default", async () => {
const { teamMembersMatchingAttributeLogic: result, troubleshooter } =
await findTeamMembersMatchingAttributeLogicOfRoute({
form: { routes: [], fields: [] },
response: {},
routeId: "non-existent-route",
teamId: 1,
});
expect(result).toBeNull();
expect(troubleshooter).toBeNull();
});
it("should return null if the route does not have an attributesQueryValue set", async () => {
@@ -207,7 +209,7 @@ describe("findTeamMembersMatchingAttributeLogicOfRoute", () => {
expect(result).toBeNull();
});
it("should return matching team members with a SINGLE_SELECT attribute when a static option is selected", async () => {
it("should return matching team members with a SINGLE_SELECT attribute when a static option is selected and troubleshooter should be null by default", async () => {
const Option1OfAttribute1 = { id: "opt1", value: "Option 1", slug: "option-1" };
const Attribute1 = {
id: "attr1",
@@ -234,24 +236,25 @@ describe("findTeamMembersMatchingAttributeLogicOfRoute", () => {
],
}) as AttributesQueryValue;
const { teamMembersMatchingAttributeLogic: result } = await findTeamMembersMatchingAttributeLogicOfRoute({
form: {
routes: [
{
id: "test-route",
action: { type: RouteActionType.CustomPageMessage, value: "test" },
queryValue: {
type: "group",
} as unknown as FormFieldsQueryValue,
attributesQueryValue: attributesQueryValue,
},
],
fields: [],
},
response: {},
routeId: "test-route",
teamId: 1,
});
const { teamMembersMatchingAttributeLogic: result, troubleshooter } =
await findTeamMembersMatchingAttributeLogicOfRoute({
form: {
routes: [
{
id: "test-route",
action: { type: RouteActionType.CustomPageMessage, value: "test" },
queryValue: {
type: "group",
} as unknown as FormFieldsQueryValue,
attributesQueryValue: attributesQueryValue,
},
],
fields: [],
},
response: {},
routeId: "test-route",
teamId: 1,
});
expect(result).toEqual([
{
@@ -259,6 +262,8 @@ describe("findTeamMembersMatchingAttributeLogicOfRoute", () => {
result: RaqbLogicResult.MATCH,
},
]);
expect(troubleshooter).toBeNull();
});
it("should return matching team members with a SINGLE_SELECT attribute when 'Value of Field' option is selected", async () => {
@@ -339,8 +344,6 @@ 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",
@@ -454,6 +457,158 @@ describe("findTeamMembersMatchingAttributeLogicOfRoute", () => {
})
).rejects.toThrow("Unsupported attribute type");
});
it("should not throw error in live (non-preview) mode but should throw in preview mode", async () => {
const Option1OfAttribute1HumanReadableValue = "Option 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: [["NON_EXISTING_OPTION_1", "NON_EXISTING_OPTION_2"]],
operator: "select_any_in",
valueType: ["multiselect"],
},
],
}) as AttributesQueryValue;
async function runInMode({ mode }: { mode: "preview" | "live" }) {
const { teamMembersMatchingAttributeLogic: result } =
await findTeamMembersMatchingAttributeLogicOfRoute({
form: {
routes: [
buildDefaultCustomPageRoute({
id: "test-route",
attributesQueryValue: attributesQueryValue,
}),
],
fields: [],
},
response: {},
routeId: "test-route",
teamId: 1,
isPreview: mode === "preview" ? true : false,
});
return result;
}
await (async function liveMode() {
const result = await runInMode({ mode: "live" });
expect(result).toEqual([]);
})();
await (async function previewMode() {
expect(() => runInMode({ mode: "preview" })).rejects.toThrow(
/Value NON_EXISTING_OPTION_1 is not in list of values/
);
})();
});
it("should not throw error if children1 is empty", async () => {
const Option1OfAttribute1HumanReadableValue = "Option 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 = buildQueryValue({
rules: [],
}) as AttributesQueryValue;
expect(attributesQueryValue.children1).toEqual({});
async function runInMode({ mode }: { mode: "preview" | "live" }) {
const { teamMembersMatchingAttributeLogic: result } =
await findTeamMembersMatchingAttributeLogicOfRoute({
form: {
routes: [
buildDefaultCustomPageRoute({
id: "test-route",
attributesQueryValue: attributesQueryValue,
}),
],
fields: [],
},
response: {},
routeId: "test-route",
teamId: 1,
isPreview: mode === "preview" ? true : false,
});
return result;
}
await (async function liveMode() {
const result = await runInMode({ mode: "live" });
expect(result).toEqual(null);
})();
await (async function previewMode() {
const result = await runInMode({ mode: "preview" });
expect(result).toEqual(null);
})();
});
});
describe("Performance testing", () => {
@@ -537,4 +692,83 @@ describe("findTeamMembersMatchingAttributeLogicOfRoute", () => {
}, 10000);
});
});
describe("Troubleshooter", () => {
it("troubleshooter data should be there when enableTroubleshooter is true", async () => {
const Option1OfAttribute1HumanReadableValue = "Option 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, troubleshooter } =
await findTeamMembersMatchingAttributeLogicOfRoute(
{
form: {
routes: [
buildDefaultCustomPageRoute({
id: "test-route",
attributesQueryValue: attributesQueryValue,
}),
],
fields: [],
},
response: {},
routeId: "test-route",
teamId: 1,
},
{
enableTroubleshooter: true,
}
);
expect(result).toEqual([
{
userId: 1,
result: RaqbLogicResult.MATCH,
},
]);
expect(troubleshooter).not.toBeNull();
});
});
});
@@ -25,7 +25,7 @@ export const findTeamMembersMatchingAttributeLogicHandler = async ({
input,
}: FindTeamMembersMatchingAttributeLogicHandlerOptions) => {
const { prisma, user } = ctx;
const { formId, response, routeId, _enablePerf, _concurrency } = input;
const { formId, response, routeId, isPreview, _enablePerf, _concurrency } = input;
const form = await prisma.app_RoutingForms_Form.findFirst({
where: {
@@ -53,37 +53,47 @@ export const findTeamMembersMatchingAttributeLogicHandler = async ({
const {
teamMembersMatchingAttributeLogic: matchingTeamMembersWithResult,
timeTaken: teamMembersMatchingAttributeLogicTimeTaken,
troubleshooter,
} = await findTeamMembersMatchingAttributeLogicOfRoute(
{
response,
routeId,
form: serializableForm,
teamId: form.teamId,
isPreview: !!isPreview,
},
{
enablePerf: _enablePerf,
// Reuse same flag for enabling troubleshooter. We would normall use them together
enableTroubleshooter: _enablePerf,
concurrency: _concurrency,
}
);
if (!matchingTeamMembersWithResult) {
return null;
return {
troubleshooter,
result: null,
};
}
const matchingTeamMembersIds = matchingTeamMembersWithResult.map((member) => member.userId);
const matchingTeamMembers = await UserRepository.findByIds({ ids: matchingTeamMembersIds });
console.log('_enablePerf, _concurrency', _enablePerf, _concurrency)
console.log("_enablePerf, _concurrency", _enablePerf, _concurrency);
if (_enablePerf) {
const serverTimingHeader = getServerTimingHeader(teamMembersMatchingAttributeLogicTimeTaken);
ctx.res?.setHeader("Server-Timing", serverTimingHeader);
console.log('Server-Timing', serverTimingHeader)
console.log("Server-Timing", serverTimingHeader);
}
return matchingTeamMembers.map((user) => ({
id: user.id,
name: user.name,
email: user.email,
}));
return {
troubleshooter,
result: matchingTeamMembers.map((user) => ({
id: user.id,
name: user.name,
email: user.email,
})),
};
};
function getServerTimingHeader(timeTaken: {
@@ -93,14 +103,16 @@ function getServerTimingHeader(timeTaken: {
lgcFrMbrs: number | null;
gQryVal: number | null;
}) {
const headerParts = Object.entries(timeTaken).map(([key, value]) => {
if (value !== null) {
return `${key};dur=${value}`;
}
return null;
}).filter(Boolean);
const headerParts = Object.entries(timeTaken)
.map(([key, value]) => {
if (value !== null) {
return `${key};dur=${value}`;
}
return null;
})
.filter(Boolean);
return headerParts.join(', ');
return headerParts.join(", ");
}
export default findTeamMembersMatchingAttributeLogicHandler;
@@ -4,6 +4,7 @@ export const ZFindTeamMembersMatchingAttributeLogicInputSchema = z.object({
formId: z.string(),
response: z.record(z.string(), z.any()),
routeId: z.string(),
isPreview: z.boolean().optional(),
_enablePerf: z.boolean().optional(),
_concurrency: z.number().optional(),
});
+114 -27
View File
@@ -1,12 +1,13 @@
import type { App_RoutingForms_Form, User } from "@prisma/client";
import async from "async";
import { Config, Utils as QbUtils } from "react-awesome-query-builder";
import type { ImmutableTree, JsonTree } from "react-awesome-query-builder";
import type { Config } from "react-awesome-query-builder/lib";
import { Utils as QbUtils } from "react-awesome-query-builder/lib";
import getWebhooks from "@calcom/features/webhooks/lib/getWebhooks";
import { sendGenericWebhookPayload } from "@calcom/features/webhooks/lib/sendPayload";
import getOrgIdFromMemberOrTeamId from "@calcom/lib/getOrgIdFromMemberOrTeamId";
import logger from "@calcom/lib/logger";
import { safeStringify } from "@calcom/lib/safeStringify";
import { WebhookTriggerEvents } from "@calcom/prisma/client";
import type { Ensure } from "@calcom/types/utils";
@@ -17,7 +18,7 @@ import {
} from "../lib/getAttributes";
import isRouter from "../lib/isRouter";
import jsonLogic from "../lib/jsonLogic";
import type { SerializableField, OrderedResponses, AttributesQueryValue } from "../types/types";
import type { SerializableField, OrderedResponses } from "../types/types";
import type { FormResponse, SerializableForm } from "../types/types";
import { acrossQueryValueCompatiblity, raqbQueryValueUtils } from "./raqbUtils";
@@ -118,12 +119,38 @@ function perf<ReturnValue>(fn: () => ReturnValue): [ReturnValue, number | null]
return [result, end - start];
}
function getErrorsFromImmutableTree(tree: ImmutableTree) {
const validatedQueryValue = QbUtils.getTree(tree);
if (!raqbQueryValueUtils.isQueryValueARuleGroup(validatedQueryValue)) {
return [];
}
if (!validatedQueryValue.children1) {
return [];
}
const errors: string[][] = [];
Object.values(validatedQueryValue.children1).map((rule) => {
if (rule.type !== "rule") {
return;
}
const valueError = rule.properties.valueError;
if (valueError) {
// Sometimes there are null values in it.
errors.push(valueError.filter((value) => !!value));
}
});
return errors;
}
function getJsonLogic({
attributesQueryValue,
attributesQueryBuilderConfig,
isPreview,
}: {
attributesQueryValue: AttributesQueryValue;
attributesQueryValue: JsonTree;
attributesQueryBuilderConfig: Config;
isPreview: boolean;
}) {
const state = {
tree: QbUtils.checkTree(
@@ -136,45 +163,71 @@ function getJsonLogic({
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");
// We error only in preview mode to communicate any problem.
// In live mode, we don't error and instead prefer to let no members match which then causes all of the assignes of the team event to be used.
if (isPreview) {
const errors = getErrorsFromImmutableTree(state.tree).flat();
if (errors.length) {
throw new Error(errors.toString());
}
if (!logic) {
// Empty children1 is normal where it means that no rules are added by user.
if (attributesQueryValue.children1 && Object.keys(attributesQueryValue.children1).length > 0) {
// Possible reasons for this
// 1. The attribute option value used is not in the options list. Happens if 'Value of field' value is chosen and that field's response value doesn't exist in attribute options list.
throw new Error("There is some error building the logic, please check the routes.");
}
}
console.log(
"No logic found",
safeStringify({ attributesQueryValue, queryBuilderConfigFields: attributesQueryBuilderConfig.fields })
);
}
console.log("Using LOGIC", safeStringify(logic));
return logic;
}
export const enum TroubleshooterCase {
EMPTY_QUERY_VALUE = "empty-query-value",
IS_A_ROUTER = "is-a-router",
NO_LOGIC_FOUND = "no-logic-found",
MATCH_RESULTS_READY = "match-results-ready",
NO_ROUTE_FOUND = "no-route-found",
}
export async function findTeamMembersMatchingAttributeLogicOfRoute(
{
form,
response,
routeId,
teamId,
isPreview,
}: {
form: Pick<SerializableForm<App_RoutingForms_Form>, "routes" | "fields">;
response: FormResponse;
routeId: string;
teamId: number;
isPreview?: boolean;
},
config: {
enablePerf?: boolean;
concurrency?: number;
enableTroubleshooter?: boolean;
} = {}
) {
const route = form.routes?.find((route) => route.id === routeId);
// Higher value of concurrency might not be performant as it might overwhelm the system. So, use a lower value as default.
const { enablePerf = false, concurrency = 2 } = config;
const { enablePerf = false, concurrency = 2, enableTroubleshooter } = config;
if (!route) {
return {
teamMembersMatchingAttributeLogic: null,
timeTaken: null,
troubleshooter: enableTroubleshooter
? {
type: TroubleshooterCase.NO_ROUTE_FOUND,
data: {
routeId,
},
}
: null,
};
}
@@ -182,6 +235,14 @@ export async function findTeamMembersMatchingAttributeLogicOfRoute(
return {
teamMembersMatchingAttributeLogic: null,
timeTaken: null,
troubleshooter: enableTroubleshooter
? {
type: TroubleshooterCase.IS_A_ROUTER,
data: {
routeId,
},
}
: null,
};
}
@@ -211,6 +272,14 @@ export async function findTeamMembersMatchingAttributeLogicOfRoute(
gMbrWtAtr: null,
lgcFrMbrs: null,
},
troubleshooter: enableTroubleshooter
? {
type: TroubleshooterCase.EMPTY_QUERY_VALUE,
data: {
attributesQueryValue,
},
}
: null,
};
}
@@ -222,30 +291,20 @@ export async function findTeamMembersMatchingAttributeLogicOfRoute(
})
);
moduleLogger.debug(
"Finding team members matching attribute logic",
safeStringify({
form,
response,
routeId,
teamId,
attributesQueryBuilderConfigFields: attributesQueryBuilderConfig.fields,
})
);
const [
teamMembersWithAttributeOptionValuePerAttribute,
getTeamMembersWithAttributeOptionValuePerAttributeTimeTaken,
] = await aPf(() => getTeamMembersWithAttributeOptionValuePerAttribute({ teamId: teamId }));
const logic = getJsonLogic({
attributesQueryValue,
attributesQueryValue: attributesQueryValue as JsonTree,
attributesQueryBuilderConfig: attributesQueryBuilderConfig as unknown as Config,
isPreview: !!isPreview,
});
if (!logic) {
return {
teamMembersMatchingAttributeLogic: [],
teamMembersMatchingAttributeLogic: null,
timeTaken: {
gAtr: getAttributesForTeamTimeTaken,
gQryCnfg: getAttributesQueryBuilderConfigTimeTaken,
@@ -253,9 +312,21 @@ export async function findTeamMembersMatchingAttributeLogicOfRoute(
lgcFrMbrs: null,
gQryVal: getAttributesQueryValueTimeTaken,
},
troubleshooter: enableTroubleshooter
? {
type: TroubleshooterCase.NO_LOGIC_FOUND,
data: {
attributesQueryValue,
attributesQueryBuilderConfig,
teamMembersWithAttributeOptionValuePerAttribute,
},
}
: null,
};
}
const attributesDataPerUser = new Map<number, ReturnType<typeof getAttributes>>();
const [_, teamMembersMatchingAttributeLogicTimeTaken] = await aPf(async () => {
return await async.mapLimit<TeamMemberWithAttributeOptionValuePerAttribute, Promise<void>>(
teamMembersWithAttributeOptionValuePerAttribute,
@@ -266,6 +337,10 @@ export async function findTeamMembersMatchingAttributeLogicOfRoute(
attributesQueryValue,
});
if (enableTroubleshooter) {
attributesDataPerUser.set(member.userId, attributesData);
}
const result = !!jsonLogic.apply(logic as any, attributesData)
? RaqbLogicResult.MATCH
: RaqbLogicResult.NO_MATCH;
@@ -290,6 +365,18 @@ export async function findTeamMembersMatchingAttributeLogicOfRoute(
lgcFrMbrs: teamMembersMatchingAttributeLogicTimeTaken,
gQryVal: getAttributesQueryValueTimeTaken,
},
troubleshooter: enableTroubleshooter
? {
type: TroubleshooterCase.MATCH_RESULTS_READY,
data: {
attributesDataPerUser,
attributesQueryValue,
attributesQueryBuilderConfig,
logic,
attributesForTeam,
},
}
: null,
};
function pf<ReturnValue>(fn: () => ReturnValue): [ReturnValue, number | null] {