diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.search.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.search.md index afb6ea88f9fad..78ac05b9fd386 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.search.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.search.md @@ -19,7 +19,7 @@ search: { intervalOptions: ({ display: string; val: string; - enabled(agg: import("./search/aggs/buckets/_bucket_agg_type").IBucketAggConfig): boolean | "" | undefined; + enabled(agg: import("./search/aggs/buckets/bucket_agg_type").IBucketAggConfig): boolean | "" | undefined; } | { display: string; val: string; diff --git a/src/legacy/ui/public/new_platform/new_platform.karma_mock.js b/src/legacy/ui/public/new_platform/new_platform.karma_mock.js index 25647e4a08897..f70ef069dd134 100644 --- a/src/legacy/ui/public/new_platform/new_platform.karma_mock.js +++ b/src/legacy/ui/public/new_platform/new_platform.karma_mock.js @@ -88,6 +88,9 @@ const mockCoreStart = { get: sinon.fake.returns(''), }, }, + notifications: { + toasts: {}, + }, i18n: {}, overlays: {}, savedObjects: { @@ -164,8 +167,11 @@ const mockAggTypesRegistry = () => { const registrySetup = registry.setup(); const aggTypes = getAggTypes({ uiSettings: mockCoreSetup.uiSettings, - notifications: mockCoreStart.notifications, query: querySetup, + getInternalStartServices: () => ({ + fieldFormats: getFieldFormatsRegistry(mockCoreStart), + notifications: mockCoreStart.notifications, + }), }); aggTypes.buckets.forEach(type => registrySetup.registerBucket(type)); aggTypes.metrics.forEach(type => registrySetup.registerMetric(type)); diff --git a/src/plugins/data/common/field_formats/mocks.ts b/src/plugins/data/common/field_formats/mocks.ts index bc38374e147cf..394d4c383032f 100644 --- a/src/plugins/data/common/field_formats/mocks.ts +++ b/src/plugins/data/common/field_formats/mocks.ts @@ -17,23 +17,14 @@ * under the License. */ -import { FieldFormat, IFieldFormatsRegistry } from '.'; - -const fieldFormatMock = ({ - convert: jest.fn(), - getConverterFor: jest.fn(), - getParamDefaults: jest.fn(), - param: jest.fn(), - params: jest.fn(), - toJSON: jest.fn(), - type: jest.fn(), - setupContentType: jest.fn(), -} as unknown) as FieldFormat; +import { IFieldFormatsRegistry } from '.'; export const fieldFormatsMock: IFieldFormatsRegistry = { getByFieldType: jest.fn(), getDefaultConfig: jest.fn(), - getDefaultInstance: jest.fn().mockImplementation(() => fieldFormatMock) as any, + getDefaultInstance: jest.fn().mockImplementation(() => ({ + getConverterFor: jest.fn().mockImplementation(() => (t: string) => t), + })) as any, getDefaultInstanceCacheResolver: jest.fn(), getDefaultInstancePlain: jest.fn(), getDefaultType: jest.fn(), diff --git a/src/plugins/data/public/field_formats/mocks.ts b/src/plugins/data/public/field_formats/mocks.ts new file mode 100644 index 0000000000000..ec1233a085bce --- /dev/null +++ b/src/plugins/data/public/field_formats/mocks.ts @@ -0,0 +1,41 @@ +/* + * 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 { FieldFormatsStart, FieldFormatsSetup, FieldFormatsService } from '.'; +import { fieldFormatsMock } from '../../common/field_formats/mocks'; + +type FieldFormatsServiceClientContract = PublicMethodsOf; + +const createSetupContractMock = () => fieldFormatsMock as FieldFormatsSetup; +const createStartContractMock = () => fieldFormatsMock as FieldFormatsStart; + +const createMock = () => { + const mocked: jest.Mocked = { + setup: jest.fn().mockReturnValue(createSetupContractMock()), + start: jest.fn().mockReturnValue(createStartContractMock()), + }; + + return mocked; +}; + +export const fieldFormatsServiceMock = { + create: createMock, + createSetupContract: createSetupContractMock, + createStartContract: createStartContractMock, +}; diff --git a/src/plugins/data/public/mocks.ts b/src/plugins/data/public/mocks.ts index e3fc0e97af09b..ea1c27550867e 100644 --- a/src/plugins/data/public/mocks.ts +++ b/src/plugins/data/public/mocks.ts @@ -17,8 +17,8 @@ * under the License. */ -import { Plugin, DataPublicPluginSetup, DataPublicPluginStart, IndexPatternsContract } from '.'; -import { fieldFormatsMock } from '../common/field_formats/mocks'; +import { Plugin, IndexPatternsContract } from '.'; +import { fieldFormatsServiceMock } from './field_formats/mocks'; import { searchSetupMock, searchStartMock } from './search/mocks'; import { queryServiceMock } from './query/mocks'; @@ -36,7 +36,7 @@ const createSetupContract = (): Setup => { return { autocomplete: autocompleteMock, search: searchSetupMock, - fieldFormats: fieldFormatsMock as DataPublicPluginSetup['fieldFormats'], + fieldFormats: fieldFormatsServiceMock.createSetupContract(), query: querySetupMock, }; }; @@ -49,7 +49,7 @@ const createStartContract = (): Start => { }, autocomplete: autocompleteMock, search: searchStartMock, - fieldFormats: fieldFormatsMock as DataPublicPluginStart['fieldFormats'], + fieldFormats: fieldFormatsServiceMock.createStartContract(), query: queryStartMock, ui: { IndexPatternSelect: jest.fn(), diff --git a/src/plugins/data/public/plugin.ts b/src/plugins/data/public/plugin.ts index 26587470adfd9..15067077afc43 100644 --- a/src/plugins/data/public/plugin.ts +++ b/src/plugins/data/public/plugin.ts @@ -30,6 +30,7 @@ import { DataPublicPluginStart, DataSetupDependencies, DataStartDependencies, + GetInternalStartServicesFn, } from './types'; import { AutocompleteService } from './autocomplete'; import { SearchService } from './search/search_service'; @@ -47,6 +48,8 @@ import { setQueryService, setSearchService, setUiSettings, + getFieldFormats, + getNotifications, } from './services'; import { createSearchBar } from './ui/search_bar/create_search_bar'; import { esaggs } from './search/expressions'; @@ -100,6 +103,11 @@ export class DataPublicPlugin implements Plugin ({ + fieldFormats: getFieldFormats(), + notifications: getNotifications(), + }); + const queryService = this.queryService.setup({ uiSettings: core.uiSettings, storage: this.storage, @@ -122,6 +130,7 @@ export class DataPublicPlugin implements Plugin { +export interface IDataPluginServices extends Partial { // (undocumented) appName: string; // (undocumented) data: DataPublicPluginStart; // (undocumented) - http: CoreStart_2['http']; + http: CoreStart['http']; // (undocumented) - notifications: CoreStart_2['notifications']; + notifications: CoreStart['notifications']; // (undocumented) - savedObjects: CoreStart_2['savedObjects']; + savedObjects: CoreStart['savedObjects']; // (undocumented) storage: IStorageWrapper; // (undocumented) - uiSettings: CoreStart_2['uiSettings']; + uiSettings: CoreStart['uiSettings']; } // Warning: (ae-missing-release-tag) "IEsSearchRequest" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -1094,7 +1094,7 @@ export type ISearch = // @public (undocumented) export interface ISearchContext { // (undocumented) - core: CoreStart; + core: CoreStart_2; // (undocumented) getSearchStrategy: (name: T) => TSearchStrategyProvider; } @@ -1307,7 +1307,7 @@ export class Plugin implements Plugin_2 { let indexPattern: IndexPattern; @@ -400,13 +398,6 @@ describe('AggConfig', () => { describe('#fieldFormatter - custom getFormat handler', () => { it('returns formatter from getFormat handler', () => { - setFieldFormats({ - ...dataPluginMock.createStartContract().fieldFormats, - getDefaultInstance: jest.fn().mockImplementation(() => ({ - getConverterFor: jest.fn().mockImplementation(() => (t: string) => t), - })) as any, - }); - const ac = new AggConfigs(indexPattern, [], { typesRegistry }); const configStates = { enabled: true, @@ -429,12 +420,6 @@ describe('AggConfig', () => { let aggConfig: AggConfig; beforeEach(() => { - setFieldFormats({ - ...dataPluginMock.createStartContract().fieldFormats, - getDefaultInstance: jest.fn().mockImplementation(() => ({ - getConverterFor: (t?: string) => t || identity, - })) as any, - }); indexPattern.fields.getByName = name => ({ format: { diff --git a/src/plugins/data/public/search/aggs/agg_params.test.ts b/src/plugins/data/public/search/aggs/agg_params.test.ts index b08fcf309e9ed..784be803e2644 100644 --- a/src/plugins/data/public/search/aggs/agg_params.test.ts +++ b/src/plugins/data/public/search/aggs/agg_params.test.ts @@ -22,12 +22,22 @@ import { BaseParamType } from './param_types/base'; import { FieldParamType } from './param_types/field'; import { OptionedParamType } from './param_types/optioned'; import { AggParamType } from '../aggs/param_types/agg'; +import { fieldFormatsServiceMock } from '../../field_formats/mocks'; +import { notificationServiceMock } from '../../../../../../src/core/public/mocks'; +import { AggTypeDependencies } from './agg_type'; describe('AggParams class', () => { + const aggTypesDependencies: AggTypeDependencies = { + getInternalStartServices: () => ({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), + }), + }; + describe('constructor args', () => { it('accepts an array of param defs', () => { const params = [{ name: 'one' }, { name: 'two' }] as AggParamType[]; - const aggParams = initParams(params); + const aggParams = initParams(params, aggTypesDependencies); expect(aggParams).toHaveLength(params.length); expect(Array.isArray(aggParams)).toBeTruthy(); @@ -37,7 +47,7 @@ describe('AggParams class', () => { describe('AggParam creation', () => { it('Uses the FieldParamType class for params with the name "field"', () => { const params = [{ name: 'field', type: 'field' }] as AggParamType[]; - const aggParams = initParams(params); + const aggParams = initParams(params, aggTypesDependencies); expect(aggParams).toHaveLength(params.length); expect(aggParams[0] instanceof FieldParamType).toBeTruthy(); @@ -50,7 +60,7 @@ describe('AggParams class', () => { type: 'optioned', }, ] as AggParamType[]; - const aggParams = initParams(params); + const aggParams = initParams(params, aggTypesDependencies); expect(aggParams).toHaveLength(params.length); expect(aggParams[0] instanceof OptionedParamType).toBeTruthy(); @@ -72,7 +82,7 @@ describe('AggParams class', () => { }, ] as AggParamType[]; - const aggParams = initParams(params); + const aggParams = initParams(params, aggTypesDependencies); expect(aggParams).toHaveLength(params.length); diff --git a/src/plugins/data/public/search/aggs/agg_params.ts b/src/plugins/data/public/search/aggs/agg_params.ts index 551cb81529a0a..e7b2f72bae656 100644 --- a/src/plugins/data/public/search/aggs/agg_params.ts +++ b/src/plugins/data/public/search/aggs/agg_params.ts @@ -26,6 +26,7 @@ import { BaseParamType } from './param_types/base'; import { AggConfig } from './agg_config'; import { IAggConfigs } from './agg_configs'; +import { AggTypeDependencies } from './agg_type'; const paramTypeMap = { field: FieldParamType, @@ -45,12 +46,13 @@ export interface AggParamOption { } export const initParams = ( - params: TAggParam[] + params: TAggParam[], + { getInternalStartServices }: AggTypeDependencies ): TAggParam[] => params.map((config: TAggParam) => { const Class = paramTypeMap[config.type] || paramTypeMap._default; - return new Class(config); + return new Class(config, { getInternalStartServices }); }) as TAggParam[]; /** diff --git a/src/plugins/data/public/search/aggs/agg_type.test.ts b/src/plugins/data/public/search/aggs/agg_type.test.ts index 3fb03dc31e2b2..0c9e110c34ae6 100644 --- a/src/plugins/data/public/search/aggs/agg_type.test.ts +++ b/src/plugins/data/public/search/aggs/agg_type.test.ts @@ -17,40 +17,49 @@ * under the License. */ -import { AggType, AggTypeConfig } from './agg_type'; +import { AggType, AggTypeConfig, AggTypeDependencies } from './agg_type'; import { IAggConfig } from './agg_config'; -import { mockDataServices } from './test_helpers'; -import { dataPluginMock } from '../../../public/mocks'; -import { setFieldFormats } from '../../../public/services'; +import { fieldFormatsServiceMock } from '../../field_formats/mocks'; +import { notificationServiceMock } from '../../../../../../src/core/public/mocks'; describe('AggType Class', () => { + let dependencies: AggTypeDependencies; + beforeEach(() => { - mockDataServices(); + dependencies = { + getInternalStartServices: () => ({ + fieldFormats: { + ...fieldFormatsServiceMock.createStartContract(), + getDefaultInstance: jest.fn(() => 'default') as any, + }, + notifications: notificationServiceMock.createStartContract(), + }), + }; }); describe('constructor', () => { - it("requires a valid config object as it's first param", () => { + test("requires a valid config object as it's first param", () => { expect(() => { const aggConfig: AggTypeConfig = (undefined as unknown) as AggTypeConfig; - new AggType(aggConfig); + new AggType(aggConfig, dependencies); }).toThrowError(); }); describe('application of config properties', () => { - it('assigns the config value to itself', () => { + test('assigns the config value to itself', () => { const config: AggTypeConfig = { name: 'name', title: 'title', }; - const aggType = new AggType(config); + const aggType = new AggType(config, dependencies); expect(aggType.name).toBe('name'); expect(aggType.title).toBe('title'); }); describe('makeLabel', () => { - it('makes a function when the makeLabel config is not specified', () => { + test('makes a function when the makeLabel config is not specified', () => { const makeLabel = () => 'label'; const aggConfig = {} as IAggConfig; const config: AggTypeConfig = { @@ -59,7 +68,7 @@ describe('AggType Class', () => { makeLabel, }; - const aggType = new AggType(config); + const aggType = new AggType(config, dependencies); expect(aggType.makeLabel).toBe(makeLabel); expect(aggType.makeLabel(aggConfig)).toBe('label'); @@ -67,26 +76,32 @@ describe('AggType Class', () => { }); describe('getResponseAggs/getRequestAggs', () => { - it('copies the value', () => { + test('copies the value', () => { const testConfig = (aggConfig: IAggConfig) => [aggConfig]; - const aggType = new AggType({ - name: 'name', - title: 'title', - getResponseAggs: testConfig, - getRequestAggs: testConfig, - }); + const aggType = new AggType( + { + name: 'name', + title: 'title', + getResponseAggs: testConfig, + getRequestAggs: testConfig, + }, + dependencies + ); expect(aggType.getResponseAggs).toBe(testConfig); expect(aggType.getResponseAggs).toBe(testConfig); }); - it('defaults to noop', () => { + test('defaults to noop', () => { const aggConfig = {} as IAggConfig; - const aggType = new AggType({ - name: 'name', - title: 'title', - }); + const aggType = new AggType( + { + name: 'name', + title: 'title', + }, + dependencies + ); const responseAggs = aggType.getRequestAggs(aggConfig); expect(responseAggs).toBe(undefined); @@ -94,11 +109,14 @@ describe('AggType Class', () => { }); describe('params', () => { - it('defaults to AggParams object with JSON param', () => { - const aggType = new AggType({ - name: 'smart agg', - title: 'title', - }); + test('defaults to AggParams object with JSON param', () => { + const aggType = new AggType( + { + name: 'smart agg', + title: 'title', + }, + dependencies + ); expect(Array.isArray(aggType.params)).toBeTruthy(); expect(aggType.params.length).toBe(2); @@ -106,26 +124,32 @@ describe('AggType Class', () => { expect(aggType.params[1].name).toBe('customLabel'); }); - it('can disable customLabel', () => { - const aggType = new AggType({ - name: 'smart agg', - title: 'title', - customLabels: false, - }); + test('can disable customLabel', () => { + const aggType = new AggType( + { + name: 'smart agg', + title: 'title', + customLabels: false, + }, + dependencies + ); expect(aggType.params.length).toBe(1); expect(aggType.params[0].name).toBe('json'); }); - it('passes the params arg directly to the AggParams constructor', () => { + test('passes the params arg directly to the AggParams constructor', () => { const params = [{ name: 'one' }, { name: 'two' }]; const paramLength = params.length + 2; // json and custom label are always appended - const aggType = new AggType({ - name: 'bucketeer', - title: 'title', - params, - }); + const aggType = new AggType( + { + name: 'bucketeer', + title: 'title', + params, + }, + dependencies + ); expect(Array.isArray(aggType.params)).toBeTruthy(); expect(aggType.params.length).toBe(paramLength); @@ -143,11 +167,14 @@ describe('AggType Class', () => { } as unknown) as IAggConfig; }); - it('returns the formatter for the aggConfig', () => { - const aggType = new AggType({ - name: 'name', - title: 'title', - }); + test('returns the formatter for the aggConfig', () => { + const aggType = new AggType( + { + name: 'name', + title: 'title', + }, + dependencies + ); field = { format: 'format', @@ -156,16 +183,14 @@ describe('AggType Class', () => { expect(aggType.getFormat(aggConfig)).toBe('format'); }); - it('returns default formatter', () => { - setFieldFormats({ - ...dataPluginMock.createStartContract().fieldFormats, - getDefaultInstance: jest.fn(() => 'default') as any, - }); - - const aggType = new AggType({ - name: 'name', - title: 'title', - }); + test('returns default formatter', () => { + const aggType = new AggType( + { + name: 'name', + title: 'title', + }, + dependencies + ); field = undefined; diff --git a/src/plugins/data/public/search/aggs/agg_type.ts b/src/plugins/data/public/search/aggs/agg_type.ts index a63d01e196612..70c116d560c6f 100644 --- a/src/plugins/data/public/search/aggs/agg_type.ts +++ b/src/plugins/data/public/search/aggs/agg_type.ts @@ -28,7 +28,7 @@ import { BaseParamType } from './param_types/base'; import { AggParamType } from './param_types/agg'; import { KBN_FIELD_TYPES, IFieldFormat } from '../../../common'; import { ISearchSource } from '../search_source'; -import { getFieldFormats } from '../../../public/services'; +import { GetInternalStartServicesFn } from '../../types'; export interface AggTypeConfig< TAggConfig extends AggConfig = AggConfig, @@ -60,16 +60,13 @@ export interface AggTypeConfig< getKey?: (bucket: any, key: any, agg: TAggConfig) => any; } -const getFormat = (agg: AggConfig) => { - const field = agg.getField(); - const fieldFormatsService = getFieldFormats(); - - return field ? field.format : fieldFormatsService.getDefaultInstance(KBN_FIELD_TYPES.STRING); -}; - // TODO need to make a more explicit interface for this export type IAggType = AggType; +export interface AggTypeDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} + export class AggType< TAggConfig extends AggConfig = AggConfig, TParam extends AggParamType = AggParamType @@ -215,7 +212,10 @@ export class AggType< * @private * @param {object} config - used to set the properties of the AggType */ - constructor(config: AggTypeConfig) { + constructor( + config: AggTypeConfig, + { getInternalStartServices }: AggTypeDependencies + ) { this.name = config.name; this.type = config.type || 'metrics'; this.dslName = config.dslName || config.name; @@ -251,14 +251,22 @@ export class AggType< }); } - this.params = initParams(params); + this.params = initParams(params, { getInternalStartServices }); } this.getRequestAggs = config.getRequestAggs || noop; this.getResponseAggs = config.getResponseAggs || (() => {}); this.decorateAggConfig = config.decorateAggConfig || (() => ({})); this.postFlightRequest = config.postFlightRequest || identity; - this.getFormat = config.getFormat || getFormat; + + this.getFormat = + config.getFormat || + ((agg: TAggConfig) => { + const field = agg.getField(); + const { fieldFormats } = getInternalStartServices(); + + return field ? field.format : fieldFormats.getDefaultInstance(KBN_FIELD_TYPES.STRING); + }); this.getValue = config.getValue || ((agg: TAggConfig, bucket: any) => {}); } } diff --git a/src/plugins/data/public/search/aggs/agg_types.ts b/src/plugins/data/public/search/aggs/agg_types.ts index 556f6b0c93c41..4b154c338d48c 100644 --- a/src/plugins/data/public/search/aggs/agg_types.ts +++ b/src/plugins/data/public/search/aggs/agg_types.ts @@ -17,83 +17,89 @@ * under the License. */ -import { IUiSettingsClient, NotificationsSetup } from 'src/core/public'; +import { IUiSettingsClient } from 'src/core/public'; import { QuerySetup } from '../../query/query_service'; -import { countMetricAgg } from './metrics/count'; -import { avgMetricAgg } from './metrics/avg'; -import { sumMetricAgg } from './metrics/sum'; -import { medianMetricAgg } from './metrics/median'; -import { minMetricAgg } from './metrics/min'; -import { maxMetricAgg } from './metrics/max'; -import { topHitMetricAgg } from './metrics/top_hit'; -import { stdDeviationMetricAgg } from './metrics/std_deviation'; -import { cardinalityMetricAgg } from './metrics/cardinality'; -import { percentilesMetricAgg } from './metrics/percentiles'; -import { geoBoundsMetricAgg } from './metrics/geo_bounds'; -import { geoCentroidMetricAgg } from './metrics/geo_centroid'; -import { percentileRanksMetricAgg } from './metrics/percentile_ranks'; -import { derivativeMetricAgg } from './metrics/derivative'; -import { cumulativeSumMetricAgg } from './metrics/cumulative_sum'; -import { movingAvgMetricAgg } from './metrics/moving_avg'; -import { serialDiffMetricAgg } from './metrics/serial_diff'; +import { getCountMetricAgg } from './metrics/count'; +import { getAvgMetricAgg } from './metrics/avg'; +import { getSumMetricAgg } from './metrics/sum'; +import { getMedianMetricAgg } from './metrics/median'; +import { getMinMetricAgg } from './metrics/min'; +import { getMaxMetricAgg } from './metrics/max'; +import { getTopHitMetricAgg } from './metrics/top_hit'; +import { getStdDeviationMetricAgg } from './metrics/std_deviation'; +import { getCardinalityMetricAgg } from './metrics/cardinality'; +import { getPercentilesMetricAgg } from './metrics/percentiles'; +import { getGeoBoundsMetricAgg } from './metrics/geo_bounds'; +import { getGeoCentroidMetricAgg } from './metrics/geo_centroid'; +import { getPercentileRanksMetricAgg } from './metrics/percentile_ranks'; +import { getDerivativeMetricAgg } from './metrics/derivative'; +import { getCumulativeSumMetricAgg } from './metrics/cumulative_sum'; +import { getMovingAvgMetricAgg } from './metrics/moving_avg'; +import { getSerialDiffMetricAgg } from './metrics/serial_diff'; import { getDateHistogramBucketAgg } from './buckets/date_histogram'; import { getHistogramBucketAgg } from './buckets/histogram'; -import { rangeBucketAgg } from './buckets/range'; +import { getRangeBucketAgg } from './buckets/range'; import { getDateRangeBucketAgg } from './buckets/date_range'; -import { ipRangeBucketAgg } from './buckets/ip_range'; -import { termsBucketAgg } from './buckets/terms'; -import { filterBucketAgg } from './buckets/filter'; +import { getIpRangeBucketAgg } from './buckets/ip_range'; +import { getTermsBucketAgg } from './buckets/terms'; +import { getFilterBucketAgg } from './buckets/filter'; import { getFiltersBucketAgg } from './buckets/filters'; -import { significantTermsBucketAgg } from './buckets/significant_terms'; -import { geoHashBucketAgg } from './buckets/geo_hash'; -import { geoTileBucketAgg } from './buckets/geo_tile'; -import { bucketSumMetricAgg } from './metrics/bucket_sum'; -import { bucketAvgMetricAgg } from './metrics/bucket_avg'; -import { bucketMinMetricAgg } from './metrics/bucket_min'; -import { bucketMaxMetricAgg } from './metrics/bucket_max'; +import { getSignificantTermsBucketAgg } from './buckets/significant_terms'; +import { getGeoHashBucketAgg } from './buckets/geo_hash'; +import { getGeoTitleBucketAgg } from './buckets/geo_tile'; +import { getBucketSumMetricAgg } from './metrics/bucket_sum'; +import { getBucketAvgMetricAgg } from './metrics/bucket_avg'; +import { getBucketMinMetricAgg } from './metrics/bucket_min'; +import { getBucketMaxMetricAgg } from './metrics/bucket_max'; + +import { GetInternalStartServicesFn } from '../../types'; export interface AggTypesDependencies { - notifications: NotificationsSetup; uiSettings: IUiSettingsClient; query: QuerySetup; + getInternalStartServices: GetInternalStartServicesFn; } -export const getAggTypes = ({ notifications, uiSettings, query }: AggTypesDependencies) => ({ +export const getAggTypes = ({ + uiSettings, + query, + getInternalStartServices, +}: AggTypesDependencies) => ({ metrics: [ - countMetricAgg, - avgMetricAgg, - sumMetricAgg, - medianMetricAgg, - minMetricAgg, - maxMetricAgg, - stdDeviationMetricAgg, - cardinalityMetricAgg, - percentilesMetricAgg, - percentileRanksMetricAgg, - topHitMetricAgg, - derivativeMetricAgg, - cumulativeSumMetricAgg, - movingAvgMetricAgg, - serialDiffMetricAgg, - bucketAvgMetricAgg, - bucketSumMetricAgg, - bucketMinMetricAgg, - bucketMaxMetricAgg, - geoBoundsMetricAgg, - geoCentroidMetricAgg, + getCountMetricAgg({ getInternalStartServices }), + getAvgMetricAgg({ getInternalStartServices }), + getSumMetricAgg({ getInternalStartServices }), + getMedianMetricAgg({ getInternalStartServices }), + getMinMetricAgg({ getInternalStartServices }), + getMaxMetricAgg({ getInternalStartServices }), + getStdDeviationMetricAgg({ getInternalStartServices }), + getCardinalityMetricAgg({ getInternalStartServices }), + getPercentilesMetricAgg({ getInternalStartServices }), + getPercentileRanksMetricAgg({ getInternalStartServices }), + getTopHitMetricAgg({ getInternalStartServices }), + getDerivativeMetricAgg({ getInternalStartServices }), + getCumulativeSumMetricAgg({ getInternalStartServices }), + getMovingAvgMetricAgg({ getInternalStartServices }), + getSerialDiffMetricAgg({ getInternalStartServices }), + getBucketAvgMetricAgg({ getInternalStartServices }), + getBucketSumMetricAgg({ getInternalStartServices }), + getBucketMinMetricAgg({ getInternalStartServices }), + getBucketMaxMetricAgg({ getInternalStartServices }), + getGeoBoundsMetricAgg({ getInternalStartServices }), + getGeoCentroidMetricAgg({ getInternalStartServices }), ], buckets: [ - getDateHistogramBucketAgg({ uiSettings, query }), - getHistogramBucketAgg({ uiSettings, notifications }), - rangeBucketAgg, - getDateRangeBucketAgg({ uiSettings }), - ipRangeBucketAgg, - termsBucketAgg, - filterBucketAgg, - getFiltersBucketAgg({ uiSettings }), - significantTermsBucketAgg, - geoHashBucketAgg, - geoTileBucketAgg, + getDateHistogramBucketAgg({ uiSettings, query, getInternalStartServices }), + getHistogramBucketAgg({ uiSettings, getInternalStartServices }), + getRangeBucketAgg({ getInternalStartServices }), + getDateRangeBucketAgg({ uiSettings, getInternalStartServices }), + getIpRangeBucketAgg({ getInternalStartServices }), + getTermsBucketAgg({ getInternalStartServices }), + getFilterBucketAgg({ getInternalStartServices }), + getFiltersBucketAgg({ uiSettings, getInternalStartServices }), + getSignificantTermsBucketAgg({ getInternalStartServices }), + getGeoHashBucketAgg({ getInternalStartServices }), + getGeoTitleBucketAgg({ getInternalStartServices }), ], }); diff --git a/src/plugins/data/public/search/aggs/agg_types_registry.test.ts b/src/plugins/data/public/search/aggs/agg_types_registry.test.ts index 405f83e237de8..58d1a07d965e2 100644 --- a/src/plugins/data/public/search/aggs/agg_types_registry.test.ts +++ b/src/plugins/data/public/search/aggs/agg_types_registry.test.ts @@ -22,7 +22,7 @@ import { AggTypesRegistrySetup, AggTypesRegistryStart, } from './agg_types_registry'; -import { BucketAggType } from './buckets/_bucket_agg_type'; +import { BucketAggType } from './buckets/bucket_agg_type'; import { MetricAggType } from './metrics/metric_agg_type'; const bucketType = { name: 'terms', type: 'bucket' } as BucketAggType; diff --git a/src/plugins/data/public/search/aggs/agg_types_registry.ts b/src/plugins/data/public/search/aggs/agg_types_registry.ts index 8a8746106ae58..5a0c58120d810 100644 --- a/src/plugins/data/public/search/aggs/agg_types_registry.ts +++ b/src/plugins/data/public/search/aggs/agg_types_registry.ts @@ -17,7 +17,7 @@ * under the License. */ -import { BucketAggType } from './buckets/_bucket_agg_type'; +import { BucketAggType } from './buckets/bucket_agg_type'; import { MetricAggType } from './metrics/metric_agg_type'; export type AggTypesRegistrySetup = ReturnType; diff --git a/src/plugins/data/public/search/aggs/buckets/_interval_options.ts b/src/plugins/data/public/search/aggs/buckets/_interval_options.ts index 393d3b745250f..1c4c04c40a5c1 100644 --- a/src/plugins/data/public/search/aggs/buckets/_interval_options.ts +++ b/src/plugins/data/public/search/aggs/buckets/_interval_options.ts @@ -18,7 +18,7 @@ */ import { i18n } from '@kbn/i18n'; -import { IBucketAggConfig } from './_bucket_agg_type'; +import { IBucketAggConfig } from './bucket_agg_type'; export const intervalOptions = [ { diff --git a/src/plugins/data/public/search/aggs/buckets/_terms_other_bucket_helper.test.ts b/src/plugins/data/public/search/aggs/buckets/_terms_other_bucket_helper.test.ts index 9e4b93035384f..c664325a168b1 100644 --- a/src/plugins/data/public/search/aggs/buckets/_terms_other_bucket_helper.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/_terms_other_bucket_helper.test.ts @@ -24,8 +24,8 @@ import { } from './_terms_other_bucket_helper'; import { AggConfigs, CreateAggConfigParams } from '../agg_configs'; import { BUCKET_TYPES } from './bucket_agg_types'; -import { IBucketAggConfig } from './_bucket_agg_type'; -import { mockDataServices, mockAggTypesRegistry } from '../test_helpers'; +import { IBucketAggConfig } from './bucket_agg_type'; +import { mockAggTypesRegistry } from '../test_helpers'; const indexPattern = { id: '1234', @@ -223,10 +223,6 @@ describe('Terms Agg Other bucket helper', () => { return new AggConfigs(indexPattern, [...aggs], { typesRegistry }); }; - beforeEach(() => { - mockDataServices(); - }); - describe('buildOtherBucketAgg', () => { test('returns a function', () => { const aggConfigs = getAggConfigs(singleTerm.aggs); diff --git a/src/plugins/data/public/search/aggs/buckets/_terms_other_bucket_helper.ts b/src/plugins/data/public/search/aggs/buckets/_terms_other_bucket_helper.ts index 4fd988e7b7e66..abda6b5fc5980 100644 --- a/src/plugins/data/public/search/aggs/buckets/_terms_other_bucket_helper.ts +++ b/src/plugins/data/public/search/aggs/buckets/_terms_other_bucket_helper.ts @@ -21,7 +21,7 @@ import { isNumber, keys, values, find, each, cloneDeep, flatten } from 'lodash'; import { buildExistsFilter, buildPhrasesFilter, buildQueryFromFilters } from '../../../../common'; import { AggGroupNames } from '../agg_groups'; import { IAggConfigs } from '../agg_configs'; -import { IBucketAggConfig } from './_bucket_agg_type'; +import { IBucketAggConfig } from './bucket_agg_type'; /** * walks the aggregation DSL and returns DSL starting at aggregation with id of startFromAggId diff --git a/src/plugins/data/public/search/aggs/buckets/_bucket_agg_type.ts b/src/plugins/data/public/search/aggs/buckets/bucket_agg_type.ts similarity index 86% rename from src/plugins/data/public/search/aggs/buckets/_bucket_agg_type.ts rename to src/plugins/data/public/search/aggs/buckets/bucket_agg_type.ts index 03629c3189cbb..f3c95b444dee9 100644 --- a/src/plugins/data/public/search/aggs/buckets/_bucket_agg_type.ts +++ b/src/plugins/data/public/search/aggs/buckets/bucket_agg_type.ts @@ -21,6 +21,7 @@ import { IAggConfig } from '../agg_config'; import { KBN_FIELD_TYPES } from '../../../../common'; import { AggType, AggTypeConfig } from '../agg_type'; import { AggParamType } from '../param_types/agg'; +import { GetInternalStartServicesFn } from '../../../types'; export interface IBucketAggConfig extends IAggConfig { type: InstanceType; @@ -39,6 +40,10 @@ interface BucketAggTypeConfig getKey?: (bucket: any, key: any, agg: IAggConfig) => any; } +interface BucketAggTypeDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} + export class BucketAggType extends AggType< TBucketAggConfig, BucketAggParam @@ -46,8 +51,11 @@ export class BucketAggType any; type = bucketType; - constructor(config: BucketAggTypeConfig) { - super(config); + constructor( + config: BucketAggTypeConfig, + dependencies: BucketAggTypeDependencies + ) { + super(config, dependencies); this.getKey = config.getKey || diff --git a/src/plugins/data/public/search/aggs/buckets/create_filter/date_histogram.test.ts b/src/plugins/data/public/search/aggs/buckets/create_filter/date_histogram.test.ts index def354c4557cb..97c940b4ff4b1 100644 --- a/src/plugins/data/public/search/aggs/buckets/create_filter/date_histogram.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/create_filter/date_histogram.test.ts @@ -29,8 +29,9 @@ import { } from '../date_histogram'; import { BUCKET_TYPES } from '../bucket_agg_types'; import { RangeFilter } from '../../../../../common'; -import { coreMock } from '../../../../../../../core/public/mocks'; +import { coreMock, notificationServiceMock } from '../../../../../../../core/public/mocks'; import { queryServiceMock } from '../../../../query/mocks'; +import { fieldFormatsServiceMock } from '../../../../field_formats/mocks'; describe('AggConfig Filters', () => { describe('date_histogram', () => { @@ -46,6 +47,10 @@ describe('AggConfig Filters', () => { aggTypesDependencies = { uiSettings, query: queryServiceMock.createSetupContract(), + getInternalStartServices: () => ({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), + }), }; mockDataServices(); @@ -90,7 +95,7 @@ describe('AggConfig Filters', () => { filter = createFilterDateHistogram(agg, bucketKey); }; - it('creates a valid range filter', () => { + test('creates a valid range filter', () => { init(); expect(filter).toHaveProperty('range'); @@ -110,7 +115,7 @@ describe('AggConfig Filters', () => { expect(filter.meta).toHaveProperty('index', '1234'); }); - it('extends the filter edge to 1ms before the next bucket for all interval options', () => { + test('extends the filter edge to 1ms before the next bucket for all interval options', () => { intervalOptions.forEach(option => { let duration; if (option.val !== 'custom' && moment(1, option.val).isValid()) { diff --git a/src/plugins/data/public/search/aggs/buckets/create_filter/date_range.test.ts b/src/plugins/data/public/search/aggs/buckets/create_filter/date_range.test.ts index 6a03176959a83..8c0466b769a7e 100644 --- a/src/plugins/data/public/search/aggs/buckets/create_filter/date_range.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/create_filter/date_range.test.ts @@ -25,8 +25,9 @@ import { DateFormat } from '../../../../field_formats'; import { AggConfigs } from '../../agg_configs'; import { mockAggTypesRegistry } from '../../test_helpers'; import { BUCKET_TYPES } from '../bucket_agg_types'; -import { IBucketAggConfig } from '../_bucket_agg_type'; -import { coreMock } from '../../../../../../../core/public/mocks'; +import { IBucketAggConfig } from '../bucket_agg_type'; +import { coreMock, notificationServiceMock } from '../../../../../../../core/public/mocks'; +import { fieldFormatsServiceMock } from '../../../../field_formats/mocks'; describe('AggConfig Filters', () => { describe('Date range', () => { @@ -37,6 +38,10 @@ describe('AggConfig Filters', () => { aggTypesDependencies = { uiSettings, + getInternalStartServices: () => ({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), + }), }; }); @@ -71,7 +76,7 @@ describe('AggConfig Filters', () => { ); }; - it('should return a range filter for date_range agg', () => { + test('should return a range filter for date_range agg', () => { const aggConfigs = getAggConfigs(); const from = new Date('1 Feb 2015'); const to = new Date('7 Feb 2015'); diff --git a/src/plugins/data/public/search/aggs/buckets/create_filter/date_range.ts b/src/plugins/data/public/search/aggs/buckets/create_filter/date_range.ts index 9bfded0ce9729..118e9b26e87d5 100644 --- a/src/plugins/data/public/search/aggs/buckets/create_filter/date_range.ts +++ b/src/plugins/data/public/search/aggs/buckets/create_filter/date_range.ts @@ -18,7 +18,7 @@ */ import moment from 'moment'; -import { IBucketAggConfig } from '../_bucket_agg_type'; +import { IBucketAggConfig } from '../bucket_agg_type'; import { DateRangeKey } from '../lib/date_range'; import { buildRangeFilter, RangeFilterParams } from '../../../../../common'; diff --git a/src/plugins/data/public/search/aggs/buckets/create_filter/filters.test.ts b/src/plugins/data/public/search/aggs/buckets/create_filter/filters.test.ts index 32ada8d57c768..f5a0b5a7b9094 100644 --- a/src/plugins/data/public/search/aggs/buckets/create_filter/filters.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/create_filter/filters.test.ts @@ -21,8 +21,9 @@ import { getFiltersBucketAgg, FiltersBucketAggDependencies } from '../filters'; import { createFilterFilters } from './filters'; import { AggConfigs } from '../../agg_configs'; import { mockAggTypesRegistry } from '../../test_helpers'; -import { IBucketAggConfig } from '../_bucket_agg_type'; -import { coreMock } from '../../../../../../../core/public/mocks'; +import { IBucketAggConfig } from '../bucket_agg_type'; +import { coreMock, notificationServiceMock } from '../../../../../../../core/public/mocks'; +import { fieldFormatsServiceMock } from '../../../../field_formats/mocks'; describe('AggConfig Filters', () => { describe('filters', () => { @@ -33,6 +34,10 @@ describe('AggConfig Filters', () => { aggTypesDependencies = { uiSettings, + getInternalStartServices: () => ({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), + }), }; }); @@ -67,7 +72,8 @@ describe('AggConfig Filters', () => { { typesRegistry: mockAggTypesRegistry([getFiltersBucketAgg(aggTypesDependencies)]) } ); }; - it('should return a filters filter', () => { + + test('should return a filters filter', () => { const aggConfigs = getAggConfigs(); const filter = createFilterFilters(aggConfigs.aggs[0] as IBucketAggConfig, 'type:nginx'); diff --git a/src/plugins/data/public/search/aggs/buckets/create_filter/filters.ts b/src/plugins/data/public/search/aggs/buckets/create_filter/filters.ts index 3b568d805f7c0..1999b759a23d0 100644 --- a/src/plugins/data/public/search/aggs/buckets/create_filter/filters.ts +++ b/src/plugins/data/public/search/aggs/buckets/create_filter/filters.ts @@ -18,7 +18,7 @@ */ import { get } from 'lodash'; -import { IBucketAggConfig } from '../_bucket_agg_type'; +import { IBucketAggConfig } from '../bucket_agg_type'; import { buildQueryFilter } from '../../../../../common'; export const createFilterFilters = (aggConfig: IBucketAggConfig, key: string) => { diff --git a/src/plugins/data/public/search/aggs/buckets/create_filter/histogram.test.ts b/src/plugins/data/public/search/aggs/buckets/create_filter/histogram.test.ts index dc8414d80c024..18b388be74877 100644 --- a/src/plugins/data/public/search/aggs/buckets/create_filter/histogram.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/create_filter/histogram.test.ts @@ -19,19 +19,13 @@ import { createFilterHistogram } from './histogram'; import { AggConfigs } from '../../agg_configs'; -import { mockDataServices, mockAggTypesRegistry } from '../../test_helpers'; +import { mockAggTypesRegistry } from '../../test_helpers'; import { BUCKET_TYPES } from '../bucket_agg_types'; -import { IBucketAggConfig } from '../_bucket_agg_type'; +import { IBucketAggConfig } from '../bucket_agg_type'; import { BytesFormat, FieldFormatsGetConfigFn } from '../../../../../common'; describe('AggConfig Filters', () => { describe('histogram', () => { - beforeEach(() => { - mockDataServices(); - }); - - const typesRegistry = mockAggTypesRegistry(); - const getConfig = (() => {}) as FieldFormatsGetConfigFn; const getAggConfigs = () => { const field = { @@ -61,11 +55,11 @@ describe('AggConfig Filters', () => { }, }, ], - { typesRegistry } + { typesRegistry: mockAggTypesRegistry() } ); }; - it('should return an range filter for histogram', () => { + test('should return an range filter for histogram', () => { const aggConfigs = getAggConfigs(); const filter = createFilterHistogram(aggConfigs.aggs[0] as IBucketAggConfig, '2048'); diff --git a/src/plugins/data/public/search/aggs/buckets/create_filter/histogram.ts b/src/plugins/data/public/search/aggs/buckets/create_filter/histogram.ts index d4c00a0991fe2..f8e7747d49147 100644 --- a/src/plugins/data/public/search/aggs/buckets/create_filter/histogram.ts +++ b/src/plugins/data/public/search/aggs/buckets/create_filter/histogram.ts @@ -17,7 +17,7 @@ * under the License. */ -import { IBucketAggConfig } from '../_bucket_agg_type'; +import { IBucketAggConfig } from '../bucket_agg_type'; import { buildRangeFilter, RangeFilterParams } from '../../../../../common'; export const createFilterHistogram = (aggConfig: IBucketAggConfig, key: string) => { diff --git a/src/plugins/data/public/search/aggs/buckets/create_filter/ip_range.test.ts b/src/plugins/data/public/search/aggs/buckets/create_filter/ip_range.test.ts index ca51094da2f58..b528313b080d0 100644 --- a/src/plugins/data/public/search/aggs/buckets/create_filter/ip_range.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/create_filter/ip_range.test.ts @@ -17,17 +17,26 @@ * under the License. */ -import { ipRangeBucketAgg } from '../ip_range'; +import { getIpRangeBucketAgg } from '../ip_range'; import { createFilterIpRange } from './ip_range'; import { AggConfigs, CreateAggConfigParams } from '../../agg_configs'; import { mockAggTypesRegistry } from '../../test_helpers'; import { IpFormat } from '../../../../../common'; import { BUCKET_TYPES } from '../bucket_agg_types'; -import { IBucketAggConfig } from '../_bucket_agg_type'; +import { IBucketAggConfig } from '../bucket_agg_type'; +import { fieldFormatsServiceMock } from '../../../../field_formats/mocks'; +import { notificationServiceMock } from '../../../../../../../core/public/mocks'; describe('AggConfig Filters', () => { describe('IP range', () => { - const typesRegistry = mockAggTypesRegistry([ipRangeBucketAgg]); + const typesRegistry = mockAggTypesRegistry([ + getIpRangeBucketAgg({ + getInternalStartServices: () => ({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), + }), + }), + ]); const getAggConfigs = (aggs: CreateAggConfigParams[]) => { const field = { name: 'ip', @@ -46,7 +55,7 @@ describe('AggConfig Filters', () => { return new AggConfigs(indexPattern, aggs, { typesRegistry }); }; - it('should return a range filter for ip_range agg', () => { + test('should return a range filter for ip_range agg', () => { const aggConfigs = getAggConfigs([ { type: BUCKET_TYPES.IP_RANGE, @@ -75,7 +84,7 @@ describe('AggConfig Filters', () => { expect(filter.range.ip).toHaveProperty('lte', '1.1.1.1'); }); - it('should return a range filter for ip_range agg using a CIDR mask', () => { + test('should return a range filter for ip_range agg using a CIDR mask', () => { const aggConfigs = getAggConfigs([ { type: BUCKET_TYPES.IP_RANGE, diff --git a/src/plugins/data/public/search/aggs/buckets/create_filter/ip_range.ts b/src/plugins/data/public/search/aggs/buckets/create_filter/ip_range.ts index 2d34c45aaab9d..aae212783b873 100644 --- a/src/plugins/data/public/search/aggs/buckets/create_filter/ip_range.ts +++ b/src/plugins/data/public/search/aggs/buckets/create_filter/ip_range.ts @@ -18,7 +18,7 @@ */ import { CidrMask } from '../lib/cidr_mask'; -import { IBucketAggConfig } from '../_bucket_agg_type'; +import { IBucketAggConfig } from '../bucket_agg_type'; import { IpRangeKey } from '../lib/ip_range'; import { buildRangeFilter, RangeFilterParams } from '../../../../../common'; diff --git a/src/plugins/data/public/search/aggs/buckets/create_filter/range.test.ts b/src/plugins/data/public/search/aggs/buckets/create_filter/range.test.ts index 3a6f8b36a9d96..14a7538aa95a4 100644 --- a/src/plugins/data/public/search/aggs/buckets/create_filter/range.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/create_filter/range.test.ts @@ -17,22 +17,31 @@ * under the License. */ -import { rangeBucketAgg } from '../range'; +import { getRangeBucketAgg, RangeBucketAggDependencies } from '../range'; import { createFilterRange } from './range'; import { BytesFormat, FieldFormatsGetConfigFn } from '../../../../../common'; import { AggConfigs } from '../../agg_configs'; import { mockDataServices, mockAggTypesRegistry } from '../../test_helpers'; import { BUCKET_TYPES } from '../bucket_agg_types'; -import { IBucketAggConfig } from '../_bucket_agg_type'; +import { IBucketAggConfig } from '../bucket_agg_type'; +import { fieldFormatsServiceMock } from '../../../../field_formats/mocks'; +import { notificationServiceMock } from '../../../../../../../core/public/mocks'; describe('AggConfig Filters', () => { describe('range', () => { + let aggTypesDependencies: RangeBucketAggDependencies; + beforeEach(() => { + aggTypesDependencies = { + getInternalStartServices: () => ({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), + }), + }; + mockDataServices(); }); - const typesRegistry = mockAggTypesRegistry([rangeBucketAgg]); - const getConfig = (() => {}) as FieldFormatsGetConfigFn; const getAggConfigs = () => { const field = { @@ -62,11 +71,11 @@ describe('AggConfig Filters', () => { }, }, ], - { typesRegistry } + { typesRegistry: mockAggTypesRegistry([getRangeBucketAgg(aggTypesDependencies)]) } ); }; - it('should return a range filter for range agg', () => { + test('should return a range filter for range agg', () => { const aggConfigs = getAggConfigs(); const filter = createFilterRange(aggConfigs.aggs[0] as IBucketAggConfig, { gte: 1024, diff --git a/src/plugins/data/public/search/aggs/buckets/create_filter/range.ts b/src/plugins/data/public/search/aggs/buckets/create_filter/range.ts index d3d85f2441a8b..cbad8742bfab6 100644 --- a/src/plugins/data/public/search/aggs/buckets/create_filter/range.ts +++ b/src/plugins/data/public/search/aggs/buckets/create_filter/range.ts @@ -17,7 +17,7 @@ * under the License. */ -import { IBucketAggConfig } from '../_bucket_agg_type'; +import { IBucketAggConfig } from '../bucket_agg_type'; import { buildRangeFilter } from '../../../../../common'; export const createFilterRange = (aggConfig: IBucketAggConfig, params: any) => { diff --git a/src/plugins/data/public/search/aggs/buckets/create_filter/terms.test.ts b/src/plugins/data/public/search/aggs/buckets/create_filter/terms.test.ts index 511af450b0113..c11a7d1a4e6b8 100644 --- a/src/plugins/data/public/search/aggs/buckets/create_filter/terms.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/create_filter/terms.test.ts @@ -17,17 +17,30 @@ * under the License. */ -import { termsBucketAgg } from '../terms'; +import { getTermsBucketAgg } from '../terms'; import { createFilterTerms } from './terms'; import { AggConfigs, CreateAggConfigParams } from '../../agg_configs'; import { mockAggTypesRegistry } from '../../test_helpers'; import { BUCKET_TYPES } from '../bucket_agg_types'; -import { IBucketAggConfig } from '../_bucket_agg_type'; +import { IBucketAggConfig } from '../bucket_agg_type'; import { Filter, ExistsFilter } from '../../../../../common'; +import { RangeBucketAggDependencies } from '../range'; +import { fieldFormatsServiceMock } from '../../../../field_formats/mocks'; +import { notificationServiceMock } from '../../../../../../../core/public/mocks'; describe('AggConfig Filters', () => { describe('terms', () => { - const typesRegistry = mockAggTypesRegistry([termsBucketAgg]); + let aggTypesDependencies: RangeBucketAggDependencies; + + beforeEach(() => { + aggTypesDependencies = { + getInternalStartServices: () => ({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), + }), + }; + }); + const getAggConfigs = (aggs: CreateAggConfigParams[]) => { const indexPattern = { id: '1234', @@ -43,10 +56,12 @@ describe('AggConfig Filters', () => { indexPattern, }; - return new AggConfigs(indexPattern, aggs, { typesRegistry }); + return new AggConfigs(indexPattern, aggs, { + typesRegistry: mockAggTypesRegistry([getTermsBucketAgg(aggTypesDependencies)]), + }); }; - it('should return a match_phrase filter for terms', () => { + test('should return a match_phrase filter for terms', () => { const aggConfigs = getAggConfigs([ { type: BUCKET_TYPES.TERMS, schema: 'segment', params: { field: 'field' } }, ]); @@ -65,7 +80,7 @@ describe('AggConfig Filters', () => { expect(filter.meta).toHaveProperty('index', '1234'); }); - it('should set query to true or false for boolean filter', () => { + test('should set query to true or false for boolean filter', () => { const aggConfigs = getAggConfigs([ { type: BUCKET_TYPES.TERMS, schema: 'segment', params: { field: 'field' } }, ]); @@ -93,7 +108,7 @@ describe('AggConfig Filters', () => { expect(filterTrue.query.match_phrase.field).toBeTruthy(); }); - it('should generate correct __missing__ filter', () => { + test('should generate correct __missing__ filter', () => { const aggConfigs = getAggConfigs([ { type: BUCKET_TYPES.TERMS, schema: 'segment', params: { field: 'field' } }, ]); @@ -110,7 +125,7 @@ describe('AggConfig Filters', () => { expect(filter.meta).toHaveProperty('negate', true); }); - it('should generate correct __other__ filter', () => { + test('should generate correct __other__ filter', () => { const aggConfigs = getAggConfigs([ { type: BUCKET_TYPES.TERMS, schema: 'segment', params: { field: 'field' } }, ]); diff --git a/src/plugins/data/public/search/aggs/buckets/create_filter/terms.ts b/src/plugins/data/public/search/aggs/buckets/create_filter/terms.ts index 43ebfc0e90db2..95de19b96abd4 100644 --- a/src/plugins/data/public/search/aggs/buckets/create_filter/terms.ts +++ b/src/plugins/data/public/search/aggs/buckets/create_filter/terms.ts @@ -17,7 +17,7 @@ * under the License. */ -import { IBucketAggConfig } from '../_bucket_agg_type'; +import { IBucketAggConfig } from '../bucket_agg_type'; import { buildPhrasesFilter, buildExistsFilter, diff --git a/src/plugins/data/public/search/aggs/buckets/date_histogram.ts b/src/plugins/data/public/search/aggs/buckets/date_histogram.ts index 7701f1bbcb4d0..e6fd259fabc92 100644 --- a/src/plugins/data/public/search/aggs/buckets/date_histogram.ts +++ b/src/plugins/data/public/search/aggs/buckets/date_histogram.ts @@ -23,7 +23,7 @@ import { i18n } from '@kbn/i18n'; import { IUiSettingsClient } from 'src/core/public'; import { TimeBuckets } from './lib/time_buckets'; -import { BucketAggType, IBucketAggConfig } from './_bucket_agg_type'; +import { BucketAggType, IBucketAggConfig } from './bucket_agg_type'; import { BUCKET_TYPES } from './bucket_agg_types'; import { createFilterDateHistogram } from './create_filter/date_histogram'; import { intervalOptions } from './_interval_options'; @@ -33,8 +33,8 @@ import { isMetricAggType } from '../metrics/metric_agg_type'; import { FIELD_FORMAT_IDS, KBN_FIELD_TYPES } from '../../../../common'; import { TimefilterContract } from '../../../query'; -import { getFieldFormats } from '../../../../public/services'; import { QuerySetup } from '../../../query/query_service'; +import { GetInternalStartServicesFn } from '../../../types'; const detectedTimezone = moment.tz.guess(); const tzOffset = moment().format('Z'); @@ -61,6 +61,7 @@ interface ITimeBuckets { export interface DateHistogramBucketAggDependencies { uiSettings: IUiSettingsClient; query: QuerySetup; + getInternalStartServices: GetInternalStartServicesFn; } export interface IBucketDateHistogramAggConfig extends IBucketAggConfig { @@ -74,211 +75,218 @@ export function isDateHistogramBucketAggConfig(agg: any): agg is IBucketDateHist export const getDateHistogramBucketAgg = ({ uiSettings, query, + getInternalStartServices, }: DateHistogramBucketAggDependencies) => - new BucketAggType({ - name: BUCKET_TYPES.DATE_HISTOGRAM, - title: i18n.translate('data.search.aggs.buckets.dateHistogramTitle', { - defaultMessage: 'Date Histogram', - }), - ordered: { - date: true, - }, - makeLabel(agg) { - let output: Record = {}; + new BucketAggType( + { + name: BUCKET_TYPES.DATE_HISTOGRAM, + title: i18n.translate('data.search.aggs.buckets.dateHistogramTitle', { + defaultMessage: 'Date Histogram', + }), + ordered: { + date: true, + }, + makeLabel(agg) { + let output: Record = {}; - if (this.params) { - output = writeParams(this.params, agg); - } + if (this.params) { + output = writeParams(this.params, agg); + } - const field = agg.getFieldDisplayName(); - return i18n.translate('data.search.aggs.buckets.dateHistogramLabel', { - defaultMessage: '{fieldName} per {intervalDescription}', - values: { - fieldName: field, - intervalDescription: output.metricScaleText || output.bucketInterval.description, - }, - }); - }, - createFilter: createFilterDateHistogram, - decorateAggConfig() { - let buckets: any; + const field = agg.getFieldDisplayName(); + return i18n.translate('data.search.aggs.buckets.dateHistogramLabel', { + defaultMessage: '{fieldName} per {intervalDescription}', + values: { + fieldName: field, + intervalDescription: output.metricScaleText || output.bucketInterval.description, + }, + }); + }, + createFilter: createFilterDateHistogram, + decorateAggConfig() { + let buckets: any; - return { - buckets: { - configurable: true, - get() { - if (buckets) return buckets; + return { + buckets: { + configurable: true, + get() { + if (buckets) return buckets; - const { timefilter } = query.timefilter; - buckets = new TimeBuckets({ uiSettings }); - updateTimeBuckets(this, timefilter, buckets); + const { timefilter } = query.timefilter; + buckets = new TimeBuckets({ uiSettings }); + updateTimeBuckets(this, timefilter, buckets); - return buckets; - }, - } as any, - }; - }, - getFormat(agg) { - const DateFieldFormat = getFieldFormats().getType(FIELD_FORMAT_IDS.DATE); + return buckets; + }, + } as any, + }; + }, + getFormat(agg) { + const { fieldFormats } = getInternalStartServices(); + const DateFieldFormat = fieldFormats.getType(FIELD_FORMAT_IDS.DATE); - if (!DateFieldFormat) { - throw new Error('Unable to retrieve Date Field Format'); - } + if (!DateFieldFormat) { + throw new Error('Unable to retrieve Date Field Format'); + } - return new DateFieldFormat( + return new DateFieldFormat( + { + pattern: agg.buckets.getScaledDateFormat(), + }, + (key: string) => uiSettings.get(key) + ); + }, + params: [ { - pattern: agg.buckets.getScaledDateFormat(), + name: 'field', + type: 'field', + filterFieldTypes: KBN_FIELD_TYPES.DATE, + default(agg: IBucketDateHistogramAggConfig) { + return agg.getIndexPattern().timeFieldName; + }, + onChange(agg: IBucketDateHistogramAggConfig) { + if (get(agg, 'params.interval') === 'auto' && !agg.fieldIsTimeField()) { + delete agg.params.interval; + } + }, }, - (key: string) => uiSettings.get(key) - ); - }, - params: [ - { - name: 'field', - type: 'field', - filterFieldTypes: KBN_FIELD_TYPES.DATE, - default(agg: IBucketDateHistogramAggConfig) { - return agg.getIndexPattern().timeFieldName; + { + name: 'timeRange', + default: null, + write: noop, }, - onChange(agg: IBucketDateHistogramAggConfig) { - if (get(agg, 'params.interval') === 'auto' && !agg.fieldIsTimeField()) { - delete agg.params.interval; - } + { + name: 'useNormalizedEsInterval', + default: true, + write: noop, }, - }, - { - name: 'timeRange', - default: null, - write: noop, - }, - { - name: 'useNormalizedEsInterval', - default: true, - write: noop, - }, - { - name: 'scaleMetricValues', - default: false, - write: noop, - advanced: true, - }, - { - name: 'interval', - deserialize(state: any, agg) { - // For upgrading from 7.0.x to 7.1.x - intervals are now stored as key of options or custom value - if (state === 'custom') { - return get(agg, 'params.customInterval'); - } + { + name: 'scaleMetricValues', + default: false, + write: noop, + advanced: true, + }, + { + name: 'interval', + deserialize(state: any, agg) { + // For upgrading from 7.0.x to 7.1.x - intervals are now stored as key of options or custom value + if (state === 'custom') { + return get(agg, 'params.customInterval'); + } - const interval = find(intervalOptions, { val: state }); + const interval = find(intervalOptions, { val: state }); - // For upgrading from 4.0.x to 4.1.x - intervals are now stored as 'y' instead of 'year', - // but this maps the old values to the new values - if (!interval && state === 'year') { - return 'y'; - } - return state; - }, - default: 'auto', - options: intervalOptions, - write(agg, output, aggs) { - const { timefilter } = query.timefilter; - updateTimeBuckets(agg, timefilter); + // For upgrading from 4.0.x to 4.1.x - intervals are now stored as 'y' instead of 'year', + // but this maps the old values to the new values + if (!interval && state === 'year') { + return 'y'; + } + return state; + }, + default: 'auto', + options: intervalOptions, + write(agg, output, aggs) { + const { timefilter } = query.timefilter; + updateTimeBuckets(agg, timefilter); - const { useNormalizedEsInterval, scaleMetricValues } = agg.params; - const interval = agg.buckets.getInterval(useNormalizedEsInterval); - output.bucketInterval = interval; - if (interval.expression === '0ms') { - // We are hitting this code a couple of times while configuring in editor - // with an interval of 0ms because the overall time range has not yet been - // set. Since 0ms is not a valid ES interval, we cannot pass it through dateHistogramInterval - // below, since it would throw an exception. So in the cases we still have an interval of 0ms - // here we simply skip the rest of the method and never write an interval into the DSL, since - // this DSL will anyway not be used before we're passing this code with an actual interval. - return; - } - output.params = { - ...output.params, - ...dateHistogramInterval(interval.expression), - }; + const { useNormalizedEsInterval, scaleMetricValues } = agg.params; + const interval = agg.buckets.getInterval(useNormalizedEsInterval); + output.bucketInterval = interval; + if (interval.expression === '0ms') { + // We are hitting this code a couple of times while configuring in editor + // with an interval of 0ms because the overall time range has not yet been + // set. Since 0ms is not a valid ES interval, we cannot pass it through dateHistogramInterval + // below, since it would throw an exception. So in the cases we still have an interval of 0ms + // here we simply skip the rest of the method and never write an interval into the DSL, since + // this DSL will anyway not be used before we're passing this code with an actual interval. + return; + } + output.params = { + ...output.params, + ...dateHistogramInterval(interval.expression), + }; - const scaleMetrics = scaleMetricValues && interval.scaled && interval.scale < 1; - if (scaleMetrics && aggs) { - const metrics = aggs.aggs.filter(a => isMetricAggType(a.type)); - const all = every(metrics, (a: IBucketAggConfig) => { - const { type } = a; + const scaleMetrics = scaleMetricValues && interval.scaled && interval.scale < 1; + if (scaleMetrics && aggs) { + const metrics = aggs.aggs.filter(a => isMetricAggType(a.type)); + const all = every(metrics, (a: IBucketAggConfig) => { + const { type } = a; - if (isMetricAggType(type)) { - return type.isScalable(); + if (isMetricAggType(type)) { + return type.isScalable(); + } + }); + if (all) { + output.metricScale = interval.scale; + output.metricScaleText = interval.preScaled.description; } - }); - if (all) { - output.metricScale = interval.scale; - output.metricScaleText = interval.preScaled.description; } - } + }, }, - }, - { - name: 'time_zone', - default: undefined, - // 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 - // time_zones being persisted into saved_objects - serialize: noop, - write(agg, output) { - // If a time_zone has been set explicitly always prefer this. - let tz = agg.params.time_zone; - if (!tz && agg.params.field) { - // If a field has been configured check the index pattern's typeMeta if a date_histogram on that - // field requires a specific time_zone - tz = get(agg.getIndexPattern(), [ - 'typeMeta', - 'aggs', - 'date_histogram', - agg.params.field.name, - 'time_zone', - ]); - } - if (!tz) { - // If the index pattern typeMeta data, didn't had a time zone assigned for the selected field use the configured tz - const isDefaultTimezone = uiSettings.isDefault('dateFormat:tz'); - tz = isDefaultTimezone ? detectedTimezone || tzOffset : uiSettings.get('dateFormat:tz'); - } - output.params.time_zone = tz; + { + name: 'time_zone', + default: undefined, + // 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 + // time_zones being persisted into saved_objects + serialize: noop, + write(agg, output) { + // If a time_zone has been set explicitly always prefer this. + let tz = agg.params.time_zone; + if (!tz && agg.params.field) { + // If a field has been configured check the index pattern's typeMeta if a date_histogram on that + // field requires a specific time_zone + tz = get(agg.getIndexPattern(), [ + 'typeMeta', + 'aggs', + 'date_histogram', + agg.params.field.name, + 'time_zone', + ]); + } + if (!tz) { + // If the index pattern typeMeta data, didn't had a time zone assigned for the selected field use the configured tz + const isDefaultTimezone = uiSettings.isDefault('dateFormat:tz'); + tz = isDefaultTimezone + ? detectedTimezone || tzOffset + : uiSettings.get('dateFormat:tz'); + } + output.params.time_zone = tz; + }, }, - }, - { - name: 'drop_partials', - default: false, - write: noop, - shouldShow: agg => { - const field = agg.params.field; - return field && field.name && field.name === agg.getIndexPattern().timeFieldName; + { + name: 'drop_partials', + default: false, + write: noop, + shouldShow: agg => { + const field = agg.params.field; + return field && field.name && field.name === agg.getIndexPattern().timeFieldName; + }, }, - }, - { - name: 'format', - }, - { - name: 'min_doc_count', - default: 1, - }, - { - name: 'extended_bounds', - default: {}, - write(agg, output) { - const val = agg.params.extended_bounds; + { + name: 'format', + }, + { + name: 'min_doc_count', + default: 1, + }, + { + name: 'extended_bounds', + default: {}, + write(agg, output) { + const val = agg.params.extended_bounds; - if (val.min != null || val.max != null) { - output.params.extended_bounds = { - min: moment(val.min).valueOf(), - max: moment(val.max).valueOf(), - }; + if (val.min != null || val.max != null) { + output.params.extended_bounds = { + min: moment(val.min).valueOf(), + max: moment(val.max).valueOf(), + }; - return; - } + return; + } + }, }, - }, - ], - }); + ], + }, + { getInternalStartServices } + ); diff --git a/src/plugins/data/public/search/aggs/buckets/date_range.test.ts b/src/plugins/data/public/search/aggs/buckets/date_range.test.ts index 4ea550492fa09..c050620c3a856 100644 --- a/src/plugins/data/public/search/aggs/buckets/date_range.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/date_range.test.ts @@ -17,11 +17,12 @@ * under the License. */ -import { coreMock } from '../../../../../../../src/core/public/mocks'; +import { coreMock, notificationServiceMock } from '../../../../../../../src/core/public/mocks'; import { getDateRangeBucketAgg, DateRangeBucketAggDependencies } from './date_range'; import { AggConfigs } from '../agg_configs'; import { mockAggTypesRegistry } from '../test_helpers'; import { BUCKET_TYPES } from './bucket_agg_types'; +import { fieldFormatsServiceMock } from '../../../field_formats/mocks'; describe('date_range params', () => { let aggTypesDependencies: DateRangeBucketAggDependencies; @@ -31,6 +32,10 @@ describe('date_range params', () => { aggTypesDependencies = { uiSettings, + getInternalStartServices: () => ({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), + }), }; }); diff --git a/src/plugins/data/public/search/aggs/buckets/date_range.ts b/src/plugins/data/public/search/aggs/buckets/date_range.ts index 8133a47ec7248..07d927e64a943 100644 --- a/src/plugins/data/public/search/aggs/buckets/date_range.ts +++ b/src/plugins/data/public/search/aggs/buckets/date_range.ts @@ -23,12 +23,12 @@ import { i18n } from '@kbn/i18n'; import { IUiSettingsClient } from 'src/core/public'; import { BUCKET_TYPES } from './bucket_agg_types'; -import { BucketAggType, IBucketAggConfig } from './_bucket_agg_type'; +import { BucketAggType, IBucketAggConfig } from './bucket_agg_type'; import { createFilterDateRange } from './create_filter/date_range'; import { convertDateRangeToString, DateRangeKey } from './lib/date_range'; import { KBN_FIELD_TYPES, FieldFormat, TEXT_CONTEXT_TYPE } from '../../../../common'; -import { getFieldFormats } from '../../../../public/services'; +import { GetInternalStartServicesFn } from '../../../types'; const dateRangeTitle = i18n.translate('data.search.aggs.buckets.dateRangeTitle', { defaultMessage: 'Date Range', @@ -36,76 +36,85 @@ const dateRangeTitle = i18n.translate('data.search.aggs.buckets.dateRangeTitle', export interface DateRangeBucketAggDependencies { uiSettings: IUiSettingsClient; + getInternalStartServices: GetInternalStartServicesFn; } -export const getDateRangeBucketAgg = ({ uiSettings }: DateRangeBucketAggDependencies) => - new BucketAggType({ - name: BUCKET_TYPES.DATE_RANGE, - title: dateRangeTitle, - createFilter: createFilterDateRange, - getKey({ from, to }): DateRangeKey { - return { from, to }; - }, - getFormat(agg) { - const fieldFormatsService = getFieldFormats(); +export const getDateRangeBucketAgg = ({ + uiSettings, + getInternalStartServices, +}: DateRangeBucketAggDependencies) => + new BucketAggType( + { + name: BUCKET_TYPES.DATE_RANGE, + title: dateRangeTitle, + createFilter: createFilterDateRange, + getKey({ from, to }): DateRangeKey { + return { from, to }; + }, + getFormat(agg) { + const { fieldFormats } = getInternalStartServices(); - const formatter = agg.fieldOwnFormatter( - TEXT_CONTEXT_TYPE, - fieldFormatsService.getDefaultInstance(KBN_FIELD_TYPES.DATE) - ); - const DateRangeFormat = FieldFormat.from(function(range: DateRangeKey) { - return convertDateRangeToString(range, formatter); - }); - return new DateRangeFormat(); - }, - makeLabel(aggConfig) { - return aggConfig.getFieldDisplayName() + ' date ranges'; - }, - params: [ - { - name: 'field', - type: 'field', - filterFieldTypes: KBN_FIELD_TYPES.DATE, - default(agg: IBucketAggConfig) { - return agg.getIndexPattern().timeFieldName; - }, + const formatter = agg.fieldOwnFormatter( + TEXT_CONTEXT_TYPE, + fieldFormats.getDefaultInstance(KBN_FIELD_TYPES.DATE) + ); + const DateRangeFormat = FieldFormat.from(function(range: DateRangeKey) { + return convertDateRangeToString(range, formatter); + }); + return new DateRangeFormat(); }, - { - name: 'ranges', - default: [ - { - from: 'now-1w/w', - to: 'now', - }, - ], + makeLabel(aggConfig) { + return aggConfig.getFieldDisplayName() + ' date ranges'; }, - { - name: 'time_zone', - default: undefined, - // Implimentation method is the same as that of date_histogram - serialize: () => undefined, - write: (agg, output) => { - const field = agg.getParam('field'); - let tz = agg.getParam('time_zone'); + params: [ + { + name: 'field', + type: 'field', + filterFieldTypes: KBN_FIELD_TYPES.DATE, + default(agg: IBucketAggConfig) { + return agg.getIndexPattern().timeFieldName; + }, + }, + { + name: 'ranges', + default: [ + { + from: 'now-1w/w', + to: 'now', + }, + ], + }, + { + name: 'time_zone', + default: undefined, + // Implimentation method is the same as that of date_histogram + serialize: () => undefined, + write: (agg, output) => { + 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 detectedTimezone = moment.tz.guess(); - const tzOffset = moment().format('Z'); - const isDefaultTimezone = uiSettings.isDefault('dateFormat:tz'); + if (!tz && field) { + tz = get(agg.getIndexPattern(), [ + 'typeMeta', + 'aggs', + 'date_range', + field.name, + 'time_zone', + ]); + } + if (!tz) { + const detectedTimezone = moment.tz.guess(); + const tzOffset = moment().format('Z'); + const isDefaultTimezone = uiSettings.isDefault('dateFormat:tz'); - tz = isDefaultTimezone ? detectedTimezone || tzOffset : uiSettings.get('dateFormat:tz'); - } - output.params.time_zone = tz; + tz = isDefaultTimezone + ? detectedTimezone || tzOffset + : uiSettings.get('dateFormat:tz'); + } + output.params.time_zone = tz; + }, }, - }, - ], - }); + ], + }, + { getInternalStartServices } + ); diff --git a/src/plugins/data/public/search/aggs/buckets/filter.ts b/src/plugins/data/public/search/aggs/buckets/filter.ts index 80efc0cf92071..accbdf4dd783d 100644 --- a/src/plugins/data/public/search/aggs/buckets/filter.ts +++ b/src/plugins/data/public/search/aggs/buckets/filter.ts @@ -18,19 +18,28 @@ */ import { i18n } from '@kbn/i18n'; -import { BucketAggType } from './_bucket_agg_type'; +import { BucketAggType } from './bucket_agg_type'; import { BUCKET_TYPES } from './bucket_agg_types'; +import { GetInternalStartServicesFn } from '../../../types'; const filterTitle = i18n.translate('data.search.aggs.buckets.filterTitle', { defaultMessage: 'Filter', }); -export const filterBucketAgg = new BucketAggType({ - name: BUCKET_TYPES.FILTER, - title: filterTitle, - params: [ +export interface FilterBucketAggDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} + +export const getFilterBucketAgg = ({ getInternalStartServices }: FilterBucketAggDependencies) => + new BucketAggType( { - name: 'geo_bounding_box', + name: BUCKET_TYPES.FILTER, + title: filterTitle, + params: [ + { + name: 'geo_bounding_box', + }, + ], }, - ], -}); + { getInternalStartServices } + ); diff --git a/src/plugins/data/public/search/aggs/buckets/filters.ts b/src/plugins/data/public/search/aggs/buckets/filters.ts index 8b9aca87f8735..a42cb70a62b7d 100644 --- a/src/plugins/data/public/search/aggs/buckets/filters.ts +++ b/src/plugins/data/public/search/aggs/buckets/filters.ts @@ -23,11 +23,12 @@ import { IUiSettingsClient } from 'src/core/public'; import { createFilterFilters } from './create_filter/filters'; import { toAngularJSON } from '../utils'; -import { BucketAggType } from './_bucket_agg_type'; +import { BucketAggType } from './bucket_agg_type'; import { BUCKET_TYPES } from './bucket_agg_types'; import { Storage } from '../../../../../../plugins/kibana_utils/public'; import { getEsQueryConfig, buildEsQuery, Query } from '../../../../common'; import { getQueryLog } from '../../../query'; +import { GetInternalStartServicesFn } from '../../../types'; const filtersTitle = i18n.translate('data.search.aggs.buckets.filtersTitle', { defaultMessage: 'Filters', @@ -43,69 +44,81 @@ interface FilterValue { export interface FiltersBucketAggDependencies { uiSettings: IUiSettingsClient; + getInternalStartServices: GetInternalStartServicesFn; } -export const getFiltersBucketAgg = ({ uiSettings }: FiltersBucketAggDependencies) => - new BucketAggType({ - name: BUCKET_TYPES.FILTERS, - title: filtersTitle, - createFilter: createFilterFilters, - customLabels: false, - params: [ - { - name: 'filters', - default: [ - { input: { query: '', language: uiSettings.get('search:queryLanguage') }, label: '' }, - ], - write(aggConfig, output) { - const inFilters: FilterValue[] = aggConfig.params.filters; - if (!size(inFilters)) return; +export const getFiltersBucketAgg = ({ + uiSettings, + getInternalStartServices, +}: FiltersBucketAggDependencies) => + new BucketAggType( + { + name: BUCKET_TYPES.FILTERS, + title: filtersTitle, + createFilter: createFilterFilters, + customLabels: false, + params: [ + { + name: 'filters', + default: [ + { input: { query: '', language: uiSettings.get('search:queryLanguage') }, label: '' }, + ], + write(aggConfig, output) { + const inFilters: FilterValue[] = aggConfig.params.filters; + if (!size(inFilters)) return; - inFilters.forEach(filter => { - const persistedLog = getQueryLog( - uiSettings, - new Storage(window.localStorage), - 'vis_default_editor', - filter.input.language - ); - persistedLog.add(filter.input.query); - }); + inFilters.forEach(filter => { + const persistedLog = getQueryLog( + uiSettings, + new Storage(window.localStorage), + 'vis_default_editor', + filter.input.language + ); + persistedLog.add(filter.input.query); + }); - const outFilters = transform( - inFilters, - function(filters, filter) { - const input = cloneDeep(filter.input); + const outFilters = transform( + inFilters, + function(filters, filter) { + const input = cloneDeep(filter.input); - if (!input) { - console.log('malformed filter agg params, missing "input" query'); // eslint-disable-line no-console - return; - } + if (!input) { + console.log('malformed filter agg params, missing "input" query'); // eslint-disable-line no-console + return; + } - const esQueryConfigs = getEsQueryConfig(uiSettings); - const query = buildEsQuery(aggConfig.getIndexPattern(), [input], [], esQueryConfigs); + const esQueryConfigs = getEsQueryConfig(uiSettings); + const query = buildEsQuery( + aggConfig.getIndexPattern(), + [input], + [], + esQueryConfigs + ); - if (!query) { - console.log('malformed filter agg params, missing "query" on input'); // eslint-disable-line no-console - return; - } + if (!query) { + console.log('malformed filter agg params, missing "query" on input'); // eslint-disable-line no-console + return; + } - const matchAllLabel = filter.input.query === '' ? '*' : ''; - const label = - filter.label || - matchAllLabel || - (typeof filter.input.query === 'string' - ? filter.input.query - : toAngularJSON(filter.input.query)); - filters[label] = { query }; - }, - {} - ); + const matchAllLabel = filter.input.query === '' ? '*' : ''; + const label = + filter.label || + matchAllLabel || + (typeof filter.input.query === 'string' + ? filter.input.query + : toAngularJSON(filter.input.query)); + filters[label] = { query }; + }, + {} + ); - if (!size(outFilters)) return; + if (!size(outFilters)) return; - const params = output.params || (output.params = {}); - params.filters = outFilters; + const params = output.params || (output.params = {}); + params.filters = outFilters; + }, }, - }, - ], - }); + ], + }, + { getInternalStartServices } + ); diff --git a/src/plugins/data/public/search/aggs/buckets/geo_hash.test.ts b/src/plugins/data/public/search/aggs/buckets/geo_hash.test.ts index 408cdf22bcbc2..24270dd33a576 100644 --- a/src/plugins/data/public/search/aggs/buckets/geo_hash.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/geo_hash.test.ts @@ -17,13 +17,29 @@ * under the License. */ -import { geoHashBucketAgg } from './geo_hash'; +import { getGeoHashBucketAgg, GeoHashBucketAggDependencies } from './geo_hash'; import { AggConfigs, IAggConfigs } from '../agg_configs'; import { mockAggTypesRegistry } from '../test_helpers'; import { BUCKET_TYPES } from './bucket_agg_types'; -import { IBucketAggConfig } from './_bucket_agg_type'; +import { notificationServiceMock } from '../../../../../../../src/core/public/mocks'; +import { fieldFormatsServiceMock } from '../../../field_formats/mocks'; +import { BucketAggType, IBucketAggConfig } from './bucket_agg_type'; describe('Geohash Agg', () => { + let aggTypesDependencies: GeoHashBucketAggDependencies; + let geoHashBucketAgg: BucketAggType; + + beforeEach(() => { + aggTypesDependencies = { + getInternalStartServices: () => ({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), + }), + }; + + geoHashBucketAgg = getGeoHashBucketAgg(aggTypesDependencies); + }); + const getAggConfigs = (params?: Record) => { const indexPattern = { id: '1234', @@ -74,7 +90,7 @@ describe('Geohash Agg', () => { precisionParam = geoHashBucketAgg.params[PRECISION_PARAM_INDEX]; }); - it('should select precision parameter', () => { + test('should select precision parameter', () => { expect(precisionParam.name).toEqual('precision'); }); }); @@ -89,7 +105,7 @@ describe('Geohash Agg', () => { geoHashGridAgg = aggConfigs.aggs[0] as IBucketAggConfig; }); - it('should create filter, geohash_grid, and geo_centroid aggregations', () => { + test('should create filter, geohash_grid, and geo_centroid aggregations', () => { const requestAggs = geoHashBucketAgg.getRequestAggs(geoHashGridAgg) as IBucketAggConfig[]; expect(requestAggs.length).toEqual(3); @@ -101,7 +117,7 @@ describe('Geohash Agg', () => { }); describe('aggregation options', () => { - it('should only create geohash_grid and geo_centroid aggregations when isFilteredByCollar is false', () => { + test('should only create geohash_grid and geo_centroid aggregations when isFilteredByCollar is false', () => { const aggConfigs = getAggConfigs({ isFilteredByCollar: false }); const requestAggs = geoHashBucketAgg.getRequestAggs( aggConfigs.aggs[0] as IBucketAggConfig @@ -112,7 +128,7 @@ describe('Geohash Agg', () => { expect(requestAggs[1].type.name).toEqual('geo_centroid'); }); - it('should only create filter and geohash_grid aggregations when useGeocentroid is false', () => { + test('should only create filter and geohash_grid aggregations when useGeocentroid is false', () => { const aggConfigs = getAggConfigs({ useGeocentroid: false }); const requestAggs = geoHashBucketAgg.getRequestAggs( aggConfigs.aggs[0] as IBucketAggConfig @@ -138,7 +154,7 @@ describe('Geohash Agg', () => { ) as IBucketAggConfig[]; }); - it('should change geo_bounding_box filter aggregation and vis session state when map movement is outside map collar', () => { + test('should change geo_bounding_box filter aggregation and vis session state when map movement is outside map collar', () => { const [, geoBoxingBox] = geoHashBucketAgg.getRequestAggs( getAggConfigs({ boundingBox: { @@ -151,7 +167,7 @@ describe('Geohash Agg', () => { expect(originalRequestAggs[1].params).not.toEqual(geoBoxingBox.params); }); - it('should not change geo_bounding_box filter aggregation and vis session state when map movement is within map collar', () => { + test('should not change geo_bounding_box filter aggregation and vis session state when map movement is within map collar', () => { const [, geoBoxingBox] = geoHashBucketAgg.getRequestAggs( getAggConfigs({ boundingBox: { diff --git a/src/plugins/data/public/search/aggs/buckets/geo_hash.ts b/src/plugins/data/public/search/aggs/buckets/geo_hash.ts index 3ffec09a84387..eab10edad60f6 100644 --- a/src/plugins/data/public/search/aggs/buckets/geo_hash.ts +++ b/src/plugins/data/public/search/aggs/buckets/geo_hash.ts @@ -18,9 +18,10 @@ */ import { i18n } from '@kbn/i18n'; -import { BucketAggType, IBucketAggConfig } from './_bucket_agg_type'; +import { BucketAggType, IBucketAggConfig } from './bucket_agg_type'; import { KBN_FIELD_TYPES } from '../../../../common'; import { BUCKET_TYPES } from './bucket_agg_types'; +import { GetInternalStartServicesFn } from '../../../types'; const defaultBoundingBox = { top_left: { lat: 1, lon: 1 }, @@ -33,83 +34,91 @@ const geohashGridTitle = i18n.translate('data.search.aggs.buckets.geohashGridTit defaultMessage: 'Geohash', }); -export const geoHashBucketAgg = new BucketAggType({ - name: BUCKET_TYPES.GEOHASH_GRID, - title: geohashGridTitle, - params: [ - { - name: 'field', - type: 'field', - filterFieldTypes: KBN_FIELD_TYPES.GEO_POINT, - }, - { - name: 'autoPrecision', - default: true, - write: () => {}, - }, - { - name: 'precision', - default: defaultPrecision, - write(aggConfig, output) { - output.params.precision = aggConfig.params.precision; - }, - }, - { - name: 'useGeocentroid', - default: true, - write: () => {}, - }, - { - name: 'isFilteredByCollar', - default: true, - write: () => {}, - }, +export interface GeoHashBucketAggDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} + +export const getGeoHashBucketAgg = ({ getInternalStartServices }: GeoHashBucketAggDependencies) => + new BucketAggType( { - name: 'boundingBox', - default: null, - write: () => {}, - }, - ], - getRequestAggs(agg) { - const aggs = []; - const params = agg.params; + name: BUCKET_TYPES.GEOHASH_GRID, + title: geohashGridTitle, + params: [ + { + name: 'field', + type: 'field', + filterFieldTypes: KBN_FIELD_TYPES.GEO_POINT, + }, + { + name: 'autoPrecision', + default: true, + write: () => {}, + }, + { + name: 'precision', + default: defaultPrecision, + write(aggConfig, output) { + output.params.precision = aggConfig.params.precision; + }, + }, + { + name: 'useGeocentroid', + default: true, + write: () => {}, + }, + { + name: 'isFilteredByCollar', + default: true, + write: () => {}, + }, + { + name: 'boundingBox', + default: null, + write: () => {}, + }, + ], + getRequestAggs(agg) { + const aggs = []; + const params = agg.params; - if (params.isFilteredByCollar && agg.getField()) { - aggs.push( - agg.aggConfigs.createAggConfig( - { - type: 'filter', - id: 'filter_agg', - enabled: true, - params: { - geo_bounding_box: { - ignore_unmapped: true, - [agg.getField().name]: params.boundingBox || defaultBoundingBox, - }, - }, - } as any, - { addToAggConfigs: false } - ) - ); - } + if (params.isFilteredByCollar && agg.getField()) { + aggs.push( + agg.aggConfigs.createAggConfig( + { + type: 'filter', + id: 'filter_agg', + enabled: true, + params: { + geo_bounding_box: { + ignore_unmapped: true, + [agg.getField().name]: params.boundingBox || defaultBoundingBox, + }, + }, + } as any, + { addToAggConfigs: false } + ) + ); + } - aggs.push(agg); + aggs.push(agg); - if (params.useGeocentroid) { - aggs.push( - agg.aggConfigs.createAggConfig( - { - type: 'geo_centroid', - enabled: true, - params: { - field: agg.getField(), - }, - }, - { addToAggConfigs: false } - ) - ); - } + if (params.useGeocentroid) { + aggs.push( + agg.aggConfigs.createAggConfig( + { + type: 'geo_centroid', + enabled: true, + params: { + field: agg.getField(), + }, + }, + { addToAggConfigs: false } + ) + ); + } - return aggs; - }, -}); + return aggs; + }, + }, + { getInternalStartServices } + ); diff --git a/src/plugins/data/public/search/aggs/buckets/geo_tile.ts b/src/plugins/data/public/search/aggs/buckets/geo_tile.ts index 759601fc0c180..c981e8400f9a1 100644 --- a/src/plugins/data/public/search/aggs/buckets/geo_tile.ts +++ b/src/plugins/data/public/search/aggs/buckets/geo_tile.ts @@ -20,53 +20,61 @@ import { i18n } from '@kbn/i18n'; import { noop } from 'lodash'; -import { BucketAggType } from './_bucket_agg_type'; +import { BucketAggType, IBucketAggConfig } from './bucket_agg_type'; import { BUCKET_TYPES } from './bucket_agg_types'; import { KBN_FIELD_TYPES } from '../../../../common'; -import { IBucketAggConfig } from './_bucket_agg_type'; import { METRIC_TYPES } from '../metrics/metric_agg_types'; +import { GetInternalStartServicesFn } from '../../../types'; + +export interface GeoTitleBucketAggDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} const geotileGridTitle = i18n.translate('data.search.aggs.buckets.geotileGridTitle', { defaultMessage: 'Geotile', }); -export const geoTileBucketAgg = new BucketAggType({ - name: BUCKET_TYPES.GEOTILE_GRID, - title: geotileGridTitle, - params: [ - { - name: 'field', - type: 'field', - filterFieldTypes: KBN_FIELD_TYPES.GEO_POINT, - }, +export const getGeoTitleBucketAgg = ({ getInternalStartServices }: GeoTitleBucketAggDependencies) => + new BucketAggType( { - name: 'useGeocentroid', - default: true, - write: noop, - }, - { - name: 'precision', - default: 0, - }, - ], - getRequestAggs(agg) { - const aggs = []; - const useGeocentroid = agg.getParam('useGeocentroid'); + name: BUCKET_TYPES.GEOTILE_GRID, + title: geotileGridTitle, + params: [ + { + name: 'field', + type: 'field', + filterFieldTypes: KBN_FIELD_TYPES.GEO_POINT, + }, + { + name: 'useGeocentroid', + default: true, + write: noop, + }, + { + name: 'precision', + default: 0, + }, + ], + getRequestAggs(agg) { + const aggs = []; + const useGeocentroid = agg.getParam('useGeocentroid'); - aggs.push(agg); + aggs.push(agg); - if (useGeocentroid) { - const aggConfig = { - type: METRIC_TYPES.GEO_CENTROID, - enabled: true, - params: { - field: agg.getField(), - }, - }; + if (useGeocentroid) { + const aggConfig = { + type: METRIC_TYPES.GEO_CENTROID, + enabled: true, + params: { + field: agg.getField(), + }, + }; - aggs.push(agg.aggConfigs.createAggConfig(aggConfig, { addToAggConfigs: false })); - } + aggs.push(agg.aggConfigs.createAggConfig(aggConfig, { addToAggConfigs: false })); + } - return aggs as IBucketAggConfig[]; - }, -}); + return aggs as IBucketAggConfig[]; + }, + }, + { getInternalStartServices } + ); diff --git a/src/plugins/data/public/search/aggs/buckets/histogram.test.ts b/src/plugins/data/public/search/aggs/buckets/histogram.test.ts index c61b4ff37935a..bbfc263df4268 100644 --- a/src/plugins/data/public/search/aggs/buckets/histogram.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/histogram.test.ts @@ -17,7 +17,7 @@ * under the License. */ -import { coreMock } from '../../../../../../../src/core/public/mocks'; +import { coreMock, notificationServiceMock } from '../../../../../../../src/core/public/mocks'; import { AggConfigs } from '../agg_configs'; import { mockAggTypesRegistry } from '../test_helpers'; import { BUCKET_TYPES } from './bucket_agg_types'; @@ -27,17 +27,21 @@ import { AutoBounds, HistogramBucketAggDependencies, } from './histogram'; -import { BucketAggType } from './_bucket_agg_type'; +import { BucketAggType } from './bucket_agg_type'; +import { fieldFormatsServiceMock } from '../../../field_formats/mocks'; describe('Histogram Agg', () => { let aggTypesDependencies: HistogramBucketAggDependencies; beforeEach(() => { - const { uiSettings, notifications } = coreMock.createSetup(); + const { uiSettings } = coreMock.createSetup(); aggTypesDependencies = { uiSettings, - notifications, + getInternalStartServices: () => ({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), + }), }; }); @@ -87,18 +91,18 @@ describe('Histogram Agg', () => { histogramType = getHistogramBucketAgg(aggTypesDependencies); }); - it('is ordered', () => { + test('is ordered', () => { expect(histogramType.ordered).toBeDefined(); }); - it('is not ordered by date', () => { + test('is not ordered by date', () => { expect(histogramType.ordered).not.toHaveProperty('date'); }); }); describe('params', () => { describe('intervalBase', () => { - it('should not be written to the DSL', () => { + test('should not be written to the DSL', () => { const aggConfigs = getAggConfigs({ intervalBase: 100, field: { @@ -112,7 +116,7 @@ describe('Histogram Agg', () => { }); describe('interval', () => { - it('accepts a whole number', () => { + test('accepts a whole number', () => { const params = getParams({ interval: 100, }); @@ -120,7 +124,7 @@ describe('Histogram Agg', () => { expect(params).toHaveProperty('interval', 100); }); - it('accepts a decimal number', function() { + test('accepts a decimal number', () => { const params = getParams({ interval: 0.1, }); @@ -128,7 +132,7 @@ describe('Histogram Agg', () => { expect(params).toHaveProperty('interval', 0.1); }); - it('accepts a decimal number string', function() { + test('accepts a decimal number string', () => { const params = getParams({ interval: '0.1', }); @@ -136,7 +140,7 @@ describe('Histogram Agg', () => { expect(params).toHaveProperty('interval', 0.1); }); - it('accepts a whole number string', function() { + test('accepts a whole number string', () => { const params = getParams({ interval: '10', }); @@ -144,7 +148,7 @@ describe('Histogram Agg', () => { expect(params).toHaveProperty('interval', 10); }); - it('fails on non-numeric values', function() { + test('fails on non-numeric values', () => { const params = getParams({ interval: [], }); @@ -181,7 +185,7 @@ describe('Histogram Agg', () => { return aggConfig.write(aggConfigs).params; }; - it('will respect the histogram:maxBars setting', () => { + test('will respect the histogram:maxBars setting', () => { const params = getInterval( 5, { interval: 5 }, @@ -194,19 +198,19 @@ describe('Histogram Agg', () => { expect(params).toHaveProperty('interval', 2000); }); - it('will return specified interval, if bars are below histogram:maxBars config', () => { + test('will return specified interval, if bars are below histogram:maxBars config', () => { const params = getInterval(100, { interval: 5 }); expect(params).toHaveProperty('interval', 5); }); - it('will set to intervalBase if interval is below base', () => { + test('will set to intervalBase if interval is below base', () => { const params = getInterval(1000, { interval: 3, intervalBase: 8 }); expect(params).toHaveProperty('interval', 8); }); - it('will round to nearest intervalBase multiple if interval is above base', () => { + test('will round to nearest intervalBase multiple if interval is above base', () => { const roundUp = getInterval(1000, { interval: 46, intervalBase: 10 }); expect(roundUp).toHaveProperty('interval', 50); @@ -214,13 +218,13 @@ describe('Histogram Agg', () => { expect(roundDown).toHaveProperty('interval', 40); }); - it('will not change interval if it is a multiple of base', () => { + test('will not change interval if it is a multiple of base', () => { const output = getInterval(1000, { interval: 35, intervalBase: 5 }); expect(output).toHaveProperty('interval', 35); }); - it('will round to intervalBase after scaling histogram:maxBars', () => { + test('will round to intervalBase after scaling histogram:maxBars', () => { const output = getInterval(100, { interval: 5, intervalBase: 6 }, { min: 0, max: 1000 }); // 100 buckets in 0 to 1000 would result in an interval of 10, so we should @@ -232,7 +236,7 @@ describe('Histogram Agg', () => { describe('min_doc_count', () => { let output: Record; - it('casts true values to 0', () => { + test('casts true values to 0', () => { output = getParams({ min_doc_count: true }); expect(output).toHaveProperty('min_doc_count', 0); @@ -246,7 +250,7 @@ describe('Histogram Agg', () => { expect(output).toHaveProperty('min_doc_count', 0); }); - it('writes 1 for falsy values', () => { + test('writes 1 for falsy values', () => { output = getParams({ min_doc_count: '' }); expect(output).toHaveProperty('min_doc_count', 1); @@ -258,8 +262,8 @@ describe('Histogram Agg', () => { }); }); - describe('extended_bounds', function() { - it('does not write when only eb.min is set', function() { + describe('extended_bounds', () => { + test('does not write when only eb.min is set', () => { const output = getParams({ has_extended_bounds: true, extended_bounds: { min: 0 }, @@ -267,7 +271,7 @@ describe('Histogram Agg', () => { expect(output).not.toHaveProperty('extended_bounds'); }); - it('does not write when only eb.max is set', function() { + test('does not write when only eb.max is set', () => { const output = getParams({ has_extended_bounds: true, extended_bounds: { max: 0 }, @@ -276,7 +280,7 @@ describe('Histogram Agg', () => { expect(output).not.toHaveProperty('extended_bounds'); }); - it('writes when both eb.min and eb.max are set', function() { + test('writes when both eb.min and eb.max are set', () => { const output = getParams({ has_extended_bounds: true, extended_bounds: { min: 99, max: 100 }, @@ -286,7 +290,7 @@ describe('Histogram Agg', () => { expect(output.extended_bounds).toHaveProperty('max', 100); }); - it('does not write when nothing is set', function() { + test('does not write when nothing is set', () => { const output = getParams({ has_extended_bounds: true, extended_bounds: {}, @@ -295,7 +299,7 @@ describe('Histogram Agg', () => { expect(output).not.toHaveProperty('extended_bounds'); }); - it('does not write when has_extended_bounds is false', function() { + test('does not write when has_extended_bounds is false', () => { const output = getParams({ has_extended_bounds: false, extended_bounds: { min: 99, max: 100 }, diff --git a/src/plugins/data/public/search/aggs/buckets/histogram.ts b/src/plugins/data/public/search/aggs/buckets/histogram.ts index bbffc0912bf0d..f8e8720d24ea9 100644 --- a/src/plugins/data/public/search/aggs/buckets/histogram.ts +++ b/src/plugins/data/public/search/aggs/buckets/histogram.ts @@ -19,12 +19,13 @@ import { get } from 'lodash'; import { i18n } from '@kbn/i18n'; -import { IUiSettingsClient, NotificationsSetup } from 'src/core/public'; +import { IUiSettingsClient } from 'src/core/public'; -import { BucketAggType, IBucketAggConfig } from './_bucket_agg_type'; +import { BucketAggType, IBucketAggConfig } from './bucket_agg_type'; import { createFilterHistogram } from './create_filter/histogram'; import { BUCKET_TYPES } from './bucket_agg_types'; import { KBN_FIELD_TYPES } from '../../../../common'; +import { GetInternalStartServicesFn } from '../../../types'; export interface AutoBounds { min: number; @@ -33,7 +34,7 @@ export interface AutoBounds { export interface HistogramBucketAggDependencies { uiSettings: IUiSettingsClient; - notifications: NotificationsSetup; + getInternalStartServices: GetInternalStartServicesFn; } export interface IBucketHistogramAggConfig extends IBucketAggConfig { @@ -43,164 +44,167 @@ export interface IBucketHistogramAggConfig extends IBucketAggConfig { export const getHistogramBucketAgg = ({ uiSettings, - notifications, + getInternalStartServices, }: HistogramBucketAggDependencies) => - new BucketAggType({ - name: BUCKET_TYPES.HISTOGRAM, - title: i18n.translate('data.search.aggs.buckets.histogramTitle', { - defaultMessage: 'Histogram', - }), - ordered: {}, - makeLabel(aggConfig) { - return aggConfig.getFieldDisplayName(); - }, - createFilter: createFilterHistogram, - decorateAggConfig() { - let autoBounds: AutoBounds; - - return { - setAutoBounds: { - configurable: true, - value(newValue: AutoBounds) { - autoBounds = newValue; + new BucketAggType( + { + name: BUCKET_TYPES.HISTOGRAM, + title: i18n.translate('data.search.aggs.buckets.histogramTitle', { + defaultMessage: 'Histogram', + }), + ordered: {}, + makeLabel(aggConfig) { + return aggConfig.getFieldDisplayName(); + }, + createFilter: createFilterHistogram, + decorateAggConfig() { + let autoBounds: AutoBounds; + + return { + setAutoBounds: { + configurable: true, + value(newValue: AutoBounds) { + autoBounds = newValue; + }, }, - }, - getAutoBounds: { - configurable: true, - value() { - return autoBounds; + getAutoBounds: { + configurable: true, + value() { + return autoBounds; + }, }, - }, - }; - }, - params: [ - { - name: 'field', - type: 'field', - filterFieldTypes: KBN_FIELD_TYPES.NUMBER, - }, - { - /* - * This parameter can be set if you want the auto scaled interval to always - * be a multiple of a specific base. - */ - name: 'intervalBase', - default: null, - write: () => {}, + }; }, - { - name: 'interval', - modifyAggConfigOnSearchRequestStart( - aggConfig: IBucketHistogramAggConfig, - searchSource: any, - options: any - ) { - const field = aggConfig.getField(); - const aggBody = field.scripted - ? { script: { source: field.script, lang: field.lang } } - : { field: field.name }; - - const childSearchSource = searchSource - .createChild() - .setField('size', 0) - .setField('aggs', { - maxAgg: { - max: aggBody, - }, - minAgg: { - min: aggBody, - }, - }); - - return childSearchSource - .fetch(options) - .then((resp: any) => { - aggConfig.setAutoBounds({ - min: get(resp, 'aggregations.minAgg.value'), - max: get(resp, 'aggregations.maxAgg.value'), - }); - }) - .catch((e: Error) => { - if (e.name === 'AbortError') return; - notifications.toasts.addWarning( - i18n.translate('data.search.aggs.histogram.missingMaxMinValuesWarning', { - defaultMessage: - 'Unable to retrieve max and min values to auto-scale histogram buckets. This may lead to poor visualization performance.', - }) - ); - }); + params: [ + { + name: 'field', + type: 'field', + filterFieldTypes: KBN_FIELD_TYPES.NUMBER, + }, + { + /* + * This parameter can be set if you want the auto scaled interval to always + * be a multiple of a specific base. + */ + name: 'intervalBase', + default: null, + write: () => {}, }, - write(aggConfig, output) { - let interval = parseFloat(aggConfig.params.interval); - if (interval <= 0) { - interval = 1; - } - const autoBounds = aggConfig.getAutoBounds(); - - // ensure interval does not create too many buckets and crash browser - if (autoBounds) { - const range = autoBounds.max - autoBounds.min; - const bars = range / interval; - - if (bars > uiSettings.get('histogram:maxBars')) { - const minInterval = range / uiSettings.get('histogram:maxBars'); - - // Round interval by order of magnitude to provide clean intervals - // Always round interval up so there will always be less buckets than histogram:maxBars - const orderOfMagnitude = Math.pow(10, Math.floor(Math.log10(minInterval))); - let roundInterval = orderOfMagnitude; - - while (roundInterval < minInterval) { - roundInterval += orderOfMagnitude; + { + name: 'interval', + modifyAggConfigOnSearchRequestStart( + aggConfig: IBucketHistogramAggConfig, + searchSource: any, + options: any + ) { + const field = aggConfig.getField(); + const aggBody = field.scripted + ? { script: { source: field.script, lang: field.lang } } + : { field: field.name }; + + const childSearchSource = searchSource + .createChild() + .setField('size', 0) + .setField('aggs', { + maxAgg: { + max: aggBody, + }, + minAgg: { + min: aggBody, + }, + }); + + return childSearchSource + .fetch(options) + .then((resp: any) => { + aggConfig.setAutoBounds({ + min: get(resp, 'aggregations.minAgg.value'), + max: get(resp, 'aggregations.maxAgg.value'), + }); + }) + .catch((e: Error) => { + if (e.name === 'AbortError') return; + getInternalStartServices().notifications.toasts.addWarning( + i18n.translate('data.search.aggs.histogram.missingMaxMinValuesWarning', { + defaultMessage: + 'Unable to retrieve max and min values to auto-scale histogram buckets. This may lead to poor visualization performance.', + }) + ); + }); + }, + write(aggConfig, output) { + let interval = parseFloat(aggConfig.params.interval); + if (interval <= 0) { + interval = 1; + } + const autoBounds = aggConfig.getAutoBounds(); + + // ensure interval does not create too many buckets and crash browser + if (autoBounds) { + const range = autoBounds.max - autoBounds.min; + const bars = range / interval; + + if (bars > uiSettings.get('histogram:maxBars')) { + const minInterval = range / uiSettings.get('histogram:maxBars'); + + // Round interval by order of magnitude to provide clean intervals + // Always round interval up so there will always be less buckets than histogram:maxBars + const orderOfMagnitude = Math.pow(10, Math.floor(Math.log10(minInterval))); + let roundInterval = orderOfMagnitude; + + while (roundInterval < minInterval) { + roundInterval += orderOfMagnitude; + } + interval = roundInterval; } - interval = roundInterval; } - } - const base = aggConfig.params.intervalBase; - - if (base) { - if (interval < base) { - // In case the specified interval is below the base, just increase it to it's base - interval = base; - } else if (interval % base !== 0) { - // In case the interval is not a multiple of the base round it to the next base - interval = Math.round(interval / base) * base; + const base = aggConfig.params.intervalBase; + + if (base) { + if (interval < base) { + // In case the specified interval is below the base, just increase it to it's base + interval = base; + } else if (interval % base !== 0) { + // In case the interval is not a multiple of the base round it to the next base + interval = Math.round(interval / base) * base; + } } - } - output.params.interval = interval; + output.params.interval = interval; + }, }, - }, - { - name: 'min_doc_count', - default: false, - write(aggConfig, output) { - if (aggConfig.params.min_doc_count) { - output.params.min_doc_count = 0; - } else { - output.params.min_doc_count = 1; - } + { + name: 'min_doc_count', + default: false, + write(aggConfig, output) { + if (aggConfig.params.min_doc_count) { + output.params.min_doc_count = 0; + } else { + output.params.min_doc_count = 1; + } + }, }, - }, - { - name: 'has_extended_bounds', - default: false, - write: () => {}, - }, - { - name: 'extended_bounds', - default: { - min: '', - max: '', + { + name: 'has_extended_bounds', + default: false, + write: () => {}, }, - write(aggConfig, output) { - const { min, max } = aggConfig.params.extended_bounds; + { + name: 'extended_bounds', + default: { + min: '', + max: '', + }, + write(aggConfig, output) { + const { min, max } = aggConfig.params.extended_bounds; - if (aggConfig.params.has_extended_bounds && (min || min === 0) && (max || max === 0)) { - output.params.extended_bounds = { min, max }; - } + if (aggConfig.params.has_extended_bounds && (min || min === 0) && (max || max === 0)) { + output.params.extended_bounds = { min, max }; + } + }, + shouldShow: (aggConfig: IBucketAggConfig) => aggConfig.params.has_extended_bounds, }, - shouldShow: (aggConfig: IBucketAggConfig) => aggConfig.params.has_extended_bounds, - }, - ], - }); + ], + }, + { getInternalStartServices } + ); diff --git a/src/plugins/data/public/search/aggs/buckets/ip_range.ts b/src/plugins/data/public/search/aggs/buckets/ip_range.ts index da6866d40a52f..bde347d6e673d 100644 --- a/src/plugins/data/public/search/aggs/buckets/ip_range.ts +++ b/src/plugins/data/public/search/aggs/buckets/ip_range.ts @@ -19,77 +19,85 @@ import { noop, map, omit, isNull } from 'lodash'; import { i18n } from '@kbn/i18n'; -import { BucketAggType } from './_bucket_agg_type'; +import { BucketAggType } from './bucket_agg_type'; import { BUCKET_TYPES } from './bucket_agg_types'; import { createFilterIpRange } from './create_filter/ip_range'; import { IpRangeKey, convertIPRangeToString } from './lib/ip_range'; import { KBN_FIELD_TYPES, FieldFormat, TEXT_CONTEXT_TYPE } from '../../../../common'; -import { getFieldFormats } from '../../../../public/services'; +import { GetInternalStartServicesFn } from '../../../types'; const ipRangeTitle = i18n.translate('data.search.aggs.buckets.ipRangeTitle', { defaultMessage: 'IPv4 Range', }); -export const ipRangeBucketAgg = new BucketAggType({ - name: BUCKET_TYPES.IP_RANGE, - title: ipRangeTitle, - createFilter: createFilterIpRange, - getKey(bucket, key, agg): IpRangeKey { - if (agg.params.ipRangeType === 'mask') { - return { type: 'mask', mask: key }; - } - return { type: 'range', from: bucket.from, to: bucket.to }; - }, - getFormat(agg) { - const fieldFormatsService = getFieldFormats(); - const formatter = agg.fieldOwnFormatter( - TEXT_CONTEXT_TYPE, - fieldFormatsService.getDefaultInstance(KBN_FIELD_TYPES.IP) - ); - const IpRangeFormat = FieldFormat.from(function(range: IpRangeKey) { - return convertIPRangeToString(range, formatter); - }); - return new IpRangeFormat(); - }, - makeLabel(aggConfig) { - return i18n.translate('data.search.aggs.buckets.ipRangeLabel', { - defaultMessage: '{fieldName} IP ranges', - values: { - fieldName: aggConfig.getFieldDisplayName(), - }, - }); - }, - params: [ - { - name: 'field', - type: 'field', - filterFieldTypes: KBN_FIELD_TYPES.IP, - }, - { - name: 'ipRangeType', - default: 'fromTo', - write: noop, - }, +export interface IpRangeBucketAggDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} + +export const getIpRangeBucketAgg = ({ getInternalStartServices }: IpRangeBucketAggDependencies) => + new BucketAggType( { - name: 'ranges', - default: { - fromTo: [ - { from: '0.0.0.0', to: '127.255.255.255' }, - { from: '128.0.0.0', to: '191.255.255.255' }, - ], - mask: [{ mask: '0.0.0.0/1' }, { mask: '128.0.0.0/2' }], + name: BUCKET_TYPES.IP_RANGE, + title: ipRangeTitle, + createFilter: createFilterIpRange, + getKey(bucket, key, agg): IpRangeKey { + if (agg.params.ipRangeType === 'mask') { + return { type: 'mask', mask: key }; + } + return { type: 'range', from: bucket.from, to: bucket.to }; + }, + getFormat(agg) { + const { fieldFormats } = getInternalStartServices(); + const formatter = agg.fieldOwnFormatter( + TEXT_CONTEXT_TYPE, + fieldFormats.getDefaultInstance(KBN_FIELD_TYPES.IP) + ); + const IpRangeFormat = FieldFormat.from(function(range: IpRangeKey) { + return convertIPRangeToString(range, formatter); + }); + return new IpRangeFormat(); + }, + makeLabel(aggConfig) { + return i18n.translate('data.search.aggs.buckets.ipRangeLabel', { + defaultMessage: '{fieldName} IP ranges', + values: { + fieldName: aggConfig.getFieldDisplayName(), + }, + }); }, - write(aggConfig, output) { - const ipRangeType = aggConfig.params.ipRangeType; - let ranges = aggConfig.params.ranges[ipRangeType]; + params: [ + { + name: 'field', + type: 'field', + filterFieldTypes: KBN_FIELD_TYPES.IP, + }, + { + name: 'ipRangeType', + default: 'fromTo', + write: noop, + }, + { + name: 'ranges', + default: { + fromTo: [ + { from: '0.0.0.0', to: '127.255.255.255' }, + { from: '128.0.0.0', to: '191.255.255.255' }, + ], + mask: [{ mask: '0.0.0.0/1' }, { mask: '128.0.0.0/2' }], + }, + write(aggConfig, output) { + const ipRangeType = aggConfig.params.ipRangeType; + let ranges = aggConfig.params.ranges[ipRangeType]; - if (ipRangeType === 'fromTo') { - ranges = map(ranges, (range: any) => omit(range, isNull)); - } + if (ipRangeType === 'fromTo') { + ranges = map(ranges, (range: any) => omit(range, isNull)); + } - output.params.ranges = ranges; - }, + output.params.ranges = ranges; + }, + }, + ], }, - ], -}); + { getInternalStartServices } + ); diff --git a/src/plugins/data/public/search/aggs/buckets/migrate_include_exclude_format.ts b/src/plugins/data/public/search/aggs/buckets/migrate_include_exclude_format.ts index d94477b588f8d..0beeb1c372275 100644 --- a/src/plugins/data/public/search/aggs/buckets/migrate_include_exclude_format.ts +++ b/src/plugins/data/public/search/aggs/buckets/migrate_include_exclude_format.ts @@ -18,7 +18,7 @@ */ import { isString, isObject } from 'lodash'; -import { IBucketAggConfig, BucketAggType, BucketAggParam } from './_bucket_agg_type'; +import { IBucketAggConfig, BucketAggType, BucketAggParam } from './bucket_agg_type'; import { IAggConfig } from '../agg_config'; export const isType = (type: string) => { diff --git a/src/plugins/data/public/search/aggs/buckets/range.test.ts b/src/plugins/data/public/search/aggs/buckets/range.test.ts index bf3711543ae88..a1f0ab6a2326a 100644 --- a/src/plugins/data/public/search/aggs/buckets/range.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/range.test.ts @@ -17,11 +17,13 @@ * under the License. */ -import { rangeBucketAgg } from './range'; +import { getRangeBucketAgg, RangeBucketAggDependencies } from './range'; import { AggConfigs } from '../agg_configs'; import { mockDataServices, mockAggTypesRegistry } from '../test_helpers'; import { BUCKET_TYPES } from './bucket_agg_types'; import { FieldFormatsGetConfigFn, NumberFormat } from '../../../../common'; +import { fieldFormatsServiceMock } from '../../../field_formats/mocks'; +import { notificationServiceMock } from '../../../../../../../src/core/public/mocks'; const buckets = [ { @@ -44,7 +46,16 @@ const buckets = [ ]; describe('Range Agg', () => { + let aggTypesDependencies: RangeBucketAggDependencies; + beforeEach(() => { + aggTypesDependencies = { + getInternalStartServices: () => ({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), + }), + }; + mockDataServices(); }); @@ -84,15 +95,14 @@ describe('Range Agg', () => { }, }, ], - { typesRegistry: mockAggTypesRegistry([rangeBucketAgg]) } + { typesRegistry: mockAggTypesRegistry([getRangeBucketAgg(aggTypesDependencies)]) } ); }; describe('formating', () => { - it('formats bucket keys properly', () => { + test('formats bucket keys properly', () => { const aggConfigs = getAggConfigs(); const agg = aggConfigs.aggs[0]; - const format = (val: any) => agg.fieldFormatter()(agg.getKey(val)); expect(format(buckets[0])).toBe('≥ -∞ and < 1 KB'); diff --git a/src/plugins/data/public/search/aggs/buckets/range.ts b/src/plugins/data/public/search/aggs/buckets/range.ts index 036a0d4c1e8da..2c1303814a88a 100644 --- a/src/plugins/data/public/search/aggs/buckets/range.ts +++ b/src/plugins/data/public/search/aggs/buckets/range.ts @@ -18,11 +18,12 @@ */ import { i18n } from '@kbn/i18n'; -import { BucketAggType } from './_bucket_agg_type'; +import { BucketAggType } from './bucket_agg_type'; import { FieldFormat, KBN_FIELD_TYPES } from '../../../../common'; import { RangeKey } from './range_key'; import { createFilterRange } from './create_filter/range'; import { BUCKET_TYPES } from './bucket_agg_types'; +import { GetInternalStartServicesFn } from '../../../types'; const keyCaches = new WeakMap(); const formats = new WeakMap(); @@ -31,76 +32,84 @@ const rangeTitle = i18n.translate('data.search.aggs.buckets.rangeTitle', { defaultMessage: 'Range', }); -export const rangeBucketAgg = new BucketAggType({ - name: BUCKET_TYPES.RANGE, - title: rangeTitle, - createFilter: createFilterRange, - makeLabel(aggConfig) { - return i18n.translate('data.search.aggs.aggTypesLabel', { - defaultMessage: '{fieldName} ranges', - values: { - fieldName: aggConfig.getFieldDisplayName(), +export interface RangeBucketAggDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} + +export const getRangeBucketAgg = ({ getInternalStartServices }: RangeBucketAggDependencies) => + new BucketAggType( + { + name: BUCKET_TYPES.RANGE, + title: rangeTitle, + createFilter: createFilterRange, + makeLabel(aggConfig) { + return i18n.translate('data.search.aggs.aggTypesLabel', { + defaultMessage: '{fieldName} ranges', + values: { + fieldName: aggConfig.getFieldDisplayName(), + }, + }); }, - }); - }, - getKey(bucket, key, agg) { - let keys = keyCaches.get(agg); + getKey(bucket, key, agg) { + let keys = keyCaches.get(agg); - if (!keys) { - keys = new Map(); - keyCaches.set(agg, keys); - } + if (!keys) { + keys = new Map(); + keyCaches.set(agg, keys); + } - const id = RangeKey.idBucket(bucket); + const id = RangeKey.idBucket(bucket); - key = keys.get(id); - if (!key) { - key = new RangeKey(bucket); - keys.set(id, key); - } + key = keys.get(id); + if (!key) { + key = new RangeKey(bucket); + keys.set(id, key); + } - return key; - }, - getFormat(agg) { - let aggFormat = formats.get(agg); - if (aggFormat) return aggFormat; + return key; + }, + getFormat(agg) { + let aggFormat = formats.get(agg); + if (aggFormat) return aggFormat; - const RangeFormat = FieldFormat.from((range: any) => { - const format = agg.fieldOwnFormatter(); - const gte = '\u2265'; - const lt = '\u003c'; - return i18n.translate('data.search.aggs.aggTypes.rangesFormatMessage', { - defaultMessage: '{gte} {from} and {lt} {to}', - values: { - gte, - from: format(range.gte), - lt, - to: format(range.lt), - }, - }); - }); + const RangeFormat = FieldFormat.from((range: any) => { + const format = agg.fieldOwnFormatter(); + const gte = '\u2265'; + const lt = '\u003c'; + return i18n.translate('data.search.aggs.aggTypes.rangesFormatMessage', { + defaultMessage: '{gte} {from} and {lt} {to}', + values: { + gte, + from: format(range.gte), + lt, + to: format(range.lt), + }, + }); + }); - aggFormat = new RangeFormat(); + aggFormat = new RangeFormat(); - formats.set(agg, aggFormat); - return aggFormat; - }, - params: [ - { - name: 'field', - type: 'field', - filterFieldTypes: [KBN_FIELD_TYPES.NUMBER], - }, - { - name: 'ranges', - default: [ - { from: 0, to: 1000 }, - { from: 1000, to: 2000 }, - ], - write(aggConfig, output) { - output.params.ranges = aggConfig.params.ranges; - output.params.keyed = true; + formats.set(agg, aggFormat); + return aggFormat; }, + params: [ + { + name: 'field', + type: 'field', + filterFieldTypes: [KBN_FIELD_TYPES.NUMBER], + }, + { + name: 'ranges', + default: [ + { from: 0, to: 1000 }, + { from: 1000, to: 2000 }, + ], + write(aggConfig, output) { + output.params.ranges = aggConfig.params.ranges; + output.params.keyed = true; + }, + }, + ], }, - ], -}); + { getInternalStartServices } + ); diff --git a/src/plugins/data/public/search/aggs/buckets/significant_terms.test.ts b/src/plugins/data/public/search/aggs/buckets/significant_terms.test.ts index 1c221126c35e0..761d0ced6a114 100644 --- a/src/plugins/data/public/search/aggs/buckets/significant_terms.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/significant_terms.test.ts @@ -20,12 +20,27 @@ import { AggConfigs, IAggConfigs } from '../agg_configs'; import { mockAggTypesRegistry } from '../test_helpers'; import { BUCKET_TYPES } from './bucket_agg_types'; -import { significantTermsBucketAgg } from './significant_terms'; -import { IBucketAggConfig } from './_bucket_agg_type'; +import { + getSignificantTermsBucketAgg, + SignificantTermsBucketAggDependencies, +} from './significant_terms'; +import { fieldFormatsServiceMock } from '../../../field_formats/mocks'; +import { notificationServiceMock } from '../../../../../../../src/core/public/mocks'; describe('Significant Terms Agg', () => { describe('order agg editor UI', () => { describe('convert include/exclude from old format', () => { + let aggTypesDependencies: SignificantTermsBucketAggDependencies; + + beforeEach(() => { + aggTypesDependencies = { + getInternalStartServices: () => ({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), + }), + }; + }); + const getAggConfigs = (params: Record = {}) => { const indexPattern = { id: '1234', @@ -51,7 +66,11 @@ describe('Significant Terms Agg', () => { params, }, ], - { typesRegistry: mockAggTypesRegistry([significantTermsBucketAgg]) } + { + typesRegistry: mockAggTypesRegistry([ + getSignificantTermsBucketAgg(aggTypesDependencies), + ]), + } ); }; @@ -64,19 +83,19 @@ describe('Significant Terms Agg', () => { expect(params.exclude).toBe('400'); }; - it('should generate correct label', () => { + test('should generate correct label', () => { const aggConfigs = getAggConfigs({ size: 'SIZE', field: { name: 'FIELD', }, }); - const label = significantTermsBucketAgg.makeLabel(aggConfigs.aggs[0] as IBucketAggConfig); + const label = aggConfigs.aggs[0].makeLabel(); expect(label).toBe('Top SIZE unusual terms in FIELD'); }); - it('should doesnt do anything with string type', () => { + test('should doesnt do anything with string type', () => { const aggConfigs = getAggConfigs({ include: '404', exclude: '400', @@ -89,7 +108,7 @@ describe('Significant Terms Agg', () => { testSerializeAndWrite(aggConfigs); }); - it('should converts object to string type', () => { + test('should converts object to string type', () => { const aggConfigs = getAggConfigs({ include: { pattern: '404', diff --git a/src/plugins/data/public/search/aggs/buckets/significant_terms.ts b/src/plugins/data/public/search/aggs/buckets/significant_terms.ts index f12ebe58e2de2..49d797f3afbc9 100644 --- a/src/plugins/data/public/search/aggs/buckets/significant_terms.ts +++ b/src/plugins/data/public/search/aggs/buckets/significant_terms.ts @@ -18,59 +18,72 @@ */ import { i18n } from '@kbn/i18n'; -import { BucketAggType } from './_bucket_agg_type'; +import { BucketAggType } from './bucket_agg_type'; import { createFilterTerms } from './create_filter/terms'; import { isStringType, migrateIncludeExcludeFormat } from './migrate_include_exclude_format'; import { BUCKET_TYPES } from './bucket_agg_types'; import { KBN_FIELD_TYPES } from '../../../../common'; +import { GetInternalStartServicesFn } from '../../../types'; const significantTermsTitle = i18n.translate('data.search.aggs.buckets.significantTermsTitle', { defaultMessage: 'Significant Terms', }); -export const significantTermsBucketAgg = new BucketAggType({ - name: BUCKET_TYPES.SIGNIFICANT_TERMS, - title: significantTermsTitle, - makeLabel(aggConfig) { - return i18n.translate('data.search.aggs.buckets.significantTermsLabel', { - defaultMessage: 'Top {size} unusual terms in {fieldName}', - values: { - size: aggConfig.params.size, - fieldName: aggConfig.getFieldDisplayName(), - }, - }); - }, - createFilter: createFilterTerms, - params: [ - { - name: 'field', - type: 'field', - scriptable: false, - filterFieldTypes: KBN_FIELD_TYPES.STRING, - }, - { - name: 'size', - default: '', - }, +export interface SignificantTermsBucketAggDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} + +export const getSignificantTermsBucketAgg = ({ + getInternalStartServices, +}: SignificantTermsBucketAggDependencies) => + new BucketAggType( { - name: 'exclude', - displayName: i18n.translate('data.search.aggs.buckets.significantTerms.excludeLabel', { - defaultMessage: 'Exclude', - }), - type: 'string', - advanced: true, - shouldShow: isStringType, - ...migrateIncludeExcludeFormat, + name: BUCKET_TYPES.SIGNIFICANT_TERMS, + title: significantTermsTitle, + makeLabel(aggConfig) { + return i18n.translate('data.search.aggs.buckets.significantTermsLabel', { + defaultMessage: 'Top {size} unusual terms in {fieldName}', + values: { + size: aggConfig.params.size, + fieldName: aggConfig.getFieldDisplayName(), + }, + }); + }, + createFilter: createFilterTerms, + params: [ + { + name: 'field', + type: 'field', + scriptable: false, + filterFieldTypes: KBN_FIELD_TYPES.STRING, + }, + { + name: 'size', + default: '', + }, + { + name: 'exclude', + displayName: i18n.translate('data.search.aggs.buckets.significantTerms.excludeLabel', { + defaultMessage: 'Exclude', + }), + type: 'string', + advanced: true, + shouldShow: isStringType, + ...migrateIncludeExcludeFormat, + }, + { + name: 'include', + displayName: i18n.translate('data.search.aggs.buckets.significantTerms.includeLabel', { + defaultMessage: 'Include', + }), + type: 'string', + advanced: true, + shouldShow: isStringType, + ...migrateIncludeExcludeFormat, + }, + ], }, { - name: 'include', - displayName: i18n.translate('data.search.aggs.buckets.significantTerms.includeLabel', { - defaultMessage: 'Include', - }), - type: 'string', - advanced: true, - shouldShow: isStringType, - ...migrateIncludeExcludeFormat, - }, - ], -}); + getInternalStartServices, + } + ); diff --git a/src/plugins/data/public/search/aggs/buckets/terms.test.ts b/src/plugins/data/public/search/aggs/buckets/terms.test.ts index 280d78f6620bd..5afe7d0b0c35c 100644 --- a/src/plugins/data/public/search/aggs/buckets/terms.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/terms.test.ts @@ -51,7 +51,7 @@ describe('Terms Agg', () => { ); }; - it('converts object to string type', function() { + test('converts object to string type', () => { const aggConfigs = getAggConfigs({ include: { pattern: '404', diff --git a/src/plugins/data/public/search/aggs/buckets/terms.ts b/src/plugins/data/public/search/aggs/buckets/terms.ts index 813c657934a76..5baa38af0e8d6 100644 --- a/src/plugins/data/public/search/aggs/buckets/terms.ts +++ b/src/plugins/data/public/search/aggs/buckets/terms.ts @@ -19,9 +19,8 @@ import { noop } from 'lodash'; import { i18n } from '@kbn/i18n'; -import { BucketAggType } from './_bucket_agg_type'; +import { BucketAggType, IBucketAggConfig } from './bucket_agg_type'; import { BUCKET_TYPES } from './bucket_agg_types'; -import { IBucketAggConfig } from './_bucket_agg_type'; import { createFilterTerms } from './create_filter/terms'; import { isStringType, migrateIncludeExcludeFormat } from './migrate_include_exclude_format'; import { IAggConfigs } from '../agg_configs'; @@ -36,6 +35,7 @@ import { mergeOtherBucketAggResponse, updateMissingBucket, } from './_terms_other_bucket_helper'; +import { GetInternalStartServicesFn } from '../../../types'; export const termsAggFilter = [ '!top_hits', @@ -56,220 +56,230 @@ const termsTitle = i18n.translate('data.search.aggs.buckets.termsTitle', { defaultMessage: 'Terms', }); -export const termsBucketAgg = new BucketAggType({ - name: BUCKET_TYPES.TERMS, - title: termsTitle, - makeLabel(agg) { - const params = agg.params; - return agg.getFieldDisplayName() + ': ' + params.order.text; - }, - getFormat(bucket): IFieldFormat { - return { - getConverterFor: (type: FieldFormatsContentType) => { - return (val: any) => { - if (val === '__other__') { - return bucket.params.otherBucketLabel; - } - if (val === '__missing__') { - return bucket.params.missingBucketLabel; - } +export interface TermsBucketAggDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} - return bucket.params.field.format.convert(val, type); - }; +export const getTermsBucketAgg = ({ getInternalStartServices }: TermsBucketAggDependencies) => + new BucketAggType( + { + name: BUCKET_TYPES.TERMS, + title: termsTitle, + makeLabel(agg) { + const params = agg.params; + return agg.getFieldDisplayName() + ': ' + params.order.text; }, - } as IFieldFormat; - }, - createFilter: createFilterTerms, - postFlightRequest: async ( - resp: any, - aggConfigs: IAggConfigs, - aggConfig: IBucketAggConfig, - searchSource: ISearchSource, - inspectorAdapters: Adapters, - abortSignal?: AbortSignal - ) => { - if (!resp.aggregations) return resp; - const nestedSearchSource = searchSource.createChild(); - if (aggConfig.params.otherBucket) { - const filterAgg = buildOtherBucketAgg(aggConfigs, aggConfig, resp); - if (!filterAgg) return resp; + getFormat(bucket): IFieldFormat { + return { + getConverterFor: (type: FieldFormatsContentType) => { + return (val: any) => { + if (val === '__other__') { + return bucket.params.otherBucketLabel; + } + if (val === '__missing__') { + return bucket.params.missingBucketLabel; + } - nestedSearchSource.setField('aggs', filterAgg); + return bucket.params.field.format.convert(val, type); + }; + }, + } as IFieldFormat; + }, + createFilter: createFilterTerms, + postFlightRequest: async ( + resp: any, + aggConfigs: IAggConfigs, + aggConfig: IBucketAggConfig, + searchSource: ISearchSource, + inspectorAdapters: Adapters, + abortSignal?: AbortSignal + ) => { + if (!resp.aggregations) return resp; + const nestedSearchSource = searchSource.createChild(); + if (aggConfig.params.otherBucket) { + const filterAgg = buildOtherBucketAgg(aggConfigs, aggConfig, resp); + if (!filterAgg) return resp; - const request = inspectorAdapters.requests.start( - i18n.translate('data.search.aggs.buckets.terms.otherBucketTitle', { - defaultMessage: 'Other bucket', - }), - { - description: i18n.translate('data.search.aggs.buckets.terms.otherBucketDescription', { - defaultMessage: - 'This request counts the number of documents that fall ' + - 'outside the criterion of the data buckets.', - }), - } - ); - nestedSearchSource.getSearchRequestBody().then((body: string) => { - request.json(body); - }); - request.stats(getRequestInspectorStats(nestedSearchSource)); + nestedSearchSource.setField('aggs', filterAgg); - const response = await nestedSearchSource.fetch({ abortSignal }); - request.stats(getResponseInspectorStats(nestedSearchSource, response)).ok({ json: response }); - resp = mergeOtherBucketAggResponse(aggConfigs, resp, response, aggConfig, filterAgg()); - } - if (aggConfig.params.missingBucket) { - resp = updateMissingBucket(resp, aggConfigs, aggConfig); - } - return resp; - }, - params: [ - { - name: 'field', - type: 'field', - filterFieldTypes: [ - KBN_FIELD_TYPES.NUMBER, - KBN_FIELD_TYPES.BOOLEAN, - KBN_FIELD_TYPES.DATE, - KBN_FIELD_TYPES.IP, - KBN_FIELD_TYPES.STRING, - ], - }, - { - name: 'orderBy', - write: noop, // prevent default write, it's handled by orderAgg - }, - { - name: 'orderAgg', - type: 'agg', - allowedAggs: termsAggFilter, - default: null, - makeAgg(termsAgg, state) { - state = state || {}; - state.schema = 'orderAgg'; - const orderAgg = termsAgg.aggConfigs.createAggConfig(state, { - addToAggConfigs: false, - }); - orderAgg.id = termsAgg.id + '-orderAgg'; + const request = inspectorAdapters.requests.start( + i18n.translate('data.search.aggs.buckets.terms.otherBucketTitle', { + defaultMessage: 'Other bucket', + }), + { + description: i18n.translate('data.search.aggs.buckets.terms.otherBucketDescription', { + defaultMessage: + 'This request counts the number of documents that fall ' + + 'outside the criterion of the data buckets.', + }), + } + ); + nestedSearchSource.getSearchRequestBody().then((body: string) => { + request.json(body); + }); + request.stats(getRequestInspectorStats(nestedSearchSource)); - return orderAgg; + const response = await nestedSearchSource.fetch({ abortSignal }); + request + .stats(getResponseInspectorStats(nestedSearchSource, response)) + .ok({ json: response }); + resp = mergeOtherBucketAggResponse(aggConfigs, resp, response, aggConfig, filterAgg()); + } + if (aggConfig.params.missingBucket) { + resp = updateMissingBucket(resp, aggConfigs, aggConfig); + } + return resp; }, - write(agg, output, aggs) { - const dir = agg.params.order.value; - const order: Record = (output.params.order = {}); + params: [ + { + name: 'field', + type: 'field', + filterFieldTypes: [ + KBN_FIELD_TYPES.NUMBER, + KBN_FIELD_TYPES.BOOLEAN, + KBN_FIELD_TYPES.DATE, + KBN_FIELD_TYPES.IP, + KBN_FIELD_TYPES.STRING, + ], + }, + { + name: 'orderBy', + write: noop, // prevent default write, it's handled by orderAgg + }, + { + name: 'orderAgg', + type: 'agg', + allowedAggs: termsAggFilter, + default: null, + makeAgg(termsAgg, state) { + state = state || {}; + state.schema = 'orderAgg'; + const orderAgg = termsAgg.aggConfigs.createAggConfig(state, { + addToAggConfigs: false, + }); + orderAgg.id = termsAgg.id + '-orderAgg'; - let orderAgg = agg.params.orderAgg || aggs!.getResponseAggById(agg.params.orderBy); + return orderAgg; + }, + write(agg, output, aggs) { + const dir = agg.params.order.value; + const order: Record = (output.params.order = {}); - // TODO: This works around an Elasticsearch bug the always casts terms agg scripts to strings - // thus causing issues with filtering. This probably causes other issues since float might not - // be able to contain the number on the elasticsearch side - if (output.params.script) { - output.params.value_type = - agg.getField().type === 'number' ? 'float' : agg.getField().type; - } + let orderAgg = agg.params.orderAgg || aggs!.getResponseAggById(agg.params.orderBy); - if (agg.params.missingBucket && agg.params.field.type === 'string') { - output.params.missing = '__missing__'; - } + // TODO: This works around an Elasticsearch bug the always casts terms agg scripts to strings + // thus causing issues with filtering. This probably causes other issues since float might not + // be able to contain the number on the elasticsearch side + if (output.params.script) { + output.params.value_type = + agg.getField().type === 'number' ? 'float' : agg.getField().type; + } - if (!orderAgg) { - order[agg.params.orderBy || '_count'] = dir; - return; - } + if (agg.params.missingBucket && agg.params.field.type === 'string') { + output.params.missing = '__missing__'; + } - if (orderAgg.type.name === 'count') { - order._count = dir; - return; - } + if (!orderAgg) { + order[agg.params.orderBy || '_count'] = dir; + return; + } - const orderAggId = orderAgg.id; + if (orderAgg.type.name === 'count') { + order._count = dir; + return; + } - if (orderAgg.parentId && aggs) { - orderAgg = aggs.byId(orderAgg.parentId); - } + const orderAggId = orderAgg.id; - output.subAggs = (output.subAggs || []).concat(orderAgg); - order[orderAggId] = dir; - }, - }, - { - name: 'order', - type: 'optioned', - default: 'desc', - options: [ + if (orderAgg.parentId && aggs) { + orderAgg = aggs.byId(orderAgg.parentId); + } + + output.subAggs = (output.subAggs || []).concat(orderAgg); + order[orderAggId] = dir; + }, + }, { - text: i18n.translate('data.search.aggs.buckets.terms.orderDescendingTitle', { - defaultMessage: 'Descending', + name: 'order', + type: 'optioned', + default: 'desc', + options: [ + { + text: i18n.translate('data.search.aggs.buckets.terms.orderDescendingTitle', { + defaultMessage: 'Descending', + }), + value: 'desc', + }, + { + text: i18n.translate('data.search.aggs.buckets.terms.orderAscendingTitle', { + defaultMessage: 'Ascending', + }), + value: 'asc', + }, + ], + write: noop, // prevent default write, it's handled by orderAgg + }, + { + name: 'size', + default: 5, + }, + { + name: 'otherBucket', + default: false, + write: noop, + }, + { + name: 'otherBucketLabel', + type: 'string', + default: i18n.translate('data.search.aggs.buckets.terms.otherBucketLabel', { + defaultMessage: 'Other', + }), + displayName: i18n.translate('data.search.aggs.otherBucket.labelForOtherBucketLabel', { + defaultMessage: 'Label for other bucket', }), - value: 'desc', + shouldShow: agg => agg.getParam('otherBucket'), + write: noop, }, { - text: i18n.translate('data.search.aggs.buckets.terms.orderAscendingTitle', { - defaultMessage: 'Ascending', + name: 'missingBucket', + default: false, + write: noop, + }, + { + name: 'missingBucketLabel', + default: i18n.translate('data.search.aggs.buckets.terms.missingBucketLabel', { + defaultMessage: 'Missing', + description: `Default label used in charts when documents are missing a field. + Visible when you create a chart with a terms aggregation and enable "Show missing values"`, + }), + type: 'string', + displayName: i18n.translate('data.search.aggs.otherBucket.labelForMissingValuesLabel', { + defaultMessage: 'Label for missing values', }), - value: 'asc', + shouldShow: agg => agg.getParam('missingBucket'), + write: noop, + }, + { + name: 'exclude', + displayName: i18n.translate('data.search.aggs.buckets.terms.excludeLabel', { + defaultMessage: 'Exclude', + }), + type: 'string', + advanced: true, + shouldShow: isStringType, + ...migrateIncludeExcludeFormat, + }, + { + name: 'include', + displayName: i18n.translate('data.search.aggs.buckets.terms.includeLabel', { + defaultMessage: 'Include', + }), + type: 'string', + advanced: true, + shouldShow: isStringType, + ...migrateIncludeExcludeFormat, }, ], - write: noop, // prevent default write, it's handled by orderAgg - }, - { - name: 'size', - default: 5, - }, - { - name: 'otherBucket', - default: false, - write: noop, - }, - { - name: 'otherBucketLabel', - type: 'string', - default: i18n.translate('data.search.aggs.buckets.terms.otherBucketLabel', { - defaultMessage: 'Other', - }), - displayName: i18n.translate('data.search.aggs.otherBucket.labelForOtherBucketLabel', { - defaultMessage: 'Label for other bucket', - }), - shouldShow: agg => agg.getParam('otherBucket'), - write: noop, - }, - { - name: 'missingBucket', - default: false, - write: noop, }, - { - name: 'missingBucketLabel', - default: i18n.translate('data.search.aggs.buckets.terms.missingBucketLabel', { - defaultMessage: 'Missing', - description: `Default label used in charts when documents are missing a field. - Visible when you create a chart with a terms aggregation and enable "Show missing values"`, - }), - type: 'string', - displayName: i18n.translate('data.search.aggs.otherBucket.labelForMissingValuesLabel', { - defaultMessage: 'Label for missing values', - }), - shouldShow: agg => agg.getParam('missingBucket'), - write: noop, - }, - { - name: 'exclude', - displayName: i18n.translate('data.search.aggs.buckets.terms.excludeLabel', { - defaultMessage: 'Exclude', - }), - type: 'string', - advanced: true, - shouldShow: isStringType, - ...migrateIncludeExcludeFormat, - }, - { - name: 'include', - displayName: i18n.translate('data.search.aggs.buckets.terms.includeLabel', { - defaultMessage: 'Include', - }), - type: 'string', - advanced: true, - shouldShow: isStringType, - ...migrateIncludeExcludeFormat, - }, - ], -}); + { getInternalStartServices } + ); diff --git a/src/plugins/data/public/search/aggs/index.test.ts b/src/plugins/data/public/search/aggs/index.test.ts index 8c0e47763c295..419c3fdab1caf 100644 --- a/src/plugins/data/public/search/aggs/index.test.ts +++ b/src/plugins/data/public/search/aggs/index.test.ts @@ -20,16 +20,22 @@ import { coreMock } from '../../../../../../src/core/public/mocks'; import { getAggTypes } from './index'; -import { isBucketAggType } from './buckets/_bucket_agg_type'; +import { isBucketAggType } from './buckets/bucket_agg_type'; import { isMetricAggType } from './metrics/metric_agg_type'; import { QueryStart } from '../../query'; +import { FieldFormatsStart } from '../../field_formats'; describe('AggTypesComponent', () => { - const core = coreMock.createSetup(); + const coreSetup = coreMock.createSetup(); + const coreStart = coreMock.createSetup(); + const aggTypes = getAggTypes({ - uiSettings: core.uiSettings, - notifications: core.notifications, + uiSettings: coreSetup.uiSettings, query: {} as QueryStart, + getInternalStartServices: () => ({ + notifications: coreStart.notifications, + fieldFormats: {} as FieldFormatsStart, + }), }); const { buckets, metrics } = aggTypes; diff --git a/src/plugins/data/public/search/aggs/metrics/avg.ts b/src/plugins/data/public/search/aggs/metrics/avg.ts index 008dede3e1985..d53ce8d3fc489 100644 --- a/src/plugins/data/public/search/aggs/metrics/avg.ts +++ b/src/plugins/data/public/search/aggs/metrics/avg.ts @@ -21,25 +21,37 @@ import { i18n } from '@kbn/i18n'; import { MetricAggType } from './metric_agg_type'; import { METRIC_TYPES } from './metric_agg_types'; import { KBN_FIELD_TYPES } from '../../../../common'; +import { GetInternalStartServicesFn } from '../../../types'; const averageTitle = i18n.translate('data.search.aggs.metrics.averageTitle', { defaultMessage: 'Average', }); -export const avgMetricAgg = new MetricAggType({ - name: METRIC_TYPES.AVG, - title: averageTitle, - makeLabel: aggConfig => { - return i18n.translate('data.search.aggs.metrics.averageLabel', { - defaultMessage: 'Average {field}', - values: { field: aggConfig.getFieldDisplayName() }, - }); - }, - params: [ +export interface AvgMetricAggDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} + +export const getAvgMetricAgg = ({ getInternalStartServices }: AvgMetricAggDependencies) => { + return new MetricAggType( { - name: 'field', - type: 'field', - filterFieldTypes: KBN_FIELD_TYPES.NUMBER, + name: METRIC_TYPES.AVG, + title: averageTitle, + makeLabel: aggConfig => { + return i18n.translate('data.search.aggs.metrics.averageLabel', { + defaultMessage: 'Average {field}', + values: { field: aggConfig.getFieldDisplayName() }, + }); + }, + params: [ + { + name: 'field', + type: 'field', + filterFieldTypes: KBN_FIELD_TYPES.NUMBER, + }, + ], }, - ], -}); + { + getInternalStartServices, + } + ); +}; diff --git a/src/plugins/data/public/search/aggs/metrics/bucket_avg.ts b/src/plugins/data/public/search/aggs/metrics/bucket_avg.ts index 11bb559274729..2c32ebc671539 100644 --- a/src/plugins/data/public/search/aggs/metrics/bucket_avg.ts +++ b/src/plugins/data/public/search/aggs/metrics/bucket_avg.ts @@ -23,6 +23,11 @@ import { MetricAggType } from './metric_agg_type'; import { makeNestedLabel } from './lib/make_nested_label'; import { siblingPipelineAggHelper } from './lib/sibling_pipeline_agg_helper'; import { METRIC_TYPES } from './metric_agg_types'; +import { GetInternalStartServicesFn } from '../../../types'; + +export interface BucketAvgMetricAggDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} const overallAverageLabel = i18n.translate('data.search.aggs.metrics.overallAverageLabel', { defaultMessage: 'overall average', @@ -32,25 +37,34 @@ const averageBucketTitle = i18n.translate('data.search.aggs.metrics.averageBucke defaultMessage: 'Average Bucket', }); -export const bucketAvgMetricAgg = new MetricAggType({ - name: METRIC_TYPES.AVG_BUCKET, - title: averageBucketTitle, - makeLabel: agg => makeNestedLabel(agg, overallAverageLabel), - subtype: siblingPipelineAggHelper.subtype, - params: [...siblingPipelineAggHelper.params()], - getFormat: siblingPipelineAggHelper.getFormat, - getValue(agg, bucket) { - const customMetric = agg.getParam('customMetric'); - const customBucket = agg.getParam('customBucket'); - const scaleMetrics = customMetric.type && customMetric.type.isScalable(); +export const getBucketAvgMetricAgg = ({ + getInternalStartServices, +}: BucketAvgMetricAggDependencies) => { + return new MetricAggType( + { + name: METRIC_TYPES.AVG_BUCKET, + title: averageBucketTitle, + makeLabel: agg => makeNestedLabel(agg, overallAverageLabel), + subtype: siblingPipelineAggHelper.subtype, + params: [...siblingPipelineAggHelper.params()], + getFormat: siblingPipelineAggHelper.getFormat, + getValue(agg, bucket) { + const customMetric = agg.getParam('customMetric'); + const customBucket = agg.getParam('customBucket'); + const scaleMetrics = customMetric.type && customMetric.type.isScalable(); - let value = bucket[agg.id] && bucket[agg.id].value; + let value = bucket[agg.id] && bucket[agg.id].value; - if (scaleMetrics && customBucket.type.name === 'date_histogram') { - const aggInfo = customBucket.write(); + if (scaleMetrics && customBucket.type.name === 'date_histogram') { + const aggInfo = customBucket.write(); - value *= get(aggInfo, 'bucketInterval.scale', 1); + value *= get(aggInfo, 'bucketInterval.scale', 1); + } + return value; + }, + }, + { + getInternalStartServices, } - return value; - }, -}); + ); +}; diff --git a/src/plugins/data/public/search/aggs/metrics/bucket_max.ts b/src/plugins/data/public/search/aggs/metrics/bucket_max.ts index 0668a9bcf57a8..1e57a2dd8e38e 100644 --- a/src/plugins/data/public/search/aggs/metrics/bucket_max.ts +++ b/src/plugins/data/public/search/aggs/metrics/bucket_max.ts @@ -22,6 +22,11 @@ import { MetricAggType } from './metric_agg_type'; import { makeNestedLabel } from './lib/make_nested_label'; import { siblingPipelineAggHelper } from './lib/sibling_pipeline_agg_helper'; import { METRIC_TYPES } from './metric_agg_types'; +import { GetInternalStartServicesFn } from '../../../types'; + +export interface BucketMaxMetricAggDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} const overallMaxLabel = i18n.translate('data.search.aggs.metrics.overallMaxLabel', { defaultMessage: 'overall max', @@ -31,11 +36,20 @@ const maxBucketTitle = i18n.translate('data.search.aggs.metrics.maxBucketTitle', defaultMessage: 'Max Bucket', }); -export const bucketMaxMetricAgg = new MetricAggType({ - name: METRIC_TYPES.MAX_BUCKET, - title: maxBucketTitle, - makeLabel: agg => makeNestedLabel(agg, overallMaxLabel), - subtype: siblingPipelineAggHelper.subtype, - params: [...siblingPipelineAggHelper.params()], - getFormat: siblingPipelineAggHelper.getFormat, -}); +export const getBucketMaxMetricAgg = ({ + getInternalStartServices, +}: BucketMaxMetricAggDependencies) => { + return new MetricAggType( + { + name: METRIC_TYPES.MAX_BUCKET, + title: maxBucketTitle, + makeLabel: agg => makeNestedLabel(agg, overallMaxLabel), + subtype: siblingPipelineAggHelper.subtype, + params: [...siblingPipelineAggHelper.params()], + getFormat: siblingPipelineAggHelper.getFormat, + }, + { + getInternalStartServices, + } + ); +}; diff --git a/src/plugins/data/public/search/aggs/metrics/bucket_min.ts b/src/plugins/data/public/search/aggs/metrics/bucket_min.ts index 8f728cb5e7e42..0484af23a7141 100644 --- a/src/plugins/data/public/search/aggs/metrics/bucket_min.ts +++ b/src/plugins/data/public/search/aggs/metrics/bucket_min.ts @@ -22,6 +22,11 @@ import { MetricAggType } from './metric_agg_type'; import { makeNestedLabel } from './lib/make_nested_label'; import { siblingPipelineAggHelper } from './lib/sibling_pipeline_agg_helper'; import { METRIC_TYPES } from './metric_agg_types'; +import { GetInternalStartServicesFn } from '../../../types'; + +export interface BucketMinMetricAggDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} const overallMinLabel = i18n.translate('data.search.aggs.metrics.overallMinLabel', { defaultMessage: 'overall min', @@ -31,11 +36,20 @@ const minBucketTitle = i18n.translate('data.search.aggs.metrics.minBucketTitle', defaultMessage: 'Min Bucket', }); -export const bucketMinMetricAgg = new MetricAggType({ - name: METRIC_TYPES.MIN_BUCKET, - title: minBucketTitle, - makeLabel: agg => makeNestedLabel(agg, overallMinLabel), - subtype: siblingPipelineAggHelper.subtype, - params: [...siblingPipelineAggHelper.params()], - getFormat: siblingPipelineAggHelper.getFormat, -}); +export const getBucketMinMetricAgg = ({ + getInternalStartServices, +}: BucketMinMetricAggDependencies) => { + return new MetricAggType( + { + name: METRIC_TYPES.MIN_BUCKET, + title: minBucketTitle, + makeLabel: agg => makeNestedLabel(agg, overallMinLabel), + subtype: siblingPipelineAggHelper.subtype, + params: [...siblingPipelineAggHelper.params()], + getFormat: siblingPipelineAggHelper.getFormat, + }, + { + getInternalStartServices, + } + ); +}; diff --git a/src/plugins/data/public/search/aggs/metrics/bucket_sum.ts b/src/plugins/data/public/search/aggs/metrics/bucket_sum.ts index 1f9392c5bec35..0a4d29a18a980 100644 --- a/src/plugins/data/public/search/aggs/metrics/bucket_sum.ts +++ b/src/plugins/data/public/search/aggs/metrics/bucket_sum.ts @@ -22,6 +22,11 @@ import { MetricAggType } from './metric_agg_type'; import { makeNestedLabel } from './lib/make_nested_label'; import { siblingPipelineAggHelper } from './lib/sibling_pipeline_agg_helper'; import { METRIC_TYPES } from './metric_agg_types'; +import { GetInternalStartServicesFn } from '../../../types'; + +export interface BucketSumMetricAggDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} const overallSumLabel = i18n.translate('data.search.aggs.metrics.overallSumLabel', { defaultMessage: 'overall sum', @@ -31,11 +36,20 @@ const sumBucketTitle = i18n.translate('data.search.aggs.metrics.sumBucketTitle', defaultMessage: 'Sum Bucket', }); -export const bucketSumMetricAgg = new MetricAggType({ - name: METRIC_TYPES.SUM_BUCKET, - title: sumBucketTitle, - makeLabel: agg => makeNestedLabel(agg, overallSumLabel), - subtype: siblingPipelineAggHelper.subtype, - params: [...siblingPipelineAggHelper.params()], - getFormat: siblingPipelineAggHelper.getFormat, -}); +export const getBucketSumMetricAgg = ({ + getInternalStartServices, +}: BucketSumMetricAggDependencies) => { + return new MetricAggType( + { + name: METRIC_TYPES.SUM_BUCKET, + title: sumBucketTitle, + makeLabel: agg => makeNestedLabel(agg, overallSumLabel), + subtype: siblingPipelineAggHelper.subtype, + params: [...siblingPipelineAggHelper.params()], + getFormat: siblingPipelineAggHelper.getFormat, + }, + { + getInternalStartServices, + } + ); +}; diff --git a/src/plugins/data/public/search/aggs/metrics/cardinality.ts b/src/plugins/data/public/search/aggs/metrics/cardinality.ts index 88cdf3175665e..10b6b5aff1abd 100644 --- a/src/plugins/data/public/search/aggs/metrics/cardinality.ts +++ b/src/plugins/data/public/search/aggs/metrics/cardinality.ts @@ -18,36 +18,48 @@ */ import { i18n } from '@kbn/i18n'; -import { MetricAggType } from './metric_agg_type'; +import { MetricAggType, IMetricAggConfig } from './metric_agg_type'; import { METRIC_TYPES } from './metric_agg_types'; import { KBN_FIELD_TYPES } from '../../../../common'; -import { getFieldFormats } from '../../../../public/services'; +import { GetInternalStartServicesFn } from '../../../types'; const uniqueCountTitle = i18n.translate('data.search.aggs.metrics.uniqueCountTitle', { defaultMessage: 'Unique Count', }); -export const cardinalityMetricAgg = new MetricAggType({ - name: METRIC_TYPES.CARDINALITY, - title: uniqueCountTitle, - makeLabel(aggConfig) { - return i18n.translate('data.search.aggs.metrics.uniqueCountLabel', { - defaultMessage: 'Unique count of {field}', - values: { field: aggConfig.getFieldDisplayName() }, - }); - }, - getFormat() { - const fieldFormatsService = getFieldFormats(); +export interface CardinalityMetricAggDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} - return fieldFormatsService.getDefaultInstance(KBN_FIELD_TYPES.NUMBER); - }, - params: [ +export const getCardinalityMetricAgg = ({ + getInternalStartServices, +}: CardinalityMetricAggDependencies) => + new MetricAggType( { - name: 'field', - type: 'field', - filterFieldTypes: Object.values(KBN_FIELD_TYPES).filter( - type => type !== KBN_FIELD_TYPES.HISTOGRAM - ), + name: METRIC_TYPES.CARDINALITY, + title: uniqueCountTitle, + makeLabel(aggConfig: IMetricAggConfig) { + return i18n.translate('data.search.aggs.metrics.uniqueCountLabel', { + defaultMessage: 'Unique count of {field}', + values: { field: aggConfig.getFieldDisplayName() }, + }); + }, + getFormat() { + const { fieldFormats } = getInternalStartServices(); + + return fieldFormats.getDefaultInstance(KBN_FIELD_TYPES.NUMBER); + }, + params: [ + { + name: 'field', + type: 'field', + filterFieldTypes: Object.values(KBN_FIELD_TYPES).filter( + type => type !== KBN_FIELD_TYPES.HISTOGRAM + ), + }, + ], }, - ], -}); + { + getInternalStartServices, + } + ); diff --git a/src/plugins/data/public/search/aggs/metrics/count.ts b/src/plugins/data/public/search/aggs/metrics/count.ts index 3ec1e18d66ab9..bd0b83798c7db 100644 --- a/src/plugins/data/public/search/aggs/metrics/count.ts +++ b/src/plugins/data/public/search/aggs/metrics/count.ts @@ -21,28 +21,38 @@ import { i18n } from '@kbn/i18n'; import { MetricAggType } from './metric_agg_type'; import { METRIC_TYPES } from './metric_agg_types'; import { KBN_FIELD_TYPES } from '../../../../common'; -import { getFieldFormats } from '../../../../public/services'; +import { GetInternalStartServicesFn } from '../../../types'; -export const countMetricAgg = new MetricAggType({ - name: METRIC_TYPES.COUNT, - title: i18n.translate('data.search.aggs.metrics.countTitle', { - defaultMessage: 'Count', - }), - hasNoDsl: true, - makeLabel() { - return i18n.translate('data.search.aggs.metrics.countLabel', { - defaultMessage: 'Count', - }); - }, - getFormat() { - const fieldFormatsService = getFieldFormats(); +export interface CountMetricAggDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} - return fieldFormatsService.getDefaultInstance(KBN_FIELD_TYPES.NUMBER); - }, - getValue(agg, bucket) { - return bucket.doc_count; - }, - isScalable() { - return true; - }, -}); +export const getCountMetricAgg = ({ getInternalStartServices }: CountMetricAggDependencies) => + new MetricAggType( + { + name: METRIC_TYPES.COUNT, + title: i18n.translate('data.search.aggs.metrics.countTitle', { + defaultMessage: 'Count', + }), + hasNoDsl: true, + makeLabel() { + return i18n.translate('data.search.aggs.metrics.countLabel', { + defaultMessage: 'Count', + }); + }, + getFormat() { + const { fieldFormats } = getInternalStartServices(); + + return fieldFormats.getDefaultInstance(KBN_FIELD_TYPES.NUMBER); + }, + getValue(agg, bucket) { + return bucket.doc_count; + }, + isScalable() { + return true; + }, + }, + { + getInternalStartServices, + } + ); diff --git a/src/plugins/data/public/search/aggs/metrics/cumulative_sum.ts b/src/plugins/data/public/search/aggs/metrics/cumulative_sum.ts index a5d02459900bb..8ca922e144a1f 100644 --- a/src/plugins/data/public/search/aggs/metrics/cumulative_sum.ts +++ b/src/plugins/data/public/search/aggs/metrics/cumulative_sum.ts @@ -22,6 +22,11 @@ import { MetricAggType } from './metric_agg_type'; import { parentPipelineAggHelper } from './lib/parent_pipeline_agg_helper'; import { makeNestedLabel } from './lib/make_nested_label'; import { METRIC_TYPES } from './metric_agg_types'; +import { GetInternalStartServicesFn } from '../../../types'; + +export interface CumulativeSumMetricAggDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} const cumulativeSumLabel = i18n.translate('data.search.aggs.metrics.cumulativeSumLabel', { defaultMessage: 'cumulative sum', @@ -31,11 +36,20 @@ const cumulativeSumTitle = i18n.translate('data.search.aggs.metrics.cumulativeSu defaultMessage: 'Cumulative Sum', }); -export const cumulativeSumMetricAgg = new MetricAggType({ - name: METRIC_TYPES.CUMULATIVE_SUM, - title: cumulativeSumTitle, - subtype: parentPipelineAggHelper.subtype, - makeLabel: agg => makeNestedLabel(agg, cumulativeSumLabel), - params: [...parentPipelineAggHelper.params()], - getFormat: parentPipelineAggHelper.getFormat, -}); +export const getCumulativeSumMetricAgg = ({ + getInternalStartServices, +}: CumulativeSumMetricAggDependencies) => { + return new MetricAggType( + { + name: METRIC_TYPES.CUMULATIVE_SUM, + title: cumulativeSumTitle, + subtype: parentPipelineAggHelper.subtype, + makeLabel: agg => makeNestedLabel(agg, cumulativeSumLabel), + params: [...parentPipelineAggHelper.params()], + getFormat: parentPipelineAggHelper.getFormat, + }, + { + getInternalStartServices, + } + ); +}; diff --git a/src/plugins/data/public/search/aggs/metrics/derivative.ts b/src/plugins/data/public/search/aggs/metrics/derivative.ts index 1169a527b0668..5752a72c846aa 100644 --- a/src/plugins/data/public/search/aggs/metrics/derivative.ts +++ b/src/plugins/data/public/search/aggs/metrics/derivative.ts @@ -22,6 +22,11 @@ import { MetricAggType } from './metric_agg_type'; import { parentPipelineAggHelper } from './lib/parent_pipeline_agg_helper'; import { makeNestedLabel } from './lib/make_nested_label'; import { METRIC_TYPES } from './metric_agg_types'; +import { GetInternalStartServicesFn } from '../../../types'; + +export interface DerivativeMetricAggDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} const derivativeLabel = i18n.translate('data.search.aggs.metrics.derivativeLabel', { defaultMessage: 'derivative', @@ -31,13 +36,22 @@ const derivativeTitle = i18n.translate('data.search.aggs.metrics.derivativeTitle defaultMessage: 'Derivative', }); -export const derivativeMetricAgg = new MetricAggType({ - name: METRIC_TYPES.DERIVATIVE, - title: derivativeTitle, - subtype: parentPipelineAggHelper.subtype, - makeLabel(agg) { - return makeNestedLabel(agg, derivativeLabel); - }, - params: [...parentPipelineAggHelper.params()], - getFormat: parentPipelineAggHelper.getFormat, -}); +export const getDerivativeMetricAgg = ({ + getInternalStartServices, +}: DerivativeMetricAggDependencies) => { + return new MetricAggType( + { + name: METRIC_TYPES.DERIVATIVE, + title: derivativeTitle, + subtype: parentPipelineAggHelper.subtype, + makeLabel(agg) { + return makeNestedLabel(agg, derivativeLabel); + }, + params: [...parentPipelineAggHelper.params()], + getFormat: parentPipelineAggHelper.getFormat, + }, + { + getInternalStartServices, + } + ); +}; diff --git a/src/plugins/data/public/search/aggs/metrics/geo_bounds.ts b/src/plugins/data/public/search/aggs/metrics/geo_bounds.ts index 8a9f66f4b22a8..00927ebba56bf 100644 --- a/src/plugins/data/public/search/aggs/metrics/geo_bounds.ts +++ b/src/plugins/data/public/search/aggs/metrics/geo_bounds.ts @@ -21,6 +21,11 @@ import { i18n } from '@kbn/i18n'; import { MetricAggType } from './metric_agg_type'; import { METRIC_TYPES } from './metric_agg_types'; import { KBN_FIELD_TYPES } from '../../../../common'; +import { GetInternalStartServicesFn } from '../../../types'; + +export interface GeoBoundsMetricAggDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} const geoBoundsTitle = i18n.translate('data.search.aggs.metrics.geoBoundsTitle', { defaultMessage: 'Geo Bounds', @@ -30,15 +35,24 @@ const geoBoundsLabel = i18n.translate('data.search.aggs.metrics.geoBoundsLabel', defaultMessage: 'Geo Bounds', }); -export const geoBoundsMetricAgg = new MetricAggType({ - name: METRIC_TYPES.GEO_BOUNDS, - title: geoBoundsTitle, - makeLabel: () => geoBoundsLabel, - params: [ +export const getGeoBoundsMetricAgg = ({ + getInternalStartServices, +}: GeoBoundsMetricAggDependencies) => { + return new MetricAggType( { - name: 'field', - type: 'field', - filterFieldTypes: KBN_FIELD_TYPES.GEO_POINT, + name: METRIC_TYPES.GEO_BOUNDS, + title: geoBoundsTitle, + makeLabel: () => geoBoundsLabel, + params: [ + { + name: 'field', + type: 'field', + filterFieldTypes: KBN_FIELD_TYPES.GEO_POINT, + }, + ], }, - ], -}); + { + getInternalStartServices, + } + ); +}; diff --git a/src/plugins/data/public/search/aggs/metrics/geo_centroid.ts b/src/plugins/data/public/search/aggs/metrics/geo_centroid.ts index a4e4413843bdd..a4b084f794a5d 100644 --- a/src/plugins/data/public/search/aggs/metrics/geo_centroid.ts +++ b/src/plugins/data/public/search/aggs/metrics/geo_centroid.ts @@ -21,6 +21,11 @@ import { i18n } from '@kbn/i18n'; import { MetricAggType } from './metric_agg_type'; import { METRIC_TYPES } from './metric_agg_types'; import { KBN_FIELD_TYPES } from '../../../../common'; +import { GetInternalStartServicesFn } from '../../../types'; + +export interface GeoCentroidMetricAggDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} const geoCentroidTitle = i18n.translate('data.search.aggs.metrics.geoCentroidTitle', { defaultMessage: 'Geo Centroid', @@ -30,18 +35,27 @@ const geoCentroidLabel = i18n.translate('data.search.aggs.metrics.geoCentroidLab defaultMessage: 'Geo Centroid', }); -export const geoCentroidMetricAgg = new MetricAggType({ - name: METRIC_TYPES.GEO_CENTROID, - title: geoCentroidTitle, - makeLabel: () => geoCentroidLabel, - params: [ +export const getGeoCentroidMetricAgg = ({ + getInternalStartServices, +}: GeoCentroidMetricAggDependencies) => { + return new MetricAggType( { - name: 'field', - type: 'field', - filterFieldTypes: KBN_FIELD_TYPES.GEO_POINT, + name: METRIC_TYPES.GEO_CENTROID, + title: geoCentroidTitle, + makeLabel: () => geoCentroidLabel, + params: [ + { + name: 'field', + type: 'field', + filterFieldTypes: KBN_FIELD_TYPES.GEO_POINT, + }, + ], + getValue(agg, bucket) { + return bucket[agg.id] && bucket[agg.id].location; + }, }, - ], - getValue(agg, bucket) { - return bucket[agg.id] && bucket[agg.id].location; - }, -}); + { + getInternalStartServices, + } + ); +}; diff --git a/src/plugins/data/public/search/aggs/metrics/max.ts b/src/plugins/data/public/search/aggs/metrics/max.ts index 0cfb7be699a95..88e8b485cb73f 100644 --- a/src/plugins/data/public/search/aggs/metrics/max.ts +++ b/src/plugins/data/public/search/aggs/metrics/max.ts @@ -21,25 +21,37 @@ import { i18n } from '@kbn/i18n'; import { MetricAggType } from './metric_agg_type'; import { METRIC_TYPES } from './metric_agg_types'; import { KBN_FIELD_TYPES } from '../../../../common'; +import { GetInternalStartServicesFn } from '../../../types'; const maxTitle = i18n.translate('data.search.aggs.metrics.maxTitle', { defaultMessage: 'Max', }); -export const maxMetricAgg = new MetricAggType({ - name: METRIC_TYPES.MAX, - title: maxTitle, - makeLabel(aggConfig) { - return i18n.translate('data.search.aggs.metrics.maxLabel', { - defaultMessage: 'Max {field}', - values: { field: aggConfig.getFieldDisplayName() }, - }); - }, - params: [ +export interface MaxMetricAggDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} + +export const getMaxMetricAgg = ({ getInternalStartServices }: MaxMetricAggDependencies) => { + return new MetricAggType( { - name: 'field', - type: 'field', - filterFieldTypes: [KBN_FIELD_TYPES.NUMBER, KBN_FIELD_TYPES.DATE], + name: METRIC_TYPES.MAX, + title: maxTitle, + makeLabel(aggConfig) { + return i18n.translate('data.search.aggs.metrics.maxLabel', { + defaultMessage: 'Max {field}', + values: { field: aggConfig.getFieldDisplayName() }, + }); + }, + params: [ + { + name: 'field', + type: 'field', + filterFieldTypes: [KBN_FIELD_TYPES.NUMBER, KBN_FIELD_TYPES.DATE], + }, + ], }, - ], -}); + { + getInternalStartServices, + } + ); +}; diff --git a/src/plugins/data/public/search/aggs/metrics/median.test.ts b/src/plugins/data/public/search/aggs/metrics/median.test.ts index ad55837ec9a30..f80c46026f50a 100644 --- a/src/plugins/data/public/search/aggs/metrics/median.test.ts +++ b/src/plugins/data/public/search/aggs/metrics/median.test.ts @@ -17,16 +17,24 @@ * under the License. */ -import { medianMetricAgg } from './median'; +import { getMedianMetricAgg, MedianMetricAggDependencies } from './median'; import { AggConfigs, IAggConfigs } from '../agg_configs'; import { mockAggTypesRegistry } from '../test_helpers'; import { METRIC_TYPES } from './metric_agg_types'; +import { fieldFormatsServiceMock } from '../../../field_formats/mocks'; +import { notificationServiceMock } from '../../../../../../../src/core/public/mocks'; describe('AggTypeMetricMedianProvider class', () => { let aggConfigs: IAggConfigs; + const aggTypesDependencies: MedianMetricAggDependencies = { + getInternalStartServices: () => ({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), + }), + }; beforeEach(() => { - const typesRegistry = mockAggTypesRegistry([medianMetricAgg]); + const typesRegistry = mockAggTypesRegistry([getMedianMetricAgg(aggTypesDependencies)]); const field = { name: 'bytes', }; diff --git a/src/plugins/data/public/search/aggs/metrics/median.ts b/src/plugins/data/public/search/aggs/metrics/median.ts index faa0694cd5312..a398f017602b0 100644 --- a/src/plugins/data/public/search/aggs/metrics/median.ts +++ b/src/plugins/data/public/search/aggs/metrics/median.ts @@ -21,33 +21,49 @@ import { i18n } from '@kbn/i18n'; import { MetricAggType } from './metric_agg_type'; import { METRIC_TYPES } from './metric_agg_types'; import { KBN_FIELD_TYPES } from '../../../../common'; +import { GetInternalStartServicesFn } from '../../../types'; const medianTitle = i18n.translate('data.search.aggs.metrics.medianTitle', { defaultMessage: 'Median', }); -export const medianMetricAgg = new MetricAggType({ - name: METRIC_TYPES.MEDIAN, - dslName: 'percentiles', - title: medianTitle, - makeLabel(aggConfig) { - return i18n.translate('data.search.aggs.metrics.medianLabel', { - defaultMessage: 'Median {field}', - values: { field: aggConfig.getFieldDisplayName() }, - }); - }, - params: [ +export interface MedianMetricAggDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} + +export const getMedianMetricAgg = ({ getInternalStartServices }: MedianMetricAggDependencies) => { + return new MetricAggType( { - name: 'field', - type: 'field', - filterFieldTypes: [KBN_FIELD_TYPES.NUMBER, KBN_FIELD_TYPES.DATE, KBN_FIELD_TYPES.HISTOGRAM], - write(agg, output) { - output.params.field = agg.getParam('field').name; - output.params.percents = [50]; + name: METRIC_TYPES.MEDIAN, + dslName: 'percentiles', + title: medianTitle, + makeLabel(aggConfig) { + return i18n.translate('data.search.aggs.metrics.medianLabel', { + defaultMessage: 'Median {field}', + values: { field: aggConfig.getFieldDisplayName() }, + }); + }, + params: [ + { + name: 'field', + type: 'field', + filterFieldTypes: [ + KBN_FIELD_TYPES.NUMBER, + KBN_FIELD_TYPES.DATE, + KBN_FIELD_TYPES.HISTOGRAM, + ], + write(agg, output) { + output.params.field = agg.getParam('field').name; + output.params.percents = [50]; + }, + }, + ], + getValue(agg, bucket) { + return bucket[agg.id].values['50.0']; }, }, - ], - getValue(agg, bucket) { - return bucket[agg.id].values['50.0']; - }, -}); + { + getInternalStartServices, + } + ); +}; diff --git a/src/plugins/data/public/search/aggs/metrics/metric_agg_type.ts b/src/plugins/data/public/search/aggs/metrics/metric_agg_type.ts index 05c4cb3de4bdf..bb16cba1bee62 100644 --- a/src/plugins/data/public/search/aggs/metrics/metric_agg_type.ts +++ b/src/plugins/data/public/search/aggs/metrics/metric_agg_type.ts @@ -23,8 +23,8 @@ import { AggParamType } from '../param_types/agg'; import { AggConfig } from '../agg_config'; import { METRIC_TYPES } from './metric_agg_types'; import { KBN_FIELD_TYPES } from '../../../../common'; -import { getFieldFormats } from '../../../../public/services'; import { FieldTypes } from '../param_types'; +import { GetInternalStartServicesFn } from '../../../types'; export interface IMetricAggConfig extends AggConfig { type: InstanceType; @@ -44,6 +44,10 @@ interface MetricAggTypeConfig subtype?: string; } +interface MetricAggTypeDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} + // TODO need to make a more explicit interface for this export type IMetricAggType = MetricAggType; @@ -57,8 +61,11 @@ export class MetricAggType {}; - constructor(config: MetricAggTypeConfig) { - super(config); + constructor( + config: MetricAggTypeConfig, + dependencies: MetricAggTypeDependencies + ) { + super(config, dependencies); this.getValue = config.getValue || @@ -78,11 +85,9 @@ export class MetricAggType { - const fieldFormatsService = getFieldFormats(); + const { fieldFormats } = dependencies.getInternalStartServices(); const field = agg.getField(); - return field - ? field.format - : fieldFormatsService.getDefaultInstance(KBN_FIELD_TYPES.NUMBER); + return field ? field.format : fieldFormats.getDefaultInstance(KBN_FIELD_TYPES.NUMBER); }); this.subtype = diff --git a/src/plugins/data/public/search/aggs/metrics/min.ts b/src/plugins/data/public/search/aggs/metrics/min.ts index 0a9abf1edcd04..aae16f357186c 100644 --- a/src/plugins/data/public/search/aggs/metrics/min.ts +++ b/src/plugins/data/public/search/aggs/metrics/min.ts @@ -21,25 +21,37 @@ import { i18n } from '@kbn/i18n'; import { MetricAggType } from './metric_agg_type'; import { METRIC_TYPES } from './metric_agg_types'; import { KBN_FIELD_TYPES } from '../../../../common'; +import { GetInternalStartServicesFn } from '../../../types'; const minTitle = i18n.translate('data.search.aggs.metrics.minTitle', { defaultMessage: 'Min', }); -export const minMetricAgg = new MetricAggType({ - name: METRIC_TYPES.MIN, - title: minTitle, - makeLabel(aggConfig) { - return i18n.translate('data.search.aggs.metrics.minLabel', { - defaultMessage: 'Min {field}', - values: { field: aggConfig.getFieldDisplayName() }, - }); - }, - params: [ +export interface MinMetricAggDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} + +export const getMinMetricAgg = ({ getInternalStartServices }: MinMetricAggDependencies) => { + return new MetricAggType( { - name: 'field', - type: 'field', - filterFieldTypes: [KBN_FIELD_TYPES.NUMBER, KBN_FIELD_TYPES.DATE], + name: METRIC_TYPES.MIN, + title: minTitle, + makeLabel(aggConfig) { + return i18n.translate('data.search.aggs.metrics.minLabel', { + defaultMessage: 'Min {field}', + values: { field: aggConfig.getFieldDisplayName() }, + }); + }, + params: [ + { + name: 'field', + type: 'field', + filterFieldTypes: [KBN_FIELD_TYPES.NUMBER, KBN_FIELD_TYPES.DATE], + }, + ], }, - ], -}); + { + getInternalStartServices, + } + ); +}; diff --git a/src/plugins/data/public/search/aggs/metrics/moving_avg.ts b/src/plugins/data/public/search/aggs/metrics/moving_avg.ts index cb733507858bc..94b9b1d8cd487 100644 --- a/src/plugins/data/public/search/aggs/metrics/moving_avg.ts +++ b/src/plugins/data/public/search/aggs/metrics/moving_avg.ts @@ -22,6 +22,11 @@ import { MetricAggType } from './metric_agg_type'; import { parentPipelineAggHelper } from './lib/parent_pipeline_agg_helper'; import { makeNestedLabel } from './lib/make_nested_label'; import { METRIC_TYPES } from './metric_agg_types'; +import { GetInternalStartServicesFn } from '../../../types'; + +export interface MovingAvgMetricAggDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} const movingAvgTitle = i18n.translate('data.search.aggs.metrics.movingAvgTitle', { defaultMessage: 'Moving Avg', @@ -31,34 +36,43 @@ const movingAvgLabel = i18n.translate('data.search.aggs.metrics.movingAvgLabel', defaultMessage: 'moving avg', }); -export const movingAvgMetricAgg = new MetricAggType({ - name: METRIC_TYPES.MOVING_FN, - dslName: 'moving_fn', - title: movingAvgTitle, - subtype: parentPipelineAggHelper.subtype, - makeLabel: agg => makeNestedLabel(agg, movingAvgLabel), - params: [ - ...parentPipelineAggHelper.params(), +export const getMovingAvgMetricAgg = ({ + getInternalStartServices, +}: MovingAvgMetricAggDependencies) => { + return new MetricAggType( { - name: 'window', - default: 5, + name: METRIC_TYPES.MOVING_FN, + dslName: 'moving_fn', + title: movingAvgTitle, + subtype: parentPipelineAggHelper.subtype, + makeLabel: agg => makeNestedLabel(agg, movingAvgLabel), + params: [ + ...parentPipelineAggHelper.params(), + { + name: 'window', + default: 5, + }, + { + name: 'script', + default: 'MovingFunctions.unweightedAvg(values)', + }, + ], + getValue(agg, bucket) { + /** + * The previous implementation using `moving_avg` did not + * return any bucket in case there are no documents or empty window. + * The `moving_fn` aggregation returns buckets with the value null if the + * window is empty or doesn't return any value if the sibiling metric + * is null. Since our generic MetricAggType.getValue implementation + * would return the value 0 for null buckets, we need a specific + * implementation here, that preserves the null value. + */ + return bucket[agg.id] ? bucket[agg.id].value : null; + }, + getFormat: parentPipelineAggHelper.getFormat, }, { - name: 'script', - default: 'MovingFunctions.unweightedAvg(values)', - }, - ], - getValue(agg, bucket) { - /** - * The previous implementation using `moving_avg` did not - * return any bucket in case there are no documents or empty window. - * The `moving_fn` aggregation returns buckets with the value null if the - * window is empty or doesn't return any value if the sibiling metric - * is null. Since our generic MetricAggType.getValue implementation - * would return the value 0 for null buckets, we need a specific - * implementation here, that preserves the null value. - */ - return bucket[agg.id] ? bucket[agg.id].value : null; - }, - getFormat: parentPipelineAggHelper.getFormat, -}); + getInternalStartServices, + } + ); +}; diff --git a/src/plugins/data/public/search/aggs/metrics/parent_pipeline.test.ts b/src/plugins/data/public/search/aggs/metrics/parent_pipeline.test.ts index 02e63f653f94f..af983a50f6c23 100644 --- a/src/plugins/data/public/search/aggs/metrics/parent_pipeline.test.ts +++ b/src/plugins/data/public/search/aggs/metrics/parent_pipeline.test.ts @@ -17,26 +17,47 @@ * under the License. */ -import { derivativeMetricAgg } from './derivative'; -import { cumulativeSumMetricAgg } from './cumulative_sum'; -import { movingAvgMetricAgg } from './moving_avg'; -import { serialDiffMetricAgg } from './serial_diff'; +import { getDerivativeMetricAgg } from './derivative'; +import { getCumulativeSumMetricAgg } from './cumulative_sum'; +import { getMovingAvgMetricAgg } from './moving_avg'; +import { getSerialDiffMetricAgg } from './serial_diff'; import { AggConfigs } from '../agg_configs'; -import { mockDataServices, mockAggTypesRegistry } from '../test_helpers'; +import { mockAggTypesRegistry } from '../test_helpers'; import { IMetricAggConfig, MetricAggType } from './metric_agg_type'; +import { fieldFormatsServiceMock } from '../../../field_formats/mocks'; +import { GetInternalStartServicesFn } from '../../../types'; +import { notificationServiceMock } from '../../../../../../../src/core/public/mocks'; describe('parent pipeline aggs', function() { - beforeEach(() => { - mockDataServices(); + const getInternalStartServices: GetInternalStartServicesFn = () => ({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), }); const typesRegistry = mockAggTypesRegistry(); const metrics = [ - { name: 'derivative', title: 'Derivative', provider: derivativeMetricAgg }, - { name: 'cumulative_sum', title: 'Cumulative Sum', provider: cumulativeSumMetricAgg }, - { name: 'moving_avg', title: 'Moving Avg', provider: movingAvgMetricAgg, dslName: 'moving_fn' }, - { name: 'serial_diff', title: 'Serial Diff', provider: serialDiffMetricAgg }, + { + name: 'derivative', + title: 'Derivative', + provider: getDerivativeMetricAgg({ getInternalStartServices }), + }, + { + name: 'cumulative_sum', + title: 'Cumulative Sum', + provider: getCumulativeSumMetricAgg({ getInternalStartServices }), + }, + { + name: 'moving_avg', + title: 'Moving Avg', + provider: getMovingAvgMetricAgg({ getInternalStartServices }), + dslName: 'moving_fn', + }, + { + name: 'serial_diff', + title: 'Serial Diff', + provider: getSerialDiffMetricAgg({ getInternalStartServices }), + }, ]; metrics.forEach(metric => { diff --git a/src/plugins/data/public/search/aggs/metrics/percentile_ranks.test.ts b/src/plugins/data/public/search/aggs/metrics/percentile_ranks.test.ts index 628f1cd204ee5..2944fc8c11b23 100644 --- a/src/plugins/data/public/search/aggs/metrics/percentile_ranks.test.ts +++ b/src/plugins/data/public/search/aggs/metrics/percentile_ranks.test.ts @@ -17,18 +17,28 @@ * under the License. */ -import { IPercentileRanksAggConfig, percentileRanksMetricAgg } from './percentile_ranks'; +import { + IPercentileRanksAggConfig, + getPercentileRanksMetricAgg, + PercentileRanksMetricAggDependencies, +} from './percentile_ranks'; import { AggConfigs, IAggConfigs } from '../agg_configs'; -import { mockDataServices, mockAggTypesRegistry } from '../test_helpers'; +import { mockAggTypesRegistry } from '../test_helpers'; import { METRIC_TYPES } from './metric_agg_types'; +import { fieldFormatsServiceMock } from '../../../field_formats/mocks'; +import { notificationServiceMock } from '../../../../../../../src/core/public/mocks'; describe('AggTypesMetricsPercentileRanksProvider class', function() { let aggConfigs: IAggConfigs; + const aggTypesDependencies: PercentileRanksMetricAggDependencies = { + getInternalStartServices: () => ({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), + }), + }; beforeEach(() => { - mockDataServices(); - - const typesRegistry = mockAggTypesRegistry([percentileRanksMetricAgg]); + const typesRegistry = mockAggTypesRegistry([getPercentileRanksMetricAgg(aggTypesDependencies)]); const field = { name: 'bytes', }; @@ -65,7 +75,7 @@ describe('AggTypesMetricsPercentileRanksProvider class', function() { }); it('uses the custom label if it is set', function() { - const responseAggs: any = percentileRanksMetricAgg.getResponseAggs( + const responseAggs: any = getPercentileRanksMetricAgg(aggTypesDependencies).getResponseAggs( aggConfigs.aggs[0] as IPercentileRanksAggConfig ); diff --git a/src/plugins/data/public/search/aggs/metrics/percentile_ranks.ts b/src/plugins/data/public/search/aggs/metrics/percentile_ranks.ts index 7dc0f70ea7b80..0d79665ff9c4e 100644 --- a/src/plugins/data/public/search/aggs/metrics/percentile_ranks.ts +++ b/src/plugins/data/public/search/aggs/metrics/percentile_ranks.ts @@ -23,68 +23,86 @@ import { getResponseAggConfigClass, IResponseAggConfig } from './lib/get_respons import { getPercentileValue } from './percentiles_get_value'; import { METRIC_TYPES } from './metric_agg_types'; import { FIELD_FORMAT_IDS, KBN_FIELD_TYPES } from '../../../../common'; -import { getFieldFormats } from '../../../../public/services'; +import { GetInternalStartServicesFn } from '../../../types'; // required by the values editor export type IPercentileRanksAggConfig = IResponseAggConfig; -const valueProps = { - makeLabel(this: IPercentileRanksAggConfig) { - const fieldFormatsService = getFieldFormats(); - const field = this.getField(); - const format = - (field && field.format) || fieldFormatsService.getDefaultInstance(KBN_FIELD_TYPES.NUMBER); - const customLabel = this.getParam('customLabel'); - const label = customLabel || this.getFieldDisplayName(); +export interface PercentileRanksMetricAggDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} - return i18n.translate('data.search.aggs.metrics.percentileRanks.valuePropsLabel', { - defaultMessage: 'Percentile rank {format} of "{label}"', - values: { format: format.convert(this.key, 'text'), label }, - }); - }, -}; +const getValueProps = (getInternalStartServices: GetInternalStartServicesFn) => { + return { + makeLabel(this: IPercentileRanksAggConfig) { + const { fieldFormats } = getInternalStartServices(); + const field = this.getField(); + const format = + (field && field.format) || fieldFormats.getDefaultInstance(KBN_FIELD_TYPES.NUMBER); + const customLabel = this.getParam('customLabel'); + const label = customLabel || this.getFieldDisplayName(); -export const percentileRanksMetricAgg = new MetricAggType({ - name: METRIC_TYPES.PERCENTILE_RANKS, - title: i18n.translate('data.search.aggs.metrics.percentileRanksTitle', { - defaultMessage: 'Percentile Ranks', - }), - makeLabel(agg) { - return i18n.translate('data.search.aggs.metrics.percentileRanksLabel', { - defaultMessage: 'Percentile ranks of {field}', - values: { field: agg.getFieldDisplayName() }, - }); - }, - params: [ - { - name: 'field', - type: 'field', - filterFieldTypes: [KBN_FIELD_TYPES.NUMBER, KBN_FIELD_TYPES.HISTOGRAM], - }, - { - name: 'values', - default: [], + return i18n.translate('data.search.aggs.metrics.percentileRanks.valuePropsLabel', { + defaultMessage: 'Percentile rank {format} of "{label}"', + values: { format: format.convert(this.key, 'text'), label }, + }); }, + }; +}; + +export const getPercentileRanksMetricAgg = ({ + getInternalStartServices, +}: PercentileRanksMetricAggDependencies) => { + return new MetricAggType( { - write(agg, output) { - output.params.keyed = false; + name: METRIC_TYPES.PERCENTILE_RANKS, + title: i18n.translate('data.search.aggs.metrics.percentileRanksTitle', { + defaultMessage: 'Percentile Ranks', + }), + makeLabel(agg) { + return i18n.translate('data.search.aggs.metrics.percentileRanksLabel', { + defaultMessage: 'Percentile ranks of {field}', + values: { field: agg.getFieldDisplayName() }, + }); }, - }, - ], - getResponseAggs(agg) { - const ValueAggConfig = getResponseAggConfigClass(agg, valueProps); - const values = agg.getParam('values'); + params: [ + { + name: 'field', + type: 'field', + filterFieldTypes: [KBN_FIELD_TYPES.NUMBER, KBN_FIELD_TYPES.HISTOGRAM], + }, + { + name: 'values', + default: [], + }, + { + write(agg, output) { + output.params.keyed = false; + }, + }, + ], + getResponseAggs(agg) { + const ValueAggConfig = getResponseAggConfigClass( + agg, + getValueProps(getInternalStartServices) + ); + const values = agg.getParam('values'); - return values.map((value: any) => new ValueAggConfig(value)); - }, - getFormat() { - const fieldFormatsService = getFieldFormats(); - return ( - fieldFormatsService.getInstance(FIELD_FORMAT_IDS.PERCENT) || - fieldFormatsService.getDefaultInstance(KBN_FIELD_TYPES.NUMBER) - ); - }, - getValue(agg, bucket) { - return getPercentileValue(agg, bucket) / 100; - }, -}); + return values.map((value: any) => new ValueAggConfig(value)); + }, + getFormat() { + const { fieldFormats } = getInternalStartServices(); + return ( + fieldFormats.getInstance(FIELD_FORMAT_IDS.PERCENT) || + fieldFormats.getDefaultInstance(KBN_FIELD_TYPES.NUMBER) + ); + }, + getValue(agg, bucket) { + return getPercentileValue(agg, bucket) / 100; + }, + }, + { + getInternalStartServices, + } + ); +}; diff --git a/src/plugins/data/public/search/aggs/metrics/percentiles.test.ts b/src/plugins/data/public/search/aggs/metrics/percentiles.test.ts index e077bc0f8c773..33bd42df74cc7 100644 --- a/src/plugins/data/public/search/aggs/metrics/percentiles.test.ts +++ b/src/plugins/data/public/search/aggs/metrics/percentiles.test.ts @@ -17,16 +17,28 @@ * under the License. */ -import { IPercentileAggConfig, percentilesMetricAgg } from './percentiles'; +import { + IPercentileAggConfig, + getPercentilesMetricAgg, + PercentilesMetricAggDependencies, +} from './percentiles'; import { AggConfigs, IAggConfigs } from '../agg_configs'; import { mockAggTypesRegistry } from '../test_helpers'; import { METRIC_TYPES } from './metric_agg_types'; +import { fieldFormatsServiceMock } from '../../../field_formats/mocks'; +import { notificationServiceMock } from '../../../../../../../src/core/public/mocks'; describe('AggTypesMetricsPercentilesProvider class', () => { let aggConfigs: IAggConfigs; + const aggTypesDependencies: PercentilesMetricAggDependencies = { + getInternalStartServices: () => ({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), + }), + }; beforeEach(() => { - const typesRegistry = mockAggTypesRegistry([percentilesMetricAgg]); + const typesRegistry = mockAggTypesRegistry([getPercentilesMetricAgg(aggTypesDependencies)]); const field = { name: 'bytes', }; @@ -63,7 +75,7 @@ describe('AggTypesMetricsPercentilesProvider class', () => { }); it('uses the custom label if it is set', () => { - const responseAggs: any = percentilesMetricAgg.getResponseAggs( + const responseAggs: any = getPercentilesMetricAgg(aggTypesDependencies).getResponseAggs( aggConfigs.aggs[0] as IPercentileAggConfig ); diff --git a/src/plugins/data/public/search/aggs/metrics/percentiles.ts b/src/plugins/data/public/search/aggs/metrics/percentiles.ts index a39d68248d608..040a52588dd94 100644 --- a/src/plugins/data/public/search/aggs/metrics/percentiles.ts +++ b/src/plugins/data/public/search/aggs/metrics/percentiles.ts @@ -24,9 +24,14 @@ import { KBN_FIELD_TYPES } from '../../../../common'; import { getResponseAggConfigClass, IResponseAggConfig } from './lib/get_response_agg_config_class'; import { getPercentileValue } from './percentiles_get_value'; import { ordinalSuffix } from './lib/ordinal_suffix'; +import { GetInternalStartServicesFn } from '../../../types'; export type IPercentileAggConfig = IResponseAggConfig; +export interface PercentilesMetricAggDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} + const valueProps = { makeLabel(this: IPercentileAggConfig) { const customLabel = this.getParam('customLabel'); @@ -39,38 +44,51 @@ const valueProps = { }, }; -export const percentilesMetricAgg = new MetricAggType({ - name: METRIC_TYPES.PERCENTILES, - title: i18n.translate('data.search.aggs.metrics.percentilesTitle', { - defaultMessage: 'Percentiles', - }), - makeLabel(agg) { - return i18n.translate('data.search.aggs.metrics.percentilesLabel', { - defaultMessage: 'Percentiles of {field}', - values: { field: agg.getFieldDisplayName() }, - }); - }, - params: [ - { - name: 'field', - type: 'field', - filterFieldTypes: [KBN_FIELD_TYPES.NUMBER, KBN_FIELD_TYPES.DATE, KBN_FIELD_TYPES.HISTOGRAM], - }, - { - name: 'percents', - default: [1, 5, 25, 50, 75, 95, 99], - }, +export const getPercentilesMetricAgg = ({ + getInternalStartServices, +}: PercentilesMetricAggDependencies) => { + return new MetricAggType( { - write(agg, output) { - output.params.keyed = false; + name: METRIC_TYPES.PERCENTILES, + title: i18n.translate('data.search.aggs.metrics.percentilesTitle', { + defaultMessage: 'Percentiles', + }), + makeLabel(agg) { + return i18n.translate('data.search.aggs.metrics.percentilesLabel', { + defaultMessage: 'Percentiles of {field}', + values: { field: agg.getFieldDisplayName() }, + }); }, - }, - ], - getResponseAggs(agg) { - const ValueAggConfig = getResponseAggConfigClass(agg, valueProps); + params: [ + { + name: 'field', + type: 'field', + filterFieldTypes: [ + KBN_FIELD_TYPES.NUMBER, + KBN_FIELD_TYPES.DATE, + KBN_FIELD_TYPES.HISTOGRAM, + ], + }, + { + name: 'percents', + default: [1, 5, 25, 50, 75, 95, 99], + }, + { + write(agg, output) { + output.params.keyed = false; + }, + }, + ], + getResponseAggs(agg) { + const ValueAggConfig = getResponseAggConfigClass(agg, valueProps); - return agg.getParam('percents').map((percent: any) => new ValueAggConfig(percent)); - }, + return agg.getParam('percents').map((percent: any) => new ValueAggConfig(percent)); + }, - getValue: getPercentileValue, -}); + getValue: getPercentileValue, + }, + { + getInternalStartServices, + } + ); +}; diff --git a/src/plugins/data/public/search/aggs/metrics/serial_diff.ts b/src/plugins/data/public/search/aggs/metrics/serial_diff.ts index 5af6e1952d135..2b1498560f862 100644 --- a/src/plugins/data/public/search/aggs/metrics/serial_diff.ts +++ b/src/plugins/data/public/search/aggs/metrics/serial_diff.ts @@ -22,6 +22,11 @@ import { MetricAggType } from './metric_agg_type'; import { parentPipelineAggHelper } from './lib/parent_pipeline_agg_helper'; import { makeNestedLabel } from './lib/make_nested_label'; import { METRIC_TYPES } from './metric_agg_types'; +import { GetInternalStartServicesFn } from '../../../types'; + +export interface SerialDiffMetricAggDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} const serialDiffTitle = i18n.translate('data.search.aggs.metrics.serialDiffTitle', { defaultMessage: 'Serial Diff', @@ -31,11 +36,20 @@ const serialDiffLabel = i18n.translate('data.search.aggs.metrics.serialDiffLabel defaultMessage: 'serial diff', }); -export const serialDiffMetricAgg = new MetricAggType({ - name: METRIC_TYPES.SERIAL_DIFF, - title: serialDiffTitle, - subtype: parentPipelineAggHelper.subtype, - makeLabel: agg => makeNestedLabel(agg, serialDiffLabel), - params: [...parentPipelineAggHelper.params()], - getFormat: parentPipelineAggHelper.getFormat, -}); +export const getSerialDiffMetricAgg = ({ + getInternalStartServices, +}: SerialDiffMetricAggDependencies) => { + return new MetricAggType( + { + name: METRIC_TYPES.SERIAL_DIFF, + title: serialDiffTitle, + subtype: parentPipelineAggHelper.subtype, + makeLabel: agg => makeNestedLabel(agg, serialDiffLabel), + params: [...parentPipelineAggHelper.params()], + getFormat: parentPipelineAggHelper.getFormat, + }, + { + getInternalStartServices, + } + ); +}; diff --git a/src/plugins/data/public/search/aggs/metrics/sibling_pipeline.test.ts b/src/plugins/data/public/search/aggs/metrics/sibling_pipeline.test.ts index 8389ed8262ce5..ab480fe44227e 100644 --- a/src/plugins/data/public/search/aggs/metrics/sibling_pipeline.test.ts +++ b/src/plugins/data/public/search/aggs/metrics/sibling_pipeline.test.ts @@ -17,27 +17,47 @@ * under the License. */ -import { bucketSumMetricAgg } from './bucket_sum'; -import { bucketAvgMetricAgg } from './bucket_avg'; -import { bucketMinMetricAgg } from './bucket_min'; -import { bucketMaxMetricAgg } from './bucket_max'; +import { getBucketSumMetricAgg } from './bucket_sum'; +import { getBucketAvgMetricAgg } from './bucket_avg'; +import { getBucketMinMetricAgg } from './bucket_min'; +import { getBucketMaxMetricAgg } from './bucket_max'; import { AggConfigs } from '../agg_configs'; import { IMetricAggConfig, MetricAggType } from './metric_agg_type'; -import { mockDataServices, mockAggTypesRegistry } from '../test_helpers'; +import { mockAggTypesRegistry } from '../test_helpers'; +import { fieldFormatsServiceMock } from '../../../field_formats/mocks'; +import { GetInternalStartServicesFn } from '../../../types'; +import { notificationServiceMock } from '../../../../../../../src/core/public/mocks'; describe('sibling pipeline aggs', () => { - beforeEach(() => { - mockDataServices(); + const getInternalStartServices: GetInternalStartServicesFn = () => ({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), }); const typesRegistry = mockAggTypesRegistry(); const metrics = [ - { name: 'sum_bucket', title: 'Overall Sum', provider: bucketSumMetricAgg }, - { name: 'avg_bucket', title: 'Overall Average', provider: bucketAvgMetricAgg }, - { name: 'min_bucket', title: 'Overall Min', provider: bucketMinMetricAgg }, - { name: 'max_bucket', title: 'Overall Max', provider: bucketMaxMetricAgg }, + { + name: 'sum_bucket', + title: 'Overall Sum', + provider: getBucketSumMetricAgg({ getInternalStartServices }), + }, + { + name: 'avg_bucket', + title: 'Overall Average', + provider: getBucketAvgMetricAgg({ getInternalStartServices }), + }, + { + name: 'min_bucket', + title: 'Overall Min', + provider: getBucketMinMetricAgg({ getInternalStartServices }), + }, + { + name: 'max_bucket', + title: 'Overall Max', + provider: getBucketMaxMetricAgg({ getInternalStartServices }), + }, ]; metrics.forEach(metric => { diff --git a/src/plugins/data/public/search/aggs/metrics/std_deviation.test.ts b/src/plugins/data/public/search/aggs/metrics/std_deviation.test.ts index 0679831b1e6ac..6bbff3009cc11 100644 --- a/src/plugins/data/public/search/aggs/metrics/std_deviation.test.ts +++ b/src/plugins/data/public/search/aggs/metrics/std_deviation.test.ts @@ -17,13 +17,25 @@ * under the License. */ -import { IStdDevAggConfig, stdDeviationMetricAgg } from './std_deviation'; +import { + IStdDevAggConfig, + getStdDeviationMetricAgg, + StdDeviationMetricAggDependencies, +} from './std_deviation'; import { AggConfigs } from '../agg_configs'; import { mockAggTypesRegistry } from '../test_helpers'; import { METRIC_TYPES } from './metric_agg_types'; +import { fieldFormatsServiceMock } from '../../../field_formats/mocks'; +import { notificationServiceMock } from '../../../../../../../src/core/public/mocks'; describe('AggTypeMetricStandardDeviationProvider class', () => { - const typesRegistry = mockAggTypesRegistry([stdDeviationMetricAgg]); + const aggTypesDependencies: StdDeviationMetricAggDependencies = { + getInternalStartServices: () => ({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), + }), + }; + const typesRegistry = mockAggTypesRegistry([getStdDeviationMetricAgg(aggTypesDependencies)]); const getAggConfigs = (customLabel?: string) => { const field = { name: 'memory', @@ -58,7 +70,7 @@ describe('AggTypeMetricStandardDeviationProvider class', () => { it('uses the custom label if it is set', () => { const aggConfigs = getAggConfigs('custom label'); - const responseAggs: any = stdDeviationMetricAgg.getResponseAggs( + const responseAggs: any = getStdDeviationMetricAgg(aggTypesDependencies).getResponseAggs( aggConfigs.aggs[0] as IStdDevAggConfig ); @@ -72,7 +84,7 @@ describe('AggTypeMetricStandardDeviationProvider class', () => { it('uses the default labels if custom label is not set', () => { const aggConfigs = getAggConfigs(); - const responseAggs: any = stdDeviationMetricAgg.getResponseAggs( + const responseAggs: any = getStdDeviationMetricAgg(aggTypesDependencies).getResponseAggs( aggConfigs.aggs[0] as IStdDevAggConfig ); diff --git a/src/plugins/data/public/search/aggs/metrics/std_deviation.ts b/src/plugins/data/public/search/aggs/metrics/std_deviation.ts index 5e069e317e052..e972132542ceb 100644 --- a/src/plugins/data/public/search/aggs/metrics/std_deviation.ts +++ b/src/plugins/data/public/search/aggs/metrics/std_deviation.ts @@ -23,6 +23,7 @@ import { MetricAggType } from './metric_agg_type'; import { METRIC_TYPES } from './metric_agg_types'; import { getResponseAggConfigClass, IResponseAggConfig } from './lib/get_response_agg_config_class'; import { KBN_FIELD_TYPES } from '../../../../common'; +import { GetInternalStartServicesFn } from '../../../types'; interface ValProp { valProp: string[]; @@ -34,6 +35,10 @@ export interface IStdDevAggConfig extends IResponseAggConfig { valProp: () => ValProp; } +export interface StdDeviationMetricAggDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} + const responseAggConfigProps = { valProp(this: IStdDevAggConfig) { const customLabel = this.getParam('customLabel'); @@ -75,33 +80,42 @@ const responseAggConfigProps = { }, }; -export const stdDeviationMetricAgg = new MetricAggType({ - name: METRIC_TYPES.STD_DEV, - dslName: 'extended_stats', - title: i18n.translate('data.search.aggs.metrics.standardDeviationTitle', { - defaultMessage: 'Standard Deviation', - }), - makeLabel(agg) { - return i18n.translate('data.search.aggs.metrics.standardDeviationLabel', { - defaultMessage: 'Standard Deviation of {field}', - values: { field: agg.getFieldDisplayName() }, - }); - }, - params: [ +export const getStdDeviationMetricAgg = ({ + getInternalStartServices, +}: StdDeviationMetricAggDependencies) => { + return new MetricAggType( { - name: 'field', - type: 'field', - filterFieldTypes: KBN_FIELD_TYPES.NUMBER, - }, - ], + name: METRIC_TYPES.STD_DEV, + dslName: 'extended_stats', + title: i18n.translate('data.search.aggs.metrics.standardDeviationTitle', { + defaultMessage: 'Standard Deviation', + }), + makeLabel(agg) { + return i18n.translate('data.search.aggs.metrics.standardDeviationLabel', { + defaultMessage: 'Standard Deviation of {field}', + values: { field: agg.getFieldDisplayName() }, + }); + }, + params: [ + { + name: 'field', + type: 'field', + filterFieldTypes: KBN_FIELD_TYPES.NUMBER, + }, + ], - getResponseAggs(agg) { - const ValueAggConfig = getResponseAggConfigClass(agg, responseAggConfigProps); + getResponseAggs(agg) { + const ValueAggConfig = getResponseAggConfigClass(agg, responseAggConfigProps); - return [new ValueAggConfig('std_lower'), new ValueAggConfig('std_upper')]; - }, + return [new ValueAggConfig('std_lower'), new ValueAggConfig('std_upper')]; + }, - getValue(agg, bucket) { - return get(bucket[agg.parentId], agg.valProp()); - }, -}); + getValue(agg, bucket) { + return get(bucket[agg.parentId], agg.valProp()); + }, + }, + { + getInternalStartServices, + } + ); +}; diff --git a/src/plugins/data/public/search/aggs/metrics/sum.ts b/src/plugins/data/public/search/aggs/metrics/sum.ts index ffb117dda0839..545c6d6a4939e 100644 --- a/src/plugins/data/public/search/aggs/metrics/sum.ts +++ b/src/plugins/data/public/search/aggs/metrics/sum.ts @@ -21,28 +21,40 @@ import { i18n } from '@kbn/i18n'; import { MetricAggType } from './metric_agg_type'; import { METRIC_TYPES } from './metric_agg_types'; import { KBN_FIELD_TYPES } from '../../../../common'; +import { GetInternalStartServicesFn } from '../../../types'; const sumTitle = i18n.translate('data.search.aggs.metrics.sumTitle', { defaultMessage: 'Sum', }); -export const sumMetricAgg = new MetricAggType({ - name: METRIC_TYPES.SUM, - title: sumTitle, - makeLabel(aggConfig) { - return i18n.translate('data.search.aggs.metrics.sumLabel', { - defaultMessage: 'Sum of {field}', - values: { field: aggConfig.getFieldDisplayName() }, - }); - }, - isScalable() { - return true; - }, - params: [ +export interface SumMetricAggDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} + +export const getSumMetricAgg = ({ getInternalStartServices }: SumMetricAggDependencies) => { + return new MetricAggType( { - name: 'field', - type: 'field', - filterFieldTypes: KBN_FIELD_TYPES.NUMBER, + name: METRIC_TYPES.SUM, + title: sumTitle, + makeLabel(aggConfig) { + return i18n.translate('data.search.aggs.metrics.sumLabel', { + defaultMessage: 'Sum of {field}', + values: { field: aggConfig.getFieldDisplayName() }, + }); + }, + isScalable() { + return true; + }, + params: [ + { + name: 'field', + type: 'field', + filterFieldTypes: KBN_FIELD_TYPES.NUMBER, + }, + ], }, - ], -}); + { + getInternalStartServices, + } + ); +}; diff --git a/src/plugins/data/public/search/aggs/metrics/top_hit.test.ts b/src/plugins/data/public/search/aggs/metrics/top_hit.test.ts index c65a714f26247..8294ad09bae22 100644 --- a/src/plugins/data/public/search/aggs/metrics/top_hit.test.ts +++ b/src/plugins/data/public/search/aggs/metrics/top_hit.test.ts @@ -18,15 +18,23 @@ */ import { dropRight, last } from 'lodash'; -import { topHitMetricAgg } from './top_hit'; +import { getTopHitMetricAgg, TopHitMetricAggDependencies } from './top_hit'; import { AggConfigs } from '../agg_configs'; import { mockAggTypesRegistry } from '../test_helpers'; import { IMetricAggConfig } from './metric_agg_type'; import { KBN_FIELD_TYPES } from '../../../../common'; +import { fieldFormatsServiceMock } from '../../../field_formats/mocks'; +import { notificationServiceMock } from '../../../../../../../src/core/public/mocks'; describe('Top hit metric', () => { let aggDsl: Record; let aggConfig: IMetricAggConfig; + const aggTypesDependencies: TopHitMetricAggDependencies = { + getInternalStartServices: () => ({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), + }), + }; const init = ({ fieldName = 'field', @@ -36,7 +44,7 @@ describe('Top hit metric', () => { fieldType = KBN_FIELD_TYPES.NUMBER, size = 1, }: any) => { - const typesRegistry = mockAggTypesRegistry([topHitMetricAgg]); + const typesRegistry = mockAggTypesRegistry([getTopHitMetricAgg(aggTypesDependencies)]); const field = { name: fieldName, displayName: fieldName, @@ -91,7 +99,7 @@ describe('Top hit metric', () => { it('should return a label prefixed with Last if sorting in descending order', () => { init({ fieldName: 'bytes' }); - expect(topHitMetricAgg.makeLabel(aggConfig)).toEqual('Last bytes'); + expect(getTopHitMetricAgg(aggTypesDependencies).makeLabel(aggConfig)).toEqual('Last bytes'); }); it('should return a label prefixed with First if sorting in ascending order', () => { @@ -99,7 +107,7 @@ describe('Top hit metric', () => { fieldName: 'bytes', sortOrder: 'asc', }); - expect(topHitMetricAgg.makeLabel(aggConfig)).toEqual('First bytes'); + expect(getTopHitMetricAgg(aggTypesDependencies).makeLabel(aggConfig)).toEqual('First bytes'); }); it('should request the _source field', () => { @@ -140,7 +148,7 @@ describe('Top hit metric', () => { }; init({ fieldName: '@tags' }); - expect(topHitMetricAgg.getValue(aggConfig, bucket)).toBe(null); + expect(getTopHitMetricAgg(aggTypesDependencies).getValue(aggConfig, bucket)).toBe(null); }); // it('should return undefined if the field does not appear in the source', () => { @@ -159,7 +167,7 @@ describe('Top hit metric', () => { }; init({ fieldName: '@tags' }); - expect(topHitMetricAgg.getValue(aggConfig, bucket)).toBe(undefined); + expect(getTopHitMetricAgg(aggTypesDependencies).getValue(aggConfig, bucket)).toBe(undefined); }); it('should return the field value from the top hit', () => { @@ -178,7 +186,7 @@ describe('Top hit metric', () => { }; init({ fieldName: '@tags' }); - expect(topHitMetricAgg.getValue(aggConfig, bucket)).toBe('aaa'); + expect(getTopHitMetricAgg(aggTypesDependencies).getValue(aggConfig, bucket)).toBe('aaa'); }); it('should return the object if the field value is an object', () => { @@ -200,7 +208,9 @@ describe('Top hit metric', () => { init({ fieldName: '@tags' }); - expect(topHitMetricAgg.getValue(aggConfig, bucket)).toEqual({ label: 'aaa' }); + expect(getTopHitMetricAgg(aggTypesDependencies).getValue(aggConfig, bucket)).toEqual({ + label: 'aaa', + }); }); it('should return an array if the field has more than one values', () => { @@ -219,7 +229,10 @@ describe('Top hit metric', () => { }; init({ fieldName: '@tags' }); - expect(topHitMetricAgg.getValue(aggConfig, bucket)).toEqual(['aaa', 'bbb']); + expect(getTopHitMetricAgg(aggTypesDependencies).getValue(aggConfig, bucket)).toEqual([ + 'aaa', + 'bbb', + ]); }); it('should return undefined if the field is not in the source nor in the doc_values field', () => { @@ -241,7 +254,7 @@ describe('Top hit metric', () => { }; init({ fieldName: 'machine.os.raw', readFromDocValues: true }); - expect(topHitMetricAgg.getValue(aggConfig, bucket)).toBe(undefined); + expect(getTopHitMetricAgg(aggTypesDependencies).getValue(aggConfig, bucket)).toBe(undefined); }); describe('Multivalued field and first/last X docs', () => { @@ -250,7 +263,9 @@ describe('Top hit metric', () => { fieldName: 'bytes', size: 2, }); - expect(topHitMetricAgg.makeLabel(aggConfig)).toEqual('Last 2 bytes'); + expect(getTopHitMetricAgg(aggTypesDependencies).makeLabel(aggConfig)).toEqual( + 'Last 2 bytes' + ); }); it('should return a label prefixed with First X docs if sorting in ascending order', () => { @@ -259,7 +274,9 @@ describe('Top hit metric', () => { size: 2, sortOrder: 'asc', }); - expect(topHitMetricAgg.makeLabel(aggConfig)).toEqual('First 2 bytes'); + expect(getTopHitMetricAgg(aggTypesDependencies).makeLabel(aggConfig)).toEqual( + 'First 2 bytes' + ); }); [ @@ -334,7 +351,9 @@ describe('Top hit metric', () => { }; init({ fieldName: 'bytes', aggregate: agg.type }); - expect(topHitMetricAgg.getValue(aggConfig, bucket)).toEqual(agg.result); + expect(getTopHitMetricAgg(aggTypesDependencies).getValue(aggConfig, bucket)).toEqual( + agg.result + ); }); it(`should return the result of the ${agg.type} aggregation over the last X docs - ${agg.description}`, () => { @@ -358,7 +377,9 @@ describe('Top hit metric', () => { }; init({ fieldName: 'bytes', aggregate: agg.type }); - expect(topHitMetricAgg.getValue(aggConfig, bucket)).toEqual(agg.result); + expect(getTopHitMetricAgg(aggTypesDependencies).getValue(aggConfig, bucket)).toEqual( + agg.result + ); }); }); }); diff --git a/src/plugins/data/public/search/aggs/metrics/top_hit.ts b/src/plugins/data/public/search/aggs/metrics/top_hit.ts index d0c668c577e62..15da2b485aee7 100644 --- a/src/plugins/data/public/search/aggs/metrics/top_hit.ts +++ b/src/plugins/data/public/search/aggs/metrics/top_hit.ts @@ -22,6 +22,11 @@ import { i18n } from '@kbn/i18n'; import { IMetricAggConfig, MetricAggType } from './metric_agg_type'; import { METRIC_TYPES } from './metric_agg_types'; import { KBN_FIELD_TYPES } from '../../../../common'; +import { GetInternalStartServicesFn } from '../../../types'; + +export interface TopHitMetricAggDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} const isNumericFieldSelected = (agg: IMetricAggConfig) => { const field = agg.getParam('field'); @@ -29,214 +34,225 @@ const isNumericFieldSelected = (agg: IMetricAggConfig) => { return field && field.type && field.type === KBN_FIELD_TYPES.NUMBER; }; -export const topHitMetricAgg = new MetricAggType({ - name: METRIC_TYPES.TOP_HITS, - title: i18n.translate('data.search.aggs.metrics.topHitTitle', { - defaultMessage: 'Top Hit', - }), - makeLabel(aggConfig) { - const lastPrefixLabel = i18n.translate('data.search.aggs.metrics.topHit.lastPrefixLabel', { - defaultMessage: 'Last', - }); - const firstPrefixLabel = i18n.translate('data.search.aggs.metrics.topHit.firstPrefixLabel', { - defaultMessage: 'First', - }); - - let prefix = - aggConfig.getParam('sortOrder').value === 'desc' ? lastPrefixLabel : firstPrefixLabel; - - const size = aggConfig.getParam('size'); - - if (size !== 1) { - prefix += ` ${size}`; - } - - const field = aggConfig.getParam('field'); - - return `${prefix} ${field ? field.displayName : ''}`; - }, - params: [ +export const getTopHitMetricAgg = ({ getInternalStartServices }: TopHitMetricAggDependencies) => { + return new MetricAggType( { - name: 'field', - type: 'field', - onlyAggregatable: false, - filterFieldTypes: Object.values(KBN_FIELD_TYPES).filter( - type => type !== KBN_FIELD_TYPES.HISTOGRAM - ), - write(agg, output) { - const field = agg.getParam('field'); - output.params = {}; - - if (field.scripted) { - output.params.script_fields = { - [field.name]: { - script: { - source: field.script, - lang: field.lang, - }, - }, - }; - } else { - if (field.readFromDocValues) { - // always format date fields as date_time to avoid - // displaying unformatted dates like epoch_millis - // or other not-accepted momentjs formats - const format = field.type === KBN_FIELD_TYPES.DATE ? 'date_time' : 'use_field_mapping'; - output.params.docvalue_fields = [{ field: field.name, format }]; + name: METRIC_TYPES.TOP_HITS, + title: i18n.translate('data.search.aggs.metrics.topHitTitle', { + defaultMessage: 'Top Hit', + }), + makeLabel(aggConfig) { + const lastPrefixLabel = i18n.translate('data.search.aggs.metrics.topHit.lastPrefixLabel', { + defaultMessage: 'Last', + }); + const firstPrefixLabel = i18n.translate( + 'data.search.aggs.metrics.topHit.firstPrefixLabel', + { + defaultMessage: 'First', } - output.params._source = field.name === '_source' ? true : field.name; + ); + + let prefix = + aggConfig.getParam('sortOrder').value === 'desc' ? lastPrefixLabel : firstPrefixLabel; + + const size = aggConfig.getParam('size'); + + if (size !== 1) { + prefix += ` ${size}`; } + + const field = aggConfig.getParam('field'); + + return `${prefix} ${field ? field.displayName : ''}`; }, - }, - { - name: 'aggregate', - type: 'optioned', - options: [ + params: [ { - text: i18n.translate('data.search.aggs.metrics.topHit.minLabel', { - defaultMessage: 'Min', - }), - isCompatible: isNumericFieldSelected, - disabled: true, - value: 'min', - }, - { - text: i18n.translate('data.search.aggs.metrics.topHit.maxLabel', { - defaultMessage: 'Max', - }), - isCompatible: isNumericFieldSelected, - disabled: true, - value: 'max', + name: 'field', + type: 'field', + onlyAggregatable: false, + filterFieldTypes: Object.values(KBN_FIELD_TYPES).filter( + type => type !== KBN_FIELD_TYPES.HISTOGRAM + ), + write(agg, output) { + const field = agg.getParam('field'); + output.params = {}; + + if (field.scripted) { + output.params.script_fields = { + [field.name]: { + script: { + source: field.script, + lang: field.lang, + }, + }, + }; + } else { + if (field.readFromDocValues) { + // always format date fields as date_time to avoid + // displaying unformatted dates like epoch_millis + // or other not-accepted momentjs formats + const format = + field.type === KBN_FIELD_TYPES.DATE ? 'date_time' : 'use_field_mapping'; + output.params.docvalue_fields = [{ field: field.name, format }]; + } + output.params._source = field.name === '_source' ? true : field.name; + } + }, }, { - text: i18n.translate('data.search.aggs.metrics.topHit.sumLabel', { - defaultMessage: 'Sum', - }), - isCompatible: isNumericFieldSelected, - disabled: true, - value: 'sum', + name: 'aggregate', + type: 'optioned', + options: [ + { + text: i18n.translate('data.search.aggs.metrics.topHit.minLabel', { + defaultMessage: 'Min', + }), + isCompatible: isNumericFieldSelected, + disabled: true, + value: 'min', + }, + { + text: i18n.translate('data.search.aggs.metrics.topHit.maxLabel', { + defaultMessage: 'Max', + }), + isCompatible: isNumericFieldSelected, + disabled: true, + value: 'max', + }, + { + text: i18n.translate('data.search.aggs.metrics.topHit.sumLabel', { + defaultMessage: 'Sum', + }), + isCompatible: isNumericFieldSelected, + disabled: true, + value: 'sum', + }, + { + text: i18n.translate('data.search.aggs.metrics.topHit.averageLabel', { + defaultMessage: 'Average', + }), + isCompatible: isNumericFieldSelected, + disabled: true, + value: 'average', + }, + { + text: i18n.translate('data.search.aggs.metrics.topHit.concatenateLabel', { + defaultMessage: 'Concatenate', + }), + isCompatible(aggConfig: IMetricAggConfig) { + return _.get(aggConfig.params, 'field.filterFieldTypes', '*') === '*'; + }, + disabled: true, + value: 'concat', + }, + ], + write: _.noop, }, { - text: i18n.translate('data.search.aggs.metrics.topHit.averageLabel', { - defaultMessage: 'Average', - }), - isCompatible: isNumericFieldSelected, - disabled: true, - value: 'average', + name: 'size', + default: 1, }, { - text: i18n.translate('data.search.aggs.metrics.topHit.concatenateLabel', { - defaultMessage: 'Concatenate', - }), - isCompatible(aggConfig: IMetricAggConfig) { - return _.get(aggConfig.params, 'field.filterFieldTypes', '*') === '*'; + name: 'sortField', + type: 'field', + filterFieldTypes: [ + KBN_FIELD_TYPES.NUMBER, + KBN_FIELD_TYPES.DATE, + KBN_FIELD_TYPES.IP, + KBN_FIELD_TYPES.STRING, + ], + default(agg: IMetricAggConfig) { + return agg.getIndexPattern().timeFieldName; }, - disabled: true, - value: 'concat', - }, - ], - write: _.noop, - }, - { - name: 'size', - default: 1, - }, - { - name: 'sortField', - type: 'field', - filterFieldTypes: [ - KBN_FIELD_TYPES.NUMBER, - KBN_FIELD_TYPES.DATE, - KBN_FIELD_TYPES.IP, - KBN_FIELD_TYPES.STRING, - ], - default(agg: IMetricAggConfig) { - return agg.getIndexPattern().timeFieldName; - }, - write: _.noop, // prevent default write, it is handled below - }, - { - name: 'sortOrder', - type: 'optioned', - default: 'desc', - options: [ - { - text: i18n.translate('data.search.aggs.metrics.topHit.descendingLabel', { - defaultMessage: 'Descending', - }), - value: 'desc', + write: _.noop, // prevent default write, it is handled below }, { - text: i18n.translate('data.search.aggs.metrics.topHit.ascendingLabel', { - defaultMessage: 'Ascending', - }), - value: 'asc', - }, - ], - write(agg, output) { - const sortField = agg.params.sortField; - const sortOrder = agg.params.sortOrder; - - if (sortField.scripted) { - output.params.sort = [ + name: 'sortOrder', + type: 'optioned', + default: 'desc', + options: [ { - _script: { - script: { - source: sortField.script, - lang: sortField.lang, - }, - type: sortField.type, - order: sortOrder.value, - }, + text: i18n.translate('data.search.aggs.metrics.topHit.descendingLabel', { + defaultMessage: 'Descending', + }), + value: 'desc', }, - ]; - } else { - output.params.sort = [ { - [sortField.name]: { - order: sortOrder.value, - }, + text: i18n.translate('data.search.aggs.metrics.topHit.ascendingLabel', { + defaultMessage: 'Ascending', + }), + value: 'asc', }, - ]; + ], + write(agg, output) { + const sortField = agg.params.sortField; + const sortOrder = agg.params.sortOrder; + + if (sortField.scripted) { + output.params.sort = [ + { + _script: { + script: { + source: sortField.script, + lang: sortField.lang, + }, + type: sortField.type, + order: sortOrder.value, + }, + }, + ]; + } else { + output.params.sort = [ + { + [sortField.name]: { + order: sortOrder.value, + }, + }, + ]; + } + }, + }, + ], + getValue(agg, bucket) { + const hits: any[] = _.get(bucket, `${agg.id}.hits.hits`); + if (!hits || !hits.length) { + return null; } - }, - }, - ], - getValue(agg, bucket) { - const hits: any[] = _.get(bucket, `${agg.id}.hits.hits`); - if (!hits || !hits.length) { - return null; - } - const path = agg.getParam('field').name; + const path = agg.getParam('field').name; - let values = _.flatten( - hits.map(hit => - path === '_source' ? hit._source : agg.getIndexPattern().flattenHit(hit, true)[path] - ) - ); + let values = _.flatten( + hits.map(hit => + path === '_source' ? hit._source : agg.getIndexPattern().flattenHit(hit, true)[path] + ) + ); - if (values.length === 1) { - values = values[0]; - } + if (values.length === 1) { + values = values[0]; + } + + if (Array.isArray(values)) { + if (!_.compact(values).length) { + return null; + } + + const aggregate = agg.getParam('aggregate'); - if (Array.isArray(values)) { - if (!_.compact(values).length) { - return null; - } - - const aggregate = agg.getParam('aggregate'); - - switch (aggregate.value) { - case 'max': - return _.max(values); - case 'min': - return _.min(values); - case 'sum': - return _.sum(values); - case 'average': - return _.sum(values) / values.length; - } + switch (aggregate.value) { + case 'max': + return _.max(values); + case 'min': + return _.min(values); + case 'sum': + return _.sum(values); + case 'average': + return _.sum(values) / values.length; + } + } + return values; + }, + }, + { + getInternalStartServices, } - return values; - }, -}); + ); +}; diff --git a/src/plugins/data/public/search/aggs/param_types/field.test.ts b/src/plugins/data/public/search/aggs/param_types/field.test.ts index 0182471392910..ea7931130b84a 100644 --- a/src/plugins/data/public/search/aggs/param_types/field.test.ts +++ b/src/plugins/data/public/search/aggs/param_types/field.test.ts @@ -18,11 +18,20 @@ */ import { BaseParamType } from './base'; -import { FieldParamType } from './field'; +import { FieldParamType, FieldParamTypeDependencies } from './field'; import { ES_FIELD_TYPES, KBN_FIELD_TYPES } from '../../../../common'; import { IAggConfig } from '../agg_config'; +import { fieldFormatsServiceMock } from '../../../field_formats/mocks'; +import { notificationServiceMock } from '../../../../../../../src/core/public/mocks'; describe('Field', () => { + const fieldParamTypeDependencies: FieldParamTypeDependencies = { + getInternalStartServices: () => ({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), + }), + }; + const indexPattern = { id: '1234', title: 'logstash-*', @@ -52,10 +61,13 @@ describe('Field', () => { describe('constructor', () => { it('it is an instance of BaseParamType', () => { - const aggParam = new FieldParamType({ - name: 'field', - type: 'field', - }); + const aggParam = new FieldParamType( + { + name: 'field', + type: 'field', + }, + fieldParamTypeDependencies + ); expect(aggParam instanceof BaseParamType).toBeTruthy(); }); @@ -63,10 +75,13 @@ describe('Field', () => { describe('getAvailableFields', () => { it('should return only aggregatable fields by default', () => { - const aggParam = new FieldParamType({ - name: 'field', - type: 'field', - }); + const aggParam = new FieldParamType( + { + name: 'field', + type: 'field', + }, + fieldParamTypeDependencies + ); const fields = aggParam.getAvailableFields(agg); @@ -78,10 +93,13 @@ describe('Field', () => { }); it('should return all fields if onlyAggregatable is false', () => { - const aggParam = new FieldParamType({ - name: 'field', - type: 'field', - }); + const aggParam = new FieldParamType( + { + name: 'field', + type: 'field', + }, + fieldParamTypeDependencies + ); aggParam.onlyAggregatable = false; @@ -91,10 +109,13 @@ describe('Field', () => { }); it('should return all fields if filterFieldTypes was not specified', () => { - const aggParam = new FieldParamType({ - name: 'field', - type: 'field', - }); + const aggParam = new FieldParamType( + { + name: 'field', + type: 'field', + }, + fieldParamTypeDependencies + ); indexPattern.fields[1].aggregatable = true; diff --git a/src/plugins/data/public/search/aggs/param_types/field.ts b/src/plugins/data/public/search/aggs/param_types/field.ts index 34b77e14a3a71..4d67f41905c5a 100644 --- a/src/plugins/data/public/search/aggs/param_types/field.ts +++ b/src/plugins/data/public/search/aggs/param_types/field.ts @@ -24,7 +24,7 @@ import { BaseParamType } from './base'; import { propFilter } from '../filter'; import { isNestedField, KBN_FIELD_TYPES } from '../../../../common'; import { Field as IndexPatternField } from '../../../index_patterns'; -import { getNotifications } from '../../../../public/services'; +import { GetInternalStartServicesFn } from '../../../types'; const filterByType = propFilter('type'); @@ -32,13 +32,20 @@ export type FieldTypes = KBN_FIELD_TYPES | KBN_FIELD_TYPES[] | '*'; // TODO need to make a more explicit interface for this export type IFieldParamType = FieldParamType; +export interface FieldParamTypeDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} + export class FieldParamType extends BaseParamType { required = true; scriptable = true; filterFieldTypes: FieldTypes; onlyAggregatable: boolean; - constructor(config: Record) { + constructor( + config: Record, + { getInternalStartServices }: FieldParamTypeDependencies + ) { super(config); this.filterFieldTypes = config.filterFieldTypes || '*'; @@ -87,7 +94,7 @@ export class FieldParamType extends BaseParamType { // @ts-ignore const validField = this.getAvailableFields(aggConfig).find((f: any) => f.name === fieldName); if (!validField) { - getNotifications().toasts.addDanger( + getInternalStartServices().notifications.toasts.addDanger( i18n.translate( 'data.search.aggs.paramTypes.field.invalidSavedFieldParameterErrorMessage', { diff --git a/src/plugins/data/public/search/aggs/test_helpers/mock_agg_types_registry.ts b/src/plugins/data/public/search/aggs/test_helpers/mock_agg_types_registry.ts index 57d27b7da6313..2383affa2a8c5 100644 --- a/src/plugins/data/public/search/aggs/test_helpers/mock_agg_types_registry.ts +++ b/src/plugins/data/public/search/aggs/test_helpers/mock_agg_types_registry.ts @@ -18,12 +18,13 @@ */ // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { coreMock } from '../../../../../../../src/core/public/mocks'; +import { coreMock, notificationServiceMock } from '../../../../../../../src/core/public/mocks'; import { AggTypesRegistry, AggTypesRegistryStart } from '../agg_types_registry'; import { getAggTypes } from '../agg_types'; -import { BucketAggType } from '../buckets/_bucket_agg_type'; +import { BucketAggType } from '../buckets/bucket_agg_type'; import { MetricAggType } from '../metrics/metric_agg_type'; import { queryServiceMock } from '../../../query/mocks'; +import { fieldFormatsServiceMock } from '../../../field_formats/mocks'; /** * Testing utility which creates a new instance of AggTypesRegistry, @@ -55,8 +56,11 @@ export function mockAggTypesRegistry | MetricAggTyp const core = coreMock.createSetup(); const aggTypes = getAggTypes({ uiSettings: core.uiSettings, - notifications: core.notifications, query: queryServiceMock.createSetupContract(), + getInternalStartServices: () => ({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), + }), }); aggTypes.buckets.forEach(type => registrySetup.registerBucket(type)); diff --git a/src/plugins/data/public/search/search_service.ts b/src/plugins/data/public/search/search_service.ts index dc1c99f76d59a..42f31ef450d28 100644 --- a/src/plugins/data/public/search/search_service.ts +++ b/src/plugins/data/public/search/search_service.ts @@ -26,6 +26,7 @@ import { getEsClient, LegacyApiCaller } from './es_client'; import { ES_SEARCH_STRATEGY, DEFAULT_SEARCH_STRATEGY } from '../../common/search'; import { esSearchStrategyProvider } from './es_search/es_search_strategy'; import { QuerySetup } from '../query/query_service'; +import { GetInternalStartServicesFn } from '../types'; import { SearchInterceptor } from './search_interceptor'; import { getAggTypes, @@ -44,6 +45,7 @@ import { interface SearchServiceSetupDependencies { packageInfo: PackageInfo; query: QuerySetup; + getInternalStartServices: GetInternalStartServicesFn; } /** @@ -81,7 +83,7 @@ export class SearchService implements Plugin { public setup( core: CoreSetup, - { packageInfo, query }: SearchServiceSetupDependencies + { packageInfo, query, getInternalStartServices }: SearchServiceSetupDependencies ): ISearchSetup { this.esClient = getEsClient(core.injectedMetadata, core.http, packageInfo); this.registerSearchStrategyProvider(SYNC_SEARCH_STRATEGY, syncSearchStrategyProvider); @@ -91,7 +93,7 @@ export class SearchService implements Plugin { const aggTypes = getAggTypes({ query, uiSettings: core.uiSettings, - notifications: core.notifications, + getInternalStartServices, }); aggTypes.buckets.forEach(b => aggTypesSetup.registerBucket(b)); diff --git a/src/plugins/data/public/types.ts b/src/plugins/data/public/types.ts index 45160cbf30179..e24e01d241278 100644 --- a/src/plugins/data/public/types.ts +++ b/src/plugins/data/public/types.ts @@ -71,3 +71,12 @@ export interface IDataPluginServices extends Partial { storage: IStorageWrapper; data: DataPublicPluginStart; } + +/** @internal **/ +export interface InternalStartServices { + fieldFormats: FieldFormatsStart; + notifications: CoreStart['notifications']; +} + +/** @internal **/ +export type GetInternalStartServicesFn = () => InternalStartServices;