elastic-builder
Version:
A JavaScript implementation of the elasticsearch Query DSL
179 lines (161 loc) • 6.27 kB
JavaScript
;
const has = require('lodash.has');
const isNil = require('lodash.isnil');
const {
util: { invalidParam }
} = require('../../core');
const TermsAggregationBase = require('./terms-aggregation-base');
const ES_REF_URL =
'https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-terms-aggregation.html';
const invalidDirectionParam = invalidParam(
ES_REF_URL,
'direction',
"'asc' or 'desc'"
);
const invalidCollectModeParam = invalidParam(
ES_REF_URL,
'mode',
"'breadth_first' or 'depth_first'"
);
/**
* A multi-bucket value source based aggregation where buckets are dynamically
* built - one per unique value.
*
* [Elasticsearch reference](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-terms-aggregation.html)
*
* @example
* const agg = esb.termsAggregation('genres', 'genre');
*
* @param {string} name The name which will be used to refer to this aggregation.
* @param {string=} field The field to aggregate on
*
* @extends TermsAggregationBase
*/
class TermsAggregation extends TermsAggregationBase {
// eslint-disable-next-line require-jsdoc
constructor(name, field) {
super(name, 'terms', ES_REF_URL, field);
}
/**
* When set to `true`, shows an error value for each term returned by the aggregation
* which represents the _worst case error_ in the document count and can be useful
* when deciding on a value for the shard_size parameter.
*
* @param {boolean} enable
* @returns {TermsAggregation} returns `this` so that calls can be chained
*/
showTermDocCountError(enable) {
this._aggsDef.show_term_doc_count_error = enable;
return this;
}
/**
* Break the analysis up into multiple requests by grouping the field’s values
* into a number of partitions at query-time and processing only one
* partition in each request.
*
* Note that this method is a special case as the name doesn't map to the
* elasticsearch parameter name. This is required because there is already
* a method for `include` applicable for Terms aggregations. However, this
* could change depending on community interest.
*
* @example
* const agg = esb.termsAggregation('expired_sessions', 'account_id')
* .includePartition(0, 20)
* .size(10000)
* .order('last_access', 'asc')
* .agg(esb.maxAggregation('last_access', 'access_date'));
*
* @param {number} partition
* @param {number} numPartitions
* @returns {TermsAggregation} returns `this` so that calls can be chained
*/
includePartition(partition, numPartitions) {
// TODO: Print warning if include key is being overwritten
this._aggsDef.include = {
partition,
num_partitions: numPartitions
};
return this;
}
/**
* Can be used for deferring calculation of child aggregations by using
* `breadth_first` mode. In `depth_first` mode all branches of the aggregation
* tree are expanded in one depth-first pass and only then any pruning occurs.
*
* @example
* const agg = esb.termsAggregation('actors', 'actors')
* .size(10)
* .collectMode('breadth_first')
* .agg(esb.termsAggregation('costars', 'actors').size(5));
*
* @param {string} mode The possible values are `breadth_first` and `depth_first`.
* @returns {TermsAggregation} returns `this` so that calls can be chained
*/
collectMode(mode) {
if (isNil(mode)) invalidCollectModeParam(mode);
const modeLower = mode.toLowerCase();
if (modeLower !== 'breadth_first' && modeLower !== 'depth_first') {
invalidCollectModeParam(mode);
}
this._aggsDef.collect_mode = modeLower;
return this;
}
/**
* Sets the ordering for buckets
*
* @example
* // Ordering the buckets by their doc `_count` in an ascending manner
* const agg = esb.termsAggregation('genres', 'genre').order('_count', 'asc');
*
* @example
* // Ordering the buckets alphabetically by their terms in an ascending manner
* const agg = esb.termsAggregation('genres', 'genre').order('_term', 'asc');
*
* @example
* // Ordering the buckets by single value metrics sub-aggregation
* // (identified by the aggregation name)
* const agg = esb.termsAggregation('genres', 'genre')
* .order('max_play_count', 'asc')
* .agg(esb.maxAggregation('max_play_count', 'play_count'));
*
* @example
* // Ordering the buckets by multi value metrics sub-aggregation
* // (identified by the aggregation name):
* const agg = esb.termsAggregation('genres', 'genre')
* .order('playback_stats.max', 'desc')
* .agg(esb.statsAggregation('playback_stats', 'play_count'));
*
* @example
* // Multiple order criteria
* const agg = esb.termsAggregation('countries')
* .field('artist.country')
* .order('rock>playback_stats.avg', 'desc')
* .order('_count', 'desc')
* .agg(
* esb.filterAggregation('rock')
* .filter(esb.termQuery('genre', 'rock'))
* .agg(esb.statsAggregation('playback_stats', 'play_count'))
* );
*
* @param {string} key
* @param {string} direction `asc` or `desc`
* @returns {TermsAggregation} returns `this` so that calls can be chained
*/
order(key, direction = 'desc') {
if (isNil(direction)) invalidDirectionParam(direction);
const directionLower = direction.toLowerCase();
if (directionLower !== 'asc' && directionLower !== 'desc') {
invalidDirectionParam(direction);
}
if (has(this._aggsDef, 'order')) {
if (!Array.isArray(this._aggsDef.order)) {
this._aggsDef.order = [this._aggsDef.order];
}
this._aggsDef.order.push({ [key]: directionLower });
} else {
this._aggsDef.order = { [key]: directionLower };
}
return this;
}
}
module.exports = TermsAggregation;