import * as queryDef from './query_def'; export class ElasticQueryBuilder { timeField: string; esVersion: number; constructor(options) { this.timeField = options.timeField; this.esVersion = options.esVersion; } getRangeFilter() { var filter = {}; filter[this.timeField] = { gte: '$timeFrom', lte: '$timeTo', format: 'epoch_millis', }; return filter; } buildTermsAgg(aggDef, queryNode, target) { var metricRef, metric, y; queryNode.terms = { field: aggDef.field }; if (!aggDef.settings) { return queryNode; } queryNode.terms.size = parseInt(aggDef.settings.size, 10) === 0 ? 500 : parseInt(aggDef.settings.size, 10); if (aggDef.settings.orderBy !== void 0) { queryNode.terms.order = {}; queryNode.terms.order[aggDef.settings.orderBy] = aggDef.settings.order; // if metric ref, look it up and add it to this agg level metricRef = parseInt(aggDef.settings.orderBy, 10); if (!isNaN(metricRef)) { for (y = 0; y < target.metrics.length; y++) { metric = target.metrics[y]; if (metric.id === aggDef.settings.orderBy) { queryNode.aggs = {}; queryNode.aggs[metric.id] = {}; queryNode.aggs[metric.id][metric.type] = { field: metric.field }; break; } } } } if (aggDef.settings.min_doc_count !== void 0) { queryNode.terms.min_doc_count = parseInt(aggDef.settings.min_doc_count, 10); } if (aggDef.settings.missing) { queryNode.terms.missing = aggDef.settings.missing; } return queryNode; } getDateHistogramAgg(aggDef) { var esAgg: any = {}; var settings = aggDef.settings || {}; esAgg.interval = settings.interval; esAgg.field = this.timeField; esAgg.min_doc_count = settings.min_doc_count || 0; esAgg.extended_bounds = { min: '$timeFrom', max: '$timeTo' }; esAgg.format = 'epoch_millis'; if (esAgg.interval === 'auto') { esAgg.interval = '$__interval'; } if (settings.missing) { esAgg.missing = settings.missing; } return esAgg; } getHistogramAgg(aggDef) { var esAgg: any = {}; var settings = aggDef.settings || {}; esAgg.interval = settings.interval; esAgg.field = aggDef.field; esAgg.min_doc_count = settings.min_doc_count || 0; if (settings.missing) { esAgg.missing = settings.missing; } return esAgg; } getFiltersAgg(aggDef) { var filterObj = {}; for (var i = 0; i < aggDef.settings.filters.length; i++) { var query = aggDef.settings.filters[i].query; var label = aggDef.settings.filters[i].label; label = label === '' || label === undefined ? query : label; filterObj[label] = { query_string: { query: query, analyze_wildcard: true, }, }; } return filterObj; } documentQuery(query, size) { query.size = size; query.sort = {}; query.sort[this.timeField] = { order: 'desc', unmapped_type: 'boolean' }; // fields field not supported on ES 5.x if (this.esVersion < 5) { query.fields = ['*', '_source']; } query.script_fields = {}; if (this.esVersion < 5) { query.fielddata_fields = [this.timeField]; } else { query.docvalue_fields = [this.timeField]; } return query; } addAdhocFilters(query, adhocFilters) { if (!adhocFilters) { return; } var i, filter, condition, queryCondition; for (i = 0; i < adhocFilters.length; i++) { filter = adhocFilters[i]; condition = {}; condition[filter.key] = filter.value; queryCondition = {}; queryCondition[filter.key] = { query: filter.value }; switch (filter.operator) { case '=': if (!query.query.bool.must) { query.query.bool.must = []; } query.query.bool.must.push({ match_phrase: queryCondition }); break; case '!=': if (!query.query.bool.must_not) { query.query.bool.must_not = []; } query.query.bool.must_not.push({ match_phrase: queryCondition }); break; case '<': condition[filter.key] = { lt: filter.value }; query.query.bool.filter.push({ range: condition }); break; case '>': condition[filter.key] = { gt: filter.value }; query.query.bool.filter.push({ range: condition }); break; case '=~': query.query.bool.filter.push({ regexp: condition }); break; case '!~': query.query.bool.filter.push({ bool: { must_not: { regexp: condition } }, }); break; } } } build(target, adhocFilters?, queryString?) { // make sure query has defaults; target.metrics = target.metrics || [{ type: 'count', id: '1' }]; target.bucketAggs = target.bucketAggs || [{ type: 'date_histogram', id: '2', settings: { interval: 'auto' } }]; target.timeField = this.timeField; var i, nestedAggs, metric; var query = { size: 0, query: { bool: { filter: [ { range: this.getRangeFilter() }, { query_string: { analyze_wildcard: true, query: queryString, }, }, ], }, }, }; this.addAdhocFilters(query, adhocFilters); // handle document query if (target.bucketAggs.length === 0) { metric = target.metrics[0]; if (!metric || metric.type !== 'raw_document') { throw { message: 'Invalid query' }; } var size = (metric.settings && metric.settings.size) || 500; return this.documentQuery(query, size); } nestedAggs = query; for (i = 0; i < target.bucketAggs.length; i++) { var aggDef = target.bucketAggs[i]; var esAgg = {}; switch (aggDef.type) { case 'date_histogram': { esAgg['date_histogram'] = this.getDateHistogramAgg(aggDef); break; } case 'histogram': { esAgg['histogram'] = this.getHistogramAgg(aggDef); break; } case 'filters': { esAgg['filters'] = { filters: this.getFiltersAgg(aggDef) }; break; } case 'terms': { this.buildTermsAgg(aggDef, esAgg, target); break; } case 'geohash_grid': { esAgg['geohash_grid'] = { field: aggDef.field, precision: aggDef.settings.precision, }; break; } } nestedAggs.aggs = nestedAggs.aggs || {}; nestedAggs.aggs[aggDef.id] = esAgg; nestedAggs = esAgg; } nestedAggs.aggs = {}; for (i = 0; i < target.metrics.length; i++) { metric = target.metrics[i]; if (metric.type === 'count') { continue; } var aggField = {}; var metricAgg = null; if (queryDef.isPipelineAgg(metric.type)) { if (metric.pipelineAgg && /^\d*$/.test(metric.pipelineAgg)) { metricAgg = { buckets_path: metric.pipelineAgg }; } else { continue; } } else { metricAgg = { field: metric.field }; } for (var prop in metric.settings) { if (metric.settings.hasOwnProperty(prop) && metric.settings[prop] !== null) { metricAgg[prop] = metric.settings[prop]; } } aggField[metric.type] = metricAgg; nestedAggs.aggs[metric.id] = aggField; } return query; } getTermsQuery(queryDef) { var query: any = { size: 0, query: { bool: { filter: [{ range: this.getRangeFilter() }], }, }, }; if (queryDef.query) { query.query.bool.filter.push({ query_string: { analyze_wildcard: true, query: queryDef.query, }, }); } var size = 500; if (queryDef.size) { size = queryDef.size; } query.aggs = { '1': { terms: { field: queryDef.field, size: size, order: { _term: 'asc', }, }, }, }; return query; } }