diff options
| author | Mathieu Duckerts-Antoine <dam@odoo.com> | 2020-02-21 12:28:46 +0000 |
|---|---|---|
| committer | Mathieu Duckerts-Antoine <dam@odoo.com> | 2020-02-25 15:30:04 +0000 |
| commit | c7851add36575ce3f34a692a53acfdf4432a4d0c (patch) | |
| tree | e5e55c92052749e2dc1df7e7e36326e66b2422fe /addons/web | |
| parent | a39cc6bc00df1a72cb12cfaa9c56cbe9fafaee83 (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.js | 51 | ||||
| -rw-r--r-- | addons/web/static/src/js/views/graph/graph_renderer.js | 12 | ||||
| -rw-r--r-- | addons/web/static/tests/views/graph_tests.js | 29 |
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'; |
