mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
simplified tabify (#19061)
This commit is contained in:
parent
f6a3f900b1
commit
e5a94e7a10
66 changed files with 682 additions and 1375 deletions
|
@ -110,6 +110,7 @@ export default function GaugeVisType(Private) {
|
||||||
aggFilter: ['!geohash_grid', '!filter']
|
aggFilter: ['!geohash_grid', '!filter']
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
}
|
},
|
||||||
|
useCustomNoDataScreen: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -105,6 +105,7 @@ export default function GoalVisType(Private) {
|
||||||
aggFilter: ['!geohash_grid', '!filter']
|
aggFilter: ['!geohash_grid', '!filter']
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
}
|
},
|
||||||
|
useCustomNoDataScreen: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,7 +35,7 @@ import 'ui/query_bar';
|
||||||
import { hasSearchStategyForIndexPattern, isDefaultTypeIndexPattern } from 'ui/courier';
|
import { hasSearchStategyForIndexPattern, isDefaultTypeIndexPattern } from 'ui/courier';
|
||||||
import { toastNotifications } from 'ui/notify';
|
import { toastNotifications } from 'ui/notify';
|
||||||
import { VisProvider } from 'ui/vis';
|
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 { DocTitleProvider } from 'ui/doc_title';
|
||||||
import PluginsKibanaDiscoverHitSortFnProvider from '../_hit_sort_fn';
|
import PluginsKibanaDiscoverHitSortFnProvider from '../_hit_sort_fn';
|
||||||
import { FilterBarQueryFilterProvider } from 'ui/filter_bar/query_filter';
|
import { FilterBarQueryFilterProvider } from 'ui/filter_bar/query_filter';
|
||||||
|
@ -156,7 +156,7 @@ function discoverController(
|
||||||
const docTitle = Private(DocTitleProvider);
|
const docTitle = Private(DocTitleProvider);
|
||||||
const HitSortFn = Private(PluginsKibanaDiscoverHitSortFnProvider);
|
const HitSortFn = Private(PluginsKibanaDiscoverHitSortFnProvider);
|
||||||
const queryFilter = Private(FilterBarQueryFilterProvider);
|
const queryFilter = Private(FilterBarQueryFilterProvider);
|
||||||
const responseHandler = Private(BasicResponseHandlerProvider).handler;
|
const responseHandler = Private(VislibResponseHandlerProvider).handler;
|
||||||
const filterManager = Private(FilterManagerProvider);
|
const filterManager = Private(FilterManagerProvider);
|
||||||
const notify = new Notifier({
|
const notify = new Notifier({
|
||||||
location: 'Discover'
|
location: 'Discover'
|
||||||
|
|
|
@ -25,7 +25,7 @@ import { VisProvider } from 'ui/vis';
|
||||||
import LogstashIndexPatternStubProvider from 'fixtures/stubbed_logstash_index_pattern';
|
import LogstashIndexPatternStubProvider from 'fixtures/stubbed_logstash_index_pattern';
|
||||||
import MetricVisProvider from '../metric_vis';
|
import MetricVisProvider from '../metric_vis';
|
||||||
|
|
||||||
describe('metric_vis', () => {
|
describe('metric vis', () => {
|
||||||
let setup = null;
|
let setup = null;
|
||||||
let vis;
|
let vis;
|
||||||
|
|
||||||
|
@ -62,10 +62,8 @@ describe('metric_vis', () => {
|
||||||
|
|
||||||
const ip = '235.195.237.208';
|
const ip = '235.195.237.208';
|
||||||
render({
|
render({
|
||||||
tables: [{
|
columns: [{ id: 'col-0', title: 'ip', aggConfig: vis.aggs[0] }],
|
||||||
columns: [{ title: 'ip', aggConfig: vis.aggs[0] }],
|
rows: [{ 'col-0': ip }]
|
||||||
rows: [[ ip ]]
|
|
||||||
}]
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const $link = $(el)
|
const $link = $(el)
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
import expect from 'expect.js';
|
import expect from 'expect.js';
|
||||||
import { MetricVisComponent } from '../metric_vis_controller';
|
import { MetricVisComponent } from '../metric_vis_controller';
|
||||||
|
|
||||||
describe('metric vis', function () {
|
describe('metric vis controller', function () {
|
||||||
|
|
||||||
const vis = {
|
const vis = {
|
||||||
params: {
|
params: {
|
||||||
|
@ -53,10 +53,8 @@ describe('metric vis', function () {
|
||||||
|
|
||||||
it('should set the metric label and value', function () {
|
it('should set the metric label and value', function () {
|
||||||
const metrics = metricController._processTableGroups({
|
const metrics = metricController._processTableGroups({
|
||||||
tables: [{
|
columns: [{ id: 'col-0', title: 'Count', aggConfig: { ...aggConfig, makeLabel: () => 'Count' } }],
|
||||||
columns: [{ title: 'Count', aggConfig: { ...aggConfig, makeLabel: () => 'Count' } }],
|
rows: [{ 'col-0': 4301021 }]
|
||||||
rows: [[ 4301021 ]]
|
|
||||||
}]
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(metrics.length).to.be(1);
|
expect(metrics.length).to.be(1);
|
||||||
|
@ -66,13 +64,11 @@ describe('metric vis', function () {
|
||||||
|
|
||||||
it('should support multi-value metrics', function () {
|
it('should support multi-value metrics', function () {
|
||||||
const metrics = metricController._processTableGroups({
|
const metrics = metricController._processTableGroups({
|
||||||
tables: [{
|
columns: [
|
||||||
columns: [
|
{ id: 'col-0', aggConfig: { ...aggConfig, makeLabel: () => '1st percentile of bytes' } },
|
||||||
{ aggConfig: { ...aggConfig, makeLabel: () => '1st percentile of bytes' } },
|
{ id: 'col-1', aggConfig: { ...aggConfig, makeLabel: () => '99th percentile of bytes' } }
|
||||||
{ aggConfig: { ...aggConfig, makeLabel: () => '99th percentile of bytes' } }
|
],
|
||||||
],
|
rows: [{ 'col-0': 182, 'col-1': 445842.4634666484 }]
|
||||||
rows: [[ 182, 445842.4634666484 ]]
|
|
||||||
}]
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(metrics.length).to.be(2);
|
expect(metrics.length).to.be(2);
|
||||||
|
|
|
@ -100,6 +100,7 @@ function MetricVisProvider(Private) {
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
|
responseHandler: 'tabify',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -89,7 +89,7 @@ export class MetricVisComponent extends Component {
|
||||||
return fieldFormatter(value);
|
return fieldFormatter(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
_processTableGroups(tableGroups) {
|
_processTableGroups(table) {
|
||||||
const config = this.props.vis.params.metric;
|
const config = this.props.vis.params.metric;
|
||||||
const isPercentageMode = config.percentageMode;
|
const isPercentageMode = config.percentageMode;
|
||||||
const min = config.colorsRange[0].from;
|
const min = config.colorsRange[0].from;
|
||||||
|
@ -98,56 +98,55 @@ export class MetricVisComponent extends Component {
|
||||||
const labels = this._getLabels();
|
const labels = this._getLabels();
|
||||||
const metrics = [];
|
const metrics = [];
|
||||||
|
|
||||||
tableGroups.tables.forEach((table, tableIndex) => {
|
let bucketAgg;
|
||||||
let bucketAgg;
|
let bucketColumnId;
|
||||||
let rowHeaderIndex;
|
let rowHeaderIndex;
|
||||||
|
|
||||||
table.columns.forEach((column, columnIndex) => {
|
table.columns.forEach((column, columnIndex) => {
|
||||||
const aggConfig = column.aggConfig;
|
const aggConfig = column.aggConfig;
|
||||||
|
|
||||||
if (aggConfig && aggConfig.type.type === 'buckets') {
|
if (aggConfig && aggConfig.type.type === 'buckets') {
|
||||||
bucketAgg = aggConfig;
|
bucketAgg = aggConfig;
|
||||||
// Store the current index, so we later know in which position in the
|
// 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.
|
// row array, the bucket agg key will be, so we can create filters on it.
|
||||||
rowHeaderIndex = columnIndex;
|
rowHeaderIndex = columnIndex;
|
||||||
return;
|
bucketColumnId = column.id;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.rows.forEach((row, rowIndex) => {
|
||||||
|
|
||||||
|
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}%`;
|
||||||
}
|
}
|
||||||
|
|
||||||
table.rows.forEach((row, rowIndex) => {
|
if (aggConfig) {
|
||||||
|
if (!isPercentageMode) value = this._getFormattedValue(aggConfig.fieldFormatter('html'), value);
|
||||||
let title = column.title;
|
if (bucketAgg) {
|
||||||
let value = row[columnIndex];
|
const bucketValue = bucketAgg.fieldFormatter('text')(row[bucketColumnId]);
|
||||||
const color = this._getColor(value, labels, colors);
|
title = `${bucketValue} - ${aggConfig.makeLabel()}`;
|
||||||
|
} else {
|
||||||
if (isPercentageMode) {
|
title = aggConfig.makeLabel();
|
||||||
const percentage = Math.round(100 * (value - min) / (max - min));
|
|
||||||
value = `${percentage}%`;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (aggConfig) {
|
const shouldColor = config.colorsRange.length > 1;
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const shouldColor = config.colorsRange.length > 1;
|
metrics.push({
|
||||||
|
label: title,
|
||||||
metrics.push({
|
value: value,
|
||||||
label: title,
|
color: shouldColor && config.style.labelColor ? color : null,
|
||||||
value: value,
|
bgColor: shouldColor && config.style.bgColor ? color : null,
|
||||||
color: shouldColor && config.style.labelColor ? color : null,
|
lightText: shouldColor && config.style.bgColor && this._needsLightText(color),
|
||||||
bgColor: shouldColor && config.style.bgColor ? color : null,
|
filterKey: bucketColumnId !== undefined ? row[bucketColumnId] : null,
|
||||||
lightText: shouldColor && config.style.bgColor && this._needsLightText(color),
|
rowIndex: rowIndex,
|
||||||
filterKey: rowHeaderIndex !== undefined ? row[rowHeaderIndex] : null,
|
columnIndex: rowHeaderIndex,
|
||||||
tableIndex: tableIndex,
|
bucketAgg: bucketAgg,
|
||||||
rowIndex: rowIndex,
|
|
||||||
columnIndex: rowHeaderIndex,
|
|
||||||
bucketAgg: bucketAgg,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -159,7 +158,7 @@ export class MetricVisComponent extends Component {
|
||||||
if (!metric.filterKey || !metric.bucketAgg) {
|
if (!metric.filterKey || !metric.bucketAgg) {
|
||||||
return;
|
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);
|
this.props.vis.API.events.addFilter(table, metric.columnIndex, metric.rowIndex);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -108,22 +108,26 @@ describe('RegionMapsVisualizationTests', function () {
|
||||||
const _makeJsonAjaxCallOld = ChoroplethLayer.prototype._makeJsonAjaxCall;
|
const _makeJsonAjaxCallOld = ChoroplethLayer.prototype._makeJsonAjaxCall;
|
||||||
|
|
||||||
const dummyTableGroup = {
|
const dummyTableGroup = {
|
||||||
tables: [
|
columns: [{
|
||||||
{
|
'id': 'col-0',
|
||||||
columns: [{
|
'aggConfig': {
|
||||||
'aggConfig': {
|
'id': '2',
|
||||||
'id': '2',
|
'enabled': true,
|
||||||
'enabled': true,
|
'type': 'terms',
|
||||||
'type': 'terms',
|
'schema': 'segment',
|
||||||
'schema': 'segment',
|
'params': { 'field': 'geo.dest', 'size': 5, 'order': 'desc', 'orderBy': '1' }
|
||||||
'params': { 'field': 'geo.dest', 'size': 5, 'order': 'desc', 'orderBy': '1' }
|
}, 'title': 'geo.dest: Descending'
|
||||||
}, 'title': 'geo.dest: Descending'
|
}, {
|
||||||
}, {
|
'id': 'col-1',
|
||||||
'aggConfig': { 'id': '1', 'enabled': true, 'type': 'count', 'schema': 'metric', 'params': {} },
|
'aggConfig': { 'id': '1', 'enabled': true, 'type': 'count', 'schema': 'metric', 'params': {} },
|
||||||
'title': 'Count'
|
'title': 'Count'
|
||||||
}],
|
}],
|
||||||
rows: [['CN', 26], ['IN', 17], ['US', 6], ['DE', 4], ['BR', 3]]
|
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);
|
const newTableGroup = _.cloneDeep(dummyTableGroup);
|
||||||
newTableGroup.tables[0].rows.pop();//remove one shape
|
newTableGroup.rows.pop();//remove one shape
|
||||||
|
|
||||||
await regionMapsVisualization.render(newTableGroup, {
|
await regionMapsVisualization.render(newTableGroup, {
|
||||||
resize: false,
|
resize: false,
|
||||||
|
@ -306,7 +310,7 @@ describe('RegionMapsVisualizationTests', function () {
|
||||||
|
|
||||||
|
|
||||||
const anotherTableGroup = _.cloneDeep(newTableGroup);
|
const anotherTableGroup = _.cloneDeep(newTableGroup);
|
||||||
anotherTableGroup.tables[0].rows.pop();//remove one shape
|
anotherTableGroup.rows.pop();//remove one shape
|
||||||
domNode.style.width = '412px';
|
domNode.style.width = '412px';
|
||||||
domNode.style.height = '112px';
|
domNode.style.height = '112px';
|
||||||
await regionMapsVisualization.render(anotherTableGroup, {
|
await regionMapsVisualization.render(anotherTableGroup, {
|
||||||
|
@ -336,7 +340,7 @@ describe('RegionMapsVisualizationTests', function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
const newTableGroup = _.cloneDeep(dummyTableGroup);
|
const newTableGroup = _.cloneDeep(dummyTableGroup);
|
||||||
newTableGroup.tables[0].rows.pop();//remove one shape
|
newTableGroup.rows.pop();//remove one shape
|
||||||
vis.params.colorSchema = 'Blues';
|
vis.params.colorSchema = 'Blues';
|
||||||
await regionMapsVisualization.render(newTableGroup, {
|
await regionMapsVisualization.render(newTableGroup, {
|
||||||
resize: false,
|
resize: false,
|
||||||
|
|
|
@ -46,14 +46,17 @@ export function RegionMapsVisualizationProvider(Private, config) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async _updateData(tableGroup) {
|
async _updateData(table) {
|
||||||
this._chartData = tableGroup;
|
this._chartData = table;
|
||||||
let results;
|
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 = [];
|
results = [];
|
||||||
} else {
|
} else {
|
||||||
const buckets = tableGroup.tables[0].rows;
|
const termColumn = table.columns[0].id;
|
||||||
results = buckets.map(([term, value]) => {
|
const valueColumn = table.columns[1].id;
|
||||||
|
results = table.rows.map(row => {
|
||||||
|
const term = row[termColumn];
|
||||||
|
const value = row[valueColumn];
|
||||||
return { term: term, value: value };
|
return { term: term, value: value };
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -150,8 +153,8 @@ export function RegionMapsVisualizationProvider(Private, config) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const rowIndex = this._chartData.tables[0].rows.findIndex(row => row[0] === event);
|
const rowIndex = this._chartData.rows.findIndex(row => row[0] === event);
|
||||||
this._vis.API.events.addFilter(this._chartData.tables[0], 0, rowIndex, event);
|
this._vis.API.events.addFilter(this._chartData, 0, rowIndex, event);
|
||||||
});
|
});
|
||||||
|
|
||||||
this._choroplethLayer.on('styleChanged', (event) => {
|
this._choroplethLayer.on('styleChanged', (event) => {
|
||||||
|
|
|
@ -21,7 +21,7 @@ import $ from 'jquery';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import expect from 'expect.js';
|
import expect from 'expect.js';
|
||||||
import ngMock from 'ng_mock';
|
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 { VisProvider } from 'ui/vis';
|
||||||
import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';
|
import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';
|
||||||
import { AppStateProvider } from 'ui/state_management/app_state';
|
import { AppStateProvider } from 'ui/state_management/app_state';
|
||||||
|
@ -35,6 +35,7 @@ describe('Table Vis Controller', function () {
|
||||||
let Vis;
|
let Vis;
|
||||||
let fixtures;
|
let fixtures;
|
||||||
let AppState;
|
let AppState;
|
||||||
|
let tableAggResponse;
|
||||||
|
|
||||||
beforeEach(ngMock.module('kibana', 'kibana/table_vis'));
|
beforeEach(ngMock.module('kibana', 'kibana/table_vis'));
|
||||||
beforeEach(ngMock.inject(function ($injector) {
|
beforeEach(ngMock.inject(function ($injector) {
|
||||||
|
@ -44,6 +45,7 @@ describe('Table Vis Controller', function () {
|
||||||
fixtures = require('fixtures/fake_hierarchical_data');
|
fixtures = require('fixtures/fake_hierarchical_data');
|
||||||
AppState = Private(AppStateProvider);
|
AppState = Private(AppStateProvider);
|
||||||
Vis = Private(VisProvider);
|
Vis = Private(VisProvider);
|
||||||
|
tableAggResponse = Private(LegacyResponseHandlerProvider).handler;
|
||||||
}));
|
}));
|
||||||
|
|
||||||
function OneRangeVis(params) {
|
function OneRangeVis(params) {
|
||||||
|
@ -99,16 +101,14 @@ describe('Table Vis Controller', function () {
|
||||||
$rootScope.$apply();
|
$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();
|
const vis = new OneRangeVis();
|
||||||
initController(vis);
|
initController(vis);
|
||||||
|
|
||||||
expect(!$scope.tableGroups).to.be.ok();
|
expect(!$scope.tableGroups).to.be.ok();
|
||||||
expect(!$scope.hasSomeRows).to.be.ok();
|
expect(!$scope.hasSomeRows).to.be.ok();
|
||||||
|
|
||||||
attachEsResponseToScope(tabifyAggResponse(vis.getAggConfig(), fixtures.oneRangeBucket, {
|
attachEsResponseToScope(await tableAggResponse(vis, fixtures.oneRangeBucket));
|
||||||
isHierarchical: vis.isHierarchical()
|
|
||||||
}));
|
|
||||||
|
|
||||||
expect($scope.hasSomeRows).to.be(true);
|
expect($scope.hasSomeRows).to.be(true);
|
||||||
expect($scope.tableGroups).to.have.property('tables');
|
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);
|
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();
|
const vis = new OneRangeVis();
|
||||||
initController(vis);
|
initController(vis);
|
||||||
|
|
||||||
attachEsResponseToScope(tabifyAggResponse(vis.getAggConfig(), fixtures.oneRangeBucket, {
|
attachEsResponseToScope(await tableAggResponse(vis, fixtures.oneRangeBucket));
|
||||||
isHierarchical: vis.isHierarchical()
|
|
||||||
}));
|
|
||||||
removeEsResponseFromScope();
|
removeEsResponseFromScope();
|
||||||
|
|
||||||
expect(!$scope.hasSomeRows).to.be.ok();
|
expect(!$scope.hasSomeRows).to.be.ok();
|
||||||
expect(!$scope.tableGroups).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 = {
|
const sortObj = {
|
||||||
columnIndex: 1,
|
columnIndex: 1,
|
||||||
direction: 'asc'
|
direction: 'asc'
|
||||||
|
@ -142,15 +140,13 @@ describe('Table Vis Controller', function () {
|
||||||
const resp = _.cloneDeep(fixtures.oneRangeBucket);
|
const resp = _.cloneDeep(fixtures.oneRangeBucket);
|
||||||
resp.aggregations.agg_2.buckets = {};
|
resp.aggregations.agg_2.buckets = {};
|
||||||
|
|
||||||
attachEsResponseToScope(tabifyAggResponse(vis.getAggConfig(), resp, {
|
attachEsResponseToScope(await tableAggResponse(vis, resp));
|
||||||
isHierarchical: vis.isHierarchical()
|
|
||||||
}));
|
|
||||||
|
|
||||||
expect($scope.sort.columnIndex).to.equal(sortObj.columnIndex);
|
expect($scope.sort.columnIndex).to.equal(sortObj.columnIndex);
|
||||||
expect($scope.sort.direction).to.equal(sortObj.direction);
|
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();
|
const vis = new OneRangeVis();
|
||||||
initController(vis);
|
initController(vis);
|
||||||
|
|
||||||
|
@ -158,9 +154,7 @@ describe('Table Vis Controller', function () {
|
||||||
const resp = _.cloneDeep(fixtures.oneRangeBucket);
|
const resp = _.cloneDeep(fixtures.oneRangeBucket);
|
||||||
resp.aggregations.agg_2.buckets = {};
|
resp.aggregations.agg_2.buckets = {};
|
||||||
|
|
||||||
attachEsResponseToScope(tabifyAggResponse(vis.getAggConfig(), resp, {
|
attachEsResponseToScope(await tableAggResponse(vis, resp));
|
||||||
isHierarchical: vis.isHierarchical()
|
|
||||||
}));
|
|
||||||
|
|
||||||
expect($scope.hasSomeRows).to.be(false);
|
expect($scope.hasSomeRows).to.be(false);
|
||||||
expect(!$scope.tableGroups).to.be.ok();
|
expect(!$scope.tableGroups).to.be.ok();
|
||||||
|
|
|
@ -96,6 +96,7 @@ function TableVisTypeProvider(Private) {
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
|
responseHandler: 'legacy',
|
||||||
responseHandlerConfig: {
|
responseHandlerConfig: {
|
||||||
asAggConfigResults: true
|
asAggConfigResults: true
|
||||||
},
|
},
|
||||||
|
|
|
@ -39,23 +39,27 @@ describe('TagCloudVisualizationTest', function () {
|
||||||
let imageComparator;
|
let imageComparator;
|
||||||
|
|
||||||
const dummyTableGroup = {
|
const dummyTableGroup = {
|
||||||
tables: [
|
columns: [{
|
||||||
{
|
id: 'col-0',
|
||||||
columns: [{
|
'aggConfig': {
|
||||||
'aggConfig': {
|
'id': '2',
|
||||||
'id': '2',
|
'enabled': true,
|
||||||
'enabled': true,
|
'type': 'terms',
|
||||||
'type': 'terms',
|
'schema': 'segment',
|
||||||
'schema': 'segment',
|
'params': { 'field': 'geo.dest', 'size': 5, 'order': 'desc', 'orderBy': '1' },
|
||||||
'params': { 'field': 'geo.dest', 'size': 5, 'order': 'desc', 'orderBy': '1' },
|
fieldFormatter: () => (x => x)
|
||||||
fieldFormatter: () => (x => x)
|
}, 'title': 'geo.dest: Descending'
|
||||||
}, 'title': 'geo.dest: Descending'
|
}, {
|
||||||
}, {
|
id: 'col-1',
|
||||||
'aggConfig': { 'id': '1', 'enabled': true, 'type': 'count', 'schema': 'metric', 'params': {} },
|
'aggConfig': { 'id': '1', 'enabled': true, 'type': 'count', 'schema': 'metric', 'params': {} },
|
||||||
'title': 'Count'
|
'title': 'Count'
|
||||||
}],
|
}],
|
||||||
rows: [['CN', 26], ['IN', 17], ['US', 6], ['DE', 4], ['BR', 3]]
|
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 }
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -77,6 +77,7 @@ VisTypesRegistryProvider.register(function (Private) {
|
||||||
aggFilter: ['terms', 'significant_terms']
|
aggFilter: ['terms', 'significant_terms']
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
}
|
},
|
||||||
|
useCustomNoDataScreen: true
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -110,13 +110,12 @@ export class TagCloudVisualization {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_updateData(response) {
|
_updateData(data) {
|
||||||
if (!response || !response.tables.length) {
|
if (!data || !data.rows.length) {
|
||||||
this._tagCloud.setData([]);
|
this._tagCloud.setData([]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = response.tables[0];
|
|
||||||
const segmentAggs = this._vis.aggs.bySchemaName.segment;
|
const segmentAggs = this._vis.aggs.bySchemaName.segment;
|
||||||
if (segmentAggs && segmentAggs.length > 0) {
|
if (segmentAggs && segmentAggs.length > 0) {
|
||||||
this._bucketAgg = segmentAggs[0];
|
this._bucketAgg = segmentAggs[0];
|
||||||
|
@ -124,12 +123,16 @@ export class TagCloudVisualization {
|
||||||
this._bucketAgg = null;
|
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 tags = data.rows.map((row, rowIndex) => {
|
||||||
const [tag, count] = row;
|
const tag = row[tagColumn] || 'all';
|
||||||
|
const metric = row[metricColumn];
|
||||||
return {
|
return {
|
||||||
displayText: this._bucketAgg ? this._bucketAgg.fieldFormatter()(tag) : tag,
|
displayText: this._bucketAgg ? this._bucketAgg.fieldFormatter()(tag) : tag,
|
||||||
rawText: tag,
|
rawText: tag,
|
||||||
value: count,
|
value: metric,
|
||||||
meta: {
|
meta: {
|
||||||
data: data,
|
data: data,
|
||||||
rowIndex: rowIndex,
|
rowIndex: rowIndex,
|
||||||
|
|
|
@ -34,9 +34,7 @@ export function makeGeoJsonResponseHandler() {
|
||||||
|
|
||||||
//double conversion, first to table, then to geojson
|
//double conversion, first to table, then to geojson
|
||||||
//This is to future-proof this code for Canvas-refactoring
|
//This is to future-proof this code for Canvas-refactoring
|
||||||
const tabifiedResponse = tabifyAggResponse(vis.getAggConfig(), esResponse, {
|
const tabifiedResponse = tabifyAggResponse(vis.getAggConfig(), esResponse);
|
||||||
asAggConfigResults: false
|
|
||||||
});
|
|
||||||
lastGeoJsonResponse = convertToGeoJson(tabifiedResponse);
|
lastGeoJsonResponse = convertToGeoJson(tabifiedResponse);
|
||||||
|
|
||||||
return lastGeoJsonResponse;
|
return lastGeoJsonResponse;
|
||||||
|
|
|
@ -43,22 +43,18 @@ describe('makeFakeXAspect', function () {
|
||||||
|
|
||||||
expect(aspect)
|
expect(aspect)
|
||||||
.to.have.property('i', -1)
|
.to.have.property('i', -1)
|
||||||
.and.have.property('agg')
|
.and.have.property('aggConfig')
|
||||||
.and.have.property('col');
|
.and.have.property('title');
|
||||||
|
|
||||||
expect(aspect.agg)
|
expect(aspect.aggConfig)
|
||||||
.to.be.an(AggConfig)
|
.to.be.an(AggConfig)
|
||||||
.and.to.have.property('type');
|
.and.to.have.property('type');
|
||||||
|
|
||||||
expect(aspect.agg.type)
|
expect(aspect.aggConfig.type)
|
||||||
.to.be.an(AggType)
|
.to.be.an(AggType)
|
||||||
.and.to.have.property('name', 'all')
|
.and.to.have.property('name', 'all')
|
||||||
.and.to.have.property('title', 'All docs')
|
.and.to.have.property('title', 'All docs')
|
||||||
.and.to.have.property('hasNoDsl', true);
|
.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());
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -58,8 +58,7 @@ describe('getAspects', function () {
|
||||||
expect(aspect)
|
expect(aspect)
|
||||||
.to.be.an('object')
|
.to.be.an('object')
|
||||||
.and.have.property('i', i)
|
.and.have.property('i', i)
|
||||||
.and.have.property('agg', vis.aggs[i])
|
.and.have.property('aggConfig', vis.aggs[i]);
|
||||||
.and.have.property('col', table.columns[i]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function init(group, x, y) {
|
function init(group, x, y) {
|
||||||
|
@ -150,13 +149,10 @@ describe('getAspects', function () {
|
||||||
expect(aspects.x)
|
expect(aspects.x)
|
||||||
.to.be.an('object')
|
.to.be.an('object')
|
||||||
.and.have.property('i', -1)
|
.and.have.property('i', -1)
|
||||||
.and.have.property('agg')
|
.and.have.property('aggConfig')
|
||||||
.and.have.property('col');
|
.and.have.property('title');
|
||||||
|
|
||||||
expect(aspects.x.agg).to.be.an(AggConfig);
|
expect(aspects.x.aggConfig).to.be.an(AggConfig);
|
||||||
expect(aspects.x.col)
|
|
||||||
.to.be.an('object')
|
|
||||||
.and.to.have.property('aggConfig', aspects.x.agg);
|
|
||||||
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -37,15 +37,13 @@ describe('getPoint', function () {
|
||||||
describe('Without series aspect', function () {
|
describe('Without series aspect', function () {
|
||||||
let seriesAspect;
|
let seriesAspect;
|
||||||
let xAspect;
|
let xAspect;
|
||||||
let yCol;
|
|
||||||
let yAspect;
|
let yAspect;
|
||||||
let yScale;
|
let yScale;
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
seriesAspect = null;
|
seriesAspect = null;
|
||||||
xAspect = { i: 0 };
|
xAspect = { i: 0 };
|
||||||
yCol = { title: 'Y', aggConfig: {} };
|
yAspect = { i: 1, title: 'Y', aggConfig: {} };
|
||||||
yAspect = { i: 1, col: yCol };
|
|
||||||
yScale = 5;
|
yScale = 5;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -58,7 +56,7 @@ describe('getPoint', function () {
|
||||||
.to.have.property('x', 1)
|
.to.have.property('x', 1)
|
||||||
.and.have.property('y', 10)
|
.and.have.property('y', 10)
|
||||||
.and.have.property('z', 3)
|
.and.have.property('z', 3)
|
||||||
.and.have.property('series', yCol.title)
|
.and.have.property('series', yAspect.title)
|
||||||
.and.have.property('aggConfigResult', row[1]);
|
.and.have.property('aggConfigResult', row[1]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -83,7 +81,7 @@ describe('getPoint', function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('properly unwraps and scales values', 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);
|
const point = getPoint(xAspect, seriesAspect, yScale, row, yAspect);
|
||||||
|
|
||||||
expect(point)
|
expect(point)
|
||||||
|
@ -94,7 +92,7 @@ describe('getPoint', function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('properly formats series values', 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);
|
const point = getPoint(xAspect, seriesAspect, yScale, row, yAspect);
|
||||||
|
|
||||||
expect(point)
|
expect(point)
|
||||||
|
@ -105,7 +103,7 @@ describe('getPoint', function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
it ('adds the aggConfig to the points', 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);
|
const point = getPoint(xAspect, seriesAspect, yScale, row, yAspect);
|
||||||
|
|
||||||
expect(point).to.have.property('aggConfig', truthFormatted);
|
expect(point).to.have.property('aggConfig', truthFormatted);
|
||||||
|
|
|
@ -47,11 +47,10 @@ describe('getSeries', function () {
|
||||||
[1, 2, 3]
|
[1, 2, 3]
|
||||||
].map(wrapRows);
|
].map(wrapRows);
|
||||||
|
|
||||||
const yCol = { aggConfig: {}, title: 'y' };
|
|
||||||
const chart = {
|
const chart = {
|
||||||
aspects: {
|
aspects: {
|
||||||
x: { i: 0 },
|
x: { i: 0 },
|
||||||
y: { i: 1, col: yCol, agg: { id: 'id' } },
|
y: { i: 1, title: 'y', aggConfig: { id: 'id' } },
|
||||||
z: { i: 2 }
|
z: { i: 2 }
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -65,7 +64,7 @@ describe('getSeries', function () {
|
||||||
const siri = series[0];
|
const siri = series[0];
|
||||||
expect(siri)
|
expect(siri)
|
||||||
.to.be.an('object')
|
.to.be.an('object')
|
||||||
.and.have.property('label', yCol.title)
|
.and.have.property('label', chart.aspects.y.title)
|
||||||
.and.have.property('values');
|
.and.have.property('values');
|
||||||
|
|
||||||
expect(siri.values)
|
expect(siri.values)
|
||||||
|
@ -93,8 +92,8 @@ describe('getSeries', function () {
|
||||||
aspects: {
|
aspects: {
|
||||||
x: { i: 0 },
|
x: { i: 0 },
|
||||||
y: [
|
y: [
|
||||||
{ i: 1, col: { title: '0' }, agg: { id: 1 } },
|
{ i: 1, title: '0', aggConfig: { id: 1 } },
|
||||||
{ i: 2, col: { title: '1' }, agg: { id: 2 } },
|
{ i: 2, title: '1', aggConfig: { id: 2 } },
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -138,8 +137,8 @@ describe('getSeries', function () {
|
||||||
const chart = {
|
const chart = {
|
||||||
aspects: {
|
aspects: {
|
||||||
x: { i: -1 },
|
x: { i: -1 },
|
||||||
series: { i: 0, agg: agg },
|
series: { i: 0, aggConfig: agg },
|
||||||
y: { i: 1, col: { title: '0' }, agg: agg }
|
y: { i: 1, title: '0', aggConfig: agg }
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -180,10 +179,10 @@ describe('getSeries', function () {
|
||||||
const chart = {
|
const chart = {
|
||||||
aspects: {
|
aspects: {
|
||||||
x: { i: -1 },
|
x: { i: -1 },
|
||||||
series: { i: 0, agg: agg },
|
series: { i: 0, aggConfig: agg },
|
||||||
y: [
|
y: [
|
||||||
{ i: 1, col: { title: '0' }, agg: { id: 1 } },
|
{ i: 1, title: '0', aggConfig: { id: 1 } },
|
||||||
{ i: 2, col: { title: '1' }, agg: { id: 2 } }
|
{ i: 2, title: '1', aggConfig: { id: 2 } }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -230,10 +229,10 @@ describe('getSeries', function () {
|
||||||
const chart = {
|
const chart = {
|
||||||
aspects: {
|
aspects: {
|
||||||
x: { i: -1 },
|
x: { i: -1 },
|
||||||
series: { i: 0, agg: agg },
|
series: { i: 0, aggConfig: agg },
|
||||||
y: [
|
y: [
|
||||||
{ i: 1, col: { title: '0' }, agg: { id: 1 } },
|
{ i: 1, title: '0', aggConfig: { id: 1 } },
|
||||||
{ i: 2, col: { title: '1' }, agg: { id: 2 } }
|
{ i: 2, title: '1', aggConfig: { id: 2 } }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -34,14 +34,12 @@ describe('initXAxis', function () {
|
||||||
const baseChart = {
|
const baseChart = {
|
||||||
aspects: {
|
aspects: {
|
||||||
x: {
|
x: {
|
||||||
agg: {
|
aggConfig: {
|
||||||
fieldFormatter: _.constant({}),
|
fieldFormatter: _.constant({}),
|
||||||
write: _.constant({ params: {} }),
|
write: _.constant({ params: {} }),
|
||||||
type: {}
|
type: {}
|
||||||
},
|
},
|
||||||
col: {
|
title: 'label'
|
||||||
title: 'label'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -53,23 +51,23 @@ describe('initXAxis', function () {
|
||||||
initXAxis(chart);
|
initXAxis(chart);
|
||||||
expect(chart)
|
expect(chart)
|
||||||
.to.have.property('xAxisLabel', 'label')
|
.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 () {
|
it('makes the chart ordered if the agg is ordered', function () {
|
||||||
const chart = _.cloneDeep(baseChart);
|
const chart = _.cloneDeep(baseChart);
|
||||||
chart.aspects.x.agg.type.ordered = true;
|
chart.aspects.x.aggConfig.type.ordered = true;
|
||||||
chart.aspects.x.agg.params = {
|
chart.aspects.x.aggConfig.params = {
|
||||||
field: field
|
field: field
|
||||||
};
|
};
|
||||||
chart.aspects.x.agg.vis = {
|
chart.aspects.x.aggConfig.vis = {
|
||||||
indexPattern: indexPattern
|
indexPattern: indexPattern
|
||||||
};
|
};
|
||||||
|
|
||||||
initXAxis(chart);
|
initXAxis(chart);
|
||||||
expect(chart)
|
expect(chart)
|
||||||
.to.have.property('xAxisLabel', 'label')
|
.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('indexPattern', indexPattern)
|
||||||
.and.have.property('xAxisField', field)
|
.and.have.property('xAxisField', field)
|
||||||
.and.have.property('ordered');
|
.and.have.property('ordered');
|
||||||
|
@ -81,19 +79,19 @@ describe('initXAxis', function () {
|
||||||
|
|
||||||
it('reads the interval param from the x agg', function () {
|
it('reads the interval param from the x agg', function () {
|
||||||
const chart = _.cloneDeep(baseChart);
|
const chart = _.cloneDeep(baseChart);
|
||||||
chart.aspects.x.agg.type.ordered = true;
|
chart.aspects.x.aggConfig.type.ordered = true;
|
||||||
chart.aspects.x.agg.write = _.constant({ params: { interval: 10 } });
|
chart.aspects.x.aggConfig.write = _.constant({ params: { interval: 10 } });
|
||||||
chart.aspects.x.agg.params = {
|
chart.aspects.x.aggConfig.params = {
|
||||||
field: field
|
field: field
|
||||||
};
|
};
|
||||||
chart.aspects.x.agg.vis = {
|
chart.aspects.x.aggConfig.vis = {
|
||||||
indexPattern: indexPattern
|
indexPattern: indexPattern
|
||||||
};
|
};
|
||||||
|
|
||||||
initXAxis(chart);
|
initXAxis(chart);
|
||||||
expect(chart)
|
expect(chart)
|
||||||
.to.have.property('xAxisLabel', 'label')
|
.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('indexPattern', indexPattern)
|
||||||
.and.have.property('xAxisField', field)
|
.and.have.property('xAxisField', field)
|
||||||
.and.have.property('ordered');
|
.and.have.property('ordered');
|
||||||
|
|
|
@ -42,12 +42,12 @@ describe('initYAxis', function () {
|
||||||
const baseChart = {
|
const baseChart = {
|
||||||
aspects: {
|
aspects: {
|
||||||
y: [
|
y: [
|
||||||
{ agg: agg(), col: { title: 'y1' } },
|
{ aggConfig: agg(), title: 'y1' },
|
||||||
{ agg: agg(), col: { title: 'y2' } },
|
{ aggConfig: agg(), title: 'y2' },
|
||||||
],
|
],
|
||||||
x: {
|
x: {
|
||||||
agg: agg(),
|
aggConfig: agg(),
|
||||||
col: { title: 'x' }
|
title: 'x'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -59,7 +59,7 @@ describe('initYAxis', function () {
|
||||||
it('sets the yAxisFormatter the the field formats convert fn', function () {
|
it('sets the yAxisFormatter the the field formats convert fn', function () {
|
||||||
const chart = _.cloneDeep(singleYBaseChart);
|
const chart = _.cloneDeep(singleYBaseChart);
|
||||||
initYAxis(chart);
|
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 () {
|
it('sets the yAxisLabel', function () {
|
||||||
|
@ -76,8 +76,8 @@ describe('initYAxis', function () {
|
||||||
|
|
||||||
expect(chart).to.have.property('yAxisFormatter');
|
expect(chart).to.have.property('yAxisFormatter');
|
||||||
expect(chart.yAxisFormatter)
|
expect(chart.yAxisFormatter)
|
||||||
.to.be(chart.aspects.y[0].agg.fieldFormatter())
|
.to.be(chart.aspects.y[0].aggConfig.fieldFormatter())
|
||||||
.and.not.be(chart.aspects.y[1].agg.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 () {
|
it('does not set the yAxisLabel, it does not make sense to put multiple labels on the same axis', function () {
|
||||||
|
|
|
@ -23,7 +23,6 @@ import AggConfigResult from '../../../vis/agg_config_result';
|
||||||
import expect from 'expect.js';
|
import expect from 'expect.js';
|
||||||
import ngMock from 'ng_mock';
|
import ngMock from 'ng_mock';
|
||||||
import { VisProvider } from '../../../vis';
|
import { VisProvider } from '../../../vis';
|
||||||
import { TabifyTable } from '../../tabify/_table';
|
|
||||||
import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';
|
import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';
|
||||||
import { AggResponsePointSeriesProvider } from '../point_series';
|
import { AggResponsePointSeriesProvider } from '../point_series';
|
||||||
|
|
||||||
|
@ -47,7 +46,7 @@ describe('pointSeriesChartDataFromTable', function () {
|
||||||
const agg = vis.aggs[0];
|
const agg = vis.aggs[0];
|
||||||
const result = new AggConfigResult(vis.aggs[0], void 0, 100, 100);
|
const result = new AggConfigResult(vis.aggs[0], void 0, 100, 100);
|
||||||
|
|
||||||
const table = new TabifyTable();
|
const table = { rows: [] };
|
||||||
table.columns = [ { aggConfig: agg } ];
|
table.columns = [ { aggConfig: agg } ];
|
||||||
table.rows.push([ result ]);
|
table.rows.push([ result ]);
|
||||||
|
|
||||||
|
@ -86,7 +85,7 @@ describe('pointSeriesChartDataFromTable', function () {
|
||||||
};
|
};
|
||||||
|
|
||||||
const rowCount = 3;
|
const rowCount = 3;
|
||||||
const table = new TabifyTable();
|
const table = { rows: [] };
|
||||||
table.columns = [ x.col, y.col ];
|
table.columns = [ x.col, y.col ];
|
||||||
_.times(rowCount, function (i) {
|
_.times(rowCount, function (i) {
|
||||||
const date = new AggConfigResult(x.agg, void 0, x.at(i));
|
const date = new AggConfigResult(x.agg, void 0, x.at(i));
|
||||||
|
@ -147,7 +146,7 @@ describe('pointSeriesChartDataFromTable', function () {
|
||||||
};
|
};
|
||||||
|
|
||||||
const rowCount = 3;
|
const rowCount = 3;
|
||||||
const table = new TabifyTable();
|
const table = { rows: [] };
|
||||||
table.columns = [ date.col, avg.col, max.col ];
|
table.columns = [ date.col, avg.col, max.col ];
|
||||||
_.times(rowCount, function (i) {
|
_.times(rowCount, function (i) {
|
||||||
const dateResult = new AggConfigResult(date.agg, void 0, date.at(i));
|
const dateResult = new AggConfigResult(date.agg, void 0, date.at(i));
|
||||||
|
@ -226,7 +225,7 @@ describe('pointSeriesChartDataFromTable', function () {
|
||||||
const metricCount = 2;
|
const metricCount = 2;
|
||||||
const rowsPerSegment = 2;
|
const rowsPerSegment = 2;
|
||||||
const rowCount = extensions.length * rowsPerSegment;
|
const rowCount = extensions.length * rowsPerSegment;
|
||||||
const table = new TabifyTable();
|
const table = { rows: [] };
|
||||||
table.columns = [ date.col, term.col, avg.col, max.col ];
|
table.columns = [ date.col, term.col, avg.col, max.col ];
|
||||||
_.times(rowCount, function (i) {
|
_.times(rowCount, function (i) {
|
||||||
const dateResult = new AggConfigResult(date.agg, void 0, date.at(i));
|
const dateResult = new AggConfigResult(date.agg, void 0, date.at(i));
|
||||||
|
|
|
@ -35,7 +35,7 @@ describe('orderedDateAxis', function () {
|
||||||
chart: {
|
chart: {
|
||||||
aspects: {
|
aspects: {
|
||||||
x: {
|
x: {
|
||||||
agg: {
|
aggConfig: {
|
||||||
fieldIsTimeField: _.constant(true),
|
fieldIsTimeField: _.constant(true),
|
||||||
buckets: {
|
buckets: {
|
||||||
getScaledDateFormat: _.constant('hh:mm:ss'),
|
getScaledDateFormat: _.constant('hh:mm:ss'),
|
||||||
|
@ -88,7 +88,7 @@ describe('orderedDateAxis', function () {
|
||||||
|
|
||||||
it('relies on agg.buckets for the interval', function () {
|
it('relies on agg.buckets for the interval', function () {
|
||||||
const args = _.cloneDeep(baseArgs);
|
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);
|
orderedDateAxis(args.vis, args.chart);
|
||||||
expect(spy).to.have.property('callCount', 1);
|
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 () {
|
it('does not set the min/max when the buckets are unbounded', function () {
|
||||||
const args = _.cloneDeep(baseArgs);
|
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);
|
orderedDateAxis(args.vis, args.chart);
|
||||||
expect(args.chart.ordered).to.not.have.property('min');
|
expect(args.chart.ordered).to.not.have.property('min');
|
||||||
expect(args.chart.ordered).to.not.have.property('max');
|
expect(args.chart.ordered).to.not.have.property('max');
|
||||||
|
|
|
@ -17,15 +17,16 @@
|
||||||
* under the License.
|
* 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 () {
|
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');
|
||||||
});
|
});
|
||||||
|
|
|
@ -37,11 +37,8 @@ export function PointSeriesFakeXAxisProvider() {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
i: -1,
|
i: -1,
|
||||||
agg: fake,
|
aggConfig: fake,
|
||||||
col: {
|
title: fake.makeLabel(),
|
||||||
aggConfig: fake,
|
|
||||||
label: fake.makeLabel()
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,8 +39,8 @@ export function PointSeriesGetAspectsProvider(Private) {
|
||||||
|
|
||||||
const aspect = {
|
const aspect = {
|
||||||
i: i,
|
i: i,
|
||||||
col: col,
|
title: col.title,
|
||||||
agg: col.aggConfig
|
aggConfig: col.aggConfig
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!aspects[name]) aspects[name] = [];
|
if (!aspects[name]) aspects[name] = [];
|
||||||
|
|
|
@ -45,13 +45,13 @@ export function PointSeriesGetPointProvider() {
|
||||||
|
|
||||||
if (series) {
|
if (series) {
|
||||||
const seriesArray = series.length ? series : [ series ];
|
const seriesArray = series.length ? series : [ series ];
|
||||||
point.aggConfig = seriesArray[0].agg;
|
point.aggConfig = seriesArray[0].aggConfig;
|
||||||
point.series = seriesArray.map(s => s.agg.fieldFormatter()(unwrap(row[s.i]))).join(' - ');
|
point.series = seriesArray.map(s => s.aggConfig.fieldFormatter()(unwrap(row[s.i]))).join(' - ');
|
||||||
} else if (y) {
|
} else if (y) {
|
||||||
// If the data is not split up with a series aspect, then
|
// If the data is not split up with a series aspect, then
|
||||||
// each point's "series" becomes the y-agg that produced it
|
// each point's "series" becomes the y-agg that produced it
|
||||||
point.aggConfig = y.col.aggConfig;
|
point.aggConfig = y.aggConfig;
|
||||||
point.series = y.col.title;
|
point.series = y.title;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (yScale) {
|
if (yScale) {
|
||||||
|
|
|
@ -35,7 +35,7 @@ export function PointSeriesGetSeriesProvider(Private) {
|
||||||
.transform(function (series, row) {
|
.transform(function (series, row) {
|
||||||
if (!multiY) {
|
if (!multiY) {
|
||||||
const point = partGetPoint(row, aspects.y, aspects.z);
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,8 +46,8 @@ export function PointSeriesGetSeriesProvider(Private) {
|
||||||
// use the point's y-axis as it's series by default,
|
// use the point's y-axis as it's series by default,
|
||||||
// but augment that with series aspect if it's actually
|
// but augment that with series aspect if it's actually
|
||||||
// available
|
// available
|
||||||
let seriesId = y.agg.id;
|
let seriesId = y.aggConfig.id;
|
||||||
let seriesLabel = y.col.title;
|
let seriesLabel = y.title;
|
||||||
|
|
||||||
if (aspects.series) {
|
if (aspects.series) {
|
||||||
const prefix = point.series ? point.series + ': ' : '';
|
const prefix = point.series ? point.series + ': ' : '';
|
||||||
|
@ -55,7 +55,7 @@ export function PointSeriesGetSeriesProvider(Private) {
|
||||||
seriesLabel = prefix + seriesLabel;
|
seriesLabel = prefix + seriesLabel;
|
||||||
}
|
}
|
||||||
|
|
||||||
addToSiri(series, point, seriesId, seriesLabel, y.agg);
|
addToSiri(series, point, seriesId, seriesLabel, y.aggConfig);
|
||||||
});
|
});
|
||||||
|
|
||||||
}, new Map())
|
}, new Map())
|
||||||
|
@ -70,7 +70,7 @@ export function PointSeriesGetSeriesProvider(Private) {
|
||||||
if (firstVal) {
|
if (firstVal) {
|
||||||
const agg = firstVal.aggConfigResult.aggConfig;
|
const agg = firstVal.aggConfigResult.aggConfig;
|
||||||
y = _.find(aspects.y, function (y) {
|
y = _.find(aspects.y, function (y) {
|
||||||
return y.agg === agg;
|
return y.aggConfig === agg;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,16 +21,16 @@
|
||||||
export function PointSeriesInitXAxisProvider() {
|
export function PointSeriesInitXAxisProvider() {
|
||||||
return function initXAxis(chart) {
|
return function initXAxis(chart) {
|
||||||
const x = chart.aspects.x;
|
const x = chart.aspects.x;
|
||||||
chart.xAxisFormatter = x.agg ? x.agg.fieldFormatter() : String;
|
chart.xAxisFormatter = x.aggConfig ? x.aggConfig.fieldFormatter() : String;
|
||||||
chart.xAxisLabel = x.col.title;
|
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.indexPattern = x.aggConfig.vis.indexPattern;
|
||||||
chart.xAxisField = x.agg.params.field;
|
chart.xAxisField = x.aggConfig.params.field;
|
||||||
|
|
||||||
chart.ordered = {};
|
chart.ordered = {};
|
||||||
const xAggOutput = x.agg.write();
|
const xAggOutput = x.aggConfig.write();
|
||||||
if (xAggOutput.params.interval) {
|
if (xAggOutput.params.interval) {
|
||||||
chart.ordered.interval = xAggOutput.params.interval;
|
chart.ordered.interval = xAggOutput.params.interval;
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,21 +24,21 @@ export function PointSeriesInitYAxisProvider() {
|
||||||
|
|
||||||
if (Array.isArray(y)) {
|
if (Array.isArray(y)) {
|
||||||
// TODO: vis option should allow choosing this format
|
// 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
|
chart.yAxisLabel = ''; // use the legend
|
||||||
} else {
|
} else {
|
||||||
chart.yAxisFormatter = y.agg.fieldFormatter();
|
chart.yAxisFormatter = y.aggConfig.fieldFormatter();
|
||||||
chart.yAxisLabel = y.col.title;
|
chart.yAxisLabel = y.title;
|
||||||
}
|
}
|
||||||
|
|
||||||
const z = chart.aspects.series;
|
const z = chart.aspects.series;
|
||||||
if (z) {
|
if (z) {
|
||||||
if (Array.isArray(z)) {
|
if (Array.isArray(z)) {
|
||||||
chart.zAxisFormatter = z[0].agg.fieldFormatter();
|
chart.zAxisFormatter = z[0].aggConfig.fieldFormatter();
|
||||||
chart.zAxisLabel = ''; // use the legend
|
chart.zAxisLabel = ''; // use the legend
|
||||||
} else {
|
} else {
|
||||||
chart.zAxisFormatter = z.agg.fieldFormatter();
|
chart.zAxisFormatter = z.aggConfig.fieldFormatter();
|
||||||
chart.zAxisLabel = z.col.title;
|
chart.zAxisLabel = z.title;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -22,7 +22,7 @@ import moment from 'moment';
|
||||||
export function PointSeriesOrderedDateAxisProvider() {
|
export function PointSeriesOrderedDateAxisProvider() {
|
||||||
|
|
||||||
return function orderedDateAxis(vis, chart) {
|
return function orderedDateAxis(vis, chart) {
|
||||||
const xAgg = chart.aspects.x.agg;
|
const xAgg = chart.aspects.x.aggConfig;
|
||||||
const buckets = xAgg.buckets;
|
const buckets = xAgg.buckets;
|
||||||
const format = buckets.getScaledDateFormat();
|
const format = buckets.getScaledDateFormat();
|
||||||
|
|
||||||
|
|
|
@ -42,7 +42,7 @@ export function AggResponsePointSeriesProvider(Private) {
|
||||||
initXAxis(chart);
|
initXAxis(chart);
|
||||||
initYAxis(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) {
|
if (datedX) {
|
||||||
setupOrderedDateXAxis(vis, chart);
|
setupOrderedDateXAxis(vis, chart);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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).to.have.length(2);
|
||||||
expect(columns[1]).to.have.property('aggConfig');
|
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);
|
expect(columns).to.have.length(8);
|
||||||
columns.forEach(function (column, i) {
|
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) {
|
function checkColumns(column, i) {
|
||||||
expect(column).to.have.property('aggConfig');
|
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);
|
expect(columns).to.have.length(6);
|
||||||
|
|
||||||
// sum should be last
|
// sum should be last
|
||||||
|
|
|
@ -49,16 +49,14 @@ describe('tabifyAggResponse Integration', function () {
|
||||||
normalizeIds(vis);
|
normalizeIds(vis);
|
||||||
|
|
||||||
const resp = tabifyAggResponse(vis.getAggConfig(), fixtures.metricOnly, {
|
const resp = tabifyAggResponse(vis.getAggConfig(), fixtures.metricOnly, {
|
||||||
canSplit: false,
|
metricsAtAllLevels: vis.isHierarchical()
|
||||||
isHierarchical: vis.isHierarchical()
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(resp).to.not.have.property('tables');
|
|
||||||
expect(resp).to.have.property('rows').and.property('columns');
|
expect(resp).to.have.property('rows').and.property('columns');
|
||||||
expect(resp.rows).to.have.length(1);
|
expect(resp.rows).to.have.length(1);
|
||||||
expect(resp.columns).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]);
|
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 = [];
|
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
|
// check that the columns of a table are formed properly
|
||||||
function expectColumns(table, aggs) {
|
function expectColumns(table, aggs) {
|
||||||
expect(table.columns).to.be.an('array').and.have.length(aggs.length);
|
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
|
// check that a row has expected values
|
||||||
function expectRow(row, asserts) {
|
function expectRow(row, asserts) {
|
||||||
expect(row).to.be.an('array');
|
expect(row).to.be.an('object');
|
||||||
expect(row).to.have.length(asserts.length);
|
|
||||||
asserts.forEach(function (assert, i) {
|
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);
|
expect(val).to.have.length(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
// check for an empty cell
|
// check for an OS term
|
||||||
function expectEmpty(val) {
|
function expectExtension(val) {
|
||||||
expect(val)
|
expect(val)
|
||||||
.to.be('');
|
.to.match(/^(js|png|html|css|jpg)$/);
|
||||||
}
|
}
|
||||||
|
|
||||||
// check for an OS term
|
// check for an OS term
|
||||||
|
@ -162,127 +134,44 @@ describe('tabifyAggResponse Integration', function () {
|
||||||
expect(val === 0 || val > 1000).to.be.ok();
|
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 () {
|
it('for non-hierarchical vis', function () {
|
||||||
// the default for a non-hierarchical vis is to display
|
// the default for a non-hierarchical vis is to display
|
||||||
// only complete rows, and only put the metrics at the end.
|
// only complete rows, and only put the metrics at the end.
|
||||||
|
|
||||||
vis.isHierarchical = _.constant(false);
|
const tabbed = tabifyAggResponse(vis.getAggConfig(), esResp, { minimalColumns: true });
|
||||||
const tabbed = tabifyAggResponse(vis.getAggConfig(), esResp, { isHierarchical: vis.isHierarchical() });
|
|
||||||
|
|
||||||
expectRootGroup(tabbed, function expectTable(table, splitKey) {
|
expectColumns(tabbed, [ext, src, os, avg]);
|
||||||
expectColumns(table, [src, os, avg]);
|
|
||||||
|
|
||||||
table.rows.forEach(function (row) {
|
tabbed.rows.forEach(function (row) {
|
||||||
if (splitKey === 'css' && row[0] === 'MX') {
|
expectRow(row, [
|
||||||
throw new Error('expected the MX row in the css table to be removed');
|
expectExtension,
|
||||||
} else {
|
expectCountry,
|
||||||
expectRow(row, [
|
expectOS,
|
||||||
expectCountry,
|
expectAvgBytes
|
||||||
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
|
// 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
|
// values, and since the vis is hierarchical and we are NOT using
|
||||||
// minimalColumns we should expect the partial row to be completely after
|
// minimalColumns we should expect the partial row to be completely after
|
||||||
// the existing bucket and it's metric
|
// the existing bucket and it's metric
|
||||||
|
|
||||||
vis.isHierarchical = _.constant(true);
|
vis.isHierarchical = _.constant(true);
|
||||||
const tabbed = tabifyAggResponse(vis.getAggConfig(), esResp, {
|
const tabbed = tabifyAggResponse(vis.getAggConfig(), esResp, { metricsAtAllLevels: true });
|
||||||
partialRows: true,
|
|
||||||
isHierarchical: vis.isHierarchical()
|
|
||||||
});
|
|
||||||
|
|
||||||
expectRootGroup(tabbed, function expectTable(table, splitKey) {
|
expectColumns(tabbed, [ext, avg, src, avg, os, avg]);
|
||||||
expectColumns(table, [src, avg, os, avg]);
|
|
||||||
|
|
||||||
table.rows.forEach(function (row) {
|
tabbed.rows.forEach(function (row) {
|
||||||
if (splitKey === 'css' && row[0] === 'MX') {
|
expectRow(row, [
|
||||||
expectRow(row, [
|
expectExtension,
|
||||||
expectCountry,
|
expectAvgBytes,
|
||||||
expectAvgBytes,
|
expectCountry,
|
||||||
expectEmpty,
|
expectAvgBytes,
|
||||||
expectEmpty
|
expectOS,
|
||||||
]);
|
expectAvgBytes
|
||||||
} 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
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -17,13 +17,9 @@
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import _ from 'lodash';
|
|
||||||
import sinon from 'sinon';
|
|
||||||
import expect from 'expect.js';
|
import expect from 'expect.js';
|
||||||
import ngMock from 'ng_mock';
|
import ngMock from 'ng_mock';
|
||||||
import { TabbedAggResponseWriter } from '../_response_writer';
|
import { TabbedAggResponseWriter } from '../_response_writer';
|
||||||
import { TabifyTableGroup } from '../_table_group';
|
|
||||||
import { TabifyBuckets } from '../_buckets';
|
|
||||||
import { VisProvider } from '../../../vis';
|
import { VisProvider } from '../../../vis';
|
||||||
import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';
|
import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';
|
||||||
|
|
||||||
|
@ -32,70 +28,55 @@ describe('TabbedAggResponseWriter class', function () {
|
||||||
let Private;
|
let Private;
|
||||||
let indexPattern;
|
let indexPattern;
|
||||||
|
|
||||||
function defineSetup() {
|
beforeEach(ngMock.module('kibana'));
|
||||||
beforeEach(ngMock.module('kibana'));
|
beforeEach(ngMock.inject(function ($injector) {
|
||||||
beforeEach(ngMock.inject(function ($injector) {
|
Private = $injector.get('Private');
|
||||||
Private = $injector.get('Private');
|
|
||||||
|
|
||||||
Vis = Private(VisProvider);
|
Vis = Private(VisProvider);
|
||||||
indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider);
|
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 () {
|
describe('Constructor', function () {
|
||||||
defineSetup();
|
let responseWriter;
|
||||||
|
beforeEach(() => {
|
||||||
it('sets canSplit=true by default', function () {
|
responseWriter = createResponseWritter(twoSplitsAggConfig);
|
||||||
const vis = new Vis(indexPattern, { type: 'histogram', aggs: [] });
|
|
||||||
const writer = new TabbedAggResponseWriter(vis.getAggConfig(), {
|
|
||||||
isHierarchical: vis.isHierarchical()
|
|
||||||
});
|
|
||||||
expect(writer).to.have.property('canSplit', true);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('sets canSplit=false when config says to', function () {
|
it('creates aggStack', () => {
|
||||||
const vis = new Vis(indexPattern, { type: 'histogram', aggs: [] });
|
expect(responseWriter.aggStack.length).to.eql(3);
|
||||||
const writer = new TabbedAggResponseWriter(vis.getAggConfig(), {
|
|
||||||
canSplit: false,
|
|
||||||
isHierarchical: vis.isHierarchical()
|
|
||||||
});
|
|
||||||
expect(writer).to.have.property('canSplit', false);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('sets partialRows', function () {
|
it('generates columns', () => {
|
||||||
it('to the value of the config if set', function () {
|
expect(responseWriter.columns.length).to.eql(3);
|
||||||
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('starts off with a root TabifyTableGroup', function () {
|
it('correctly generates columns with metricsAtAllLevels set to true', () => {
|
||||||
const vis = new Vis(indexPattern, { type: 'histogram', aggs: [] });
|
const minimalColumnsResponseWriter = createResponseWritter(twoSplitsAggConfig, { metricsAtAllLevels: true });
|
||||||
|
expect(minimalColumnsResponseWriter.columns.length).to.eql(4);
|
||||||
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);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('sets timeRange', function () {
|
describe('sets timeRange', function () {
|
||||||
|
@ -128,296 +109,71 @@ describe('TabbedAggResponseWriter class', function () {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('', function () {
|
describe('row()', function () {
|
||||||
defineSetup();
|
let responseWriter;
|
||||||
|
|
||||||
describe('#response()', function () {
|
beforeEach(() => {
|
||||||
it('returns the root TabifyTableGroup if splitting', function () {
|
responseWriter = createResponseWritter(splitAggConfig, { partialRows: true });
|
||||||
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');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('#split()', function () {
|
it('adds the row to the array', () => {
|
||||||
it('with break if the user has specified that splitting is to be disabled', function () {
|
responseWriter.rowBuffer['col-0'] = 'US';
|
||||||
const vis = new Vis(indexPattern, {
|
responseWriter.rowBuffer['col-1'] = 5;
|
||||||
type: 'histogram',
|
responseWriter.row();
|
||||||
aggs: [
|
expect(responseWriter.rows.length).to.eql(1);
|
||||||
{ type: 'terms', schema: 'split', params: { field: '_type' } },
|
expect(responseWriter.rows[0]).to.eql({ 'col-0': 'US', 'col-1': 5 });
|
||||||
{ 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);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('#cell()', function () {
|
it('correctly handles bucketBuffer', () => {
|
||||||
it('logs a cell in the TabbedAggResponseWriters row buffer, calls the block arg, then removes the value from the buffer',
|
responseWriter.bucketBuffer.push({ id: 'col-0', value: 'US' });
|
||||||
function () {
|
responseWriter.rowBuffer['col-1'] = 5;
|
||||||
const vis = new Vis(indexPattern, { type: 'histogram', aggs: [] });
|
responseWriter.row();
|
||||||
const writer = new TabbedAggResponseWriter(vis.getAggConfig(), {
|
expect(responseWriter.rows.length).to.eql(1);
|
||||||
isHierarchical: vis.isHierarchical()
|
expect(responseWriter.rows[0]).to.eql({ 'col-0': 'US', 'col-1': 5 });
|
||||||
});
|
|
||||||
|
|
||||||
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);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('#row()', function () {
|
it('doesn\'t add an empty row', () => {
|
||||||
it('writes the TabbedAggResponseWriters internal rowBuffer into a table', function () {
|
responseWriter.row();
|
||||||
const vis = new Vis(indexPattern, { type: 'histogram', aggs: [] });
|
expect(responseWriter.rows.length).to.eql(0);
|
||||||
const writer = new TabbedAggResponseWriter(vis.getAggConfig(), {
|
});
|
||||||
isHierarchical: vis.isHierarchical()
|
});
|
||||||
});
|
|
||||||
|
|
||||||
const table = writer._table();
|
describe('response()', () => {
|
||||||
writer.cell({}, 1, function () {
|
let responseWriter;
|
||||||
writer.cell({}, 2, function () {
|
|
||||||
writer.cell({}, 3, function () {
|
|
||||||
writer.row();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(table.rows).to.have.length(1);
|
beforeEach(() => {
|
||||||
expect(table.rows[0]).to.eql([1, 2, 3]);
|
responseWriter = createResponseWritter(splitAggConfig);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('always writes to the table group at the top of the split stack', function () {
|
it('produces correct response', () => {
|
||||||
const vis = new Vis(indexPattern, {
|
responseWriter.rowBuffer['col-0-1'] = 'US';
|
||||||
type: 'histogram',
|
responseWriter.rowBuffer['col-1-2'] = 5;
|
||||||
aggs: [
|
responseWriter.row();
|
||||||
{ type: 'terms', schema: 'split', params: { field: '_type' } },
|
const response = responseWriter.response();
|
||||||
{ type: 'terms', schema: 'split', params: { field: 'extension' } },
|
expect(response).to.have.property('rows');
|
||||||
{ type: 'terms', schema: 'split', params: { field: 'machine.os' } },
|
expect(response.rows).to.eql([{ 'col-0-1': 'US', 'col-1-2': 5 }]);
|
||||||
{ type: 'count', schema: 'metric' }
|
expect(response).to.have.property('columns');
|
||||||
]
|
expect(response.columns.length).to.equal(2);
|
||||||
});
|
expect(response.columns[0]).to.have.property('id', 'col-0-1');
|
||||||
const splits = vis.aggs.bySchemaName.split;
|
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');
|
||||||
|
});
|
||||||
|
|
||||||
const type = splits[0];
|
it('produces correct response for no data', () => {
|
||||||
const typeTabifyBuckets = new TabifyBuckets({ buckets: [ { key: 'nginx' }, { key: 'apache' } ] });
|
const response = responseWriter.response();
|
||||||
|
expect(response).to.have.property('rows');
|
||||||
const ext = splits[1];
|
expect(response.rows.length).to.be(0);
|
||||||
const extTabifyBuckets = new TabifyBuckets({ buckets: [ { key: 'jpg' }, { key: 'png' } ] });
|
expect(response).to.have.property('columns');
|
||||||
|
expect(response.columns.length).to.equal(2);
|
||||||
const os = splits[2];
|
expect(response.columns[0]).to.have.property('id', 'col-0-1');
|
||||||
const osTabifyBuckets = new TabifyBuckets({ buckets: [ { key: 'windows' }, { key: 'mac' } ] });
|
expect(response.columns[0]).to.have.property('name', 'geo.src: Descending');
|
||||||
|
expect(response.columns[0]).to.have.property('aggConfig');
|
||||||
const count = vis.aggs[3];
|
expect(response.columns[1]).to.have.property('id', 'col-1-2');
|
||||||
|
expect(response.columns[1]).to.have.property('name', 'Count');
|
||||||
const writer = new TabbedAggResponseWriter(vis.getAggConfig(), {
|
expect(response.columns[1]).to.have.property('aggConfig');
|
||||||
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();
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(table.rows).to.have.length(1);
|
|
||||||
expect(table.rows[0]).to.eql(['apache', '']);
|
|
||||||
});
|
|
||||||
|
|
||||||
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' }
|
|
||||||
]
|
|
||||||
});
|
|
||||||
|
|
||||||
const writer = new TabbedAggResponseWriter(vis.getAggConfig(), {
|
|
||||||
isHierarchical: vis.isHierarchical()
|
|
||||||
});
|
|
||||||
const table = writer._table();
|
|
||||||
writer.cell(vis.aggs[0], 'apache', function () {
|
|
||||||
writer.row();
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(table.rows).to.have.length(0);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -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);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -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);
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -19,8 +19,6 @@
|
||||||
|
|
||||||
import './_get_columns';
|
import './_get_columns';
|
||||||
import './_buckets';
|
import './_buckets';
|
||||||
import './_table';
|
|
||||||
import './_table_group';
|
|
||||||
import './_response_writer';
|
import './_response_writer';
|
||||||
import './_integration';
|
import './_integration';
|
||||||
describe('Tabify Agg Response', function () {
|
describe('Tabify Agg Response', function () {
|
||||||
|
|
|
@ -19,15 +19,19 @@
|
||||||
|
|
||||||
import _ from 'lodash';
|
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
|
// pick the columns
|
||||||
if (minimal) {
|
if (minimal) {
|
||||||
return aggs.map(function (agg) {
|
return aggs.map((agg, i) => getColumn(agg, i));
|
||||||
return { aggConfig: agg };
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// supposed to be bucket,...metrics,bucket,...metrics
|
// supposed to be bucket,...metrics,bucket,...metrics
|
||||||
|
@ -40,16 +44,15 @@ export function tabifyGetColumns(aggs, minimal, hierarchical) {
|
||||||
|
|
||||||
if (!grouped.buckets) {
|
if (!grouped.buckets) {
|
||||||
// return just the metrics, in column format
|
// return just the metrics, in column format
|
||||||
return grouped.metrics.map(function (agg) {
|
return grouped.metrics.map((agg, i) => getColumn(agg, i));
|
||||||
return { aggConfig: agg };
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let columnIndex = 0;
|
||||||
// return the buckets, and after each place all of the metrics
|
// return the buckets, and after each place all of the metrics
|
||||||
grouped.buckets.forEach(function (agg) {
|
grouped.buckets.forEach(function (agg) {
|
||||||
columns.push({ aggConfig: agg });
|
columns.push(getColumn(agg, columnIndex++));
|
||||||
grouped.metrics.forEach(function (metric) {
|
grouped.metrics.forEach(function (metric) {
|
||||||
columns.push({ aggConfig: metric });
|
columns.push(getColumn(metric, columnIndex++));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -17,294 +17,74 @@
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import _ from 'lodash';
|
import { toArray } from 'lodash';
|
||||||
import AggConfigResult from '../../vis/agg_config_result';
|
|
||||||
import { TabifyTable } from './_table';
|
|
||||||
import { TabifyTableGroup } from './_table_group';
|
|
||||||
import { tabifyGetColumns } from './_get_columns';
|
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
|
* Writer class that collects information about an aggregation response and
|
||||||
* produces a table, or a series of tables.
|
* 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) {
|
function TabbedAggResponseWriter(aggs, { metricsAtAllLevels = false, partialRows = false, timeRange } = {}) {
|
||||||
this.opts = opts || {};
|
this.rowBuffer = {};
|
||||||
this.rowBuffer = [];
|
this.bucketBuffer = [];
|
||||||
|
this.metricBuffer = [];
|
||||||
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;
|
|
||||||
|
|
||||||
|
this.metricsForAllBuckets = metricsAtAllLevels;
|
||||||
|
this.partialRows = partialRows;
|
||||||
this.aggs = aggs;
|
this.aggs = aggs;
|
||||||
this.columns = tabifyGetColumns(aggs.getResponseAggs(), this.minimalColumns);
|
this.columns = tabifyGetColumns(aggs.getResponseAggs(), !metricsAtAllLevels);
|
||||||
this.aggStack = _.pluck(this.columns, 'aggConfig');
|
this.aggStack = [...this.columns];
|
||||||
|
|
||||||
this.root = new TabifyTableGroup();
|
this.rows = [];
|
||||||
this.acrStack = [];
|
|
||||||
this.splitStack = [this.root];
|
|
||||||
|
|
||||||
// Extract the time range object if provided
|
// Extract the time range object if provided
|
||||||
if (this.opts.timeRange) {
|
if (timeRange) {
|
||||||
const timeRangeKey = Object.keys(this.opts.timeRange)[0];
|
const timeRangeKey = Object.keys(timeRange)[0];
|
||||||
this.timeRange = this.opts.timeRange[timeRangeKey];
|
this.timeRange = timeRange[timeRangeKey];
|
||||||
if (this.timeRange) {
|
if (this.timeRange) {
|
||||||
this.timeRange.name = timeRangeKey;
|
this.timeRange.name = timeRangeKey;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
TabbedAggResponseWriter.prototype.isPartialRow = function (row) {
|
||||||
* Create a Table of TableGroup object, link it to it's parent (if any), and determine if
|
return !this.columns.map(column => row.hasOwnProperty(column.id)).every(c => (c === true));
|
||||||
* 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;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enter into a split table, called for each bucket of a splitting agg. The new table
|
* Create a new row by reading the row buffer and bucketBuffer
|
||||||
* 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
|
|
||||||
*/
|
*/
|
||||||
TabbedAggResponseWriter.prototype.split = function (agg, buckets, block) {
|
TabbedAggResponseWriter.prototype.row = function () {
|
||||||
const self = this;
|
this.bucketBuffer.forEach(bucket => {
|
||||||
|
this.rowBuffer[bucket.id] = bucket.value;
|
||||||
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;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// we must have already removed this column
|
this.metricBuffer.forEach(metric => {
|
||||||
if (i === -1) return;
|
this.rowBuffer[metric.id] = metric.value;
|
||||||
|
|
||||||
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;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (mI > -1) this.aggStack.splice(mI, 1);
|
if (!toArray(this.rowBuffer).length || (!this.partialRows && this.isPartialRow(this.rowBuffer))) {
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const split = this.splitStack[0];
|
this.rows.push(this.rowBuffer);
|
||||||
const table = split.tables[0] || this._table(false);
|
this.rowBuffer = {};
|
||||||
|
|
||||||
while (cells.length < this.columns.length) cells.push('');
|
|
||||||
table.rows.push(cells);
|
|
||||||
return table;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the actual response
|
* Get the actual response
|
||||||
*
|
*
|
||||||
* @return {object} - the final table-tree
|
* @return {object} - the final table
|
||||||
*/
|
*/
|
||||||
TabbedAggResponseWriter.prototype.response = function () {
|
TabbedAggResponseWriter.prototype.response = function () {
|
||||||
const columns = this.columns;
|
return {
|
||||||
|
columns: this.columns,
|
||||||
// give the columns some metadata
|
rows: this.rows
|
||||||
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;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export { TabbedAggResponseWriter };
|
export { TabbedAggResponseWriter };
|
||||||
|
|
|
@ -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 };
|
|
|
@ -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 };
|
|
|
@ -43,7 +43,8 @@ export function tabifyAggResponse(aggs, esResponse, respOpts = {}) {
|
||||||
* @returns {undefined}
|
* @returns {undefined}
|
||||||
*/
|
*/
|
||||||
function collectBucket(write, bucket, key, aggScale) {
|
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);
|
const aggInfo = agg.write(write.aggs);
|
||||||
aggScale *= aggInfo.metricScale || 1;
|
aggScale *= aggInfo.metricScale || 1;
|
||||||
|
|
||||||
|
@ -51,23 +52,24 @@ function collectBucket(write, bucket, key, aggScale) {
|
||||||
case 'buckets':
|
case 'buckets':
|
||||||
const buckets = new TabifyBuckets(bucket[agg.id], agg.params, write.timeRange);
|
const buckets = new TabifyBuckets(bucket[agg.id], agg.params, write.timeRange);
|
||||||
if (buckets.length) {
|
if (buckets.length) {
|
||||||
const splitting = write.canSplit && agg.schema.name === 'split';
|
buckets.forEach(function (subBucket, key) {
|
||||||
if (splitting) {
|
// if the bucket doesn't have value don't add it to the row
|
||||||
write.split(agg, buckets, function forEachBucket(subBucket, key) {
|
// we don't want rows like: { column1: undefined, column2: 10 }
|
||||||
collectBucket(write, subBucket, agg.getKey(subBucket, key), aggScale);
|
const bucketValue = agg.getKey(subBucket, key);
|
||||||
});
|
const hasBucketValue = typeof bucketValue !== 'undefined';
|
||||||
} else {
|
if (hasBucketValue) {
|
||||||
buckets.forEach(function (subBucket, key) {
|
write.bucketBuffer.push({ id: column.id, value: bucketValue });
|
||||||
write.cell(agg, agg.getKey(subBucket, key), function () {
|
}
|
||||||
collectBucket(write, subBucket, agg.getKey(subBucket, key), aggScale);
|
collectBucket(write, subBucket, agg.getKey(subBucket, key), aggScale);
|
||||||
}, subBucket.filters);
|
if (hasBucketValue) {
|
||||||
});
|
write.bucketBuffer.pop();
|
||||||
}
|
}
|
||||||
} else if (write.partialRows && write.metricsForAllBuckets && write.minimalColumns) {
|
});
|
||||||
|
} else if (write.partialRows) {
|
||||||
// we don't have any buckets, but we do have metrics at this
|
// 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
|
// level, then pass all the empty buckets and jump back in for
|
||||||
// the metrics.
|
// the metrics.
|
||||||
write.aggStack.unshift(agg);
|
write.aggStack.unshift(column);
|
||||||
passEmptyBuckets(write, bucket, key, aggScale);
|
passEmptyBuckets(write, bucket, key, aggScale);
|
||||||
write.aggStack.shift();
|
write.aggStack.shift();
|
||||||
} else {
|
} else {
|
||||||
|
@ -83,39 +85,41 @@ function collectBucket(write, bucket, key, aggScale) {
|
||||||
if (aggScale !== 1) {
|
if (aggScale !== 1) {
|
||||||
value *= aggScale;
|
value *= aggScale;
|
||||||
}
|
}
|
||||||
write.cell(agg, value, function () {
|
write.metricBuffer.push({ id: column.id, value: value });
|
||||||
if (!write.aggStack.length) {
|
|
||||||
// row complete
|
if (!write.aggStack.length) {
|
||||||
write.row();
|
// row complete
|
||||||
} else {
|
write.row();
|
||||||
// process the next agg at this same level
|
} else {
|
||||||
collectBucket(write, bucket, key, aggScale);
|
// process the next agg at this same level
|
||||||
}
|
collectBucket(write, bucket, key, aggScale);
|
||||||
});
|
}
|
||||||
|
|
||||||
|
write.metricBuffer.pop();
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
write.aggStack.unshift(agg);
|
write.aggStack.unshift(column);
|
||||||
}
|
}
|
||||||
|
|
||||||
// write empty values for each bucket agg, then write
|
// write empty values for each bucket agg, then write
|
||||||
// the metrics from the initial bucket using collectBucket()
|
// the metrics from the initial bucket using collectBucket()
|
||||||
function passEmptyBuckets(write, bucket, key, aggScale) {
|
function passEmptyBuckets(write, bucket, key, aggScale) {
|
||||||
const agg = write.aggStack.shift();
|
const column = write.aggStack.shift();
|
||||||
|
const agg = column.aggConfig;
|
||||||
|
|
||||||
switch (agg.type.type) {
|
switch (agg.type.type) {
|
||||||
case 'metrics':
|
case 'metrics':
|
||||||
// pass control back to collectBucket()
|
// pass control back to collectBucket()
|
||||||
write.aggStack.unshift(agg);
|
write.aggStack.unshift(column);
|
||||||
collectBucket(write, bucket, key, aggScale);
|
collectBucket(write, bucket, key, aggScale);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case 'buckets':
|
case 'buckets':
|
||||||
write.cell(agg, '', function () {
|
passEmptyBuckets(write, bucket, key, aggScale);
|
||||||
passEmptyBuckets(write, bucket, key, aggScale);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
write.aggStack.unshift(agg);
|
write.aggStack.unshift(column);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ import $ from 'jquery';
|
||||||
import ngMock from 'ng_mock';
|
import ngMock from 'ng_mock';
|
||||||
import expect from 'expect.js';
|
import expect from 'expect.js';
|
||||||
import fixtures from 'fixtures/fake_hierarchical_data';
|
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 FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';
|
||||||
import { VisProvider } from '../../vis';
|
import { VisProvider } from '../../vis';
|
||||||
describe('AggTableGroup Directive', function () {
|
describe('AggTableGroup Directive', function () {
|
||||||
|
@ -30,9 +30,11 @@ describe('AggTableGroup Directive', function () {
|
||||||
let $compile;
|
let $compile;
|
||||||
let Vis;
|
let Vis;
|
||||||
let indexPattern;
|
let indexPattern;
|
||||||
|
let tableAggResponse;
|
||||||
|
|
||||||
beforeEach(ngMock.module('kibana'));
|
beforeEach(ngMock.module('kibana'));
|
||||||
beforeEach(ngMock.inject(function ($injector, Private) {
|
beforeEach(ngMock.inject(function ($injector, Private) {
|
||||||
|
tableAggResponse = Private(LegacyResponseHandlerProvider).handler;
|
||||||
indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider);
|
indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider);
|
||||||
Vis = Private(VisProvider);
|
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');
|
const vis = new Vis(indexPattern, 'table');
|
||||||
$scope.group = tabifyAggResponse(vis.getAggConfig(), fixtures.metricOnly);
|
$scope.group = await tableAggResponse(vis, fixtures.metricOnly);
|
||||||
$scope.sort = {
|
$scope.sort = {
|
||||||
columnIndex: null,
|
columnIndex: null,
|
||||||
direction: null
|
direction: null
|
||||||
|
@ -79,7 +81,7 @@ describe('AggTableGroup Directive', function () {
|
||||||
expect($subTables.length).to.be(0);
|
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, {
|
const vis = new Vis(indexPattern, {
|
||||||
type: 'pie',
|
type: 'pie',
|
||||||
aggs: [
|
aggs: [
|
||||||
|
@ -93,7 +95,7 @@ describe('AggTableGroup Directive', function () {
|
||||||
agg.id = 'agg_' + (i + 1);
|
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 = $('<kbn-agg-table-group group="group"></kbn-agg-table-group>');
|
const $el = $('<kbn-agg-table-group group="group"></kbn-agg-table-group>');
|
||||||
$compile($el)($scope);
|
$compile($el)($scope);
|
||||||
$scope.$digest();
|
$scope.$digest();
|
||||||
|
|
|
@ -17,14 +17,13 @@
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import _ from 'lodash';
|
|
||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import ngMock from 'ng_mock';
|
import ngMock from 'ng_mock';
|
||||||
import expect from 'expect.js';
|
import expect from 'expect.js';
|
||||||
import fixtures from 'fixtures/fake_hierarchical_data';
|
import fixtures from 'fixtures/fake_hierarchical_data';
|
||||||
import sinon from 'sinon';
|
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 FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';
|
||||||
import { VisProvider } from '../../vis';
|
import { VisProvider } from '../../vis';
|
||||||
describe('AggTable Directive', function () {
|
describe('AggTable Directive', function () {
|
||||||
|
@ -34,9 +33,11 @@ describe('AggTable Directive', function () {
|
||||||
let Vis;
|
let Vis;
|
||||||
let indexPattern;
|
let indexPattern;
|
||||||
let settings;
|
let settings;
|
||||||
|
let tableAggResponse;
|
||||||
|
|
||||||
beforeEach(ngMock.module('kibana'));
|
beforeEach(ngMock.module('kibana'));
|
||||||
beforeEach(ngMock.inject(function ($injector, Private, config) {
|
beforeEach(ngMock.inject(function ($injector, Private, config) {
|
||||||
|
tableAggResponse = Private(LegacyResponseHandlerProvider).handler;
|
||||||
indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider);
|
indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider);
|
||||||
Vis = Private(VisProvider);
|
Vis = Private(VisProvider);
|
||||||
settings = config;
|
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');
|
const vis = new Vis(indexPattern, 'table');
|
||||||
$scope.table = tabifyAggResponse(
|
$scope.table = (await tableAggResponse(
|
||||||
vis.getAggConfig(),
|
vis,
|
||||||
fixtures.metricOnly,
|
fixtures.metricOnly
|
||||||
{ canSplit: false, hierarchical: vis.isHierarchical() }
|
)).tables[0];
|
||||||
);
|
|
||||||
|
|
||||||
const $el = $compile('<kbn-agg-table table="table"></kbn-agg-table>')($scope);
|
const $el = $compile('<kbn-agg-table table="table"></kbn-agg-table>')($scope);
|
||||||
$scope.$digest();
|
$scope.$digest();
|
||||||
|
|
||||||
expect($el.find('tbody').length).to.be(1);
|
expect($el.find('tbody').length).to.be(1);
|
||||||
expect($el.find('td').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 () {
|
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);
|
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, {
|
const vis = new Vis(indexPattern, {
|
||||||
type: 'pie',
|
type: 'table',
|
||||||
|
params: {
|
||||||
|
showMetricsAtAllLevels: true
|
||||||
|
},
|
||||||
aggs: [
|
aggs: [
|
||||||
{ type: 'avg', schema: 'metric', params: { field: 'bytes' } },
|
{ type: 'avg', schema: 'metric', params: { field: 'bytes' } },
|
||||||
{ type: 'terms', schema: 'split', params: { field: 'extension' } },
|
{ type: 'terms', schema: 'bucket', params: { field: 'extension' } },
|
||||||
{ type: 'terms', schema: 'segment', params: { field: 'geo.src' } },
|
{ type: 'terms', schema: 'bucket', params: { field: 'geo.src' } },
|
||||||
{ type: 'terms', schema: 'segment', params: { field: 'machine.os' } }
|
{ type: 'terms', schema: 'bucket', params: { field: 'machine.os' } }
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
vis.aggs.forEach(function (agg, i) {
|
vis.aggs.forEach(function (agg, i) {
|
||||||
agg.id = 'agg_' + (i + 1);
|
agg.id = 'agg_' + (i + 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
$scope.table = tabifyAggResponse(vis.getAggConfig(), fixtures.threeTermBuckets, {
|
$scope.table = (await tableAggResponse(vis, fixtures.threeTermBuckets)).tables[0];
|
||||||
canSplit: false,
|
|
||||||
isHierarchical: vis.isHierarchical()
|
|
||||||
});
|
|
||||||
const $el = $('<kbn-agg-table table="table"></kbn-agg-table>');
|
const $el = $('<kbn-agg-table table="table"></kbn-agg-table>');
|
||||||
$compile($el)($scope);
|
$compile($el)($scope);
|
||||||
$scope.$digest();
|
$scope.$digest();
|
||||||
|
@ -106,9 +106,10 @@ describe('AggTable Directive', function () {
|
||||||
expect($rows.length).to.be.greaterThan(0);
|
expect($rows.length).to.be.greaterThan(0);
|
||||||
|
|
||||||
function validBytes(str) {
|
function validBytes(str) {
|
||||||
expect(str).to.match(/^\d+$/);
|
const num = str.replace(/,/g, '');
|
||||||
const bytesAsNum = _.parseInt(str);
|
if (num !== '-') {
|
||||||
expect(bytesAsNum === 0 || bytesAsNum > 1000).to.be.ok();
|
expect(num).to.match(/^\d+$/);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$rows.each(function () {
|
$rows.each(function () {
|
||||||
|
@ -135,7 +136,7 @@ describe('AggTable Directive', function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('renders totals row', function () {
|
describe('renders totals row', function () {
|
||||||
function totalsRowTest(totalFunc, expected) {
|
async function totalsRowTest(totalFunc, expected) {
|
||||||
const vis = new Vis(indexPattern, {
|
const vis = new Vis(indexPattern, {
|
||||||
type: 'table',
|
type: 'table',
|
||||||
aggs: [
|
aggs: [
|
||||||
|
@ -158,10 +159,10 @@ describe('AggTable Directive', function () {
|
||||||
const oldTimezoneSetting = settings.get('dateFormat:tz');
|
const oldTimezoneSetting = settings.get('dateFormat:tz');
|
||||||
settings.set('dateFormat:tz', 'UTC');
|
settings.set('dateFormat:tz', 'UTC');
|
||||||
|
|
||||||
$scope.table = tabifyAggResponse(vis.getAggConfig(),
|
$scope.table = (await tableAggResponse(vis,
|
||||||
fixtures.oneTermOneHistogramBucketWithTwoMetricsOneTopHitOneDerivative,
|
fixtures.oneTermOneHistogramBucketWithTwoMetricsOneTopHitOneDerivative,
|
||||||
{ canSplit: false, minimalColumns: true, asAggConfigResults: true }
|
{ canSplit: false, minimalColumns: true, asAggConfigResults: true }
|
||||||
);
|
)).tables[0];
|
||||||
$scope.showTotal = true;
|
$scope.showTotal = true;
|
||||||
$scope.totalFunc = totalFunc;
|
$scope.totalFunc = totalFunc;
|
||||||
const $el = $('<kbn-agg-table table="table" show-total="showTotal" total-func="totalFunc"></kbn-agg-table>');
|
const $el = $('<kbn-agg-table table="table" show-total="showTotal" total-func="totalFunc"></kbn-agg-table>');
|
||||||
|
@ -182,11 +183,11 @@ describe('AggTable Directive', function () {
|
||||||
settings.set('dateFormat:tz', oldTimezoneSetting);
|
settings.set('dateFormat:tz', oldTimezoneSetting);
|
||||||
off();
|
off();
|
||||||
}
|
}
|
||||||
it('as count', function () {
|
it('as count', async function () {
|
||||||
totalsRowTest('count', ['18', '18', '18', '18', '18', '18']);
|
await totalsRowTest('count', ['18', '18', '18', '18', '18', '18']);
|
||||||
});
|
});
|
||||||
it('as min', function () {
|
it('as min', async function () {
|
||||||
totalsRowTest('min', [
|
await totalsRowTest('min', [
|
||||||
'',
|
'',
|
||||||
'2014-09-28',
|
'2014-09-28',
|
||||||
'9,283',
|
'9,283',
|
||||||
|
@ -195,8 +196,8 @@ describe('AggTable Directive', function () {
|
||||||
'11'
|
'11'
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
it('as max', function () {
|
it('as max', async function () {
|
||||||
totalsRowTest('max', [
|
await totalsRowTest('max', [
|
||||||
'',
|
'',
|
||||||
'2014-10-03',
|
'2014-10-03',
|
||||||
'220,943',
|
'220,943',
|
||||||
|
@ -205,8 +206,8 @@ describe('AggTable Directive', function () {
|
||||||
'837'
|
'837'
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
it('as avg', function () {
|
it('as avg', async function () {
|
||||||
totalsRowTest('avg', [
|
await totalsRowTest('avg', [
|
||||||
'',
|
'',
|
||||||
'',
|
'',
|
||||||
'87,221.5',
|
'87,221.5',
|
||||||
|
@ -215,8 +216,8 @@ describe('AggTable Directive', function () {
|
||||||
'206.833'
|
'206.833'
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
it('as sum', function () {
|
it('as sum', async function () {
|
||||||
totalsRowTest('sum', [
|
await totalsRowTest('sum', [
|
||||||
'',
|
'',
|
||||||
'',
|
'',
|
||||||
'1,569,987',
|
'1,569,987',
|
||||||
|
|
|
@ -102,10 +102,10 @@ uiModules
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.csv.filename = ($scope.exportTitle || table.title() || 'table') + '.csv';
|
self.csv.filename = ($scope.exportTitle || table.title || 'table') + '.csv';
|
||||||
$scope.rows = table.rows;
|
$scope.rows = table.rows;
|
||||||
$scope.formattedColumns = table.columns.map(function (col, i) {
|
$scope.formattedColumns = table.columns.map(function (col, i) {
|
||||||
const agg = $scope.table.aggConfig(col);
|
const agg = col.aggConfig;
|
||||||
const field = agg.getField();
|
const field = agg.getField();
|
||||||
const formattedColumn = {
|
const formattedColumn = {
|
||||||
title: col.title,
|
title: col.title,
|
||||||
|
|
|
@ -35,8 +35,7 @@ describe('AggTypeMetricMedianProvider class', function () {
|
||||||
'title': 'New Visualization',
|
'title': 'New Visualization',
|
||||||
'type': 'metric',
|
'type': 'metric',
|
||||||
'params': {
|
'params': {
|
||||||
'fontSize': 60,
|
'fontSize': 60
|
||||||
'handleNoResults': true
|
|
||||||
},
|
},
|
||||||
'aggs': [
|
'aggs': [
|
||||||
{
|
{
|
||||||
|
|
|
@ -59,8 +59,7 @@ describe('parent pipeline aggs', function () {
|
||||||
title: 'New Visualization',
|
title: 'New Visualization',
|
||||||
type: 'metric',
|
type: 'metric',
|
||||||
params: {
|
params: {
|
||||||
fontSize: 60,
|
fontSize: 60
|
||||||
handleNoResults: true
|
|
||||||
},
|
},
|
||||||
aggs: [
|
aggs: [
|
||||||
{
|
{
|
||||||
|
|
|
@ -68,8 +68,7 @@ describe('sibling pipeline aggs', function () {
|
||||||
title: 'New Visualization',
|
title: 'New Visualization',
|
||||||
type: 'metric',
|
type: 'metric',
|
||||||
params: {
|
params: {
|
||||||
fontSize: 60,
|
fontSize: 60
|
||||||
handleNoResults: true
|
|
||||||
},
|
},
|
||||||
aggs: [
|
aggs: [
|
||||||
{
|
{
|
||||||
|
|
|
@ -49,8 +49,7 @@ describe('Top hit metric', function () {
|
||||||
title: 'New Visualization',
|
title: 'New Visualization',
|
||||||
type: 'metric',
|
type: 'metric',
|
||||||
params: {
|
params: {
|
||||||
fontSize: 60,
|
fontSize: 60
|
||||||
handleNoResults: true
|
|
||||||
},
|
},
|
||||||
aggs: [
|
aggs: [
|
||||||
{
|
{
|
||||||
|
|
|
@ -269,10 +269,11 @@ describe('Vis Class', function () {
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
columns: [{
|
columns: [{
|
||||||
|
id: 'col-0',
|
||||||
title: 'test',
|
title: 'test',
|
||||||
aggConfig
|
aggConfig
|
||||||
}],
|
}],
|
||||||
rows: [['US']]
|
rows: [{ 'col-0': 'US' }]
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -21,9 +21,8 @@ import _ from 'lodash';
|
||||||
import ngMock from 'ng_mock';
|
import ngMock from 'ng_mock';
|
||||||
import expect from 'expect.js';
|
import expect from 'expect.js';
|
||||||
import sinon from 'sinon';
|
import sinon from 'sinon';
|
||||||
import { TabifyTable } from '../../../agg_response/tabify/_table';
|
|
||||||
import { AggResponseIndexProvider } from '../../../agg_response';
|
import { AggResponseIndexProvider } from '../../../agg_response';
|
||||||
import { BasicResponseHandlerProvider } from '../../response_handlers/basic';
|
import { VislibResponseHandlerProvider } from '../../response_handlers/vislib';
|
||||||
|
|
||||||
describe('renderbot#buildChartData', function () {
|
describe('renderbot#buildChartData', function () {
|
||||||
let buildChartData;
|
let buildChartData;
|
||||||
|
@ -32,7 +31,7 @@ describe('renderbot#buildChartData', function () {
|
||||||
beforeEach(ngMock.module('kibana'));
|
beforeEach(ngMock.module('kibana'));
|
||||||
beforeEach(ngMock.inject(function (Private) {
|
beforeEach(ngMock.inject(function (Private) {
|
||||||
aggResponse = Private(AggResponseIndexProvider);
|
aggResponse = Private(AggResponseIndexProvider);
|
||||||
buildChartData = Private(BasicResponseHandlerProvider).handler;
|
buildChartData = Private(VislibResponseHandlerProvider).handler;
|
||||||
}));
|
}));
|
||||||
|
|
||||||
describe('for hierarchical vis', function () {
|
describe('for hierarchical vis', function () {
|
||||||
|
@ -79,7 +78,7 @@ describe('renderbot#buildChartData', function () {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const esResp = { hits: { total: 1 } };
|
const esResp = { hits: { total: 1 } };
|
||||||
const tabbed = { tables: [ new TabifyTable() ] };
|
const tabbed = { tables: [ {}] };
|
||||||
|
|
||||||
sinon.stub(aggResponse, 'tabify').returns(tabbed);
|
sinon.stub(aggResponse, 'tabify').returns(tabbed);
|
||||||
expect(buildChartData.call(renderbot, esResp)).to.eql(chart);
|
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 () {
|
it('converts table groups into rows/columns wrappers for charts', function () {
|
||||||
const converter = sinon.stub().returns('chart');
|
const converter = sinon.stub().returns('chart');
|
||||||
const esResp = { hits: { total: 1 } };
|
const esResp = { hits: { total: 1 } };
|
||||||
const tables = [new TabifyTable(), new TabifyTable(), new TabifyTable(), new TabifyTable()];
|
const tables = [{}, {}, {}, {}];
|
||||||
|
|
||||||
const renderbot = {
|
const renderbot = {
|
||||||
vis: {
|
vis: {
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
|
|
||||||
import ngMock from 'ng_mock';
|
import ngMock from 'ng_mock';
|
||||||
import expect from 'expect.js';
|
import expect from 'expect.js';
|
||||||
import { BasicResponseHandlerProvider } from '../../response_handlers/basic';
|
import { VislibResponseHandlerProvider } from '../../response_handlers/vislib';
|
||||||
import { VisProvider } from '../..';
|
import { VisProvider } from '../..';
|
||||||
import fixtures from 'fixtures/fake_hierarchical_data';
|
import fixtures from 'fixtures/fake_hierarchical_data';
|
||||||
import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';
|
import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';
|
||||||
|
@ -39,7 +39,7 @@ describe('Basic Response Handler', function () {
|
||||||
|
|
||||||
beforeEach(ngMock.module('kibana'));
|
beforeEach(ngMock.module('kibana'));
|
||||||
beforeEach(ngMock.inject(function (Private) {
|
beforeEach(ngMock.inject(function (Private) {
|
||||||
basicResponseHandler = Private(BasicResponseHandlerProvider).handler;
|
basicResponseHandler = Private(VislibResponseHandlerProvider).handler;
|
||||||
Vis = Private(VisProvider);
|
Vis = Private(VisProvider);
|
||||||
indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider);
|
indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider);
|
||||||
}));
|
}));
|
||||||
|
|
|
@ -39,9 +39,9 @@ describe('Vislib Vis Type', function () {
|
||||||
}));
|
}));
|
||||||
|
|
||||||
describe('initialization', () => {
|
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);
|
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', () => {
|
it('should not change response handler if its already set', () => {
|
||||||
|
|
|
@ -28,27 +28,28 @@ export function convertToGeoJson(tabifiedResponse) {
|
||||||
let max = -Infinity;
|
let max = -Infinity;
|
||||||
let geoAgg;
|
let geoAgg;
|
||||||
|
|
||||||
if (tabifiedResponse && tabifiedResponse.tables && tabifiedResponse.tables[0] && tabifiedResponse.tables[0].rows) {
|
if (tabifiedResponse && tabifiedResponse.rows) {
|
||||||
|
|
||||||
const table = tabifiedResponse.tables[0];
|
const table = tabifiedResponse;
|
||||||
const geohashIndex = table.columns.findIndex(column => column.aggConfig.type.dslName === 'geohash_grid');
|
const geohashColumn = table.columns.find(column => column.aggConfig.type.dslName === 'geohash_grid');
|
||||||
geoAgg = table.columns.find(column => column.aggConfig.type.dslName === 'geohash_grid');
|
|
||||||
|
|
||||||
if (geohashIndex === -1) {
|
if (!geohashColumn) {
|
||||||
features = [];
|
features = [];
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
const metricIndex = table.columns.findIndex(column => column.aggConfig.type.type === 'metrics');
|
geoAgg = geohashColumn.aggConfig;
|
||||||
const geocentroidIndex = table.columns.findIndex(column => column.aggConfig.type.dslName === 'geo_centroid');
|
|
||||||
|
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 => {
|
features = table.rows.map(row => {
|
||||||
|
|
||||||
const geohash = row[geohashIndex];
|
const geohash = row[geohashColumn.id];
|
||||||
const geohashLocation = decodeGeoHash(geohash);
|
const geohashLocation = decodeGeoHash(geohash);
|
||||||
|
|
||||||
let pointCoordinates;
|
let pointCoordinates;
|
||||||
if (geocentroidIndex > -1) {
|
if (geocentroidColumn) {
|
||||||
const location = row[geocentroidIndex];
|
const location = row[geocentroidColumn.id];
|
||||||
pointCoordinates = [location.lon, location.lat];
|
pointCoordinates = [location.lon, location.lat];
|
||||||
} else {
|
} else {
|
||||||
pointCoordinates = [geohashLocation.longitude[2], geohashLocation.latitude[2]];
|
pointCoordinates = [geohashLocation.longitude[2], geohashLocation.latitude[2]];
|
||||||
|
@ -66,13 +67,13 @@ export function convertToGeoJson(tabifiedResponse) {
|
||||||
geohashLocation.longitude[2]
|
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
|
// 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[0] = clampGrid(pointCoordinates[0], geohashLocation.longitude[0], geohashLocation.longitude[1]);
|
||||||
pointCoordinates[1] = clampGrid(pointCoordinates[1], geohashLocation.latitude[0], geohashLocation.latitude[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);
|
min = Math.min(min, value);
|
||||||
max = Math.max(max, value);
|
max = Math.max(max, value);
|
||||||
|
|
||||||
|
@ -111,8 +112,8 @@ export function convertToGeoJson(tabifiedResponse) {
|
||||||
meta: {
|
meta: {
|
||||||
min: min,
|
min: min,
|
||||||
max: max,
|
max: max,
|
||||||
geohashPrecision: geoAgg && geoAgg.aggConfig.params.precision,
|
geohashPrecision: geoAgg && geoAgg.params.precision,
|
||||||
geohashGridDimensionsAtEquator: geoAgg && gridDimensions(geoAgg.aggConfig.params.precision)
|
geohashGridDimensionsAtEquator: geoAgg && gridDimensions(geoAgg.params.precision)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,10 +35,8 @@ const CourierRequestHandlerProvider = function () {
|
||||||
*/
|
*/
|
||||||
async function buildTabularInspectorData(vis, searchSource, aggConfigs) {
|
async function buildTabularInspectorData(vis, searchSource, aggConfigs) {
|
||||||
const table = tabifyAggResponse(aggConfigs, searchSource.finalResponse, {
|
const table = tabifyAggResponse(aggConfigs, searchSource.finalResponse, {
|
||||||
canSplit: false,
|
|
||||||
asAggConfigResults: false,
|
|
||||||
partialRows: true,
|
partialRows: true,
|
||||||
isHierarchical: vis.isHierarchical(),
|
metricsAtAllLevels: vis.isHierarchical(),
|
||||||
});
|
});
|
||||||
const columns = table.columns.map((col, index) => {
|
const columns = table.columns.map((col, index) => {
|
||||||
const field = col.aggConfig.getField();
|
const field = col.aggConfig.getField();
|
||||||
|
@ -46,7 +44,7 @@ const CourierRequestHandlerProvider = function () {
|
||||||
col.aggConfig.isFilterable()
|
col.aggConfig.isFilterable()
|
||||||
&& (!field || field.filterable);
|
&& (!field || field.filterable);
|
||||||
return ({
|
return ({
|
||||||
name: col.title,
|
name: col.name,
|
||||||
field: `col${index}`,
|
field: `col${index}`,
|
||||||
filter: isCellContentFilterable && ((value) => {
|
filter: isCellContentFilterable && ((value) => {
|
||||||
const filter = col.aggConfig.createFilter(value.raw);
|
const filter = col.aggConfig.createFilter(value.raw);
|
||||||
|
@ -61,9 +59,10 @@ const CourierRequestHandlerProvider = function () {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
const rows = table.rows.map(row => {
|
const rows = table.rows.map(row => {
|
||||||
return row.reduce((prev, cur, index) => {
|
return table.columns.reduce((prev, cur, index) => {
|
||||||
const fieldFormatter = table.columns[index].aggConfig.fieldFormatter('text');
|
const value = row[cur.id];
|
||||||
prev[`col${index}`] = new FormattedData(cur, fieldFormatter(cur));
|
const fieldFormatter = cur.aggConfig.fieldFormatter('text');
|
||||||
|
prev[`col${index}`] = new FormattedData(value, fieldFormatter(value));
|
||||||
return prev;
|
return prev;
|
||||||
}, {});
|
}, {});
|
||||||
});
|
});
|
||||||
|
|
110
src/ui/public/vis/response_handlers/legacy.js
Normal file
110
src/ui/public/vis/response_handlers/legacy.js
Normal file
|
@ -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 };
|
|
@ -17,7 +17,6 @@
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import _ from 'lodash';
|
|
||||||
import { AggResponseIndexProvider } from '../../agg_response';
|
import { AggResponseIndexProvider } from '../../agg_response';
|
||||||
import { VisResponseHandlersRegistryProvider } from '../../registry/vis_response_handlers';
|
import { VisResponseHandlersRegistryProvider } from '../../registry/vis_response_handlers';
|
||||||
import { getTime } from 'ui/timefilter/get_time';
|
import { getTime } from 'ui/timefilter/get_time';
|
||||||
|
@ -32,10 +31,9 @@ const TabifyResponseHandlerProvider = function (Private) {
|
||||||
const time = getTime(vis.indexPattern, vis.filters.timeRange);
|
const time = getTime(vis.indexPattern, vis.filters.timeRange);
|
||||||
|
|
||||||
const tableGroup = aggResponse.tabify(vis.getAggConfig(), response, {
|
const tableGroup = aggResponse.tabify(vis.getAggConfig(), response, {
|
||||||
canSplit: true,
|
metricsAtAllLevels: vis.isHierarchical(),
|
||||||
asAggConfigResults: _.get(vis, 'type.responseHandlerConfig.asAggConfigResults', false),
|
partialRows: vis.params.showPartialRows,
|
||||||
isHierarchical: vis.isHierarchical(),
|
timeRange: time ? time.range : undefined,
|
||||||
timeRange: time ? time.range : undefined
|
|
||||||
});
|
});
|
||||||
|
|
||||||
resolve(tableGroup);
|
resolve(tableGroup);
|
||||||
|
|
|
@ -18,18 +18,18 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { AggResponseIndexProvider } from '../../agg_response';
|
import { AggResponseIndexProvider } from '../../agg_response';
|
||||||
import { TabifyTable } from '../../agg_response/tabify/_table';
|
import { LegacyResponseHandlerProvider } from './legacy';
|
||||||
import { getTime } from 'ui/timefilter/get_time';
|
|
||||||
|
|
||||||
import { VisResponseHandlersRegistryProvider } from '../../registry/vis_response_handlers';
|
import { VisResponseHandlersRegistryProvider } from '../../registry/vis_response_handlers';
|
||||||
|
|
||||||
const BasicResponseHandlerProvider = function (Private) {
|
const VislibResponseHandlerProvider = function (Private) {
|
||||||
const aggResponse = Private(AggResponseIndexProvider);
|
const aggResponse = Private(AggResponseIndexProvider);
|
||||||
|
const tableResponseProvider = Private(LegacyResponseHandlerProvider).handler;
|
||||||
|
|
||||||
function convertTableGroup(vis, tableGroup) {
|
function convertTableGroup(vis, tableGroup) {
|
||||||
const tables = tableGroup.tables;
|
const tables = tableGroup.tables;
|
||||||
const firstChild = tables[0];
|
const firstChild = tables[0];
|
||||||
if (firstChild instanceof TabifyTable) {
|
|
||||||
|
if (firstChild.columns) {
|
||||||
|
|
||||||
const chart = convertTable(vis, firstChild);
|
const chart = convertTable(vis, firstChild);
|
||||||
// if chart is within a split, assign group title to its label
|
// if chart is within a split, assign group title to its label
|
||||||
|
@ -40,6 +40,7 @@ const BasicResponseHandlerProvider = function (Private) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!tables.length) return;
|
if (!tables.length) return;
|
||||||
|
|
||||||
const out = {};
|
const out = {};
|
||||||
let outList;
|
let outList;
|
||||||
|
|
||||||
|
@ -64,38 +65,34 @@ const BasicResponseHandlerProvider = function (Private) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: 'basic',
|
name: 'vislib',
|
||||||
handler: function (vis, response) {
|
handler: function (vis, response) {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
if (vis.isHierarchical()) {
|
if (vis.isHierarchical()) {
|
||||||
// the hierarchical converter is very self-contained (woot!)
|
// 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));
|
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, {
|
converted.hits = response.hits.total;
|
||||||
canSplit: true,
|
|
||||||
asAggConfigResults: true,
|
resolve(converted);
|
||||||
isHierarchical: vis.isHierarchical(),
|
|
||||||
timeRange: time ? time.range : undefined
|
|
||||||
});
|
});
|
||||||
|
|
||||||
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: [] };
|
|
||||||
}
|
|
||||||
|
|
||||||
converted.hits = response.hits.total;
|
|
||||||
|
|
||||||
resolve(converted);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
VisResponseHandlersRegistryProvider.register(BasicResponseHandlerProvider);
|
VisResponseHandlersRegistryProvider.register(VislibResponseHandlerProvider);
|
||||||
|
|
||||||
export { BasicResponseHandlerProvider };
|
export { VislibResponseHandlerProvider };
|
|
@ -49,7 +49,11 @@ const getTerms = (table, columnIndex, rowIndex) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// get only rows where cell value matches current row for all the fields before columnIndex
|
// 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]);
|
const terms = rows.map(row => row[columnIndex]);
|
||||||
|
|
||||||
return [...new Set(terms.filter(term => {
|
return [...new Set(terms.filter(term => {
|
||||||
|
@ -99,17 +103,17 @@ export function VisProvider(Private, indexPatterns, getAppState) {
|
||||||
filterBarClickHandler(appState)(event);
|
filterBarClickHandler(appState)(event);
|
||||||
},
|
},
|
||||||
addFilter: (data, columnIndex, rowIndex, cellValue) => {
|
addFilter: (data, columnIndex, rowIndex, cellValue) => {
|
||||||
const agg = data.columns[columnIndex].aggConfig;
|
const { aggConfig, id: columnId } = data.columns[columnIndex];
|
||||||
let filter = [];
|
let filter = [];
|
||||||
const value = rowIndex > -1 ? data.rows[rowIndex][columnIndex] : cellValue;
|
const value = rowIndex > -1 ? data.rows[rowIndex][columnId] : cellValue;
|
||||||
if (!value) {
|
if (!value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (agg.type.name === 'terms' && agg.params.otherBucket) {
|
if (aggConfig.type.name === 'terms' && aggConfig.params.otherBucket) {
|
||||||
const terms = getTerms(data, columnIndex, rowIndex);
|
const terms = getTerms(data, columnIndex, rowIndex);
|
||||||
filter = agg.createFilter(value, { terms });
|
filter = aggConfig.createFilter(value, { terms });
|
||||||
} else {
|
} else {
|
||||||
filter = agg.createFilter(value);
|
filter = aggConfig.createFilter(value);
|
||||||
}
|
}
|
||||||
queryFilter.addFilters(filter);
|
queryFilter.addFilters(filter);
|
||||||
}, brush: (event) => {
|
}, brush: (event) => {
|
||||||
|
|
|
@ -108,7 +108,8 @@ export function VislibVisTypeProvider(Private, $rootScope, $timeout, $compile) {
|
||||||
class VislibVisType extends BaseVisType {
|
class VislibVisType extends BaseVisType {
|
||||||
constructor(opts) {
|
constructor(opts) {
|
||||||
if (!opts.responseHandler) {
|
if (!opts.responseHandler) {
|
||||||
opts.responseHandler = 'basic';
|
opts.responseHandler = 'vislib';
|
||||||
|
opts.responseHandlerConfig = { asAggConfigResults: true };
|
||||||
}
|
}
|
||||||
if (!opts.responseConverter) {
|
if (!opts.responseConverter) {
|
||||||
opts.responseConverter = pointSeries;
|
opts.responseConverter = pointSeries;
|
||||||
|
|
|
@ -219,18 +219,7 @@ export function VisHandlerProvider(Private) {
|
||||||
// to continuously call render on resize
|
// to continuously call render on resize
|
||||||
.attr('class', 'visualize-error chart error');
|
.attr('class', 'visualize-error chart error');
|
||||||
|
|
||||||
if (message === 'No results found') {
|
div.append('h4').text(markdownIt.renderInline(message));
|
||||||
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('<i class="fa fa-meh-o"></i>')
|
|
||||||
.append('h4').text(message);
|
|
||||||
|
|
||||||
div.append('div').attr('class', 'item bottom');
|
|
||||||
} else {
|
|
||||||
div.append('h4').text(markdownIt.renderInline(message));
|
|
||||||
}
|
|
||||||
|
|
||||||
dispatchRenderComplete(this.el);
|
dispatchRenderComplete(this.el);
|
||||||
return div;
|
return div;
|
||||||
|
|
|
@ -43,7 +43,7 @@ class VisualizationStub {
|
||||||
describe('<Visualization/>', () => {
|
describe('<Visualization/>', () => {
|
||||||
|
|
||||||
const visData = {
|
const visData = {
|
||||||
hits: { total: 1 }
|
hits: 1
|
||||||
};
|
};
|
||||||
|
|
||||||
const uiState = {
|
const uiState = {
|
||||||
|
@ -63,19 +63,18 @@ describe('<Visualization/>', () => {
|
||||||
return this.uiState;
|
return this.uiState;
|
||||||
},
|
},
|
||||||
params: {
|
params: {
|
||||||
|
|
||||||
},
|
},
|
||||||
type: {
|
type: {
|
||||||
title: 'new vis',
|
title: 'new vis',
|
||||||
requiresSearch: true,
|
requiresSearch: true,
|
||||||
handleNoResults: true,
|
useCustomNoDataScreen: false,
|
||||||
visualization: VisualizationStub
|
visualization: VisualizationStub
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should display no result message when length of data is 0', () => {
|
it('should display no result message when length of data is 0', () => {
|
||||||
const data = { hits: { total: 0 } };
|
const data = { rows: [] };
|
||||||
const wrapper = render(<Visualization vis={vis} visData={data} listenOnChange={true} uiState={uiState} />);
|
const wrapper = render(<Visualization vis={vis} visData={data} listenOnChange={true} uiState={uiState} />);
|
||||||
expect(wrapper.text()).toBe('No results found');
|
expect(wrapper.text()).toBe('No results found');
|
||||||
});
|
});
|
||||||
|
@ -87,7 +86,7 @@ describe('<Visualization/>', () => {
|
||||||
|
|
||||||
it('should call onInit when rendering no data', () => {
|
it('should call onInit when rendering no data', () => {
|
||||||
const spy = jest.fn();
|
const spy = jest.fn();
|
||||||
const noData = { hits: { total: 0 } };
|
const noData = { hits: 0 };
|
||||||
mount(
|
mount(
|
||||||
<Visualization
|
<Visualization
|
||||||
vis={vis}
|
vis={vis}
|
||||||
|
|
|
@ -30,8 +30,9 @@ import './visualization.less';
|
||||||
|
|
||||||
function shouldShowNoResultsMessage(vis: Vis, visData: any): boolean {
|
function shouldShowNoResultsMessage(vis: Vis, visData: any): boolean {
|
||||||
const requiresSearch = get(vis, 'type.requiresSearch');
|
const requiresSearch = get(vis, 'type.requiresSearch');
|
||||||
const isZeroHits = get(visData, 'hits.total') === 0;
|
const rows: object[] | undefined = get(visData, 'rows');
|
||||||
const shouldShowMessage = !get(vis, 'params.handleNoResults');
|
const isZeroHits = get(visData, 'hits') === 0 || (rows && !rows.length);
|
||||||
|
const shouldShowMessage = !get(vis, 'type.useCustomNoDataScreen');
|
||||||
|
|
||||||
return Boolean(requiresSearch && isZeroHits && shouldShowMessage);
|
return Boolean(requiresSearch && isZeroHits && shouldShowMessage);
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,15 +18,18 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { dispatchRenderComplete } from '../../render_complete';
|
||||||
|
|
||||||
interface VisualizationNoResultsProps {
|
interface VisualizationNoResultsProps {
|
||||||
onInit?: () => void;
|
onInit?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class VisualizationNoResults extends React.Component<VisualizationNoResultsProps> {
|
export class VisualizationNoResults extends React.Component<VisualizationNoResultsProps> {
|
||||||
|
private containerDiv = React.createRef<HTMLDivElement>();
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
return (
|
return (
|
||||||
<div className="text-center visualize-error visualize-chart">
|
<div className="text-center visualize-error visualize-chart" ref={this.containerDiv}>
|
||||||
<div className="item top" />
|
<div className="item top" />
|
||||||
<div className="item">
|
<div className="item">
|
||||||
<h2 aria-hidden="true">
|
<h2 aria-hidden="true">
|
||||||
|
@ -40,14 +43,19 @@ export class VisualizationNoResults extends React.Component<VisualizationNoResul
|
||||||
}
|
}
|
||||||
|
|
||||||
public componentDidMount() {
|
public componentDidMount() {
|
||||||
if (this.props.onInit) {
|
this.afterRender();
|
||||||
this.props.onInit();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public componentDidUpdate() {
|
public componentDidUpdate() {
|
||||||
|
this.afterRender();
|
||||||
|
}
|
||||||
|
|
||||||
|
private afterRender() {
|
||||||
if (this.props.onInit) {
|
if (this.props.onInit) {
|
||||||
this.props.onInit();
|
this.props.onInit();
|
||||||
}
|
}
|
||||||
|
if (this.containerDiv.current) {
|
||||||
|
dispatchRenderComplete(this.containerDiv.current);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,9 +46,10 @@ export const UI_EXPORT_DEFAULTS = {
|
||||||
'ui/vis/request_handlers/none'
|
'ui/vis/request_handlers/none'
|
||||||
],
|
],
|
||||||
visResponseHandlers: [
|
visResponseHandlers: [
|
||||||
'ui/vis/response_handlers/basic',
|
'ui/vis/response_handlers/vislib',
|
||||||
'ui/vis/response_handlers/none',
|
'ui/vis/response_handlers/none',
|
||||||
'ui/vis/response_handlers/tabify',
|
'ui/vis/response_handlers/tabify',
|
||||||
|
'ui/vis/response_handlers/legacy',
|
||||||
],
|
],
|
||||||
visEditorTypes: [
|
visEditorTypes: [
|
||||||
'ui/vis/editors/default/default',
|
'ui/vis/editors/default/default',
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue