mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
parent
a517fd9a0d
commit
8d4b0cf92f
101 changed files with 1960 additions and 1343 deletions
|
@ -18,6 +18,7 @@ type B = UnwrapPromise<A>; // string
|
|||
|
||||
## Reference
|
||||
|
||||
- `Assign<T, U>` — From `U` assign properties to `T` (just like object assign).
|
||||
- `Ensure<T, X>` — Makes sure `T` is of type `X`.
|
||||
- `ObservableLike<T>` — Minimal interface for an object resembling an `Observable`.
|
||||
- `PublicContract<T>` — Returns an object with public keys only.
|
||||
|
|
|
@ -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`.
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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() {
|
||||
|
|
|
@ -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'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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
|
||||
|
|
|
@ -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', () => {
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
|
|
@ -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],
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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()),
|
||||
};
|
||||
},
|
||||
};
|
||||
};
|
||||
}
|
|
@ -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>
|
||||
> {
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { IBucketAggConfig } from './_bucket_agg_type';
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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 }
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -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', () => {
|
||||
|
|
|
@ -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 }
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -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', () => {
|
||||
|
|
|
@ -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 }
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -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', () => {
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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 };
|
||||
},
|
||||
{}
|
||||
|
|
|
@ -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 }
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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', () => {
|
||||
|
|
|
@ -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');
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 }
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -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 }
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -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 }
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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([]);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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', () => {
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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);
|
||||
},
|
||||
|
|
|
@ -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);
|
||||
},
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { assign } from 'lodash';
|
||||
import { IMetricAggConfig } from '../metric_agg_type';
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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 }
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
@ -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', {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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, {}]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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 }
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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 }
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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, {}]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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 }
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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',
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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: {} };
|
||||
});
|
||||
|
||||
|
|
|
@ -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];
|
||||
|
||||
|
|
|
@ -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', () => {
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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: {} };
|
||||
});
|
||||
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
|
|
|
@ -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';
|
|
@ -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();
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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';
|
||||
|
|
|
@ -19,8 +19,6 @@
|
|||
|
||||
import { isValidJson } from './utils';
|
||||
|
||||
jest.mock('ui/new_platform');
|
||||
|
||||
const input = {
|
||||
valid: '{ "test": "json input" }',
|
||||
invalid: 'strings are not json',
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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,
|
||||
|
|
85
src/legacy/core_plugins/data/public/search/mocks.ts
Normal file
85
src/legacy/core_plugins/data/public/search/mocks.ts
Normal 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,
|
||||
},
|
||||
},
|
||||
});
|
|
@ -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
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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', () => {
|
||||
|
|
|
@ -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', () => {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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'
|
||||
);
|
|
@ -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),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -22,12 +22,12 @@ export {
|
|||
AggType,
|
||||
IAggType,
|
||||
IAggConfig,
|
||||
AggConfigs,
|
||||
IAggConfigs,
|
||||
AggParam,
|
||||
AggGroupNames,
|
||||
aggGroupNamesMap,
|
||||
aggTypes,
|
||||
createAggConfigs,
|
||||
FieldParamType,
|
||||
IFieldParamType,
|
||||
BUCKET_TYPES,
|
||||
|
|
|
@ -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' },
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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()
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
49
src/plugins/data/common/field_formats/mocks.ts
Normal file
49
src/plugins/data/common/field_formats/mocks.ts
Normal 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(),
|
||||
};
|
|
@ -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 = {
|
||||
|
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue