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