diff --git a/addons/web/static/src/views/sample_server.js b/addons/web/static/src/views/sample_server.js index 2b7dabdaf36..7ea837ff08a 100644 --- a/addons/web/static/src/views/sample_server.js +++ b/addons/web/static/src/views/sample_server.js @@ -9,7 +9,7 @@ import { } from "@web/core/l10n/dates"; import { ORM } from "@web/core/orm_service"; import { registry } from "@web/core/registry"; -import { groupBy as arrayGroupBy, sortBy as arraySortBy } from "@web/core/utils/arrays"; +import { cartesian, sortBy as arraySortBy } from "@web/core/utils/arrays"; class UnimplementedRouteError extends Error {} @@ -207,7 +207,7 @@ export class SampleServer { if (["date", "datetime"].includes(type)) { const fmt = SampleServer.FORMATS[interval]; return parseDate(value).toFormat(fmt); - } else if (type === "many2one") { + } else if (["many2one", "many2many"].includes(type)) { const rec = this.data[relation].records.find(({ id }) => id === value); return [value, rec.display_name]; } else { @@ -450,20 +450,42 @@ export class SampleServer { normalizedGroupBys.push(gb); } } - const groups = arrayGroupBy(records, (record) => { - const vals = {}; + + const groupsFromRecord = (record) => { + const values = []; for (const gb of normalizedGroupBys) { const { fieldName, type } = gb; - let value; + let fieldVals; if (["date", "datetime"].includes(type)) { - value = this._formatValue(record[fieldName], gb); + fieldVals = [this._formatValue(record[fieldName], gb)]; + } else if (type === "many2many") { + fieldVals = record[fieldName].length ? record[fieldName] : [false]; } else { - value = record[fieldName]; + fieldVals = [record[fieldName]]; } - vals[fieldName] = value; + values.push(fieldVals.map((val) => ({ [fieldName]: val }))); } - return JSON.stringify(vals); - }); + const cart = cartesian(...values); + return cart.map((tuple) => { + if (!Array.isArray(tuple)) { + tuple = [tuple]; + } + return Object.assign({}, ...tuple); + }); + }; + + const groups = {}; + for (const record of records) { + const recordGroups = groupsFromRecord(record); + for (const group of recordGroups) { + const groupId = JSON.stringify(group); + if (!(groupId in groups)) { + groups[groupId] = []; + } + groups[groupId].push(record); + } + } + const measures = []; for (const measureSpec of params.fields || Object.keys(fields)) { const matches = measureSpec.match(MEASURE_SPEC_REGEX); @@ -496,20 +518,26 @@ export class SampleServer { } group[countKey] = records.length; const firstElem = records[0]; + const parsedId = JSON.parse(id); for (const gb of normalizedGroupBys) { const { alias, fieldName, type } = gb; - group[alias] = this._formatValue(firstElem[fieldName], gb); - if (["date", "datetime"].includes(type)) { - group.__range = {}; - const val = firstElem[fieldName]; - if (val) { - const deserialize = type === "date" ? deserializeDate : deserializeDateTime; - const serialize = type === "date" ? serializeDate : serializeDateTime; - const from = deserialize(val).startOf(gb.interval); - const to = SampleServer.INTERVALS[gb.interval](from); - group.__range[alias] = { from: serialize(from), to: serialize(to) }; - } else { - group.__range[alias] = false; + if (type === "many2many") { + group[alias] = this._formatValue(parsedId[fieldName], gb); + } else { + group[alias] = this._formatValue(firstElem[fieldName], gb); + if (["date", "datetime"].includes(type)) { + group.__range = {}; + const val = firstElem[fieldName]; + if (val) { + const deserialize = + type === "date" ? deserializeDate : deserializeDateTime; + const serialize = type === "date" ? serializeDate : serializeDateTime; + const from = deserialize(val).startOf(gb.interval); + const to = SampleServer.INTERVALS[gb.interval](from); + group.__range[alias] = { from: serialize(from), to: serialize(to) }; + } else { + group.__range[alias] = false; + } } } } diff --git a/addons/web/static/tests/views/sample_server_tests.js b/addons/web/static/tests/views/sample_server_tests.js index 1022c4e50ca..4e90e42ca5a 100644 --- a/addons/web/static/tests/views/sample_server_tests.js +++ b/addons/web/static/tests/views/sample_server_tests.js @@ -466,6 +466,22 @@ QUnit.module( } ); + QUnit.test( + "Send 'read_group' RPC: multiple groupBys among which a many2many", + async function (assert) { + const server = new DeterministicSampleServer("res.users", fields["res.users"]); + const result = await server.mockRpc({ + method: "read_group", + model: "res.users", + fields: [], + groupBy: ["height", "tag_ids"], + lazy: false, + }); + assert.ok(typeof result[0].tag_ids[0] === "number"); + assert.ok(typeof result[0].tag_ids[1] === "string"); + } + ); + QUnit.test("Send 'read' RPC: no id", async function (assert) { assert.expect(1);