[IMP] web: enhance kanban progressbars filtering
When a progressbar filter is activated, it will only show records matching the filter and nothing else. "Load more" link will update its counter accordingly. closes odoo/odoo#55466 Taskid: 2196661 Signed-off-by: Aaron Bohy (aab) <aab@odoo.com>
This commit is contained in:
@@ -64,7 +64,8 @@ var KanbanColumn = Widget.extend({
|
||||
this.recordsDraggable = options.recordsDraggable;
|
||||
this.relation = options.relation;
|
||||
this.offset = 0;
|
||||
this.remaining = data.count - this.data_records.length;
|
||||
this.loadMoreCount = data.loadMoreCount;
|
||||
this.loadMoreOffset = data.loadMoreOffset;
|
||||
this.canBeFolded = this.folded;
|
||||
|
||||
if (options.hasProgressBar) {
|
||||
@@ -162,7 +163,7 @@ var KanbanColumn = Widget.extend({
|
||||
if (this.tooltipInfo) {
|
||||
this.$header.find('.o_kanban_header_title').tooltip({}).attr('data-original-title', this.tooltipInfo);
|
||||
}
|
||||
if (!this.remaining) {
|
||||
if (!this.loadMoreCount) {
|
||||
this.$('.o_kanban_load_more').remove();
|
||||
} else {
|
||||
this.$('.o_kanban_load_more').html(QWeb.render('KanbanView.LoadMore', {widget: this}));
|
||||
@@ -349,7 +350,7 @@ var KanbanColumn = Widget.extend({
|
||||
*/
|
||||
_onLoadMore: function (event) {
|
||||
event.preventDefault();
|
||||
this.trigger_up('kanban_load_more');
|
||||
this.trigger_up('kanban_load_column_records', { loadMoreOffset: this.loadMoreOffset });
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
|
||||
@@ -11,7 +11,6 @@ const _t = core._t;
|
||||
var KanbanColumnProgressBar = Widget.extend({
|
||||
template: 'KanbanView.ColumnProgressBar',
|
||||
events: {
|
||||
'click .o_kanban_counter_progress': '_onProgressBarParentClick',
|
||||
'click .progress-bar': '_onProgressBarClick',
|
||||
},
|
||||
/**
|
||||
@@ -28,6 +27,7 @@ var KanbanColumnProgressBar = Widget.extend({
|
||||
|
||||
this.columnID = options.columnID;
|
||||
this.columnState = columnState;
|
||||
this.activeFilter = {};
|
||||
|
||||
// <progressbar/> attributes
|
||||
this.fieldName = columnState.progressBarValues.field;
|
||||
@@ -90,16 +90,13 @@ var KanbanColumnProgressBar = Widget.extend({
|
||||
*/
|
||||
computeCounters() {
|
||||
const subgroupCounts = {};
|
||||
let allSubgroupCount = 0;
|
||||
for (const key of Object.keys(this.colors)) {
|
||||
const subgroupCount = this.columnState.progressBarValues.counts[key] || 0;
|
||||
if (this.activeFilter === key && subgroupCount === 0) {
|
||||
this.activeFilter = false;
|
||||
if (this.activeFilter.value === key && subgroupCount === 0) {
|
||||
this.activeFilter = {};
|
||||
}
|
||||
subgroupCounts[key] = subgroupCount;
|
||||
allSubgroupCount += subgroupCount;
|
||||
};
|
||||
subgroupCounts.__false = this.columnState.count - allSubgroupCount;
|
||||
}
|
||||
|
||||
this.groupCount = this.columnState.count;
|
||||
this.subgroupCounts = subgroupCounts;
|
||||
@@ -127,8 +124,8 @@ var KanbanColumnProgressBar = Widget.extend({
|
||||
_.each(self.colors, function (val, key) {
|
||||
$el.removeClass('o_kanban_group_show_' + val);
|
||||
});
|
||||
if (self.activeFilter) {
|
||||
$el.addClass('o_kanban_group_show o_kanban_group_show_' + self.colors[self.activeFilter]);
|
||||
if (self.activeFilter.value) {
|
||||
$el.addClass('o_kanban_group_show o_kanban_group_show_' + self.colors[self.activeFilter.value]);
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -171,7 +168,7 @@ var KanbanColumnProgressBar = Widget.extend({
|
||||
});
|
||||
|
||||
// Adapt active state
|
||||
$bar.toggleClass('progress-bar-animated progress-bar-striped', key === self.activeFilter);
|
||||
$bar.toggleClass('progress-bar-animated progress-bar-striped', key === self.activeFilter.value);
|
||||
|
||||
// Adapt width
|
||||
$bar.removeClass('o_bar_has_records transition-off');
|
||||
@@ -198,24 +195,24 @@ var KanbanColumnProgressBar = Widget.extend({
|
||||
var start = this.prevTotalCounterValue;
|
||||
var end = this.totalCounterValue;
|
||||
|
||||
if (this.activeFilter) {
|
||||
if (this.activeFilter.value) {
|
||||
if (this.sumField) {
|
||||
end = 0;
|
||||
_.each(self.columnState.data, function (record) {
|
||||
var recordData = record.data;
|
||||
if (self.activeFilter === recordData[self.fieldName] ||
|
||||
(self.activeFilter === '__false' && !recordData[self.fieldName])) {
|
||||
if (self.activeFilter.value === recordData[self.fieldName] ||
|
||||
(self.activeFilter.value === '__false' && !recordData[self.fieldName])) {
|
||||
end += parseFloat(recordData[self.sumField]);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
end = this.subgroupCounts[this.activeFilter];
|
||||
end = this.subgroupCounts[this.activeFilter.value];
|
||||
}
|
||||
}
|
||||
this.prevTotalCounterValue = end;
|
||||
var animationClass = start > 999 ? 'o_kanban_grow' : 'o_kanban_grow_huge';
|
||||
|
||||
if (start !== undefined && (end > start || this.activeFilter) && this.ANIMATE) {
|
||||
if (start !== undefined && (end > start || this.activeFilter.value) && this.ANIMATE) {
|
||||
$({currentValue: start}).animate({currentValue: end}, {
|
||||
duration: 1000,
|
||||
start: function () {
|
||||
@@ -255,33 +252,47 @@ var KanbanColumnProgressBar = Widget.extend({
|
||||
},
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Toggles the active filter on this progressbar.
|
||||
* It also computes the corresponding domain extension.
|
||||
*
|
||||
* @private
|
||||
* @param {string} value
|
||||
*/
|
||||
_toggleActiveFilter(value) {
|
||||
const activeFilter = Object.assign({}, this.activeFilter);
|
||||
if (activeFilter.value === value) {
|
||||
// If the filter was active and we click again on the same one, deactivate.
|
||||
activeFilter.domain = [];
|
||||
activeFilter.value = false;
|
||||
} else {
|
||||
const field = this.fieldName;
|
||||
if (value === '__false') {
|
||||
const values = Object.keys(this.colors).filter(el => el !== value);
|
||||
activeFilter.domain = ['!', [field, 'in', values]];
|
||||
} else {
|
||||
activeFilter.domain = [[field, '=', value]];
|
||||
}
|
||||
activeFilter.value = value;
|
||||
}
|
||||
this.activeFilter = activeFilter;
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Handlers
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {Event} ev
|
||||
*/
|
||||
_onProgressBarClick: function (ev) {
|
||||
this.$clickedBar = $(ev.currentTarget);
|
||||
var filter = this.$clickedBar.data('filter');
|
||||
this.activeFilter = (this.activeFilter === filter ? false : filter);
|
||||
const filterValue = this.$clickedBar.data('filter');
|
||||
this._toggleActiveFilter(filterValue);
|
||||
this._notifyState();
|
||||
this._render();
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {Event} ev
|
||||
*/
|
||||
_onProgressBarParentClick: function (ev) {
|
||||
if (ev.target !== ev.currentTarget) {
|
||||
return;
|
||||
}
|
||||
this.activeFilter = false;
|
||||
this._notifyState();
|
||||
this._render();
|
||||
this.trigger_up('kanban_load_column_records', {
|
||||
activeFilter: this.activeFilter
|
||||
});
|
||||
},
|
||||
});
|
||||
return KanbanColumnProgressBar;
|
||||
|
||||
@@ -31,7 +31,7 @@ var KanbanController = BasicController.extend({
|
||||
kanban_column_delete: '_onDeleteColumn',
|
||||
kanban_column_add_record: '_onAddRecordToColumn',
|
||||
kanban_column_resequence: '_onColumnResequence',
|
||||
kanban_load_more: '_onLoadMore',
|
||||
kanban_load_column_records: '_onLoadColumnRecords',
|
||||
column_toggle_fold: '_onToggleColumn',
|
||||
kanban_column_records_toggle_active: '_onToggleActiveRecords',
|
||||
}),
|
||||
@@ -389,14 +389,15 @@ var KanbanController = BasicController.extend({
|
||||
/**
|
||||
* @private
|
||||
* @param {OdooEvent} ev
|
||||
* @param {Object} ev.data see model.reload options
|
||||
*/
|
||||
_onLoadMore: function (ev) {
|
||||
var self = this;
|
||||
var column = ev.target;
|
||||
this.model.loadMore(column.db_id).then(function (db_id) {
|
||||
var data = self.model.get(db_id);
|
||||
self.renderer.updateColumn(db_id, data);
|
||||
});
|
||||
async _onLoadColumnRecords(ev) {
|
||||
const column = ev.target;
|
||||
const id = column.columnID || column.db_id;
|
||||
const options = ev.data;
|
||||
const dbID = await this.model.reload(id, options);
|
||||
const data = this.model.get(dbID);
|
||||
return this.renderer.updateColumn(dbID, data);
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
@@ -520,9 +521,11 @@ var KanbanController = BasicController.extend({
|
||||
this.model.actionArchive(recordIds, column.db_id) :
|
||||
this.model.actionUnarchive(recordIds, column.db_id);
|
||||
prom.then(function (dbID) {
|
||||
var data = self.model.get(dbID);
|
||||
let data = self.model.get(dbID);
|
||||
if (data) { // Could be null if a wizard is returned for example
|
||||
self.model.reload(self.handle).then(function () {
|
||||
// Retrieve fresher data as the reload may have changed it.
|
||||
data = self.model.get(dbID);
|
||||
const state = self.model.get(self.handle);
|
||||
self.renderer.updateColumn(dbID, data, { state });
|
||||
});
|
||||
|
||||
@@ -143,8 +143,10 @@ var KanbanModel = BasicModel.extend({
|
||||
* Add the following (kanban specific) keys when performing a `get`:
|
||||
*
|
||||
* - tooltipData
|
||||
* - progressBarValues
|
||||
* - isGroupedByM2ONoColumn
|
||||
* - progressBarValues
|
||||
* - loadMoreCount
|
||||
* - loadMoreOffset
|
||||
*
|
||||
* @override
|
||||
* @see _readTooltipFields
|
||||
@@ -154,18 +156,47 @@ var KanbanModel = BasicModel.extend({
|
||||
var result = this._super.apply(this, arguments);
|
||||
var dp = result && this.localData[result.id];
|
||||
if (dp) {
|
||||
// Add tooltipData key
|
||||
if (dp.tooltipData) {
|
||||
result.tooltipData = $.extend(true, {}, dp.tooltipData);
|
||||
}
|
||||
if (dp.progressBarValues) {
|
||||
result.progressBarValues = $.extend(true, {}, dp.progressBarValues);
|
||||
}
|
||||
// Add isGroupedByM2ONoColumn key
|
||||
if (dp.fields[dp.groupedBy[0]]) {
|
||||
var groupedByM2O = dp.fields[dp.groupedBy[0]].type === 'many2one';
|
||||
result.isGroupedByM2ONoColumn = !dp.data.length && groupedByM2O;
|
||||
} else {
|
||||
result.isGroupedByM2ONoColumn = false;
|
||||
}
|
||||
// Add progressBarValues, loadMoreCount and loadMoreOffset key
|
||||
let loadMoreCount = result.count - result.data.length;
|
||||
let loadMoreOffset = result.data.length;
|
||||
if (dp.progressBarValues) {
|
||||
result.progressBarValues = $.extend(true, {}, dp.progressBarValues);
|
||||
if (dp.activeFilter && dp.activeFilter.value) {
|
||||
// A progressbar filter is in an activated state?
|
||||
// Then set loadMoreCount and loadMoreOffset accordingly.
|
||||
// These computations are needed because there might be
|
||||
// records that doesn't match the current filter, e.g.:
|
||||
// - newly created records
|
||||
// - records dragged in the column
|
||||
const barValues = dp.progressBarValues;
|
||||
const barField = barValues.field;
|
||||
const definedBarFieldValues = Object.keys(barValues.colors);
|
||||
const filterValue = dp.activeFilter.value;
|
||||
|
||||
const filteredRecordsTotal = barValues.counts[filterValue];
|
||||
const filteredRecords = result.data.filter(element => {
|
||||
const elementBarFieldValue = element.data[barField];
|
||||
return (filterValue === elementBarFieldValue)
|
||||
|| (filterValue === '__false'
|
||||
&& !definedBarFieldValues.includes(elementBarFieldValue));
|
||||
});
|
||||
loadMoreCount = filteredRecordsTotal - filteredRecords.length;
|
||||
loadMoreOffset = filteredRecords.length;
|
||||
}
|
||||
}
|
||||
result.loadMoreCount = loadMoreCount;
|
||||
result.loadMoreOffset = loadMoreOffset;
|
||||
}
|
||||
return result;
|
||||
},
|
||||
@@ -190,19 +221,6 @@ var KanbanModel = BasicModel.extend({
|
||||
params.groupedBy = (params.groupedBy && params.groupedBy.length) ? params.groupedBy : this.defaultGroupedBy;
|
||||
return this._super(params);
|
||||
},
|
||||
/**
|
||||
* Load more records in a group.
|
||||
*
|
||||
* @param {string} groupID localID of the group
|
||||
* @returns {Promise<string>} resolves to the localID of the group
|
||||
*/
|
||||
loadMore: function (groupID) {
|
||||
var group = this.localData[groupID];
|
||||
var offset = group.loadMoreOffset + group.limit;
|
||||
return this.reload(group.id, {
|
||||
loadMoreOffset: offset,
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Moves a record from a group to another.
|
||||
*
|
||||
@@ -262,8 +280,24 @@ var KanbanModel = BasicModel.extend({
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
* @param {Object} options.activeFilter
|
||||
*/
|
||||
reload: function (id, options) {
|
||||
const element = this.localData[id];
|
||||
// Reset the limit right before reloading a group as a reload
|
||||
// of a kanban view may update the limit of a group. @see BasicModel._readGroup()
|
||||
// (e.g. if we are coming back to the kanban view from a form view)
|
||||
// As this mechanism may only affect grouped kanban views, only reset
|
||||
// the limit if the current element is a group (and thus, has a parent).
|
||||
if (element.parentID) {
|
||||
element.limit = this.loadParams.limit;
|
||||
}
|
||||
// Register the domain extension in the element.
|
||||
if (options && options.activeFilter) {
|
||||
element.activeFilter = options.activeFilter || element.activeFilter;
|
||||
// Cleanup as this option is not used further
|
||||
delete options.activeFilter;
|
||||
}
|
||||
// if the groupBy is given in the options and if it is an empty array,
|
||||
// fallback on the default groupBy
|
||||
if (options && options.groupBy && !options.groupBy.length) {
|
||||
@@ -293,6 +327,23 @@ var KanbanModel = BasicModel.extend({
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
_getGroupedListPropsToKeep(list, options) {
|
||||
const propsToKeep = this._super(...arguments);
|
||||
if (list.activeFilter) {
|
||||
propsToKeep.activeFilter = list.activeFilter;
|
||||
}
|
||||
return propsToKeep;
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
_getUngroupedListDomain(list) {
|
||||
const domainExtension = (list.activeFilter && list.activeFilter.domain) || [];
|
||||
return [...this._super(...arguments), ...domainExtension];
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
@@ -334,10 +385,36 @@ var KanbanModel = BasicModel.extend({
|
||||
* @param {Object} dataPoint
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
_readProgressBarGroup: function (list, options) {
|
||||
var self = this;
|
||||
var groupsDef = this._readGroup(list, options);
|
||||
var progressBarDef = this._rpc({
|
||||
async _readProgressBarGroup(list, options) {
|
||||
const groupsDef = this._readGroup(list, options);
|
||||
const progressBarDef = this._readProgressBar(list);
|
||||
const [groups, progressBar] = await Promise.all([groupsDef, progressBarDef]);
|
||||
list.data.forEach(groupId => {
|
||||
const group = this.localData[groupId];
|
||||
|
||||
const valuesCount = progressBar[group.value] || {};
|
||||
const valuesCountTotal = Object.keys(valuesCount).reduce((sum, key) => {
|
||||
return sum + valuesCount[key];
|
||||
}, 0);
|
||||
|
||||
// Compute records count for progressbar field values
|
||||
// not specified in the progressbar attributes
|
||||
const counts = Object.assign({
|
||||
__false: group.count - valuesCountTotal
|
||||
}, valuesCount);
|
||||
|
||||
group.progressBarValues = Object.assign({
|
||||
counts,
|
||||
}, list.progressBar);
|
||||
});
|
||||
return list;
|
||||
},
|
||||
/**
|
||||
* @param {Object} list valid resource object
|
||||
* @returns {Promise}
|
||||
*/
|
||||
_readProgressBar: function (list) {
|
||||
return this._rpc({
|
||||
model: list.model,
|
||||
method: 'read_progress_bar',
|
||||
kwargs: {
|
||||
@@ -347,16 +424,6 @@ var KanbanModel = BasicModel.extend({
|
||||
context: list.context,
|
||||
},
|
||||
});
|
||||
return Promise.all([groupsDef, progressBarDef]).then(function (results) {
|
||||
var data = results[1];
|
||||
_.each(list.data, function (groupID) {
|
||||
var group = self.localData[groupID];
|
||||
group.progressBarValues = _.extend({
|
||||
counts: data[group.value] || {},
|
||||
}, list.progressBar);
|
||||
});
|
||||
return list;
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Fetches tooltip specific fields on the group by relation and stores it in
|
||||
@@ -407,34 +474,37 @@ var KanbanModel = BasicModel.extend({
|
||||
},
|
||||
/**
|
||||
* Reloads all progressbar data. This is done after given promise and
|
||||
* insures that the given promise's result is not lost.
|
||||
* ensures that the given promise's result is not lost.
|
||||
*
|
||||
* @private
|
||||
* @param {string} recordID
|
||||
* @param {Promise} def
|
||||
* @returns {Promise}
|
||||
*/
|
||||
_reloadProgressBarGroupFromRecord: function (recordID, def) {
|
||||
var element = this.localData[recordID];
|
||||
if (element.type === 'list' && !element.parentID) {
|
||||
// we are reloading the whole view, so there is no need to manually
|
||||
// reload the progressbars
|
||||
return def;
|
||||
}
|
||||
|
||||
async _reloadProgressBarGroupFromRecord(recordID, def) {
|
||||
let element = this.localData[recordID];
|
||||
if (element.type === 'list') {
|
||||
if (!element.parentID) {
|
||||
// we are reloading the whole view, so there is no need to manually
|
||||
// reload the progressbars
|
||||
return def;
|
||||
}
|
||||
if (element.activeFilter) {
|
||||
// We must not read_group when an active filter (and thus
|
||||
// a domain extension) is applied to the list datapoint.
|
||||
return def;
|
||||
}
|
||||
}
|
||||
// If we updated a record, then we must potentially update columns'
|
||||
// progressbars, so we need to load groups info again
|
||||
var self = this;
|
||||
while (element) {
|
||||
if (element.progressBar) {
|
||||
return def.then(function (data) {
|
||||
return self._load(element, {
|
||||
keepEmptyGroups: true,
|
||||
onlyGroups: true,
|
||||
}).then(function () {
|
||||
return data;
|
||||
});
|
||||
const data = await def;
|
||||
await this._load(element, {
|
||||
keepEmptyGroups: true,
|
||||
onlyGroups: true,
|
||||
});
|
||||
return data;
|
||||
}
|
||||
element = this.localData[element.parentID];
|
||||
}
|
||||
|
||||
@@ -32,14 +32,14 @@
|
||||
<span t-if="widget.quick_create" class="o_kanban_quick_add"><i class="fa fa-plus" role="img" aria-label="Quick add" title="Quick add"/></span>
|
||||
</div>
|
||||
</div>
|
||||
<div t-if="widget.remaining > 0" class="o_kanban_load_more">
|
||||
<div t-if="widget.loadMoreCount > 0" class="o_kanban_load_more">
|
||||
<t t-call="KanbanView.LoadMore"/>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-name="KanbanView.LoadMore">
|
||||
<a href="#">Load more... (<t t-esc="widget.remaining"/> remaining)</a>
|
||||
<a href="#">Load more... (<t t-esc="widget.loadMoreCount"/> remaining)</a>
|
||||
</t>
|
||||
|
||||
<t t-name="KanbanView.ColumnQuickCreate">
|
||||
|
||||
@@ -5937,6 +5937,50 @@ QUnit.module('Views', {
|
||||
kanban.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('column progressbars with an active filter are working with load more', async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
this.data.partner.records.push(
|
||||
{ id: 5, bar: true, foo: "blork" },
|
||||
{ id: 6, bar: true, foo: "blork" },
|
||||
{ id: 7, bar: true, foo: "blork" }
|
||||
);
|
||||
|
||||
const kanban = await createView({
|
||||
View: KanbanView,
|
||||
model: 'partner',
|
||||
data: this.data,
|
||||
domain: [['bar', '=', true]],
|
||||
arch:
|
||||
`<kanban limit="1">
|
||||
<progressbar field="foo" colors='{"blork": "success"}'/>
|
||||
<field name="foo"/>
|
||||
<templates><t t-name="kanban-box">
|
||||
<div><field name="id"/></div>
|
||||
</t></templates>
|
||||
</kanban>`,
|
||||
groupBy: ['bar'],
|
||||
});
|
||||
|
||||
await testUtils.dom.click(kanban.el.querySelector('.o_kanban_counter_progress .progress-bar[data-filter="blork"]'));
|
||||
|
||||
// we should have 1 record shown
|
||||
assert.deepEqual(
|
||||
[...kanban.el.querySelectorAll('.o_kanban_record')].map(el => parseInt(el.innerText)),
|
||||
[5]
|
||||
);
|
||||
|
||||
// load 2 more and check it worked
|
||||
await testUtils.dom.click(kanban.el.querySelector('.o_kanban_group .o_kanban_load_more'));
|
||||
await testUtils.dom.click(kanban.el.querySelector('.o_kanban_group .o_kanban_load_more'));
|
||||
assert.deepEqual(
|
||||
[...kanban.el.querySelectorAll('.o_kanban_record')].map(el => parseInt(el.innerText)),
|
||||
[5, 6, 7]
|
||||
);
|
||||
|
||||
kanban.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('column progressbars on archiving records update counter', async function (assert) {
|
||||
assert.expect(4);
|
||||
|
||||
@@ -6088,6 +6132,54 @@ QUnit.module('Views', {
|
||||
kanban.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('RPCs when (de)activating kanban view progressbar filters', async function (assert) {
|
||||
assert.expect(8);
|
||||
|
||||
const kanban = await createView({
|
||||
View: KanbanView,
|
||||
model: 'partner',
|
||||
data: this.data,
|
||||
arch: `
|
||||
<kanban>
|
||||
<field name="bar"/>
|
||||
<field name="int_field"/>
|
||||
<progressbar field="foo" colors='{"yop": "success", "gnap": "warning", "blip": "danger"}' sum_field="int_field"/>
|
||||
<templates><t t-name="kanban-box">
|
||||
<div><field name="name"/></div>
|
||||
</t></templates>
|
||||
</kanban>
|
||||
`,
|
||||
groupBy: ['bar'],
|
||||
mockRPC(route, args) {
|
||||
assert.step(args.method || route);
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
});
|
||||
|
||||
// Activate "yop" on second column
|
||||
await testUtils.dom.click(kanban.el.querySelector('.o_kanban_group:nth-child(2) .progress-bar[data-filter="yop"]'));
|
||||
// Activate "gnap" on second column
|
||||
await testUtils.dom.click(kanban.el.querySelector('.o_kanban_group:nth-child(2) .progress-bar[data-filter="gnap"]'));
|
||||
// Deactivate "gnap" on second column
|
||||
await testUtils.dom.click(kanban.el.querySelector('.o_kanban_group:nth-child(2) .progress-bar[data-filter="gnap"]'));
|
||||
|
||||
assert.verifySteps([
|
||||
// initial load
|
||||
'web_read_group',
|
||||
'read_progress_bar',
|
||||
'/web/dataset/search_read',
|
||||
'/web/dataset/search_read',
|
||||
// activate filter
|
||||
'/web/dataset/search_read',
|
||||
// activate another filter (switching)
|
||||
'/web/dataset/search_read',
|
||||
// deactivate active filter
|
||||
'/web/dataset/search_read',
|
||||
]);
|
||||
|
||||
kanban.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('drag & drop records grouped by m2o with progressbar', async function (assert) {
|
||||
assert.expect(4);
|
||||
|
||||
@@ -6139,34 +6231,152 @@ QUnit.module('Views', {
|
||||
QUnit.test('progress bar subgroup count recompute', async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
var kanban = await createView({
|
||||
const kanban = await createView({
|
||||
View: KanbanView,
|
||||
model: 'partner',
|
||||
data: this.data,
|
||||
arch:
|
||||
'<kanban>' +
|
||||
'<progressbar field="foo" colors=\'{"yop": "success", "gnap": "warning", "blip": "danger"}\'/>' +
|
||||
'<templates><t t-name="kanban-box">' +
|
||||
'<div>' +
|
||||
'<field name="foo"/>' +
|
||||
'</div>' +
|
||||
'</t></templates>' +
|
||||
'</kanban>',
|
||||
`<kanban>
|
||||
<progressbar field="foo" colors='{"yop": "success", "gnap": "warning", "blip": "danger"}'/>
|
||||
<templates><t t-name="kanban-box">
|
||||
<div>
|
||||
<field name="foo"/>
|
||||
</div>
|
||||
</t></templates>
|
||||
</kanban>`,
|
||||
groupBy: ['bar'],
|
||||
});
|
||||
|
||||
var $secondGroup = kanban.$('.o_kanban_group:eq(1)');
|
||||
var initialCount = parseInt($secondGroup.find('.o_kanban_counter_side').text());
|
||||
assert.strictEqual(initialCount, 3,
|
||||
let secondCounter = kanban.el.querySelector('.o_kanban_group:nth-child(2) .o_kanban_counter_side');
|
||||
assert.strictEqual(parseInt(secondCounter.innerText), 3,
|
||||
"Initial count should be Three");
|
||||
await testUtils.dom.click($secondGroup.find('.bg-success-full'));
|
||||
var lastCount = parseInt($secondGroup.find('.o_kanban_counter_side').text());
|
||||
assert.strictEqual(lastCount, 1,
|
||||
await testUtils.dom.click(kanban.el.querySelector('.o_kanban_group:nth-child(2) .bg-success-full'));
|
||||
|
||||
secondCounter = kanban.el.querySelector('.o_kanban_group:nth-child(2) .o_kanban_counter_side');
|
||||
assert.strictEqual(parseInt(secondCounter.innerText), 1,
|
||||
"kanban counters should vary according to what subgroup is selected");
|
||||
|
||||
kanban.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('progress bar recompute after drag&drop to and from other column', async function (assert) {
|
||||
assert.expect(4);
|
||||
|
||||
const view = await createView({
|
||||
View: KanbanView,
|
||||
model: 'partner',
|
||||
data: this.data,
|
||||
arch:
|
||||
`<kanban>
|
||||
<progressbar field="foo" colors='{"yop": "success", "gnap": "warning", "blip": "danger"}'/>
|
||||
<templates><t t-name="kanban-box">
|
||||
<div>
|
||||
<field name="foo"/>
|
||||
</div>
|
||||
</t></templates>
|
||||
</kanban>`,
|
||||
groupBy: ['bar'],
|
||||
});
|
||||
|
||||
assert.deepEqual(
|
||||
[...view.el.querySelectorAll('.progress-bar')].map(el => el.getAttribute('data-original-title')),
|
||||
['0 yop', '0 gnap', '1 blip', '0 __false', '1 yop', '1 gnap', '1 blip', '0 __false']
|
||||
);
|
||||
|
||||
assert.deepEqual(
|
||||
[...view.el.querySelectorAll('.o_kanban_counter_side')].map(el => parseInt(el.innerText)),
|
||||
[1, 3]
|
||||
);
|
||||
|
||||
// Drag the last kanban record to the first column
|
||||
await testUtils.dom.dragAndDrop(
|
||||
[...view.el.querySelectorAll('.o_kanban_record')].pop(),
|
||||
[...view.el.querySelectorAll('.o_kanban_group')].shift()
|
||||
);
|
||||
|
||||
assert.deepEqual(
|
||||
[...view.el.querySelectorAll('.progress-bar')].map(el => el.getAttribute('data-original-title')),
|
||||
['0 yop', '1 gnap', '1 blip', '0 __false', '1 yop', '0 gnap', '1 blip', '0 __false']
|
||||
);
|
||||
|
||||
assert.deepEqual(
|
||||
[...view.el.querySelectorAll('.o_kanban_counter_side')].map(el => parseInt(el.innerText)),
|
||||
[2, 2]
|
||||
);
|
||||
|
||||
view.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('load more should load correct records after drag&drop event', async function (assert) {
|
||||
assert.expect(3);
|
||||
|
||||
// Add a sequence number and initialize
|
||||
this.data.partner.fields = Object.assign(this.data.partner.fields, {
|
||||
sequence: { type: 'integer' }
|
||||
});
|
||||
this.data.partner.records.forEach((el, i) => {
|
||||
el.sequence = i;
|
||||
});
|
||||
|
||||
const view = await createView({
|
||||
View: KanbanView,
|
||||
model: 'partner',
|
||||
data: this.data,
|
||||
arch:
|
||||
`<kanban limit="1">
|
||||
<field name="id"/>
|
||||
<field name="foo"/>
|
||||
<field name="sequence"/>
|
||||
<templates><t t-name="kanban-box">
|
||||
<div>
|
||||
<field name="id"/>
|
||||
</div>
|
||||
</t></templates>
|
||||
</kanban>`,
|
||||
groupBy: ['bar'],
|
||||
favoriteFilters: [
|
||||
{
|
||||
domain: '[]',
|
||||
is_default: true,
|
||||
sort: '["sequence asc"]',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
assert.deepEqual(
|
||||
[...view.el.querySelectorAll('.o_kanban_group:nth-child(1) .o_kanban_record span')]
|
||||
.map(el => parseInt(el.innerText))[0],
|
||||
4,
|
||||
"first column's first record must be id 4"
|
||||
);
|
||||
|
||||
assert.deepEqual(
|
||||
[...view.el.querySelectorAll('.o_kanban_group:nth-child(2) .o_kanban_record span')]
|
||||
.map(el => parseInt(el.innerText)),
|
||||
[1],
|
||||
"second column's records should be only the id 1"
|
||||
);
|
||||
|
||||
// Drag the first kanban record on top of the last
|
||||
await testUtils.dom.dragAndDrop(
|
||||
[...view.el.querySelectorAll('.o_kanban_record')].shift(),
|
||||
[...view.el.querySelectorAll('.o_kanban_record')].pop(),
|
||||
{ position: 'top' }
|
||||
);
|
||||
|
||||
// load more twice to load all records of second column
|
||||
await testUtils.dom.click(view.el.querySelector('.o_kanban_group:nth-child(2) .o_kanban_load_more'));
|
||||
await testUtils.dom.click(view.el.querySelector('.o_kanban_group:nth-child(2) .o_kanban_load_more'));
|
||||
|
||||
// Check records of the second column
|
||||
assert.deepEqual(
|
||||
[...view.el.querySelectorAll('.o_kanban_group:nth-child(2) .o_kanban_record span')].map(el => parseInt(el.innerText)),
|
||||
[4, 1, 2, 3]
|
||||
);
|
||||
|
||||
view.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('column progressbars on quick create with quick_create_view are updated', async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
@@ -6206,6 +6416,61 @@ QUnit.module('Views', {
|
||||
kanban.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('column progressbars and active filter on quick create with quick_create_view are updated', async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
const kanban = await createView({
|
||||
View: KanbanView,
|
||||
model: 'partner',
|
||||
data: this.data,
|
||||
arch: `
|
||||
<kanban on_create="quick_create" quick_create_view="some_view_ref">
|
||||
<field name="int_field"/>
|
||||
<field name="foo"/>
|
||||
<progressbar field="foo" colors='{"yop": "success", "gnap": "warning", "blip": "danger"}' sum_field="int_field"/>
|
||||
<templates><t t-name="kanban-box">
|
||||
<div><field name="name"/></div>
|
||||
</t></templates>
|
||||
</kanban>
|
||||
`,
|
||||
archs: {
|
||||
'partner,some_view_ref,form': `
|
||||
<form>
|
||||
<field name="int_field"/>
|
||||
<field name="foo"/>
|
||||
</form>
|
||||
`,
|
||||
},
|
||||
groupBy: ['bar'],
|
||||
});
|
||||
|
||||
await testUtils.dom.click(kanban.el.querySelector('.o_kanban_group:nth-child(1) .progress-bar[data-filter="blip"]'));
|
||||
const initialCount = parseInt(kanban.el.querySelector('.o_kanban_group:nth-child(1) .o_kanban_counter_side').innerText);
|
||||
assert.strictEqual(initialCount, -4, "Initial count should be -4");
|
||||
|
||||
// open the quick create
|
||||
await testUtils.kanban.clickCreate(kanban);
|
||||
|
||||
// fill it with a record that satisfies the activated filter
|
||||
let quickCreate = kanban.el.querySelector('.o_kanban_group:nth-child(1) .o_kanban_quick_create');
|
||||
await testUtils.fields.editInput(quickCreate.querySelector('.o_field_widget[name="int_field"]'), '44');
|
||||
await testUtils.fields.editInput(quickCreate.querySelector('.o_field_widget[name="foo"]'), 'blip');
|
||||
await testUtils.dom.click(quickCreate.querySelector('button.o_kanban_add'));
|
||||
|
||||
// fill it again with another record that DOES NOT satisfies the activated filter
|
||||
quickCreate = kanban.el.querySelector('.o_kanban_group:nth-child(1) .o_kanban_quick_create');
|
||||
await testUtils.fields.editInput(quickCreate.querySelector('.o_field_widget[name="int_field"]'), '1000');
|
||||
await testUtils.fields.editInput(quickCreate.querySelector('.o_field_widget[name="foo"]'), 'yop');
|
||||
await testUtils.dom.click(quickCreate.querySelector('button.o_kanban_add'));
|
||||
|
||||
// check counter
|
||||
const lastCount = parseInt(kanban.el.querySelector('.o_kanban_group:nth-child(1) .o_kanban_counter_side').innerText);
|
||||
assert.strictEqual(lastCount, initialCount + 44,
|
||||
"kanban counters should have been updated on quick create, respecting the activated filter");
|
||||
|
||||
kanban.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('keep adding quickcreate in first column after a record from this column was moved', async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user