[data.search.aggs] Remove service getters from agg types (#61069)

* [data.search.aggs] Remove service getters from agg types

Part of #60333

* fix JEST

* fix karma:unit

* fix PR commnets

* fix PR comments

* try to fix ci

* fix CI

* fix karma:unit

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
Alexey Antonov 2020-03-27 11:57:00 +03:00 committed by GitHub
parent 55ed873f32
commit 37c4fd4b71
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 713 additions and 627 deletions

View file

@ -33,7 +33,11 @@ class ReactVisController {
const I18nContext = getI18n().Context;
return new Promise(resolve => {
return new Promise((resolve, reject) => {
if (!this.vis.type || !this.vis.type.visConfig || !this.vis.type.visConfig.component) {
reject('Missing component for ReactVisType');
}
const Component = this.vis.type.visConfig.component;
const config = getUISettings();
render(

View file

@ -57,6 +57,10 @@ const mockComponent = () => {
return null;
};
let refreshInterval = undefined;
let isTimeRangeSelectorEnabled = true;
let isAutoRefreshSelectorEnabled = true;
export const mockUiSettings = {
get: item => {
return mockUiSettings[item];
@ -64,6 +68,7 @@ export const mockUiSettings = {
getUpdate$: () => ({
subscribe: sinon.fake(),
}),
isDefault: sinon.fake(),
'query:allowLeadingWildcards': true,
'query:queryString:options': {},
'courier:ignoreFilterIfFieldNotInIndex': true,
@ -81,10 +86,77 @@ const mockCore = {
},
};
const querySetup = {
state$: mockObservable(),
filterManager: {
getFetches$: sinon.fake(),
getFilters: sinon.fake(),
getAppFilters: sinon.fake(),
getGlobalFilters: sinon.fake(),
removeFilter: sinon.fake(),
addFilters: sinon.fake(),
setFilters: sinon.fake(),
removeAll: sinon.fake(),
getUpdates$: mockObservable,
},
timefilter: {
timefilter: {
getFetch$: mockObservable,
getAutoRefreshFetch$: mockObservable,
getEnabledUpdated$: mockObservable,
getTimeUpdate$: mockObservable,
getRefreshIntervalUpdate$: mockObservable,
isTimeRangeSelectorEnabled: () => {
return isTimeRangeSelectorEnabled;
},
isAutoRefreshSelectorEnabled: () => {
return isAutoRefreshSelectorEnabled;
},
disableAutoRefreshSelector: () => {
isAutoRefreshSelectorEnabled = false;
},
enableAutoRefreshSelector: () => {
isAutoRefreshSelectorEnabled = true;
},
getRefreshInterval: () => {
return refreshInterval;
},
setRefreshInterval: interval => {
refreshInterval = interval;
},
enableTimeRangeSelector: () => {
isTimeRangeSelectorEnabled = true;
},
disableTimeRangeSelector: () => {
isTimeRangeSelectorEnabled = false;
},
getTime: sinon.fake(),
setTime: sinon.fake(),
getActiveBounds: sinon.fake(),
getBounds: sinon.fake(),
calculateBounds: sinon.fake(),
createFilter: sinon.fake(),
},
history: sinon.fake(),
},
savedQueries: {
saveQuery: sinon.fake(),
getAllSavedQueries: sinon.fake(),
findSavedQueries: sinon.fake(),
getSavedQuery: sinon.fake(),
deleteSavedQuery: sinon.fake(),
getSavedQueryCount: sinon.fake(),
},
};
const mockAggTypesRegistry = () => {
const registry = new AggTypesRegistry();
const registrySetup = registry.setup();
const aggTypes = getAggTypes({ uiSettings: mockCore.uiSettings });
const aggTypes = getAggTypes({
uiSettings: mockCore.uiSettings,
notifications: mockCore.notifications,
query: querySetup,
});
aggTypes.buckets.forEach(type => registrySetup.registerBucket(type));
aggTypes.metrics.forEach(type => registrySetup.registerMetric(type));
@ -93,10 +165,6 @@ const mockAggTypesRegistry = () => {
const aggTypesRegistry = mockAggTypesRegistry();
let refreshInterval = undefined;
let isTimeRangeSelectorEnabled = true;
let isAutoRefreshSelectorEnabled = true;
export const npSetup = {
core: mockCore,
plugins: {
@ -135,72 +203,7 @@ export const npSetup = {
addProvider: sinon.fake(),
getProvider: sinon.fake(),
},
query: {
state$: mockObservable(),
filterManager: {
getFetches$: sinon.fake(),
getFilters: sinon.fake(),
getAppFilters: sinon.fake(),
getGlobalFilters: sinon.fake(),
removeFilter: sinon.fake(),
addFilters: sinon.fake(),
setFilters: sinon.fake(),
removeAll: sinon.fake(),
getUpdates$: mockObservable,
},
timefilter: {
timefilter: {
getTime: sinon.fake(),
getRefreshInterval: sinon.fake(),
getTimeUpdate$: mockObservable,
getRefreshIntervalUpdate$: mockObservable,
getFetch$: mockObservable,
getAutoRefreshFetch$: mockObservable,
getEnabledUpdated$: mockObservable,
getTimeUpdate$: mockObservable,
getRefreshIntervalUpdate$: mockObservable,
isTimeRangeSelectorEnabled: () => {
return isTimeRangeSelectorEnabled;
},
isAutoRefreshSelectorEnabled: () => {
return isAutoRefreshSelectorEnabled;
},
disableAutoRefreshSelector: () => {
isAutoRefreshSelectorEnabled = false;
},
enableAutoRefreshSelector: () => {
isAutoRefreshSelectorEnabled = true;
},
getRefreshInterval: () => {
return refreshInterval;
},
setRefreshInterval: interval => {
refreshInterval = interval;
},
enableTimeRangeSelector: () => {
isTimeRangeSelectorEnabled = true;
},
disableTimeRangeSelector: () => {
isTimeRangeSelectorEnabled = false;
},
getTime: sinon.fake(),
setTime: sinon.fake(),
getActiveBounds: sinon.fake(),
getBounds: sinon.fake(),
calculateBounds: sinon.fake(),
createFilter: sinon.fake(),
},
history: sinon.fake(),
},
savedQueries: {
saveQuery: sinon.fake(),
getAllSavedQueries: sinon.fake(),
findSavedQueries: sinon.fake(),
getSavedQuery: sinon.fake(),
deleteSavedQuery: sinon.fake(),
getSavedQueryCount: sinon.fake(),
},
},
query: querySetup,
search: {
aggs: {
calculateAutoTimeExpression: sinon.fake(),
@ -410,7 +413,6 @@ export const npStart = {
search: {
aggs: {
calculateAutoTimeExpression: sinon.fake(),
createAggConfigs: sinon.fake(),
createAggConfigs: (indexPattern, configStates = []) => {
return new AggConfigs(indexPattern, configStates, {
typesRegistry: aggTypesRegistry.start(),

View file

@ -121,7 +121,10 @@ export class DataPublicPlugin implements Plugin<DataPublicPluginSetup, DataPubli
return {
autocomplete: this.autocomplete.setup(core),
search: this.searchService.setup(core, this.packageInfo),
search: this.searchService.setup(core, {
packageInfo: this.packageInfo,
query: queryService,
}),
fieldFormats: this.fieldFormatsService.setup(core),
query: queryService,
};

View file

@ -17,7 +17,8 @@
* under the License.
*/
import { IUiSettingsClient } from 'src/core/public';
import { IUiSettingsClient, NotificationsSetup } from 'src/core/public';
import { QuerySetup } from '../../query/query_service';
import { countMetricAgg } from './metrics/count';
import { avgMetricAgg } from './metrics/avg';
@ -36,10 +37,10 @@ import { derivativeMetricAgg } from './metrics/derivative';
import { cumulativeSumMetricAgg } from './metrics/cumulative_sum';
import { movingAvgMetricAgg } from './metrics/moving_avg';
import { serialDiffMetricAgg } from './metrics/serial_diff';
import { dateHistogramBucketAgg } from './buckets/date_histogram';
import { histogramBucketAgg } from './buckets/histogram';
import { getDateHistogramBucketAgg } from './buckets/date_histogram';
import { getHistogramBucketAgg } from './buckets/histogram';
import { rangeBucketAgg } from './buckets/range';
import { dateRangeBucketAgg } from './buckets/date_range';
import { getDateRangeBucketAgg } from './buckets/date_range';
import { ipRangeBucketAgg } from './buckets/ip_range';
import { termsBucketAgg } from './buckets/terms';
import { filterBucketAgg } from './buckets/filter';
@ -52,44 +53,47 @@ import { bucketAvgMetricAgg } from './metrics/bucket_avg';
import { bucketMinMetricAgg } from './metrics/bucket_min';
import { bucketMaxMetricAgg } from './metrics/bucket_max';
export function getAggTypes(deps: { uiSettings: IUiSettingsClient }) {
const { uiSettings } = deps;
return {
metrics: [
countMetricAgg,
avgMetricAgg,
sumMetricAgg,
medianMetricAgg,
minMetricAgg,
maxMetricAgg,
stdDeviationMetricAgg,
cardinalityMetricAgg,
percentilesMetricAgg,
percentileRanksMetricAgg,
topHitMetricAgg,
derivativeMetricAgg,
cumulativeSumMetricAgg,
movingAvgMetricAgg,
serialDiffMetricAgg,
bucketAvgMetricAgg,
bucketSumMetricAgg,
bucketMinMetricAgg,
bucketMaxMetricAgg,
geoBoundsMetricAgg,
geoCentroidMetricAgg,
],
buckets: [
dateHistogramBucketAgg,
histogramBucketAgg,
rangeBucketAgg,
dateRangeBucketAgg,
ipRangeBucketAgg,
termsBucketAgg,
filterBucketAgg,
getFiltersBucketAgg({ uiSettings }),
significantTermsBucketAgg,
geoHashBucketAgg,
geoTileBucketAgg,
],
};
export interface AggTypesDependencies {
notifications: NotificationsSetup;
uiSettings: IUiSettingsClient;
query: QuerySetup;
}
export const getAggTypes = ({ notifications, uiSettings, query }: AggTypesDependencies) => ({
metrics: [
countMetricAgg,
avgMetricAgg,
sumMetricAgg,
medianMetricAgg,
minMetricAgg,
maxMetricAgg,
stdDeviationMetricAgg,
cardinalityMetricAgg,
percentilesMetricAgg,
percentileRanksMetricAgg,
topHitMetricAgg,
derivativeMetricAgg,
cumulativeSumMetricAgg,
movingAvgMetricAgg,
serialDiffMetricAgg,
bucketAvgMetricAgg,
bucketSumMetricAgg,
bucketMinMetricAgg,
bucketMaxMetricAgg,
geoBoundsMetricAgg,
geoCentroidMetricAgg,
],
buckets: [
getDateHistogramBucketAgg({ uiSettings, query }),
getHistogramBucketAgg({ uiSettings, notifications }),
rangeBucketAgg,
getDateRangeBucketAgg({ uiSettings }),
ipRangeBucketAgg,
termsBucketAgg,
filterBucketAgg,
getFiltersBucketAgg({ uiSettings }),
significantTermsBucketAgg,
geoHashBucketAgg,
geoTileBucketAgg,
],
});

View file

@ -22,23 +22,35 @@ import { createFilterDateHistogram } from './date_histogram';
import { intervalOptions } from '../_interval_options';
import { AggConfigs } from '../../agg_configs';
import { mockDataServices, mockAggTypesRegistry } from '../../test_helpers';
import { dateHistogramBucketAgg, IBucketDateHistogramAggConfig } from '../date_histogram';
import {
getDateHistogramBucketAgg,
DateHistogramBucketAggDependencies,
IBucketDateHistogramAggConfig,
} from '../date_histogram';
import { BUCKET_TYPES } from '../bucket_agg_types';
import { RangeFilter } from '../../../../../common';
import { coreMock } from '../../../../../../../core/public/mocks';
import { queryServiceMock } from '../../../../query/mocks';
describe('AggConfig Filters', () => {
describe('date_histogram', () => {
beforeEach(() => {
mockDataServices();
});
const typesRegistry = mockAggTypesRegistry([dateHistogramBucketAgg]);
let aggTypesDependencies: DateHistogramBucketAggDependencies;
let agg: IBucketDateHistogramAggConfig;
let filter: RangeFilter;
let bucketStart: any;
let field: any;
beforeEach(() => {
const { uiSettings } = coreMock.createSetup();
aggTypesDependencies = {
uiSettings,
query: queryServiceMock.createSetupContract(),
};
mockDataServices();
});
const init = (interval: string = 'auto', duration: any = moment.duration(15, 'minutes')) => {
field = {
name: 'date',
@ -61,7 +73,7 @@ describe('AggConfig Filters', () => {
params: { field: field.name, interval, customInterval: '5d' },
},
],
{ typesRegistry }
{ typesRegistry: mockAggTypesRegistry([getDateHistogramBucketAgg(aggTypesDependencies)]) }
);
const bucketKey = 1422579600000;

View file

@ -18,7 +18,7 @@
*/
import moment from 'moment';
import { dateRangeBucketAgg } from '../date_range';
import { getDateRangeBucketAgg, DateRangeBucketAggDependencies } from '../date_range';
import { createFilterDateRange } from './date_range';
import { FieldFormatsGetConfigFn } from '../../../../../common';
import { DateFormat } from '../../../../field_formats';
@ -26,10 +26,20 @@ 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';
describe('AggConfig Filters', () => {
describe('Date range', () => {
const typesRegistry = mockAggTypesRegistry([dateRangeBucketAgg]);
let aggTypesDependencies: DateRangeBucketAggDependencies;
beforeEach(() => {
const { uiSettings } = coreMock.createSetup();
aggTypesDependencies = {
uiSettings,
};
});
const getConfig = (() => {}) as FieldFormatsGetConfigFn;
const getAggConfigs = () => {
const field = {
@ -57,7 +67,7 @@ describe('AggConfig Filters', () => {
},
},
],
{ typesRegistry }
{ typesRegistry: mockAggTypesRegistry([getDateRangeBucketAgg(aggTypesDependencies)]) }
);
};

View file

@ -17,25 +17,24 @@
* under the License.
*/
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { coreMock } from '../../../../../../../../src/core/public/mocks';
import { getFiltersBucketAgg } from '../filters';
import { getFiltersBucketAgg, FiltersBucketAggDependencies } from '../filters';
import { createFilterFilters } from './filters';
import { AggConfigs } from '../../agg_configs';
import { mockDataServices, mockAggTypesRegistry } from '../../test_helpers';
import { mockAggTypesRegistry } from '../../test_helpers';
import { IBucketAggConfig } from '../_bucket_agg_type';
import { coreMock } from '../../../../../../../core/public/mocks';
describe('AggConfig Filters', () => {
describe('filters', () => {
beforeEach(() => {
mockDataServices();
});
let aggTypesDependencies: FiltersBucketAggDependencies;
const typesRegistry = mockAggTypesRegistry([
getFiltersBucketAgg({
uiSettings: coreMock.createSetup().uiSettings,
}),
]);
beforeEach(() => {
const { uiSettings } = coreMock.createSetup();
aggTypesDependencies = {
uiSettings,
};
});
const getAggConfigs = () => {
const field = {
@ -65,7 +64,7 @@ describe('AggConfig Filters', () => {
},
},
],
{ typesRegistry }
{ typesRegistry: mockAggTypesRegistry([getFiltersBucketAgg(aggTypesDependencies)]) }
);
};
it('should return a filters filter', () => {

View file

@ -17,9 +17,10 @@
* under the License.
*/
import _ from 'lodash';
import { get, noop, find, every } from 'lodash';
import moment from 'moment-timezone';
import { i18n } from '@kbn/i18n';
import { IUiSettingsClient } from 'src/core/public';
import { TimeBuckets } from './lib/time_buckets';
import { BucketAggType, IBucketAggConfig } from './_bucket_agg_type';
@ -32,7 +33,8 @@ import { isMetricAggType } from '../metrics/metric_agg_type';
import { FIELD_FORMAT_IDS, KBN_FIELD_TYPES } from '../../../../common';
import { TimefilterContract } from '../../../query';
import { getFieldFormats, getQueryService, getUiSettings } from '../../../../public/services';
import { getFieldFormats } from '../../../../public/services';
import { QuerySetup } from '../../../query/query_service';
const detectedTimezone = moment.tz.guess();
const tzOffset = moment().format('Z');
@ -56,6 +58,11 @@ interface ITimeBuckets {
getInterval: Function;
}
export interface DateHistogramBucketAggDependencies {
uiSettings: IUiSettingsClient;
query: QuerySetup;
}
export interface IBucketDateHistogramAggConfig extends IBucketAggConfig {
buckets: ITimeBuckets;
}
@ -64,212 +71,214 @@ export function isDateHistogramBucketAggConfig(agg: any): agg is IBucketDateHist
return Boolean(agg.buckets);
}
export const dateHistogramBucketAgg = 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> = {};
export const getDateHistogramBucketAgg = ({
uiSettings,
query,
}: 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> = {};
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() {
const uiSettings = getUiSettings();
let buckets: any;
return {
buckets: {
configurable: true,
get() {
if (buckets) return buckets;
const { timefilter } = getQueryService().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 DateFieldFormat = getFieldFormats().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) => getUiSettings().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 } = getQueryService().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) {
const config = getUiSettings();
// If the index pattern typeMeta data, didn't had a time zone assigned for the selected field use the configured tz
const isDefaultTimezone = config.isDefault('dateFormat:tz');
tz = isDefaultTimezone ? detectedTimezone || tzOffset : config.get('dateFormat:tz');
}
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;
}
},
},
],
});

View file

@ -17,20 +17,22 @@
* under the License.
*/
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { coreMock } from '../../../../../../../src/core/public/mocks';
import { setUiSettings } from '../../../../public/services';
import { dateRangeBucketAgg } from './date_range';
import { getDateRangeBucketAgg, DateRangeBucketAggDependencies } from './date_range';
import { AggConfigs } from '../agg_configs';
import { mockDataServices, mockAggTypesRegistry } from '../test_helpers';
import { mockAggTypesRegistry } from '../test_helpers';
import { BUCKET_TYPES } from './bucket_agg_types';
describe('date_range params', () => {
beforeEach(() => {
mockDataServices();
});
let aggTypesDependencies: DateRangeBucketAggDependencies;
const typesRegistry = mockAggTypesRegistry([dateRangeBucketAgg]);
beforeEach(() => {
const { uiSettings } = coreMock.createSetup();
aggTypesDependencies = {
uiSettings,
};
});
const getAggConfigs = (params: Record<string, any> = {}, hasIncludeTypeMeta: boolean = true) => {
const field = {
@ -67,12 +69,12 @@ describe('date_range params', () => {
params,
},
],
{ typesRegistry }
{ typesRegistry: mockAggTypesRegistry([getDateRangeBucketAgg(aggTypesDependencies)]) }
);
};
describe('getKey', () => {
it('should return object', () => {
test('should return object', () => {
const aggConfigs = getAggConfigs();
const dateRange = aggConfigs.aggs[0];
const bucket = { from: 'from-date', to: 'to-date', key: 'from-dateto-date' };
@ -82,7 +84,7 @@ describe('date_range params', () => {
});
describe('time_zone', () => {
it('should use the specified time_zone', () => {
test('should use the specified time_zone', () => {
const aggConfigs = getAggConfigs({
time_zone: 'Europe/Minsk',
field: 'bytes',
@ -93,7 +95,7 @@ describe('date_range params', () => {
expect(params.time_zone).toBe('Europe/Minsk');
});
it('should use the fixed time_zone from the index pattern typeMeta', () => {
test('should use the fixed time_zone from the index pattern typeMeta', () => {
const aggConfigs = getAggConfigs({
field: 'bytes',
});
@ -103,12 +105,14 @@ describe('date_range params', () => {
expect(params.time_zone).toBe('defaultTimeZone');
});
it('should use the Kibana time_zone if no parameter specified', () => {
const core = coreMock.createStart();
setUiSettings({
...core.uiSettings,
get: () => 'kibanaTimeZone' as any,
});
test('should use the Kibana time_zone if no parameter specified', () => {
aggTypesDependencies = {
...aggTypesDependencies,
uiSettings: {
...aggTypesDependencies.uiSettings,
get: () => 'kibanaTimeZone' as any,
},
};
const aggConfigs = getAggConfigs(
{
@ -119,8 +123,6 @@ describe('date_range params', () => {
const dateRange = aggConfigs.aggs[0];
const params = dateRange.toDsl()[BUCKET_TYPES.DATE_RANGE];
setUiSettings(core.uiSettings); // clean up
expect(params.time_zone).toBe('kibanaTimeZone');
});
});

View file

@ -20,86 +20,92 @@
import { get } from 'lodash';
import moment from 'moment-timezone';
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 { 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, getUiSettings } from '../../../../public/services';
import { getFieldFormats } from '../../../../public/services';
const dateRangeTitle = i18n.translate('data.search.aggs.buckets.dateRangeTitle', {
defaultMessage: 'Date Range',
});
export const dateRangeBucketAgg = new BucketAggType({
name: BUCKET_TYPES.DATE_RANGE,
title: dateRangeTitle,
createFilter: createFilterDateRange,
getKey({ from, to }): DateRangeKey {
return { from, to };
},
getFormat(agg) {
const fieldFormatsService = getFieldFormats();
export interface DateRangeBucketAggDependencies {
uiSettings: IUiSettingsClient;
}
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 }: 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 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;
},
],
},
{
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 config = getUiSettings();
const detectedTimezone = moment.tz.guess();
const tzOffset = moment().format('Z');
const isDefaultTimezone = config.isDefault('dateFormat:tz');
tz = isDefaultTimezone ? detectedTimezone || tzOffset : config.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;
},
},
],
});

View file

@ -17,9 +17,8 @@
* under the License.
*/
import _ from 'lodash';
import { i18n } from '@kbn/i18n';
import { size, transform, cloneDeep } from 'lodash';
import { IUiSettingsClient } from 'src/core/public';
import { createFilterFilters } from './create_filter/filters';
@ -27,7 +26,6 @@ import { toAngularJSON } from '../utils';
import { BucketAggType } from './_bucket_agg_type';
import { BUCKET_TYPES } from './bucket_agg_types';
import { Storage } from '../../../../../../plugins/kibana_utils/public';
import { getEsQueryConfig, buildEsQuery, Query } from '../../../../common';
import { getQueryLog } from '../../../query';
@ -43,9 +41,12 @@ interface FilterValue {
id: string;
}
export function getFiltersBucketAgg(deps: { uiSettings: IUiSettingsClient }) {
const { uiSettings } = deps;
return new BucketAggType({
export interface FiltersBucketAggDependencies {
uiSettings: IUiSettingsClient;
}
export const getFiltersBucketAgg = ({ uiSettings }: FiltersBucketAggDependencies) =>
new BucketAggType({
name: BUCKET_TYPES.FILTERS,
title: filtersTitle,
createFilter: createFilterFilters,
@ -58,7 +59,7 @@ export function getFiltersBucketAgg(deps: { uiSettings: IUiSettingsClient }) {
],
write(aggConfig, output) {
const inFilters: FilterValue[] = aggConfig.params.filters;
if (!_.size(inFilters)) return;
if (!size(inFilters)) return;
inFilters.forEach(filter => {
const persistedLog = getQueryLog(
@ -70,10 +71,10 @@ export function getFiltersBucketAgg(deps: { uiSettings: IUiSettingsClient }) {
persistedLog.add(filter.input.query);
});
const outFilters = _.transform(
const outFilters = transform(
inFilters,
function(filters, filter) {
const input = _.cloneDeep(filter.input);
const input = cloneDeep(filter.input);
if (!input) {
console.log('malformed filter agg params, missing "input" query'); // eslint-disable-line no-console
@ -100,7 +101,7 @@ export function getFiltersBucketAgg(deps: { uiSettings: IUiSettingsClient }) {
{}
);
if (!_.size(outFilters)) return;
if (!size(outFilters)) return;
const params = output.params || (output.params = {});
params.filters = outFilters;
@ -108,4 +109,3 @@ export function getFiltersBucketAgg(deps: { uiSettings: IUiSettingsClient }) {
},
],
});
}

View file

@ -24,8 +24,6 @@ import { BUCKET_TYPES } from './bucket_agg_types';
import { IBucketAggConfig } from './_bucket_agg_type';
describe('Geohash Agg', () => {
// const typesRegistry = mockAggTypesRegistry([geoHashBucketAgg]);
const typesRegistry = mockAggTypesRegistry();
const getAggConfigs = (params?: Record<string, any>) => {
const indexPattern = {
id: '1234',
@ -63,7 +61,7 @@ describe('Geohash Agg', () => {
},
},
],
{ typesRegistry }
{ typesRegistry: mockAggTypesRegistry() }
);
};

View file

@ -17,21 +17,29 @@
* under the License.
*/
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { coreMock } from '../../../../../../../src/core/public/mocks';
import { setUiSettings } from '../../../../public/services';
import { AggConfigs } from '../agg_configs';
import { mockDataServices, mockAggTypesRegistry } from '../test_helpers';
import { mockAggTypesRegistry } from '../test_helpers';
import { BUCKET_TYPES } from './bucket_agg_types';
import { IBucketHistogramAggConfig, histogramBucketAgg, AutoBounds } from './histogram';
import {
IBucketHistogramAggConfig,
getHistogramBucketAgg,
AutoBounds,
HistogramBucketAggDependencies,
} from './histogram';
import { BucketAggType } from './_bucket_agg_type';
describe('Histogram Agg', () => {
beforeEach(() => {
mockDataServices();
});
let aggTypesDependencies: HistogramBucketAggDependencies;
const typesRegistry = mockAggTypesRegistry([histogramBucketAgg]);
beforeEach(() => {
const { uiSettings, notifications } = coreMock.createSetup();
aggTypesDependencies = {
uiSettings,
notifications,
};
});
const getAggConfigs = (params: Record<string, any>) => {
const indexPattern = {
@ -58,7 +66,7 @@ describe('Histogram Agg', () => {
params,
},
],
{ typesRegistry }
{ typesRegistry: mockAggTypesRegistry([getHistogramBucketAgg(aggTypesDependencies)]) }
);
};
@ -76,7 +84,7 @@ describe('Histogram Agg', () => {
let histogramType: BucketAggType<IBucketHistogramAggConfig>;
beforeEach(() => {
histogramType = histogramBucketAgg;
histogramType = getHistogramBucketAgg(aggTypesDependencies);
});
it('is ordered', () => {
@ -150,6 +158,14 @@ describe('Histogram Agg', () => {
params?: Record<string, any>,
autoBounds?: AutoBounds
) => {
aggTypesDependencies = {
...aggTypesDependencies,
uiSettings: {
...aggTypesDependencies.uiSettings,
get: () => maxBars as any,
},
};
const aggConfigs = getAggConfigs({
...params,
field: {
@ -162,15 +178,7 @@ describe('Histogram Agg', () => {
aggConfig.setAutoBounds(autoBounds);
}
const core = coreMock.createStart();
setUiSettings({
...core.uiSettings,
get: () => maxBars as any,
});
const interval = aggConfig.write(aggConfigs).params;
setUiSettings(core.uiSettings); // clean up
return interval;
return aggConfig.write(aggConfigs).params;
};
it('will respect the histogram:maxBars setting', () => {

View file

@ -17,182 +17,190 @@
* under the License.
*/
import _ from 'lodash';
import { get } from 'lodash';
import { i18n } from '@kbn/i18n';
import { IUiSettingsClient, NotificationsSetup } from 'src/core/public';
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 { getNotifications, getUiSettings } from '../../../../public/services';
export interface AutoBounds {
min: number;
max: number;
}
export interface HistogramBucketAggDependencies {
uiSettings: IUiSettingsClient;
notifications: NotificationsSetup;
}
export interface IBucketHistogramAggConfig extends IBucketAggConfig {
setAutoBounds: (bounds: AutoBounds) => void;
getAutoBounds: () => AutoBounds;
}
export const histogramBucketAgg = 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,
export const getHistogramBucketAgg = ({
uiSettings,
notifications,
}: HistogramBucketAggDependencies) =>
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;
getNotifications().toasts.addWarning(
i18n.translate('data.search.aggs.histogram.missingMaxMinValuesWarning', {
defaultMessage:
'Unable to retrieve max and min values to auto-scale histogram buckets. This may lead to poor visualization performance.',
})
);
});
},
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;
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();
const config = getUiSettings();
if (bars > config.get('histogram:maxBars')) {
const minInterval = range / config.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,
},
],
});
],
});

View file

@ -48,8 +48,6 @@ describe('Range Agg', () => {
mockDataServices();
});
const typesRegistry = mockAggTypesRegistry([rangeBucketAgg]);
const getConfig = (() => {}) as FieldFormatsGetConfigFn;
const getAggConfigs = () => {
const field = {
@ -86,7 +84,7 @@ describe('Range Agg', () => {
},
},
],
{ typesRegistry }
{ typesRegistry: mockAggTypesRegistry([rangeBucketAgg]) }
);
};

View file

@ -26,7 +26,6 @@ import { IBucketAggConfig } from './_bucket_agg_type';
describe('Significant Terms Agg', () => {
describe('order agg editor UI', () => {
describe('convert include/exclude from old format', () => {
const typesRegistry = mockAggTypesRegistry([significantTermsBucketAgg]);
const getAggConfigs = (params: Record<string, any> = {}) => {
const indexPattern = {
id: '1234',
@ -52,12 +51,12 @@ describe('Significant Terms Agg', () => {
params,
},
],
{ typesRegistry }
{ typesRegistry: mockAggTypesRegistry([significantTermsBucketAgg]) }
);
};
const testSerializeAndWrite = (aggs: IAggConfigs) => {
const agg = aggs.aggs[0];
const [agg] = aggs.aggs;
const { [BUCKET_TYPES.SIGNIFICANT_TERMS]: params } = agg.toDsl();
expect(params.field).toBe('field');

View file

@ -23,7 +23,6 @@ import { BUCKET_TYPES } from './bucket_agg_types';
describe('Terms Agg', () => {
describe('order agg editor UI', () => {
const typesRegistry = mockAggTypesRegistry();
const getAggConfigs = (params: Record<string, any> = {}) => {
const indexPattern = {
id: '1234',
@ -48,7 +47,7 @@ describe('Terms Agg', () => {
type: BUCKET_TYPES.TERMS,
},
],
{ typesRegistry }
{ typesRegistry: mockAggTypesRegistry() }
);
};

View file

@ -22,24 +22,29 @@ import { getAggTypes } from './index';
import { isBucketAggType } from './buckets/_bucket_agg_type';
import { isMetricAggType } from './metrics/metric_agg_type';
const aggTypes = getAggTypes({ uiSettings: coreMock.createStart().uiSettings });
const bucketAggs = aggTypes.buckets;
const metricAggs = aggTypes.metrics;
import { QueryStart } from '../../query';
describe('AggTypesComponent', () => {
const core = coreMock.createSetup();
const aggTypes = getAggTypes({
uiSettings: core.uiSettings,
notifications: core.notifications,
query: {} as QueryStart,
});
const { buckets, metrics } = aggTypes;
describe('bucket aggs', () => {
it('all extend BucketAggType', () => {
bucketAggs.forEach(bucketAgg => {
test('all extend BucketAggType', () => {
buckets.forEach(bucketAgg => {
expect(isBucketAggType(bucketAgg)).toBeTruthy();
});
});
});
describe('metric aggs', () => {
it('all extend MetricAggType', () => {
metricAggs.forEach(metricAgg => {
test('all extend MetricAggType', () => {
metrics.forEach(metricAgg => {
expect(isMetricAggType(metricAgg)).toBeTruthy();
});
});

View file

@ -23,6 +23,7 @@ import { AggTypesRegistry, AggTypesRegistryStart } from '../agg_types_registry';
import { getAggTypes } from '../agg_types';
import { BucketAggType } from '../buckets/_bucket_agg_type';
import { MetricAggType } from '../metrics/metric_agg_type';
import { queryServiceMock } from '../../../query/mocks';
/**
* Testing utility which creates a new instance of AggTypesRegistry,
@ -51,7 +52,13 @@ export function mockAggTypesRegistry<T extends BucketAggType<any> | MetricAggTyp
}
});
} else {
const aggTypes = getAggTypes({ uiSettings: coreMock.createSetup().uiSettings });
const core = coreMock.createSetup();
const aggTypes = getAggTypes({
uiSettings: core.uiSettings,
notifications: core.notifications,
query: queryServiceMock.createSetupContract(),
});
aggTypes.buckets.forEach(type => registrySetup.registerBucket(type));
aggTypes.metrics.forEach(type => registrySetup.registerMetric(type));
}

View file

@ -34,7 +34,7 @@ describe('Search service', () => {
describe('setup()', () => {
it('exposes proper contract', async () => {
const setup = searchService.setup(mockCoreSetup, {
version: '8',
packageInfo: { version: '8' },
} as any);
expect(setup).toHaveProperty('registerSearchStrategyProvider');
});

View file

@ -25,6 +25,7 @@ import { TStrategyTypes } from './strategy_types';
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 { SearchInterceptor } from './search_interceptor';
import {
getAggTypes,
@ -40,6 +41,11 @@ import {
siblingPipelineAggHelper,
} from './aggs';
interface SearchServiceSetupDependencies {
packageInfo: PackageInfo;
query: QuerySetup;
}
/**
* The search plugin exposes two registration methods for other plugins:
* - registerSearchStrategyProvider for plugins to add their own custom
@ -73,13 +79,21 @@ export class SearchService implements Plugin<ISearchSetup, ISearchStart> {
return strategyProvider;
};
public setup(core: CoreSetup, packageInfo: PackageInfo): ISearchSetup {
public setup(
core: CoreSetup,
{ packageInfo, query }: SearchServiceSetupDependencies
): ISearchSetup {
this.esClient = getEsClient(core.injectedMetadata, core.http, packageInfo);
this.registerSearchStrategyProvider(SYNC_SEARCH_STRATEGY, syncSearchStrategyProvider);
this.registerSearchStrategyProvider(ES_SEARCH_STRATEGY, esSearchStrategyProvider);
const aggTypesSetup = this.aggTypesRegistry.setup();
const aggTypes = getAggTypes({ uiSettings: core.uiSettings });
const aggTypes = getAggTypes({
query,
uiSettings: core.uiSettings,
notifications: core.notifications,
});
aggTypes.buckets.forEach(b => aggTypesSetup.registerBucket(b));
aggTypes.metrics.forEach(m => aggTypesSetup.registerMetric(m));

View file

@ -17,8 +17,7 @@
* under the License.
*/
import { NotificationsStart } from 'src/core/public';
import { CoreSetup, CoreStart } from 'kibana/public';
import { NotificationsStart, CoreSetup, CoreStart } from 'src/core/public';
import { FieldFormatsStart } from './field_formats';
import { createGetterSetter } from '../../kibana_utils/public';
import { IndexPatternsContract } from './index_patterns';