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(); } }, /** @@ -875,6 +895,25 @@ return AbstractRenderer.extend({ options: options, }); }, + /** + * @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 * @@ -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 @@ -
-
-
This helper should not be displayed in graph views
' + } + }, }); - 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: 'This helper should not be displayed in graph views
' + } + }, }); - 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: 'This helper should not be displayed in graph views
' + } + }, }); - 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: ` +click to add a foo
' + } + }, + }); + + 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: ` +click to add a foo
' + } + }, + }) + + 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 @@