From d111e66525424f4da9938f2e882023db09f5c2af Mon Sep 17 00:00:00 2001 From: Aaron Bohy Date: Tue, 27 Feb 2024 09:18:14 +0000 Subject: [PATCH] [FIX] web: kanban progress bar: display correct aggregates Before this commit, in a grouped kanban view with progressbars and an aggregate field, after clicking on a bar to filter records, the aggregate value was always 0 (at least when grouped by a many2one or date(time) field). This was due to a mismatch when trying to find the value of the aggregate in the web_read_group result, as when grouped by a date or datetime field, the key is `fieldname:granularity`, and we were looking for the fieldname only. And for the many2one case, we were comparing a pair [id, display_name] with an id. This commit fixes the issue. It also fixes the mocked version of read_progress_bar in the MockServer, s.t. we can correctly reproduce the scenario in tests, as in the previous version, keys in the returned object weren't computed the same way as in the real read_progress_bar (e.g., "14,Mitchel", instead of "Mitchel"). A similar fix has been done in [1]. This allows us to introduce a test when grouped by many2one, which doesn't work as of 17.0. [1] fd759f18d056844c486a68d0c394df5a03e789f0 closes odoo/odoo#155701 X-original-commit: 69b1809ed763d03560983bb0e4c996946dd694c5 Signed-off-by: Lucas Perais (lpe) Signed-off-by: Aaron Bohy (aab) --- .../src/views/kanban/progress_bar_hook.js | 28 ++-- .../web/static/tests/helpers/mock_server.js | 3 + .../tests/views/kanban/kanban_view_tests.js | 129 +++++++++++++++++- 3 files changed, 143 insertions(+), 17 deletions(-) diff --git a/addons/web/static/src/views/kanban/progress_bar_hook.js b/addons/web/static/src/views/kanban/progress_bar_hook.js index a6d155cc876..33c53a9cde4 100644 --- a/addons/web/static/src/views/kanban/progress_bar_hook.js +++ b/addons/web/static/src/views/kanban/progress_bar_hook.js @@ -30,6 +30,14 @@ function _createFilterDomain(fieldName, bars, value) { return filterDomain; } +function _groupsToAggregateValues(groups, groupBy, fields) { + const groupByFieldName = groupBy[0].split(":")[0]; + return groups.map((g) => { + const groupInfo = extractInfoFromGroupData(g, groupBy, fields); + return Object.assign(groupInfo.aggregates, { [groupByFieldName]: groupInfo.serverValue }); + }); +} + class ProgressBarState { constructor(progressAttributes, model, aggregateFields, activeBars = {}) { this.progressAttributes = progressAttributes; @@ -185,22 +193,19 @@ class ProgressBarState { bars, activeBar.value ); - const { context, groupBy, resModel } = this.model.root; + const { context, fields, groupBy, resModel } = this.model.root; const kwargs = { context }; - const fieldNames = this._aggregateFields.map((f) => f.name); - const fields = [...fieldNames, group.groupByField.name]; + const fieldNames = [...this._aggregateFields.map((f) => f.name), group.groupByField.name]; const domain = filterDomain ? Domain.and([group.groupDomain, filterDomain]).toList() : group.groupDomain; return this.model.orm - .webReadGroup(resModel, domain, fields, groupBy, kwargs) + .webReadGroup(resModel, domain, fieldNames, groupBy, kwargs) .then((res) => { if (res.length) { - const resGroup = _findGroup(res.groups, group.groupByField, group.serverValue); - activeBar.aggregates = { - ...resGroup, - [group.groupByField.name]: group.serverValue, - }; + const groupByField = group.groupByField; + const aggrValues = _groupsToAggregateValues(res.groups, groupBy, fields); + activeBar.aggregates = _findGroup(aggrValues, groupByField, group.serverValue); } }); } @@ -239,10 +244,7 @@ class ProgressBarState { groupBy, kwargs ); - this._aggregateValues = res.groups.map((r) => { - const groupInfo = extractInfoFromGroupData(r, groupBy, fields); - return { ...groupInfo.aggregates, [firstGroupByName]: groupInfo.serverValue }; - }); + this._aggregateValues = _groupsToAggregateValues(res.groups, groupBy, fields); } async _updateProgressBar() { diff --git a/addons/web/static/tests/helpers/mock_server.js b/addons/web/static/tests/helpers/mock_server.js index e22fe96064e..fe7bb8c092a 100644 --- a/addons/web/static/tests/helpers/mock_server.js +++ b/addons/web/static/tests/helpers/mock_server.js @@ -1327,6 +1327,9 @@ export class MockServer { for (const group of groups) { const records = this.getRecords(modelName, group.__domain || []); let groupByValue = group[groupBy]; // always technical value here + if (Array.isArray(groupByValue)) { + groupByValue = groupByValue[1]; + } // special case for bool values: rpc call response with capitalized strings if (!(groupByValue in data)) { diff --git a/addons/web/static/tests/views/kanban/kanban_view_tests.js b/addons/web/static/tests/views/kanban/kanban_view_tests.js index 194c5b49e37..206bad364c6 100644 --- a/addons/web/static/tests/views/kanban/kanban_view_tests.js +++ b/addons/web/static/tests/views/kanban/kanban_view_tests.js @@ -10417,6 +10417,123 @@ QUnit.module("Views", (hooks) => { assert.verifySteps(["web_read_group", "read_progress_bar", "web_search_read"]); }); + QUnit.test( + "progress bar with aggregates: activate bars (grouped by boolean)", + async (assert) => { + serverData.models.partner.records = [ + { foo: "yop", bar: true, int_field: 1 }, + { foo: "yop", bar: true, int_field: 2 }, + { foo: "blip", bar: true, int_field: 4 }, + { foo: "gnap", bar: true, int_field: 8 }, + ]; + + await makeView({ + type: "kanban", + resModel: "partner", + serverData, + arch: ` + + + +
+ +
+
+
`, + groupBy: ["bar"], + }); + + assert.deepEqual(getTooltips(target, 0), ["2 yop", "1 gnap", "1 blip"]); + assert.deepEqual(getCounters(target), ["15"]); + + await click(getProgressBars(target, 0)[0]); + assert.deepEqual(getCounters(target), ["3"]); + + await click(getProgressBars(target, 0)[2]); + assert.deepEqual(getCounters(target), ["4"]); + + await click(getProgressBars(target, 0)[2]); + assert.deepEqual(getCounters(target), ["15"]); + } + ); + + QUnit.test( + "progress bar with aggregates: activate bars (grouped by many2one)", + async (assert) => { + serverData.models.partner.records = [ + { foo: "yop", product_id: 3, int_field: 1 }, + { foo: "yop", product_id: 3, int_field: 2 }, + { foo: "blip", product_id: 3, int_field: 4 }, + { foo: "gnap", product_id: 3, int_field: 8 }, + ]; + + await makeView({ + type: "kanban", + resModel: "partner", + serverData, + arch: ` + + + +
+ +
+
+
`, + groupBy: ["product_id"], + }); + + assert.deepEqual(getTooltips(target, 0), ["2 yop", "1 gnap", "1 blip"]); + assert.deepEqual(getCounters(target), ["15"]); + + await click(getProgressBars(target, 0)[0]); + assert.deepEqual(getCounters(target), ["3"]); + + await click(getProgressBars(target, 0)[2]); + assert.deepEqual(getCounters(target), ["4"]); + + await click(getProgressBars(target, 0)[2]); + assert.deepEqual(getCounters(target), ["15"]); + } + ); + + QUnit.test("progress bar with aggregates: activate bars (grouped by date)", async (assert) => { + serverData.models.partner.records = [ + { foo: "yop", date: "2023-10-08", int_field: 1 }, + { foo: "yop", date: "2023-10-08", int_field: 2 }, + { foo: "blip", date: "2023-10-08", int_field: 4 }, + { foo: "gnap", date: "2023-10-08", int_field: 8 }, + ]; + + await makeView({ + type: "kanban", + resModel: "partner", + serverData, + arch: ` + + + +
+ +
+
+
`, + groupBy: ["date:week"], + }); + + assert.deepEqual(getTooltips(target, 0), ["2 yop", "1 gnap", "1 blip"]); + assert.deepEqual(getCounters(target), ["15"]); + + await click(getProgressBars(target, 0)[0]); + assert.deepEqual(getCounters(target), ["3"]); + + await click(getProgressBars(target, 0)[2]); + assert.deepEqual(getCounters(target), ["4"]); + + await click(getProgressBars(target, 0)[2]); + assert.deepEqual(getCounters(target), ["15"]); + }); + QUnit.test("load more should load correct records after drag&drop event", async (assert) => { // Add a sequence number and initialize serverData.models.partner.records.forEach((el, i) => (el.sequence = i)); @@ -13944,7 +14061,7 @@ QUnit.module("Views", (hooks) => { }); QUnit.test("scroll on group unfold and progressbar click", async (assert) => { - assert.expect(7); + assert.expect(15); await makeView({ type: "kanban", @@ -13959,17 +14076,21 @@ QUnit.module("Views", (hooks) => { `, groupBy: ["product_id"], async mockRPC(route, args, performRPC) { + assert.step(args.method); if (args.method === "web_read_group") { const result = await performRPC(route, args); if (result.groups.length) { result.groups[0].__fold = false; - result.groups[1].__fold = true; + if (result.groups[1]) { + result.groups[1].__fold = true; + } } return result; } }, }); + assert.verifySteps(["get_views", "web_read_group", "read_progress_bar", "web_search_read"]); const content = target.querySelector(".o_content"); content.scrollTo = (params) => { assert.step("scrolled"); @@ -13977,12 +14098,12 @@ QUnit.module("Views", (hooks) => { }; await click(getProgressBars(target, 0)[0]); - assert.verifySteps(["scrolled"]); + assert.verifySteps(["web_read_group", "web_search_read", "scrolled"]); const column1 = getColumn(target, 1); assert.hasClass(column1, "o_column_folded"); await click(column1); - assert.verifySteps(["scrolled"]); + assert.verifySteps(["web_search_read", "scrolled"]); }); QUnit.test(