mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
Format the totals row (#10414)
* Format the totals row * Add tests and format count results * Handle the new metrics better * Rename variable to isFieldNumeric and isFieldDate * Add pipeline tests * Fix timezone issues in tests
This commit is contained in:
parent
c13e786e10
commit
74641d7d85
4 changed files with 497 additions and 13 deletions
|
@ -110,6 +110,361 @@ data.threeTermBuckets = {
|
|||
}
|
||||
};
|
||||
|
||||
data.oneTermOneHistogramBucketWithTwoMetricsOneTopHitOneDerivative = {
|
||||
hits: { total: 1000, hits: [], max_score: 0 },
|
||||
aggregations: {
|
||||
agg_3: {
|
||||
buckets: [
|
||||
{
|
||||
key: 'png',
|
||||
doc_count: 50,
|
||||
agg_4: {
|
||||
buckets: [
|
||||
{
|
||||
key_as_string: '2014-09-28T00:00:00.000Z',
|
||||
key: 1411862400000,
|
||||
doc_count: 1,
|
||||
agg_1: { value: 9283 },
|
||||
agg_2: { value: 1411862400000 },
|
||||
agg_6: {
|
||||
hits: {
|
||||
total: 2,
|
||||
hits: [{
|
||||
fields: {
|
||||
bytes: 23
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
key_as_string: '2014-09-29T00:00:00.000Z',
|
||||
key: 1411948800000,
|
||||
doc_count: 2,
|
||||
agg_1: { value: 28349 },
|
||||
agg_2: { value: 1411948800000 },
|
||||
agg_5: { value: 203 },
|
||||
agg_6: {
|
||||
hits: {
|
||||
total: 2,
|
||||
hits: [{
|
||||
fields: {
|
||||
bytes: 39
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
key_as_string: '2014-09-30T00:00:00.000Z',
|
||||
key: 1412035200000,
|
||||
doc_count: 3,
|
||||
agg_1: { value: 84330 },
|
||||
agg_2: { value: 1412035200000 },
|
||||
agg_5: { value: 200 },
|
||||
agg_6: {
|
||||
hits: {
|
||||
total: 2,
|
||||
hits: [{
|
||||
fields: {
|
||||
bytes: 329
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
key_as_string: '2014-10-01T00:00:00.000Z',
|
||||
key: 1412121600000,
|
||||
doc_count: 4,
|
||||
agg_1: { value: 34992 },
|
||||
agg_2: { value: 1412121600000 },
|
||||
agg_5: { value: 103 },
|
||||
agg_6: {
|
||||
hits: {
|
||||
total: 2,
|
||||
hits: [{
|
||||
fields: {
|
||||
bytes: 22
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
key_as_string: '2014-10-02T00:00:00.000Z',
|
||||
key: 1412208000000,
|
||||
doc_count: 5,
|
||||
agg_1: { value: 145432 },
|
||||
agg_2: { value: 1412208000000 },
|
||||
agg_5: { value: 153 },
|
||||
agg_6: {
|
||||
hits: {
|
||||
total: 2,
|
||||
hits: [{
|
||||
fields: {
|
||||
bytes: 93
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
key_as_string: '2014-10-03T00:00:00.000Z',
|
||||
key: 1412294400000,
|
||||
doc_count: 35,
|
||||
agg_1: { value: 220943 },
|
||||
agg_2: { value: 1412294400000 },
|
||||
agg_5: { value: 239 },
|
||||
agg_6: {
|
||||
hits: {
|
||||
total: 2,
|
||||
hits: [{
|
||||
fields: {
|
||||
bytes: 72
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'css',
|
||||
doc_count: 20,
|
||||
agg_4: {
|
||||
buckets: [
|
||||
{
|
||||
key_as_string: '2014-09-28T00:00:00.000Z',
|
||||
key: 1411862400000,
|
||||
doc_count: 1,
|
||||
agg_1: { value: 9283 },
|
||||
agg_2: { value: 1411862400000 },
|
||||
agg_6: {
|
||||
hits: {
|
||||
total: 2,
|
||||
hits: [{
|
||||
fields: {
|
||||
bytes: 75
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
key_as_string: '2014-09-29T00:00:00.000Z',
|
||||
key: 1411948800000,
|
||||
doc_count: 2,
|
||||
agg_1: { value: 28349 },
|
||||
agg_2: { value: 1411948800000 },
|
||||
agg_5: { value: 10 },
|
||||
agg_6: {
|
||||
hits: {
|
||||
total: 2,
|
||||
hits: [{
|
||||
fields: {
|
||||
bytes: 11
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
key_as_string: '2014-09-30T00:00:00.000Z',
|
||||
key: 1412035200000,
|
||||
doc_count: 3,
|
||||
agg_1: { value: 84330 },
|
||||
agg_2: { value: 1412035200000 },
|
||||
agg_5: { value: 24 },
|
||||
agg_6: {
|
||||
hits: {
|
||||
total: 2,
|
||||
hits: [{
|
||||
fields: {
|
||||
bytes: 238
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
key_as_string: '2014-10-01T00:00:00.000Z',
|
||||
key: 1412121600000,
|
||||
doc_count: 4,
|
||||
agg_1: { value: 34992 },
|
||||
agg_2: { value: 1412121600000 },
|
||||
agg_5: { value: 49 },
|
||||
agg_6: {
|
||||
hits: {
|
||||
total: 2,
|
||||
hits: [{
|
||||
fields: {
|
||||
bytes: 343
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
key_as_string: '2014-10-02T00:00:00.000Z',
|
||||
key: 1412208000000,
|
||||
doc_count: 5,
|
||||
agg_1: { value: 145432 },
|
||||
agg_2: { value: 1412208000000 },
|
||||
agg_5: { value: 100 },
|
||||
agg_6: {
|
||||
hits: {
|
||||
total: 2,
|
||||
hits: [{
|
||||
fields: {
|
||||
bytes: 837
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
key_as_string: '2014-10-03T00:00:00.000Z',
|
||||
key: 1412294400000,
|
||||
doc_count: 5,
|
||||
agg_1: { value: 220943 },
|
||||
agg_2: { value: 1412294400000 },
|
||||
agg_5: { value: 23 },
|
||||
agg_6: {
|
||||
hits: {
|
||||
total: 2,
|
||||
hits: [{
|
||||
fields: {
|
||||
bytes: 302
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'html',
|
||||
doc_count: 90,
|
||||
agg_4: {
|
||||
buckets: [
|
||||
{
|
||||
key_as_string: '2014-09-28T00:00:00.000Z',
|
||||
key: 1411862400000,
|
||||
doc_count: 10,
|
||||
agg_1: { value: 9283 },
|
||||
agg_2: { value: 1411862400000 },
|
||||
agg_6: {
|
||||
hits: {
|
||||
total: 2,
|
||||
hits: [{
|
||||
fields: {
|
||||
bytes: 30
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
key_as_string: '2014-09-29T00:00:00.000Z',
|
||||
key: 1411948800000,
|
||||
doc_count: 20,
|
||||
agg_1: { value: 28349 },
|
||||
agg_2: { value: 1411948800000 },
|
||||
agg_5: { value: 1 },
|
||||
agg_6: {
|
||||
hits: {
|
||||
total: 2,
|
||||
hits: [{
|
||||
fields: {
|
||||
bytes: 43
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
key_as_string: '2014-09-30T00:00:00.000Z',
|
||||
key: 1412035200000,
|
||||
doc_count: 30,
|
||||
agg_1: { value: 84330 },
|
||||
agg_2: { value: 1412035200000 },
|
||||
agg_5: { value: 5 },
|
||||
agg_6: {
|
||||
hits: {
|
||||
total: 2,
|
||||
hits: [{
|
||||
fields: {
|
||||
bytes: 88
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
key_as_string: '2014-10-01T00:00:00.000Z',
|
||||
key: 1412121600000,
|
||||
doc_count: 11,
|
||||
agg_1: { value: 34992 },
|
||||
agg_2: { value: 1412121600000 },
|
||||
agg_5: { value: 10 },
|
||||
agg_6: {
|
||||
hits: {
|
||||
total: 2,
|
||||
hits: [{
|
||||
fields: {
|
||||
bytes: 91
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
key_as_string: '2014-10-02T00:00:00.000Z',
|
||||
key: 1412208000000,
|
||||
doc_count: 12,
|
||||
agg_1: { value: 145432 },
|
||||
agg_2: { value: 1412208000000 },
|
||||
agg_5: { value: 43 },
|
||||
agg_6: {
|
||||
hits: {
|
||||
total: 2,
|
||||
hits: [{
|
||||
fields: {
|
||||
bytes: 534
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
key_as_string: '2014-10-03T00:00:00.000Z',
|
||||
key: 1412294400000,
|
||||
doc_count: 7,
|
||||
agg_1: { value: 220943 },
|
||||
agg_2: { value: 1412294400000 },
|
||||
agg_5: { value: 1 },
|
||||
agg_6: {
|
||||
hits: {
|
||||
total: 2,
|
||||
hits: [{
|
||||
fields: {
|
||||
bytes: 553
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
data.oneRangeBucket = {
|
||||
'took': 35,
|
||||
'timed_out': false,
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
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';
|
||||
|
@ -14,12 +15,14 @@ describe('AggTable Directive', function () {
|
|||
let tabifyAggResponse;
|
||||
let Vis;
|
||||
let indexPattern;
|
||||
let settings;
|
||||
|
||||
beforeEach(ngMock.module('kibana'));
|
||||
beforeEach(ngMock.inject(function ($injector, Private) {
|
||||
beforeEach(ngMock.inject(function ($injector, Private, config) {
|
||||
tabifyAggResponse = Private(AggResponseTabifyTabifyProvider);
|
||||
indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider);
|
||||
Vis = Private(VisProvider);
|
||||
settings = config;
|
||||
|
||||
$rootScope = $injector.get('$rootScope');
|
||||
$compile = $injector.get('$compile');
|
||||
|
@ -107,6 +110,99 @@ describe('AggTable Directive', function () {
|
|||
});
|
||||
});
|
||||
|
||||
describe('renders totals row', function () {
|
||||
function totalsRowTest(totalFunc, expected) {
|
||||
const vis = new Vis(indexPattern, {
|
||||
type: 'table',
|
||||
aggs: [
|
||||
{ type: 'avg', schema: 'metric', params: { field: 'bytes' } },
|
||||
{ type: 'min', schema: 'metric', params: { field: '@timestamp' } },
|
||||
{ type: 'terms', schema: 'bucket', params: { field: 'extension' } },
|
||||
{ type: 'date_histogram', schema: 'bucket', params: { field: '@timestamp', interval: 'd' } },
|
||||
{ type: 'derivative', schema: 'metric', params: { metricAgg: 'custom', customMetric: { id:'5-orderAgg', type: 'count' } } },
|
||||
{ type: 'top_hits', schema: 'metric', params: { field: 'bytes', aggregate: { val: 'min' }, size: 1 } }
|
||||
]
|
||||
});
|
||||
vis.aggs.forEach(function (agg, i) {
|
||||
agg.id = 'agg_' + (i + 1);
|
||||
});
|
||||
function setDefaultTimezone() {
|
||||
moment.tz.setDefault(settings.get('dateFormat:tz'));
|
||||
}
|
||||
|
||||
const off = $scope.$on('change:config.dateFormat:tz', setDefaultTimezone);
|
||||
const oldTimezoneSetting = settings.get('dateFormat:tz');
|
||||
settings.set('dateFormat:tz', 'UTC');
|
||||
|
||||
$scope.table = tabifyAggResponse(vis,
|
||||
fixtures.oneTermOneHistogramBucketWithTwoMetricsOneTopHitOneDerivative,
|
||||
{ canSplit: false, minimalColumns: true, asAggConfigResults: true }
|
||||
);
|
||||
$scope.showTotal = true;
|
||||
$scope.totalFunc = totalFunc;
|
||||
const $el = $('<kbn-agg-table table="table" show-total="showTotal" total-func="totalFunc"></kbn-agg-table>');
|
||||
$compile($el)($scope);
|
||||
$scope.$digest();
|
||||
|
||||
expect($el.find('tfoot').size()).to.be(1);
|
||||
|
||||
const $rows = $el.find('tfoot tr');
|
||||
expect($rows.size()).to.be(1);
|
||||
|
||||
const $cells = $($rows[0]).find('th');
|
||||
expect($cells.size()).to.be(6);
|
||||
|
||||
for (let i = 0; i < 6; i++) {
|
||||
expect($($cells[i]).text()).to.be(expected[i]);
|
||||
}
|
||||
settings.set('dateFormat:tz', oldTimezoneSetting);
|
||||
off();
|
||||
}
|
||||
it('as count', function () {
|
||||
totalsRowTest('count', ['18', '18', '18', '18', '18', '18']);
|
||||
});
|
||||
it('as min', function () {
|
||||
totalsRowTest('min', [
|
||||
'',
|
||||
'September 28th 2014, 00:00:00.000',
|
||||
'9,283',
|
||||
'September 28th 2014, 00:00:00.000',
|
||||
'1',
|
||||
'11'
|
||||
]);
|
||||
});
|
||||
it('as max', function () {
|
||||
totalsRowTest('max', [
|
||||
'',
|
||||
'October 3rd 2014, 00:00:00.000',
|
||||
'220,943',
|
||||
'October 3rd 2014, 00:00:00.000',
|
||||
'239',
|
||||
'837'
|
||||
]);
|
||||
});
|
||||
it('as avg', function () {
|
||||
totalsRowTest('avg', [
|
||||
'',
|
||||
'',
|
||||
'87,221.5',
|
||||
'',
|
||||
'64.667',
|
||||
'206.833'
|
||||
]);
|
||||
});
|
||||
it('as sum', function () {
|
||||
totalsRowTest('sum', [
|
||||
'',
|
||||
'',
|
||||
'1,569,987',
|
||||
'',
|
||||
'1,164',
|
||||
'3,723'
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('aggTable.toCsv()', function () {
|
||||
it('escapes and formats the rows and columns properly', function () {
|
||||
const $el = $compile('<kbn-agg-table table="table">')($scope);
|
||||
|
|
|
@ -4,10 +4,13 @@ import 'ui/agg_table/agg_table.less';
|
|||
import _ from 'lodash';
|
||||
import uiModules from 'ui/modules';
|
||||
import aggTableTemplate from 'ui/agg_table/agg_table.html';
|
||||
import RegistryFieldFormatsProvider from 'ui/registry/field_formats';
|
||||
|
||||
uiModules
|
||||
.get('kibana')
|
||||
.directive('kbnAggTable', function ($filter, config, Private, compileRecursiveDirective) {
|
||||
const fieldFormats = Private(RegistryFieldFormatsProvider);
|
||||
const numberFormatter = fieldFormats.getDefaultInstance('number').getConverterFor('html');
|
||||
|
||||
return {
|
||||
restrict: 'E',
|
||||
|
@ -95,29 +98,59 @@ uiModules
|
|||
formattedColumn.class = 'visualize-table-right';
|
||||
}
|
||||
|
||||
const isFieldNumeric = (field && field.type === 'number');
|
||||
const isFirstValueNumeric = _.isNumber(_.get(table, `rows[0][${i}].value`));
|
||||
|
||||
if (isFieldNumeric || isFirstValueNumeric) {
|
||||
function sum(tableRows) {
|
||||
return _.reduce(tableRows, function (prev, curr) {return prev + curr[i].value; }, 0);
|
||||
let isFieldNumeric = false;
|
||||
let isFieldDate = false;
|
||||
const aggType = agg.type;
|
||||
if (aggType && aggType.type === 'metrics') {
|
||||
if (aggType.name === 'top_hits') {
|
||||
if (agg._opts.params.aggregate !== 'concat') {
|
||||
// all other aggregate types for top_hits output numbers
|
||||
// so treat this field as numeric
|
||||
isFieldNumeric = true;
|
||||
}
|
||||
} else if (field) {
|
||||
// if the metric has a field, check if it is either number or date
|
||||
isFieldNumeric = field.type === 'number';
|
||||
isFieldDate = field.type === 'date';
|
||||
} else {
|
||||
// if there is no field, then it is count or similar so just say number
|
||||
isFieldNumeric = true;
|
||||
}
|
||||
} else if (field) {
|
||||
isFieldNumeric = field.type === 'number';
|
||||
isFieldDate = field.type === 'date';
|
||||
}
|
||||
|
||||
if (isFieldNumeric || isFieldDate || $scope.totalFunc === 'count') {
|
||||
function sum(tableRows) {
|
||||
return _.reduce(tableRows, function (prev, curr) {
|
||||
// some metrics return undefined for some of the values
|
||||
// derivative is an example of this as it returns undefined in the first row
|
||||
if (curr[i].value === undefined) return prev;
|
||||
return prev + curr[i].value;
|
||||
}, 0);
|
||||
}
|
||||
const formatter = agg.fieldFormatter('html');
|
||||
|
||||
switch ($scope.totalFunc) {
|
||||
case 'sum':
|
||||
formattedColumn.total = sum(table.rows);
|
||||
if (!isFieldDate) {
|
||||
formattedColumn.total = formatter(sum(table.rows));
|
||||
}
|
||||
break;
|
||||
case 'avg':
|
||||
formattedColumn.total = sum(table.rows) / table.rows.length;
|
||||
if (!isFieldDate) {
|
||||
formattedColumn.total = formatter(sum(table.rows) / table.rows.length);
|
||||
}
|
||||
break;
|
||||
case 'min':
|
||||
formattedColumn.total = _.chain(table.rows).map(i).map('value').min().value();
|
||||
formattedColumn.total = formatter(_.chain(table.rows).map(i).map('value').min().value());
|
||||
break;
|
||||
case 'max':
|
||||
formattedColumn.total = _.chain(table.rows).map(i).map('value').max().value();
|
||||
formattedColumn.total = formatter(_.chain(table.rows).map(i).map('value').max().value());
|
||||
break;
|
||||
case 'count':
|
||||
formattedColumn.total = table.rows.length;
|
||||
formattedColumn.total = numberFormatter(table.rows.length);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
<tbody kbn-rows="page" kbn-rows-min="paginatedTable.rowsToShow(perPage, page.length)"></tbody>
|
||||
<tfoot ng-if="showTotal">
|
||||
<tr>
|
||||
<th ng-repeat="col in columns" class="numeric-value">{{col.total | number}}</th>
|
||||
<th ng-repeat="col in columns" class="numeric-value">{{col.total}}</th>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue