Skip to content

Commit

Permalink
- added logic for trying to get the field value from the source or fr…
Browse files Browse the repository at this point in the history
…om a

  doc_values field.
- added onlyAggregatable option for a field agg param to decide whether
  or not to retain only aggregatable fields.
  • Loading branch information
scampi committed Nov 13, 2016
1 parent 2577298 commit 6a2bc01
Show file tree
Hide file tree
Showing 5 changed files with 278 additions and 7 deletions.
150 changes: 150 additions & 0 deletions src/ui/public/agg_types/__tests__/metrics/get_values_at_path.js
Original file line number Diff line number Diff line change
@@ -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 ]);
});
});
});
39 changes: 38 additions & 1 deletion src/ui/public/agg_types/__tests__/param_types/_field.js
Original file line number Diff line number Diff line change
@@ -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 () {
Expand All @@ -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 changes: 61 additions & 0 deletions src/ui/public/agg_types/metrics/_get_values_at_path.js
Original file line number Diff line number Diff line change
@@ -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;
};
30 changes: 25 additions & 5 deletions src/ui/public/agg_types/metrics/top_hit.js
Original file line number Diff line number Diff line change
@@ -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);
Expand All @@ -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;
Expand All @@ -35,7 +37,10 @@ export default function AggTypeMetricTopProvider(Private) {
}
};
} else {
output.params.docvalue_fields = [ field.name ];
if (field.doc_values) {
output.params.docvalue_fields = [ field.name ];
}
output.params._source = field.name;
}
}
},
Expand Down Expand Up @@ -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];
}
});
};
5 changes: 4 additions & 1 deletion src/ui/public/agg_types/param_types/field.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -36,7 +37,9 @@ export default function FieldAggParamFactory(Private, $filter) {
const indexPattern = aggConfig.getIndexPattern();
let fields = indexPattern.fields.raw;

fields = fields.filter(f => f.aggregatable);
if (this.onlyAggregatable) {
fields = fields.filter(f => f.aggregatable);
}

if (!this.scriptable) {
fields = fields.filter(field => !field.scripted);
Expand Down

0 comments on commit 6a2bc01

Please sign in to comment.