summaryrefslogtreecommitdiff
path: root/addons/web/static/src/js/views/graph/graph_model.js
diff options
context:
space:
mode:
Diffstat (limited to 'addons/web/static/src/js/views/graph/graph_model.js')
-rw-r--r--addons/web/static/src/js/views/graph/graph_model.js172
1 files changed, 89 insertions, 83 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 91b017524d8..33b8896b29d 100644
--- a/addons/web/static/src/js/views/graph/graph_model.js
+++ b/addons/web/static/src/js/views/graph/graph_model.js
@@ -1,11 +1,13 @@
odoo.define('web.GraphModel', function (require) {
"use strict";
+var core = require('web.core');
+var _t = core._t;
+
/**
* The graph model is responsible for fetching and processing data from the
- * server. It basically just do a read_group and format/normalize data.
+ * server. It basically just do a(some) read_group(s) and format/normalize data.
*/
-
var AbstractModel = require('web.AbstractModel');
return AbstractModel.extend({
@@ -23,18 +25,12 @@ return AbstractModel.extend({
//--------------------------------------------------------------------------
/**
- * We defend against outside modifications by extending the chart data. It
- * may be overkill.
- *
- * @todo Adding the fields parameter looks wrong. If the renderer or the
- * controller need the fields, they should get it via their init method.
+ * override
*
* @returns {Object}
*/
get: function () {
- return _.extend({}, this.chart, {
- fields: this.fields
- });
+ return this.chart;
},
/**
* Initial loading.
@@ -43,13 +39,19 @@ return AbstractModel.extend({
* should be done by the graphView I think.
*
* @param {Object} params
- * @param {string} params.mode one of 'pie', 'bar', 'line
- * @param {string} params.measure a valid field name
- * @param {string[]} params.groupBys a list of valid field names
+ * @param {boolean} params.compare
* @param {Object} params.context
+ * @param {Object} params.fields
+ * @param {string[]} params.comparisonTimeRange
* @param {string[]} params.domain
- * @param {Object} params.intervalMapping object linking fieldNames with intervals.
- * this could be useful to simplify the code. For now this parameter is not used.
+ * @param {string[]} params.groupBy a list of valid field names
+ * @param {string[]} params.timeRange
+ * @param {string} params.comparisonField
+ * @param {string} params.comparisonTimeRangeDescription
+ * @param {string} params.measure a valid field name
+ * @param {'pie'|'bar'|'line'} params.mode
+ * @param {string} params.modelName
+ * @param {string} params.timeRangeDescription
* @returns {Promise} The promise does not return a handle, we don't need
* to keep track of various entities.
*/
@@ -59,25 +61,21 @@ return AbstractModel.extend({
this.fields = params.fields;
this.modelName = params.modelName;
this.chart = {
- compare: params.compare,
+ comparisonField: params.comparisonField,
comparisonTimeRange: params.comparisonTimeRange,
comparisonTimeRangeDescription: params.comparisonTimeRangeDescription,
- data: [],
- groupedBy: params.groupedBy.length ? params.groupedBy : groupBys,
- // this parameter is not used anywhere for now.
- // the idea would be to seperate intervals from
- // fieldnames in groupbys. This could be done
- // in graph view only or everywhere but this is
- // a big refactoring.
- intervalMapping: params.intervalMapping,
+ compare: params.compare,
+ context: params.context,
+ dataPoints: [],
+ domain: params.domain,
+ groupBy: params.groupedBy.length ? params.groupedBy : groupBys,
measure: params.context.graph_measure || params.measure,
mode: params.context.graph_mode || params.mode,
+ origins: [],
timeRange: params.timeRange,
timeRangeDescription: params.timeRangeDescription,
- domain: params.domain,
- context: params.context,
};
- return this._loadGraph();
+ return this._loadGraph(this._getDomains());
},
/**
* Reload data. It is similar to the load function. Note that we ignore the
@@ -88,31 +86,33 @@ return AbstractModel.extend({
*
* @param {any} handle ignored!
* @param {Object} params
+ * @param {Object} [params.context]
* @param {string[]} [params.domain]
* @param {string[]} [params.groupBy]
- * @param {string} [params.mode] one of 'bar', 'pie', 'line'
* @param {string} [params.measure] a valid field name
+ * @param {string} [params.mode] one of 'bar', 'pie', 'line'
* @returns {Promise}
*/
reload: function (handle, params) {
if ('context' in params) {
this.chart.context = params.context;
- this.chart.groupedBy = params.context.graph_groupbys || this.chart.groupedBy;
+ this.chart.groupBy = params.context.graph_groupbys || this.chart.groupBy;
this.chart.measure = params.context.graph_measure || this.chart.measure;
this.chart.mode = params.context.graph_mode || this.chart.mode;
var timeRangeMenuData = params.context.timeRangeMenuData;
if (timeRangeMenuData) {
+ this.chart.comparisonField = timeRangeMenuData.comparisonField || undefined;
this.chart.comparisonTimeRange = timeRangeMenuData.comparisonTimeRange || [];
this.chart.compare = this.chart.comparisonTimeRange.length > 0;
- this.chart.timeRange = timeRangeMenuData.timeRange || [];
this.chart.comparisonTimeRangeDescription = timeRangeMenuData.comparisonTimeRangeDescription;
+ this.chart.timeRange = timeRangeMenuData.timeRange || [];
this.chart.timeRangeDescription = timeRangeMenuData.timeRangeDescription;
} else {
- this.chart = _.omit(this.chart, 'comparisonData');
+ this.chart.comparisonField = undefined;
this.chart.comparisonTimeRange = [];
this.chart.compare = false;
- this.chart.timeRange = [];
this.chart.comparisonTimeRangeDescription = undefined;
+ this.chart.timeRange = [];
this.chart.timeRangeDescription = undefined;
}
}
@@ -120,10 +120,7 @@ return AbstractModel.extend({
this.chart.domain = params.domain;
}
if ('groupBy' in params) {
- this.chart.groupedBy = params.groupBy.length ? params.groupBy : this.initialGroupBys;
- }
- if ('intervalMapping' in params) {
- this.chart.intervalMapping = params.intervalMapping;
+ this.chart.groupBy = params.groupBy.length ? params.groupBy : this.initialGroupBys;
}
if ('measure' in params) {
this.chart.measure = params.measure;
@@ -132,7 +129,7 @@ return AbstractModel.extend({
this.chart.mode = params.mode;
return Promise.resolve();
}
- return this._loadGraph();
+ return this._loadGraph(this._getDomains());
},
//--------------------------------------------------------------------------
@@ -140,16 +137,32 @@ return AbstractModel.extend({
//--------------------------------------------------------------------------
/**
- * Fetch and process graph data. It is basically a read_group with correct
- * fields. We have to do some light processing to separate date groups
- * in the field list, because they can be defined with an aggregation
- * function, such as my_date:week
+ * @returns {Object[]}
+ */
+ _getDomains: function () {
+ var domains = [this.chart.domain.concat(this.chart.timeRange)];
+ this.chart.origins = [this.chart.timeRangeDescription];
+ if (this.chart.compare) {
+ domains.push(this.chart.domain.concat(this.chart.comparisonTimeRange));
+ this.chart.origins.push(this.chart.comparisonTimeRangeDescription);
+ }
+ return domains;
+ },
+
+ /**
+ * Fetch and process graph data. It is basically a(some) read_group(s)
+ * with correct fields for each domain. We have to do some light processing
+ * to separate date groups in the field list, because they can be defined
+ * with an aggregation function, such as my_date:week.
*
+ * @param {Array[]} domains
* @returns {Promise}
*/
- _loadGraph: function () {
- var groupedBy = this.chart.groupedBy;
- var fields = _.map(groupedBy, function (groupBy) {
+ _loadGraph: function (domains) {
+ var self = this;
+ this.chart.dataPoints = [];
+ var groupBy = this.chart.groupBy;
+ var fields = _.map(groupBy, function (groupBy) {
return groupBy.split(':')[0];
});
@@ -164,55 +177,46 @@ return AbstractModel.extend({
var context = _.extend({fill_temporal: true}, this.chart.context);
var defs = [];
- defs.push(this._rpc({
- model: this.modelName,
- method: 'read_group',
- context: context,
- domain: this.chart.domain.concat(this.chart.timeRange),
- fields: fields,
- groupBy: groupedBy,
- lazy: false,
- }).then(this._processData.bind(this, 'data')));
- if (this.chart.compare) {
- defs.push(this._rpc({
- model: this.modelName,
+ domains.forEach(function (domain, originIndex) {
+ defs.push(self._rpc({
+ model: self.modelName,
method: 'read_group',
context: context,
- domain: this.chart.domain.concat(this.chart.comparisonTimeRange),
+ domain: domain,
fields: fields,
- groupBy: groupedBy,
+ groupBy: groupBy,
lazy: false,
- }).then(this._processData.bind(this, 'comparisonData')));
- }
-
+ }).then(self._processData.bind(self, originIndex)));
+ });
return Promise.all(defs);
},
/**
* Since read_group is insane and returns its result on different keys
* depending of some input, we have to normalize the result.
- * The final chart data is added to this.chart object.
+ * Each group comming from the read_group produces a dataPoint
*
* @todo This is not good for race conditions. The processing should get
* the object this.chart in argument, or an array or something. We want to
* avoid writing on a this.chart object modified by a subsequent read_group
*
- * @param {String} dataKey
- * @param {any} raw_data result from the read_group
+ * @param {number} originIndex
+ * @param {any} rawData result from the read_group
*/
- _processData: function (dataKey, raw_data) {
+ _processData: function (originIndex, rawData) {
var self = this;
- var is_count = this.chart.measure === '__count__';
- var data_pt, labels;
+ var isCount = this.chart.measure === '__count__';
+ var labels;
- this.chart[dataKey] = [];
- for (var i = 0; i < raw_data.length; i++) {
- data_pt = raw_data[i];
- labels = _.map(this.chart.groupedBy, function (field) {
- return self._sanitizeValue(data_pt[field], field);
+ function getLabels (dataPt) {
+ return self.chart.groupBy.map(function (field) {
+ return self._sanitizeValue(dataPt[field], field.split(":")[0]);
});
- var count = data_pt.__count || data_pt[this.chart.groupedBy[0]+'_count'] || 0;
- var value = is_count ? count : data_pt[this.chart.measure];
+ }
+ rawData.forEach(function (dataPt){
+ labels = getLabels(dataPt);
+ var count = dataPt.__count || dataPt[self.chart.groupBy[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
// field, bad things happen. The server will only return the
@@ -223,28 +227,30 @@ return AbstractModel.extend({
// value for that field.
value = 1;
}
- this.chart[dataKey].push({
+ self.chart.dataPoints.push({
count: count,
value: value,
labels: labels,
+ originIndex: originIndex,
});
- }
+ });
},
/**
* Helper function (for _processData), turns various values in a usable
* string form, that we can display in the interface.
*
- * @param {any} value some value received by the read_group rpc
- * @param {string} field the name of the corresponding field
+ * @param {any} value value for the field fieldName received by the read_group rpc
+ * @param {string} fieldName
* @returns {string}
*/
- _sanitizeValue: function (value, field) {
- var fieldName = field.split(':')[0];
+ _sanitizeValue: function (value, fieldName) {
if (value === false && this.fields[fieldName].type !== 'boolean') {
- return undefined;
+ return _t("Undefined");
+ }
+ if (value instanceof Array) {
+ return value[1];
}
- if (value instanceof Array) return value[1];
- if (field && (this.fields[fieldName].type === 'selection')) {
+ if (fieldName && (this.fields[fieldName].type === 'selection')) {
var selected = _.where(this.fields[fieldName].selection, {0: value})[0];
return selected ? selected[1] : value;
}