diff options
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.js | 172 |
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; } |
