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