diff --git a/packages/kbn-utility-types/README.md b/packages/kbn-utility-types/README.md index 829fd21e143669..b57e98e379707e 100644 --- a/packages/kbn-utility-types/README.md +++ b/packages/kbn-utility-types/README.md @@ -18,6 +18,7 @@ type B = UnwrapPromise; // string ## Reference +- `Assign` — From `U` assign properties to `T` (just like object assign). - `Ensure` — Makes sure `T` is of type `X`. - `ObservableLike` — Minimal interface for an object resembling an `Observable`. - `PublicContract` — Returns an object with public keys only. diff --git a/packages/kbn-utility-types/index.ts b/packages/kbn-utility-types/index.ts index 808935ed4cb5b8..657d9f547de66e 100644 --- a/packages/kbn-utility-types/index.ts +++ b/packages/kbn-utility-types/index.ts @@ -18,7 +18,7 @@ */ import { PromiseType } from 'utility-types'; -export { $Values, Required, Optional, Class } from 'utility-types'; +export { $Values, Assign, Class, Optional, Required } from 'utility-types'; /** * A type that may or may not be a `Promise`. diff --git a/src/legacy/core_plugins/data/public/actions/filters/brush_event.test.ts b/src/legacy/core_plugins/data/public/actions/filters/brush_event.test.ts index 0e18c7c707fa3f..eb29530f92fee7 100644 --- a/src/legacy/core_plugins/data/public/actions/filters/brush_event.test.ts +++ b/src/legacy/core_plugins/data/public/actions/filters/brush_event.test.ts @@ -19,34 +19,14 @@ import moment from 'moment'; -jest.mock('../../search/aggs', () => ({ - AggConfigs: function AggConfigs() { - return { - createAggConfig: ({ params }: Record) => ({ - params, - getIndexPattern: () => ({ - timeFieldName: 'time', - }), - }), - }; - }, -})); - -jest.mock('../../../../../../plugins/data/public/services', () => ({ - getIndexPatterns: () => { - return { - get: async () => { - return { - id: 'logstash-*', - timeFieldName: 'time', - }; - }, - }; - }, -})); - import { onBrushEvent, BrushEvent } from './brush_event'; +import { mockDataServices } from '../../search/aggs/test_helpers'; +import { IndexPatternsContract } from '../../../../../../plugins/data/public'; +import { dataPluginMock } from '../../../../../../plugins/data/public/mocks'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { setIndexPatterns } from '../../../../../../plugins/data/public/services'; + describe('brushEvent', () => { const DAY_IN_MS = 24 * 60 * 60 * 1000; const JAN_01_2014 = 1388559600000; @@ -59,11 +39,28 @@ describe('brushEvent', () => { }, getIndexPattern: () => ({ timeFieldName: 'time', + fields: { + getByName: () => undefined, + filter: () => [], + }, }), }, ]; beforeEach(() => { + mockDataServices(); + setIndexPatterns(({ + ...dataPluginMock.createStartContract().indexPatterns, + get: async () => ({ + id: 'indexPatternId', + timeFieldName: 'time', + fields: { + getByName: () => undefined, + filter: () => [], + }, + }), + } as unknown) as IndexPatternsContract); + baseEvent = { data: { ordered: { diff --git a/src/legacy/core_plugins/data/public/index.ts b/src/legacy/core_plugins/data/public/index.ts index 8cde5d0a1fc115..8d730d18a17559 100644 --- a/src/legacy/core_plugins/data/public/index.ts +++ b/src/legacy/core_plugins/data/public/index.ts @@ -35,18 +35,18 @@ export { } from '../../../../plugins/data/public'; export { // agg_types - AggParam, - AggParamOption, - DateRangeKey, + AggParam, // only the type is used externally, only in vis editor + AggParamOption, // only the type is used externally + DateRangeKey, // only used in field formatter deserialization, which will live in data IAggConfig, IAggConfigs, IAggType, IFieldParamType, IMetricAggType, - IpRangeKey, + IpRangeKey, // only used in field formatter deserialization, which will live in data ISchemas, - OptionedParamEditorProps, - OptionedValueProp, + OptionedParamEditorProps, // only type is used externally + OptionedValueProp, // only type is used externally } from './search/types'; /** @public static code */ diff --git a/src/legacy/core_plugins/data/public/plugin.ts b/src/legacy/core_plugins/data/public/plugin.ts index e13e8e34eaebec..e2b8ca5dda78cf 100644 --- a/src/legacy/core_plugins/data/public/plugin.ts +++ b/src/legacy/core_plugins/data/public/plugin.ts @@ -36,6 +36,7 @@ import { setOverlays, // eslint-disable-next-line @kbn/eslint/no-restricted-paths } from '../../../../plugins/data/public/services'; +import { setSearchServiceShim } from './services'; import { SELECT_RANGE_ACTION, selectRangeAction } from './actions/select_range_action'; import { VALUE_CLICK_ACTION, valueClickAction } from './actions/value_click_action'; import { @@ -112,6 +113,9 @@ export class DataPlugin } public start(core: CoreStart, { data, uiActions }: DataPluginStartDependencies): DataStart { + const search = this.search.start(core); + setSearchServiceShim(search); + setUiSettings(core.uiSettings); setQueryService(data.query); setIndexPatterns(data.indexPatterns); @@ -123,7 +127,7 @@ export class DataPlugin uiActions.attachAction(VALUE_CLICK_TRIGGER, VALUE_CLICK_ACTION); return { - search: this.search.start(core), + search, }; } diff --git a/src/legacy/core_plugins/data/public/search/aggs/agg_config.test.ts b/src/legacy/core_plugins/data/public/search/aggs/agg_config.test.ts new file mode 100644 index 00000000000000..7769aa29184d3a --- /dev/null +++ b/src/legacy/core_plugins/data/public/search/aggs/agg_config.test.ts @@ -0,0 +1,497 @@ +/* + * 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 { identity } from 'lodash'; + +import { AggConfig, IAggConfig } from './agg_config'; +import { AggConfigs, CreateAggConfigParams } from './agg_configs'; +import { AggType } from './agg_types'; +import { AggTypesRegistryStart } from './agg_types_registry'; +import { mockDataServices, mockAggTypesRegistry } from './test_helpers'; +import { IndexPatternField, IndexPattern } from '../../../../../../plugins/data/public'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { stubIndexPatternWithFields } from '../../../../../../plugins/data/public/stubs'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { dataPluginMock } from '../../../../../../plugins/data/public/mocks'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { setFieldFormats } from '../../../../../../plugins/data/public/services'; + +describe('AggConfig', () => { + let indexPattern: IndexPattern; + let typesRegistry: AggTypesRegistryStart; + + beforeEach(() => { + jest.restoreAllMocks(); + mockDataServices(); + indexPattern = stubIndexPatternWithFields as IndexPattern; + typesRegistry = mockAggTypesRegistry(); + }); + + describe('#toDsl', () => { + it('calls #write()', () => { + const ac = new AggConfigs(indexPattern, [], { typesRegistry }); + const configStates = { + enabled: true, + type: 'date_histogram', + schema: 'segment', + params: {}, + }; + const aggConfig = ac.createAggConfig(configStates); + + const spy = jest.spyOn(aggConfig, 'write').mockImplementation(() => ({ params: {} })); + aggConfig.toDsl(); + expect(spy).toHaveBeenCalledTimes(1); + }); + + it('uses the type name as the agg name', () => { + const ac = new AggConfigs(indexPattern, [], { typesRegistry }); + const configStates = { + enabled: true, + type: 'date_histogram', + schema: 'segment', + params: {}, + }; + const aggConfig = ac.createAggConfig(configStates); + + jest.spyOn(aggConfig, 'write').mockImplementation(() => ({ params: {} })); + const dsl = aggConfig.toDsl(); + expect(dsl).toHaveProperty('date_histogram'); + }); + + it('uses the params from #write() output as the agg params', () => { + const ac = new AggConfigs(indexPattern, [], { typesRegistry }); + const configStates = { + enabled: true, + type: 'date_histogram', + schema: 'segment', + params: {}, + }; + const aggConfig = ac.createAggConfig(configStates); + + const football = {}; + jest.spyOn(aggConfig, 'write').mockImplementation(() => ({ params: football })); + const dsl = aggConfig.toDsl(); + expect(dsl.date_histogram).toBe(football); + }); + + it('includes subAggs from #write() output', () => { + const configStates = [ + { + enabled: true, + type: 'avg', + schema: 'metric', + params: {}, + }, + { + enabled: true, + type: 'date_histogram', + schema: 'segment', + params: {}, + }, + ]; + const ac = new AggConfigs(indexPattern, configStates, { typesRegistry }); + + const histoConfig = ac.byName('date_histogram')[0]; + const avgConfig = ac.byName('avg')[0]; + const football = {}; + + jest + .spyOn(histoConfig, 'write') + .mockImplementation(() => ({ params: {}, subAggs: [avgConfig] })); + jest.spyOn(avgConfig, 'write').mockImplementation(() => ({ params: football })); + + const dsl = histoConfig.toDsl(); + expect(dsl).toHaveProperty('aggs'); + expect(dsl.aggs).toHaveProperty(avgConfig.id); + expect(dsl.aggs[avgConfig.id]).toHaveProperty('avg'); + expect(dsl.aggs[avgConfig.id].avg).toBe(football); + }); + }); + + describe('::ensureIds', () => { + it('accepts an array of objects and assigns ids to them', () => { + const objs = [{}, {}, {}, {}]; + AggConfig.ensureIds(objs); + expect(objs[0]).toHaveProperty('id', '1'); + expect(objs[1]).toHaveProperty('id', '2'); + expect(objs[2]).toHaveProperty('id', '3'); + expect(objs[3]).toHaveProperty('id', '4'); + }); + + it('assigns ids relative to the other only item in the list', () => { + const objs = [{ id: '100' }, {}]; + AggConfig.ensureIds(objs); + expect(objs[0]).toHaveProperty('id', '100'); + expect(objs[1]).toHaveProperty('id', '101'); + }); + + it('assigns ids relative to the other items in the list', () => { + const objs = [{ id: '100' }, { id: '200' }, { id: '500' }, { id: '350' }, {}]; + AggConfig.ensureIds(objs); + expect(objs[0]).toHaveProperty('id', '100'); + expect(objs[1]).toHaveProperty('id', '200'); + expect(objs[2]).toHaveProperty('id', '500'); + expect(objs[3]).toHaveProperty('id', '350'); + expect(objs[4]).toHaveProperty('id', '501'); + }); + + it('uses ::nextId to get the starting value', () => { + jest.spyOn(AggConfig, 'nextId').mockImplementation(() => 534); + const objs = AggConfig.ensureIds([{}]); + expect(objs[0]).toHaveProperty('id', '534'); + }); + + it('only calls ::nextId once', () => { + const start = 420; + const spy = jest.spyOn(AggConfig, 'nextId').mockImplementation(() => start); + const objs = AggConfig.ensureIds([{}, {}, {}, {}, {}, {}, {}]); + + expect(spy).toHaveBeenCalledTimes(1); + objs.forEach((obj, i) => { + expect(obj).toHaveProperty('id', String(start + i)); + }); + }); + }); + + describe('::nextId', () => { + it('accepts a list of objects and picks the next id', () => { + const next = AggConfig.nextId([{ id: '100' }, { id: '500' }] as IAggConfig[]); + expect(next).toBe(501); + }); + + it('handles an empty list', () => { + const next = AggConfig.nextId([]); + expect(next).toBe(1); + }); + + it('fails when the list is not defined', () => { + expect(() => { + AggConfig.nextId((undefined as unknown) as IAggConfig[]); + }).toThrowError(); + }); + }); + + describe('#toJsonDataEquals', () => { + const testsIdentical = [ + [ + { + enabled: true, + type: 'count', + schema: 'metric', + params: { field: '@timestamp' }, + }, + ], + [ + { + enabled: true, + type: 'avg', + schema: 'metric', + params: {}, + }, + { + enabled: true, + type: 'date_histogram', + schema: 'segment', + params: {}, + }, + ], + ]; + + testsIdentical.forEach((configState, index) => { + it(`identical aggregations (${index})`, () => { + const ac1 = new AggConfigs(indexPattern, configState, { typesRegistry }); + const ac2 = new AggConfigs(indexPattern, configState, { typesRegistry }); + expect(ac1.jsonDataEquals(ac2.aggs)).toBe(true); + }); + }); + + const testsIdenticalDifferentOrder = [ + { + config1: [ + { + enabled: true, + type: 'avg', + schema: 'metric', + params: {}, + }, + { + enabled: true, + type: 'date_histogram', + schema: 'segment', + params: {}, + }, + ], + config2: [ + { + enabled: true, + schema: 'metric', + type: 'avg', + params: {}, + }, + { + enabled: true, + schema: 'segment', + type: 'date_histogram', + params: {}, + }, + ], + }, + ]; + + testsIdenticalDifferentOrder.forEach((test, index) => { + it(`identical aggregations (${index}) - init json is in different order`, () => { + const ac1 = new AggConfigs(indexPattern, test.config1, { typesRegistry }); + const ac2 = new AggConfigs(indexPattern, test.config2, { typesRegistry }); + expect(ac1.jsonDataEquals(ac2.aggs)).toBe(true); + }); + }); + + const testsDifferent = [ + { + config1: [ + { + enabled: true, + type: 'avg', + schema: 'metric', + params: {}, + }, + { + enabled: true, + type: 'date_histogram', + schema: 'segment', + params: {}, + }, + ], + config2: [ + { + enabled: true, + type: 'max', + schema: 'metric', + params: {}, + }, + { + enabled: true, + type: 'date_histogram', + schema: 'segment', + params: {}, + }, + ], + }, + { + config1: [ + { + enabled: true, + type: 'count', + schema: 'metric', + params: { field: '@timestamp' }, + }, + ], + config2: [ + { + enabled: true, + type: 'count', + schema: 'metric', + params: { field: '@timestamp' }, + }, + { + enabled: true, + type: 'date_histogram', + schema: 'segment', + params: {}, + }, + ], + }, + ]; + + testsDifferent.forEach((test, index) => { + it(`different aggregations (${index})`, () => { + const ac1 = new AggConfigs(indexPattern, test.config1, { typesRegistry }); + const ac2 = new AggConfigs(indexPattern, test.config2, { typesRegistry }); + expect(ac1.jsonDataEquals(ac2.aggs)).toBe(false); + }); + }); + }); + + describe('#toJSON', () => { + it('includes the aggs id, params, type and schema', () => { + const ac = new AggConfigs(indexPattern, [], { typesRegistry }); + const configStates = { + enabled: true, + type: 'date_histogram', + schema: 'segment', + params: {}, + }; + const aggConfig = ac.createAggConfig(configStates); + + expect(aggConfig.id).toBe('1'); + expect(typeof aggConfig.params).toBe('object'); + expect(aggConfig.type).toBeInstanceOf(AggType); + expect(aggConfig.type).toHaveProperty('name', 'date_histogram'); + expect(typeof aggConfig.schema).toBe('object'); + expect(aggConfig.schema).toHaveProperty('name', 'segment'); + + const state = aggConfig.toJSON(); + expect(state).toHaveProperty('id', '1'); + expect(typeof state.params).toBe('object'); + expect(state).toHaveProperty('type', 'date_histogram'); + expect(state).toHaveProperty('schema', 'segment'); + }); + + it('test serialization order is identical (for visual consistency)', () => { + const configStates = [ + { + enabled: true, + type: 'date_histogram', + schema: 'segment', + params: {}, + }, + ]; + const ac1 = new AggConfigs(indexPattern, configStates, { typesRegistry }); + const ac2 = new AggConfigs(indexPattern, configStates, { typesRegistry }); + + // this relies on the assumption that js-engines consistently loop over properties in insertion order. + // most likely the case, but strictly speaking not guaranteed by the JS and JSON specifications. + expect(JSON.stringify(ac1.aggs) === JSON.stringify(ac2.aggs)).toBe(true); + }); + }); + + describe('#makeLabel', () => { + let aggConfig: AggConfig; + + beforeEach(() => { + const ac = new AggConfigs(indexPattern, [], { typesRegistry }); + aggConfig = ac.createAggConfig({ type: 'count' } as CreateAggConfigParams); + }); + + it('uses the custom label if it is defined', () => { + aggConfig.params.customLabel = 'Custom label'; + const label = aggConfig.makeLabel(); + expect(label).toBe(aggConfig.params.customLabel); + }); + + it('default label should be "Count"', () => { + const label = aggConfig.makeLabel(); + expect(label).toBe('Count'); + }); + + it('default label should be "Percentage of Count" when percentageMode is set to true', () => { + const label = aggConfig.makeLabel(true); + expect(label).toBe('Percentage of Count'); + }); + + it('empty label if the type is not defined', () => { + aggConfig.type = (undefined as unknown) as AggType; + const label = aggConfig.makeLabel(); + expect(label).toBe(''); + }); + }); + + 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, + type: 'count', + schema: 'metric', + params: { field: '@timestamp' }, + }; + const aggConfig = ac.createAggConfig(configStates); + + const fieldFormatter = aggConfig.fieldFormatter(); + expect(fieldFormatter).toBeDefined(); + expect(fieldFormatter('text')).toBe('text'); + }); + }); + + // TODO: Converting these field formatter tests from browser tests to unit + // tests makes them much less helpful due to the extensive use of mocking. + // We should revisit these and rewrite them into something more useful. + describe('#fieldFormatter - no custom getFormat handler', () => { + let aggConfig: AggConfig; + + beforeEach(() => { + setFieldFormats({ + ...dataPluginMock.createStartContract().fieldFormats, + getDefaultInstance: jest.fn().mockImplementation(() => ({ + getConverterFor: (t?: string) => t || identity, + })) as any, + }); + indexPattern.fields.getByName = name => + ({ + format: { + getConverterFor: (t?: string) => t || identity, + }, + } as IndexPatternField); + + const configStates = { + enabled: true, + type: 'histogram', + schema: 'bucket', + params: { + field: { + format: { + getConverterFor: (t?: string) => t || identity, + }, + }, + }, + }; + const ac = new AggConfigs(indexPattern, [configStates], { typesRegistry }); + aggConfig = ac.createAggConfig(configStates); + }); + + it("returns the field's formatter", () => { + expect(aggConfig.fieldFormatter().toString()).toBe( + aggConfig + .getField() + .format.getConverterFor() + .toString() + ); + }); + + it('returns the string format if the field does not have a format', () => { + const agg = aggConfig; + agg.params.field = { type: 'number', format: null }; + const fieldFormatter = agg.fieldFormatter(); + expect(fieldFormatter).toBeDefined(); + expect(fieldFormatter('text')).toBe('text'); + }); + + it('returns the string format if there is no field', () => { + const agg = aggConfig; + delete agg.params.field; + const fieldFormatter = agg.fieldFormatter(); + expect(fieldFormatter).toBeDefined(); + expect(fieldFormatter('text')).toBe('text'); + }); + + it('returns the html converter if "html" is passed in', () => { + const field = indexPattern.fields.getByName('bytes'); + expect(aggConfig.fieldFormatter('html').toString()).toBe( + field!.format.getConverterFor('html').toString() + ); + }); + }); +}); diff --git a/src/legacy/core_plugins/data/public/search/aggs/agg_config.ts b/src/legacy/core_plugins/data/public/search/aggs/agg_config.ts index 2b21c5c4868a52..659bec3f702e37 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/agg_config.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/agg_config.ts @@ -17,16 +17,8 @@ * under the License. */ -/** - * @name AggConfig - * - * @description This class represents an aggregation, which is displayed in the left-hand nav of - * the Visualize app. - */ - import _ from 'lodash'; import { i18n } from '@kbn/i18n'; -import { npStart } from 'ui/new_platform'; import { IAggType } from './agg_type'; import { AggGroupNames } from './agg_groups'; import { writeParams } from './agg_params'; @@ -38,18 +30,20 @@ import { FieldFormatsContentType, KBN_FIELD_TYPES, } from '../../../../../../plugins/data/public'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { getFieldFormats } from '../../../../../../plugins/data/public/services'; export interface AggConfigOptions { - enabled: boolean; - type: string; - params: any; + type: IAggType; + enabled?: boolean; id?: string; - schema?: string; + params?: Record; + schema?: string | Schema; } const unknownSchema: Schema = { name: 'unknown', - title: 'Unknown', + title: 'Unknown', // only here for illustrative purposes hideCustomLabel: true, aggFilter: [], min: 1, @@ -65,21 +59,6 @@ const unknownSchema: Schema = { }, }; -const getTypeFromRegistry = (type: string): IAggType => { - // We need to inline require here, since we're having a cyclic dependency - // from somewhere inside agg_types back to AggConfig. - const aggTypes = require('../aggs').aggTypes; - const registeredType = - aggTypes.metrics.find((agg: IAggType) => agg.name === type) || - aggTypes.buckets.find((agg: IAggType) => agg.name === type); - - if (!registeredType) { - throw new Error('unknown type'); - } - - return registeredType; -}; - const getSchemaFromRegistry = (schemas: any, schema: string): Schema => { let registeredSchema = schemas ? schemas.byName[schema] : null; if (!registeredSchema) { @@ -90,6 +69,13 @@ const getSchemaFromRegistry = (schemas: any, schema: string): Schema => { return registeredSchema; }; +/** + * @name AggConfig + * + * @description This class represents an aggregation, which is displayed in the left-hand nav of + * the Visualize app. + */ + // TODO need to make a more explicit interface for this export type IAggConfig = AggConfig; @@ -101,9 +87,9 @@ export class AggConfig { * @param {array[object]} list - a list of objects, objects can be anything really * @return {array} - the list that was passed in */ - static ensureIds(list: AggConfig[]) { - const have: AggConfig[] = []; - const haveNot: AggConfig[] = []; + static ensureIds(list: any[]) { + const have: IAggConfig[] = []; + const haveNot: AggConfigOptions[] = []; list.forEach(function(obj) { (obj.id ? have : haveNot).push(obj); }); @@ -121,7 +107,7 @@ export class AggConfig { * * @return {array} list - a list of objects with id properties */ - static nextId(list: AggConfig[]) { + static nextId(list: IAggConfig[]) { return ( 1 + list.reduce(function(max, obj) { @@ -161,10 +147,10 @@ export class AggConfig { // set the params to the values from opts, or just to the defaults this.setParams(opts.params || {}); - // @ts-ignore - this.__type = this.__type; // @ts-ignore this.__schema = this.__schema; + // @ts-ignore + this.__type = this.__type; } /** @@ -394,7 +380,8 @@ export class AggConfig { } fieldOwnFormatter(contentType?: FieldFormatsContentType, defaultFormat?: any) { - const fieldFormatsService = npStart.plugins.data.fieldFormats; + const fieldFormatsService = getFieldFormats(); + const field = this.getField(); let format = field && field.format; if (!format) format = defaultFormat; @@ -456,8 +443,8 @@ export class AggConfig { }); } - public setType(type: string | IAggType) { - this.type = typeof type === 'string' ? getTypeFromRegistry(type) : type; + public setType(type: IAggType) { + this.type = type; } public get schema() { diff --git a/src/legacy/core_plugins/data/public/search/aggs/agg_configs.test.ts b/src/legacy/core_plugins/data/public/search/aggs/agg_configs.test.ts new file mode 100644 index 00000000000000..29f16b1e4f0bf2 --- /dev/null +++ b/src/legacy/core_plugins/data/public/search/aggs/agg_configs.test.ts @@ -0,0 +1,503 @@ +/* + * 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 { indexBy } from 'lodash'; +import { AggConfig } from './agg_config'; +import { AggConfigs } from './agg_configs'; +import { AggTypesRegistryStart } from './agg_types_registry'; +import { Schemas } from './schemas'; +import { AggGroupNames } from './agg_groups'; +import { mockDataServices, mockAggTypesRegistry } from './test_helpers'; +import { IndexPatternField, IndexPattern } from '../../../../../../plugins/data/public'; +import { + stubIndexPattern, + stubIndexPatternWithFields, + // eslint-disable-next-line @kbn/eslint/no-restricted-paths +} from '../../../../../../plugins/data/public/stubs'; + +describe('AggConfigs', () => { + let indexPattern: IndexPattern; + let typesRegistry: AggTypesRegistryStart; + + beforeEach(() => { + indexPattern = stubIndexPatternWithFields as IndexPattern; + typesRegistry = mockAggTypesRegistry(); + }); + + describe('constructor', () => { + it('handles passing just a type', () => { + const configStates = [ + { + enabled: true, + type: 'histogram', + params: {}, + }, + ]; + + const ac = new AggConfigs(indexPattern, configStates, { typesRegistry }); + expect(ac.aggs).toHaveLength(1); + }); + + it('attempts to ensure that all states have an id', () => { + const configStates = [ + { + enabled: true, + type: 'histogram', + params: {}, + }, + { + enabled: true, + type: 'date_histogram', + params: {}, + }, + { + enabled: true, + type: 'terms', + params: {}, + schema: 'split', + }, + ]; + + const spy = jest.spyOn(AggConfig, 'ensureIds'); + new AggConfigs(indexPattern, configStates, { typesRegistry }); + expect(spy).toHaveBeenCalledTimes(1); + expect(spy.mock.calls[0]).toEqual([configStates]); + spy.mockRestore(); + }); + + describe('defaults', () => { + const schemas = new Schemas([ + { + group: AggGroupNames.Metrics, + name: 'metric', + title: 'Simple', + min: 1, + max: 2, + defaults: [ + { schema: 'metric', type: 'count' }, + { schema: 'metric', type: 'avg' }, + { schema: 'metric', type: 'sum' }, + ], + }, + { + group: AggGroupNames.Buckets, + name: 'segment', + title: 'Example', + min: 0, + max: 1, + defaults: [ + { schema: 'segment', type: 'terms' }, + { schema: 'segment', type: 'filters' }, + ], + }, + ]); + + it('should only set the number of defaults defined by the max', () => { + const ac = new AggConfigs(indexPattern, [], { + schemas: schemas.all, + typesRegistry, + }); + expect(ac.bySchemaName('metric')).toHaveLength(2); + }); + + it('should set the defaults defined in the schema when none exist', () => { + const ac = new AggConfigs(indexPattern, [], { + schemas: schemas.all, + typesRegistry, + }); + expect(ac.aggs).toHaveLength(3); + }); + + it('should NOT set the defaults defined in the schema when some exist', () => { + const configStates = [ + { + enabled: true, + type: 'date_histogram', + params: {}, + schema: 'segment', + }, + ]; + const ac = new AggConfigs(indexPattern, configStates, { + schemas: schemas.all, + typesRegistry, + }); + expect(ac.aggs).toHaveLength(3); + expect(ac.bySchemaName('segment')[0].type.name).toEqual('date_histogram'); + }); + }); + }); + + describe('#createAggConfig', () => { + it('accepts a configState which is provided as an AggConfig object', () => { + const configStates = [ + { + enabled: true, + type: 'histogram', + params: {}, + }, + { + enabled: true, + type: 'date_histogram', + params: {}, + }, + ]; + + const ac = new AggConfigs(indexPattern, configStates, { typesRegistry }); + expect(ac.aggs).toHaveLength(2); + + ac.createAggConfig( + new AggConfig(ac, { + enabled: true, + type: typesRegistry.get('terms'), + params: {}, + schema: 'split', + }) + ); + expect(ac.aggs).toHaveLength(3); + }); + + it('adds new AggConfig entries to AggConfigs by default', () => { + const configStates = [ + { + enabled: true, + type: 'histogram', + params: {}, + }, + ]; + + const ac = new AggConfigs(indexPattern, configStates, { typesRegistry }); + expect(ac.aggs).toHaveLength(1); + + ac.createAggConfig({ + enabled: true, + type: 'terms', + params: {}, + schema: 'split', + }); + expect(ac.aggs).toHaveLength(2); + }); + + it('does not add an agg to AggConfigs if addToAggConfigs: false', () => { + const configStates = [ + { + enabled: true, + type: 'histogram', + params: {}, + }, + ]; + + const ac = new AggConfigs(indexPattern, configStates, { typesRegistry }); + expect(ac.aggs).toHaveLength(1); + + ac.createAggConfig( + { + enabled: true, + type: 'terms', + params: {}, + schema: 'split', + }, + { addToAggConfigs: false } + ); + expect(ac.aggs).toHaveLength(1); + }); + }); + + describe('#getRequestAggs', () => { + it('performs a stable sort, but moves metrics to the bottom', () => { + const configStates = [ + { type: 'avg', enabled: true, params: {}, schema: 'metric' }, + { type: 'terms', enabled: true, params: {}, schema: 'split' }, + { type: 'histogram', enabled: true, params: {}, schema: 'split' }, + { type: 'sum', enabled: true, params: {}, schema: 'metric' }, + { type: 'date_histogram', enabled: true, params: {}, schema: 'segment' }, + { type: 'filters', enabled: true, params: {}, schema: 'split' }, + { type: 'percentiles', enabled: true, params: {}, schema: 'metric' }, + ]; + + const ac = new AggConfigs(indexPattern, configStates, { typesRegistry }); + const sorted = ac.getRequestAggs(); + const aggs = indexBy(ac.aggs, agg => agg.type.name); + + expect(sorted.shift()).toBe(aggs.terms); + expect(sorted.shift()).toBe(aggs.histogram); + expect(sorted.shift()).toBe(aggs.date_histogram); + expect(sorted.shift()).toBe(aggs.filters); + expect(sorted.shift()).toBe(aggs.avg); + expect(sorted.shift()).toBe(aggs.sum); + expect(sorted.shift()).toBe(aggs.percentiles); + expect(sorted).toHaveLength(0); + }); + }); + + describe('#getResponseAggs', () => { + it('returns all request aggs for basic aggs', () => { + const configStates = [ + { type: 'terms', enabled: true, params: {}, schema: 'split' }, + { type: 'date_histogram', enabled: true, params: {}, schema: 'segment' }, + { type: 'count', enabled: true, params: {}, schema: 'metric' }, + ]; + + const ac = new AggConfigs(indexPattern, configStates, { typesRegistry }); + const sorted = ac.getResponseAggs(); + const aggs = indexBy(ac.aggs, agg => agg.type.name); + + expect(sorted.shift()).toBe(aggs.terms); + expect(sorted.shift()).toBe(aggs.date_histogram); + expect(sorted.shift()).toBe(aggs.count); + expect(sorted).toHaveLength(0); + }); + + it('expands aggs that have multiple responses', () => { + const configStates = [ + { type: 'terms', enabled: true, params: {}, schema: 'split' }, + { type: 'date_histogram', enabled: true, params: {}, schema: 'segment' }, + { type: 'percentiles', enabled: true, params: { percents: [1, 2, 3] }, schema: 'metric' }, + ]; + + const ac = new AggConfigs(indexPattern, configStates, { typesRegistry }); + const sorted = ac.getResponseAggs(); + const aggs = indexBy(ac.aggs, agg => agg.type.name); + + expect(sorted.shift()).toBe(aggs.terms); + expect(sorted.shift()).toBe(aggs.date_histogram); + expect(sorted.shift()!.id!).toBe(aggs.percentiles.id + '.' + 1); + expect(sorted.shift()!.id!).toBe(aggs.percentiles.id + '.' + 2); + expect(sorted.shift()!.id!).toBe(aggs.percentiles.id + '.' + 3); + expect(sorted).toHaveLength(0); + }); + }); + + describe('#toDsl', () => { + const schemas = new Schemas([ + { + group: AggGroupNames.Buckets, + name: 'segment', + }, + { + group: AggGroupNames.Buckets, + name: 'split', + }, + ]); + + beforeEach(() => { + mockDataServices(); + indexPattern = stubIndexPattern as IndexPattern; + indexPattern.fields.getByName = name => (name as unknown) as IndexPatternField; + }); + + it('uses the sorted aggs', () => { + const configStates = [{ enabled: true, type: 'avg', params: { field: 'bytes' } }]; + const ac = new AggConfigs(indexPattern, configStates, { typesRegistry }); + const spy = jest.spyOn(AggConfigs.prototype, 'getRequestAggs'); + ac.toDsl(); + expect(spy).toHaveBeenCalledTimes(1); + spy.mockRestore(); + }); + + it('calls aggConfig#toDsl() on each aggConfig and compiles the nested output', () => { + const configStates = [ + { enabled: true, type: 'date_histogram', params: {}, schema: 'segment' }, + { enabled: true, type: 'terms', params: {}, schema: 'split' }, + { enabled: true, type: 'count', params: {} }, + ]; + + const ac = new AggConfigs(indexPattern, configStates, { + typesRegistry, + schemas: schemas.all, + }); + + const aggInfos = ac.aggs.map(aggConfig => { + const football = {}; + aggConfig.toDsl = jest.fn().mockImplementation(() => football); + + return { + id: aggConfig.id, + football, + }; + }); + + (function recurse(lvl: Record): void { + const info = aggInfos.shift(); + if (!info) return; + + expect(lvl).toHaveProperty(info.id); + expect(lvl[info.id]).toBe(info.football); + + if (lvl[info.id].aggs) { + return recurse(lvl[info.id].aggs); + } + })(ac.toDsl()); + + expect(aggInfos).toHaveLength(1); + }); + + it("skips aggs that don't have a dsl representation", () => { + const configStates = [ + { + enabled: true, + type: 'date_histogram', + params: { field: '@timestamp', interval: '10s' }, + schema: 'segment', + }, + { + enabled: true, + type: 'count', + params: {}, + schema: 'metric', + }, + ]; + + const ac = new AggConfigs(indexPattern, configStates, { typesRegistry }); + const dsl = ac.toDsl(); + const histo = ac.byName('date_histogram')[0]; + const count = ac.byName('count')[0]; + + expect(dsl).toHaveProperty(histo.id); + expect(typeof dsl[histo.id]).toBe('object'); + expect(dsl[histo.id]).not.toHaveProperty('aggs'); + expect(dsl).not.toHaveProperty(count.id); + }); + + it('writes multiple metric aggregations at the same level', () => { + const configStates = [ + { + enabled: true, + type: 'date_histogram', + schema: 'segment', + params: { field: '@timestamp', interval: '10s' }, + }, + { enabled: true, type: 'avg', schema: 'metric', params: { field: 'bytes' } }, + { enabled: true, type: 'sum', schema: 'metric', params: { field: 'bytes' } }, + { enabled: true, type: 'min', schema: 'metric', params: { field: 'bytes' } }, + { enabled: true, type: 'max', schema: 'metric', params: { field: 'bytes' } }, + ]; + + const ac = new AggConfigs(indexPattern, configStates, { + typesRegistry, + schemas: schemas.all, + }); + const dsl = ac.toDsl(); + const histo = ac.byName('date_histogram')[0]; + const metrics = ac.bySchemaGroup('metrics'); + + expect(dsl).toHaveProperty(histo.id); + expect(typeof dsl[histo.id]).toBe('object'); + expect(dsl[histo.id]).toHaveProperty('aggs'); + + metrics.forEach(metric => { + expect(dsl[histo.id].aggs).toHaveProperty(metric.id); + expect(dsl[histo.id].aggs[metric.id]).not.toHaveProperty('aggs'); + }); + }); + + it('writes multiple metric aggregations at every level if the vis is hierarchical', () => { + const configStates = [ + { enabled: true, type: 'terms', schema: 'segment', params: { field: 'bytes', orderBy: 1 } }, + { enabled: true, type: 'terms', schema: 'segment', params: { field: 'bytes', orderBy: 1 } }, + { enabled: true, id: '1', type: 'avg', schema: 'metric', params: { field: 'bytes' } }, + { enabled: true, type: 'sum', schema: 'metric', params: { field: 'bytes' } }, + { enabled: true, type: 'min', schema: 'metric', params: { field: 'bytes' } }, + { enabled: true, type: 'max', schema: 'metric', params: { field: 'bytes' } }, + ]; + + const ac = new AggConfigs(indexPattern, configStates, { typesRegistry }); + const topLevelDsl = ac.toDsl(true); + const buckets = ac.bySchemaGroup('buckets'); + const metrics = ac.bySchemaGroup('metrics'); + + (function checkLevel(dsl) { + const bucket = buckets.shift(); + if (!bucket) return; + + expect(dsl).toHaveProperty(bucket.id); + + expect(typeof dsl[bucket.id]).toBe('object'); + expect(dsl[bucket.id]).toHaveProperty('aggs'); + + metrics.forEach((metric: AggConfig) => { + expect(dsl[bucket.id].aggs).toHaveProperty(metric.id); + expect(dsl[bucket.id].aggs[metric.id]).not.toHaveProperty('aggs'); + }); + + if (buckets.length) { + checkLevel(dsl[bucket.id].aggs); + } + })(topLevelDsl); + }); + + it('adds the parent aggs of nested metrics at every level if the vis is hierarchical', () => { + const configStates = [ + { + enabled: true, + id: '1', + type: 'avg_bucket', + schema: 'metric', + params: { + customBucket: { + id: '1-bucket', + type: 'date_histogram', + schema: 'bucketAgg', + params: { + field: '@timestamp', + interval: '10s', + }, + }, + customMetric: { + id: '1-metric', + type: 'count', + schema: 'metricAgg', + params: {}, + }, + }, + }, + { + enabled: true, + id: '2', + type: 'terms', + schema: 'bucket', + params: { + field: 'clientip', + }, + }, + { + enabled: true, + id: '3', + type: 'terms', + schema: 'bucket', + params: { + field: 'machine.os.raw', + }, + }, + ]; + + const ac = new AggConfigs(indexPattern, configStates, { typesRegistry }); + const topLevelDsl = ac.toDsl(true)['2']; + + expect(Object.keys(topLevelDsl.aggs)).toContain('1'); + expect(Object.keys(topLevelDsl.aggs)).toContain('1-bucket'); + expect(topLevelDsl.aggs['1'].avg_bucket).toHaveProperty('buckets_path', '1-bucket>_count'); + expect(Object.keys(topLevelDsl.aggs['3'].aggs)).toContain('1'); + expect(Object.keys(topLevelDsl.aggs['3'].aggs)).toContain('1-bucket'); + expect(topLevelDsl.aggs['3'].aggs['1'].avg_bucket).toHaveProperty( + 'buckets_path', + '1-bucket>_count' + ); + }); + }); +}); diff --git a/src/legacy/core_plugins/data/public/search/aggs/agg_configs.ts b/src/legacy/core_plugins/data/public/search/aggs/agg_configs.ts index 8e091ed5f21ae0..ab70e66b1e138c 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/agg_configs.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/agg_configs.ts @@ -17,17 +17,12 @@ * under the License. */ -/** - * @name AggConfig - * - * @extends IndexedArray - * - * @description A "data structure"-like class with methods for indexing and - * accessing instances of AggConfig. - */ - import _ from 'lodash'; +import { Assign } from '@kbn/utility-types'; + import { AggConfig, AggConfigOptions, IAggConfig } from './agg_config'; +import { IAggType } from './agg_type'; +import { AggTypesRegistryStart } from './agg_types_registry'; import { Schema } from './schemas'; import { AggGroupNames } from './agg_groups'; import { @@ -55,6 +50,24 @@ function parseParentAggs(dslLvlCursor: any, dsl: any) { } } +export interface AggConfigsOptions { + schemas?: Schemas; + typesRegistry: AggTypesRegistryStart; +} + +export type CreateAggConfigParams = Assign; + +/** + * @name AggConfigs + * + * @description A "data structure"-like class with methods for indexing and + * accessing instances of AggConfig. This should never be instantiated directly + * outside of this plugin. Rather, downstream plugins should do this via + * `createAggConfigs()` + * + * @internal + */ + // TODO need to make a more explicit interface for this export type IAggConfigs = AggConfigs; @@ -62,23 +75,31 @@ export class AggConfigs { public indexPattern: IndexPattern; public schemas: any; public timeRange?: TimeRange; + private readonly typesRegistry: AggTypesRegistryStart; aggs: IAggConfig[]; - constructor(indexPattern: IndexPattern, configStates = [] as any, schemas?: any) { + constructor( + indexPattern: IndexPattern, + configStates: CreateAggConfigParams[] = [], + opts: AggConfigsOptions + ) { + this.typesRegistry = opts.typesRegistry; + configStates = AggConfig.ensureIds(configStates); this.aggs = []; this.indexPattern = indexPattern; - this.schemas = schemas; + this.schemas = opts.schemas; configStates.forEach((params: any) => this.createAggConfig(params)); - if (schemas) { - this.initializeDefaultsFromSchemas(schemas); + if (this.schemas) { + this.initializeDefaultsFromSchemas(this.schemas); } } + // do this wherever the schemas were passed in, & pass in state defaults instead initializeDefaultsFromSchemas(schemas: Schemas) { // Set the defaults for any schema which has them. If the defaults // for some reason has more then the max only set the max number @@ -91,10 +112,11 @@ export class AggConfigs { }) .each((schema: any) => { if (!this.aggs.find((agg: AggConfig) => agg.schema && agg.schema.name === schema.name)) { + // the result here should be passable as a configState const defaults = schema.defaults.slice(0, schema.max); _.each(defaults, defaultState => { const state = _.defaults({ id: AggConfig.nextId(this.aggs) }, defaultState); - this.aggs.push(new AggConfig(this, state as AggConfigOptions)); + this.createAggConfig(state as AggConfigOptions); }); } }) @@ -124,28 +146,36 @@ export class AggConfigs { if (!enabledOnly) return true; return agg.enabled; }; - const aggConfigs = new AggConfigs( - this.indexPattern, - this.aggs.filter(filterAggs), - this.schemas - ); + + const aggConfigs = new AggConfigs(this.indexPattern, this.aggs.filter(filterAggs), { + schemas: this.schemas, + typesRegistry: this.typesRegistry, + }); + return aggConfigs; } createAggConfig = ( - params: AggConfig | AggConfigOptions, + params: CreateAggConfigParams, { addToAggConfigs = true } = {} ) => { + const { type } = params; let aggConfig; + if (params instanceof AggConfig) { aggConfig = params; params.parent = this; } else { - aggConfig = new AggConfig(this, params); + aggConfig = new AggConfig(this, { + ...params, + type: typeof type === 'string' ? this.typesRegistry.get(type) : type, + }); } + if (addToAggConfigs) { this.aggs.push(aggConfig); } + return aggConfig as T; }; @@ -166,10 +196,10 @@ export class AggConfigs { return true; } - toDsl(hierarchical: boolean = false) { + toDsl(hierarchical: boolean = false): Record { const dslTopLvl = {}; let dslLvlCursor: Record; - let nestedMetrics: Array<{ config: AggConfig; dsl: any }> | []; + let nestedMetrics: Array<{ config: AggConfig; dsl: Record }> | []; if (hierarchical) { // collect all metrics, and filter out the ones that we won't be copying diff --git a/src/legacy/core_plugins/data/public/search/aggs/agg_params.test.ts b/src/legacy/core_plugins/data/public/search/aggs/agg_params.test.ts index 30ab272537dad1..b08fcf309e9ed6 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/agg_params.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/agg_params.test.ts @@ -23,8 +23,6 @@ import { FieldParamType } from './param_types/field'; import { OptionedParamType } from './param_types/optioned'; import { AggParamType } from '../aggs/param_types/agg'; -jest.mock('ui/new_platform'); - describe('AggParams class', () => { describe('constructor args', () => { it('accepts an array of param defs', () => { diff --git a/src/legacy/core_plugins/data/public/search/aggs/agg_type.test.ts b/src/legacy/core_plugins/data/public/search/aggs/agg_type.test.ts index 6d4c2d1317f505..c78e56dd25887d 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/agg_type.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/agg_type.test.ts @@ -19,11 +19,16 @@ import { AggType, AggTypeConfig } from './agg_type'; import { IAggConfig } from './agg_config'; -import { npStart } from 'ui/new_platform'; - -jest.mock('ui/new_platform'); +import { mockDataServices } from './test_helpers'; +import { dataPluginMock } from '../../../../../../plugins/data/public/mocks'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { setFieldFormats } from '../../../../../../plugins/data/public/services'; describe('AggType Class', () => { + beforeEach(() => { + mockDataServices(); + }); + describe('constructor', () => { it("requires a valid config object as it's first param", () => { expect(() => { @@ -153,7 +158,10 @@ describe('AggType Class', () => { }); it('returns default formatter', () => { - npStart.plugins.data.fieldFormats.getDefaultInstance = jest.fn(() => 'default') as any; + setFieldFormats({ + ...dataPluginMock.createStartContract().fieldFormats, + getDefaultInstance: jest.fn(() => 'default') as any, + }); const aggType = new AggType({ name: 'name', diff --git a/src/legacy/core_plugins/data/public/search/aggs/agg_type.ts b/src/legacy/core_plugins/data/public/search/aggs/agg_type.ts index 5ccf0f65c0e921..3cd9496d3f23d0 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/agg_type.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/agg_type.ts @@ -19,7 +19,6 @@ import { constant, noop, identity } from 'lodash'; import { i18n } from '@kbn/i18n'; -import { npStart } from 'ui/new_platform'; import { initParams } from './agg_params'; import { AggConfig } from './agg_config'; @@ -32,6 +31,8 @@ import { IFieldFormat, ISearchSource, } from '../../../../../../plugins/data/public'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { getFieldFormats } from '../../../../../../plugins/data/public/services'; export interface AggTypeConfig< TAggConfig extends AggConfig = AggConfig, @@ -65,7 +66,7 @@ export interface AggTypeConfig< const getFormat = (agg: AggConfig) => { const field = agg.getField(); - const fieldFormatsService = npStart.plugins.data.fieldFormats; + const fieldFormatsService = getFieldFormats(); return field ? field.format : fieldFormatsService.getDefaultInstance(KBN_FIELD_TYPES.STRING); }; diff --git a/src/legacy/core_plugins/data/public/search/aggs/agg_types_registry.test.ts b/src/legacy/core_plugins/data/public/search/aggs/agg_types_registry.test.ts new file mode 100644 index 00000000000000..405f83e237de83 --- /dev/null +++ b/src/legacy/core_plugins/data/public/search/aggs/agg_types_registry.test.ts @@ -0,0 +1,91 @@ +/* + * 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 { + AggTypesRegistry, + AggTypesRegistrySetup, + AggTypesRegistryStart, +} from './agg_types_registry'; +import { BucketAggType } from './buckets/_bucket_agg_type'; +import { MetricAggType } from './metrics/metric_agg_type'; + +const bucketType = { name: 'terms', type: 'bucket' } as BucketAggType; +const metricType = { name: 'count', type: 'metric' } as MetricAggType; + +describe('AggTypesRegistry', () => { + let registry: AggTypesRegistry; + let setup: AggTypesRegistrySetup; + let start: AggTypesRegistryStart; + + beforeEach(() => { + registry = new AggTypesRegistry(); + setup = registry.setup(); + start = registry.start(); + }); + + it('registerBucket adds new buckets', () => { + setup.registerBucket(bucketType); + expect(start.getBuckets()).toEqual([bucketType]); + }); + + it('registerBucket throws error when registering duplicate bucket', () => { + expect(() => { + setup.registerBucket(bucketType); + setup.registerBucket(bucketType); + }).toThrow(/already been registered with name: terms/); + }); + + it('registerMetric adds new metrics', () => { + setup.registerMetric(metricType); + expect(start.getMetrics()).toEqual([metricType]); + }); + + it('registerMetric throws error when registering duplicate metric', () => { + expect(() => { + setup.registerMetric(metricType); + setup.registerMetric(metricType); + }).toThrow(/already been registered with name: count/); + }); + + it('gets either buckets or metrics by id', () => { + setup.registerBucket(bucketType); + setup.registerMetric(metricType); + expect(start.get('terms')).toEqual(bucketType); + expect(start.get('count')).toEqual(metricType); + }); + + it('getBuckets retrieves only buckets', () => { + setup.registerBucket(bucketType); + expect(start.getBuckets()).toEqual([bucketType]); + }); + + it('getMetrics retrieves only metrics', () => { + setup.registerMetric(metricType); + expect(start.getMetrics()).toEqual([metricType]); + }); + + it('getAll returns all buckets and metrics', () => { + setup.registerBucket(bucketType); + setup.registerMetric(metricType); + expect(start.getAll()).toEqual({ + buckets: [bucketType], + metrics: [metricType], + }); + }); +}); diff --git a/src/legacy/core_plugins/data/public/search/aggs/agg_types_registry.ts b/src/legacy/core_plugins/data/public/search/aggs/agg_types_registry.ts new file mode 100644 index 00000000000000..8a8746106ae587 --- /dev/null +++ b/src/legacy/core_plugins/data/public/search/aggs/agg_types_registry.ts @@ -0,0 +1,68 @@ +/* + * 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 { BucketAggType } from './buckets/_bucket_agg_type'; +import { MetricAggType } from './metrics/metric_agg_type'; + +export type AggTypesRegistrySetup = ReturnType; +export type AggTypesRegistryStart = ReturnType; + +export class AggTypesRegistry { + private readonly bucketAggs = new Map(); + private readonly metricAggs = new Map(); + + setup = () => { + return { + registerBucket: >(type: T): void => { + const { name } = type; + if (this.bucketAggs.get(name)) { + throw new Error(`Bucket agg has already been registered with name: ${name}`); + } + this.bucketAggs.set(name, type); + }, + registerMetric: >(type: T): void => { + const { name } = type; + if (this.metricAggs.get(name)) { + throw new Error(`Metric agg has already been registered with name: ${name}`); + } + this.metricAggs.set(name, type); + }, + }; + }; + + start = () => { + return { + get: (name: string) => { + return this.bucketAggs.get(name) || this.metricAggs.get(name); + }, + getBuckets: () => { + return Array.from(this.bucketAggs.values()); + }, + getMetrics: () => { + return Array.from(this.metricAggs.values()); + }, + getAll: () => { + return { + buckets: Array.from(this.bucketAggs.values()), + metrics: Array.from(this.metricAggs.values()), + }; + }, + }; + }; +} diff --git a/src/legacy/core_plugins/data/public/search/aggs/buckets/_bucket_agg_type.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/_bucket_agg_type.ts index 546d054c5af978..d6ab58d5250a8c 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/buckets/_bucket_agg_type.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/_bucket_agg_type.ts @@ -17,16 +17,16 @@ * under the License. */ -import { AggConfig } from '../agg_config'; +import { IAggConfig } from '../agg_config'; import { KBN_FIELD_TYPES } from '../../../../../../../plugins/data/public'; import { AggType, AggTypeConfig } from '../agg_type'; import { AggParamType } from '../param_types/agg'; -export interface IBucketAggConfig extends AggConfig { +export interface IBucketAggConfig extends IAggConfig { type: InstanceType; } -export interface BucketAggParam +export interface BucketAggParam extends AggParamType { scriptable?: boolean; filterFieldTypes?: KBN_FIELD_TYPES | KBN_FIELD_TYPES[] | '*'; @@ -34,12 +34,12 @@ export interface BucketAggParam const bucketType = 'buckets'; -interface BucketAggTypeConfig +interface BucketAggTypeConfig extends AggTypeConfig> { - getKey?: (bucket: any, key: any, agg: AggConfig) => any; + getKey?: (bucket: any, key: any, agg: IAggConfig) => any; } -export class BucketAggType extends AggType< +export class BucketAggType extends AggType< TBucketAggConfig, BucketAggParam > { diff --git a/src/legacy/core_plugins/data/public/search/aggs/buckets/_interval_options.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/_interval_options.ts index e196687607d198..393d3b745250f4 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/buckets/_interval_options.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/_interval_options.ts @@ -16,6 +16,7 @@ * specific language governing permissions and limitations * under the License. */ + import { i18n } from '@kbn/i18n'; import { IBucketAggConfig } from './_bucket_agg_type'; diff --git a/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/date_histogram.test.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/date_histogram.test.ts index 0d3f58c50a42e7..2b47dc384bca2f 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/date_histogram.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/date_histogram.test.ts @@ -21,14 +21,22 @@ import moment from 'moment'; import { createFilterDateHistogram } from './date_histogram'; import { intervalOptions } from '../_interval_options'; import { AggConfigs } from '../../agg_configs'; -import { IBucketDateHistogramAggConfig } from '../date_histogram'; +import { mockDataServices, mockAggTypesRegistry } from '../../test_helpers'; +import { dateHistogramBucketAgg, IBucketDateHistogramAggConfig } from '../date_histogram'; import { BUCKET_TYPES } from '../bucket_agg_types'; import { RangeFilter } from '../../../../../../../../plugins/data/public'; +// TODO: remove this once time buckets is migrated jest.mock('ui/new_platform'); describe('AggConfig Filters', () => { describe('date_histogram', () => { + beforeEach(() => { + mockDataServices(); + }); + + const typesRegistry = mockAggTypesRegistry([dateHistogramBucketAgg]); + let agg: IBucketDateHistogramAggConfig; let filter: RangeFilter; let bucketStart: any; @@ -56,7 +64,7 @@ describe('AggConfig Filters', () => { params: { field: field.name, interval, customInterval: '5d' }, }, ], - null + { typesRegistry } ); const bucketKey = 1422579600000; diff --git a/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/date_range.test.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/date_range.test.ts index 41e806668337e2..c594c7718e58bb 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/date_range.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/date_range.test.ts @@ -18,16 +18,17 @@ */ import moment from 'moment'; +import { dateRangeBucketAgg } from '../date_range'; import { createFilterDateRange } from './date_range'; import { fieldFormats, FieldFormatsGetConfigFn } from '../../../../../../../../plugins/data/public'; import { AggConfigs } from '../../agg_configs'; +import { mockAggTypesRegistry } from '../../test_helpers'; import { BUCKET_TYPES } from '../bucket_agg_types'; import { IBucketAggConfig } from '../_bucket_agg_type'; -jest.mock('ui/new_platform'); - describe('AggConfig Filters', () => { describe('Date range', () => { + const typesRegistry = mockAggTypesRegistry([dateRangeBucketAgg]); const getConfig = (() => {}) as FieldFormatsGetConfigFn; const getAggConfigs = () => { const field = { @@ -55,7 +56,7 @@ describe('AggConfig Filters', () => { }, }, ], - null + { typesRegistry } ); }; diff --git a/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/filters.test.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/filters.test.ts index 34cf996826865f..3b9c771e0f15f9 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/filters.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/filters.test.ts @@ -16,14 +16,21 @@ * specific language governing permissions and limitations * under the License. */ + +import { filtersBucketAgg } from '../filters'; import { createFilterFilters } from './filters'; import { AggConfigs } from '../../agg_configs'; +import { mockDataServices, mockAggTypesRegistry } from '../../test_helpers'; import { IBucketAggConfig } from '../_bucket_agg_type'; -jest.mock('ui/new_platform'); - describe('AggConfig Filters', () => { describe('filters', () => { + beforeEach(() => { + mockDataServices(); + }); + + const typesRegistry = mockAggTypesRegistry([filtersBucketAgg]); + const getAggConfigs = () => { const field = { name: 'bytes', @@ -52,7 +59,7 @@ describe('AggConfig Filters', () => { }, }, ], - null + { typesRegistry } ); }; it('should return a filters filter', () => { diff --git a/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/histogram.test.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/histogram.test.ts index 9f845847df5d9d..b046c802c58c15 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/histogram.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/histogram.test.ts @@ -16,16 +16,22 @@ * specific language governing permissions and limitations * under the License. */ + import { createFilterHistogram } from './histogram'; 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 { fieldFormats, FieldFormatsGetConfigFn } from '../../../../../../../../plugins/data/public'; -jest.mock('ui/new_platform'); - describe('AggConfig Filters', () => { describe('histogram', () => { + beforeEach(() => { + mockDataServices(); + }); + + const typesRegistry = mockAggTypesRegistry(); + const getConfig = (() => {}) as FieldFormatsGetConfigFn; const getAggConfigs = () => { const field = { @@ -55,7 +61,7 @@ describe('AggConfig Filters', () => { }, }, ], - null + { typesRegistry } ); }; diff --git a/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/ip_range.test.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/ip_range.test.ts index e92ba5cb2852a1..7572c48390dc25 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/ip_range.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/ip_range.test.ts @@ -17,17 +17,18 @@ * under the License. */ +import { ipRangeBucketAgg } from '../ip_range'; import { createFilterIpRange } from './ip_range'; -import { AggConfigs } from '../../agg_configs'; +import { AggConfigs, CreateAggConfigParams } from '../../agg_configs'; +import { mockAggTypesRegistry } from '../../test_helpers'; import { fieldFormats } from '../../../../../../../../plugins/data/public'; import { BUCKET_TYPES } from '../bucket_agg_types'; import { IBucketAggConfig } from '../_bucket_agg_type'; -jest.mock('ui/new_platform'); - describe('AggConfig Filters', () => { describe('IP range', () => { - const getAggConfigs = (aggs: Array>) => { + const typesRegistry = mockAggTypesRegistry([ipRangeBucketAgg]); + const getAggConfigs = (aggs: CreateAggConfigParams[]) => { const field = { name: 'ip', format: fieldFormats.IpFormat, @@ -42,7 +43,7 @@ describe('AggConfig Filters', () => { }, } as any; - return new AggConfigs(indexPattern, aggs, null); + return new AggConfigs(indexPattern, aggs, { typesRegistry }); }; it('should return a range filter for ip_range agg', () => { diff --git a/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/range.test.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/range.test.ts index 33344ca0a34845..324d4252908324 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/range.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/range.test.ts @@ -17,16 +17,22 @@ * under the License. */ +import { rangeBucketAgg } from '../range'; import { createFilterRange } from './range'; import { fieldFormats, FieldFormatsGetConfigFn } from '../../../../../../../../plugins/data/public'; import { AggConfigs } from '../../agg_configs'; +import { mockDataServices, mockAggTypesRegistry } from '../../test_helpers'; import { BUCKET_TYPES } from '../bucket_agg_types'; import { IBucketAggConfig } from '../_bucket_agg_type'; -jest.mock('ui/new_platform'); - describe('AggConfig Filters', () => { describe('range', () => { + beforeEach(() => { + mockDataServices(); + }); + + const typesRegistry = mockAggTypesRegistry([rangeBucketAgg]); + const getConfig = (() => {}) as FieldFormatsGetConfigFn; const getAggConfigs = () => { const field = { @@ -56,7 +62,7 @@ describe('AggConfig Filters', () => { }, }, ], - null + { typesRegistry } ); }; diff --git a/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/terms.test.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/terms.test.ts index 7c6e769437ca1d..6db6eb11a5f527 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/terms.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/terms.test.ts @@ -17,17 +17,18 @@ * under the License. */ +import { termsBucketAgg } from '../terms'; import { createFilterTerms } from './terms'; -import { AggConfigs } from '../../agg_configs'; +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 { Filter, ExistsFilter } from '../../../../../../../../plugins/data/public'; -jest.mock('ui/new_platform'); - describe('AggConfig Filters', () => { describe('terms', () => { - const getAggConfigs = (aggs: Array>) => { + const typesRegistry = mockAggTypesRegistry([termsBucketAgg]); + const getAggConfigs = (aggs: CreateAggConfigParams[]) => { const indexPattern = { id: '1234', title: 'logstash-*', @@ -42,7 +43,7 @@ describe('AggConfig Filters', () => { indexPattern, }; - return new AggConfigs(indexPattern, aggs, null); + return new AggConfigs(indexPattern, aggs, { typesRegistry }); }; it('should return a match_phrase filter for terms', () => { diff --git a/src/legacy/core_plugins/data/public/search/aggs/buckets/date_histogram.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/date_histogram.ts index dc0f9baa6d0cc7..a5368135728d49 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/buckets/date_histogram.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/date_histogram.ts @@ -21,8 +21,7 @@ import _ from 'lodash'; import moment from 'moment-timezone'; import { i18n } from '@kbn/i18n'; -import { npStart } from 'ui/new_platform'; -import { timefilter } from 'ui/timefilter'; +// TODO need to move TimeBuckets import { TimeBuckets } from 'ui/time_buckets'; import { BucketAggType, IBucketAggConfig } from './_bucket_agg_type'; import { BUCKET_TYPES } from './bucket_agg_types'; @@ -33,6 +32,8 @@ import { writeParams } from '../agg_params'; import { isMetricAggType } from '../metrics/metric_agg_type'; import { KBN_FIELD_TYPES } from '../../../../../../../plugins/data/public'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { getQueryService, getUiSettings } from '../../../../../../../plugins/data/public/services'; const detectedTimezone = moment.tz.guess(); const tzOffset = moment().format('Z'); @@ -40,6 +41,7 @@ const tzOffset = moment().format('Z'); const getInterval = (agg: IBucketAggConfig): string => _.get(agg, ['params', 'interval']); export const setBounds = (agg: IBucketDateHistogramAggConfig, force?: boolean) => { + const { timefilter } = getQueryService().timefilter; if (agg.buckets._alreadySet && !force) return; agg.buckets._alreadySet = true; const bounds = agg.params.timeRange ? timefilter.calculateBounds(agg.params.timeRange) : null; @@ -221,7 +223,7 @@ export const dateHistogramBucketAgg = new BucketAggType { + beforeEach(() => { + mockDataServices(); + }); + + const typesRegistry = mockAggTypesRegistry([dateRangeBucketAgg]); + const getAggConfigs = (params: Record = {}, hasIncludeTypeMeta: boolean = true) => { const field = { name: 'bytes', @@ -58,7 +67,7 @@ describe('date_range params', () => { params, }, ], - null + { typesRegistry } ); }; @@ -95,7 +104,11 @@ describe('date_range params', () => { }); it('should use the Kibana time_zone if no parameter specified', () => { - npStart.core.uiSettings.get = jest.fn(() => 'kibanaTimeZone' as any); + const core = coreMock.createStart(); + setUiSettings({ + ...core.uiSettings, + get: () => 'kibanaTimeZone' as any, + }); const aggConfigs = getAggConfigs( { @@ -106,6 +119,8 @@ describe('date_range params', () => { const dateRange = aggConfigs.aggs[0]; const params = dateRange.toDsl()[BUCKET_TYPES.DATE_RANGE]; + setUiSettings(core.uiSettings); // clean up + expect(params.time_zone).toBe('kibanaTimeZone'); }); }); diff --git a/src/legacy/core_plugins/data/public/search/aggs/buckets/date_range.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/date_range.ts index 1dc24ca80035c0..933cdd0577f8da 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/buckets/date_range.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/date_range.ts @@ -16,18 +16,20 @@ * specific language governing permissions and limitations * under the License. */ + import { get } from 'lodash'; import moment from 'moment-timezone'; import { i18n } from '@kbn/i18n'; -import { npStart } from 'ui/new_platform'; -import { convertDateRangeToString, DateRangeKey } from './lib/date_range'; import { BUCKET_TYPES } from './bucket_agg_types'; import { BucketAggType, IBucketAggConfig } from './_bucket_agg_type'; import { createFilterDateRange } from './create_filter/date_range'; import { KBN_FIELD_TYPES, fieldFormats } from '../../../../../../../plugins/data/public'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { getFieldFormats, getUiSettings } from '../../../../../../../plugins/data/public/services'; -export { convertDateRangeToString, DateRangeKey }; +import { convertDateRangeToString, DateRangeKey } from './lib/date_range'; +export { convertDateRangeToString, DateRangeKey }; // for BWC const dateRangeTitle = i18n.translate('data.search.aggs.buckets.dateRangeTitle', { defaultMessage: 'Date Range', @@ -41,7 +43,7 @@ export const dateRangeBucketAgg = new BucketAggType({ return { from, to }; }, getFormat(agg) { - const fieldFormatsService = npStart.plugins.data.fieldFormats; + const fieldFormatsService = getFieldFormats(); const formatter = agg.fieldOwnFormatter( fieldFormats.TEXT_CONTEXT_TYPE, @@ -92,7 +94,7 @@ export const dateRangeBucketAgg = new BucketAggType({ ]); } if (!tz) { - const config = npStart.core.uiSettings; + const config = getUiSettings(); const detectedTimezone = moment.tz.guess(); const tzOffset = moment().format('Z'); const isDefaultTimezone = config.isDefault('dateFormat:tz'); diff --git a/src/legacy/core_plugins/data/public/search/aggs/buckets/filter.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/filter.ts index b52e2d6cfd4df4..80efc0cf92071d 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/buckets/filter.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/filter.ts @@ -16,6 +16,7 @@ * specific language governing permissions and limitations * under the License. */ + import { i18n } from '@kbn/i18n'; import { BucketAggType } from './_bucket_agg_type'; import { BUCKET_TYPES } from './bucket_agg_types'; diff --git a/src/legacy/core_plugins/data/public/search/aggs/buckets/filters.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/filters.ts index 6eaf788b83c04a..2852f3e4bdf464 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/buckets/filters.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/filters.ts @@ -18,19 +18,21 @@ */ import _ from 'lodash'; -import angular from 'angular'; - import { i18n } from '@kbn/i18n'; import chrome from 'ui/chrome'; + import { createFilterFilters } from './create_filter/filters'; +import { toAngularJSON } from '../utils'; import { BucketAggType } from './_bucket_agg_type'; +import { BUCKET_TYPES } from './bucket_agg_types'; import { Storage } from '../../../../../../../plugins/kibana_utils/public'; + import { getQueryLog, esQuery, Query } from '../../../../../../../plugins/data/public'; -import { BUCKET_TYPES } from './bucket_agg_types'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { getUiSettings } from '../../../../../../../plugins/data/public/services'; const config = chrome.getUiSettingsClient(); -const storage = new Storage(window.localStorage); const filtersTitle = i18n.translate('data.search.aggs.buckets.filtersTitle', { defaultMessage: 'Filters', @@ -52,15 +54,17 @@ export const filtersBucketAgg = new BucketAggType({ params: [ { name: 'filters', + // TODO need to get rid of reference to `config` below default: [{ input: { query: '', language: config.get('search:queryLanguage') }, label: '' }], write(aggConfig, output) { + const uiSettings = getUiSettings(); const inFilters: FilterValue[] = aggConfig.params.filters; if (!_.size(inFilters)) return; inFilters.forEach(filter => { const persistedLog = getQueryLog( - config, - storage, + uiSettings, + new Storage(window.localStorage), 'vis_default_editor', filter.input.language ); @@ -77,7 +81,13 @@ export const filtersBucketAgg = new BucketAggType({ return; } - const query = esQuery.buildEsQuery(aggConfig.getIndexPattern(), [input], [], config); + const esQueryConfigs = esQuery.getEsQueryConfig(uiSettings); + const query = esQuery.buildEsQuery( + aggConfig.getIndexPattern(), + [input], + [], + esQueryConfigs + ); if (!query) { console.log('malformed filter agg params, missing "query" on input'); // eslint-disable-line no-console @@ -90,7 +100,7 @@ export const filtersBucketAgg = new BucketAggType({ matchAllLabel || (typeof filter.input.query === 'string' ? filter.input.query - : angular.toJson(filter.input.query)); + : toAngularJSON(filter.input.query)); filters[label] = { query }; }, {} diff --git a/src/legacy/core_plugins/data/public/search/aggs/buckets/geo_hash.test.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/geo_hash.test.ts index f0ad5954764867..09dd03c759155e 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/buckets/geo_hash.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/geo_hash.test.ts @@ -19,12 +19,13 @@ import { geoHashBucketAgg } 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'; -jest.mock('ui/new_platform'); - describe('Geohash Agg', () => { + // const typesRegistry = mockAggTypesRegistry([geoHashBucketAgg]); + const typesRegistry = mockAggTypesRegistry(); const getAggConfigs = (params?: Record) => { const indexPattern = { id: '1234', @@ -62,7 +63,7 @@ describe('Geohash Agg', () => { }, }, ], - null + { typesRegistry } ); }; diff --git a/src/legacy/core_plugins/data/public/search/aggs/buckets/geo_tile.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/geo_tile.ts index 57e8f6e8c5ded4..9142a30338163c 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/buckets/geo_tile.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/geo_tile.ts @@ -19,7 +19,6 @@ import { i18n } from '@kbn/i18n'; import { noop } from 'lodash'; -import { AggConfigOptions } from '../agg_config'; import { BucketAggType } from './_bucket_agg_type'; import { BUCKET_TYPES } from './bucket_agg_types'; @@ -57,7 +56,7 @@ export const geoTileBucketAgg = new BucketAggType({ aggs.push(agg); if (useGeocentroid) { - const aggConfig: AggConfigOptions = { + const aggConfig = { type: METRIC_TYPES.GEO_CENTROID, enabled: true, params: { diff --git a/src/legacy/core_plugins/data/public/search/aggs/buckets/histogram.test.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/histogram.test.ts index 4e89d7db1ff647..11dc8e42fd6538 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/buckets/histogram.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/histogram.test.ts @@ -17,16 +17,23 @@ * under the License. */ -import { npStart } from 'ui/new_platform'; -import { AggConfigs } from '../index'; +import { AggConfigs } from '../agg_configs'; +import { mockDataServices, mockAggTypesRegistry } from '../test_helpers'; import { BUCKET_TYPES } from './bucket_agg_types'; import { IBucketHistogramAggConfig, histogramBucketAgg, AutoBounds } from './histogram'; import { BucketAggType } from './_bucket_agg_type'; - -jest.mock('ui/new_platform'); +import { coreMock } from '../../../../../../../../src/core/public/mocks'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { setUiSettings } from '../../../../../../../plugins/data/public/services'; describe('Histogram Agg', () => { - const getAggConfigs = (params: Record = {}) => { + beforeEach(() => { + mockDataServices(); + }); + + const typesRegistry = mockAggTypesRegistry([histogramBucketAgg]); + + const getAggConfigs = (params: Record) => { const indexPattern = { id: '1234', title: 'logstash-*', @@ -45,16 +52,13 @@ describe('Histogram Agg', () => { indexPattern, [ { - field: { - name: 'field', - }, id: 'test', type: BUCKET_TYPES.HISTOGRAM, schema: 'segment', params, }, ], - null + { typesRegistry } ); }; @@ -158,10 +162,15 @@ describe('Histogram Agg', () => { aggConfig.setAutoBounds(autoBounds); } - // mock histogram:maxBars value; - npStart.core.uiSettings.get = jest.fn(() => maxBars as any); + const core = coreMock.createStart(); + setUiSettings({ + ...core.uiSettings, + get: () => maxBars as any, + }); - return aggConfig.write(aggConfigs).params; + const interval = aggConfig.write(aggConfigs).params; + setUiSettings(core.uiSettings); // clean up + return interval; }; it('will respect the histogram:maxBars setting', () => { diff --git a/src/legacy/core_plugins/data/public/search/aggs/buckets/histogram.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/histogram.ts index f7e9ef45961e04..70df2f230db094 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/buckets/histogram.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/histogram.ts @@ -19,13 +19,13 @@ import _ from 'lodash'; import { i18n } from '@kbn/i18n'; -import { toastNotifications } from 'ui/notify'; -import { npStart } from 'ui/new_platform'; import { BucketAggType, IBucketAggConfig } from './_bucket_agg_type'; import { createFilterHistogram } from './create_filter/histogram'; -import { KBN_FIELD_TYPES } from '../../../../../../../plugins/data/public'; import { BUCKET_TYPES } from './bucket_agg_types'; +import { KBN_FIELD_TYPES } from '../../../../../../../plugins/data/public'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { getNotifications, getUiSettings } from '../../../../../../../plugins/data/public/services'; export interface AutoBounds { min: number; @@ -37,8 +37,6 @@ export interface IBucketHistogramAggConfig extends IBucketAggConfig { getAutoBounds: () => AutoBounds; } -const getUIConfig = () => npStart.core.uiSettings; - export const histogramBucketAgg = new BucketAggType({ name: BUCKET_TYPES.HISTOGRAM, title: i18n.translate('data.search.aggs.buckets.histogramTitle', { @@ -116,7 +114,7 @@ export const histogramBucketAgg = new BucketAggType({ }) .catch((e: Error) => { if (e.name === 'AbortError') return; - toastNotifications.addWarning( + getNotifications().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.', @@ -136,7 +134,7 @@ export const histogramBucketAgg = new BucketAggType({ const range = autoBounds.max - autoBounds.min; const bars = range / interval; - const config = getUIConfig(); + const config = getUiSettings(); if (bars > config.get('histogram:maxBars')) { const minInterval = range / config.get('histogram:maxBars'); diff --git a/src/legacy/core_plugins/data/public/search/aggs/buckets/ip_range.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/ip_range.ts index 91bdf53e7f8091..3fb464d8fa7a8b 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/buckets/ip_range.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/ip_range.ts @@ -19,15 +19,17 @@ import { noop, map, omit, isNull } from 'lodash'; import { i18n } from '@kbn/i18n'; -import { npStart } from 'ui/new_platform'; -import { IpRangeKey, convertIPRangeToString } from './lib/ip_range'; import { BucketAggType } from './_bucket_agg_type'; import { BUCKET_TYPES } from './bucket_agg_types'; -// @ts-ignore import { createFilterIpRange } from './create_filter/ip_range'; import { KBN_FIELD_TYPES, fieldFormats } from '../../../../../../../plugins/data/public'; -export { IpRangeKey, convertIPRangeToString }; + +import { IpRangeKey, convertIPRangeToString } from './lib/ip_range'; +export { IpRangeKey, convertIPRangeToString }; // for BWC + +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { getFieldFormats } from '../../../../../../../plugins/data/public/services'; const ipRangeTitle = i18n.translate('data.search.aggs.buckets.ipRangeTitle', { defaultMessage: 'IPv4 Range', @@ -44,7 +46,7 @@ export const ipRangeBucketAgg = new BucketAggType({ return { type: 'range', from: bucket.from, to: bucket.to }; }, getFormat(agg) { - const fieldFormatsService = npStart.plugins.data.fieldFormats; + const fieldFormatsService = getFieldFormats(); const formatter = agg.fieldOwnFormatter( fieldFormats.TEXT_CONTEXT_TYPE, fieldFormatsService.getDefaultInstance(KBN_FIELD_TYPES.IP) diff --git a/src/legacy/core_plugins/data/public/search/aggs/buckets/migrate_include_exclude_format.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/migrate_include_exclude_format.ts index 77e84e044de55a..d94477b588f8d1 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/buckets/migrate_include_exclude_format.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/migrate_include_exclude_format.ts @@ -19,10 +19,10 @@ import { isString, isObject } from 'lodash'; import { IBucketAggConfig, BucketAggType, BucketAggParam } from './_bucket_agg_type'; -import { AggConfig } from '../agg_config'; +import { IAggConfig } from '../agg_config'; export const isType = (type: string) => { - return (agg: AggConfig): boolean => { + return (agg: IAggConfig): boolean => { const field = agg.params.field; return field && field.type === type; diff --git a/src/legacy/core_plugins/data/public/search/aggs/buckets/range.test.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/range.test.ts index b1b0c4bc30a58e..096b19fe7de66d 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/buckets/range.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/range.test.ts @@ -17,12 +17,12 @@ * under the License. */ +import { rangeBucketAgg } from './range'; import { AggConfigs } from '../agg_configs'; +import { mockDataServices, mockAggTypesRegistry } from '../test_helpers'; import { BUCKET_TYPES } from './bucket_agg_types'; import { FieldFormatsGetConfigFn, fieldFormats } from '../../../../../../../plugins/data/public'; -jest.mock('ui/new_platform'); - const buckets = [ { to: 1024, @@ -44,6 +44,12 @@ const buckets = [ ]; describe('Range Agg', () => { + beforeEach(() => { + mockDataServices(); + }); + + const typesRegistry = mockAggTypesRegistry([rangeBucketAgg]); + const getConfig = (() => {}) as FieldFormatsGetConfigFn; const getAggConfigs = () => { const field = { @@ -80,7 +86,7 @@ describe('Range Agg', () => { }, }, ], - null + { typesRegistry } ); }; diff --git a/src/legacy/core_plugins/data/public/search/aggs/buckets/significant_terms.test.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/significant_terms.test.ts index 37b829bfc20fb2..cee3ed506c29c4 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/buckets/significant_terms.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/significant_terms.test.ts @@ -17,17 +17,16 @@ * under the License. */ -import { AggConfigs } from '../index'; -import { IAggConfigs } from '../types'; +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'; -jest.mock('ui/new_platform'); - describe('Significant Terms Agg', () => { describe('order agg editor UI', () => { describe('convert include/exclude from old format', () => { + const typesRegistry = mockAggTypesRegistry([significantTermsBucketAgg]); const getAggConfigs = (params: Record = {}) => { const indexPattern = { id: '1234', @@ -53,7 +52,7 @@ describe('Significant Terms Agg', () => { params, }, ], - null + { typesRegistry } ); }; diff --git a/src/legacy/core_plugins/data/public/search/aggs/buckets/terms.test.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/terms.test.ts index 24ac332ae4d55c..9a4f28afd3edf2 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/buckets/terms.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/terms.test.ts @@ -17,13 +17,13 @@ * under the License. */ -import { AggConfigs } from '../index'; +import { AggConfigs } from '../agg_configs'; +import { mockAggTypesRegistry } from '../test_helpers'; import { BUCKET_TYPES } from './bucket_agg_types'; -jest.mock('ui/new_platform'); - describe('Terms Agg', () => { describe('order agg editor UI', () => { + const typesRegistry = mockAggTypesRegistry(); const getAggConfigs = (params: Record = {}) => { const indexPattern = { id: '1234', @@ -48,7 +48,7 @@ describe('Terms Agg', () => { type: BUCKET_TYPES.TERMS, }, ], - null + { typesRegistry } ); }; diff --git a/src/legacy/core_plugins/data/public/search/aggs/filter/agg_type_filters.test.ts b/src/legacy/core_plugins/data/public/search/aggs/filter/agg_type_filters.test.ts index cc1288d339692f..0de1c31d02f961 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/filter/agg_type_filters.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/filter/agg_type_filters.test.ts @@ -19,13 +19,12 @@ import { IndexPattern } from '../../../../../../../plugins/data/public'; import { AggTypeFilters } from './agg_type_filters'; -import { AggConfig } from '..'; -import { IAggType } from '../types'; +import { IAggConfig, IAggType } from '../types'; describe('AggTypeFilters', () => { let registry: AggTypeFilters; const indexPattern = ({ id: '1234', fields: [], title: 'foo' } as unknown) as IndexPattern; - const aggConfig = {} as AggConfig; + const aggConfig = {} as IAggConfig; beforeEach(() => { registry = new AggTypeFilters(); diff --git a/src/legacy/core_plugins/data/public/search/aggs/filter/agg_type_filters.ts b/src/legacy/core_plugins/data/public/search/aggs/filter/agg_type_filters.ts index d3b38ce041d7ee..13a4cc0856b090 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/filter/agg_type_filters.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/filter/agg_type_filters.ts @@ -16,6 +16,7 @@ * specific language governing permissions and limitations * under the License. */ + import { IndexPattern } from 'src/plugins/data/public'; import { IAggConfig, IAggType } from '../types'; diff --git a/src/legacy/core_plugins/data/public/search/aggs/filter/prop_filter.test.ts b/src/legacy/core_plugins/data/public/search/aggs/filter/prop_filter.test.ts index 431e1161e0dbdf..32cda7b950e93d 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/filter/prop_filter.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/filter/prop_filter.test.ts @@ -17,7 +17,6 @@ * under the License. */ -import expect from '@kbn/expect'; import { propFilter } from './prop_filter'; describe('prop filter', () => { @@ -47,48 +46,48 @@ describe('prop filter', () => { it('returns list when no filters are provided', () => { const objects = getObjects('table', 'table', 'pie'); - expect(nameFilter(objects)).to.eql(objects); + expect(nameFilter(objects)).toEqual(objects); }); it('returns list when empty list of filters is provided', () => { const objects = getObjects('table', 'table', 'pie'); - expect(nameFilter(objects, [])).to.eql(objects); + expect(nameFilter(objects, [])).toEqual(objects); }); it('should keep only the tables', () => { const objects = getObjects('table', 'table', 'pie'); - expect(nameFilter(objects, 'table')).to.eql(getObjects('table', 'table')); + expect(nameFilter(objects, 'table')).toEqual(getObjects('table', 'table')); }); it('should support comma-separated values', () => { const objects = getObjects('table', 'line', 'pie'); - expect(nameFilter(objects, 'table,line')).to.eql(getObjects('table', 'line')); + expect(nameFilter(objects, 'table,line')).toEqual(getObjects('table', 'line')); }); it('should support an array of values', () => { const objects = getObjects('table', 'line', 'pie'); - expect(nameFilter(objects, ['table', 'line'])).to.eql(getObjects('table', 'line')); + expect(nameFilter(objects, ['table', 'line'])).toEqual(getObjects('table', 'line')); }); it('should return all objects', () => { const objects = getObjects('table', 'line', 'pie'); - expect(nameFilter(objects, '*')).to.eql(objects); + expect(nameFilter(objects, '*')).toEqual(objects); }); it('should allow negation', () => { const objects = getObjects('table', 'line', 'pie'); - expect(nameFilter(objects, ['!line'])).to.eql(getObjects('table', 'pie')); + expect(nameFilter(objects, ['!line'])).toEqual(getObjects('table', 'pie')); }); it('should support a function for specifying what should be kept', () => { const objects = getObjects('table', 'line', 'pie'); const line = (value: string) => value === 'line'; - expect(nameFilter(objects, line)).to.eql(getObjects('line')); + expect(nameFilter(objects, line)).toEqual(getObjects('line')); }); it('gracefully handles a filter function with zero arity', () => { const objects = getObjects('table', 'line', 'pie'); const rejectEverything = () => false; - expect(nameFilter(objects, rejectEverything)).to.eql([]); + expect(nameFilter(objects, rejectEverything)).toEqual([]); }); }); diff --git a/src/legacy/core_plugins/data/public/search/aggs/index.test.ts b/src/legacy/core_plugins/data/public/search/aggs/index.test.ts index a867769a77fc1d..4d0cd55b09d533 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/index.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/index.test.ts @@ -25,8 +25,6 @@ import { isMetricAggType } from './metrics/metric_agg_type'; const bucketAggs = aggTypes.buckets; const metricAggs = aggTypes.metrics; -jest.mock('ui/new_platform'); - describe('AggTypesComponent', () => { describe('bucket aggs', () => { it('all extend BucketAggType', () => { diff --git a/src/legacy/core_plugins/data/public/search/aggs/index.ts b/src/legacy/core_plugins/data/public/search/aggs/index.ts index 0bdb92b8de65e8..f6914c36f6c05c 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/index.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/index.ts @@ -17,8 +17,13 @@ * under the License. */ -export { aggTypes } from './agg_types'; +export { + AggTypesRegistry, + AggTypesRegistrySetup, + AggTypesRegistryStart, +} from './agg_types_registry'; export { AggType } from './agg_type'; +export { aggTypes } from './agg_types'; export { AggConfig } from './agg_config'; export { AggConfigs } from './agg_configs'; export { FieldParamType } from './param_types'; @@ -52,4 +57,4 @@ export { METRIC_TYPES } from './metrics/metric_agg_types'; export { ISchemas, Schema, Schemas } from './schemas'; // types -export { IAggConfig, IAggConfigs } from './types'; +export { CreateAggConfigParams, IAggConfig, IAggConfigs } from './types'; diff --git a/src/legacy/core_plugins/data/public/search/aggs/metrics/bucket_avg.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/bucket_avg.ts index 9fb28f8631bc63..11bb5592747297 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/metrics/bucket_avg.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/bucket_avg.ts @@ -19,7 +19,6 @@ import { i18n } from '@kbn/i18n'; import { get } from 'lodash'; - import { MetricAggType } from './metric_agg_type'; import { makeNestedLabel } from './lib/make_nested_label'; import { siblingPipelineAggHelper } from './lib/sibling_pipeline_agg_helper'; diff --git a/src/legacy/core_plugins/data/public/search/aggs/metrics/bucket_max.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/bucket_max.ts index 83837f0de51146..0668a9bcf57a8f 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/metrics/bucket_max.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/bucket_max.ts @@ -18,7 +18,6 @@ */ import { i18n } from '@kbn/i18n'; - import { MetricAggType } from './metric_agg_type'; import { makeNestedLabel } from './lib/make_nested_label'; import { siblingPipelineAggHelper } from './lib/sibling_pipeline_agg_helper'; diff --git a/src/legacy/core_plugins/data/public/search/aggs/metrics/bucket_min.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/bucket_min.ts index d96197693dc2ee..8f728cb5e7e426 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/metrics/bucket_min.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/bucket_min.ts @@ -16,6 +16,7 @@ * specific language governing permissions and limitations * under the License. */ + import { i18n } from '@kbn/i18n'; import { MetricAggType } from './metric_agg_type'; import { makeNestedLabel } from './lib/make_nested_label'; diff --git a/src/legacy/core_plugins/data/public/search/aggs/metrics/cardinality.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/cardinality.ts index 147e9255210887..4f7b6e555ca33e 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/metrics/cardinality.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/cardinality.ts @@ -18,10 +18,11 @@ */ import { i18n } from '@kbn/i18n'; -import { npStart } from 'ui/new_platform'; import { MetricAggType } from './metric_agg_type'; import { METRIC_TYPES } from './metric_agg_types'; import { KBN_FIELD_TYPES } from '../../../../../../../plugins/data/public'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { getFieldFormats } from '../../../../../../../plugins/data/public/services'; const uniqueCountTitle = i18n.translate('data.search.aggs.metrics.uniqueCountTitle', { defaultMessage: 'Unique Count', @@ -37,7 +38,7 @@ export const cardinalityMetricAgg = new MetricAggType({ }); }, getFormat() { - const fieldFormatsService = npStart.plugins.data.fieldFormats; + const fieldFormatsService = getFieldFormats(); return fieldFormatsService.getDefaultInstance(KBN_FIELD_TYPES.NUMBER); }, diff --git a/src/legacy/core_plugins/data/public/search/aggs/metrics/count.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/count.ts index 14a9bd073ff2bc..8b3e0a488c68a1 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/metrics/count.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/count.ts @@ -18,10 +18,11 @@ */ import { i18n } from '@kbn/i18n'; -import { npStart } from 'ui/new_platform'; -import { KBN_FIELD_TYPES } from '../../../../../../../plugins/data/public'; import { MetricAggType } from './metric_agg_type'; import { METRIC_TYPES } from './metric_agg_types'; +import { KBN_FIELD_TYPES } from '../../../../../../../plugins/data/public'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { getFieldFormats } from '../../../../../../../plugins/data/public/services'; export const countMetricAgg = new MetricAggType({ name: METRIC_TYPES.COUNT, @@ -35,7 +36,7 @@ export const countMetricAgg = new MetricAggType({ }); }, getFormat() { - const fieldFormatsService = npStart.plugins.data.fieldFormats; + const fieldFormatsService = getFieldFormats(); return fieldFormatsService.getDefaultInstance(KBN_FIELD_TYPES.NUMBER); }, diff --git a/src/legacy/core_plugins/data/public/search/aggs/metrics/lib/get_response_agg_config_class.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/lib/get_response_agg_config_class.ts index 054543de3dd067..00d866e6f2b3ed 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/metrics/lib/get_response_agg_config_class.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/lib/get_response_agg_config_class.ts @@ -16,6 +16,7 @@ * specific language governing permissions and limitations * under the License. */ + import { assign } from 'lodash'; import { IMetricAggConfig } from '../metric_agg_type'; diff --git a/src/legacy/core_plugins/data/public/search/aggs/metrics/lib/parent_pipeline_agg_helper.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/lib/parent_pipeline_agg_helper.ts index e24aca08271c72..88549ee3019ee6 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/metrics/lib/parent_pipeline_agg_helper.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/lib/parent_pipeline_agg_helper.ts @@ -23,7 +23,6 @@ import { noop, identity } from 'lodash'; import { forwardModifyAggConfigOnSearchRequestStart } from './nested_agg_helpers'; import { IMetricAggConfig, MetricAggParam } from '../metric_agg_type'; import { parentPipelineAggWriter } from './parent_pipeline_agg_writer'; - import { Schemas } from '../../schemas'; import { fieldFormats } from '../../../../../../../../plugins/data/public'; diff --git a/src/legacy/core_plugins/data/public/search/aggs/metrics/lib/sibling_pipeline_agg_helper.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/lib/sibling_pipeline_agg_helper.ts index e7c98e575fdb4f..05e009cc9da30e 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/metrics/lib/sibling_pipeline_agg_helper.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/lib/sibling_pipeline_agg_helper.ts @@ -21,7 +21,6 @@ import { identity } from 'lodash'; import { i18n } from '@kbn/i18n'; import { siblingPipelineAggWriter } from './sibling_pipeline_agg_writer'; import { forwardModifyAggConfigOnSearchRequestStart } from './nested_agg_helpers'; - import { IMetricAggConfig, MetricAggParam } from '../metric_agg_type'; import { Schemas } from '../../schemas'; import { fieldFormats } from '../../../../../../../../plugins/data/public'; diff --git a/src/legacy/core_plugins/data/public/search/aggs/metrics/median.test.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/median.test.ts index 4755a873e69779..ad55837ec9a300 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/metrics/median.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/median.test.ts @@ -17,15 +17,16 @@ * under the License. */ +import { medianMetricAgg } from './median'; import { AggConfigs, IAggConfigs } from '../agg_configs'; +import { mockAggTypesRegistry } from '../test_helpers'; import { METRIC_TYPES } from './metric_agg_types'; -jest.mock('ui/new_platform'); - describe('AggTypeMetricMedianProvider class', () => { let aggConfigs: IAggConfigs; beforeEach(() => { + const typesRegistry = mockAggTypesRegistry([medianMetricAgg]); const field = { name: 'bytes', }; @@ -50,7 +51,7 @@ describe('AggTypeMetricMedianProvider class', () => { }, }, ], - null + { typesRegistry } ); }); diff --git a/src/legacy/core_plugins/data/public/search/aggs/metrics/median.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/median.ts index 53a5ffff418f10..68fc98261118c2 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/metrics/median.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/median.ts @@ -16,12 +16,10 @@ * specific language governing permissions and limitations * under the License. */ + import { i18n } from '@kbn/i18n'; import { MetricAggType } from './metric_agg_type'; import { METRIC_TYPES } from './metric_agg_types'; - -// @ts-ignore -import { percentilesMetricAgg } from './percentiles'; import { KBN_FIELD_TYPES } from '../../../../../../../plugins/data/public'; const medianTitle = i18n.translate('data.search.aggs.metrics.medianTitle', { diff --git a/src/legacy/core_plugins/data/public/search/aggs/metrics/metric_agg_type.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/metric_agg_type.ts index 3bae7b92618dcd..952dcc96de8330 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/metrics/metric_agg_type.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/metric_agg_type.ts @@ -18,13 +18,14 @@ */ import { i18n } from '@kbn/i18n'; -import { npStart } from 'ui/new_platform'; import { AggType, AggTypeConfig } from '../agg_type'; import { AggParamType } from '../param_types/agg'; import { AggConfig } from '../agg_config'; +import { FilterFieldTypes } from '../param_types/field'; import { METRIC_TYPES } from './metric_agg_types'; import { KBN_FIELD_TYPES } from '../../../../../../../plugins/data/public'; -import { FilterFieldTypes } from '../param_types/field'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { getFieldFormats } from '../../../../../../../plugins/data/public/services'; export interface IMetricAggConfig extends AggConfig { type: InstanceType; @@ -78,7 +79,7 @@ export class MetricAggType { - const fieldFormatsService = npStart.plugins.data.fieldFormats; + const fieldFormatsService = getFieldFormats(); const field = agg.getField(); return field ? field.format diff --git a/src/legacy/core_plugins/data/public/search/aggs/metrics/min.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/min.ts index 48851051634356..1806c6d9d77107 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/metrics/min.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/min.ts @@ -16,6 +16,7 @@ * specific language governing permissions and limitations * under the License. */ + import { i18n } from '@kbn/i18n'; import { MetricAggType } from './metric_agg_type'; import { METRIC_TYPES } from './metric_agg_types'; diff --git a/src/legacy/core_plugins/data/public/search/aggs/metrics/parent_pipeline.test.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/parent_pipeline.test.ts index 11fc39c20bdc42..58b4ee530a8c2d 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/metrics/parent_pipeline.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/parent_pipeline.test.ts @@ -17,12 +17,12 @@ * under the License. */ -import sinon from 'sinon'; import { derivativeMetricAgg } from './derivative'; import { cumulativeSumMetricAgg } from './cumulative_sum'; import { movingAvgMetricAgg } from './moving_avg'; import { serialDiffMetricAgg } from './serial_diff'; import { AggConfigs } from '../agg_configs'; +import { mockDataServices, mockAggTypesRegistry } from '../test_helpers'; import { IMetricAggConfig, MetricAggType } from './metric_agg_type'; jest.mock('../schemas', () => { @@ -34,9 +34,13 @@ jest.mock('../schemas', () => { }; }); -jest.mock('ui/new_platform'); - describe('parent pipeline aggs', function() { + beforeEach(() => { + mockDataServices(); + }); + + const typesRegistry = mockAggTypesRegistry(); + const metrics = [ { name: 'derivative', title: 'Derivative', provider: derivativeMetricAgg }, { name: 'cumulative_sum', title: 'Cumulative Sum', provider: cumulativeSumMetricAgg }, @@ -94,7 +98,7 @@ describe('parent pipeline aggs', function() { schema: 'metric', }, ], - null + { typesRegistry } ); // Grab the aggConfig off the vis (we don't actually use the vis for anything else) @@ -220,16 +224,16 @@ describe('parent pipeline aggs', function() { }); const searchSource: any = {}; - const customMetricSpy = sinon.spy(); + const customMetricSpy = jest.fn(); const customMetric = aggConfig.params.customMetric; // Attach a modifyAggConfigOnSearchRequestStart with a spy to the first parameter customMetric.type.params[0].modifyAggConfigOnSearchRequestStart = customMetricSpy; aggConfig.type.params.forEach(param => { - param.modifyAggConfigOnSearchRequestStart(aggConfig, searchSource); + param.modifyAggConfigOnSearchRequestStart(aggConfig, searchSource, {}); }); - expect(customMetricSpy.calledWith(customMetric, searchSource)).toBe(true); + expect(customMetricSpy.mock.calls[0]).toEqual([customMetric, searchSource, {}]); }); }); }); diff --git a/src/legacy/core_plugins/data/public/search/aggs/metrics/percentile_ranks.test.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/percentile_ranks.test.ts index 655e918ce07deb..628f1cd204ee5e 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/metrics/percentile_ranks.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/percentile_ranks.test.ts @@ -19,14 +19,16 @@ import { IPercentileRanksAggConfig, percentileRanksMetricAgg } from './percentile_ranks'; import { AggConfigs, IAggConfigs } from '../agg_configs'; +import { mockDataServices, mockAggTypesRegistry } from '../test_helpers'; import { METRIC_TYPES } from './metric_agg_types'; -jest.mock('ui/new_platform'); - describe('AggTypesMetricsPercentileRanksProvider class', function() { let aggConfigs: IAggConfigs; beforeEach(() => { + mockDataServices(); + + const typesRegistry = mockAggTypesRegistry([percentileRanksMetricAgg]); const field = { name: 'bytes', }; @@ -58,7 +60,7 @@ describe('AggTypesMetricsPercentileRanksProvider class', function() { }, }, ], - null + { typesRegistry } ); }); diff --git a/src/legacy/core_plugins/data/public/search/aggs/metrics/percentile_ranks.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/percentile_ranks.ts index 38b47a7e97d2f2..1d640a9c1fa42f 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/metrics/percentile_ranks.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/percentile_ranks.ts @@ -18,20 +18,17 @@ */ import { i18n } from '@kbn/i18n'; -import { npStart } from 'ui/new_platform'; import { MetricAggType } from './metric_agg_type'; import { getResponseAggConfigClass, IResponseAggConfig } from './lib/get_response_agg_config_class'; - import { getPercentileValue } from './percentiles_get_value'; import { METRIC_TYPES } from './metric_agg_types'; import { fieldFormats, KBN_FIELD_TYPES } from '../../../../../../../plugins/data/public'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { getFieldFormats } from '../../../../../../../plugins/data/public/services'; // required by the values editor - export type IPercentileRanksAggConfig = IResponseAggConfig; -const getFieldFormats = () => npStart.plugins.data.fieldFormats; - const valueProps = { makeLabel(this: IPercentileRanksAggConfig) { const fieldFormatsService = getFieldFormats(); diff --git a/src/legacy/core_plugins/data/public/search/aggs/metrics/percentiles.test.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/percentiles.test.ts index dd1aaca973e473..e077bc0f8c7737 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/metrics/percentiles.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/percentiles.test.ts @@ -19,14 +19,14 @@ import { IPercentileAggConfig, percentilesMetricAgg } from './percentiles'; import { AggConfigs, IAggConfigs } from '../agg_configs'; +import { mockAggTypesRegistry } from '../test_helpers'; import { METRIC_TYPES } from './metric_agg_types'; -jest.mock('ui/new_platform'); - describe('AggTypesMetricsPercentilesProvider class', () => { let aggConfigs: IAggConfigs; beforeEach(() => { + const typesRegistry = mockAggTypesRegistry([percentilesMetricAgg]); const field = { name: 'bytes', }; @@ -58,7 +58,7 @@ describe('AggTypesMetricsPercentilesProvider class', () => { }, }, ], - null + { typesRegistry } ); }); diff --git a/src/legacy/core_plugins/data/public/search/aggs/metrics/percentiles.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/percentiles.ts index 39dc0d0f181e92..49e927d07d8dd0 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/metrics/percentiles.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/percentiles.ts @@ -18,15 +18,11 @@ */ import { i18n } from '@kbn/i18n'; - import { MetricAggType } from './metric_agg_type'; import { METRIC_TYPES } from './metric_agg_types'; import { KBN_FIELD_TYPES } from '../../../../../../../plugins/data/public'; - import { getResponseAggConfigClass, IResponseAggConfig } from './lib/get_response_agg_config_class'; import { getPercentileValue } from './percentiles_get_value'; - -// @ts-ignore import { ordinalSuffix } from './lib/ordinal_suffix'; export type IPercentileAggConfig = IResponseAggConfig; diff --git a/src/legacy/core_plugins/data/public/search/aggs/metrics/sibling_pipeline.test.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/sibling_pipeline.test.ts index d643cf0d2a4781..d3456bacceb6ab 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/metrics/sibling_pipeline.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/sibling_pipeline.test.ts @@ -17,7 +17,6 @@ * under the License. */ -import { spy } from 'sinon'; import { bucketSumMetricAgg } from './bucket_sum'; import { bucketAvgMetricAgg } from './bucket_avg'; import { bucketMinMetricAgg } from './bucket_min'; @@ -25,6 +24,7 @@ import { bucketMaxMetricAgg } from './bucket_max'; import { AggConfigs } from '../agg_configs'; import { IMetricAggConfig, MetricAggType } from './metric_agg_type'; +import { mockDataServices, mockAggTypesRegistry } from '../test_helpers'; jest.mock('../schemas', () => { class MockedSchemas { @@ -35,9 +35,13 @@ jest.mock('../schemas', () => { }; }); -jest.mock('ui/new_platform'); - describe('sibling pipeline aggs', () => { + beforeEach(() => { + mockDataServices(); + }); + + const typesRegistry = mockAggTypesRegistry(); + const metrics = [ { name: 'sum_bucket', title: 'Overall Sum', provider: bucketSumMetricAgg }, { name: 'avg_bucket', title: 'Overall Average', provider: bucketAvgMetricAgg }, @@ -96,7 +100,7 @@ describe('sibling pipeline aggs', () => { }, }, ], - null + { typesRegistry } ); // Grab the aggConfig off the vis (we don't actually use the vis for anything else) @@ -162,8 +166,8 @@ describe('sibling pipeline aggs', () => { init(); const searchSource: any = {}; - const customMetricSpy = spy(); - const customBucketSpy = spy(); + const customMetricSpy = jest.fn(); + const customBucketSpy = jest.fn(); const { customMetric, customBucket } = aggConfig.params; // Attach a modifyAggConfigOnSearchRequestStart with a spy to the first parameter @@ -171,11 +175,11 @@ describe('sibling pipeline aggs', () => { customBucket.type.params[0].modifyAggConfigOnSearchRequestStart = customBucketSpy; aggConfig.type.params.forEach(param => { - param.modifyAggConfigOnSearchRequestStart(aggConfig, searchSource); + param.modifyAggConfigOnSearchRequestStart(aggConfig, searchSource, {}); }); - expect(customMetricSpy.calledWith(customMetric, searchSource)).toBe(true); - expect(customBucketSpy.calledWith(customBucket, searchSource)).toBe(true); + expect(customMetricSpy.mock.calls[0]).toEqual([customMetric, searchSource, {}]); + expect(customBucketSpy.mock.calls[0]).toEqual([customBucket, searchSource, {}]); }); }); }); diff --git a/src/legacy/core_plugins/data/public/search/aggs/metrics/std_deviation.test.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/std_deviation.test.ts index 3125026a521854..0679831b1e6ac8 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/metrics/std_deviation.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/std_deviation.test.ts @@ -19,11 +19,11 @@ import { IStdDevAggConfig, stdDeviationMetricAgg } from './std_deviation'; import { AggConfigs } from '../agg_configs'; +import { mockAggTypesRegistry } from '../test_helpers'; import { METRIC_TYPES } from './metric_agg_types'; -jest.mock('ui/new_platform'); - describe('AggTypeMetricStandardDeviationProvider class', () => { + const typesRegistry = mockAggTypesRegistry([stdDeviationMetricAgg]); const getAggConfigs = (customLabel?: string) => { const field = { name: 'memory', @@ -52,7 +52,7 @@ describe('AggTypeMetricStandardDeviationProvider class', () => { }, }, ], - null + { typesRegistry } ); }; diff --git a/src/legacy/core_plugins/data/public/search/aggs/metrics/top_hit.test.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/top_hit.test.ts index a973de4fe8659e..ad1f42f5c563e6 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/metrics/top_hit.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/top_hit.test.ts @@ -20,11 +20,10 @@ import { dropRight, last } from 'lodash'; import { topHitMetricAgg } from './top_hit'; import { AggConfigs } from '../agg_configs'; +import { mockAggTypesRegistry } from '../test_helpers'; import { IMetricAggConfig } from './metric_agg_type'; import { KBN_FIELD_TYPES } from '../../../../../../../plugins/data/public'; -jest.mock('ui/new_platform'); - describe('Top hit metric', () => { let aggDsl: Record; let aggConfig: IMetricAggConfig; @@ -37,6 +36,7 @@ describe('Top hit metric', () => { fieldType = KBN_FIELD_TYPES.NUMBER, size = 1, }: any) => { + const typesRegistry = mockAggTypesRegistry([topHitMetricAgg]); const field = { name: fieldName, displayName: fieldName, @@ -81,7 +81,7 @@ describe('Top hit metric', () => { params, }, ], - null + { typesRegistry } ); // Grab the aggConfig off the vis (we don't actually use the vis for anything else) diff --git a/src/legacy/core_plugins/data/public/search/aggs/param_types/agg.ts b/src/legacy/core_plugins/data/public/search/aggs/param_types/agg.ts index 2e7c11004b472e..d31abe64491d0d 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/param_types/agg.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/param_types/agg.ts @@ -17,10 +17,10 @@ * under the License. */ -import { AggConfig } from '../agg_config'; +import { AggConfig, IAggConfig } from '../agg_config'; import { BaseParamType } from './base'; -export class AggParamType extends BaseParamType< +export class AggParamType extends BaseParamType< TAggConfig > { makeAgg: (agg: TAggConfig, state?: any) => TAggConfig; diff --git a/src/legacy/core_plugins/data/public/search/aggs/param_types/base.ts b/src/legacy/core_plugins/data/public/search/aggs/param_types/base.ts index 1523cb03eb966c..95ad71a616ab27 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/param_types/base.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/param_types/base.ts @@ -18,10 +18,10 @@ */ import { IAggConfigs } from '../agg_configs'; -import { AggConfig } from '../agg_config'; +import { IAggConfig } from '../agg_config'; import { FetchOptions, ISearchSource } from '../../../../../../../plugins/data/public'; -export class BaseParamType { +export class BaseParamType { name: string; type: string; displayName: string; diff --git a/src/legacy/core_plugins/data/public/search/aggs/param_types/field.test.ts b/src/legacy/core_plugins/data/public/search/aggs/param_types/field.test.ts index fa88754ac60b94..7338c41f920d72 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/param_types/field.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/param_types/field.test.ts @@ -25,8 +25,6 @@ import { IAggConfig } from '../agg_config'; import { IMetricAggConfig } from '../metrics/metric_agg_type'; import { Schema } from '../schemas'; -jest.mock('ui/new_platform'); - describe('Field', () => { const indexPattern = { id: '1234', diff --git a/src/legacy/core_plugins/data/public/search/aggs/param_types/field.ts b/src/legacy/core_plugins/data/public/search/aggs/param_types/field.ts index 40c30f6210a833..bb5707cbb482ec 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/param_types/field.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/param_types/field.ts @@ -19,7 +19,6 @@ import { i18n } from '@kbn/i18n'; import { isFunction } from 'lodash'; -import { npStart } from 'ui/new_platform'; import { IAggConfig } from '../agg_config'; import { SavedObjectNotFound } from '../../../../../../../plugins/kibana_utils/public'; import { BaseParamType } from './base'; @@ -30,6 +29,8 @@ import { indexPatterns, KBN_FIELD_TYPES, } from '../../../../../../../plugins/data/public'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { getNotifications } from '../../../../../../../plugins/data/public/services'; const filterByType = propFilter('type'); @@ -93,7 +94,7 @@ export class FieldParamType extends BaseParamType { // @ts-ignore const validField = this.getAvailableFields(aggConfig).find((f: any) => f.name === fieldName); if (!validField) { - npStart.core.notifications.toasts.addDanger( + getNotifications().toasts.addDanger( i18n.translate( 'data.search.aggs.paramTypes.field.invalidSavedFieldParameterErrorMessage', { diff --git a/src/legacy/core_plugins/data/public/search/aggs/param_types/filter/field_filters.test.ts b/src/legacy/core_plugins/data/public/search/aggs/param_types/filter/field_filters.test.ts index bc36bb46d3d166..1a453a225797db 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/param_types/filter/field_filters.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/param_types/filter/field_filters.test.ts @@ -17,27 +17,26 @@ * under the License. */ -import { IndexedArray } from 'ui/indexed_array'; import { AggTypeFieldFilters } from './field_filters'; -import { AggConfig } from '../../agg_config'; +import { IAggConfig } from '../../agg_config'; import { IndexPatternField } from '../../../../../../../../plugins/data/public'; describe('AggTypeFieldFilters', () => { let registry: AggTypeFieldFilters; - const aggConfig = {} as AggConfig; + const aggConfig = {} as IAggConfig; beforeEach(() => { registry = new AggTypeFieldFilters(); }); it('should filter nothing without registered filters', async () => { - const fields = [{ name: 'foo' }, { name: 'bar' }] as IndexedArray; + const fields = [{ name: 'foo' }, { name: 'bar' }] as IndexPatternField[]; const filtered = registry.filter(fields, aggConfig); expect(filtered).toEqual(fields); }); it('should pass all fields to the registered filter', async () => { - const fields = [{ name: 'foo' }, { name: 'bar' }] as IndexedArray; + const fields = [{ name: 'foo' }, { name: 'bar' }] as IndexPatternField[]; const filter = jest.fn(); registry.addFilter(filter); registry.filter(fields, aggConfig); @@ -46,7 +45,7 @@ describe('AggTypeFieldFilters', () => { }); it('should allow registered filters to filter out fields', async () => { - const fields = [{ name: 'foo' }, { name: 'bar' }] as IndexedArray; + const fields = [{ name: 'foo' }, { name: 'bar' }] as IndexPatternField[]; let filtered = registry.filter(fields, aggConfig); expect(filtered).toEqual(fields); diff --git a/src/legacy/core_plugins/data/public/search/aggs/param_types/filter/field_filters.ts b/src/legacy/core_plugins/data/public/search/aggs/param_types/filter/field_filters.ts index 7d1348ab5423be..1cbf0c9ae36245 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/param_types/filter/field_filters.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/param_types/filter/field_filters.ts @@ -17,9 +17,9 @@ * under the License. */ import { IndexPatternField } from 'src/plugins/data/public'; -import { AggConfig } from '../../agg_config'; +import { IAggConfig } from '../../agg_config'; -type AggTypeFieldFilter = (field: IndexPatternField, aggConfig: AggConfig) => boolean; +type AggTypeFieldFilter = (field: IndexPatternField, aggConfig: IAggConfig) => boolean; /** * A registry to store {@link AggTypeFieldFilter} which are used to filter down @@ -41,11 +41,11 @@ class AggTypeFieldFilters { /** * Returns the {@link any|fields} filtered by all registered filters. * - * @param fields An IndexedArray of fields that will be filtered down by this registry. + * @param fields An array of fields that will be filtered down by this registry. * @param aggConfig The aggConfig for which the returning list will be used. * @return A filtered list of the passed fields. */ - public filter(fields: IndexPatternField[], aggConfig: AggConfig) { + public filter(fields: IndexPatternField[], aggConfig: IAggConfig) { const allFilters = Array.from(this.filters); const allowedAggTypeFields = fields.filter(field => { const isAggTypeFieldAllowed = allFilters.every(filter => filter(field, aggConfig)); diff --git a/src/legacy/core_plugins/data/public/search/aggs/param_types/json.test.ts b/src/legacy/core_plugins/data/public/search/aggs/param_types/json.test.ts index 827299814c62aa..12fd29b3a14527 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/param_types/json.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/param_types/json.test.ts @@ -19,13 +19,11 @@ import { BaseParamType } from './base'; import { JsonParamType } from './json'; -import { AggConfig } from '../agg_config'; - -jest.mock('ui/new_platform'); +import { IAggConfig } from '../agg_config'; describe('JSON', function() { const paramName = 'json_test'; - let aggConfig: AggConfig; + let aggConfig: IAggConfig; let output: Record; const initAggParam = (config: Record = {}) => @@ -36,7 +34,7 @@ describe('JSON', function() { }); beforeEach(function() { - aggConfig = { params: {} } as AggConfig; + aggConfig = { params: {} } as IAggConfig; output = { params: {} }; }); diff --git a/src/legacy/core_plugins/data/public/search/aggs/param_types/json.ts b/src/legacy/core_plugins/data/public/search/aggs/param_types/json.ts index 771919b0bb56b9..bf85b3b890c358 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/param_types/json.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/param_types/json.ts @@ -19,7 +19,7 @@ import _ from 'lodash'; -import { AggConfig } from '../agg_config'; +import { IAggConfig } from '../agg_config'; import { BaseParamType } from './base'; export class JsonParamType extends BaseParamType { @@ -29,7 +29,7 @@ export class JsonParamType extends BaseParamType { this.name = config.name || 'json'; if (!config.write) { - this.write = (aggConfig: AggConfig, output: Record) => { + this.write = (aggConfig: IAggConfig, output: Record) => { let paramJson; const param = aggConfig.params[this.name]; diff --git a/src/legacy/core_plugins/data/public/search/aggs/param_types/optioned.test.ts b/src/legacy/core_plugins/data/public/search/aggs/param_types/optioned.test.ts index 6b58d81914097c..c03d6cdfa1c700 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/param_types/optioned.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/param_types/optioned.test.ts @@ -20,8 +20,6 @@ import { BaseParamType } from './base'; import { OptionedParamType } from './optioned'; -jest.mock('ui/new_platform'); - describe('Optioned', () => { describe('constructor', () => { it('it is an instance of BaseParamType', () => { diff --git a/src/legacy/core_plugins/data/public/search/aggs/param_types/optioned.ts b/src/legacy/core_plugins/data/public/search/aggs/param_types/optioned.ts index 5ffda3740af49f..9eb7ceda607117 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/param_types/optioned.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/param_types/optioned.ts @@ -17,14 +17,14 @@ * under the License. */ -import { AggConfig } from '../agg_config'; +import { IAggConfig } from '../agg_config'; import { BaseParamType } from './base'; export interface OptionedValueProp { value: string; text: string; disabled?: boolean; - isCompatible: (agg: AggConfig) => boolean; + isCompatible: (agg: IAggConfig) => boolean; } export interface OptionedParamEditorProps { @@ -40,7 +40,7 @@ export class OptionedParamType extends BaseParamType { super(config); if (!config.write) { - this.write = (aggConfig: AggConfig, output: Record) => { + this.write = (aggConfig: IAggConfig, output: Record) => { output.params[this.name] = aggConfig.params[this.name].value; }; } diff --git a/src/legacy/core_plugins/data/public/search/aggs/param_types/string.test.ts b/src/legacy/core_plugins/data/public/search/aggs/param_types/string.test.ts index fd5ccebde993eb..29ec9741611a37 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/param_types/string.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/param_types/string.test.ts @@ -19,13 +19,11 @@ import { BaseParamType } from './base'; import { StringParamType } from './string'; -import { AggConfig } from '../agg_config'; - -jest.mock('ui/new_platform'); +import { IAggConfig } from '../agg_config'; describe('String', function() { let paramName = 'json_test'; - let aggConfig: AggConfig; + let aggConfig: IAggConfig; let output: Record; const initAggParam = (config: Record = {}) => @@ -36,7 +34,7 @@ describe('String', function() { }); beforeEach(() => { - aggConfig = { params: {} } as AggConfig; + aggConfig = { params: {} } as IAggConfig; output = { params: {} }; }); diff --git a/src/legacy/core_plugins/data/public/search/aggs/param_types/string.ts b/src/legacy/core_plugins/data/public/search/aggs/param_types/string.ts index 58ba99f8a6d636..750606eb8433bf 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/param_types/string.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/param_types/string.ts @@ -17,7 +17,7 @@ * under the License. */ -import { AggConfig } from '../agg_config'; +import { IAggConfig } from '../agg_config'; import { BaseParamType } from './base'; export class StringParamType extends BaseParamType { @@ -25,7 +25,7 @@ export class StringParamType extends BaseParamType { super(config); if (!config.write) { - this.write = (aggConfig: AggConfig, output: Record) => { + this.write = (aggConfig: IAggConfig, output: Record) => { if (aggConfig.params[this.name] && aggConfig.params[this.name].length) { output.params[this.name] = aggConfig.params[this.name]; } diff --git a/src/legacy/ui/public/vis/__tests__/index.js b/src/legacy/core_plugins/data/public/search/aggs/test_helpers/index.ts similarity index 86% rename from src/legacy/ui/public/vis/__tests__/index.js rename to src/legacy/core_plugins/data/public/search/aggs/test_helpers/index.ts index 46074f2c5197b7..131f921586144c 100644 --- a/src/legacy/ui/public/vis/__tests__/index.js +++ b/src/legacy/core_plugins/data/public/search/aggs/test_helpers/index.ts @@ -17,5 +17,5 @@ * under the License. */ -import './_agg_config'; -import './_agg_configs'; +export { mockAggTypesRegistry } from './mock_agg_types_registry'; +export { mockDataServices } from './mock_data_services'; diff --git a/src/legacy/core_plugins/data/public/search/aggs/test_helpers/mock_agg_types_registry.ts b/src/legacy/core_plugins/data/public/search/aggs/test_helpers/mock_agg_types_registry.ts new file mode 100644 index 00000000000000..d6bb7938664932 --- /dev/null +++ b/src/legacy/core_plugins/data/public/search/aggs/test_helpers/mock_agg_types_registry.ts @@ -0,0 +1,57 @@ +/* + * 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 { AggTypesRegistry, AggTypesRegistryStart } from '../agg_types_registry'; +import { aggTypes } from '../agg_types'; +import { BucketAggType } from '../buckets/_bucket_agg_type'; +import { MetricAggType } from '../metrics/metric_agg_type'; + +/** + * Testing utility which creates a new instance of AggTypesRegistry, + * registers the provided agg types, and returns AggTypesRegistry.start() + * + * This is useful if your test depends on a certain agg type to be present + * in the registry. + * + * @param [types] - Optional array of AggTypes to register. + * If no value is provided, all default types will be registered. + * + * @internal + */ +export function mockAggTypesRegistry | MetricAggType>( + types?: T[] +): AggTypesRegistryStart { + const registry = new AggTypesRegistry(); + const registrySetup = registry.setup(); + + if (types) { + types.forEach(type => { + if (type instanceof BucketAggType) { + registrySetup.registerBucket(type); + } else if (type instanceof MetricAggType) { + registrySetup.registerMetric(type); + } + }); + } else { + aggTypes.buckets.forEach(type => registrySetup.registerBucket(type)); + aggTypes.metrics.forEach(type => registrySetup.registerMetric(type)); + } + + return registry.start(); +} diff --git a/src/legacy/core_plugins/data/public/search/aggs/test_helpers/mock_data_services.ts b/src/legacy/core_plugins/data/public/search/aggs/test_helpers/mock_data_services.ts new file mode 100644 index 00000000000000..c4e78ab8f64226 --- /dev/null +++ b/src/legacy/core_plugins/data/public/search/aggs/test_helpers/mock_data_services.ts @@ -0,0 +1,54 @@ +/* + * 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 { coreMock } from '../../../../../../../../src/core/public/mocks'; +import { dataPluginMock } from '../../../../../../../plugins/data/public/mocks'; +import { searchStartMock } from '../../mocks'; +import { setSearchServiceShim } from '../../../services'; +import { + setFieldFormats, + setIndexPatterns, + setNotifications, + setOverlays, + setQueryService, + setSearchService, + setUiSettings, + // eslint-disable-next-line @kbn/eslint/no-restricted-paths +} from '../../../../../../../plugins/data/public/services'; + +/** + * Testing helper which calls all of the service setters used in the + * data plugin. Services are added using their provided mocks. + * + * @internal + */ +export function mockDataServices() { + const core = coreMock.createStart(); + const data = dataPluginMock.createStartContract(); + const searchShim = searchStartMock(); + + setSearchServiceShim(searchShim); + setFieldFormats(data.fieldFormats); + setIndexPatterns(data.indexPatterns); + setNotifications(core.notifications); + setOverlays(core.overlays); + setQueryService(data.query); + setSearchService(data.search); + setUiSettings(core.uiSettings); +} diff --git a/src/legacy/core_plugins/data/public/search/aggs/types.ts b/src/legacy/core_plugins/data/public/search/aggs/types.ts index 2c918abf99fcaf..5d02f426b58967 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/types.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/types.ts @@ -18,7 +18,7 @@ */ export { IAggConfig } from './agg_config'; -export { IAggConfigs } from './agg_configs'; +export { CreateAggConfigParams, IAggConfigs } from './agg_configs'; export { IAggType } from './agg_type'; export { AggParam, AggParamOption } from './agg_params'; export { IFieldParamType } from './param_types'; diff --git a/src/legacy/core_plugins/data/public/search/aggs/utils.test.tsx b/src/legacy/core_plugins/data/public/search/aggs/utils.test.tsx index a3c7f24f3927d8..c0662c98755a3d 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/utils.test.tsx +++ b/src/legacy/core_plugins/data/public/search/aggs/utils.test.tsx @@ -19,8 +19,6 @@ import { isValidJson } from './utils'; -jest.mock('ui/new_platform'); - const input = { valid: '{ "test": "json input" }', invalid: 'strings are not json', diff --git a/src/legacy/core_plugins/data/public/search/aggs/utils.ts b/src/legacy/core_plugins/data/public/search/aggs/utils.ts index 62f07ce44ab46e..67ea373f438fb8 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/utils.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/utils.ts @@ -26,7 +26,7 @@ import { isValidEsInterval } from '../../../common'; * @param {string} value a string that should be validated * @returns {boolean} true if value is a valid JSON or if value is an empty string, or a string with whitespaces, otherwise false */ -function isValidJson(value: string): boolean { +export function isValidJson(value: string): boolean { if (!value || value.length === 0) { return true; } @@ -49,7 +49,7 @@ function isValidJson(value: string): boolean { } } -function isValidInterval(value: string, baseInterval?: string) { +export function isValidInterval(value: string, baseInterval?: string) { if (baseInterval) { return _parseWithBase(value, baseInterval); } else { @@ -69,4 +69,37 @@ function _parseWithBase(value: string, baseInterval: string) { } } -export { isValidJson, isValidInterval }; +// An inlined version of angular.toJSON() +// source: https://github.com/angular/angular.js/blob/master/src/Angular.js#L1312 +// @internal +export function toAngularJSON(obj: any, pretty?: any): string { + if (obj === undefined) return ''; + if (typeof pretty === 'number') { + pretty = pretty ? 2 : null; + } + return JSON.stringify(obj, toJsonReplacer, pretty); +} + +function isWindow(obj: any) { + return obj && obj.window === obj; +} + +function isScope(obj: any) { + return obj && obj.$evalAsync && obj.$watch; +} + +function toJsonReplacer(key: any, value: any) { + let val = value; + + if (typeof key === 'string' && key.charAt(0) === '$' && key.charAt(1) === '$') { + val = undefined; + } else if (isWindow(value)) { + val = '$WINDOW'; + } else if (value && window.document === value) { + val = '$DOCUMENT'; + } else if (isScope(value)) { + val = '$SCOPE'; + } + + return val; +} diff --git a/src/legacy/core_plugins/data/public/search/expressions/esaggs.ts b/src/legacy/core_plugins/data/public/search/expressions/esaggs.ts index 7a5d927d0f219a..24dd1c4944bfba 100644 --- a/src/legacy/core_plugins/data/public/search/expressions/esaggs.ts +++ b/src/legacy/core_plugins/data/public/search/expressions/esaggs.ts @@ -19,7 +19,7 @@ import { get, has } from 'lodash'; import { i18n } from '@kbn/i18n'; -import { AggConfigs, IAggConfigs } from 'ui/agg_types'; +import { createAggConfigs, IAggConfigs } from 'ui/agg_types'; import { createFormat } from 'ui/visualize/loader/pipeline_helpers/utilities'; import { KibanaContext, @@ -258,7 +258,7 @@ export const esaggs = (): ExpressionFunctionDefinition { - const aggConfigs = new AggConfigs(indexPattern); + const { aggs } = getSearchServiceShim(); + const aggConfigs = aggs.createAggConfigs(indexPattern); const aggConfig = aggConfigs.createAggConfig({ enabled: true, type, diff --git a/src/legacy/core_plugins/data/public/search/mocks.ts b/src/legacy/core_plugins/data/public/search/mocks.ts new file mode 100644 index 00000000000000..86b6a928dc5b42 --- /dev/null +++ b/src/legacy/core_plugins/data/public/search/mocks.ts @@ -0,0 +1,85 @@ +/* + * 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 { SearchSetup, SearchStart } from './search_service'; +import { AggTypesRegistrySetup, AggTypesRegistryStart } from './aggs/agg_types_registry'; +import { AggConfigs } from './aggs/agg_configs'; +import { mockAggTypesRegistry } from './aggs/test_helpers'; + +const aggTypeBaseParamMock = () => ({ + name: 'some_param', + type: 'some_param_type', + displayName: 'some_agg_type_param', + required: false, + advanced: false, + default: {}, + write: jest.fn(), + serialize: jest.fn().mockImplementation(() => {}), + deserialize: jest.fn().mockImplementation(() => {}), + options: [], +}); + +const aggTypeConfigMock = () => ({ + name: 'some_name', + title: 'some_title', + params: [aggTypeBaseParamMock()], +}); + +export const aggTypesRegistrySetupMock = (): MockedKeys => ({ + registerBucket: jest.fn(), + registerMetric: jest.fn(), +}); + +export const aggTypesRegistryStartMock = (): MockedKeys => ({ + get: jest.fn().mockImplementation(aggTypeConfigMock), + getBuckets: jest.fn().mockImplementation(() => [aggTypeConfigMock()]), + getMetrics: jest.fn().mockImplementation(() => [aggTypeConfigMock()]), + getAll: jest.fn().mockImplementation(() => ({ + buckets: [aggTypeConfigMock()], + metrics: [aggTypeConfigMock()], + })), +}); + +export const searchSetupMock = (): MockedKeys => ({ + aggs: { + types: aggTypesRegistrySetupMock(), + }, +}); + +export const searchStartMock = (): MockedKeys => ({ + aggs: { + createAggConfigs: jest.fn().mockImplementation((indexPattern, configStates = [], schemas) => { + return new AggConfigs(indexPattern, configStates, { + schemas, + typesRegistry: mockAggTypesRegistry(), + }); + }), + types: mockAggTypesRegistry(), + __LEGACY: { + AggConfig: jest.fn() as any, + AggType: jest.fn(), + aggTypeFieldFilters: jest.fn() as any, + FieldParamType: jest.fn(), + MetricAggType: jest.fn(), + parentPipelineAggHelper: jest.fn() as any, + setBounds: jest.fn(), + siblingPipelineAggHelper: jest.fn() as any, + }, + }, +}); diff --git a/src/legacy/core_plugins/data/public/search/search_service.ts b/src/legacy/core_plugins/data/public/search/search_service.ts index 45f9ff17328adb..6754c0e3551af5 100644 --- a/src/legacy/core_plugins/data/public/search/search_service.ts +++ b/src/legacy/core_plugins/data/public/search/search_service.ts @@ -18,11 +18,16 @@ */ import { CoreSetup, CoreStart } from '../../../../../core/public'; +import { IndexPattern } from '../../../../../plugins/data/public'; import { aggTypes, AggType, + AggTypesRegistry, + AggTypesRegistrySetup, + AggTypesRegistryStart, AggConfig, AggConfigs, + CreateAggConfigParams, FieldParamType, MetricAggType, aggTypeFieldFilters, @@ -32,20 +37,28 @@ import { } from './aggs'; interface AggsSetup { - types: typeof aggTypes; + types: AggTypesRegistrySetup; } -interface AggsStart { - types: typeof aggTypes; +interface AggsStartLegacy { AggConfig: typeof AggConfig; - AggConfigs: typeof AggConfigs; AggType: typeof AggType; aggTypeFieldFilters: typeof aggTypeFieldFilters; FieldParamType: typeof FieldParamType; MetricAggType: typeof MetricAggType; parentPipelineAggHelper: typeof parentPipelineAggHelper; - siblingPipelineAggHelper: typeof siblingPipelineAggHelper; setBounds: typeof setBounds; + siblingPipelineAggHelper: typeof siblingPipelineAggHelper; +} + +interface AggsStart { + createAggConfigs: ( + indexPattern: IndexPattern, + configStates?: CreateAggConfigParams[], + schemas?: Record + ) => InstanceType; + types: AggTypesRegistryStart; + __LEGACY: AggsStartLegacy; } export interface SearchSetup { @@ -63,28 +76,41 @@ export interface SearchStart { * it will move into the existing search service in src/plugins/data/public/search */ export class SearchService { + private readonly aggTypesRegistry = new AggTypesRegistry(); + public setup(core: CoreSetup): SearchSetup { + const aggTypesSetup = this.aggTypesRegistry.setup(); + aggTypes.buckets.forEach(b => aggTypesSetup.registerBucket(b)); + aggTypes.metrics.forEach(m => aggTypesSetup.registerMetric(m)); + return { aggs: { - types: aggTypes, // TODO convert to registry - // TODO add other items as needed + types: aggTypesSetup, }, }; } public start(core: CoreStart): SearchStart { + const aggTypesStart = this.aggTypesRegistry.start(); return { aggs: { - types: aggTypes, // TODO convert to registry - AggConfig, // TODO make static - AggConfigs, - AggType, - aggTypeFieldFilters, - FieldParamType, - MetricAggType, - parentPipelineAggHelper, // TODO make static - siblingPipelineAggHelper, // TODO make static - setBounds, // TODO make static + createAggConfigs: (indexPattern, configStates = [], schemas) => { + return new AggConfigs(indexPattern, configStates, { + schemas, + typesRegistry: aggTypesStart, + }); + }, + types: aggTypesStart, + __LEGACY: { + AggConfig, // TODO make static + AggType, + aggTypeFieldFilters, + FieldParamType, + MetricAggType, + parentPipelineAggHelper, // TODO make static + setBounds, // TODO make static + siblingPipelineAggHelper, // TODO make static + }, }, }; } diff --git a/src/legacy/core_plugins/data/public/search/tabify/buckets.test.ts b/src/legacy/core_plugins/data/public/search/tabify/buckets.test.ts index ef2748102623ab..98048cb25db2fe 100644 --- a/src/legacy/core_plugins/data/public/search/tabify/buckets.test.ts +++ b/src/legacy/core_plugins/data/public/search/tabify/buckets.test.ts @@ -20,8 +20,6 @@ import { TabifyBuckets } from './buckets'; import { AggGroupNames } from '../aggs'; -jest.mock('ui/new_platform'); - describe('Buckets wrapper', () => { const check = (aggResp: any, count: number, keys: string[]) => { test('reads the length', () => { diff --git a/src/legacy/core_plugins/data/public/search/tabify/get_columns.test.ts b/src/legacy/core_plugins/data/public/search/tabify/get_columns.test.ts index cfd4cd7de640b2..6c5dc790ef9767 100644 --- a/src/legacy/core_plugins/data/public/search/tabify/get_columns.test.ts +++ b/src/legacy/core_plugins/data/public/search/tabify/get_columns.test.ts @@ -18,12 +18,17 @@ */ import { tabifyGetColumns } from './get_columns'; -import { AggConfigs, AggGroupNames, Schemas } from '../aggs'; import { TabbedAggColumn } from './types'; - -jest.mock('ui/new_platform'); +import { AggConfigs, AggGroupNames, Schemas } from '../aggs'; +import { mockAggTypesRegistry, mockDataServices } from '../aggs/test_helpers'; describe('get columns', () => { + beforeEach(() => { + mockDataServices(); + }); + + const typesRegistry = mockAggTypesRegistry(); + const createAggConfigs = (aggs: any[] = []) => { const field = { name: '@timestamp', @@ -38,18 +43,17 @@ describe('get columns', () => { }, } as any; - return new AggConfigs( - indexPattern, - aggs, - new Schemas([ + return new AggConfigs(indexPattern, aggs, { + typesRegistry, + schemas: new Schemas([ { group: AggGroupNames.Metrics, name: 'metric', min: 1, defaults: [{ schema: 'metric', type: 'count' }], }, - ]).all - ); + ]).all, + }); }; test('should inject a count metric if no aggs exist', () => { diff --git a/src/legacy/core_plugins/data/public/search/tabify/response_writer.test.ts b/src/legacy/core_plugins/data/public/search/tabify/response_writer.test.ts index f5df0a683ca00c..94301eedac74a7 100644 --- a/src/legacy/core_plugins/data/public/search/tabify/response_writer.test.ts +++ b/src/legacy/core_plugins/data/public/search/tabify/response_writer.test.ts @@ -19,14 +19,19 @@ import { TabbedAggResponseWriter } from './response_writer'; import { AggConfigs, AggGroupNames, Schemas, BUCKET_TYPES } from '../aggs'; +import { mockDataServices, mockAggTypesRegistry } from '../aggs/test_helpers'; import { TabbedResponseWriterOptions } from './types'; -jest.mock('ui/new_platform'); - describe('TabbedAggResponseWriter class', () => { + beforeEach(() => { + mockDataServices(); + }); + let responseWriter: TabbedAggResponseWriter; + const typesRegistry = mockAggTypesRegistry(); + const splitAggConfig = [ { type: BUCKET_TYPES.TERMS, @@ -66,18 +71,17 @@ describe('TabbedAggResponseWriter class', () => { } as any; return new TabbedAggResponseWriter( - new AggConfigs( - indexPattern, - aggs, - new Schemas([ + new AggConfigs(indexPattern, aggs, { + typesRegistry, + schemas: new Schemas([ { group: AggGroupNames.Metrics, name: 'metric', min: 1, defaults: [{ schema: 'metric', type: 'count' }], }, - ]).all - ), + ]).all, + }), { metricsAtAllLevels: false, partialRows: false, diff --git a/src/legacy/core_plugins/data/public/search/tabify/tabify.test.ts b/src/legacy/core_plugins/data/public/search/tabify/tabify.test.ts index 13fe7719b0a856..db4ad3bdea96bd 100644 --- a/src/legacy/core_plugins/data/public/search/tabify/tabify.test.ts +++ b/src/legacy/core_plugins/data/public/search/tabify/tabify.test.ts @@ -20,11 +20,12 @@ import { IndexPattern } from '../../../../../../plugins/data/public'; import { tabifyAggResponse } from './tabify'; import { IAggConfig, IAggConfigs, AggGroupNames, Schemas, AggConfigs } from '../aggs'; +import { mockAggTypesRegistry } from '../aggs/test_helpers'; import { metricOnly, threeTermBuckets } from 'fixtures/fake_hierarchical_data'; -jest.mock('ui/new_platform'); - describe('tabifyAggResponse Integration', () => { + const typesRegistry = mockAggTypesRegistry(); + const createAggConfigs = (aggs: IAggConfig[] = []) => { const field = { name: '@timestamp', @@ -39,18 +40,17 @@ describe('tabifyAggResponse Integration', () => { }, } as unknown) as IndexPattern; - return new AggConfigs( - indexPattern, - aggs, - new Schemas([ + return new AggConfigs(indexPattern, aggs, { + typesRegistry, + schemas: new Schemas([ { group: AggGroupNames.Metrics, name: 'metric', min: 1, defaults: [{ schema: 'metric', type: 'count' }], }, - ]).all - ); + ]).all, + }); }; const mockAggConfig = (agg: any): IAggConfig => (agg as unknown) as IAggConfig; diff --git a/src/legacy/core_plugins/data/public/actions/filters/brush_event.test.mocks.ts b/src/legacy/core_plugins/data/public/services.ts similarity index 76% rename from src/legacy/core_plugins/data/public/actions/filters/brush_event.test.mocks.ts rename to src/legacy/core_plugins/data/public/services.ts index 2cecfd0fe8b767..7ecd041c70e22e 100644 --- a/src/legacy/core_plugins/data/public/actions/filters/brush_event.test.mocks.ts +++ b/src/legacy/core_plugins/data/public/services.ts @@ -17,12 +17,9 @@ * under the License. */ -import { chromeServiceMock } from '../../../../../../core/public/mocks'; +import { createGetterSetter } from '../../../../plugins/kibana_utils/public'; +import { SearchStart } from './search/search_service'; -jest.doMock('ui/new_platform', () => ({ - npStart: { - core: { - chrome: chromeServiceMock.createStartContract(), - }, - }, -})); +export const [getSearchServiceShim, setSearchServiceShim] = createGetterSetter( + 'searchShim' +); diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/state/reducers.ts b/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/state/reducers.ts index 6591aa5fb53d5f..6ae4e415f8caa8 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/state/reducers.ts +++ b/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/state/reducers.ts @@ -20,7 +20,8 @@ import { cloneDeep } from 'lodash'; import { Vis, VisState } from 'src/legacy/core_plugins/visualizations/public'; -import { AggConfigs, IAggConfig, AggGroupNames } from '../../../legacy_imports'; + +import { createAggConfigs, IAggConfig, AggGroupNames } from '../../../legacy_imports'; import { EditorStateActionTypes } from './constants'; import { getEnabledMetricAggsCount } from '../../agg_group_helper'; import { EditorAction } from './actions'; @@ -32,7 +33,8 @@ function initEditorState(vis: Vis) { function editorStateReducer(state: VisState, action: EditorAction): VisState { switch (action.type) { case EditorStateActionTypes.ADD_NEW_AGG: { - const aggConfig = state.aggs.createAggConfig(action.payload as IAggConfig, { + const payloadAggConfig = action.payload as IAggConfig; + const aggConfig = state.aggs.createAggConfig(payloadAggConfig, { addToAggConfigs: false, }); aggConfig.brandNew = true; @@ -40,7 +42,7 @@ function editorStateReducer(state: VisState, action: EditorAction): VisState { return { ...state, - aggs: new AggConfigs(state.aggs.indexPattern, newAggs, state.aggs.schemas), + aggs: createAggConfigs(state.aggs.indexPattern, newAggs, state.aggs.schemas), }; } @@ -63,7 +65,7 @@ function editorStateReducer(state: VisState, action: EditorAction): VisState { return { ...state, - aggs: new AggConfigs(state.aggs.indexPattern, newAggs, state.aggs.schemas), + aggs: createAggConfigs(state.aggs.indexPattern, newAggs, state.aggs.schemas), }; } @@ -88,7 +90,7 @@ function editorStateReducer(state: VisState, action: EditorAction): VisState { return { ...state, - aggs: new AggConfigs(state.aggs.indexPattern, newAggs, state.aggs.schemas), + aggs: createAggConfigs(state.aggs.indexPattern, newAggs, state.aggs.schemas), }; } @@ -129,7 +131,7 @@ function editorStateReducer(state: VisState, action: EditorAction): VisState { return { ...state, - aggs: new AggConfigs(state.aggs.indexPattern, newAggs, state.aggs.schemas), + aggs: createAggConfigs(state.aggs.indexPattern, newAggs, state.aggs.schemas), }; } @@ -141,7 +143,7 @@ function editorStateReducer(state: VisState, action: EditorAction): VisState { return { ...state, - aggs: new AggConfigs(state.aggs.indexPattern, newAggs, state.aggs.schemas), + aggs: createAggConfigs(state.aggs.indexPattern, newAggs, state.aggs.schemas), }; } @@ -163,7 +165,7 @@ function editorStateReducer(state: VisState, action: EditorAction): VisState { return { ...state, - aggs: new AggConfigs(state.aggs.indexPattern, newAggs, state.aggs.schemas), + aggs: createAggConfigs(state.aggs.indexPattern, newAggs, state.aggs.schemas), }; } diff --git a/src/legacy/core_plugins/vis_default_editor/public/legacy_imports.ts b/src/legacy/core_plugins/vis_default_editor/public/legacy_imports.ts index 832f73752a99b6..8aed263c4e4d1d 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/legacy_imports.ts +++ b/src/legacy/core_plugins/vis_default_editor/public/legacy_imports.ts @@ -22,12 +22,12 @@ export { AggType, IAggType, IAggConfig, - AggConfigs, IAggConfigs, AggParam, AggGroupNames, aggGroupNamesMap, aggTypes, + createAggConfigs, FieldParamType, IFieldParamType, BUCKET_TYPES, diff --git a/src/legacy/core_plugins/vis_type_table/public/table_vis_controller.test.ts b/src/legacy/core_plugins/vis_type_table/public/table_vis_controller.test.ts index 0e1e48d00a1b27..736152c7014dc2 100644 --- a/src/legacy/core_plugins/vis_type_table/public/table_vis_controller.test.ts +++ b/src/legacy/core_plugins/vis_type_table/public/table_vis_controller.test.ts @@ -34,7 +34,7 @@ import { stubFields } from '../../../../plugins/data/public/stubs'; import { tableVisResponseHandler } from './table_vis_response_handler'; import { coreMock } from '../../../../core/public/mocks'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { AggConfigs } from 'ui/agg_types'; +import { createAggConfigs } from 'ui/agg_types'; import { tabifyAggResponse, IAggConfig } from './legacy_imports'; jest.mock('ui/new_platform'); @@ -113,7 +113,7 @@ describe('Table Vis - Controller', () => { return ({ type: tableVisTypeDefinition, params: Object.assign({}, tableVisTypeDefinition.visConfig.defaults, params), - aggs: new AggConfigs( + aggs: createAggConfigs( stubIndexPattern, [ { type: 'count', schema: 'metric' }, diff --git a/src/legacy/core_plugins/visualizations/public/legacy_imports.ts b/src/legacy/core_plugins/visualizations/public/legacy_imports.ts index fb7a157b53a9ac..0a3b1938436c0e 100644 --- a/src/legacy/core_plugins/visualizations/public/legacy_imports.ts +++ b/src/legacy/core_plugins/visualizations/public/legacy_imports.ts @@ -18,10 +18,10 @@ */ export { - AggConfigs, IAggConfig, IAggConfigs, isDateHistogramBucketAggConfig, setBounds, } from '../../data/public'; +export { createAggConfigs } from 'ui/agg_types'; export { createSavedSearchesLoader } from '../../../../plugins/discover/public'; diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/vis_impl.js b/src/legacy/core_plugins/visualizations/public/np_ready/public/vis_impl.js index 2f36322c67256a..15a826cc6ddbe5 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/vis_impl.js +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/vis_impl.js @@ -30,7 +30,7 @@ import { EventEmitter } from 'events'; import _ from 'lodash'; import { PersistedState } from '../../../../../../../src/plugins/visualizations/public'; -import { AggConfigs } from '../../legacy_imports'; +import { createAggConfigs } from '../../legacy_imports'; import { updateVisualizationConfig } from './legacy/vis_update'; import { getTypes } from './services'; @@ -83,7 +83,7 @@ class VisImpl extends EventEmitter { updateVisualizationConfig(state.params, this.params); if (state.aggs || !this.aggs) { - this.aggs = new AggConfigs( + this.aggs = createAggConfigs( this.indexPattern, state.aggs ? state.aggs.aggs || state.aggs : [], this.type.schemas.all @@ -125,7 +125,7 @@ class VisImpl extends EventEmitter { copyCurrentState(includeDisabled = false) { const state = this.getCurrentState(includeDisabled); - state.aggs = new AggConfigs( + state.aggs = createAggConfigs( this.indexPattern, state.aggs.aggs || state.aggs, this.type.schemas.all diff --git a/src/legacy/ui/public/agg_types/index.ts b/src/legacy/ui/public/agg_types/index.ts index ac5d0bed7ef153..ffc300251c4bb3 100644 --- a/src/legacy/ui/public/agg_types/index.ts +++ b/src/legacy/ui/public/agg_types/index.ts @@ -27,18 +27,19 @@ import { start as dataStart } from '../../../core_plugins/data/public/legacy'; // runtime contracts +const { types } = dataStart.search.aggs; +export const aggTypes = types.getAll(); +export const { createAggConfigs } = dataStart.search.aggs; export const { - types: aggTypes, AggConfig, - AggConfigs, AggType, aggTypeFieldFilters, FieldParamType, MetricAggType, parentPipelineAggHelper, - siblingPipelineAggHelper, setBounds, -} = dataStart.search.aggs; + siblingPipelineAggHelper, +} = dataStart.search.aggs.__LEGACY; // types export { diff --git a/src/legacy/ui/public/vis/__tests__/_agg_config.js b/src/legacy/ui/public/vis/__tests__/_agg_config.js deleted file mode 100644 index 9e53044f681baa..00000000000000 --- a/src/legacy/ui/public/vis/__tests__/_agg_config.js +++ /dev/null @@ -1,485 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import sinon from 'sinon'; -import expect from '@kbn/expect'; -import ngMock from 'ng_mock'; -import { AggType, AggConfig } from '../../agg_types'; -import { start as visualizationsStart } from '../../../../core_plugins/visualizations/public/np_ready/public/legacy'; - -import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; - -describe('AggConfig', function() { - let indexPattern; - - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function(Private) { - indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider); - }) - ); - - describe('#toDsl', function() { - it('calls #write()', function() { - const vis = new visualizationsStart.Vis(indexPattern, { - type: 'histogram', - aggs: [ - { - type: 'date_histogram', - schema: 'segment', - }, - ], - }); - - const aggConfig = vis.aggs.byName('date_histogram')[0]; - const stub = sinon.stub(aggConfig, 'write').returns({ params: {} }); - - aggConfig.toDsl(); - expect(stub.callCount).to.be(1); - }); - - it('uses the type name as the agg name', function() { - const vis = new visualizationsStart.Vis(indexPattern, { - type: 'histogram', - aggs: [ - { - type: 'date_histogram', - schema: 'segment', - }, - ], - }); - - const aggConfig = vis.aggs.byName('date_histogram')[0]; - sinon.stub(aggConfig, 'write').returns({ params: {} }); - - const dsl = aggConfig.toDsl(); - expect(dsl).to.have.property('date_histogram'); - }); - - it('uses the params from #write() output as the agg params', function() { - const vis = new visualizationsStart.Vis(indexPattern, { - type: 'histogram', - aggs: [ - { - type: 'date_histogram', - schema: 'segment', - }, - ], - }); - - const aggConfig = vis.aggs.byName('date_histogram')[0]; - const football = {}; - - sinon.stub(aggConfig, 'write').returns({ params: football }); - - const dsl = aggConfig.toDsl(); - expect(dsl.date_histogram).to.be(football); - }); - - it('includes subAggs from #write() output', function() { - const vis = new visualizationsStart.Vis(indexPattern, { - type: 'histogram', - aggs: [ - { - type: 'avg', - schema: 'metric', - }, - { - type: 'date_histogram', - schema: 'segment', - }, - ], - }); - - const histoConfig = vis.aggs.byName('date_histogram')[0]; - const avgConfig = vis.aggs.byName('avg')[0]; - const football = {}; - - sinon.stub(histoConfig, 'write').returns({ params: {}, subAggs: [avgConfig] }); - sinon.stub(avgConfig, 'write').returns({ params: football }); - - const dsl = histoConfig.toDsl(); - - // didn't use .eql() because of variable key names, and final check is strict - expect(dsl).to.have.property('aggs'); - expect(dsl.aggs).to.have.property(avgConfig.id); - expect(dsl.aggs[avgConfig.id]).to.have.property('avg'); - expect(dsl.aggs[avgConfig.id].avg).to.be(football); - }); - }); - - describe('::ensureIds', function() { - it('accepts an array of objects and assigns ids to them', function() { - const objs = [{}, {}, {}, {}]; - AggConfig.ensureIds(objs); - expect(objs[0]).to.have.property('id', '1'); - expect(objs[1]).to.have.property('id', '2'); - expect(objs[2]).to.have.property('id', '3'); - expect(objs[3]).to.have.property('id', '4'); - }); - - it('assigns ids relative to the other only item in the list', function() { - const objs = [{ id: '100' }, {}]; - AggConfig.ensureIds(objs); - expect(objs[0]).to.have.property('id', '100'); - expect(objs[1]).to.have.property('id', '101'); - }); - - it('assigns ids relative to the other items in the list', function() { - const objs = [{ id: '100' }, { id: '200' }, { id: '500' }, { id: '350' }, {}]; - AggConfig.ensureIds(objs); - expect(objs[0]).to.have.property('id', '100'); - expect(objs[1]).to.have.property('id', '200'); - expect(objs[2]).to.have.property('id', '500'); - expect(objs[3]).to.have.property('id', '350'); - expect(objs[4]).to.have.property('id', '501'); - }); - - it('uses ::nextId to get the starting value', function() { - sinon.stub(AggConfig, 'nextId').returns(534); - const objs = AggConfig.ensureIds([{}]); - AggConfig.nextId.restore(); - expect(objs[0]).to.have.property('id', '534'); - }); - - it('only calls ::nextId once', function() { - const start = 420; - sinon.stub(AggConfig, 'nextId').returns(start); - const objs = AggConfig.ensureIds([{}, {}, {}, {}, {}, {}, {}]); - - expect(AggConfig.nextId).to.have.property('callCount', 1); - - AggConfig.nextId.restore(); - objs.forEach(function(obj, i) { - expect(obj).to.have.property('id', String(start + i)); - }); - }); - }); - - describe('::nextId', function() { - it('accepts a list of objects and picks the next id', function() { - const next = AggConfig.nextId([{ id: 100 }, { id: 500 }]); - expect(next).to.be(501); - }); - - it('handles an empty list', function() { - const next = AggConfig.nextId([]); - expect(next).to.be(1); - }); - - it('fails when the list is not defined', function() { - expect(function() { - AggConfig.nextId(); - }).to.throwError(); - }); - }); - - describe('#toJsonDataEquals', function() { - const testsIdentical = [ - { - type: 'metric', - aggs: [ - { - type: 'count', - schema: 'metric', - params: { field: '@timestamp' }, - }, - ], - }, - { - type: 'histogram', - aggs: [ - { - type: 'avg', - schema: 'metric', - }, - { - type: 'date_histogram', - schema: 'segment', - }, - ], - }, - ]; - - testsIdentical.forEach((visConfig, index) => { - it(`identical aggregations (${index})`, function() { - const vis1 = new visualizationsStart.Vis(indexPattern, visConfig); - const vis2 = new visualizationsStart.Vis(indexPattern, visConfig); - expect(vis1.aggs.jsonDataEquals(vis2.aggs.aggs)).to.be(true); - }); - }); - - const testsIdenticalDifferentOrder = [ - { - config1: { - type: 'histogram', - aggs: [ - { - type: 'avg', - schema: 'metric', - }, - { - type: 'date_histogram', - schema: 'segment', - }, - ], - }, - config2: { - type: 'histogram', - aggs: [ - { - schema: 'metric', - type: 'avg', - }, - { - schema: 'segment', - type: 'date_histogram', - }, - ], - }, - }, - ]; - - testsIdenticalDifferentOrder.forEach((test, index) => { - it(`identical aggregations (${index}) - init json is in different order`, function() { - const vis1 = new visualizationsStart.Vis(indexPattern, test.config1); - const vis2 = new visualizationsStart.Vis(indexPattern, test.config2); - expect(vis1.aggs.jsonDataEquals(vis2.aggs.aggs)).to.be(true); - }); - }); - - const testsDifferent = [ - { - config1: { - type: 'histogram', - aggs: [ - { - type: 'avg', - schema: 'metric', - }, - { - type: 'date_histogram', - schema: 'segment', - }, - ], - }, - config2: { - type: 'histogram', - aggs: [ - { - type: 'max', - schema: 'metric', - }, - { - type: 'date_histogram', - schema: 'segment', - }, - ], - }, - }, - { - config1: { - type: 'metric', - aggs: [ - { - type: 'count', - schema: 'metric', - params: { field: '@timestamp' }, - }, - ], - }, - config2: { - type: 'metric', - aggs: [ - { - type: 'count', - schema: 'metric', - params: { field: '@timestamp' }, - }, - { - type: 'date_histogram', - schema: 'segment', - }, - ], - }, - }, - ]; - - testsDifferent.forEach((test, index) => { - it(`different aggregations (${index})`, function() { - const vis1 = new visualizationsStart.Vis(indexPattern, test.config1); - const vis2 = new visualizationsStart.Vis(indexPattern, test.config2); - expect(vis1.aggs.jsonDataEquals(vis2.aggs.aggs)).to.be(false); - }); - }); - }); - - describe('#toJSON', function() { - it('includes the aggs id, params, type and schema', function() { - const vis = new visualizationsStart.Vis(indexPattern, { - type: 'histogram', - aggs: [ - { - type: 'date_histogram', - schema: 'segment', - }, - ], - }); - - const aggConfig = vis.aggs.byName('date_histogram')[0]; - expect(aggConfig.id).to.be('1'); - expect(aggConfig.params).to.be.an('object'); - expect(aggConfig.type) - .to.be.an(AggType) - .and.have.property('name', 'date_histogram'); - expect(aggConfig.schema) - .to.be.an('object') - .and.have.property('name', 'segment'); - - const state = aggConfig.toJSON(); - expect(state).to.have.property('id', '1'); - expect(state.params).to.be.an('object'); - expect(state).to.have.property('type', 'date_histogram'); - expect(state).to.have.property('schema', 'segment'); - }); - - it('test serialization order is identical (for visual consistency)', function() { - const vis1 = new visualizationsStart.Vis(indexPattern, { - type: 'histogram', - aggs: [ - { - type: 'date_histogram', - schema: 'segment', - }, - ], - }); - const vis2 = new visualizationsStart.Vis(indexPattern, { - type: 'histogram', - aggs: [ - { - schema: 'segment', - type: 'date_histogram', - }, - ], - }); - - //this relies on the assumption that js-engines consistently loop over properties in insertion order. - //most likely the case, but strictly speaking not guaranteed by the JS and JSON specifications. - expect(JSON.stringify(vis1.aggs.aggs) === JSON.stringify(vis2.aggs.aggs)).to.be(true); - }); - }); - - describe('#makeLabel', function() { - it('uses the custom label if it is defined', function() { - const vis = new visualizationsStart.Vis(indexPattern, {}); - const aggConfig = vis.aggs.aggs[0]; - aggConfig.params.customLabel = 'Custom label'; - const label = aggConfig.makeLabel(); - expect(label).to.be(aggConfig.params.customLabel); - }); - it('default label should be "Count"', function() { - const vis = new visualizationsStart.Vis(indexPattern, {}); - const aggConfig = vis.aggs.aggs[0]; - const label = aggConfig.makeLabel(); - expect(label).to.be('Count'); - }); - it('default label should be "Percentage of Count" when percentageMode is set to true', function() { - const vis = new visualizationsStart.Vis(indexPattern, {}); - const aggConfig = vis.aggs.aggs[0]; - const label = aggConfig.makeLabel(true); - expect(label).to.be('Percentage of Count'); - }); - it('empty label if the visualizationsStart.Vis type is not defined', function() { - const vis = new visualizationsStart.Vis(indexPattern, {}); - const aggConfig = vis.aggs.aggs[0]; - aggConfig.type = undefined; - const label = aggConfig.makeLabel(); - expect(label).to.be(''); - }); - }); - - describe('#fieldFormatter - custom getFormat handler', function() { - it('returns formatter from getFormat handler', function() { - const vis = new visualizationsStart.Vis(indexPattern, { - type: 'metric', - aggs: [ - { - type: 'count', - schema: 'metric', - params: { field: '@timestamp' }, - }, - ], - }); - - const fieldFormatter = vis.aggs.aggs[0].fieldFormatter(); - - expect(fieldFormatter).to.be.defined; - expect(fieldFormatter('text')).to.be('text'); - }); - }); - - describe('#fieldFormatter - no custom getFormat handler', function() { - const visStateAggWithoutCustomGetFormat = { - aggs: [ - { - type: 'histogram', - schema: 'bucket', - params: { field: 'bytes' }, - }, - ], - }; - let vis; - - beforeEach(function() { - vis = new visualizationsStart.Vis(indexPattern, visStateAggWithoutCustomGetFormat); - }); - - it("returns the field's formatter", function() { - expect(vis.aggs.aggs[0].fieldFormatter().toString()).to.be( - vis.aggs.aggs[0] - .getField() - .format.getConverterFor() - .toString() - ); - }); - - it('returns the string format if the field does not have a format', function() { - const agg = vis.aggs.aggs[0]; - agg.params.field = { type: 'number', format: null }; - const fieldFormatter = agg.fieldFormatter(); - expect(fieldFormatter).to.be.defined; - expect(fieldFormatter('text')).to.be('text'); - }); - - it('returns the string format if their is no field', function() { - const agg = vis.aggs.aggs[0]; - delete agg.params.field; - const fieldFormatter = agg.fieldFormatter(); - expect(fieldFormatter).to.be.defined; - expect(fieldFormatter('text')).to.be('text'); - }); - - it('returns the html converter if "html" is passed in', function() { - const field = indexPattern.fields.getByName('bytes'); - expect(vis.aggs.aggs[0].fieldFormatter('html').toString()).to.be( - field.format.getConverterFor('html').toString() - ); - }); - }); -}); diff --git a/src/legacy/ui/public/vis/__tests__/_agg_configs.js b/src/legacy/ui/public/vis/__tests__/_agg_configs.js deleted file mode 100644 index 172523ec50c8b0..00000000000000 --- a/src/legacy/ui/public/vis/__tests__/_agg_configs.js +++ /dev/null @@ -1,420 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; -import sinon from 'sinon'; -import expect from '@kbn/expect'; -import ngMock from 'ng_mock'; -import { AggConfig, AggConfigs, AggGroupNames, Schemas } from '../../agg_types'; -import { start as visualizationsStart } from '../../../../core_plugins/visualizations/public/np_ready/public/legacy'; -import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; - -describe('AggConfigs', function() { - let indexPattern; - - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function(Private) { - // load main deps - indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider); - }) - ); - - describe('constructor', function() { - it('handles passing just a vis', function() { - const vis = new visualizationsStart.Vis(indexPattern, { - type: 'histogram', - aggs: [], - }); - - const ac = new AggConfigs(vis.indexPattern, [], vis.type.schemas.all); - expect(ac.aggs).to.have.length(1); - }); - - it('converts configStates into AggConfig objects if they are not already', function() { - const vis = new visualizationsStart.Vis(indexPattern, { - type: 'histogram', - aggs: [], - }); - - const ac = new AggConfigs( - vis.indexPattern, - [ - { - type: 'date_histogram', - schema: 'segment', - }, - new AggConfig(vis.aggs, { - type: 'terms', - schema: 'split', - }), - ], - vis.type.schemas.all - ); - - expect(ac.aggs).to.have.length(3); - }); - - it('attempts to ensure that all states have an id', function() { - const vis = new visualizationsStart.Vis(indexPattern, { - type: 'histogram', - aggs: [], - }); - - const states = [ - { - type: 'date_histogram', - schema: 'segment', - }, - { - type: 'terms', - schema: 'split', - }, - ]; - - const spy = sinon.spy(AggConfig, 'ensureIds'); - new AggConfigs(vis.indexPattern, states, vis.type.schemas.all); - expect(spy.callCount).to.be(1); - expect(spy.firstCall.args[0]).to.be(states); - AggConfig.ensureIds.restore(); - }); - - describe('defaults', function() { - let vis; - beforeEach(function() { - vis = { - indexPattern: indexPattern, - type: { - schemas: new Schemas([ - { - group: AggGroupNames.Metrics, - name: 'metric', - title: 'Simple', - min: 1, - max: 2, - defaults: [ - { schema: 'metric', type: 'count' }, - { schema: 'metric', type: 'avg' }, - { schema: 'metric', type: 'sum' }, - ], - }, - { - group: AggGroupNames.Buckets, - name: 'segment', - title: 'Example', - min: 0, - max: 1, - defaults: [ - { schema: 'segment', type: 'terms' }, - { schema: 'segment', type: 'filters' }, - ], - }, - ]), - }, - }; - }); - - it('should only set the number of defaults defined by the max', function() { - const ac = new AggConfigs(vis.indexPattern, [], vis.type.schemas.all); - expect(ac.bySchemaName('metric')).to.have.length(2); - }); - - it('should set the defaults defined in the schema when none exist', function() { - const ac = new AggConfigs(vis.indexPattern, [], vis.type.schemas.all); - expect(ac.aggs).to.have.length(3); - }); - - it('should NOT set the defaults defined in the schema when some exist', function() { - const ac = new AggConfigs( - vis.indexPattern, - [{ schema: 'segment', type: 'date_histogram' }], - vis.type.schemas.all - ); - expect(ac.aggs).to.have.length(3); - expect(ac.bySchemaName('segment')[0].type.name).to.equal('date_histogram'); - }); - }); - }); - - describe('#getRequestAggs', function() { - it('performs a stable sort, but moves metrics to the bottom', function() { - const vis = new visualizationsStart.Vis(indexPattern, { - type: 'histogram', - aggs: [ - { type: 'avg', schema: 'metric' }, - { type: 'terms', schema: 'split' }, - { type: 'histogram', schema: 'split' }, - { type: 'sum', schema: 'metric' }, - { type: 'date_histogram', schema: 'segment' }, - { type: 'filters', schema: 'split' }, - { type: 'percentiles', schema: 'metric' }, - ], - }); - - const sorted = vis.aggs.getRequestAggs(); - const aggs = _.indexBy(vis.aggs.aggs, function(agg) { - return agg.type.name; - }); - - expect(sorted.shift()).to.be(aggs.terms); - expect(sorted.shift()).to.be(aggs.histogram); - expect(sorted.shift()).to.be(aggs.date_histogram); - expect(sorted.shift()).to.be(aggs.filters); - expect(sorted.shift()).to.be(aggs.avg); - expect(sorted.shift()).to.be(aggs.sum); - expect(sorted.shift()).to.be(aggs.percentiles); - expect(sorted).to.have.length(0); - }); - }); - - describe('#getResponseAggs', function() { - it('returns all request aggs for basic aggs', function() { - const vis = new visualizationsStart.Vis(indexPattern, { - type: 'histogram', - aggs: [ - { type: 'terms', schema: 'split' }, - { type: 'date_histogram', schema: 'segment' }, - { type: 'count', schema: 'metric' }, - ], - }); - - const sorted = vis.aggs.getResponseAggs(); - const aggs = _.indexBy(vis.aggs.aggs, function(agg) { - return agg.type.name; - }); - - expect(sorted.shift()).to.be(aggs.terms); - expect(sorted.shift()).to.be(aggs.date_histogram); - expect(sorted.shift()).to.be(aggs.count); - expect(sorted).to.have.length(0); - }); - - it('expands aggs that have multiple responses', function() { - const vis = new visualizationsStart.Vis(indexPattern, { - type: 'histogram', - aggs: [ - { type: 'terms', schema: 'split' }, - { type: 'date_histogram', schema: 'segment' }, - { type: 'percentiles', schema: 'metric', params: { percents: [1, 2, 3] } }, - ], - }); - - const sorted = vis.aggs.getResponseAggs(); - const aggs = _.indexBy(vis.aggs.aggs, function(agg) { - return agg.type.name; - }); - - expect(sorted.shift()).to.be(aggs.terms); - expect(sorted.shift()).to.be(aggs.date_histogram); - expect(sorted.shift().id).to.be(aggs.percentiles.id + '.' + 1); - expect(sorted.shift().id).to.be(aggs.percentiles.id + '.' + 2); - expect(sorted.shift().id).to.be(aggs.percentiles.id + '.' + 3); - expect(sorted).to.have.length(0); - }); - }); - - describe('#toDsl', function() { - it('uses the sorted aggs', function() { - const vis = new visualizationsStart.Vis(indexPattern, { type: 'histogram' }); - sinon.spy(vis.aggs, 'getRequestAggs'); - vis.aggs.toDsl(); - expect(vis.aggs.getRequestAggs).to.have.property('callCount', 1); - }); - - it('calls aggConfig#toDsl() on each aggConfig and compiles the nested output', function() { - const vis = new visualizationsStart.Vis(indexPattern, { - type: 'histogram', - aggs: [ - { type: 'date_histogram', schema: 'segment' }, - { type: 'filters', schema: 'split' }, - ], - }); - - const aggInfos = vis.aggs.aggs.map(function(aggConfig) { - const football = {}; - - sinon.stub(aggConfig, 'toDsl').returns(football); - - return { - id: aggConfig.id, - football: football, - }; - }); - - (function recurse(lvl) { - const info = aggInfos.shift(); - - expect(lvl).to.have.property(info.id); - expect(lvl[info.id]).to.be(info.football); - - if (lvl[info.id].aggs) { - return recurse(lvl[info.id].aggs); - } - })(vis.aggs.toDsl()); - - expect(aggInfos).to.have.length(1); - }); - - it("skips aggs that don't have a dsl representation", function() { - const vis = new visualizationsStart.Vis(indexPattern, { - type: 'histogram', - aggs: [ - { - type: 'date_histogram', - schema: 'segment', - params: { field: '@timestamp', interval: '10s' }, - }, - { type: 'count', schema: 'metric' }, - ], - }); - - const dsl = vis.aggs.toDsl(); - const histo = vis.aggs.byName('date_histogram')[0]; - const count = vis.aggs.byName('count')[0]; - - expect(dsl).to.have.property(histo.id); - expect(dsl[histo.id]).to.be.an('object'); - expect(dsl[histo.id]).to.not.have.property('aggs'); - expect(dsl).to.not.have.property(count.id); - }); - - it('writes multiple metric aggregations at the same level', function() { - const vis = new visualizationsStart.Vis(indexPattern, { - type: 'histogram', - aggs: [ - { - type: 'date_histogram', - schema: 'segment', - params: { field: '@timestamp', interval: '10s' }, - }, - { type: 'avg', schema: 'metric', params: { field: 'bytes' } }, - { type: 'sum', schema: 'metric', params: { field: 'bytes' } }, - { type: 'min', schema: 'metric', params: { field: 'bytes' } }, - { type: 'max', schema: 'metric', params: { field: 'bytes' } }, - ], - }); - - const dsl = vis.aggs.toDsl(); - - const histo = vis.aggs.byName('date_histogram')[0]; - const metrics = vis.aggs.bySchemaGroup('metrics'); - - expect(dsl).to.have.property(histo.id); - expect(dsl[histo.id]).to.be.an('object'); - expect(dsl[histo.id]).to.have.property('aggs'); - - metrics.forEach(function(metric) { - expect(dsl[histo.id].aggs).to.have.property(metric.id); - expect(dsl[histo.id].aggs[metric.id]).to.not.have.property('aggs'); - }); - }); - - it('writes multiple metric aggregations at every level if the vis is hierarchical', function() { - const vis = new visualizationsStart.Vis(indexPattern, { - type: 'histogram', - aggs: [ - { type: 'terms', schema: 'segment', params: { field: 'ip', orderBy: 1 } }, - { type: 'terms', schema: 'segment', params: { field: 'extension', orderBy: 1 } }, - { id: 1, type: 'avg', schema: 'metric', params: { field: 'bytes' } }, - { type: 'sum', schema: 'metric', params: { field: 'bytes' } }, - { type: 'min', schema: 'metric', params: { field: 'bytes' } }, - { type: 'max', schema: 'metric', params: { field: 'bytes' } }, - ], - }); - vis.isHierarchical = _.constant(true); - - const topLevelDsl = vis.aggs.toDsl(vis.isHierarchical()); - const buckets = vis.aggs.bySchemaGroup('buckets'); - const metrics = vis.aggs.bySchemaGroup('metrics'); - - (function checkLevel(dsl) { - const bucket = buckets.shift(); - expect(dsl).to.have.property(bucket.id); - - expect(dsl[bucket.id]).to.be.an('object'); - expect(dsl[bucket.id]).to.have.property('aggs'); - - metrics.forEach(function(metric) { - expect(dsl[bucket.id].aggs).to.have.property(metric.id); - expect(dsl[bucket.id].aggs[metric.id]).to.not.have.property('aggs'); - }); - - if (buckets.length) { - checkLevel(dsl[bucket.id].aggs); - } - })(topLevelDsl); - }); - - it('adds the parent aggs of nested metrics at every level if the vis is hierarchical', function() { - const vis = new visualizationsStart.Vis(indexPattern, { - type: 'histogram', - aggs: [ - { - id: '1', - type: 'avg_bucket', - schema: 'metric', - params: { - customBucket: { - id: '1-bucket', - type: 'date_histogram', - schema: 'bucketAgg', - params: { - field: '@timestamp', - interval: '10s', - }, - }, - customMetric: { - id: '1-metric', - type: 'count', - schema: 'metricAgg', - params: {}, - }, - }, - }, - { - id: '2', - type: 'terms', - schema: 'bucket', - params: { - field: 'geo.src', - }, - }, - { - id: '3', - type: 'terms', - schema: 'bucket', - params: { - field: 'machine.os', - }, - }, - ], - }); - vis.isHierarchical = _.constant(true); - - const topLevelDsl = vis.aggs.toDsl(vis.isHierarchical())['2']; - expect(topLevelDsl.aggs).to.have.keys(['1', '1-bucket']); - expect(topLevelDsl.aggs['1'].avg_bucket).to.have.property('buckets_path', '1-bucket>_count'); - expect(topLevelDsl.aggs['3'].aggs).to.have.keys(['1', '1-bucket']); - expect(topLevelDsl.aggs['3'].aggs['1'].avg_bucket).to.have.property( - 'buckets_path', - '1-bucket>_count' - ); - }); - }); -}); diff --git a/src/plugins/data/common/field_formats/mocks.ts b/src/plugins/data/common/field_formats/mocks.ts new file mode 100644 index 00000000000000..bc38374e147cf6 --- /dev/null +++ b/src/plugins/data/common/field_formats/mocks.ts @@ -0,0 +1,49 @@ +/* + * 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 { 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; + +export const fieldFormatsMock: IFieldFormatsRegistry = { + getByFieldType: jest.fn(), + getDefaultConfig: jest.fn(), + getDefaultInstance: jest.fn().mockImplementation(() => fieldFormatMock) as any, + getDefaultInstanceCacheResolver: jest.fn(), + getDefaultInstancePlain: jest.fn(), + getDefaultType: jest.fn(), + getDefaultTypeName: jest.fn(), + getInstance: jest.fn() as any, + getType: jest.fn(), + getTypeNameByEsTypes: jest.fn(), + init: jest.fn(), + register: jest.fn(), + parseDefaultTypeMap: jest.fn(), + deserialize: jest.fn(), + getTypeWithoutMetaParams: jest.fn(), +}; diff --git a/src/plugins/data/public/mocks.ts b/src/plugins/data/public/mocks.ts index 6a0a33096eaac1..27de3b5a29bfd0 100644 --- a/src/plugins/data/public/mocks.ts +++ b/src/plugins/data/public/mocks.ts @@ -16,13 +16,9 @@ * specific language governing permissions and limitations * under the License. */ -import { - Plugin, - DataPublicPluginSetup, - DataPublicPluginStart, - IndexPatternsContract, - IFieldFormatsRegistry, -} from '.'; + +import { Plugin, DataPublicPluginSetup, DataPublicPluginStart, IndexPatternsContract } from '.'; +import { fieldFormatsMock } from '../common/field_formats/mocks'; import { searchSetupMock } from './search/mocks'; import { queryServiceMock } from './query/mocks'; @@ -35,24 +31,6 @@ const autocompleteMock: any = { hasQuerySuggestions: jest.fn(), }; -const fieldFormatsMock: IFieldFormatsRegistry = { - getByFieldType: jest.fn(), - getDefaultConfig: jest.fn(), - getDefaultInstance: jest.fn() as any, - getDefaultInstanceCacheResolver: jest.fn(), - getDefaultInstancePlain: jest.fn(), - getDefaultType: jest.fn(), - getDefaultTypeName: jest.fn(), - getInstance: jest.fn() as any, - getType: jest.fn(), - getTypeNameByEsTypes: jest.fn(), - init: jest.fn(), - register: jest.fn(), - parseDefaultTypeMap: jest.fn(), - deserialize: jest.fn(), - getTypeWithoutMetaParams: jest.fn(), -}; - const createSetupContract = (): Setup => { const querySetupMock = queryServiceMock.createSetupContract(); const setupContract = { diff --git a/src/plugins/data/public/search/search_source/mocks.ts b/src/plugins/data/public/search/search_source/mocks.ts index fd72158012de6f..700bea741bd6ae 100644 --- a/src/plugins/data/public/search/search_source/mocks.ts +++ b/src/plugins/data/public/search/search_source/mocks.ts @@ -17,25 +17,6 @@ * under the License. */ -/* - * 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 { ISearchSource } from './search_source'; export const searchSourceMock: MockedKeys = { diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_service/service.test.tsx b/x-pack/legacy/plugins/lens/public/editor_frame_service/service.test.tsx index ef4b5f6d7b834f..2e1645c816140c 100644 --- a/x-pack/legacy/plugins/lens/public/editor_frame_service/service.test.tsx +++ b/x-pack/legacy/plugins/lens/public/editor_frame_service/service.test.tsx @@ -17,10 +17,6 @@ jest.mock('ui/new_platform'); // mock away actual dependencies to prevent all of it being loaded jest.mock('../../../../../../src/legacy/core_plugins/interpreter/public/registries', () => {}); -jest.mock('../../../../../../src/legacy/core_plugins/data/public/legacy', () => ({ - start: {}, - setup: {}, -})); jest.mock('./embeddable/embeddable_factory', () => ({ EmbeddableFactory: class Mock {}, }));