Ensure conflicted fields can be searchable and/or aggregatable (#13070)

* Ensure that conflict fields can be searchable and/or aggregatable in the UI

* Use `some` instead of `reduce`

* Revert UI changes

* Attempt to convert multiple ES types to kibana types, and if they all resolve to the same kibana type, there is no conflict

* Add comma back

* Cleaner code

* Add tests

* Update failing test to handle searchable and aggregatable properly

* Add functional test to ensure similar ES types are properly merged

* Update tests

* Revert shard size
This commit is contained in:
Chris Roberson 2017-07-28 10:45:43 -04:00 committed by GitHub
parent d14da34032
commit 389115cad0
6 changed files with 195 additions and 38 deletions

View file

@ -19,7 +19,7 @@ describe('index_patterns/field_capabilities/field_caps_response', () => {
describe('conflicts', () => {
it('returns a field for each in response, no filtering', () => {
const fields = readFieldCapsResponse(esResponse);
expect(fields).to.have.length(13);
expect(fields).to.have.length(19);
});
it('includes only name, type, searchable, aggregatable, readFromDocValues, and maybe conflictDescriptions of each field', () => {
@ -65,8 +65,8 @@ describe('index_patterns/field_capabilities/field_caps_response', () => {
{
name: 'success',
type: 'conflict',
searchable: false,
aggregatable: false,
searchable: true,
aggregatable: true,
readFromDocValues: false,
conflictDescriptions: {
boolean: [
@ -79,6 +79,30 @@ describe('index_patterns/field_capabilities/field_caps_response', () => {
}
]);
});
it('does not return conflicted fields if the types are resolvable to the same kibana type', () => {
const fields = readFieldCapsResponse(esResponse);
const resolvableToString = fields.find(f => f.name === 'resolvable_to_string');
const resolvableToNumber = fields.find(f => f.name === 'resolvable_to_number');
expect(resolvableToString.type).to.be('string');
expect(resolvableToNumber.type).to.be('number');
});
it('returns aggregatable if at least one field is aggregatable', () => {
const fields = readFieldCapsResponse(esResponse);
const mixAggregatable = fields.find(f => f.name === 'mix_aggregatable');
const mixAggregatableOther = fields.find(f => f.name === 'mix_aggregatable_other');
expect(mixAggregatable.aggregatable).to.be(true);
expect(mixAggregatableOther.aggregatable).to.be(true);
});
it('returns searchable if at least one field is searchable', () => {
const fields = readFieldCapsResponse(esResponse);
const mixSearchable = fields.find(f => f.name === 'mix_searchable');
const mixSearchableOther = fields.find(f => f.name === 'mix_searchable_other');
expect(mixSearchable.searchable).to.be(true);
expect(mixSearchableOther.searchable).to.be(true);
});
});
});
});

View file

@ -101,6 +101,98 @@
"searchable": false,
"aggregatable": true
}
},
"resolvable_to_string": {
"text": {
"type": "text",
"searchable": true,
"aggregatable": true,
"indices": [
"index1"
]
},
"keyword": {
"type": "keyword",
"searchable": true,
"aggregatable": true,
"indices": [
"index2"
]
}
},
"resolvable_to_number": {
"integer": {
"type": "integer",
"searchable": true,
"aggregatable": true,
"indices": [
"index1"
]
},
"long": {
"type": "long",
"searchable": true,
"aggregatable": true,
"indices": [
"index2"
]
}
},
"mix_searchable": {
"text": {
"type": "text",
"searchable": false,
"aggregatable": true,
"indices": [
"index1"
]
},
"keyword": {
"type": "keyword",
"searchable": true,
"aggregatable": true,
"indices": [
"index2"
]
}
},
"mix_aggregatable": {
"text": {
"type": "text",
"searchable": true,
"aggregatable": false,
"indices": [
"index1"
]
},
"keyword": {
"type": "keyword",
"searchable": true,
"aggregatable": true,
"indices": [
"index2"
]
}
},
"mix_searchable_other": {
"int": {
"type": "integer",
"searchable": false,
"aggregatable": false,
"non_searchable_indices": [
"index1"
]
}
},
"mix_aggregatable_other": {
"int": {
"type": "integer",
"searchable": false,
"aggregatable": false,
"non_aggregatable_indices": [
"index1"
]
}
}
}
}

