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 type { dynamicFieldValueOperands } from "@calcom/lib/raqb/types"; import { safeStringify } from "@calcom/lib/safeStringify"; import type { AttributeOptionValueWithType, AttributeOptionValue, Attribute, } from "@calcom/lib/service/attribute/server/getAttributes"; import { AttributeType } from "@calcom/prisma/enums"; import { resolveQueryValue } from "./resolveQueryValue"; import { caseInsensitive } from "./utils"; 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. */ 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, };