From 099522aa436acd8654d27d68f6a8cbe009a39b7e Mon Sep 17 00:00:00 2001 From: Peter Pisljar Date: Thu, 10 May 2018 06:42:26 -0500 Subject: [PATCH] Allow splitting series on multiple fields (#17855) (#18953) --- .../kbn_vislib_vis_types/public/area.js | 2 +- .../kbn_vislib_vis_types/public/histogram.js | 2 +- .../public/horizontal_bar.js | 2 +- .../kbn_vislib_vis_types/public/line.js | 2 +- .../point_series/__tests__/_get_aspects.js | 8 --- .../point_series/__tests__/_get_point.js | 4 +- .../agg_response/point_series/_get_aspects.js | 4 +- .../agg_response/point_series/_get_point.js | 5 +- .../apps/visualize/_vertical_bar_chart.js | 51 ++++++++++++++++++- .../functional/page_objects/visualize_page.js | 13 ++++- 10 files changed, 72 insertions(+), 21 deletions(-) diff --git a/src/core_plugins/kbn_vislib_vis_types/public/area.js b/src/core_plugins/kbn_vislib_vis_types/public/area.js index bd655e1bdb1ee..98e9fcbc4a9a0 100644 --- a/src/core_plugins/kbn_vislib_vis_types/public/area.js +++ b/src/core_plugins/kbn_vislib_vis_types/public/area.js @@ -150,7 +150,7 @@ export default function PointSeriesVisType(Private) { name: 'group', title: 'Split Series', min: 0, - max: 1, + max: 3, aggFilter: ['!geohash_grid', '!filter'] }, { diff --git a/src/core_plugins/kbn_vislib_vis_types/public/histogram.js b/src/core_plugins/kbn_vislib_vis_types/public/histogram.js index dd4b356ab8f33..8af5440aaba68 100644 --- a/src/core_plugins/kbn_vislib_vis_types/public/histogram.js +++ b/src/core_plugins/kbn_vislib_vis_types/public/histogram.js @@ -151,7 +151,7 @@ export default function PointSeriesVisType(Private) { name: 'group', title: 'Split Series', min: 0, - max: 1, + max: 3, aggFilter: ['!geohash_grid', '!filter'] }, { diff --git a/src/core_plugins/kbn_vislib_vis_types/public/horizontal_bar.js b/src/core_plugins/kbn_vislib_vis_types/public/horizontal_bar.js index 6b6a2d6e7d5b6..c43d18446fac8 100644 --- a/src/core_plugins/kbn_vislib_vis_types/public/horizontal_bar.js +++ b/src/core_plugins/kbn_vislib_vis_types/public/horizontal_bar.js @@ -153,7 +153,7 @@ export default function PointSeriesVisType(Private) { name: 'group', title: 'Split Series', min: 0, - max: 1, + max: 3, aggFilter: ['!geohash_grid', '!filter'] }, { diff --git a/src/core_plugins/kbn_vislib_vis_types/public/line.js b/src/core_plugins/kbn_vislib_vis_types/public/line.js index fd72bd37ae37e..7e2c7aa759357 100644 --- a/src/core_plugins/kbn_vislib_vis_types/public/line.js +++ b/src/core_plugins/kbn_vislib_vis_types/public/line.js @@ -151,7 +151,7 @@ export default function PointSeriesVisType(Private) { name: 'group', title: 'Split Series', min: 0, - max: 1, + max: 3, aggFilter: ['!geohash_grid', '!filter'] }, { diff --git a/src/ui/public/agg_response/point_series/__tests__/_get_aspects.js b/src/ui/public/agg_response/point_series/__tests__/_get_aspects.js index 320276cd89412..6fcddaf3f1ef5 100644 --- a/src/ui/public/agg_response/point_series/__tests__/_get_aspects.js +++ b/src/ui/public/agg_response/point_series/__tests__/_get_aspects.js @@ -123,14 +123,6 @@ describe('getAspects', function () { }).to.throwError(TypeError); }); - it('throws an error if there are multiple series aspects', function () { - init(2, 1, 1); - - expect(function () { - getAspects(vis, table); - }).to.throwError(TypeError); - }); - it('creates a fake x aspect if the column does not exist', function () { init(0, 0, 1); diff --git a/src/ui/public/agg_response/point_series/__tests__/_get_point.js b/src/ui/public/agg_response/point_series/__tests__/_get_point.js index 91f988d3c29e8..76921c229529c 100644 --- a/src/ui/public/agg_response/point_series/__tests__/_get_point.js +++ b/src/ui/public/agg_response/point_series/__tests__/_get_point.js @@ -69,7 +69,7 @@ describe('getPoint', function () { expect(point) .to.have.property('x', 1) - .and.have.property('series', 2) + .and.have.property('series', '2') .and.have.property('y', 3) .and.have.property('aggConfigResult', row[2]); }); @@ -80,7 +80,7 @@ describe('getPoint', function () { expect(point) .to.have.property('x', 1) - .and.have.property('series', true) + .and.have.property('series', 'true') .and.have.property('y', 3) .and.have.property('aggConfigResult', row[2]); }); diff --git a/src/ui/public/agg_response/point_series/_get_aspects.js b/src/ui/public/agg_response/point_series/_get_aspects.js index b05404255a18b..d1ef447eb830a 100644 --- a/src/ui/public/agg_response/point_series/_get_aspects.js +++ b/src/ui/public/agg_response/point_series/_get_aspects.js @@ -42,8 +42,8 @@ export function PointSeriesGetAspectsProvider(Private) { .transform(columnToAspect, {}) // unwrap groups that only have one value, and validate groups that have more .transform(function (aspects, group, name) { - if (name !== 'y' && group.length > 1) { - throw new TypeError('Only multiple metrics are supported in point series'); + if ((name !== 'y' && name !== 'series') && group.length > 1) { + throw new TypeError('Only multiple metrics and series are supported in point series'); } aspects[name] = group.length > 1 ? group : group[0]; diff --git a/src/ui/public/agg_response/point_series/_get_point.js b/src/ui/public/agg_response/point_series/_get_point.js index aaefcde145505..9a7a87a0e1b17 100644 --- a/src/ui/public/agg_response/point_series/_get_point.js +++ b/src/ui/public/agg_response/point_series/_get_point.js @@ -25,8 +25,9 @@ export function PointSeriesGetPointProvider() { } if (series) { - point.aggConfig = series.agg; - point.series = series.agg.fieldFormatter()(unwrap(row[series.i])); + const seriesArray = series.length ? series : [ series ]; + point.aggConfig = seriesArray[0].agg; + point.series = seriesArray.map(s => s.agg.fieldFormatter()(unwrap(row[s.i]))).join(' - '); } else if (y) { // If the data is not split up with a series aspect, then // each point's "series" becomes the y-agg that produced it diff --git a/test/functional/apps/visualize/_vertical_bar_chart.js b/test/functional/apps/visualize/_vertical_bar_chart.js index d4ee320e2647a..feba75dddd333 100644 --- a/test/functional/apps/visualize/_vertical_bar_chart.js +++ b/test/functional/apps/visualize/_vertical_bar_chart.js @@ -9,7 +9,7 @@ export default function ({ getService, getPageObjects }) { const fromTime = '2015-09-19 06:31:44.000'; const toTime = '2015-09-23 18:31:44.000'; - before(function () { + beforeEach(function () { log.debug('navigateToApp visualize'); return PageObjects.common.navigateToUrl('visualize', 'new') .then(function () { @@ -118,5 +118,54 @@ export default function ({ getService, getPageObjects }) { }); }); }); + + describe('vertical bar with split series', function () { + it('should show correct series', async function () { + await PageObjects.visualize.toggleOpenEditor(2, 'false'); + await PageObjects.visualize.clickAddBucket(); + await PageObjects.visualize.clickBucket('Split Series'); + await PageObjects.visualize.selectAggregation('Terms'); + await PageObjects.visualize.selectField('response.raw'); + await PageObjects.header.waitUntilLoadingHasFinished(); + + await PageObjects.common.sleep(1003); + await PageObjects.visualize.clickGo(); + await PageObjects.header.waitUntilLoadingHasFinished(); + + const expectedEntries = ['200', '404', '503']; + const legendEntries = await PageObjects.visualize.getLegendEntries(); + expect(legendEntries).to.eql(expectedEntries); + }); + }); + + describe('vertical bar with multiple splits', function () { + it('should show correct series', async function () { + await PageObjects.visualize.toggleOpenEditor(2, 'false'); + await PageObjects.visualize.clickAddBucket(); + await PageObjects.visualize.clickBucket('Split Series'); + await PageObjects.visualize.selectAggregation('Terms'); + await PageObjects.visualize.selectField('response.raw'); + await PageObjects.header.waitUntilLoadingHasFinished(); + + await PageObjects.visualize.toggleOpenEditor(3, 'false'); + await PageObjects.visualize.clickAddBucket(); + await PageObjects.visualize.clickBucket('Split Series'); + await PageObjects.visualize.selectAggregation('Terms'); + await PageObjects.visualize.selectField('machine.os'); + await PageObjects.header.waitUntilLoadingHasFinished(); + + await PageObjects.common.sleep(1003); + await PageObjects.visualize.clickGo(); + await PageObjects.header.waitUntilLoadingHasFinished(); + + const expectedEntries = [ + '200 - win 8', '200 - win xp', '200 - ios', '200 - osx', '200 - win 7', + '404 - ios', '503 - ios', '503 - osx', '503 - win 7', '503 - win 8', + '503 - win xp', '404 - osx', '404 - win 7', '404 - win 8', '404 - win xp' + ]; + const legendEntries = await PageObjects.visualize.getLegendEntries(); + expect(legendEntries).to.eql(expectedEntries); + }); + }); }); } diff --git a/test/functional/page_objects/visualize_page.js b/test/functional/page_objects/visualize_page.js index 9561a9079fef8..c17f8a17f3f43 100644 --- a/test/functional/page_objects/visualize_page.js +++ b/test/functional/page_objects/visualize_page.js @@ -42,6 +42,10 @@ export function VisualizePageProvider({ getService, getPageObjects }) { await find.clickByCssSelector('[group-name="metrics"] [data-test-subj="visualizeEditorAddAggregationButton"]'); } + async clickAddBucket() { + await find.clickByCssSelector('[group-name="buckets"] [data-test-subj="visualizeEditorAddAggregationButton"]'); + } + async clickMetric() { await find.clickByPartialLinkText('Metric'); } @@ -391,12 +395,12 @@ export function VisualizePageProvider({ getService, getPageObjects }) { }); } - async toggleOpenEditor(index) { + async toggleOpenEditor(index, toState = 'true') { // index, see selectYAxisAggregation const toggle = await find.byCssSelector(`button[aria-controls="visAggEditorParams${index}"]`); const toggleOpen = await toggle.getAttribute('aria-expanded'); log.debug(`toggle ${index} expand = ${toggleOpen}`); - if (toggleOpen === 'false') { + if (toggleOpen !== toState) { log.debug(`toggle ${index} click()`); await toggle.click(); } @@ -889,6 +893,11 @@ export function VisualizePageProvider({ getService, getPageObjects }) { } } + async getLegendEntries() { + const legendEntries = await find.allByCssSelector('.legend-value-title', defaultFindTimeout * 2); + return await Promise.all(legendEntries.map(async chart => await chart.getAttribute('data-label'))); + } + async clickLegendOption(name) { await testSubjects.click(`legend-${name}`); }