[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>
This commit is contained in:
Mathieu Duckerts-Antoine
2020-08-12 08:52:20 +00:00
co-authored by Aaron Bohy
parent fd6182443f
commit ae58021255
5 changed files with 169 additions and 77 deletions
@@ -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
@@ -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
+2 -10
View File
@@ -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>
+114 -31
View File
@@ -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';
+1
View File
@@ -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>