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 ngMock from 'ng_mock';
|
||||
import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';
|
||||
import AggTypesParamTypesBaseProvider from 'ui/agg_types/param_types/base';
|
||||
import AggTypesParamTypesFieldProvider from 'ui/agg_types/param_types/field';
|
||||
|
||||
describe('Field', function () {
|
||||
|
||||
let BaseAggParam;
|
||||
let FieldAggParam;
|
||||
let indexPattern;
|
||||
|
||||
beforeEach(ngMock.module('kibana'));
|
||||
// fetch out deps
|
||||
beforeEach(ngMock.inject(function (Private) {
|
||||
BaseAggParam = Private(AggTypesParamTypesBaseProvider);
|
||||
FieldAggParam = Private(AggTypesParamTypesFieldProvider);
|
||||
indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider);
|
||||
}));
|
||||
|
||||
describe('constructor', function () {
|
||||
|
@ -24,4 +27,38 @@ describe('Field', function () {
|
|||
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 topSortEditor from 'ui/agg_types/controls/top_sort.html';
|
||||
import getValuesAtPath from './_get_values_at_path';
|
||||
|
||||
export default function AggTypeMetricTopProvider(Private) {
|
||||
const MetricAggType = Private(MetricAggTypeProvider);
|
||||
|
@ -15,6 +16,7 @@ export default function AggTypeMetricTopProvider(Private) {
|
|||
params: [
|
||||
{
|
||||
name: 'field',
|
||||
onlyAggregatable: false,
|
||||
filterFieldTypes: function (vis, value) {
|
||||
if (vis.type.name === 'table' || vis.type.name === 'metric') {
|
||||
return true;
|
||||
|
@ -35,8 +37,11 @@ export default function AggTypeMetricTopProvider(Private) {
|
|||
}
|
||||
};
|
||||
} else {
|
||||
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) {
|
||||
const hits = get(bucket, `${agg.id}.hits.hits`);
|
||||
if (!hits || !hits.length || !has(hits[0], 'fields')) {
|
||||
return;
|
||||
if (!hits || !hits.length) {
|
||||
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.scriptable = true;
|
||||
FieldAggParam.prototype.filterFieldTypes = '*';
|
||||
FieldAggParam.prototype.onlyAggregatable = true;
|
||||
|
||||
/**
|
||||
* Called to serialize values for saving an aggConfig object
|
||||
|
@ -36,7 +37,9 @@ export default function FieldAggParamFactory(Private, $filter) {
|
|||
const indexPattern = aggConfig.getIndexPattern();
|
||||
let fields = indexPattern.fields.raw;
|
||||
|
||||
if (this.onlyAggregatable) {
|
||||
fields = fields.filter(f => f.aggregatable);
|
||||
}
|
||||
|
||||
if (!this.scriptable) {
|
||||
fields = fields.filter(field => !field.scripted);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue