From e5a94e7a1062f34471444ea29e20a844815842f6 Mon Sep 17 00:00:00 2001 From: Peter Pisljar Date: Thu, 30 Aug 2018 14:56:18 +0200 Subject: [PATCH] simplified tabify (#19061) --- .../kbn_vislib_vis_types/public/gauge.js | 3 +- .../kbn_vislib_vis_types/public/goal.js | 3 +- .../public/discover/controllers/discover.js | 4 +- .../metric_vis/public/__tests__/metric_vis.js | 8 +- .../public/__tests__/metric_vis_controller.js | 20 +- .../metric_vis/public/metric_vis.js | 1 + .../public/metric_vis_controller.js | 87 ++-- .../__tests__/region_map_visualization.js | 42 +- .../public/region_map_visualization.js | 17 +- .../public/__tests__/_table_vis_controller.js | 28 +- .../table_vis/public/table_vis.js | 1 + .../__tests__/tag_cloud_visualization.js | 38 +- .../tagcloud/public/tag_cloud_vis.js | 3 +- .../public/tag_cloud_visualization.js | 13 +- .../public/coordinatemap_response_handler.js | 4 +- .../point_series/__tests__/_fake_x_aspect.js | 12 +- .../point_series/__tests__/_get_aspects.js | 12 +- .../point_series/__tests__/_get_point.js | 12 +- .../point_series/__tests__/_get_series.js | 25 +- .../point_series/__tests__/_init_x_axis.js | 26 +- .../point_series/__tests__/_init_y_axis.js | 14 +- .../point_series/__tests__/_main.js | 9 +- .../__tests__/_ordered_date_axis.js | 6 +- .../point_series/__tests__/point_series.js | 21 +- .../point_series/_fake_x_aspect.js | 7 +- .../agg_response/point_series/_get_aspects.js | 4 +- .../agg_response/point_series/_get_point.js | 8 +- .../agg_response/point_series/_get_series.js | 10 +- .../agg_response/point_series/_init_x_axis.js | 12 +- .../agg_response/point_series/_init_y_axis.js | 12 +- .../point_series/_ordered_date_axis.js | 2 +- .../agg_response/point_series/point_series.js | 2 +- .../tabify/__tests__/_get_columns.js | 8 +- .../tabify/__tests__/_integration.js | 175 ++----- .../tabify/__tests__/_response_writer.js | 434 ++++-------------- .../agg_response/tabify/__tests__/_table.js | 94 ---- .../tabify/__tests__/_table_group.js | 32 -- .../agg_response/tabify/__tests__/tabify.js | 2 - .../agg_response/tabify/_get_columns.js | 23 +- .../agg_response/tabify/_response_writer.js | 284 ++---------- src/ui/public/agg_response/tabify/_table.js | 53 --- .../agg_response/tabify/_table_group.js | 39 -- src/ui/public/agg_response/tabify/tabify.js | 66 +-- src/ui/public/agg_table/__tests__/_group.js | 12 +- src/ui/public/agg_table/__tests__/_table.js | 69 +-- src/ui/public/agg_table/agg_table.js | 4 +- .../agg_types/__tests__/metrics/median.js | 3 +- .../__tests__/metrics/parent_pipeline.js | 3 +- .../__tests__/metrics/sibling_pipeline.js | 3 +- .../agg_types/__tests__/metrics/top_hit.js | 3 +- src/ui/public/vis/__tests__/_vis.js | 3 +- .../response_handlers/_build_chart_data.js | 9 +- .../vis/__tests__/response_handlers/basic.js | 4 +- .../__tests__/vis_types/vislib_vis_type.js | 4 +- src/ui/public/vis/map/convert_to_geojson.js | 29 +- src/ui/public/vis/request_handlers/courier.js | 13 +- src/ui/public/vis/response_handlers/legacy.js | 110 +++++ src/ui/public/vis/response_handlers/tabify.js | 8 +- .../response_handlers/{basic.js => vislib.js} | 43 +- src/ui/public/vis/vis.js | 16 +- .../public/vis/vis_types/vislib_vis_type.js | 3 +- src/ui/public/vislib/lib/handler.js | 13 +- .../components/visualization.test.js | 9 +- .../visualize/components/visualization.tsx | 5 +- .../components/visualization_noresults.tsx | 16 +- src/ui/ui_exports/ui_export_defaults.js | 3 +- 66 files changed, 684 insertions(+), 1377 deletions(-) delete mode 100644 src/ui/public/agg_response/tabify/__tests__/_table.js delete mode 100644 src/ui/public/agg_response/tabify/__tests__/_table_group.js delete mode 100644 src/ui/public/agg_response/tabify/_table.js delete mode 100644 src/ui/public/agg_response/tabify/_table_group.js create mode 100644 src/ui/public/vis/response_handlers/legacy.js rename src/ui/public/vis/response_handlers/{basic.js => vislib.js} (66%) diff --git a/src/core_plugins/kbn_vislib_vis_types/public/gauge.js b/src/core_plugins/kbn_vislib_vis_types/public/gauge.js index bcf5e3f6850c7..fae7248e82e1c 100644 --- a/src/core_plugins/kbn_vislib_vis_types/public/gauge.js +++ b/src/core_plugins/kbn_vislib_vis_types/public/gauge.js @@ -110,6 +110,7 @@ export default function GaugeVisType(Private) { aggFilter: ['!geohash_grid', '!filter'] } ]) - } + }, + useCustomNoDataScreen: true }); } diff --git a/src/core_plugins/kbn_vislib_vis_types/public/goal.js b/src/core_plugins/kbn_vislib_vis_types/public/goal.js index 5d94ebb0f5d4b..2ce74fd4d9812 100644 --- a/src/core_plugins/kbn_vislib_vis_types/public/goal.js +++ b/src/core_plugins/kbn_vislib_vis_types/public/goal.js @@ -105,6 +105,7 @@ export default function GoalVisType(Private) { aggFilter: ['!geohash_grid', '!filter'] } ]) - } + }, + useCustomNoDataScreen: true }); } diff --git a/src/core_plugins/kibana/public/discover/controllers/discover.js b/src/core_plugins/kibana/public/discover/controllers/discover.js index d449bdcfef832..b2b1ef1bbe152 100644 --- a/src/core_plugins/kibana/public/discover/controllers/discover.js +++ b/src/core_plugins/kibana/public/discover/controllers/discover.js @@ -35,7 +35,7 @@ import 'ui/query_bar'; import { hasSearchStategyForIndexPattern, isDefaultTypeIndexPattern } from 'ui/courier'; import { toastNotifications } from 'ui/notify'; import { VisProvider } from 'ui/vis'; -import { BasicResponseHandlerProvider } from 'ui/vis/response_handlers/basic'; +import { VislibResponseHandlerProvider } from 'ui/vis/response_handlers/vislib'; import { DocTitleProvider } from 'ui/doc_title'; import PluginsKibanaDiscoverHitSortFnProvider from '../_hit_sort_fn'; import { FilterBarQueryFilterProvider } from 'ui/filter_bar/query_filter'; @@ -156,7 +156,7 @@ function discoverController( const docTitle = Private(DocTitleProvider); const HitSortFn = Private(PluginsKibanaDiscoverHitSortFnProvider); const queryFilter = Private(FilterBarQueryFilterProvider); - const responseHandler = Private(BasicResponseHandlerProvider).handler; + const responseHandler = Private(VislibResponseHandlerProvider).handler; const filterManager = Private(FilterManagerProvider); const notify = new Notifier({ location: 'Discover' diff --git a/src/core_plugins/metric_vis/public/__tests__/metric_vis.js b/src/core_plugins/metric_vis/public/__tests__/metric_vis.js index 10b16f0652c5b..5ec32b480ad3f 100644 --- a/src/core_plugins/metric_vis/public/__tests__/metric_vis.js +++ b/src/core_plugins/metric_vis/public/__tests__/metric_vis.js @@ -25,7 +25,7 @@ import { VisProvider } from 'ui/vis'; import LogstashIndexPatternStubProvider from 'fixtures/stubbed_logstash_index_pattern'; import MetricVisProvider from '../metric_vis'; -describe('metric_vis', () => { +describe('metric vis', () => { let setup = null; let vis; @@ -62,10 +62,8 @@ describe('metric_vis', () => { const ip = '235.195.237.208'; render({ - tables: [{ - columns: [{ title: 'ip', aggConfig: vis.aggs[0] }], - rows: [[ ip ]] - }] + columns: [{ id: 'col-0', title: 'ip', aggConfig: vis.aggs[0] }], + rows: [{ 'col-0': ip }] }); const $link = $(el) diff --git a/src/core_plugins/metric_vis/public/__tests__/metric_vis_controller.js b/src/core_plugins/metric_vis/public/__tests__/metric_vis_controller.js index 7ce5cb6ab1824..ec3aa063d939a 100644 --- a/src/core_plugins/metric_vis/public/__tests__/metric_vis_controller.js +++ b/src/core_plugins/metric_vis/public/__tests__/metric_vis_controller.js @@ -20,7 +20,7 @@ import expect from 'expect.js'; import { MetricVisComponent } from '../metric_vis_controller'; -describe('metric vis', function () { +describe('metric vis controller', function () { const vis = { params: { @@ -53,10 +53,8 @@ describe('metric vis', function () { it('should set the metric label and value', function () { const metrics = metricController._processTableGroups({ - tables: [{ - columns: [{ title: 'Count', aggConfig: { ...aggConfig, makeLabel: () => 'Count' } }], - rows: [[ 4301021 ]] - }] + columns: [{ id: 'col-0', title: 'Count', aggConfig: { ...aggConfig, makeLabel: () => 'Count' } }], + rows: [{ 'col-0': 4301021 }] }); expect(metrics.length).to.be(1); @@ -66,13 +64,11 @@ describe('metric vis', function () { it('should support multi-value metrics', function () { const metrics = metricController._processTableGroups({ - tables: [{ - columns: [ - { aggConfig: { ...aggConfig, makeLabel: () => '1st percentile of bytes' } }, - { aggConfig: { ...aggConfig, makeLabel: () => '99th percentile of bytes' } } - ], - rows: [[ 182, 445842.4634666484 ]] - }] + columns: [ + { id: 'col-0', aggConfig: { ...aggConfig, makeLabel: () => '1st percentile of bytes' } }, + { id: 'col-1', aggConfig: { ...aggConfig, makeLabel: () => '99th percentile of bytes' } } + ], + rows: [{ 'col-0': 182, 'col-1': 445842.4634666484 }] }); expect(metrics.length).to.be(2); diff --git a/src/core_plugins/metric_vis/public/metric_vis.js b/src/core_plugins/metric_vis/public/metric_vis.js index 4d25bad420da2..c4cbd0c11716e 100644 --- a/src/core_plugins/metric_vis/public/metric_vis.js +++ b/src/core_plugins/metric_vis/public/metric_vis.js @@ -100,6 +100,7 @@ function MetricVisProvider(Private) { } ]) }, + responseHandler: 'tabify', }); } diff --git a/src/core_plugins/metric_vis/public/metric_vis_controller.js b/src/core_plugins/metric_vis/public/metric_vis_controller.js index fa9adb7885ced..abd6d63cc8ec4 100644 --- a/src/core_plugins/metric_vis/public/metric_vis_controller.js +++ b/src/core_plugins/metric_vis/public/metric_vis_controller.js @@ -89,7 +89,7 @@ export class MetricVisComponent extends Component { return fieldFormatter(value); } - _processTableGroups(tableGroups) { + _processTableGroups(table) { const config = this.props.vis.params.metric; const isPercentageMode = config.percentageMode; const min = config.colorsRange[0].from; @@ -98,56 +98,55 @@ export class MetricVisComponent extends Component { const labels = this._getLabels(); const metrics = []; - tableGroups.tables.forEach((table, tableIndex) => { - let bucketAgg; - let rowHeaderIndex; + let bucketAgg; + let bucketColumnId; + let rowHeaderIndex; - table.columns.forEach((column, columnIndex) => { - const aggConfig = column.aggConfig; + table.columns.forEach((column, columnIndex) => { + const aggConfig = column.aggConfig; - if (aggConfig && aggConfig.type.type === 'buckets') { - bucketAgg = aggConfig; - // Store the current index, so we later know in which position in the - // row array, the bucket agg key will be, so we can create filters on it. - rowHeaderIndex = columnIndex; - return; - } + if (aggConfig && aggConfig.type.type === 'buckets') { + bucketAgg = aggConfig; + // Store the current index, so we later know in which position in the + // row array, the bucket agg key will be, so we can create filters on it. + rowHeaderIndex = columnIndex; + bucketColumnId = column.id; + return; + } - table.rows.forEach((row, rowIndex) => { + table.rows.forEach((row, rowIndex) => { - let title = column.title; - let value = row[columnIndex]; - const color = this._getColor(value, labels, colors); + let title = column.name; + let value = row[column.id]; + const color = this._getColor(value, labels, colors); - if (isPercentageMode) { - const percentage = Math.round(100 * (value - min) / (max - min)); - value = `${percentage}%`; - } + if (isPercentageMode) { + const percentage = Math.round(100 * (value - min) / (max - min)); + value = `${percentage}%`; + } - if (aggConfig) { - if (!isPercentageMode) value = this._getFormattedValue(aggConfig.fieldFormatter('html'), value); - if (bucketAgg) { - const bucketValue = bucketAgg.fieldFormatter('text')(row[0]); - title = `${bucketValue} - ${aggConfig.makeLabel()}`; - } else { - title = aggConfig.makeLabel(); - } + if (aggConfig) { + if (!isPercentageMode) value = this._getFormattedValue(aggConfig.fieldFormatter('html'), value); + if (bucketAgg) { + const bucketValue = bucketAgg.fieldFormatter('text')(row[bucketColumnId]); + title = `${bucketValue} - ${aggConfig.makeLabel()}`; + } else { + title = aggConfig.makeLabel(); } + } - const shouldColor = config.colorsRange.length > 1; - - metrics.push({ - label: title, - value: value, - color: shouldColor && config.style.labelColor ? color : null, - bgColor: shouldColor && config.style.bgColor ? color : null, - lightText: shouldColor && config.style.bgColor && this._needsLightText(color), - filterKey: rowHeaderIndex !== undefined ? row[rowHeaderIndex] : null, - tableIndex: tableIndex, - rowIndex: rowIndex, - columnIndex: rowHeaderIndex, - bucketAgg: bucketAgg, - }); + const shouldColor = config.colorsRange.length > 1; + + metrics.push({ + label: title, + value: value, + color: shouldColor && config.style.labelColor ? color : null, + bgColor: shouldColor && config.style.bgColor ? color : null, + lightText: shouldColor && config.style.bgColor && this._needsLightText(color), + filterKey: bucketColumnId !== undefined ? row[bucketColumnId] : null, + rowIndex: rowIndex, + columnIndex: rowHeaderIndex, + bucketAgg: bucketAgg, }); }); }); @@ -159,7 +158,7 @@ export class MetricVisComponent extends Component { if (!metric.filterKey || !metric.bucketAgg) { return; } - const table = this.props.visData.tables[metric.tableIndex]; + const table = this.props.visData; this.props.vis.API.events.addFilter(table, metric.columnIndex, metric.rowIndex); }; diff --git a/src/core_plugins/region_map/public/__tests__/region_map_visualization.js b/src/core_plugins/region_map/public/__tests__/region_map_visualization.js index b0acdb85fddcf..7f3064b99380f 100644 --- a/src/core_plugins/region_map/public/__tests__/region_map_visualization.js +++ b/src/core_plugins/region_map/public/__tests__/region_map_visualization.js @@ -108,22 +108,26 @@ describe('RegionMapsVisualizationTests', function () { const _makeJsonAjaxCallOld = ChoroplethLayer.prototype._makeJsonAjaxCall; const dummyTableGroup = { - tables: [ - { - columns: [{ - 'aggConfig': { - 'id': '2', - 'enabled': true, - 'type': 'terms', - 'schema': 'segment', - 'params': { 'field': 'geo.dest', 'size': 5, 'order': 'desc', 'orderBy': '1' } - }, 'title': 'geo.dest: Descending' - }, { - 'aggConfig': { 'id': '1', 'enabled': true, 'type': 'count', 'schema': 'metric', 'params': {} }, - 'title': 'Count' - }], - rows: [['CN', 26], ['IN', 17], ['US', 6], ['DE', 4], ['BR', 3]] - } + columns: [{ + 'id': 'col-0', + 'aggConfig': { + 'id': '2', + 'enabled': true, + 'type': 'terms', + 'schema': 'segment', + 'params': { 'field': 'geo.dest', 'size': 5, 'order': 'desc', 'orderBy': '1' } + }, 'title': 'geo.dest: Descending' + }, { + 'id': 'col-1', + 'aggConfig': { 'id': '1', 'enabled': true, 'type': 'count', 'schema': 'metric', 'params': {} }, + 'title': 'Count' + }], + rows: [ + { 'col-0': 'CN', 'col-1': 26 }, + { 'col-0': 'IN', 'col-1': 17 }, + { 'col-0': 'US', 'col-1': 6 }, + { 'col-0': 'DE', 'col-1': 4 }, + { 'col-0': 'BR', 'col-1': 3 } ] }; @@ -293,7 +297,7 @@ describe('RegionMapsVisualizationTests', function () { }); const newTableGroup = _.cloneDeep(dummyTableGroup); - newTableGroup.tables[0].rows.pop();//remove one shape + newTableGroup.rows.pop();//remove one shape await regionMapsVisualization.render(newTableGroup, { resize: false, @@ -306,7 +310,7 @@ describe('RegionMapsVisualizationTests', function () { const anotherTableGroup = _.cloneDeep(newTableGroup); - anotherTableGroup.tables[0].rows.pop();//remove one shape + anotherTableGroup.rows.pop();//remove one shape domNode.style.width = '412px'; domNode.style.height = '112px'; await regionMapsVisualization.render(anotherTableGroup, { @@ -336,7 +340,7 @@ describe('RegionMapsVisualizationTests', function () { }); const newTableGroup = _.cloneDeep(dummyTableGroup); - newTableGroup.tables[0].rows.pop();//remove one shape + newTableGroup.rows.pop();//remove one shape vis.params.colorSchema = 'Blues'; await regionMapsVisualization.render(newTableGroup, { resize: false, diff --git a/src/core_plugins/region_map/public/region_map_visualization.js b/src/core_plugins/region_map/public/region_map_visualization.js index 881bfd4ea8dc6..fd2be9aef49dc 100644 --- a/src/core_plugins/region_map/public/region_map_visualization.js +++ b/src/core_plugins/region_map/public/region_map_visualization.js @@ -46,14 +46,17 @@ export function RegionMapsVisualizationProvider(Private, config) { } } - async _updateData(tableGroup) { - this._chartData = tableGroup; + async _updateData(table) { + this._chartData = table; let results; - if (!tableGroup || !tableGroup.tables || !tableGroup.tables.length || tableGroup.tables[0].columns.length !== 2) { + if (!table || !table.rows.length || table.columns.length !== 2) { results = []; } else { - const buckets = tableGroup.tables[0].rows; - results = buckets.map(([term, value]) => { + const termColumn = table.columns[0].id; + const valueColumn = table.columns[1].id; + results = table.rows.map(row => { + const term = row[termColumn]; + const value = row[valueColumn]; return { term: term, value: value }; }); } @@ -150,8 +153,8 @@ export function RegionMapsVisualizationProvider(Private, config) { return; } - const rowIndex = this._chartData.tables[0].rows.findIndex(row => row[0] === event); - this._vis.API.events.addFilter(this._chartData.tables[0], 0, rowIndex, event); + const rowIndex = this._chartData.rows.findIndex(row => row[0] === event); + this._vis.API.events.addFilter(this._chartData, 0, rowIndex, event); }); this._choroplethLayer.on('styleChanged', (event) => { diff --git a/src/core_plugins/table_vis/public/__tests__/_table_vis_controller.js b/src/core_plugins/table_vis/public/__tests__/_table_vis_controller.js index d13075ed2c043..acdb2d1b6aa08 100644 --- a/src/core_plugins/table_vis/public/__tests__/_table_vis_controller.js +++ b/src/core_plugins/table_vis/public/__tests__/_table_vis_controller.js @@ -21,7 +21,7 @@ import $ from 'jquery'; import _ from 'lodash'; import expect from 'expect.js'; import ngMock from 'ng_mock'; -import { tabifyAggResponse } from 'ui/agg_response/tabify/tabify'; +import { LegacyResponseHandlerProvider } from 'ui/vis/response_handlers/legacy'; import { VisProvider } from 'ui/vis'; import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; import { AppStateProvider } from 'ui/state_management/app_state'; @@ -35,6 +35,7 @@ describe('Table Vis Controller', function () { let Vis; let fixtures; let AppState; + let tableAggResponse; beforeEach(ngMock.module('kibana', 'kibana/table_vis')); beforeEach(ngMock.inject(function ($injector) { @@ -44,6 +45,7 @@ describe('Table Vis Controller', function () { fixtures = require('fixtures/fake_hierarchical_data'); AppState = Private(AppStateProvider); Vis = Private(VisProvider); + tableAggResponse = Private(LegacyResponseHandlerProvider).handler; })); function OneRangeVis(params) { @@ -99,16 +101,14 @@ describe('Table Vis Controller', function () { $rootScope.$apply(); } - it('exposes #tableGroups and #hasSomeRows when a response is attached to scope', function () { + it('exposes #tableGroups and #hasSomeRows when a response is attached to scope', async function () { const vis = new OneRangeVis(); initController(vis); expect(!$scope.tableGroups).to.be.ok(); expect(!$scope.hasSomeRows).to.be.ok(); - attachEsResponseToScope(tabifyAggResponse(vis.getAggConfig(), fixtures.oneRangeBucket, { - isHierarchical: vis.isHierarchical() - })); + attachEsResponseToScope(await tableAggResponse(vis, fixtures.oneRangeBucket)); expect($scope.hasSomeRows).to.be(true); expect($scope.tableGroups).to.have.property('tables'); @@ -117,20 +117,18 @@ describe('Table Vis Controller', function () { expect($scope.tableGroups.tables[0].rows).to.have.length(2); }); - it('clears #tableGroups and #hasSomeRows when the response is removed', function () { + it('clears #tableGroups and #hasSomeRows when the response is removed', async function () { const vis = new OneRangeVis(); initController(vis); - attachEsResponseToScope(tabifyAggResponse(vis.getAggConfig(), fixtures.oneRangeBucket, { - isHierarchical: vis.isHierarchical() - })); + attachEsResponseToScope(await tableAggResponse(vis, fixtures.oneRangeBucket)); removeEsResponseFromScope(); expect(!$scope.hasSomeRows).to.be.ok(); expect(!$scope.tableGroups).to.be.ok(); }); - it('sets the sort on the scope when it is passed as a vis param', function () { + it('sets the sort on the scope when it is passed as a vis param', async function () { const sortObj = { columnIndex: 1, direction: 'asc' @@ -142,15 +140,13 @@ describe('Table Vis Controller', function () { const resp = _.cloneDeep(fixtures.oneRangeBucket); resp.aggregations.agg_2.buckets = {}; - attachEsResponseToScope(tabifyAggResponse(vis.getAggConfig(), resp, { - isHierarchical: vis.isHierarchical() - })); + attachEsResponseToScope(await tableAggResponse(vis, resp)); expect($scope.sort.columnIndex).to.equal(sortObj.columnIndex); expect($scope.sort.direction).to.equal(sortObj.direction); }); - it('sets #hasSomeRows properly if the table group is empty', function () { + it('sets #hasSomeRows properly if the table group is empty', async function () { const vis = new OneRangeVis(); initController(vis); @@ -158,9 +154,7 @@ describe('Table Vis Controller', function () { const resp = _.cloneDeep(fixtures.oneRangeBucket); resp.aggregations.agg_2.buckets = {}; - attachEsResponseToScope(tabifyAggResponse(vis.getAggConfig(), resp, { - isHierarchical: vis.isHierarchical() - })); + attachEsResponseToScope(await tableAggResponse(vis, resp)); expect($scope.hasSomeRows).to.be(false); expect(!$scope.tableGroups).to.be.ok(); diff --git a/src/core_plugins/table_vis/public/table_vis.js b/src/core_plugins/table_vis/public/table_vis.js index 5d430b71e26f6..05cc5f4c3aab5 100644 --- a/src/core_plugins/table_vis/public/table_vis.js +++ b/src/core_plugins/table_vis/public/table_vis.js @@ -96,6 +96,7 @@ function TableVisTypeProvider(Private) { } ]) }, + responseHandler: 'legacy', responseHandlerConfig: { asAggConfigResults: true }, diff --git a/src/core_plugins/tagcloud/public/__tests__/tag_cloud_visualization.js b/src/core_plugins/tagcloud/public/__tests__/tag_cloud_visualization.js index 7a8db667f7d56..d37c19786b69b 100644 --- a/src/core_plugins/tagcloud/public/__tests__/tag_cloud_visualization.js +++ b/src/core_plugins/tagcloud/public/__tests__/tag_cloud_visualization.js @@ -39,23 +39,27 @@ describe('TagCloudVisualizationTest', function () { let imageComparator; const dummyTableGroup = { - tables: [ - { - columns: [{ - 'aggConfig': { - 'id': '2', - 'enabled': true, - 'type': 'terms', - 'schema': 'segment', - 'params': { 'field': 'geo.dest', 'size': 5, 'order': 'desc', 'orderBy': '1' }, - fieldFormatter: () => (x => x) - }, 'title': 'geo.dest: Descending' - }, { - 'aggConfig': { 'id': '1', 'enabled': true, 'type': 'count', 'schema': 'metric', 'params': {} }, - 'title': 'Count' - }], - rows: [['CN', 26], ['IN', 17], ['US', 6], ['DE', 4], ['BR', 3]] - } + columns: [{ + id: 'col-0', + 'aggConfig': { + 'id': '2', + 'enabled': true, + 'type': 'terms', + 'schema': 'segment', + 'params': { 'field': 'geo.dest', 'size': 5, 'order': 'desc', 'orderBy': '1' }, + fieldFormatter: () => (x => x) + }, 'title': 'geo.dest: Descending' + }, { + id: 'col-1', + 'aggConfig': { 'id': '1', 'enabled': true, 'type': 'count', 'schema': 'metric', 'params': {} }, + 'title': 'Count' + }], + rows: [ + { 'col-0': 'CN', 'col-1': 26 }, + { 'col-0': 'IN', 'col-1': 17 }, + { 'col-0': 'US', 'col-1': 6 }, + { 'col-0': 'DE', 'col-1': 4 }, + { 'col-0': 'BR', 'col-1': 3 } ] }; diff --git a/src/core_plugins/tagcloud/public/tag_cloud_vis.js b/src/core_plugins/tagcloud/public/tag_cloud_vis.js index ee6aed7b6868b..4ac7d92efdf47 100644 --- a/src/core_plugins/tagcloud/public/tag_cloud_vis.js +++ b/src/core_plugins/tagcloud/public/tag_cloud_vis.js @@ -77,6 +77,7 @@ VisTypesRegistryProvider.register(function (Private) { aggFilter: ['terms', 'significant_terms'] } ]) - } + }, + useCustomNoDataScreen: true }); }); diff --git a/src/core_plugins/tagcloud/public/tag_cloud_visualization.js b/src/core_plugins/tagcloud/public/tag_cloud_visualization.js index e625caa13394f..251673ca30cae 100644 --- a/src/core_plugins/tagcloud/public/tag_cloud_visualization.js +++ b/src/core_plugins/tagcloud/public/tag_cloud_visualization.js @@ -110,13 +110,12 @@ export class TagCloudVisualization { } - _updateData(response) { - if (!response || !response.tables.length) { + _updateData(data) { + if (!data || !data.rows.length) { this._tagCloud.setData([]); return; } - const data = response.tables[0]; const segmentAggs = this._vis.aggs.bySchemaName.segment; if (segmentAggs && segmentAggs.length > 0) { this._bucketAgg = segmentAggs[0]; @@ -124,12 +123,16 @@ export class TagCloudVisualization { this._bucketAgg = null; } + const hasTags = data.columns.length === 2; + const tagColumn = hasTags ? data.columns[0].id : -1; + const metricColumn = data.columns[hasTags ? 1 : 0].id; const tags = data.rows.map((row, rowIndex) => { - const [tag, count] = row; + const tag = row[tagColumn] || 'all'; + const metric = row[metricColumn]; return { displayText: this._bucketAgg ? this._bucketAgg.fieldFormatter()(tag) : tag, rawText: tag, - value: count, + value: metric, meta: { data: data, rowIndex: rowIndex, diff --git a/src/core_plugins/tile_map/public/coordinatemap_response_handler.js b/src/core_plugins/tile_map/public/coordinatemap_response_handler.js index b99cef25104bb..f742a651ed6a8 100644 --- a/src/core_plugins/tile_map/public/coordinatemap_response_handler.js +++ b/src/core_plugins/tile_map/public/coordinatemap_response_handler.js @@ -34,9 +34,7 @@ export function makeGeoJsonResponseHandler() { //double conversion, first to table, then to geojson //This is to future-proof this code for Canvas-refactoring - const tabifiedResponse = tabifyAggResponse(vis.getAggConfig(), esResponse, { - asAggConfigResults: false - }); + const tabifiedResponse = tabifyAggResponse(vis.getAggConfig(), esResponse); lastGeoJsonResponse = convertToGeoJson(tabifiedResponse); return lastGeoJsonResponse; diff --git a/src/ui/public/agg_response/point_series/__tests__/_fake_x_aspect.js b/src/ui/public/agg_response/point_series/__tests__/_fake_x_aspect.js index 9475b564d92cc..ecd374871fe73 100644 --- a/src/ui/public/agg_response/point_series/__tests__/_fake_x_aspect.js +++ b/src/ui/public/agg_response/point_series/__tests__/_fake_x_aspect.js @@ -43,22 +43,18 @@ describe('makeFakeXAspect', function () { expect(aspect) .to.have.property('i', -1) - .and.have.property('agg') - .and.have.property('col'); + .and.have.property('aggConfig') + .and.have.property('title'); - expect(aspect.agg) + expect(aspect.aggConfig) .to.be.an(AggConfig) .and.to.have.property('type'); - expect(aspect.agg.type) + expect(aspect.aggConfig.type) .to.be.an(AggType) .and.to.have.property('name', 'all') .and.to.have.property('title', 'All docs') .and.to.have.property('hasNoDsl', true); - expect(aspect.col) - .to.be.an('object') - .and.to.have.property('aggConfig', aspect.agg) - .and.to.have.property('label', aspect.agg.makeLabel()); }); }); 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 7338cd781239b..8dabce275d507 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 @@ -58,8 +58,7 @@ describe('getAspects', function () { expect(aspect) .to.be.an('object') .and.have.property('i', i) - .and.have.property('agg', vis.aggs[i]) - .and.have.property('col', table.columns[i]); + .and.have.property('aggConfig', vis.aggs[i]); } function init(group, x, y) { @@ -150,13 +149,10 @@ describe('getAspects', function () { expect(aspects.x) .to.be.an('object') .and.have.property('i', -1) - .and.have.property('agg') - .and.have.property('col'); + .and.have.property('aggConfig') + .and.have.property('title'); - expect(aspects.x.agg).to.be.an(AggConfig); - expect(aspects.x.col) - .to.be.an('object') - .and.to.have.property('aggConfig', aspects.x.agg); + expect(aspects.x.aggConfig).to.be.an(AggConfig); }); }); 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 db2d7e5e1171e..92a1fc871e694 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 @@ -37,15 +37,13 @@ describe('getPoint', function () { describe('Without series aspect', function () { let seriesAspect; let xAspect; - let yCol; let yAspect; let yScale; beforeEach(function () { seriesAspect = null; xAspect = { i: 0 }; - yCol = { title: 'Y', aggConfig: {} }; - yAspect = { i: 1, col: yCol }; + yAspect = { i: 1, title: 'Y', aggConfig: {} }; yScale = 5; }); @@ -58,7 +56,7 @@ describe('getPoint', function () { .to.have.property('x', 1) .and.have.property('y', 10) .and.have.property('z', 3) - .and.have.property('series', yCol.title) + .and.have.property('series', yAspect.title) .and.have.property('aggConfigResult', row[1]); }); @@ -83,7 +81,7 @@ describe('getPoint', function () { }); it('properly unwraps and scales values', function () { - const seriesAspect = { i: 1, agg: identFormatted }; + const seriesAspect = { i: 1, aggConfig: identFormatted }; const point = getPoint(xAspect, seriesAspect, yScale, row, yAspect); expect(point) @@ -94,7 +92,7 @@ describe('getPoint', function () { }); it('properly formats series values', function () { - const seriesAspect = { i: 1, agg: truthFormatted }; + const seriesAspect = { i: 1, aggConfig: truthFormatted }; const point = getPoint(xAspect, seriesAspect, yScale, row, yAspect); expect(point) @@ -105,7 +103,7 @@ describe('getPoint', function () { }); it ('adds the aggConfig to the points', function () { - const seriesAspect = { i: 1, agg: truthFormatted }; + const seriesAspect = { i: 1, aggConfig: truthFormatted }; const point = getPoint(xAspect, seriesAspect, yScale, row, yAspect); expect(point).to.have.property('aggConfig', truthFormatted); diff --git a/src/ui/public/agg_response/point_series/__tests__/_get_series.js b/src/ui/public/agg_response/point_series/__tests__/_get_series.js index 0823f07d61143..67c1522299253 100644 --- a/src/ui/public/agg_response/point_series/__tests__/_get_series.js +++ b/src/ui/public/agg_response/point_series/__tests__/_get_series.js @@ -47,11 +47,10 @@ describe('getSeries', function () { [1, 2, 3] ].map(wrapRows); - const yCol = { aggConfig: {}, title: 'y' }; const chart = { aspects: { x: { i: 0 }, - y: { i: 1, col: yCol, agg: { id: 'id' } }, + y: { i: 1, title: 'y', aggConfig: { id: 'id' } }, z: { i: 2 } } }; @@ -65,7 +64,7 @@ describe('getSeries', function () { const siri = series[0]; expect(siri) .to.be.an('object') - .and.have.property('label', yCol.title) + .and.have.property('label', chart.aspects.y.title) .and.have.property('values'); expect(siri.values) @@ -93,8 +92,8 @@ describe('getSeries', function () { aspects: { x: { i: 0 }, y: [ - { i: 1, col: { title: '0' }, agg: { id: 1 } }, - { i: 2, col: { title: '1' }, agg: { id: 2 } }, + { i: 1, title: '0', aggConfig: { id: 1 } }, + { i: 2, title: '1', aggConfig: { id: 2 } }, ] } }; @@ -138,8 +137,8 @@ describe('getSeries', function () { const chart = { aspects: { x: { i: -1 }, - series: { i: 0, agg: agg }, - y: { i: 1, col: { title: '0' }, agg: agg } + series: { i: 0, aggConfig: agg }, + y: { i: 1, title: '0', aggConfig: agg } } }; @@ -180,10 +179,10 @@ describe('getSeries', function () { const chart = { aspects: { x: { i: -1 }, - series: { i: 0, agg: agg }, + series: { i: 0, aggConfig: agg }, y: [ - { i: 1, col: { title: '0' }, agg: { id: 1 } }, - { i: 2, col: { title: '1' }, agg: { id: 2 } } + { i: 1, title: '0', aggConfig: { id: 1 } }, + { i: 2, title: '1', aggConfig: { id: 2 } } ] } }; @@ -230,10 +229,10 @@ describe('getSeries', function () { const chart = { aspects: { x: { i: -1 }, - series: { i: 0, agg: agg }, + series: { i: 0, aggConfig: agg }, y: [ - { i: 1, col: { title: '0' }, agg: { id: 1 } }, - { i: 2, col: { title: '1' }, agg: { id: 2 } } + { i: 1, title: '0', aggConfig: { id: 1 } }, + { i: 2, title: '1', aggConfig: { id: 2 } } ] } }; diff --git a/src/ui/public/agg_response/point_series/__tests__/_init_x_axis.js b/src/ui/public/agg_response/point_series/__tests__/_init_x_axis.js index 48beab766df44..0389a393ad15a 100644 --- a/src/ui/public/agg_response/point_series/__tests__/_init_x_axis.js +++ b/src/ui/public/agg_response/point_series/__tests__/_init_x_axis.js @@ -34,14 +34,12 @@ describe('initXAxis', function () { const baseChart = { aspects: { x: { - agg: { + aggConfig: { fieldFormatter: _.constant({}), write: _.constant({ params: {} }), type: {} }, - col: { - title: 'label' - } + title: 'label' } } }; @@ -53,23 +51,23 @@ describe('initXAxis', function () { initXAxis(chart); expect(chart) .to.have.property('xAxisLabel', 'label') - .and.have.property('xAxisFormatter', chart.aspects.x.agg.fieldFormatter()); + .and.have.property('xAxisFormatter', chart.aspects.x.aggConfig.fieldFormatter()); }); it('makes the chart ordered if the agg is ordered', function () { const chart = _.cloneDeep(baseChart); - chart.aspects.x.agg.type.ordered = true; - chart.aspects.x.agg.params = { + chart.aspects.x.aggConfig.type.ordered = true; + chart.aspects.x.aggConfig.params = { field: field }; - chart.aspects.x.agg.vis = { + chart.aspects.x.aggConfig.vis = { indexPattern: indexPattern }; initXAxis(chart); expect(chart) .to.have.property('xAxisLabel', 'label') - .and.have.property('xAxisFormatter', chart.aspects.x.agg.fieldFormatter()) + .and.have.property('xAxisFormatter', chart.aspects.x.aggConfig.fieldFormatter()) .and.have.property('indexPattern', indexPattern) .and.have.property('xAxisField', field) .and.have.property('ordered'); @@ -81,19 +79,19 @@ describe('initXAxis', function () { it('reads the interval param from the x agg', function () { const chart = _.cloneDeep(baseChart); - chart.aspects.x.agg.type.ordered = true; - chart.aspects.x.agg.write = _.constant({ params: { interval: 10 } }); - chart.aspects.x.agg.params = { + chart.aspects.x.aggConfig.type.ordered = true; + chart.aspects.x.aggConfig.write = _.constant({ params: { interval: 10 } }); + chart.aspects.x.aggConfig.params = { field: field }; - chart.aspects.x.agg.vis = { + chart.aspects.x.aggConfig.vis = { indexPattern: indexPattern }; initXAxis(chart); expect(chart) .to.have.property('xAxisLabel', 'label') - .and.have.property('xAxisFormatter', chart.aspects.x.agg.fieldFormatter()) + .and.have.property('xAxisFormatter', chart.aspects.x.aggConfig.fieldFormatter()) .and.have.property('indexPattern', indexPattern) .and.have.property('xAxisField', field) .and.have.property('ordered'); diff --git a/src/ui/public/agg_response/point_series/__tests__/_init_y_axis.js b/src/ui/public/agg_response/point_series/__tests__/_init_y_axis.js index 507bd01b88fa2..d8e06f2b355a2 100644 --- a/src/ui/public/agg_response/point_series/__tests__/_init_y_axis.js +++ b/src/ui/public/agg_response/point_series/__tests__/_init_y_axis.js @@ -42,12 +42,12 @@ describe('initYAxis', function () { const baseChart = { aspects: { y: [ - { agg: agg(), col: { title: 'y1' } }, - { agg: agg(), col: { title: 'y2' } }, + { aggConfig: agg(), title: 'y1' }, + { aggConfig: agg(), title: 'y2' }, ], x: { - agg: agg(), - col: { title: 'x' } + aggConfig: agg(), + title: 'x' } } }; @@ -59,7 +59,7 @@ describe('initYAxis', function () { it('sets the yAxisFormatter the the field formats convert fn', function () { const chart = _.cloneDeep(singleYBaseChart); initYAxis(chart); - expect(chart).to.have.property('yAxisFormatter', chart.aspects.y.agg.fieldFormatter()); + expect(chart).to.have.property('yAxisFormatter', chart.aspects.y.aggConfig.fieldFormatter()); }); it('sets the yAxisLabel', function () { @@ -76,8 +76,8 @@ describe('initYAxis', function () { expect(chart).to.have.property('yAxisFormatter'); expect(chart.yAxisFormatter) - .to.be(chart.aspects.y[0].agg.fieldFormatter()) - .and.not.be(chart.aspects.y[1].agg.fieldFormatter()); + .to.be(chart.aspects.y[0].aggConfig.fieldFormatter()) + .and.not.be(chart.aspects.y[1].aggConfig.fieldFormatter()); }); it('does not set the yAxisLabel, it does not make sense to put multiple labels on the same axis', function () { diff --git a/src/ui/public/agg_response/point_series/__tests__/_main.js b/src/ui/public/agg_response/point_series/__tests__/_main.js index 9cfad3eaa053e..3652e537d73df 100644 --- a/src/ui/public/agg_response/point_series/__tests__/_main.js +++ b/src/ui/public/agg_response/point_series/__tests__/_main.js @@ -23,7 +23,6 @@ import AggConfigResult from '../../../vis/agg_config_result'; import expect from 'expect.js'; import ngMock from 'ng_mock'; import { VisProvider } from '../../../vis'; -import { TabifyTable } from '../../tabify/_table'; import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; import { AggResponsePointSeriesProvider } from '../point_series'; @@ -47,7 +46,7 @@ describe('pointSeriesChartDataFromTable', function () { const agg = vis.aggs[0]; const result = new AggConfigResult(vis.aggs[0], void 0, 100, 100); - const table = new TabifyTable(); + const table = { rows: [] }; table.columns = [ { aggConfig: agg } ]; table.rows.push([ result ]); @@ -86,7 +85,7 @@ describe('pointSeriesChartDataFromTable', function () { }; const rowCount = 3; - const table = new TabifyTable(); + const table = { rows: [] }; table.columns = [ x.col, y.col ]; _.times(rowCount, function (i) { const date = new AggConfigResult(x.agg, void 0, x.at(i)); @@ -147,7 +146,7 @@ describe('pointSeriesChartDataFromTable', function () { }; const rowCount = 3; - const table = new TabifyTable(); + const table = { rows: [] }; table.columns = [ date.col, avg.col, max.col ]; _.times(rowCount, function (i) { const dateResult = new AggConfigResult(date.agg, void 0, date.at(i)); @@ -226,7 +225,7 @@ describe('pointSeriesChartDataFromTable', function () { const metricCount = 2; const rowsPerSegment = 2; const rowCount = extensions.length * rowsPerSegment; - const table = new TabifyTable(); + const table = { rows: [] }; table.columns = [ date.col, term.col, avg.col, max.col ]; _.times(rowCount, function (i) { const dateResult = new AggConfigResult(date.agg, void 0, date.at(i)); diff --git a/src/ui/public/agg_response/point_series/__tests__/_ordered_date_axis.js b/src/ui/public/agg_response/point_series/__tests__/_ordered_date_axis.js index 92eaa98b739fc..611907733e86b 100644 --- a/src/ui/public/agg_response/point_series/__tests__/_ordered_date_axis.js +++ b/src/ui/public/agg_response/point_series/__tests__/_ordered_date_axis.js @@ -35,7 +35,7 @@ describe('orderedDateAxis', function () { chart: { aspects: { x: { - agg: { + aggConfig: { fieldIsTimeField: _.constant(true), buckets: { getScaledDateFormat: _.constant('hh:mm:ss'), @@ -88,7 +88,7 @@ describe('orderedDateAxis', function () { it('relies on agg.buckets for the interval', function () { const args = _.cloneDeep(baseArgs); - const spy = sinon.spy(args.chart.aspects.x.agg.buckets, 'getInterval'); + const spy = sinon.spy(args.chart.aspects.x.aggConfig.buckets, 'getInterval'); orderedDateAxis(args.vis, args.chart); expect(spy).to.have.property('callCount', 1); }); @@ -102,7 +102,7 @@ describe('orderedDateAxis', function () { it('does not set the min/max when the buckets are unbounded', function () { const args = _.cloneDeep(baseArgs); - args.chart.aspects.x.agg.buckets.getBounds = _.constant(); + args.chart.aspects.x.aggConfig.buckets.getBounds = _.constant(); orderedDateAxis(args.vis, args.chart); expect(args.chart.ordered).to.not.have.property('min'); expect(args.chart.ordered).to.not.have.property('max'); diff --git a/src/ui/public/agg_response/point_series/__tests__/point_series.js b/src/ui/public/agg_response/point_series/__tests__/point_series.js index ff56ce1f12bca..a649b7f38fc91 100644 --- a/src/ui/public/agg_response/point_series/__tests__/point_series.js +++ b/src/ui/public/agg_response/point_series/__tests__/point_series.js @@ -17,15 +17,16 @@ * under the License. */ -import './_main'; -import './_add_to_siri'; -import './_fake_x_aspect'; -import './_get_aspects'; -import './_get_point'; -import './_get_series'; -import './_init_x_axis'; -import './_init_y_axis'; -import './_ordered_date_axis'; -import './_tooltip_formatter'; + describe('Point Series Agg Response', function () { + require ('./_main'); + require('./_add_to_siri'); + require('./_fake_x_aspect'); + require('./_get_aspects'); + require('./_get_point'); + require('./_get_series'); + require('./_init_x_axis'); + require('./_init_y_axis'); + require('./_ordered_date_axis'); + require('./_tooltip_formatter'); }); diff --git a/src/ui/public/agg_response/point_series/_fake_x_aspect.js b/src/ui/public/agg_response/point_series/_fake_x_aspect.js index a6088ec45a71d..1fb873dda1f5d 100644 --- a/src/ui/public/agg_response/point_series/_fake_x_aspect.js +++ b/src/ui/public/agg_response/point_series/_fake_x_aspect.js @@ -37,11 +37,8 @@ export function PointSeriesFakeXAxisProvider() { return { i: -1, - agg: fake, - col: { - aggConfig: fake, - label: fake.makeLabel() - } + aggConfig: fake, + title: fake.makeLabel(), }; }; } 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 b1f8539618686..4bb3ab3c8f1cb 100644 --- a/src/ui/public/agg_response/point_series/_get_aspects.js +++ b/src/ui/public/agg_response/point_series/_get_aspects.js @@ -39,8 +39,8 @@ export function PointSeriesGetAspectsProvider(Private) { const aspect = { i: i, - col: col, - agg: col.aggConfig + title: col.title, + aggConfig: col.aggConfig }; if (!aspects[name]) aspects[name] = []; 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 aa914efa7e0a3..2713483e3e342 100644 --- a/src/ui/public/agg_response/point_series/_get_point.js +++ b/src/ui/public/agg_response/point_series/_get_point.js @@ -45,13 +45,13 @@ export function PointSeriesGetPointProvider() { if (series) { const seriesArray = series.length ? series : [ series ]; - point.aggConfig = seriesArray[0].agg; - point.series = seriesArray.map(s => s.agg.fieldFormatter()(unwrap(row[s.i]))).join(' - '); + point.aggConfig = seriesArray[0].aggConfig; + point.series = seriesArray.map(s => s.aggConfig.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 - point.aggConfig = y.col.aggConfig; - point.series = y.col.title; + point.aggConfig = y.aggConfig; + point.series = y.title; } if (yScale) { diff --git a/src/ui/public/agg_response/point_series/_get_series.js b/src/ui/public/agg_response/point_series/_get_series.js index 24ac1a589f53b..31a40ede7e3ef 100644 --- a/src/ui/public/agg_response/point_series/_get_series.js +++ b/src/ui/public/agg_response/point_series/_get_series.js @@ -35,7 +35,7 @@ export function PointSeriesGetSeriesProvider(Private) { .transform(function (series, row) { if (!multiY) { const point = partGetPoint(row, aspects.y, aspects.z); - if (point) addToSiri(series, point, point.series, point.series, aspects.y.agg); + if (point) addToSiri(series, point, point.series, point.series, aspects.y.aggConfig); return; } @@ -46,8 +46,8 @@ export function PointSeriesGetSeriesProvider(Private) { // use the point's y-axis as it's series by default, // but augment that with series aspect if it's actually // available - let seriesId = y.agg.id; - let seriesLabel = y.col.title; + let seriesId = y.aggConfig.id; + let seriesLabel = y.title; if (aspects.series) { const prefix = point.series ? point.series + ': ' : ''; @@ -55,7 +55,7 @@ export function PointSeriesGetSeriesProvider(Private) { seriesLabel = prefix + seriesLabel; } - addToSiri(series, point, seriesId, seriesLabel, y.agg); + addToSiri(series, point, seriesId, seriesLabel, y.aggConfig); }); }, new Map()) @@ -70,7 +70,7 @@ export function PointSeriesGetSeriesProvider(Private) { if (firstVal) { const agg = firstVal.aggConfigResult.aggConfig; y = _.find(aspects.y, function (y) { - return y.agg === agg; + return y.aggConfig === agg; }); } diff --git a/src/ui/public/agg_response/point_series/_init_x_axis.js b/src/ui/public/agg_response/point_series/_init_x_axis.js index d07cbc5ba49c3..74be642a41874 100644 --- a/src/ui/public/agg_response/point_series/_init_x_axis.js +++ b/src/ui/public/agg_response/point_series/_init_x_axis.js @@ -21,16 +21,16 @@ export function PointSeriesInitXAxisProvider() { return function initXAxis(chart) { const x = chart.aspects.x; - chart.xAxisFormatter = x.agg ? x.agg.fieldFormatter() : String; - chart.xAxisLabel = x.col.title; + chart.xAxisFormatter = x.aggConfig ? x.aggConfig.fieldFormatter() : String; + chart.xAxisLabel = x.title; - if (!x.agg || !x.agg.type.ordered) return; + if (!x.aggConfig || !x.aggConfig.type.ordered) return; - chart.indexPattern = x.agg.vis.indexPattern; - chart.xAxisField = x.agg.params.field; + chart.indexPattern = x.aggConfig.vis.indexPattern; + chart.xAxisField = x.aggConfig.params.field; chart.ordered = {}; - const xAggOutput = x.agg.write(); + const xAggOutput = x.aggConfig.write(); if (xAggOutput.params.interval) { chart.ordered.interval = xAggOutput.params.interval; } diff --git a/src/ui/public/agg_response/point_series/_init_y_axis.js b/src/ui/public/agg_response/point_series/_init_y_axis.js index dd3165041547d..1f38a10030955 100644 --- a/src/ui/public/agg_response/point_series/_init_y_axis.js +++ b/src/ui/public/agg_response/point_series/_init_y_axis.js @@ -24,21 +24,21 @@ export function PointSeriesInitYAxisProvider() { if (Array.isArray(y)) { // TODO: vis option should allow choosing this format - chart.yAxisFormatter = y[0].agg.fieldFormatter(); + chart.yAxisFormatter = y[0].aggConfig.fieldFormatter(); chart.yAxisLabel = ''; // use the legend } else { - chart.yAxisFormatter = y.agg.fieldFormatter(); - chart.yAxisLabel = y.col.title; + chart.yAxisFormatter = y.aggConfig.fieldFormatter(); + chart.yAxisLabel = y.title; } const z = chart.aspects.series; if (z) { if (Array.isArray(z)) { - chart.zAxisFormatter = z[0].agg.fieldFormatter(); + chart.zAxisFormatter = z[0].aggConfig.fieldFormatter(); chart.zAxisLabel = ''; // use the legend } else { - chart.zAxisFormatter = z.agg.fieldFormatter(); - chart.zAxisLabel = z.col.title; + chart.zAxisFormatter = z.aggConfig.fieldFormatter(); + chart.zAxisLabel = z.title; } } }; diff --git a/src/ui/public/agg_response/point_series/_ordered_date_axis.js b/src/ui/public/agg_response/point_series/_ordered_date_axis.js index e5d5f46883ec0..0b63e82ebd643 100644 --- a/src/ui/public/agg_response/point_series/_ordered_date_axis.js +++ b/src/ui/public/agg_response/point_series/_ordered_date_axis.js @@ -22,7 +22,7 @@ import moment from 'moment'; export function PointSeriesOrderedDateAxisProvider() { return function orderedDateAxis(vis, chart) { - const xAgg = chart.aspects.x.agg; + const xAgg = chart.aspects.x.aggConfig; const buckets = xAgg.buckets; const format = buckets.getScaledDateFormat(); diff --git a/src/ui/public/agg_response/point_series/point_series.js b/src/ui/public/agg_response/point_series/point_series.js index 6dce54ee9b3c9..b6788bbbec00e 100644 --- a/src/ui/public/agg_response/point_series/point_series.js +++ b/src/ui/public/agg_response/point_series/point_series.js @@ -42,7 +42,7 @@ export function AggResponsePointSeriesProvider(Private) { initXAxis(chart); initYAxis(chart); - const datedX = aspects.x.agg.type.ordered && aspects.x.agg.type.ordered.date; + const datedX = aspects.x.aggConfig.type.ordered && aspects.x.aggConfig.type.ordered.date; if (datedX) { setupOrderedDateXAxis(vis, chart); } diff --git a/src/ui/public/agg_response/tabify/__tests__/_get_columns.js b/src/ui/public/agg_response/tabify/__tests__/_get_columns.js index fe01fe0913782..fbc42aaa04c37 100644 --- a/src/ui/public/agg_response/tabify/__tests__/_get_columns.js +++ b/src/ui/public/agg_response/tabify/__tests__/_get_columns.js @@ -52,7 +52,7 @@ describe('get columns', function () { ] }); - const columns = tabifyGetColumns(vis.getAggConfig().getResponseAggs(), null, vis.isHierarchical()); + const columns = tabifyGetColumns(vis.getAggConfig().getResponseAggs(), !vis.isHierarchical()); expect(columns).to.have.length(2); expect(columns[1]).to.have.property('aggConfig'); @@ -70,7 +70,7 @@ describe('get columns', function () { ] }); - const columns = tabifyGetColumns(vis.getAggConfig().getResponseAggs(), null, vis.isHierarchical()); + const columns = tabifyGetColumns(vis.getAggConfig().getResponseAggs(), !vis.isHierarchical()); expect(columns).to.have.length(8); columns.forEach(function (column, i) { @@ -92,7 +92,7 @@ describe('get columns', function () { ] }); - const columns = tabifyGetColumns(vis.getAggConfig().getResponseAggs(), null, vis.isHierarchical()); + const columns = tabifyGetColumns(vis.getAggConfig().getResponseAggs(), !vis.isHierarchical()); function checkColumns(column, i) { expect(column).to.have.property('aggConfig'); @@ -128,7 +128,7 @@ describe('get columns', function () { ] }); - const columns = tabifyGetColumns(vis.getAggConfig().getResponseAggs(), null, vis.isHierarchical()); + const columns = tabifyGetColumns(vis.getAggConfig().getResponseAggs(), !vis.isHierarchical()); expect(columns).to.have.length(6); // sum should be last diff --git a/src/ui/public/agg_response/tabify/__tests__/_integration.js b/src/ui/public/agg_response/tabify/__tests__/_integration.js index b06293fa48093..e9a99bc33d201 100644 --- a/src/ui/public/agg_response/tabify/__tests__/_integration.js +++ b/src/ui/public/agg_response/tabify/__tests__/_integration.js @@ -49,16 +49,14 @@ describe('tabifyAggResponse Integration', function () { normalizeIds(vis); const resp = tabifyAggResponse(vis.getAggConfig(), fixtures.metricOnly, { - canSplit: false, - isHierarchical: vis.isHierarchical() + metricsAtAllLevels: vis.isHierarchical() }); - expect(resp).to.not.have.property('tables'); expect(resp).to.have.property('rows').and.property('columns'); expect(resp.rows).to.have.length(1); expect(resp.columns).to.have.length(1); - expect(resp.rows[0]).to.eql([1000]); + expect(resp.rows[0]).to.eql({ 'col-0-agg_1': 1000 }); expect(resp.columns[0]).to.have.property('aggConfig', vis.aggs[0]); }); @@ -94,33 +92,6 @@ describe('tabifyAggResponse Integration', function () { esResp.aggregations.agg_2.buckets[1].agg_3.buckets[0].agg_4.buckets = []; }); - // check that the root table group is formed properly, then pass - // each table to expectExtensionSplit, along with the expectInnerTables() - // function. - function expectRootGroup(rootTableGroup, expectInnerTables) { - expect(rootTableGroup).to.have.property('tables'); - - const tables = rootTableGroup.tables; - expect(tables).to.be.an('array').and.have.length(3); - expectExtensionSplit(tables[0], 'png', expectInnerTables); - expectExtensionSplit(tables[1], 'css', expectInnerTables); - expectExtensionSplit(tables[2], 'html', expectInnerTables); - } - - // check that the tableGroup for the extension agg was formed properly - // then call expectTable() on each table inside. it should validate that - // each table is formed properly - function expectExtensionSplit(tableGroup, key, expectTable) { - expect(tableGroup).to.have.property('tables'); - expect(tableGroup).to.have.property('aggConfig', ext); - expect(tableGroup).to.have.property('key', key); - expect(tableGroup.tables).to.be.an('array').and.have.length(1); - - tableGroup.tables.forEach(function (table) { - expectTable(table, key); - }); - } - // check that the columns of a table are formed properly function expectColumns(table, aggs) { expect(table.columns).to.be.an('array').and.have.length(aggs.length); @@ -131,10 +102,11 @@ describe('tabifyAggResponse Integration', function () { // check that a row has expected values function expectRow(row, asserts) { - expect(row).to.be.an('array'); - expect(row).to.have.length(asserts.length); + expect(row).to.be.an('object'); asserts.forEach(function (assert, i) { - assert(row[i]); + if (row[`col-${i}`]) { + assert(row[`col-${i}`]); + } }); } @@ -144,10 +116,10 @@ describe('tabifyAggResponse Integration', function () { expect(val).to.have.length(2); } - // check for an empty cell - function expectEmpty(val) { + // check for an OS term + function expectExtension(val) { expect(val) - .to.be(''); + .to.match(/^(js|png|html|css|jpg)$/); } // check for an OS term @@ -162,127 +134,44 @@ describe('tabifyAggResponse Integration', function () { expect(val === 0 || val > 1000).to.be.ok(); } - // create an assert that checks for an expected value - function expectVal(expected) { - return function (val) { - expect(val).to.be(expected); - }; - } - it('for non-hierarchical vis', function () { // the default for a non-hierarchical vis is to display // only complete rows, and only put the metrics at the end. - vis.isHierarchical = _.constant(false); - const tabbed = tabifyAggResponse(vis.getAggConfig(), esResp, { isHierarchical: vis.isHierarchical() }); + const tabbed = tabifyAggResponse(vis.getAggConfig(), esResp, { minimalColumns: true }); - expectRootGroup(tabbed, function expectTable(table, splitKey) { - expectColumns(table, [src, os, avg]); + expectColumns(tabbed, [ext, src, os, avg]); - table.rows.forEach(function (row) { - if (splitKey === 'css' && row[0] === 'MX') { - throw new Error('expected the MX row in the css table to be removed'); - } else { - expectRow(row, [ - expectCountry, - expectOS, - expectAvgBytes - ]); - } - }); + tabbed.rows.forEach(function (row) { + expectRow(row, [ + expectExtension, + expectCountry, + expectOS, + expectAvgBytes + ]); }); }); - it('for hierarchical vis, with partial rows', function () { + it('for hierarchical vis', function () { // since we have partialRows we expect that one row will have some empty // values, and since the vis is hierarchical and we are NOT using // minimalColumns we should expect the partial row to be completely after // the existing bucket and it's metric vis.isHierarchical = _.constant(true); - const tabbed = tabifyAggResponse(vis.getAggConfig(), esResp, { - partialRows: true, - isHierarchical: vis.isHierarchical() - }); - - expectRootGroup(tabbed, function expectTable(table, splitKey) { - expectColumns(table, [src, avg, os, avg]); - - table.rows.forEach(function (row) { - if (splitKey === 'css' && row[0] === 'MX') { - expectRow(row, [ - expectCountry, - expectAvgBytes, - expectEmpty, - expectEmpty - ]); - } else { - expectRow(row, [ - expectCountry, - expectAvgBytes, - expectOS, - expectAvgBytes - ]); - } - }); - }); - }); - - it('for hierarchical vis, with partial rows, and minimal columns', function () { - // since we have partialRows we expect that one row has some empty - // values, and since the vis is hierarchical and we are displaying using - // minimalColumns, we should expect the partial row to have a metric at - // the end - - vis.isHierarchical = _.constant(true); - const tabbed = tabifyAggResponse(vis.getAggConfig(), esResp, { - partialRows: true, - minimalColumns: true, - isHierarchical: vis.isHierarchical() - }); - - expectRootGroup(tabbed, function expectTable(table, splitKey) { - expectColumns(table, [src, os, avg]); - - table.rows.forEach(function (row) { - if (splitKey === 'css' && row[0] === 'MX') { - expectRow(row, [ - expectCountry, - expectEmpty, - expectVal(9299) - ]); - } else { - expectRow(row, [ - expectCountry, - expectOS, - expectAvgBytes - ]); - } - }); - }); - }); - - it('for non-hierarchical vis, minimal columns set to false', function () { - // the reason for this test is mainly to check that setting - // minimalColumns = false on a non-hierarchical vis doesn't - // create metric columns after each bucket - - vis.isHierarchical = _.constant(false); - const tabbed = tabifyAggResponse(vis.getAggConfig(), esResp, { - minimalColumns: false, - isHierarchical: vis.isHierarchical() - }); - - expectRootGroup(tabbed, function expectTable(table) { - expectColumns(table, [src, os, avg]); - - table.rows.forEach(function (row) { - expectRow(row, [ - expectCountry, - expectOS, - expectAvgBytes - ]); - }); + const tabbed = tabifyAggResponse(vis.getAggConfig(), esResp, { metricsAtAllLevels: true }); + + expectColumns(tabbed, [ext, avg, src, avg, os, avg]); + + tabbed.rows.forEach(function (row) { + expectRow(row, [ + expectExtension, + expectAvgBytes, + expectCountry, + expectAvgBytes, + expectOS, + expectAvgBytes + ]); }); }); }); diff --git a/src/ui/public/agg_response/tabify/__tests__/_response_writer.js b/src/ui/public/agg_response/tabify/__tests__/_response_writer.js index d9e04af7253db..b395467146762 100644 --- a/src/ui/public/agg_response/tabify/__tests__/_response_writer.js +++ b/src/ui/public/agg_response/tabify/__tests__/_response_writer.js @@ -17,13 +17,9 @@ * under the License. */ -import _ from 'lodash'; -import sinon from 'sinon'; import expect from 'expect.js'; import ngMock from 'ng_mock'; import { TabbedAggResponseWriter } from '../_response_writer'; -import { TabifyTableGroup } from '../_table_group'; -import { TabifyBuckets } from '../_buckets'; import { VisProvider } from '../../../vis'; import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; @@ -32,70 +28,55 @@ describe('TabbedAggResponseWriter class', function () { let Private; let indexPattern; - function defineSetup() { - beforeEach(ngMock.module('kibana')); - beforeEach(ngMock.inject(function ($injector) { - Private = $injector.get('Private'); - - Vis = Private(VisProvider); - indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider); - })); - } + beforeEach(ngMock.module('kibana')); + beforeEach(ngMock.inject(function ($injector) { + Private = $injector.get('Private'); + + Vis = Private(VisProvider); + indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider); + })); + + const splitAggConfig = [ { + type: 'terms', + params: { + field: 'geo.src', + } + }]; + + const twoSplitsAggConfig = [{ + type: 'terms', + params: { + field: 'geo.src', + } + }, { + type: 'terms', + params: { + field: 'machine.os.raw', + } + }]; + + const createResponseWritter = (aggs = [], opts = {}) => { + const vis = new Vis(indexPattern, { type: 'histogram', aggs: aggs }); + return new TabbedAggResponseWriter(vis.getAggConfig(), opts); + }; describe('Constructor', function () { - defineSetup(); - - it('sets canSplit=true by default', function () { - const vis = new Vis(indexPattern, { type: 'histogram', aggs: [] }); - const writer = new TabbedAggResponseWriter(vis.getAggConfig(), { - isHierarchical: vis.isHierarchical() - }); - expect(writer).to.have.property('canSplit', true); + let responseWriter; + beforeEach(() => { + responseWriter = createResponseWritter(twoSplitsAggConfig); }); - it('sets canSplit=false when config says to', function () { - const vis = new Vis(indexPattern, { type: 'histogram', aggs: [] }); - const writer = new TabbedAggResponseWriter(vis.getAggConfig(), { - canSplit: false, - isHierarchical: vis.isHierarchical() - }); - expect(writer).to.have.property('canSplit', false); + it('creates aggStack', () => { + expect(responseWriter.aggStack.length).to.eql(3); }); - describe('sets partialRows', function () { - it('to the value of the config if set', function () { - const vis = new Vis(indexPattern, { type: 'histogram', aggs: [] }); - const partial = Boolean(Math.round(Math.random())); - - const writer = new TabbedAggResponseWriter(vis.getAggConfig(), { - isHierarchical: vis.isHierarchical(), - partialRows: partial - }); - expect(writer).to.have.property('partialRows', partial); - }); - - it('to the value of vis.isHierarchical if no config', function () { - const vis = new Vis(indexPattern, { type: 'histogram', aggs: [] }); - const hierarchical = Boolean(Math.round(Math.random())); - sinon.stub(vis, 'isHierarchical').returns(hierarchical); - - const writer = new TabbedAggResponseWriter(vis.getAggConfig(), { - isHierarchical: vis.isHierarchical() - }); - expect(writer).to.have.property('partialRows', hierarchical); - }); + it('generates columns', () => { + expect(responseWriter.columns.length).to.eql(3); }); - it('starts off with a root TabifyTableGroup', function () { - const vis = new Vis(indexPattern, { type: 'histogram', aggs: [] }); - - const writer = new TabbedAggResponseWriter(vis.getAggConfig(), { - isHierarchical: vis.isHierarchical() - }); - expect(writer.root).to.be.a(TabifyTableGroup); - expect(writer.splitStack).to.be.an('array'); - expect(writer.splitStack).to.have.length(1); - expect(writer.splitStack[0]).to.be(writer.root); + it('correctly generates columns with metricsAtAllLevels set to true', () => { + const minimalColumnsResponseWriter = createResponseWritter(twoSplitsAggConfig, { metricsAtAllLevels: true }); + expect(minimalColumnsResponseWriter.columns.length).to.eql(4); }); describe('sets timeRange', function () { @@ -128,296 +109,71 @@ describe('TabbedAggResponseWriter class', function () { }); }); - describe('', function () { - defineSetup(); + describe('row()', function () { + let responseWriter; - describe('#response()', function () { - it('returns the root TabifyTableGroup if splitting', function () { - const vis = new Vis(indexPattern, { type: 'histogram', aggs: [] }); - const writer = new TabbedAggResponseWriter(vis.getAggConfig(), { - isHierarchical: vis.isHierarchical() - }); - expect(writer.response()).to.be(writer.root); - }); - - it('returns the first table if not splitting', function () { - const vis = new Vis(indexPattern, { type: 'histogram', aggs: [] }); - const writer = new TabbedAggResponseWriter(vis.getAggConfig(), { - isHierarchical: vis.isHierarchical(), - canSplit: false - }); - const table = writer._table(); - expect(writer.response()).to.be(table); - }); - - it('adds columns to all of the tables', function () { - const vis = new Vis(indexPattern, { - type: 'histogram', - aggs: [ - { type: 'terms', params: { field: '_type' }, schema: 'split' }, - { type: 'count', schema: 'metric' } - ] - }); - const buckets = new TabifyBuckets({ buckets: [ { key: 'nginx' }, { key: 'apache' } ] }); - const writer = new TabbedAggResponseWriter(vis.getAggConfig(), { - isHierarchical: vis.isHierarchical() - }); - const tables = []; - - writer.split(vis.aggs[0], buckets, function () { - writer.cell(vis.aggs[1], 100, function () { - tables.push(writer.row()); - }); - }); - - tables.forEach(function (table) { - expect(table.columns == null).to.be(true); - }); - - const resp = writer.response(); - expect(resp).to.be.a(TabifyTableGroup); - expect(resp.tables).to.have.length(2); - - const nginx = resp.tables.shift(); - expect(nginx).to.have.property('aggConfig', vis.aggs[0]); - expect(nginx).to.have.property('key', 'nginx'); - expect(nginx.tables).to.have.length(1); - nginx.tables.forEach(function (table) { - expect(_.contains(tables, table)).to.be(true); - }); - - const apache = resp.tables.shift(); - expect(apache).to.have.property('aggConfig', vis.aggs[0]); - expect(apache).to.have.property('key', 'apache'); - expect(apache.tables).to.have.length(1); - apache.tables.forEach(function (table) { - expect(_.contains(tables, table)).to.be(true); - }); - - tables.forEach(function (table) { - expect(table.columns).to.be.an('array'); - expect(table.columns).to.have.length(1); - expect(table.columns[0].aggConfig.type.name).to.be('count'); - }); - }); + beforeEach(() => { + responseWriter = createResponseWritter(splitAggConfig, { partialRows: true }); }); - describe('#split()', function () { - it('with break if the user has specified that splitting is to be disabled', function () { - const vis = new Vis(indexPattern, { - type: 'histogram', - aggs: [ - { type: 'terms', schema: 'split', params: { field: '_type' } }, - { type: 'count', schema: 'metric' } - ] - }); - const agg = vis.aggs.bySchemaName.split[0]; - const buckets = new TabifyBuckets({ buckets: [ { key: 'apache' } ] }); - const writer = new TabbedAggResponseWriter(vis.getAggConfig(), { - isHierarchical: vis.isHierarchical(), - canSplit: false - }); - - expect(function () { - writer.split(agg, buckets, _.noop); - }).to.throwException(/splitting is disabled/); - }); - - it('forks the acrStack and rewrites the parents', function () { - const vis = new Vis(indexPattern, { - type: 'histogram', - aggs: [ - { type: 'terms', params: { field: 'extension' }, schema: 'segment' }, - { type: 'terms', params: { field: '_type' }, schema: 'split' }, - { type: 'terms', params: { field: 'machine.os' }, schema: 'segment' }, - { type: 'count', schema: 'metric' } - ] - }); - - const writer = new TabbedAggResponseWriter(vis.getAggConfig(), { - isHierarchical: vis.isHierarchical(), - asAggConfigResults: true - }); - const extensions = new TabifyBuckets({ buckets: [ { key: 'jpg' }, { key: 'png' } ] }); - const types = new TabifyBuckets({ buckets: [ { key: 'nginx' }, { key: 'apache' } ] }); - const os = new TabifyBuckets({ buckets: [ { key: 'window' }, { key: 'osx' } ] }); - - extensions.forEach(function (b, extension) { - writer.cell(vis.aggs[0], extension, function () { - writer.split(vis.aggs[1], types, function () { - os.forEach(function (b, os) { - writer.cell(vis.aggs[2], os, function () { - writer.cell(vis.aggs[3], 200, function () { - writer.row(); - }); - }); - }); - }); - }); - }); - - const tables = _.flattenDeep(_.pluck(writer.response().tables, 'tables')); - expect(tables.length).to.be(types.length); - - // collect the far left acr from each table - const leftAcrs = _.pluck(tables, 'rows[0][0]'); - - leftAcrs.forEach(function (acr, i, acrs) { - expect(acr.aggConfig).to.be(vis.aggs[0]); - expect(acr.$parent.aggConfig).to.be(vis.aggs[1]); - expect(acr.$parent.$parent).to.be(void 0); - - // for all but the last acr, compare to the next - if (i + 1 >= acrs.length) return; - const acr2 = leftAcrs[i + 1]; - - expect(acr.key).to.be(acr2.key); - expect(acr.value).to.be(acr2.value); - expect(acr.aggConfig).to.be(acr2.aggConfig); - expect(acr.$parent).to.not.be(acr2.$parent); - }); - }); - - + it('adds the row to the array', () => { + responseWriter.rowBuffer['col-0'] = 'US'; + responseWriter.rowBuffer['col-1'] = 5; + responseWriter.row(); + expect(responseWriter.rows.length).to.eql(1); + expect(responseWriter.rows[0]).to.eql({ 'col-0': 'US', 'col-1': 5 }); }); - describe('#cell()', function () { - it('logs a cell in the TabbedAggResponseWriters row buffer, calls the block arg, then removes the value from the buffer', - function () { - const vis = new Vis(indexPattern, { type: 'histogram', aggs: [] }); - const writer = new TabbedAggResponseWriter(vis.getAggConfig(), { - isHierarchical: vis.isHierarchical() - }); - - expect(writer.rowBuffer).to.have.length(0); - writer.cell({}, 500, function () { - expect(writer.rowBuffer).to.have.length(1); - expect(writer.rowBuffer[0]).to.be(500); - }); - expect(writer.rowBuffer).to.have.length(0); - }); + it('correctly handles bucketBuffer', () => { + responseWriter.bucketBuffer.push({ id: 'col-0', value: 'US' }); + responseWriter.rowBuffer['col-1'] = 5; + responseWriter.row(); + expect(responseWriter.rows.length).to.eql(1); + expect(responseWriter.rows[0]).to.eql({ 'col-0': 'US', 'col-1': 5 }); }); - describe('#row()', function () { - it('writes the TabbedAggResponseWriters internal rowBuffer into a table', function () { - const vis = new Vis(indexPattern, { type: 'histogram', aggs: [] }); - const writer = new TabbedAggResponseWriter(vis.getAggConfig(), { - isHierarchical: vis.isHierarchical() - }); - - const table = writer._table(); - writer.cell({}, 1, function () { - writer.cell({}, 2, function () { - writer.cell({}, 3, function () { - writer.row(); - }); - }); - }); - - expect(table.rows).to.have.length(1); - expect(table.rows[0]).to.eql([1, 2, 3]); - }); - - it('always writes to the table group at the top of the split stack', function () { - const vis = new Vis(indexPattern, { - type: 'histogram', - aggs: [ - { type: 'terms', schema: 'split', params: { field: '_type' } }, - { type: 'terms', schema: 'split', params: { field: 'extension' } }, - { type: 'terms', schema: 'split', params: { field: 'machine.os' } }, - { type: 'count', schema: 'metric' } - ] - }); - const splits = vis.aggs.bySchemaName.split; - - const type = splits[0]; - const typeTabifyBuckets = new TabifyBuckets({ buckets: [ { key: 'nginx' }, { key: 'apache' } ] }); - - const ext = splits[1]; - const extTabifyBuckets = new TabifyBuckets({ buckets: [ { key: 'jpg' }, { key: 'png' } ] }); - - const os = splits[2]; - const osTabifyBuckets = new TabifyBuckets({ buckets: [ { key: 'windows' }, { key: 'mac' } ] }); - - const count = vis.aggs[3]; - - const writer = new TabbedAggResponseWriter(vis.getAggConfig(), { - isHierarchical: vis.isHierarchical() - }); - writer.split(type, typeTabifyBuckets, function () { - writer.split(ext, extTabifyBuckets, function () { - writer.split(os, osTabifyBuckets, function (bucket, key) { - writer.cell(count, key === 'windows' ? 1 : 2, function () { - writer.row(); - }); - }); - }); - }); - - const resp = writer.response(); - let sum = 0; - let tables = 0; - (function recurse(t) { - if (t.tables) { - // table group - t.tables.forEach(function (tt) { - recurse(tt); - }); - } else { - tables += 1; - // table - t.rows.forEach(function (row) { - row.forEach(function (cell) { - sum += cell; - }); - }); - } - }(resp)); - - expect(tables).to.be(8); - expect(sum).to.be(12); - }); - - it('writes partial rows for hierarchical vis', function () { - const vis = new Vis(indexPattern, { - type: 'pie', - aggs: [ - { type: 'terms', schema: 'segment', params: { field: '_type' } }, - { type: 'count', schema: 'metric' } - ] - }); - - const writer = new TabbedAggResponseWriter(vis.getAggConfig(), { - isHierarchical: vis.isHierarchical() - }); - const table = writer._table(); - writer.cell(vis.aggs[0], 'apache', function () { - writer.row(); - }); + it('doesn\'t add an empty row', () => { + responseWriter.row(); + expect(responseWriter.rows.length).to.eql(0); + }); + }); - expect(table.rows).to.have.length(1); - expect(table.rows[0]).to.eql(['apache', '']); - }); + describe('response()', () => { + let responseWriter; - it('skips partial rows for non-hierarchical vis', function () { - const vis = new Vis(indexPattern, { - type: 'histogram', - aggs: [ - { type: 'terms', schema: 'segment', params: { field: '_type' } }, - { type: 'count', schema: 'metric' } - ] - }); + beforeEach(() => { + responseWriter = createResponseWritter(splitAggConfig); + }); - const writer = new TabbedAggResponseWriter(vis.getAggConfig(), { - isHierarchical: vis.isHierarchical() - }); - const table = writer._table(); - writer.cell(vis.aggs[0], 'apache', function () { - writer.row(); - }); + it('produces correct response', () => { + responseWriter.rowBuffer['col-0-1'] = 'US'; + responseWriter.rowBuffer['col-1-2'] = 5; + responseWriter.row(); + const response = responseWriter.response(); + expect(response).to.have.property('rows'); + expect(response.rows).to.eql([{ 'col-0-1': 'US', 'col-1-2': 5 }]); + expect(response).to.have.property('columns'); + expect(response.columns.length).to.equal(2); + expect(response.columns[0]).to.have.property('id', 'col-0-1'); + expect(response.columns[0]).to.have.property('name', 'geo.src: Descending'); + expect(response.columns[0]).to.have.property('aggConfig'); + expect(response.columns[1]).to.have.property('id', 'col-1-2'); + expect(response.columns[1]).to.have.property('name', 'Count'); + expect(response.columns[1]).to.have.property('aggConfig'); + }); - expect(table.rows).to.have.length(0); - }); + it('produces correct response for no data', () => { + const response = responseWriter.response(); + expect(response).to.have.property('rows'); + expect(response.rows.length).to.be(0); + expect(response).to.have.property('columns'); + expect(response.columns.length).to.equal(2); + expect(response.columns[0]).to.have.property('id', 'col-0-1'); + expect(response.columns[0]).to.have.property('name', 'geo.src: Descending'); + expect(response.columns[0]).to.have.property('aggConfig'); + expect(response.columns[1]).to.have.property('id', 'col-1-2'); + expect(response.columns[1]).to.have.property('name', 'Count'); + expect(response.columns[1]).to.have.property('aggConfig'); }); }); }); diff --git a/src/ui/public/agg_response/tabify/__tests__/_table.js b/src/ui/public/agg_response/tabify/__tests__/_table.js deleted file mode 100644 index 6248c6cb77af8..0000000000000 --- a/src/ui/public/agg_response/tabify/__tests__/_table.js +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; -import expect from 'expect.js'; -import { TabifyTable } from '../_table'; - -describe('TabifyTable class', function () { - it('exposes rows array, but not the columns', function () { - const table = new TabifyTable(); - expect(table.rows).to.be.an('array'); - expect(table.columns == null).to.be.ok(); - }); - - describe('#aggConfig', function () { - it('accepts a column from the table and returns its agg config', function () { - const table = new TabifyTable(); - const football = {}; - const column = { - aggConfig: football - }; - - expect(table.aggConfig(column)).to.be(football); - }); - - it('throws a TypeError if the column is malformed', function () { - expect(function () { - const notAColumn = {}; - (new TabifyTable()).aggConfig(notAColumn); - }).to.throwException(TypeError); - }); - }); - - describe('#title', function () { - it('returns nothing if the table is not part of a table group', function () { - const table = new TabifyTable(); - expect(table.title()).to.be(''); - }); - - it('returns the title of the TabifyTableGroup if the table is part of one', function () { - const table = new TabifyTable(); - table.$parent = { - title: 'TabifyTableGroup Title', - tables: [table] - }; - - expect(table.title()).to.be('TabifyTableGroup Title'); - }); - }); - - describe('#field', function () { - it('calls the columns aggConfig#getField() method', function () { - const table = new TabifyTable(); - const football = {}; - const column = { - aggConfig: { - getField: _.constant(football) - } - }; - - expect(table.field(column)).to.be(football); - }); - }); - - describe('#fieldFormatter', function () { - it('calls the columns aggConfig#fieldFormatter() method', function () { - const table = new TabifyTable(); - const football = {}; - const column = { - aggConfig: { - fieldFormatter: _.constant(football) - } - }; - - expect(table.fieldFormatter(column)).to.be(football); - }); - }); -}); diff --git a/src/ui/public/agg_response/tabify/__tests__/_table_group.js b/src/ui/public/agg_response/tabify/__tests__/_table_group.js deleted file mode 100644 index 0051e46247ea2..0000000000000 --- a/src/ui/public/agg_response/tabify/__tests__/_table_group.js +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import expect from 'expect.js'; -import { TabifyTableGroup } from '../_table_group'; - -describe('Table Group class', function () { - - it('exposes tables array and empty aggConfig, key and title', function () { - const tableGroup = new TabifyTableGroup(); - expect(tableGroup.tables).to.be.an('array'); - expect(tableGroup.aggConfig).to.be(null); - expect(tableGroup.key).to.be(null); - expect(tableGroup.title).to.be(null); - }); -}); diff --git a/src/ui/public/agg_response/tabify/__tests__/tabify.js b/src/ui/public/agg_response/tabify/__tests__/tabify.js index 7f82817470c93..2e66d583fdeeb 100644 --- a/src/ui/public/agg_response/tabify/__tests__/tabify.js +++ b/src/ui/public/agg_response/tabify/__tests__/tabify.js @@ -19,8 +19,6 @@ import './_get_columns'; import './_buckets'; -import './_table'; -import './_table_group'; import './_response_writer'; import './_integration'; describe('Tabify Agg Response', function () { diff --git a/src/ui/public/agg_response/tabify/_get_columns.js b/src/ui/public/agg_response/tabify/_get_columns.js index e5010f0f0358a..68df7e1a03333 100644 --- a/src/ui/public/agg_response/tabify/_get_columns.js +++ b/src/ui/public/agg_response/tabify/_get_columns.js @@ -19,15 +19,19 @@ import _ from 'lodash'; -export function tabifyGetColumns(aggs, minimal, hierarchical) { +const getColumn = (agg, i) => { + return { + aggConfig: agg, + id: `col-${i}-${agg.id}`, + name: agg.makeLabel() + }; +}; - if (minimal == null) minimal = !hierarchical; +export function tabifyGetColumns(aggs, minimal) { // pick the columns if (minimal) { - return aggs.map(function (agg) { - return { aggConfig: agg }; - }); + return aggs.map((agg, i) => getColumn(agg, i)); } // supposed to be bucket,...metrics,bucket,...metrics @@ -40,16 +44,15 @@ export function tabifyGetColumns(aggs, minimal, hierarchical) { if (!grouped.buckets) { // return just the metrics, in column format - return grouped.metrics.map(function (agg) { - return { aggConfig: agg }; - }); + return grouped.metrics.map((agg, i) => getColumn(agg, i)); } + let columnIndex = 0; // return the buckets, and after each place all of the metrics grouped.buckets.forEach(function (agg) { - columns.push({ aggConfig: agg }); + columns.push(getColumn(agg, columnIndex++)); grouped.metrics.forEach(function (metric) { - columns.push({ aggConfig: metric }); + columns.push(getColumn(metric, columnIndex++)); }); }); diff --git a/src/ui/public/agg_response/tabify/_response_writer.js b/src/ui/public/agg_response/tabify/_response_writer.js index 0b6d4cb947466..e71fe842e0bc6 100644 --- a/src/ui/public/agg_response/tabify/_response_writer.js +++ b/src/ui/public/agg_response/tabify/_response_writer.js @@ -17,294 +17,74 @@ * under the License. */ -import _ from 'lodash'; -import AggConfigResult from '../../vis/agg_config_result'; -import { TabifyTable } from './_table'; -import { TabifyTableGroup } from './_table_group'; +import { toArray } from 'lodash'; import { tabifyGetColumns } from './_get_columns'; -import { createLegacyClass } from '../../utils/legacy_class'; - -createLegacyClass(SplitAcr).inherits(AggConfigResult); -function SplitAcr(agg, parent, key) { - SplitAcr.Super.call(this, agg, parent, key, key); -} /** * Writer class that collects information about an aggregation response and * produces a table, or a series of tables. * - * @param {Vis} vis - the vis object to which the aggregation response correlates + * @param {AggConfigs} aggs - the agg configs object to which the aggregation response correlates + * @param {boolean} metricsAtAllLevels - setting to true will produce metrics for every bucket + * @param {boolean} partialRows - setting to true will not remove rows with missing values */ -function TabbedAggResponseWriter(aggs, opts) { - this.opts = opts || {}; - this.rowBuffer = []; - - const visIsHier = opts.isHierarchical; - - // do the options allow for splitting? we will only split if true and - // tabify calls the split method. - this.canSplit = this.opts.canSplit !== false; - - // should we allow partial rows to be included in the tables? if a - // partial row is found, it is filled with empty strings '' - this.partialRows = this.opts.partialRows == null ? visIsHier : this.opts.partialRows; - - // if true, we will not place metric columns after every bucket - // even if the vis is hierarchical. if false, and the vis is - // hierarchical, then we will display metric columns after - // every bucket col - this.minimalColumns = visIsHier ? !!this.opts.minimalColumns : true; - - // true if we can expect metrics to have been calculated - // for every bucket - this.metricsForAllBuckets = visIsHier; - - // if true, values will be wrapped in aggConfigResult objects which link them - // to their aggConfig and enable the filterbar and tooltip formatters - this.asAggConfigResults = !!this.opts.asAggConfigResults; +function TabbedAggResponseWriter(aggs, { metricsAtAllLevels = false, partialRows = false, timeRange } = {}) { + this.rowBuffer = {}; + this.bucketBuffer = []; + this.metricBuffer = []; + this.metricsForAllBuckets = metricsAtAllLevels; + this.partialRows = partialRows; this.aggs = aggs; - this.columns = tabifyGetColumns(aggs.getResponseAggs(), this.minimalColumns); - this.aggStack = _.pluck(this.columns, 'aggConfig'); + this.columns = tabifyGetColumns(aggs.getResponseAggs(), !metricsAtAllLevels); + this.aggStack = [...this.columns]; - this.root = new TabifyTableGroup(); - this.acrStack = []; - this.splitStack = [this.root]; + this.rows = []; // Extract the time range object if provided - if (this.opts.timeRange) { - const timeRangeKey = Object.keys(this.opts.timeRange)[0]; - this.timeRange = this.opts.timeRange[timeRangeKey]; + if (timeRange) { + const timeRangeKey = Object.keys(timeRange)[0]; + this.timeRange = timeRange[timeRangeKey]; if (this.timeRange) { this.timeRange.name = timeRangeKey; } } } -/** - * Create a Table of TableGroup object, link it to it's parent (if any), and determine if - * it's the root - * - * @param {boolean} group - is this a TableGroup or just a normal Table - * @param {AggConfig} agg - the aggregation that create this table, only applies to groups - * @param {any} key - the bucketKey that this table relates to - * @return {Table/TableGroup} table - the created table - */ -TabbedAggResponseWriter.prototype._table = function (group, agg, key) { - const Class = (group) ? TabifyTableGroup : TabifyTable; - const table = new Class(); - const parent = this.splitStack[0]; - - if (group) { - table.aggConfig = agg; - table.key = key; - table.title = (table.fieldFormatter()(key)); - // aggs that don't implement makeLabel should not add to title - if (agg.makeLabel() !== agg.name) { - table.title += ': ' + agg.makeLabel(); - } - } - - // link the parent and child - table.$parent = parent; - parent.tables.push(table); - - return table; +TabbedAggResponseWriter.prototype.isPartialRow = function (row) { + return !this.columns.map(column => row.hasOwnProperty(column.id)).every(c => (c === true)); }; /** - * Enter into a split table, called for each bucket of a splitting agg. The new table - * is either created or located using the agg and key arguments, and then the block is - * executed with the table as it's this context. Within this function, you should - * walk into the remaining branches and end up writing some rows to the table. - * - * @param {aggConfig} agg - the aggConfig that created this split - * @param {Buckets} buckets - the buckets produces by the agg - * @param {function} block - a function to execute for each sub bucket + * Create a new row by reading the row buffer and bucketBuffer */ -TabbedAggResponseWriter.prototype.split = function (agg, buckets, block) { - const self = this; - - if (!self.canSplit) { - throw new Error('attempted to split when splitting is disabled'); - } - - self._removeAggFromColumns(agg); - - buckets.forEach(function (bucket, key) { - // find the existing split that we should extend - let tableGroup = _.find(self.splitStack[0].tables, { aggConfig: agg, key: key }); - // create the split if it doesn't exist yet - if (!tableGroup) tableGroup = self._table(true, agg, key); - - let splitAcr = false; - if (self.asAggConfigResults) { - splitAcr = self._injectParentSplit(agg, key); - } - - // push the split onto the stack so that it will receive written tables - self.splitStack.unshift(tableGroup); - - // call the block - if (_.isFunction(block)) block.call(self, bucket, key); - - // remove the split from the stack - self.splitStack.shift(); - splitAcr && _.pull(self.acrStack, splitAcr); - }); -}; - -TabbedAggResponseWriter.prototype._removeAggFromColumns = function (agg) { - const i = _.findIndex(this.columns, function (col) { - return col.aggConfig === agg; +TabbedAggResponseWriter.prototype.row = function () { + this.bucketBuffer.forEach(bucket => { + this.rowBuffer[bucket.id] = bucket.value; }); - // we must have already removed this column - if (i === -1) return; - - this.columns.splice(i, 1); - - if (this.minimalColumns) return; - - // hierarchical vis creates additional columns for each bucket - // we will remove those too - const mCol = this.columns.splice(i, 1).pop(); - const mI = _.findIndex(this.aggStack, function (agg) { - return agg === mCol.aggConfig; + this.metricBuffer.forEach(metric => { + this.rowBuffer[metric.id] = metric.value; }); - if (mI > -1) this.aggStack.splice(mI, 1); -}; - -/** - * When a split is found while building the aggConfigResult tree, we - * want to push the split into the tree at another point. Since each - * branch in the tree is a double-linked list we need do some special - * shit to pull this off. - * - * @private - * @param {AggConfig} - The agg which produced the split bucket - * @param {any} - The value which identifies the bucket - * @return {SplitAcr} - the AggConfigResult created for the split bucket - */ -TabbedAggResponseWriter.prototype._injectParentSplit = function (agg, key) { - const oldList = this.acrStack; - const newList = this.acrStack = []; - - // walk from right to left through the old stack - // and move things to the new stack - let injected = false; - - if (!oldList.length) { - injected = new SplitAcr(agg, null, key); - newList.unshift(injected); - return injected; - } - - // walk from right to left, emptying the previous list - while (oldList.length) { - const acr = oldList.pop(); - - // ignore other splits - if (acr instanceof SplitAcr) { - newList.unshift(acr); - continue; - } - - // inject the split - if (!injected) { - injected = new SplitAcr(agg, newList[0], key); - newList.unshift(injected); - } - - const newAcr = new AggConfigResult(acr.aggConfig, newList[0], acr.value, acr.aggConfig.getKey(acr), acr.filters); - newList.unshift(newAcr); - - // and replace the acr in the row buffer if its there - const rowI = this.rowBuffer.indexOf(acr); - if (rowI > -1) { - this.rowBuffer[rowI] = newAcr; - } - } - - return injected; -}; - -/** - * Push a value into the row, then run a block. Once the block is - * complete the value is pulled from the stack. - * - * @param {any} value - the value that should be added to the row - * @param {function} block - the function to run while this value is in the row - * @return {any} - the value that was added - */ -TabbedAggResponseWriter.prototype.cell = function (agg, value, block, filters) { - if (this.asAggConfigResults) { - value = new AggConfigResult(agg, this.acrStack[0], value, value, filters); - } - - const stackResult = this.asAggConfigResults && value.type === 'bucket'; - - this.rowBuffer.push(value); - if (stackResult) this.acrStack.unshift(value); - - if (_.isFunction(block)) block.call(this); - - this.rowBuffer.pop(value); - if (stackResult) this.acrStack.shift(); - - return value; -}; - -/** - * Create a new row by reading the row buffer. This will do nothing if - * the row is incomplete and the vis this data came from is NOT flagged as - * hierarchical. - * - * @param {array} [buffer] - optional buffer to use in place of the stored rowBuffer - * @return {undefined} - */ -TabbedAggResponseWriter.prototype.row = function (buffer) { - const cells = buffer || this.rowBuffer.slice(0); - - if (!this.partialRows && cells.length < this.columns.length) { + if (!toArray(this.rowBuffer).length || (!this.partialRows && this.isPartialRow(this.rowBuffer))) { return; } - const split = this.splitStack[0]; - const table = split.tables[0] || this._table(false); - - while (cells.length < this.columns.length) cells.push(''); - table.rows.push(cells); - return table; + this.rows.push(this.rowBuffer); + this.rowBuffer = {}; }; /** * Get the actual response * - * @return {object} - the final table-tree + * @return {object} - the final table */ TabbedAggResponseWriter.prototype.response = function () { - const columns = this.columns; - - // give the columns some metadata - columns.map(function (col) { - col.title = col.aggConfig.makeLabel(); - }); - - // walk the tree and write the columns to each table - ((function step(table) { - if (table.tables) table.tables.forEach(step); - else table.columns = columns.slice(0); - })(this.root)); - - if (this.canSplit) return this.root; - - const table = this.root.tables[0]; - if (!table) return; - - delete table.$parent; - return table; + return { + columns: this.columns, + rows: this.rows + }; }; export { TabbedAggResponseWriter }; diff --git a/src/ui/public/agg_response/tabify/_table.js b/src/ui/public/agg_response/tabify/_table.js deleted file mode 100644 index 80b84407c6584..0000000000000 --- a/src/ui/public/agg_response/tabify/_table.js +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -/** - * Simple table class that is used to contain the rows and columns that create - * a table. This is usually found at the root of the response or within a TableGroup - */ -function TabifyTable() { - this.columns = null; // written with the first row - this.rows = []; -} - -TabifyTable.prototype.title = function () { - if (this.$parent) { - return this.$parent.title; - } else { - return ''; - } -}; - -TabifyTable.prototype.aggConfig = function (col) { - if (!col.aggConfig) { - throw new TypeError('Column is missing the aggConfig property'); - } - return col.aggConfig; -}; - -TabifyTable.prototype.field = function (col) { - return this.aggConfig(col).getField(); -}; - -TabifyTable.prototype.fieldFormatter = function (col) { - return this.aggConfig(col).fieldFormatter(); -}; - - -export { TabifyTable }; diff --git a/src/ui/public/agg_response/tabify/_table_group.js b/src/ui/public/agg_response/tabify/_table_group.js deleted file mode 100644 index 6306a783e1ecf..0000000000000 --- a/src/ui/public/agg_response/tabify/_table_group.js +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -/** - * Simple object that wraps multiple tables. It contains information about the aggConfig - * and bucket that created this group and a list of the tables within it. - */ -function TabifyTableGroup() { - this.aggConfig = null; - this.key = null; - this.title = null; - this.tables = []; -} - -TabifyTableGroup.prototype.field = function () { - if (this.aggConfig) return this.aggConfig.getField(); -}; - -TabifyTableGroup.prototype.fieldFormatter = function () { - if (this.aggConfig) return this.aggConfig.fieldFormatter(); -}; - -export { TabifyTableGroup }; diff --git a/src/ui/public/agg_response/tabify/tabify.js b/src/ui/public/agg_response/tabify/tabify.js index 7e91670d83fb3..08abe3fbb3596 100644 --- a/src/ui/public/agg_response/tabify/tabify.js +++ b/src/ui/public/agg_response/tabify/tabify.js @@ -43,7 +43,8 @@ export function tabifyAggResponse(aggs, esResponse, respOpts = {}) { * @returns {undefined} */ function collectBucket(write, bucket, key, aggScale) { - const agg = write.aggStack.shift(); + const column = write.aggStack.shift(); + const agg = column.aggConfig; const aggInfo = agg.write(write.aggs); aggScale *= aggInfo.metricScale || 1; @@ -51,23 +52,24 @@ function collectBucket(write, bucket, key, aggScale) { case 'buckets': const buckets = new TabifyBuckets(bucket[agg.id], agg.params, write.timeRange); if (buckets.length) { - const splitting = write.canSplit && agg.schema.name === 'split'; - if (splitting) { - write.split(agg, buckets, function forEachBucket(subBucket, key) { - collectBucket(write, subBucket, agg.getKey(subBucket, key), aggScale); - }); - } else { - buckets.forEach(function (subBucket, key) { - write.cell(agg, agg.getKey(subBucket, key), function () { - collectBucket(write, subBucket, agg.getKey(subBucket, key), aggScale); - }, subBucket.filters); - }); - } - } else if (write.partialRows && write.metricsForAllBuckets && write.minimalColumns) { + buckets.forEach(function (subBucket, key) { + // if the bucket doesn't have value don't add it to the row + // we don't want rows like: { column1: undefined, column2: 10 } + const bucketValue = agg.getKey(subBucket, key); + const hasBucketValue = typeof bucketValue !== 'undefined'; + if (hasBucketValue) { + write.bucketBuffer.push({ id: column.id, value: bucketValue }); + } + collectBucket(write, subBucket, agg.getKey(subBucket, key), aggScale); + if (hasBucketValue) { + write.bucketBuffer.pop(); + } + }); + } else if (write.partialRows) { // we don't have any buckets, but we do have metrics at this // level, then pass all the empty buckets and jump back in for // the metrics. - write.aggStack.unshift(agg); + write.aggStack.unshift(column); passEmptyBuckets(write, bucket, key, aggScale); write.aggStack.shift(); } else { @@ -83,39 +85,41 @@ function collectBucket(write, bucket, key, aggScale) { if (aggScale !== 1) { value *= aggScale; } - write.cell(agg, value, function () { - if (!write.aggStack.length) { - // row complete - write.row(); - } else { - // process the next agg at this same level - collectBucket(write, bucket, key, aggScale); - } - }); + write.metricBuffer.push({ id: column.id, value: value }); + + if (!write.aggStack.length) { + // row complete + write.row(); + } else { + // process the next agg at this same level + collectBucket(write, bucket, key, aggScale); + } + + write.metricBuffer.pop(); + break; } - write.aggStack.unshift(agg); + write.aggStack.unshift(column); } // write empty values for each bucket agg, then write // the metrics from the initial bucket using collectBucket() function passEmptyBuckets(write, bucket, key, aggScale) { - const agg = write.aggStack.shift(); + const column = write.aggStack.shift(); + const agg = column.aggConfig; switch (agg.type.type) { case 'metrics': // pass control back to collectBucket() - write.aggStack.unshift(agg); + write.aggStack.unshift(column); collectBucket(write, bucket, key, aggScale); return; case 'buckets': - write.cell(agg, '', function () { - passEmptyBuckets(write, bucket, key, aggScale); - }); + passEmptyBuckets(write, bucket, key, aggScale); } - write.aggStack.unshift(agg); + write.aggStack.unshift(column); } diff --git a/src/ui/public/agg_table/__tests__/_group.js b/src/ui/public/agg_table/__tests__/_group.js index 5bf8741362ee0..2c6c988f674c4 100644 --- a/src/ui/public/agg_table/__tests__/_group.js +++ b/src/ui/public/agg_table/__tests__/_group.js @@ -21,7 +21,7 @@ import $ from 'jquery'; import ngMock from 'ng_mock'; import expect from 'expect.js'; import fixtures from 'fixtures/fake_hierarchical_data'; -import { tabifyAggResponse } from '../../agg_response/tabify/tabify'; +import { LegacyResponseHandlerProvider } from '../../vis/response_handlers/legacy'; import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; import { VisProvider } from '../../vis'; describe('AggTableGroup Directive', function () { @@ -30,9 +30,11 @@ describe('AggTableGroup Directive', function () { let $compile; let Vis; let indexPattern; + let tableAggResponse; beforeEach(ngMock.module('kibana')); beforeEach(ngMock.inject(function ($injector, Private) { + tableAggResponse = Private(LegacyResponseHandlerProvider).handler; indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider); Vis = Private(VisProvider); @@ -49,9 +51,9 @@ describe('AggTableGroup Directive', function () { }); - it('renders a simple split response properly', function () { + it('renders a simple split response properly', async function () { const vis = new Vis(indexPattern, 'table'); - $scope.group = tabifyAggResponse(vis.getAggConfig(), fixtures.metricOnly); + $scope.group = await tableAggResponse(vis, fixtures.metricOnly); $scope.sort = { columnIndex: null, direction: null @@ -79,7 +81,7 @@ describe('AggTableGroup Directive', function () { expect($subTables.length).to.be(0); }); - it('renders a complex response properly', function () { + it('renders a complex response properly', async function () { const vis = new Vis(indexPattern, { type: 'pie', aggs: [ @@ -93,7 +95,7 @@ describe('AggTableGroup Directive', function () { agg.id = 'agg_' + (i + 1); }); - const group = $scope.group = tabifyAggResponse(vis.getAggConfig(), fixtures.threeTermBuckets); + const group = $scope.group = await tableAggResponse(vis, fixtures.threeTermBuckets); const $el = $(''); $compile($el)($scope); $scope.$digest(); diff --git a/src/ui/public/agg_table/__tests__/_table.js b/src/ui/public/agg_table/__tests__/_table.js index 4d556bfafe9fc..9e67c6bf42a17 100644 --- a/src/ui/public/agg_table/__tests__/_table.js +++ b/src/ui/public/agg_table/__tests__/_table.js @@ -17,14 +17,13 @@ * under the License. */ -import _ from 'lodash'; import $ from 'jquery'; import moment from 'moment'; import ngMock from 'ng_mock'; import expect from 'expect.js'; import fixtures from 'fixtures/fake_hierarchical_data'; import sinon from 'sinon'; -import { tabifyAggResponse } from '../../agg_response/tabify/tabify'; +import { LegacyResponseHandlerProvider } from '../../vis/response_handlers/legacy'; import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; import { VisProvider } from '../../vis'; describe('AggTable Directive', function () { @@ -34,9 +33,11 @@ describe('AggTable Directive', function () { let Vis; let indexPattern; let settings; + let tableAggResponse; beforeEach(ngMock.module('kibana')); beforeEach(ngMock.inject(function ($injector, Private, config) { + tableAggResponse = Private(LegacyResponseHandlerProvider).handler; indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider); Vis = Private(VisProvider); settings = config; @@ -54,20 +55,19 @@ describe('AggTable Directive', function () { }); - it('renders a simple response properly', function () { + it('renders a simple response properly', async function () { const vis = new Vis(indexPattern, 'table'); - $scope.table = tabifyAggResponse( - vis.getAggConfig(), - fixtures.metricOnly, - { canSplit: false, hierarchical: vis.isHierarchical() } - ); + $scope.table = (await tableAggResponse( + vis, + fixtures.metricOnly + )).tables[0]; const $el = $compile('')($scope); $scope.$digest(); expect($el.find('tbody').length).to.be(1); expect($el.find('td').length).to.be(1); - expect($el.find('td').text()).to.eql(1000); + expect($el.find('td').text()).to.eql('1,000'); }); it('renders nothing if the table is empty', function () { @@ -78,24 +78,24 @@ describe('AggTable Directive', function () { expect($el.find('tbody').length).to.be(0); }); - it('renders a complex response properly', function () { + it('renders a complex response properly', async function () { const vis = new Vis(indexPattern, { - type: 'pie', + type: 'table', + params: { + showMetricsAtAllLevels: true + }, aggs: [ { type: 'avg', schema: 'metric', params: { field: 'bytes' } }, - { type: 'terms', schema: 'split', params: { field: 'extension' } }, - { type: 'terms', schema: 'segment', params: { field: 'geo.src' } }, - { type: 'terms', schema: 'segment', params: { field: 'machine.os' } } + { type: 'terms', schema: 'bucket', params: { field: 'extension' } }, + { type: 'terms', schema: 'bucket', params: { field: 'geo.src' } }, + { type: 'terms', schema: 'bucket', params: { field: 'machine.os' } } ] }); vis.aggs.forEach(function (agg, i) { agg.id = 'agg_' + (i + 1); }); - $scope.table = tabifyAggResponse(vis.getAggConfig(), fixtures.threeTermBuckets, { - canSplit: false, - isHierarchical: vis.isHierarchical() - }); + $scope.table = (await tableAggResponse(vis, fixtures.threeTermBuckets)).tables[0]; const $el = $(''); $compile($el)($scope); $scope.$digest(); @@ -106,9 +106,10 @@ describe('AggTable Directive', function () { expect($rows.length).to.be.greaterThan(0); function validBytes(str) { - expect(str).to.match(/^\d+$/); - const bytesAsNum = _.parseInt(str); - expect(bytesAsNum === 0 || bytesAsNum > 1000).to.be.ok(); + const num = str.replace(/,/g, ''); + if (num !== '-') { + expect(num).to.match(/^\d+$/); + } } $rows.each(function () { @@ -135,7 +136,7 @@ describe('AggTable Directive', function () { }); describe('renders totals row', function () { - function totalsRowTest(totalFunc, expected) { + async function totalsRowTest(totalFunc, expected) { const vis = new Vis(indexPattern, { type: 'table', aggs: [ @@ -158,10 +159,10 @@ describe('AggTable Directive', function () { const oldTimezoneSetting = settings.get('dateFormat:tz'); settings.set('dateFormat:tz', 'UTC'); - $scope.table = tabifyAggResponse(vis.getAggConfig(), + $scope.table = (await tableAggResponse(vis, fixtures.oneTermOneHistogramBucketWithTwoMetricsOneTopHitOneDerivative, { canSplit: false, minimalColumns: true, asAggConfigResults: true } - ); + )).tables[0]; $scope.showTotal = true; $scope.totalFunc = totalFunc; const $el = $(''); @@ -182,11 +183,11 @@ describe('AggTable Directive', function () { settings.set('dateFormat:tz', oldTimezoneSetting); off(); } - it('as count', function () { - totalsRowTest('count', ['18', '18', '18', '18', '18', '18']); + it('as count', async function () { + await totalsRowTest('count', ['18', '18', '18', '18', '18', '18']); }); - it('as min', function () { - totalsRowTest('min', [ + it('as min', async function () { + await totalsRowTest('min', [ '', '2014-09-28', '9,283', @@ -195,8 +196,8 @@ describe('AggTable Directive', function () { '11' ]); }); - it('as max', function () { - totalsRowTest('max', [ + it('as max', async function () { + await totalsRowTest('max', [ '', '2014-10-03', '220,943', @@ -205,8 +206,8 @@ describe('AggTable Directive', function () { '837' ]); }); - it('as avg', function () { - totalsRowTest('avg', [ + it('as avg', async function () { + await totalsRowTest('avg', [ '', '', '87,221.5', @@ -215,8 +216,8 @@ describe('AggTable Directive', function () { '206.833' ]); }); - it('as sum', function () { - totalsRowTest('sum', [ + it('as sum', async function () { + await totalsRowTest('sum', [ '', '', '1,569,987', diff --git a/src/ui/public/agg_table/agg_table.js b/src/ui/public/agg_table/agg_table.js index dba9ebeb5d10b..352d1152aa99d 100644 --- a/src/ui/public/agg_table/agg_table.js +++ b/src/ui/public/agg_table/agg_table.js @@ -102,10 +102,10 @@ uiModules return; } - self.csv.filename = ($scope.exportTitle || table.title() || 'table') + '.csv'; + self.csv.filename = ($scope.exportTitle || table.title || 'table') + '.csv'; $scope.rows = table.rows; $scope.formattedColumns = table.columns.map(function (col, i) { - const agg = $scope.table.aggConfig(col); + const agg = col.aggConfig; const field = agg.getField(); const formattedColumn = { title: col.title, diff --git a/src/ui/public/agg_types/__tests__/metrics/median.js b/src/ui/public/agg_types/__tests__/metrics/median.js index b4db30e3d929b..de81913494bae 100644 --- a/src/ui/public/agg_types/__tests__/metrics/median.js +++ b/src/ui/public/agg_types/__tests__/metrics/median.js @@ -35,8 +35,7 @@ describe('AggTypeMetricMedianProvider class', function () { 'title': 'New Visualization', 'type': 'metric', 'params': { - 'fontSize': 60, - 'handleNoResults': true + 'fontSize': 60 }, 'aggs': [ { diff --git a/src/ui/public/agg_types/__tests__/metrics/parent_pipeline.js b/src/ui/public/agg_types/__tests__/metrics/parent_pipeline.js index d149fd34a2a0f..78a9ad4d10797 100644 --- a/src/ui/public/agg_types/__tests__/metrics/parent_pipeline.js +++ b/src/ui/public/agg_types/__tests__/metrics/parent_pipeline.js @@ -59,8 +59,7 @@ describe('parent pipeline aggs', function () { title: 'New Visualization', type: 'metric', params: { - fontSize: 60, - handleNoResults: true + fontSize: 60 }, aggs: [ { diff --git a/src/ui/public/agg_types/__tests__/metrics/sibling_pipeline.js b/src/ui/public/agg_types/__tests__/metrics/sibling_pipeline.js index e3e09b886d03d..43ae223eaf17e 100644 --- a/src/ui/public/agg_types/__tests__/metrics/sibling_pipeline.js +++ b/src/ui/public/agg_types/__tests__/metrics/sibling_pipeline.js @@ -68,8 +68,7 @@ describe('sibling pipeline aggs', function () { title: 'New Visualization', type: 'metric', params: { - fontSize: 60, - handleNoResults: true + fontSize: 60 }, aggs: [ { diff --git a/src/ui/public/agg_types/__tests__/metrics/top_hit.js b/src/ui/public/agg_types/__tests__/metrics/top_hit.js index 5dc109422c16f..a83053140f5cd 100644 --- a/src/ui/public/agg_types/__tests__/metrics/top_hit.js +++ b/src/ui/public/agg_types/__tests__/metrics/top_hit.js @@ -49,8 +49,7 @@ describe('Top hit metric', function () { title: 'New Visualization', type: 'metric', params: { - fontSize: 60, - handleNoResults: true + fontSize: 60 }, aggs: [ { diff --git a/src/ui/public/vis/__tests__/_vis.js b/src/ui/public/vis/__tests__/_vis.js index faccce9643a93..81ca0d31b5a03 100644 --- a/src/ui/public/vis/__tests__/_vis.js +++ b/src/ui/public/vis/__tests__/_vis.js @@ -269,10 +269,11 @@ describe('Vis Class', function () { data = { columns: [{ + id: 'col-0', title: 'test', aggConfig }], - rows: [['US']] + rows: [{ 'col-0': 'US' }] }; }); diff --git a/src/ui/public/vis/__tests__/response_handlers/_build_chart_data.js b/src/ui/public/vis/__tests__/response_handlers/_build_chart_data.js index ee3c8647b1635..789b2f94f43fc 100644 --- a/src/ui/public/vis/__tests__/response_handlers/_build_chart_data.js +++ b/src/ui/public/vis/__tests__/response_handlers/_build_chart_data.js @@ -21,9 +21,8 @@ import _ from 'lodash'; import ngMock from 'ng_mock'; import expect from 'expect.js'; import sinon from 'sinon'; -import { TabifyTable } from '../../../agg_response/tabify/_table'; import { AggResponseIndexProvider } from '../../../agg_response'; -import { BasicResponseHandlerProvider } from '../../response_handlers/basic'; +import { VislibResponseHandlerProvider } from '../../response_handlers/vislib'; describe('renderbot#buildChartData', function () { let buildChartData; @@ -32,7 +31,7 @@ describe('renderbot#buildChartData', function () { beforeEach(ngMock.module('kibana')); beforeEach(ngMock.inject(function (Private) { aggResponse = Private(AggResponseIndexProvider); - buildChartData = Private(BasicResponseHandlerProvider).handler; + buildChartData = Private(VislibResponseHandlerProvider).handler; })); describe('for hierarchical vis', function () { @@ -79,7 +78,7 @@ describe('renderbot#buildChartData', function () { } }; const esResp = { hits: { total: 1 } }; - const tabbed = { tables: [ new TabifyTable() ] }; + const tabbed = { tables: [ {}] }; sinon.stub(aggResponse, 'tabify').returns(tabbed); expect(buildChartData.call(renderbot, esResp)).to.eql(chart); @@ -88,7 +87,7 @@ describe('renderbot#buildChartData', function () { it('converts table groups into rows/columns wrappers for charts', function () { const converter = sinon.stub().returns('chart'); const esResp = { hits: { total: 1 } }; - const tables = [new TabifyTable(), new TabifyTable(), new TabifyTable(), new TabifyTable()]; + const tables = [{}, {}, {}, {}]; const renderbot = { vis: { diff --git a/src/ui/public/vis/__tests__/response_handlers/basic.js b/src/ui/public/vis/__tests__/response_handlers/basic.js index 11aabc0d8d91c..84febf6270ef2 100644 --- a/src/ui/public/vis/__tests__/response_handlers/basic.js +++ b/src/ui/public/vis/__tests__/response_handlers/basic.js @@ -19,7 +19,7 @@ import ngMock from 'ng_mock'; import expect from 'expect.js'; -import { BasicResponseHandlerProvider } from '../../response_handlers/basic'; +import { VislibResponseHandlerProvider } from '../../response_handlers/vislib'; import { VisProvider } from '../..'; import fixtures from 'fixtures/fake_hierarchical_data'; import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; @@ -39,7 +39,7 @@ describe('Basic Response Handler', function () { beforeEach(ngMock.module('kibana')); beforeEach(ngMock.inject(function (Private) { - basicResponseHandler = Private(BasicResponseHandlerProvider).handler; + basicResponseHandler = Private(VislibResponseHandlerProvider).handler; Vis = Private(VisProvider); indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider); })); diff --git a/src/ui/public/vis/__tests__/vis_types/vislib_vis_type.js b/src/ui/public/vis/__tests__/vis_types/vislib_vis_type.js index 6779638f1a4ca..5ad5f58b70b95 100644 --- a/src/ui/public/vis/__tests__/vis_types/vislib_vis_type.js +++ b/src/ui/public/vis/__tests__/vis_types/vislib_vis_type.js @@ -39,9 +39,9 @@ describe('Vislib Vis Type', function () { })); describe('initialization', () => { - it('should set the basic response handler if not set', () => { + it('should set the vislib response handler if not set', () => { const visType = new VislibVisType(visConfig); - expect(visType.responseHandler).to.equal('basic'); + expect(visType.responseHandler).to.equal('vislib'); }); it('should not change response handler if its already set', () => { diff --git a/src/ui/public/vis/map/convert_to_geojson.js b/src/ui/public/vis/map/convert_to_geojson.js index 317c2b4a5865c..0b4193460f1e5 100644 --- a/src/ui/public/vis/map/convert_to_geojson.js +++ b/src/ui/public/vis/map/convert_to_geojson.js @@ -28,27 +28,28 @@ export function convertToGeoJson(tabifiedResponse) { let max = -Infinity; let geoAgg; - if (tabifiedResponse && tabifiedResponse.tables && tabifiedResponse.tables[0] && tabifiedResponse.tables[0].rows) { + if (tabifiedResponse && tabifiedResponse.rows) { - const table = tabifiedResponse.tables[0]; - const geohashIndex = table.columns.findIndex(column => column.aggConfig.type.dslName === 'geohash_grid'); - geoAgg = table.columns.find(column => column.aggConfig.type.dslName === 'geohash_grid'); + const table = tabifiedResponse; + const geohashColumn = table.columns.find(column => column.aggConfig.type.dslName === 'geohash_grid'); - if (geohashIndex === -1) { + if (!geohashColumn) { features = []; } else { - const metricIndex = table.columns.findIndex(column => column.aggConfig.type.type === 'metrics'); - const geocentroidIndex = table.columns.findIndex(column => column.aggConfig.type.dslName === 'geo_centroid'); + geoAgg = geohashColumn.aggConfig; + + const metricColumn = table.columns.find(column => column.aggConfig.type.type === 'metrics'); + const geocentroidColumn = table.columns.find(column => column.aggConfig.type.dslName === 'geo_centroid'); features = table.rows.map(row => { - const geohash = row[geohashIndex]; + const geohash = row[geohashColumn.id]; const geohashLocation = decodeGeoHash(geohash); let pointCoordinates; - if (geocentroidIndex > -1) { - const location = row[geocentroidIndex]; + if (geocentroidColumn) { + const location = row[geocentroidColumn.id]; pointCoordinates = [location.lon, location.lat]; } else { pointCoordinates = [geohashLocation.longitude[2], geohashLocation.latitude[2]]; @@ -66,13 +67,13 @@ export function convertToGeoJson(tabifiedResponse) { geohashLocation.longitude[2] ]; - if (geoAgg.aggConfig.params.useGeocentroid) { + if (geoAgg.params.useGeocentroid) { // see https://github.com/elastic/elasticsearch/issues/24694 for why clampGrid is used pointCoordinates[0] = clampGrid(pointCoordinates[0], geohashLocation.longitude[0], geohashLocation.longitude[1]); pointCoordinates[1] = clampGrid(pointCoordinates[1], geohashLocation.latitude[0], geohashLocation.latitude[1]); } - const value = row[metricIndex]; + const value = row[metricColumn.id]; min = Math.min(min, value); max = Math.max(max, value); @@ -111,8 +112,8 @@ export function convertToGeoJson(tabifiedResponse) { meta: { min: min, max: max, - geohashPrecision: geoAgg && geoAgg.aggConfig.params.precision, - geohashGridDimensionsAtEquator: geoAgg && gridDimensions(geoAgg.aggConfig.params.precision) + geohashPrecision: geoAgg && geoAgg.params.precision, + geohashGridDimensionsAtEquator: geoAgg && gridDimensions(geoAgg.params.precision) } }; } diff --git a/src/ui/public/vis/request_handlers/courier.js b/src/ui/public/vis/request_handlers/courier.js index eb9ff172d6226..f39130280e49c 100644 --- a/src/ui/public/vis/request_handlers/courier.js +++ b/src/ui/public/vis/request_handlers/courier.js @@ -35,10 +35,8 @@ const CourierRequestHandlerProvider = function () { */ async function buildTabularInspectorData(vis, searchSource, aggConfigs) { const table = tabifyAggResponse(aggConfigs, searchSource.finalResponse, { - canSplit: false, - asAggConfigResults: false, partialRows: true, - isHierarchical: vis.isHierarchical(), + metricsAtAllLevels: vis.isHierarchical(), }); const columns = table.columns.map((col, index) => { const field = col.aggConfig.getField(); @@ -46,7 +44,7 @@ const CourierRequestHandlerProvider = function () { col.aggConfig.isFilterable() && (!field || field.filterable); return ({ - name: col.title, + name: col.name, field: `col${index}`, filter: isCellContentFilterable && ((value) => { const filter = col.aggConfig.createFilter(value.raw); @@ -61,9 +59,10 @@ const CourierRequestHandlerProvider = function () { }); }); const rows = table.rows.map(row => { - return row.reduce((prev, cur, index) => { - const fieldFormatter = table.columns[index].aggConfig.fieldFormatter('text'); - prev[`col${index}`] = new FormattedData(cur, fieldFormatter(cur)); + return table.columns.reduce((prev, cur, index) => { + const value = row[cur.id]; + const fieldFormatter = cur.aggConfig.fieldFormatter('text'); + prev[`col${index}`] = new FormattedData(value, fieldFormatter(value)); return prev; }, {}); }); diff --git a/src/ui/public/vis/response_handlers/legacy.js b/src/ui/public/vis/response_handlers/legacy.js new file mode 100644 index 0000000000000..c8072efa4b4d2 --- /dev/null +++ b/src/ui/public/vis/response_handlers/legacy.js @@ -0,0 +1,110 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import _ from 'lodash'; +import { tabifyAggResponse } from '../../agg_response/tabify'; +import AggConfigResult from '../../vis/agg_config_result'; +import { VisResponseHandlersRegistryProvider } from '../../registry/vis_response_handlers'; + +const LegacyResponseHandlerProvider = function () { + + return { + name: 'legacy', + handler: function (vis, response) { + return new Promise((resolve) => { + const converted = { tables: [] }; + const metricsAtAllLevels = vis.params.hasOwnProperty('showMetricsAtAllLevels') ? + vis.params.showMetricsAtAllLevels : vis.isHierarchical(); + + const table = tabifyAggResponse(vis.getAggConfig(), response, { + metricsAtAllLevels: metricsAtAllLevels, + partialRows: vis.params.showPartialRows, + }); + + const asAggConfigResults = _.get(vis, 'type.responseHandlerConfig.asAggConfigResults', false); + + const splitColumn = table.columns.find(column => column.aggConfig.schema.name === 'split'); + if (splitColumn) { + const splitAgg = splitColumn.aggConfig; + const splitMap = {}; + let splitIndex = 0; + + table.rows.forEach(row => { + const splitValue = row[splitColumn.id]; + const splitColumnIndex = table.columns.findIndex(column => column === splitColumn); + + if (!splitMap.hasOwnProperty(splitValue)) { + splitMap[splitValue] = splitIndex++; + const tableGroup = { + $parent: converted, + aggConfig: splitAgg, + title: `${splitValue}: ${splitAgg.makeLabel()}`, + key: splitValue, + tables: [] + }; + tableGroup.tables.push({ + $parent: tableGroup, + columns: table.columns.filter((column, i) => i !== splitColumnIndex).map(column => ({ title: column.name, ...column })), + rows: [] + }); + + converted.tables.push(tableGroup); + } + + let previousSplitAgg = new AggConfigResult(splitAgg, null, splitValue, splitValue); + const tableIndex = splitMap[splitValue]; + const newRow = _.map(converted.tables[tableIndex].tables[0].columns, column => { + const value = row[column.id]; + const aggConfigResult = new AggConfigResult(column.aggConfig, previousSplitAgg, value, value); + if (column.aggConfig.type.type === 'buckets') { + previousSplitAgg = aggConfigResult; + } + return asAggConfigResults ? aggConfigResult : value; + }); + + converted.tables[tableIndex].tables[0].rows.push(newRow); + }); + } else { + + converted.tables.push({ + columns: table.columns.map(column => ({ title: column.name, ...column })), + rows: table.rows.map(row => { + let previousSplitAgg; + return table.columns.map(column => { + const value = row[column.id]; + const aggConfigResult = new AggConfigResult(column.aggConfig, previousSplitAgg, value, value); + if (column.aggConfig.type.type === 'buckets') { + previousSplitAgg = aggConfigResult; + } + return asAggConfigResults ? aggConfigResult : value; + }); + }), + aggConfig: (column) => column.aggConfig + }); + } + + resolve(converted); + }); + } + }; +}; + +VisResponseHandlersRegistryProvider.register(LegacyResponseHandlerProvider); + +export { LegacyResponseHandlerProvider }; diff --git a/src/ui/public/vis/response_handlers/tabify.js b/src/ui/public/vis/response_handlers/tabify.js index 6a74fd2dbd728..ac45995b73c24 100644 --- a/src/ui/public/vis/response_handlers/tabify.js +++ b/src/ui/public/vis/response_handlers/tabify.js @@ -17,7 +17,6 @@ * under the License. */ -import _ from 'lodash'; import { AggResponseIndexProvider } from '../../agg_response'; import { VisResponseHandlersRegistryProvider } from '../../registry/vis_response_handlers'; import { getTime } from 'ui/timefilter/get_time'; @@ -32,10 +31,9 @@ const TabifyResponseHandlerProvider = function (Private) { const time = getTime(vis.indexPattern, vis.filters.timeRange); const tableGroup = aggResponse.tabify(vis.getAggConfig(), response, { - canSplit: true, - asAggConfigResults: _.get(vis, 'type.responseHandlerConfig.asAggConfigResults', false), - isHierarchical: vis.isHierarchical(), - timeRange: time ? time.range : undefined + metricsAtAllLevels: vis.isHierarchical(), + partialRows: vis.params.showPartialRows, + timeRange: time ? time.range : undefined, }); resolve(tableGroup); diff --git a/src/ui/public/vis/response_handlers/basic.js b/src/ui/public/vis/response_handlers/vislib.js similarity index 66% rename from src/ui/public/vis/response_handlers/basic.js rename to src/ui/public/vis/response_handlers/vislib.js index 42625fade7b59..9cdf11596aaf4 100644 --- a/src/ui/public/vis/response_handlers/basic.js +++ b/src/ui/public/vis/response_handlers/vislib.js @@ -18,18 +18,18 @@ */ import { AggResponseIndexProvider } from '../../agg_response'; -import { TabifyTable } from '../../agg_response/tabify/_table'; -import { getTime } from 'ui/timefilter/get_time'; - +import { LegacyResponseHandlerProvider } from './legacy'; import { VisResponseHandlersRegistryProvider } from '../../registry/vis_response_handlers'; -const BasicResponseHandlerProvider = function (Private) { +const VislibResponseHandlerProvider = function (Private) { const aggResponse = Private(AggResponseIndexProvider); + const tableResponseProvider = Private(LegacyResponseHandlerProvider).handler; function convertTableGroup(vis, tableGroup) { const tables = tableGroup.tables; const firstChild = tables[0]; - if (firstChild instanceof TabifyTable) { + + if (firstChild.columns) { const chart = convertTable(vis, firstChild); // if chart is within a split, assign group title to its label @@ -40,6 +40,7 @@ const BasicResponseHandlerProvider = function (Private) { } if (!tables.length) return; + const out = {}; let outList; @@ -64,38 +65,34 @@ const BasicResponseHandlerProvider = function (Private) { } return { - name: 'basic', + name: 'vislib', handler: function (vis, response) { return new Promise((resolve) => { if (vis.isHierarchical()) { // the hierarchical converter is very self-contained (woot!) + // todo: it should be updated to be based on tabified data just as other responseConverters resolve(aggResponse.hierarchical(vis, response)); } - const time = getTime(vis.indexPattern, vis.filters.timeRange); + return tableResponseProvider(vis, response).then(tableGroup => { + let converted = convertTableGroup(vis, tableGroup); + if (!converted) { + // mimic a row of tables that doesn't have any tables + // https://github.com/elastic/kibana/blob/7bfb68cd24ed42b1b257682f93c50cd8d73e2520/src/kibana/components/vislib/components/zero_injection/inject_zeros.js#L32 + converted = { rows: [] }; + } - const tableGroup = aggResponse.tabify(vis.getAggConfig(), response, { - canSplit: true, - asAggConfigResults: true, - isHierarchical: vis.isHierarchical(), - timeRange: time ? time.range : undefined - }); + converted.hits = response.hits.total; - let converted = convertTableGroup(vis, tableGroup); - if (!converted) { - // mimic a row of tables that doesn't have any tables - // https://github.com/elastic/kibana/blob/7bfb68cd24ed42b1b257682f93c50cd8d73e2520/src/kibana/components/vislib/components/zero_injection/inject_zeros.js#L32 - converted = { rows: [] }; - } + resolve(converted); + }); - converted.hits = response.hits.total; - resolve(converted); }); } }; }; -VisResponseHandlersRegistryProvider.register(BasicResponseHandlerProvider); +VisResponseHandlersRegistryProvider.register(VislibResponseHandlerProvider); -export { BasicResponseHandlerProvider }; +export { VislibResponseHandlerProvider }; diff --git a/src/ui/public/vis/vis.js b/src/ui/public/vis/vis.js index e00943d815a66..84606c5b65688 100644 --- a/src/ui/public/vis/vis.js +++ b/src/ui/public/vis/vis.js @@ -49,7 +49,11 @@ const getTerms = (table, columnIndex, rowIndex) => { } // get only rows where cell value matches current row for all the fields before columnIndex - const rows = table.rows.filter(row => row.every((cell, i) => cell === table.rows[rowIndex][i] || i >= columnIndex)); + const rows = table.rows.filter(row => { + return table.columns.every((column, i) => { + return row[column.id] === table.rows[rowIndex][column.id] || i >= columnIndex; + }); + }); const terms = rows.map(row => row[columnIndex]); return [...new Set(terms.filter(term => { @@ -99,17 +103,17 @@ export function VisProvider(Private, indexPatterns, getAppState) { filterBarClickHandler(appState)(event); }, addFilter: (data, columnIndex, rowIndex, cellValue) => { - const agg = data.columns[columnIndex].aggConfig; + const { aggConfig, id: columnId } = data.columns[columnIndex]; let filter = []; - const value = rowIndex > -1 ? data.rows[rowIndex][columnIndex] : cellValue; + const value = rowIndex > -1 ? data.rows[rowIndex][columnId] : cellValue; if (!value) { return; } - if (agg.type.name === 'terms' && agg.params.otherBucket) { + if (aggConfig.type.name === 'terms' && aggConfig.params.otherBucket) { const terms = getTerms(data, columnIndex, rowIndex); - filter = agg.createFilter(value, { terms }); + filter = aggConfig.createFilter(value, { terms }); } else { - filter = agg.createFilter(value); + filter = aggConfig.createFilter(value); } queryFilter.addFilters(filter); }, brush: (event) => { diff --git a/src/ui/public/vis/vis_types/vislib_vis_type.js b/src/ui/public/vis/vis_types/vislib_vis_type.js index 7d503a66d2d3c..f691f511a2d7e 100644 --- a/src/ui/public/vis/vis_types/vislib_vis_type.js +++ b/src/ui/public/vis/vis_types/vislib_vis_type.js @@ -108,7 +108,8 @@ export function VislibVisTypeProvider(Private, $rootScope, $timeout, $compile) { class VislibVisType extends BaseVisType { constructor(opts) { if (!opts.responseHandler) { - opts.responseHandler = 'basic'; + opts.responseHandler = 'vislib'; + opts.responseHandlerConfig = { asAggConfigResults: true }; } if (!opts.responseConverter) { opts.responseConverter = pointSeries; diff --git a/src/ui/public/vislib/lib/handler.js b/src/ui/public/vislib/lib/handler.js index c370d5189f554..c07a8d7426e35 100644 --- a/src/ui/public/vislib/lib/handler.js +++ b/src/ui/public/vislib/lib/handler.js @@ -219,18 +219,7 @@ export function VisHandlerProvider(Private) { // to continuously call render on resize .attr('class', 'visualize-error chart error'); - if (message === 'No results found') { - div.append('div') - .attr('class', 'text-center visualize-error visualize-chart') - .append('div').attr('class', 'item top') - .append('div').attr('class', 'item') - .append('h2').html('') - .append('h4').text(message); - - div.append('div').attr('class', 'item bottom'); - } else { - div.append('h4').text(markdownIt.renderInline(message)); - } + div.append('h4').text(markdownIt.renderInline(message)); dispatchRenderComplete(this.el); return div; diff --git a/src/ui/public/visualize/components/visualization.test.js b/src/ui/public/visualize/components/visualization.test.js index 8c88e004c6fc6..1d768787ce193 100644 --- a/src/ui/public/visualize/components/visualization.test.js +++ b/src/ui/public/visualize/components/visualization.test.js @@ -43,7 +43,7 @@ class VisualizationStub { describe('', () => { const visData = { - hits: { total: 1 } + hits: 1 }; const uiState = { @@ -63,19 +63,18 @@ describe('', () => { return this.uiState; }, params: { - }, type: { title: 'new vis', requiresSearch: true, - handleNoResults: true, + useCustomNoDataScreen: false, visualization: VisualizationStub } }; }); it('should display no result message when length of data is 0', () => { - const data = { hits: { total: 0 } }; + const data = { rows: [] }; const wrapper = render(); expect(wrapper.text()).toBe('No results found'); }); @@ -87,7 +86,7 @@ describe('', () => { it('should call onInit when rendering no data', () => { const spy = jest.fn(); - const noData = { hits: { total: 0 } }; + const noData = { hits: 0 }; mount( void; } export class VisualizationNoResults extends React.Component { + private containerDiv = React.createRef(); + public render() { return ( -
+