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 66d1b893b53..98114d2f75b 100644 --- a/addons/web/static/src/core/expression_editor/expression_editor.js +++ b/addons/web/static/src/core/expression_editor/expression_editor.js @@ -11,6 +11,8 @@ 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"; import { getDefaultPath } from "@web/core/tree_editor/utils"; +import { ModelFieldSelector } from "@web/core/model_field_selector/model_field_selector"; +import { _t } from "@web/core/l10n/translation"; function getDefaultCondition(fieldDefs) { const defaultPath = getDefaultPath(fieldDefs); @@ -20,15 +22,6 @@ function getDefaultCondition(fieldDefs) { return condition(fieldDef.name, operator, value); } -class ExpressionEditorFieldSelector extends Component { - static template = "web.ExpressionEditorFieldSelector"; - static props = { - value: [String, { value: 1 }, { value: 0 }], - update: Function, - fields: Object, - }; -} - export class ExpressionEditor extends Component { static template = "web.ExpressionEditor"; static components = { TreeEditor }; @@ -45,7 +38,10 @@ export class ExpressionEditor extends Component { } async onPropsUpdated(props) { - this.defaultCondition = getDefaultCondition(props.fields); + this.filteredFields = Object.fromEntries( + Object.entries(props.fields).filter(([_, fieldDef]) => fieldDef.type !== "properties") + ); + this.defaultCondition = getDefaultCondition(this.filteredFields); try { this.tree = treeFromExpression(props.expression, { getFieldDef: (name) => this.getFieldDef(name, props), @@ -75,14 +71,23 @@ export class ExpressionEditor extends Component { getPathEditorInfo() { return { - component: ExpressionEditorFieldSelector, + component: ModelFieldSelector, extractProps: ({ value, update }) => ({ - value, + path: value, update, - fields: this.props.fields, + resModel: this.props.resModel, + readonly: false, + filter: (fieldDef) => fieldDef.name in this.filteredFields, + showDebugInput: false, + followRelations: false, + isDebugMode: this.isDebugMode, }), - isSupported: (value) => [0, 1].includes(value) || this.getFieldDef(value), - // by construction, all values received by the path editor will be supported. + isSupported: (value) => [0, 1].includes(value) || value in this.filteredFields, + // by construction, all values received by the path editor are O/1 or a field (name) in this.props.fields. + // (see _leafFromAST in condition_tree.js) + stringify: (value) => this.props.fields[value].string, + defaultValue: () => this.defaultCondition.path, + message: _t("Field properties not supported"), }; } diff --git a/addons/web/static/src/core/expression_editor/expression_editor.xml b/addons/web/static/src/core/expression_editor/expression_editor.xml index fd7522dc52e..02434f7b5f1 100644 --- a/addons/web/static/src/core/expression_editor/expression_editor.xml +++ b/addons/web/static/src/core/expression_editor/expression_editor.xml @@ -1,21 +1,6 @@ - - - -
diff --git a/addons/web/static/src/core/model_field_selector/model_field_selector.js b/addons/web/static/src/core/model_field_selector/model_field_selector.js index 08154c48cf5..c28fbbe0cca 100644 --- a/addons/web/static/src/core/model_field_selector/model_field_selector.js +++ b/addons/web/static/src/core/model_field_selector/model_field_selector.js @@ -21,13 +21,14 @@ export class ModelFieldSelector extends Component { update: { type: Function, optional: true }, filter: { type: Function, optional: true }, followRelations: { type: Boolean, optional: true }, + showDebugInput: { type: Boolean, optional: true }, }; static defaultProps = { readonly: true, allowEmpty: false, isDebugMode: false, showSearchInput: true, - update: () => { }, + update: () => {}, followRelations: true, }; @@ -67,6 +68,7 @@ export class ModelFieldSelector extends Component { isDebugMode: this.props.isDebugMode, filter: this.props.filter, followRelations: this.props.followRelations, + showDebugInput: this.props.showDebugInput, }); } diff --git a/addons/web/static/src/core/model_field_selector/model_field_selector_popover.js b/addons/web/static/src/core/model_field_selector/model_field_selector_popover.js index 3fbe0da5a6a..e53097052cb 100644 --- a/addons/web/static/src/core/model_field_selector/model_field_selector_popover.js +++ b/addons/web/static/src/core/model_field_selector/model_field_selector_popover.js @@ -94,6 +94,7 @@ export class ModelFieldSelectorPopover extends Component { close: Function, filter: { type: Function, optional: true }, followRelations: { type: Boolean, optional: true }, + showDebugInput: { type: Boolean, optional: true }, isDebugMode: { type: Boolean, optional: true }, path: { optional: true }, resModel: String, @@ -139,6 +140,10 @@ export class ModelFieldSelectorPopover extends Component { ); } + get showDebugInput() { + return this.props.showDebugInput ?? this.props.isDebugMode; + } + filter(fieldDefs, path) { const filteredKeys = Object.keys(fieldDefs).filter((k) => this.props.filter(fieldDefs[k], path) diff --git a/addons/web/static/src/core/model_field_selector/model_field_selector_popover.xml b/addons/web/static/src/core/model_field_selector/model_field_selector_popover.xml index 2e4a9c79017..ce13ae1a462 100644 --- a/addons/web/static/src/core/model_field_selector/model_field_selector_popover.xml +++ b/addons/web/static/src/core/model_field_selector/model_field_selector_popover.xml @@ -50,7 +50,7 @@
- + diff --git a/addons/web/static/tests/core/condition_tree_editor_helpers.js b/addons/web/static/tests/core/condition_tree_editor_helpers.js index 29c12d52fa7..4fe3692f95b 100644 --- a/addons/web/static/tests/core/condition_tree_editor_helpers.js +++ b/addons/web/static/tests/core/condition_tree_editor_helpers.js @@ -153,10 +153,6 @@ export function getCurrentPath(target, index = 0) { if (pathEditor) { if (pathEditor.querySelector(".o_model_field_selector")) { return getModelFieldSelectorValues(pathEditor).join(" > "); - } else if (pathEditor.querySelector("select")) { - const select = pathEditor.querySelector("select"); - const option = [...select.children].find((child) => child.selected); - return option.textContent; } return pathEditor.textContent; } diff --git a/addons/web/static/tests/core/expression_editor_tests.js b/addons/web/static/tests/core/expression_editor_tests.js index 729f21c1b36..a38bd43cd9a 100644 --- a/addons/web/static/tests/core/expression_editor_tests.js +++ b/addons/web/static/tests/core/expression_editor_tests.js @@ -3,7 +3,6 @@ import { click, editInput, - editSelect, getFixture, getNodesTextContent, mount, @@ -27,7 +26,10 @@ import { setupConditionTreeEditorServices, getOperatorOptions, getValueOptions, + isNotSupportedPath, + clearNotSupported, } from "./condition_tree_editor_helpers"; +import { openModelFieldSelectorPopover } from "./model_field_selector_tests"; export { clickOnButtonAddBranch, @@ -39,6 +41,8 @@ export { makeServerData, selectOperator, setupConditionTreeEditorServices, + isNotSupportedPath, + clearNotSupported, } from "./condition_tree_editor_helpers"; let serverData; @@ -49,11 +53,6 @@ export const SELECTORS = { debugArea: ".o_expression_editor_debug_container textarea", }; -export async function selectPath(target, path, index = 0) { - const el = get(target, SELECTORS.pathEditor, index); - await editSelect(el, "select", path); -} - async function editExpression(target, value, index = 0) { const input = get(target, SELECTORS.complexConditionInput, index); await editInput(input, null, value); @@ -208,7 +207,8 @@ QUnit.module("Components", (hooks) => { { level: 1, value: ["Bar", "is not", "blabla"] }, ]); const tree = getTreeEditorContent(target, { node: true }); - await selectPath(tree[1].node, "foo"); + await openModelFieldSelectorPopover(target); + await click(target.querySelectorAll(".o_model_field_selector_popover_item_name")[4]); await selectOperator(tree[1].node, "not in"); await editValue(target, ["Doku", "Lukaku", "KDB"]); assert.deepEqual(getTreeEditorContent(target), [ @@ -384,13 +384,8 @@ QUnit.module("Components", (hooks) => { QUnit.test("check condition by default when creating a new rule", async (assert) => { patchWithCleanup(odoo, { debug: false }); - await makeExpressionEditor({ - fields: { - ...serverData.fields, - country_id: { string: "Country ID", type: "char" }, - }, - expression: "expr", - }); + serverData.models.partner.fields.country_id = { string: "Country ID", type: "char" }; + await makeExpressionEditor({ expression: "expr" }); await click(target, "a[role='button']"); assert.deepEqual(getTreeEditorContent(target), [ { level: 0, value: "all" }, @@ -405,7 +400,8 @@ QUnit.module("Components", (hooks) => { { level: 0, value: "all" }, { level: 1, value: ["ID", "is set"] }, ]); - await selectPath(target, "bar"); + await openModelFieldSelectorPopover(target); + await click(target.querySelector(".o_model_field_selector_popover_item_name")); assert.deepEqual(getTreeEditorContent(target), [ { level: 0, value: "all" }, { level: 1, value: ["Bar", "is", "set"] }, @@ -419,4 +415,53 @@ QUnit.module("Components", (hooks) => { assert.deepEqual(getOperatorOptions(target, -1), ["="]); assert.deepEqual(getValueOptions(target, -1), ["1"]); }); + + QUnit.test("no field of type properties in model field selector", async (assert) => { + patchWithCleanup(odoo, { debug: false }); + serverData.models.partner.fields.properties = { + string: "Properties", + type: "properties", + definition_record: "product_id", + definition_record_field: "definitions", + searchable: true, + }; + serverData.models.product.fields.definitions = { + string: "Definitions", + type: "properties_definition", + }; + + await makeExpressionEditor({ + expression: `properties`, + fields: Object.fromEntries( + Object.entries(serverData.models.partner.fields).filter(([name]) => + ["bar", "foo", "properties"].includes(name) + ) + ), + update(expression) { + assert.step(expression); + }, + }); + assert.deepEqual(getTreeEditorContent(target), [ + { + level: 0, + value: "all", + }, + { + level: 1, + value: ["Properties", "is set"], + }, + ]); + assert.ok(isNotSupportedPath(target)); + + await clearNotSupported(target); + assert.verifySteps([`foo == ""`]); + + await openModelFieldSelectorPopover(target); + assert.deepEqual( + getNodesTextContent([ + ...target.querySelectorAll(".o_model_field_selector_popover_item_name"), + ]), + ["Bar", "Foo"] + ); + }); }); diff --git a/addons/web/static/tests/core/model_field_selector_tests.js b/addons/web/static/tests/core/model_field_selector_tests.js index 84179ccf0b0..5ea73178895 100644 --- a/addons/web/static/tests/core/model_field_selector_tests.js +++ b/addons/web/static/tests/core/model_field_selector_tests.js @@ -775,7 +775,7 @@ QUnit.module("Components", (hooks) => { setup() { this.state = useState({ path: `foo` }); } - update() { } + update() {} } Parent.components = { ModelFieldSelector }; Parent.template = xml``; @@ -1001,4 +1001,19 @@ QUnit.module("Components", (hooks) => { assert.deepEqual(getModelFieldSelectorValues(target), ["foooooo"]); assert.verifySteps(["foooooo"]); }); + + QUnit.test("showDebugInput = false", async (assert) => { + await mountComponent(ModelFieldSelector, { + props: { + readonly: false, + path: "product_id", + resModel: "partner", + isDebugMode: true, + showDebugInput: false, + }, + }); + + await openModelFieldSelectorPopover(target); + assert.containsNone(target, ".o_model_field_selector_debug"); + }); });