From 9779efa93e216176ff5e4d464a9ac405b5dd17c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Voet=20=28ryv=29?= Date: Tue, 28 Sep 2021 13:22:44 +0000 Subject: [PATCH] [FIX] stock: fix lazy list views MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Avoid traceback when click on priority widget - Also, fetch directly the lazy column if the web client read one record (happen when we modify a single record). closes odoo/odoo#77402 X-original-commit: eb26406cff7a0c61768c287a0525c2b6bc123f9e Signed-off-by: William Henrotin Signed-off-by: Rémy Voet --- .../stock/static/src/js/lazy_column_list.js | 24 +++- .../static/tests/lazy_column_list_tests.js | 103 +++++++++++++++--- 2 files changed, 110 insertions(+), 17 deletions(-) diff --git a/addons/stock/static/src/js/lazy_column_list.js b/addons/stock/static/src/js/lazy_column_list.js index e8a22e27def..cb51b60b9c1 100644 --- a/addons/stock/static/src/js/lazy_column_list.js +++ b/addons/stock/static/src/js/lazy_column_list.js @@ -16,9 +16,14 @@ function _findRecordsInState(entrypoint, result = []) { return result; } -function _bindLazyColumnToState(entrypoint, lazyColumns, values) { +function _bindLazyColumnToState(entrypoint, lazyColumns, values, startRequestTime) { let records = _findRecordsInState(entrypoint); for (const recordData of records.values()) { + // keep track of request start time (in _fetchRecord) when we fetch a particular record (can happens we + // modify the priority field) to avoids to erase the old data if the lazy loaded finished after. + if (startRequestTime && recordData.specialData && recordData.specialData.lastUpdateLazy && startRequestTime <= recordData.specialData.lastUpdateLazy){ + continue + } for (const lazyCol of lazyColumns) { recordData.data[lazyCol] = (values && values[recordData.data.id] && values[recordData.data.id][lazyCol]) || @@ -29,6 +34,9 @@ function _bindLazyColumnToState(entrypoint, lazyColumns, values) { function _getLazyColumns(fieldsInfo) { const lazyColumns = []; + if (!fieldsInfo) { + return lazyColumns; + } for (const [key, value] of Object.entries(fieldsInfo)) { if (value.options && value.options.lazy) { lazyColumns.push(key); @@ -50,6 +58,10 @@ const LazyColumnListModel = ListModel.extend({ */ _getFieldNames: function (element, options) { const res = this._super.apply(this, arguments); + if (element.type !== "list") { + // lazy is only for list type element + return res; + } const fieldsInfo = element.fieldsInfo; const viewType = (options && options.viewType) || element.viewType; const lazyColumns = _getLazyColumns(fieldsInfo[viewType]); @@ -66,6 +78,11 @@ const LazyColumnListModel = ListModel.extend({ } return dataPoint; }, + + _fetchRecord: function(record, options) { + record.specialData.lastUpdateLazy = Date.now(); + return this._super.apply(this, arguments); + } }); const LazyColumnListRenderer = ListRenderer.extend({ @@ -75,6 +92,7 @@ const LazyColumnListRenderer = ListRenderer.extend({ }, _lazyLoad: async function (state, lazyColumns) { + const startRequestTime = Date.now() const res = await this._rpc( { model: state.model, @@ -90,9 +108,9 @@ const LazyColumnListRenderer = ListRenderer.extend({ resById[element.id][lazyCol] = element[lazyCol]; } } - _bindLazyColumnToState(this.state, lazyColumns, resById); + _bindLazyColumnToState(this.state, lazyColumns, resById, startRequestTime); this.currentNbLazyRpc -= 1; - // Render only thr + // Render only the lazy columns for (const lazyCol of lazyColumns) { const node = this.columns.find((colAttribute) => colAttribute.attrs.name === lazyCol); if (!node) { diff --git a/addons/stock/static/tests/lazy_column_list_tests.js b/addons/stock/static/tests/lazy_column_list_tests.js index 6844a787451..41449f39b98 100644 --- a/addons/stock/static/tests/lazy_column_list_tests.js +++ b/addons/stock/static/tests/lazy_column_list_tests.js @@ -14,11 +14,12 @@ QUnit.module( person: { fields: { name: { string: "Name", type: "char" }, + priority: { string: "Priority", type: "selection", selection: [['0', 'normal'],['1', 'important']]}, slowField: { string: "Hard to Compute", type: "char" }, }, records: [ - { id: 1, name: "Daniel Fortesque" }, - { id: 2, name: "Samuel Oak" }, + { id: 1, priority: '0', name: "Daniel Fortesque" }, + { id: 2, priority: '0', name: "Samuel Oak" }, ], }, }; @@ -40,23 +41,21 @@ QUnit.module( '' + "", mockRPC: async function (route, args) { - console.log(route); if (route === "/web/dataset/call_kw/person/read") { + const record_ids = args.args[0]; const fields = args.args[1]; - const records = []; + const recs = []; for (const d of this.data.person.records) { - let reco = { id: d.id }; - for (const f of fields) { - if (f === "slowField") { - reco[f] = `${d.name} ${d.id}`; - } else { - reco[f] = d[f]; - } + if (record_ids.includes(d.id)) { + d["slowField"] = `${d.name} ${d.id}`; + recs.push(d); } - records.push(reco); } - await promiseRead; - return Promise.resolve(records); + // Don't wait if it read only one record + if (fields.includes("slowField") && record_ids.length > 1) { + await promiseRead; + } + return Promise.resolve(recs); } return this._super.apply(this, arguments); }, @@ -87,5 +86,81 @@ QUnit.module( list.destroy(); }); + + QUnit.test("With the priority widget", async function (assert) { + assert.expect(19); + const promiseRead = makeDeferred(); + const list = await createView({ + View: LazyColumnList, + model: "person", + data: this.data, + arch: + '' + + '' + + '' + + '' + + "", + mockRPC: async function (route, args) { + if (route === "/web/dataset/call_kw/person/read") { + const record_ids = args.args[0]; + const fields = args.args[1]; + const recs = []; + for (const d of this.data.person.records) { + if (record_ids.includes(d.id)) { + d["slowField"] = `${d.name} ${d.id} ${d.priority}`; + recs.push(d); + } + } + // Don't wait if it read only one record + if (fields.includes("slowField") && record_ids.length > 1) { + await promiseRead; + } + return Promise.resolve(recs); + } + return this._super.apply(this, arguments); + }, + }); + // Checks we have initially 2 records + assert.containsN(list, ".o_data_row", 2, "should have 2 records"); + + let $spin = list.$el.find("th[data-name='slowField'] .fa.fa-spin"); + let $results = list.$el.find("td[name='slowField']"); + assert.equal($spin.length, 1); + assert.equal($spin.hasClass("invisible"), false); + assert.equal($results.length, 2); + assert.equal($results.first().text(), ""); + assert.equal($results.last().text(), ""); + + assert.strictEqual(list.$('.o_data_row').first().find('.o_priority a.fa-star').length, 0, + "widget shouldn't be considered set"); + await testUtils.dom.click(list.$('.o_data_row').first().find('.o_priority a.fa-star-o').first()); + + assert.strictEqual(list.$('.o_data_row').first().find('.o_priority a.fa-star').length, 1, + "widget should be considered set"); + $spin = list.$el.find("th[data-name='slowField'] .fa.fa-spin"); + $results = list.$el.find("td[name='slowField']"); + assert.equal($spin.length, 1); + assert.equal($spin.hasClass("invisible"), false); + assert.equal($results.length, 2); + assert.equal($results.first().text(), "Daniel Fortesque 1 1", + "It should be read by the click on priority"); + assert.equal($results.last().text(), ""); + + promiseRead.resolve(); + await nextTick(); + + $spin = list.$el.find("th[data-name='slowField'] .fa.fa-spin"); + $results = list.$el.find("td[name='slowField']"); + assert.equal($spin.length, 1); + assert.equal($spin.hasClass("invisible"), true); + assert.equal($results.length, 2); + assert.equal($results.first().text(), "Daniel Fortesque 1 1", + "It shouldn't be erase the data from priority click because this data is newer"); + assert.equal($results.last().text(), "Samuel Oak 2 0"); + + assert.containsN(list, ".o_data_row", 2, "should have 2 records"); + + list.destroy(); + }); } );