summaryrefslogtreecommitdiff
path: root/addons/web
diff options
context:
space:
mode:
authorMathieu Duckerts-Antoine <dam@odoo.com>2020-02-21 12:28:46 +0000
committerMathieu Duckerts-Antoine <dam@odoo.com>2020-02-25 15:30:04 +0000
commitc7851add36575ce3f34a692a53acfdf4432a4d0c (patch)
treee5e55c92052749e2dc1df7e7e36326e66b2422fe /addons/web
parenta39cc6bc00df1a72cb12cfaa9c56cbe9fafaee83 (diff)
[FIX] web: graph renders only finer date groupby option
Rendering a graph view with several options selected for a given date groupby can take too much time and block the page. Since grouping on the same date/datetime field several times barely makes sense in a graph view, we choose to use only the finer option, e.g. 'week' is prefered to 'quarter'. Task ID: 2198846 X-original-commit: 7e36bee14e18ca17e15de1bacb7ff26010d0b372
Diffstat (limited to 'addons/web')
-rw-r--r--addons/web/static/src/js/views/graph/graph_model.js51
-rw-r--r--addons/web/static/src/js/views/graph/graph_renderer.js12
-rw-r--r--addons/web/static/tests/views/graph_tests.js29
3 files changed, 82 insertions, 10 deletions
diff --git a/addons/web/static/src/js/views/graph/graph_model.js b/addons/web/static/src/js/views/graph/graph_model.js
index cf17e7267ab..b09d0a464d9 100644
--- a/addons/web/static/src/js/views/graph/graph_model.js
+++ b/addons/web/static/src/js/views/graph/graph_model.js
@@ -2,8 +2,14 @@ odoo.define('web.GraphModel', function (require) {
"use strict";
var core = require('web.core');
+const { DEFAULT_INTERVAL, INTERVAL_OPTIONS } = require('web.controlPanelViewParameters');
+
var _t = core._t;
+function rank(oId) {
+ return INTERVAL_OPTIONS.findIndex(o => o.optionId === oId);
+}
+
/**
* The graph model is responsible for fetching and processing data from the
* server. It basically just do a(some) read_group(s) and format/normalize data.
@@ -84,6 +90,7 @@ return AbstractModel.extend({
timeRange: params.timeRange,
timeRangeDescription: params.timeRangeDescription,
};
+ this.chart.processedGroupBy = this._processGroupBy(this.chart.groupBy);
return this._loadGraph(this._getDomains());
},
/**
@@ -135,6 +142,7 @@ return AbstractModel.extend({
if ('measure' in params) {
this.chart.measure = params.measure;
}
+ this.chart.processedGroupBy = this._processGroupBy(this.chart.groupBy);
if ('mode' in params) {
this.chart.mode = params.mode;
return Promise.resolve();
@@ -155,7 +163,7 @@ return AbstractModel.extend({
* @returns {number}
*/
_getComparisonFieldIndex: function () {
- var groupBys = this.chart.groupBy.map(function (gb) {
+ var groupBys = this.chart.processedGroupBy.map(function (gb) {
return gb.split(":")[0];
});
return groupBys.indexOf(this.chart.comparisonField);
@@ -186,7 +194,7 @@ return AbstractModel.extend({
_loadGraph: function (domains) {
var self = this;
this.chart.dataPoints = [];
- var groupBy = this.chart.groupBy;
+ var groupBy = this.chart.processedGroupBy;
var fields = _.map(groupBy, function (groupBy) {
return groupBy.split(':')[0];
});
@@ -235,13 +243,13 @@ return AbstractModel.extend({
var labels;
function getLabels (dataPt) {
- return self.chart.groupBy.map(function (field) {
+ return self.chart.processedGroupBy.map(function (field) {
return self._sanitizeValue(dataPt[field], field.split(":")[0]);
});
}
rawData.forEach(function (dataPt){
labels = getLabels(dataPt);
- var count = dataPt.__count || dataPt[self.chart.groupBy[0]+'_count'] || 0;
+ var count = dataPt.__count || dataPt[self.chart.processedGroupBy[0]+'_count'] || 0;
var value = isCount ? count : dataPt[self.chart.measure];
if (value instanceof Array) {
// when a many2one field is used as a measure AND as a grouped
@@ -262,6 +270,41 @@ return AbstractModel.extend({
});
},
/**
+ * Process the groupBy parameter in order to keep only the finer interval option for
+ * elements based on date/datetime field (e.g. 'date:year'). This means that
+ * 'week' is prefered to 'month'. The field stays at the place of its first occurence.
+ * For instance,
+ * ['foo', 'date:month', 'bar', 'date:week'] becomes ['foo', 'date:week', 'bar'].
+ *
+ * @private
+ * @param {string[]} groupBy
+ * @returns {string[]}
+ */
+ _processGroupBy: function(groupBy) {
+ const groupBysMap = new Map();
+ for (const gb of groupBy) {
+ let [fieldName, interval] = gb.split(':');
+ const field = this.fields[fieldName];
+ if (['date', 'datetime'].includes(field.type)) {
+ interval = interval || DEFAULT_INTERVAL;
+ }
+ if (groupBysMap.has(fieldName)) {
+ const registeredInterval = groupBysMap.get(fieldName);
+ if (rank(registeredInterval) < rank(interval)) {
+ groupBysMap.set(fieldName, interval);
+ }
+ } else {
+ groupBysMap.set(fieldName, interval);
+ }
+ }
+ return [...groupBysMap].map(([fieldName, interval]) => {
+ if (interval) {
+ return `${fieldName}:${interval}`;
+ }
+ return fieldName;
+ });
+ },
+ /**
* Helper function (for _processData), turns various values in a usable
* string form, that we can display in the interface.
*
diff --git a/addons/web/static/src/js/views/graph/graph_renderer.js b/addons/web/static/src/js/views/graph/graph_renderer.js
index 6cb2c023d0e..e83edbbbf04 100644
--- a/addons/web/static/src/js/views/graph/graph_renderer.js
+++ b/addons/web/static/src/js/views/graph/graph_renderer.js
@@ -462,9 +462,9 @@ return AbstractRenderer.extend({
xAxes: [{
type: 'category',
scaleLabel: {
- display: this.state.groupBy.length && !this.isEmbedded,
- labelString: this.state.groupBy.length ?
- this.fields[this.state.groupBy[0].split(':')[0]].string : '',
+ display: this.state.processedGroupBy.length && !this.isEmbedded,
+ labelString: this.state.processedGroupBy.length ?
+ this.fields[this.state.processedGroupBy[0].split(':')[0]].string : '',
},
ticks: {
// don't use bind: callback is called with 'index' as second parameter
@@ -506,14 +506,14 @@ return AbstractRenderer.extend({
var boxColor;
if (this.state.mode === 'bar') {
label = this._relabelling(label, dataset.originIndex);
- if (this.state.groupBy.length > 1 || this.state.origins.length > 1) {
+ if (this.state.processedGroupBy.length > 1 || this.state.origins.length > 1) {
label = label + "/" + dataset.label;
}
value = this._formatValue(item.yLabel);
boxColor = dataset.backgroundColor;
} else if (this.state.mode === 'line') {
label = this._relabelling(label, dataset.originIndex);
- if (this.state.groupBy.length > 1 || this.state.origins.length > 1) {
+ if (this.state.processedGroupBy.length > 1 || this.state.origins.length > 1) {
label = label + "/" + dataset.label;
}
value = this._formatValue(item.yLabel);
@@ -811,7 +811,7 @@ return AbstractRenderer.extend({
// prepare data
var data = this._prepareData(dataPoints);
data.datasets.forEach(function (dataset, index) {
- if (self.state.groupBy.length <= 1 && self.state.origins.length > 1) {
+ if (self.state.processedGroupBy.length <= 1 && self.state.origins.length > 1) {
if (dataset.originIndex === 0) {
dataset.fill = 'origin';
dataset.backgroundColor = hexToRGBA(COLORS[0], 0.4);
diff --git a/addons/web/static/tests/views/graph_tests.js b/addons/web/static/tests/views/graph_tests.js
index d44d2e61a81..d0cbe9d7a2e 100644
--- a/addons/web/static/tests/views/graph_tests.js
+++ b/addons/web/static/tests/views/graph_tests.js
@@ -946,6 +946,35 @@ QUnit.module('Views', {
graph.destroy();
});
+ QUnit.test('graph view only keeps finer groupby filter option for a given groupby', async function (assert) {
+ assert.expect(3);
+
+ var graph = await createView({
+ View: GraphView,
+ model: "foo",
+ groupBy:['date:year','product_id', 'date', 'date:quarter'],
+ data: this.data,
+ arch: '<graph string="Partners" type="line">' +
+ '<field name="bar"/>' +
+ '</graph>',
+ });
+
+ assert.checkLabels(graph, [["January 2016"], ["March 2016"], ["May 2016"], ["April 2016"]]);
+ // mockReadGroup does not always sort groups -> May 2016 is before April 2016 for that reason.
+ assert.checkLegend(graph, ["xphone","xpad"]);
+ assert.checkDatasets(graph, ['label', 'data'], [
+ {
+ label: 'xphone',
+ data: [2, 2, 0, 0],
+ }, {
+ label: 'xpad',
+ data: [0, 0, 1, 1],
+ }
+ ]);
+
+ graph.destroy();
+ });
+
QUnit.module('GraphView: comparison mode', {
beforeEach: async function () {
this.data.foo.records[0].date = '2016-12-15';