diff --git a/CHANGELOG.md b/CHANGELOG.md index 11d3eee22..5888aaea1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Changelog - 6.6.8 - Support safe navigation operator in SpEL operators/functions (PR #1172) (issue #1010) + - SpEL: backward compatibility for import of `CollectionUtils.containsAny` (PR #1174) (issue #1007) - 6.6.7 - Fix import of ambiguous operators (like, select_any_in) (PR #1168) (issue #1159) - Allow import of epoch for date/datetime widgets from JsonLogic (PR #1171) (issue #1154) diff --git a/packages/core/modules/config/index.js b/packages/core/modules/config/index.js index 2c59ce781..90abff35c 100644 --- a/packages/core/modules/config/index.js +++ b/packages/core/modules/config/index.js @@ -496,6 +496,20 @@ const operators = { }), //spelOp: "${0}.containsAll(${1})", spelOp: "T(CollectionUtils).containsAny(${0}, ${1})", + spelImportFuncs: [ + // just for backward compatibility (issue #1007) + { + obj: { + type: "property", + val: "CollectionUtils" + }, + methodName: "containsAny", + args: [ + {var: "0"}, + {var: "1"}, + ], + } + ], elasticSearchQueryType: "term", mongoFormatOp: function(...args) { return this.utils.mongoFormatOp1("$in", v => v, false, ...args); }, }, diff --git a/packages/core/modules/import/spel/convert.js b/packages/core/modules/import/spel/convert.js index 31836d40d..4812d0b5c 100644 --- a/packages/core/modules/import/spel/convert.js +++ b/packages/core/modules/import/spel/convert.js @@ -523,12 +523,13 @@ const convertFuncToValue = (spel, conv, config, meta, parentSpel, fsigns, conver }; const convertFuncToOp = (spel, conv, config, meta, parentSpel, fsigns, convertFuncArg) => { - let errs, opKey, foundSign; + const candidates = []; + for (const {s, params} of fsigns) { const found = conv.opFuncs[s] || []; for (const {op, argsOrder} of found) { const argsArr = params.map(convertFuncArg); - opKey = op; + const errs = []; if (op === "!compare") { if ( parentSpel.type.startsWith("op-") @@ -544,9 +545,7 @@ const convertFuncToOp = (spel, conv, config, meta, parentSpel, fsigns, convertFu errs.push("Result of compareTo() should be compared to 0"); } } - foundSign = s; - errs = []; - const opDef = config.operators[opKey]; + const opDef = config.operators[op]; const {valueTypes} = opDef; const argsObj = Object.fromEntries( argsOrder.map((argKey, i) => [argKey, argsArr[i]]) @@ -557,14 +556,50 @@ const convertFuncToOp = (spel, conv, config, meta, parentSpel, fsigns, convertFu if (valueTypes && valueType && !valueTypes.includes(valueType)) { errs.push(`Op supports types ${valueTypes}, but got ${valueType}`); } - if (!errs.length) { - return buildRule(config, meta, field, opKey, convertedArgs, spel); + candidates.push({ + opKey: op, foundSign: s, field, convertedArgs, errs, + }); + } + } + + for (let op in config.operators) { + const opDef = config.operators[op]; + const {spelImportFuncs, valueTypes} = opDef; + if (spelImportFuncs) { + for (let i = 0 ; i < spelImportFuncs.length ; i++) { + const fj = spelImportFuncs[i]; + if (isObject(fj)) { + const argsObj = {}; + if (isJsonCompatible(fj, spel, argsObj)) { + const errs = []; + for (const k in argsObj) { + argsObj[k] = convertFuncArg(argsObj[k]); + } + const field = argsObj["0"]; + const convertedArgs = Object.keys(argsObj).filter(k => parseInt(k) > 0).map(k => argsObj[k]); + const valueType = argsObj["1"]?.valueType; + if (valueTypes && valueType && !valueTypes.includes(valueType)) { + errs.push(`Op supports types ${valueTypes}, but got ${valueType}`); + } + candidates.push({ + opKey: op, foundSign: `spelImportFuncs[${i}]`, field, convertedArgs, errs, + }); + } + } } } } - if (opKey && errs.length) { - meta.errors.push(`Signature ${foundSign} - looks like convertable to ${opKey}, but: ${errs.join("; ")}`); + const bestCandidate = candidates.find(({errs}) => !errs.length); + if (bestCandidate) { + const {opKey, foundSign, field, convertedArgs, errs} = bestCandidate; + return buildRule(config, meta, field, opKey, convertedArgs, spel); + } else if (candidates.length) { + const allErrs = candidates.map( + ({foundSign, opKey, errs}) => + `Looks like convertable to ${opKey} with signature ${foundSign}, but: ${errs.join("; ")}` + ).join(". "); + meta.errors.push(allErrs); } return undefined; diff --git a/packages/tests/specs/QueryWithOperators.test.js b/packages/tests/specs/QueryWithOperators.test.js index 5202a08a4..33f219ae3 100644 --- a/packages/tests/specs/QueryWithOperators.test.js +++ b/packages/tests/specs/QueryWithOperators.test.js @@ -487,6 +487,18 @@ describe("query with ops", () => { "sql": inits.with_ops_sql, }); }); + + describe("@spel multiselect_contains import works", () => { + export_checks([configs.with_all_types], "T(CollectionUtils).containsAny(multicolor, {'yellow'})", "SpEL", { + spel: "T(CollectionUtils).containsAny(multicolor, {'yellow'})", + }); + }); + + describe("@spel multiselect_contains import is backward compatible", () => { + export_checks([configs.with_all_types], "CollectionUtils.containsAny(multicolor, {'yellow'})", "SpEL", { + spel: "T(CollectionUtils).containsAny(multicolor, {'yellow'})", + }); + }); }); describe("query with exclamation operators", () => {