diff --git a/packages/core/modules/actions/rule.js b/packages/core/modules/actions/rule.js index ec16eba8e..7bef933f4 100644 --- a/packages/core/modules/actions/rule.js +++ b/packages/core/modules/actions/rule.js @@ -1,16 +1,30 @@ import * as constants from "../stores/constants"; -import {toImmutableList} from "../utils/stuff"; +import { toImmutableList } from "../utils/stuff"; /** * @param {object} config * @param {Immutable.List} path * @param {string} field */ -export const setField = (config, path, field) => ({ - type: constants.SET_FIELD, +export const setField = (config, path, field) => { + return { + type: constants.SET_FIELD, + path: toImmutableList(path), + field: field, + config: config, + }; +}; + +/** + * @param {object} config + * @param {Immutable.List} path + * @param {*} srcKey + */ +export const setFieldSrc = (config, path, srcKey) => ({ + type: constants.SET_FIELD_SRC, path: toImmutableList(path), - field: field, - config: config + srcKey: srcKey, + config: config, }); /** @@ -22,7 +36,7 @@ export const setOperator = (config, path, operator) => ({ type: constants.SET_OPERATOR, path: toImmutableList(path), operator: operator, - config: config + config: config, }); /** @@ -34,7 +48,15 @@ export const setOperator = (config, path, operator) => ({ * @param {*} asyncListValues * @param {boolean} __isInternal */ -export const setValue = (config, path, delta, value, valueType, asyncListValues, __isInternal) => ({ +export const setValue = ( + config, + path, + delta, + value, + valueType, + asyncListValues, + __isInternal +) => ({ type: constants.SET_VALUE, path: toImmutableList(path), delta: delta, @@ -42,7 +64,7 @@ export const setValue = (config, path, delta, value, valueType, asyncListValues, valueType: valueType, asyncListValues: asyncListValues, config: config, - __isInternal: __isInternal + __isInternal: __isInternal, }); /** @@ -56,7 +78,7 @@ export const setValueSrc = (config, path, delta, srcKey) => ({ path: toImmutableList(path), delta: delta, srcKey: srcKey, - config: config + config: config, }); /** @@ -70,5 +92,5 @@ export const setOperatorOption = (config, path, name, value) => ({ path: toImmutableList(path), name: name, value: value, - config: config + config: config, }); diff --git a/packages/core/modules/config/default.js b/packages/core/modules/config/default.js index 1978bba8c..f404756b9 100644 --- a/packages/core/modules/config/default.js +++ b/packages/core/modules/config/default.js @@ -8,7 +8,7 @@ export const settings = { return field; }, - + fieldSources: ["field", "func"], valueSourcesInfo: { value: {}, }, @@ -54,5 +54,4 @@ export const settings = { valueSourcesPopupTitle: "Select value source", removeRuleConfirmOptions: null, removeGroupConfirmOptions: null, - }; diff --git a/packages/core/modules/config/funcs.js b/packages/core/modules/config/funcs.js index 3d2ca0a24..8a064a360 100644 --- a/packages/core/modules/config/funcs.js +++ b/packages/core/modules/config/funcs.js @@ -20,12 +20,8 @@ const RELATIVE_DATETIME = { returnType: "datetime", renderBrackets: ["", ""], renderSeps: ["", "", ""], - jsonLogic: ({date, op, val, dim}) => ({ - "date_add": [ - date, - val * (op == "minus" ? -1 : +1), - dim - ] + jsonLogic: ({ date, op, val, dim }) => ({ + date_add: [date, val * (op == "minus" ? -1 : +1), dim], }), jsonLogicImport: (v) => { const date = v["date_add"][0]; @@ -35,19 +31,23 @@ const RELATIVE_DATETIME = { return [date, op, val, dim]; }, jsonLogicCustomOps: { - date_add: (date, val, dim) => moment(date).add(val, dim).toDate() + date_add: (date, val, dim) => moment(date).add(val, dim).toDate(), }, // MySQL //todo: other SQL dialects? - sqlFormatFunc: ({date, op, val, dim}) => `DATE_ADD(${date}, INTERVAL ${parseInt(val) * (op == "minus" ? -1 : +1)} ${dim.replace(/^'|'$/g, "")})`, + sqlFormatFunc: ({ date, op, val, dim }) => + `DATE_ADD(${date}, INTERVAL ${ + parseInt(val) * (op == "minus" ? -1 : +1) + } ${dim.replace(/^'|'$/g, "")})`, mongoFormatFunc: null, //todo: support? //todo: spel - formatFunc: ({date, op, val, dim}) => (!val ? date : `${date} ${op == "minus" ? "-" : "+"} ${val} ${dim}`), + formatFunc: ({ date, op, val, dim }) => + !val ? date : `${date} ${op == "minus" ? "-" : "+"} ${val} ${dim}`, args: { date: { label: "Date", type: "datetime", - defaultValue: {func: "NOW", args: []}, + defaultValue: { func: "NOW", args: [] }, valueSources: ["func", "field"], }, op: { @@ -57,15 +57,15 @@ const RELATIVE_DATETIME = { valueSources: ["value"], mainWidgetProps: { customProps: { - showSearch: false - } + showSearch: false, + }, }, fieldSettings: { listValues: { plus: "+", minus: "-", }, - } + }, }, val: { label: "Value", @@ -83,8 +83,8 @@ const RELATIVE_DATETIME = { valueSources: ["value"], mainWidgetProps: { customProps: { - showSearch: false - } + showSearch: false, + }, }, fieldSettings: { listValues: { @@ -93,13 +93,15 @@ const RELATIVE_DATETIME = { month: "month", year: "year", }, - } + }, }, - } + }, }; const LOWER = { label: "Lowercase", + allowSelfNesting: true, + valueSources: ["value", "field", "func"], mongoFunc: "$toLower", jsonLogic: "toLowerCase", spelFunc: ".toLowerCase", @@ -110,15 +112,17 @@ const LOWER = { returnType: "text", args: { str: { - label: "String", + label: "ARGUMENT", type: "text", - valueSources: ["value", "field"], + valueSources: ["value", "field", "func"], }, - } + }, }; const UPPER = { label: "Uppercase", + allowSelfNesting: true, + valueSources: ["value", "field", "func"], mongoFunc: "$toUpper", jsonLogic: "toUpperCase", spelFunc: ".toUpperCase", @@ -129,21 +133,23 @@ const UPPER = { returnType: "text", args: { str: { - label: "String", + label: "ARGUMENT", type: "text", - valueSources: ["value", "field"], + valueSources: ["value", "field", "func"], }, - } + }, }; const LINEAR_REGRESSION = { label: "Linear regression", returnType: "number", - formatFunc: ({coef, bias, val}, _) => `(${coef} * ${val} + ${bias})`, - sqlFormatFunc: ({coef, bias, val}) => `(${coef} * ${val} + ${bias})`, - spelFormatFunc: ({coef, bias, val}) => `(${coef} * ${val} + ${bias})`, - mongoFormatFunc: ({coef, bias, val}) => ({"$sum": [{"$multiply": [coef, val]}, bias]}), - jsonLogic: ({coef, bias, val}) => ({ "+": [ {"*": [coef, val]}, bias ] }), + formatFunc: ({ coef, bias, val }, _) => `(${coef} * ${val} + ${bias})`, + sqlFormatFunc: ({ coef, bias, val }) => `(${coef} * ${val} + ${bias})`, + spelFormatFunc: ({ coef, bias, val }) => `(${coef} * ${val} + ${bias})`, + mongoFormatFunc: ({ coef, bias, val }) => ({ + $sum: [{ $multiply: [coef, val] }, bias], + }), + jsonLogic: ({ coef, bias, val }) => ({ "+": [{ "*": [coef, val] }, bias] }), jsonLogicImport: (v) => { const coef = v["+"][0]["*"][0]; const val = v["+"][0]["*"][1]; @@ -169,14 +175,8 @@ const LINEAR_REGRESSION = { type: "number", defaultValue: 0, valueSources: ["value"], - } - } + }, + }, }; -export { - LOWER, - UPPER, - NOW, - RELATIVE_DATETIME, - LINEAR_REGRESSION, -}; +export { LOWER, UPPER, NOW, RELATIVE_DATETIME, LINEAR_REGRESSION }; diff --git a/packages/core/modules/export/jsonLogic.js b/packages/core/modules/export/jsonLogic.js index f3ca879bd..251e0eb69 100644 --- a/packages/core/modules/export/jsonLogic.js +++ b/packages/core/modules/export/jsonLogic.js @@ -1,54 +1,54 @@ -import {defaultValue} from "../utils/stuff"; +import { defaultValue } from "../utils/stuff"; import { - getFieldConfig, getOperatorConfig, getFieldWidgetConfig, getFuncConfig + getFieldConfig, + getOperatorConfig, + getFieldWidgetConfig, + getFuncConfig, } from "../utils/configUtils"; -import {getWidgetForFieldOp} from "../utils/ruleUtils"; -import {defaultConjunction} from "../utils/defaultUtils"; -import {completeValue} from "../utils/funcUtils"; -import {List, Map} from "immutable"; +import { getWidgetForFieldOp } from "../utils/ruleUtils"; +import { defaultConjunction } from "../utils/defaultUtils"; +import { completeValue } from "../utils/funcUtils"; +import { List, Map } from "immutable"; import omit from "lodash/omit"; import pick from "lodash/pick"; // http://jsonlogic.com/ - export const jsonLogicFormat = (item, config) => { //meta is mutable let meta = { usedFields: [], - errors: [] + errors: [], }; - + const logic = formatItem(item, config, meta, true); - + // build empty data - const {errors, usedFields} = meta; - const {fieldSeparator} = config.settings; + const { errors, usedFields } = meta; + const { fieldSeparator } = config.settings; let data = {}; for (let ff of usedFields) { - const def = getFieldConfig(config, ff) || {}; - const parts = ff.split(fieldSeparator); + const fieldSrc = typeof ff === "string" ? "field" : "func"; + const parts = + fieldSrc === "func" ? [ff.get("func")] : ff.split(fieldSeparator); let tmp = data; - for (let i = 0 ; i < parts.length ; i++) { + for (let i = 0; i < parts.length; i++) { const p = parts[i]; - const pdef = getFieldConfig(config, parts.slice(0, i+1)) || {}; + const pdef = + getFieldConfig(config, parts.slice(0, i + 1), fieldSrc) || {}; if (i != parts.length - 1) { if (pdef.type == "!group" && pdef.mode != "struct") { - if (!tmp[p]) - tmp[p] = [{}]; + if (!tmp[p]) tmp[p] = [{}]; tmp = tmp[p][0]; } else { - if (!tmp[p]) - tmp[p] = {}; + if (!tmp[p]) tmp[p] = {}; tmp = tmp[p]; } } else { - if (!tmp[p]) - tmp[p] = null; // can use def.type for sample values + if (!tmp[p]) tmp[p] = null; // can use def.type for sample values } } } - return { errors, logic, @@ -56,13 +56,12 @@ export const jsonLogicFormat = (item, config) => { }; }; - const formatItem = (item, config, meta, isRoot, parentField = null) => { if (!item) return undefined; const type = item.get("type"); const properties = item.get("properties") || new Map(); const isLocked = properties.get("isLocked"); - const {lockedOp} = config.settings.jsonLogic; + const { lockedOp } = config.settings.jsonLogic; let ret; if (type === "group" || type === "rule_group") { ret = formatGroup(item, config, meta, isRoot, parentField); @@ -70,22 +69,20 @@ const formatItem = (item, config, meta, isRoot, parentField = null) => { ret = formatRule(item, config, meta, parentField); } if (isLocked && ret && lockedOp) { - ret = { [lockedOp] : ret }; + ret = { [lockedOp]: ret }; } return ret; }; - const formatGroup = (item, config, meta, isRoot, parentField = null) => { const type = item.get("type"); const properties = item.get("properties") || new Map(); const mode = properties.get("mode"); const children = item.get("children1") || new List(); const field = properties.get("field"); - + let conjunction = properties.get("conjunction"); - if (!conjunction) - conjunction = defaultConjunction(config); + if (!conjunction) conjunction = defaultConjunction(config); const conjunctionDefinition = config.conjunctions[conjunction]; const conj = conjunctionDefinition.jsonLogicConj || conjunction.toLowerCase(); const not = properties.get("not"); @@ -94,32 +91,38 @@ const formatGroup = (item, config, meta, isRoot, parentField = null) => { return undefined; } - const isRuleGroup = (type === "rule_group" && !isRoot); + const isRuleGroup = type === "rule_group" && !isRoot; const groupField = isRuleGroup && mode != "struct" ? field : parentField; const groupOperator = properties.get("operator"); - const groupOperatorDefinition = groupOperator && getOperatorConfig(config, groupOperator, field) || null; - const formattedValue = formatItemValue(config, properties, meta, groupOperator, parentField); - const isGroup0 = isRuleGroup && (!groupOperator || groupOperatorDefinition.cardinality == 0); + const groupOperatorDefinition = + (groupOperator && getOperatorConfig(config, groupOperator, field)) || null; + const formattedValue = formatItemValue( + config, + properties, + meta, + groupOperator, + parentField + ); + const isGroup0 = + isRuleGroup && (!groupOperator || groupOperatorDefinition.cardinality == 0); const list = children - .map((currentChild) => formatItem(currentChild, config, meta, false, groupField)) + .map((currentChild) => + formatItem(currentChild, config, meta, false, groupField) + ) .filter((currentChild) => typeof currentChild !== "undefined"); - + if (isRuleGroup && mode != "struct" && !isGroup0) { // "count" rule can have no "having" children, but should have number value - if (formattedValue == undefined) - return undefined; + if (formattedValue == undefined) return undefined; } else { - if (!list.size) - return undefined; + if (!list.size) return undefined; } let resultQuery = {}; - if (list.size == 1 && !isRoot) - resultQuery = list.first(); - else - resultQuery[conj] = list.toList().toJS(); - + if (list.size == 1 && !isRoot) resultQuery = list.first(); + else resultQuery[conj] = list.toList().toJS(); + // revert if (not) { resultQuery = { "!": resultQuery }; @@ -132,39 +135,35 @@ const formatGroup = (item, config, meta, isRoot, parentField = null) => { // config.settings.groupOperators const op = groupOperator || "some"; resultQuery = { - [op]: [ - formattedField, - resultQuery - ] + [op]: [formattedField, resultQuery], }; } else { // there is rule for count - const filter = !list.size + const filter = !list.size ? formattedField : { - "filter": [ - formattedField, - resultQuery - ] - }; + filter: [formattedField, resultQuery], + }; const count = { - "reduce": [ - filter, - { "+": [1, { var: "accumulator" }] }, - 0 - ] + reduce: [filter, { "+": [1, { var: "accumulator" }] }, 0], }; - resultQuery = formatLogic(config, properties, count, formattedValue, groupOperator); + resultQuery = formatLogic( + config, + properties, + count, + formattedValue, + groupOperator + ); } } - + return resultQuery; }; - const formatRule = (item, config, meta, parentField = null) => { const properties = item.get("properties") || new Map(); const field = properties.get("field"); + const fieldSrc = properties.get("fieldSrc"); let operator = properties.get("operator"); let operatorOptions = properties.get("operatorOptions"); @@ -172,13 +171,14 @@ const formatRule = (item, config, meta, parentField = null) => { if (operatorOptions && !Object.keys(operatorOptions).length) operatorOptions = null; - if (field == null || operator == null) - return undefined; + if (field == null || operator == null) return undefined; - const fieldDefinition = getFieldConfig(config, field) || {}; - let operatorDefinition = getOperatorConfig(config, operator, field) || {}; + const fieldDefinition = getFieldConfig(config, field, fieldSrc) || {}; + let operatorDefinition = + getOperatorConfig(config, operator, field, fieldSrc) || {}; let reversedOp = operatorDefinition.reversedOp; - let revOperatorDefinition = getOperatorConfig(config, reversedOp, field) || {}; + let revOperatorDefinition = + getOperatorConfig(config, reversedOp, field, fieldSrc) || {}; // check op let isRev = false; @@ -189,31 +189,58 @@ const formatRule = (item, config, meta, parentField = null) => { if (!operatorDefinition.jsonLogic && revOperatorDefinition.jsonLogic) { isRev = true; [operator, reversedOp] = [reversedOp, operator]; - [operatorDefinition, revOperatorDefinition] = [revOperatorDefinition, operatorDefinition]; + [operatorDefinition, revOperatorDefinition] = [ + revOperatorDefinition, + operatorDefinition, + ]; } - const formattedValue = formatItemValue(config, properties, meta, operator, parentField); - if (formattedValue === undefined) - return undefined; - - const formattedField = formatField(meta, config, field, parentField); - - return formatLogic(config, properties, formattedField, formattedValue, operator, operatorOptions, fieldDefinition, isRev); + const formattedValue = formatItemValue( + config, + properties, + meta, + operator, + parentField, + fieldSrc + ); + if (formattedValue === undefined) return undefined; + + const formattedField = + fieldSrc === "func" + ? formatFunc(meta, config, field, parentField) + : formatField(meta, config, field, parentField); + + return formatLogic( + config, + properties, + formattedField, + formattedValue, + operator, + operatorOptions, + fieldDefinition, + isRev + ); }; - -const formatItemValue = (config, properties, meta, operator, parentField) => { +const formatItemValue = ( + config, + properties, + meta, + operator, + parentField, + fieldSrc +) => { const field = properties.get("field"); const iValueSrc = properties.get("valueSrc"); const iValueType = properties.get("valueType"); - const fieldDefinition = getFieldConfig(config, field) || {}; - const operatorDefinition = getOperatorConfig(config, operator, field) || {}; + const fieldDefinition = getFieldConfig(config, field, fieldSrc) || {}; + const operatorDefinition = + getOperatorConfig(config, operator, field, fieldSrc) || {}; const cardinality = defaultValue(operatorDefinition.cardinality, 1); const iValue = properties.get("value"); const asyncListValues = properties.get("asyncListValues"); - if (iValue == undefined) - return undefined; - + if (iValue == undefined) return undefined; + let valueSrcs = []; let valueTypes = []; let oldUsedFields = meta.usedFields; @@ -221,10 +248,29 @@ const formatItemValue = (config, properties, meta, operator, parentField) => { const valueSrc = iValueSrc ? iValueSrc.get(ind) : null; const valueType = iValueType ? iValueType.get(ind) : null; const cValue = completeValue(currentValue, valueSrc, config); - const widget = getWidgetForFieldOp(config, field, operator, valueSrc); - const fieldWidgetDef = omit(getFieldWidgetConfig(config, field, operator, widget, valueSrc), ["factory"]); + const widget = getWidgetForFieldOp( + config, + field, + operator, + valueSrc, + fieldSrc + ); + const fieldWidgetDef = omit( + getFieldWidgetConfig(config, field, operator, widget, valueSrc, fieldSrc), + ["factory"] + ); const fv = formatValue( - meta, config, cValue, valueSrc, valueType, fieldWidgetDef, fieldDefinition, operator, operatorDefinition, parentField, asyncListValues + meta, + config, + cValue, + valueSrc, + valueType, + fieldWidgetDef, + fieldDefinition, + operator, + operatorDefinition, + parentField, + asyncListValues ); if (fv !== undefined) { valueSrcs.push(valueSrc); @@ -232,18 +278,32 @@ const formatItemValue = (config, properties, meta, operator, parentField) => { } return fv; }); - const hasUndefinedValues = fvalue.filter(v => v === undefined).size > 0; + const hasUndefinedValues = fvalue.filter((v) => v === undefined).size > 0; if (fvalue.size < cardinality || hasUndefinedValues) { meta.usedFields = oldUsedFields; // restore return undefined; } - return cardinality > 1 ? fvalue.toArray() : (cardinality == 1 ? fvalue.first() : null); + return cardinality > 1 + ? fvalue.toArray() + : cardinality == 1 + ? fvalue.first() + : null; }; - -const formatValue = (meta, config, currentValue, valueSrc, valueType, fieldWidgetDef, fieldDef, operator, operatorDef, parentField = null, asyncListValues) => { - if (currentValue === undefined) - return undefined; +const formatValue = ( + meta, + config, + currentValue, + valueSrc, + valueType, + fieldWidgetDef, + fieldDef, + operator, + operatorDef, + parentField = null, + asyncListValues +) => { + if (currentValue === undefined) return undefined; let ret; if (valueSrc == "field") { ret = formatField(meta, config, currentValue, parentField); @@ -255,10 +315,17 @@ const formatValue = (meta, config, currentValue, valueSrc, valueType, fieldWidge currentValue, { ...pick(fieldDef, ["fieldSettings", "listValues"]), - asyncListValues + asyncListValues, }, //useful options: valueFormat for date/time - omit(fieldWidgetDef, ["formatValue", "mongoFormatValue", "sqlFormatValue", "jsonLogic", "elasticSearchFormatValue", "spelFormatValue"]), + omit(fieldWidgetDef, [ + "formatValue", + "mongoFormatValue", + "sqlFormatValue", + "jsonLogic", + "elasticSearchFormatValue", + "spelFormatValue", + ]), ]; if (operator) { args.push(operator); @@ -271,7 +338,6 @@ const formatValue = (meta, config, currentValue, valueSrc, valueType, fieldWidge return ret; }; - const formatFunc = (meta, config, currentValue, parentField = null) => { const funcKey = currentValue.get("func"); const args = currentValue.get("args"); @@ -289,13 +355,25 @@ const formatFunc = (meta, config, currentValue, parentField = null) => { const argValue = argVal ? argVal.get("value") : undefined; const argValueSrc = argVal ? argVal.get("valueSrc") : undefined; const formattedArgVal = formatValue( - meta, config, argValue, argValueSrc, argConfig.type, fieldDef, argConfig, null, null, parentField + meta, + config, + argValue, + argValueSrc, + argConfig.type, + fieldDef, + argConfig, + null, + null, + parentField ); if (argValue != undefined && formattedArgVal === undefined) { - meta.errors.push(`Can't format value of arg ${argKey} for func ${funcKey}`); + meta.errors.push( + `Can't format value of arg ${argKey} for func ${funcKey}` + ); return undefined; } - if (formattedArgVal !== undefined) { // skip optional in the end + if (formattedArgVal !== undefined) { + // skip optional in the end formattedArgs[argKey] = formattedArgVal; } } @@ -304,9 +382,7 @@ const formatFunc = (meta, config, currentValue, parentField = null) => { let ret; if (typeof funcConfig.jsonLogic === "function") { const fn = funcConfig.jsonLogic; - const args = [ - formattedArgs, - ]; + const args = [formattedArgs]; ret = fn(...args); } else { const funcName = funcConfig.jsonLogic || funcKey; @@ -314,9 +390,9 @@ const formatFunc = (meta, config, currentValue, parentField = null) => { if (isMethod) { const [obj, ...params] = formattedArgsArr; if (params.length) { - ret = { "method": [ obj, funcName, params ] }; + ret = { method: [obj, funcName, params] }; } else { - ret = { "method": [ obj, funcName ] }; + ret = { method: [obj, funcName] }; } } else { ret = { [funcName]: formattedArgsArr }; @@ -325,14 +401,12 @@ const formatFunc = (meta, config, currentValue, parentField = null) => { return ret; }; - const formatField = (meta, config, field, parentField = null) => { - const {fieldSeparator, jsonLogic} = config.settings; + const { fieldSeparator, jsonLogic } = config.settings; let ret; if (field) { - if (Array.isArray(field)) - field = field.join(fieldSeparator); + if (Array.isArray(field)) field = field.join(fieldSeparator); const fieldDef = getFieldConfig(config, field) || {}; let fieldName = field; if (fieldDef.fieldName) { @@ -347,21 +421,32 @@ const formatField = (meta, config, field, parentField = null) => { if (fieldName.indexOf(parentFieldName + fieldSeparator) == 0) { fieldName = fieldName.slice((parentFieldName + fieldSeparator).length); } else { - meta.errors.push(`Can't cut group ${parentFieldName} from field ${fieldName}`); + meta.errors.push( + `Can't cut group ${parentFieldName} from field ${fieldName}` + ); } } - let varName = fieldDef.jsonLogicVar || (fieldDef.type == "!group" ? jsonLogic.groupVarKey : "var"); - ret = { [varName] : fieldName }; - if (meta.usedFields.indexOf(field) == -1) - meta.usedFields.push(field); + let varName = + fieldDef.jsonLogicVar || + (fieldDef.type == "!group" ? jsonLogic.groupVarKey : "var"); + ret = { [varName]: fieldName }; + if (meta.usedFields.indexOf(field) == -1) meta.usedFields.push(field); } return ret; }; -const buildFnToFormatOp = (operator, operatorDefinition, formattedField, formattedValue) => { +const buildFnToFormatOp = ( + operator, + operatorDefinition, + formattedField, + formattedValue +) => { let formatteOp = operator; const cardinality = defaultValue(operatorDefinition.cardinality, 1); - const isReverseArgs = defaultValue(operatorDefinition._jsonLogicIsRevArgs, false); + const isReverseArgs = defaultValue( + operatorDefinition._jsonLogicIsRevArgs, + false + ); if (typeof operatorDefinition.jsonLogic == "string") formatteOp = operatorDefinition.jsonLogic; const rangeOps = ["<", "<=", ">", ">="]; @@ -369,31 +454,54 @@ const buildFnToFormatOp = (operator, operatorDefinition, formattedField, formatt const fn = (field, op, val, opDef, opOpts) => { if (cardinality == 0 && eqOps.includes(formatteOp)) return { [formatteOp]: [formattedField, null] }; - else if (cardinality == 0) - return { [formatteOp]: formattedField }; + else if (cardinality == 0) return { [formatteOp]: formattedField }; else if (cardinality == 1 && isReverseArgs) return { [formatteOp]: [formattedValue, formattedField] }; else if (cardinality == 1) return { [formatteOp]: [formattedField, formattedValue] }; else if (cardinality == 2 && rangeOps.includes(formatteOp)) - return { [formatteOp]: [formattedValue[0], formattedField, formattedValue[1]] }; - else - return { [formatteOp]: [formattedField, ...formattedValue] }; + return { + [formatteOp]: [formattedValue[0], formattedField, formattedValue[1]], + }; + else return { [formatteOp]: [formattedField, ...formattedValue] }; }; return fn; }; -const formatLogic = (config, properties, formattedField, formattedValue, operator, operatorOptions = null, fieldDefinition = null, isRev = false) => { +const formatLogic = ( + config, + properties, + formattedField, + formattedValue, + operator, + operatorOptions = null, + fieldDefinition = null, + isRev = false +) => { const field = properties.get("field"); - const operatorDefinition = getOperatorConfig(config, operator, field) || {}; - let fn = typeof operatorDefinition.jsonLogic == "function" - ? operatorDefinition.jsonLogic - : buildFnToFormatOp(operator, operatorDefinition, formattedField, formattedValue); + const fieldSrc = properties.get("fieldSrc"); + const operatorDefinition = + getOperatorConfig(config, operator, field, fieldSrc) || {}; + let fn = + typeof operatorDefinition.jsonLogic == "function" + ? operatorDefinition.jsonLogic + : buildFnToFormatOp( + operator, + operatorDefinition, + formattedField, + formattedValue + ); const args = [ formattedField, operator, formattedValue, - omit(operatorDefinition, ["formatOp", "mongoFormatOp", "sqlFormatOp", "jsonLogic", "spelFormatOp"]), + omit(operatorDefinition, [ + "formatOp", + "mongoFormatOp", + "sqlFormatOp", + "jsonLogic", + "spelFormatOp", + ]), operatorOptions, fieldDefinition, ]; diff --git a/packages/core/modules/import/jsonLogic.js b/packages/core/modules/import/jsonLogic.js index 6fe64290d..2d6b1fc86 100644 --- a/packages/core/modules/import/jsonLogic.js +++ b/packages/core/modules/import/jsonLogic.js @@ -1,9 +1,23 @@ import uuid from "../utils/uuid"; -import {defaultValue, isJsonLogic, shallowEqual, logger} from "../utils/stuff"; -import {getFieldConfig, extendConfig, normalizeField} from "../utils/configUtils"; -import {getWidgetForFieldOp} from "../utils/ruleUtils"; -import {loadTree} from "./tree"; -import {defaultConjunction, defaultGroupConjunction} from "../utils/defaultUtils"; +import { + defaultValue, + isJsonLogic, + shallowEqual, + logger, +} from "../utils/stuff"; +import { + getFieldConfig, + getFuncConfig, + extendConfig, + normalizeField, +} from "../utils/configUtils"; +import { getWidgetForFieldOp } from "../utils/ruleUtils"; +import { setFunc } from "../utils/funcUtils"; +import { loadTree } from "./tree"; +import { + defaultConjunction, + defaultGroupConjunction, +} from "../utils/defaultUtils"; import moment from "moment"; @@ -11,7 +25,8 @@ import moment from "moment"; // helpers const arrayUniq = (arr) => Array.from(new Set(arr)); -const arrayToObject = (arr) => arr.reduce((acc, [f, fc]) => ({ ...acc, [f]: fc }), {}); +const arrayToObject = (arr) => + arr.reduce((acc, [f, fc]) => ({ ...acc, [f]: fc }), {}); export const loadFromJsonLogic = (logicTree, config) => { return _loadFromJsonLogic(logicTree, config, false); @@ -20,11 +35,13 @@ export const loadFromJsonLogic = (logicTree, config) => { export const _loadFromJsonLogic = (logicTree, config, returnErrors = true) => { //meta is mutable let meta = { - errors: [] + errors: [], }; const extendedConfig = extendConfig(config); const conv = buildConv(extendedConfig); - let jsTree = logicTree ? convertFromLogic(logicTree, conv, extendedConfig, "rule", meta) : undefined; + let jsTree = logicTree + ? convertFromLogic(logicTree, conv, extendedConfig, "rule", meta) + : undefined; if (jsTree && jsTree.type != "group") { jsTree = wrapInDefaultConj(jsTree, extendedConfig); } @@ -39,22 +56,24 @@ export const _loadFromJsonLogic = (logicTree, config, returnErrors = true) => { } }; - const buildConv = (config) => { let operators = {}; for (let opKey in config.operators) { const opConfig = config.operators[opKey]; if (typeof opConfig.jsonLogic == "string") { // example: " { fk = funcConfig.jsonLogic; } if (fk) { - if (!funcs[fk]) - funcs[fk] = []; + if (!funcs[fk]) funcs[fk] = []; funcs[fk].push(funcKey); } } - const {groupVarKey, altVarKey} = config.settings.jsonLogic; + const { groupVarKey, altVarKey } = config.settings.jsonLogic; return { operators, @@ -92,36 +110,130 @@ const buildConv = (config) => { }; }; +const convertFuncFromLogic = ( + op, + vals, + field, + conv, + config, + meta, + parentField +) => { + field = setFunc(null, conv.funcs[op][0], config); + Object.entries(config.funcs[conv.funcs[op]].args).forEach((entry, idx) => { + const subK = Object.keys(vals[idx])[0]; + const subV = Object.values(vals[idx])[0]; + if ( + Object.keys(conv.funcs).includes(subK) && + typeof Object.values(subV) === "object" && + Array.isArray(Object.values(subV)) + ) { + const argVal = convertFuncFromLogic( + subK, + subV, + null, + conv, + config, + meta, + parentField + ); + field = field.setIn(["args", entry[0], "valueSrc"], "func"); + field = field.setIn(["args", entry[0], "value"], argVal); + } else { + let argVal = convertFromLogic( + vals[idx], + conv, + config, + "val", + meta, + false, + entry[1], + null, + parentField + ); + field = field.setIn(["args", entry[0], "valueSrc"], argVal.valueSrc); + field = field.setIn(["args", entry[0], "value"], argVal.value); + } + }); + return field; +}; -const convertFromLogic = (logic, conv, config, expectedType, meta, not = false, fieldConfig, widget, parentField = null, _isLockedLogic = false) => { +const convertFromLogic = ( + logic, + conv, + config, + expectedType, + meta, + not = false, + fieldConfig, + widget, + parentField = null, + _isLockedLogic = false +) => { let op, vals; if (isJsonLogic(logic)) { op = Object.keys(logic)[0]; vals = logic[op]; - if (!Array.isArray(vals)) - vals = [ vals ]; + if (!Array.isArray(vals)) vals = [vals]; } - + let ret; let beforeErrorsCnt = meta.errors.length; - const {lockedOp} = config.settings.jsonLogic; - const isEmptyOp = op == "!" && (vals.length == 1 && vals[0] && isJsonLogic(vals[0]) && conv.varKeys.includes(Object.keys(vals[0])[0])); + const { lockedOp } = config.settings.jsonLogic; + const isEmptyOp = + op == "!" && + vals.length == 1 && + vals[0] && + isJsonLogic(vals[0]) && + conv.varKeys.includes(Object.keys(vals[0])[0]); const isRev = op == "!" && !isEmptyOp; const isLocked = lockedOp && op == lockedOp; if (isLocked) { - ret = convertFromLogic(vals[0], conv, config, expectedType, meta, not, fieldConfig, widget, parentField, true); + ret = convertFromLogic( + vals[0], + conv, + config, + expectedType, + meta, + not, + fieldConfig, + widget, + parentField, + true + ); } else if (isRev) { // reverse with not - ret = convertFromLogic(vals[0], conv, config, expectedType, meta, !not, fieldConfig, widget, parentField); - } else if(expectedType == "val") { + ret = convertFromLogic( + vals[0], + conv, + config, + expectedType, + meta, + !not, + fieldConfig, + widget, + parentField + ); + } else if (expectedType == "val") { // not is not used here - ret = convertField(op, vals, conv, config, not, meta, parentField) - || convertFunc(op, vals, conv, config, not, fieldConfig, meta, parentField) - || convertVal(logic, fieldConfig, widget, config, meta); - } else if(expectedType == "rule") { - ret = convertConj(op, vals, conv, config, not, meta, parentField, false) - || convertOp(op, vals, conv, config, not, meta, parentField); + ret = + convertField(op, vals, conv, config, not, meta, parentField) || + convertFunc( + op, + vals, + conv, + config, + not, + fieldConfig, + meta, + parentField + ) || + convertVal(logic, fieldConfig, widget, config, meta); + } else if (expectedType == "rule") { + ret = + convertConj(op, vals, conv, config, not, meta, parentField, false) || + convertOp(op, vals, conv, config, not, meta, parentField); } let afterErrorsCnt = meta.errors.length; @@ -136,7 +248,6 @@ const convertFromLogic = (logic, conv, config, expectedType, meta, not = false, return ret; }; - const convertVal = (val, fieldConfig, widget, config, meta) => { if (val === undefined) return undefined; const widgetConfig = config.widgets[widget || fieldConfig.mainWidget]; @@ -153,7 +264,11 @@ const convertVal = (val, fieldConfig, widget, config, meta) => { // number of seconds -> time string if (fieldConfig && fieldConfig.type == "time" && typeof val == "number") { - const [h, m, s] = [Math.floor(val / 60 / 60) % 24, Math.floor(val / 60) % 60, val % 60]; + const [h, m, s] = [ + Math.floor(val / 60 / 60) % 24, + Math.floor(val / 60) % 60, + val % 60, + ]; const valueFormat = widgetConfig.valueFormat; if (valueFormat) { const dateVal = new Date(val); @@ -168,13 +283,18 @@ const convertVal = (val, fieldConfig, widget, config, meta) => { } // "2020-01-08T22:00:00.000Z" -> Date object - if (fieldConfig && ["date", "datetime"].includes(fieldConfig.type) && val && !(val instanceof Date)) { + if ( + fieldConfig && + ["date", "datetime"].includes(fieldConfig.type) && + val && + !(val instanceof Date) + ) { try { const dateVal = new Date(val); if (dateVal instanceof Date && dateVal.toISOString() === val) { val = dateVal; } - } catch(e) { + } catch (e) { meta.errors.push(`Can't convert value ${val} as Date`); val = undefined; } @@ -189,7 +309,11 @@ const convertVal = (val, fieldConfig, widget, config, meta) => { } let asyncListValues; - if (val && fieldConfig.fieldSettings && fieldConfig.fieldSettings.asyncFetch) { + if ( + val && + fieldConfig.fieldSettings && + fieldConfig.fieldSettings.asyncFetch + ) { const vals = Array.isArray(val) ? val : [val]; asyncListValues = vals; } @@ -198,17 +322,23 @@ const convertVal = (val, fieldConfig, widget, config, meta) => { valueSrc: "value", value: val, valueType: widgetConfig.type, - asyncListValues + asyncListValues, }; }; - -const convertField = (op, vals, conv, config, not, meta, parentField = null) => { - const {fieldSeparator} = config.settings; +const convertField = ( + op, + vals, + conv, + config, + not, + meta, + parentField = null +) => { + const { fieldSeparator } = config.settings; if (conv.varKeys.includes(op) && typeof vals[0] == "string") { let field = vals[0]; - if (parentField) - field = [parentField, field].join(fieldSeparator); + if (parentField) field = [parentField, field].join(fieldSeparator); field = normalizeField(config, field); const fieldConfig = getFieldConfig(config, field); if (!fieldConfig) { @@ -226,11 +356,19 @@ const convertField = (op, vals, conv, config, not, meta, parentField = null) => return undefined; }; - -const convertFunc = (op, vals, conv, config, not, fieldConfig, meta, parentField = null) => { +const convertFunc = ( + op, + vals, + conv, + config, + not, + fieldConfig, + meta, + parentField = null +) => { if (!op) return undefined; let func, argsArr, funcKey; - const jsonLogicIsMethod = (op == "method"); + const jsonLogicIsMethod = op == "method"; if (jsonLogicIsMethod) { let obj, opts; [obj, func, ...opts] = vals; @@ -241,19 +379,25 @@ const convertFunc = (op, vals, conv, config, not, fieldConfig, meta, parentField } const fk = (jsonLogicIsMethod ? "#" : "") + func; - const funcKeys = (conv.funcs[fk] || []).filter(k => - (fieldConfig ? config.funcs[k].returnType == fieldConfig.type : true) + const funcKeys = (conv.funcs[fk] || []).filter((k) => + fieldConfig + ? config.funcs[k].returnType == + (fieldConfig.type ?? fieldConfig.returnType) + : true ); if (funcKeys.length) { funcKey = funcKeys[0]; } else { - const v = {[op]: vals}; + const v = { [op]: vals }; for (const [f, fc] of Object.entries(config.funcs || {})) { - if (fc.jsonLogicImport && fc.returnType == fieldConfig.type) { + if ( + fc.jsonLogicImport && + fc.returnType == (fieldConfig.type ?? fieldConfig.returnType) + ) { let parsed; try { parsed = fc.jsonLogicImport(v); - } catch(_e) { + } catch (_e) { // given expression `v` can't be parsed into function } if (parsed) { @@ -263,8 +407,7 @@ const convertFunc = (op, vals, conv, config, not, fieldConfig, meta, parentField } } } - if (!funcKey) - return undefined; + if (!funcKey) return undefined; if (funcKey) { const funcConfig = config.funcs[funcKey]; @@ -272,7 +415,17 @@ const convertFunc = (op, vals, conv, config, not, fieldConfig, meta, parentField let args = argsArr.reduce((acc, val, ind) => { const argKey = argKeys[ind]; const argConfig = funcConfig.args[argKey]; - let argVal = convertFromLogic(val, conv, config, "val", meta, false, argConfig, null, parentField); + let argVal = convertFromLogic( + val, + conv, + config, + "val", + meta, + false, + argConfig, + null, + parentField + ); if (argVal === undefined) { argVal = argConfig.defaultValue; if (argVal === undefined) { @@ -280,14 +433,14 @@ const convertFunc = (op, vals, conv, config, not, fieldConfig, meta, parentField return undefined; } } - return {...acc, [argKey]: argVal}; + return { ...acc, [argKey]: argVal }; }, {}); return { valueSrc: "func", value: { func: funcKey, - args: args + args: args, }, valueType: funcConfig.returnType, }; @@ -296,43 +449,68 @@ const convertFunc = (op, vals, conv, config, not, fieldConfig, meta, parentField return undefined; }; - -const convertConj = (op, vals, conv, config, not, meta, parentField = null, isRuleGroup = false) => { +const convertConj = ( + op, + vals, + conv, + config, + not, + meta, + parentField = null, + isRuleGroup = false +) => { const conjKey = conv.conjunctions[op]; - const {fieldSeparator} = config.settings; - const parentFieldConfig = parentField ? getFieldConfig(config, parentField) : null; + const { fieldSeparator } = config.settings; + const parentFieldConfig = parentField + ? getFieldConfig(config, parentField) + : null; const isParentGroup = parentFieldConfig?.type == "!group"; if (conjKey) { let type = "group"; const children = vals - .map(v => convertFromLogic(v, conv, config, "rule", meta, false, null, null, parentField)) - .filter(r => r !== undefined) - .reduce((acc, r) => ({...acc, [r.id] : r}), {}); + .map((v) => + convertFromLogic( + v, + conv, + config, + "rule", + meta, + false, + null, + null, + parentField + ) + ) + .filter((r) => r !== undefined) + .reduce((acc, r) => ({ ...acc, [r.id]: r }), {}); const complexFields = Object.values(children) - .map(v => v?.properties?.field) - .filter(f => f && f.includes(fieldSeparator)); + .map((v) => v?.properties?.field) + .filter((f) => f && f.includes(fieldSeparator)); const complexFieldsGroupAncestors = Object.fromEntries( - arrayUniq(complexFields).map(f => { + arrayUniq(complexFields).map((f) => { const parts = f.split(fieldSeparator); const ancs = Object.fromEntries( - parts.slice(0, -1) + parts + .slice(0, -1) .map((f, i, parts) => [...parts.slice(0, i), f]) - .map(fp => [fp.join(fieldSeparator), getFieldConfig(config, fp)]) + .map((fp) => [fp.join(fieldSeparator), getFieldConfig(config, fp)]) .filter(([_f, fc]) => fc.type == "!group") ); return [f, Object.keys(ancs)]; }) ); const childrenInRuleGroup = Object.values(children) - .map(v => v?.properties?.field) - .map(f => complexFieldsGroupAncestors[f]) - .filter(ancs => ancs && ancs.length); - const usedRuleGroups = arrayUniq(Object.values(complexFieldsGroupAncestors).flat()); + .map((v) => v?.properties?.field) + .map((f) => complexFieldsGroupAncestors[f]) + .filter((ancs) => ancs && ancs.length); + const usedRuleGroups = arrayUniq( + Object.values(complexFieldsGroupAncestors).flat() + ); const usedTopRuleGroups = topLevelFieldsFilter(usedRuleGroups); - + let properties = { conjunction: conjKey, - not: not + not: not, }; const id = uuid(); @@ -352,18 +530,26 @@ const convertConj = (op, vals, conv, config, not, meta, parentField = null, isRu } else { // wrap field in rule_group (with creating hierarchy if need) let ch = children1; - let parentFieldParts = parentField ? parentField.split(fieldSeparator) : []; - const isInParent = shallowEqual(parentFieldParts, groupField.split(fieldSeparator).slice(0, parentFieldParts.length)); - if (!isInParent) - parentFieldParts = []; // should not be + let parentFieldParts = parentField + ? parentField.split(fieldSeparator) + : []; + const isInParent = shallowEqual( + parentFieldParts, + groupField.split(fieldSeparator).slice(0, parentFieldParts.length) + ); + if (!isInParent) parentFieldParts = []; // should not be const traverseGroupFields = groupField .split(fieldSeparator) .slice(parentFieldParts.length) - .map((f, i, parts) => [...parentFieldParts, ...parts.slice(0, i), f].join(fieldSeparator)) - .map(f => normalizeField(config, f)) - .map((f) => ({f, fc: getFieldConfig(config, f) || {}})) - .filter(({fc}) => (fc.type != "!struct")); - traverseGroupFields.map(({f: gf, fc: gfc}, i) => { + .map((f, i, parts) => + [...parentFieldParts, ...parts.slice(0, i), f].join( + fieldSeparator + ) + ) + .map((f) => normalizeField(config, f)) + .map((f) => ({ f, fc: getFieldConfig(config, f) || {} })) + .filter(({ fc }) => fc.type != "!struct"); + traverseGroupFields.map(({ f: gf, fc: gfc }, i) => { let groupId = groupToId[gf]; if (!groupId) { groupId = uuid(); @@ -376,8 +562,9 @@ const convertConj = (op, vals, conv, config, not, meta, parentField = null, isRu conjunction: conjKey, not: false, field: gf, + fieldSrc: gf, mode: gfc.mode, - } + }, }; } ch = ch[groupId].children1; @@ -393,18 +580,17 @@ const convertConj = (op, vals, conv, config, not, meta, parentField = null, isRu type: type, id: id, children1: children1, - properties: properties + properties: properties, }; } return undefined; }; - const topLevelFieldsFilter = (fields) => { - let arr = [...fields].sort((a, b) => (a.length - b.length)); - for (let i = 0 ; i < arr.length ; i++) { - for (let j = i + 1 ; j < arr.length ; j++) { + let arr = [...fields].sort((a, b) => a.length - b.length); + for (let i = 0; i < arr.length; i++) { + for (let j = i + 1; j < arr.length; j++) { if (arr[j].indexOf(arr[i]) == 0) { // arr[j] is inside arr[i] (eg. "a.b" inside "a") arr.splice(j, 1); @@ -415,7 +601,13 @@ const topLevelFieldsFilter = (fields) => { return arr; }; -const wrapInDefaultConjRuleGroup = (rule, parentField, parentFieldConfig, config, conj) => { +const wrapInDefaultConjRuleGroup = ( + rule, + parentField, + parentFieldConfig, + config, + conj +) => { if (!rule) return undefined; return { type: "rule_group", @@ -425,7 +617,7 @@ const wrapInDefaultConjRuleGroup = (rule, parentField, parentFieldConfig, config conjunction: conj || defaultGroupConjunction(config, parentFieldConfig), not: false, field: parentField, - } + }, }; }; @@ -436,50 +628,73 @@ const wrapInDefaultConj = (rule, config, not = false) => { children1: { [rule.id]: rule }, properties: { conjunction: defaultConjunction(config), - not: not - } + not: not, + }, }; }; const parseRule = (op, arity, vals, parentField, conv, config, meta) => { let errors = []; - let res = _parseRule(op, arity, vals, parentField, conv, config, errors, false) - || _parseRule(op, arity, vals, parentField, conv, config, errors, true) ; - + let res = + _parseRule( + op, + arity, + vals, + parentField, + conv, + config, + errors, + false, + meta + ) || + _parseRule(op, arity, vals, parentField, conv, config, errors, true, meta); if (!res) { meta.errors.push(errors.join("; ") || `Unknown op ${op}/${arity}`); return undefined; } - + return res; }; -const _parseRule = (op, arity, vals, parentField, conv, config, errors, isRevArgs) => { +const _parseRule = ( + op, + arity, + vals, + parentField, + conv, + config, + errors, + isRevArgs, + meta +) => { // config.settings.groupOperators are used for group count (cardinality = 0 is exception) // but don't confuse with "all-in" or "some-in" for multiselect - const isAllOrSomeInForMultiselect = (op == "all" || op == "some") && isJsonLogic(vals[1]) && Object.keys(vals[1])[0] == "in"; - const isGroup0 = !isAllOrSomeInForMultiselect && config.settings.groupOperators.includes(op); + const isAllOrSomeInForMultiselect = + (op == "all" || op == "some") && + isJsonLogic(vals[1]) && + Object.keys(vals[1])[0] == "in"; + const isGroup0 = + !isAllOrSomeInForMultiselect && config.settings.groupOperators.includes(op); const eqOps = ["==", "!="]; let cardinality = isGroup0 ? 0 : arity - 1; - if (isGroup0) - cardinality = 0; + if (isGroup0) cardinality = 0; else if (eqOps.includes(op) && cardinality == 1 && vals[1] === null) cardinality = 0; const opk = op + "/" + cardinality; - const {fieldSeparator} = config.settings; + const { fieldSeparator, fieldSources } = config.settings; let opKeys = conv.operators[(isRevArgs ? "#" : "") + opk]; - if (!opKeys) - return; - - let jlField, args = []; + if (!opKeys) return; + + let jlField, + args = []; const rangeOps = ["<", "<=", ">", ">="]; if (rangeOps.includes(op) && arity == 3) { jlField = vals[1]; - args = [ vals[0], vals[2] ]; + args = [vals[0], vals[2]]; } else if (isRevArgs) { jlField = vals[1]; - args = [ vals[0] ]; + args = [vals[0]]; } else { [jlField, ...args] = vals; } @@ -490,10 +705,18 @@ const _parseRule = (op, arity, vals, parentField, conv, config, errors, isRevArg } let k = Object.keys(jlField)[0]; let v = Object.values(jlField)[0]; - - let field, having, isGroup; + + let field, fieldSrc, having, isGroup; if (conv.varKeys.includes(k) && typeof v == "string") { field = v; + fieldSrc = "field"; + } else if ( + Object.keys(conv.funcs).includes(k) && + typeof v === "object" && + Array.isArray(v) + ) { + fieldSrc = "func"; + field = convertFuncFromLogic(k, v, field, conv, config, meta, parentField); } if (isGroup0) { isGroup = true; @@ -503,7 +726,15 @@ const _parseRule = (op, arity, vals, parentField, conv, config, errors, isRevArg // reduce/filter for group ext if (k == "reduce" && Array.isArray(v) && v.length == 3) { let [filter, acc, init] = v; - if (isJsonLogic(filter) && init == 0 && isJsonLogic(acc) && Array.isArray(acc["+"]) && acc["+"][0] == 1 && isJsonLogic(acc["+"][1]) && acc["+"][1]["var"] == "accumulator") { + if ( + isJsonLogic(filter) && + init == 0 && + isJsonLogic(acc) && + Array.isArray(acc["+"]) && + acc["+"][0] == 1 && + isJsonLogic(acc["+"][1]) && + acc["+"][1]["var"] == "accumulator" + ) { k = Object.keys(filter)[0]; v = Object.values(filter)[0]; if (k == "filter") { @@ -515,24 +746,37 @@ const _parseRule = (op, arity, vals, parentField, conv, config, errors, isRevArg field = v; having = filter; isGroup = true; + } else if ( + Object.keys(conv.funcs).includes(k) && + typeof v === "object" && + Array.isArray(v) + ) { + field = v; + having = filter; + isGroup = true; } } } else if (conv.varKeys.includes(k) && typeof v == "string") { field = v; isGroup = true; + } else if ( + Object.keys(conv.funcs).includes(k) && + typeof v === "object" && + Array.isArray(v) + ) { + field = v; + isGroup = true; } } } - + if (!field) { errors.push(`Unknown field ${JSON.stringify(jlField)}`); return; } - if (parentField) - field = [parentField, field].join(fieldSeparator); + if (parentField) field = [parentField, field].join(fieldSeparator); field = normalizeField(config, field); - - const fieldConfig = getFieldConfig(config, field); + const fieldConfig = getFieldConfig(config, field, fieldSrc); if (!fieldConfig) { errors.push(`No config for field ${field}`); return; @@ -541,17 +785,21 @@ const _parseRule = (op, arity, vals, parentField, conv, config, errors, isRevArg let opKey = opKeys[0]; if (opKeys.length > 1 && fieldConfig && fieldConfig.operators) { // eg. for "equal" and "select_equals" - opKeys = opKeys - .filter(k => fieldConfig.operators.includes(k)); + opKeys = opKeys.filter((k) => fieldConfig.operators.includes(k)); if (opKeys.length == 0) { errors.push(`No corresponding ops for field ${field}`); return; } opKey = opKeys[0]; } - + return { - field, fieldConfig, opKey, args, having + field, + fieldSrc, + fieldConfig, + opKey, + args, + having, }; }; @@ -563,22 +811,22 @@ const convertOp = (op, vals, conv, config, not, meta, parentField = null) => { // special case for "all-in" and "some-in" const op2 = Object.keys(vals[1])[0]; if (op2 == "in") { - vals = [ - vals[0], - vals[1][op2][1] - ]; + vals = [vals[0], vals[1][op2][1]]; op = op + "-" + op2; // "all-in" and "some-in" } } const parseRes = parseRule(op, arity, vals, parentField, conv, config, meta); if (!parseRes) return undefined; - let {field, fieldConfig, opKey, args, having} = parseRes; + let { field, fieldSrc, fieldConfig, opKey, args, having } = parseRes; let opConfig = config.operators[opKey]; // Group component in array mode can show NOT checkbox, so do nothing in this case // Otherwise try to revert - const showNot = fieldConfig.showNot !== undefined ? fieldConfig.showNot : config.settings.showNot; + const showNot = + fieldConfig.showNot !== undefined + ? fieldConfig.showNot + : config.settings.showNot; let canRev = true; // if (fieldConfig.type == "!group" && fieldConfig.mode == "array" && showNot) // canRev = false; @@ -589,19 +837,22 @@ const convertOp = (op, vals, conv, config, not, meta, parentField = null) => { if (fieldConfig.type == "!group" && having) { conj = Object.keys(having)[0]; havingVals = having[conj]; - if (!Array.isArray(havingVals)) - havingVals = [ havingVals ]; + if (!Array.isArray(havingVals)) havingVals = [havingVals]; // Preprocess "!": Try to reverse op in single rule in having // Eg. use `not_equal` instead of `not` `equal` - const isEmptyOp = conj == "!" && (havingVals.length == 1 && havingVals[0] && isJsonLogic(havingVals[0]) && conv.varKeys.includes(Object.keys(havingVals[0])[0])); + const isEmptyOp = + conj == "!" && + havingVals.length == 1 && + havingVals[0] && + isJsonLogic(havingVals[0]) && + conv.varKeys.includes(Object.keys(havingVals[0])[0]); if (conj == "!" && !isEmptyOp) { havingNot = true; having = having["!"]; conj = Object.keys(having)[0]; havingVals = having[conj]; - if (!Array.isArray(havingVals)) - havingVals = [ havingVals ]; + if (!Array.isArray(havingVals)) havingVals = [havingVals]; } } @@ -612,11 +863,23 @@ const convertOp = (op, vals, conv, config, not, meta, parentField = null) => { opConfig = config.operators[opKey]; } - const widget = getWidgetForFieldOp(config, field, opKey); + const widget = getWidgetForFieldOp(config, field, opKey, null, fieldSrc); - const convertedArgs = args - .map(v => convertFromLogic(v, conv, config, "val", meta, false, fieldConfig, widget, parentField)); - if (convertedArgs.filter(v => v === undefined).length) { + const convertedArgs = args.map((v) => { + const convertedArg = convertFromLogic( + v, + conv, + config, + "val", + meta, + false, + fieldConfig, + widget, + parentField + ); + return convertedArg; + }); + if (convertedArgs.filter((v) => v === undefined).length) { //meta.errors.push(`Undefined arg for field ${field} and op ${opKey}`); return undefined; } @@ -626,17 +889,39 @@ const convertOp = (op, vals, conv, config, not, meta, parentField = null) => { if (fieldConfig.type == "!group" && having) { if (conv.conjunctions[conj] !== undefined) { // group - res = convertConj(conj, havingVals, conv, config, havingNot, meta, field, true); + res = convertConj( + conj, + havingVals, + conv, + config, + havingNot, + meta, + field, + true + ); havingNot = false; } else { // need to be wrapped in `rule_group` - const rule = convertOp(conj, havingVals, conv, config, havingNot, meta, field); + const rule = convertOp( + conj, + havingVals, + conv, + config, + havingNot, + meta, + field + ); havingNot = false; - res = wrapInDefaultConjRuleGroup(rule, field, fieldConfig, config, conv.conjunctions["and"]); + res = wrapInDefaultConjRuleGroup( + rule, + field, + fieldConfig, + config, + conv.conjunctions["and"] + ); } - if (!res) - return undefined; - + if (!res) return undefined; + res.type = "rule_group"; Object.assign(res.properties, { field: field, @@ -645,9 +930,9 @@ const convertOp = (op, vals, conv, config, not, meta, parentField = null) => { }); if (fieldConfig.mode == "array") { Object.assign(res.properties, { - value: convertedArgs.map(v => v.value), - valueSrc: convertedArgs.map(v => v.valueSrc), - valueType: convertedArgs.map(v => v.valueType), + value: convertedArgs.map((v) => v.value), + valueSrc: convertedArgs.map((v) => v.valueSrc), + valueType: convertedArgs.map((v) => v.valueType), }); } if (not) { @@ -664,29 +949,34 @@ const convertOp = (op, vals, conv, config, not, meta, parentField = null) => { mode: fieldConfig.mode, field: field, operator: opKey, - } + }, }; if (fieldConfig.mode == "array") { Object.assign(res.properties, { - value: convertedArgs.map(v => v.value), - valueSrc: convertedArgs.map(v => v.valueSrc), - valueType: convertedArgs.map(v => v.valueType), + value: convertedArgs.map((v) => v.value), + valueSrc: convertedArgs.map((v) => v.valueSrc), + valueType: convertedArgs.map((v) => v.valueType), }); } } else { - const asyncListValuesArr = convertedArgs.map(v => v.asyncListValues).filter(v => v != undefined); - const asyncListValues = asyncListValuesArr.length ? asyncListValuesArr[0] : undefined; + const asyncListValuesArr = convertedArgs + .map((v) => v.asyncListValues) + .filter((v) => v != undefined); + const asyncListValues = asyncListValuesArr.length + ? asyncListValuesArr[0] + : undefined; res = { type: "rule", id: uuid(), properties: { field: field, + fieldSrc: fieldSrc, operator: opKey, - value: convertedArgs.map(v => v.value), - valueSrc: convertedArgs.map(v => v.valueSrc), - valueType: convertedArgs.map(v => v.valueType), + value: convertedArgs.map((v) => v.value), + valueSrc: convertedArgs.map((v) => v.valueSrc), + valueType: convertedArgs.map((v) => v.valueType), asyncListValues, - } + }, }; if (not) { //meta.errors.push(`No rev op for ${opKey}`); @@ -696,4 +986,3 @@ const convertOp = (op, vals, conv, config, not, meta, parentField = null) => { return res; }; - diff --git a/packages/core/modules/index.d.ts b/packages/core/modules/index.d.ts index c978c7cf7..417d3e18c 100644 --- a/packages/core/modules/index.d.ts +++ b/packages/core/modules/index.d.ts @@ -66,6 +66,7 @@ interface BasicItemProperties { interface RuleProperties extends BasicItemProperties { field: string | Empty, + fieldSrc?: ValueSource, operator: string | Empty, value: Array, valueSrc?: Array, @@ -267,6 +268,7 @@ export interface Actions { setLock(path: IdPath, lock: boolean): undefined; setConjunction(path: IdPath, conjunction: string): undefined; setField(path: IdPath, field: string): undefined; + setFieldSrc(path: IdPath, fieldSrc: ValueSource): undefined; setOperator(path: IdPath, operator: string): undefined; setValue(path: IdPath, delta: number, value: RuleValue, valueType: string): undefined; setValueSrc(path: IdPath, delta: number, valueSrc: ValueSource): undefined; @@ -300,6 +302,7 @@ export interface TreeActions { }, rule: { setField(config: Config, path: IdPath, field: string): InputAction; + setFieldSrc(config: Config, path: IdPath, fieldSrc: ValueSource): InputAction; setOperator(config: Config, path: IdPath, operator: string): InputAction; setValue(config: Config, path: IdPath, delta: number, value: RuleValue, valueType: string): InputAction; setValueSrc(config: Config, path: IdPath, delta: number, valueSrc: ValueSource): InputAction; @@ -318,6 +321,7 @@ interface BaseWidgetProps { setValue(val: RuleValue, asyncListValues?: Array): void, placeholder: string, field: string, + fieldSrc: string, parentField?: string, operator: string, fieldDefinition: Field, @@ -362,6 +366,7 @@ type FieldItems = FieldItem[]; export interface FieldProps { items: FieldItems, + selectedFieldSrc?: string, setField(fieldPath: string): void, selectedKey: string | Empty, selectedKeys?: Array | Empty, @@ -783,6 +788,7 @@ export interface LocaleSettings { export interface BehaviourSettings { + fieldSources?: Array, valueSourcesInfo?: ValueSourcesInfo, canCompareFieldWithField?: CanCompareFieldWithField, canReorder?: boolean, @@ -867,6 +873,7 @@ export interface Func { renderSeps?: Array, spelFormatFunc?: SpelFormatFunc, allowSelfNesting?: boolean, + valueSources?: Array, } export interface FuncArg extends ValueField { isOptional?: boolean, diff --git a/packages/core/modules/stores/constants.js b/packages/core/modules/stores/constants.js index 5a26a736e..a0eee8729 100644 --- a/packages/core/modules/stores/constants.js +++ b/packages/core/modules/stores/constants.js @@ -6,6 +6,7 @@ export const SET_NOT = "SET_NOT"; export const ADD_RULE = "ADD_RULE"; export const REMOVE_RULE = "REMOVE_RULE"; export const SET_FIELD = "SET_FIELD"; +export const SET_FIELD_SRC = "SET_FIELD_SRC"; export const SET_OPERATOR = "SET_OPERATOR"; export const SET_VALUE = "SET_VALUE"; export const SET_VALUE_SRC = "SET_VALUE_SRC"; diff --git a/packages/core/modules/stores/tree.js b/packages/core/modules/stores/tree.js index f88868c04..e329cd8d4 100644 --- a/packages/core/modules/stores/tree.js +++ b/packages/core/modules/stores/tree.js @@ -1,23 +1,39 @@ import Immutable from "immutable"; import { - expandTreePath, expandTreeSubpath, getItemByPath, fixPathsInTree, - getTotalRulesCountInTree, fixEmptyGroupsInTree, isEmptyTree, hasChildren, removeIsLockedInTree + expandTreePath, + expandTreeSubpath, + getItemByPath, + fixPathsInTree, + getTotalRulesCountInTree, + fixEmptyGroupsInTree, + isEmptyTree, + hasChildren, + removeIsLockedInTree, } from "../utils/treeUtils"; import { - defaultRuleProperties, defaultGroupProperties, defaultOperator, - defaultOperatorOptions, defaultRoot, defaultItemProperties + defaultRuleProperties, + defaultGroupProperties, + defaultOperator, + defaultOperatorOptions, + defaultRoot, + defaultItemProperties, } from "../utils/defaultUtils"; import * as constants from "./constants"; import uuid from "../utils/uuid"; import { - getFuncConfig, getFieldConfig, getFieldWidgetConfig, getOperatorConfig + getFuncConfig, + getFieldConfig, + getFieldWidgetConfig, + getOperatorConfig, } from "../utils/configUtils"; import { - getOperatorsForField, getFirstOperator, getWidgetForFieldOp, - getNewValueForFieldOp + getOperatorsForField, + getFirstOperator, + getWidgetForFieldOp, + getNewValueForFieldOp, } from "../utils/ruleUtils"; -import {deepEqual, defaultValue, applyToJS} from "../utils/stuff"; -import {validateValue} from "../utils/validation"; +import { deepEqual, defaultValue, applyToJS } from "../utils/stuff"; +import { validateValue } from "../utils/validation"; import omit from "lodash/omit"; import mapValues from "lodash/mapValues"; @@ -26,27 +42,54 @@ import mapValues from "lodash/mapValues"; * @param {Immutable.List} path * @param {Immutable.Map} properties */ -const addNewGroup = (state, path, type, groupUuid, properties, config, children = null, meta = {}) => { - const {shouldCreateEmptyGroup} = config.settings; +const addNewGroup = ( + state, + path, + type, + groupUuid, + properties, + config, + children = null, + meta = {} +) => { + const { shouldCreateEmptyGroup } = config.settings; const groupPath = path.push(groupUuid); const canAddNewRule = !shouldCreateEmptyGroup; const isDefaultCase = !!meta?.isDefaultCase; const origState = state; - state = addItem(state, path, type, groupUuid, defaultGroupProperties(config).merge(properties || {}), config, children); + state = addItem( + state, + path, + type, + groupUuid, + defaultGroupProperties(config).merge(properties || {}), + config, + children + ); if (state !== origState) { if (!children && !isDefaultCase) { - state = state.setIn(expandTreePath(groupPath, "children1"), new Immutable.OrderedMap()); + state = state.setIn( + expandTreePath(groupPath, "children1"), + new Immutable.OrderedMap() + ); // Add one empty rule into new group if (canAddNewRule) { - state = addItem(state, groupPath, "rule", uuid(), defaultRuleProperties(config), config); + state = addItem( + state, + groupPath, + "rule", + uuid(), + defaultRuleProperties(config), + config + ); } } state = fixPathsInTree(state); } - + return state; }; @@ -58,16 +101,23 @@ const addNewGroup = (state, path, type, groupUuid, properties, config, children const removeGroup = (state, path, config) => { state = removeItem(state, path); - const {canLeaveEmptyGroup} = config.settings; + const { canLeaveEmptyGroup } = config.settings; const parentPath = path.slice(0, -1); const isEmptyParentGroup = !hasChildren(state, parentPath); if (isEmptyParentGroup && !canLeaveEmptyGroup) { // check ancestors for emptiness (and delete 'em if empty) state = fixEmptyGroupsInTree(state); - + if (isEmptyTree(state) && !canLeaveEmptyGroup) { // if whole query is empty, add one empty rule to root - state = addItem(state, new Immutable.List(), "rule", uuid(), defaultRuleProperties(config), config); + state = addItem( + state, + new Immutable.List(), + "rule", + uuid(), + defaultRuleProperties(config), + config + ); } } state = fixPathsInTree(state); @@ -81,23 +131,28 @@ const removeGroup = (state, path, config) => { const removeRule = (state, path, config) => { state = removeItem(state, path); - const {canLeaveEmptyGroup} = config.settings; + const { canLeaveEmptyGroup } = config.settings; const parentPath = path.pop(); const parent = state.getIn(expandTreePath(parentPath)); const parentField = parent.getIn(["properties", "field"]); const parentOperator = parent.getIn(["properties", "operator"]); const parentValue = parent.getIn(["properties", "value", 0]); - const parentFieldConfig = parentField ? getFieldConfig(config, parentField) : null; - const parentOperatorConfig = parentOperator ? getOperatorConfig(config, parentOperator, parentField) : null; - const hasGroupCountRule = parentField && parentOperator && parentOperatorConfig.cardinality != 0; // && parentValue != undefined; - + const parentFieldConfig = parentField + ? getFieldConfig(config, parentField) + : null; + const parentOperatorConfig = parentOperator + ? getOperatorConfig(config, parentOperator, parentField) + : null; + const hasGroupCountRule = + parentField && parentOperator && parentOperatorConfig.cardinality != 0; // && parentValue != undefined; + const isParentRuleGroup = parent.get("type") == "rule_group"; const isEmptyParentGroup = !hasChildren(state, parentPath); - const canLeaveEmpty = isParentRuleGroup + const canLeaveEmpty = isParentRuleGroup ? hasGroupCountRule && parentFieldConfig.initialEmptyWhere : canLeaveEmptyGroup; - + if (isEmptyParentGroup && !canLeaveEmpty) { if (isParentRuleGroup) { // deleted last rule from rule_group, so delete whole rule_group @@ -109,7 +164,14 @@ const removeRule = (state, path, config) => { if (isEmptyTree(state) && !canLeaveEmptyGroup) { // if whole query is empty, add one empty rule to root - state = addItem(state, new Immutable.List(), "rule", uuid(), defaultRuleProperties(config), config); + state = addItem( + state, + new Immutable.List(), + "rule", + uuid(), + defaultRuleProperties(config), + config + ); } } state = fixPathsInTree(state); @@ -130,7 +192,9 @@ const setNot = (state, path, not) => * @param {bool} lock */ const setLock = (state, path, lock) => - removeIsLockedInTree(state.setIn(expandTreePath(path, "properties", "isLocked"), lock)); + removeIsLockedInTree( + state.setIn(expandTreePath(path, "properties", "isLocked"), lock) + ); /** * @param {Immutable.Map} state @@ -148,14 +212,16 @@ const _addChildren1 = (config, item, children) => { const id1 = uuid(); const it1 = { ...it, - properties: defaultItemProperties(config, it).merge(it.properties || {}), - id: id1 + properties: defaultItemProperties(config, it).merge( + it.properties || {} + ), + id: id1, }; _addChildren1(config, it1, it1.children1); //todo: guarantee order return { ...map, - [id1]: new Immutable.Map(it1) + [id1]: new Immutable.Map(it1), }; }, {}) ); @@ -170,14 +236,24 @@ const _addChildren1 = (config, item, children) => { * @param {Immutable.OrderedMap} properties * @param {object} config */ -const addItem = (state, path, type, id, properties, config, children = null) => { +const addItem = ( + state, + path, + type, + id, + properties, + config, + children = null +) => { if (type == "switch_group") throw new Error("Can't add switch_group programmatically"); const { maxNumberOfCases, maxNumberOfRules, maxNesting } = config.settings; const rootType = state.get("type"); const isTernary = rootType == "switch_group"; const targetItem = state.getIn(expandTreePath(path)); - const caseGroup = isTernary ? state.getIn(expandTreePath(path.take(2))) : null; + const caseGroup = isTernary + ? state.getIn(expandTreePath(path.take(2))) + : null; const childrenPath = expandTreePath(path, "children1"); const targetChildren = state.getIn(childrenPath); const hasChildren = !!targetChildren && targetChildren.size; @@ -192,19 +268,24 @@ const addItem = (state, path, type, id, properties, config, children = null) => } else if (targetItem?.get("type") == "rule_group") { // don't restrict } else { - currentNumber = isTernary ? getTotalRulesCountInTree(caseGroup) : getTotalRulesCountInTree(state); + currentNumber = isTernary + ? getTotalRulesCountInTree(caseGroup) + : getTotalRulesCountInTree(state); maxNumber = maxNumberOfRules; } - const canAdd = maxNumber && currentNumber ? (currentNumber < maxNumber) : true; - - const item = {type, id, properties}; + const canAdd = maxNumber && currentNumber ? currentNumber < maxNumber : true; + + const item = { type, id, properties }; _addChildren1(config, item, children); - const isLastDefaultCase = type == "case_group" && hasChildren && targetChildren.last().get("children1") == null; + const isLastDefaultCase = + type == "case_group" && + hasChildren && + targetChildren.last().get("children1") == null; if (canAdd) { const newChildren = new Immutable.OrderedMap({ - [id]: new Immutable.Map(item) + [id]: new Immutable.Map(item), }); if (!hasChildren) { state = state.setIn(childrenPath, newChildren); @@ -212,9 +293,11 @@ const addItem = (state, path, type, id, properties, config, children = null) => const last = targetChildren.last(); const newChildrenWithLast = new Immutable.OrderedMap({ [id]: new Immutable.Map(item), - [last.get("id")]: last + [last.get("id")]: last, }); - state = state.deleteIn(expandTreePath(childrenPath, "children1", last.get("id"))); + state = state.deleteIn( + expandTreePath(childrenPath, "children1", last.get("id")) + ); state = state.mergeIn(childrenPath, newChildrenWithLast); } else { state = state.mergeIn(childrenPath, newChildren); @@ -248,44 +331,70 @@ const moveItem = (state, fromPath, toPath, placement, config) => { const sourceChildren = source ? source.get("children1") : null; const to = getItemByPath(state, toPath); - const targetPath = (placement == constants.PLACEMENT_APPEND || placement == constants.PLACEMENT_PREPEND) ? toPath : toPath.pop(); - const target = (placement == constants.PLACEMENT_APPEND || placement == constants.PLACEMENT_PREPEND) - ? to - : toPath.size > 1 ? getItemByPath(state, targetPath) : null; + const targetPath = + placement == constants.PLACEMENT_APPEND || + placement == constants.PLACEMENT_PREPEND + ? toPath + : toPath.pop(); + const target = + placement == constants.PLACEMENT_APPEND || + placement == constants.PLACEMENT_PREPEND + ? to + : toPath.size > 1 + ? getItemByPath(state, targetPath) + : null; const targetChildren = target ? target.get("children1") : null; - if (!source || !target || !from) - return state; + if (!source || !target || !from) return state; - const isSameParent = (source.get("id") == target.get("id")); - const isSourceInsideTarget = targetPath.size < sourcePath.size - && deepEqual(targetPath.toArray(), sourcePath.toArray().slice(0, targetPath.size)); - const isTargetInsideSource = targetPath.size > sourcePath.size - && deepEqual(sourcePath.toArray(), targetPath.toArray().slice(0, sourcePath.size)); + const isSameParent = source.get("id") == target.get("id"); + const isSourceInsideTarget = + targetPath.size < sourcePath.size && + deepEqual( + targetPath.toArray(), + sourcePath.toArray().slice(0, targetPath.size) + ); + const isTargetInsideSource = + targetPath.size > sourcePath.size && + deepEqual( + sourcePath.toArray(), + targetPath.toArray().slice(0, sourcePath.size) + ); let sourceSubpathFromTarget = null; let targetSubpathFromSource = null; if (isSourceInsideTarget) { - sourceSubpathFromTarget = Immutable.List(sourcePath.toArray().slice(targetPath.size)); + sourceSubpathFromTarget = Immutable.List( + sourcePath.toArray().slice(targetPath.size) + ); } else if (isTargetInsideSource) { - targetSubpathFromSource = Immutable.List(targetPath.toArray().slice(sourcePath.size)); + targetSubpathFromSource = Immutable.List( + targetPath.toArray().slice(sourcePath.size) + ); } - let newTargetChildren = targetChildren, newSourceChildren = sourceChildren; + let newTargetChildren = targetChildren, + newSourceChildren = sourceChildren; if (!isTargetInsideSource) newSourceChildren = newSourceChildren.delete(from.get("id")); if (isSameParent) { newTargetChildren = newSourceChildren; } else if (isSourceInsideTarget) { - newTargetChildren = newTargetChildren.updateIn(expandTreeSubpath(sourceSubpathFromTarget, "children1"), (_oldChildren) => newSourceChildren); + newTargetChildren = newTargetChildren.updateIn( + expandTreeSubpath(sourceSubpathFromTarget, "children1"), + (_oldChildren) => newSourceChildren + ); } - if (placement == constants.PLACEMENT_BEFORE || placement == constants.PLACEMENT_AFTER) { - newTargetChildren = Immutable.OrderedMap().withMutations(r => { + if ( + placement == constants.PLACEMENT_BEFORE || + placement == constants.PLACEMENT_AFTER + ) { + newTargetChildren = Immutable.OrderedMap().withMutations((r) => { for (let [itemId, item] of newTargetChildren.entries()) { if (itemId == to.get("id") && placement == constants.PLACEMENT_BEFORE) { r.set(from.get("id"), from); } - + r.set(itemId, item); if (itemId == to.get("id") && placement == constants.PLACEMENT_AFTER) { @@ -294,25 +403,53 @@ const moveItem = (state, fromPath, toPath, placement, config) => { } }); } else if (placement == constants.PLACEMENT_APPEND) { - newTargetChildren = newTargetChildren.merge({[from.get("id")]: from}); + newTargetChildren = newTargetChildren.merge({ [from.get("id")]: from }); } else if (placement == constants.PLACEMENT_PREPEND) { - newTargetChildren = Immutable.OrderedMap({[from.get("id")]: from}).merge(newTargetChildren); + newTargetChildren = Immutable.OrderedMap({ [from.get("id")]: from }).merge( + newTargetChildren + ); } if (isTargetInsideSource) { - newSourceChildren = newSourceChildren.updateIn(expandTreeSubpath(targetSubpathFromSource, "children1"), (_oldChildren) => newTargetChildren); + newSourceChildren = newSourceChildren.updateIn( + expandTreeSubpath(targetSubpathFromSource, "children1"), + (_oldChildren) => newTargetChildren + ); newSourceChildren = newSourceChildren.delete(from.get("id")); } if (!isSameParent && !isSourceInsideTarget) - state = state.updateIn(expandTreePath(sourcePath, "children1"), (_oldChildren) => newSourceChildren); + state = state.updateIn( + expandTreePath(sourcePath, "children1"), + (_oldChildren) => newSourceChildren + ); if (!isTargetInsideSource) - state = state.updateIn(expandTreePath(targetPath, "children1"), (_oldChildren) => newTargetChildren); + state = state.updateIn( + expandTreePath(targetPath, "children1"), + (_oldChildren) => newTargetChildren + ); state = fixPathsInTree(state); return state; }; +/** + * @param {Immutable.Map} state + * @param {Immutable.List} path + * @param {integer} delta + * @param {*} srcKey + */ +const setFieldSrc = (state, path, srcKey, config) => { + state = state.setIn( + expandTreePath(path, "properties"), + defaultRuleProperties(config) + ); + + // set fieldSrc + state = state.setIn(expandTreePath(path, "properties", "fieldSrc"), srcKey); + + return state; +}; /** * @param {Immutable.Map} state @@ -320,44 +457,55 @@ const moveItem = (state, fromPath, toPath, placement, config) => { * @param {string} field */ const setField = (state, path, newField, config) => { - if (!newField) - return removeItem(state, path); + if (!newField) return removeItem(state, path); - const {fieldSeparator, setOpOnChangeField, showErrorMessage} = config.settings; - if (Array.isArray(newField)) - newField = newField.join(fieldSeparator); + const { fieldSeparator, setOpOnChangeField, showErrorMessage } = + config.settings; + if (Array.isArray(newField)) newField = newField.join(fieldSeparator); const currentType = state.getIn(expandTreePath(path, "type")); const currentProperties = state.getIn(expandTreePath(path, "properties")); const wasRuleGroup = currentType == "rule_group"; - const newFieldConfig = getFieldConfig(config, newField); + const currentFieldSrc = currentProperties.get("fieldSrc"); + const newFieldConfig = getFieldConfig(config, newField, currentFieldSrc); const isRuleGroup = newFieldConfig.type == "!group"; const isRuleGroupExt = isRuleGroup && newFieldConfig.mode == "array"; const isChangeToAnotherType = wasRuleGroup != isRuleGroup; - + const currentOperator = currentProperties.get("operator"); const currentOperatorOptions = currentProperties.get("operatorOptions"); const _currentField = currentProperties.get("field"); const _currentValue = currentProperties.get("value"); - const _currentValueSrc = currentProperties.get("valueSrc", new Immutable.List()); - const _currentValueType = currentProperties.get("valueType", new Immutable.List()); + const _currentValueSrc = currentProperties.get( + "valueSrc", + new Immutable.List() + ); + const _currentValueType = currentProperties.get( + "valueType", + new Immutable.List() + ); // If the newly selected field supports the same operator the rule currently // uses, keep it selected. - const lastOp = newFieldConfig && newFieldConfig.operators.indexOf(currentOperator) !== -1 ? currentOperator : null; + const lastOp = + newFieldConfig && newFieldConfig.operators?.indexOf(currentOperator) !== -1 + ? currentOperator + : null; let newOperator = null; - const availOps = getOperatorsForField(config, newField); - if (availOps && availOps.length == 1) - newOperator = availOps[0]; + const availOps = + currentFieldSrc === "func" + ? ["equal"] + : getOperatorsForField(config, newField); + if (availOps && availOps.length == 1) newOperator = availOps[0]; else if (availOps && availOps.length > 1) { for (let strategy of setOpOnChangeField || []) { - if (strategy == "keep" && !isChangeToAnotherType) - newOperator = lastOp; + if (strategy == "keep" && !isChangeToAnotherType) newOperator = lastOp; else if (strategy == "default") newOperator = defaultOperator(config, newField, false); else if (strategy == "first") newOperator = getFirstOperator(config, newField); - if (newOperator) //found op for strategy + if (newOperator) + //found op for strategy break; } } @@ -366,17 +514,31 @@ const setField = (state, path, newField, config) => { console.warn(`Type ${newFieldConfig.type} is not supported`); return state; } - if (wasRuleGroup && !isRuleGroup) { state = state.setIn(expandTreePath(path, "type"), "rule"); state = state.deleteIn(expandTreePath(path, "children1")); - state = state.setIn(expandTreePath(path, "properties"), new Immutable.OrderedMap()); + state = state.setIn( + expandTreePath(path, "properties"), + new Immutable.OrderedMap() + ); } if (isRuleGroup) { state = state.setIn(expandTreePath(path, "type"), "rule_group"); - const {canReuseValue, newValue, newValueSrc, newValueType, operatorCardinality} = getNewValueForFieldOp( - config, config, currentProperties, newField, newOperator, "field", true + const { + canReuseValue, + newValue, + newValueSrc, + newValueType, + operatorCardinality, + } = getNewValueForFieldOp( + config, + config, + currentProperties, + newField, + newOperator, + "field", + true ); let groupProperties = defaultGroupProperties(config, newFieldConfig).merge({ field: newField, @@ -390,37 +552,66 @@ const setField = (state, path, newField, config) => { valueType: newValueType, }); } - state = state.setIn(expandTreePath(path, "children1"), new Immutable.OrderedMap()); + state = state.setIn( + expandTreePath(path, "children1"), + new Immutable.OrderedMap() + ); state = state.setIn(expandTreePath(path, "properties"), groupProperties); - if (newFieldConfig.initialEmptyWhere && operatorCardinality == 1) { // just `COUNT(grp) > 1` without `HAVING ..` + if (newFieldConfig.initialEmptyWhere && operatorCardinality == 1) { + // just `COUNT(grp) > 1` without `HAVING ..` // no childeren } else { - state = addItem(state, path, "rule", uuid(), defaultRuleProperties(config, newField), config); + state = addItem( + state, + path, + "rule", + uuid(), + defaultRuleProperties(config, newField), + config + ); } state = fixPathsInTree(state); - return state; } - - return state.updateIn(expandTreePath(path, "properties"), (map) => map.withMutations((current) => { - const {canReuseValue, newValue, newValueSrc, newValueType, newValueError} = getNewValueForFieldOp( - config, config, current, newField, newOperator, "field", true - ); - if (showErrorMessage) { - current = current - .set("valueError", newValueError); - } - const newOperatorOptions = canReuseValue ? currentOperatorOptions : defaultOperatorOptions(config, newOperator, newField); - - return current - .set("field", newField) - .set("operator", newOperator) - .set("operatorOptions", newOperatorOptions) - .set("value", newValue) - .set("valueSrc", newValueSrc) - .set("valueType", newValueType) - .delete("asyncListValues"); - })); + state = state.updateIn(expandTreePath(path, "properties"), (map) => + map.withMutations((current) => { + const { + canReuseValue, + newValue, + newValueSrc, + newValueType, + newValueError, + } = getNewValueForFieldOp( + config, + config, + current, + newField, + newOperator, + "field", + true + ); + if (showErrorMessage) { + current = current.set("valueError", newValueError); + } + const newOperatorOptions = canReuseValue + ? currentOperatorOptions + : defaultOperatorOptions( + config, + newOperator, + newField, + currentFieldSrc + ); + return current + .set("field", newField) + .set("operator", newOperator) + .set("operatorOptions", newOperatorOptions) + .set("value", newValue) + .set("valueSrc", newValueSrc) + .set("valueType", newValueType) + .delete("asyncListValues"); + }) + ); + return state; }; /** @@ -429,48 +620,82 @@ const setField = (state, path, newField, config) => { * @param {string} operator */ const setOperator = (state, path, newOperator, config) => { - const {showErrorMessage} = config.settings; + const { showErrorMessage } = config.settings; const properties = state.getIn(expandTreePath(path, "properties")); const children = state.getIn(expandTreePath(path, "children1")); const currentField = properties.get("field"); - const fieldConfig = getFieldConfig(config, currentField); + const currentFieldSrc = properties.get("fieldSrc"); + const fieldConfig = getFieldConfig(config, currentField, currentFieldSrc); const isRuleGroup = fieldConfig.type == "!group"; - const operatorConfig = getOperatorConfig(config, newOperator, currentField); - const operatorCardinality = operatorConfig ? defaultValue(operatorConfig.cardinality, 1) : null; - - state = state.updateIn(expandTreePath(path, "properties"), (map) => map.withMutations((current) => { - const currentField = current.get("field"); - const currentOperatorOptions = current.get("operatorOptions"); - const _currentValue = current.get("value", new Immutable.List()); - const _currentValueSrc = current.get("valueSrc", new Immutable.List()); - const _currentOperator = current.get("operator"); - - const {canReuseValue, newValue, newValueSrc, newValueType, newValueError} = getNewValueForFieldOp( - config, config, current, currentField, newOperator, "operator", true - ); - if (showErrorMessage) { - current = current - .set("valueError", newValueError); - } - const newOperatorOptions = canReuseValue ? currentOperatorOptions : defaultOperatorOptions(config, newOperator, currentField); - - if (!canReuseValue) { - current = current - .delete("asyncListValues"); - } + const operatorConfig = getOperatorConfig( + config, + newOperator, + currentField, + currentFieldSrc + ); + const operatorCardinality = operatorConfig + ? defaultValue(operatorConfig.cardinality, 1) + : null; + + state = state.updateIn(expandTreePath(path, "properties"), (map) => + map.withMutations((current) => { + const currentField = current.get("field"); + const currentOperatorOptions = current.get("operatorOptions"); + const _currentValue = current.get("value", new Immutable.List()); + const _currentValueSrc = current.get("valueSrc", new Immutable.List()); + const _currentOperator = current.get("operator"); + + const { + canReuseValue, + newValue, + newValueSrc, + newValueType, + newValueError, + } = getNewValueForFieldOp( + config, + config, + current, + currentField, + newOperator, + "operator", + true + ); + if (showErrorMessage) { + current = current.set("valueError", newValueError); + } + const newOperatorOptions = canReuseValue + ? currentOperatorOptions + : defaultOperatorOptions( + config, + newOperator, + currentField, + currentFieldSrc + ); + + if (!canReuseValue) { + current = current.delete("asyncListValues"); + } - return current - .set("operator", newOperator) - .set("operatorOptions", newOperatorOptions) - .set("value", newValue) - .set("valueSrc", newValueSrc) - .set("valueType", newValueType); - })); + return current + .set("operator", newOperator) + .set("operatorOptions", newOperatorOptions) + .set("value", newValue) + .set("valueSrc", newValueSrc) + .set("valueType", newValueType); + }) + ); if (isRuleGroup) { if (operatorCardinality == 0 && children.size == 0) { - state = addItem(state, path, "rule", uuid(), defaultRuleProperties(config, currentField), config); + state = addItem( + state, + path, + "rule", + uuid(), + defaultRuleProperties(config, currentField), + config + ); } } @@ -486,23 +711,52 @@ const setOperator = (state, path, newOperator, config) => { * @param {*} asyncListValues * @param {boolean} __isInternal */ -const setValue = (state, path, delta, value, valueType, config, asyncListValues, __isInternal) => { - const {fieldSeparator, showErrorMessage} = config.settings; +const setValue = ( + state, + path, + delta, + value, + valueType, + config, + asyncListValues, + __isInternal +) => { + const { fieldSeparator, showErrorMessage } = config.settings; let isInternalValueChange; - const valueSrc = state.getIn(expandTreePath(path, "properties", "valueSrc", delta + "")) || null; + const valueSrc = + state.getIn(expandTreePath(path, "properties", "valueSrc", delta + "")) || + null; if (valueSrc === "field" && Array.isArray(value)) value = value.join(fieldSeparator); - const field = state.getIn(expandTreePath(path, "properties", "field")) || null; - const operator = state.getIn(expandTreePath(path, "properties", "operator")) || null; - const operatorConfig = getOperatorConfig(config, operator, field); - const operatorCardinality = operator ? defaultValue(operatorConfig.cardinality, 1) : null; + const field = + state.getIn(expandTreePath(path, "properties", "field")) || null; + const fieldSrc = + state.getIn(expandTreePath(path, "properties", "fieldSrc")) || null; + const operator = + state.getIn(expandTreePath(path, "properties", "operator")) || null; + const operatorConfig = getOperatorConfig(config, operator, field, fieldSrc); + const operatorCardinality = operator + ? defaultValue(operatorConfig.cardinality, 1) + : null; const isEndValue = false; - const calculatedValueType = valueType || calculateValueType(value, valueSrc, config); + const calculatedValueType = + valueType || calculateValueType(value, valueSrc, config); const canFix = false; const [validateError, fixedValue] = validateValue( - config, field, field, operator, value, calculatedValueType, valueSrc, asyncListValues, canFix, isEndValue + config, + field, + field, + operator, + value, + calculatedValueType, + valueSrc, + asyncListValues, + canFix, + isEndValue, + true, + fieldSrc ); const isValid = !validateError; if (fixedValue !== value) { @@ -512,55 +766,124 @@ const setValue = (state, path, delta, value, valueType, config, asyncListValues, // Additional validation for range values if (showErrorMessage) { - const w = getWidgetForFieldOp(config, field, operator, valueSrc); - const fieldWidgetDefinition = getFieldWidgetConfig(config, field, operator, w, valueSrc); - const valueSrcs = Array.from({length: operatorCardinality}, (_, i) => (state.getIn(expandTreePath(path, "properties", "valueSrc", i + "")) || null)); - - if (operatorConfig && operatorConfig.validateValues && valueSrcs.filter(vs => vs == "value" || vs == null).length == operatorCardinality) { - const values = Array.from({length: operatorCardinality}, (_, i) => (i == delta ? value : state.getIn(expandTreePath(path, "properties", "value", i + "")) || null)); - const jsValues = fieldWidgetDefinition && fieldWidgetDefinition.toJS ? values.map(v => fieldWidgetDefinition.toJS(v, fieldWidgetDefinition)) : values; + const w = getWidgetForFieldOp(config, field, operator, valueSrc, fieldSrc); + const fieldWidgetDefinition = getFieldWidgetConfig( + config, + field, + operator, + w, + valueSrc, + fieldSrc + ); + const valueSrcs = Array.from( + { length: operatorCardinality }, + (_, i) => + state.getIn(expandTreePath(path, "properties", "valueSrc", i + "")) || + null + ); + + if ( + operatorConfig && + operatorConfig.validateValues && + valueSrcs.filter((vs) => vs == "value" || vs == null).length == + operatorCardinality + ) { + const values = Array.from({ length: operatorCardinality }, (_, i) => + i == delta + ? value + : state.getIn(expandTreePath(path, "properties", "value", i + "")) || + null + ); + const jsValues = + fieldWidgetDefinition && fieldWidgetDefinition.toJS + ? values.map((v) => + fieldWidgetDefinition.toJS(v, fieldWidgetDefinition) + ) + : values; const rangeValidateError = operatorConfig.validateValues(jsValues); - state = state.setIn(expandTreePath(path, "properties", "valueError", operatorCardinality), rangeValidateError); + state = state.setIn( + expandTreePath(path, "properties", "valueError", operatorCardinality), + rangeValidateError + ); } } - + const lastValueArr = state.getIn(expandTreePath(path, "properties", "value")); if (!lastValueArr) { state = state - .setIn(expandTreePath(path, "properties", "value"), new Immutable.List(new Array(operatorCardinality))) - .setIn(expandTreePath(path, "properties", "valueType"), new Immutable.List(new Array(operatorCardinality))) - .setIn(expandTreePath(path, "properties", "valueError"), new Immutable.List(new Array(operatorCardinality))); + .setIn( + expandTreePath(path, "properties", "value"), + new Immutable.List(new Array(operatorCardinality)) + ) + .setIn( + expandTreePath(path, "properties", "valueType"), + new Immutable.List(new Array(operatorCardinality)) + ) + .setIn( + expandTreePath(path, "properties", "valueError"), + new Immutable.List(new Array(operatorCardinality)) + ); } - const lastValue = state.getIn(expandTreePath(path, "properties", "value", delta + "")); - const lastError = state.getIn(expandTreePath(path, "properties", "valueError", delta)); + const lastValue = state.getIn( + expandTreePath(path, "properties", "value", delta + "") + ); + const lastError = state.getIn( + expandTreePath(path, "properties", "valueError", delta) + ); const isLastEmpty = lastValue == undefined; const isLastError = !!lastError; if (isValid || showErrorMessage) { - state = state.deleteIn(expandTreePath(path, "properties", "asyncListValues")); + state = state.deleteIn( + expandTreePath(path, "properties", "asyncListValues") + ); // set only good value if (typeof value === "undefined") { - state = state.setIn(expandTreePath(path, "properties", "value", delta + ""), undefined); - state = state.setIn(expandTreePath(path, "properties", "valueType", delta + ""), null); + state = state.setIn( + expandTreePath(path, "properties", "value", delta + ""), + undefined + ); + state = state.setIn( + expandTreePath(path, "properties", "valueType", delta + ""), + null + ); } else { if (asyncListValues) { - state = state.setIn(expandTreePath(path, "properties", "asyncListValues"), asyncListValues); + state = state.setIn( + expandTreePath(path, "properties", "asyncListValues"), + asyncListValues + ); } - state = state.setIn(expandTreePath(path, "properties", "value", delta + ""), value); - state = state.setIn(expandTreePath(path, "properties", "valueType", delta + ""), calculatedValueType); + state = state.setIn( + expandTreePath(path, "properties", "value", delta + ""), + value + ); + state = state.setIn( + expandTreePath(path, "properties", "valueType", delta + ""), + calculatedValueType + ); isInternalValueChange = __isInternal && !isLastEmpty && !isLastError; } } if (showErrorMessage) { - state = state.setIn(expandTreePath(path, "properties", "valueError", delta), validateError); + state = state.setIn( + expandTreePath(path, "properties", "valueError", delta), + validateError + ); } - if (__isInternal && (isValid && isLastError || !isValid && !isLastError)) { - state = state.setIn(expandTreePath(path, "properties", "valueError", delta), validateError); + if ( + __isInternal && + ((isValid && isLastError) || (!isValid && !isLastError)) + ) { + state = state.setIn( + expandTreePath(path, "properties", "valueError", delta), + validateError + ); isInternalValueChange = false; } - - return {tree: state, isInternalValueChange}; + + return { tree: state, isInternalValueChange }; }; /** @@ -570,44 +893,86 @@ const setValue = (state, path, delta, value, valueType, config, asyncListValues, * @param {*} srcKey */ const setValueSrc = (state, path, delta, srcKey, config) => { - const {showErrorMessage} = config.settings; - - const field = state.getIn(expandTreePath(path, "properties", "field")) || null; - const operator = state.getIn(expandTreePath(path, "properties", "operator")) || null; - - state = state.setIn(expandTreePath(path, "properties", "value", delta + ""), undefined); - state = state.setIn(expandTreePath(path, "properties", "valueType", delta + ""), null); + const { showErrorMessage } = config.settings; + + const field = + state.getIn(expandTreePath(path, "properties", "field")) || null; + const fieldSrc = + state.getIn(expandTreePath(path, "properties", "fieldSrc")) || null; + const operator = + state.getIn(expandTreePath(path, "properties", "operator")) || null; + + state = state.setIn( + expandTreePath(path, "properties", "value", delta + ""), + undefined + ); + state = state.setIn( + expandTreePath(path, "properties", "valueType", delta + ""), + null + ); state = state.deleteIn(expandTreePath(path, "properties", "asyncListValues")); if (showErrorMessage) { // clear value error - state = state.setIn(expandTreePath(path, "properties", "valueError", delta), null); + state = state.setIn( + expandTreePath(path, "properties", "valueError", delta), + null + ); // if current operator is range, clear possible range error - const operatorConfig = getOperatorConfig(config, operator, field); - const operatorCardinality = operator ? defaultValue(operatorConfig.cardinality, 1) : null; + const operatorConfig = getOperatorConfig(config, operator, field, fieldSrc); + const operatorCardinality = operator + ? defaultValue(operatorConfig.cardinality, 1) + : null; if (operatorConfig.validateValues) { - state = state.setIn(expandTreePath(path, "properties", "valueError", operatorCardinality), null); + state = state.setIn( + expandTreePath(path, "properties", "valueError", operatorCardinality), + null + ); } } - + // set valueSrc if (typeof srcKey === "undefined") { - state = state.setIn(expandTreePath(path, "properties", "valueSrc", delta + ""), null); + state = state.setIn( + expandTreePath(path, "properties", "valueSrc", delta + ""), + null + ); } else { - state = state.setIn(expandTreePath(path, "properties", "valueSrc", delta + ""), srcKey); + state = state.setIn( + expandTreePath(path, "properties", "valueSrc", delta + ""), + srcKey + ); } // maybe set default value if (srcKey) { const properties = state.getIn(expandTreePath(path, "properties")); // this call should return canReuseValue = false and provide default value - const {canReuseValue, newValue, newValueSrc, newValueType, newValueError} = getNewValueForFieldOp( - config, config, properties, field, operator, "valueSrc", true + const { + canReuseValue, + newValue, + newValueSrc, + newValueType, + newValueError, + } = getNewValueForFieldOp( + config, + config, + properties, + field, + operator, + "valueSrc", + true ); if (!canReuseValue && newValueSrc.get(delta) == srcKey) { - state = state.setIn(expandTreePath(path, "properties", "value", delta + ""), newValue.get(delta)); - state = state.setIn(expandTreePath(path, "properties", "valueType", delta + ""), newValueType.get(delta)); + state = state.setIn( + expandTreePath(path, "properties", "value", delta + ""), + newValue.get(delta) + ); + state = state.setIn( + expandTreePath(path, "properties", "valueType", delta + ""), + newValueType.get(delta) + ); } } @@ -621,23 +986,25 @@ const setValueSrc = (state, path, delta, srcKey, config) => { * @param {*} value */ const setOperatorOption = (state, path, name, value) => { - return state.setIn(expandTreePath(path, "properties", "operatorOptions", name), value); + return state.setIn( + expandTreePath(path, "properties", "operatorOptions", name), + value + ); }; /** * @param {Immutable.Map} state */ const checkEmptyGroups = (state, config) => { - const {canLeaveEmptyGroup} = config.settings; + const { canLeaveEmptyGroup } = config.settings; if (!canLeaveEmptyGroup) { state = fixEmptyGroupsInTree(state); } return state; }; - /** - * + * */ const calculateValueType = (value, valueSrc, config) => { let calculatedValueType = null; @@ -661,7 +1028,8 @@ const calculateValueType = (value, valueSrc, config) => { }; const getField = (state, path) => { - const field = state.getIn(expandTreePath(path, "properties", "field")) || null; + const field = + state.getIn(expandTreePath(path, "properties", "field")) || null; return field; }; @@ -671,7 +1039,7 @@ const emptyDrag = { x: null, y: null, w: null, - h: null + h: null, }, mousePos: {}, dragStart: { @@ -680,11 +1048,8 @@ const emptyDrag = { }; const getActionMeta = (action, state) => { - if (!action || !action.type) - return null; - const actionKeysToOmit = [ - "config", "asyncListValues", "__isInternal" - ]; + if (!action || !action.type) return null; + const actionKeysToOmit = ["config", "asyncListValues", "__isInternal"]; const actionTypesToIgnore = [ constants.SET_TREE, constants.SET_DRAG_START, @@ -692,10 +1057,13 @@ const getActionMeta = (action, state) => { constants.SET_DRAG_END, ]; let meta = mapValues(omit(action, actionKeysToOmit), applyToJS); - let affectedField = action.path && getField(state.tree, action.path) || action.field; - if (affectedField) - meta.affectedField = affectedField; - if (actionTypesToIgnore.includes(action.type) || action.type.indexOf("@@redux") == 0) + let affectedField = + (action.path && getField(state.tree, action.path)) || action.field; + if (affectedField) meta.affectedField = affectedField; + if ( + actionTypesToIgnore.includes(action.type) || + action.type.indexOf("@@redux") == 0 + ) meta = null; return meta; }; @@ -708,125 +1076,197 @@ export default (config, tree, getMemoizedTree) => { const emptyTree = defaultRoot(config); const initTree = tree || emptyTree; const emptyState = { - tree: initTree, - ...emptyDrag + tree: initTree, + ...emptyDrag, }; - + return (state = emptyState, action) => { - const unset = {__isInternalValueChange: undefined, __lastAction: undefined}; + const unset = { + __isInternalValueChange: undefined, + __lastAction: undefined, + }; let set = {}; let actionMeta = getActionMeta(action, state); switch (action?.type) { - case constants.SET_TREE: { - const validatedTree = getMemoizedTree(action.config, action.tree); - set.tree = validatedTree; - break; - } + case constants.SET_TREE: { + const validatedTree = getMemoizedTree(action.config, action.tree); + set.tree = validatedTree; + break; + } - case constants.ADD_CASE_GROUP: { - set.tree = addNewGroup(state.tree, action.path, "case_group", action.id, action.properties, action.config, action.children, action.meta); - break; - } + case constants.ADD_CASE_GROUP: { + set.tree = addNewGroup( + state.tree, + action.path, + "case_group", + action.id, + action.properties, + action.config, + action.children, + action.meta + ); + break; + } - case constants.ADD_GROUP: { - set.tree = addNewGroup(state.tree, action.path, "group", action.id, action.properties, action.config, action.children, action.meta); - break; - } + case constants.ADD_GROUP: { + set.tree = addNewGroup( + state.tree, + action.path, + "group", + action.id, + action.properties, + action.config, + action.children, + action.meta + ); + break; + } - case constants.REMOVE_GROUP: { - set.tree = removeGroup(state.tree, action.path, action.config); - break; - } + case constants.REMOVE_GROUP: { + set.tree = removeGroup(state.tree, action.path, action.config); + break; + } - case constants.ADD_RULE: { - set.tree = addItem(state.tree, action.path, action.ruleType, action.id, action.properties, action.config, action.children); - break; - } + case constants.ADD_RULE: { + set.tree = addItem( + state.tree, + action.path, + action.ruleType, + action.id, + action.properties, + action.config, + action.children + ); + break; + } - case constants.REMOVE_RULE: { - set.tree = removeRule(state.tree, action.path, action.config); - break; - } + case constants.REMOVE_RULE: { + set.tree = removeRule(state.tree, action.path, action.config); + break; + } - case constants.SET_CONJUNCTION: { - set.tree = setConjunction(state.tree, action.path, action.conjunction); - break; - } + case constants.SET_CONJUNCTION: { + set.tree = setConjunction(state.tree, action.path, action.conjunction); + break; + } - case constants.SET_NOT: { - set.tree = setNot(state.tree, action.path, action.not); - break; - } + case constants.SET_NOT: { + set.tree = setNot(state.tree, action.path, action.not); + break; + } - case constants.SET_FIELD: { - set.tree = setField(state.tree, action.path, action.field, action.config); - break; - } + case constants.SET_FIELD_SRC: { + set.tree = setFieldSrc( + state.tree, + action.path, + action.srcKey, + action.config + ); + break; + } - case constants.SET_LOCK: { - set.tree = setLock(state.tree, action.path, action.lock); - break; - } + case constants.SET_FIELD: { + set.tree = setField( + state.tree, + action.path, + action.field, + action.config + ); + break; + } - case constants.SET_OPERATOR: { - set.tree = setOperator(state.tree, action.path, action.operator, action.config); - break; - } + case constants.SET_LOCK: { + set.tree = setLock(state.tree, action.path, action.lock); + break; + } - case constants.SET_VALUE: { - const {tree, isInternalValueChange} = setValue( - state.tree, action.path, action.delta, action.value, action.valueType, action.config, action.asyncListValues, action.__isInternal - ); - set.__isInternalValueChange = isInternalValueChange; - set.tree = tree; - break; - } + case constants.SET_OPERATOR: { + set.tree = setOperator( + state.tree, + action.path, + action.operator, + action.config + ); + break; + } - case constants.SET_VALUE_SRC: { - set.tree = setValueSrc(state.tree, action.path, action.delta, action.srcKey, action.config); - break; - } + case constants.SET_VALUE: { + const { tree, isInternalValueChange } = setValue( + state.tree, + action.path, + action.delta, + action.value, + action.valueType, + action.config, + action.asyncListValues, + action.__isInternal + ); + set.__isInternalValueChange = isInternalValueChange; + set.tree = tree; + break; + } - case constants.SET_OPERATOR_OPTION: { - set.tree = setOperatorOption(state.tree, action.path, action.name, action.value); - break; - } + case constants.SET_VALUE_SRC: { + set.tree = setValueSrc( + state.tree, + action.path, + action.delta, + action.srcKey, + action.config + ); + break; + } - case constants.MOVE_ITEM: { - set.tree = moveItem(state.tree, action.fromPath, action.toPath, action.placement, action.config); - break; - } + case constants.SET_OPERATOR_OPTION: { + set.tree = setOperatorOption( + state.tree, + action.path, + action.name, + action.value + ); + break; + } - case constants.SET_DRAG_START: { - set.dragStart = action.dragStart; - set.dragging = action.dragging; - set.mousePos = action.mousePos; - break; - } + case constants.MOVE_ITEM: { + set.tree = moveItem( + state.tree, + action.fromPath, + action.toPath, + action.placement, + action.config + ); + break; + } - case constants.SET_DRAG_PROGRESS: { - set.mousePos = action.mousePos; - set.dragging = action.dragging; - break; - } + case constants.SET_DRAG_START: { + set.dragStart = action.dragStart; + set.dragging = action.dragging; + set.mousePos = action.mousePos; + break; + } - case constants.SET_DRAG_END: { - set.tree = checkEmptyGroups(state.tree, config); - set = { ...set, ...emptyDrag }; - break; - } + case constants.SET_DRAG_PROGRESS: { + set.mousePos = action.mousePos; + set.dragging = action.dragging; + break; + } - default: { - break; - } + case constants.SET_DRAG_END: { + set.tree = checkEmptyGroups(state.tree, config); + set = { ...set, ...emptyDrag }; + break; + } + + default: { + break; + } } if (actionMeta) { set.__lastAction = actionMeta; } - - return {...state, ...unset, ...set}; - }; + return { ...state, ...unset, ...set }; + }; }; diff --git a/packages/core/modules/utils/configUtils.js b/packages/core/modules/utils/configUtils.js index ce55e6fce..32da8ab10 100644 --- a/packages/core/modules/utils/configUtils.js +++ b/packages/core/modules/utils/configUtils.js @@ -1,13 +1,12 @@ import merge from "lodash/merge"; import uuid from "../utils/uuid"; import mergeWith from "lodash/mergeWith"; -import {settings as defaultSettings} from "../config/default"; +import { settings as defaultSettings } from "../config/default"; import moment from "moment"; -import {mergeArraysSmart} from "./stuff"; -import {getWidgetForFieldOp} from "./ruleUtils"; +import { mergeArraysSmart } from "./stuff"; +import { getWidgetForFieldOp } from "./ruleUtils"; import clone from "clone"; - export const extendConfig = (config, configId) => { //operators, defaultOperator - merge //widgetProps (including valueLabel, valuePlaceholder, hideOperator, operatorInlineLabel) - concrete by widget @@ -15,8 +14,8 @@ export const extendConfig = (config, configId) => { if (config.__configId) { return config; } - - config = {...config}; + + config = { ...config }; config.settings = merge({}, defaultSettings, config.settings); config._fieldsCntByType = {}; config._funcsCntByType = {}; @@ -36,7 +35,7 @@ export const extendConfig = (config, configId) => { Object.defineProperty(config, "__configId", { enumerable: false, writable: false, - value: configId || uuid() + value: configId || uuid(), }); return config; @@ -50,21 +49,32 @@ function _extendTypesConfig(typesConfig, config) { } function _extendTypeConfig(type, typeConfig, config) { - let operators = null, defaultOperator = null; - typeConfig.mainWidget = typeConfig.mainWidget || Object.keys(typeConfig.widgets).filter(w => w != "field" && w != "func")[0]; + let operators = null, + defaultOperator = null; + typeConfig.mainWidget = + typeConfig.mainWidget || + Object.keys(typeConfig.widgets).filter( + (w) => w != "field" && w != "func" + )[0]; for (let widget in typeConfig.widgets) { let typeWidgetConfig = typeConfig.widgets[widget]; if (typeWidgetConfig.operators) { let typeWidgetOperators = typeWidgetConfig.operators; if (typeConfig.excludeOperators) { - typeWidgetOperators = typeWidgetOperators.filter(op => !typeConfig.excludeOperators.includes(op)); + typeWidgetOperators = typeWidgetOperators.filter( + (op) => !typeConfig.excludeOperators.includes(op) + ); } operators = mergeArraysSmart(operators, typeWidgetOperators); } if (typeWidgetConfig.defaultOperator) defaultOperator = typeWidgetConfig.defaultOperator; if (widget == typeConfig.mainWidget) { - typeWidgetConfig = merge({}, {widgetProps: typeConfig.mainWidgetProps || {}}, typeWidgetConfig); + typeWidgetConfig = merge( + {}, + { widgetProps: typeConfig.mainWidgetProps || {} }, + typeWidgetConfig + ); } typeConfig.widgets[widget] = typeWidgetConfig; } @@ -72,8 +82,7 @@ function _extendTypeConfig(type, typeConfig, config) { typeConfig.valueSources = Object.keys(config.settings.valueSourcesInfo); for (let valueSrc of typeConfig.valueSources) { if (valueSrc != "value" && !typeConfig.widgets[valueSrc]) { - typeConfig.widgets[valueSrc] = { - }; + typeConfig.widgets[valueSrc] = {}; } } if (!typeConfig.operators && operators) @@ -113,8 +122,7 @@ function _extendFuncArgsConfig(subconfig, config) { if (!tmpIsOptional && argDef.isOptional) { delete argDef.isOptional; } - if (!argDef.isOptional) - tmpIsOptional = false; + if (!argDef.isOptional) tmpIsOptional = false; } } @@ -124,14 +132,23 @@ function _extendFuncArgsConfig(subconfig, config) { } } -function _extendFieldConfig(fieldConfig, config, path = null, isFuncArg = false) { - let operators = null, defaultOperator = null; +function _extendFieldConfig( + fieldConfig, + config, + path = null, + isFuncArg = false +) { + let operators = null, + defaultOperator = null; const typeConfig = config.types[fieldConfig.type]; const excludeOperatorsForField = fieldConfig.excludeOperators || []; if (fieldConfig.type != "!struct" && fieldConfig.type != "!group") { - const keysToPutInFieldSettings = ["listValues", "allowCustomValues", "validateValue"]; - if (!fieldConfig.fieldSettings) - fieldConfig.fieldSettings = {}; + const keysToPutInFieldSettings = [ + "listValues", + "allowCustomValues", + "validateValue", + ]; + if (!fieldConfig.fieldSettings) fieldConfig.fieldSettings = {}; for (const k of keysToPutInFieldSettings) { if (fieldConfig[k]) { fieldConfig.fieldSettings[k] = fieldConfig[k]; @@ -141,9 +158,12 @@ function _extendFieldConfig(fieldConfig, config, path = null, isFuncArg = false) if (fieldConfig.fieldSettings.listValues) { if (config.settings.normalizeListValues) { - fieldConfig.fieldSettings.listValues = config.settings.normalizeListValues( - fieldConfig.fieldSettings.listValues, fieldConfig.type, fieldConfig.fieldSettings - ); + fieldConfig.fieldSettings.listValues = + config.settings.normalizeListValues( + fieldConfig.fieldSettings.listValues, + fieldConfig.type, + fieldConfig.fieldSettings + ); } } @@ -158,27 +178,35 @@ function _extendFieldConfig(fieldConfig, config, path = null, isFuncArg = false) config._fieldsCntByType[fieldConfig.type]++; } - if (!fieldConfig.widgets) - fieldConfig.widgets = {}; - if (isFuncArg) - fieldConfig._isFuncArg = true; + if (!fieldConfig.widgets) fieldConfig.widgets = {}; + if (isFuncArg) fieldConfig._isFuncArg = true; fieldConfig.mainWidget = fieldConfig.mainWidget || typeConfig.mainWidget; - fieldConfig.valueSources = fieldConfig.valueSources || typeConfig.valueSources; + fieldConfig.valueSources = + fieldConfig.valueSources || typeConfig.valueSources; const excludeOperatorsForType = typeConfig.excludeOperators || []; for (let widget in typeConfig.widgets) { let fieldWidgetConfig = fieldConfig.widgets[widget] || {}; const typeWidgetConfig = typeConfig.widgets[widget] || {}; if (!isFuncArg) { //todo: why I've excluded isFuncArg ? - const excludeOperators = [...excludeOperatorsForField, ...excludeOperatorsForType]; - const shouldIncludeOperators = fieldConfig.preferWidgets - && (widget == "field" || fieldConfig.preferWidgets.includes(widget)) - || excludeOperators.length > 0; + const excludeOperators = [ + ...excludeOperatorsForField, + ...excludeOperatorsForType, + ]; + const shouldIncludeOperators = + (fieldConfig.preferWidgets && + (widget == "field" || + fieldConfig.preferWidgets.includes(widget))) || + excludeOperators.length > 0; if (fieldWidgetConfig.operators) { - const addOperators = fieldWidgetConfig.operators.filter(o => !excludeOperators.includes(o)); + const addOperators = fieldWidgetConfig.operators.filter( + (o) => !excludeOperators.includes(o) + ); operators = [...(operators || []), ...addOperators]; } else if (shouldIncludeOperators && typeWidgetConfig.operators) { - const addOperators = typeWidgetConfig.operators.filter(o => !excludeOperators.includes(o)); + const addOperators = typeWidgetConfig.operators.filter( + (o) => !excludeOperators.includes(o) + ); operators = [...(operators || []), ...addOperators]; } if (fieldWidgetConfig.defaultOperator) @@ -186,7 +214,11 @@ function _extendFieldConfig(fieldConfig, config, path = null, isFuncArg = false) } if (widget == fieldConfig.mainWidget) { - fieldWidgetConfig = merge({}, {widgetProps: fieldConfig.mainWidgetProps || {}}, fieldWidgetConfig); + fieldWidgetConfig = merge( + {}, + { widgetProps: fieldConfig.mainWidgetProps || {} }, + fieldWidgetConfig + ); } fieldConfig.widgets[widget] = fieldWidgetConfig; } @@ -208,34 +240,37 @@ function _extendFieldConfig(fieldConfig, config, path = null, isFuncArg = false) } } -export const getFieldRawConfig = (config, field, fieldsKey = "fields", subfieldsKey = "subfields") => { - if (!field) - return null; +export const getFieldRawConfig = ( + config, + field, + fieldsKey = "fields", + subfieldsKey = "subfields" +) => { + if (!field) return null; if (field == "!case_value") { return { type: "case_value", mainWidget: "case_value", widgets: { - "case_value": config.widgets["case_value"] - } + case_value: config.widgets["case_value"], + }, }; } const fieldSeparator = config.settings.fieldSeparator; //field = normalizeField(config, field); const parts = Array.isArray(field) ? field : field.split(fieldSeparator); const targetFields = config[fieldsKey]; - if (!targetFields) - return null; + if (!targetFields) return null; let fields = targetFields; let fieldConfig = null; let path = []; - for (let i = 0 ; i < parts.length ; i++) { + for (let i = 0; i < parts.length; i++) { const part = parts[i]; path.push(part); const pathKey = path.join(fieldSeparator); fieldConfig = fields[pathKey]; - if (i < parts.length-1) { + if (i < parts.length - 1) { if (fieldConfig && fieldConfig[subfieldsKey]) { fields = fieldConfig[subfieldsKey]; path = []; @@ -249,10 +284,12 @@ export const getFieldRawConfig = (config, field, fieldsKey = "fields", subfields }; const computeFieldName = (config, path) => { - if (!path) - return null; + if (!path) return null; const fieldSeparator = config.settings.fieldSeparator; - let l = [...path], r = [], f, fConfig; + let l = [...path], + r = [], + f, + fConfig; while ((f = l.pop()) !== undefined && l.length > 0) { r.unshift(f); fConfig = getFieldRawConfig(config, l); @@ -273,85 +310,112 @@ export const normalizeField = (config, field) => { }; export const getFuncConfig = (config, func) => { - if (!func) - return null; + if (!func) return null; const funcConfig = getFieldRawConfig(config, func, "funcs", "subfields"); - if (!funcConfig) - return null; //throw new Error("Can't find func " + func + ", please check your config"); - return funcConfig; + if (!funcConfig) return null; //throw new Error("Can't find func " + func + ", please check your config"); + return { ...config.types[funcConfig.returnType], ...funcConfig }; }; export const getFuncArgConfig = (config, funcKey, argKey) => { const funcConfig = getFuncConfig(config, funcKey); - if (!funcConfig) - return null; //throw new Error(`Can't find func ${funcKey}, please check your config`); - const argConfig = funcConfig.args && funcConfig.args[argKey] || null; - if (!argConfig) - return null; //throw new Error(`Can't find arg ${argKey} for func ${funcKey}, please check your config`); + if (!funcConfig) return null; //throw new Error(`Can't find func ${funcKey}, please check your config`); + const argConfig = (funcConfig.args && funcConfig.args[argKey]) || null; + if (!argConfig) return null; //throw new Error(`Can't find arg ${argKey} for func ${funcKey}, please check your config`); //merge, but don't merge operators (rewrite instead) const typeConfig = config.types[argConfig.type] || {}; - let ret = mergeWith({}, typeConfig, argConfig || {}, (objValue, srcValue, _key, _object, _source, _stack) => { - if (Array.isArray(objValue)) { - return srcValue; + let ret = mergeWith( + {}, + typeConfig, + argConfig || {}, + (objValue, srcValue, _key, _object, _source, _stack) => { + if (Array.isArray(objValue)) { + return srcValue; + } } - }); + ); return ret; }; -export const getFieldConfig = (config, field) => { - if (!field) - return null; - if (typeof field == "object" && !field.func && !!field.type) - return field; +export const getFieldConfig = (config, field, fieldSrc) => { + if (!field) return null; + if (typeof field == "object" && !field.func && !!field.type) return field; if (typeof field == "object" && field.func && field.arg) return getFuncArgConfig(config, field.func, field.arg); + if (fieldSrc === "func") return getFuncConfig(config, field?.get("func")); const fieldConfig = getFieldRawConfig(config, field); - if (!fieldConfig) - return null; //throw new Error("Can't find field " + field + ", please check your config"); + if (!fieldConfig) return null; //throw new Error("Can't find field " + field + ", please check your config"); //merge, but don't merge operators (rewrite instead) const typeConfig = config.types[fieldConfig.type] || {}; - let ret = mergeWith({}, typeConfig, fieldConfig || {}, (objValue, srcValue, _key, _object, _source, _stack) => { - if (Array.isArray(objValue)) { - return srcValue; + let ret = mergeWith( + {}, + typeConfig, + fieldConfig || {}, + (objValue, srcValue, _key, _object, _source, _stack) => { + if (Array.isArray(objValue)) { + return srcValue; + } } - }); + ); return ret; }; -export const getOperatorConfig = (config, operator, field = null) => { - if (!operator) - return null; +export const getOperatorConfig = (config, operator, field = null, fieldSrc) => { + if (!operator) return null; const opConfig = config.operators[operator]; if (field) { - const fieldConfig = getFieldConfig(config, field); - const widget = getWidgetForFieldOp(config, field, operator); + const fieldConfig = getFieldConfig(config, field, fieldSrc); + const widget = getWidgetForFieldOp(config, field, operator, null, fieldSrc); const widgetConfig = config.widgets[widget] || {}; - const fieldWidgetConfig = (fieldConfig && fieldConfig.widgets ? fieldConfig.widgets[widget] : {}) || {}; + const fieldWidgetConfig = + (fieldConfig && fieldConfig.widgets ? fieldConfig.widgets[widget] : {}) || + {}; const widgetOpProps = (widgetConfig.opProps || {})[operator]; const fieldWidgetOpProps = (fieldWidgetConfig.opProps || {})[operator]; - const mergedOpConfig = merge({}, opConfig, widgetOpProps, fieldWidgetOpProps); + const mergedOpConfig = merge( + {}, + opConfig, + widgetOpProps, + fieldWidgetOpProps + ); return mergedOpConfig; } else { return opConfig; } }; -export const getFieldWidgetConfig = (config, field, operator, widget = null, valueSrc = null) => { - if (!field) - return null; +export const getFieldWidgetConfig = ( + config, + field, + operator, + widget = null, + valueSrc = null, + fieldSrc = null +) => { + if (!field) return null; if (!(operator || widget) && valueSrc != "const" && field != "!case_value") return null; - const fieldConfig = getFieldConfig(config, field); + const fieldConfig = getFieldConfig(config, field, fieldSrc); if (!widget) - widget = getWidgetForFieldOp(config, field, operator, valueSrc); + widget = getWidgetForFieldOp(config, field, operator, valueSrc, fieldSrc); const widgetConfig = config.widgets[widget] || {}; - const fieldWidgetConfig = (fieldConfig && fieldConfig.widgets ? fieldConfig.widgets[widget] : {}) || {}; - const fieldWidgetProps = (fieldWidgetConfig.widgetProps || {}); - const valueFieldSettings = (valueSrc == "value" || !valueSrc) && fieldConfig && fieldConfig.fieldSettings || {}; // useful to take 'validateValue' - const mergedConfig = merge({}, widgetConfig, fieldWidgetProps, valueFieldSettings); + const fieldWidgetConfig = + (fieldConfig && fieldConfig.widgets ? fieldConfig.widgets[widget] : {}) || + {}; + const fieldWidgetProps = fieldWidgetConfig.widgetProps || {}; + const valueFieldSettings = + ((valueSrc == "value" || !valueSrc) && + fieldConfig && + fieldConfig.fieldSettings) || + {}; // useful to take 'validateValue' + const mergedConfig = merge( + {}, + widgetConfig, + fieldWidgetProps, + valueFieldSettings + ); return mergedConfig; }; diff --git a/packages/core/modules/utils/defaultUtils.js b/packages/core/modules/utils/defaultUtils.js index 83138cffe..ff61f8071 100644 --- a/packages/core/modules/utils/defaultUtils.js +++ b/packages/core/modules/utils/defaultUtils.js @@ -1,42 +1,59 @@ import Immutable from "immutable"; import uuid from "./uuid"; -import {getFieldConfig, getOperatorConfig} from "./configUtils"; -import {getNewValueForFieldOp, getFirstField, getFirstOperator} from "../utils/ruleUtils"; +import { getFieldConfig, getOperatorConfig } from "./configUtils"; +import { + getNewValueForFieldOp, + getFirstField, + getFirstOperator, +} from "../utils/ruleUtils"; - -export const defaultField = (config, canGetFirst = true, parentRuleGroupPath = null) => { +export const defaultField = ( + config, + canGetFirst = true, + parentRuleGroupPath = null +) => { return typeof config.settings.defaultField === "function" - ? config.settings.defaultField(parentRuleGroupPath) - : (config.settings.defaultField || (canGetFirst ? getFirstField(config, parentRuleGroupPath) : null)); + ? config.settings.defaultField(parentRuleGroupPath) + : config.settings.defaultField || + (canGetFirst ? getFirstField(config, parentRuleGroupPath) : null); }; export const defaultOperator = (config, field, canGetFirst = true) => { let fieldConfig = getFieldConfig(config, field); - let fieldOperators = fieldConfig && fieldConfig.operators || []; + let fieldOperators = (fieldConfig && fieldConfig.operators) || []; let fieldDefaultOperator = fieldConfig && fieldConfig.defaultOperator; if (!fieldOperators.includes(fieldDefaultOperator)) fieldDefaultOperator = null; if (!fieldDefaultOperator && canGetFirst) fieldDefaultOperator = getFirstOperator(config, field); - let op = typeof config.settings.defaultOperator === "function" - ? config.settings.defaultOperator(field, fieldConfig) : fieldDefaultOperator; + let op = + typeof config.settings.defaultOperator === "function" + ? config.settings.defaultOperator(field, fieldConfig) + : fieldDefaultOperator; return op; }; //used for complex operators like proximity -export const defaultOperatorOptions = (config, operator, field) => { - let operatorConfig = operator ? getOperatorConfig(config, operator, field) : null; - if (!operatorConfig) - return null; //new Immutable.Map(); - return operatorConfig.options ? new Immutable.Map( - operatorConfig.options - && operatorConfig.options.defaults || {} - ) : null; +export const defaultOperatorOptions = (config, operator, field, fieldSrc) => { + let operatorConfig = operator + ? getOperatorConfig(config, operator, field, fieldSrc) + : null; + if (!operatorConfig) return null; //new Immutable.Map(); + return operatorConfig.options + ? new Immutable.Map( + (operatorConfig.options && operatorConfig.options.defaults) || {} + ) + : null; }; -export const defaultRuleProperties = (config, parentRuleGroupPath = null, item = null) => { - let field = null, operator = null; - const {setDefaultFieldAndOp, showErrorMessage} = config.settings; +export const defaultRuleProperties = ( + config, + parentRuleGroupPath = null, + item = null +) => { + let field = null, + operator = null; + const { setDefaultFieldAndOp, showErrorMessage } = config.settings; if (item) { field = item?.properties?.field; operator = item?.properties?.operator; @@ -55,43 +72,54 @@ export const defaultRuleProperties = (config, parentRuleGroupPath = null, item = if (showErrorMessage) { current = current.set("valueError", new Immutable.List()); } - + if (field && operator) { - let {newValue, newValueSrc, newValueType, newValueError} = getNewValueForFieldOp( - config, config, current, field, operator, "operator", false - ); + let { newValue, newValueSrc, newValueType, newValueError } = + getNewValueForFieldOp( + config, + config, + current, + field, + operator, + "operator", + false + ); current = current .set("value", newValue) .set("valueSrc", newValueSrc) .set("valueType", newValueType); if (showErrorMessage) { - current = current - .set("valueError", newValueError); + current = current.set("valueError", newValueError); } } - return current; + return current; }; - export const defaultGroupConjunction = (config, fieldConfig = null) => { fieldConfig = getFieldConfig(config, fieldConfig); // if `fieldConfig` is field name, not config - const conjs = fieldConfig && fieldConfig.conjunctions || Object.keys(config.conjunctions); - if (conjs.length == 1) - return conjs[0]; - return config.settings.defaultGroupConjunction || config.settings.defaultConjunction || conjs[0]; + const conjs = + (fieldConfig && fieldConfig.conjunctions) || + Object.keys(config.conjunctions); + if (conjs.length == 1) return conjs[0]; + return ( + config.settings.defaultGroupConjunction || + config.settings.defaultConjunction || + conjs[0] + ); }; export const defaultConjunction = (config) => config.settings.defaultConjunction || Object.keys(config.conjunctions)[0]; -export const defaultGroupProperties = (config, fieldConfig = null) => new Immutable.Map({ - conjunction: defaultGroupConjunction(config, fieldConfig), - not: false -}); +export const defaultGroupProperties = (config, fieldConfig = null) => + new Immutable.Map({ + conjunction: defaultGroupConjunction(config, fieldConfig), + not: false, + }); export const defaultItemProperties = (config, item) => { - return item && item.type == "group" - ? defaultGroupProperties(config, item?.properties?.field) + return item && item.type == "group" + ? defaultGroupProperties(config, item?.properties?.field) : defaultRuleProperties(config, null, item); }; @@ -99,8 +127,8 @@ export const defaultRule = (id, config) => ({ [id]: new Immutable.Map({ type: "rule", id: id, - properties: defaultRuleProperties(config) - }) + properties: defaultRuleProperties(config), + }), }); export const defaultRoot = (config) => { @@ -108,7 +136,7 @@ export const defaultRoot = (config) => { type: "group", id: uuid(), children1: new Immutable.OrderedMap({ ...defaultRule(uuid(), config) }), - properties: defaultGroupProperties(config) + properties: defaultGroupProperties(config), }); }; diff --git a/packages/core/modules/utils/funcUtils.js b/packages/core/modules/utils/funcUtils.js index fc9dbbf60..3eaad390f 100644 --- a/packages/core/modules/utils/funcUtils.js +++ b/packages/core/modules/utils/funcUtils.js @@ -1,10 +1,9 @@ - -import {getFieldConfig, getFuncConfig} from "../utils/configUtils"; -import {filterValueSourcesForField} from "../utils/ruleUtils"; +import { getFieldConfig, getFuncConfig } from "../utils/configUtils"; +import { filterValueSourcesForField } from "../utils/ruleUtils"; import Immutable from "immutable"; // helpers -const isObject = (v) => (typeof v == "object" && v !== null && !Array.isArray(v)); +const isObject = (v) => typeof v == "object" && v !== null && !Array.isArray(v); /** * @param {*} value @@ -13,10 +12,8 @@ const isObject = (v) => (typeof v == "object" && v !== null && !Array.isArray(v) * @return {* | undefined} - undefined if func value is not complete (missing required arg vals); can return completed value != value */ export const completeValue = (value, valueSrc, config) => { - if (valueSrc == "func") - return completeFuncValue(value, config); - else - return value; + if (valueSrc == "func") return completeFuncValue(value, config); + else return value; }; /** @@ -26,36 +23,46 @@ export const completeValue = (value, valueSrc, config) => { */ export const completeFuncValue = (value, config) => { const _checkFuncValue = (value) => { - if (!value) - return undefined; + if (!value) return undefined; const funcKey = value.get("func"); const funcConfig = funcKey && getFuncConfig(config, funcKey); - if (!funcConfig) - return undefined; + if (!funcConfig) return undefined; let complValue = value; let tmpHasOptional = false; for (const argKey in funcConfig.args) { const argConfig = funcConfig.args[argKey]; - const {valueSources, isOptional, defaultValue} = argConfig; - const filteredValueSources = filterValueSourcesForField(config, valueSources, argConfig); + const { valueSources, isOptional, defaultValue } = argConfig; + const filteredValueSources = filterValueSourcesForField( + config, + valueSources, + argConfig + ); const args = complValue.get("args"); - const argDefaultValueSrc = filteredValueSources.length == 1 ? filteredValueSources[0] : undefined; + const argDefaultValueSrc = + filteredValueSources.length == 1 ? filteredValueSources[0] : undefined; const argVal = args ? args.get(argKey) : undefined; const argValue = argVal ? argVal.get("value") : undefined; - const argValueSrc = (argVal ? argVal.get("valueSrc") : undefined) || argDefaultValueSrc; + const argValueSrc = + (argVal ? argVal.get("valueSrc") : undefined) || argDefaultValueSrc; if (argValue !== undefined) { const completeArgValue = completeValue(argValue, argValueSrc, config); if (completeArgValue === undefined) { return undefined; } else if (completeArgValue !== argValue) { - complValue = complValue.setIn(["args", argKey, "value"], completeArgValue); + complValue = complValue.setIn( + ["args", argKey, "value"], + completeArgValue + ); } if (tmpHasOptional) { // has gap return undefined; } } else if (defaultValue !== undefined && !isObject(defaultValue)) { - complValue = complValue.setIn(["args", argKey, "value"], getDefaultArgValue(argConfig)); + complValue = complValue.setIn( + ["args", argKey, "value"], + getDefaultArgValue(argConfig) + ); complValue = complValue.setIn(["args", argKey, "valueSrc"], "value"); } else if (isOptional) { // optional @@ -71,9 +78,8 @@ export const completeFuncValue = (value, config) => { return _checkFuncValue(value); }; - /** - * @param {Immutable.Map} value + * @param {Immutable.Map} value * @return {array} - [usedFields, badFields] */ const getUsedFieldsInFuncValue = (value, config) => { @@ -87,11 +93,11 @@ const getUsedFieldsInFuncValue = (value, config) => { if (arg.get("valueSrc") == "field") { const rightField = arg.get("value"); if (rightField) { - const rightFieldDefinition = config ? getFieldConfig(config, rightField) : undefined; - if (config && !rightFieldDefinition) - badFields.push(rightField); - else - usedFields.push(rightField); + const rightFieldDefinition = config + ? getFieldConfig(config, rightField) + : undefined; + if (config && !rightFieldDefinition) badFields.push(rightField); + else usedFields.push(rightField); } } else if (arg.get("valueSrc") == "func") { _traverse(arg.get("value")); @@ -104,12 +110,11 @@ const getUsedFieldsInFuncValue = (value, config) => { return [usedFields, badFields]; }; - /** * Used @ FuncWidget - * @param {Immutable.Map} value - * @param {string} funcKey - * @param {object} config + * @param {Immutable.Map} value + * @param {string} funcKey + * @param {object} config */ export const setFunc = (value, funcKey, config) => { const fieldSeparator = config.settings.fieldSeparator; @@ -126,24 +131,36 @@ export const setFunc = (value, funcKey, config) => { if (funcConfig) { for (const argKey in funcConfig.args) { const argConfig = funcConfig.args[argKey]; - const {valueSources, defaultValue} = argConfig; - const filteredValueSources = filterValueSourcesForField(config, valueSources, argConfig); - const firstValueSrc = filteredValueSources.length ? filteredValueSources[0] : undefined; - const defaultValueSrc = defaultValue ? (isObject(defaultValue) && !!defaultValue.func ? "func" : "value") : undefined; + const { valueSources, defaultValue } = argConfig; + const filteredValueSources = filterValueSourcesForField( + config, + valueSources, + argConfig + ); + const firstValueSrc = filteredValueSources.length + ? filteredValueSources[0] + : undefined; + const defaultValueSrc = defaultValue + ? isObject(defaultValue) && !!defaultValue.func + ? "func" + : "value" + : undefined; const argDefaultValueSrc = defaultValueSrc || firstValueSrc; if (defaultValue !== undefined) { - value = value.setIn(["args", argKey, "value"], getDefaultArgValue(argConfig)); + value = value.setIn( + ["args", argKey, "value"], + getDefaultArgValue(argConfig) + ); } if (argDefaultValueSrc) { value = value.setIn(["args", argKey, "valueSrc"], argDefaultValueSrc); } } } - return value; }; -const getDefaultArgValue = ({defaultValue: value}) => { +const getDefaultArgValue = ({ defaultValue: value }) => { if (isObject(value) && !Immutable.Map.isMap(value) && value.func) { return Immutable.fromJS(value, function (k, v) { return Immutable.Iterable.isIndexed(v) ? v.toList() : v.toOrderedMap(); @@ -153,20 +170,25 @@ const getDefaultArgValue = ({defaultValue: value}) => { }; /** -* Used @ FuncWidget -* @param {Immutable.Map} value -* @param {string} argKey -* @param {*} argVal -* @param {object} argConfig -*/ + * Used @ FuncWidget + * @param {Immutable.Map} value + * @param {string} argKey + * @param {*} argVal + * @param {object} argConfig + */ export const setArgValue = (value, argKey, argVal, argConfig, config) => { if (value && value.get("func")) { value = value.setIn(["args", argKey, "value"], argVal); // set default arg value sorce - const {valueSources} = argConfig; - const filteredValueSources = filterValueSourcesForField(config, valueSources, argConfig); - const argDefaultValueSrc = filteredValueSources.length == 1 ? filteredValueSources[0] : undefined; + const { valueSources } = argConfig; + const filteredValueSources = filterValueSourcesForField( + config, + valueSources, + argConfig + ); + const argDefaultValueSrc = + filteredValueSources.length == 1 ? filteredValueSources[0] : undefined; if (argDefaultValueSrc) { value = value.setIn(["args", argKey, "valueSrc"], argDefaultValueSrc); } @@ -175,15 +197,24 @@ export const setArgValue = (value, argKey, argVal, argConfig, config) => { }; /** -* Used @ FuncWidget -* @param {Immutable.Map} value -* @param {string} argKey -* @param {string} argValSrc -* @param {object} argConfig -*/ -export const setArgValueSrc = (value, argKey, argValSrc, _argConfig, _config) => { + * Used @ FuncWidget + * @param {Immutable.Map} value + * @param {string} argKey + * @param {string} argValSrc + * @param {object} argConfig + */ +export const setArgValueSrc = ( + value, + argKey, + argValSrc, + _argConfig, + _config +) => { if (value && value.get("func")) { - value = value.setIn(["args", argKey], new Immutable.Map({valueSrc: argValSrc})); + value = value.setIn( + ["args", argKey], + new Immutable.Map({ valueSrc: argValSrc }) + ); } return value; }; diff --git a/packages/core/modules/utils/ruleUtils.js b/packages/core/modules/utils/ruleUtils.js index b6294f149..10012ac6c 100644 --- a/packages/core/modules/utils/ruleUtils.js +++ b/packages/core/modules/utils/ruleUtils.js @@ -1,17 +1,16 @@ import { - getFieldConfig, getOperatorConfig, getFieldWidgetConfig, getFieldRawConfig + getFieldConfig, + getFuncConfig, + getOperatorConfig, + getFieldWidgetConfig, + getFieldRawConfig, } from "./configUtils"; -import {defaultValue, getFirstDefined} from "../utils/stuff"; +import { defaultValue, getFirstDefined } from "../utils/stuff"; import Immutable from "immutable"; -import {validateValue} from "../utils/validation"; +import { validateValue } from "../utils/validation"; import last from "lodash/last"; -const selectTypes = [ - "select", - "multiselect", - "treeselect", - "treemultiselect", -]; +const selectTypes = ["select", "multiselect", "treeselect", "treemultiselect"]; /** * @param {object} config @@ -22,9 +21,18 @@ const selectTypes = [ * @param {string} changedProp * @return {object} - {canReuseValue, newValue, newValueSrc, newValueType, newValueError} */ -export const getNewValueForFieldOp = function (config, oldConfig = null, current, newField, newOperator, changedProp = null, canFix = true, isEndValue = false) { - if (!oldConfig) - oldConfig = config; +export const getNewValueForFieldOp = function ( + config, + oldConfig = null, + current, + newField, + newOperator, + changedProp = null, + canFix = true, + isEndValue = false +) { + if (!oldConfig) oldConfig = config; + const currentFieldSrc = current.get("fieldSrc"); const currentField = current.get("field"); const currentOperator = current.get("operator"); const currentValue = current.get("value"); @@ -33,59 +41,143 @@ export const getNewValueForFieldOp = function (config, oldConfig = null, current const currentAsyncListValues = current.get("asyncListValues"); //const isValidatingTree = (changedProp === null); - const {convertableWidgets, clearValueOnChangeField, clearValueOnChangeOp, showErrorMessage} = config.settings; + const { + convertableWidgets, + clearValueOnChangeField, + clearValueOnChangeOp, + showErrorMessage, + } = config.settings; //const currentOperatorConfig = getOperatorConfig(oldConfig, currentOperator, currentField); - const newOperatorConfig = getOperatorConfig(config, newOperator, newField); + const newOperatorConfig = getOperatorConfig( + config, + newOperator, + newField, + currentFieldSrc + ); //const currentOperatorCardinality = currentOperator ? defaultValue(currentOperatorConfig.cardinality, 1) : null; - const operatorCardinality = newOperator ? defaultValue(newOperatorConfig.cardinality, 1) : null; - const currentFieldConfig = getFieldConfig(oldConfig, currentField); - const newFieldConfig = getFieldConfig(config, newField); - - let canReuseValue = currentField && currentOperator && newOperator && currentValue != undefined - && (!changedProp - || changedProp == "field" && !clearValueOnChangeField - || changedProp == "operator" && !clearValueOnChangeOp) - && (currentFieldConfig && newFieldConfig && currentFieldConfig.type == newFieldConfig.type); - if (canReuseValue && selectTypes.includes(currentFieldConfig.type) && changedProp == "field") { + const operatorCardinality = newOperator + ? defaultValue(newOperatorConfig.cardinality, 1) + : null; + const currentFieldConfig = getFieldConfig( + oldConfig, + currentField, + currentFieldSrc + ); + const newFieldConfig = getFieldConfig(config, newField, currentFieldSrc); + + let canReuseValue = + currentField && + currentOperator && + newOperator && + currentValue != undefined && + (!changedProp || + (changedProp == "field" && !clearValueOnChangeField) || + (changedProp == "operator" && !clearValueOnChangeOp)) && + currentFieldConfig && + newFieldConfig && + currentFieldConfig.type == newFieldConfig.type; + if ( + canReuseValue && + selectTypes.includes(currentFieldConfig.type) && + changedProp == "field" + ) { // different fields of select types has different listValues canReuseValue = false; } // compare old & new widgets - for (let i = 0 ; i < operatorCardinality ; i++) { + for (let i = 0; i < operatorCardinality; i++) { const vs = currentValueSrc.get(i) || null; - const currentWidget = getWidgetForFieldOp(oldConfig, currentField, currentOperator, vs); - const newWidget = getWidgetForFieldOp(config, newField, newOperator, vs); + const currentWidget = getWidgetForFieldOp( + oldConfig, + currentField, + currentOperator, + vs, + currentFieldSrc + ); + const newWidget = getWidgetForFieldOp( + config, + newField, + newOperator, + vs, + currentFieldSrc + ); // need to also check value widgets if we changed operator and current value source was 'field' // cause for select type op '=' requires single value and op 'in' requires array value - const currentValueWidget = vs == "value" ? currentWidget : getWidgetForFieldOp(oldConfig, currentField, currentOperator, "value"); - const newValueWidget = vs == "value" ? newWidget : getWidgetForFieldOp(config, newField, newOperator, "value"); - - const canReuseWidget = newValueWidget == currentValueWidget || (convertableWidgets[currentValueWidget] || []).includes(newValueWidget); - if (!canReuseWidget) - canReuseValue = false; + const currentValueWidget = + vs == "value" + ? currentWidget + : getWidgetForFieldOp( + oldConfig, + currentField, + currentOperator, + "value", + currentFieldSrc + ); + const newValueWidget = + vs == "value" + ? newWidget + : getWidgetForFieldOp( + config, + newField, + newOperator, + "value", + currentFieldSrc + ); + + const canReuseWidget = + newValueWidget == currentValueWidget || + (convertableWidgets[currentValueWidget] || []).includes(newValueWidget); + if (!canReuseWidget) canReuseValue = false; } - if (currentOperator != newOperator && [currentOperator, newOperator].includes("proximity")) + if ( + currentOperator != newOperator && + [currentOperator, newOperator].includes("proximity") + ) canReuseValue = false; - const firstWidgetConfig = getFieldWidgetConfig(config, newField, newOperator, null, currentValueSrc.first()); - const valueSources = getValueSourcesForFieldOp(config, newField, newOperator); - + const firstWidgetConfig = getFieldWidgetConfig( + config, + newField, + newOperator, + null, + currentValueSrc.first(), + currentFieldSrc + ); + const valueSources = getValueSourcesForFieldOp( + config, + newField, + newOperator, + null, + null, + currentFieldSrc + ); + let valueFixes = {}; - let valueErrors = Array.from({length: operatorCardinality}, () => null); + let valueErrors = Array.from({ length: operatorCardinality }, () => null); if (canReuseValue) { - for (let i = 0 ; i < operatorCardinality ; i++) { + for (let i = 0; i < operatorCardinality; i++) { const v = currentValue.get(i); const vType = currentValueType.get(i) || null; const vSrc = currentValueSrc.get(i) || null; - let isValidSrc = (valueSources.find(v => v == vSrc) != null); - if (!isValidSrc && i > 0 && vSrc == null) - isValidSrc = true; // make exception for range widgets (when changing op from '==' to 'between') + let isValidSrc = valueSources.find((v) => v == vSrc) != null; + if (!isValidSrc && i > 0 && vSrc == null) isValidSrc = true; // make exception for range widgets (when changing op from '==' to 'between') const asyncListValues = currentAsyncListValues; const [validateError, fixedValue] = validateValue( - config, newField, newField, newOperator, v, vType, vSrc, asyncListValues, canFix, isEndValue + config, + newField, + newField, + newOperator, + v, + vType, + vSrc, + asyncListValues, + canFix, + isEndValue, + true, + currentFieldSrc ); const isValid = !validateError; // Allow bad value with error message @@ -94,8 +186,12 @@ export const getNewValueForFieldOp = function (config, oldConfig = null, current // For bad multiselect value we have both error message + fixed value. // If we show error message, it will gone on next tree validation const fixValue = fixedValue !== v; - const dropValue = !isValidSrc || !isValid && (changedProp == "field" || !showErrorMessage && !fixValue); - const showValueError = !!validateError && showErrorMessage && !dropValue && !fixValue; + const dropValue = + !isValidSrc || + (!isValid && + (changedProp == "field" || (!showErrorMessage && !fixValue))); + const showValueError = + !!validateError && showErrorMessage && !dropValue && !fixValue; if (showValueError) { valueErrors[i] = validateError; } @@ -110,45 +206,59 @@ export const getNewValueForFieldOp = function (config, oldConfig = null, current } // reuse value OR get defaultValue for cardinality 1 (it means default range values is not supported yet, todo) - let newValue = null, newValueSrc = null, newValueType = null, newValueError = null; - newValue = new Immutable.List(Array.from({length: operatorCardinality}, (_ignore, i) => { - let v = undefined; - if (canReuseValue) { - if (i < currentValue.size) { - v = currentValue.get(i); - if (valueFixes[i] !== undefined) { - v = valueFixes[i]; + let newValue = null, + newValueSrc = null, + newValueType = null, + newValueError = null; + newValue = new Immutable.List( + Array.from({ length: operatorCardinality }, (_ignore, i) => { + let v = undefined; + if (canReuseValue) { + if (i < currentValue.size) { + v = currentValue.get(i); + if (valueFixes[i] !== undefined) { + v = valueFixes[i]; + } } + } else if (operatorCardinality == 1) { + v = getFirstDefined([ + newFieldConfig?.defaultValue, + newFieldConfig?.fieldSettings?.defaultValue, + firstWidgetConfig?.defaultValue, + ]); } - } else if (operatorCardinality == 1) { - v = getFirstDefined([ - newFieldConfig?.defaultValue, - newFieldConfig?.fieldSettings?.defaultValue, - firstWidgetConfig?.defaultValue - ]); - } - return v; - })); - - newValueSrc = new Immutable.List(Array.from({length: operatorCardinality}, (_ignore, i) => { - let vs = null; - if (canReuseValue) { - if (i < currentValueSrc.size) - vs = currentValueSrc.get(i); - } else if (valueSources.length == 1) { - vs = valueSources[0]; - } else if (valueSources.length > 1) { - vs = valueSources[0]; - } - return vs; - })); + return v; + }) + ); + + newValueSrc = new Immutable.List( + Array.from({ length: operatorCardinality }, (_ignore, i) => { + let vs = null; + if (canReuseValue) { + if (i < currentValueSrc.size) vs = currentValueSrc.get(i); + } else if (valueSources.length == 1) { + vs = valueSources[0]; + } else if (valueSources.length > 1) { + vs = valueSources[0]; + } + return vs; + }) + ); if (showErrorMessage) { - if (newOperatorConfig && newOperatorConfig.validateValues && newValueSrc.toJS().filter(vs => vs == "value" || vs == null).length == operatorCardinality) { + if ( + newOperatorConfig && + newOperatorConfig.validateValues && + newValueSrc.toJS().filter((vs) => vs == "value" || vs == null).length == + operatorCardinality + ) { // last element in `valueError` list is for range validation error - const jsValues = firstWidgetConfig && firstWidgetConfig.toJS - ? newValue.toJS().map(v => firstWidgetConfig.toJS(v, firstWidgetConfig)) - : newValue.toJS(); + const jsValues = + firstWidgetConfig && firstWidgetConfig.toJS + ? newValue + .toJS() + .map((v) => firstWidgetConfig.toJS(v, firstWidgetConfig)) + : newValue.toJS(); const rangeValidateError = newOperatorConfig.validateValues(jsValues); if (showErrorMessage) { valueErrors.push(rangeValidateError); @@ -157,30 +267,54 @@ export const getNewValueForFieldOp = function (config, oldConfig = null, current newValueError = new Immutable.List(valueErrors); } - newValueType = new Immutable.List(Array.from({length: operatorCardinality}, (_ignore, i) => { - let vt = null; - if (canReuseValue) { - if (i < currentValueType.size) - vt = currentValueType.get(i); - } else if (operatorCardinality == 1 && firstWidgetConfig && firstWidgetConfig.type !== undefined) { - vt = firstWidgetConfig.type; - } else if (operatorCardinality == 1 && newFieldConfig && newFieldConfig.type !== undefined) { - vt = newFieldConfig.type == "!group" ? "number" : newFieldConfig.type; - } - return vt; - })); - - return {canReuseValue, newValue, newValueSrc, newValueType, newValueError, operatorCardinality}; + newValueType = new Immutable.List( + Array.from({ length: operatorCardinality }, (_ignore, i) => { + let vt = null; + if (canReuseValue) { + if (i < currentValueType.size) vt = currentValueType.get(i); + } else if ( + operatorCardinality == 1 && + firstWidgetConfig && + firstWidgetConfig.type !== undefined + ) { + vt = firstWidgetConfig.type; + } else if ( + operatorCardinality == 1 && + newFieldConfig && + newFieldConfig.type !== undefined + ) { + vt = newFieldConfig.type == "!group" ? "number" : newFieldConfig.type; + } + return vt; + }) + ); + + return { + canReuseValue, + newValue, + newValueSrc, + newValueType, + newValueError, + operatorCardinality, + }; }; export const getFirstField = (config, parentRuleGroupPath = null) => { const fieldSeparator = config.settings.fieldSeparator; - const parentPathArr = typeof parentRuleGroupPath == "string" ? parentRuleGroupPath.split(fieldSeparator) : parentRuleGroupPath; - const parentField = parentRuleGroupPath ? getFieldRawConfig(config, parentRuleGroupPath) : config; - - let firstField = parentField, key = null, keysPath = []; + const parentPathArr = + typeof parentRuleGroupPath == "string" + ? parentRuleGroupPath.split(fieldSeparator) + : parentRuleGroupPath; + const parentField = parentRuleGroupPath + ? getFieldRawConfig(config, parentRuleGroupPath) + : config; + + let firstField = parentField, + key = null, + keysPath = []; do { - const subfields = firstField === config ? config.fields : firstField.subfields; + const subfields = + firstField === config ? config.fields : firstField.subfields; if (!subfields || !Object.keys(subfields).length) { firstField = key = null; break; @@ -192,8 +326,8 @@ export const getFirstField = (config, parentRuleGroupPath = null) => { return (parentPathArr || []).concat(keysPath).join(fieldSeparator); }; -export const getOperatorsForField = (config, field) => { - const fieldConfig = getFieldConfig(config, field); +export const getOperatorsForField = (config, field, fieldSrc) => { + const fieldConfig = getFieldConfig(config, field, fieldSrc); const fieldOps = fieldConfig ? fieldConfig.operators : []; return fieldOps; }; @@ -204,15 +338,13 @@ export const getFirstOperator = (config, field) => { }; export const getFieldPath = (field, config, onlyKeys = false) => { - if (!field) - return null; + if (!field) return null; const fieldSeparator = config.settings.fieldSeparator; const parts = Array.isArray(field) ? field : field.split(fieldSeparator); - if (onlyKeys) - return parts; + if (onlyKeys) return parts; else return parts - .map((_curr, ind, arr) => arr.slice(0, ind+1)) + .map((_curr, ind, arr) => arr.slice(0, ind + 1)) .map((parts) => parts.join(fieldSeparator)); }; @@ -220,96 +352,133 @@ export const getFuncPathLabels = (field, config, parentField = null) => { return getFieldPathLabels(field, config, parentField, "funcs", "subfields"); }; -export const getFieldPathLabels = (field, config, parentField = null, fieldsKey = "fields", subfieldsKey = "subfields") => { - if (!field) - return null; +export const getFieldPathLabels = ( + field, + config, + parentField = null, + fieldsKey = "fields", + subfieldsKey = "subfields", + fieldSrc = null +) => { + if (!field) return null; const fieldSeparator = config.settings.fieldSeparator; - const parts = Array.isArray(field) ? field : field.split(fieldSeparator); - const parentParts = parentField ? (Array.isArray(parentField) ? parentField : parentField.split(fieldSeparator)) : []; + const parts = + fieldSrc === "func" + ? [field] + : Array.isArray(field) + ? field + : field.split(fieldSeparator); + const parentParts = parentField + ? Array.isArray(parentField) + ? parentField + : parentField.split(fieldSeparator) + : []; return parts .slice(parentParts.length) - .map((_curr, ind, arr) => arr.slice(0, ind+1)) + .map((_curr, ind, arr) => arr.slice(0, ind + 1)) .map((parts) => [...parentParts, ...parts].join(fieldSeparator)) - .map(part => { + .map((part) => { const cnf = getFieldRawConfig(config, part, fieldsKey, subfieldsKey); - return cnf && cnf.label || cnf && last(part.split(fieldSeparator)); + return (cnf && cnf.label) || (cnf && last(part.split(fieldSeparator))); }) - .filter(label => label != null); + .filter((label) => label != null); }; export const getFieldPartsConfigs = (field, config, parentField = null) => { - if (!field) - return null; - const parentFieldDef = parentField && getFieldRawConfig(config, parentField) || null; + if (!field) return null; + const parentFieldDef = + (parentField && getFieldRawConfig(config, parentField)) || null; const fieldSeparator = config.settings.fieldSeparator; const parts = Array.isArray(field) ? field : field.split(fieldSeparator); - const parentParts = parentField ? (Array.isArray(parentField) ? parentField : parentField.split(fieldSeparator)) : []; + const parentParts = parentField + ? Array.isArray(parentField) + ? parentField + : parentField.split(fieldSeparator) + : []; return parts .slice(parentParts.length) - .map((_curr, ind, arr) => arr.slice(0, ind+1)) + .map((_curr, ind, arr) => arr.slice(0, ind + 1)) .map((parts) => ({ part: [...parentParts, ...parts].join(fieldSeparator), - key: parts[parts.length - 1] + key: parts[parts.length - 1], })) - .map(({part, key}) => { + .map(({ part, key }) => { const cnf = getFieldRawConfig(config, part); - return {key, cnf}; + return { key, cnf }; }) - .map(({key, cnf}, ind, arr) => { + .map(({ key, cnf }, ind, arr) => { const parentCnf = ind > 0 ? arr[ind - 1].cnf : parentFieldDef; return [key, cnf, parentCnf]; }); }; -export const getValueLabel = (config, field, operator, delta, valueSrc = null, isSpecialRange = false) => { - const isFuncArg = field && typeof field == "object" && !!field.func && !!field.arg; - const {showLabels} = config.settings; - const fieldConfig = getFieldConfig(config, field); - const fieldWidgetConfig = getFieldWidgetConfig(config, field, operator, null, valueSrc) || {}; - const mergedOpConfig = getOperatorConfig(config, operator, field) || {}; - +export const getValueLabel = ( + config, + field, + operator, + delta, + valueSrc = null, + isSpecialRange = false, + fieldSrc = null +) => { + const isFuncArg = + field && typeof field == "object" && !!field.func && !!field.arg; + const { showLabels } = config.settings; + const fieldConfig = getFieldConfig(config, field, fieldSrc); + const fieldWidgetConfig = + getFieldWidgetConfig(config, field, operator, null, valueSrc, fieldSrc) || + {}; + const mergedOpConfig = + getOperatorConfig(config, operator, field, fieldSrc) || {}; + const cardinality = isSpecialRange ? 1 : mergedOpConfig.cardinality; let ret = null; if (cardinality > 1) { - const valueLabels = fieldWidgetConfig.valueLabels || mergedOpConfig.valueLabels; - if (valueLabels) - ret = valueLabels[delta]; + const valueLabels = + fieldWidgetConfig.valueLabels || mergedOpConfig.valueLabels; + if (valueLabels) ret = valueLabels[delta]; if (ret && typeof ret != "object") { - ret = {label: ret, placeholder: ret}; + ret = { label: ret, placeholder: ret }; } if (!ret) { ret = { - label: config.settings.valueLabel + " " + (delta+1), - placeholder: config.settings.valuePlaceholder + " " + (delta+1), + label: config.settings.valueLabel + " " + (delta + 1), + placeholder: config.settings.valuePlaceholder + " " + (delta + 1), }; } } else { let label = fieldWidgetConfig.valueLabel; let placeholder = fieldWidgetConfig.valuePlaceholder; if (isFuncArg) { - if (!label) - label = fieldConfig.label || field.arg; + if (!label) label = fieldConfig.label || field.arg; if (!placeholder && !showLabels) placeholder = fieldConfig.label || field.arg; } ret = { - label: label || config.settings.valueLabel, + label: label || config.settings.valueLabel, placeholder: placeholder || config.settings.valuePlaceholder, }; } return ret; }; -function _getWidgetsAndSrcsForFieldOp (config, field, operator = null, valueSrc = null) { +function _getWidgetsAndSrcsForFieldOp( + config, + field, + operator = null, + valueSrc = null, + fieldSrc = null +) { let widgets = []; let valueSrcs = []; - if (!field) - return {widgets, valueSrcs}; - const isFuncArg = typeof field == "object" && (!!field.func && !!field.arg || field._isFuncArg); - const fieldConfig = getFieldConfig(config, field); + if (!field) return { widgets, valueSrcs }; + const isFuncArg = + typeof field == "object" && + ((!!field.func && !!field.arg) || field._isFuncArg); + const fieldConfig = getFieldConfig(config, field, fieldSrc); const opConfig = operator ? config.operators[operator] : null; - + if (fieldConfig && fieldConfig.widgets) { for (const widget in fieldConfig.widgets) { const widgetConfig = fieldConfig.widgets[widget]; @@ -319,27 +488,42 @@ function _getWidgetsAndSrcsForFieldOp (config, field, operator = null, valueSrc const widgetValueSrc = config.widgets[widget].valueSrc || "value"; let canAdd = true; if (widget == "field") { - canAdd = canAdd && filterValueSourcesForField(config, ["field"], fieldConfig).length > 0; + canAdd = + canAdd && + filterValueSourcesForField(config, ["field"], fieldConfig).length > 0; } if (widget == "func") { - canAdd = canAdd && filterValueSourcesForField(config, ["func"], fieldConfig).length > 0; + canAdd = + canAdd && + filterValueSourcesForField(config, ["func"], fieldConfig).length > 0; } // If can't check operators, don't add // Func args don't have operators - if (valueSrc == "value" && !widgetConfig.operators && !isFuncArg && field != "!case_value") + if ( + valueSrc == "value" && + !widgetConfig.operators && + !isFuncArg && + field != "!case_value" + ) canAdd = false; if (widgetConfig.operators && operator) canAdd = canAdd && widgetConfig.operators.indexOf(operator) != -1; if (valueSrc && valueSrc != widgetValueSrc && valueSrc != "const") canAdd = false; - if (opConfig && opConfig.cardinality == 0 && (widgetValueSrc != "value")) + if (opConfig && opConfig.cardinality == 0 && widgetValueSrc != "value") canAdd = false; if (canAdd) { widgets.push(widget); - let canAddValueSrc = fieldConfig.valueSources && fieldConfig.valueSources.indexOf(widgetValueSrc) != -1; - if (opConfig && opConfig.valueSources && opConfig.valueSources.indexOf(widgetValueSrc) == -1) + let canAddValueSrc = + fieldConfig.valueSources && + fieldConfig.valueSources.indexOf(widgetValueSrc) != -1; + if ( + opConfig && + opConfig.valueSources && + opConfig.valueSources.indexOf(widgetValueSrc) == -1 + ) canAddValueSrc = false; - if (canAddValueSrc && !valueSrcs.find(v => v == widgetValueSrc)) + if (canAddValueSrc && !valueSrcs.find((v) => v == widgetValueSrc)) valueSrcs.push(widgetValueSrc); } } @@ -349,7 +533,7 @@ function _getWidgetsAndSrcsForFieldOp (config, field, operator = null, valueSrc let wg = 0; if (fieldConfig.preferWidgets) { if (fieldConfig.preferWidgets.includes(w)) - wg += (10 - fieldConfig.preferWidgets.indexOf(w)); + wg += 10 - fieldConfig.preferWidgets.indexOf(w); } else if (w == fieldConfig.mainWidget) { wg += 100; } @@ -362,31 +546,53 @@ function _getWidgetsAndSrcsForFieldOp (config, field, operator = null, valueSrc return wg; }; - widgets.sort((w1, w2) => (widgetWeight(w2) - widgetWeight(w1))); - - return {widgets, valueSrcs}; + widgets.sort((w1, w2) => widgetWeight(w2) - widgetWeight(w1)); + return { widgets, valueSrcs }; } -export const getWidgetsForFieldOp = (config, field, operator, valueSrc = null) => { - const {widgets} = _getWidgetsAndSrcsForFieldOp(config, field, operator, valueSrc); +export const getWidgetsForFieldOp = ( + config, + field, + operator, + valueSrc = null, + fieldSrc = null +) => { + const { widgets } = _getWidgetsAndSrcsForFieldOp( + config, + field, + operator, + valueSrc, + fieldSrc + ); return widgets; }; -export const filterValueSourcesForField = (config, valueSrcs, fieldDefinition) => { - if (!fieldDefinition) - return valueSrcs; - return valueSrcs.filter(vs => { +export const filterValueSourcesForField = ( + config, + valueSrcs, + fieldDefinition +) => { + if (!fieldDefinition) return valueSrcs; + return valueSrcs.filter((vs) => { let canAdd = true; if (vs == "field") { if (config._fieldsCntByType) { // tip: LHS field can be used as arg in RHS function const minCnt = fieldDefinition._isFuncArg ? 0 : 1; - canAdd = canAdd && config._fieldsCntByType[fieldDefinition.type] > minCnt; + canAdd = + canAdd && + config._fieldsCntByType[ + fieldDefinition.type ?? fieldDefinition.returnType + ] > minCnt; } } if (vs == "func") { if (config._funcsCntByType) - canAdd = canAdd && !!config._funcsCntByType[fieldDefinition.type]; + canAdd = + canAdd && + !!config._funcsCntByType[ + fieldDefinition.type ?? fieldDefinition.returnType + ]; if (fieldDefinition.funcs) canAdd = canAdd && fieldDefinition.funcs.length > 0; } @@ -394,27 +600,56 @@ export const filterValueSourcesForField = (config, valueSrcs, fieldDefinition) = }); }; -export const getValueSourcesForFieldOp = (config, field, operator, fieldDefinition = null, leftFieldForFunc = null) => { - const {valueSrcs} = _getWidgetsAndSrcsForFieldOp(config, field, operator, null); - const filteredValueSrcs = filterValueSourcesForField(config, valueSrcs, fieldDefinition); +export const getValueSourcesForFieldOp = ( + config, + field, + operator, + fieldDefinition = null, + leftFieldForFunc = null, + fieldSrc = null +) => { + const { valueSrcs } = _getWidgetsAndSrcsForFieldOp( + config, + field, + operator, + null, + fieldSrc + ); + const filteredValueSrcs = filterValueSourcesForField( + config, + valueSrcs, + fieldDefinition + ); return filteredValueSrcs; }; -export const getWidgetForFieldOp = (config, field, operator, valueSrc = null) => { - const {widgets} = _getWidgetsAndSrcsForFieldOp(config, field, operator, valueSrc); +export const getWidgetForFieldOp = ( + config, + field, + operator, + valueSrc = null, + fieldSrc = null +) => { + const { widgets } = _getWidgetsAndSrcsForFieldOp( + config, + field, + operator, + valueSrc, + fieldSrc + ); let widget = null; - if (widgets.length) - widget = widgets[0]; + if (widgets.length) widget = widgets[0]; return widget; }; export const formatFieldName = (field, config, meta, parentField = null) => { if (!field) return; const fieldDef = getFieldConfig(config, field) || {}; - const {fieldSeparator} = config.settings; + const { fieldSeparator } = config.settings; const fieldParts = Array.isArray(field) ? field : field.split(fieldSeparator); let fieldName = Array.isArray(field) ? field.join(fieldSeparator) : field; - if (fieldDef.tableName) { // legacy + if (fieldDef.tableName) { + // legacy const fieldPartsCopy = [...fieldParts]; fieldPartsCopy[0] = fieldDef.tableName; fieldName = fieldPartsCopy.join(fieldSeparator); @@ -431,9 +666,10 @@ export const formatFieldName = (field, config, meta, parentField = null) => { if (fieldName.indexOf(parentFieldName + fieldSeparator) == 0) { fieldName = fieldName.slice((parentFieldName + fieldSeparator).length); } else { - meta.errors.push(`Can't cut group ${parentFieldName} from field ${fieldName}`); + meta.errors.push( + `Can't cut group ${parentFieldName} from field ${fieldName}` + ); } } return fieldName; }; - diff --git a/packages/core/modules/utils/validation.js b/packages/core/modules/utils/validation.js index 48a0a2d3b..baff4c6f2 100644 --- a/packages/core/modules/utils/validation.js +++ b/packages/core/modules/utils/validation.js @@ -1,27 +1,29 @@ import { - getFieldConfig, getOperatorConfig, getFieldWidgetConfig, getFuncConfig, + getFieldConfig, + getOperatorConfig, + getFieldWidgetConfig, + getFuncConfig, } from "./configUtils"; -import {getOperatorsForField, getWidgetForFieldOp, getNewValueForFieldOp} from "../utils/ruleUtils"; -import {defaultValue, deepEqual, logger} from "../utils/stuff"; -import {getItemInListValues} from "../utils/listValues"; -import {defaultOperatorOptions} from "../utils/defaultUtils"; -import {fixPathsInTree} from "../utils/treeUtils"; +import { + getOperatorsForField, + getWidgetForFieldOp, + getNewValueForFieldOp, +} from "../utils/ruleUtils"; +import { defaultValue, deepEqual, logger } from "../utils/stuff"; +import { getItemInListValues } from "../utils/listValues"; +import { defaultOperatorOptions } from "../utils/defaultUtils"; +import { fixPathsInTree } from "../utils/treeUtils"; import omit from "lodash/omit"; import { List } from "immutable"; - const typeOf = (v) => { - if (typeof v == "object" && v !== null && Array.isArray(v)) - return "array"; - else - return (typeof v); + if (typeof v == "object" && v !== null && Array.isArray(v)) return "array"; + else return typeof v; }; const isTypeOf = (v, type) => { - if (typeOf(v) == type) - return true; - if (type == "number" && !isNaN(v)) - return true; //can be casted + if (typeOf(v) == type) return true; + if (type == "number" && !isNaN(v)) return true; //can be casted return false; }; @@ -31,7 +33,14 @@ export const validateAndFixTree = (newTree, _oldTree, newConfig, oldConfig) => { return tree; }; -export const validateTree = (tree, _oldTree, config, oldConfig, removeEmptyGroups, removeIncompleteRules) => { +export const validateTree = ( + tree, + _oldTree, + config, + oldConfig, + removeEmptyGroups, + removeIncompleteRules +) => { if (removeEmptyGroups === undefined) { removeEmptyGroups = config.settings.removeEmptyGroupsOnLoad; } @@ -39,16 +48,26 @@ export const validateTree = (tree, _oldTree, config, oldConfig, removeEmptyGroup removeIncompleteRules = config.settings.removeIncompleteRulesOnLoad; } const c = { - config, oldConfig, removeEmptyGroups, removeIncompleteRules + config, + oldConfig, + removeEmptyGroups, + removeIncompleteRules, }; return validateItem(tree, [], null, {}, c); }; -function validateItem (item, path, itemId, meta, c) { +function validateItem(item, path, itemId, meta, c) { const type = item.get("type"); const children = item.get("children1"); - if ((type === "group" || type === "rule_group" || type == "case_group" || type == "switch_group") && children && children.size) { + if ( + (type === "group" || + type === "rule_group" || + type == "case_group" || + type == "switch_group") && + children && + children.size + ) { return validateGroup(item, path, itemId, meta, c); } else if (type === "rule") { return validateRule(item, path, itemId, meta, c); @@ -57,8 +76,8 @@ function validateItem (item, path, itemId, meta, c) { } } -function validateGroup (item, path, itemId, meta, c) { - const {removeEmptyGroups} = c; +function validateGroup(item, path, itemId, meta, c) { + const { removeEmptyGroups } = c; let id = item.get("id"); let children = item.get("children1"); const oldChildren = children; @@ -71,30 +90,29 @@ function validateGroup (item, path, itemId, meta, c) { //validate children let submeta = {}; - children = children - .map( (currentChild, childId) => validateItem(currentChild, path.concat(id), childId, submeta, c) ); + children = children.map((currentChild, childId) => + validateItem(currentChild, path.concat(id), childId, submeta, c) + ); if (removeEmptyGroups) - children = children.filter((currentChild) => (currentChild != undefined)); - let sanitized = submeta.sanitized || (oldChildren.size != children.size); + children = children.filter((currentChild) => currentChild != undefined); + let sanitized = submeta.sanitized || oldChildren.size != children.size; if (!children.size && removeEmptyGroups && path.length) { sanitized = true; item = undefined; } - if (sanitized) - meta.sanitized = true; - if (sanitized && item) - item = item.set("children1", children); + if (sanitized) meta.sanitized = true; + if (sanitized && item) item = item.set("children1", children); return item; } - -function validateRule (item, path, itemId, meta, c) { - const {removeIncompleteRules, config, oldConfig} = c; - const {showErrorMessage} = config.settings; +function validateRule(item, path, itemId, meta, c) { + const { removeIncompleteRules, config, oldConfig } = c; + const { showErrorMessage } = config.settings; let id = item.get("id"); let properties = item.get("properties"); let field = properties.get("field") || null; + let fieldSrc = properties.get("fieldSrc") || null; let operator = properties.get("operator") || null; let operatorOptions = properties.get("operatorOptions"); let valueSrc = properties.get("valueSrc"); @@ -117,13 +135,18 @@ function validateRule (item, path, itemId, meta, c) { } //validate field - const fieldDefinition = field ? getFieldConfig(config, field) : null; + const fieldDefinition = field + ? getFieldConfig(config, field, fieldSrc) + : null; if (field && !fieldDefinition) { logger.warn(`No config for field ${field}`); field = null; } if (field == null) { - properties = ["operator", "operatorOptions", "valueSrc", "value"].reduce((map, key) => map.delete(key), properties); + properties = ["operator", "operatorOptions", "valueSrc", "value"].reduce( + (map, key) => map.delete(key), + properties + ); operator = null; } @@ -134,12 +157,14 @@ function validateRule (item, path, itemId, meta, c) { console.info(`Fixed operator ${properties.get("operator")} to ${operator}`); properties = properties.set("operator", operator); } - const operatorDefinition = operator ? getOperatorConfig(config, operator, field) : null; + const operatorDefinition = operator + ? getOperatorConfig(config, operator, field, fieldSrc) + : null; if (operator && !operatorDefinition) { console.warn(`No config for operator ${operator}`); operator = null; } - const availOps = field ? getOperatorsForField(config, field) : []; + const availOps = field ? getOperatorsForField(config, field, fieldSrc) : []; if (!availOps) { console.warn(`Type of field ${field} is not supported`); operator = null; @@ -147,7 +172,11 @@ function validateRule (item, path, itemId, meta, c) { if (operator == "is_empty" || operator == "is_not_empty") { // Backward compatibility: is_empty #494 operator = operator == "is_empty" ? "is_null" : "is_not_null"; - console.info(`Fixed operator ${properties.get("operator")} to ${operator} for ${field}`); + console.info( + `Fixed operator ${properties.get( + "operator" + )} to ${operator} for ${field}` + ); properties = properties.set("operator", operator); } else { console.warn(`Operator ${operator} is not supported for field ${field}`); @@ -162,8 +191,10 @@ function validateRule (item, path, itemId, meta, c) { //validate operator options operatorOptions = properties.get("operatorOptions"); - let _operatorCardinality = operator ? defaultValue(operatorDefinition.cardinality, 1) : null; - if (!operator || operatorOptions && !operatorDefinition.options) { + let _operatorCardinality = operator + ? defaultValue(operatorDefinition.cardinality, 1) + : null; + if (!operator || (operatorOptions && !operatorDefinition.options)) { operatorOptions = null; properties = properties.delete("operatorOptions"); } else if (operator && !operatorOptions && operatorDefinition.options) { @@ -176,7 +207,16 @@ function validateRule (item, path, itemId, meta, c) { value = properties.get("value"); const canFix = !showErrorMessage; const isEndValue = true; - let {newValue, newValueSrc, newValueError} = getNewValueForFieldOp(config, oldConfig, properties, field, operator, null, canFix, isEndValue); + let { newValue, newValueSrc, newValueError } = getNewValueForFieldOp( + config, + oldConfig, + properties, + field, + operator, + null, + canFix, + isEndValue + ); value = newValue; valueSrc = newValueSrc; valueError = newValueError; @@ -196,59 +236,106 @@ function validateRule (item, path, itemId, meta, c) { }; const sanitized = !deepEqual(oldSerialized, newSerialized); const isComplete = field && operator && value && !value.includes(undefined); - if (sanitized) - meta.sanitized = true; - if (!isComplete && removeIncompleteRules) - item = undefined; - else if (sanitized) - item = item.set("properties", properties); + if (sanitized) meta.sanitized = true; + if (!isComplete && removeIncompleteRules) item = undefined; + else if (sanitized) item = item.set("properties", properties); return item; } - /** - * + * * @param {bool} canFix true is useful for func values to remove bad args * @param {bool} isEndValue false if value is in process of editing by user * @param {bool} isRawValue false is used only internally from validateFuncValue * @return {array} [validError, fixedValue] - if validError === null and canFix == true, fixedValue can differ from value if was fixed */ -export const validateValue = (config, leftField, field, operator, value, valueType, valueSrc, asyncListValues, canFix = false, isEndValue = false, isRawValue = true) => { +export const validateValue = ( + config, + leftField, + field, + operator, + value, + valueType, + valueSrc, + asyncListValues, + canFix = false, + isEndValue = false, + isRawValue = true, + fieldSrc +) => { let validError = null; let fixedValue = value; if (value != null) { if (valueSrc == "field") { - [validError, fixedValue] = validateFieldValue(leftField, field, value, valueSrc, valueType, asyncListValues, config, operator, isEndValue, canFix); + [validError, fixedValue] = validateFieldValue( + leftField, + field, + value, + valueSrc, + valueType, + asyncListValues, + config, + operator, + isEndValue, + canFix + ); } else if (valueSrc == "func") { - [validError, fixedValue] = validateFuncValue(leftField, field, value, valueSrc, valueType, asyncListValues, config, operator, isEndValue, canFix); + [validError, fixedValue] = validateFuncValue( + leftField, + field, + value, + valueSrc, + valueType, + asyncListValues, + config, + operator, + isEndValue, + canFix + ); } else if (valueSrc == "value" || !valueSrc) { - [validError, fixedValue] = validateNormalValue(leftField, field, value, valueSrc, valueType, asyncListValues, config, operator, isEndValue, canFix); + [validError, fixedValue] = validateNormalValue( + leftField, + field, + value, + valueSrc, + valueType, + asyncListValues, + config, + operator, + isEndValue, + canFix, + fieldSrc + ); } if (!validError) { - const fieldConfig = getFieldConfig(config, field); - const w = getWidgetForFieldOp(config, field, operator, valueSrc); - const operatorDefinition = operator ? getOperatorConfig(config, operator, field) : null; - const fieldWidgetDefinition = omit(getFieldWidgetConfig(config, field, operator, w, valueSrc), ["factory"]); - const rightFieldDefinition = (valueSrc == "field" ? getFieldConfig(config, value) : null); + const w = getWidgetForFieldOp( + config, + field, + operator, + valueSrc, + fieldSrc + ); + const operatorDefinition = operator + ? getOperatorConfig(config, operator, field, fieldSrc) + : null; + const fieldWidgetDefinition = omit( + getFieldWidgetConfig(config, field, operator, w, valueSrc, fieldSrc), + ["factory"] + ); + const rightFieldDefinition = + valueSrc == "field" ? getFieldConfig(config, value) : null; const fieldSettings = fieldWidgetDefinition; // widget definition merged with fieldSettings const fn = fieldWidgetDefinition.validateValue; if (typeof fn == "function") { - const args = [ - fixedValue, - fieldSettings, - operator, - operatorDefinition - ]; - if (valueSrc == "field") - args.push(rightFieldDefinition); + const args = [fixedValue, fieldSettings, operator, operatorDefinition]; + if (valueSrc == "field") args.push(rightFieldDefinition); const validResult = fn(...args); if (typeof validResult == "boolean") { - if (validResult == false) - validError = "Invalid value"; + if (validResult == false) validError = "Invalid value"; } else { validError = validResult; } @@ -259,23 +346,40 @@ export const validateValue = (config, leftField, field, operator, value, valueTy if (isRawValue && validError) { console.warn("[RAQB validate]", `Field ${field}: ${validError}`); } - + return [validError, fixedValue]; }; -const validateValueInList = (value, listValues, canFix, isEndValue, removeInvalidMultiSelectValuesOnLoad) => { - const values = List.isList(value) ? value.toJS() : (value instanceof Array ? [...value] : undefined); +const validateValueInList = ( + value, + listValues, + canFix, + isEndValue, + removeInvalidMultiSelectValuesOnLoad +) => { + const values = List.isList(value) + ? value.toJS() + : value instanceof Array + ? [...value] + : undefined; if (values) { - const [goodValues, badValues] = values.reduce(([goodVals, badVals], val) => { - const vv = getItemInListValues(listValues, val); - if (vv == undefined) { - return [goodVals, [...badVals, val]]; - } else { - return [[...goodVals, vv.value], badVals]; - } - }, [[], []]); + const [goodValues, badValues] = values.reduce( + ([goodVals, badVals], val) => { + const vv = getItemInListValues(listValues, val); + if (vv == undefined) { + return [goodVals, [...badVals, val]]; + } else { + return [[...goodVals, vv.value], badVals]; + } + }, + [[], []] + ); const plural = badValues.length > 1; - const err = badValues.length ? `${plural ? "Values" : "Value"} ${badValues.join(", ")} ${plural ? "are" : "is"} not in list of values` : null; + const err = badValues.length + ? `${plural ? "Values" : "Value"} ${badValues.join(", ")} ${ + plural ? "are" : "is" + } not in list of values` + : null; // always remove bad values at tree validation as user can't unselect them (except AntDesign widget) if (removeInvalidMultiSelectValuesOnLoad !== undefined) { canFix = removeInvalidMultiSelectValuesOnLoad; @@ -295,33 +399,64 @@ const validateValueInList = (value, listValues, canFix, isEndValue, removeInvali }; /** -* -*/ -const validateNormalValue = (leftField, field, value, valueSrc, valueType, asyncListValues, config, operator = null, isEndValue = false, canFix = false) => { + * + */ +const validateNormalValue = ( + leftField, + field, + value, + valueSrc, + valueType, + asyncListValues, + config, + operator = null, + isEndValue = false, + canFix = false, + fieldSrc +) => { if (field) { - const fieldConfig = getFieldConfig(config, field); - const w = getWidgetForFieldOp(config, field, operator, valueSrc); + const fieldConfig = getFieldConfig(config, field, fieldSrc); + const w = getWidgetForFieldOp(config, field, operator, valueSrc, fieldSrc); const wConfig = config.widgets[w]; const wType = wConfig.type; const jsType = wConfig.jsType; const fieldSettings = fieldConfig.fieldSettings; if (valueType && valueType != wType) - return [`Value should have type ${wType}, but got value of type ${valueType}`, value]; - if (jsType && !isTypeOf(value, jsType) && !fieldSettings.listValues) { //tip: can skip tye check for listValues - return [`Value should have JS type ${jsType}, but got value of type ${typeof value}`, value]; + return [ + `Value should have type ${wType}, but got value of type ${valueType}`, + value, + ]; + if (jsType && !isTypeOf(value, jsType) && !fieldSettings.listValues) { + //tip: can skip tye check for listValues + return [ + `Value should have JS type ${jsType}, but got value of type ${typeof value}`, + value, + ]; } if (fieldSettings) { const listValues = asyncListValues || fieldSettings.listValues; if (listValues && !fieldSettings.allowCustomValues) { - return validateValueInList(value, listValues, canFix, isEndValue, config.settings.removeInvalidMultiSelectValuesOnLoad); + return validateValueInList( + value, + listValues, + canFix, + isEndValue, + config.settings.removeInvalidMultiSelectValuesOnLoad + ); } if (fieldSettings.min != null && value < fieldSettings.min) { - return [`Value ${value} < min ${fieldSettings.min}`, canFix ? fieldSettings.min : value]; + return [ + `Value ${value} < min ${fieldSettings.min}`, + canFix ? fieldSettings.min : value, + ]; } if (fieldSettings.max != null && value > fieldSettings.max) { - return [`Value ${value} > max ${fieldSettings.max}`, canFix ? fieldSettings.max : value]; + return [ + `Value ${value} > max ${fieldSettings.max}`, + canFix ? fieldSettings.max : value, + ]; } } } @@ -329,29 +464,56 @@ const validateNormalValue = (leftField, field, value, valueSrc, valueType, async return [null, value]; }; - /** -* -*/ -const validateFieldValue = (leftField, field, value, _valueSrc, valueType, asyncListValues, config, operator = null, isEndValue = false, canFix = false) => { - const {fieldSeparator} = config.settings; + * + */ +const validateFieldValue = ( + leftField, + field, + value, + _valueSrc, + valueType, + asyncListValues, + config, + operator = null, + isEndValue = false, + canFix = false +) => { + const { fieldSeparator } = config.settings; const isFuncArg = typeof field == "object" && field?._isFuncArg; - const leftFieldStr = Array.isArray(leftField) ? leftField.join(fieldSeparator) : leftField; - const rightFieldStr = Array.isArray(value) ? value.join(fieldSeparator) : value; + const leftFieldStr = Array.isArray(leftField) + ? leftField.join(fieldSeparator) + : leftField; + const rightFieldStr = Array.isArray(value) + ? value.join(fieldSeparator) + : value; const rightFieldDefinition = getFieldConfig(config, value); - if (!rightFieldDefinition) - return [`Unknown field ${value}`, value]; + if (!rightFieldDefinition) return [`Unknown field ${value}`, value]; if (rightFieldStr == leftFieldStr && !isFuncArg) return [`Can't compare field ${leftField} with itself`, value]; if (valueType && valueType != rightFieldDefinition.type) - return [`Field ${value} is of type ${rightFieldDefinition.type}, but expected ${valueType}`, value]; + return [ + `Field ${value} is of type ${rightFieldDefinition.type}, but expected ${valueType}`, + value, + ]; return [null, value]; }; /** -* -*/ -const validateFuncValue = (leftField, field, value, _valueSrc, valueType, asyncListValues, config, operator = null, isEndValue = false, canFix = false) => { + * + */ +const validateFuncValue = ( + leftField, + field, + value, + _valueSrc, + valueType, + asyncListValues, + config, + operator = null, + isEndValue = false, + canFix = false +) => { let fixedValue = value; if (value) { @@ -360,7 +522,10 @@ const validateFuncValue = (leftField, field, value, _valueSrc, valueType, asyncL const funcConfig = getFuncConfig(config, funcKey); if (funcConfig) { if (valueType && funcConfig.returnType != valueType) - return [`Function ${funcKey} should return value of type ${funcConfig.returnType}, but got ${valueType}`, value]; + return [ + `Function ${funcKey} should return value of type ${funcConfig.returnType}, but got ${valueType}`, + value, + ]; for (const argKey in funcConfig.args) { const argConfig = funcConfig.args[argKey]; const args = fixedValue.get("args"); @@ -370,23 +535,52 @@ const validateFuncValue = (leftField, field, value, _valueSrc, valueType, asyncL const argValueSrc = argVal ? argVal.get("valueSrc") : undefined; if (argValue !== undefined) { const [argValidError, fixedArgVal] = validateValue( - config, leftField, fieldDef, operator, argValue, argConfig.type, argValueSrc, asyncListValues, canFix, isEndValue, false + config, + leftField, + fieldDef, + operator, + argValue, + argConfig.type, + argValueSrc, + asyncListValues, + canFix, + isEndValue, + false ); if (argValidError !== null) { if (canFix) { fixedValue = fixedValue.deleteIn(["args", argKey]); if (argConfig.defaultValue !== undefined) { - fixedValue = fixedValue.setIn(["args", argKey, "value"], argConfig.defaultValue); - fixedValue = fixedValue.setIn(["args", argKey, "valueSrc"], "value"); + fixedValue = fixedValue.setIn( + ["args", argKey, "value"], + argConfig.defaultValue + ); + fixedValue = fixedValue.setIn( + ["args", argKey, "valueSrc"], + "value" + ); } } else { - return [`Invalid value of arg ${argKey} for func ${funcKey}: ${argValidError}`, value]; + return [ + `Invalid value of arg ${argKey} for func ${funcKey}: ${argValidError}`, + value, + ]; } } else if (fixedArgVal !== argValue) { - fixedValue = fixedValue.setIn(["args", argKey, "value"], fixedArgVal); + fixedValue = fixedValue.setIn( + ["args", argKey, "value"], + fixedArgVal + ); } - } else if (isEndValue && argConfig.defaultValue === undefined && !canFix) { - return [`Value of arg ${argKey} for func ${funcKey} is required`, value]; + } else if ( + isEndValue && + argConfig.defaultValue === undefined && + !canFix + ) { + return [ + `Value of arg ${argKey} for func ${funcKey} is required`, + value, + ]; } } } else return [`Unknown function ${funcKey}`, value]; diff --git a/packages/examples/demo/config.tsx b/packages/examples/demo/config.tsx index cdce0034c..4448cba9d 100644 --- a/packages/examples/demo/config.tsx +++ b/packages/examples/demo/config.tsx @@ -239,7 +239,6 @@ export default (skin: string) => { defaultSelectWidth: "200px", defaultSearchWidth: "100px", defaultMaxRows: 5, - valueSourcesInfo: { value: { label: "Value" @@ -263,7 +262,7 @@ export default (skin: string) => { shouldCreateEmptyGroup: false, showErrorMessage: true, customFieldSelectProps: { - showSearch: true + showSearch: false }, // renderField: (props) => , // renderOperator: (props) => , diff --git a/packages/examples/demo/index.tsx b/packages/examples/demo/index.tsx index 07bb54aa6..2c3018288 100644 --- a/packages/examples/demo/index.tsx +++ b/packages/examples/demo/index.tsx @@ -29,7 +29,7 @@ let initValue: JsonTree = loadedInitValue && Object.keys(loadedInitValue).length const initLogic: JsonLogicTree = loadedInitLogic && Object.keys(loadedInitLogic).length > 0 ? loadedInitLogic as JsonLogicTree : undefined; let initTree: ImmutableTree; //initTree = checkTree(loadTree(initValue), loadedConfig); -initTree = checkTree(loadFromJsonLogic(initLogic, loadedConfig), loadedConfig); // <- this will work same +initTree = checkTree(loadFromJsonLogic(initLogic, loadedConfig), loadedConfig); // <- this will work same // Trick to hot-load new config when you edit `config.tsx` @@ -98,12 +98,6 @@ const DemoQueryBuilder: React.FC = () => { initValue = _initValue; }; - const switchShowLock = () => { - const newConfig: Config = clone(state.config); - newConfig.settings.showLock = !newConfig.settings.showLock; - setState({...state, config: newConfig}); - }; - const resetValue = () => { setState({ ...state, @@ -111,30 +105,6 @@ const DemoQueryBuilder: React.FC = () => { }); }; - const validate = () => { - setState({ - ...state, - tree: checkTree(state.tree, state.config) - }); - }; - - const onChangeSpelStr = (e: React.ChangeEvent) => { - const spelStr = e.target.value; - setState({ - ...state, - spelStr - }); - }; - - const importFromSpel = () => { - const [tree, spelErrors] = loadFromSpel(state.spelStr, state.config); - setState({ - ...state, - tree: tree ? checkTree(tree, state.config) : state.tree, - spelErrors - }); - }; - const changeSkin = (e: React.ChangeEvent) => { const skin = e.target.value; const config = loadConfig(e.target.value); @@ -177,174 +147,21 @@ const DemoQueryBuilder: React.FC = () => { setState(prevState => ({...prevState, tree: memo.current.immutableTree, config: memo.current.config})); }, 100); - // Demonstrates how actions can be called programmatically - const runActions = () => { - const rootPath = [ state.tree.get("id") as string ]; - const isEmptyTree = !state.tree.get("children1"); - const firstPath = [ - state.tree.get("id"), - ((state.tree.get("children1") as ImmOMap)?.first() as ImmOMap)?.get("id") - ]; - const lastPath = [ - state.tree.get("id"), - ((state.tree.get("children1") as ImmOMap)?.last() as ImmOMap)?.get("id") - ]; - - // Change root group to NOT OR - memo.current._actions.setNot(rootPath, true); - memo.current._actions.setConjunction(rootPath, "OR"); - - // Move first item - if (!isEmptyTree) { - memo.current._actions.moveItem(firstPath, lastPath, "before"); - } - - // Remove last rule - if (!isEmptyTree) { - memo.current._actions.removeRule(lastPath); - } - - // Change first rule to `num between 2 and 4` - if (!isEmptyTree) { - memo.current._actions.setField(firstPath, "num"); - memo.current._actions.setOperator(firstPath, "between"); - memo.current._actions.setValueSrc(firstPath, 0, "value"); - memo.current._actions.setValue(firstPath, 0, 2, "number"); - memo.current._actions.setValue(firstPath, 1, 4, "number"); - } - - // Add rule `login == "denis"` - memo.current._actions.addRule( - rootPath, - { - field: "user.login", - operator: "equal", - value: ["denis"], - valueSrc: ["value"], - valueType: ["text"] - }, - ); - - // Add rule `login == firstName` - memo.current._actions.addRule( - rootPath, - { - field: "user.login", - operator: "equal", - value: ["user.firstName"], - valueSrc: ["field"] - }, - ); - - // Add rule-group `cars` with `year == 2021` - memo.current._actions.addRule( - rootPath, - { - field: "cars", - mode: "array", - operator: "all", - }, - "rule_group", - [ - { - type: "rule", - properties: { - field: "cars.year", - operator: "equal", - value: [2021] - } - } - ] - ); - - // Add group with `slider == 40` and subgroup `slider < 20` - memo.current._actions.addGroup( - rootPath, - { - conjunction: "AND" - }, - [ - { - type: "rule", - properties: { - field: "slider", - operator: "equal", - value: [40] - } - }, - { - type: "group", - properties: { - conjunction: "AND" - }, - children1: [ - { - type: "rule", - properties: { - field: "slider", - operator: "less", - value: [20] - } - }, - ] - } - ] - ); - }; - const renderResult = ({tree: immutableTree, config} : {tree: ImmutableTree, config: Config}) => { const isValid = isValidTree(immutableTree); const treeJs = getTree(immutableTree); const {logic, data: logicData, errors: logicErrors} = jsonLogicFormat(immutableTree, config); - const [spel, spelErrors] = _spelFormat(immutableTree, config); - const queryStr = queryString(immutableTree, config); - const humanQueryStr = queryString(immutableTree, config, true); - const [sql, sqlErrors] = _sqlFormat(immutableTree, config); - const [mongo, mongoErrors] = _mongodbFormat(immutableTree, config); - const elasticSearch = elasticSearchFormat(immutableTree, config); + // const [spel, spelErrors] = _spelFormat(immutableTree, config); + // const queryStr = queryString(immutableTree, config); + // const humanQueryStr = queryString(immutableTree, config, true); + // const [sql, sqlErrors] = _sqlFormat(immutableTree, config); + // const [mongo, mongoErrors] = _mongodbFormat(immutableTree, config); + // const elasticSearch = elasticSearchFormat(immutableTree, config); return (
{isValid ? null :
{"Tree has errors"}
}
-
- spelFormat: - { spelErrors.length > 0 - &&
-              {stringify(spelErrors, undefined, 2)}
-            
- } -
-            {stringify(spel, undefined, 2)}
-          
-
-
-
- stringFormat: -
-            {stringify(queryStr, undefined, 2)}
-          
-
-
-
- humanStringFormat: -
-            {stringify(humanQueryStr, undefined, 2)}
-          
-
-
-
- sqlFormat: - { sqlErrors.length > 0 - &&
-              {stringify(sqlErrors, undefined, 2)}
-            
- } -
-            {stringify(sql, undefined, 2)}
-          
-
-
jsonLogicFormat: { logicErrors.length > 0 @@ -365,25 +182,6 @@ const DemoQueryBuilder: React.FC = () => {

- mongodbFormat: - { mongoErrors.length > 0 - &&
-              {stringify(mongoErrors, undefined, 2)}
-            
- } -
-            {stringify(mongo, undefined, 2)}
-          
-
-
-
- elasticSearchFormat: -
-            {stringify(elasticSearch, undefined, 2)}
-          
-
-
-
Tree:
             {stringify(treeJs, undefined, 2)}
@@ -413,9 +211,6 @@ const DemoQueryBuilder: React.FC = () => {
         
         
         
-        
-        
-        
       
@@ -426,19 +221,6 @@ const DemoQueryBuilder: React.FC = () => { onChange={onChange} renderBuilder={renderBuilder} /> - -
- SpEL: - - -
- { state.spelErrors.length > 0 - &&
-              {stringify(state.spelErrors, undefined, 2)}
-            
- } -
-
{renderResult(state)}
diff --git a/packages/examples/demo/init_logic.js b/packages/examples/demo/init_logic.js index e6e42ddab..aea836b1f 100644 --- a/packages/examples/demo/init_logic.js +++ b/packages/examples/demo/init_logic.js @@ -1,107 +1,7 @@ -export default -{ - "and": [ +export default { + and: [ { - "==": [ - { - "var": "user.login" - }, - { - "toLowerCase": [ - { - "var": "user.firstName" - } - ] - } - ] + "==": [{ toUpperCase: [{ toLowerCase: ["a"] }] }, { toLowerCase: ["b"] }], }, - { - "==": [ - { - "var": "stock" - }, - false - ] - }, - { - "==": [ - { - "var": "slider" - }, - 35 - ] - }, - { - "some": [ - { - "var": "results" - }, - { - "and": [ - { - "==": [ - { - "var": "product" - }, - "abc" - ] - }, - { - ">": [ - { - "var": "score" - }, - 8 - ] - } - ] - } - ] - }, - { - ">": [ - { - "reduce": [ - { - "filter": [ - { - "var": "cars" - }, - { - "and": [ - { - "==": [ - { - "var": "vendor" - }, - "Toyota" - ] - }, - { - ">=": [ - { - "var": "year" - }, - 2010 - ] - } - ] - } - ] - }, - { - "+": [ - 1, - { - "var": "accumulator" - } - ] - }, - 0 - ] - }, - 2 - ] - } - ] -}; \ No newline at end of file + ], +}; diff --git a/packages/examples/demo/init_value.js b/packages/examples/demo/init_value.js index 55727338f..d3058e248 100644 --- a/packages/examples/demo/init_value.js +++ b/packages/examples/demo/init_value.js @@ -6,7 +6,8 @@ export default "a98ab9b9-cdef-4012-b456-71607f326fd9": { "type": "rule", "properties": { - "field": "user.login", + "field": "LOWER", + "fieldSrc": "func", "operator": "equal", "value": [ { @@ -29,152 +30,6 @@ export default null ] } - }, - "98a8a9ba-0123-4456-b89a-b16e721c8cd0": { - "type": "rule", - "properties": { - "field": "stock", - "operator": "equal", - "value": [ - false - ], - "valueSrc": [ - "value" - ], - "valueType": [ - "boolean" - ], - "valueError": [ - null - ] - } - }, - "aabbab8a-cdef-4012-b456-716e85c65e9c": { - "type": "rule", - "properties": { - "field": "slider", - "operator": "equal", - "value": [ - 35 - ], - "valueSrc": [ - "value" - ], - "valueType": [ - "number" - ], - "valueError": [ - null - ] - } - }, - "aaab8999-cdef-4012-b456-71702cd50090": { - "type": "rule_group", - "properties": { - "conjunction": "AND", - "field": "results" - }, - "children1": { - "99b8a8a8-89ab-4cde-b012-31702cd5078b": { - "type": "rule", - "properties": { - "field": "results.product", - "operator": "select_equals", - "value": [ - "abc" - ], - "valueSrc": [ - "value" - ], - "valueType": [ - "select" - ], - "valueError": [ - null - ] - } - }, - "88b9bb89-4567-489a-bcde-f1702cd53266": { - "type": "rule", - "properties": { - "field": "results.score", - "operator": "greater", - "value": [ - 8 - ], - "valueSrc": [ - "value" - ], - "valueType": [ - "number" - ], - "valueError": [ - null - ] - } - } - } - }, - "a99a9b9b-cdef-4012-b456-7175a7d54553": { - "type": "rule_group", - "properties": { - "mode": "array", - "operator": "greater", - "valueType": [ - "number" - ], - "value": [ - 2 - ], - "valueSrc": [ - "value" - ], - "conjunction": "AND", - "valueError": [ - null - ], - "field": "cars" - }, - "children1": { - "99a9a9a8-89ab-4cde-b012-3175a7d55374": { - "type": "rule", - "properties": { - "field": "cars.vendor", - "operator": "select_equals", - "value": [ - "Toyota" - ], - "valueSrc": [ - "value" - ], - "valueError": [ - null - ], - "valueType": [ - "select" - ] - } - }, - "988bbbab-4567-489a-bcde-f175a7d58793": { - "type": "rule", - "properties": { - "field": "cars.year", - "operator": "greater_or_equal", - "value": [ - 2010 - ], - "valueSrc": [ - "value" - ], - "valueError": [ - null - ], - "valueType": [ - "number" - ] - } - } - } } }, "properties": { diff --git a/packages/examples/package.json b/packages/examples/package.json index baf86c216..625bf7b02 100644 --- a/packages/examples/package.json +++ b/packages/examples/package.json @@ -96,7 +96,7 @@ "url-loader": "^4.1.1", "webpack": "^5.61.0", "webpack-bundle-analyzer": "^4.4.2", - "webpack-cli": "^4.9.1", + "webpack-cli": "^4.10.0", "webpack-dev-server": "^4.4.0" } } diff --git a/packages/mui/modules/config/index.jsx b/packages/mui/modules/config/index.jsx index 2fee93081..678571ee8 100644 --- a/packages/mui/modules/config/index.jsx +++ b/packages/mui/modules/config/index.jsx @@ -4,7 +4,6 @@ import { Utils, BasicConfig } from "@react-awesome-query-builder/ui"; const { SqlString, stringifyForDisplay } = Utils.ExportUtils; - const { MuiBooleanWidget, MuiTextWidget, @@ -32,14 +31,22 @@ const { MuiUseConfirm, } = MuiWidgets; - const settings = { ...BasicConfig.settings, - renderField: (props) => props?.customProps?.showSearch - ? - : , - renderOperator: (props) => , + renderFieldSources: (props) => { + return ; + }, + renderField: (props) => { + return props?.customProps?.showSearch ? ( + + ) : ( + + ); + }, + renderOperator: (props) => { + return ; + }, renderFunc: (props) => , renderConjs: (props) => , renderSwitch: (props) => , @@ -51,7 +58,6 @@ const settings = { useConfirm: MuiUseConfirm, }; - const widgets = { ...BasicConfig.widgets, text: { @@ -69,17 +75,21 @@ const widgets = { multiselect: { ...BasicConfig.widgets.multiselect, factory: (props) => { - return (props.asyncFetch || props.showSearch) - ? - : ; + return props.asyncFetch || props.showSearch ? ( + + ) : ( + + ); }, }, select: { ...BasicConfig.widgets.select, factory: (props) => { - return (props.asyncFetch || props.showSearch) - ? - : ; + return props.asyncFetch || props.showSearch ? ( + + ) : ( + + ); }, }, slider: { @@ -121,11 +131,10 @@ const widgets = { return SqlString.escape(val); }, singleWidget: "slider", - toJS: (val, fieldSettings) => (val), + toJS: (val, fieldSettings) => val, }, }; - const types = { ...BasicConfig.types, number: { @@ -139,15 +148,10 @@ const types = { }, not_between: { isSpecialRange: true, - } + }, }, - operators: [ - "between", - "not_between", - "is_empty", - "is_not_empty", - ], - } + operators: ["between", "not_between", "is_empty", "is_not_empty"], + }, }, }, }; @@ -157,4 +161,4 @@ export default { types, widgets, settings, -}; \ No newline at end of file +}; diff --git a/packages/mui/modules/widgets/core/MuiFieldAutocomplete.jsx b/packages/mui/modules/widgets/core/MuiFieldAutocomplete.jsx index 497069579..14df2374f 100644 --- a/packages/mui/modules/widgets/core/MuiFieldAutocomplete.jsx +++ b/packages/mui/modules/widgets/core/MuiFieldAutocomplete.jsx @@ -1,35 +1,36 @@ import React from "react"; import MuiAutocomplete from "../value/MuiAutocomplete"; -const itemsToListValues = (items, level = 0) => ( - items.map(item => { - const {items, path, label, disabled, grouplabel} = item; - const prefix = "\u00A0\u00A0".repeat(level); - if (items) { - return itemsToListValues(items, level+1); - } else { - return { - title: label, - renderTitle: prefix+label, - value: path, - disabled, - groupTitle: level > 0 ? prefix+grouplabel : null, - }; - } - }).flat(Infinity) -); +const itemsToListValues = (items, level = 0) => + items + .map((item) => { + const { items, path, label, disabled, grouplabel } = item; + const prefix = "\u00A0\u00A0".repeat(level); + if (items) { + return itemsToListValues(items, level + 1); + } else { + return { + title: label, + renderTitle: prefix + label, + value: path, + disabled, + groupTitle: level > 0 ? prefix + grouplabel : null, + }; + } + }) + .flat(Infinity); const filterOptionsConfig = { stringify: (option) => { const keysForFilter = ["title", "value", "grouplabel", "label"]; const valueForFilter = keysForFilter - .map(k => (typeof option[k] == "string" ? option[k] : "")) + .map((k) => (typeof option[k] == "string" ? option[k] : "")) .join("\0"); return valueForFilter; - } + }, }; -const fieldAdapter = ({items, selectedKey, setField, ...rest}) => { +const fieldAdapter = ({ items, selectedKey, setField, ...rest }) => { const listValues = itemsToListValues(items); const groupBy = (option) => option.groupTitle; const value = selectedKey; @@ -51,5 +52,5 @@ const fieldAdapter = ({items, selectedKey, setField, ...rest}) => { }; export default (props) => { - return ; + return ; }; diff --git a/packages/mui/modules/widgets/core/MuiFieldSelect.jsx b/packages/mui/modules/widgets/core/MuiFieldSelect.jsx index 3e9aa2c80..ea13fa71d 100644 --- a/packages/mui/modules/widgets/core/MuiFieldSelect.jsx +++ b/packages/mui/modules/widgets/core/MuiFieldSelect.jsx @@ -3,12 +3,21 @@ import Select from "@mui/material/Select"; import MenuItem from "@mui/material/MenuItem"; import ListSubheader from "@mui/material/ListSubheader"; import FormControl from "@mui/material/FormControl"; +import { Utils } from "@react-awesome-query-builder/core"; +const { setFunc } = Utils.FuncUtils; -export default ({items, setField, selectedKey, readonly, placeholder}) => { - const renderOptions = (fields, level = 0) => ( - Object.keys(fields).map(fieldKey => { +export default ({ + items, + setField, + selectedKey, + readonly, + placeholder, + config, +}) => { + const renderOptions = (fields, level = 0) => { + return Object.keys(fields).map((fieldKey) => { const field = fields[fieldKey]; - const {items, path, label, disabled} = field; + const { items, path, label, disabled } = field; const prefix = "\u00A0\u00A0".repeat(level); if (items) { return [ @@ -16,41 +25,44 @@ export default ({items, setField, selectedKey, readonly, placeholder}) => { {prefix && {prefix}} {label} , - renderOptions(items, level+1), + renderOptions(items, level + 1), ]; } else { - return - {prefix && {prefix}} - {label} - ; + return ( + + {prefix && {prefix}} + {label} + + ); } - }) - ); + }); + }; - const onChange = e => { - if (e.target.value === undefined) - return; + const onChange = (e) => { + if (e.target.value === undefined) return; setField(e.target.value); }; const renderValue = (selectedValue) => { - if (!readonly && !selectedValue) - return placeholder; + if (!readonly && !selectedValue) return placeholder; const findLabel = (fields) => { - return fields.map(field => { - if(!field.items) return field.path === selectedValue ? field.label : null; + return fields.map((field) => { + if (!field.items) + return field.path === selectedValue ? field.label : null; return findLabel(field.items); }); }; - return findLabel(items).filter((v) => { - if (Array.isArray(v)) { - return v.some((value) => value !== null); - } else { - return v !== null; - } - }).pop(); + return findLabel(items) + .filter((v) => { + if (Array.isArray(v)) { + return v.some((value) => value !== null); + } else { + return v !== null; + } + }) + .pop(); }; - + const hasValue = selectedKey != null; return ( diff --git a/packages/mui/modules/widgets/value/MuiAutocomplete.jsx b/packages/mui/modules/widgets/value/MuiAutocomplete.jsx index 3c456504e..b2878e213 100644 --- a/packages/mui/modules/widgets/value/MuiAutocomplete.jsx +++ b/packages/mui/modules/widgets/value/MuiAutocomplete.jsx @@ -53,7 +53,7 @@ export default (props) => { customInputProps = omit(customInputProps, ["width"]); const customAutocompleteProps = omit(rest, ["showSearch", "showCheckboxes"]); - const fullWidth = true; + const fullWidth = false; const minWidth = width || defaultSelectWidth; const style = { width: (multiple ? undefined : minWidth), diff --git a/packages/sandbox_simple/src/demo/demo.jsx b/packages/sandbox_simple/src/demo/demo.jsx index 5bd05496c..6f7114cf9 100644 --- a/packages/sandbox_simple/src/demo/demo.jsx +++ b/packages/sandbox_simple/src/demo/demo.jsx @@ -102,20 +102,6 @@ export default class DemoQueryBuilder extends Component {

-
- sqlFormat: -
-            {stringify(sqlFormat(immutableTree, config), undefined, 2)}
-          
-
-
-
- mongodbFormat: -
-            {stringify(mongodbFormat(immutableTree, config), undefined, 2)}
-          
-
-
jsonLogicFormat: { errors.length > 0 diff --git a/packages/ui/modules/components/Query.jsx b/packages/ui/modules/components/Query.jsx index 209a170d3..c58f9abef 100644 --- a/packages/ui/modules/components/Query.jsx +++ b/packages/ui/modules/components/Query.jsx @@ -79,7 +79,6 @@ class Query extends Component { dispatch: dispatch, __isInternalValueChange }; - return renderBuilder(builderProps); } } diff --git a/packages/ui/modules/components/containers/GroupContainer.jsx b/packages/ui/modules/components/containers/GroupContainer.jsx index ad765ba5f..a4ab62410 100644 --- a/packages/ui/modules/components/containers/GroupContainer.jsx +++ b/packages/ui/modules/components/containers/GroupContainer.jsx @@ -13,7 +13,7 @@ const createGroupContainer = (Group) => static propTypes = { //tree: PropTypes.instanceOf(Immutable.Map).isRequired, config: PropTypes.object.isRequired, - actions: PropTypes.object.isRequired, //{setConjunction: Funciton, removeGroup, addGroup, addRule, ...} + actions: PropTypes.object.isRequired, //{setConjunction: Function, removeGroup, addGroup, addRule, ...} path: PropTypes.any.isRequired, //instanceOf(Immutable.List) id: PropTypes.string.isRequired, groupId: PropTypes.string, diff --git a/packages/ui/modules/components/containers/RuleContainer.jsx b/packages/ui/modules/components/containers/RuleContainer.jsx index aa3be1f64..1bab8d707 100644 --- a/packages/ui/modules/components/containers/RuleContainer.jsx +++ b/packages/ui/modules/components/containers/RuleContainer.jsx @@ -2,13 +2,12 @@ import React, { Component } from "react"; import { Utils } from "@react-awesome-query-builder/core"; import PropTypes from "prop-types"; import context from "../../stores/context"; -import {pureShouldComponentUpdate} from "../../utils/reactUtils"; -import {connect} from "react-redux"; +import { pureShouldComponentUpdate } from "../../utils/reactUtils"; +import { connect } from "react-redux"; import classNames from "classnames"; -const {getFieldConfig} = Utils.ConfigUtils; +const { getFieldConfig, getFuncConfig } = Utils.ConfigUtils; - -const createRuleContainer = (Rule) => +const createRuleContainer = (Rule) => class RuleContainer extends Component { static propTypes = { id: PropTypes.string.isRequired, @@ -16,8 +15,9 @@ const createRuleContainer = (Rule) => config: PropTypes.object.isRequired, path: PropTypes.any.isRequired, //instanceOf(Immutable.List) operator: PropTypes.string, - field: PropTypes.string, - actions: PropTypes.object.isRequired, //{removeRule: Funciton, setField, setOperator, setOperatorOption, setValue, setValueSrc, ...} + field: PropTypes.any, + fieldSrc: PropTypes.any, + actions: PropTypes.object.isRequired, //{removeRule: Function, setField, setFieldSrc, setOperator, setOperatorOption, setValue, setValueSrc, ...} onDragStart: PropTypes.func, value: PropTypes.any, //depends on widget valueSrc: PropTypes.any, @@ -35,7 +35,7 @@ const createRuleContainer = (Rule) => constructor(props) { super(props); - + this.dummyFn.isDummyFn = true; } @@ -53,6 +53,10 @@ const createRuleContainer = (Rule) => this.props.actions.setField(this.props.path, field); }; + setFieldSrc = (srcKey) => { + this.props.actions.setFieldSrc(this.props.path, srcKey); + }; + setOperator = (operator) => { this.props.actions.setOperator(this.props.path, operator); }; @@ -62,7 +66,14 @@ const createRuleContainer = (Rule) => }; setValue = (delta, value, type, asyncListValues, __isInternal) => { - this.props.actions.setValue(this.props.path, delta, value, type, asyncListValues, __isInternal); + this.props.actions.setValue( + this.props.path, + delta, + value, + type, + asyncListValues, + __isInternal + ); }; setValueSrc = (delta, srcKey) => { @@ -76,11 +87,11 @@ const createRuleContainer = (Rule) => let should = pureShouldComponentUpdate(this)(nextProps, nextState); if (should) { if (prevState == nextState && prevProps != nextProps) { - const draggingId = (nextProps.dragging.id || prevProps.dragging.id); + const draggingId = nextProps.dragging.id || prevProps.dragging.id; const isDraggingMe = draggingId == nextProps.id; let chs = []; for (let k in nextProps) { - let changed = (nextProps[k] != prevProps[k]); + let changed = nextProps[k] != prevProps[k]; if (k == "dragging" && !isDraggingMe) { changed = false; //dragging another item -> ignore } @@ -88,8 +99,7 @@ const createRuleContainer = (Rule) => chs.push(k); } } - if (!chs.length) - should = false; + if (!chs.length) should = false; } } return should; @@ -97,51 +107,68 @@ const createRuleContainer = (Rule) => render() { const isDraggingMe = this.props.dragging.id == this.props.id; - const fieldConfig = getFieldConfig(this.props.config, this.props.field); - const {showErrorMessage} = this.props.config.settings; + const fieldConfig = getFieldConfig( + this.props.config, + this.props.field, + this.props.fieldSrc + ); + const { showErrorMessage } = this.props.config.settings; const _isGroup = fieldConfig && fieldConfig.type == "!struct"; const isInDraggingTempo = !isDraggingMe && this.props.isDraggingTempo; const valueError = this.props.valueError; - const oneValueError = valueError && valueError.toArray().filter(e => !!e).shift() || null; + const oneValueError = + (valueError && + valueError + .toArray() + .filter((e) => !!e) + .shift()) || + null; const hasError = oneValueError != null && showErrorMessage; return (
{[ - isDraggingMe ? : null - , + isDraggingMe ? ( + + ) : null, setLock={isInDraggingTempo ? this.dummyFn : this.setLock} removeSelf={isInDraggingTempo ? this.dummyFn : this.removeSelf} setField={isInDraggingTempo ? this.dummyFn : this.setField} + setFieldSrc={isInDraggingTempo ? this.dummyFn : this.setFieldSrc} setOperator={isInDraggingTempo ? this.dummyFn : this.setOperator} - setOperatorOption={isInDraggingTempo ? this.dummyFn : this.setOperatorOption} + setOperatorOption={ + isInDraggingTempo ? this.dummyFn : this.setOperatorOption + } setValue={isInDraggingTempo ? this.dummyFn : this.setValue} setValueSrc={isInDraggingTempo ? this.dummyFn : this.setValueSrc} selectedField={this.props.field || null} + selectedFieldSrc={this.props.fieldSrc || null} parentField={this.props.parentField || null} selectedOperator={this.props.operator || null} value={this.props.value || null} @@ -170,15 +201,13 @@ const createRuleContainer = (Rule) => isLocked={this.props.isLocked} isTrueLocked={this.props.isTrueLocked} parentReordableNodesCnt={this.props.parentReordableNodesCnt} - /> + />, ]}
); } - }; - export default (Rule) => { const ConnectedRuleContainer = connect( (state) => { @@ -189,7 +218,7 @@ export default (Rule) => { null, null, { - context + context, } )(createRuleContainer(Rule)); ConnectedRuleContainer.displayName = "ConnectedRuleContainer"; diff --git a/packages/ui/modules/components/item/Item.jsx b/packages/ui/modules/components/item/Item.jsx index 63ccec951..86e2e0515 100644 --- a/packages/ui/modules/components/item/Item.jsx +++ b/packages/ui/modules/components/item/Item.jsx @@ -7,17 +7,11 @@ import RuleGroupExt from "./RuleGroupExt"; import SwitchGroup from "./SwitchGroup"; import CaseGroup from "./CaseGroup"; -const types = [ - "rule", - "group", - "rule_group", - "switch_group", - "case_group" -]; +const types = ["rule", "group", "rule_group", "switch_group", "case_group"]; const getProperties = (props) => { const properties = props.properties?.toObject() || {}; - const result = {...properties}; + const result = { ...properties }; if (props.isParentLocked) { result.isLocked = true; } @@ -28,24 +22,26 @@ const getProperties = (props) => { }; const typeMap = { - rule: (props) => ( - - ), + rule: (props) => { + return ( + + ); + }, group: (props) => ( - ), rule_group: (props) => ( - ), rule_group_ext: (props) => ( - ), switch_group: (props) => ( - ), case_group: (props) => ( - (nextProps[k] !== prevProps[k])).filter(ch => ch).length > 0; + const keysForMeta = [ + "selectedField", + "selectedOperator", + "config", + "reordableNodesCnt", + "isLocked", + ]; + const needUpdateMeta = + !this.meta || + keysForMeta.map((k) => nextProps[k] !== prevProps[k]).filter((ch) => ch) + .length > 0; if (needUpdateMeta) { this.meta = this.getMeta(nextProps); } } - getMeta({selectedField, selectedOperator, config, reordableNodesCnt, isLocked}) { - const selectedFieldPartsLabels = getFieldPathLabels(selectedField, config); - const selectedFieldConfig = getFieldConfig(config, selectedField); - const isSelectedGroup = selectedFieldConfig && selectedFieldConfig.type == "!struct"; - const isFieldAndOpSelected = selectedField && selectedOperator && !isSelectedGroup; - const selectedOperatorConfig = getOperatorConfig(config, selectedOperator, selectedField); - const selectedOperatorHasOptions = selectedOperatorConfig && selectedOperatorConfig.options != null; - const selectedFieldWidgetConfig = getFieldWidgetConfig(config, selectedField, selectedOperator) || {}; + getMeta({ + selectedField, + selectedFieldSrc, + selectedOperator, + config, + reordableNodesCnt, + isLocked, + }) { + const selectedFieldPartsLabels = getFieldPathLabels( + selectedField, + config, + null, + "fields", + "subfields", + selectedFieldSrc + ); + const selectedFieldConfig = getFieldConfig( + config, + selectedField, + selectedFieldSrc + ); + const isSelectedGroup = + selectedFieldConfig && selectedFieldConfig.type == "!struct"; + const isFieldAndOpSelected = + selectedField && selectedOperator && !isSelectedGroup; + const selectedOperatorConfig = getOperatorConfig( + config, + selectedOperator, + selectedField, + selectedFieldSrc + ); + const selectedOperatorHasOptions = + selectedOperatorConfig && selectedOperatorConfig.options != null; + const selectedFieldWidgetConfig = + getFieldWidgetConfig( + config, + selectedField, + selectedOperator, + null, + null, + selectedFieldSrc + ) || {}; const hideOperator = selectedFieldWidgetConfig.hideOperator; - const showDragIcon = config.settings.canReorder && reordableNodesCnt > 1 && !isLocked; + const showDragIcon = + config.settings.canReorder && reordableNodesCnt > 1 && !isLocked; const showOperator = selectedField && !hideOperator; - const showOperatorLabel = selectedField && hideOperator && selectedFieldWidgetConfig.operatorInlineLabel; + const showOperatorLabel = + selectedField && + hideOperator && + selectedFieldWidgetConfig.operatorInlineLabel; const showWidget = isFieldAndOpSelected; - const showOperatorOptions = isFieldAndOpSelected && selectedOperatorHasOptions; + const showOperatorOptions = + isFieldAndOpSelected && selectedOperatorHasOptions; return { - selectedFieldPartsLabels, selectedFieldWidgetConfig, - showDragIcon, showOperator, showOperatorLabel, showWidget, showOperatorOptions + selectedFieldPartsLabels, + selectedFieldWidgetConfig, + showDragIcon, + showOperator, + showOperatorLabel, + showWidget, + showOperatorOptions, }; } @@ -90,16 +149,18 @@ class Rule extends PureComponent { } removeSelf() { - const {confirmFn} = this.props; - const {renderConfirm, removeRuleConfirmOptions: confirmOptions} = this.props.config.settings; + const { confirmFn } = this.props; + const { renderConfirm, removeRuleConfirmOptions: confirmOptions } = + this.props.config.settings; const doRemove = () => { this.props.removeSelf(); }; if (confirmOptions && !this.isEmptyCurrentRule()) { - renderConfirm({...confirmOptions, + renderConfirm({ + ...confirmOptions, onOk: doRemove, onCancel: null, - confirmFn: confirmFn + confirmFn: confirmFn, }); } else { doRemove(); @@ -108,178 +169,256 @@ class Rule extends PureComponent { isEmptyCurrentRule() { return !( - this.props.selectedField !== null - && this.props.selectedOperator !== null - && this.props.value.filter((val) => val !== undefined).size > 0 + this.props.selectedField !== null && + this.props.selectedOperator !== null && + this.props.value.filter((val) => val !== undefined).size > 0 ); } renderField() { - const {config, isLocked} = this.props; + const { config, isLocked } = this.props; const { immutableFieldsMode } = config.settings; - return ; + return ( + + ); } - renderOperator () { - const {config, isLocked} = this.props; + renderOperator() { + const { config, isLocked } = this.props; const { - selectedFieldPartsLabels, selectedFieldWidgetConfig, showOperator, showOperatorLabel + selectedFieldPartsLabels, + selectedFieldWidgetConfig, + showOperator, + showOperatorLabel, } = this.meta; const { immutableOpsMode } = config.settings; - return ; + return ( + + ); } renderWidget() { - const {config, valueError, isLocked} = this.props; + const { config, valueError, isLocked } = this.props; const { showWidget } = this.meta; const { immutableValuesMode } = config.settings; if (!showWidget) return null; - const widget = ; + const widget = ( + + ); return ( - + {widget} ); } renderOperatorOptions() { - const {config} = this.props; + const { config } = this.props; const { showOperatorOptions } = this.meta; const { immutableOpsMode, immutableValuesMode } = config.settings; if (!showOperatorOptions) return null; - const opOpts = ; + const opOpts = ( + + ); return ( - + {opOpts} ); } renderBeforeWidget() { - const {config} = this.props; + const { config } = this.props; const { renderBeforeWidget } = config.settings; - return renderBeforeWidget - && - {typeof renderBeforeWidget === "function" ? renderBeforeWidget(this.props) : renderBeforeWidget} - ; + return ( + renderBeforeWidget && ( + + {typeof renderBeforeWidget === "function" + ? renderBeforeWidget(this.props) + : renderBeforeWidget} + + ) + ); } renderAfterWidget() { - const {config} = this.props; + const { config } = this.props; const { renderAfterWidget } = config.settings; - return renderAfterWidget - && - {typeof renderAfterWidget === "function" ? renderAfterWidget(this.props) : renderAfterWidget} - ; + return ( + renderAfterWidget && ( + + {typeof renderAfterWidget === "function" + ? renderAfterWidget(this.props) + : renderAfterWidget} + + ) + ); } renderError() { - const {config, valueError} = this.props; + const { config, valueError } = this.props; const { renderRuleError, showErrorMessage } = config.settings; - const oneValueError = valueError && valueError.toArray().filter(e => !!e).shift() || null; - return showErrorMessage && oneValueError - &&
- {renderRuleError ? renderRuleError({error: oneValueError}) : oneValueError} -
; + const oneValueError = + (valueError && + valueError + .toArray() + .filter((e) => !!e) + .shift()) || + null; + return ( + showErrorMessage && + oneValueError && ( +
+ {renderRuleError + ? renderRuleError({ error: oneValueError }) + : oneValueError} +
+ ) + ); } renderDrag() { const { showDragIcon } = this.meta; - return showDragIcon - && ; + > + {" "} + + ) + ); } renderDel() { - const {config, isLocked} = this.props; + const { config, isLocked } = this.props; const { - deleteLabel, - immutableGroupsMode, + deleteLabel, + immutableGroupsMode, renderButton: Btn, - canDeleteLocked + canDeleteLocked, } = config.settings; - return !immutableGroupsMode && (!isLocked || isLocked && canDeleteLocked) && ( - + return ( + !immutableGroupsMode && + (!isLocked || (isLocked && canDeleteLocked)) && ( + + ) ); } renderLock() { - const {config, isLocked, isTrueLocked, id} = this.props; + const { config, isLocked, isTrueLocked, id } = this.props; const { - lockLabel, lockedLabel, showLock, - renderSwitch: Switch + lockLabel, + lockedLabel, + showLock, + renderSwitch: Switch, } = config.settings; - - return showLock && !(isLocked && !isTrueLocked) && ( - + + return ( + showLock && + !(isLocked && !isTrueLocked) && ( + + ) ); } - render () { + render() { const { showOperatorOptions, selectedFieldWidgetConfig } = this.meta; const { valueSrc, value, config } = this.props; - const canShrinkValue = valueSrc.first() == "value" && !showOperatorOptions && value.size == 1 && selectedFieldWidgetConfig.fullWidth; + const canShrinkValue = + valueSrc.first() == "value" && + !showOperatorOptions && + value.size == 1 && + selectedFieldWidgetConfig.fullWidth; const { renderButtonGroup: BtnGrp } = config.settings; const parts = [ @@ -290,18 +429,29 @@ class Rule extends PureComponent { this.renderAfterWidget(), this.renderOperatorOptions(), ]; - const body =
{parts}
; + const body = ( +
+ {parts} +
+ ); const error = this.renderError(); const drag = this.renderDrag(); const lock = this.renderLock(); const del = this.renderDel(); - + return ( <> {drag}
- {body}{error} + {body} + {error}
@@ -312,8 +462,6 @@ class Rule extends PureComponent { ); } - } - export default RuleContainer(Draggable("rule")(ConfirmFn(Rule))); diff --git a/packages/ui/modules/components/item/RuleGroup.jsx b/packages/ui/modules/components/item/RuleGroup.jsx index 0a8f980df..6ea96b6cd 100644 --- a/packages/ui/modules/components/item/RuleGroup.jsx +++ b/packages/ui/modules/components/item/RuleGroup.jsx @@ -13,8 +13,10 @@ class RuleGroup extends BasicGroup { static propTypes = { ...BasicGroup.propTypes, selectedField: PropTypes.string, + fieldSrc: PropTypes.any, parentField: PropTypes.string, setField: PropTypes.func, + setFieldSrc: PropTypes.func, }; constructor(props) { @@ -54,14 +56,16 @@ class RuleGroup extends BasicGroup { } renderField() { - const { config, selectedField, setField, parentField, id, groupId, isLocked } = this.props; + const { config, selectedField, fieldSrc, setField, setFieldSrc, parentField, id, groupId, isLocked } = this.props; const { immutableFieldsMode } = config.settings; return (nextProps[k] !== prevProps[k])).filter(ch => ch).length > 0; + const needUpdateMeta = + !this.meta || + keysForMeta.map((k) => nextProps[k] !== prevProps[k]).filter((ch) => ch) + .length > 0; if (needUpdateMeta) { this.meta = this.getMeta(nextProps); } } - getMeta({selectedField, config, parentField}) { + getMeta({ selectedField, config, parentField }) { const selectedKey = selectedField; - const {maxLabelsLength, fieldSeparatorDisplay, fieldPlaceholder, fieldSeparator} = config.settings; + const { + maxLabelsLength, + fieldSeparatorDisplay, + fieldPlaceholder, + fieldSeparator, + } = config.settings; const isFieldSelected = !!selectedField; - const placeholder = !isFieldSelected ? truncateString(fieldPlaceholder, maxLabelsLength) : null; - const currField = isFieldSelected ? getFieldConfig(config, selectedKey) : null; + const placeholder = !isFieldSelected + ? truncateString(fieldPlaceholder, maxLabelsLength) + : null; + const currField = isFieldSelected + ? getFieldConfig(config, selectedKey) + : null; const selectedOpts = currField || {}; const selectedKeys = getFieldPath(selectedKey, config); const selectedPath = getFieldPath(selectedKey, config, true); const selectedLabel = this.getFieldLabel(currField, selectedKey, config); const partsLabels = getFieldPathLabels(selectedKey, config); - let selectedFullLabel = partsLabels ? partsLabels.join(fieldSeparatorDisplay) : null; + let selectedFullLabel = partsLabels + ? partsLabels.join(fieldSeparatorDisplay) + : null; if (selectedFullLabel == selectedLabel || parentField) selectedFullLabel = null; const selectedAltLabel = selectedOpts.label2; - const parentFieldPath = typeof parentField == "string" ? parentField.split(fieldSeparator) : parentField; - const parentFieldConfig = parentField ? getFieldConfig(config, parentField) : null; - const sourceFields = parentField ? parentFieldConfig && parentFieldConfig.subfields : config.fields; - const items = this.buildOptions(parentFieldPath, config, sourceFields, parentFieldPath); + const parentFieldPath = + typeof parentField == "string" + ? parentField.split(fieldSeparator) + : parentField; + const parentFieldConfig = parentField + ? getFieldConfig(config, parentField) + : null; + const sourceFields = parentField + ? parentFieldConfig && parentFieldConfig.subfields + : config.fields; + const items = this.buildOptions( + parentFieldPath, + config, + sourceFields, + parentFieldPath + ); return { - placeholder, items, parentField, - selectedKey, selectedKeys, selectedPath, selectedLabel, selectedOpts, selectedAltLabel, selectedFullLabel, + placeholder, + items, + parentField, + selectedKey, + selectedKeys, + selectedPath, + selectedLabel, + selectedOpts, + selectedAltLabel, + selectedFullLabel, }; } @@ -71,72 +107,94 @@ export default class Field extends PureComponent { if (!fieldKey) return null; let fieldSeparator = config.settings.fieldSeparator; let maxLabelsLength = config.settings.maxLabelsLength; - let fieldParts = Array.isArray(fieldKey) ? fieldKey : fieldKey.split(fieldSeparator); - let label = fieldOpts && fieldOpts.label || last(fieldParts); + let fieldParts = Array.isArray(fieldKey) + ? fieldKey + : fieldKey.split(fieldSeparator); + let label = (fieldOpts && fieldOpts.label) || last(fieldParts); label = truncateString(label, maxLabelsLength); return label; } - buildOptions(parentFieldPath, config, fields, path = null, optGroupLabel = null) { - if (!fields) - return null; - const {fieldSeparator, fieldSeparatorDisplay} = config.settings; + buildOptions( + parentFieldPath, + config, + fields, + path = null, + optGroupLabel = null + ) { + if (!fields) return null; + const { fieldSeparator, fieldSeparatorDisplay } = config.settings; const prefix = path ? path.join(fieldSeparator) + fieldSeparator : ""; - return keys(fields).map(fieldKey => { - const field = fields[fieldKey]; - const label = this.getFieldLabel(field, fieldKey, config); - const partsLabels = getFieldPathLabels(prefix+fieldKey, config); - let fullLabel = partsLabels.join(fieldSeparatorDisplay); - if (fullLabel == label || parentFieldPath) - fullLabel = null; - const altLabel = field.label2; - const tooltip = field.tooltip; - const subpath = (path ? path : []).concat(fieldKey); - const disabled = field.disabled; - - if (field.hideForSelect) - return undefined; - - if (field.type == "!struct") { - return { - disabled, - key: fieldKey, - path: prefix+fieldKey, - label, - fullLabel, - altLabel, - tooltip, - items: this.buildOptions(parentFieldPath, config, field.subfields, subpath, label) - }; - } else { - return { - disabled, - key: fieldKey, - path: prefix+fieldKey, - label, - fullLabel, - altLabel, - tooltip, - grouplabel: optGroupLabel - }; - } - }).filter(o => !!o); + return keys(fields) + .map((fieldKey) => { + const field = fields[fieldKey]; + const label = this.getFieldLabel(field, fieldKey, config); + const partsLabels = getFieldPathLabels(prefix + fieldKey, config); + let fullLabel = partsLabels.join(fieldSeparatorDisplay); + if (fullLabel == label || parentFieldPath) fullLabel = null; + const altLabel = field.label2; + const tooltip = field.tooltip; + const subpath = (path ? path : []).concat(fieldKey); + const disabled = field.disabled; + + if (field.hideForSelect) return undefined; + + if (field.type == "!struct") { + return { + disabled, + key: fieldKey, + path: prefix + fieldKey, + label, + fullLabel, + altLabel, + tooltip, + items: this.buildOptions( + parentFieldPath, + config, + field.subfields, + subpath, + label + ), + }; + } else { + return { + disabled, + key: fieldKey, + path: prefix + fieldKey, + label, + fullLabel, + altLabel, + tooltip, + grouplabel: optGroupLabel, + }; + } + }) + .filter((o) => !!o); } render() { - const {config, customProps, setField, readonly, id, groupId} = this.props; - const {renderField} = config.settings; - const renderProps = { + const { + config, + customProps, + setField, + setFieldSrc, + readonly, id, groupId, - config, - customProps, + } = this.props; + const { renderField } = config.settings; + const renderFieldProps = { + id, + groupId, + config, + customProps, readonly, setField, - ...this.meta + setFieldSrc, + ...this.meta, + // items: this.props.config.funcs, }; - return renderField(renderProps); + return renderField(renderFieldProps); } - } diff --git a/packages/ui/modules/components/rule/FieldWrapper.jsx b/packages/ui/modules/components/rule/FieldWrapper.jsx index e434cc193..c27577cf9 100644 --- a/packages/ui/modules/components/rule/FieldWrapper.jsx +++ b/packages/ui/modules/components/rule/FieldWrapper.jsx @@ -1,26 +1,90 @@ import React, { PureComponent } from "react"; import Field from "./Field"; -import {Col} from "../utils"; - +import { Col } from "../utils"; +import FuncWidget from "./FuncWidget"; export default class FieldWrapper extends PureComponent { - render() { - const {config, selectedField, setField, parentField, classname, readonly, id, groupId} = this.props; + renderFieldSources = () => { + const { config, readonly, selectedFieldSrc, setFieldSrc, id } = this.props; + const { settings } = config; + const { + fieldSources, + valueSourcesInfo, + renderValueSources: ValueSources, + } = settings; + const fieldSourcesOptions = fieldSources.map((srcKey) => [ + srcKey, + { + label: valueSourcesInfo[srcKey].label, + }, + ]); + + const sourceLabel = settings.showLabels ? ( + + ) : null; + return ( - - { config.settings.showLabels - && - } + fieldSources.length > 1 && + !readonly && ( +
+ {sourceLabel} + +
+ ) + ); + }; + + render() { + const { + config, + selectedField, + selectedFieldSrc, + setField, + parentField, + readonly, + id, + groupId, + } = this.props; + const fieldSources = this.renderFieldSources({ + valueSources: this.props.config.settings.fieldSources, + }); + const field = + this.props.selectedFieldSrc === "func" ? ( + + ) : ( + ); + return ( + + {[fieldSources, field]} ); } diff --git a/packages/ui/modules/components/rule/FuncSelect.jsx b/packages/ui/modules/components/rule/FuncSelect.jsx index 40fd28a9d..0d2bc4385 100644 --- a/packages/ui/modules/components/rule/FuncSelect.jsx +++ b/packages/ui/modules/components/rule/FuncSelect.jsx @@ -1,13 +1,14 @@ import React, { PureComponent } from "react"; import { Utils } from "@react-awesome-query-builder/core"; import PropTypes from "prop-types"; -import {truncateString} from "../../utils/stuff"; -import {useOnPropsChanged} from "../../utils/reactUtils"; +import { truncateString } from "../../utils/stuff"; +import { useOnPropsChanged } from "../../utils/reactUtils"; import last from "lodash/last"; import keys from "lodash/keys"; const { clone } = Utils; -const {getFieldConfig, getFuncConfig} = Utils.ConfigUtils; -const {getFieldPath, getFuncPathLabels, getWidgetForFieldOp} = Utils.RuleUtils; +const { getFieldConfig, getFuncConfig } = Utils.ConfigUtils; +const { getFieldPath, getFuncPathLabels, getWidgetForFieldOp } = + Utils.RuleUtils; //tip: this.props.value - right value, this.props.field - left value @@ -16,7 +17,8 @@ export default class FuncSelect extends PureComponent { id: PropTypes.string, groupId: PropTypes.string, config: PropTypes.object.isRequired, - field: PropTypes.string.isRequired, + field: PropTypes.any, + fieldSrc: PropTypes.string, operator: PropTypes.string, customProps: PropTypes.object, value: PropTypes.string, @@ -30,7 +32,7 @@ export default class FuncSelect extends PureComponent { constructor(props) { super(props); useOnPropsChanged(this); - + this.onPropsChanged(props); } @@ -38,8 +40,14 @@ export default class FuncSelect extends PureComponent { const prevProps = this.props; const keysForItems = ["config", "field", "operator", "isFuncArg"]; const keysForMeta = ["config", "field", "value"]; - const needUpdateItems = !this.items || keysForItems.map(k => (nextProps[k] !== prevProps[k])).filter(ch => ch).length > 0; - const needUpdateMeta = !this.meta || keysForMeta.map(k => (nextProps[k] !== prevProps[k])).filter(ch => ch).length > 0; + const needUpdateItems = + !this.items || + keysForItems.map((k) => nextProps[k] !== prevProps[k]).filter((ch) => ch) + .length > 0; + const needUpdateMeta = + !this.meta || + keysForMeta.map((k) => nextProps[k] !== prevProps[k]).filter((ch) => ch) + .length > 0; if (needUpdateMeta) { this.meta = this.getMeta(nextProps); @@ -49,47 +57,87 @@ export default class FuncSelect extends PureComponent { } } - getItems({config, field, operator, parentFuncs, fieldDefinition, isFuncArg}) { - const {canUseFuncForField} = config.settings; - const filteredFuncs = this.filterFuncs(config, config.funcs, field, operator, canUseFuncForField, parentFuncs, isFuncArg, fieldDefinition); + getItems({ + config, + field, + operator, + parentFuncs, + fieldDefinition, + isFuncArg, + }) { + const { canUseFuncForField } = config.settings; + const filteredFuncs = this.filterFuncs( + config, + config.funcs, + field, + operator, + canUseFuncForField, + parentFuncs, + isFuncArg, + fieldDefinition + ); const items = this.buildOptions(config, filteredFuncs); return items; } - getMeta({config, field, value}) { - const {funcPlaceholder, fieldSeparatorDisplay} = config.settings; + getMeta({ config, field, fieldSrc, value }) { + const { funcPlaceholder, fieldSeparatorDisplay } = config.settings; const selectedFuncKey = value; const isFuncSelected = !!value; - - const leftFieldConfig = getFieldConfig(config, field); - const leftFieldWidgetField = leftFieldConfig.widgets.field; - const leftFieldWidgetFieldProps = leftFieldWidgetField && leftFieldWidgetField.widgetProps || {}; + const leftFieldConfig = getFieldConfig(config, field, fieldSrc); + const leftFieldWidgetField = leftFieldConfig?.widgets?.field; + const leftFieldWidgetFieldProps = + (leftFieldWidgetField && leftFieldWidgetField.widgetProps) || {}; const placeholder = !isFuncSelected ? funcPlaceholder : null; - const currFunc = isFuncSelected ? getFuncConfig(config, selectedFuncKey) : null; + const currFunc = isFuncSelected + ? getFuncConfig(config, selectedFuncKey) + : null; const selectedOpts = currFunc || {}; const selectedKeys = getFieldPath(selectedFuncKey, config); const selectedPath = getFieldPath(selectedFuncKey, config, true); const selectedLabel = this.getFuncLabel(currFunc, selectedFuncKey, config); const partsLabels = getFuncPathLabels(selectedFuncKey, config); - let selectedFullLabel = partsLabels ? partsLabels.join(fieldSeparatorDisplay) : null; - if (selectedFullLabel == selectedLabel) - selectedFullLabel = null; - + let selectedFullLabel = partsLabels + ? partsLabels.join(fieldSeparatorDisplay) + : null; + if (selectedFullLabel == selectedLabel) selectedFullLabel = null; + return { placeholder, - selectedKey: selectedFuncKey, selectedKeys, selectedPath, selectedLabel, selectedOpts, selectedFullLabel, + selectedKey: selectedFuncKey, + selectedKeys, + selectedPath, + selectedLabel, + selectedOpts, + selectedFullLabel, }; } - filterFuncs(config, funcs, leftFieldFullkey, operator, canUseFuncForField, parentFuncs, isFuncArg, fieldDefinition) { + filterFuncs( + config, + funcs, + leftFieldFullkey, + operator, + canUseFuncForField, + parentFuncs, + isFuncArg, + fieldDefinition + ) { funcs = clone(funcs); + const fieldSrc = this.props.fieldSrc; const fieldSeparator = config.settings.fieldSeparator; - const leftFieldConfig = getFieldConfig(config, leftFieldFullkey); + const leftFieldConfig = getFieldConfig(config, leftFieldFullkey, fieldSrc); let expectedType; let targetDefinition = leftFieldConfig; - const widget = getWidgetForFieldOp(config, leftFieldFullkey, operator, "value"); + const widget = getWidgetForFieldOp( + config, + leftFieldFullkey, + operator, + "value", + fieldSrc + ); if (isFuncArg && fieldDefinition) { targetDefinition = fieldDefinition; expectedType = fieldDefinition.type; @@ -99,7 +147,7 @@ export default class FuncSelect extends PureComponent { //expectedType = leftFieldConfig.type; expectedType = widgetType; } else { - expectedType = leftFieldConfig.type; + expectedType = leftFieldConfig?.type; } function _filter(list, path) { @@ -109,19 +157,29 @@ export default class FuncSelect extends PureComponent { let funcFullkey = subpath.join(fieldSeparator); let funcConfig = getFuncConfig(config, funcFullkey); if (funcConfig.type == "!struct") { - if(_filter(subfields, subpath) == 0) - delete list[funcKey]; + if (_filter(subfields, subpath) == 0) delete list[funcKey]; } else { - let canUse = funcConfig.returnType == expectedType; - if (targetDefinition.funcs) + let canUse = !expectedType || funcConfig.returnType == expectedType; + if (targetDefinition?.funcs) canUse = canUse && targetDefinition.funcs.includes(funcFullkey); if (canUseFuncForField) - canUse = canUse && canUseFuncForField(leftFieldFullkey, leftFieldConfig, funcFullkey, funcConfig, operator); + canUse = + canUse && + canUseFuncForField( + leftFieldFullkey, + leftFieldConfig, + funcFullkey, + funcConfig, + operator + ); // don't use func in func (can be configurable, but usually users don't need this) - if (!funcConfig.allowSelfNesting && parentFuncs && parentFuncs.map(([func, _arg]) => func).includes(funcFullkey)) + if ( + !funcConfig.allowSelfNesting && + parentFuncs && + parentFuncs.map(([func, _arg]) => func).includes(funcFullkey) + ) canUse = false; - if (!canUse) - delete list[funcKey]; + if (!canUse) delete list[funcKey]; } } return keys(list).length; @@ -133,38 +191,36 @@ export default class FuncSelect extends PureComponent { } buildOptions(config, funcs, path = null, optGroupLabel = null) { - if (!funcs) - return null; - const {fieldSeparator, fieldSeparatorDisplay} = config.settings; + if (!funcs) return null; + const { fieldSeparator, fieldSeparatorDisplay } = config.settings; const prefix = path ? path.join(fieldSeparator) + fieldSeparator : ""; - return keys(funcs).map(funcKey => { + return keys(funcs).map((funcKey) => { const func = funcs[funcKey]; const label = this.getFuncLabel(func, funcKey, config); const partsLabels = getFuncPathLabels(funcKey, config); let fullLabel = partsLabels.join(fieldSeparatorDisplay); - if (fullLabel == label) - fullLabel = null; + if (fullLabel == label) fullLabel = null; const tooltip = func.tooltip; const subpath = (path ? path : []).concat(funcKey); if (func.type == "!struct") { return { key: funcKey, - path: prefix+funcKey, + path: prefix + funcKey, label, fullLabel, tooltip, - items: this.buildOptions(config, func.subfields, subpath, label) + items: this.buildOptions(config, func.subfields, subpath, label), }; } else { return { key: funcKey, - path: prefix+funcKey, + path: prefix + funcKey, label, fullLabel, tooltip, - grouplabel: optGroupLabel + grouplabel: optGroupLabel, }; } }); @@ -174,15 +230,17 @@ export default class FuncSelect extends PureComponent { if (!funcKey) return null; let fieldSeparator = config.settings.fieldSeparator; let maxLabelsLength = config.settings.maxLabelsLength; - let funcParts = Array.isArray(funcKey) ? funcKey : funcKey.split(fieldSeparator); + let funcParts = Array.isArray(funcKey) + ? funcKey + : funcKey.split(fieldSeparator); let label = funcOpts.label || last(funcParts); label = truncateString(label, maxLabelsLength); return label; } render() { - const {config, customProps, setValue, readonly, id, groupId} = this.props; - const {renderFunc} = config.settings; + const { config, customProps, setValue, readonly, id, groupId } = this.props; + const { renderFunc } = config.settings; const renderProps = { config, customProps, @@ -191,9 +249,8 @@ export default class FuncSelect extends PureComponent { items: this.items, id, groupId, - ...this.meta + ...this.meta, }; return renderFunc(renderProps); } - } diff --git a/packages/ui/modules/components/rule/FuncWidget.jsx b/packages/ui/modules/components/rule/FuncWidget.jsx index c7eee8858..75ef55107 100644 --- a/packages/ui/modules/components/rule/FuncWidget.jsx +++ b/packages/ui/modules/components/rule/FuncWidget.jsx @@ -3,18 +3,18 @@ import { Utils } from "@react-awesome-query-builder/core"; import PropTypes from "prop-types"; import FuncSelect from "./FuncSelect"; import Widget from "./Widget"; -import {Col} from "../utils"; -import {useOnPropsChanged} from "../../utils/reactUtils"; -const {getFuncConfig} = Utils.ConfigUtils; -const {setFunc, setArgValue, setArgValueSrc} = Utils.FuncUtils; - +import { Col } from "../utils"; +import { useOnPropsChanged } from "../../utils/reactUtils"; +const { getFuncConfig } = Utils.ConfigUtils; +const { setFunc, setArgValue, setArgValueSrc } = Utils.FuncUtils; export default class FuncWidget extends PureComponent { static propTypes = { id: PropTypes.string, groupId: PropTypes.string, config: PropTypes.object.isRequired, - field: PropTypes.string.isRequired, + field: PropTypes.any, + fieldSrc: PropTypes.string, operator: PropTypes.string, customProps: PropTypes.object, value: PropTypes.object, //instanceOf(Immutable.Map) //with keys 'func' and `args` @@ -35,58 +35,87 @@ export default class FuncWidget extends PureComponent { onPropsChanged(nextProps) { const prevProps = this.props; const keysForMeta = ["config", "field", "operator", "value"]; - const needUpdateMeta = !this.meta || keysForMeta.map(k => (nextProps[k] !== prevProps[k])).filter(ch => ch).length > 0; + const needUpdateMeta = + !this.meta || + keysForMeta.map((k) => nextProps[k] !== prevProps[k]).filter((ch) => ch) + .length > 0; if (needUpdateMeta) { this.meta = this.getMeta(nextProps); } } - getMeta({config, field, operator, value}) { + getMeta({ config, field, operator, value }) { const funcKey = value ? value.get("func") : null; const funcDefinition = funcKey ? getFuncConfig(config, funcKey) : null; return { - funcDefinition, funcKey + funcDefinition, + funcKey, }; } setFunc = (funcKey) => { - this.props.setValue( setFunc(this.props.value, funcKey, this.props.config) ); + this.props.setValue(setFunc(this.props.value, funcKey, this.props.config)); }; setArgValue = (argKey, argVal) => { - const {config} = this.props; - const {funcDefinition} = this.meta; - const {args} = funcDefinition; + const { config } = this.props; + const { funcDefinition } = this.meta; + const { args } = funcDefinition; const argDefinition = args[argKey]; - this.props.setValue( setArgValue(this.props.value, argKey, argVal, argDefinition, config) ); + this.props.setValue( + setArgValue(this.props.value, argKey, argVal, argDefinition, config) + ); }; setArgValueSrc = (argKey, argValSrc) => { - const {config} = this.props; - const {funcDefinition} = this.meta; - const {args} = funcDefinition; + const { config } = this.props; + const { funcDefinition } = this.meta; + const { args } = funcDefinition; const argDefinition = args[argKey]; - this.props.setValue( setArgValueSrc(this.props.value, argKey, argValSrc, argDefinition, config) ); + this.props.setValue( + setArgValueSrc(this.props.value, argKey, argValSrc, argDefinition, config) + ); }; renderFuncSelect = () => { - const {config, field, operator, customProps, value, readonly, parentFuncs, id, groupId, isFuncArg, fieldDefinition} = this.props; + const { + config, + field, + fieldSrc, + operator, + customProps, + value, + readonly, + parentFuncs, + id, + groupId, + isFuncArg, + fieldDefinition, + } = this.props; const funcKey = value ? value.get("func") : null; const selectProps = { value: funcKey, setValue: this.setFunc, - config, field, operator, customProps, readonly, parentFuncs, - isFuncArg, fieldDefinition, - id, groupId, + config, + field, + fieldSrc, + operator, + customProps, + readonly, + parentFuncs, + isFuncArg, + fieldDefinition, + id, + groupId, }; - const {showLabels, funcLabel} = config.settings; - const widgetLabel = showLabels - ? - : null; + const { showLabels, funcLabel } = config.settings; + const widgetLabel = showLabels ? ( + + ) : null; return ( @@ -97,43 +126,58 @@ export default class FuncWidget extends PureComponent { }; renderArgLabel = (argKey, argDefinition) => { - const {valueSources, type, showPrefix, label} = argDefinition; - const {config} = this.props; - const isConst = valueSources && valueSources.length == 1 && valueSources[0] == "const"; - const forceShow = !config.settings.showLabels && (type == "boolean" || isConst) && showPrefix; + const { valueSources, type, showPrefix, label } = argDefinition; + const { config } = this.props; + const isConst = + valueSources && valueSources.length == 1 && valueSources[0] == "const"; + const forceShow = + !config.settings.showLabels && + (type == "boolean" || isConst) && + showPrefix; if (!forceShow) return null; - return ( - - {label || argKey} - - ); + return {label || argKey}; }; renderArgLabelSep = (argKey, argDefinition) => { - const {valueSources, type, showPrefix} = argDefinition; - const {config} = this.props; - const isConst = valueSources && valueSources.length == 1 && valueSources[0] == "const"; - const forceShow = !config.settings.showLabels && (type == "boolean" || isConst) && showPrefix; + const { valueSources, type, showPrefix } = argDefinition; + const { config } = this.props; + const isConst = + valueSources && valueSources.length == 1 && valueSources[0] == "const"; + const forceShow = + !config.settings.showLabels && + (type == "boolean" || isConst) && + showPrefix; if (!forceShow) return null; - return ( - - {":"} - - ); + return {":"}; }; renderArgVal = (funcKey, argKey, argDefinition) => { - const {config, field, operator, value, readonly, parentFuncs, id, groupId} = this.props; + const { + config, + field, + fieldSrc, + value, + readonly, + parentFuncs, + id, + groupId, + } = this.props; const arg = value ? value.getIn(["args", argKey]) : null; const argVal = arg ? arg.get("value") : undefined; - const defaultValueSource = argDefinition.valueSources.length == 1 ? argDefinition.valueSources[0] : undefined; - const argValSrc = arg ? (arg.get("valueSrc") || defaultValueSource || "value") : defaultValueSource; + const defaultValueSource = + argDefinition.valueSources.length == 1 + ? argDefinition.valueSources[0] + : undefined; + const argValSrc = arg + ? arg.get("valueSrc") || defaultValueSource || "value" + : defaultValueSource; const widgetProps = { - config, + config, fieldFunc: funcKey, fieldArg: argKey, leftField: field, + fieldSrc, operator: null, value: argVal, valueSrc: argValSrc, @@ -156,7 +200,7 @@ export default class FuncWidget extends PureComponent { ); }; - renderArgSep = (argKey, argDefinition, argIndex, {renderSeps}) => { + renderArgSep = (argKey, argDefinition, argIndex, { renderSeps }) => { if (!argIndex) return null; return ( @@ -165,7 +209,7 @@ export default class FuncWidget extends PureComponent { ); }; - renderBracketBefore = ({renderBrackets}) => { + renderBracketBefore = ({ renderBrackets }) => { return ( {renderBrackets ? renderBrackets[0] : "("} @@ -173,7 +217,7 @@ export default class FuncWidget extends PureComponent { ); }; - renderBracketAfter = ({renderBrackets}) => { + renderBracketAfter = ({ renderBrackets }) => { return ( {renderBrackets ? renderBrackets[1] : ")"} @@ -182,9 +226,9 @@ export default class FuncWidget extends PureComponent { }; renderFuncArgs = () => { - const {funcDefinition, funcKey} = this.meta; + const { funcDefinition, funcKey } = this.meta; if (!funcKey) return null; - const {args} = funcDefinition; + const { args } = funcDefinition; if (!args) return null; return ( @@ -193,7 +237,12 @@ export default class FuncWidget extends PureComponent { {Object.keys(args).map((argKey, argIndex) => ( - {this.renderArgSep(argKey, args[argKey], argIndex, funcDefinition)} + {this.renderArgSep( + argKey, + args[argKey], + argIndex, + funcDefinition + )} {this.renderArgLabel(argKey, args[argKey])} {this.renderArgLabelSep(argKey, args[argKey])} {this.renderArgVal(funcKey, argKey, args[argKey])} @@ -215,7 +264,6 @@ export default class FuncWidget extends PureComponent { } } - class ArgWidget extends PureComponent { static propTypes = { funcKey: PropTypes.string.isRequired, @@ -229,22 +277,22 @@ class ArgWidget extends PureComponent { }; setValue = (_delta, value, _widgetType) => { - const {setValue, argKey} = this.props; + const { setValue, argKey } = this.props; setValue(argKey, value); }; setValueSrc = (_delta, valueSrc, _widgetType) => { - const {setValueSrc, argKey} = this.props; + const { setValueSrc, argKey } = this.props; setValueSrc(argKey, valueSrc); }; render() { - const {funcKey, argKey, parentFuncs} = this.props; + const { funcKey, argKey, parentFuncs } = this.props; return ( diff --git a/packages/ui/modules/components/rule/Operator.jsx b/packages/ui/modules/components/rule/Operator.jsx index fb64c6ce8..8affc61ec 100644 --- a/packages/ui/modules/components/rule/Operator.jsx +++ b/packages/ui/modules/components/rule/Operator.jsx @@ -4,16 +4,16 @@ import PropTypes from "prop-types"; import keys from "lodash/keys"; import pickBy from "lodash/pickBy"; import mapValues from "lodash/mapValues"; -import {useOnPropsChanged} from "../../utils/reactUtils"; -const {getFieldConfig, getOperatorConfig} = Utils.ConfigUtils; - +import { useOnPropsChanged } from "../../utils/reactUtils"; +const { getFieldConfig, getFuncConfig, getOperatorConfig } = Utils.ConfigUtils; export default class Operator extends PureComponent { static propTypes = { id: PropTypes.string, groupId: PropTypes.string, config: PropTypes.object.isRequired, - selectedField: PropTypes.string, + selectedField: PropTypes.any, + selectedFieldSrc: PropTypes.string, selectedOperator: PropTypes.string, readonly: PropTypes.bool, //actions @@ -30,25 +30,25 @@ export default class Operator extends PureComponent { onPropsChanged(nextProps) { const prevProps = this.props; const keysForMeta = ["config", "selectedField", "selectedOperator"]; - const needUpdateMeta = !this.meta || keysForMeta.map(k => (nextProps[k] !== prevProps[k])).filter(ch => ch).length > 0; + const needUpdateMeta = + !this.meta || + keysForMeta.map((k) => nextProps[k] !== prevProps[k]).filter((ch) => ch) + .length > 0; if (needUpdateMeta) { this.meta = this.getMeta(nextProps); } } - getMeta({config, selectedField, selectedOperator}) { - const fieldConfig = getFieldConfig(config, selectedField); + getMeta({ config, selectedField, selectedFieldSrc, selectedOperator }) { + const fieldConfig = getFieldConfig(config, selectedField, selectedFieldSrc); const operators = fieldConfig?.operators; - const operatorOptions - = mapValues( - pickBy( - config.operators, - (item, key) => operators?.indexOf(key) !== -1 - ), - (_opts, op) => getOperatorConfig(config, op, selectedField) - ); - + const operatorOptions = mapValues( + pickBy(config.operators, (item, key) => operators?.indexOf(key) !== -1), + (_opts, op) => + getOperatorConfig(config, op, selectedField, selectedFieldSrc) + ); + const items = this.buildOptions(config, operatorOptions, operators); const isOpSelected = !!selectedOperator; @@ -59,44 +59,49 @@ export default class Operator extends PureComponent { const selectedKeys = isOpSelected ? [selectedKey] : null; const selectedPath = selectedKeys; const selectedLabel = selectedOpts.label; - + return { - placeholder, items, - selectedKey, selectedKeys, selectedPath, selectedLabel, selectedOpts, fieldConfig + placeholder, + items, + selectedKey, + selectedKeys, + selectedPath, + selectedLabel, + selectedOpts, + fieldConfig, }; } buildOptions(config, fields, ops) { - if (!fields || !ops) - return null; + if (!fields || !ops) return null; - return keys(fields).sort((a, b) => (ops.indexOf(a) - ops.indexOf(b))).map(fieldKey => { - const field = fields[fieldKey]; - const label = field.label; - return { - key: fieldKey, - path: fieldKey, - label, - }; - }); + return keys(fields) + .sort((a, b) => ops.indexOf(a) - ops.indexOf(b)) + .map((fieldKey) => { + const field = fields[fieldKey]; + const label = field.label; + return { + key: fieldKey, + path: fieldKey, + label, + }; + }); } render() { - const {config, customProps, setOperator, readonly, id, groupId} = this.props; - const {renderOperator} = config.settings; + const { config, customProps, setOperator, readonly, id, groupId } = + this.props; + const { renderOperator } = config.settings; const renderProps = { id, groupId, - config, - customProps, + config, + customProps, readonly, setField: setOperator, - ...this.meta + ...this.meta, }; - if (!renderProps.items) - return null; + if (!renderProps.items) return null; return renderOperator(renderProps); } - - } diff --git a/packages/ui/modules/components/rule/OperatorOptions.jsx b/packages/ui/modules/components/rule/OperatorOptions.jsx index 21e98e6e5..24648e7fd 100644 --- a/packages/ui/modules/components/rule/OperatorOptions.jsx +++ b/packages/ui/modules/components/rule/OperatorOptions.jsx @@ -1,13 +1,14 @@ import React, { PureComponent } from "react"; import { Utils } from "@react-awesome-query-builder/core"; import PropTypes from "prop-types"; -const {getOperatorConfig} = Utils.ConfigUtils; +const { getOperatorConfig } = Utils.ConfigUtils; export default class OperatorOptions extends PureComponent { static propTypes = { config: PropTypes.object.isRequired, operatorOptions: PropTypes.any.isRequired, //instanceOf(Immutable.Map) selectedField: PropTypes.string.isRequired, + selectedFieldSrc: PropTypes.string, selectedOperator: PropTypes.string.isRequired, readonly: PropTypes.bool, //actions @@ -15,17 +16,23 @@ export default class OperatorOptions extends PureComponent { }; render() { - if (!this.props.selectedOperator) - return null; - const operatorDefinitions = getOperatorConfig(this.props.config, this.props.selectedOperator, this.props.selectedField); + if (!this.props.selectedOperator) return null; + const operatorDefinitions = getOperatorConfig( + this.props.config, + this.props.selectedOperator, + this.props.selectedField, + this.props.selectedFieldSrc + ); if (typeof operatorDefinitions.options === "undefined") { return null; } - const { factory: optionsFactory, ...basicOptionsProps } = operatorDefinitions.options; + const { factory: optionsFactory, ...basicOptionsProps } = + operatorDefinitions.options; const optionsProps = Object.assign({}, basicOptionsProps, { config: this.props.config, field: this.props.selectedField, + fieldSrc: this.props.selectedFieldSrc, operator: this.props.selectedOperator, options: this.props.operatorOptions, setOption: this.props.setOperatorOption, diff --git a/packages/ui/modules/components/rule/OperatorWrapper.jsx b/packages/ui/modules/components/rule/OperatorWrapper.jsx index 822a85e2d..f641d1f62 100644 --- a/packages/ui/modules/components/rule/OperatorWrapper.jsx +++ b/packages/ui/modules/components/rule/OperatorWrapper.jsx @@ -1,44 +1,61 @@ import React, { PureComponent } from "react"; import Operator from "./Operator"; -import {Col} from "../utils"; - +import { Col } from "../utils"; export default class OperatorWrapper extends PureComponent { render() { const { - config, selectedField, selectedOperator, setOperator, - selectedFieldPartsLabels, showOperator, showOperatorLabel, selectedFieldWidgetConfig, readonly, id, groupId + config, + selectedField, + selectedFieldSrc, + selectedOperator, + setOperator, + selectedFieldPartsLabels, + showOperator, + showOperatorLabel, + selectedFieldWidgetConfig, + readonly, + id, + groupId, } = this.props; - const operator = showOperator - && - { config.settings.showLabels - && - } - - ; - const hiddenOperator = showOperatorLabel - && -
- {config.settings.showLabels - ? - : null} -
- {selectedFieldWidgetConfig.operatorInlineLabel} -
-
- ; - return [ - operator, - hiddenOperator - ]; + const operator = showOperator && ( + + {config.settings.showLabels && ( + + )} + + + ); + const hiddenOperator = showOperatorLabel && ( + +
+ {config.settings.showLabels ? ( + + ) : null} +
+ + {selectedFieldWidgetConfig.operatorInlineLabel} + +
+
+ + ); + return [operator, hiddenOperator]; } } diff --git a/packages/ui/modules/components/rule/ValueField.jsx b/packages/ui/modules/components/rule/ValueField.jsx index 3b1cecc2c..95d30a197 100644 --- a/packages/ui/modules/components/rule/ValueField.jsx +++ b/packages/ui/modules/components/rule/ValueField.jsx @@ -1,13 +1,14 @@ import React, { PureComponent } from "react"; import { Utils } from "@react-awesome-query-builder/core"; import PropTypes from "prop-types"; -import {truncateString} from "../../utils/stuff"; -import {useOnPropsChanged} from "../../utils/reactUtils"; +import { truncateString } from "../../utils/stuff"; +import { useOnPropsChanged } from "../../utils/reactUtils"; import last from "lodash/last"; import keys from "lodash/keys"; -const {clone} = Utils; -const {getFieldConfig} = Utils.ConfigUtils; -const {getFieldPath, getFieldPathLabels, getWidgetForFieldOp} = Utils.RuleUtils; +const { clone } = Utils; +const { getFieldConfig, getFuncConfig } = Utils.ConfigUtils; +const { getFieldPath, getFieldPathLabels, getWidgetForFieldOp } = + Utils.RuleUtils; //tip: this.props.value - right value, this.props.field - left value @@ -17,7 +18,8 @@ export default class ValueField extends PureComponent { groupId: PropTypes.string, setValue: PropTypes.func.isRequired, config: PropTypes.object.isRequired, - field: PropTypes.string.isRequired, + field: PropTypes.any, + fieldSrc: PropTypes.string, value: PropTypes.string, operator: PropTypes.string, customProps: PropTypes.object, @@ -36,10 +38,30 @@ export default class ValueField extends PureComponent { onPropsChanged(nextProps) { const prevProps = this.props; - const keysForItems = ["config", "field", "operator", "isFuncArg", "parentField"]; - const keysForMeta = ["config", "field", "operator", "value", "placeholder", "isFuncArg", "parentField"]; - const needUpdateItems = !this.items || keysForItems.map(k => (nextProps[k] !== prevProps[k])).filter(ch => ch).length > 0; - const needUpdateMeta = !this.meta || keysForMeta.map(k => (nextProps[k] !== prevProps[k])).filter(ch => ch).length > 0; + const keysForItems = [ + "config", + "field", + "operator", + "isFuncArg", + "parentField", + ]; + const keysForMeta = [ + "config", + "field", + "operator", + "value", + "placeholder", + "isFuncArg", + "parentField", + ]; + const needUpdateItems = + !this.items || + keysForItems.map((k) => nextProps[k] !== prevProps[k]).filter((ch) => ch) + .length > 0; + const needUpdateMeta = + !this.meta || + keysForMeta.map((k) => nextProps[k] !== prevProps[k]).filter((ch) => ch) + .length > 0; if (needUpdateItems) { this.items = this.getItems(nextProps); @@ -49,52 +71,136 @@ export default class ValueField extends PureComponent { } } - getItems({config, field, operator, parentField, isFuncArg, fieldDefinition}) { - const {canCompareFieldWithField} = config.settings; + getItems({ + config, + field, + operator, + parentField, + isFuncArg, + fieldDefinition, + }) { + const { canCompareFieldWithField } = config.settings; const fieldSeparator = config.settings.fieldSeparator; - const parentFieldPath = typeof parentField == "string" ? parentField.split(fieldSeparator) : parentField; - const parentFieldConfig = parentField ? getFieldConfig(config, parentField) : null; - const sourceFields = parentField ? parentFieldConfig && parentFieldConfig.subfields : config.fields; + const parentFieldPath = + typeof parentField == "string" + ? parentField.split(fieldSeparator) + : parentField; + const parentFieldConfig = parentField + ? getFieldConfig(config, parentField) + : null; + const sourceFields = parentField + ? parentFieldConfig && parentFieldConfig.subfields + : config.fields; - const filteredFields = this.filterFields(config, sourceFields, field, parentField, parentFieldPath, operator, canCompareFieldWithField, isFuncArg, fieldDefinition); - const items = this.buildOptions(parentFieldPath, config, filteredFields, parentFieldPath); + const filteredFields = this.filterFields( + config, + sourceFields, + field, + parentField, + parentFieldPath, + operator, + canCompareFieldWithField, + isFuncArg, + fieldDefinition + ); + const items = this.buildOptions( + parentFieldPath, + config, + filteredFields, + parentFieldPath + ); return items; } - getMeta({config, field, operator, value, placeholder: customPlaceholder, isFuncArg, parentField}) { - const {fieldPlaceholder, fieldSeparatorDisplay} = config.settings; + getMeta({ + config, + field, + operator, + value, + placeholder: customPlaceholder, + isFuncArg, + parentField, + }) { + const { fieldPlaceholder, fieldSeparatorDisplay } = config.settings; const selectedKey = value; const isFieldSelected = !!value; - - const leftFieldConfig = getFieldConfig(config, field); - const leftFieldWidgetField = leftFieldConfig.widgets.field; - const leftFieldWidgetFieldProps = leftFieldWidgetField && leftFieldWidgetField.widgetProps || {}; - const placeholder = isFieldSelected ? null - : (isFuncArg && customPlaceholder || leftFieldWidgetFieldProps.valuePlaceholder || fieldPlaceholder); - const currField = isFieldSelected ? getFieldConfig(config, selectedKey) : null; - const selectedOpts = currField || {}; - + const currField = isFieldSelected + ? getFieldConfig(config, selectedKey) + : null; const selectedKeys = getFieldPath(selectedKey, config); const selectedPath = getFieldPath(selectedKey, config, true); const selectedLabel = this.getFieldLabel(currField, selectedKey, config); + const selectedOpts = currField || {}; + const selectedAltLabel = selectedOpts.label2; const partsLabels = getFieldPathLabels(selectedKey, config); - let selectedFullLabel = partsLabels ? partsLabels.join(fieldSeparatorDisplay) : null; + let selectedFullLabel = partsLabels + ? partsLabels.join(fieldSeparatorDisplay) + : null; if (selectedFullLabel == selectedLabel || parentField) selectedFullLabel = null; - const selectedAltLabel = selectedOpts.label2; + + if (!field) { + // LHS func + const placeholder = isFieldSelected + ? null + : (isFuncArg && customPlaceholder) || fieldPlaceholder; + return { + placeholder, + selectedKey, + selectedKeys, + selectedPath, + selectedLabel, + selectedOpts, + selectedAltLabel, + selectedFullLabel, + }; + } + + const leftFieldConfig = getFieldConfig(config, field, this.props.fieldSrc); + const leftFieldWidgetField = leftFieldConfig.widgets.field; + const leftFieldWidgetFieldProps = + (leftFieldWidgetField && leftFieldWidgetField.widgetProps) || {}; + const placeholder = isFieldSelected + ? null + : (isFuncArg && customPlaceholder) || + leftFieldWidgetFieldProps.valuePlaceholder || + fieldPlaceholder; return { placeholder, - selectedKey, selectedKeys, selectedPath, selectedLabel, selectedOpts, selectedAltLabel, selectedFullLabel, + selectedKey, + selectedKeys, + selectedPath, + selectedLabel, + selectedOpts, + selectedAltLabel, + selectedFullLabel, }; } - filterFields(config, fields, leftFieldFullkey, parentField, parentFieldPath, operator, canCompareFieldWithField, isFuncArg, fieldDefinition) { + filterFields( + config, + fields, + leftFieldFullkey, + parentField, + parentFieldPath, + operator, + canCompareFieldWithField, + isFuncArg, + fieldDefinition + ) { fields = clone(fields); + const fieldSrc = this.props.fieldSrc; const fieldSeparator = config.settings.fieldSeparator; - const leftFieldConfig = getFieldConfig(config, leftFieldFullkey); + const leftFieldConfig = getFieldConfig(config, leftFieldFullkey, fieldSrc); const _relyOnWidgetType = false; //TODO: remove this, see issue #758 - const widget = getWidgetForFieldOp(config, leftFieldFullkey, operator, "value"); + const widget = getWidgetForFieldOp( + config, + leftFieldFullkey, + operator, + "value", + fieldSrc + ); const widgetConfig = config.widgets[widget]; let expectedType; if (isFuncArg && fieldDefinition) { @@ -102,9 +208,9 @@ export default class ValueField extends PureComponent { } else if (_relyOnWidgetType && widgetConfig) { expectedType = widgetConfig.type; } else { - expectedType = leftFieldConfig.type; + expectedType = leftFieldConfig.type ?? leftFieldConfig.returnType; } - + function _filter(list, path) { for (let rightFieldKey in list) { let subfields = list[rightFieldKey].subfields; @@ -113,84 +219,108 @@ export default class ValueField extends PureComponent { let rightFieldConfig = getFieldConfig(config, rightFieldFullkey); if (!rightFieldConfig) { delete list[rightFieldKey]; - } else if (rightFieldConfig.type == "!struct" || rightFieldConfig.type == "!group") { - if(_filter(subfields, subpath) == 0) - delete list[rightFieldKey]; + } else if ( + rightFieldConfig.type == "!struct" || + rightFieldConfig.type == "!group" + ) { + if (_filter(subfields, subpath) == 0) delete list[rightFieldKey]; } else { // tip: LHS field can be used as arg in RHS function - let canUse = rightFieldConfig.type == expectedType && (isFuncArg ? true : rightFieldFullkey != leftFieldFullkey); - let fn = canCompareFieldWithField || config.settings.canCompareFieldWithField; + let canUse = + rightFieldConfig.type == expectedType && + (isFuncArg ? true : rightFieldFullkey != leftFieldFullkey); + let fn = + canCompareFieldWithField || + config.settings.canCompareFieldWithField; if (fn) - canUse = canUse && fn(leftFieldFullkey, leftFieldConfig, rightFieldFullkey, rightFieldConfig, operator); - if (!canUse) - delete list[rightFieldKey]; + canUse = + canUse && + fn( + leftFieldFullkey, + leftFieldConfig, + rightFieldFullkey, + rightFieldConfig, + operator + ); + if (!canUse) delete list[rightFieldKey]; } } return keys(list).length; } - _filter(fields, parentFieldPath || []); - return fields; } - buildOptions(parentFieldPath, config, fields, path = null, optGroupLabel = null) { - if (!fields) - return null; - const {fieldSeparator, fieldSeparatorDisplay} = config.settings; + buildOptions( + parentFieldPath, + config, + fields, + path = null, + optGroupLabel = null + ) { + if (!fields) return null; + const { fieldSeparator, fieldSeparatorDisplay } = config.settings; const prefix = path ? path.join(fieldSeparator) + fieldSeparator : ""; - return keys(fields).map(fieldKey => { - const field = fields[fieldKey]; - const label = this.getFieldLabel(field, fieldKey, config); - const partsLabels = getFieldPathLabels(fieldKey, config); - let fullLabel = partsLabels.join(fieldSeparatorDisplay); - if (fullLabel == label || parentFieldPath) - fullLabel = null; - const altLabel = field.label2; - const tooltip = field.tooltip; - const subpath = (path ? path : []).concat(fieldKey); - - if (field.hideForCompare) - return undefined; - - if (field.type == "!struct" || field.type == "!group") { - return { - key: fieldKey, - path: prefix+fieldKey, - label, - fullLabel, - altLabel, - tooltip, - items: this.buildOptions(parentFieldPath, config, field.subfields, subpath, label) - }; - } else { - return { - key: fieldKey, - path: prefix+fieldKey, - label, - fullLabel, - altLabel, - tooltip, - grouplabel: optGroupLabel - }; - } - }).filter(o => !!o); + return keys(fields) + .map((fieldKey) => { + const field = fields[fieldKey]; + const label = this.getFieldLabel(field, fieldKey, config); + const partsLabels = getFieldPathLabels(fieldKey, config); + let fullLabel = partsLabels.join(fieldSeparatorDisplay); + if (fullLabel == label || parentFieldPath) fullLabel = null; + const altLabel = field.label2; + const tooltip = field.tooltip; + const subpath = (path ? path : []).concat(fieldKey); + + if (field.hideForCompare) return undefined; + + if (field.type == "!struct" || field.type == "!group") { + return { + key: fieldKey, + path: prefix + fieldKey, + label, + fullLabel, + altLabel, + tooltip, + items: this.buildOptions( + parentFieldPath, + config, + field.subfields, + subpath, + label + ), + }; + } else { + return { + key: fieldKey, + path: prefix + fieldKey, + label, + fullLabel, + altLabel, + tooltip, + grouplabel: optGroupLabel, + }; + } + }) + .filter((o) => !!o); } getFieldLabel(fieldOpts, fieldKey, config) { if (!fieldKey) return null; let fieldSeparator = config.settings.fieldSeparator; let maxLabelsLength = config.settings.maxLabelsLength; - let fieldParts = Array.isArray(fieldKey) ? fieldKey : fieldKey.split(fieldSeparator); + let fieldParts = Array.isArray(fieldKey) + ? fieldKey + : fieldKey.split(fieldSeparator); let label = fieldOpts.label || last(fieldParts); label = truncateString(label, maxLabelsLength); return label; } render() { - const {config, customProps, setValue, readonly, id, groupId} = this.props; - const {renderField} = config.settings; + const { config, customProps, setValue, readonly, id, groupId } = this.props; + const { renderField } = config.settings; const renderProps = { config, customProps, @@ -199,9 +329,8 @@ export default class ValueField extends PureComponent { items: this.items, id, groupId, - ...this.meta + ...this.meta, }; return renderField(renderProps); } - } diff --git a/packages/ui/modules/components/rule/Widget.jsx b/packages/ui/modules/components/rule/Widget.jsx index 46ffec38d..50e5a45c7 100644 --- a/packages/ui/modules/components/rule/Widget.jsx +++ b/packages/ui/modules/components/rule/Widget.jsx @@ -2,16 +2,26 @@ import React, { PureComponent } from "react"; import { Utils } from "@react-awesome-query-builder/core"; import PropTypes from "prop-types"; import range from "lodash/range"; -import {defaultValue} from "../../utils/stuff"; -import {useOnPropsChanged} from "../../utils/reactUtils"; +import { defaultValue } from "../../utils/stuff"; +import { useOnPropsChanged } from "../../utils/reactUtils"; import pick from "lodash/pick"; import WidgetFactory from "./WidgetFactory"; -import {Col} from "../utils"; -const {getFieldConfig, getOperatorConfig, getFieldWidgetConfig} = Utils.ConfigUtils; -const {getValueSourcesForFieldOp, getWidgetsForFieldOp, getWidgetForFieldOp, getValueLabel} = Utils.RuleUtils; +import { Col } from "../utils"; +const { + getFieldConfig, + getFuncConfig, + getOperatorConfig, + getFieldWidgetConfig, +} = Utils.ConfigUtils; +const { + getValueSourcesForFieldOp, + getWidgetsForFieldOp, + getWidgetForFieldOp, + getValueLabel, +} = Utils.RuleUtils; const { createListFromArray } = Utils.DefaultUtils; -const funcArgDummyOpDef = {cardinality: 1}; +const funcArgDummyOpDef = { cardinality: 1 }; export default class Widget extends PureComponent { static propTypes = { @@ -19,7 +29,8 @@ export default class Widget extends PureComponent { value: PropTypes.any, //instanceOf(Immutable.List) valueSrc: PropTypes.any, //instanceOf(Immutable.List) valueError: PropTypes.any, - field: PropTypes.string, + field: PropTypes.any, + fieldSrc: PropTypes.string, operator: PropTypes.string, readonly: PropTypes.bool, asyncListValues: PropTypes.array, @@ -32,7 +43,7 @@ export default class Widget extends PureComponent { isFuncArg: PropTypes.bool, fieldFunc: PropTypes.string, fieldArg: PropTypes.string, - leftField: PropTypes.string, + leftField: PropTypes.any, // for RuleGroupExt isForRuleGruop: PropTypes.bool, parentField: PropTypes.string, @@ -52,31 +63,68 @@ export default class Widget extends PureComponent { onPropsChanged(nextProps) { const prevProps = this.props; const keysForMeta = [ - "config", "field", "fieldFunc", "fieldArg", "leftField", "operator", "valueSrc", "isFuncArg", "asyncListValues" + "config", + "field", + "fieldFunc", + "fieldArg", + "leftField", + "operator", + "valueSrc", + "isFuncArg", + "asyncListValues", ]; - const needUpdateMeta = !this.meta - || keysForMeta - .map(k => ( - nextProps[k] !== prevProps[k] - //tip: for isFuncArg we need to wrap value in Imm list - || k == "isFuncArg" && nextProps["isFuncArg"] && nextProps["value"] !== prevProps["value"]) - ) - .filter(ch => ch).length > 0; + const needUpdateMeta = + !this.meta || + keysForMeta + .map( + (k) => + nextProps[k] !== prevProps[k] || + //tip: for isFuncArg we need to wrap value in Imm list + (k == "isFuncArg" && + nextProps["isFuncArg"] && + nextProps["value"] !== prevProps["value"]) + ) + .filter((ch) => ch).length > 0; if (needUpdateMeta) { this.meta = this.getMeta(nextProps); } } - _setValue = (isSpecialRange, delta, widgetType, value, asyncListValues, __isInternal) => { + _setValue = ( + isSpecialRange, + delta, + widgetType, + value, + asyncListValues, + __isInternal + ) => { if (isSpecialRange && Array.isArray(value)) { const oldRange = [this.props.value.get(0), this.props.value.get(1)]; if (oldRange[0] != value[0]) - this.props.setValue(0, value[0], widgetType, asyncListValues, __isInternal); + this.props.setValue( + 0, + value[0], + widgetType, + asyncListValues, + __isInternal + ); if (oldRange[1] != value[1]) - this.props.setValue(1, value[1], widgetType, asyncListValues, __isInternal); + this.props.setValue( + 1, + value[1], + widgetType, + asyncListValues, + __isInternal + ); } else { - this.props.setValue(delta, value, widgetType, asyncListValues, __isInternal); + this.props.setValue( + delta, + value, + widgetType, + asyncListValues, + __isInternal + ); } }; @@ -85,46 +133,137 @@ export default class Widget extends PureComponent { }; getMeta({ - config, field: simpleField, fieldFunc, fieldArg, operator, valueSrc: valueSrcs, value: values, - isForRuleGruop, isCaseValue, isFuncArg, leftField, asyncListValues + config, + field: simpleField, + fieldSrc, + fieldFunc, + fieldArg, + operator, + valueSrc: valueSrcs, + value: values, + isForRuleGruop: isForRuleGroup, + isCaseValue, + isFuncArg, + leftField, + asyncListValues, }) { - const field = isFuncArg ? {func: fieldFunc, arg: fieldArg} : simpleField; + const field = isFuncArg ? { func: fieldFunc, arg: fieldArg } : simpleField; let iValueSrcs = valueSrcs; let iValues = values; - if (isFuncArg || isForRuleGruop || isCaseValue) { + if (isFuncArg || isForRuleGroup || isCaseValue) { iValueSrcs = createListFromArray([valueSrcs]); iValues = createListFromArray([values]); } - const fieldDefinition = getFieldConfig(config, field); - const defaultWidget = getWidgetForFieldOp(config, field, operator); - const _widgets = getWidgetsForFieldOp(config, field, operator); - const operatorDefinition = isFuncArg ? funcArgDummyOpDef : getOperatorConfig(config, operator, field); - if ((fieldDefinition == null || operatorDefinition == null) && !isCaseValue) { + const fieldDefinition = isFuncArg + ? getFieldConfig(config, field) + : getFieldConfig(config, field, fieldSrc); + const defaultWidget = isFuncArg + ? getWidgetForFieldOp(config, field, operator) + : getWidgetForFieldOp(config, field, operator, null, fieldSrc); + const operatorDefinition = isFuncArg + ? funcArgDummyOpDef + : getOperatorConfig(config, operator, field, fieldSrc); + if ( + (fieldDefinition == null || operatorDefinition == null) && + !isCaseValue + ) { return null; } const isSpecialRange = operatorDefinition?.isSpecialRange; - const isSpecialRangeForSrcField = isSpecialRange && (iValueSrcs.get(0) == "field" || iValueSrcs.get(1) == "field"); + const isSpecialRangeForSrcField = + isSpecialRange && + (iValueSrcs.get(0) == "field" || iValueSrcs.get(1) == "field"); const isTrueSpecialRange = isSpecialRange && !isSpecialRangeForSrcField; - const cardinality = isTrueSpecialRange ? 1 : defaultValue(operatorDefinition?.cardinality, 1); + const cardinality = isTrueSpecialRange + ? 1 + : defaultValue(operatorDefinition?.cardinality, 1); if (cardinality === 0) { return null; } - const valueSources = getValueSourcesForFieldOp(config, field, operator, fieldDefinition, isFuncArg ? leftField : null); + const valueSources = isFuncArg + ? getValueSourcesForFieldOp( + config, + field, + operator, + fieldDefinition, + leftField + ) + : getValueSourcesForFieldOp( + config, + field, + operator, + fieldDefinition, + null, + fieldSrc + ); - const widgets = range(0, cardinality).map(delta => { + const widgets = range(0, cardinality).map((delta) => { const valueSrc = iValueSrcs.get(delta) || null; - let widget = getWidgetForFieldOp(config, field, operator, valueSrc); - let widgetDefinition = getFieldWidgetConfig(config, field, operator, widget, valueSrc); + let widget = isFuncArg + ? getWidgetForFieldOp(config, field, operator, valueSrc) + : getWidgetForFieldOp(config, field, operator, valueSrc, fieldSrc); + let widgetDefinition = isFuncArg + ? getFieldWidgetConfig(config, field, operator, widget, valueSrc) + : getFieldWidgetConfig( + config, + field, + operator, + widget, + valueSrc, + fieldSrc + ); if (isSpecialRangeForSrcField) { widget = widgetDefinition.singleWidget; - widgetDefinition = getFieldWidgetConfig(config, field, operator, widget, valueSrc); + widgetDefinition = getFieldWidgetConfig( + config, + field, + operator, + widget, + valueSrc + ); } const widgetType = widgetDefinition?.type; - const valueLabel = getValueLabel(config, field, operator, delta, valueSrc, isTrueSpecialRange); - const widgetValueLabel = getValueLabel(config, field, operator, delta, null, isTrueSpecialRange); - const sepText = operatorDefinition?.textSeparators ? operatorDefinition?.textSeparators[delta] : null; + const valueLabel = isFuncArg + ? getValueLabel( + config, + field, + operator, + delta, + valueSrc, + isTrueSpecialRange + ) + : getValueLabel( + config, + field, + operator, + delta, + valueSrc, + isTrueSpecialRange, + fieldSrc + ); + const widgetValueLabel = isFuncArg + ? getValueLabel( + config, + field, + operator, + delta, + null, + isTrueSpecialRange + ) + : getValueLabel( + config, + field, + operator, + delta, + null, + isTrueSpecialRange, + fieldSrc + ); + const sepText = operatorDefinition?.textSeparators + ? operatorDefinition?.textSeparators[delta] + : null; const setValueSrcHandler = this._onChangeValueSrc.bind(this, delta); let valueLabels = null; @@ -132,16 +271,21 @@ export default class Widget extends PureComponent { if (isSpecialRange) { valueLabels = [ getValueLabel(config, field, operator, 0), - getValueLabel(config, field, operator, 1) + getValueLabel(config, field, operator, 1), ]; valueLabels = { - placeholder: [ valueLabels[0].placeholder, valueLabels[1].placeholder ], - label: [ valueLabels[0].label, valueLabels[1].label ], + placeholder: [valueLabels[0].placeholder, valueLabels[1].placeholder], + label: [valueLabels[0].label, valueLabels[1].label], }; textSeparators = operatorDefinition?.textSeparators; } - const setValueHandler = this._setValue.bind(this, isSpecialRange, delta, widgetType); + const setValueHandler = this._setValue.bind( + this, + isSpecialRange, + delta, + widgetType + ); return { valueSrc, @@ -153,10 +297,10 @@ export default class Widget extends PureComponent { widgetValueLabel, valueLabels, textSeparators, - setValueHandler + setValueHandler, }; }); - + return { defaultWidget, fieldDefinition, @@ -172,19 +316,32 @@ export default class Widget extends PureComponent { } renderWidget = (delta, meta, props) => { - const {config, isFuncArg, leftField, operator, value: values, valueError, readonly, parentField, parentFuncs, id, groupId} = props; - const {settings} = config; + const { + config, + isFuncArg, + leftField, + operator, + value: values, + valueError, + readonly, + parentField, + parentFuncs, + id, + groupId, + fieldSrc, + } = props; + const { settings } = config; const { widgets, iValues, aField } = meta; const value = isFuncArg ? iValues : values; const field = isFuncArg ? leftField : aField; - const {valueSrc, valueLabel} = widgets[delta]; - - const widgetLabel = settings.showLabels - ? - : null; + const { valueSrc, valueLabel } = widgets[delta]; + + const widgetLabel = settings.showLabels ? ( + + ) : null; return ( -
+
{valueSrc == "func" ? null : widgetLabel} { - const {config, isFuncArg, leftField, operator, readonly} = props; - const {settings} = config; + const { config, isFuncArg, leftField, operator, readonly } = props; + const { settings } = config; const { valueSources, widgets, aField } = meta; const field = isFuncArg ? leftField : aField; - const {valueSrc, setValueSrcHandler} = widgets[delta]; - const {valueSourcesInfo, renderValueSources: ValueSources} = settings; - const valueSourcesOptions = valueSources.map(srcKey => [srcKey, { - label: valueSourcesInfo[srcKey].label - }]); + const { valueSrc, setValueSrcHandler } = widgets[delta]; + const { valueSourcesInfo, renderValueSources: ValueSources } = settings; + const valueSourcesOptions = valueSources.map((srcKey) => [ + srcKey, + { + label: valueSourcesInfo[srcKey].label, + }, + ]); - const sourceLabel = settings.showLabels - ? - : null; + const sourceLabel = settings.showLabels ? ( + + ) : null; - return valueSources.length > 1 && !readonly - &&
- {sourceLabel} - -
; + return ( + valueSources.length > 1 && + !readonly && ( +
+ {sourceLabel} + +
+ ) + ); }; renderSep = (delta, meta, props) => { - const {config} = props; - const {widgets} = meta; - const {settings} = config; - const {sepText} = widgets[delta]; + const { config } = props; + const { widgets } = meta; + const { settings } = config; + const { sepText } = widgets[delta]; - const sepLabel = settings.showLabels - ? - : null; + const sepLabel = settings.showLabels ? ( + + ) : null; - return sepText - &&
- {sepLabel} - {sepText} -
; + return ( + sepText && ( +
+ {sepLabel} + {sepText} +
+ ) + ); }; renderWidgetDelta = (delta) => { @@ -262,11 +444,7 @@ export default class Widget extends PureComponent { const sources = this.renderValueSources(delta, this.meta, this.props); const widgetCmp = this.renderWidget(delta, this.meta, this.props); - return [ - sep, - sources, - widgetCmp, - ]; + return [sep, sources, widgetCmp]; }; render() { @@ -278,11 +456,10 @@ export default class Widget extends PureComponent { return ( {range(0, cardinality).map(this.renderWidgetDelta)} ); } - } diff --git a/packages/ui/modules/components/rule/WidgetFactory.jsx b/packages/ui/modules/components/rule/WidgetFactory.jsx index 130302c0f..c557b882e 100644 --- a/packages/ui/modules/components/rule/WidgetFactory.jsx +++ b/packages/ui/modules/components/rule/WidgetFactory.jsx @@ -3,33 +3,60 @@ import { Utils } from "@react-awesome-query-builder/core"; const { getTitleInListValues } = Utils.ListUtils; export default ({ - delta, isFuncArg, valueSrc, - value: immValue, valueError: immValueError, asyncListValues, - isSpecialRange, fieldDefinition, - widget, widgetDefinition, widgetValueLabel, valueLabels, textSeparators, setValueHandler, - config, field, operator, readonly, parentField, parentFuncs, id, groupId + delta, + isFuncArg, + valueSrc, + value: immValue, + valueError: immValueError, + asyncListValues, + isSpecialRange, + fieldDefinition, + widget, + widgetDefinition, + widgetValueLabel, + valueLabels, + textSeparators, + setValueHandler, + config, + field, + fieldSrc, + operator, + readonly, + parentField, + parentFuncs, + id, + groupId, }) => { - const {factory: widgetFactory, ...fieldWidgetProps} = widgetDefinition; - const isConst = isFuncArg && fieldDefinition.valueSources && fieldDefinition.valueSources.length == 1 && fieldDefinition.valueSources[0] == "const"; + const { factory: widgetFactory, ...fieldWidgetProps } = widgetDefinition; + const isConst = + isFuncArg && + fieldDefinition.valueSources && + fieldDefinition.valueSources.length == 1 && + fieldDefinition.valueSources[0] == "const"; const defaultValue = fieldDefinition.defaultValue; if (!widgetFactory) { return "?"; } - - let value = isSpecialRange - ? [immValue.get(0), immValue.get(1)] - : (immValue ? immValue.get(delta) : undefined); - const valueError = immValueError && (isSpecialRange - ? [immValueError.get(0), immValueError.get(1)] - : immValueError.get(delta) - ) || null; + + let value = isSpecialRange + ? [immValue.get(0), immValue.get(1)] + : immValue + ? immValue.get(delta) + : undefined; + const valueError = + (immValueError && + (isSpecialRange + ? [immValueError.get(0), immValueError.get(1)] + : immValueError.get(delta))) || + null; if (isSpecialRange && value[0] === undefined && value[1] === undefined) value = undefined; - const {fieldSettings} = fieldDefinition || {}; + const { fieldSettings } = fieldDefinition || {}; const widgetProps = Object.assign({}, fieldWidgetProps, fieldSettings, { config: config, field: field, + fieldSrc: fieldSrc, parentField: parentField, parentFuncs: parentFuncs, fieldDefinition: fieldDefinition, @@ -46,24 +73,32 @@ export default ({ setValue: setValueHandler, readonly: readonly, asyncListValues: asyncListValues, - id, groupId + id, + groupId, }); - + if (widget == "field") { // } if (isConst && defaultValue) { if (typeof defaultValue == "boolean") { - return defaultValue ? (widgetProps.labelYes || "YES") : (widgetProps.labelNo || "NO"); + return defaultValue + ? widgetProps.labelYes || "YES" + : widgetProps.labelNo || "NO"; } else if (fieldSettings.listValues) { if (Array.isArray(defaultValue)) - return defaultValue.map(v => getTitleInListValues(fieldSettings.listValues, v) || v).join(", "); + return defaultValue + .map((v) => getTitleInListValues(fieldSettings.listValues, v) || v) + .join(", "); else - return (getTitleInListValues(fieldSettings.listValues, defaultValue) || defaultValue); + return ( + getTitleInListValues(fieldSettings.listValues, defaultValue) || + defaultValue + ); } - return ""+defaultValue; + return "" + defaultValue; } - + return widgetFactory(widgetProps); }; diff --git a/packages/ui/modules/components/widgets/vanilla/core/VanillaValueSources.jsx b/packages/ui/modules/components/widgets/vanilla/core/VanillaValueSources.jsx index 6988bbb38..8df5a3544 100644 --- a/packages/ui/modules/components/widgets/vanilla/core/VanillaValueSources.jsx +++ b/packages/ui/modules/components/widgets/vanilla/core/VanillaValueSources.jsx @@ -7,7 +7,9 @@ export default ({config, valueSources, valueSrc, title, setValueSrc, readonly}) )) ); - const onChange = e => setValueSrc(e.target.value); + const onChange = e => { + setValueSrc(e.target.value); + }; return ( setValue(e.target.value)} + factory: ({ value, setValue }) => ( + setValue(e.target.value)} /> - } + ), + }, }; //---------------------------- types @@ -133,12 +132,12 @@ const types = { ...CoreConfig.types.select.widgets.select, widgetProps: { customProps: { - showSearch: true - } + showSearch: true, + }, }, - } - } - } + }, + }, + }, }; //---------------------------- settings @@ -146,6 +145,7 @@ const types = { const settings = { ...CoreConfig.settings, + renderFieldSources: (props) => , renderField: (props) => , renderOperator: (props) => , renderFunc: (props) => , @@ -159,7 +159,7 @@ const settings = { renderSwitchPrefix: () => <>{"Conditions"}, customFieldSelectProps: { - showSearch: true + showSearch: true, }, defaultSliderWidth: "200px", @@ -173,7 +173,6 @@ const settings = { showNot: true, forceShowConj: false, groupActionsPosition: "topRight", // oneOf [topLeft, topCenter, topRight, bottomLeft, bottomCenter, bottomRight] - }; //---------------------------- diff --git a/packages/ui/modules/index.d.ts b/packages/ui/modules/index.d.ts index 0f32880c7..f0342fac0 100644 --- a/packages/ui/modules/index.d.ts +++ b/packages/ui/modules/index.d.ts @@ -134,6 +134,7 @@ type AntdSize = "small" | "large" | "medium"; export interface RenderSettings { + renderFieldSources?: Factory, renderField?: Factory, renderOperator?: Factory, renderFunc?: Factory, diff --git a/packages/ui/styles/styles.scss b/packages/ui/styles/styles.scss index 3216abaff..7e60ed56f 100644 --- a/packages/ui/styles/styles.scss +++ b/packages/ui/styles/styles.scss @@ -503,7 +503,7 @@ $rule-group-actions-offset-left: 10px !default; margin-right: $drag-offset-right; } -$rule_items: ".rule--field", ".group--field", ".rule--operator", ".rule--value", ".rule--operator-options", ".rule--widget", +$rule_items: ".rule--field", ".rule--fieldsrc", ".group--field", ".rule--operator", ".rule--value", ".rule--operator-options", ".rule--widget", ".widget--widget", ".widget--valuesrc", ".widget--sep", ".operator--options--sep", ".rule--before-widget", ".rule--after-widget"; @@ -519,6 +519,10 @@ $seps: ".widget--sep", ".operator--options--sep", ".rule--func--bracket-before", margin-bottom: $seps-offset-bottom; } +.rule--fieldsrc { + vertical-align: top; + margin-right: calc(-1 * $rule-parts-distance + 2px); // be closer to field +} .operator--options { display: flex; } @@ -530,7 +534,7 @@ $seps: ".widget--sep", ".operator--options--sep", ".rule--func--bracket-before", flex-wrap: wrap; } -.rule--operator, .widget--widget, .widget--valuesrc, .widget--sep { +.rule--field, .rule--operator, .widget--widget, .widget--valuesrc, .widget--sep { margin-left: $rule-parts-distance; } .widget--valuesrc { @@ -683,7 +687,7 @@ $rule_func_items: ".rule--func--wrapper", ".rule--func", ".rule--func--args", ". $group_actions: (".group--drag-handler", ".group--actions") !default; $inactive_conjs: false !default; $active_conjs: false !default; -$rule_actions: (".widget--valuesrc", ".rule--drag-handler", ".rule--header") !default; +$rule_actions: (".rule--fieldsrc", ".widget--valuesrc", ".rule--drag-handler", ".rule--header") !default; .qb-lite {