summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMathieu Duckerts-Antoine <dam@odoo.com>2020-08-10 14:27:58 +0000
committerMathieu Duckerts-Antoine <dam@odoo.com>2020-08-12 08:52:20 +0000
commitae58021255274d3733e58ee8b020ca275aaa430d (patch)
tree926009fc46bb86c608d76044a3a4de52119555a2
parentfd6182443f68af3b6418195b13a4ab6c40599572 (diff)
[IMP] web: sample data in graph view
A previous commit has paved the way for displaying sample data in the reporting views. The present commit brings the required changes to have sample data in graph views. Task ID: 2282196 Co-authored-by: Aaron Bohy <aab@odoo.com> Co-authored-by: Mathieu Duckerts-Antoine <dam@odoo.com>
-rw-r--r--addons/web/static/src/js/views/graph/graph_model.js9
-rw-r--r--addons/web/static/src/js/views/graph/graph_renderer.js79
-rw-r--r--addons/web/static/src/xml/base.xml12
-rw-r--r--addons/web/static/tests/views/graph_tests.js145
-rw-r--r--odoo/addons/base/rng/graph_view.rng1
5 files changed, 169 insertions, 77 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 8679fafe4ff..b1bcddb47c2 100644
--- a/addons/web/static/src/js/views/graph/graph_model.js
+++ b/addons/web/static/src/js/views/graph/graph_model.js
@@ -35,7 +35,7 @@ return AbstractModel.extend({
* @returns {Object}
*/
__get: function () {
- return _.extend({}, this.chart);
+ return Object.assign({ isSample: this.isSampleModel }, this.chart);
},
/**
* Initial loading.
@@ -165,7 +165,12 @@ return AbstractModel.extend({
this.chart.comparisonFieldIndex = -1;
}
},
-
+ /**
+ * @override
+ */
+ _isEmpty() {
+ return this.chart.dataPoints.length === 0;
+ },
/**
* 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
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 52608e7b9cd..5cbe3f75324 100644
--- a/addons/web/static/src/js/views/graph/graph_renderer.js
+++ b/addons/web/static/src/js/views/graph/graph_renderer.js
@@ -54,6 +54,7 @@ var MAX_LEGEND_LENGTH = 4 * (Math.max(1, config.device.size_class));
return AbstractRenderer.extend({
className: "o_graph_renderer",
+ sampleDataTargets: ['.o_graph_canvas_container'],
/**
* @override
* @param {Widget} parent
@@ -762,9 +763,30 @@ return AbstractRenderer.extend({
}
var dataPoints = this._filterDataPoints();
dataPoints = this._sortDataPoints(dataPoints);
- if (!dataPoints.length && this.state.mode !== 'pie') {
- this.$el.append(qweb.render('View.NoContentHelper'));
- } else if (this.isInDOM) {
+ if (this.isInDOM) {
+ this._renderTitle();
+
+ // detect if some pathologies are still present after the filtering
+ if (this.state.mode === 'pie') {
+ const someNegative = dataPoints.some(dataPt => dataPt.value < 0);
+ const somePositive = dataPoints.some(dataPt => dataPt.value > 0);
+ if (someNegative && somePositive) {
+ const context = {
+ title: _t("Invalid data"),
+ description: [
+ _t("Pie chart cannot mix positive and negative numbers. "),
+ _t("Try to change your domain to only display positive results")
+ ].join("")
+ };
+ this._renderNoContentHelper(context);
+ return;
+ }
+ }
+
+ if (this.state.isSample && !this.isEmbedded) {
+ this._renderNoContentHelper();
+ }
+
// only render the graph if the widget is already in the DOM (this
// happens typically after an update), otherwise, it will be
// rendered when the widget will be attached to the DOM (see
@@ -785,8 +807,6 @@ return AbstractRenderer.extend({
} else if (this.state.mode === 'pie') {
this._renderPieChart(dataPoints);
}
-
- this._renderTitle();
}
},
/**
@@ -876,6 +896,25 @@ return AbstractRenderer.extend({
});
},
/**
+ * @private
+ */
+ _renderNoContentHelper: function (context) {
+ let templateName;
+ if (!context && this.noContentHelp) {
+ templateName = "web.ActionHelper";
+ context = { noContentHelp: this.noContentHelp };
+ } else {
+ templateName = "web.NoContentHelper";
+ }
+ const template = document.createElement('template');
+ // FIXME: retrieve owl qweb instance via the env set on Component s.t.
+ // it also works in the tests (importing 'web.env' wouldn't). This won't
+ // be necessary as soon as this rendering will be written in owl.
+ const OwlQweb = owl.Component.env.qweb;
+ template.innerHTML = OwlQweb.renderToString(templateName, context);
+ this.el.append(template.content.firstChild);
+ },
+ /**
* create pie chart
*
* @private
@@ -883,38 +922,10 @@ return AbstractRenderer.extend({
*/
_renderPieChart: function (dataPoints) {
var self = this;
-
- // try to see if some pathologies are still present after the filtering
- var allNegative = true;
- var someNegative = false;
- var allZero = true;
- dataPoints.forEach(function (datapt) {
- allNegative = allNegative && (datapt.value < 0);
- someNegative = someNegative || (datapt.value < 0);
- allZero = allZero && (datapt.value === 0);
- });
- if (someNegative && !allNegative) {
- this.$el.empty();
- this.$el.append(qweb.render('View.NoContentHelper', {
- title: _t("Invalid data"),
- description: _t("Pie chart cannot mix positive and negative numbers. " +
- "Try to change your domain to only display positive results"),
- }));
- return;
- }
- if (allZero && !this.isEmbedded && this.state.origins.length === 1) {
- this.$el.empty();
- this.$el.append(qweb.render('View.NoContentHelper', {
- title: _t("Invalid data"),
- description: _t("Pie chart cannot display all zero numbers.. " +
- "Try to change your domain to display positive results"),
- }));
- return;
- }
-
// prepare data
var data = {};
var colors = [];
+ const allZero = dataPoints.every(dataPt => dataPt.value === 0);
if (allZero) {
// add fake data to display a pie chart with a grey zone associated
// with every origin
diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml
index 12eff26e6ac..27cc0fdc4f9 100644
--- a/addons/web/static/src/xml/base.xml
+++ b/addons/web/static/src/xml/base.xml
@@ -803,17 +803,9 @@
</div>
</div>
-<div t-name="View.NoContentHelper" class="o_view_nocontent" role="alert">
+<div t-name="web.ActionHelper" class="o_view_nocontent" owl="1">
<div class="o_nocontent_help">
- <p class="o_view_nocontent_empty_folder">
- <t t-if="title" t-esc="title"/>
- <t t-else="1">No data to display</t>
- </p>
- <p>
- <t t-if="description" t-esc="description"/>
- <t t-else="1">Try to add some records, or make sure that there is no
- active filter in the search bar.</t>
- </p>
+ <t t-raw="noContentHelp"/>
</div>
</div>
diff --git a/addons/web/static/tests/views/graph_tests.js b/addons/web/static/tests/views/graph_tests.js
index 68c28952877..253c98d706a 100644
--- a/addons/web/static/tests/views/graph_tests.js
+++ b/addons/web/static/tests/views/graph_tests.js
@@ -338,43 +338,53 @@ QUnit.module('Views', {
});
QUnit.test('no content helper (bar chart)', async function (assert) {
- assert.expect(2);
+ assert.expect(3);
this.data.foo.records = [];
var graph = await createView({
View: GraphView,
model: "foo",
data: this.data,
- arch: '<graph string="Gloups">' +
- '<field name="product_id"/>' +
- '</graph>',
+ arch: `
+ <graph string="Gloups">
+ <field name="product_id"/>
+ </graph>`,
+ viewOptions: {
+ action: {
+ help: '<p class="abc">This helper should not be displayed in graph views</p>'
+ }
+ },
});
- assert.containsNone(graph, 'div.o_graph_canvas_container canvas',
- "should not contain a div with a canvas element");
- assert.containsOnce(graph, 'div.o_view_nocontent',
- "should display the no content helper");
+ assert.containsOnce(graph, 'div.o_graph_canvas_container canvas');
+ assert.containsNone(graph, 'div.o_view_nocontent');
+ assert.containsNone(graph, '.abc');
graph.destroy();
});
QUnit.test('no content helper (pie chart)', async function (assert) {
- assert.expect(2);
+ assert.expect(3);
this.data.foo.records = [];
var graph = await createView({
View: GraphView,
model: "foo",
data: this.data,
- arch: '<graph type="pie">' +
- '<field name="product_id"/>' +
- '</graph>',
+ arch: `
+ <graph type="pie">
+ <field name="product_id"/>
+ </graph>`,
+ viewOptions: {
+ action: {
+ help: '<p class="abc">This helper should not be displayed in graph views</p>'
+ }
+ },
});
- assert.containsNone(graph, 'div.o_graph_canvas_container canvas',
- "should not contain a div with a canvas element");
- assert.containsOnce(graph, 'div.o_view_nocontent',
- "should display the no content helper");
+ assert.containsOnce(graph, 'div.o_graph_canvas_container canvas');
+ assert.containsNone(graph, 'div.o_view_nocontent');
+ assert.containsNone(graph, '.abc');
graph.destroy();
});
@@ -413,27 +423,33 @@ QUnit.module('Views', {
});
QUnit.test('no content helper after update', async function (assert) {
- assert.expect(4);
+ assert.expect(6);
var graph = await createView({
View: GraphView,
model: "foo",
data: this.data,
- arch: '<graph string="Gloups">' +
- '<field name="product_id"/>' +
- '</graph>',
+ arch: `
+ <graph string="Gloups">
+ <field name="product_id"/>
+ </graph>`,
+ viewOptions: {
+ action: {
+ help: '<p class="abc">This helper should not be displayed in graph views</p>'
+ }
+ },
});
- assert.containsOnce(graph, 'div.o_graph_canvas_container canvas',
- "should contain a div with a canvas element");
- assert.containsNone(graph, 'div.o_view_nocontent',
- "should not display the no content helper");
+ assert.containsOnce(graph, 'div.o_graph_canvas_container canvas');
+ assert.containsNone(graph, 'div.o_view_nocontent');
+ assert.containsNone(graph, '.abc');
+
+ await testUtils.graph.reload(graph, {domain: [['product_id', '<', 0]]});
+
+ assert.containsOnce(graph, 'div.o_graph_canvas_container canvas');
+ assert.containsNone(graph, 'div.o_view_nocontent');
+ assert.containsNone(graph, '.abc');
- await testUtils.graph.reload(graph, {domain: [['product_id', '=', 4]]});
- assert.containsNone(graph, 'div.o_graph_canvas_container canvas',
- "should not contain a div with a canvas element");
- assert.containsOnce(graph, 'div.o_view_nocontent',
- "should display the no content helper");
graph.destroy();
});
@@ -1259,7 +1275,6 @@ QUnit.module('Views', {
assert.checkDatasets(graph, 'data', { data: [4, 3, 1] });
graph.destroy();
-
});
QUnit.test('graph view sort by measure for grouped data', async function (assert) {
@@ -1301,7 +1316,6 @@ QUnit.module('Views', {
assert.checkDatasets(graph, 'data', [{ data: [3, 0, 0] }, { data: [1, 3, 1] }]);
graph.destroy();
-
});
QUnit.test('graph view sort by measure for multiple grouped data', async function (assert) {
@@ -1356,6 +1370,75 @@ QUnit.module('Views', {
graph.destroy();
});
+ QUnit.test('empty graph view with sample data', async function (assert) {
+ assert.expect(8);
+
+ const graph = await createView({
+ View: GraphView,
+ model: "foo",
+ data: this.data,
+ arch: `
+ <graph sample="1">
+ <field name="product_id"/>
+ <field name="date"/>
+ </graph>`,
+ domain: [['id', '<', 0]],
+ viewOptions: {
+ action: {
+ help: '<p class="abc">click to add a foo</p>'
+ }
+ },
+ });
+
+ assert.hasClass(graph.el, 'o_view_sample_data');
+ assert.containsOnce(graph, '.o_view_nocontent');
+ assert.containsOnce(graph, '.o_graph_canvas_container canvas');
+ assert.hasClass(graph.$('.o_graph_canvas_container'), 'o_sample_data_disabled');
+
+ await graph.reload({ domain: [] });
+
+ assert.doesNotHaveClass(graph.el, 'o_view_sample_data');
+ assert.containsNone(graph, '.o_view_nocontent');
+ assert.containsOnce(graph, '.o_graph_canvas_container canvas');
+ assert.doesNotHaveClass(graph.$('.o_graph_canvas_container'), 'o_sample_data_disabled');
+
+ graph.destroy();
+ });
+
+ QUnit.test('non empty graph view with sample data', async function (assert) {
+ assert.expect(8);
+
+ const graph = await createView({
+ View: GraphView,
+ model: "foo",
+ data: this.data,
+ arch: `
+ <graph sample="1">
+ <field name="product_id"/>
+ <field name="date"/>
+ </graph>`,
+ viewOptions: {
+ action: {
+ help: '<p class="abc">click to add a foo</p>'
+ }
+ },
+ })
+
+ assert.doesNotHaveClass(graph.el, 'o_view_sample_data');
+ assert.containsNone(graph, '.o_view_nocontent');
+ assert.containsOnce(graph, '.o_graph_canvas_container canvas');
+ assert.doesNotHaveClass(graph.$('.o_graph_canvas_container'), 'o_sample_data_disabled');
+
+ await graph.reload({ domain: [['id', '<', 0]] });
+
+ assert.doesNotHaveClass(graph.el, 'o_view_sample_data');
+ assert.containsOnce(graph, '.o_graph_canvas_container canvas');
+ assert.doesNotHaveClass(graph.$('.o_graph_canvas_container'), 'o_sample_data_disabled');
+ assert.containsNone(graph, '.o_view_nocontent');
+
+ graph.destroy();
+ });
+
QUnit.module('GraphView: comparison mode', {
beforeEach: async function () {
this.data.foo.records[0].date = '2016-12-15';
diff --git a/odoo/addons/base/rng/graph_view.rng b/odoo/addons/base/rng/graph_view.rng
index 32639b30d9c..47f547f0c13 100644
--- a/odoo/addons/base/rng/graph_view.rng
+++ b/odoo/addons/base/rng/graph_view.rng
@@ -25,6 +25,7 @@
<rng:optional><rng:attribute name="orientation"/></rng:optional>
<rng:optional><rng:attribute name="interval"/></rng:optional>
<rng:optional><rng:attribute name="disable_linking"/></rng:optional>
+ <rng:optional><rng:attribute name="sample"/></rng:optional>
<rng:zeroOrMore>
<rng:ref name="field"/>
</rng:zeroOrMore>