Convert ui/agg_types/buckets to TypeScript / Jest (#47730) (#48266)

* Convert `ui/agg_types/buckets` to TypeScript / Jest

* fix eslint issues

* fix CI

* fix JEST test

* fix mocha tests

* Fix PR comments
This commit is contained in:
Alexey Antonov 2019-10-15 23:16:50 +03:00 committed by GitHub
parent baeb7d4d25
commit c3b9013082
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
45 changed files with 875 additions and 601 deletions

View file

@ -41,7 +41,7 @@ export function buildPhrasesFilter(
indexPattern: IndexPattern indexPattern: IndexPattern
): PhrasesFilter; ): PhrasesFilter;
export function buildQueryFilter(query: any, index: string): CustomFilter; export function buildQueryFilter(query: any, index: string, alias?: string): CustomFilter;
export function buildRangeFilter( export function buildRangeFilter(
field: Field, field: Field,

View file

@ -28,7 +28,12 @@ import { GeoPolygonFilter, isGeoPolygonFilter } from './geo_polygon_filter';
import { PhraseFilter, isPhraseFilter, isScriptedPhraseFilter } from './phrase_filter'; import { PhraseFilter, isPhraseFilter, isScriptedPhraseFilter } from './phrase_filter';
import { PhrasesFilter, isPhrasesFilter } from './phrases_filter'; import { PhrasesFilter, isPhrasesFilter } from './phrases_filter';
import { QueryStringFilter, isQueryStringFilter } from './query_string_filter'; import { QueryStringFilter, isQueryStringFilter } from './query_string_filter';
import { RangeFilter, isRangeFilter, isScriptedRangeFilter } from './range_filter'; import {
RangeFilter,
isRangeFilter,
isScriptedRangeFilter,
RangeFilterParams,
} from './range_filter';
import { MatchAllFilter, isMatchAllFilter } from './match_all_filter'; import { MatchAllFilter, isMatchAllFilter } from './match_all_filter';
import { MissingFilter, isMissingFilter } from './missing_filter'; import { MissingFilter, isMissingFilter } from './missing_filter';
@ -50,6 +55,7 @@ export {
RangeFilter, RangeFilter,
isRangeFilter, isRangeFilter,
isScriptedRangeFilter, isScriptedRangeFilter,
RangeFilterParams,
MatchAllFilter, MatchAllFilter,
isMatchAllFilter, isMatchAllFilter,
MissingFilter, MissingFilter,

View file

@ -19,23 +19,16 @@
import { get, keys } from 'lodash'; import { get, keys } from 'lodash';
import { Filter, FilterMeta } from './meta_filter'; import { Filter, FilterMeta } from './meta_filter';
interface FilterRange { export interface RangeFilterParams {
from?: number | string; from?: number | string;
to?: number | string; to?: number | string;
}
interface FilterRangeGt {
gt?: number | string; gt?: number | string;
lt?: number | string; lt?: number | string;
}
interface FilterRangeGte {
gte?: number | string; gte?: number | string;
lte?: number | string; lte?: number | string;
format?: string;
} }
export type RangeFilterParams = FilterRange & FilterRangeGt & FilterRangeGte;
export type RangeFilterMeta = FilterMeta & { export type RangeFilterMeta = FilterMeta & {
params: RangeFilterParams; params: RangeFilterParams;
field?: any; field?: any;

View file

@ -38,7 +38,8 @@ describe('Significant Terms Agg', function () {
$rootScope.agg = { $rootScope.agg = {
id: 'test', id: 'test',
params: aggParams, params: aggParams,
type: significantTerms type: significantTerms,
getParam: key => aggParams[key],
}; };
}); });
} }

View file

@ -39,7 +39,8 @@ describe('Terms Agg', function () {
type: terms, type: terms,
vis: { vis: {
aggs: [] aggs: []
} },
getParam: key => aggParams[key],
}; };
$rootScope.metricAggs = metricAggs; $rootScope.metricAggs = metricAggs;
$controller(orderAggController, { $scope: $rootScope }); $controller(orderAggController, { $scope: $rootScope });

View file

@ -26,20 +26,23 @@
import _ from 'lodash'; import _ from 'lodash';
import { i18n } from '@kbn/i18n'; import { i18n } from '@kbn/i18n';
import { AggType, FieldParamType, BucketAggType } from '.'; import { AggType } from './agg_type';
import { FieldParamType } from './param_types/field';
import { AggGroupNames } from '../vis/editors/default/agg_groups'; import { AggGroupNames } from '../vis/editors/default/agg_groups';
// @ts-ignore
import { fieldFormats } from '../registry/field_formats';
import { writeParams } from './agg_params'; import { writeParams } from './agg_params';
import { AggConfigs } from './agg_configs'; import { AggConfigs } from './agg_configs';
import { Schema } from '../vis/editors/default/schemas'; import { Schema } from '../vis/editors/default/schemas';
import { ContentType } from '../../../../plugins/data/common';
// @ts-ignore
import { fieldFormats } from '../registry/field_formats';
export interface AggConfigOptions { export interface AggConfigOptions {
id: string;
enabled: boolean; enabled: boolean;
type: string; type: string;
schema: string;
params: any; params: any;
id?: string;
schema?: string;
} }
const unknownSchema: Schema = { const unknownSchema: Schema = {
@ -139,7 +142,10 @@ export class AggConfig {
// setters // setters
this.setType(opts.type); this.setType(opts.type);
this.setSchema(opts.schema);
if (opts.schema) {
this.setSchema(opts.schema);
}
// set the params to the values from opts, or just to the defaults // set the params to the values from opts, or just to the defaults
this.setParams(opts.params || {}); this.setParams(opts.params || {});
@ -253,7 +259,7 @@ export class AggConfig {
* @return {void|Object} - if the config has a dsl representation, it is * @return {void|Object} - if the config has a dsl representation, it is
* returned, else undefined is returned * returned, else undefined is returned
*/ */
toDsl(aggConfigs: AggConfigs) { toDsl(aggConfigs?: AggConfigs) {
if (this.type.hasNoDsl) return; if (this.type.hasNoDsl) return;
const output = this.write(aggConfigs) as any; const output = this.write(aggConfigs) as any;
@ -327,8 +333,8 @@ export class AggConfig {
} }
getKey(bucket: any, key: string) { getKey(bucket: any, key: string) {
if (this.type instanceof BucketAggType) { if (this.type.getKey) {
return (this.type as BucketAggType).getKey(bucket, key, this); return this.type.getKey(bucket, key, this);
} else { } else {
return ''; return '';
} }
@ -336,6 +342,7 @@ export class AggConfig {
getFieldDisplayName() { getFieldDisplayName() {
const field = this.getField(); const field = this.getField();
return field ? field.displayName || this.fieldName() : ''; return field ? field.displayName || this.fieldName() : '';
} }
@ -365,13 +372,16 @@ export class AggConfig {
return this.aggConfigs.timeRange; return this.aggConfigs.timeRange;
} }
fieldFormatter(contentType: string, defaultFormat: any) { fieldFormatter(contentType?: ContentType, defaultFormat?: any) {
const format = this.type && this.type.getFormat(this); const format = this.type && this.type.getFormat(this);
if (format) return format.getConverterFor(contentType);
if (format) {
return format.getConverterFor(contentType);
}
return this.fieldOwnFormatter(contentType, defaultFormat); return this.fieldOwnFormatter(contentType, defaultFormat);
} }
fieldOwnFormatter(contentType: string, defaultFormat: any) { fieldOwnFormatter(contentType?: ContentType, defaultFormat?: any) {
const field = this.getField(); const field = this.getField();
let format = field && field.format; let format = field && field.format;
if (!format) format = defaultFormat; if (!format) format = defaultFormat;

View file

@ -20,17 +20,19 @@
import { constant, noop, identity } from 'lodash'; import { constant, noop, identity } from 'lodash';
import { i18n } from '@kbn/i18n'; import { i18n } from '@kbn/i18n';
import { AggParam, initParams } from './agg_params'; import { AggParam, initParams } from './agg_params';
// @ts-ignore
import { FieldFormat, fieldFormats } from '../registry/field_formats';
import { AggConfig } from '../vis'; import { AggConfig } from '../vis';
import { AggConfigs } from './agg_configs'; import { AggConfigs } from './agg_configs';
import { SearchSource } from '../courier'; import { SearchSource } from '../courier';
import { Adapters } from '../inspector'; import { Adapters } from '../inspector';
import { BaseParamType } from './param_types/base'; import { BaseParamType } from './param_types/base';
// @ts-ignore
import { FieldFormat, fieldFormats } from '../registry/field_formats';
export interface AggTypeConfig< export interface AggTypeConfig<
TAggConfig extends AggConfig = AggConfig, TAggConfig extends AggConfig = AggConfig,
TParam extends AggParam = TAggConfig['params'][number] TParam extends AggParam = AggParam
> { > {
name: string; name: string;
title: string; title: string;
@ -50,7 +52,8 @@ export interface AggTypeConfig<
aggConfigs: AggConfigs, aggConfigs: AggConfigs,
aggConfig: TAggConfig, aggConfig: TAggConfig,
searchSource: SearchSource, searchSource: SearchSource,
inspectorAdapters: Adapters inspectorAdapters: Adapters,
abortSignal?: AbortSignal
) => Promise<any>; ) => Promise<any>;
getFormat?: (agg: TAggConfig) => FieldFormat; getFormat?: (agg: TAggConfig) => FieldFormat;
getValue?: (agg: TAggConfig, bucket: any) => any; getValue?: (agg: TAggConfig, bucket: any) => any;
@ -62,7 +65,7 @@ const getFormat = (agg: AggConfig) => {
return field ? field.format : fieldFormats.getDefaultInstance('string'); return field ? field.format : fieldFormats.getDefaultInstance('string');
}; };
export class AggType<TAggConfig extends AggConfig = AggConfig> { export class AggType<TAggConfig extends AggConfig = AggConfig, TParam extends AggParam = AggParam> {
/** /**
* the unique, unchanging, name that we have assigned this aggType * the unique, unchanging, name that we have assigned this aggType
* *
@ -131,7 +134,7 @@ export class AggType<TAggConfig extends AggConfig = AggConfig> {
* @property params * @property params
* @type {AggParams} * @type {AggParams}
*/ */
params: TAggConfig['params']; params: TParam[];
/** /**
* Designed for multi-value metric aggs, this method can return a * Designed for multi-value metric aggs, this method can return a
* set of AggConfigs that should replace this aggConfig in requests * set of AggConfigs that should replace this aggConfig in requests
@ -188,6 +191,8 @@ export class AggType<TAggConfig extends AggConfig = AggConfig> {
getValue: (agg: TAggConfig, bucket: any) => any; getValue: (agg: TAggConfig, bucket: any) => any;
getKey?: (bucket: any, key: any, agg: TAggConfig) => any;
paramByName = (name: string) => { paramByName = (name: string) => {
return this.params.find((p: AggParam) => p.name === name); return this.params.find((p: AggParam) => p.name === name);
}; };
@ -215,7 +220,7 @@ export class AggType<TAggConfig extends AggConfig = AggConfig> {
} }
if (config.params && config.params.length && config.params[0] instanceof BaseParamType) { if (config.params && config.params.length && config.params[0] instanceof BaseParamType) {
this.params = config.params as TAggConfig['params']; this.params = config.params as TParam[];
} else { } else {
// always append the raw JSON param // always append the raw JSON param
const params: any[] = config.params ? [...config.params] : []; const params: any[] = config.params ? [...config.params] : [];

View file

@ -17,13 +17,32 @@
* under the License. * under the License.
*/ */
import { AggType, AggTypeConfig } from '../agg_type'; import { AggParamType } from '../param_types/agg';
import { AggConfig } from '../../vis'; import { AggConfig } from '../../vis';
import { KBN_FIELD_TYPES } from '../../../../../plugins/data/common';
import { AggType, AggTypeConfig } from '../agg_type';
export class BucketAggType extends AggType { export type IBucketAggConfig = AggConfig;
getKey: (bucket: any, key: any, agg: AggConfig) => any;
constructor(config: AggTypeConfig) { export interface BucketAggParam extends AggParamType {
scriptable?: boolean;
filterFieldTypes?: KBN_FIELD_TYPES | KBN_FIELD_TYPES[] | '*';
}
export interface BucketAggTypeConfig<TBucketAggConfig extends IBucketAggConfig>
extends AggTypeConfig<TBucketAggConfig, BucketAggParam> {
getKey?: (bucket: any, key: any, agg: AggConfig) => any;
}
const bucketType = 'buckets';
export class BucketAggType<
TBucketAggConfig extends IBucketAggConfig = IBucketAggConfig
> extends AggType<TBucketAggConfig, BucketAggParam> {
getKey: (bucket: any, key: any, agg: IBucketAggConfig) => any;
type = bucketType;
constructor(config: BucketAggTypeConfig<TBucketAggConfig>) {
super(config); super(config);
this.getKey = this.getKey =
@ -33,3 +52,7 @@ export class BucketAggType extends AggType {
}); });
} }
} }
export function isBucketAggType(aggConfig: any): aggConfig is BucketAggType {
return aggConfig && aggConfig.type === bucketType;
}

View file

@ -17,6 +17,7 @@
* under the License. * under the License.
*/ */
import { i18n } from '@kbn/i18n'; import { i18n } from '@kbn/i18n';
import { IBucketAggConfig } from './_bucket_agg_type';
export const intervalOptions = [ export const intervalOptions = [
{ {
@ -24,58 +25,58 @@ export const intervalOptions = [
defaultMessage: 'Auto', defaultMessage: 'Auto',
}), }),
val: 'auto', val: 'auto',
enabled: function (agg) { enabled(agg: IBucketAggConfig) {
// not only do we need a time field, but the selected field needs // not only do we need a time field, but the selected field needs
// to be the time field. (see #3028) // to be the time field. (see #3028)
return agg.fieldIsTimeField(); return agg.fieldIsTimeField();
} },
}, },
{ {
display: i18n.translate('common.ui.aggTypes.buckets.intervalOptions.millisecondDisplayName', { display: i18n.translate('common.ui.aggTypes.buckets.intervalOptions.millisecondDisplayName', {
defaultMessage: 'Millisecond', defaultMessage: 'Millisecond',
}), }),
val: 'ms' val: 'ms',
}, },
{ {
display: i18n.translate('common.ui.aggTypes.buckets.intervalOptions.secondDisplayName', { display: i18n.translate('common.ui.aggTypes.buckets.intervalOptions.secondDisplayName', {
defaultMessage: 'Second', defaultMessage: 'Second',
}), }),
val: 's' val: 's',
}, },
{ {
display: i18n.translate('common.ui.aggTypes.buckets.intervalOptions.minuteDisplayName', { display: i18n.translate('common.ui.aggTypes.buckets.intervalOptions.minuteDisplayName', {
defaultMessage: 'Minute', defaultMessage: 'Minute',
}), }),
val: 'm' val: 'm',
}, },
{ {
display: i18n.translate('common.ui.aggTypes.buckets.intervalOptions.hourlyDisplayName', { display: i18n.translate('common.ui.aggTypes.buckets.intervalOptions.hourlyDisplayName', {
defaultMessage: 'Hourly', defaultMessage: 'Hourly',
}), }),
val: 'h' val: 'h',
}, },
{ {
display: i18n.translate('common.ui.aggTypes.buckets.intervalOptions.dailyDisplayName', { display: i18n.translate('common.ui.aggTypes.buckets.intervalOptions.dailyDisplayName', {
defaultMessage: 'Daily', defaultMessage: 'Daily',
}), }),
val: 'd' val: 'd',
}, },
{ {
display: i18n.translate('common.ui.aggTypes.buckets.intervalOptions.weeklyDisplayName', { display: i18n.translate('common.ui.aggTypes.buckets.intervalOptions.weeklyDisplayName', {
defaultMessage: 'Weekly', defaultMessage: 'Weekly',
}), }),
val: 'w' val: 'w',
}, },
{ {
display: i18n.translate('common.ui.aggTypes.buckets.intervalOptions.monthlyDisplayName', { display: i18n.translate('common.ui.aggTypes.buckets.intervalOptions.monthlyDisplayName', {
defaultMessage: 'Monthly', defaultMessage: 'Monthly',
}), }),
val: 'M' val: 'M',
}, },
{ {
display: i18n.translate('common.ui.aggTypes.buckets.intervalOptions.yearlyDisplayName', { display: i18n.translate('common.ui.aggTypes.buckets.intervalOptions.yearlyDisplayName', {
defaultMessage: 'Yearly', defaultMessage: 'Yearly',
}), }),
val: 'y' val: 'y',
} },
]; ];

View file

@ -59,7 +59,9 @@ const getAggResultBuckets = (aggConfigs, response, aggWithOtherBucket, key) => {
const aggKey = _.keys(responseAgg)[aggId]; const aggKey = _.keys(responseAgg)[aggId];
const aggConfig = _.find(aggConfigs.aggs, agg => agg.id === aggKey); const aggConfig = _.find(aggConfigs.aggs, agg => agg.id === aggKey);
const bucket = _.find(agg.buckets, (bucket, bucketObjKey) => { const bucket = _.find(agg.buckets, (bucket, bucketObjKey) => {
const bucketKey = aggConfig.getKey(bucket, Number.isInteger(bucketObjKey) ? null : bucketObjKey).toString(); const bucketKey = aggConfig
.getKey(bucket, Number.isInteger(bucketObjKey) ? null : bucketObjKey)
.toString();
return bucketKey === keyParts[i]; return bucketKey === keyParts[i];
}); });
if (bucket) { if (bucket) {
@ -93,7 +95,7 @@ const getAggConfigResultMissingBuckets = (responseAggs, aggId) => {
_.each(agg.buckets, bucket => { _.each(agg.buckets, bucket => {
resultBuckets = [ resultBuckets = [
...resultBuckets, ...resultBuckets,
...getAggConfigResultMissingBuckets(bucket, aggId, missingKey) ...getAggConfigResultMissingBuckets(bucket, aggId, missingKey),
]; ];
}); });
} }
@ -109,14 +111,11 @@ const getAggConfigResultMissingBuckets = (responseAggs, aggId) => {
* @param otherAgg: AggConfig of the aggregation with other bucket * @param otherAgg: AggConfig of the aggregation with other bucket
*/ */
const getOtherAggTerms = (requestAgg, key, otherAgg) => { const getOtherAggTerms = (requestAgg, key, otherAgg) => {
return requestAgg['other-filter'].filters.filters[key].bool.must_not.filter(filter => return requestAgg['other-filter'].filters.filters[key].bool.must_not
filter.match_phrase && filter.match_phrase[otherAgg.params.field.name] .filter(filter => filter.match_phrase && filter.match_phrase[otherAgg.params.field.name])
).map(filter => .map(filter => filter.match_phrase[otherAgg.params.field.name].query);
filter.match_phrase[otherAgg.params.field.name].query
);
}; };
export const buildOtherBucketAgg = (aggConfigs, aggWithOtherBucket, response) => { export const buildOtherBucketAgg = (aggConfigs, aggWithOtherBucket, response) => {
const bucketAggs = aggConfigs.aggs.filter(agg => agg.type.type === AggGroupNames.Buckets); const bucketAggs = aggConfigs.aggs.filter(agg => agg.type.type === AggGroupNames.Buckets);
const index = bucketAggs.findIndex(agg => agg.id === aggWithOtherBucket.id); const index = bucketAggs.findIndex(agg => agg.id === aggWithOtherBucket.id);
@ -124,15 +123,18 @@ export const buildOtherBucketAgg = (aggConfigs, aggWithOtherBucket, response) =>
const indexPattern = aggWithOtherBucket.params.field.indexPattern; const indexPattern = aggWithOtherBucket.params.field.indexPattern;
// create filters aggregation // create filters aggregation
const filterAgg = aggConfigs.createAggConfig({ const filterAgg = aggConfigs.createAggConfig(
type: 'filters', {
id: 'other', type: 'filters',
params: { id: 'other',
filters: [], params: {
filters: [],
},
}, },
}, { {
addToAggConfigs: false, addToAggConfigs: false,
}); }
);
// nest all the child aggregations of aggWithOtherBucket // nest all the child aggregations of aggWithOtherBucket
const resultAgg = { const resultAgg = {
@ -156,16 +158,33 @@ export const buildOtherBucketAgg = (aggConfigs, aggWithOtherBucket, response) =>
const currentAgg = bucketAggs[aggIndex]; const currentAgg = bucketAggs[aggIndex];
if (aggIndex < index) { if (aggIndex < index) {
_.each(agg.buckets, (bucket, bucketObjKey) => { _.each(agg.buckets, (bucket, bucketObjKey) => {
const bucketKey = currentAgg.getKey(bucket, Number.isInteger(bucketObjKey) ? null : bucketObjKey); const bucketKey = currentAgg.getKey(
bucket,
Number.isInteger(bucketObjKey) ? null : bucketObjKey
);
const filter = _.cloneDeep(bucket.filters) || currentAgg.createFilter(bucketKey); const filter = _.cloneDeep(bucket.filters) || currentAgg.createFilter(bucketKey);
const newFilters = _.flatten([...filters, filter]); const newFilters = _.flatten([...filters, filter]);
walkBucketTree(newAggIndex, bucket, newAgg.id, newFilters, `${key}-${bucketKey.toString()}`); walkBucketTree(
newAggIndex,
bucket,
newAgg.id,
newFilters,
`${key}-${bucketKey.toString()}`
);
}); });
return; return;
} }
if (!aggWithOtherBucket.params.missingBucket || agg.buckets.some(bucket => bucket.key === '__missing__')) { if (
filters.push(buildExistsFilter(aggWithOtherBucket.params.field, aggWithOtherBucket.params.field.indexPattern)); !aggWithOtherBucket.params.missingBucket ||
agg.buckets.some(bucket => bucket.key === '__missing__')
) {
filters.push(
buildExistsFilter(
aggWithOtherBucket.params.field,
aggWithOtherBucket.params.field.indexPattern
)
);
} }
// create not filters for all the buckets // create not filters for all the buckets
@ -177,7 +196,7 @@ export const buildOtherBucketAgg = (aggConfigs, aggWithOtherBucket, response) =>
}); });
resultAgg.filters.filters[key] = { resultAgg.filters.filters[key] = {
bool: buildQueryFromFilters(filters, indexPattern) bool: buildQueryFromFilters(filters, indexPattern),
}; };
}; };
walkBucketTree(0, response.aggregations, bucketAggs[0].id, [], ''); walkBucketTree(0, response.aggregations, bucketAggs[0].id, [], '');
@ -189,26 +208,43 @@ export const buildOtherBucketAgg = (aggConfigs, aggWithOtherBucket, response) =>
return () => { return () => {
return { return {
'other-filter': resultAgg 'other-filter': resultAgg,
}; };
}; };
}; };
export const mergeOtherBucketAggResponse = (aggsConfig, response, otherResponse, otherAgg, requestAgg) => { export const mergeOtherBucketAggResponse = (
aggsConfig,
response,
otherResponse,
otherAgg,
requestAgg
) => {
const updatedResponse = _.cloneDeep(response); const updatedResponse = _.cloneDeep(response);
_.each(otherResponse.aggregations['other-filter'].buckets, (bucket, key) => { _.each(otherResponse.aggregations['other-filter'].buckets, (bucket, key) => {
if (!bucket.doc_count) return; if (!bucket.doc_count) return;
const bucketKey = key.replace(/^-/, ''); const bucketKey = key.replace(/^-/, '');
const aggResultBuckets = getAggResultBuckets(aggsConfig, updatedResponse.aggregations, otherAgg, bucketKey); const aggResultBuckets = getAggResultBuckets(
aggsConfig,
updatedResponse.aggregations,
otherAgg,
bucketKey
);
const requestFilterTerms = getOtherAggTerms(requestAgg, key, otherAgg); const requestFilterTerms = getOtherAggTerms(requestAgg, key, otherAgg);
const phraseFilter = buildPhrasesFilter(otherAgg.params.field, requestFilterTerms, otherAgg.params.field.indexPattern); const phraseFilter = buildPhrasesFilter(
otherAgg.params.field,
requestFilterTerms,
otherAgg.params.field.indexPattern
);
phraseFilter.meta.negate = true; phraseFilter.meta.negate = true;
bucket.filters = [ phraseFilter ]; bucket.filters = [phraseFilter];
bucket.key = '__other__'; bucket.key = '__other__';
if (aggResultBuckets.some(bucket => bucket.key === '__missing__')) { if (aggResultBuckets.some(bucket => bucket.key === '__missing__')) {
bucket.filters.push(buildExistsFilter(otherAgg.params.field, otherAgg.params.field.indexPattern)); bucket.filters.push(
buildExistsFilter(otherAgg.params.field, otherAgg.params.field.indexPattern)
);
} }
aggResultBuckets.push(bucket); aggResultBuckets.push(bucket);
}); });

View file

@ -17,7 +17,15 @@
* under the License. * under the License.
*/ */
import { AggConfig } from 'ui/agg_types/agg_config'; export enum BUCKET_TYPES {
FILTER = 'filter',
export function isStringType(type: AggConfig): boolean; HISTOGRAM = 'histogram',
export function isType(type: string): (agg: AggConfig) => boolean; IP_RANGE = 'ip_range',
DATE_RANGE = 'date_range',
RANGE = 'range',
TERMS = 'terms',
SIGNIFICANT_TERMS = 'significant_terms',
GEOHASH_GRID = 'geohash_grid',
GEOTILE_GRID = 'geotile_grid',
DATE_HISTOGRAM = 'date_histogram',
}

View file

@ -19,14 +19,19 @@
import moment from 'moment'; import moment from 'moment';
import { buildRangeFilter } from '@kbn/es-query'; import { buildRangeFilter } from '@kbn/es-query';
import { IBucketDateHistogramAggConfig } from '../date_histogram';
export function createFilterDateHistogram(agg, key) { export const createFilterDateHistogram = (agg: IBucketDateHistogramAggConfig, key: string) => {
const start = moment(key); const start = moment(key);
const interval = agg.buckets.getInterval(); const interval = agg.buckets.getInterval();
return buildRangeFilter(agg.params.field, { return buildRangeFilter(
gte: start.toISOString(), agg.params.field,
lt: start.add(interval).toISOString(), {
format: 'strict_date_optional_time' gte: start.toISOString(),
}, agg.getIndexPattern()); lt: start.add(interval).toISOString(),
} format: 'strict_date_optional_time',
},
agg.getIndexPattern()
);
};

View file

@ -17,19 +17,20 @@
* under the License. * under the License.
*/ */
import chrome from '../../../chrome'; import { buildRangeFilter, RangeFilterParams } from '@kbn/es-query';
import { npSetup } from 'ui/new_platform';
import { IBucketAggConfig } from '../_bucket_agg_type';
// @ts-ignore
import { dateRange } from '../../../utils/date_range'; import { dateRange } from '../../../utils/date_range';
import { buildRangeFilter } from '@kbn/es-query';
const config = chrome.getUiSettingsClient(); export const createFilterDateRange = (agg: IBucketAggConfig, rangeString: string) => {
const range = dateRange.parse(rangeString, npSetup.core.uiSettings.get('dateFormat'));
export function createFilterDateRange(agg, key) { const filter: RangeFilterParams = {};
const range = dateRange.parse(key, config.get('dateFormat'));
const filter = {};
if (range.from) filter.gte = range.from.toISOString(); if (range.from) filter.gte = range.from.toISOString();
if (range.to) filter.lt = range.to.toISOString(); if (range.to) filter.lt = range.to.toISOString();
if (range.to && range.from) filter.format = 'strict_date_optional_time'; if (range.to && range.from) filter.format = 'strict_date_optional_time';
return buildRangeFilter(agg.params.field, filter, agg.getIndexPattern()); return buildRangeFilter(agg.params.field, filter, agg.getIndexPattern());
} };

View file

@ -17,15 +17,17 @@
* under the License. * under the License.
*/ */
import { get } from 'lodash';
import { buildQueryFilter } from '@kbn/es-query'; import { buildQueryFilter } from '@kbn/es-query';
import _ from 'lodash'; import { IBucketAggConfig } from '../_bucket_agg_type';
export function createFilterFilters(aggConfig, key) { export const createFilterFilters = (aggConfig: IBucketAggConfig, key: string) => {
// have the aggConfig write agg dsl params // have the aggConfig write agg dsl params
const dslFilters = _.get(aggConfig.toDsl(), 'filters.filters'); const dslFilters: any = get(aggConfig.toDsl(), 'filters.filters');
const filter = dslFilters[key]; const filter = dslFilters[key];
const indexPattern = aggConfig.getIndexPattern();
if (filter) { if (filter && indexPattern && indexPattern.id) {
return buildQueryFilter(filter.query, aggConfig.getIndexPattern().id, key); return buildQueryFilter(filter.query, indexPattern.id, key);
} }
} };

View file

@ -17,15 +17,17 @@
* under the License. * under the License.
*/ */
import { buildRangeFilter } from '@kbn/es-query'; import { buildRangeFilter, RangeFilterParams } from '@kbn/es-query';
import { IBucketAggConfig } from '../_bucket_agg_type';
export function createFilterHistogram(aggConfig, key) { export const createFilterHistogram = (aggConfig: IBucketAggConfig, key: string) => {
const value = parseInt(key, 10); const value = parseInt(key, 10);
const params: RangeFilterParams = { gte: value, lt: value + aggConfig.params.interval };
return buildRangeFilter( return buildRangeFilter(
aggConfig.params.field, aggConfig.params.field,
{ gte: value, lt: value + aggConfig.params.interval }, params,
aggConfig.getIndexPattern(), aggConfig.getIndexPattern(),
aggConfig.fieldFormatter()(key) aggConfig.fieldFormatter()(key)
); );
} };

View file

@ -17,20 +17,27 @@
* under the License. * under the License.
*/ */
import { buildRangeFilter, RangeFilterParams } from '@kbn/es-query';
import { CidrMask } from '../../../utils/cidr_mask'; import { CidrMask } from '../../../utils/cidr_mask';
import { buildRangeFilter } from '@kbn/es-query'; import { IBucketAggConfig } from '../_bucket_agg_type';
export const createFilterIpRange = (aggConfig: IBucketAggConfig, key: string) => {
let range: RangeFilterParams;
export function createFilterIpRange(aggConfig, key) {
let range;
if (aggConfig.params.ipRangeType === 'mask') { if (aggConfig.params.ipRangeType === 'mask') {
range = new CidrMask(key).getRange(); range = new CidrMask(key).getRange();
} else { } else {
const [from, to] = key.split(/\s+to\s+/); const [from, to] = key.split(/\s+to\s+/);
range = { range = {
from: from === '-Infinity' ? -Infinity : from, from: from === '-Infinity' ? -Infinity : from,
to: to === 'Infinity' ? Infinity : to to: to === 'Infinity' ? Infinity : to,
}; };
} }
return buildRangeFilter(aggConfig.params.field, { gte: range.from, lte: range.to }, aggConfig.getIndexPattern()); return buildRangeFilter(
} aggConfig.params.field,
{ gte: range.from, lte: range.to },
aggConfig.getIndexPattern()
);
};

View file

@ -18,12 +18,13 @@
*/ */
import { buildRangeFilter } from '@kbn/es-query'; import { buildRangeFilter } from '@kbn/es-query';
import { IBucketAggConfig } from '../_bucket_agg_type';
export function createFilterRange(aggConfig, key) { export const createFilterRange = (aggConfig: IBucketAggConfig, params: any) => {
return buildRangeFilter( return buildRangeFilter(
aggConfig.params.field, aggConfig.params.field,
key, params,
aggConfig.getIndexPattern(), aggConfig.getIndexPattern(),
aggConfig.fieldFormatter()(key) aggConfig.fieldFormatter()(params)
); );
} };

View file

@ -17,9 +17,10 @@
* under the License. * under the License.
*/ */
import { buildPhraseFilter, buildPhrasesFilter, buildExistsFilter } from '@kbn/es-query'; import { Filter, buildPhraseFilter, buildPhrasesFilter, buildExistsFilter } from '@kbn/es-query';
import { IBucketAggConfig } from '../_bucket_agg_type';
export function createFilterTerms(aggConfig, key, params) { export const createFilterTerms = (aggConfig: IBucketAggConfig, key: string, params: any) => {
const field = aggConfig.params.field; const field = aggConfig.params.field;
const indexPattern = field.indexPattern; const indexPattern = field.indexPattern;
@ -29,9 +30,9 @@ export function createFilterTerms(aggConfig, key, params) {
const phraseFilter = buildPhrasesFilter(field, terms, indexPattern); const phraseFilter = buildPhrasesFilter(field, terms, indexPattern);
phraseFilter.meta.negate = true; phraseFilter.meta.negate = true;
const filters = [phraseFilter]; const filters: Filter[] = [phraseFilter];
if (terms.some(term => term === '__missing__')) { if (terms.some((term: string) => term === '__missing__')) {
filters.push(buildExistsFilter(field, indexPattern)); filters.push(buildExistsFilter(field, indexPattern));
} }
@ -42,4 +43,4 @@ export function createFilterTerms(aggConfig, key, params) {
return existsFilter; return existsFilter;
} }
return buildPhraseFilter(field, key, indexPattern); return buildPhraseFilter(field, key, indexPattern);
} };

View file

@ -18,10 +18,11 @@
*/ */
import _ from 'lodash'; import _ from 'lodash';
import chrome from '../../chrome';
import moment from 'moment-timezone'; import moment from 'moment-timezone';
import { BucketAggType } from './_bucket_agg_type'; import { i18n } from '@kbn/i18n';
import { TimeBuckets } from '../../time_buckets'; import { BUCKET_TYPES } from 'ui/agg_types/buckets/bucket_agg_types';
import chrome from '../../chrome';
import { BucketAggParam, BucketAggType, IBucketAggConfig } from './_bucket_agg_type';
import { createFilterDateHistogram } from './create_filter/date_histogram'; import { createFilterDateHistogram } from './create_filter/date_histogram';
import { intervalOptions } from './_interval_options'; import { intervalOptions } from './_interval_options';
import { TimeIntervalParamEditor } from '../../vis/editors/default/controls/time_interval'; import { TimeIntervalParamEditor } from '../../vis/editors/default/controls/time_interval';
@ -29,51 +30,72 @@ import { timefilter } from '../../timefilter';
import { DropPartialsParamEditor } from '../../vis/editors/default/controls/drop_partials'; import { DropPartialsParamEditor } from '../../vis/editors/default/controls/drop_partials';
import { ScaleMetricsParamEditor } from '../../vis/editors/default/controls/scale_metrics'; import { ScaleMetricsParamEditor } from '../../vis/editors/default/controls/scale_metrics';
import { dateHistogramInterval } from '../../../../core_plugins/data/public'; import { dateHistogramInterval } from '../../../../core_plugins/data/public';
import { i18n } from '@kbn/i18n';
import { writeParams } from '../agg_params'; import { writeParams } from '../agg_params';
import { AggConfigs } from '../agg_configs';
import { AggConfig } from '../agg_config';
import { isMetricAggType } from '../metrics/metric_agg_type';
import { KBN_FIELD_TYPES } from '../../../../../plugins/data/common';
// @ts-ignore
import { TimeBuckets } from '../../time_buckets';
const config = chrome.getUiSettingsClient(); const config = chrome.getUiSettingsClient();
const detectedTimezone = moment.tz.guess(); const detectedTimezone = moment.tz.guess();
const tzOffset = moment().format('Z'); const tzOffset = moment().format('Z');
function getInterval(agg) { const getInterval = (agg: IBucketAggConfig): string => _.get(agg, ['params', 'interval']);
return _.get(agg, ['params', 'interval']);
}
export function setBounds(agg, force) { export const setBounds = (agg: IBucketDateHistogramAggConfig, force?: boolean) => {
if (agg.buckets._alreadySet && !force) return; if (agg.buckets._alreadySet && !force) return;
agg.buckets._alreadySet = true; agg.buckets._alreadySet = true;
const bounds = agg.params.timeRange ? timefilter.calculateBounds(agg.params.timeRange) : null; const bounds = agg.params.timeRange ? timefilter.calculateBounds(agg.params.timeRange) : null;
agg.buckets.setBounds(agg.fieldIsTimeField() && bounds); agg.buckets.setBounds(agg.fieldIsTimeField() && bounds);
};
// will be replaced by src/legacy/ui/public/time_buckets/time_buckets.js
interface TimeBuckets {
_alreadySet?: boolean;
setBounds: Function;
getScaledDateFormatter: Function;
setInterval: Function;
getInterval: Function;
} }
export interface IBucketDateHistogramAggConfig extends IBucketAggConfig {
buckets: TimeBuckets;
}
export const dateHistogramBucketAgg = new BucketAggType({ export function isDateHistogramBucketAggConfig(agg: any): agg is IBucketDateHistogramAggConfig {
name: 'date_histogram', return Boolean(agg.buckets);
}
export const dateHistogramBucketAgg = new BucketAggType<IBucketDateHistogramAggConfig>({
name: BUCKET_TYPES.DATE_HISTOGRAM,
title: i18n.translate('common.ui.aggTypes.buckets.dateHistogramTitle', { title: i18n.translate('common.ui.aggTypes.buckets.dateHistogramTitle', {
defaultMessage: 'Date Histogram', defaultMessage: 'Date Histogram',
}), }),
ordered: { ordered: {
date: true date: true,
}, },
makeLabel: function (agg) { makeLabel(agg) {
const output = writeParams(this.params, agg); const output: Record<string, any> = writeParams(this.params as BucketAggParam[], agg);
const field = agg.getFieldDisplayName(); const field = agg.getFieldDisplayName();
return i18n.translate('common.ui.aggTypes.buckets.dateHistogramLabel', { return i18n.translate('common.ui.aggTypes.buckets.dateHistogramLabel', {
defaultMessage: '{fieldName} per {intervalDescription}', defaultMessage: '{fieldName} per {intervalDescription}',
values: { values: {
fieldName: field, fieldName: field,
intervalDescription: output.metricScaleText || output.bucketInterval.description, intervalDescription: output.metricScaleText || output.bucketInterval.description,
} },
}); });
}, },
createFilter: createFilterDateHistogram, createFilter: createFilterDateHistogram,
decorateAggConfig: function () { decorateAggConfig() {
let buckets; let buckets: any;
return { return {
buckets: { buckets: {
configurable: true, configurable: true,
get: function () { get() {
if (buckets) return buckets; if (buckets) return buckets;
buckets = new TimeBuckets(); buckets = new TimeBuckets();
@ -81,28 +103,28 @@ export const dateHistogramBucketAgg = new BucketAggType({
setBounds(this); setBounds(this);
return buckets; return buckets;
} },
} },
}; };
}, },
getFormat: function (agg) { getFormat(agg) {
return agg.buckets.getScaledDateFormatter(); return agg.buckets.getScaledDateFormatter();
}, },
params: [ params: [
{ {
name: 'field', name: 'field',
type: 'field', type: 'field',
filterFieldTypes: 'date', filterFieldTypes: KBN_FIELD_TYPES.DATE,
default: function (agg) { default(agg: IBucketDateHistogramAggConfig) {
return agg.getIndexPattern().timeFieldName; return agg.getIndexPattern().timeFieldName;
}, },
onChange: function (agg) { onChange(agg: IBucketDateHistogramAggConfig) {
if (_.get(agg, 'params.interval') === 'auto' && !agg.fieldIsTimeField()) { if (_.get(agg, 'params.interval') === 'auto' && !agg.fieldIsTimeField()) {
delete agg.params.interval; delete agg.params.interval;
} }
setBounds(agg, true); setBounds(agg, true);
} },
}, },
{ {
name: 'timeRange', name: 'timeRange',
@ -124,7 +146,7 @@ export const dateHistogramBucketAgg = new BucketAggType({
{ {
name: 'interval', name: 'interval',
editorComponent: TimeIntervalParamEditor, editorComponent: TimeIntervalParamEditor,
deserialize: function (state, agg) { deserialize(state: any, agg: IBucketDateHistogramAggConfig) {
// For upgrading from 7.0.x to 7.1.x - intervals are now stored as key of options or custom value // For upgrading from 7.0.x to 7.1.x - intervals are now stored as key of options or custom value
if (state === 'custom') { if (state === 'custom') {
return _.get(agg, 'params.customInterval'); return _.get(agg, 'params.customInterval');
@ -141,10 +163,10 @@ export const dateHistogramBucketAgg = new BucketAggType({
}, },
default: 'auto', default: 'auto',
options: intervalOptions, options: intervalOptions,
modifyAggConfigOnSearchRequestStart: function (agg) { modifyAggConfigOnSearchRequestStart(agg: IBucketDateHistogramAggConfig) {
setBounds(agg, true); setBounds(agg, true);
}, },
write: function (agg, output, aggs) { write(agg: IBucketDateHistogramAggConfig, output: Record<string, any>, aggs: AggConfigs) {
setBounds(agg, true); setBounds(agg, true);
agg.buckets.setInterval(getInterval(agg)); agg.buckets.setInterval(getInterval(agg));
const { useNormalizedEsInterval, scaleMetricValues } = agg.params; const { useNormalizedEsInterval, scaleMetricValues } = agg.params;
@ -166,31 +188,41 @@ export const dateHistogramBucketAgg = new BucketAggType({
const scaleMetrics = scaleMetricValues && interval.scaled && interval.scale < 1; const scaleMetrics = scaleMetricValues && interval.scaled && interval.scale < 1;
if (scaleMetrics && aggs) { if (scaleMetrics && aggs) {
const metrics = aggs.aggs.filter(agg => agg.type && agg.type.type === 'metrics'); const metrics = aggs.aggs.filter(a => isMetricAggType(a.type));
const all = _.every(metrics, function (agg) { const all = _.every(metrics, (a: AggConfig) => {
return agg.type.isScalable(); const { type } = a;
if (isMetricAggType(type)) {
return type.isScalable();
}
}); });
if (all) { if (all) {
output.metricScale = interval.scale; output.metricScale = interval.scale;
output.metricScaleText = interval.preScaled.description; output.metricScaleText = interval.preScaled.description;
} }
} }
} },
}, } as BucketAggParam,
{ {
name: 'time_zone', name: 'time_zone',
default: undefined, default: undefined,
// We don't ever want this parameter to be serialized out (when saving or to URLs) // We don't ever want this parameter to be serialized out (when saving or to URLs)
// since we do all the logic handling it "on the fly" in the `write` method, to prevent // since we do all the logic handling it "on the fly" in the `write` method, to prevent
// time_zones being persisted into saved_objects // time_zones being persisted into saved_objects
serialize: () => undefined, serialize: _.noop,
write: (agg, output) => { write(agg: IBucketDateHistogramAggConfig, output: Record<string, any>) {
// If a time_zone has been set explicitly always prefer this. // If a time_zone has been set explicitly always prefer this.
let tz = agg.params.time_zone; let tz = agg.params.time_zone;
if (!tz && agg.params.field) { if (!tz && agg.params.field) {
// If a field has been configured check the index pattern's typeMeta if a date_histogram on that // If a field has been configured check the index pattern's typeMeta if a date_histogram on that
// field requires a specific time_zone // field requires a specific time_zone
tz = _.get(agg.getIndexPattern(), ['typeMeta', 'aggs', 'date_histogram', agg.params.field.name, 'time_zone']); tz = _.get(agg.getIndexPattern(), [
'typeMeta',
'aggs',
'date_histogram',
agg.params.field.name,
'time_zone',
]);
} }
if (!tz) { if (!tz) {
// If the index pattern typeMeta data, didn't had a time zone assigned for the selected field use the configured tz // If the index pattern typeMeta data, didn't had a time zone assigned for the selected field use the configured tz
@ -199,7 +231,7 @@ export const dateHistogramBucketAgg = new BucketAggType({
} }
output.params.time_zone = tz; output.params.time_zone = tz;
}, },
}, } as BucketAggParam,
{ {
name: 'drop_partials', name: 'drop_partials',
default: false, default: false,
@ -211,29 +243,27 @@ export const dateHistogramBucketAgg = new BucketAggType({
}, },
}, },
{ {
name: 'format' name: 'format',
}, },
{ {
name: 'min_doc_count', name: 'min_doc_count',
default: 1 default: 1,
}, },
{ {
name: 'extended_bounds', name: 'extended_bounds',
default: {}, default: {},
write: function (agg, output) { write(agg: IBucketDateHistogramAggConfig, output: Record<string, any>) {
const val = agg.params.extended_bounds; const val = agg.params.extended_bounds;
if (val.min != null || val.max != null) { if (val.min != null || val.max != null) {
output.params.extended_bounds = { output.params.extended_bounds = {
min: moment(val.min).valueOf(), min: moment(val.min).valueOf(),
max: moment(val.max).valueOf() max: moment(val.max).valueOf(),
}; };
return; return;
} }
} },
} } as BucketAggParam,
] ],
}); });

View file

@ -1,80 +0,0 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { get } from 'lodash';
import chrome from '../../chrome';
import moment from 'moment-timezone';
import { dateRange } from '../../utils/date_range';
import { BucketAggType } from './_bucket_agg_type';
import { createFilterDateRange } from './create_filter/date_range';
import { fieldFormats } from '../../registry/field_formats';
import { DateRangesParamEditor } from '../../vis/editors/default/controls/date_ranges';
import { i18n } from '@kbn/i18n';
const config = chrome.getUiSettingsClient();
const detectedTimezone = moment.tz.guess();
const tzOffset = moment().format('Z');
export const dateRangeBucketAgg = new BucketAggType({
name: 'date_range',
title: i18n.translate('common.ui.aggTypes.buckets.dateRangeTitle', {
defaultMessage: 'Date Range',
}),
createFilter: createFilterDateRange,
getKey: function (bucket, key, agg) {
const formatter = agg.fieldOwnFormatter('text', fieldFormats.getDefaultInstance('date'));
return dateRange.toString(bucket, formatter);
},
getFormat: function () {
return fieldFormats.getDefaultInstance('string');
},
makeLabel: function (aggConfig) {
return aggConfig.getFieldDisplayName() + ' date ranges';
},
params: [{
name: 'field',
type: 'field',
filterFieldTypes: 'date',
default: function (agg) {
return agg.getIndexPattern().timeFieldName;
}
}, {
name: 'ranges',
default: [{
from: 'now-1w/w',
to: 'now'
}],
editorComponent: DateRangesParamEditor,
}, {
name: 'time_zone',
default: undefined,
// Implimentation method is the same as that of date_histogram
serialize: () => undefined,
write: (agg, output) => {
let tz = agg.params.time_zone;
if (!tz && agg.params.field) {
tz = get(agg.getIndexPattern(), ['typeMeta', 'aggs', 'date_range', agg.params.field.name, 'time_zone']);
}
if (!tz) {
const isDefaultTimezone = config.isDefault('dateFormat:tz');
tz = isDefaultTimezone ? detectedTimezone || tzOffset : config.get('dateFormat:tz');
}
output.params.time_zone = tz;
}
}]
});

View file

@ -0,0 +1,102 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { get } from 'lodash';
import moment from 'moment-timezone';
import { i18n } from '@kbn/i18n';
import { npStart } from 'ui/new_platform';
import { BUCKET_TYPES } from './bucket_agg_types';
import { BucketAggType } from './_bucket_agg_type';
import { createFilterDateRange } from './create_filter/date_range';
import { AggConfig } from '../agg_config';
import { DateRangesParamEditor } from '../../vis/editors/default/controls/date_ranges';
// @ts-ignore
import { fieldFormats } from '../../registry/field_formats';
// @ts-ignore
import { dateRange } from '../../utils/date_range';
import { KBN_FIELD_TYPES } from '../../../../../plugins/data/common';
const dateRangeTitle = i18n.translate('common.ui.aggTypes.buckets.dateRangeTitle', {
defaultMessage: 'Date Range',
});
export const dateRangeBucketAgg = new BucketAggType({
name: BUCKET_TYPES.DATE_RANGE,
title: dateRangeTitle,
createFilter: createFilterDateRange,
getKey(bucket, key, agg) {
const formatter = agg.fieldOwnFormatter('text', fieldFormats.getDefaultInstance('date'));
return dateRange.toString(bucket, formatter);
},
getFormat() {
return fieldFormats.getDefaultInstance('string');
},
makeLabel(aggConfig) {
return aggConfig.getFieldDisplayName() + ' date ranges';
},
params: [
{
name: 'field',
type: 'field',
filterFieldTypes: KBN_FIELD_TYPES.DATE,
default(agg: AggConfig) {
return agg.getIndexPattern().timeFieldName;
},
},
{
name: 'ranges',
default: [
{
from: 'now-1w/w',
to: 'now',
},
],
editorComponent: DateRangesParamEditor,
},
{
name: 'time_zone',
default: undefined,
// Implimentation method is the same as that of date_histogram
serialize: () => undefined,
write: (agg: AggConfig, output: Record<string, any>) => {
const field = agg.getParam('field');
let tz = agg.getParam('time_zone');
if (!tz && field) {
tz = get(agg.getIndexPattern(), [
'typeMeta',
'aggs',
'date_range',
field.name,
'time_zone',
]);
}
if (!tz) {
const config = npStart.core.uiSettings;
const detectedTimezone = moment.tz.guess();
const tzOffset = moment().format('Z');
const isDefaultTimezone = config.isDefault('dateFormat:tz');
tz = isDefaultTimezone ? detectedTimezone || tzOffset : config.get('dateFormat:tz');
}
output.params.time_zone = tz;
},
},
],
});

View file

@ -16,18 +16,20 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * under the License.
*/ */
import { BucketAggType } from './_bucket_agg_type';
import { i18n } from '@kbn/i18n'; import { i18n } from '@kbn/i18n';
import { BucketAggType } from './_bucket_agg_type';
import { BUCKET_TYPES } from './bucket_agg_types';
const filterTitle = i18n.translate('common.ui.aggTypes.buckets.filterTitle', {
defaultMessage: 'Filter',
});
export const filterBucketAgg = new BucketAggType({ export const filterBucketAgg = new BucketAggType({
name: 'filter', name: BUCKET_TYPES.FILTER,
title: i18n.translate('common.ui.aggTypes.buckets.filterTitle', { title: filterTitle,
defaultMessage: 'Filter',
}),
params: [ params: [
{ {
name: 'geo_bounding_box' name: 'geo_bounding_box',
} },
] ],
}); });

View file

@ -20,69 +20,79 @@
import _ from 'lodash'; import _ from 'lodash';
import angular from 'angular'; import angular from 'angular';
import { BucketAggType } from './_bucket_agg_type';
import { createFilterFilters } from './create_filter/filters';
import { FiltersParamEditor } from '../../vis/editors/default/controls/filters';
import { i18n } from '@kbn/i18n'; import { i18n } from '@kbn/i18n';
import { Storage } from 'ui/storage'; import { Storage } from 'ui/storage';
import chrome from 'ui/chrome'; import chrome from 'ui/chrome';
import { buildEsQuery } from '@kbn/es-query'; import { buildEsQuery } from '@kbn/es-query';
import { FiltersParamEditor, FilterValue } from '../../vis/editors/default/controls/filters';
import { createFilterFilters } from './create_filter/filters';
import { BucketAggType, IBucketAggConfig } from './_bucket_agg_type';
import { setup as data } from '../../../../core_plugins/data/public/legacy'; import { setup as data } from '../../../../core_plugins/data/public/legacy';
const { getQueryLog } = data.query.helpers; const { getQueryLog } = data.query.helpers;
const config = chrome.getUiSettingsClient(); const config = chrome.getUiSettingsClient();
const storage = new Storage(window.localStorage); const storage = new Storage(window.localStorage);
const filtersTitle = i18n.translate('common.ui.aggTypes.buckets.filtersTitle', {
defaultMessage: 'Filters',
description:
'The name of an aggregation, that allows to specify multiple individual filters to group data by.',
});
export const filtersBucketAgg = new BucketAggType({ export const filtersBucketAgg = new BucketAggType({
name: 'filters', name: 'filters',
title: i18n.translate('common.ui.aggTypes.buckets.filtersTitle', { title: filtersTitle,
defaultMessage: 'Filters',
description: 'The name of an aggregation, that allows to specify multiple individual filters to group data by.'
}),
createFilter: createFilterFilters, createFilter: createFilterFilters,
customLabels: false, customLabels: false,
params: [ params: [
{ {
name: 'filters', name: 'filters',
editorComponent: FiltersParamEditor, editorComponent: FiltersParamEditor,
default: [ { input: { query: '', language: config.get('search:queryLanguage') }, label: '' } ], default: [{ input: { query: '', language: config.get('search:queryLanguage') }, label: '' }],
write: function (aggConfig, output) { write(aggConfig: IBucketAggConfig, output: Record<string, any>) {
const inFilters = aggConfig.params.filters; const inFilters: FilterValue[] = aggConfig.params.filters;
if (!_.size(inFilters)) return; if (!_.size(inFilters)) return;
inFilters.forEach((filter) => { inFilters.forEach(filter => {
const persistedLog = getQueryLog(config, storage, 'filtersAgg', filter.input.language); const persistedLog = getQueryLog(config, storage, 'filtersAgg', filter.input.language);
persistedLog.add(filter.input.query); persistedLog.add(filter.input.query);
}); });
const outFilters = _.transform(inFilters, function (filters, filter) { const outFilters = _.transform(
const input = _.cloneDeep(filter.input); inFilters,
function(filters, filter) {
const input = _.cloneDeep(filter.input);
if (!input) { if (!input) {
console.log('malformed filter agg params, missing "input" query'); // eslint-disable-line no-console console.log('malformed filter agg params, missing "input" query'); // eslint-disable-line no-console
return; return;
} }
const query = buildEsQuery(aggConfig.getIndexPattern(), [input], [], config); const query = buildEsQuery(aggConfig.getIndexPattern(), [input], [], config);
if (!query) { if (!query) {
console.log('malformed filter agg params, missing "query" on input'); // eslint-disable-line no-console console.log('malformed filter agg params, missing "query" on input'); // eslint-disable-line no-console
return; return;
} }
const matchAllLabel = filter.input.query === '' ? '*' : ''; const matchAllLabel = filter.input.query === '' ? '*' : '';
const label = filter.label const label =
|| matchAllLabel filter.label ||
|| (typeof filter.input.query === 'string' ? filter.input.query : angular.toJson(filter.input.query)); matchAllLabel ||
filters[label] = { query }; (typeof filter.input.query === 'string'
}, {}); ? filter.input.query
: angular.toJson(filter.input.query));
filters[label] = { query };
},
{}
);
if (!_.size(outFilters)) return; if (!_.size(outFilters)) return;
const params = output.params || (output.params = {}); const params = output.params || (output.params = {});
params.filters = outFilters; params.filters = outFilters;
} },
} },
] ],
}); });

View file

@ -17,16 +17,21 @@
* under the License. * under the License.
*/ */
import { i18n } from '@kbn/i18n';
import chrome from '../../chrome'; import chrome from '../../chrome';
import { BucketAggType } from './_bucket_agg_type'; import { BucketAggType, IBucketAggConfig } from './_bucket_agg_type';
import { AutoPrecisionParamEditor } from '../../vis/editors/default/controls/auto_precision'; import { AutoPrecisionParamEditor } from '../../vis/editors/default/controls/auto_precision';
import { UseGeocentroidParamEditor } from '../../vis/editors/default/controls/use_geocentroid'; import { UseGeocentroidParamEditor } from '../../vis/editors/default/controls/use_geocentroid';
import { IsFilteredByCollarParamEditor } from '../../vis/editors/default/controls/is_filtered_by_collar'; import { IsFilteredByCollarParamEditor } from '../../vis/editors/default/controls/is_filtered_by_collar';
import { PrecisionParamEditor } from '../../vis/editors/default/controls/precision'; import { PrecisionParamEditor } from '../../vis/editors/default/controls/precision';
import { geohashColumns } from '../../utils/decode_geo_hash'; import { geohashColumns } from '../../utils/decode_geo_hash';
import { geoContains, scaleBounds } from '../../utils/geo_utils';
import { i18n } from '@kbn/i18n';
import { AggGroupNames } from '../../vis/editors/default/agg_groups'; import { AggGroupNames } from '../../vis/editors/default/agg_groups';
import { AggConfig } from '../agg_config';
import { KBN_FIELD_TYPES } from '../../../../../plugins/data/common';
// @ts-ignore
import { geoContains, scaleBounds } from '../../utils/geo_utils';
import { BUCKET_TYPES } from './bucket_agg_types';
const config = chrome.getUiSettingsClient(); const config = chrome.getUiSettingsClient();
@ -36,14 +41,15 @@ const maxPrecision = parseInt(config.get('visualization:tileMap:maxPrecision'),
* Map Leaflet zoom levels to geohash precision levels. * Map Leaflet zoom levels to geohash precision levels.
* The size of a geohash column-width on the map should be at least `minGeohashPixels` pixels wide. * The size of a geohash column-width on the map should be at least `minGeohashPixels` pixels wide.
*/ */
const zoomPrecision = {}; const zoomPrecision: any = {};
const minGeohashPixels = 16; const minGeohashPixels = 16;
for (let zoom = 0; zoom <= 21; zoom += 1) { for (let zoom = 0; zoom <= 21; zoom += 1) {
const worldPixels = 256 * Math.pow(2, zoom); const worldPixels = 256 * Math.pow(2, zoom);
zoomPrecision[zoom] = 1; zoomPrecision[zoom] = 1;
for (let precision = 2; precision <= maxPrecision; precision += 1) { for (let precision = 2; precision <= maxPrecision; precision += 1) {
const columns = geohashColumns(precision); const columns = geohashColumns(precision);
if ((worldPixels / columns) >= minGeohashPixels) { if (worldPixels / columns >= minGeohashPixels) {
zoomPrecision[zoom] = precision; zoomPrecision[zoom] = precision;
} else { } else {
break; break;
@ -51,11 +57,10 @@ for (let zoom = 0; zoom <= 21; zoom += 1) {
} }
} }
function getPrecision(precision) { function getPrecision(val: string) {
let precision = parseInt(val, 10);
precision = parseInt(precision, 10); if (Number.isNaN(precision)) {
if (isNaN(precision)) {
precision = defaultPrecision; precision = defaultPrecision;
} }
@ -66,20 +71,29 @@ function getPrecision(precision) {
return precision; return precision;
} }
function isOutsideCollar(bounds, collar) { const isOutsideCollar = (bounds: unknown, collar: MapCollar) =>
return bounds && collar && !geoContains(collar, bounds); bounds && collar && !geoContains(collar, bounds);
const geohashGridTitle = i18n.translate('common.ui.aggTypes.buckets.geohashGridTitle', {
defaultMessage: 'Geohash',
});
interface MapCollar {
zoom: unknown;
} }
export const geoHashBucketAgg = new BucketAggType({ export interface IBucketGeoHashGridAggConfig extends IBucketAggConfig {
name: 'geohash_grid', lastMapCollar: MapCollar;
title: i18n.translate('common.ui.aggTypes.buckets.geohashGridTitle', { }
defaultMessage: 'Geohash',
}), export const geoHashBucketAgg = new BucketAggType<IBucketGeoHashGridAggConfig>({
name: BUCKET_TYPES.GEOHASH_GRID,
title: geohashGridTitle,
params: [ params: [
{ {
name: 'field', name: 'field',
type: 'field', type: 'field',
filterFieldTypes: 'geo_point' filterFieldTypes: KBN_FIELD_TYPES.GEO_POINT,
}, },
{ {
name: 'autoPrecision', name: 'autoPrecision',
@ -92,12 +106,13 @@ export const geoHashBucketAgg = new BucketAggType({
editorComponent: PrecisionParamEditor, editorComponent: PrecisionParamEditor,
default: defaultPrecision, default: defaultPrecision,
deserialize: getPrecision, deserialize: getPrecision,
write: function (aggConfig, output) { write(aggConfig, output) {
const currZoom = aggConfig.params.mapZoom; const currZoom = aggConfig.params.mapZoom;
const autoPrecisionVal = zoomPrecision[currZoom]; const autoPrecisionVal = zoomPrecision[currZoom];
output.params.precision = aggConfig.params.autoPrecision ? output.params.precision = aggConfig.params.autoPrecision
autoPrecisionVal : getPrecision(aggConfig.params.precision); ? autoPrecisionVal
} : getPrecision(aggConfig.params.precision);
},
}, },
{ {
name: 'useGeocentroid', name: 'useGeocentroid',
@ -125,17 +140,21 @@ export const geoHashBucketAgg = new BucketAggType({
name: 'mapBounds', name: 'mapBounds',
default: null, default: null,
write: () => {}, write: () => {},
} },
], ],
getRequestAggs: function (agg) { getRequestAggs(agg) {
const aggs = []; const aggs: AggConfig[] = [];
const params = agg.params; const params = agg.params;
if (params.isFilteredByCollar && agg.getField()) { if (params.isFilteredByCollar && agg.getField()) {
const { mapBounds, mapZoom } = params; const { mapBounds, mapZoom } = params;
if (mapBounds) { if (mapBounds) {
let mapCollar; let mapCollar;
if (!agg.lastMapCollar || agg.lastMapCollar.zoom !== mapZoom || isOutsideCollar(mapBounds, agg.lastMapCollar)) { if (
!agg.lastMapCollar ||
agg.lastMapCollar.zoom !== mapZoom ||
isOutsideCollar(mapBounds, agg.lastMapCollar)
) {
mapCollar = scaleBounds(mapBounds); mapCollar = scaleBounds(mapBounds);
mapCollar.zoom = mapZoom; mapCollar.zoom = mapZoom;
agg.lastMapCollar = mapCollar; agg.lastMapCollar = mapCollar;
@ -146,35 +165,45 @@ export const geoHashBucketAgg = new BucketAggType({
ignore_unmapped: true, ignore_unmapped: true,
[agg.getField().name]: { [agg.getField().name]: {
top_left: mapCollar.top_left, top_left: mapCollar.top_left,
bottom_right: mapCollar.bottom_right bottom_right: mapCollar.bottom_right,
}
};
aggs.push(agg.aggConfigs.createAggConfig({
type: 'filter',
id: 'filter_agg',
enabled: true,
params: {
geo_bounding_box: boundingBox
}, },
schema: { };
group: AggGroupNames.Buckets aggs.push(
} agg.aggConfigs.createAggConfig(
}, { addToAggConfigs: false })); {
type: 'filter',
id: 'filter_agg',
enabled: true,
params: {
geo_bounding_box: boundingBox,
},
schema: {
group: AggGroupNames.Buckets,
},
} as any,
{ addToAggConfigs: false }
)
);
} }
} }
aggs.push(agg); aggs.push(agg);
if (params.useGeocentroid) { if (params.useGeocentroid) {
aggs.push(agg.aggConfigs.createAggConfig({ aggs.push(
type: 'geo_centroid', agg.aggConfigs.createAggConfig(
enabled: true, {
params: { type: 'geo_centroid',
field: agg.getField() enabled: true,
} params: {
}, { addToAggConfigs: false })); field: agg.getField(),
},
},
{ addToAggConfigs: false }
)
);
} }
return aggs; return aggs as IBucketGeoHashGridAggConfig[];
} },
}); });

View file

@ -17,47 +17,56 @@
* under the License. * under the License.
*/ */
import _ from 'lodash';
import { BucketAggType } from './_bucket_agg_type';
import { i18n } from '@kbn/i18n'; import { i18n } from '@kbn/i18n';
import { noop } from 'lodash';
import { METRIC_TYPES } from 'ui/agg_types/metrics/metric_agg_types';
import { AggConfigOptions } from 'ui/agg_types/agg_config';
import { BucketAggType } from './_bucket_agg_type';
import { BUCKET_TYPES } from './bucket_agg_types';
import { KBN_FIELD_TYPES } from '../../../../../plugins/data/common';
const geotileGridTitle = i18n.translate('common.ui.aggTypes.buckets.geotileGridTitle', {
defaultMessage: 'Geotile',
});
export const geoTileBucketAgg = new BucketAggType({ export const geoTileBucketAgg = new BucketAggType({
name: 'geotile_grid', name: BUCKET_TYPES.GEOTILE_GRID,
title: i18n.translate('common.ui.aggTypes.buckets.geotileGridTitle', { title: geotileGridTitle,
defaultMessage: 'Geotile',
}),
params: [ params: [
{ {
name: 'field', name: 'field',
type: 'field', type: 'field',
filterFieldTypes: 'geo_point' filterFieldTypes: KBN_FIELD_TYPES.GEO_POINT,
}, },
{ {
name: 'useGeocentroid', name: 'useGeocentroid',
default: true, default: true,
write: _.noop write: noop,
}, },
{ {
name: 'precision', name: 'precision',
default: 0, default: 0,
} },
], ],
getRequestAggs: function (agg) { getRequestAggs(agg) {
const aggs = []; const aggs = [];
const params = agg.params; const useGeocentroid = agg.getParam('useGeocentroid');
aggs.push(agg); aggs.push(agg);
if (params.useGeocentroid) { if (useGeocentroid) {
aggs.push(agg.aggConfigs.createAggConfig({ const aggConfig: AggConfigOptions = {
type: 'geo_centroid', type: METRIC_TYPES.GEO_CENTROID,
enabled: true, enabled: true,
params: { params: {
field: agg.getField() field: agg.getField(),
} },
}, { addToAggConfigs: false })); };
aggs.push(agg.aggConfigs.createAggConfig(aggConfig, { addToAggConfigs: false }));
} }
return aggs; return aggs;
} },
}); });

View file

@ -18,51 +18,64 @@
*/ */
import _ from 'lodash'; import _ from 'lodash';
import { i18n } from '@kbn/i18n';
import { toastNotifications } from 'ui/notify'; import { toastNotifications } from 'ui/notify';
import chrome from '../../chrome'; import chrome from '../../chrome';
import { BucketAggType } from './_bucket_agg_type'; import { BucketAggType, IBucketAggConfig, BucketAggParam } from './_bucket_agg_type';
import { createFilterHistogram } from './create_filter/histogram'; import { createFilterHistogram } from './create_filter/histogram';
import { NumberIntervalParamEditor } from '../../vis/editors/default/controls/number_interval'; import { NumberIntervalParamEditor } from '../../vis/editors/default/controls/number_interval';
import { MinDocCountParamEditor } from '../../vis/editors/default/controls/min_doc_count'; import { MinDocCountParamEditor } from '../../vis/editors/default/controls/min_doc_count';
import { HasExtendedBoundsParamEditor } from '../../vis/editors/default/controls/has_extended_bounds'; import { HasExtendedBoundsParamEditor } from '../../vis/editors/default/controls/has_extended_bounds';
import { ExtendedBoundsParamEditor } from '../../vis/editors/default/controls/extended_bounds'; import { ExtendedBoundsParamEditor } from '../../vis/editors/default/controls/extended_bounds';
import { i18n } from '@kbn/i18n'; import { AggConfig } from '../agg_config';
import { KBN_FIELD_TYPES } from '../../../../../plugins/data/common';
import { BUCKET_TYPES } from './bucket_agg_types';
interface AutoBounds {
min: number;
max: number;
}
export interface IBucketHistogramAggConfig extends IBucketAggConfig {
setAutoBounds: (bounds: AutoBounds) => void;
getAutoBounds: () => AutoBounds;
}
const config = chrome.getUiSettingsClient(); const config = chrome.getUiSettingsClient();
export const histogramBucketAgg = new BucketAggType({ export const histogramBucketAgg = new BucketAggType<IBucketHistogramAggConfig>({
name: 'histogram', name: BUCKET_TYPES.HISTOGRAM,
title: i18n.translate('common.ui.aggTypes.buckets.histogramTitle', { title: i18n.translate('common.ui.aggTypes.buckets.histogramTitle', {
defaultMessage: 'Histogram', defaultMessage: 'Histogram',
}), }),
ordered: {}, ordered: {},
makeLabel: function (aggConfig) { makeLabel(aggConfig) {
return aggConfig.getFieldDisplayName(); return aggConfig.getFieldDisplayName();
}, },
createFilter: createFilterHistogram, createFilter: createFilterHistogram,
decorateAggConfig: function () { decorateAggConfig() {
let autoBounds; let autoBounds: AutoBounds;
return { return {
setAutoBounds: { setAutoBounds: {
configurable: true, configurable: true,
value(newValue) { value(newValue: AutoBounds) {
autoBounds = newValue; autoBounds = newValue;
} },
}, },
getAutoBounds: { getAutoBounds: {
configurable: true, configurable: true,
value() { value() {
return autoBounds; return autoBounds;
} },
} },
}; };
}, },
params: [ params: [
{ {
name: 'field', name: 'field',
type: 'field', type: 'field',
filterFieldTypes: 'number' filterFieldTypes: KBN_FIELD_TYPES.NUMBER,
}, },
{ {
/* /*
@ -76,7 +89,11 @@ export const histogramBucketAgg = new BucketAggType({
{ {
name: 'interval', name: 'interval',
editorComponent: NumberIntervalParamEditor, editorComponent: NumberIntervalParamEditor,
modifyAggConfigOnSearchRequestStart(aggConfig, searchSource, searchRequest) { modifyAggConfigOnSearchRequestStart(
aggConfig: IBucketHistogramAggConfig,
searchSource: any,
searchRequest: any
) {
const field = aggConfig.getField(); const field = aggConfig.getField();
const aggBody = field.scripted const aggBody = field.scripted
? { script: { source: field.script, lang: field.lang } } ? { script: { source: field.script, lang: field.lang } }
@ -87,31 +104,35 @@ export const histogramBucketAgg = new BucketAggType({
.setField('size', 0) .setField('size', 0)
.setField('aggs', { .setField('aggs', {
maxAgg: { maxAgg: {
max: aggBody max: aggBody,
}, },
minAgg: { minAgg: {
min: aggBody min: aggBody,
} },
}); });
searchRequest.whenAborted(() => childSearchSource.cancelQueued()); searchRequest.whenAborted(() => childSearchSource.cancelQueued());
return childSearchSource.fetch() return childSearchSource
.then((resp) => { .fetch()
.then((resp: any) => {
aggConfig.setAutoBounds({ aggConfig.setAutoBounds({
min: _.get(resp, 'aggregations.minAgg.value'), min: _.get(resp, 'aggregations.minAgg.value'),
max: _.get(resp, 'aggregations.maxAgg.value') max: _.get(resp, 'aggregations.maxAgg.value'),
}); });
}) })
.catch(e => { .catch((e: Error) => {
if (e.name === 'AbortError') return; if (e.name === 'AbortError') return;
toastNotifications.addWarning(i18n.translate('common.ui.aggTypes.histogram.missingMaxMinValuesWarning', { toastNotifications.addWarning(
// eslint-disable-next-line max-len i18n.translate('common.ui.aggTypes.histogram.missingMaxMinValuesWarning', {
defaultMessage: 'Unable to retrieve max and min values to auto-scale histogram buckets. This may lead to poor visualization performance.' // eslint-disable-next-line max-len
})); defaultMessage:
'Unable to retrieve max and min values to auto-scale histogram buckets. This may lead to poor visualization performance.',
})
);
}); });
}, },
write: function (aggConfig, output) { write(aggConfig: IBucketHistogramAggConfig, output: Record<string, any>) {
let interval = parseFloat(aggConfig.params.interval); let interval = parseFloat(aggConfig.params.interval);
if (interval <= 0) { if (interval <= 0) {
interval = 1; interval = 1;
@ -146,29 +167,26 @@ export const histogramBucketAgg = new BucketAggType({
} }
output.params.interval = interval; output.params.interval = interval;
} },
}, } as BucketAggParam,
{ {
name: 'min_doc_count', name: 'min_doc_count',
default: false, default: false,
editorComponent: MinDocCountParamEditor, editorComponent: MinDocCountParamEditor,
write: function (aggConfig, output) { write(aggConfig: AggConfig, output: Record<string, any>) {
if (aggConfig.params.min_doc_count) { if (aggConfig.params.min_doc_count) {
output.params.min_doc_count = 0; output.params.min_doc_count = 0;
} else { } else {
output.params.min_doc_count = 1; output.params.min_doc_count = 1;
} }
} },
}, },
{ {
name: 'has_extended_bounds', name: 'has_extended_bounds',
default: false, default: false,
editorComponent: HasExtendedBoundsParamEditor, editorComponent: HasExtendedBoundsParamEditor,
write: () => {}, write: () => {},
}, },
{ {
name: 'extended_bounds', name: 'extended_bounds',
default: { default: {
@ -176,16 +194,14 @@ export const histogramBucketAgg = new BucketAggType({
max: '', max: '',
}, },
editorComponent: ExtendedBoundsParamEditor, editorComponent: ExtendedBoundsParamEditor,
write: function (aggConfig, output) { write(aggConfig: AggConfig, output: Record<string, any>) {
const { min, max } = aggConfig.params.extended_bounds; const { min, max } = aggConfig.params.extended_bounds;
if (aggConfig.params.has_extended_bounds && if (aggConfig.params.has_extended_bounds && (min || min === 0) && (max || max === 0)) {
(min || min === 0) &&
(max || max === 0)) {
output.params.extended_bounds = { min, max }; output.params.extended_bounds = { min, max };
} }
}, },
shouldShow: aggConfig => aggConfig.params.has_extended_bounds shouldShow: (aggConfig: AggConfig) => aggConfig.params.has_extended_bounds,
} },
] ],
}); });

View file

@ -0,0 +1,29 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React, { ComponentType } from 'react';
import { AggParamEditorProps } from 'ui/vis/editors/default';
export const wrapWithInlineComp = <T extends unknown>(
WrapComponent: ComponentType<AggParamEditorProps<T>>
) => (props: AggParamEditorProps<T>) => (
<div className={`visEditorAggParam--half visEditorAggParam--half-${props.aggParam.name}`}>
<WrapComponent {...props} />
</div>
);

View file

@ -17,68 +17,72 @@
* under the License. * under the License.
*/ */
import _ from 'lodash'; import { get, noop, map, omit, isNull } from 'lodash';
import { BucketAggType } from './_bucket_agg_type'; import { i18n } from '@kbn/i18n';
import { createFilterIpRange } from './create_filter/ip_range'; import { BucketAggType, IBucketAggConfig } from './_bucket_agg_type';
import { IpRangeTypeParamEditor } from '../../vis/editors/default/controls/ip_range_type'; import { IpRangeTypeParamEditor } from '../../vis/editors/default/controls/ip_range_type';
import { IpRangesParamEditor } from '../../vis/editors/default/controls/ip_ranges'; import { IpRangesParamEditor } from '../../vis/editors/default/controls/ip_ranges';
import { i18n } from '@kbn/i18n'; import { BUCKET_TYPES } from './bucket_agg_types';
// @ts-ignore
import { createFilterIpRange } from './create_filter/ip_range';
import { KBN_FIELD_TYPES } from '../../../../../plugins/data/common';
const ipRangeTitle = i18n.translate('common.ui.aggTypes.buckets.ipRangeTitle', {
defaultMessage: 'IPv4 Range',
});
export const ipRangeBucketAgg = new BucketAggType({ export const ipRangeBucketAgg = new BucketAggType({
name: 'ip_range', name: BUCKET_TYPES.IP_RANGE,
title: i18n.translate('common.ui.aggTypes.buckets.ipRangeTitle', { title: ipRangeTitle,
defaultMessage: 'IPv4 Range',
}),
createFilter: createFilterIpRange, createFilter: createFilterIpRange,
getKey: function (bucket, key) { getKey(bucket, key) {
if (key) return key; if (key) return key;
const from = _.get(bucket, 'from', '-Infinity'); const from = get(bucket, 'from', '-Infinity');
const to = _.get(bucket, 'to', 'Infinity'); const to = get(bucket, 'to', 'Infinity');
return `${from} to ${to}`; return `${from} to ${to}`;
}, },
makeLabel: function (aggConfig) { makeLabel(aggConfig) {
return i18n.translate('common.ui.aggTypes.buckets.ipRangeLabel', { return i18n.translate('common.ui.aggTypes.buckets.ipRangeLabel', {
defaultMessage: '{fieldName} IP ranges', defaultMessage: '{fieldName} IP ranges',
values: { values: {
fieldName: aggConfig.getFieldDisplayName() fieldName: aggConfig.getFieldDisplayName(),
} },
}); });
}, },
params: [ params: [
{ {
name: 'field', name: 'field',
type: 'field', type: 'field',
filterFieldTypes: 'ip' filterFieldTypes: KBN_FIELD_TYPES.IP,
}, { },
{
name: 'ipRangeType', name: 'ipRangeType',
editorComponent: IpRangeTypeParamEditor, editorComponent: IpRangeTypeParamEditor,
default: 'fromTo', default: 'fromTo',
write: _.noop write: noop,
}, { },
{
name: 'ranges', name: 'ranges',
default: { default: {
fromTo: [ fromTo: [
{ from: '0.0.0.0', to: '127.255.255.255' }, { from: '0.0.0.0', to: '127.255.255.255' },
{ from: '128.0.0.0', to: '191.255.255.255' } { from: '128.0.0.0', to: '191.255.255.255' },
], ],
mask: [ mask: [{ mask: '0.0.0.0/1' }, { mask: '128.0.0.0/2' }],
{ mask: '0.0.0.0/1' },
{ mask: '128.0.0.0/2' }
]
}, },
editorComponent: IpRangesParamEditor, editorComponent: IpRangesParamEditor,
write: function (aggConfig, output) { write(aggConfig: IBucketAggConfig, output: Record<string, any>) {
const ipRangeType = aggConfig.params.ipRangeType; const ipRangeType = aggConfig.params.ipRangeType;
let ranges = aggConfig.params.ranges[ipRangeType]; let ranges = aggConfig.params.ranges[ipRangeType];
if (ipRangeType === 'fromTo') { if (ipRangeType === 'fromTo') {
ranges = _.map(ranges, (range) => { ranges = map(ranges, (range: any) => omit(range, isNull));
return _.omit(range, _.isNull);
});
} }
output.params.ranges = ranges; output.params.ranges = ranges;
} },
} },
] ],
}); });

View file

@ -18,35 +18,32 @@
*/ */
import { isString, isObject } from 'lodash'; import { isString, isObject } from 'lodash';
import { AggConfig } from 'ui/agg_types';
import { IBucketAggConfig, BucketAggType, BucketAggParam } from './_bucket_agg_type';
function isType(type) { export const isType = (type: string) => {
return function (agg) { return (agg: IBucketAggConfig): boolean => {
const field = agg.params.field; const field = agg.params.field;
return field && field.type === type; return field && field.type === type;
}; };
} };
const isStringType = isType('string'); export const isStringType = isType('string');
const migrateIncludeExcludeFormat = { export const migrateIncludeExcludeFormat = {
serialize: function (value, agg) { serialize(this: BucketAggParam, value: any, agg: AggConfig) {
if (this.shouldShow && !this.shouldShow(agg)) return; if (this.shouldShow && !this.shouldShow(agg)) return;
if (!value || isString(value)) return value; if (!value || isString(value)) return value;
else return value.pattern; else return value.pattern;
}, },
write: function (aggConfig, output) { write(this: BucketAggType<IBucketAggConfig>, aggConfig: AggConfig, output: Record<string, any>) {
const value = aggConfig.params[this.name]; const value = aggConfig.getParam(this.name);
if (isObject(value)) { if (isObject(value)) {
output.params[this.name] = value.pattern; output.params[this.name] = value.pattern;
} else if (value && isStringType(aggConfig)) { } else if (value && isStringType(aggConfig)) {
output.params[this.name] = value; output.params[this.name] = value;
} }
} },
};
export {
isType,
isStringType,
migrateIncludeExcludeFormat
}; };

View file

@ -17,32 +17,38 @@
* under the License. * under the License.
*/ */
import React from 'react'; import { i18n } from '@kbn/i18n';
import { IBucketAggConfig } from './_bucket_agg_type';
import { BucketAggType } from './_bucket_agg_type'; import { BucketAggType } from './_bucket_agg_type';
import { createFilterRange } from './create_filter/range';
import { FieldFormat } from '../../../../../plugins/data/common/field_formats'; import { FieldFormat } from '../../../../../plugins/data/common/field_formats';
import { RangeKey } from './range_key'; import { RangeKey } from './range_key';
import { RangesParamEditor } from '../../vis/editors/default/controls/ranges'; import { RangesEditor } from './range_editor';
import { i18n } from '@kbn/i18n';
// @ts-ignore
import { createFilterRange } from './create_filter/range';
import { BUCKET_TYPES } from './bucket_agg_types';
import { KBN_FIELD_TYPES } from '../../../../../plugins/data/common';
const keyCaches = new WeakMap(); const keyCaches = new WeakMap();
const formats = new WeakMap(); const formats = new WeakMap();
const rangeTitle = i18n.translate('common.ui.aggTypes.buckets.rangeTitle', {
defaultMessage: 'Range',
});
export const rangeBucketAgg = new BucketAggType({ export const rangeBucketAgg = new BucketAggType({
name: 'range', name: BUCKET_TYPES.RANGE,
title: i18n.translate('common.ui.aggTypes.buckets.rangeTitle', { title: rangeTitle,
defaultMessage: 'Range',
}),
createFilter: createFilterRange, createFilter: createFilterRange,
makeLabel: function (aggConfig) { makeLabel(aggConfig) {
return i18n.translate('common.ui.aggTypes.buckets.rangesLabel', { return i18n.translate('common.ui.aggTypes.buckets.rangesLabel', {
defaultMessage: '{fieldName} ranges', defaultMessage: '{fieldName} ranges',
values: { values: {
fieldName: aggConfig.getFieldDisplayName() fieldName: aggConfig.getFieldDisplayName(),
} },
}); });
}, },
getKey: function (bucket, key, agg) { getKey(bucket, key, agg) {
let keys = keyCaches.get(agg); let keys = keyCaches.get(agg);
if (!keys) { if (!keys) {
@ -60,11 +66,11 @@ export const rangeBucketAgg = new BucketAggType({
return key; return key;
}, },
getFormat: function (agg) { getFormat(agg) {
let format = formats.get(agg); let aggFormat = formats.get(agg);
if (format) return format; if (aggFormat) return aggFormat;
const RangeFormat = FieldFormat.from(function (range) { const RangeFormat = FieldFormat.from((range: any) => {
const format = agg.fieldOwnFormatter(); const format = agg.fieldOwnFormatter();
const gte = '\u2265'; const gte = '\u2265';
const lt = '\u003c'; const lt = '\u003c';
@ -79,28 +85,25 @@ export const rangeBucketAgg = new BucketAggType({
}); });
}); });
format = new RangeFormat(); aggFormat = new RangeFormat();
formats.set(agg, format); formats.set(agg, aggFormat);
return format; return aggFormat;
}, },
params: [ params: [
{ {
name: 'field', name: 'field',
type: 'field', type: 'field',
filterFieldTypes: ['number'] filterFieldTypes: [KBN_FIELD_TYPES.NUMBER],
}, },
{ {
name: 'ranges', name: 'ranges',
default: [ default: [{ from: 0, to: 1000 }, { from: 1000, to: 2000 }],
{ from: 0, to: 1000 }, editorComponent: RangesEditor,
{ from: 1000, to: 2000 } write(aggConfig: IBucketAggConfig, output: Record<string, any>) {
],
editorComponent: ({ value, setValue }) => <RangesParamEditor value={value} setValue={setValue}/>,
write: function (aggConfig, output) {
output.params.ranges = aggConfig.params.ranges; output.params.ranges = aggConfig.params.ranges;
output.params.keyed = true; output.params.keyed = true;
} },
} },
] ],
}); });

View file

@ -18,10 +18,9 @@
*/ */
import React from 'react'; import React from 'react';
import { AggParamEditorProps } from 'ui/vis/editors/default';
import { RangesParamEditor } from '../../vis/editors/default/controls/ranges';
const wrapWithInlineComp = Component => props => ( export const RangesEditor = (props: AggParamEditorProps<any>) => (
<div className={`visEditorAggParam--half visEditorAggParam--half-${props.aggParam.name}`}> <RangesParamEditor value={props.value} setValue={props.setValue} />
<Component {...props}/> );
</div>);
export { wrapWithInlineComp };

View file

@ -19,16 +19,19 @@
const id = Symbol('id'); const id = Symbol('id');
class RangeKey { export class RangeKey {
constructor(bucket) { [id]: string;
gte: string | number;
lt: string | number;
constructor(bucket: any) {
this.gte = bucket.from == null ? -Infinity : bucket.from; this.gte = bucket.from == null ? -Infinity : bucket.from;
this.lt = bucket.to == null ? +Infinity : bucket.to; this.lt = bucket.to == null ? +Infinity : bucket.to;
this[id] = RangeKey.idBucket(bucket); this[id] = RangeKey.idBucket(bucket);
} }
static idBucket(bucket: any) {
static idBucket(bucket) {
return `from:${bucket.from},to:${bucket.to}`; return `from:${bucket.from},to:${bucket.to}`;
} }
@ -36,5 +39,3 @@ class RangeKey {
return this[id]; return this[id];
} }
} }
export { RangeKey };

View file

@ -19,22 +19,26 @@
import { i18n } from '@kbn/i18n'; import { i18n } from '@kbn/i18n';
import { SizeParamEditor } from '../../vis/editors/default/controls/size'; import { SizeParamEditor } from '../../vis/editors/default/controls/size';
import { BucketAggType } from './_bucket_agg_type'; import { BucketAggType, BucketAggParam } from './_bucket_agg_type';
import { createFilterTerms } from './create_filter/terms'; import { createFilterTerms } from './create_filter/terms';
import { isStringType, migrateIncludeExcludeFormat } from './migrate_include_exclude_format'; import { isStringType, migrateIncludeExcludeFormat } from './migrate_include_exclude_format';
import { BUCKET_TYPES } from './bucket_agg_types';
import { KBN_FIELD_TYPES } from '../../../../../plugins/data/common';
const significantTermsTitle = i18n.translate('common.ui.aggTypes.buckets.significantTermsTitle', {
defaultMessage: 'Significant Terms',
});
export const significantTermsBucketAgg = new BucketAggType({ export const significantTermsBucketAgg = new BucketAggType({
name: 'significant_terms', name: BUCKET_TYPES.SIGNIFICANT_TERMS,
title: i18n.translate('common.ui.aggTypes.buckets.significantTermsTitle', { title: significantTermsTitle,
defaultMessage: 'Significant Terms', makeLabel(aggConfig) {
}),
makeLabel: function (aggConfig) {
return i18n.translate('common.ui.aggTypes.buckets.significantTermsLabel', { return i18n.translate('common.ui.aggTypes.buckets.significantTermsLabel', {
defaultMessage: 'Top {size} unusual terms in {fieldName}', defaultMessage: 'Top {size} unusual terms in {fieldName}',
values: { values: {
size: aggConfig.params.size, size: aggConfig.params.size,
fieldName: aggConfig.getFieldDisplayName(), fieldName: aggConfig.getFieldDisplayName(),
} },
}); });
}, },
createFilter: createFilterTerms, createFilter: createFilterTerms,
@ -43,7 +47,7 @@ export const significantTermsBucketAgg = new BucketAggType({
name: 'field', name: 'field',
type: 'field', type: 'field',
scriptable: false, scriptable: false,
filterFieldTypes: 'string' filterFieldTypes: KBN_FIELD_TYPES.STRING,
}, },
{ {
name: 'size', name: 'size',
@ -53,22 +57,22 @@ export const significantTermsBucketAgg = new BucketAggType({
{ {
name: 'exclude', name: 'exclude',
displayName: i18n.translate('common.ui.aggTypes.buckets.significantTerms.excludeLabel', { displayName: i18n.translate('common.ui.aggTypes.buckets.significantTerms.excludeLabel', {
defaultMessage: 'Exclude' defaultMessage: 'Exclude',
}), }),
type: 'string', type: 'string',
advanced: true, advanced: true,
shouldShow: isStringType, shouldShow: isStringType,
...migrateIncludeExcludeFormat ...migrateIncludeExcludeFormat,
}, } as BucketAggParam,
{ {
name: 'include', name: 'include',
displayName: i18n.translate('common.ui.aggTypes.buckets.significantTerms.includeLabel', { displayName: i18n.translate('common.ui.aggTypes.buckets.significantTerms.includeLabel', {
defaultMessage: 'Include' defaultMessage: 'Include',
}), }),
type: 'string', type: 'string',
advanced: true, advanced: true,
shouldShow: isStringType, shouldShow: isStringType,
...migrateIncludeExcludeFormat ...migrateIncludeExcludeFormat,
} } as BucketAggParam,
] ],
}); });

View file

@ -18,13 +18,20 @@
*/ */
import chrome from 'ui/chrome'; import chrome from 'ui/chrome';
import { noop } from 'lodash';
import { SearchSource } from 'ui/courier';
import { i18n } from '@kbn/i18n'; import { i18n } from '@kbn/i18n';
import { BucketAggType } from './_bucket_agg_type'; import { BucketAggType, BucketAggParam } from './_bucket_agg_type';
import { Schemas } from '../../vis/editors/default/schemas'; import { BUCKET_TYPES } from './bucket_agg_types';
import { getRequestInspectorStats, getResponseInspectorStats } from '../../courier/utils/courier_inspector_utils'; import { KBN_FIELD_TYPES } from '../../../../../plugins/data/common';
import { AggConfigOptions } from '../agg_config';
import { IBucketAggConfig } from './_bucket_agg_type';
import {
getRequestInspectorStats,
getResponseInspectorStats,
} from '../../courier/utils/courier_inspector_utils';
import { createFilterTerms } from './create_filter/terms'; import { createFilterTerms } from './create_filter/terms';
import { wrapWithInlineComp } from './_inline_comp_wrapper'; import { wrapWithInlineComp } from './inline_comp_wrapper';
import { buildOtherBucketAgg, mergeOtherBucketAggResponse, updateMissingBucket } from './_terms_other_bucket_helper';
import { isStringType, migrateIncludeExcludeFormat } from './migrate_include_exclude_format'; import { isStringType, migrateIncludeExcludeFormat } from './migrate_include_exclude_format';
import { OrderAggParamEditor } from '../../vis/editors/default/controls/order_agg'; import { OrderAggParamEditor } from '../../vis/editors/default/controls/order_agg';
import { OrderParamEditor } from '../../vis/editors/default/controls/order'; import { OrderParamEditor } from '../../vis/editors/default/controls/order';
@ -32,31 +39,47 @@ import { OrderByParamEditor, aggFilter } from '../../vis/editors/default/control
import { SizeParamEditor } from '../../vis/editors/default/controls/size'; import { SizeParamEditor } from '../../vis/editors/default/controls/size';
import { MissingBucketParamEditor } from '../../vis/editors/default/controls/missing_bucket'; import { MissingBucketParamEditor } from '../../vis/editors/default/controls/missing_bucket';
import { OtherBucketParamEditor } from '../../vis/editors/default/controls/other_bucket'; import { OtherBucketParamEditor } from '../../vis/editors/default/controls/other_bucket';
import { ContentType } from '../../../../../plugins/data/common';
import { AggConfigs } from '../agg_configs';
const orderAggSchema = (new Schemas([ import { Adapters } from '../../../../../plugins/inspector/public';
// @ts-ignore
import { Schemas } from '../../vis/editors/default/schemas';
import {
buildOtherBucketAgg,
mergeOtherBucketAggResponse,
updateMissingBucket,
// @ts-ignore
} from './_terms_other_bucket_helper';
const [orderAggSchema] = new Schemas([
{ {
group: 'none', group: 'none',
name: 'orderAgg', name: 'orderAgg',
// This string is never visible to the user so it doesn't need to be translated // This string is never visible to the user so it doesn't need to be translated
title: 'Order Agg', title: 'Order Agg',
hideCustomLabel: true, hideCustomLabel: true,
aggFilter: aggFilter aggFilter,
} },
])).all[0]; ]).all;
const termsTitle = i18n.translate('common.ui.aggTypes.buckets.termsTitle', {
defaultMessage: 'Terms',
});
export const termsBucketAgg = new BucketAggType({ export const termsBucketAgg = new BucketAggType({
name: 'terms', name: BUCKET_TYPES.TERMS,
title: i18n.translate('common.ui.aggTypes.buckets.termsTitle', { title: termsTitle,
defaultMessage: 'Terms', makeLabel(agg) {
}),
makeLabel: function (agg) {
const params = agg.params; const params = agg.params;
return agg.getFieldDisplayName() + ': ' + params.order.text; return agg.getFieldDisplayName() + ': ' + params.order.text;
}, },
getFormat: function (bucket) { getFormat(bucket) {
return { return {
getConverterFor: (type) => { getConverterFor: (type: ContentType) => {
return (val) => { return (val: any) => {
if (val === '__other__') { if (val === '__other__') {
return bucket.params.otherBucketLabel; return bucket.params.otherBucketLabel;
} }
@ -71,11 +94,18 @@ export const termsBucketAgg = new BucketAggType({
const converter = bucket.params.field.format.getConverterFor(type); const converter = bucket.params.field.format.getConverterFor(type);
return converter(val, undefined, undefined, parsedUrl); return converter(val, undefined, undefined, parsedUrl);
}; };
} },
}; };
}, },
createFilter: createFilterTerms, createFilter: createFilterTerms,
postFlightRequest: async (resp, aggConfigs, aggConfig, searchSource, inspectorAdapters, abortSignal) => { postFlightRequest: async (
resp: any,
aggConfigs: AggConfigs,
aggConfig: IBucketAggConfig,
searchSource: SearchSource,
inspectorAdapters: Adapters,
abortSignal?: AbortSignal
) => {
if (!resp.aggregations) return resp; if (!resp.aggregations) return resp;
const nestedSearchSource = searchSource.createChild(); const nestedSearchSource = searchSource.createChild();
if (aggConfig.params.otherBucket) { if (aggConfig.params.otherBucket) {
@ -88,23 +118,24 @@ export const termsBucketAgg = new BucketAggType({
nestedSearchSource.setField('aggs', filterAgg); nestedSearchSource.setField('aggs', filterAgg);
const request = inspectorAdapters.requests.start( const request = inspectorAdapters.requests.start(
i18n.translate('common.ui.aggTypes.buckets.terms.otherBucketTitle', { defaultMessage: 'Other bucket' }), i18n.translate('common.ui.aggTypes.buckets.terms.otherBucketTitle', {
defaultMessage: 'Other bucket',
}),
{ {
description: i18n.translate('common.ui.aggTypes.buckets.terms.otherBucketDescription', { description: i18n.translate('common.ui.aggTypes.buckets.terms.otherBucketDescription', {
defaultMessage: 'This request counts the number of documents that fall ' + defaultMessage:
'outside the criterion of the data buckets.' 'This request counts the number of documents that fall ' +
'outside the criterion of the data buckets.',
}), }),
} }
); );
nestedSearchSource.getSearchRequestBody().then(body => { nestedSearchSource.getSearchRequestBody().then((body: string) => {
request.json(body); request.json(body);
}); });
request.stats(getRequestInspectorStats(nestedSearchSource)); request.stats(getRequestInspectorStats(nestedSearchSource));
const response = await nestedSearchSource.fetch(); const response = await nestedSearchSource.fetch();
request request.stats(getResponseInspectorStats(nestedSearchSource, response)).ok({ json: response });
.stats(getResponseInspectorStats(nestedSearchSource, response))
.ok({ json: response });
resp = mergeOtherBucketAggResponse(aggConfigs, resp, response, aggConfig, filterAgg()); resp = mergeOtherBucketAggResponse(aggConfigs, resp, response, aggConfig, filterAgg());
} }
if (aggConfig.params.missingBucket) { if (aggConfig.params.missingBucket) {
@ -116,28 +147,34 @@ export const termsBucketAgg = new BucketAggType({
{ {
name: 'field', name: 'field',
type: 'field', type: 'field',
filterFieldTypes: ['number', 'boolean', 'date', 'ip', 'string'] filterFieldTypes: [
KBN_FIELD_TYPES.NUMBER,
KBN_FIELD_TYPES.BOOLEAN,
KBN_FIELD_TYPES.DATE,
KBN_FIELD_TYPES.IP,
KBN_FIELD_TYPES.STRING,
],
}, },
{ {
name: 'orderBy', name: 'orderBy',
editorComponent: OrderByParamEditor, editorComponent: OrderByParamEditor,
write: () => {} // prevent default write, it's handled by orderAgg write: noop, // prevent default write, it's handled by orderAgg
}, },
{ {
name: 'orderAgg', name: 'orderAgg',
type: 'agg', type: 'agg',
default: null, default: null,
editorComponent: OrderAggParamEditor, editorComponent: OrderAggParamEditor,
makeAgg: function (termsAgg, state) { makeAgg(termsAgg: IBucketAggConfig, state: AggConfigOptions) {
state = state || {}; state = state || {};
state.schema = orderAggSchema; state.schema = orderAggSchema;
const orderAgg = termsAgg.aggConfigs.createAggConfig(state, { addToAggConfigs: false }); const orderAgg = termsAgg.aggConfigs.createAggConfig(state, { addToAggConfigs: false });
orderAgg.id = termsAgg.id + '-orderAgg'; orderAgg.id = termsAgg.id + '-orderAgg';
return orderAgg; return orderAgg;
}, },
write: function (agg, output, aggs) { write(agg: IBucketAggConfig, output: Record<string, any>, aggs: AggConfigs) {
const dir = agg.params.order.value; const dir = agg.params.order.value;
const order = output.params.order = {}; const order: Record<string, any> = (output.params.order = {});
let orderAgg = agg.params.orderAgg || aggs.getResponseAggById(agg.params.orderBy); let orderAgg = agg.params.orderAgg || aggs.getResponseAggById(agg.params.orderBy);
@ -145,7 +182,8 @@ export const termsBucketAgg = new BucketAggType({
// thus causing issues with filtering. This probably causes other issues since float might not // thus causing issues with filtering. This probably causes other issues since float might not
// be able to contain the number on the elasticsearch side // be able to contain the number on the elasticsearch side
if (output.params.script) { if (output.params.script) {
output.params.value_type = agg.getField().type === 'number' ? 'float' : agg.getField().type; output.params.value_type =
agg.getField().type === 'number' ? 'float' : agg.getField().type;
} }
if (agg.params.missingBucket && agg.params.field.type === 'string') { if (agg.params.missingBucket && agg.params.field.type === 'string') {
@ -169,7 +207,7 @@ export const termsBucketAgg = new BucketAggType({
output.subAggs = (output.subAggs || []).concat(orderAgg); output.subAggs = (output.subAggs || []).concat(orderAgg);
order[orderAggId] = dir; order[orderAggId] = dir;
} },
}, },
{ {
name: 'order', name: 'order',
@ -181,27 +219,27 @@ export const termsBucketAgg = new BucketAggType({
text: i18n.translate('common.ui.aggTypes.buckets.terms.orderDescendingTitle', { text: i18n.translate('common.ui.aggTypes.buckets.terms.orderDescendingTitle', {
defaultMessage: 'Descending', defaultMessage: 'Descending',
}), }),
value: 'desc' value: 'desc',
}, },
{ {
text: i18n.translate('common.ui.aggTypes.buckets.terms.orderAscendingTitle', { text: i18n.translate('common.ui.aggTypes.buckets.terms.orderAscendingTitle', {
defaultMessage: 'Ascending', defaultMessage: 'Ascending',
}), }),
value: 'asc' value: 'asc',
} },
], ],
write: () => {} // prevent default write, it's handled by orderAgg write: noop, // prevent default write, it's handled by orderAgg
}, },
{ {
name: 'size', name: 'size',
editorComponent: wrapWithInlineComp(SizeParamEditor), editorComponent: wrapWithInlineComp(SizeParamEditor),
default: 5 default: 5,
}, },
{ {
name: 'otherBucket', name: 'otherBucket',
default: false, default: false,
editorComponent: OtherBucketParamEditor, editorComponent: OtherBucketParamEditor,
write: () => {}, write: noop,
}, },
{ {
name: 'otherBucketLabel', name: 'otherBucketLabel',
@ -212,14 +250,14 @@ export const termsBucketAgg = new BucketAggType({
displayName: i18n.translate('common.ui.aggTypes.otherBucket.labelForOtherBucketLabel', { displayName: i18n.translate('common.ui.aggTypes.otherBucket.labelForOtherBucketLabel', {
defaultMessage: 'Label for other bucket', defaultMessage: 'Label for other bucket',
}), }),
shouldShow: agg => agg.params.otherBucket, shouldShow: (agg: IBucketAggConfig) => agg.getParam('otherBucket'),
write: () => {}, write: noop,
}, } as BucketAggParam,
{ {
name: 'missingBucket', name: 'missingBucket',
default: false, default: false,
editorComponent: MissingBucketParamEditor, editorComponent: MissingBucketParamEditor,
write: () => {}, write: noop,
}, },
{ {
name: 'missingBucketLabel', name: 'missingBucketLabel',
@ -232,24 +270,28 @@ export const termsBucketAgg = new BucketAggType({
displayName: i18n.translate('common.ui.aggTypes.otherBucket.labelForMissingValuesLabel', { displayName: i18n.translate('common.ui.aggTypes.otherBucket.labelForMissingValuesLabel', {
defaultMessage: 'Label for missing values', defaultMessage: 'Label for missing values',
}), }),
shouldShow: agg => agg.params.missingBucket, shouldShow: (agg: IBucketAggConfig) => agg.getParam('missingBucket'),
write: () => {}, write: noop,
}, },
{ {
name: 'exclude', name: 'exclude',
displayName: i18n.translate('common.ui.aggTypes.buckets.terms.excludeLabel', { defaultMessage: 'Exclude' }), displayName: i18n.translate('common.ui.aggTypes.buckets.terms.excludeLabel', {
defaultMessage: 'Exclude',
}),
type: 'string', type: 'string',
advanced: true, advanced: true,
shouldShow: isStringType, shouldShow: isStringType,
...migrateIncludeExcludeFormat ...migrateIncludeExcludeFormat,
}, },
{ {
name: 'include', name: 'include',
displayName: i18n.translate('common.ui.aggTypes.buckets.terms.includeLabel', { defaultMessage: 'Include' }), displayName: i18n.translate('common.ui.aggTypes.buckets.terms.includeLabel', {
defaultMessage: 'Include',
}),
type: 'string', type: 'string',
advanced: true, advanced: true,
shouldShow: isStringType, shouldShow: isStringType,
...migrateIncludeExcludeFormat ...migrateIncludeExcludeFormat,
}, },
] ] as BucketAggParam[],
}); });

View file

@ -17,73 +17,41 @@
* under the License. * under the License.
*/ */
// @ts-ignore
import { AggType } from './agg_type';
// @ts-ignore
import { countMetricAgg } from './metrics/count'; import { countMetricAgg } from './metrics/count';
// @ts-ignore
import { avgMetricAgg } from './metrics/avg'; import { avgMetricAgg } from './metrics/avg';
// @ts-ignore
import { sumMetricAgg } from './metrics/sum'; import { sumMetricAgg } from './metrics/sum';
// @ts-ignore
import { medianMetricAgg } from './metrics/median'; import { medianMetricAgg } from './metrics/median';
// @ts-ignore
import { minMetricAgg } from './metrics/min'; import { minMetricAgg } from './metrics/min';
// @ts-ignore
import { maxMetricAgg } from './metrics/max'; import { maxMetricAgg } from './metrics/max';
// @ts-ignore
import { topHitMetricAgg } from './metrics/top_hit'; import { topHitMetricAgg } from './metrics/top_hit';
// @ts-ignore
import { stdDeviationMetricAgg } from './metrics/std_deviation'; import { stdDeviationMetricAgg } from './metrics/std_deviation';
// @ts-ignore
import { cardinalityMetricAgg } from './metrics/cardinality'; import { cardinalityMetricAgg } from './metrics/cardinality';
// @ts-ignore
import { percentilesMetricAgg } from './metrics/percentiles'; import { percentilesMetricAgg } from './metrics/percentiles';
// @ts-ignore
import { geoBoundsMetricAgg } from './metrics/geo_bounds'; import { geoBoundsMetricAgg } from './metrics/geo_bounds';
// @ts-ignore
import { geoCentroidMetricAgg } from './metrics/geo_centroid'; import { geoCentroidMetricAgg } from './metrics/geo_centroid';
// @ts-ignore
import { percentileRanksMetricAgg } from './metrics/percentile_ranks'; import { percentileRanksMetricAgg } from './metrics/percentile_ranks';
// @ts-ignore
import { derivativeMetricAgg } from './metrics/derivative'; import { derivativeMetricAgg } from './metrics/derivative';
// @ts-ignore
import { cumulativeSumMetricAgg } from './metrics/cumulative_sum'; import { cumulativeSumMetricAgg } from './metrics/cumulative_sum';
// @ts-ignore
import { movingAvgMetricAgg } from './metrics/moving_avg'; import { movingAvgMetricAgg } from './metrics/moving_avg';
// @ts-ignore
import { serialDiffMetricAgg } from './metrics/serial_diff'; import { serialDiffMetricAgg } from './metrics/serial_diff';
// @ts-ignore
import { dateHistogramBucketAgg, setBounds } from './buckets/date_histogram'; import { dateHistogramBucketAgg, setBounds } from './buckets/date_histogram';
// @ts-ignore
import { histogramBucketAgg } from './buckets/histogram'; import { histogramBucketAgg } from './buckets/histogram';
// @ts-ignore
import { rangeBucketAgg } from './buckets/range'; import { rangeBucketAgg } from './buckets/range';
// @ts-ignore
import { dateRangeBucketAgg } from './buckets/date_range'; import { dateRangeBucketAgg } from './buckets/date_range';
// @ts-ignore
import { ipRangeBucketAgg } from './buckets/ip_range'; import { ipRangeBucketAgg } from './buckets/ip_range';
// @ts-ignore
import { termsBucketAgg } from './buckets/terms'; import { termsBucketAgg } from './buckets/terms';
// @ts-ignore
import { filterBucketAgg } from './buckets/filter'; import { filterBucketAgg } from './buckets/filter';
// @ts-ignore
import { filtersBucketAgg } from './buckets/filters'; import { filtersBucketAgg } from './buckets/filters';
// @ts-ignore
import { significantTermsBucketAgg } from './buckets/significant_terms'; import { significantTermsBucketAgg } from './buckets/significant_terms';
// @ts-ignore
import { geoHashBucketAgg } from './buckets/geo_hash'; import { geoHashBucketAgg } from './buckets/geo_hash';
// @ts-ignore
import { geoTileBucketAgg } from './buckets/geo_tile'; import { geoTileBucketAgg } from './buckets/geo_tile';
// @ts-ignore
import { bucketSumMetricAgg } from './metrics/bucket_sum'; import { bucketSumMetricAgg } from './metrics/bucket_sum';
// @ts-ignore
import { bucketAvgMetricAgg } from './metrics/bucket_avg'; import { bucketAvgMetricAgg } from './metrics/bucket_avg';
// @ts-ignore
import { bucketMinMetricAgg } from './metrics/bucket_min'; import { bucketMinMetricAgg } from './metrics/bucket_min';
// @ts-ignore
import { bucketMaxMetricAgg } from './metrics/bucket_max'; import { bucketMaxMetricAgg } from './metrics/bucket_max';
export { AggType } from './agg_type';
export const aggTypes = { export const aggTypes = {
metrics: [ metrics: [
countMetricAgg, countMetricAgg,
@ -123,13 +91,9 @@ export const aggTypes = {
], ],
}; };
aggTypes.metrics.forEach(aggType => (aggType.type = 'metrics'));
aggTypes.buckets.forEach(aggType => (aggType.type = 'buckets'));
export { AggParam } from './agg_params'; export { AggParam } from './agg_params';
export { AggType } from './agg_type';
export { AggConfig } from './agg_config'; export { AggConfig } from './agg_config';
export { AggConfigs } from './agg_configs'; export { AggConfigs } from './agg_configs';
export { FieldParamType } from './param_types'; export { FieldParamType } from './param_types';
export { BucketAggType } from './buckets/_bucket_agg_type';
export { setBounds }; export { setBounds };

View file

@ -17,7 +17,7 @@
* under the License. * under the License.
*/ */
import { i18n } from '@kbn/i18n'; import { i18n } from '@kbn/i18n';
import { MetricAggType } from './metric_agg_type'; import { IMetricAggConfig, MetricAggType } from './metric_agg_type';
import { METRIC_TYPES } from './metric_agg_types'; import { METRIC_TYPES } from './metric_agg_types';
// @ts-ignore // @ts-ignore
@ -49,7 +49,7 @@ export const medianMetricAgg = new MetricAggType({
default: [50], default: [50],
}, },
{ {
write(agg, output) { write(agg: IMetricAggConfig, output: Record<string, any>) {
output.params.keyed = false; output.params.keyed = false;
}, },
}, },

View file

@ -27,12 +27,10 @@ import { METRIC_TYPES } from './metric_agg_types';
import { fieldFormats } from '../../registry/field_formats'; import { fieldFormats } from '../../registry/field_formats';
import { KBN_FIELD_TYPES } from '../../../../../plugins/data/common'; import { KBN_FIELD_TYPES } from '../../../../../plugins/data/common';
export interface IMetricAggConfig extends AggConfig { export type IMetricAggConfig = AggConfig;
params: MetricAggParam[];
}
export interface MetricAggTypeConfig<TMetricAggConfig extends IMetricAggConfig> export interface MetricAggTypeConfig<TMetricAggConfig extends IMetricAggConfig>
extends AggTypeConfig<TMetricAggConfig> { extends AggTypeConfig<TMetricAggConfig, MetricAggParam> {
isScalable?: () => boolean; isScalable?: () => boolean;
subtype?: string; subtype?: string;
} }
@ -42,11 +40,16 @@ export interface MetricAggParam extends AggParamType {
onlyAggregatable?: boolean; onlyAggregatable?: boolean;
} }
export class MetricAggType<TMetricAggConfig extends IMetricAggConfig> extends AggType< const metricType = 'metrics';
TMetricAggConfig
> { export class MetricAggType<
TMetricAggConfig extends IMetricAggConfig = IMetricAggConfig
> extends AggType<TMetricAggConfig, MetricAggParam> {
subtype: string; subtype: string;
isScalable: () => boolean; isScalable: () => boolean;
type = metricType;
getKey = () => {};
constructor(config: MetricAggTypeConfig<TMetricAggConfig>) { constructor(config: MetricAggTypeConfig<TMetricAggConfig>) {
super(config); super(config);
@ -81,3 +84,7 @@ export class MetricAggType<TMetricAggConfig extends IMetricAggConfig> extends Ag
this.isScalable = config.isScalable || (() => false); this.isScalable = config.isScalable || (() => false);
} }
} }
export function isMetricAggType(aggConfig: any): aggConfig is MetricAggType {
return aggConfig && aggConfig.type === metricType;
}

View file

@ -19,7 +19,7 @@
import { i18n } from '@kbn/i18n'; import { i18n } from '@kbn/i18n';
import { PercentileRanksEditor } from '../../vis/editors/default/controls/percentile_ranks'; import { PercentileRanksEditor } from '../../vis/editors/default/controls/percentile_ranks';
import { MetricAggType } from './metric_agg_type'; import { IMetricAggConfig, MetricAggType } from './metric_agg_type';
import { getResponseAggConfigClass, IResponseAggConfig } from './get_response_agg_config_class'; import { getResponseAggConfigClass, IResponseAggConfig } from './get_response_agg_config_class';
import { getPercentileValue } from './percentiles_get_value'; import { getPercentileValue } from './percentiles_get_value';
@ -69,7 +69,7 @@ export const percentileRanksMetricAgg = new MetricAggType<IPercentileRanksAggCon
default: [], default: [],
}, },
{ {
write(agg, output) { write(agg: IMetricAggConfig, output: Record<string, any>) {
output.params.keyed = false; output.params.keyed = false;
}, },
}, },

View file

@ -19,8 +19,9 @@
import { i18n } from '@kbn/i18n'; import { i18n } from '@kbn/i18n';
import { MetricAggType } from './metric_agg_type'; import { IMetricAggConfig, MetricAggType } from './metric_agg_type';
import { METRIC_TYPES } from './metric_agg_types'; import { METRIC_TYPES } from './metric_agg_types';
import { KBN_FIELD_TYPES } from '../../../../../plugins/data/common';
import { getResponseAggConfigClass, IResponseAggConfig } from './get_response_agg_config_class'; import { getResponseAggConfigClass, IResponseAggConfig } from './get_response_agg_config_class';
import { getPercentileValue } from './percentiles_get_value'; import { getPercentileValue } from './percentiles_get_value';
@ -28,7 +29,6 @@ import { PercentilesEditor } from '../../vis/editors/default/controls/percentile
// @ts-ignore // @ts-ignore
import { ordinalSuffix } from '../../utils/ordinal_suffix'; import { ordinalSuffix } from '../../utils/ordinal_suffix';
import { KBN_FIELD_TYPES } from '../../../../../plugins/data/common';
type IPercentileAggConfig = IResponseAggConfig; type IPercentileAggConfig = IResponseAggConfig;
@ -67,7 +67,7 @@ export const percentilesMetricAgg = new MetricAggType<IPercentileAggConfig>({
default: [1, 5, 25, 50, 75, 95, 99], default: [1, 5, 25, 50, 75, 95, 99],
}, },
{ {
write(agg, output) { write(agg: IMetricAggConfig, output: Record<string, any>) {
output.params.keyed = false; output.params.keyed = false;
}, },
}, },

View file

@ -30,7 +30,7 @@ import { METRIC_TYPES } from './metric_agg_types';
import { KBN_FIELD_TYPES } from '../../../../../plugins/data/common'; import { KBN_FIELD_TYPES } from '../../../../../plugins/data/common';
// @ts-ignore // @ts-ignore
import { wrapWithInlineComp } from '../buckets/_inline_comp_wrapper'; import { wrapWithInlineComp } from '../buckets/inline_comp_wrapper';
const isNumericFieldSelected = (agg: IMetricAggConfig) => { const isNumericFieldSelected = (agg: IMetricAggConfig) => {
const field = agg.getParam('field'); const field = agg.getParam('field');
@ -46,7 +46,7 @@ aggTypeFieldFilters.addFilter((field, aggConfig: IMetricAggConfig) => {
return true; return true;
} }
return field.type === 'number'; return field.type === KBN_FIELD_TYPES.NUMBER;
}); });
export const topHitMetricAgg = new MetricAggType({ export const topHitMetricAgg = new MetricAggType({
@ -82,7 +82,7 @@ export const topHitMetricAgg = new MetricAggType({
editorComponent: TopFieldParamEditor, editorComponent: TopFieldParamEditor,
onlyAggregatable: false, onlyAggregatable: false,
filterFieldTypes: '*', filterFieldTypes: '*',
write(agg, output) { write(agg: IMetricAggConfig, output: Record<string, any>) {
const field = agg.getParam('field'); const field = agg.getParam('field');
output.params = {}; output.params = {};
@ -196,7 +196,7 @@ export const topHitMetricAgg = new MetricAggType({
value: 'asc', value: 'asc',
}, },
], ],
write(agg, output) { write(agg: IMetricAggConfig, output: Record<string, any>) {
const sortField = agg.params.sortField; const sortField = agg.params.sortField;
const sortOrder = agg.params.sortOrder; const sortOrder = agg.params.sortOrder;

View file

@ -56,8 +56,8 @@ export class BaseParamType implements AggParam {
*/ */
modifyAggConfigOnSearchRequestStart: ( modifyAggConfigOnSearchRequestStart: (
aggconfig: AggConfig, aggconfig: AggConfig,
searchSource: SearchSource, searchSource?: SearchSource,
searchRequest: any searchRequest?: any
) => void; ) => void;
constructor(config: Record<string, any>) { constructor(config: Record<string, any>) {

View file

@ -29,7 +29,7 @@ import { AggParamEditorProps } from '..';
const generateId = htmlIdGenerator(); const generateId = htmlIdGenerator();
const config = chrome.getUiSettingsClient(); const config = chrome.getUiSettingsClient();
interface FilterValue { export interface FilterValue {
input: Query; input: Query;
label: string; label: string;
id: string; id: string;

View file

@ -34,6 +34,7 @@ jest.mock('ui/new_platform');
jest.mock('ui/agg_types/buckets/date_histogram', () => ({ jest.mock('ui/agg_types/buckets/date_histogram', () => ({
setBounds: () => {}, setBounds: () => {},
dateHistogramBucketAgg: () => {}, dateHistogramBucketAgg: () => {},
isDateHistogramBucketAggConfig: () => true,
})); }));
describe('visualize loader pipeline helpers: build pipeline', () => { describe('visualize loader pipeline helpers: build pipeline', () => {

View file

@ -22,6 +22,7 @@ import { cloneDeep, get } from 'lodash';
import { setBounds } from 'ui/agg_types'; import { setBounds } from 'ui/agg_types';
import { SearchSource } from 'ui/courier'; import { SearchSource } from 'ui/courier';
import { AggConfig, Vis, VisParams, VisState } from 'ui/vis'; import { AggConfig, Vis, VisParams, VisState } from 'ui/vis';
import { isDateHistogramBucketAggConfig } from 'ui/agg_types/buckets/date_histogram';
import moment from 'moment'; import moment from 'moment';
import { SerializedFieldFormat } from 'src/plugins/expressions/common/expressions/types/common'; import { SerializedFieldFormat } from 'src/plugins/expressions/common/expressions/types/common';
import { createFormat } from './utilities'; import { createFormat } from './utilities';
@ -76,7 +77,7 @@ const vislibCharts: string[] = [
export const getSchemas = (vis: Vis, timeRange?: any): Schemas => { export const getSchemas = (vis: Vis, timeRange?: any): Schemas => {
const createSchemaConfig = (accessor: number, agg: AggConfig): SchemaConfig => { const createSchemaConfig = (accessor: number, agg: AggConfig): SchemaConfig => {
if (agg.type.name === 'date_histogram') { if (isDateHistogramBucketAggConfig(agg)) {
agg.params.timeRange = timeRange; agg.params.timeRange = timeRange;
setBounds(agg, true); setBounds(agg, true);
} }

View file

@ -37,7 +37,8 @@
], ],
"monitoring/common/*": [ "monitoring/common/*": [
"x-pack/monitoring/common/*" "x-pack/monitoring/common/*"
] ],
"plugins/*": ["src/legacy/core_plugins/*/public/"]
}, },
"types": [ "types": [
"node", "node",