-
Notifications
You must be signed in to change notification settings - Fork 8.4k
Unify getting fields for aggs, and filter scripted fields for significant terms agg #8734
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
e49ba04
a9c139a
0757c45
2dbb462
95d704c
0e11c22
e71b0f2
6157275
fbf884a
0018786
7205993
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,36 +1,65 @@ | ||
function stubbedLogstashFields() { | ||
let sourceData = [ | ||
{ name: 'bytes', type: 'number', indexed: true, analyzed: true, sortable: true, filterable: true, count: 10 }, | ||
{ name: 'ssl', type: 'boolean', indexed: true, analyzed: true, sortable: true, filterable: true, count: 20 }, | ||
{ name: '@timestamp', type: 'date', indexed: true, analyzed: true, sortable: true, filterable: true, count: 30 }, | ||
{ name: 'time', type: 'date', indexed: true, analyzed: true, sortable: true, filterable: true, count: 30 }, | ||
{ name: '@tags', type: 'string', indexed: true, analyzed: true, sortable: true, filterable: true }, | ||
{ name: 'utc_time', type: 'date', indexed: true, analyzed: true, sortable: true, filterable: true }, | ||
{ name: 'phpmemory', type: 'number', indexed: true, analyzed: true, sortable: true, filterable: true }, | ||
{ name: 'ip', type: 'ip', indexed: true, analyzed: true, sortable: true, filterable: true }, | ||
{ name: 'request_body', type: 'attachment', indexed: true, analyzed: true, sortable: false, filterable: true }, | ||
{ name: 'point', type: 'geo_point', indexed: true, analyzed: true, sortable: false, filterable: false }, | ||
{ name: 'area', type: 'geo_shape', indexed: true, analyzed: true, sortable: true, filterable: false }, | ||
{ name: 'hashed', type: 'murmur3', indexed: true, analyzed: true, sortable: false, filterable: false }, | ||
{ name: 'geo.coordinates', type: 'geo_point', indexed: true, analyzed: true, sortable: false, filterable: true }, | ||
{ name: 'extension', type: 'string', indexed: true, analyzed: true, sortable: true, filterable: true }, | ||
{ name: 'machine.os', type: 'string', indexed: true, analyzed: true, sortable: true, filterable: true }, | ||
{ name: 'geo.src', type: 'string', indexed: true, analyzed: true, sortable: true, filterable: true }, | ||
{ name: '_type', type: 'string', indexed: false, analyzed: true, sortable: true, filterable: true }, | ||
{ name: '_id', type: 'string', indexed: false, analyzed: false, sortable: false, filterable: true}, | ||
{ name: '_source', type: 'string', indexed: false, analyzed: false, sortable: false, filterable: false}, | ||
{ name: 'custom_user_field', type: 'conflict', indexed: false, analyzed: false, sortable: false, filterable: true }, | ||
{ name: 'script string', type: 'string', scripted: true, script: '\'i am a string\'', lang: 'expression' }, | ||
{ name: 'script number', type: 'number', scripted: true, script: '1234', lang: 'expression' }, | ||
{ name: 'script date', type: 'date', scripted: true, script: '1234', lang: 'painless' }, | ||
{ name: 'script murmur3', type: 'murmur3', scripted: true, script: '1234', lang: 'expression'}, | ||
].map(function (field) { | ||
field.count = field.count || 0; | ||
field.scripted = field.scripted || false; | ||
return field; | ||
}); | ||
return [ | ||
// |indexed | ||
// | |analyzed | ||
// | | |aggregatable | ||
// | | | |searchable | ||
// name type | | | | |metadata | ||
['bytes', 'number', true, true, true, true, { count: 10 } ], | ||
['ssl', 'boolean', true, true, true, true, { count: 20 } ], | ||
['@timestamp', 'date', true, true, true, true, { count: 30 } ], | ||
['time', 'date', true, true, true, true, { count: 30 } ], | ||
['@tags', 'string', true, true, true, true ], | ||
['utc_time', 'date', true, true, true, true ], | ||
['phpmemory', 'number', true, true, true, true ], | ||
['ip', 'ip', true, true, true, true ], | ||
['request_body', 'attachment', true, true, true, true ], | ||
['point', 'geo_point', true, true, true, true ], | ||
['area', 'geo_shape', true, true, true, true ], | ||
['hashed', 'murmur3', true, true, false, true ], | ||
['geo.coordinates', 'geo_point', true, true, true, true ], | ||
['extension', 'string', true, true, true, true ], | ||
['machine.os', 'string', true, true, true, true ], | ||
['geo.src', 'string', true, true, true, true ], | ||
['_id', 'string', false, false, true, true ], | ||
['_type', 'string', false, false, true, true ], | ||
['_source', 'string', false, false, true, true ], | ||
['custom_user_field', 'conflict', false, false, true, true ], | ||
['script string', 'string', false, false, true, false, { script: '\'i am a string\'' } ], | ||
['script number', 'number', false, false, true, false, { script: '1234' } ], | ||
['script date', 'date', false, false, true, false, { script: '1234', lang: 'painless' } ], | ||
['script murmur3', 'murmur3', false, false, true, false, { script: '1234' } ], | ||
].map(function (row) { | ||
const [ | ||
name, | ||
type, | ||
indexed, | ||
analyzed, | ||
aggregatable, | ||
searchable, | ||
metadata = {} | ||
] = row; | ||
|
||
const { | ||
count = 0, | ||
script, | ||
lang = script ? 'expression' : undefined, | ||
scripted = !!script, | ||
} = metadata; | ||
|
||
return sourceData; | ||
return { | ||
name, | ||
type, | ||
indexed, | ||
analyzed, | ||
aggregatable, | ||
searchable, | ||
count, | ||
script, | ||
lang, | ||
scripted, | ||
}; | ||
}); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think I am missing what changed here. Is this a rewrite of the original? I guess 'expression' is now no longer hardcoded. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The primary driver was to add aggregatable and searchable, which overflowed the rows when added in the previous style. |
||
} | ||
|
||
export default stubbedLogstashFields; |
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
|
@@ -53,7 +53,7 @@ describe('ResponseWriter class', function () { | |||
let aggs = [ | ||||
{ type: 'date_histogram', schema: 'segment', params: { field: '@timestamp' } }, | ||||
{ type: 'terms', schema: 'segment', params: { field: 'extension' } }, | ||||
{ type: 'avg', schema: 'metric', params: { field: '@timestamp' } } | ||||
{ type: 'avg', schema: 'metric', params: { field: 'bytes' } } | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Were these tests suddenly blowing up for some reason? Why There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ugh, I guess I miscalculated this and it was just the type that was an issue (dates in the |
||||
]; | ||||
|
||||
getColumns.returns(aggs.map(function (agg) { | ||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,17 +2,21 @@ import { SavedObjectNotFound } from 'ui/errors'; | |
import _ from 'lodash'; | ||
import editorHtml from 'ui/agg_types/controls/field.html'; | ||
import AggTypesParamTypesBaseProvider from 'ui/agg_types/param_types/base'; | ||
export default function FieldAggParamFactory(Private) { | ||
import 'ui/filters/field_type'; | ||
import IndexedArray from 'ui/indexed_array'; | ||
import Notifier from 'ui/notify/notifier'; | ||
|
||
export default function FieldAggParamFactory(Private, $filter) { | ||
let BaseAggParam = Private(AggTypesParamTypesBaseProvider); | ||
const notifier = new Notifier(); | ||
|
||
_.class(FieldAggParam).inherits(BaseAggParam); | ||
function FieldAggParam(config) { | ||
FieldAggParam.Super.call(this, config); | ||
} | ||
|
||
FieldAggParam.prototype.editor = editorHtml; | ||
FieldAggParam.prototype.scriptable = false; | ||
FieldAggParam.prototype.scriptable = true; | ||
FieldAggParam.prototype.filterFieldTypes = '*'; | ||
|
||
/** | ||
|
@@ -25,6 +29,32 @@ export default function FieldAggParamFactory(Private) { | |
return field.name; | ||
}; | ||
|
||
/** | ||
* Get the options for this field from the indexPattern | ||
*/ | ||
FieldAggParam.prototype.getFieldOptions = function (aggConfig) { | ||
const indexPattern = aggConfig.getIndexPattern(); | ||
let fields = indexPattern.fields.raw; | ||
|
||
fields = fields.filter(f => f.aggregatable); | ||
|
||
if (!this.scriptable) { | ||
fields = fields.filter(field => !field.scripted); | ||
} | ||
|
||
if (this.filterFieldTypes) { | ||
fields = $filter('fieldType')(fields, this.filterFieldTypes); | ||
fields = $filter('orderBy')(fields, ['type', 'name']); | ||
} | ||
|
||
|
||
return new IndexedArray({ | ||
index: ['name'], | ||
group: ['type'], | ||
initialSet: fields | ||
}); | ||
}; | ||
|
||
/** | ||
* Called to read values from a database record into the | ||
* aggConfig object | ||
|
@@ -33,13 +63,18 @@ export default function FieldAggParamFactory(Private) { | |
* @return {field} | ||
*/ | ||
FieldAggParam.prototype.deserialize = function (fieldName, aggConfig) { | ||
let field = aggConfig.vis.indexPattern.fields.byName[fieldName]; | ||
const field = aggConfig.getIndexPattern().fields.byName[fieldName]; | ||
|
||
if (!field) { | ||
throw new SavedObjectNotFound('index-pattern-field', fieldName); | ||
} | ||
|
||
return field; | ||
const validField = this.getFieldOptions(aggConfig).byName[fieldName]; | ||
if (!validField) { | ||
notifier.error(`Saved "field" parameter is now invalid. Please select a new field.`); | ||
} | ||
|
||
return validField; | ||
}; | ||
|
||
/** | ||
|
@@ -56,7 +91,7 @@ export default function FieldAggParamFactory(Private) { | |
let field = aggConfig.getField(); | ||
|
||
if (!field) { | ||
throw new Error(`"${aggConfig.makeLabel()}" requires a field`); | ||
throw new TypeError('"field" is a required parameter'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is this just an unrelated improvement? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Somewhat, in a few situations I saw this cause call-stack overflows because certain aggs call _Edit:_ This is more likely to happen now that this branch has the field validation logic too. Since we are changing the field options of the significant terms agg, if someone had a saved visualization with a scripted field for a signification terms agg this would have caused a call stack overflow There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fair enough |
||
} | ||
|
||
if (field.scripted) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,8 +12,11 @@ import IndexPatternsMapperProvider from 'ui/index_patterns/_mapper'; | |
import UtilsMappingSetupProvider from 'ui/utils/mapping_setup'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you explain the reason for the changes in this file? I don't understand how they connect to the agg changes. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Now that the stubbed logstash index pattern fields have aggregatable and searchable properties, the call to refresh the fields is not happening, which means that the stubbed version of |
||
import IndexPatternsIntervalsProvider from 'ui/index_patterns/_intervals'; | ||
import IndexPatternsIndexPatternProvider from 'ui/index_patterns/_index_pattern'; | ||
import NoDigestPromises from 'test_utils/no_digest_promises'; | ||
|
||
describe('index pattern', function () { | ||
NoDigestPromises.activateForSuite(); | ||
|
||
let IndexPattern; | ||
let mapper; | ||
let mappingSetup; | ||
|
@@ -55,7 +58,7 @@ describe('index pattern', function () { | |
|
||
// stub calculateIndices | ||
calculateIndices = sinon.spy(function () { | ||
return $injector.get('Promise').resolve([ | ||
return Promise.resolve([ | ||
{ index: 'foo', max: Infinity, min: -Infinity }, | ||
{ index: 'bar', max: Infinity, min: -Infinity } | ||
]); | ||
|
@@ -150,7 +153,6 @@ describe('index pattern', function () { | |
|
||
describe('refresh fields', function () { | ||
// override the default indexPattern, with a truncated field list | ||
require('test_utils/no_digest_promises').activateForSuite(); | ||
const indexPatternId = 'test-pattern'; | ||
let indexPattern; | ||
let fieldLength; | ||
|
@@ -321,7 +323,6 @@ describe('index pattern', function () { | |
}); | ||
|
||
describe('#toDetailedIndexList', function () { | ||
require('test_utils/no_digest_promises').activateForSuite(); | ||
context('when index pattern is an interval', function () { | ||
let interval; | ||
beforeEach(function () { | ||
|
@@ -400,7 +401,6 @@ describe('index pattern', function () { | |
|
||
describe('#toIndexList', function () { | ||
context('when index pattern is an interval', function () { | ||
require('test_utils/no_digest_promises').activateForSuite(); | ||
|
||
let interval; | ||
beforeEach(function () { | ||
|
@@ -431,7 +431,6 @@ describe('index pattern', function () { | |
}); | ||
|
||
context('when index pattern is a time-base wildcard', function () { | ||
require('test_utils/no_digest_promises').activateForSuite(); | ||
beforeEach(function () { | ||
sinon.stub(indexPattern, 'getInterval').returns(false); | ||
sinon.stub(indexPattern, 'hasTimeField').returns(true); | ||
|
@@ -453,7 +452,6 @@ describe('index pattern', function () { | |
}); | ||
|
||
context('when index pattern is a time-base wildcard that is configured not to expand', function () { | ||
require('test_utils/no_digest_promises').activateForSuite(); | ||
beforeEach(function () { | ||
sinon.stub(indexPattern, 'getInterval').returns(false); | ||
sinon.stub(indexPattern, 'hasTimeField').returns(true); | ||
|
@@ -472,13 +470,8 @@ describe('index pattern', function () { | |
sinon.stub(indexPattern, 'getInterval').returns(false); | ||
}); | ||
|
||
it('is fulfilled by id', function () { | ||
let indexList; | ||
indexPattern.toIndexList().then(function (val) { | ||
indexList = val; | ||
}); | ||
$rootScope.$apply(); | ||
|
||
it('is fulfilled by id', async function () { | ||
let indexList = await indexPattern.toIndexList(); | ||
expect(indexList).to.equal(indexPattern.id); | ||
}); | ||
}); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -481,12 +481,12 @@ describe('AggConfig', function () { | |
{ | ||
type: 'avg', | ||
schema: 'metric', | ||
params: { field: 'ssl' } | ||
params: { field: 'bytes' } | ||
} | ||
] | ||
}); | ||
|
||
let field = indexPattern.fields.byName.ssl; | ||
let field = indexPattern.fields.byName.bytes; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ssl -> bytes makes sense, but why timestamp -> time? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That was incorrect, I misread the field list and miscategorized the issue. Removed the changes. |
||
expect(vis.aggs[0].fieldFormatter('html')).to.be(field.format.getConverterFor('html')); | ||
}); | ||
}); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Previously this required access to the field param, but now the aggConfig knows how to get it's own field options with aggConfig.getFieldOptions()