[resubmit] Prep agg types for new platform (#58893) (#58922)

This commit is contained in:
Luke Elmers 2020-02-28 15:31:18 -07:00 committed by GitHub
parent a517fd9a0d
commit 8d4b0cf92f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
101 changed files with 1960 additions and 1343 deletions

View file

@ -18,6 +18,7 @@ type B = UnwrapPromise<A>; // string
## Reference
- `Assign<T, U>` &mdash; From `U` assign properties to `T` (just like object assign).
- `Ensure<T, X>` &mdash; Makes sure `T` is of type `X`.
- `ObservableLike<T>` &mdash; Minimal interface for an object resembling an `Observable`.
- `PublicContract<T>` &mdash; Returns an object with public keys only.

View file

@ -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`.

View file

@ -19,34 +19,14 @@
import moment from 'moment';
jest.mock('../../search/aggs', () => ({
AggConfigs: function AggConfigs() {
return {
createAggConfig: ({ params }: Record<string, any>) => ({
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: {

View file

@ -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 */

View file

@ -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,
};
}

View file

@ -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()
);
});
});
});

View file

@ -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<string, any>;
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() {

View file

@ -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<string, any>): 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'
);
});
});
});

View file

@ -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<AggConfigOptions, { type: string | IAggType }>;
/**
* @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 = <T extends AggConfig = AggConfig>(
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<string, any> {
const dslTopLvl = {};
let dslLvlCursor: Record<string, any>;
let nestedMetrics: Array<{ config: AggConfig; dsl: any }> | [];
let nestedMetrics: Array<{ config: AggConfig; dsl: Record<string, any> }> | [];
if (hierarchical) {
// collect all metrics, and filter out the ones that we won't be copying

View file

@ -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', () => {

View file

@ -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',

View file

@ -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);
};

View file

@ -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<any>;
const metricType = { name: 'count', type: 'metric' } as MetricAggType<any>;
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],
});
});
});

View file

@ -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<AggTypesRegistry['setup']>;
export type AggTypesRegistryStart = ReturnType<AggTypesRegistry['start']>;
export class AggTypesRegistry {
private readonly bucketAggs = new Map();
private readonly metricAggs = new Map();
setup = () => {
return {
registerBucket: <T extends BucketAggType<any>>(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: <T extends MetricAggType<any>>(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()),
};
},
};
};
}

View file

@ -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<typeof BucketAggType>;
}
export interface BucketAggParam<TBucketAggConfig extends AggConfig>
export interface BucketAggParam<TBucketAggConfig extends IAggConfig>
extends AggParamType<TBucketAggConfig> {
scriptable?: boolean;
filterFieldTypes?: KBN_FIELD_TYPES | KBN_FIELD_TYPES[] | '*';
@ -34,12 +34,12 @@ export interface BucketAggParam<TBucketAggConfig extends AggConfig>
const bucketType = 'buckets';
interface BucketAggTypeConfig<TBucketAggConfig extends AggConfig>
interface BucketAggTypeConfig<TBucketAggConfig extends IAggConfig>
extends AggTypeConfig<TBucketAggConfig, BucketAggParam<TBucketAggConfig>> {
getKey?: (bucket: any, key: any, agg: AggConfig) => any;
getKey?: (bucket: any, key: any, agg: IAggConfig) => any;
}
export class BucketAggType<TBucketAggConfig extends AggConfig = IBucketAggConfig> extends AggType<
export class BucketAggType<TBucketAggConfig extends IAggConfig = IBucketAggConfig> extends AggType<
TBucketAggConfig,
BucketAggParam<TBucketAggConfig>
> {

View file

@ -16,6 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
import { i18n } from '@kbn/i18n';
import { IBucketAggConfig } from './_bucket_agg_type';

View file

@ -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;

View file

@ -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 }
);
};

View file

@ -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', () => {

View file

@ -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 }
);
};

View file

@ -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<Record<string, any>>) => {
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', () => {

View file

@ -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 }
);
};

View file

@ -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<Record<string, any>>) => {
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', () => {

View file

@ -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<IBucketDateHistogramAggC
]);
}
if (!tz) {
const config = npStart.core.uiSettings;
const config = getUiSettings();
// If the index pattern typeMeta data, didn't had a time zone assigned for the selected field use the configured tz
const isDefaultTimezone = config.isDefault('dateFormat:tz');
tz = isDefaultTimezone ? detectedTimezone || tzOffset : config.get('dateFormat:tz');

View file

@ -16,13 +16,22 @@
* specific language governing permissions and limitations
* under the License.
*/
import { AggConfigs } from '../agg_configs';
import { BUCKET_TYPES } from './bucket_agg_types';
import { npStart } from 'ui/new_platform';
jest.mock('ui/new_platform');
import { dateRangeBucketAgg } from './date_range';
import { AggConfigs } from '../agg_configs';
import { mockDataServices, mockAggTypesRegistry } from '../test_helpers';
import { BUCKET_TYPES } from './bucket_agg_types';
import { coreMock } from '../../../../../../../../src/core/public/mocks';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { setUiSettings } from '../../../../../../../plugins/data/public/services';
describe('date_range params', () => {
beforeEach(() => {
mockDataServices();
});
const typesRegistry = mockAggTypesRegistry([dateRangeBucketAgg]);
const getAggConfigs = (params: Record<string, any> = {}, 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');
});
});

View file

@ -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');

View file

@ -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';

View file

@ -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 { Storage } from '../../../../../../../plugins/kibana_utils/public';
import { getQueryLog, esQuery, Query } from '../../../../../../../plugins/data/public';
import { BUCKET_TYPES } from './bucket_agg_types';
import { Storage } from '../../../../../../../plugins/kibana_utils/public';
import { getQueryLog, esQuery, Query } from '../../../../../../../plugins/data/public';
// 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 };
},
{}

View file

@ -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<string, any>) => {
const indexPattern = {
id: '1234',
@ -62,7 +63,7 @@ describe('Geohash Agg', () => {
},
},
],
null
{ typesRegistry }
);
};

View file

@ -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: {

View file

@ -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<string, any> = {}) => {
beforeEach(() => {
mockDataServices();
});
const typesRegistry = mockAggTypesRegistry([histogramBucketAgg]);
const getAggConfigs = (params: Record<string, any>) => {
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', () => {

View file

@ -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<IBucketHistogramAggConfig>({
name: BUCKET_TYPES.HISTOGRAM,
title: i18n.translate('data.search.aggs.buckets.histogramTitle', {
@ -116,7 +114,7 @@ export const histogramBucketAgg = new BucketAggType<IBucketHistogramAggConfig>({
})
.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<IBucketHistogramAggConfig>({
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');

View file

@ -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)

View file

@ -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;

View file

@ -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 }
);
};

View file

@ -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<string, any> = {}) => {
const indexPattern = {
id: '1234',
@ -53,7 +52,7 @@ describe('Significant Terms Agg', () => {
params,
},
],
null
{ typesRegistry }
);
};

View file

@ -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<string, any> = {}) => {
const indexPattern = {
id: '1234',
@ -48,7 +48,7 @@ describe('Terms Agg', () => {
type: BUCKET_TYPES.TERMS,
},
],
null
{ typesRegistry }
);
};

View file

@ -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();

View file

@ -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';

View file

@ -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([]);
});
});

View file

@ -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', () => {

View file

@ -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';

View file

@ -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';

View file

@ -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';

View file

@ -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';

View file

@ -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);
},

View file

@ -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);
},

View file

@ -16,6 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
import { assign } from 'lodash';
import { IMetricAggConfig } from '../metric_agg_type';

View file

@ -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';

View file

@ -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';

View file

@ -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 }
);
});

View file

@ -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', {

View file

@ -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<typeof MetricAggType>;
@ -78,7 +79,7 @@ export class MetricAggType<TMetricAggConfig extends AggConfig = IMetricAggConfig
this.getFormat =
config.getFormat ||
(agg => {
const fieldFormatsService = npStart.plugins.data.fieldFormats;
const fieldFormatsService = getFieldFormats();
const field = agg.getField();
return field
? field.format

View file

@ -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';

View file

@ -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, {}]);
});
});
});

View file

@ -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 }
);
});

View file

@ -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();

View file

@ -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 }
);
});

View file

@ -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;

View file

@ -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, {}]);
});
});
});

View file

@ -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 }
);
};

View file

@ -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<string, any>;
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)

View file

@ -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<TAggConfig extends AggConfig = AggConfig> extends BaseParamType<
export class AggParamType<TAggConfig extends IAggConfig = IAggConfig> extends BaseParamType<
TAggConfig
> {
makeAgg: (agg: TAggConfig, state?: any) => TAggConfig;

View file

@ -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<TAggConfig extends AggConfig = AggConfig> {
export class BaseParamType<TAggConfig extends IAggConfig = IAggConfig> {
name: string;
type: string;
displayName: string;

View file

@ -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',

View file

@ -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',
{

View file

@ -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<IndexPatternField>;
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<IndexPatternField>;
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<IndexPatternField>;
const fields = [{ name: 'foo' }, { name: 'bar' }] as IndexPatternField[];
let filtered = registry.filter(fields, aggConfig);
expect(filtered).toEqual(fields);

View file

@ -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));

View file

@ -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<string, any>;
const initAggParam = (config: Record<string, any> = {}) =>
@ -36,7 +34,7 @@ describe('JSON', function() {
});
beforeEach(function() {
aggConfig = { params: {} } as AggConfig;
aggConfig = { params: {} } as IAggConfig;
output = { params: {} };
});

View file

@ -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<string, any>) => {
this.write = (aggConfig: IAggConfig, output: Record<string, any>) => {
let paramJson;
const param = aggConfig.params[this.name];

View file

@ -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', () => {

View file

@ -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<T = OptionedValueProp> {
@ -40,7 +40,7 @@ export class OptionedParamType extends BaseParamType {
super(config);
if (!config.write) {
this.write = (aggConfig: AggConfig, output: Record<string, any>) => {
this.write = (aggConfig: IAggConfig, output: Record<string, any>) => {
output.params[this.name] = aggConfig.params[this.name].value;
};
}

View file

@ -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<string, any>;
const initAggParam = (config: Record<string, any> = {}) =>
@ -36,7 +34,7 @@ describe('String', function() {
});
beforeEach(() => {
aggConfig = { params: {} } as AggConfig;
aggConfig = { params: {} } as IAggConfig;
output = { params: {} };
});

View file

@ -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<string, any>) => {
this.write = (aggConfig: IAggConfig, output: Record<string, any>) => {
if (aggConfig.params[this.name] && aggConfig.params[this.name].length) {
output.params[this.name] = aggConfig.params[this.name];
}

View file

@ -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';

View file

@ -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<T extends BucketAggType<any> | MetricAggType<any>>(
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();
}

View file

@ -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);
}

View file

@ -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';

View file

@ -19,8 +19,6 @@
import { isValidJson } from './utils';
jest.mock('ui/new_platform');
const input = {
valid: '{ "test": "json input" }',
invalid: 'strings are not json',

View file

@ -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;
}

View file

@ -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<typeof name, Input, Argum
const aggConfigsState = JSON.parse(args.aggConfigs);
const indexPattern = await indexPatterns.get(args.index);
const aggs = new AggConfigs(indexPattern, aggConfigsState);
const aggs = createAggConfigs(indexPattern, aggConfigsState);
// we should move searchSource creation inside courier request handler
const searchSource = new SearchSource();

View file

@ -17,7 +17,7 @@
* under the License.
*/
import { AggConfigs } from '../aggs';
import { getSearchServiceShim } from '../../services';
import { IAggConfig } from '../aggs/types';
import { KibanaDatatableColumnMeta } from '../../../../../../plugins/expressions/common/expression_types';
import { IndexPattern } from '../../../../../../plugins/data/public';
@ -41,7 +41,8 @@ export const deserializeAggConfig = ({
aggConfigParams,
indexPattern,
}: DeserializeAggConfigParams) => {
const aggConfigs = new AggConfigs(indexPattern);
const { aggs } = getSearchServiceShim();
const aggConfigs = aggs.createAggConfigs(indexPattern);
const aggConfig = aggConfigs.createAggConfig({
enabled: true,
type,

View file

@ -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<AggTypesRegistrySetup> => ({
registerBucket: jest.fn(),
registerMetric: jest.fn(),
});
export const aggTypesRegistryStartMock = (): MockedKeys<AggTypesRegistryStart> => ({
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<SearchSetup> => ({
aggs: {
types: aggTypesRegistrySetupMock(),
},
});
export const searchStartMock = (): MockedKeys<SearchStart> => ({
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,
},
},
});

View file

@ -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<string, any>
) => InstanceType<typeof AggConfigs>;
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
},
},
};
}

View file

@ -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', () => {

View file

@ -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', () => {

View file

@ -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,

View file

@ -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;

View file

@ -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<SearchStart>(
'searchShim'
);

View file

@ -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),
};
}

View file

@ -22,12 +22,12 @@ export {
AggType,
IAggType,
IAggConfig,
AggConfigs,
IAggConfigs,
AggParam,
AggGroupNames,
aggGroupNamesMap,
aggTypes,
createAggConfigs,
FieldParamType,
IFieldParamType,
BUCKET_TYPES,

View file

@ -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' },

View file

@ -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';

View file

@ -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

View file

@ -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 {

View file

@ -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()
);
});
});
});

View file

@ -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'
);
});
});
});

View file

@ -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(),
};

View file

@ -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 = {

View file

@ -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<ISearchSource> = {

Some files were not shown because too many files have changed in this diff Show more