[FIX] web: pager: race condition when paging and fetching count

Have a list or kanban view with 10k+ records (or manually change
`WEB_SEARCH_READ_COUNT_LIMIT` in the code). On a slow network,
click on the pager to go to the next page and click on `10OOO+` to
fetch the total number of records.

Before this commit, this might lead to a race condition where the
total number of records displayed in the pager is incorrect (if the
search_count returned before the web_search_read).

This commit fixes the issue at the pager level. We already disabled
it when clicking on next/previous or editing it. However, this
didn't prevent from fetching the count. Moreover, fetching the
count didn't disable the other features either. With this commit,
the pager is fully disabled when the user interacted with it, until
the view is reloaded.

closes odoo/odoo#110265

X-original-commit: 0ac84bda8de0c1a620d05469562932c0d04e8c3f
Signed-off-by: Géry Debongnie <ged@odoo.com>
Signed-off-by: Aaron Bohy (aab) <aab@odoo.com>
This commit is contained in:
Aaron Bohy
2023-01-18 17:38:29 +01:00
parent d51e62c6ae
commit 25d1209cf0
4 changed files with 48 additions and 2 deletions
@@ -113,6 +113,14 @@ export class Pager extends Component {
this.state.isEditing = false;
}
async updateTotal() {
if (!this.state.isDisabled) {
this.state.isDisabled = true;
await this.props.updateTotal();
this.state.isDisabled = false;
}
}
/**
* @param {MouseEvent} ev
*/
+1 -1
View File
@@ -12,7 +12,7 @@
</t>
<span> / </span>
<t t-if="props.updateTotal">
<span class="o_pager_limit o_pager_limit_fetch" t-on-click.stop="props.updateTotal"><t t-esc="props.total"/>+</span>
<span class="o_pager_limit o_pager_limit_fetch" t-att-class="{ 'disabled': state.isDisabled }" t-on-click.stop="updateTotal"><t t-esc="props.total"/>+</span>
</t>
<t t-else="">
<span class="o_pager_limit" t-esc="props.total"/>
@@ -208,7 +208,7 @@
padding-left: 5px;
text-align: center;
user-select: none;
.o_pager_limit_fetch, .o_pager_value {
.o_pager_limit_fetch:not(.disabled), .o_pager_value {
cursor: pointer;
}
}
@@ -5734,6 +5734,44 @@ QUnit.module("Views", (hooks) => {
assert.verifySteps([]);
});
QUnit.test("pager, ungrouped, next and fetch count simultaneously", async function (assert) {
patchWithCleanup(DynamicRecordList, { WEB_SEARCH_READ_COUNT_LIMIT: 5 });
serverData.models.foo.records.push({ id: 11, foo: "r11", bar: true });
serverData.models.foo.records.push({ id: 12, foo: "r12", bar: true });
serverData.models.foo.records.push({ id: 13, foo: "r13", bar: true });
let def;
await makeView({
type: "list",
resModel: "foo",
serverData,
arch: '<tree limit="2"><field name="foo"/><field name="bar"/></tree>',
async mockRPC(route, args) {
assert.step(args.method);
if (args.method === "web_search_read") {
await def;
}
},
});
assert.containsN(target, ".o_data_row", 2);
assert.strictEqual(target.querySelector(".o_pager_value").innerText, "1-2");
assert.strictEqual(target.querySelector(".o_pager_limit").innerText, "5+");
assert.verifySteps(["get_views", "web_search_read"]);
def = makeDeferred();
await click(target.querySelector(".o_pager_next")); // this request will be pending
assert.strictEqual(target.querySelector(".o_pager_value").innerText, "1-2");
assert.strictEqual(target.querySelector(".o_pager_limit").innerText, "5+");
// can't fetch count simultaneously as it is temporarily disabled while updating
assert.hasClass(target.querySelector(".o_pager_limit"), "disabled");
assert.verifySteps(["web_search_read"]);
def.resolve();
await nextTick();
assert.doesNotHaveClass(target.querySelector(".o_pager_limit"), "disabled");
});
QUnit.test("pager, grouped, with groups count limit reached", async function (assert) {
patchWithCleanup(DynamicRecordList, { WEB_SEARCH_READ_COUNT_LIMIT: 3 });
serverData.models.foo.records.push({ id: 398, foo: "ozfijz" }); // to have 4 groups