From ab3032465ce8b06f3ff651e3ef85d0e076f719d2 Mon Sep 17 00:00:00 2001 From: Bruno Boi Date: Mon, 1 Aug 2022 14:26:57 +0000 Subject: [PATCH] [FIX] web: invalid date/datetime value/range/domain in mockReadGroup The mock server's mockReadGroup function would not properly compute the value, range and domain of the returned groups for date/datetime fields. The value should be expressed in the local timezone, and the range/domain shoulb be expressed in UTC timezone. Consider the following: - a record with a datetime field of value "2016-12-14 12:34:56" (expressed in UTC) - a user with an UTC+2 timezone offset - a read_group with groupby "['datetime:hour']" The corresponding group would have had the following metadata: - value: `"12:00 14 Dec"` - domain: `[["datetime", ">=", "2022-12-14 11:00:00"], ["datetime", "<", "2022-12-14 12:00:00"]]` - range: `{ from: "2022-12-14 11:00:00", to: "2022-12-14 12:00:00" }` It now has the following valid metadata: - value: `"13:00 14 Dec"` - domain: `[["datetime", ">=", "2022-12-14 12:00:00"], ["datetime", "<", "2022-12-14 13:00:00"]]` - range: `{ from: "2022-12-14 12:00:00", to: "2022-12-14 13:00:00" }` closes odoo/odoo#96946 Signed-off-by: Aaron Bohy (aab) --- .../web/static/tests/helpers/mock_server.js | 59 ++-- .../tests/legacy/helpers/mock_server.js | 3 + addons/web/static/tests/mock_server_tests.js | 317 +++++++++++++++++- .../static/tests/views/kanban_view_tests.js | 8 +- 4 files changed, 361 insertions(+), 26 deletions(-) diff --git a/addons/web/static/tests/helpers/mock_server.js b/addons/web/static/tests/helpers/mock_server.js index 00caecbb162..77a125b717f 100644 --- a/addons/web/static/tests/helpers/mock_server.js +++ b/addons/web/static/tests/helpers/mock_server.js @@ -8,9 +8,14 @@ import { intersection } from "@web/core/utils/arrays"; import { registry } from "@web/core/registry"; import { makeFakeRPCService, makeMockFetch } from "./mock_services"; import { patchWithCleanup } from "./utils"; -import { parseDateTime } from "@web/core/l10n/dates"; +import { + deserializeDate, + deserializeDateTime, + parseDateTime, + serializeDate, + serializeDateTime, +} from "@web/core/l10n/dates"; -const { DateTime } = luxon; const serviceRegistry = registry.category("services"); const domParser = new DOMParser(); @@ -879,7 +884,7 @@ export class MockServer { const [fieldName, aggregateFunction = "month"] = groupByField.split(":"); const { type } = fields[fieldName]; if (type === "date") { - const date = DateTime.fromSQL(val); + const date = deserializeDate(val).setZone("default"); if (aggregateFunction === "day") { return date.toFormat("yyyy-MM-dd"); } else if (aggregateFunction === "week") { @@ -892,7 +897,7 @@ export class MockServer { return date.toFormat("MMMM yyyy"); } } else if (type === "datetime") { - const date = DateTime.fromSQL(val); + const date = deserializeDateTime(val).setZone("default"); if (aggregateFunction === "hour") { return date.toFormat("HH:00 dd MMM"); } else if (aggregateFunction === "day") { @@ -987,48 +992,64 @@ export class MockServer { switch (dateRange) { case "hour": { try { - startDate = parseDateTime(value, { format: "HH dd MMM" }); + startDate = parseDateTime(value, { + format: "HH dd MMM", + timezone: type !== "date", + }); } catch { - startDate = parseDateTime(value, { format: "HH:00 dd MMM" }); + startDate = parseDateTime(value, { + format: "HH:00 dd MMM", + timezone: type !== "date", + }); } endDate = startDate.plus({ hours: 1 }); break; } case "day": { - startDate = parseDateTime(value, { format: "yyyy-MM-dd" }); + startDate = parseDateTime(value, { + format: "yyyy-MM-dd", + timezone: type !== "date", + }); endDate = startDate.plus({ days: 1 }); break; } case "week": { - startDate = parseDateTime(value, { format: "WW kkkk" }); + startDate = parseDateTime(value, { + format: "WW kkkk", + timezone: type !== "date", + }); endDate = startDate.plus({ weeks: 1 }); break; } case "quarter": { - startDate = parseDateTime(value, { format: "q yyyy" }); + startDate = parseDateTime(value, { + format: "q yyyy", + timezone: type !== "date", + }); endDate = startDate.plus({ quarters: 1 }); break; } case "year": { - startDate = parseDateTime(value, { format: "y" }); + startDate = parseDateTime(value, { + format: "y", + timezone: type !== "date", + }); endDate = startDate.plus({ years: 1 }); break; } case "month": default: { - startDate = parseDateTime(value, { format: "MMMM yyyy" }); + startDate = parseDateTime(value, { + format: "MMMM yyyy", + timezone: type !== "date", + }); endDate = startDate.plus({ months: 1 }); break; } } - const from = - type === "date" - ? startDate.toFormat("yyyy-MM-dd") - : startDate.toFormat("yyyy-MM-dd HH:mm:ss"); - const to = - type === "date" - ? endDate.toFormat("yyyy-MM-dd") - : endDate.toFormat("yyyy-MM-dd HH:mm:ss"); + const serialize = type === "date" ? serializeDate : serializeDateTime; + const from = serialize(startDate); + const to = serialize(endDate); group.__range[gbField] = { from, to }; group.__domain = [ [fieldName, ">=", from], diff --git a/addons/web/static/tests/legacy/helpers/mock_server.js b/addons/web/static/tests/legacy/helpers/mock_server.js index a575175ca5a..ccd24922dd0 100644 --- a/addons/web/static/tests/legacy/helpers/mock_server.js +++ b/addons/web/static/tests/legacy/helpers/mock_server.js @@ -1644,6 +1644,9 @@ var MockServer = Class.extend({ const to = type === "date" ? endDate.format("YYYY-MM-DD") : endDate.format("YYYY-MM-DD HH:mm:ss"); + // NOTE THAT the range and the domain computed here are not really accurate + // due to a the timezone not really taken into account. + // FYI, the non legacy version of the mock server handles this correctly. group.__range[gbField] = { from, to }; group.__domain = [ [fieldName, ">=", from], diff --git a/addons/web/static/tests/mock_server_tests.js b/addons/web/static/tests/mock_server_tests.js index 0264598b06e..4d2f0b37602 100644 --- a/addons/web/static/tests/mock_server_tests.js +++ b/addons/web/static/tests/mock_server_tests.js @@ -394,6 +394,36 @@ QUnit.module("MockServer", (hooks) => { result.map((x) => x.date_count), [1, 1, 3, 1] ); + assert.deepEqual( + result.map((x) => x.__domain), + [ + [ + ["date", ">=", "2016-04-01"], + ["date", "<", "2016-05-01"], + ], + [ + ["date", ">=", "2016-10-01"], + ["date", "<", "2016-11-01"], + ], + [ + ["date", ">=", "2016-12-01"], + ["date", "<", "2017-01-01"], + ], + [ + ["date", ">=", "2019-12-01"], + ["date", "<", "2020-01-01"], + ], + ] + ); + assert.deepEqual( + result.map((x) => x.__range["date"]), + [ + { from: "2016-04-01", to: "2016-05-01" }, + { from: "2016-10-01", to: "2016-11-01" }, + { from: "2016-12-01", to: "2017-01-01" }, + { from: "2019-12-01", to: "2020-01-01" }, + ] + ); result = await server.performRPC("", { model: "bar", @@ -413,6 +443,41 @@ QUnit.module("MockServer", (hooks) => { result.map((x) => x.date_count), [1, 1, 1, 2, 1] ); + assert.deepEqual( + result.map((x) => x.__domain), + [ + [ + ["date", ">=", "2016-04-11"], + ["date", "<", "2016-04-12"], + ], + [ + ["date", ">=", "2016-10-26"], + ["date", "<", "2016-10-27"], + ], + [ + ["date", ">=", "2016-12-14"], + ["date", "<", "2016-12-15"], + ], + [ + ["date", ">=", "2016-12-15"], + ["date", "<", "2016-12-16"], + ], + [ + ["date", ">=", "2019-12-30"], + ["date", "<", "2019-12-31"], + ], + ] + ); + assert.deepEqual( + result.map((x) => x.__range["date:day"]), + [ + { from: "2016-04-11", to: "2016-04-12" }, + { from: "2016-10-26", to: "2016-10-27" }, + { from: "2016-12-14", to: "2016-12-15" }, + { from: "2016-12-15", to: "2016-12-16" }, + { from: "2019-12-30", to: "2019-12-31" }, + ] + ); result = await server.performRPC("", { model: "bar", @@ -432,6 +497,36 @@ QUnit.module("MockServer", (hooks) => { result.map((x) => x.date_count), [1, 1, 3, 1] ); + assert.deepEqual( + result.map((x) => x.__domain), + [ + [ + ["date", ">=", "2016-04-11"], + ["date", "<", "2016-04-18"], + ], + [ + ["date", ">=", "2016-10-24"], + ["date", "<", "2016-10-31"], + ], + [ + ["date", ">=", "2016-12-12"], + ["date", "<", "2016-12-19"], + ], + [ + ["date", ">=", "2019-12-30"], + ["date", "<", "2020-01-06"], + ], + ] + ); + assert.deepEqual( + result.map((x) => x.__range["date:week"]), + [ + { from: "2016-04-11", to: "2016-04-18" }, + { from: "2016-10-24", to: "2016-10-31" }, + { from: "2016-12-12", to: "2016-12-19" }, + { from: "2019-12-30", to: "2020-01-06" }, + ] + ); result = await server.performRPC("", { model: "bar", @@ -451,6 +546,31 @@ QUnit.module("MockServer", (hooks) => { result.map((x) => x.date_count), [1, 4, 1] ); + assert.deepEqual( + result.map((x) => x.__domain), + [ + [ + ["date", ">=", "2016-04-01"], + ["date", "<", "2016-07-01"], + ], + [ + ["date", ">=", "2016-10-01"], + ["date", "<", "2017-01-01"], + ], + [ + ["date", ">=", "2019-10-01"], + ["date", "<", "2020-01-01"], + ], + ] + ); + assert.deepEqual( + result.map((x) => x.__range["date:quarter"]), + [ + { from: "2016-04-01", to: "2016-07-01" }, + { from: "2016-10-01", to: "2017-01-01" }, + { from: "2019-10-01", to: "2020-01-01" }, + ] + ); result = await server.performRPC("", { model: "bar", @@ -470,6 +590,26 @@ QUnit.module("MockServer", (hooks) => { result.map((x) => x.date_count), [5, 1] ); + assert.deepEqual( + result.map((x) => x.__domain), + [ + [ + ["date", ">=", "2016-01-01"], + ["date", "<", "2017-01-01"], + ], + [ + ["date", ">=", "2019-01-01"], + ["date", "<", "2020-01-01"], + ], + ] + ); + assert.deepEqual( + result.map((x) => x.__range["date:year"]), + [ + { from: "2016-01-01", to: "2017-01-01" }, + { from: "2019-01-01", to: "2020-01-01" }, + ] + ); }); QUnit.test("performRPC: read_group, group by datetime", async function (assert) { @@ -492,6 +632,36 @@ QUnit.module("MockServer", (hooks) => { result.map((x) => x.datetime_count), [1, 1, 3, 1] ); + assert.deepEqual( + result.map((x) => x.__domain), + [ + [ + ["datetime", ">=", "2016-03-31 23:00:00"], + ["datetime", "<", "2016-04-30 23:00:00"], + ], + [ + ["datetime", ">=", "2016-09-30 23:00:00"], + ["datetime", "<", "2016-10-31 23:00:00"], + ], + [ + ["datetime", ">=", "2016-11-30 23:00:00"], + ["datetime", "<", "2016-12-31 23:00:00"], + ], + [ + ["datetime", ">=", "2019-11-30 23:00:00"], + ["datetime", "<", "2019-12-31 23:00:00"], + ], + ] + ); + assert.deepEqual( + result.map((x) => x.__range["datetime"]), + [ + { from: "2016-03-31 23:00:00", to: "2016-04-30 23:00:00" }, + { from: "2016-09-30 23:00:00", to: "2016-10-31 23:00:00" }, + { from: "2016-11-30 23:00:00", to: "2016-12-31 23:00:00" }, + { from: "2019-11-30 23:00:00", to: "2019-12-31 23:00:00" }, + ] + ); result = await server.performRPC("", { model: "bar", @@ -505,12 +675,47 @@ QUnit.module("MockServer", (hooks) => { }); assert.deepEqual( result.map((x) => x["datetime:hour"]), - ["12:00 11 Apr", "12:00 26 Oct", "12:00 14 Dec", "12:00 15 Dec", "12:00 30 Dec"] + ["13:00 11 Apr", "13:00 26 Oct", "13:00 14 Dec", "13:00 15 Dec", "13:00 30 Dec"] ); assert.deepEqual( result.map((x) => x.datetime_count), [1, 1, 1, 2, 1] ); + assert.deepEqual( + result.map((x) => x.__domain), + [ + [ + ["datetime", ">=", "2022-04-11 12:00:00"], + ["datetime", "<", "2022-04-11 13:00:00"], + ], + [ + ["datetime", ">=", "2022-10-26 12:00:00"], + ["datetime", "<", "2022-10-26 13:00:00"], + ], + [ + ["datetime", ">=", "2022-12-14 12:00:00"], + ["datetime", "<", "2022-12-14 13:00:00"], + ], + [ + ["datetime", ">=", "2022-12-15 12:00:00"], + ["datetime", "<", "2022-12-15 13:00:00"], + ], + [ + ["datetime", ">=", "2022-12-30 12:00:00"], + ["datetime", "<", "2022-12-30 13:00:00"], + ], + ] + ); + assert.deepEqual( + result.map((x) => x.__range["datetime:hour"]), + [ + { from: "2022-04-11 12:00:00", to: "2022-04-11 13:00:00" }, + { from: "2022-10-26 12:00:00", to: "2022-10-26 13:00:00" }, + { from: "2022-12-14 12:00:00", to: "2022-12-14 13:00:00" }, + { from: "2022-12-15 12:00:00", to: "2022-12-15 13:00:00" }, + { from: "2022-12-30 12:00:00", to: "2022-12-30 13:00:00" }, + ] + ); result = await server.performRPC("", { model: "bar", @@ -530,6 +735,41 @@ QUnit.module("MockServer", (hooks) => { result.map((x) => x.datetime_count), [1, 1, 1, 2, 1] ); + assert.deepEqual( + result.map((x) => x.__domain), + [ + [ + ["datetime", ">=", "2016-04-10 23:00:00"], + ["datetime", "<", "2016-04-11 23:00:00"], + ], + [ + ["datetime", ">=", "2016-10-25 23:00:00"], + ["datetime", "<", "2016-10-26 23:00:00"], + ], + [ + ["datetime", ">=", "2016-12-13 23:00:00"], + ["datetime", "<", "2016-12-14 23:00:00"], + ], + [ + ["datetime", ">=", "2016-12-14 23:00:00"], + ["datetime", "<", "2016-12-15 23:00:00"], + ], + [ + ["datetime", ">=", "2019-12-29 23:00:00"], + ["datetime", "<", "2019-12-30 23:00:00"], + ], + ] + ); + assert.deepEqual( + result.map((x) => x.__range["datetime:day"]), + [ + { from: "2016-04-10 23:00:00", to: "2016-04-11 23:00:00" }, + { from: "2016-10-25 23:00:00", to: "2016-10-26 23:00:00" }, + { from: "2016-12-13 23:00:00", to: "2016-12-14 23:00:00" }, + { from: "2016-12-14 23:00:00", to: "2016-12-15 23:00:00" }, + { from: "2019-12-29 23:00:00", to: "2019-12-30 23:00:00" }, + ] + ); result = await server.performRPC("", { model: "bar", @@ -549,6 +789,36 @@ QUnit.module("MockServer", (hooks) => { result.map((x) => x.datetime_count), [1, 1, 3, 1] ); + assert.deepEqual( + result.map((x) => x.__domain), + [ + [ + ["datetime", ">=", "2016-04-10 23:00:00"], + ["datetime", "<", "2016-04-17 23:00:00"], + ], + [ + ["datetime", ">=", "2016-10-23 23:00:00"], + ["datetime", "<", "2016-10-30 23:00:00"], + ], + [ + ["datetime", ">=", "2016-12-11 23:00:00"], + ["datetime", "<", "2016-12-18 23:00:00"], + ], + [ + ["datetime", ">=", "2019-12-29 23:00:00"], + ["datetime", "<", "2020-01-05 23:00:00"], + ], + ] + ); + assert.deepEqual( + result.map((x) => x.__range["datetime:week"]), + [ + { from: "2016-04-10 23:00:00", to: "2016-04-17 23:00:00" }, + { from: "2016-10-23 23:00:00", to: "2016-10-30 23:00:00" }, + { from: "2016-12-11 23:00:00", to: "2016-12-18 23:00:00" }, + { from: "2019-12-29 23:00:00", to: "2020-01-05 23:00:00" }, + ] + ); result = await server.performRPC("", { model: "bar", @@ -568,6 +838,31 @@ QUnit.module("MockServer", (hooks) => { result.map((x) => x.datetime_count), [1, 4, 1] ); + assert.deepEqual( + result.map((x) => x.__domain), + [ + [ + ["datetime", ">=", "2016-03-31 23:00:00"], + ["datetime", "<", "2016-06-30 23:00:00"], + ], + [ + ["datetime", ">=", "2016-09-30 23:00:00"], + ["datetime", "<", "2016-12-31 23:00:00"], + ], + [ + ["datetime", ">=", "2019-09-30 23:00:00"], + ["datetime", "<", "2019-12-31 23:00:00"], + ], + ] + ); + assert.deepEqual( + result.map((x) => x.__range["datetime:quarter"]), + [ + { from: "2016-03-31 23:00:00", to: "2016-06-30 23:00:00" }, + { from: "2016-09-30 23:00:00", to: "2016-12-31 23:00:00" }, + { from: "2019-09-30 23:00:00", to: "2019-12-31 23:00:00" }, + ] + ); result = await server.performRPC("", { model: "bar", @@ -587,6 +882,26 @@ QUnit.module("MockServer", (hooks) => { result.map((x) => x.datetime_count), [5, 1] ); + assert.deepEqual( + result.map((x) => x.__domain), + [ + [ + ["datetime", ">=", "2015-12-31 23:00:00"], + ["datetime", "<", "2016-12-31 23:00:00"], + ], + [ + ["datetime", ">=", "2018-12-31 23:00:00"], + ["datetime", "<", "2019-12-31 23:00:00"], + ], + ] + ); + assert.deepEqual( + result.map((x) => x.__range["datetime:year"]), + [ + { from: "2015-12-31 23:00:00", to: "2016-12-31 23:00:00" }, + { from: "2018-12-31 23:00:00", to: "2019-12-31 23:00:00" }, + ] + ); }); QUnit.test("performRPC: read_group, group by m2m", async function (assert) { diff --git a/addons/web/static/tests/views/kanban_view_tests.js b/addons/web/static/tests/views/kanban_view_tests.js index 8e75160c0ab..063924896ca 100644 --- a/addons/web/static/tests/views/kanban_view_tests.js +++ b/addons/web/static/tests/views/kanban_view_tests.js @@ -4333,7 +4333,7 @@ QUnit.module("Views", (hooks) => { if ("date" in args[1]) { assert.deepEqual(args[1], { date: "2017-02-28" }); } else if ("datetime" in args[1]) { - assert.deepEqual(args[1], { datetime: "2017-02-28 23:59:59" }); + assert.deepEqual(args[1], { datetime: "2017-02-28 22:59:59" }); } } }, @@ -6923,10 +6923,8 @@ QUnit.module("Views", (hooks) => { "", }); - patchWithCleanup(luxon.Settings, { defaultLocale: "en" }); - assert.strictEqual(getCard(0).querySelector(".date").innerText, "01/25/2017"); - assert.strictEqual(getCard(1).querySelector(".datetime").innerText, "12/12/2016 10:55:05"); + assert.strictEqual(getCard(1).querySelector(".datetime").innerText, "12/12/2016 11:55:05"); }); QUnit.test("rendering date and datetime (raw value)", async (assert) => { @@ -6950,8 +6948,6 @@ QUnit.module("Views", (hooks) => { "", }); - patchWithCleanup(luxon.Settings, { defaultLocale: "en" }); - assert.ok(getCard(0).querySelector(".date").innerText.startsWith("Wed Jan 25 2017")); assert.ok(getCard(1).querySelector(".datetime").innerText.startsWith("Mon Dec 12 2016")); });