diff --git a/addons/web/static/src/core/domain_selector/domain_selector.js b/addons/web/static/src/core/domain_selector/domain_selector.js index 8dae195bb3c..a0fbe9805ff 100644 --- a/addons/web/static/src/core/domain_selector/domain_selector.js +++ b/addons/web/static/src/core/domain_selector/domain_selector.js @@ -4,7 +4,12 @@ import { extractPathsFromDomain, useGetDefaultCondition } from "@web/core/domain import { Component, onWillStart, onWillUpdateProps } from "@odoo/owl"; import { Domain } from "@web/core/domain"; import { TreeEditor } from "@web/core/tree_editor/tree_editor"; -import { domainFromTree, treeFromDomain, formatValue } from "@web/core/tree_editor/condition_tree"; +import { + domainFromTree, + treeFromDomain, + formatValue, + condition, +} from "@web/core/tree_editor/condition_tree"; import { useLoadFieldInfo } from "@web/core/model_field_selector/utils"; import { CheckBox } from "@web/core/checkbox/checkbox"; import { deepEqual } from "@web/core/utils/objects"; @@ -13,13 +18,7 @@ import { getOperatorEditorInfo } from "@web/core/tree_editor/tree_editor_operato import { _t } from "@web/core/l10n/translation"; import { ModelFieldSelector } from "@web/core/model_field_selector/model_field_selector"; -const ARCHIVED_CONDITION = { - type: "condition", - value: [true, false], - negate: false, - path: "active", - operator: "in", -}; +const ARCHIVED_CONDITION = condition("active", "in", [true, false]); const ARCHIVED_DOMAIN = `[("active", "in", [True, False])]`; export class DomainSelector extends Component { diff --git a/addons/web/static/src/core/domain_selector/utils.js b/addons/web/static/src/core/domain_selector/utils.js index a0868d74dcc..79081bd34c6 100644 --- a/addons/web/static/src/core/domain_selector/utils.js +++ b/addons/web/static/src/core/domain_selector/utils.js @@ -11,6 +11,7 @@ import { normalizeValue, formatValue as toString, createVirtualOperators, + condition, } from "@web/core/tree_editor/condition_tree"; import { useLoadFieldInfo, useLoadPathDescription } from "@web/core/model_field_selector/utils"; import { @@ -41,13 +42,7 @@ function getDefaultCondition(fieldDefs) { const fieldDef = fieldDefs[defaultPath]; const operator = getDomainDisplayedOperators(fieldDef)[0]; const value = getDefaultValue(fieldDef, operator); - return { - type: "condition", - negate: false, - path: fieldDef.name, - operator, - value, - }; + return condition(fieldDef.name, operator, value); } function getDefaultDomain(fieldDefs) { @@ -111,13 +106,7 @@ function simplifyTree(tree) { value.push(...child.value); } } - children.push({ - type: "condition", - negate: false, - operator: "in", - path: path, - value: normalizeValue(value), - }); + children.push(condition(path, "in", normalizeValue(value))); } if (children.length === 1) { return { ...children[0] }; diff --git a/addons/web/static/src/core/expression_editor/expression_editor.js b/addons/web/static/src/core/expression_editor/expression_editor.js index 7742eb4225a..66d1b893b53 100644 --- a/addons/web/static/src/core/expression_editor/expression_editor.js +++ b/addons/web/static/src/core/expression_editor/expression_editor.js @@ -2,7 +2,11 @@ import { Component, onWillStart, onWillUpdateProps } from "@odoo/owl"; import { getExpressionDisplayedOperators } from "@web/core/expression_editor/expression_editor_operator_editor"; -import { expressionFromTree, treeFromExpression } from "@web/core/tree_editor/condition_tree"; +import { + condition, + expressionFromTree, + treeFromExpression, +} from "@web/core/tree_editor/condition_tree"; import { TreeEditor } from "@web/core/tree_editor/tree_editor"; import { getOperatorEditorInfo } from "@web/core/tree_editor/tree_editor_operator_editor"; import { getDefaultValue } from "@web/core/tree_editor/tree_editor_value_editors"; @@ -13,13 +17,7 @@ function getDefaultCondition(fieldDefs) { const fieldDef = fieldDefs[defaultPath]; const operator = getExpressionDisplayedOperators(fieldDef)[0]; const value = getDefaultValue(fieldDef, operator); - return { - type: "condition", - negate: false, - path: fieldDef.name, - operator, - value, - }; + return condition(fieldDef.name, operator, value); } class ExpressionEditorFieldSelector extends Component { diff --git a/addons/web/static/src/core/tree_editor/condition_tree.js b/addons/web/static/src/core/tree_editor/condition_tree.js index f8a6d9f1a26..c0c38b2af50 100644 --- a/addons/web/static/src/core/tree_editor/condition_tree.js +++ b/addons/web/static/src/core/tree_editor/condition_tree.js @@ -31,27 +31,15 @@ import { toPyValue } from "@web/core/py_js/py_utils"; */ /** - * @typedef {Object} AND + * @typedef {Object} Connector * @property {"connector"} type * @property {boolean} negate - * @property {"&"} value - * @property {(Connector|OR)[]} children + * @property {"|"|"&"} value + * @property {Tree[]} children */ /** - * @typedef {Object} OR - * @property {"connector"} type - * @property {boolean} negate - * @property {"|"} value - * @property {(Connector|AND)[]} children - */ - -/** - * @typedef {AND|OR|Condition} SimpleTree - */ - -/** - * @typedef {AND|OR|Condition|ComplexCondition} Tree + * @typedef {Connector|Condition|ComplexCondition} Tree */ /** @@ -112,6 +100,70 @@ export class Expression { } } +/** + * @param {string} expr + * @returns {Expression} + */ +export function expression(expr) { + return new Expression(expr); +} + +/** + * @param {"|"|"&"} value + * @param {Tree[]} [children=[]] + * @param {boolean} [negate=false] + * @returns {Connector} + */ +export function connector(value, children = [], negate = false) { + return { type: "connector", value, children, negate }; +} + +/** + * @param {Value} path + * @param {Value} operator + * @param {Value} value + * @param {boolean} [negate=false] + * @returns {Condition} + */ +export function condition(path, operator, value, negate = false) { + return { type: "condition", path, operator, value, negate }; +} + +/** + * @param {string} value + * @returns {ComplexCondition} + */ +export function complexCondition(value) { + parseExpr(value); + return { type: "complex_condition", value }; +} + +/** + * @param {Value} value + * @returns {Value} + */ +function cloneValue(value) { + if (value instanceof Expression) { + return new Expression(value.toAST()); + } + if (Array.isArray(value)) { + return value.map(cloneValue); + } + return value; +} + +/** + * @param {Tree} tree + * @returns {Tree} + */ +export function cloneTree(tree) { + const clone = {}; + for (const key in tree) { + clone[key] = cloneValue(tree[key]); + } + return clone; +} + export function formatValue(value) { return formatAST(toAST(value)); } @@ -239,7 +291,7 @@ function _construcTree(ASTs, distributeNot, negate = false) { */ function construcTree(initialASTs, options) { if (!initialASTs.length) { - return { type: "connector", value: "&", negate: false, children: [] }; + return connector("&"); } const { tree } = _construcTree(initialASTs, options.distributeNot); return tree; @@ -342,8 +394,6 @@ function _getConditionFromComparator(ast, options) { return null; } - const tree = { type: "condition", negate: false }; - let operator = ast.op; if (operator === "==") { operator = "="; @@ -364,18 +414,15 @@ function _getConditionFromComparator(ast, options) { return null; } } - tree.path = left.value; - tree.operator = operator; - tree.value = toValue(right); - return tree; + return condition(left.value, operator, toValue(right)); } /** * @param {AST} ast * @param {Options} options * @param {boolean} [negate=false] - * @returns {import("@web/core/tree_editor/condition_tree").Condition|ComplexCondition} + * @returns {Condition|ComplexCondition} */ function _leafFromAST(ast, options, negate = false) { if (isNot(ast)) { @@ -383,24 +430,12 @@ function _leafFromAST(ast, options, negate = false) { } if (ast.type === 5 /** name */ && isValidPath(ast, options)) { - return { - type: "condition", - negate: false, - path: ast.value, - operator: negate ? "=" : "!=", - value: false, - }; + return condition(ast.value, negate ? "=" : "!=", false); } const astValue = toValue(ast); if (["boolean", "number", "string"].includes(typeof astValue)) { - return { - negate: false, - type: "condition", - path: astValue ? 1 : 0, - operator: "=", - value: 1, - }; + return condition(astValue ? 1 : 0, "=", 1); } if (ast.type === 7 && COMPARATORS.includes(ast.op)) { @@ -414,10 +449,7 @@ function _leafFromAST(ast, options, negate = false) { } // no conclusive way to transform ast in a condition - return { - type: "complex_condition", - value: formatAST(negate ? not(ast) : ast), - }; + return complexCondition(formatAST(negate ? not(ast) : ast)); } /** @@ -432,14 +464,11 @@ function _treeFromAST(ast, options, negate = false) { } if (ast.type === 14) { - const tree = { - type: "connector", - value: ast.op === "and" ? "&" : "|", // and/or are the only ops that are given type 14 (for now) - children: [], - }; + const tree = connector( + ast.op === "and" ? "&" : "|" // and/or are the only ops that are given type 14 (for now) + ); if (options.distributeNot && negate) { tree.value = tree.value === "&" ? "|" : "&"; - tree.negate = false; } else { tree.negate = negate; } @@ -574,13 +603,9 @@ function createBetweenOperators(tree) { child1.operator === ">=" && child2.operator === "<=" ) { - children.push({ - type: "condition", - negate: false, - path: child1.path, - operator: "between", - value: normalizeValue([child1.value, child2.value]), - }); + children.push( + condition(child1.path, "between", normalizeValue([child1.value, child2.value])) + ); i += 1; } else { children.push(child1); @@ -605,27 +630,11 @@ export function removeBetweenOperators(tree) { return tree; } const { negate, path, value } = tree; - return { - type: "connector", - negate, - value: "&", - children: [ - { - type: "condition", - negate: false, - path, - operator: ">=", - value: value[0], - }, - { - type: "condition", - negate: false, - path, - operator: "<=", - value: value[1], - }, - ], - }; + return connector( + "&", + [condition(path, ">=", value[0]), condition(path, "<=", value[1])], + negate + ); } const processedChildren = tree.children.map(removeBetweenOperators); if (tree.value === "|") { @@ -701,15 +710,12 @@ function createComplexConditions(tree) { if (tree.path instanceof Expression && tree.operator === "=" && tree.value === 1) { // not sure about this one -> we should maybe evaluate the condition and check // if it does not become something e.g. the name of a integer field? - return { - type: "complex_condition", - value: String(tree.path), - }; + return complexCondition(String(tree.path)); } - return { ...tree }; + return cloneTree(tree); } if (tree.type === "complex_condition") { - return { ...tree }; + return cloneTree(tree); } return { ...tree, @@ -723,16 +729,11 @@ function createComplexConditions(tree) { */ function removeComplexConditions(tree) { if (tree.type === "condition") { - return { ...tree }; + return cloneTree(tree); } if (tree.type === "complex_condition") { const ast = parseExpr(tree.value); - return { - type: "condition", - path: new Expression(bool(ast)), - operator: "=", - value: 1, - }; + return condition(new Expression(bool(ast)), "=", 1); } return { ...tree, diff --git a/addons/web/static/src/core/tree_editor/tree_editor.js b/addons/web/static/src/core/tree_editor/tree_editor.js index c6774cb478c..db46a3dbc2b 100644 --- a/addons/web/static/src/core/tree_editor/tree_editor.js +++ b/addons/web/static/src/core/tree_editor/tree_editor.js @@ -1,7 +1,6 @@ /** @odoo-module **/ import { - cloneTree, leafToString, useLoadDisplayNames, extractIdsFromTree, @@ -11,7 +10,13 @@ import { import { Component, onWillStart, onWillUpdateProps } from "@odoo/owl"; import { Dropdown } from "@web/core/dropdown/dropdown"; import { DropdownItem } from "@web/core/dropdown/dropdown_item"; -import { formatValue, removeVirtualOperators } from "@web/core/tree_editor/condition_tree"; +import { + condition, + cloneTree, + formatValue, + removeVirtualOperators, + connector, +} from "@web/core/tree_editor/condition_tree"; import { getDefaultValue, getValueEditorInfo, @@ -20,21 +25,9 @@ import { ModelFieldSelector } from "@web/core/model_field_selector/model_field_s import { useLoadFieldInfo } from "@web/core/model_field_selector/utils"; import { deepEqual, shallowEqual } from "@web/core/utils/objects"; -const TRUE_TREE = { - type: "condition", - value: 1, - negate: false, - path: 1, - operator: "=", -}; +const TRUE_TREE = condition(1, "=", 1); -const DEFAULT_CONDITION = { - type: "condition", - path: "id", - negate: false, - operator: "=", - value: 1, -}; +const DEFAULT_CONDITION = condition("id", "=", 1); function collectDifferences(tree, otherTree) { // some differences shadow the other differences "below": @@ -132,19 +125,9 @@ export class TreeEditor extends Component { async onPropsUpdated(props) { this.tree = cloneTree(props.tree); if (shallowEqual(this.tree, TRUE_TREE)) { - this.tree = { - type: "connector", - negate: false, - value: props.defaultConnector, - children: [], - }; + this.tree = connector(props.defaultConnector); } else if (this.tree.type !== "connector") { - this.tree = { - type: "connector", - negate: false, - value: props.defaultConnector, - children: [this.tree], - }; + this.tree = connector(props.defaultConnector, [this.tree]); } if (this.previousTree) { @@ -213,13 +196,8 @@ export class TreeEditor extends Component { return cloneTree(this.props.defaultCondition); } - createNewBranch(connector) { - return { - type: "connector", - value: connector, - negate: false, - children: [this.createNewLeaf(), this.createNewLeaf()], - }; + createNewBranch(value) { + return connector(value, [this.createNewLeaf(), this.createNewLeaf()]); } insertRootLeaf(parent) { diff --git a/addons/web/static/src/core/tree_editor/utils.js b/addons/web/static/src/core/tree_editor/utils.js index c0c7222a77f..66192134588 100644 --- a/addons/web/static/src/core/tree_editor/utils.js +++ b/addons/web/static/src/core/tree_editor/utils.js @@ -6,32 +6,6 @@ import { Expression } from "@web/core/tree_editor/condition_tree"; import { useService } from "@web/core/utils/hooks"; import { _t } from "@web/core/l10n/translation"; -/** - * @param {import("@web/core/tree_editor/condition_tree").Value} value - * @returns {import("@web/core/tree_editor/condition_tree").Value} - */ -function cloneValue(value) { - if (value instanceof Expression) { - return new Expression(value.toAST()); - } - if (Array.isArray(value)) { - return value.map(cloneValue); - } - return value; -} - -/** - * @param {import("@web/core/tree_editor/condition_tree").Tree} tree - * @returns {import("@web/core/tree_editor/condition_tree").Tree} - */ -export function cloneTree(tree) { - const clone = {}; - for (const key in tree) { - clone[key] = cloneValue(tree[key]); - } - return clone; -} - /** * @param {import("@web/core/tree_editor/condition_tree").Value} val * @param {boolean} disambiguate diff --git a/addons/web/static/tests/core/condition_tree_tests.js b/addons/web/static/tests/core/condition_tree_tests.js index 7eb7ec47f0b..d21176b86ff 100644 --- a/addons/web/static/tests/core/condition_tree_tests.js +++ b/addons/web/static/tests/core/condition_tree_tests.js @@ -1,8 +1,12 @@ /** @odoo-module **/ import { + complexCondition, + condition, + connector, domainFromExpression, domainFromTree, + expression, Expression, expressionFromDomain, expressionFromTree, @@ -15,39 +19,27 @@ QUnit.module("condition tree", {}); QUnit.test("domainFromTree", function (assert) { const toTest = [ { - tree: { type: "condition", negate: false, path: "foo", operator: "=", value: false }, + tree: condition("foo", "=", false), result: `[("foo", "=", False)]`, }, { - tree: { type: "condition", negate: true, path: "foo", operator: "=", value: false }, + tree: condition("foo", "=", false, true), result: `["!", ("foo", "=", False)]`, }, { - tree: { type: "condition", negate: false, path: "foo", operator: "=?", value: false }, + tree: condition("foo", "=?", false), result: `[("foo", "=?", False)]`, }, { - tree: { type: "condition", negate: true, path: "foo", operator: "=?", value: false }, + tree: condition("foo", "=?", false, true), result: `["!", ("foo", "=?", False)]`, }, { - tree: { - type: "condition", - negate: false, - path: "foo", - operator: "between", - value: [1, 3], - }, + tree: condition("foo", "between", [1, 3]), result: `["&", ("foo", ">=", 1), ("foo", "<=", 3)]`, }, { - tree: { - type: "condition", - negate: true, - path: "foo", - operator: "between", - value: [1, new Expression({ type: 5, value: "uid" })], - }, + tree: condition("foo", "between", [1, expression("uid")], true), result: `["!", "&", ("foo", ">=", 1), ("foo", "<=", uid)]`, }, ]; @@ -255,80 +247,35 @@ QUnit.test("expressionFromTree", function (assert) { }; const toTest = [ { - expressionTree: { - type: "condition", - path: "x", - negate: false, - operator: "=", - value: false, - }, + expressionTree: condition("x", "=", false), result: `not x`, }, { - expressionTree: { - type: "condition", - path: "x", - negate: true, - operator: "=", - value: false, - }, + expressionTree: condition("x", "=", false, true), result: `x`, }, { - expressionTree: { - type: "condition", - path: "x", - negate: false, - operator: "!=", - value: false, - }, + expressionTree: condition("x", "!=", false), result: `x`, }, { - expressionTree: { - type: "condition", - path: "x", - negate: true, - operator: "!=", - value: false, - }, + expressionTree: condition("x", "!=", false, true), result: `not x`, }, { - expressionTree: { - type: "condition", - path: "y", - negate: false, - operator: "=", - value: false, - }, + expressionTree: condition("y", "=", false), result: `not "y"`, }, { - expressionTree: { - type: "condition", - path: "x", - negate: false, - operator: "between", - value: [1, 3], - }, + expressionTree: condition("x", "between", [1, 3]), result: `x >= 1 and x <= 3`, }, { - expressionTree: { - type: "condition", - path: "x", - negate: true, - operator: "between", - value: [1, new Expression("uid")], - }, + expressionTree: condition("x", "between", [1, expression("uid")], true), result: `not ( x >= 1 and x <= uid )`, }, { - expressionTree: { - type: "complex_condition", - value: "uid", - }, + expressionTree: complexCondition("uid"), result: `uid`, }, ]; @@ -353,202 +300,70 @@ QUnit.test("treeFromExpression", function (assert) { const toTest = [ { expression: `not foo`, - result: { - type: "condition", - path: "foo", - negate: false, - operator: "not_set", - value: false, - }, + result: condition("foo", "not_set", false), }, { expression: `foo == False`, - result: { - type: "condition", - path: "foo", - operator: "not_set", - negate: false, - value: false, - }, + result: condition("foo", "not_set", false), }, { expression: `foo`, - result: { - type: "condition", - path: "foo", - negate: false, - operator: "set", - value: false, - }, + result: condition("foo", "set", false), }, { expression: `foo == True`, - result: { - type: "condition", - path: "foo", - negate: false, - operator: "=", - value: true, - }, + result: condition("foo", "=", true), }, { expression: `foo is True`, - result: { - type: "complex_condition", - value: `foo is True`, - }, + result: complexCondition(`foo is True`), }, { expression: `not (foo == False)`, - result: { - type: "condition", - path: "foo", - negate: false, - operator: "set", - value: false, - }, + result: condition("foo", "set", false), }, { expression: `not (not foo)`, - result: { - type: "condition", - path: "foo", - negate: false, - operator: "set", - value: false, - }, + result: condition("foo", "set", false), }, { expression: `foo >= 1 and foo <= 3`, - result: { - type: "condition", - negate: false, - operator: "between", - path: "foo", - value: [1, 3], - }, + result: condition("foo", "between", [1, 3]), }, { expression: `foo >= 1 and foo <= uid`, - result: { - type: "condition", - path: "foo", - negate: false, - operator: "between", - value: [1, new Expression("uid")], - }, + result: condition("foo", "between", [1, expression("uid")]), }, { expression: `foo >= 1 if bar else foo <= uid`, - result: { - type: "connector", - negate: false, - value: "|", - children: [ - { - type: "connector", - negate: false, - value: "&", - children: [ - { - type: "condition", - path: "bar", - negate: false, - operator: "set", - value: false, - }, - { - type: "condition", - path: "foo", - negate: false, - operator: ">=", - value: 1, - }, - ], - }, - { - type: "connector", - negate: false, - value: "&", - children: [ - { - type: "condition", - path: "bar", - negate: false, - operator: "not_set", - value: false, - }, - { - type: "condition", - path: "foo", - negate: false, - operator: "<=", - value: new Expression("uid"), - }, - ], - }, - ], - }, + result: connector("|", [ + connector("&", [condition("bar", "set", false), condition("foo", ">=", 1)]), + connector("&", [ + condition("bar", "not_set", false), + condition("foo", "<=", new Expression("uid")), + ]), + ]), }, { expression: `context.get('toto')`, - result: { - type: "complex_condition", - value: `context.get("toto")`, - }, + result: complexCondition(`context.get("toto")`), }, { expression: `not context.get('toto')`, - result: { - type: "complex_condition", - value: `not context.get("toto")`, - }, + result: complexCondition(`not context.get("toto")`), }, { expression: `foo >= 1 if context.get('toto') else bar == 42`, - result: { - type: "connector", - negate: false, - value: "|", - children: [ - { - type: "connector", - negate: false, - value: "&", - children: [ - { - type: "complex_condition", - value: `context.get("toto")`, - }, - { - type: "condition", - path: "foo", - negate: false, - operator: ">=", - value: 1, - }, - ], - }, - { - type: "connector", - negate: false, - value: "&", - children: [ - { - type: "complex_condition", - value: `not context.get("toto")`, - }, - { - type: "condition", - path: "bar", - negate: false, - operator: "=", - value: 42, - }, - ], - }, - ], - }, + result: connector("|", [ + connector("&", [ + complexCondition(`context.get("toto")`), + condition("foo", ">=", 1), + ]), + connector("&", [ + complexCondition(`not context.get("toto")`), + condition("bar", "=", 42), + ]), + ]), }, ]; for (const { expression, result, extraOptions } of toTest) {