diff --git a/addons/web/static/src/views/view_dialogs/export_data_dialog.js b/addons/web/static/src/views/view_dialogs/export_data_dialog.js index d9c76846ca9..05b2e7d6993 100644 --- a/addons/web/static/src/views/view_dialogs/export_data_dialog.js +++ b/addons/web/static/src/views/view_dialogs/export_data_dialog.js @@ -23,18 +23,31 @@ DeleteExportListDialog.template = "web.DeleteExportListDialog"; class ExportDataItem extends Component { setup() { this.state = useState({ - isExpanded: this.subFields.length > 0 && this.props.isExpanded, + subfields: [], + }); + onWillStart(() => { + if (this.props.isExpanded) { + // automatically expand the item when subfields are already loaded + // and display subfields that match the search string + return this.toggleItem(this.props.field.id, false); + } }); } - get subFields() { - return this.props.getSubFields(this.props.field.id); - } - - async loadExpandedContent(id) { + async toggleItem(id, isUserToggle) { if (this.props.isFieldExpandable(id)) { - await this.props.onToggleExpandField(id); - this.state.isExpanded = !this.state.isExpanded; + if (this.state.subfields.length) { + this.state.subfields = []; + } else { + const subfields = await this.props.loadFields(id, !isUserToggle); + if (subfields) { + this.state.subfields = isUserToggle + ? subfields + : this.props.filterSubfields(subfields); + } else { + this.state.subfields = []; + } + } } } @@ -53,13 +66,12 @@ ExportDataItem.components = { ExportDataItem }; ExportDataItem.props = { exportList: { type: Object, optional: true }, field: { type: Object, optional: true }, - getSubFields: Function, + filterSubfields: Function, isDebug: Boolean, isExpanded: Boolean, isFieldExpandable: Function, onAdd: Function, - onToggleExpandField: Function, - search: Array, + loadFields: Function, }; export class ExportDataDialog extends Component { @@ -164,17 +176,20 @@ export class ExportDataDialog extends Component { return this.fieldsAvailable.filter(({ parent }) => !parent); } - getSubFields(id) { + filterSubfields(subfields) { let subfieldsFromSearchResults = []; - const fieldsAvailable = this.fieldsAvailable; - const expandedFields = (this.expandedFields[id] && this.expandedFields[id].fields) || []; + let searchResults; + if (this.searchRef.el && this.searchRef.el.value) { + searchResults = this.lookup(this.searchRef.el.value); + } + const fieldsAvailable = Object.values(searchResults || this.knownFields); if (this.searchRef.el && this.searchRef.el.value) { subfieldsFromSearchResults = fieldsAvailable .filter((f) => f.parent && this.knownFields[f.parent.id].parent) .map((f) => f.parent); } const availableSubFields = unique([...fieldsAvailable, ...subfieldsFromSearchResults]); - return expandedFields.filter((a) => availableSubFields.some((b) => a.id === b.id)); + return subfields.filter((a) => availableSubFields.some((b) => a.id === b.id)); } updateSize() { @@ -225,7 +240,7 @@ export class ExportDataDialog extends Component { }); } - async loadFields(id) { + async loadFields(id, preventLoad = false) { let model = this.props.root.resModel; let parentField, parentParams; if (id) { @@ -243,6 +258,9 @@ export class ExportDataDialog extends Component { exclude: [parentField.relation_field], }; } + if (preventLoad) { + return; + } const fields = await this.props.getExportedFields( model, this.state.isCompatible, @@ -349,22 +367,27 @@ export class ExportDataDialog extends Component { }); } - async onSearch(ev) { - this.state.search = fuzzyLookup( - ev.target.value, + onSearch(ev) { + this.state.search = this.lookup(ev.target.value); + } + + lookup(value) { + let lookupResult = fuzzyLookup( + value, Object.values(this.knownFields), // because fuzzyLookup gives an higher score if the string starts with the pattern, // reversing the string makes the search more reliable in this context - (field) => field.id.split("/").reverse().join("/") + (field) => field.string.split("/").reverse().join("/") ); if (this.isDebug) { - this.state.search = unique([ - ...this.state.search, + lookupResult = unique([ + ...lookupResult, ...Object.values(this.knownFields).filter((f) => { - return f.id.includes(ev.target.value); + return f.id.includes(value); }), ]); } + return lookupResult; } onToggleCompatibleExport(value) { @@ -372,10 +395,6 @@ export class ExportDataDialog extends Component { this.fetchFields(); } - onToggleExpandField(id) { - return this.loadFields(id); - } - async setDefaultExportList() { this.state.exportList = Object.values(this.knownFields).filter( (e) => e.default_export || this.props.defaultExportList.find((i) => i.name === e.id) diff --git a/addons/web/static/src/views/view_dialogs/export_data_dialog.xml b/addons/web/static/src/views/view_dialogs/export_data_dialog.xml index 4bf9a887320..a166b32de00 100644 --- a/addons/web/static/src/views/view_dialogs/export_data_dialog.xml +++ b/addons/web/static/src/views/view_dialogs/export_data_dialog.xml @@ -12,21 +12,19 @@ -
+
- +
- - + -
@@ -48,13 +46,12 @@ diff --git a/addons/web/static/tests/views/view_dialogs/export_data_dialog_tests.js b/addons/web/static/tests/views/view_dialogs/export_data_dialog_tests.js index cd142117611..e9b920817f3 100644 --- a/addons/web/static/tests/views/view_dialogs/export_data_dialog_tests.js +++ b/addons/web/static/tests/views/view_dialogs/export_data_dialog_tests.js @@ -1135,9 +1135,7 @@ QUnit.module("ViewDialogs", (hooks) => { ); }); - QUnit.test("Export dialog: search in debug", async function (assert) { - patchWithCleanup(odoo, { debug: "1" }); - + QUnit.test("Export dialog: expand subfields after search", async function (assert) { await makeView({ serverData, type: "list", @@ -1163,8 +1161,57 @@ QUnit.module("ViewDialogs", (hooks) => { const firstField = target.querySelector( ".o_left_field_panel .o_export_tree_item:first-child" ); + // show then hide content for the 'activity_ids' field. + // this will load subfields and make them available to search await click(firstField); - await click(firstField.querySelector(".o_export_tree_item")); + await click(firstField); + await editInput(target, ".o_export_search_input", "Attendants"); + assert.containsOnce( + target, + ".o_export_tree_item[data-field_id='activity_ids/partner_ids']", + "subfield that was known has been found and is displayed" + ); + + await click( + target.querySelector(".o_export_tree_item[data-field_id='activity_ids/partner_ids']") + ); + await nextTick(); + // 'Company' should be shown even if the company_ids string doesn't match the search string + // since the toggle was done by the user to show subfields + assert.containsOnce( + target, + ".o_export_tree_item[data-field_id='activity_ids/partner_ids/company_ids']", + "subfield has been loaded and is displayed" + ); + }); + + QUnit.test("Export dialog: search in debug", async function (assert) { + patchWithCleanup(odoo, { debug: "1" }); + + await makeView({ + serverData, + type: "list", + resModel: "partner", + arch: ` + `, + actionMenus: {}, + mockRPC(route, args) { + if (route === "/web/export/formats") { + return Promise.resolve([{ tag: "csv", label: "CSV" }]); + } + if (route === "/web/export/get_fields") { + if (!args.parent_field) { + return Promise.resolve(fetchedFields.root); + } + return Promise.resolve(fetchedFields[args.prefix]); + } + }, + }); + + await openExportDataDialog(); + + await click(target.querySelector(".o_left_field_panel .o_export_tree_item:first-child")); + await click(target.querySelector(".o_export_tree_item:first-child .o_export_tree_item")); await editInput(target, ".o_export_search_input", "company_ids"); assert.containsOnce( target,