diff --git a/apps/web/app/(use-page-wrapper)/apps/routing-forms/[...pages]/RouteBuilder.tsx b/apps/web/app/(use-page-wrapper)/apps/routing-forms/[...pages]/RouteBuilder.tsx index cbcee2e923..eba7150217 100644 --- a/apps/web/app/(use-page-wrapper)/apps/routing-forms/[...pages]/RouteBuilder.tsx +++ b/apps/web/app/(use-page-wrapper)/apps/routing-forms/[...pages]/RouteBuilder.tsx @@ -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; + eventTypeAppMetadata?: Record; isRRWeightsEnabled: boolean; }[] = []; const eventTypesMap = new Map< number, { schedulingType: SchedulingType | null; - eventTypeAppMetadata?: Record; + eventTypeAppMetadata?: Record; } >(); eventTypesByGroup?.eventTypeGroups.forEach((group) => { @@ -392,13 +395,11 @@ const Route = ({ ? eventOptions[0].value.substring(0, eventOptions[0].value.lastIndexOf("/") + 1) : ""; - const [customEventTypeSlug, setCustomEventTypeSlug] = useState(""); - - useEffect(() => { + const [customEventTypeSlug, setCustomEventTypeSlug] = useState(() => { 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 } }) => { - 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, diff --git a/packages/app-store/_utils/raqb/raqbUtils.client.ts b/packages/app-store/_utils/raqb/raqbUtils.client.ts new file mode 100644 index 0000000000..1d11503fe3 --- /dev/null +++ b/packages/app-store/_utils/raqb/raqbUtils.client.ts @@ -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), + }; +}; diff --git a/packages/app-store/_utils/raqb/raqbUtils.server.ts b/packages/app-store/_utils/raqb/raqbUtils.server.ts new file mode 100644 index 0000000000..be8c227465 --- /dev/null +++ b/packages/app-store/_utils/raqb/raqbUtils.server.ts @@ -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 + | Pick[] +) { + if (!(attributeOptions instanceof Array)) { + return transformAttributeOption(attributeOptions); + } + return attributeOptions + .map(transformAttributeOption) + .flat() + .filter((value, index, self) => self.indexOf(value) === index); + + function transformAttributeOption( + attributeOption: Pick + ) { + 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; + attributesQueryValue: NonNullable; +}) { + 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 + ); +} +/** 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, +}; diff --git a/packages/app-store/_utils/raqb/raqbUtils.ts b/packages/app-store/_utils/raqb/raqbUtils.ts index 66556cd451..caa7c119c9 100644 --- a/packages/app-store/_utils/raqb/raqbUtils.ts +++ b/packages/app-store/_utils/raqb/raqbUtils.ts @@ -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 - | Pick[] -) { - if (!(attributeOptions instanceof Array)) { - return transformAttributeOption(attributeOptions); - } - return attributeOptions - .map(transformAttributeOption) - .flat() - .filter((value, index, self) => self.indexOf(value) === index); - - function transformAttributeOption( - attributeOption: Pick - ) { - 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; - attributesQueryValue: NonNullable; -}) { - 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 - ); -} -/** 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"; diff --git a/packages/features/Segment.tsx b/packages/features/Segment.tsx index a68be96e6e..01edec03ed 100644 --- a/packages/features/Segment.tsx +++ b/packages/features/Segment.tsx @@ -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, diff --git a/packages/features/bookings/lib/getLuckyUser.ts b/packages/features/bookings/lib/getLuckyUser.ts index 67b94d491f..1ccd66a2bc 100644 --- a/packages/features/bookings/lib/getLuckyUser.ts +++ b/packages/features/bookings/lib/getLuckyUser.ts @@ -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(); - 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; }); }); } diff --git a/packages/features/ee/round-robin/assignmentReason/AssignmentReasonRecorder.ts b/packages/features/ee/round-robin/assignmentReason/AssignmentReasonRecorder.ts index 50c7c9ff22..fe1752e2d1 100644 --- a/packages/features/ee/round-robin/assignmentReason/AssignmentReasonRecorder.ts +++ b/packages/features/ee/round-robin/assignmentReason/AssignmentReasonRecorder.ts @@ -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 = (() => {