View file

@ -1,3 +1,4 @@
import { uniq } from 'lodash';
import { castEsToKbnFieldTypeName } from '../../../../../utils';
import { shouldReadFieldFromDocValues } from './should_read_field_from_doc_values';
@ -63,12 +64,27 @@ export function readFieldCapsResponse(fieldCapsResponse) {
const capsByType = capsByNameThenType[fieldName];
const types = Object.keys(capsByType);
if (types.length > 1) {
// If a single type is marked as searchable or aggregatable, all the types are searchable or aggregatable
const isSearchable = types.some(type => {
return !!capsByType[type].searchable ||
(!!capsByType[type].non_searchable_indices && capsByType[type].non_searchable_indices.length > 0);
});
const isAggregatable = types.some(type => {
return !!capsByType[type].aggregatable ||
(!!capsByType[type].non_aggregatable_indices && capsByType[type].non_aggregatable_indices.length > 0);
});
// If there are multiple types but they all resolve to the same kibana type
// ignore the conflict and carry on (my wayward son)
const uniqueKibanaTypes = uniq(types.map(castEsToKbnFieldTypeName));
if (uniqueKibanaTypes.length > 1) {
return {
name: fieldName,
type: 'conflict',
searchable: false,
aggregatable: false,
searchable: isSearchable,
aggregatable: isAggregatable,
readFromDocValues: false,
conflictDescriptions: types.reduce((acc, esType) => ({
...acc,
@ -78,13 +94,12 @@ export function readFieldCapsResponse(fieldCapsResponse) {
}
const esType = types[0];
const caps = capsByType[esType];
return {
name: fieldName,
type: castEsToKbnFieldTypeName(esType),
searchable: caps.searchable,
aggregatable: caps.aggregatable,
readFromDocValues: shouldReadFieldFromDocValues(caps.aggregatable, esType),
searchable: isSearchable,
aggregatable: isAggregatable,
readFromDocValues: shouldReadFieldFromDocValues(isAggregatable, esType),
};
});
}

View file

@ -23,11 +23,25 @@ export default function ({ getService }) {
searchable: true,
readFromDocValues: true,
},
{
name: 'number_conflict',
type: 'number',
aggregatable: true,
searchable: true,
readFromDocValues: true,
},
{
name: 'string_conflict',
type: 'string',
aggregatable: true,
searchable: true,
readFromDocValues: false,
},
{
name: 'success',
type: 'conflict',
aggregatable: false,
searchable: false,
aggregatable: true,
searchable: true,
readFromDocValues: false,
conflictDescriptions: {
boolean: [

View file

@ -1,28 +1,3 @@
{
"type": "index",
"value": {
"index": "logs-2017.01.01",
"settings": {
"index": {
"number_of_shards": "1",
"number_of_replicas": "1"
}
},
"mappings": {
"type": {
"properties": {
"@timestamp": {
"type": "date"
},
"success": {
"type": "keyword"
}
}
}
}
}
}
{
"type": "index",
"value": {
@ -39,6 +14,12 @@
"@timestamp": {
"type": "date"
},
"number_conflict": {
"type": "float"
},
"string_conflict": {
"type": "keyword"
},
"success": {
"type": "boolean"
}
@ -46,4 +27,35 @@
}
}
}
}
}
{
"type": "index",
"value": {
"index": "logs-2017.01.01",
"settings": {
"index": {
"number_of_shards": "1",
"number_of_replicas": "1"
}
},
"mappings": {
"type": {
"properties": {
"@timestamp": {
"type": "date"
},
"number_conflict": {
"type": "integer"
},
"string_conflict": {
"type": "text"
},
"success": {
"type": "keyword"
}
}
}
}
}
}