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:
co-authored by
Volnei Munhoz
Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
parent
545eed9b0d
commit
ee6989a575
@@ -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,
|
||||
};
|
||||
@@ -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";
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 = (() => {
|
||||
|
||||
Reference in New Issue
Block a user