Fixes pie charts on empty time window (#24031) (#24517)

* Fix missing check on empty response

* Fix test order and remove applying on each entered filter

* Change quotes on addNewFilterAggregation testsubject

* Rename test hasPieChartError method to expectPieChartError

* Refactor piechart zero-value slices data cleaning.

The previous implementation used to remove zero-value slices by mutating the vis data from the legend logic.
We moved the logic of "cleaning" the zero-value slices before rendering the piechart and/or the legend, so now piechart and legends are rendering themselves with the same data structure.

* Reverting _validatePieData method to the old one
This commit is contained in:
Marco Vettorello 2018-10-24 21:20:56 +02:00 committed by GitHub
parent e400a9e49d
commit 0f677d87e8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 119 additions and 15 deletions

View file

@ -28,6 +28,7 @@
<div class="form-group">
<input
data-test-subj="visEditorFilterInput_{{agg.id}}_{{$index}}"
id="visEditorFilterInput{{agg.id}}"
parse-query
ng-model="filter.input.query"
@ -58,6 +59,7 @@
</div>
<button
data-test-subj="visEditorAddFilterButton"
click-focus="'filter'+(agg.params.filters.length-1)"
ng-click="agg.params.filters.push({input:{}})"
class="kuiButton kuiButton--primary kuiButton--fullWidth"

View file

@ -45,7 +45,7 @@ export function VislibLibDataProvider(Private) {
this.uiState = uiState;
this.data = this.copyDataObj(data);
this.type = this.getDataType();
this._cleanVisData();
this.labels = this._getLabels(this.data);
this.color = this.labels ? color(this.labels, uiState.get('vis.colors')) : undefined;
this._normalizeOrdered();
@ -96,7 +96,10 @@ export function VislibLibDataProvider(Private) {
}
_getLabels(data) {
return this.type === 'series' ? getLabels(data) : this.pieNames();
if (this.type === 'series') {
return getLabels(data);
}
return [];
}
getDataType() {
@ -345,19 +348,42 @@ export function VislibLibDataProvider(Private) {
}
/**
* Removes zeros from pie chart data
* Clean visualization data from missing/wrong values.
* Currently used only to clean remove zero slices from
* pie chart.
*/
_cleanVisData() {
const visData = this.getVisData();
if (this.type === 'slices') {
this._cleanPieChartData(visData);
}
}
/**
* Mutate the current pie chart vis data to remove slices with
* zero values.
* @param {Array} data
*/
_cleanPieChartData(data) {
_.forEach(data, (obj) => {
obj.slices = this._removeZeroSlices(obj.slices);
});
}
/**
* Removes zeros from pie chart data, mutating the passed values.
* @param slices
* @returns {*}
*/
_removeZeroSlices(slices) {
const self = this;
if (!slices.children) return slices;
if (!slices.children) {
return slices;
}
slices = _.clone(slices);
slices.children = slices.children.reduce(function (children, child) {
slices.children = slices.children.reduce((children, child) => {
if (child.size !== 0) {
children.push(self._removeZeroSlices(child));
return [...children, this._removeZeroSlices(child)];
}
return children;
}, []);
@ -378,8 +404,6 @@ export function VislibLibDataProvider(Private) {
_.forEach(data, function (obj) {
const columns = obj.raw ? obj.raw.columns : undefined;
obj.slices = self._removeZeroSlices(obj.slices);
_.forEach(self.getNames(obj, columns), function (name) {
names.push(name);
});

View file

@ -217,7 +217,8 @@ export function VisHandlerProvider(Private) {
.append('div')
// class name needs `chart` in it for the polling checkSize function
// to continuously call render on resize
.attr('class', 'visualize-error chart error');
.attr('class', 'visualize-error chart error')
.attr('data-test-subj', 'visLibVisualizeError');
if (message === 'No results found') {
div.append('div')

View file

@ -48,10 +48,8 @@ export function VislibVisualizationsPieChartProvider(Private) {
class PieChart extends Chart {
constructor(handler, chartEl, chartData) {
super(handler, chartEl, chartData);
const charts = this.handler.data.getVisData();
this._validatePieData(charts);
this._attr = _.defaults(handler.visConfig.get('chart', {}), defaults);
}
@ -61,7 +59,7 @@ export function VislibVisualizationsPieChartProvider(Private) {
* If so, an error is thrown.
*/
_validatePieData(charts) {
const isAllZeros = charts.every(function (chart) {
const isAllZeros = charts.every((chart) => {
return chart.slices.children.length === 0;
});

View file

@ -28,7 +28,6 @@ export default function ({ getService, getPageObjects }) {
describe('pie chart', async function () {
const vizName1 = 'Visualization PieChart';
before(async function () {
log.debug('navigateToApp visualize');
await PageObjects.visualize.navigateToNewVisualization();
log.debug('clickPieChart');
@ -170,5 +169,63 @@ export default function ({ getService, getPageObjects }) {
expect(pieData).to.eql(expectedTableData);
});
});
describe('empty time window', () => {
it('should show no data message when no data on selected timerange', async function () {
await PageObjects.visualize.navigateToNewVisualization();
log.debug('clickPieChart');
await PageObjects.visualize.clickPieChart();
await PageObjects.visualize.clickNewSearch();
log.debug('Set absolute time range from \"' + fromTime + '\" to \"' + toTime + '\"');
await PageObjects.header.setAbsoluteRange(fromTime, toTime);
log.debug('select bucket Split Slices');
await PageObjects.visualize.clickBucket('Split Slices');
log.debug('Click aggregation Filters');
await PageObjects.visualize.selectAggregation('Filters');
log.debug('Set the 1st filter value');
await PageObjects.visualize.setFilterAggregationValue('geo.dest:"US"');
log.debug('Add new filter');
await PageObjects.visualize.addNewFilterAggregation();
log.debug('Set the 2nd filter value');
await PageObjects.visualize.setFilterAggregationValue('geo.dest:"CN"', 1);
await PageObjects.visualize.clickGo();
const emptyFromTime = '2016-09-19 06:31:44.000';
const emptyToTime = '2016-09-23 18:31:44.000';
log.debug('Switch to a different time range from \"' + emptyFromTime + '\" to \"' + emptyToTime + '\"');
await PageObjects.header.setAbsoluteRange(emptyFromTime, emptyToTime);
await PageObjects.visualize.waitForVisualization();
await PageObjects.visualize.expectPieChartError();
});
});
describe('multi series slice', () => {
it('should still showing pie chart when a subseries have zero data', async function () {
await PageObjects.visualize.navigateToNewVisualization();
log.debug('clickPieChart');
await PageObjects.visualize.clickPieChart();
await PageObjects.visualize.clickNewSearch();
log.debug('Set absolute time range from \"' + fromTime + '\" to \"' + toTime + '\"');
await PageObjects.header.setAbsoluteRange(fromTime, toTime);
log.debug('select bucket Split Slices');
await PageObjects.visualize.clickBucket('Split Slices');
log.debug('Click aggregation Filters');
await PageObjects.visualize.selectAggregation('Filters');
log.debug('Set the 1st filter value');
await PageObjects.visualize.setFilterAggregationValue('geo.dest:"US"');
log.debug('Toggle previous editor');
await PageObjects.visualize.toggleAggegationEditor(2);
log.debug('Add a new series');
await PageObjects.visualize.clickAddBucket();
log.debug('select bucket Split Slices');
await PageObjects.visualize.clickBucket('Split Slices');
log.debug('Click aggregation Filters');
await PageObjects.visualize.selectAggregation('Filters');
log.debug('Set the 1st filter value of the aggregation id 3');
await PageObjects.visualize.setFilterAggregationValue('geo.dest:"UX"', 0, 3);
await PageObjects.visualize.clickGo();
const legends = await PageObjects.visualize.getLegendEntries();
const expectedLegends = ['geo.dest:"US"', 'geo.dest:"UX"'];
expect(legends).to.eql(expectedLegends);
});
});
});
}

View file

@ -509,6 +509,21 @@ export function VisualizePageProvider({ getService, getPageObjects }) {
await PageObjects.common.sleep(500);
}
/**
* Set the test for a filter aggregation.
* @param {*} filterValue the string value of the filter
* @param {*} filterIndex used when multiple filters are configured on the same aggregation
* @param {*} aggregationId the ID if the aggregation. On Tests, it start at from 2
*/
async setFilterAggregationValue(filterValue, filterIndex = 0, aggregationId = 2) {
const inputField = await testSubjects.find(`visEditorFilterInput_${aggregationId}_${filterIndex}`);
await inputField.type(filterValue);
}
async addNewFilterAggregation() {
return await testSubjects.click('visEditorAddFilterButton');
}
async toggleOpenEditor(index, toState = 'true') {
// index, see selectYAxisAggregation
const toggle = await find.byCssSelector(`button[aria-controls="visAggEditorParams${index}"]`);
@ -653,6 +668,10 @@ export function VisualizePageProvider({ getService, getPageObjects }) {
await testSubjects.click(`aggregationEditor${agg} disableAggregationBtn`);
await PageObjects.header.waitUntilLoadingHasFinished();
}
async toggleAggegationEditor(agg) {
await testSubjects.click(`aggregationEditor${agg} toggleEditor`);
await PageObjects.header.waitUntilLoadingHasFinished();
}
async toggleOtherBucket() {
return await find.clickByCssSelector('input[name="showOther"]');
@ -922,6 +941,9 @@ export function VisualizePageProvider({ getService, getPageObjects }) {
const getChartTypesPromises = chartTypes.map(async chart => await chart.getAttribute('data-label'));
return await Promise.all(getChartTypesPromises);
}
async expectPieChartError() {
return await testSubjects.existOrFail('visLibVisualizeError');
}
async getChartAreaWidth() {
const rect = await retry.try(async () => find.byCssSelector('clipPath rect'));