[IMP] web: Do not store search panel categories

This commit handles two things:

1) Move the local storage logic of the search panel into the documents
module.

2) This allows for a variety of optimisations now that we do not have to
constantly wait for a potential default value hidden in the storage.
This is why this commit also introduces a way for the search panel model
extension to sometimes return its query informations sooner than after
being completely loaded.

Task 2284207
This commit is contained in:
Julien Mougenot
2020-08-04 12:46:27 +00:00
parent cc6a60bec9
commit 2524c7aaac
5 changed files with 357 additions and 385 deletions
@@ -693,7 +693,6 @@ ActionManager.include({
if (config.device.isMobile && actionData.mobile) {
options = Object.assign({}, options, actionData.mobile);
}
action.flags = _.extend({}, action.flags, {searchPanelDefaultNoFilter: true});
return self.doAction(action, options).then(ev.data.on_success, ev.data.on_fail);
});
},
@@ -260,17 +260,8 @@ var AbstractView = Factory.extend({
if (this.withSearchPanel) {
// Search panel (Model)
const SearchPanelComponent = this.config.SearchPanel;
const defaultValues = {};
Object.keys(this.loadParams.context).forEach((key) => {
let match = /^searchpanel_default_(.*)$/.exec(key);
if (match) {
defaultValues[match[1]] = this.loadParams.context[key];
}
});
extensions[SearchPanelComponent.modelExtension] = {
archNodes: searchPanelInfo.children,
defaultNoFilter: params.searchPanelDefaultNoFilter,
defaultValues,
};
this.controllerParams.withSearchPanel = true;
this.rendererParams.withSearchPanel = true;
@@ -13,6 +13,14 @@ odoo.define("web/static/src/js/views/search_panel_model_extension.js", function
const DEFAULT_LIMIT = 200;
let nextSectionId = 1;
/**
* @param {Filter} filter
* @returns {boolean}
*/
function hasDomain(filter) {
return filter.domain !== "[]";
}
/**
* @param {Section} section
* @returns {boolean}
@@ -68,14 +76,44 @@ odoo.define("web/static/src/js/views/search_panel_model_extension.js", function
constructor() {
super(...arguments);
this.searchDomain = null;
this.DEFAULT_VALUE_INDEX = 0;
this.categoriesToLoad = [];
this.defaultValues = {};
this.filtersToLoad = [];
this.initialStateImport = false;
this.searchDomain = [];
for (const key in this.config.context) {
const match = /^searchpanel_default_(.*)$/.exec(key);
if (match) {
this.defaultValues[match[1]] = this.config.context[key];
}
}
}
//---------------------------------------------------------------------
// Public
//---------------------------------------------------------------------
/**
* @override
*/
async callLoad(params) {
const searchDomain = this._getExternalDomain();
params.searchDomainChanged = (
JSON.stringify(this.searchDomain) !== JSON.stringify(searchDomain)
);
if (!this.shouldLoad && !this.initialStateImport) {
const isFetchable = (section) => section.enableCounters ||
(params.searchDomainChanged && !section.expand);
this.categoriesToLoad = this.categories.filter(isFetchable);
this.filtersToLoad = this.filters.filter(isFetchable);
this.shouldLoad = params.searchDomainChanged ||
Boolean(this.categoriesToLoad.length + this.filtersToLoad.length);
}
this.searchDomain = searchDomain;
this.initialStateImport = false;
await super.callLoad(params);
}
/**
* @override
*/
@@ -109,6 +147,7 @@ odoo.define("web/static/src/js/views/search_panel_model_extension.js", function
* @override
*/
importState(importedState) {
this.initialStateImport = Boolean(importedState && !this.state.sections);
super.importState(...arguments);
if (importedState) {
this.state.sections = new Map(this.state.sections);
@@ -127,8 +166,18 @@ odoo.define("web/static/src/js/views/search_panel_model_extension.js", function
/**
* @override
*/
async load() {
await this._fetchSections({ initialLoad: true });
async isReady() {
await this.sectionsPromise;
}
/**
* @override
*/
async load(params) {
this.sectionsPromise = this._fetchSections(params.isInitialLoad);
if (this._shouldWaitForData(params)) {
await this.sectionsPromise;
}
}
/**
@@ -139,13 +188,6 @@ odoo.define("web/static/src/js/views/search_panel_model_extension.js", function
this._createSectionsFromArch();
}
/**
* @override
*/
async reloadAfterDispatch() {
await this._fetchSections();
}
//---------------------------------------------------------------------
// Actions / Getters
//---------------------------------------------------------------------
@@ -180,18 +222,13 @@ odoo.define("web/static/src/js/views/search_panel_model_extension.js", function
}
/**
* Sets the active value id of a given category. This will also update
* the local storage value.
* Sets the active value id of a given category.
* @param {number} sectionId
* @param {number} valueId
*/
toggleCategoryValue(sectionId, valueId) {
const category = this.state.sections.get(sectionId);
category.activeValueId = valueId;
const storageKey = `searchpanel_${this.config.modelName}_${category.fieldName}`;
this.env.services.local_storage.setItem(storageKey, valueId);
// FIXME this should be removed (in another commit): should no longer (re)store
// active category in localStorage
}
/**
@@ -240,7 +277,7 @@ odoo.define("web/static/src/js/views/search_panel_model_extension.js", function
*/
_applyDefaultFilterValues() {
for (const { fieldName, values } of this.filters) {
const defaultValues = this.config.defaultValues[fieldName] || [];
const defaultValues = this.defaultValues[fieldName] || [];
for (const valueId of defaultValues) {
const value = values.get(valueId);
if (value) {
@@ -266,15 +303,6 @@ odoo.define("web/static/src/js/views/search_panel_model_extension.js", function
if (category.hierarchize) {
category.parentField = parentField;
}
const allItem = {
childrenIds: [],
display_name: this.env._t("All"),
id: false,
bold: true,
parentId: false,
};
category.values = new Map([[false, allItem]]);
for (const value of values) {
category.values.set(
value.id,
@@ -299,7 +327,8 @@ odoo.define("web/static/src/js/views/search_panel_model_extension.js", function
}
}
// Set active value from context
category.activeValueId = this._getCategoryDefaultValue(values, category);
const valueIds = [false, ...values.map((val) => val.id)];
this._ensureCategoryValue(category, valueIds);
}
/**
@@ -318,8 +347,8 @@ odoo.define("web/static/src/js/views/search_panel_model_extension.js", function
// restore checked property
values.forEach((value) => {
const oldValue = filter.values && filter.values.get(value.id);
value.checked = (oldValue && oldValue.checked) || false;
const oldValue = filter.values.get(value.id);
value.checked = oldValue ? oldValue.checked : false;
});
filter.values = new Map();
@@ -390,12 +419,21 @@ odoo.define("web/static/src/js/views/search_panel_model_extension.js", function
index,
limit: pyUtils.py_eval(attrs.limit || String(DEFAULT_LIMIT)),
type,
values: new Map(),
};
if (type === "category") {
section.activeValueId = this.defaultValues[attrs.name];
section.icon = section.icon || "fa-folder";
section.hierarchize = !!pyUtils.py_eval(
attrs.hierarchize || "1"
);
section.values.set(false, {
childrenIds: [],
display_name: this.env._t("All"),
id: false,
bold: true,
parentId: false,
});
} else {
section.domain = attrs.domain || "[]";
section.groupBy = attrs.groupby;
@@ -405,104 +443,97 @@ odoo.define("web/static/src/js/views/search_panel_model_extension.js", function
});
}
/**
* Ensures that the active value of a category is one of its own
* existing values.
* @private
* @param {Category} category
* @param {number[]} valueIds
*/
_ensureCategoryValue(category, valueIds) {
if (!valueIds.includes(category.activeValueId)) {
category.activeValueId = valueIds[0];
}
}
/**
* Fetches values for each category at startup. At reload a category is
* only fetched if needed.
* @private
* @param {{
* initialLoad: boolean,
* searchDomainHasChanged: boolean
* }} options
* @param {Category[]} categories
* @returns {Promise} resolved when all categories have been fetched
*/
async _fetchCategories({ initialLoad, searchDomainHasChanged }) {
const proms = [];
for (const category of this.categories) {
const { enableCounters, expand, hierarchize, limit } = category;
if (initialLoad || enableCounters || (searchDomainHasChanged && !expand)) {
const prom = this.env.services.rpc({
method: "search_panel_select_range",
model: this.config.modelName,
args: [category.fieldName],
kwargs: {
category_domain: this._getCategoryDomain(
category.id
),
enable_counters: enableCounters,
expand,
filter_domain: this._getFilterDomain(),
hierarchize,
limit,
search_domain: this.searchDomain,
},
}).then(result => this._createCategoryTree(category.id, result));
proms.push(prom);
}
}
await Promise.all(proms);
async _fetchCategories(categories) {
const filterDomain = this._getFilterDomain();
await Promise.all(categories.map(async (category) => {
const result = await this.env.services.rpc({
method: "search_panel_select_range",
model: this.config.modelName,
args: [category.fieldName],
kwargs: {
category_domain: this._getCategoryDomain(category.id),
enable_counters: category.enableCounters,
expand: category.expand,
filter_domain: filterDomain,
hierarchize: category.hierarchize,
limit: category.limit,
search_domain: this.searchDomain,
},
});
this._createCategoryTree(category.id, result);
}));
}
/**
* Fetches values for each filter. This is done at startup and at each
* reload if needed.
* @private
* @param {{
* initialLoad: boolean,
* searchDomainHasChanged: boolean
* }} options
* @param {Filter[]} filters
* @returns {Promise} resolved when all filters have been fetched
*/
async _fetchFilters({ initialLoad, searchDomainHasChanged }) {
async _fetchFilters(filters) {
const evalContext = {};
for (const category of this.categories) {
evalContext[category.fieldName] = category.activeValueId;
}
const categoryDomain = this._getCategoryDomain();
const proms = [];
for (const filter of this.filters) {
const { enableCounters, expand, groupBy, limit } = filter;
if (initialLoad || enableCounters || (searchDomainHasChanged && !expand)) {
const prom = this.env.services.rpc({
method: "search_panel_select_multi_range",
model: this.config.modelName,
args: [filter.fieldName],
kwargs: {
category_domain: categoryDomain,
comodel_domain: Domain.prototype.stringToArray(
filter.domain,
evalContext
),
enable_counters: enableCounters,
filter_domain: this._getFilterDomain(filter.id),
expand,
group_by: groupBy || false,
group_domain: this._getGroupDomain(filter),
limit,
search_domain: this.searchDomain,
},
}).then(result => this._createFilterTree(filter.id, result));
proms.push(prom);
}
}
await Promise.all(proms);
await Promise.all(filters.map(async (filter) => {
const result = await this.env.services.rpc({
method: "search_panel_select_multi_range",
model: this.config.modelName,
args: [filter.fieldName],
kwargs: {
category_domain: categoryDomain,
comodel_domain: Domain.prototype.stringToArray(
filter.domain,
evalContext
),
enable_counters: filter.enableCounters,
filter_domain: this._getFilterDomain(filter.id),
expand: filter.expand,
group_by: filter.groupBy || false,
group_domain: this._getGroupDomain(filter),
limit: filter.limit,
search_domain: this.searchDomain,
},
});
this._createFilterTree(filter.id, result);
}));
}
/**
* Fetches categories and filters at startup or if needed.
* @private
* @param {Object} [options={}]
* @param {boolean} [options.initialLoad=false]
* @param {boolean} isInitialLoad
* @returns {Promise}
*/
async _fetchSections(options = {}) {
const searchDomain = this._getExternalDomain();
if (!options.initialLoad) {
options.searchDomainHasChanged =
JSON.stringify(this.searchDomain) !== JSON.stringify(searchDomain);
}
this.searchDomain = searchDomain;
await this._fetchCategories(options);
await this._fetchFilters(options);
if (options.initialLoad) {
async _fetchSections(isInitialLoad) {
await this._fetchCategories(
isInitialLoad ? this.categories : this.categoriesToLoad
);
await this._fetchFilters(
isInitialLoad ? this.filters : this.filtersToLoad
);
if (isInitialLoad) {
this._applyDefaultFilterValues();
}
}
@@ -520,53 +551,22 @@ odoo.define("web/static/src/js/views/search_panel_model_extension.js", function
for (const category of this.categories) {
if (
category.id === excludedCategoryId ||
category.activeValueId === false
!category.activeValueId
) {
continue;
}
if (category.activeValueId) {
const field = this.config.fields[category.fieldName];
const operator =
field.type === "many2one" && category.parentField ? "child_of" : "=";
domain.push([
category.fieldName,
operator,
category.activeValueId,
]);
}
const field = this.config.fields[category.fieldName];
const operator =
field.type === "many2one" && category.parentField ? "child_of" : "=";
domain.push([
category.fieldName,
operator,
category.activeValueId,
]);
}
return domain;
}
/**
* @private
* @param {Object[]} values
* @param {Category} category
* @returns {number | false}
*/
_getCategoryDefaultValue(values, { fieldName }) {
const validValues = [false, ...values.map((val) => val.id)];
const defaultValue = this.config.defaultValues[fieldName];
if (validValues.includes(defaultValue)) {
return defaultValue;
}
if (this.config.defaultNoFilter) {
return false;
}
// If not set in context, or set to an unknown value, set active value
// from localStorage
const storageKey = `searchpanel_${this.config.modelName}_${fieldName}`;
const storageValue = this.env.services.local_storage.getItem(storageKey);
if (validValues.includes(storageValue)) {
return storageValue;
}
// If still not a valid value, get the search panel default value
// from the given valid values.
return validValues[
Math.min(validValues.length - 1, this.DEFAULT_VALUE_INDEX)
];
}
/**
* Returns the domain retrieved from the other model extensions.
* @private
@@ -617,7 +617,7 @@ odoo.define("web/static/src/js/views/search_panel_model_extension.js", function
for (const group of groups.values()) {
addCondition(fieldName, group.values);
}
} else if (values) {
} else {
addCondition(fieldName, values);
}
}
@@ -694,6 +694,33 @@ odoo.define("web/static/src/js/views/search_panel_model_extension.js", function
return groupDomain;
}
/**
* Returns whether the query informations should be considered as ready
* before or after having (re-)fetched the sections data.
* @private
* @param {Object} params
* @param {boolean} params.isInitialLoad
* @param {boolean} params.searchDomainChanged
* @returns {boolean}
*/
_shouldWaitForData({ isInitialLoad, searchDomainChanged }) {
if (isInitialLoad && Object.keys(this.defaultValues).length) {
// Default values need to be checked on initial load
return true;
}
if (this.categories.length && this.filters.some(hasDomain)) {
// Selected category value might affect the filter values
return true;
}
if (!this.searchDomain.length) {
// No search domain -> no need to check for expand
return false;
}
return [...this.state.sections.values()].some(
(section) => !section.expand && searchDomainChanged
);
}
//---------------------------------------------------------------------
// Static
//---------------------------------------------------------------------
@@ -3591,9 +3591,6 @@ QUnit.module('ActionManager', {
active_id: 1,
active_ids: [1],
},
flags: {
searchPanelDefaultNoFilter: true,
},
});
var checkSessionStorage = false;
var actionManager = await createActionManager({
@@ -1,10 +1,8 @@
odoo.define("web/static/tests/views/search_panel_tests.js", function (require) {
"use strict";
const AbstractStorageService = require('web.AbstractStorageService');
const FormView = require('web.FormView');
const KanbanView = require('web.KanbanView');
const RamStorage = require('web.RamStorage');
const testUtils = require('web.test_utils');
const SearchPanel = require("web/static/src/js/views/search_panel.js");
@@ -115,13 +113,6 @@ QUnit.module('Views', {
</searchpanel>
</search>`,
};
var RamStorageService = AbstractStorageService.extend({
storage: new RamStorage(),
});
this.services = {
local_storage: RamStorageService,
};
},
}, function () {
@@ -138,7 +129,6 @@ QUnit.module('Views', {
assert.step((args.method || route) + (args.model ? (' on ' + args.model) : ''));
return this._super.apply(this, arguments);
},
services: this.services,
arch: `
<kanban>
<templates>
@@ -198,7 +188,6 @@ QUnit.module('Views', {
View: KanbanView,
model: 'partner',
data: this.data,
services: this.services,
arch: `
<kanban>
<templates>
@@ -237,7 +226,6 @@ QUnit.module('Views', {
View: KanbanView,
model: 'partner',
data: this.data,
services: this.services,
arch: `<kanban>
<templates>
<t t-name="kanban-box">
@@ -279,7 +267,6 @@ QUnit.module('Views', {
View: KanbanView,
model: 'partner',
data: this.data,
services: this.services,
arch: `
<kanban>
<templates>
@@ -320,7 +307,6 @@ QUnit.module('Views', {
View: KanbanView,
model: 'partner',
data: this.data,
services: this.services,
arch: `
<kanban>
<templates>
@@ -368,7 +354,6 @@ QUnit.module('Views', {
}
return this._super.apply(this, arguments);
},
services: this.services,
arch: `
<kanban>
<templates>
@@ -434,7 +419,6 @@ QUnit.module('Views', {
}
return this._super.apply(this, arguments);
},
services: this.services,
arch: `
<kanban>
<templates>
@@ -486,132 +470,6 @@ QUnit.module('Views', {
kanban.destroy();
});
QUnit.test('store and retrieve active category value', async function (assert) {
assert.expect(9);
var Storage = RamStorage.extend({
getItem: function (key) {
assert.step(`getItem ${key}`);
const item = this._super(...arguments);
return item || 3; // default: 'asustek'
},
setItem: function (key, value) {
assert.step(`setItem ${key} to ${value}`);
this._super(...arguments);
},
});
var RamStorageService = AbstractStorageService.extend({
storage: new Storage(),
});
var expectedActiveId = 3;
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
services: {
local_storage: RamStorageService,
},
arch: `
<kanban>
<templates>
<t t-name="kanban-box">
<div>
<field name="foo"/>
</div>
</t>
</templates>
</kanban>`,
archs: {
'partner,false,search': `
<search>
<searchpanel>
<field name="company_id" enable_counters="1"/>
</searchpanel>
</search>`,
},
mockRPC: function (route, args) {
if (route === '/web/dataset/search_read') {
assert.deepEqual(args.domain, [['company_id', 'child_of', expectedActiveId]]);
}
return this._super.apply(this, arguments);
},
});
// 'asustek' should be selected by default
assert.containsOnce(kanban, '.o_search_panel_category_value .active');
assert.containsOnce(kanban, '.o_search_panel_category_value:nth(1) .active');
assert.containsN(kanban, '.o_kanban_record:not(.o_kanban_ghost)', 2);
// select 'agrolait'
expectedActiveId = 5;
await testUtils.dom.click(kanban.$('.o_search_panel_category_value:nth(2) header'));
assert.verifySteps([
'getItem searchpanel_partner_company_id', // get on start
'setItem searchpanel_partner_company_id to 5',
'getItem searchpanel_partner_company_id', // get on reload
]);
kanban.destroy();
});
QUnit.test('retrieved category value does not exist', async function (assert) {
assert.expect(6);
var Storage = RamStorage.extend({
getItem: function (key) {
assert.step('getItem ' + key);
return 343; // this value doesn't exist
},
});
var RamStorageService = AbstractStorageService.extend({
storage: new Storage(),
});
var kanban = await createView({
View: KanbanView,
model: 'partner',
data: this.data,
services: {
local_storage: RamStorageService,
},
arch: `
<kanban>
<templates>
<t t-name="kanban-box">
<div>
<field name="foo"/>
</div>
</t>
</templates>
</kanban>`,
archs: {
'partner,false,search': `
<search>
<searchpanel>
<field name="company_id" enable_counters="1"/>
</searchpanel>
</search>`,
},
mockRPC: function (route, args) {
if (route === '/web/dataset/search_read') {
assert.deepEqual(args.domain, []);
}
return this._super.apply(this, arguments);
},
});
// 'All' should be selected by default as the retrieved value does not exist
assert.containsOnce(kanban, '.o_search_panel_category_value .active');
assert.containsOnce(kanban, '.o_search_panel_category_value:first .active');
assert.containsN(kanban, '.o_kanban_record:not(.o_kanban_ghost)', 4);
assert.verifySteps(['getItem searchpanel_partner_company_id']);
kanban.destroy();
});
QUnit.test('category has been archived', async function (assert) {
assert.expect(2);
@@ -688,7 +546,6 @@ QUnit.module('Views', {
}
return this._super.apply(this, arguments);
},
services: this.services,
arch: `
<kanban>
<templates>
@@ -778,7 +635,6 @@ QUnit.module('Views', {
}
return this._super.apply(this, arguments);
},
services: this.services,
arch: `
<kanban>
<templates>
@@ -876,7 +732,6 @@ QUnit.module('Views', {
}
return this._super.apply(this, arguments);
},
services: this.services,
arch: `
<kanban>
<templates>
@@ -929,7 +784,6 @@ QUnit.module('Views', {
View: KanbanView,
model: 'partner',
data: this.data,
services: this.services,
arch: `
<kanban>
<templates>
@@ -982,7 +836,6 @@ QUnit.module('Views', {
View: KanbanView,
model: 'partner',
data: this.data,
services: this.services,
arch: `
<kanban>
<templates>
@@ -1034,7 +887,6 @@ QUnit.module('Views', {
}
return result;
},
services: this.services,
arch: `
<kanban>
<templates>
@@ -1104,6 +956,187 @@ QUnit.module('Views', {
kanban.destroy();
});
QUnit.test("concurrency: single category", async function (assert) {
assert.expect(12);
let prom = testUtils.makeTestPromise();
const kanbanPromise = createView({
arch: `
<kanban>
<templates>
<div t-name="kanban-box">
<field name="foo"/>
</div>
</templates>
</kanban>`,
archs: {
"partner,false,search": `
<search>
<filter name="Filter" domain="[('id', '=', 1)]"/>
<searchpanel>
<field name="company_id" enable_counters="1"/>
</searchpanel>
</search>`,
},
context: {
searchpanel_default_company_id: [5],
},
data: this.data,
async mockRPC(route, args) {
const _super = this._super.bind(this);
if (route !== "/web/dataset/search_read") {
await prom;
}
assert.step(args.method || route);
return _super(...arguments);
},
model: 'partner',
View: KanbanView,
});
// Case 1: search panel is awaited to build the query with search defaults
await testUtils.nextTick();
assert.verifySteps([]);
prom.resolve();
const kanban = await kanbanPromise;
assert.verifySteps([
"search_panel_select_range",
"/web/dataset/search_read",
]);
// Case 2: search domain changed so we wait for the search panel once again
prom = testUtils.makeTestPromise();
await testUtils.controlPanel.toggleFilterMenu(kanban);
await testUtils.controlPanel.toggleMenuItem(kanban, 0);
assert.verifySteps([]);
prom.resolve();
await testUtils.nextTick();
assert.verifySteps([
"search_panel_select_range",
"/web/dataset/search_read",
]);
// Case 3: search domain is the same and default values do not matter anymore
prom = testUtils.makeTestPromise();
await testUtils.dom.click(kanban.$('.o_search_panel_category_value:nth(1) header'));
// The search read is executed right away in this case
assert.verifySteps(["/web/dataset/search_read"]);
prom.resolve();
await testUtils.nextTick();
assert.verifySteps(["search_panel_select_range"]);
kanban.destroy();
});
QUnit.test("concurrency: category and filter", async function (assert) {
assert.expect(5);
let prom = testUtils.makeTestPromise();
const kanbanPromise = createView({
arch: `
<kanban>
<templates>
<div t-name="kanban-box">
<field name="foo"/>
</div>
</templates>
</kanban>`,
archs: {
"partner,false,search": `
<search>
<searchpanel>
<field name="category_id" enable_counters="1"/>
<field name="company_id" select="multi" enable_counters="1"/>
</searchpanel>
</search>`,
},
data: this.data,
async mockRPC(route, args) {
const _super = this._super.bind(this);
if (route !== "/web/dataset/search_read") {
await prom;
}
assert.step(args.method || route);
return _super(...arguments);
},
model: 'partner',
View: KanbanView,
});
await testUtils.nextTick();
assert.verifySteps(["/web/dataset/search_read"]);
prom.resolve();
const kanban = await kanbanPromise;
assert.verifySteps([
"search_panel_select_range",
"search_panel_select_multi_range",
]);
kanban.destroy();
});
QUnit.test("concurrency: category and filter with a domain", async function (assert) {
assert.expect(5);
let prom = testUtils.makeTestPromise();
const kanbanPromise = createView({
arch: `
<kanban>
<templates>
<div t-name="kanban-box">
<field name="foo"/>
</div>
</templates>
</kanban>`,
archs: {
"partner,false,search": `
<search>
<searchpanel>
<field name="category_id" enable_counters="1"/>
<field name="company_id" select="multi" domain="[['category_id', '=', category_id]]" enable_counters="1"/>
</searchpanel>
</search>`,
},
data: this.data,
async mockRPC(route, args) {
const _super = this._super.bind(this);
if (route !== "/web/dataset/search_read") {
await prom;
}
assert.step(args.method || route);
return _super(...arguments);
},
model: 'partner',
View: KanbanView,
});
await testUtils.nextTick();
assert.verifySteps([]);
prom.resolve();
const kanban = await kanbanPromise;
assert.verifySteps([
"search_panel_select_range",
"search_panel_select_multi_range",
"/web/dataset/search_read",
]);
kanban.destroy();
});
QUnit.test('concurrency: misordered get_filters', async function (assert) {
assert.expect(15);
@@ -1119,7 +1152,6 @@ QUnit.module('Views', {
}
return result;
},
services: this.services,
arch: `
<kanban>
<templates>
@@ -1154,7 +1186,7 @@ QUnit.module('Views', {
// 'All' should still be selected, and there should still be 4 records
assert.containsOnce(kanban, '.o_search_panel_category_value .active');
assert.containsOnce(kanban, '.o_search_panel_category_value:nth(1) .active');
assert.containsN(kanban, '.o_kanban_record:not(.o_kanban_ghost)', 4);
assert.containsN(kanban, '.o_kanban_record:not(.o_kanban_ghost)', 1);
// select 'ghi' (delay the reload)
def = testUtils.makeTestPromise();
@@ -1164,7 +1196,7 @@ QUnit.module('Views', {
// 'All' should still be selected, and there should still be 4 records
assert.containsOnce(kanban, '.o_search_panel_category_value .active');
assert.containsOnce(kanban, '.o_search_panel_category_value:nth(3) .active');
assert.containsN(kanban, '.o_kanban_record:not(.o_kanban_ghost)', 4);
assert.containsN(kanban, '.o_kanban_record:not(.o_kanban_ghost)', 2);
// unlock ghi search
ghiDef.resolve();
@@ -1198,7 +1230,6 @@ QUnit.module('Views', {
}
return result;
},
services: this.services,
arch: `
<kanban>
<templates>
@@ -1264,7 +1295,6 @@ QUnit.module('Views', {
}
return result;
},
services: this.services,
arch: `
<kanban>
<templates>
@@ -1371,7 +1401,6 @@ QUnit.module('Views', {
}
return result;
},
services: this.services,
arch: `
<kanban>
<templates>
@@ -1463,7 +1492,6 @@ QUnit.module('Views', {
assert.step(args.method || route);
return this._super.apply(this, arguments);
},
services: this.services,
arch: `
<kanban>
<templates>
@@ -1533,7 +1561,6 @@ QUnit.module('Views', {
assert.step(args.method || route);
return this._super.apply(this, arguments);
},
services: this.services,
arch: `
<kanban>
<templates>
@@ -1606,7 +1633,6 @@ QUnit.module('Views', {
}
return this._super.apply(this, arguments);
},
services: this.services,
arch: `
<kanban>
<templates>
@@ -1711,7 +1737,6 @@ QUnit.module('Views', {
}
return this._super.apply(this, arguments);
},
services: this.services,
arch: `
<kanban>
<templates>
@@ -1823,7 +1848,6 @@ QUnit.module('Views', {
}
return result;
},
services: this.services,
arch: `
<kanban>
<templates>
@@ -1952,7 +1976,6 @@ QUnit.module('Views', {
}
return result;
},
services: this.services,
arch: `
<kanban>
<templates>
@@ -2002,7 +2025,6 @@ QUnit.module('Views', {
}
return result;
},
services: this.services,
arch: `
<kanban>
<templates>
@@ -2073,7 +2095,6 @@ QUnit.module('Views', {
View: KanbanView,
model: 'partner',
data: this.data,
services: this.services,
arch: `
<kanban>
<templates>
@@ -2123,7 +2144,6 @@ QUnit.module('Views', {
View: KanbanView,
model: 'partner',
data: this.data,
services: this.services,
arch: `
<kanban>
<templates>
@@ -2165,7 +2185,6 @@ QUnit.module('Views', {
View: KanbanView,
model: 'partner',
data: this.data,
services: this.services,
arch: `
<kanban>
<templates>
@@ -2215,7 +2234,6 @@ QUnit.module('Views', {
View: KanbanView,
model: 'partner',
data: this.data,
services: this.services,
arch: `<kanban>
<templates><t t-name="kanban-box">
<div>
@@ -2261,7 +2279,6 @@ QUnit.module('Views', {
View: KanbanView,
model: 'partner',
data: this.data,
services: this.services,
arch: `
<kanban>
<templates>
@@ -2358,7 +2375,6 @@ QUnit.module('Views', {
actions: this.actions,
archs: this.archs,
data: this.data,
services: this.services,
mockRPC: function (route, args) {
if (route === '/web/dataset/search_read') {
assert.step(JSON.stringify(args.domain));
@@ -2407,7 +2423,6 @@ QUnit.module('Views', {
actions: this.actions,
archs: this.archs,
data: this.data,
services: this.services,
mockRPC: function (route, args) {
if (route === '/web/dataset/search_read') {
assert.step(JSON.stringify(args.domain));
@@ -2488,26 +2503,10 @@ QUnit.module('Views', {
QUnit.test('after onExecuteAction, selects "All" as default category value', async function (assert) {
assert.expect(4);
var Storage = RamStorage.extend({
getItem: function (key) {
assert.step('getItem ' + key);
return 3; // 'asustek'
},
setItem: function (key, value) {
assert.step('setItem ' + key + ' to ' + value);
},
});
var RamStorageService = AbstractStorageService.extend({
storage: new Storage(),
});
var actionManager = await createActionManager({
actions: this.actions,
archs: this.archs,
data: this.data,
services: {
local_storage: RamStorageService,
},
});
await actionManager.doAction(2);
@@ -2530,7 +2529,6 @@ QUnit.module('Views', {
actions: this.actions,
archs: this.archs,
data: this.data,
services: this.services,
});
await actionManager.doAction(2);
@@ -2549,7 +2547,6 @@ QUnit.module('Views', {
actions: this.actions,
archs: this.archs,
data: this.data,
services: this.services,
mockRPC: function (route, args) {
assert.step(args.method || route);
return this._super.apply(this, arguments);
@@ -2587,7 +2584,6 @@ QUnit.module('Views', {
actions: this.actions,
archs: this.archs,
data: this.data,
services: this.services,
});
actionManager.$el.css('max-height', 300);
@@ -2669,7 +2665,6 @@ QUnit.module('Views', {
assert.step(args.method || route);
return this._super.apply(this, arguments);
},
services: this.services,
arch: `
<kanban>
<templates>
@@ -2694,7 +2689,7 @@ QUnit.module('Views', {
assert.verifySteps([
'search_panel_select_range',
"search_panel_select_multi_range",
'/web/dataset/search_read'
'/web/dataset/search_read',
]);
assert.deepEqual(getCounters(kanban), [
@@ -2712,7 +2707,7 @@ QUnit.module('Views', {
assert.verifySteps([
'search_panel_select_range',
"search_panel_select_multi_range",
'/web/dataset/search_read'
'/web/dataset/search_read',
]);
kanban.destroy();
@@ -2745,7 +2740,6 @@ QUnit.module('Views', {
},
data: this.data,
model: 'partner',
services: this.services,
View: KanbanView,
});
@@ -2788,7 +2782,6 @@ QUnit.module('Views', {
},
data: this.data,
model: 'partner',
services: this.services,
View: KanbanView,
});
@@ -2831,7 +2824,6 @@ QUnit.module('Views', {
},
data: this.data,
model: 'partner',
services: this.services,
View: KanbanView,
});
@@ -2869,7 +2861,6 @@ QUnit.module('Views', {
},
data: this.data,
model: 'partner',
services: this.services,
View: KanbanView,
});
@@ -2907,7 +2898,6 @@ QUnit.module('Views', {
},
data: this.data,
model: 'partner',
services: this.services,
View: KanbanView,
});
@@ -2950,7 +2940,6 @@ QUnit.module('Views', {
},
data: this.data,
model: 'partner',
services: this.services,
View: KanbanView,
});
@@ -2993,7 +2982,6 @@ QUnit.module('Views', {
},
data: this.data,
model: 'partner',
services: this.services,
View: KanbanView,
});
@@ -3031,7 +3019,6 @@ QUnit.module('Views', {
},
data: this.data,
model: 'partner',
services: this.services,
View: KanbanView,
});
@@ -3067,7 +3054,6 @@ QUnit.module('Views', {
},
data: this.data,
model: 'partner',
services: this.services,
View: KanbanView,
});
@@ -3103,7 +3089,6 @@ QUnit.module('Views', {
},
data: this.data,
model: 'partner',
services: this.services,
View: KanbanView,
});
@@ -3139,7 +3124,6 @@ QUnit.module('Views', {
},
data: this.data,
model: 'partner',
services: this.services,
View: KanbanView,
});
@@ -3175,7 +3159,6 @@ QUnit.module('Views', {
},
data: this.data,
model: 'partner',
services: this.services,
View: KanbanView,
});
@@ -3211,7 +3194,6 @@ QUnit.module('Views', {
},
data: this.data,
model: 'partner',
services: this.services,
View: KanbanView,
});
@@ -3247,7 +3229,6 @@ QUnit.module('Views', {
},
data: this.data,
model: 'partner',
services: this.services,
View: KanbanView,
});
@@ -3283,7 +3264,6 @@ QUnit.module('Views', {
},
data: this.data,
model: 'partner',
services: this.services,
View: KanbanView,
});
@@ -3319,7 +3299,6 @@ QUnit.module('Views', {
},
data: this.data,
model: 'partner',
services: this.services,
View: KanbanView,
});
@@ -3355,7 +3334,6 @@ QUnit.module('Views', {
},
data: this.data,
model: 'partner',
services: this.services,
View: KanbanView,
});
@@ -3391,7 +3369,6 @@ QUnit.module('Views', {
},
data: this.data,
model: 'partner',
services: this.services,
View: KanbanView,
});
@@ -3427,7 +3404,6 @@ QUnit.module('Views', {
},
data: this.data,
model: 'partner',
services: this.services,
View: KanbanView,
});
@@ -3463,7 +3439,6 @@ QUnit.module('Views', {
},
data: this.data,
model: 'partner',
services: this.services,
View: KanbanView,
});
@@ -3499,7 +3474,6 @@ QUnit.module('Views', {
},
data: this.data,
model: 'partner',
services: this.services,
View: KanbanView,
});
@@ -3535,7 +3509,6 @@ QUnit.module('Views', {
},
data: this.data,
model: 'partner',
services: this.services,
View: KanbanView,
});
@@ -3571,7 +3544,6 @@ QUnit.module('Views', {
},
data: this.data,
model: 'partner',
services: this.services,
View: KanbanView,
});
@@ -3607,7 +3579,6 @@ QUnit.module('Views', {
},
data: this.data,
model: 'partner',
services: this.services,
View: KanbanView,
});
@@ -3643,7 +3614,6 @@ QUnit.module('Views', {
},
data: this.data,
model: 'partner',
services: this.services,
View: KanbanView,
});
@@ -3679,7 +3649,6 @@ QUnit.module('Views', {
},
data: this.data,
model: 'partner',
services: this.services,
View: KanbanView,
});
@@ -3715,7 +3684,6 @@ QUnit.module('Views', {
},
data: this.data,
model: 'partner',
services: this.services,
View: KanbanView,
});
@@ -3751,7 +3719,6 @@ QUnit.module('Views', {
},
data: this.data,
model: 'partner',
services: this.services,
View: KanbanView,
});
@@ -3787,7 +3754,6 @@ QUnit.module('Views', {
},
data: this.data,
model: 'partner',
services: this.services,
View: KanbanView,
});
@@ -3823,7 +3789,6 @@ QUnit.module('Views', {
},
data: this.data,
model: 'partner',
services: this.services,
View: KanbanView,
});
@@ -3859,7 +3824,6 @@ QUnit.module('Views', {
},
data: this.data,
model: 'partner',
services: this.services,
View: KanbanView,
});
@@ -3895,7 +3859,6 @@ QUnit.module('Views', {
},
data: this.data,
model: 'partner',
services: this.services,
View: KanbanView,
});
@@ -3936,7 +3899,6 @@ QUnit.module('Views', {
},
data: this.data,
model: 'partner',
services: this.services,
View: KanbanView,
});
@@ -3980,7 +3942,6 @@ QUnit.module('Views', {
},
data: this.data,
model: 'partner',
services: this.services,
View: KanbanView,
});
@@ -4018,7 +3979,6 @@ QUnit.module('Views', {
},
data: this.data,
model: 'partner',
services: this.services,
View: KanbanView,
});
@@ -4043,7 +4003,6 @@ QUnit.module('Views', {
assert.step(args.method || route);
return this._super.apply(this, arguments);
},
services: this.services,
arch: `
<kanban>
<templates>
@@ -4115,7 +4074,6 @@ QUnit.module('Views', {
assert.step(args.method || route);
return this._super.apply(this, arguments);
},
services: this.services,
arch: `
<kanban>
<templates>