[FIX] web: AbstractController: handle concurrent reloads

Let's assume the following scenario in a Kanban view with a default
filter. The user removes the filter, and quickly adds a new one.
On a slow network, it may happen that the second reload request
returns before the first one. On odoo.com, this is easy to
reproduce on the tasks Kanban view.

Before this rev., when this occured, the result of the first
request was displayed, whereas it should have been ignored as
another request was done later on.
This commit is contained in:
Aaron Bohy
2018-05-25 08:11:49 +02:00
parent f9ce42c763
commit 04ba755f82
2 changed files with 46 additions and 3 deletions
@@ -12,6 +12,7 @@ odoo.define('web.AbstractController', function (require) {
* reading localstorage, ...) has to go through the controller.
*/
var concurrency = require('web.concurrency');
var Widget = require('web.Widget');
@@ -41,6 +42,9 @@ var AbstractController = Widget.extend({
this.handle = params.handle;
this.activeActions = params.activeActions;
this.initialState = params.initialState;
// use a DropPrevious to correctly handle concurrent updates
this.dp = new concurrency.DropPrevious();
},
/**
* Simply renders and updates the url.
@@ -206,11 +210,11 @@ var AbstractController = Widget.extend({
var self = this;
var shouldReload = (options && 'reload' in options) ? options.reload : true;
var def = shouldReload ? this.model.reload(this.handle, params) : $.when();
return def.then(function (handle) {
return this.dp.add(def).then(function (handle) {
self.handle = handle || self.handle; // update handle if we reloaded
var state = self.model.get(self.handle);
var localState = self.renderer.getLocalState();
return self.renderer.updateState(state, params).then(function () {
return self.dp.add(self.renderer.updateState(state, params)).then(function () {
self.renderer.setLocalState(localState);
self._update(state);
});
+40 -1
View File
@@ -3310,7 +3310,6 @@ QUnit.module('Views', {
list.destroy();
});
QUnit.test('basic support for widgets', function (assert) {
assert.expect(1);
@@ -3416,6 +3415,46 @@ QUnit.module('Views', {
testUtils.unpatch(mixins.ParentedMixin);
});
QUnit.test('concurrent reloads finishing in inverse order', function (assert) {
assert.expect(3);
var blockSearchRead = false;
var def = $.Deferred();
var list = createView({
View: ListView,
model: 'foo',
data: this.data,
arch: '<tree><field name="foo"/></tree>',
mockRPC: function (route) {
var result = this._super.apply(this, arguments);
if (route === '/web/dataset/search_read' && blockSearchRead) {
return $.when(def).then(_.constant(result));
}
return result;
},
});
assert.strictEqual(list.$('.o_list_view .o_data_row').length, 4,
"list view should contain 4 records");
// reload with a domain (this request is blocked)
blockSearchRead = true;
list.reload({domain: [['foo', '=', 'yop']]});
assert.strictEqual(list.$('.o_list_view .o_data_row').length, 4,
"list view should still contain 4 records (search_read being blocked)");
// reload without the domain
blockSearchRead = false;
list.reload({domain: []});
// unblock the RPC
def.resolve();
assert.strictEqual(list.$('.o_list_view .o_data_row').length, 4,
"list view should still contain 4 records");
list.destroy();
});
});
});