refactor: split raqbUtils into server and client modules to fix RAQB import in server context (#26112)

* refactor: add server and client raqbUtils modules

Add two new modules to split RAQB utilities:
- raqbUtils.server.ts: Server-safe utilities without RAQB runtime
- raqbUtils.client.ts: Client-only utilities requiring RAQB runtime

This enables server-side code to import RAQB utilities without
pulling in the react-awesome-query-builder runtime library.

Co-Authored-By: Volnei Munhoz <volnei.munhoz@gmail.com>

* refactor: update imports to use server/client raqbUtils modules and fix lint warnings

Co-Authored-By: Volnei Munhoz <volnei.munhoz@gmail.com>

* fix: fix type errors in getLuckyUser.ts and remove dead code in RouteBuilder.tsx

Co-Authored-By: Volnei Munhoz <volnei.munhoz@gmail.com>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Volnei Munhoz
2025-12-22 02:00:19 +00:00
committed by GitHub
co-authored by Volnei Munhoz Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
parent 545eed9b0d
commit ee6989a575
7 changed files with 292 additions and 313 deletions
@@ -8,9 +8,9 @@ import type { ImmutableTree, BuilderProps, Config } from "react-awesome-query-bu
import type { JsonTree } from "react-awesome-query-builder";
import type { UseFormReturn } from "react-hook-form";
import { Toaster } from "sonner";
import type { z } from "zod";
import { buildEmptyQueryValue, raqbQueryValueUtils } from "@calcom/app-store/_utils/raqb/raqbUtils";
import { buildEmptyQueryValue } from "@calcom/app-store/_utils/raqb/raqbUtils.client";
import { raqbQueryValueUtils } from "@calcom/app-store/_utils/raqb/raqbUtils.server";
import { routingFormAppComponents } from "@calcom/app-store/routing-forms/appComponents";
import DynamicAppComponent from "@calcom/app-store/routing-forms/components/DynamicAppComponent";
import { EmptyState } from "@calcom/app-store/routing-forms/components/_components/EmptyState";
@@ -39,7 +39,6 @@ import type {
EditFormRoute,
AttributeRoutingConfig,
} from "@calcom/app-store/routing-forms/types/types";
import type { zodRoutes } from "@calcom/app-store/routing-forms/zod";
import { RouteActionType } from "@calcom/app-store/routing-forms/zod";
import { useOrgBranding } from "@calcom/features/ee/organizations/context/provider";
import type { EventTypesByViewer } from "@calcom/features/eventtypes/lib/getEventTypesByViewer";
@@ -83,32 +82,36 @@ function useEnsureEventTypeIdInRedirectUrlAction({
eventOptions: { label: string; value: string; eventTypeId: number }[];
setRoute: SetRoute;
}) {
const routeActionValue = isRouter(route) ? undefined : route.action.value;
const routeActionType = isRouter(route) ? undefined : route.action.type;
const routeActionEventTypeId = isRouter(route) ? undefined : route.action.eventTypeId;
useEffect(() => {
if (isRouter(route)) {
return;
}
if (
route.action.type !== RouteActionType.EventTypeRedirectUrl ||
routeActionType !== RouteActionType.EventTypeRedirectUrl ||
// Must not be set already. Could be zero as well for custom
route.action.eventTypeId !== undefined
routeActionEventTypeId !== undefined
) {
return;
}
const matchingOption = eventOptions.find((eventOption) => eventOption.value === route.action.value);
const matchingOption = eventOptions.find((eventOption) => eventOption.value === routeActionValue);
if (!matchingOption) {
return;
}
setRoute(route.id, {
action: { ...route.action, eventTypeId: matchingOption.eventTypeId },
});
}, [eventOptions, setRoute, route.id, (route as unknown as any).action?.value]);
}, [eventOptions, setRoute, route, routeActionValue, routeActionType, routeActionEventTypeId]);
}
const hasRules = (route: EditFormRoute) => {
if (isRouter(route)) return false;
route.queryValue.children1 && Object.keys(route.queryValue.children1).length;
return route.queryValue.children1 && Object.keys(route.queryValue.children1).length;
};
function getEmptyQueryValue() {
@@ -147,14 +150,14 @@ const buildEventsData = ({
label: string;
value: string;
eventTypeId: number;
eventTypeAppMetadata?: Record<string, any>;
eventTypeAppMetadata?: Record<string, unknown>;
isRRWeightsEnabled: boolean;
}[] = [];
const eventTypesMap = new Map<
number,
{
schedulingType: SchedulingType | null;
eventTypeAppMetadata?: Record<string, any>;
eventTypeAppMetadata?: Record<string, unknown>;
}
>();
eventTypesByGroup?.eventTypeGroups.forEach((group) => {
@@ -392,13 +395,11 @@ const Route = ({
? eventOptions[0].value.substring(0, eventOptions[0].value.lastIndexOf("/") + 1)
: "";
const [customEventTypeSlug, setCustomEventTypeSlug] = useState<string>("");
useEffect(() => {
const [customEventTypeSlug, setCustomEventTypeSlug] = useState<string>(() => {
const isCustom =
!isRouter(route) && !eventOptions.find((eventOption) => eventOption.value === route.action.value);
setCustomEventTypeSlug(isCustom && !isRouter(route) ? route.action.value.split("/").pop() ?? "" : "");
}, []);
return isCustom && !isRouter(route) ? route.action.value.split("/").pop() ?? "" : "";
});
useEnsureEventTypeIdInRedirectUrlAction({
route,
@@ -1147,67 +1148,6 @@ const Routes = ({
hookForm,
});
const { data: allForms } = trpc.viewer.appRoutingForms.forms.useQuery();
const notHaveAttributesQuery = ({ form }: { form: { routes: z.infer<typeof zodRoutes> } }) => {
return form.routes?.every((route) => {
if (isRouter(route)) {
return true;
}
return !route.attributesQueryValue;
});
};
const availableRouters =
allForms?.filtered
.filter(({ form: router }) => {
const routerValidInContext = areTheySiblingEntities({
entity1: {
teamId: router.teamId ?? null,
// group doesn't have userId. The query ensures that it belongs to the user only, if teamId isn't set. So, I am manually setting it to the form userId
userId: router.userId,
},
entity2: {
teamId: hookForm.getValues().teamId ?? null,
userId: hookForm.getValues().userId,
},
});
return router.id !== hookForm.getValues().id && routerValidInContext;
})
// We don't want to support picking forms that have attributes query. We can consider it later.
// This is mainly because the Router picker feature is pretty much not used and we don't want to complicate things
.filter(({ form }) => {
return notHaveAttributesQuery({ form: form });
})
.map(({ form: router }) => {
return {
value: router.id,
label: router.name,
name: router.name,
description: router.description,
isDisabled: false,
};
}) || [];
// const isConnectedForm = (id: string) => form.connectedForms.map((f) => f.id).includes(id);
// const routers: any[] = [];
/* Disable this feature for new forms till we get it fully working with Routing Form with Attributes. This isn't much used feature */
// const routers = availableRouters.map((r) => {
// // Reset disabled state
// r.isDisabled = false;
// // Can't select a form as router that is already a connected form. It avoids cyclic dependency
// if (isConnectedForm(r.value)) {
// r.isDisabled = true;
// }
// // A route that's already used, can't be reselected
// if (routes.find((route) => route.id === r.value)) {
// r.isDisabled = true;
// }
// return r;
// });
const createRoute = useCreateRoute({
routes,
setRoutes,
@@ -0,0 +1,34 @@
"use client";
/**
* Client-only RAQB utilities that require the react-awesome-query-builder runtime.
* These functions should only be imported in client-side React components.
*/
import type { JsonTree } from "react-awesome-query-builder";
import type { Config } from "react-awesome-query-builder";
import { Utils as QbUtils } from "react-awesome-query-builder";
export function buildEmptyQueryValue() {
return { id: QbUtils.uuid(), type: "group" as const };
}
export const buildStateFromQueryValue = ({
queryValue,
config,
}: {
/**
* Allow null as the queryValue as initially there could be no queryValue and without that we can't build the state and can't show the UI
*/
queryValue: JsonTree | null;
config: Config;
}) => {
const queryValueToUse = queryValue || buildEmptyQueryValue();
const immutableTree = QbUtils.checkTree(QbUtils.loadTree(queryValueToUse), config);
return {
state: {
tree: immutableTree,
config,
},
queryValue: QbUtils.getTree(immutableTree),
};
};
@@ -0,0 +1,201 @@
/**
* Server-safe RAQB utilities that don't require the react-awesome-query-builder runtime.
* These functions can be safely imported in server-side code (cron jobs, API routes, etc.)
* without pulling in the client-only RAQB library.
*/
import type { JsonGroup, JsonItem, JsonRule, JsonTree } from "react-awesome-query-builder";
import { getQueryBuilderConfigForAttributes } from "@calcom/app-store/routing-forms/lib/getQueryBuilderConfig";
import type { LocalRoute } from "@calcom/app-store/routing-forms/types/types";
import { resolveQueryValue } from "@calcom/lib/raqb/resolveQueryValue";
import type { dynamicFieldValueOperands } from "@calcom/lib/raqb/types";
import { caseInsensitive } from "@calcom/lib/raqb/utils";
import { safeStringify } from "@calcom/lib/safeStringify";
import type {
AttributeOptionValueWithType,
AttributeOptionValue,
Attribute,
} from "@calcom/lib/service/attribute/server/getAttributes";
import { AttributeType } from "@calcom/prisma/enums";
function ensureArray(value: string | string[]) {
return typeof value === "string" ? [value] : value;
}
export const raqbQueryValueUtils = {
isQueryValueARuleGroup: function isQueryValueARuleGroup(queryValue: JsonTree): queryValue is JsonGroup {
return queryValue.type === "group";
},
isARule: function isARule(rule: JsonItem): rule is JsonRule {
return rule.type === "rule";
},
getValueTypeFromAttributesQueryValueForRaqbField:
function getValueTypeFromAttributesQueryValueForRaqbField({
attributesQueryValue,
raqbFieldId,
}: {
attributesQueryValue: JsonTree;
raqbFieldId: string;
}) {
if (!raqbQueryValueUtils.isQueryValueARuleGroup(attributesQueryValue)) {
return null;
}
if (!attributesQueryValue.children1) {
return null;
}
let raqbFieldValueType = null;
Object.values(attributesQueryValue.children1).reduce((acc, rule) => {
if (!raqbQueryValueUtils.isARule(rule)) {
return acc;
}
const ruleProperties = rule?.properties;
if (!ruleProperties) {
return acc;
}
if (ruleProperties.field === raqbFieldId) {
raqbFieldValueType = ruleProperties.valueType?.[0];
}
return acc;
}, null);
return raqbFieldValueType;
},
isQueryValueEmpty: function isQueryValueEmpty(queryValue: JsonTree | null): queryValue is null {
if (!queryValue) {
return true;
}
return !queryValue.children1;
},
};
export function getValueOfAttributeOption(
attributeOptions:
| Pick<AttributeOptionValue, "isGroup" | "contains" | "value">
| Pick<AttributeOptionValue, "isGroup" | "contains" | "value">[]
) {
if (!(attributeOptions instanceof Array)) {
return transformAttributeOption(attributeOptions);
}
return attributeOptions
.map(transformAttributeOption)
.flat()
.filter((value, index, self) => self.indexOf(value) === index);
function transformAttributeOption(
attributeOption: Pick<AttributeOptionValue, "isGroup" | "contains" | "value">
) {
if (attributeOption.isGroup) {
const subOptions = attributeOption.contains.map((option) => option.value);
console.log("A group option found. Using all its sub-options instead", safeStringify(subOptions));
return subOptions;
}
return attributeOption.value;
}
}
function getAttributesData({
attributesData,
}: {
attributesData: Record<string, AttributeOptionValueWithType>;
attributesQueryValue: NonNullable<LocalRoute["attributesQueryValue"]>;
}) {
return Object.entries(attributesData).reduce(
(acc, [attributeId, { type: attributeType, attributeOption }]) => {
const compatibleValueForAttributeAndFormFieldMatching = caseInsensitive(
getValueOfAttributeOption(attributeOption)
);
acc[attributeId] =
// multiselect attribute's value must be an array as all the operators multiselect_some_in, multiselect_all_in and their respective not operators expect an array
// If we add an operator that doesn't expect an array, we need to somehow make it operator based.
attributeType === AttributeType.MULTI_SELECT
? ensureArray(compatibleValueForAttributeAndFormFieldMatching)
: compatibleValueForAttributeAndFormFieldMatching;
return acc;
},
{} as Record<string, string | string[]>
);
}
/** Gets the attributes that were used to generate the chosen route and their values */
function getAttributesQueryValue({
attributesQueryValue,
attributes,
dynamicFieldValueOperands,
}: {
attributesQueryValue: LocalRoute["attributesQueryValue"] | null;
attributes: Attribute[];
dynamicFieldValueOperands?: dynamicFieldValueOperands;
}) {
if (!attributesQueryValue) {
return null;
}
const resolvedQueryValue = resolveQueryValue({
queryValue: attributesQueryValue,
attributes,
dynamicFieldValueOperands,
});
return resolvedQueryValue;
}
/**
* Returns attributesQueryBuilderConfig with the list of labels instead of list of ids.
*/
export function getAttributesQueryBuilderConfigHavingListofLabels({
dynamicFieldValueOperands,
attributes,
}: {
dynamicFieldValueOperands?: dynamicFieldValueOperands;
attributes: Attribute[];
}) {
const attributesQueryBuilderConfig = getQueryBuilderConfigForAttributes({
attributes,
dynamicOperandFields: dynamicFieldValueOperands?.fields,
});
const attributesQueryBuilderConfigFieldsWithCompatibleListValues = Object.fromEntries(
Object.entries(attributesQueryBuilderConfig.fields).map(([raqbFieldId, raqbField]) => {
const raqbFieldType = raqbField.type;
return [
raqbFieldId,
{
...raqbField,
type: raqbFieldType,
fieldSettings: {
...raqbField.fieldSettings,
listValues: raqbField.fieldSettings.listValues?.map((option) => {
return {
...option,
// Use the title(which is the attributeOption.value) as the value of the raqb field so that it can be compatible for matching with the form field value
value: caseInsensitive(option.title),
};
}),
},
},
];
})
);
const attributesQueryBuilderConfigWithCompatibleListValues = {
...attributesQueryBuilderConfig,
fields: attributesQueryBuilderConfigFieldsWithCompatibleListValues,
};
return attributesQueryBuilderConfigWithCompatibleListValues;
}
/**
* Utilities to establish compatibility between formFieldQueryValue and attributeQueryValue
*/
export const acrossQueryValueCompatiblity = {
getAttributesQueryBuilderConfigHavingListofLabels,
getAttributesQueryValue,
getAttributesData,
resolveQueryValue,
};
+20 -219
View File
@@ -1,223 +1,24 @@
import type { JsonGroup, JsonItem, JsonRule, JsonTree } from "react-awesome-query-builder";
import type { Config } from "react-awesome-query-builder";
import { Utils as QbUtils } from "react-awesome-query-builder";
import { getQueryBuilderConfigForAttributes } from "@calcom/app-store/routing-forms/lib/getQueryBuilderConfig";
import type { LocalRoute } from "@calcom/app-store/routing-forms/types/types";
import { resolveQueryValue } from "@calcom/lib/raqb/resolveQueryValue";
import type { dynamicFieldValueOperands } from "@calcom/lib/raqb/types";
import { caseInsensitive } from "@calcom/lib/raqb/utils";
import { safeStringify } from "@calcom/lib/safeStringify";
import type {
AttributeOptionValueWithType,
AttributeOptionValue,
Attribute,
} from "@calcom/lib/service/attribute/server/getAttributes";
import { AttributeType } from "@calcom/prisma/enums";
function ensureArray(value: string | string[]) {
return typeof value === "string" ? [value] : value;
}
export const raqbQueryValueUtils = {
isQueryValueARuleGroup: function isQueryValueARuleGroup(queryValue: JsonTree): queryValue is JsonGroup {
return queryValue.type === "group";
},
isARule: function isARule(rule: JsonItem): rule is JsonRule {
return rule.type === "rule";
},
getValueTypeFromAttributesQueryValueForRaqbField:
function getValueTypeFromAttributesQueryValueForRaqbField({
attributesQueryValue,
raqbFieldId,
}: {
attributesQueryValue: JsonTree;
raqbFieldId: string;
}) {
if (!raqbQueryValueUtils.isQueryValueARuleGroup(attributesQueryValue)) {
return null;
}
if (!attributesQueryValue.children1) {
return null;
}
let raqbFieldValueType = null;
Object.values(attributesQueryValue.children1).reduce((acc, rule) => {
if (!raqbQueryValueUtils.isARule(rule)) {
return acc;
}
const ruleProperties = rule?.properties;
if (!ruleProperties) {
return acc;
}
if (ruleProperties.field === raqbFieldId) {
raqbFieldValueType = ruleProperties.valueType?.[0];
}
return acc;
}, null);
return raqbFieldValueType;
},
isQueryValueEmpty: function isQueryValueEmpty(queryValue: JsonTree | null): queryValue is null {
if (!queryValue) {
return true;
}
return !queryValue.children1;
},
};
export function buildEmptyQueryValue() {
return { id: QbUtils.uuid(), type: "group" as const };
}
export const buildStateFromQueryValue = ({
queryValue,
config,
}: {
/**
* Allow null as the queryValue as initially there could be no queryValue and without that we can't build the state and can't show the UI
*/
queryValue: JsonTree | null;
config: Config;
}) => {
const queryValueToUse = queryValue || buildEmptyQueryValue();
const immutableTree = QbUtils.checkTree(QbUtils.loadTree(queryValueToUse), config);
return {
state: {
tree: immutableTree,
config,
},
queryValue: QbUtils.getTree(immutableTree),
};
};
export function getValueOfAttributeOption(
attributeOptions:
| Pick<AttributeOptionValue, "isGroup" | "contains" | "value">
| Pick<AttributeOptionValue, "isGroup" | "contains" | "value">[]
) {
if (!(attributeOptions instanceof Array)) {
return transformAttributeOption(attributeOptions);
}
return attributeOptions
.map(transformAttributeOption)
.flat()
.filter((value, index, self) => self.indexOf(value) === index);
function transformAttributeOption(
attributeOption: Pick<AttributeOptionValue, "isGroup" | "contains" | "value">
) {
if (attributeOption.isGroup) {
const subOptions = attributeOption.contains.map((option) => option.value);
console.log("A group option found. Using all its sub-options instead", safeStringify(subOptions));
return subOptions;
}
return attributeOption.value;
}
}
function getAttributesData({
attributesData,
}: {
attributesData: Record<string, AttributeOptionValueWithType>;
attributesQueryValue: NonNullable<LocalRoute["attributesQueryValue"]>;
}) {
return Object.entries(attributesData).reduce(
(acc, [attributeId, { type: attributeType, attributeOption }]) => {
const compatibleValueForAttributeAndFormFieldMatching = caseInsensitive(
getValueOfAttributeOption(attributeOption)
);
acc[attributeId] =
// multiselect attribute's value must be an array as all the operators multiselect_some_in, multiselect_all_in and their respective not operators expect an array
// If we add an operator that doesn't expect an array, we need to somehow make it operator based.
attributeType === AttributeType.MULTI_SELECT
? ensureArray(compatibleValueForAttributeAndFormFieldMatching)
: compatibleValueForAttributeAndFormFieldMatching;
return acc;
},
{} as Record<string, string | string[]>
);
}
/** Gets the attributes that were used to generate the chosen route and their values */
function getAttributesQueryValue({
attributesQueryValue,
attributes,
dynamicFieldValueOperands,
}: {
attributesQueryValue: LocalRoute["attributesQueryValue"] | null;
attributes: Attribute[];
dynamicFieldValueOperands?: dynamicFieldValueOperands;
}) {
if (!attributesQueryValue) {
return null;
}
const resolvedQueryValue = resolveQueryValue({
queryValue: attributesQueryValue,
attributes,
dynamicFieldValueOperands,
});
return resolvedQueryValue;
}
/**
* Returns attributesQueryBuilderConfig with the list of labels instead of list of ids.
* Barrel file that re-exports from both server and client modules for backward compatibility.
*
* IMPORTANT: This file imports from raqbUtils.client.ts which requires the react-awesome-query-builder runtime.
* For server-side code (cron jobs, API routes, etc.), import directly from raqbUtils.server.ts instead
* to avoid pulling in the client-only RAQB library.
*
* Server-side imports should use:
* import { acrossQueryValueCompatiblity, raqbQueryValueUtils } from "@calcom/app-store/_utils/raqb/raqbUtils.server";
*
* Client-side imports can use this file or import directly from raqbUtils.client.ts:
* import { buildStateFromQueryValue, buildEmptyQueryValue } from "@calcom/app-store/_utils/raqb/raqbUtils.client";
*/
export function getAttributesQueryBuilderConfigHavingListofLabels({
dynamicFieldValueOperands,
attributes,
}: {
dynamicFieldValueOperands?: dynamicFieldValueOperands;
attributes: Attribute[];
}) {
const attributesQueryBuilderConfig = getQueryBuilderConfigForAttributes({
attributes,
dynamicOperandFields: dynamicFieldValueOperands?.fields,
});
const attributesQueryBuilderConfigFieldsWithCompatibleListValues = Object.fromEntries(
Object.entries(attributesQueryBuilderConfig.fields).map(([raqbFieldId, raqbField]) => {
const raqbFieldType = raqbField.type;
return [
raqbFieldId,
{
...raqbField,
type: raqbFieldType,
fieldSettings: {
...raqbField.fieldSettings,
listValues: raqbField.fieldSettings.listValues?.map((option) => {
return {
...option,
// Use the title(which is the attributeOption.value) as the value of the raqb field so that it can be compatible for matching with the form field value
value: caseInsensitive(option.title),
};
}),
},
},
];
})
);
const attributesQueryBuilderConfigWithCompatibleListValues = {
...attributesQueryBuilderConfig,
fields: attributesQueryBuilderConfigFieldsWithCompatibleListValues,
};
return attributesQueryBuilderConfigWithCompatibleListValues;
}
/**
* Utilities to establish compatibility between formFieldQueryValue and attributeQueryValue
*/
export const acrossQueryValueCompatiblity = {
// Re-export server-safe utilities
export {
raqbQueryValueUtils,
getValueOfAttributeOption,
getAttributesQueryBuilderConfigHavingListofLabels,
getAttributesQueryValue,
getAttributesData,
resolveQueryValue,
};
acrossQueryValueCompatiblity,
} from "./raqbUtils.server";
// Re-export client-only utilities (requires react-awesome-query-builder runtime)
export { buildEmptyQueryValue, buildStateFromQueryValue } from "./raqbUtils.client";
+1 -1
View File
@@ -5,7 +5,7 @@ import { Query, Builder, Utils as QbUtils } from "react-awesome-query-builder";
import type { ImmutableTree, BuilderProps } from "react-awesome-query-builder";
import type { JsonTree } from "react-awesome-query-builder";
import { buildStateFromQueryValue } from "@calcom/app-store/_utils/raqb/raqbUtils";
import { buildStateFromQueryValue } from "@calcom/app-store/_utils/raqb/raqbUtils.client";
import {
withRaqbSettingsAndWidgets,
ConfigFor,
+18 -15
View File
@@ -1,4 +1,4 @@
import { acrossQueryValueCompatiblity } from "@calcom/app-store/_utils/raqb/raqbUtils";
import { acrossQueryValueCompatiblity } from "@calcom/app-store/_utils/raqb/raqbUtils.server";
import type { FormResponse, Fields } from "@calcom/app-store/routing-forms/types/types";
import { zodRoutes } from "@calcom/app-store/routing-forms/zod";
import dayjs from "@calcom/dayjs";
@@ -502,10 +502,10 @@ export class LuckyUserService implements ILuckyUserService {
type?: string | undefined;
properties?:
| {
field?: any;
operator?: any;
value?: any;
valueSrc?: any;
field?: string | null;
operator?: string | null;
value?: (string | string[])[];
valueSrc?: string[];
}
| undefined;
}
@@ -523,9 +523,10 @@ export class LuckyUserService implements ILuckyUserService {
const attributeId = obj.field;
const allRRHostsWeights = new Map<number, number[]>();
if (attributeId === attributeWithWeights.id) {
obj.value.forEach((arrayobj: string[]) => {
arrayobj.forEach((attributeOption: string) => {
if (attributeId === attributeWithWeights.id && obj.value) {
obj.value.forEach((valueItem) => {
const attributeOptions = Array.isArray(valueItem) ? valueItem : [valueItem];
attributeOptions.forEach((attributeOption: string) => {
const attributeOptionWithUsers = attributeWithWeights.options.find(
(option) => option.value.toLowerCase() === attributeOption.toLowerCase()
);
@@ -570,10 +571,10 @@ export class LuckyUserService implements ILuckyUserService {
type?: string | undefined;
properties?:
| {
field?: any;
operator?: any;
value?: any;
valueSrc?: any;
field?: string | null;
operator?: string | null;
value?: (string | string[])[];
valueSrc?: string[];
}
| undefined;
}
@@ -590,9 +591,10 @@ export class LuckyUserService implements ILuckyUserService {
fieldValueArray.some((obj) => {
const attributeId = obj.field;
if (attributeId === attributeWithWeights.id) {
obj.value.some((arrayobj: string[]) => {
arrayobj.some((attributeOptionId: string) => {
if (attributeId === attributeWithWeights.id && obj.value) {
obj.value.some((valueItem) => {
const attributeOptionIds = Array.isArray(valueItem) ? valueItem : [valueItem];
return attributeOptionIds.some((attributeOptionId: string) => {
const content = attributeOptionId.slice(1, -1);
const routingFormFieldId = content.includes("field:") ? content.split("field:")[1] : null;
@@ -602,6 +604,7 @@ export class LuckyUserService implements ILuckyUserService {
selectionOptions = { fieldId: routingFormFieldId, selectedOptionIds: fieldResponse.value };
return true;
}
return false;
});
});
}
@@ -1,4 +1,4 @@
import { acrossQueryValueCompatiblity } from "@calcom/app-store/_utils/raqb/raqbUtils";
import { acrossQueryValueCompatiblity } from "@calcom/app-store/_utils/raqb/raqbUtils.server";
import type { FormResponse, Fields } from "@calcom/app-store/routing-forms/types/types";
import { zodRoutes } from "@calcom/app-store/routing-forms/zod";
import { withReporting } from "@calcom/lib/sentryWrapper";
@@ -102,7 +102,7 @@ export default class AssignmentReasonRecorder {
const attributeValue = attributeToFilter.value;
if (!userAttribute || !attributeValue || typeof attributeValue[0] === null) continue;
if (!userAttribute || !attributeValue || attributeValue[0] === null) continue;
if (attributeValue && attributeValue[0]) {
const attributeValueString = (() => {