mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
* Change AggConfig(s) to ES6 syntax * Use arrow syntax * More ES6 * Fix cyclic import issue * Use more ES6 syntax * Add more ES6 code
This commit is contained in:
parent
1205ec5d2c
commit
5c34c4f915
3 changed files with 439 additions and 461 deletions
|
@ -35,14 +35,6 @@ describe('AggConfigs', function () {
|
|||
|
||||
beforeEach(ngMock.module('kibana'));
|
||||
beforeEach(ngMock.inject(function (Private) {
|
||||
// replace the AggConfig module with a spy
|
||||
|
||||
const spy = sinon.spy(AggConfig);
|
||||
Object.defineProperty(spy, 'aggTypes', {
|
||||
get: function () { return AggConfig.aggTypes; },
|
||||
set: function (val) { AggConfig.aggTypes = val; }
|
||||
});
|
||||
|
||||
// load main deps
|
||||
Vis = Private(VisProvider);
|
||||
indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider);
|
||||
|
|
|
@ -26,334 +26,332 @@
|
|||
|
||||
import _ from 'lodash';
|
||||
import { fieldFormats } from '../registry/field_formats';
|
||||
import { aggTypes } from '../agg_types/index';
|
||||
|
||||
export function AggConfig(vis, opts) {
|
||||
const self = this;
|
||||
class AggConfig {
|
||||
|
||||
self.id = String(opts.id || AggConfig.nextId(vis.aggs));
|
||||
self.vis = vis;
|
||||
self._opts = opts = (opts || {});
|
||||
self.enabled = typeof opts.enabled === 'boolean' ? opts.enabled : true;
|
||||
/**
|
||||
* Ensure that all of the objects in the list have ids, the objects
|
||||
* and list are modified by reference.
|
||||
*
|
||||
* @param {array[object]} list - a list of objects, objects can be anything really
|
||||
* @return {array} - the list that was passed in
|
||||
*/
|
||||
static ensureIds(list) {
|
||||
const have = [];
|
||||
const haveNot = [];
|
||||
list.forEach(function (obj) {
|
||||
(obj.id ? have : haveNot).push(obj);
|
||||
});
|
||||
|
||||
// start with empty params so that checks in type/schema setters don't freak
|
||||
// because self.params is undefined
|
||||
self.params = {};
|
||||
let nextId = AggConfig.nextId(have);
|
||||
haveNot.forEach(function (obj) {
|
||||
obj.id = String(nextId++);
|
||||
});
|
||||
|
||||
// setters
|
||||
self.type = opts.type;
|
||||
self.schema = opts.schema;
|
||||
return list;
|
||||
}
|
||||
|
||||
// set the params to the values from opts, or just to the defaults
|
||||
self.setParams(opts.params || {});
|
||||
}
|
||||
/**
|
||||
* Calculate the next id based on the ids in this list
|
||||
*
|
||||
* @return {array} list - a list of objects with id properties
|
||||
*/
|
||||
static nextId(list) {
|
||||
return 1 + list.reduce(function (max, obj) {
|
||||
return Math.max(max, +obj.id || 0);
|
||||
}, 0);
|
||||
}
|
||||
|
||||
AggConfig.aggTypes = aggTypes;
|
||||
/**
|
||||
* Ensure that all of the objects in the list have ids, the objects
|
||||
* and list are modified by reference.
|
||||
*
|
||||
* @param {array[object]} list - a list of objects, objects can be anything really
|
||||
* @return {array} - the list that was passed in
|
||||
*/
|
||||
AggConfig.ensureIds = function (list) {
|
||||
const have = [];
|
||||
const haveNot = [];
|
||||
list.forEach(function (obj) {
|
||||
(obj.id ? have : haveNot).push(obj);
|
||||
});
|
||||
constructor(vis, opts = {}) {
|
||||
this.id = String(opts.id || AggConfig.nextId(vis.aggs));
|
||||
this.vis = vis;
|
||||
this._opts = opts;
|
||||
this.enabled = typeof opts.enabled === 'boolean' ? opts.enabled : true;
|
||||
|
||||
let nextId = AggConfig.nextId(have);
|
||||
haveNot.forEach(function (obj) {
|
||||
obj.id = String(nextId++);
|
||||
});
|
||||
// start with empty params so that checks in type/schema setters don't freak
|
||||
// because this.params is undefined
|
||||
this.params = {};
|
||||
|
||||
return list;
|
||||
};
|
||||
// setters
|
||||
this.type = opts.type;
|
||||
this.schema = opts.schema;
|
||||
|
||||
/**
|
||||
* Calculate the next id based on the ids in this list
|
||||
*
|
||||
* @return {array} list - a list of objects with id properties
|
||||
*/
|
||||
AggConfig.nextId = function (list) {
|
||||
return 1 + list.reduce(function (max, obj) {
|
||||
return Math.max(max, +obj.id || 0);
|
||||
}, 0);
|
||||
};
|
||||
// set the params to the values from opts, or just to the defaults
|
||||
this.setParams(opts.params || {});
|
||||
}
|
||||
|
||||
Object.defineProperties(AggConfig.prototype, {
|
||||
type: {
|
||||
get: function () {
|
||||
return this.__type;
|
||||
},
|
||||
set: function (type) {
|
||||
if (this.__typeDecorations) {
|
||||
_.forOwn(this.__typeDecorations, function (prop, name) {
|
||||
delete this[name];
|
||||
}, this);
|
||||
/**
|
||||
* Write the current values to this.params, filling in the defaults as we go
|
||||
*
|
||||
* @param {object} [from] - optional object to read values from,
|
||||
* used when initializing
|
||||
* @return {undefined}
|
||||
*/
|
||||
setParams(from) {
|
||||
from = from || this.params || {};
|
||||
const to = this.params = {};
|
||||
|
||||
this.getAggParams().forEach(aggParam => {
|
||||
let val = from[aggParam.name];
|
||||
|
||||
if (val == null) {
|
||||
if (aggParam.default == null) return;
|
||||
|
||||
if (!_.isFunction(aggParam.default)) {
|
||||
val = aggParam.default;
|
||||
} else {
|
||||
val = aggParam.default(this);
|
||||
if (val == null) return;
|
||||
}
|
||||
}
|
||||
|
||||
if (_.isString(type)) {
|
||||
type = AggConfig.aggTypes.byName[type];
|
||||
if (aggParam.deserialize) {
|
||||
const isTyped = _.isFunction(aggParam.type);
|
||||
|
||||
const isType = isTyped && (val instanceof aggParam.type);
|
||||
const isObject = !isTyped && _.isObject(val);
|
||||
const isDeserialized = (isType || isObject);
|
||||
|
||||
if (!isDeserialized) {
|
||||
val = aggParam.deserialize(val, this);
|
||||
}
|
||||
|
||||
to[aggParam.name] = val;
|
||||
return;
|
||||
}
|
||||
|
||||
if (type && _.isFunction(type.decorateAggConfig)) {
|
||||
this.__typeDecorations = type.decorateAggConfig();
|
||||
Object.defineProperties(this, this.__typeDecorations);
|
||||
to[aggParam.name] = _.cloneDeep(val);
|
||||
});
|
||||
}
|
||||
|
||||
write() {
|
||||
return this.type.params.write(this);
|
||||
}
|
||||
|
||||
isFilterable() {
|
||||
return _.isFunction(this.type.createFilter);
|
||||
}
|
||||
|
||||
createFilter(key, params = {}) {
|
||||
if (!this.isFilterable()) {
|
||||
throw new TypeError(`The "${this.type.title}" aggregation does not support filtering.`);
|
||||
}
|
||||
|
||||
const field = this.getField();
|
||||
const label = this.getFieldDisplayName();
|
||||
if (field && !field.filterable) {
|
||||
let message = `The "${label}" field can not be used for filtering.`;
|
||||
if (field.scripted) {
|
||||
message = `The "${label}" field is scripted and can not be used for filtering.`;
|
||||
}
|
||||
throw new TypeError(message);
|
||||
}
|
||||
|
||||
this.__type = type;
|
||||
return this.type.createFilter(this, key, params);
|
||||
}
|
||||
|
||||
// clear out the previous params except for a few special ones
|
||||
this.setParams({
|
||||
// split row/columns is "outside" of the agg, so don't reset it
|
||||
row: this.params.row,
|
||||
/**
|
||||
* Hook for pre-flight logic, see AggType#onSearchRequestStart
|
||||
* @param {Courier.SearchSource} searchSource
|
||||
* @param {Courier.SearchRequest} searchRequest
|
||||
* @return {Promise<undefined>}
|
||||
*/
|
||||
onSearchRequestStart(searchSource, searchRequest) {
|
||||
if (!this.type) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
// almost every agg has fields, so we try to persist that when type changes
|
||||
field: _.get(this.getFieldOptions(), ['byName', this.getField()])
|
||||
return Promise.all(
|
||||
this.type.params.map(param => param.modifyAggConfigOnSearchRequestStart(this, searchSource, searchRequest))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert this aggConfig to its dsl syntax.
|
||||
*
|
||||
* Adds params and adhoc subaggs to a pojo, then returns it
|
||||
*
|
||||
* @param {AggConfig} aggConfig - the config object to convert
|
||||
* @return {void|Object} - if the config has a dsl representation, it is
|
||||
* returned, else undefined is returned
|
||||
*/
|
||||
toDsl() {
|
||||
if (this.type.hasNoDsl) return;
|
||||
const output = this.write();
|
||||
|
||||
const configDsl = {};
|
||||
configDsl[this.type.dslName || this.type.name] = output.params;
|
||||
|
||||
// if the config requires subAggs, write them to the dsl as well
|
||||
if (this.subAggs && !output.subAggs) output.subAggs = this.subAggs;
|
||||
if (output.subAggs) {
|
||||
const subDslLvl = configDsl.aggs || (configDsl.aggs = {});
|
||||
output.subAggs.forEach(function nestAdhocSubAggs(subAggConfig) {
|
||||
subDslLvl[subAggConfig.id] = subAggConfig.toDsl();
|
||||
});
|
||||
}
|
||||
},
|
||||
schema: {
|
||||
get: function () {
|
||||
return this.__schema;
|
||||
},
|
||||
set: function (schema) {
|
||||
if (_.isString(schema)) {
|
||||
schema = this.vis.type.schemas.all.byName[schema];
|
||||
}
|
||||
|
||||
this.__schema = schema;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Write the current values to this.params, filling in the defaults as we go
|
||||
*
|
||||
* @param {object} [from] - optional object to read values from,
|
||||
* used when initializing
|
||||
* @return {undefined}
|
||||
*/
|
||||
AggConfig.prototype.setParams = function (from) {
|
||||
const self = this;
|
||||
from = from || self.params || {};
|
||||
const to = self.params = {};
|
||||
|
||||
self.getAggParams().forEach(function (aggParam) {
|
||||
let val = from[aggParam.name];
|
||||
|
||||
if (val == null) {
|
||||
if (aggParam.default == null) return;
|
||||
|
||||
if (!_.isFunction(aggParam.default)) {
|
||||
val = aggParam.default;
|
||||
} else {
|
||||
val = aggParam.default(self);
|
||||
if (val == null) return;
|
||||
}
|
||||
if (output.parentAggs) {
|
||||
const subDslLvl = configDsl.parentAggs || (configDsl.parentAggs = {});
|
||||
output.parentAggs.forEach(function nestAdhocSubAggs(subAggConfig) {
|
||||
subDslLvl[subAggConfig.id] = subAggConfig.toDsl();
|
||||
});
|
||||
}
|
||||
|
||||
if (aggParam.deserialize) {
|
||||
const isTyped = _.isFunction(aggParam.type);
|
||||
return configDsl;
|
||||
}
|
||||
|
||||
const isType = isTyped && (val instanceof aggParam.type);
|
||||
const isObject = !isTyped && _.isObject(val);
|
||||
const isDeserialized = (isType || isObject);
|
||||
toJSON() {
|
||||
const params = this.params;
|
||||
|
||||
if (!isDeserialized) {
|
||||
val = aggParam.deserialize(val, self);
|
||||
}
|
||||
const outParams = _.transform(this.getAggParams(), (out, aggParam) => {
|
||||
let val = params[aggParam.name];
|
||||
|
||||
to[aggParam.name] = val;
|
||||
return;
|
||||
// don't serialize undefined/null values
|
||||
if (val == null) return;
|
||||
if (aggParam.serialize) val = aggParam.serialize(val, this);
|
||||
if (val == null) return;
|
||||
|
||||
// to prevent accidental leaking, we will clone all complex values
|
||||
out[aggParam.name] = _.cloneDeep(val);
|
||||
}, {});
|
||||
|
||||
return {
|
||||
id: this.id,
|
||||
enabled: this.enabled,
|
||||
type: this.type && this.type.name,
|
||||
schema: this.schema && this.schema.name,
|
||||
params: outParams
|
||||
};
|
||||
}
|
||||
|
||||
getAggParams() {
|
||||
return [
|
||||
...((this.type) ? this.type.params.raw : []),
|
||||
...((_.has(this, 'schema.params')) ? this.schema.params.raw : []),
|
||||
];
|
||||
}
|
||||
|
||||
getRequestAggs() {
|
||||
if (!this.type) return;
|
||||
return this.type.getRequestAggs(this) || [this];
|
||||
}
|
||||
|
||||
getResponseAggs() {
|
||||
if (!this.type) return;
|
||||
return this.type.getResponseAggs(this) || [this];
|
||||
}
|
||||
|
||||
getValue(bucket) {
|
||||
return this.type.getValue(this, bucket);
|
||||
}
|
||||
|
||||
getKey(bucket, key) {
|
||||
return this.type.getKey(bucket, key, this);
|
||||
}
|
||||
|
||||
getFieldDisplayName() {
|
||||
const field = this.getField();
|
||||
return field ? (field.displayName || this.fieldName()) : '';
|
||||
}
|
||||
|
||||
getField() {
|
||||
return this.params.field;
|
||||
}
|
||||
|
||||
makeLabel() {
|
||||
if (this.params.customLabel) {
|
||||
return this.params.customLabel;
|
||||
}
|
||||
|
||||
to[aggParam.name] = _.cloneDeep(val);
|
||||
});
|
||||
};
|
||||
|
||||
AggConfig.prototype.write = function () {
|
||||
return this.type.params.write(this);
|
||||
};
|
||||
|
||||
AggConfig.prototype.isFilterable = function () {
|
||||
return _.isFunction(this.type.createFilter);
|
||||
};
|
||||
|
||||
AggConfig.prototype.createFilter = function (key, params = {}) {
|
||||
if (!this.isFilterable()) {
|
||||
throw new TypeError('The "' + this.type.title + '" aggregation does not support filtering.');
|
||||
if (!this.type) return '';
|
||||
let pre = (_.get(this.vis, 'params.mode') === 'percentage') ? 'Percentage of ' : '';
|
||||
return pre += this.type.makeLabel(this);
|
||||
}
|
||||
|
||||
const field = this.getField();
|
||||
const label = this.getFieldDisplayName();
|
||||
if (field && !field.filterable) {
|
||||
let message = 'The "' + label + '" field can not be used for filtering.';
|
||||
if (field.scripted) {
|
||||
message = 'The "' + label + '" field is scripted and can not be used for filtering.';
|
||||
getIndexPattern() {
|
||||
return this.vis.indexPattern;
|
||||
}
|
||||
|
||||
getFieldOptions() {
|
||||
const fieldParamType = this.type && this.type.params.byName.field;
|
||||
|
||||
if (!fieldParamType || !fieldParamType.getFieldOptions) {
|
||||
return null;
|
||||
}
|
||||
throw new TypeError(message);
|
||||
|
||||
return fieldParamType.getFieldOptions(this);
|
||||
}
|
||||
|
||||
return this.type.createFilter(this, key, params);
|
||||
};
|
||||
|
||||
/**
|
||||
* Hook for pre-flight logic, see AggType#onSearchRequestStart
|
||||
* @param {Courier.SearchSource} searchSource
|
||||
* @param {Courier.SearchRequest} searchRequest
|
||||
* @return {Promise<undefined>}
|
||||
*/
|
||||
AggConfig.prototype.onSearchRequestStart = function (searchSource, searchRequest) {
|
||||
if (!this.type) {
|
||||
return Promise.resolve();
|
||||
fieldFormatter(contentType, defaultFormat) {
|
||||
const format = this.type && this.type.getFormat(this);
|
||||
if (format) return format.getConverterFor(contentType);
|
||||
return this.fieldOwnFormatter(contentType, defaultFormat);
|
||||
}
|
||||
|
||||
return Promise.all(
|
||||
this.type.params.map(param => param.modifyAggConfigOnSearchRequestStart(this, searchSource, searchRequest))
|
||||
);
|
||||
};
|
||||
fieldOwnFormatter(contentType, defaultFormat) {
|
||||
const field = this.getField();
|
||||
let format = field && field.format;
|
||||
if (!format) format = defaultFormat;
|
||||
if (!format) format = fieldFormats.getDefaultInstance('string');
|
||||
return format.getConverterFor(contentType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert this aggConfig to its dsl syntax.
|
||||
*
|
||||
* Adds params and adhoc subaggs to a pojo, then returns it
|
||||
*
|
||||
* @param {AggConfig} aggConfig - the config object to convert
|
||||
* @return {void|Object} - if the config has a dsl representation, it is
|
||||
* returned, else undefined is returned
|
||||
*/
|
||||
AggConfig.prototype.toDsl = function () {
|
||||
if (this.type.hasNoDsl) return;
|
||||
const output = this.write();
|
||||
fieldName() {
|
||||
const field = this.getField();
|
||||
return field ? field.name : '';
|
||||
}
|
||||
|
||||
const configDsl = {};
|
||||
configDsl[this.type.dslName || this.type.name] = output.params;
|
||||
fieldIsTimeField() {
|
||||
const timeFieldName = this.vis.indexPattern.timeFieldName;
|
||||
return timeFieldName && this.fieldName() === timeFieldName;
|
||||
}
|
||||
|
||||
// if the config requires subAggs, write them to the dsl as well
|
||||
if (this.subAggs && !output.subAggs) output.subAggs = this.subAggs;
|
||||
if (output.subAggs) {
|
||||
const subDslLvl = configDsl.aggs || (configDsl.aggs = {});
|
||||
output.subAggs.forEach(function nestAdhocSubAggs(subAggConfig) {
|
||||
subDslLvl[subAggConfig.id] = subAggConfig.toDsl();
|
||||
get type() {
|
||||
return this.__type;
|
||||
}
|
||||
|
||||
set type(type) {
|
||||
if (this.__typeDecorations) {
|
||||
_.forOwn(this.__typeDecorations, function (prop, name) {
|
||||
delete this[name];
|
||||
}, this);
|
||||
}
|
||||
|
||||
if (_.isString(type)) {
|
||||
// We need to inline require here, since we're having a cyclic dependency
|
||||
// from somewhere inside agg_types back to AggConfig.
|
||||
type = require('../agg_types').aggTypes.byName[type];
|
||||
}
|
||||
|
||||
if (type && _.isFunction(type.decorateAggConfig)) {
|
||||
this.__typeDecorations = type.decorateAggConfig();
|
||||
Object.defineProperties(this, this.__typeDecorations);
|
||||
}
|
||||
|
||||
this.__type = type;
|
||||
|
||||
// clear out the previous params except for a few special ones
|
||||
this.setParams({
|
||||
// split row/columns is "outside" of the agg, so don't reset it
|
||||
row: this.params.row,
|
||||
|
||||
// almost every agg has fields, so we try to persist that when type changes
|
||||
field: _.get(this.getFieldOptions(), ['byName', this.getField()])
|
||||
});
|
||||
}
|
||||
|
||||
if (output.parentAggs) {
|
||||
const subDslLvl = configDsl.parentAggs || (configDsl.parentAggs = {});
|
||||
output.parentAggs.forEach(function nestAdhocSubAggs(subAggConfig) {
|
||||
subDslLvl[subAggConfig.id] = subAggConfig.toDsl();
|
||||
});
|
||||
get schema() {
|
||||
return this.__schema;
|
||||
}
|
||||
|
||||
return configDsl;
|
||||
};
|
||||
set schema(schema) {
|
||||
if (_.isString(schema)) {
|
||||
schema = this.vis.type.schemas.all.byName[schema];
|
||||
}
|
||||
|
||||
AggConfig.prototype.toJSON = function () {
|
||||
const self = this;
|
||||
const params = self.params;
|
||||
|
||||
const outParams = _.transform(self.getAggParams(), function (out, aggParam) {
|
||||
let val = params[aggParam.name];
|
||||
|
||||
// don't serialize undefined/null values
|
||||
if (val == null) return;
|
||||
if (aggParam.serialize) val = aggParam.serialize(val, self);
|
||||
if (val == null) return;
|
||||
|
||||
// to prevent accidental leaking, we will clone all complex values
|
||||
out[aggParam.name] = _.cloneDeep(val);
|
||||
}, {});
|
||||
|
||||
return {
|
||||
id: self.id,
|
||||
enabled: self.enabled,
|
||||
type: self.type && self.type.name,
|
||||
schema: self.schema && self.schema.name,
|
||||
params: outParams
|
||||
};
|
||||
};
|
||||
|
||||
AggConfig.prototype.getAggParams = function () {
|
||||
return [].concat(
|
||||
(this.type) ? this.type.params.raw : [],
|
||||
(_.has(this, 'schema.params')) ? this.schema.params.raw : []
|
||||
);
|
||||
};
|
||||
|
||||
AggConfig.prototype.getRequestAggs = function () {
|
||||
if (!this.type) return;
|
||||
return this.type.getRequestAggs(this) || [this];
|
||||
};
|
||||
|
||||
AggConfig.prototype.getResponseAggs = function () {
|
||||
if (!this.type) return;
|
||||
return this.type.getResponseAggs(this) || [this];
|
||||
};
|
||||
|
||||
AggConfig.prototype.getValue = function (bucket) {
|
||||
return this.type.getValue(this, bucket);
|
||||
};
|
||||
|
||||
AggConfig.prototype.getKey = function (bucket, key) {
|
||||
return this.type.getKey(bucket, key, this);
|
||||
};
|
||||
|
||||
AggConfig.prototype.getFieldDisplayName = function () {
|
||||
const field = this.getField();
|
||||
return field ? (field.displayName || this.fieldName()) : '';
|
||||
};
|
||||
|
||||
AggConfig.prototype.getField = function () {
|
||||
return this.params.field;
|
||||
};
|
||||
|
||||
AggConfig.prototype.makeLabel = function () {
|
||||
if (this.params.customLabel) {
|
||||
return this.params.customLabel;
|
||||
this.__schema = schema;
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.type) return '';
|
||||
let pre = (_.get(this.vis, 'params.mode') === 'percentage') ? 'Percentage of ' : '';
|
||||
return pre += this.type.makeLabel(this);
|
||||
};
|
||||
|
||||
AggConfig.prototype.getIndexPattern = function () {
|
||||
return this.vis.indexPattern;
|
||||
};
|
||||
|
||||
AggConfig.prototype.getFieldOptions = function () {
|
||||
const fieldParamType = this.type && this.type.params.byName.field;
|
||||
|
||||
if (!fieldParamType || !fieldParamType.getFieldOptions) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return fieldParamType.getFieldOptions(this);
|
||||
};
|
||||
|
||||
AggConfig.prototype.fieldFormatter = function (contentType, defaultFormat) {
|
||||
const format = this.type && this.type.getFormat(this);
|
||||
if (format) return format.getConverterFor(contentType);
|
||||
return this.fieldOwnFormatter(contentType, defaultFormat);
|
||||
};
|
||||
|
||||
AggConfig.prototype.fieldOwnFormatter = function (contentType, defaultFormat) {
|
||||
const field = this.getField();
|
||||
let format = field && field.format;
|
||||
if (!format) format = defaultFormat;
|
||||
if (!format) format = fieldFormats.getDefaultInstance('string');
|
||||
return format.getConverterFor(contentType);
|
||||
};
|
||||
|
||||
AggConfig.prototype.fieldName = function () {
|
||||
const field = this.getField();
|
||||
return field ? field.name : '';
|
||||
};
|
||||
|
||||
AggConfig.prototype.fieldIsTimeField = function () {
|
||||
const timeFieldName = this.vis.indexPattern.timeFieldName;
|
||||
return timeFieldName && this.fieldName() === timeFieldName;
|
||||
};
|
||||
export { AggConfig };
|
||||
|
|
|
@ -29,64 +29,6 @@
|
|||
import _ from 'lodash';
|
||||
import { IndexedArray } from '../indexed_array';
|
||||
import { AggConfig } from './agg_config';
|
||||
import { createLegacyClass } from '../utils/legacy_class';
|
||||
|
||||
createLegacyClass(AggConfigs).inherits(IndexedArray);
|
||||
function AggConfigs(vis, configStates) {
|
||||
const self = this;
|
||||
self.vis = vis;
|
||||
|
||||
configStates = AggConfig.ensureIds(configStates || []);
|
||||
|
||||
AggConfigs.Super.call(self, {
|
||||
index: ['id'],
|
||||
group: ['schema.group', 'type.name', 'schema.name'],
|
||||
initialSet: configStates.map(function (aggConfigState) {
|
||||
if (aggConfigState instanceof AggConfig) return aggConfigState;
|
||||
return new AggConfig(vis, aggConfigState);
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
// Set the defaults for any schema which has them. If the defaults
|
||||
// for some reason has more then the max only set the max number
|
||||
// of defaults (not sure why a someone define more...
|
||||
// but whatever). Also if a schema.name is already set then don't
|
||||
// set anything.
|
||||
if (vis && vis.type && vis.type.schemas && vis.type.schemas.all) {
|
||||
_(vis.type.schemas.all)
|
||||
.filter(function (schema) {
|
||||
return Array.isArray(schema.defaults) && schema.defaults.length > 0;
|
||||
})
|
||||
.each(function (schema) {
|
||||
if (!self.bySchemaName[schema.name]) {
|
||||
const defaults = schema.defaults.slice(0, schema.max);
|
||||
_.each(defaults, function (defaultState) {
|
||||
const state = _.defaults({ id: AggConfig.nextId(self) }, defaultState);
|
||||
self.push(new AggConfig(vis, state));
|
||||
});
|
||||
}
|
||||
})
|
||||
.commit();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Data-by-data comparison of this Aggregation
|
||||
* Ignores the non-array indexes
|
||||
* @param aggConfigs an AggConfigs instance
|
||||
*/
|
||||
AggConfigs.prototype.jsonDataEquals = function (aggConfigs) {
|
||||
if (aggConfigs.length !== this.length) {
|
||||
return false;
|
||||
}
|
||||
for (let i = 0; i < this.length; i += 1) {
|
||||
if (!_.isEqual(aggConfigs[i].toJSON(), this[i].toJSON())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
function removeParentAggs(obj) {
|
||||
for(const prop in obj) {
|
||||
|
@ -104,122 +46,168 @@ function parseParentAggs(dslLvlCursor, dsl) {
|
|||
}
|
||||
}
|
||||
|
||||
AggConfigs.prototype.toDsl = function () {
|
||||
const dslTopLvl = {};
|
||||
let dslLvlCursor;
|
||||
let nestedMetrics;
|
||||
|
||||
if (this.vis.isHierarchical()) {
|
||||
// collect all metrics, and filter out the ones that we won't be copying
|
||||
nestedMetrics = _(this.vis.aggs.bySchemaGroup.metrics)
|
||||
.filter(function (agg) {
|
||||
return agg.type.name !== 'count';
|
||||
class AggConfigs extends IndexedArray {
|
||||
constructor(vis, configStates = []) {
|
||||
configStates = AggConfig.ensureIds(configStates);
|
||||
super({
|
||||
index: ['id'],
|
||||
group: ['schema.group', 'type.name', 'schema.name'],
|
||||
initialSet: configStates.map(function (aggConfigState) {
|
||||
if (aggConfigState instanceof AggConfig) return aggConfigState;
|
||||
return new AggConfig(vis, aggConfigState);
|
||||
})
|
||||
.map(function (agg) {
|
||||
return {
|
||||
config: agg,
|
||||
dsl: agg.toDsl()
|
||||
};
|
||||
})
|
||||
.value();
|
||||
}
|
||||
this.getRequestAggs()
|
||||
.filter(function (config) {
|
||||
return !config.type.hasNoDsl;
|
||||
})
|
||||
.forEach(function nestEachConfig(config, i, list) {
|
||||
if (!dslLvlCursor) {
|
||||
// start at the top level
|
||||
dslLvlCursor = dslTopLvl;
|
||||
} else {
|
||||
const prevConfig = list[i - 1];
|
||||
const prevDsl = dslLvlCursor[prevConfig.id];
|
||||
|
||||
// advance the cursor and nest under the previous agg, or
|
||||
// put it on the same level if the previous agg doesn't accept
|
||||
// sub aggs
|
||||
dslLvlCursor = prevDsl.aggs || dslLvlCursor;
|
||||
}
|
||||
|
||||
const dsl = dslLvlCursor[config.id] = config.toDsl();
|
||||
let subAggs;
|
||||
|
||||
parseParentAggs(dslLvlCursor, dsl);
|
||||
|
||||
if (config.schema.group === 'buckets' && i < list.length - 1) {
|
||||
// buckets that are not the last item in the list accept sub-aggs
|
||||
subAggs = dsl.aggs || (dsl.aggs = {});
|
||||
}
|
||||
|
||||
if (subAggs && nestedMetrics) {
|
||||
nestedMetrics.forEach(function (agg) {
|
||||
subAggs[agg.config.id] = agg.dsl;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
removeParentAggs(dslTopLvl);
|
||||
return dslTopLvl;
|
||||
};
|
||||
this.vis = vis;
|
||||
|
||||
AggConfigs.prototype.getRequestAggs = function () {
|
||||
//collect all the aggregations
|
||||
const aggregations = this.reduce((requestValuesAggs, agg) => {
|
||||
const aggs = agg.getRequestAggs();
|
||||
return aggs ? requestValuesAggs.concat(aggs) : requestValuesAggs;
|
||||
}, []);
|
||||
//move metrics to the end
|
||||
return _.sortBy(aggregations, function (agg) {
|
||||
return agg.schema.group === 'metrics' ? 1 : 0;
|
||||
});
|
||||
};
|
||||
// Set the defaults for any schema which has them. If the defaults
|
||||
// for some reason has more then the max only set the max number
|
||||
// of defaults (not sure why a someone define more...
|
||||
// but whatever). Also if a schema.name is already set then don't
|
||||
// set anything.
|
||||
if (vis && vis.type && vis.type.schemas && vis.type.schemas.all) {
|
||||
_(vis.type.schemas.all)
|
||||
.filter(schema => {
|
||||
return Array.isArray(schema.defaults) && schema.defaults.length > 0;
|
||||
})
|
||||
.each(schema => {
|
||||
if (!this.bySchemaName[schema.name]) {
|
||||
const defaults = schema.defaults.slice(0, schema.max);
|
||||
_.each(defaults, defaultState => {
|
||||
const state = _.defaults({ id: AggConfig.nextId(this) }, defaultState);
|
||||
this.push(new AggConfig(vis, state));
|
||||
});
|
||||
}
|
||||
})
|
||||
.commit();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the AggConfigs (and possibly ResponseAggConfigs) that
|
||||
* represent the values that will be produced when all aggs
|
||||
* are run.
|
||||
*
|
||||
* With multi-value metric aggs it is possible for a single agg
|
||||
* request to result in multiple agg values, which is why the length
|
||||
* of a vis' responseValuesAggs may be different than the vis' aggs
|
||||
*
|
||||
* @return {array[AggConfig]}
|
||||
*/
|
||||
AggConfigs.prototype.getResponseAggs = function () {
|
||||
return this.getRequestAggs().reduce(function (responseValuesAggs, agg) {
|
||||
const aggs = agg.getResponseAggs();
|
||||
return aggs ? responseValuesAggs.concat(aggs) : responseValuesAggs;
|
||||
}, []);
|
||||
};
|
||||
/**
|
||||
* Data-by-data comparison of this Aggregation
|
||||
* Ignores the non-array indexes
|
||||
* @param aggConfigs an AggConfigs instance
|
||||
*/
|
||||
jsonDataEquals(aggConfigs) {
|
||||
if (aggConfigs.length !== this.length) {
|
||||
return false;
|
||||
}
|
||||
for (let i = 0; i < this.length; i += 1) {
|
||||
if (!_.isEqual(aggConfigs[i].toJSON(), this[i].toJSON())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
toDsl() {
|
||||
const dslTopLvl = {};
|
||||
let dslLvlCursor;
|
||||
let nestedMetrics;
|
||||
|
||||
if (this.vis.isHierarchical()) {
|
||||
// collect all metrics, and filter out the ones that we won't be copying
|
||||
nestedMetrics = _(this.vis.aggs.bySchemaGroup.metrics)
|
||||
.filter(function (agg) {
|
||||
return agg.type.name !== 'count';
|
||||
})
|
||||
.map(function (agg) {
|
||||
return {
|
||||
config: agg,
|
||||
dsl: agg.toDsl()
|
||||
};
|
||||
})
|
||||
.value();
|
||||
}
|
||||
this.getRequestAggs()
|
||||
.filter(config => !config.type.hasNoDsl)
|
||||
.forEach(function nestEachConfig(config, i, list) {
|
||||
if (!dslLvlCursor) {
|
||||
// start at the top level
|
||||
dslLvlCursor = dslTopLvl;
|
||||
} else {
|
||||
const prevConfig = list[i - 1];
|
||||
const prevDsl = dslLvlCursor[prevConfig.id];
|
||||
|
||||
// advance the cursor and nest under the previous agg, or
|
||||
// put it on the same level if the previous agg doesn't accept
|
||||
// sub aggs
|
||||
dslLvlCursor = prevDsl.aggs || dslLvlCursor;
|
||||
}
|
||||
|
||||
const dsl = dslLvlCursor[config.id] = config.toDsl();
|
||||
let subAggs;
|
||||
|
||||
parseParentAggs(dslLvlCursor, dsl);
|
||||
|
||||
if (config.schema.group === 'buckets' && i < list.length - 1) {
|
||||
// buckets that are not the last item in the list accept sub-aggs
|
||||
subAggs = dsl.aggs || (dsl.aggs = {});
|
||||
}
|
||||
|
||||
if (subAggs && nestedMetrics) {
|
||||
nestedMetrics.forEach(function (agg) {
|
||||
subAggs[agg.config.id] = agg.dsl;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
removeParentAggs(dslTopLvl);
|
||||
return dslTopLvl;
|
||||
}
|
||||
|
||||
getRequestAggs() {
|
||||
//collect all the aggregations
|
||||
const aggregations = this.reduce((requestValuesAggs, agg) => {
|
||||
const aggs = agg.getRequestAggs();
|
||||
return aggs ? requestValuesAggs.concat(aggs) : requestValuesAggs;
|
||||
}, []);
|
||||
//move metrics to the end
|
||||
return _.sortBy(aggregations, agg => agg.schema.group === 'metrics' ? 1 : 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the AggConfigs (and possibly ResponseAggConfigs) that
|
||||
* represent the values that will be produced when all aggs
|
||||
* are run.
|
||||
*
|
||||
* With multi-value metric aggs it is possible for a single agg
|
||||
* request to result in multiple agg values, which is why the length
|
||||
* of a vis' responseValuesAggs may be different than the vis' aggs
|
||||
*
|
||||
* @return {array[AggConfig]}
|
||||
*/
|
||||
getResponseAggs() {
|
||||
return this.getRequestAggs().reduce(function (responseValuesAggs, agg) {
|
||||
const aggs = agg.getResponseAggs();
|
||||
return aggs ? responseValuesAggs.concat(aggs) : responseValuesAggs;
|
||||
}, []);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Find a response agg by it's id. This may be an agg in the aggConfigs, or one
|
||||
* created specifically for a response value
|
||||
*
|
||||
* @param {string} id - the id of the agg to find
|
||||
* @return {AggConfig}
|
||||
*/
|
||||
AggConfigs.prototype.getResponseAggById = function (id) {
|
||||
id = String(id);
|
||||
const reqAgg = _.find(this.getRequestAggs(), function (agg) {
|
||||
return id.substr(0, String(agg.id).length) === agg.id;
|
||||
});
|
||||
if (!reqAgg) return;
|
||||
return _.find(reqAgg.getResponseAggs(), { id: id });
|
||||
};
|
||||
/**
|
||||
* Hook for pre-flight logic, see AggType#onSearchRequestStart()
|
||||
* @param {Courier.SearchSource} searchSource
|
||||
* @param {Courier.SearchRequest} searchRequest
|
||||
* @return {Promise<undefined>}
|
||||
*/
|
||||
AggConfigs.prototype.onSearchRequestStart = function (searchSource, searchRequest) {
|
||||
return Promise.all(
|
||||
this.getRequestAggs().map(agg =>
|
||||
agg.onSearchRequestStart(searchSource, searchRequest)
|
||||
)
|
||||
);
|
||||
};
|
||||
/**
|
||||
* Find a response agg by it's id. This may be an agg in the aggConfigs, or one
|
||||
* created specifically for a response value
|
||||
*
|
||||
* @param {string} id - the id of the agg to find
|
||||
* @return {AggConfig}
|
||||
*/
|
||||
getResponseAggById(id) {
|
||||
id = String(id);
|
||||
const reqAgg = _.find(this.getRequestAggs(), function (agg) {
|
||||
return id.substr(0, String(agg.id).length) === agg.id;
|
||||
});
|
||||
if (!reqAgg) return;
|
||||
return _.find(reqAgg.getResponseAggs(), { id: id });
|
||||
}
|
||||
|
||||
onSearchRequestStart(searchSource, searchRequest) {
|
||||
return Promise.all(
|
||||
this.getRequestAggs().map(agg =>
|
||||
agg.onSearchRequestStart(searchSource, searchRequest)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export { AggConfigs };
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue