diff --git a/addons/web/static/src/search/search_model.js b/addons/web/static/src/search/search_model.js index 38beeb34ed0..e2f4a5a32d1 100644 --- a/addons/web/static/src/search/search_model.js +++ b/addons/web/static/src/search/search_model.js @@ -20,7 +20,13 @@ import { FACET_ICONS, FACET_COLORS } from "./utils/misc"; import { EventBus, toRaw } from "@odoo/owl"; import { toDomain, toTree, formatValue, normalizeValue } from "@web/core/domain_tree"; -import { createVirtualOperators, extractPathsFromDomain } from "@web/core/domain_selector/utils"; +import { + createVirtualOperators, + extractIdsFromDomain, + extractPathsFromDomain, + leafToString, + useLoadDisplayNames, +} from "@web/core/domain_selector/utils"; import { useLoadFieldInfo, useLoadPathDescription } from "@web/core/model_field_selector/utils"; import { _t } from "@web/core/l10n/translation"; import { getOperatorInfo } from "@web/core/domain_selector/domain_selector_operators"; @@ -214,11 +220,17 @@ function simplifyTree(tree, isRoot = true) { * @param {boolean} [isSubExpression=true] * @returns {string} */ -export function getDomainTreeDescription(tree, getDescription, isSubExpression = true) { +function getDomainTreeDescription( + tree, + getFieldDef, + getDescription, + displayNames, + isSubExpression = true +) { if (tree.type === "connector") { // we assume that the domain tree is normalized (--> there is at least two children) const childDescriptions = tree.children.map((c) => - getDomainTreeDescription(c, getDescription) + getDomainTreeDescription(c, getFieldDef, getDescription, displayNames) ); const separator = tree.value === "&" ? _t("and") : _t("or"); let description = childDescriptions.join(` ${separator} `); @@ -230,43 +242,28 @@ export function getDomainTreeDescription(tree, getDescription, isSubExpression = } return description; } - - const { path, operator, value } = tree; - const operatorInfo = getOperatorInfo(operator); - - let description = `${getDescription(path)} ${operatorInfo.label} `; - - if (["set", "not_set"].includes(operatorInfo.key)) { - description = description.trim(); - } else if (["is", "is_not"].includes(operatorInfo.key)) { - description += value ? _t("set") : _t("not set"); - } else { - const values = Array.isArray(value) ? value : [value]; - let join; - let addParenthesis; - switch (operator) { - case "between": - join = _t("and"); - addParenthesis = false; - break; - case "in": - case "not in": - join = ","; - addParenthesis = true; - break; - default: - join = _t("or"); - addParenthesis = values.length > 1; - } - const jointedValues = values.map((val) => String(val)).join(` ${join} `); + const { negate, operator, path, value } = tree; + const fieldDef = getFieldDef(path); + const operatorInfo = getOperatorInfo(operator, negate); + const { operatorDescription, valueDescription } = leafToString( + fieldDef, + operatorInfo, + value, + displayNames[fieldDef?.relation] + ); + let description = `${getDescription(path)} ${operatorDescription} `; + if (valueDescription) { + const { values, join, addParenthesis } = valueDescription; + const jointedValues = values.join(` ${join} `); description += addParenthesis ? `( ${jointedValues} )` : jointedValues; } return description; } -export function useGetDomainTreeDescription(fieldService) { +function useGetDomainTreeDescription(fieldService, nameService) { const loadFieldInfo = useLoadFieldInfo(fieldService); const loadPathDescription = useLoadPathDescription(fieldService); + const loadDisplayNames = useLoadDisplayNames(nameService); return async (resModel, tree) => { tree = simplifyTree(tree); const domain = toDomain(tree); @@ -287,8 +284,16 @@ export function useGetDomainTreeDescription(fieldService) { await Promise.all(promises); const getFieldDef = (path) => pathFieldDefs[formatValue(path)]; const getDescription = (path) => pathDescriptions[formatValue(path)]; + const idsByModel = extractIdsFromDomain(domain, getFieldDef); const treeWithVirtualOperators = createVirtualOperators(tree, getFieldDef); - return getDomainTreeDescription(treeWithVirtualOperators, getDescription, false); + const displayNames = await loadDisplayNames(idsByModel); + return getDomainTreeDescription( + treeWithVirtualOperators, + getFieldDef, + getDescription, + displayNames, + false + ); }; } @@ -310,12 +315,12 @@ export class SearchModel extends EventBus { */ setup(services) { // services - const { field: fieldService, orm, user, view } = services; + const { field: fieldService, name: nameService, orm, user, view } = services; this.orm = orm; this.userService = user; this.viewService = view; - this.getDomainTreeDescription = useGetDomainTreeDescription(fieldService); + this.getDomainTreeDescription = useGetDomainTreeDescription(fieldService, nameService); // used to manage search items related to date/datetime fields this.referenceMoment = DateTime.local(); diff --git a/addons/web/static/src/search/with_search/with_search.js b/addons/web/static/src/search/with_search/with_search.js index aa82c0d59ff..83398bfc607 100644 --- a/addons/web/static/src/search/with_search/with_search.js +++ b/addons/web/static/src/search/with_search/with_search.js @@ -24,6 +24,7 @@ export class WithSearch extends Component { orm: useService("orm"), view: useService("view"), field: useService("field"), + name: useService("name"), }, this.props.searchModelArgs ); diff --git a/addons/web/static/tests/search/search_bar_menu_tests.js b/addons/web/static/tests/search/search_bar_menu_tests.js index bdf3a3d68d6..0edf5483f36 100644 --- a/addons/web/static/tests/search/search_bar_menu_tests.js +++ b/addons/web/static/tests/search/search_bar_menu_tests.js @@ -1677,7 +1677,7 @@ QUnit.module("Search", (hooks) => { await editInput(target, ".o_domain_debug_input", `[("foo", "in", [uid, 1, "a"])]`); await click(target.querySelector(".modal footer button")); - assert.deepEqual(getFacetTexts(target), [`Foo is in ( uid , 1 , a )`]); + assert.deepEqual(getFacetTexts(target), [`Foo is in ( uid , 1 , "a" )`]); assert.deepEqual(getDomain(controlPanel), [ ["foo", "in", [7, 1, "a"]], // uid = 7 ]); @@ -1794,5 +1794,49 @@ QUnit.module("Search", (hooks) => { assert.deepEqual(getFacetTexts(target), ["Boolean is not set"]); assert.deepEqual(getDomain(controlPanel), [["boolean", "!=", true]]); }); + + QUnit.test("display names in facets", async function (assert) { + patchWithCleanup(odoo, { debug: true }); + serverData.models.partner = { + fields: {}, + records: [ + { id: 1, display_name: "John" }, + { id: 2, display_name: "David" }, + ], + }; + + const controlPanel = await makeWithSearch({ + serverData, + resModel: "foo", + Component: SearchBar, + searchMenuTypes: ["filter"], + searchViewId: false, + searchViewArch: ``, + }); + await toggleSearchBarMenu(target); + await openAddCustomFilterDialog(target); + await editInput( + target, + ".o_domain_debug_input", + `[("bar", "=", 1 ), ("bar", "in", [2, 5555]), ("bar", "!=", false), ("id", "=", 2)]` + ); + await click(target.querySelector(".modal footer button")); + + assert.deepEqual(getFacetTexts(target), [ + "Bar = John", + "Bar is in ( David , Inaccessible/missing record ID: 5555 )", + "Bar is set", + "ID = 2", + ]); + assert.deepEqual(getDomain(controlPanel), [ + "&", + ["bar", "=", 1], + "&", + ["bar", "in", [2, 5555]], + "&", + ["bar", "!=", false], + ["id", "=", 2], + ]); + }); }); });