mirror of
https://github.com/elastic/kibana.git
synced 2025-04-25 02:09:32 -04:00
- added logic for trying to get the field value from the source or from a
doc_values field. - added onlyAggregatable option for a field agg param to decide whether or not to retain only aggregatable fields.
This commit is contained in:
parent
257729868c
commit
6a2bc0188c
5 changed files with 278 additions and 7 deletions
150
src/ui/public/agg_types/__tests__/metrics/get_values_at_path.js
Normal file
150
src/ui/public/agg_types/__tests__/metrics/get_values_at_path.js
Normal file
|
@ -0,0 +1,150 @@
|
||||||
|
import getValuesAtPath from 'ui/agg_types/metrics/_get_values_at_path';
|
||||||
|
import expect from 'expect.js';
|
||||||
|
|
||||||
|
describe('getValuesAtPath', function () {
|
||||||
|
it('non existing path', function () {
|
||||||
|
const values = getValuesAtPath({ aaa: 'bbb' }, [ 'not', 'in', 'there' ]);
|
||||||
|
expect(values).to.have.length(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('non existing path in nested object', function () {
|
||||||
|
const json = {
|
||||||
|
aaa: {
|
||||||
|
bbb: 123
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const values = getValuesAtPath(json, [ 'aaa', 'ccc' ]);
|
||||||
|
expect(values).to.have.length(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('get value at level one', function () {
|
||||||
|
const values = getValuesAtPath({ aaa: 'bbb' }, [ 'aaa' ]);
|
||||||
|
expect(values).to.eql([ 'bbb' ]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('get nested value', function () {
|
||||||
|
const json = {
|
||||||
|
aaa: {
|
||||||
|
bbb: 123
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const values = getValuesAtPath(json, [ 'aaa', 'bbb' ]);
|
||||||
|
expect(values).to.eql([ 123 ]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('value is an array', function () {
|
||||||
|
const json = {
|
||||||
|
aaa: [ 123, 456 ]
|
||||||
|
};
|
||||||
|
const values = getValuesAtPath(json, [ 'aaa' ]);
|
||||||
|
expect(values).to.eql([ 123, 456 ]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('nested value is an array', function () {
|
||||||
|
const json = {
|
||||||
|
aaa: {
|
||||||
|
bbb: [ 123, 456 ]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const values = getValuesAtPath(json, [ 'aaa', 'bbb' ]);
|
||||||
|
expect(values).to.eql([ 123, 456 ]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('multiple values are reachable via path', function () {
|
||||||
|
const json = {
|
||||||
|
aaa: [
|
||||||
|
{
|
||||||
|
bbb: 123
|
||||||
|
},
|
||||||
|
{
|
||||||
|
bbb: 456
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
const values = getValuesAtPath(json, [ 'aaa', 'bbb' ]);
|
||||||
|
expect(values).to.eql([ 123, 456 ]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('multiple values with some that are arrays are reachable via path', function () {
|
||||||
|
const json = {
|
||||||
|
aaa: [
|
||||||
|
{
|
||||||
|
bbb: [ 123, 456 ]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
bbb: 789
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
const values = getValuesAtPath(json, [ 'aaa', 'bbb' ]);
|
||||||
|
expect(values).to.eql([ 123, 456, 789 ]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('nested array mix', function () {
|
||||||
|
const json = {
|
||||||
|
aaa: [
|
||||||
|
{
|
||||||
|
bbb: [
|
||||||
|
{
|
||||||
|
ccc: 123
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ccc: 456
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
bbb: {
|
||||||
|
ccc: 789
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
const values = getValuesAtPath(json, [ 'aaa', 'bbb', 'ccc' ]);
|
||||||
|
expect(values).to.eql([ 123, 456, 789 ]);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('nulls', function () {
|
||||||
|
it('on level 1', function () {
|
||||||
|
const json = {
|
||||||
|
aaa: null
|
||||||
|
};
|
||||||
|
expect(getValuesAtPath(json, [ 'aaa' ])).to.have.length(0);
|
||||||
|
expect(getValuesAtPath(json, [ 'aaa', 'bbb' ])).to.have.length(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('on level 2', function () {
|
||||||
|
const json = {
|
||||||
|
aaa: {
|
||||||
|
bbb: null
|
||||||
|
}
|
||||||
|
};
|
||||||
|
expect(getValuesAtPath(json, [ 'aaa', 'bbb' ])).to.have.length(0);
|
||||||
|
expect(getValuesAtPath(json, [ 'aaa', 'bbb', 'ccc' ])).to.have.length(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('in array', function () {
|
||||||
|
const json = {
|
||||||
|
aaa: [
|
||||||
|
123,
|
||||||
|
null
|
||||||
|
]
|
||||||
|
};
|
||||||
|
expect(getValuesAtPath(json, [ 'aaa' ])).to.eql([ 123 ]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('nested in array', function () {
|
||||||
|
const json = {
|
||||||
|
aaa: [
|
||||||
|
{
|
||||||
|
bbb: 123
|
||||||
|
},
|
||||||
|
{
|
||||||
|
bbb: null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
expect(getValuesAtPath(json, [ 'aaa', 'bbb' ])).to.eql([ 123 ]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,18 +1,21 @@
|
||||||
import _ from 'lodash';
|
|
||||||
import expect from 'expect.js';
|
import expect from 'expect.js';
|
||||||
import ngMock from 'ng_mock';
|
import ngMock from 'ng_mock';
|
||||||
|
import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';
|
||||||
import AggTypesParamTypesBaseProvider from 'ui/agg_types/param_types/base';
|
import AggTypesParamTypesBaseProvider from 'ui/agg_types/param_types/base';
|
||||||
import AggTypesParamTypesFieldProvider from 'ui/agg_types/param_types/field';
|
import AggTypesParamTypesFieldProvider from 'ui/agg_types/param_types/field';
|
||||||
|
|
||||||
describe('Field', function () {
|
describe('Field', function () {
|
||||||
|
|
||||||
let BaseAggParam;
|
let BaseAggParam;
|
||||||
let FieldAggParam;
|
let FieldAggParam;
|
||||||
|
let indexPattern;
|
||||||
|
|
||||||
beforeEach(ngMock.module('kibana'));
|
beforeEach(ngMock.module('kibana'));
|
||||||
// fetch out deps
|
// fetch out deps
|
||||||
beforeEach(ngMock.inject(function (Private) {
|
beforeEach(ngMock.inject(function (Private) {
|
||||||
BaseAggParam = Private(AggTypesParamTypesBaseProvider);
|
BaseAggParam = Private(AggTypesParamTypesBaseProvider);
|
||||||
FieldAggParam = Private(AggTypesParamTypesFieldProvider);
|
FieldAggParam = Private(AggTypesParamTypesFieldProvider);
|
||||||
|
indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
describe('constructor', function () {
|
describe('constructor', function () {
|
||||||
|
@ -24,4 +27,38 @@ describe('Field', function () {
|
||||||
expect(aggParam).to.be.a(BaseAggParam);
|
expect(aggParam).to.be.a(BaseAggParam);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('getFieldOptions', function () {
|
||||||
|
it('should return only aggregatable fields', function () {
|
||||||
|
const aggParam = new FieldAggParam({
|
||||||
|
name: 'field'
|
||||||
|
});
|
||||||
|
|
||||||
|
const fields = aggParam.getFieldOptions({
|
||||||
|
getIndexPattern: () => indexPattern
|
||||||
|
});
|
||||||
|
for (let field of fields) {
|
||||||
|
expect(field.aggregatable).to.be(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return all fields', function () {
|
||||||
|
const aggParam = new FieldAggParam({
|
||||||
|
name: 'field'
|
||||||
|
});
|
||||||
|
|
||||||
|
aggParam.onlyAggregatable = false;
|
||||||
|
|
||||||
|
const fields = aggParam.getFieldOptions({
|
||||||
|
getIndexPattern: () => indexPattern
|
||||||
|
});
|
||||||
|
let nAggregatable = 0;
|
||||||
|
for (let field of fields) {
|
||||||
|
if (field.aggregatable) {
|
||||||
|
nAggregatable++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expect(fields.length - nAggregatable > 0).to.be(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
61
src/ui/public/agg_types/metrics/_get_values_at_path.js
Normal file
61
src/ui/public/agg_types/metrics/_get_values_at_path.js
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
/**
|
||||||
|
* Returns the values at path, regardless if there are arrays on the way.
|
||||||
|
* Therefore, there is no need to specify the offset in an array.
|
||||||
|
* For example, for the path aaa.bbb and a JSON object like:
|
||||||
|
*
|
||||||
|
* {
|
||||||
|
* "aaa": [
|
||||||
|
* {
|
||||||
|
* "bbb": 123
|
||||||
|
* },
|
||||||
|
* {
|
||||||
|
* "bbb": 456
|
||||||
|
* }
|
||||||
|
* ]
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* the values returned are 123 and 456.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @param json the JSON object
|
||||||
|
* @param path the path as an array
|
||||||
|
* @returns an array with all the values reachable from path
|
||||||
|
*/
|
||||||
|
export default function (json, path) {
|
||||||
|
if (!path || !path.length) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const values = [];
|
||||||
|
|
||||||
|
const getValues = function (element, pathIndex) {
|
||||||
|
if (!element) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pathIndex >= path.length) {
|
||||||
|
if (element) {
|
||||||
|
if (element.constructor === Array) {
|
||||||
|
for (let i = 0; i < element.length; i++) {
|
||||||
|
if (element[i]) {
|
||||||
|
values.push(element[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
values.push(element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (element.constructor === Object) {
|
||||||
|
if (element.hasOwnProperty(path[pathIndex])) {
|
||||||
|
getValues(element[path[pathIndex]], pathIndex + 1);
|
||||||
|
}
|
||||||
|
} else if (element.constructor === Array) {
|
||||||
|
for (let childi = 0; childi < element.length; childi++) {
|
||||||
|
getValues(element[childi], pathIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
getValues(json, 0);
|
||||||
|
return values;
|
||||||
|
};
|
|
@ -1,6 +1,7 @@
|
||||||
import { get, has, noop } from 'lodash';
|
import { isObject, get, has, noop } from 'lodash';
|
||||||
import MetricAggTypeProvider from 'ui/agg_types/metrics/metric_agg_type';
|
import MetricAggTypeProvider from 'ui/agg_types/metrics/metric_agg_type';
|
||||||
import topSortEditor from 'ui/agg_types/controls/top_sort.html';
|
import topSortEditor from 'ui/agg_types/controls/top_sort.html';
|
||||||
|
import getValuesAtPath from './_get_values_at_path';
|
||||||
|
|
||||||
export default function AggTypeMetricTopProvider(Private) {
|
export default function AggTypeMetricTopProvider(Private) {
|
||||||
const MetricAggType = Private(MetricAggTypeProvider);
|
const MetricAggType = Private(MetricAggTypeProvider);
|
||||||
|
@ -15,6 +16,7 @@ export default function AggTypeMetricTopProvider(Private) {
|
||||||
params: [
|
params: [
|
||||||
{
|
{
|
||||||
name: 'field',
|
name: 'field',
|
||||||
|
onlyAggregatable: false,
|
||||||
filterFieldTypes: function (vis, value) {
|
filterFieldTypes: function (vis, value) {
|
||||||
if (vis.type.name === 'table' || vis.type.name === 'metric') {
|
if (vis.type.name === 'table' || vis.type.name === 'metric') {
|
||||||
return true;
|
return true;
|
||||||
|
@ -35,7 +37,10 @@ export default function AggTypeMetricTopProvider(Private) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
output.params.docvalue_fields = [ field.name ];
|
if (field.doc_values) {
|
||||||
|
output.params.docvalue_fields = [ field.name ];
|
||||||
|
}
|
||||||
|
output.params._source = field.name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -71,10 +76,25 @@ export default function AggTypeMetricTopProvider(Private) {
|
||||||
],
|
],
|
||||||
getValue(agg, bucket) {
|
getValue(agg, bucket) {
|
||||||
const hits = get(bucket, `${agg.id}.hits.hits`);
|
const hits = get(bucket, `${agg.id}.hits.hits`);
|
||||||
if (!hits || !hits.length || !has(hits[0], 'fields')) {
|
if (!hits || !hits.length) {
|
||||||
return;
|
return null;
|
||||||
|
}
|
||||||
|
const path = agg.params.field.name;
|
||||||
|
let values = getValuesAtPath(hits[0]._source, path.split('.'));
|
||||||
|
|
||||||
|
if (!values.length && hits[0].fields) {
|
||||||
|
// no values found in the source, check the doc_values fields
|
||||||
|
values = hits[0].fields[path] || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (values.length) {
|
||||||
|
case 0:
|
||||||
|
return null;
|
||||||
|
case 1:
|
||||||
|
return isObject(values[0]) ? JSON.stringify(values[0], null, ' ') : values [0];
|
||||||
|
default:
|
||||||
|
return JSON.stringify(values, null, ' ');
|
||||||
}
|
}
|
||||||
return hits[0].fields[agg.params.field.name] && hits[0].fields[agg.params.field.name][0];
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -18,6 +18,7 @@ export default function FieldAggParamFactory(Private, $filter) {
|
||||||
FieldAggParam.prototype.editor = editorHtml;
|
FieldAggParam.prototype.editor = editorHtml;
|
||||||
FieldAggParam.prototype.scriptable = true;
|
FieldAggParam.prototype.scriptable = true;
|
||||||
FieldAggParam.prototype.filterFieldTypes = '*';
|
FieldAggParam.prototype.filterFieldTypes = '*';
|
||||||
|
FieldAggParam.prototype.onlyAggregatable = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called to serialize values for saving an aggConfig object
|
* Called to serialize values for saving an aggConfig object
|
||||||
|
@ -36,7 +37,9 @@ export default function FieldAggParamFactory(Private, $filter) {
|
||||||
const indexPattern = aggConfig.getIndexPattern();
|
const indexPattern = aggConfig.getIndexPattern();
|
||||||
let fields = indexPattern.fields.raw;
|
let fields = indexPattern.fields.raw;
|
||||||
|
|
||||||
fields = fields.filter(f => f.aggregatable);
|
if (this.onlyAggregatable) {
|
||||||
|
fields = fields.filter(f => f.aggregatable);
|
||||||
|
}
|
||||||
|
|
||||||
if (!this.scriptable) {
|
if (!this.scriptable) {
|
||||||
fields = fields.filter(field => !field.scripted);
|
fields = fields.filter(field => !field.scripted);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue