diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/vis_types/react_vis_type.js b/src/legacy/core_plugins/visualizations/public/np_ready/public/vis_types/react_vis_type.js index e8ee7bc6e544..e2789ee612cd 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/vis_types/react_vis_type.js +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/vis_types/react_vis_type.js @@ -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( diff --git a/src/legacy/ui/public/new_platform/new_platform.karma_mock.js b/src/legacy/ui/public/new_platform/new_platform.karma_mock.js index 67877c538263..10d2818df279 100644 --- a/src/legacy/ui/public/new_platform/new_platform.karma_mock.js +++ b/src/legacy/ui/public/new_platform/new_platform.karma_mock.js @@ -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(), diff --git a/src/plugins/data/public/plugin.ts b/src/plugins/data/public/plugin.ts index fc5dde94fa85..26587470adfd 100644 --- a/src/plugins/data/public/plugin.ts +++ b/src/plugins/data/public/plugin.ts @@ -121,7 +121,10 @@ export class DataPublicPlugin implements Plugin ({ + 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, + ], +}); diff --git a/src/plugins/data/public/search/aggs/buckets/create_filter/date_histogram.test.ts b/src/plugins/data/public/search/aggs/buckets/create_filter/date_histogram.test.ts index 12817a9ba115..def354c4557c 100644 --- a/src/plugins/data/public/search/aggs/buckets/create_filter/date_histogram.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/create_filter/date_histogram.test.ts @@ -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; diff --git a/src/plugins/data/public/search/aggs/buckets/create_filter/date_range.test.ts b/src/plugins/data/public/search/aggs/buckets/create_filter/date_range.test.ts index d18a30fb6c6f..6a03176959a8 100644 --- a/src/plugins/data/public/search/aggs/buckets/create_filter/date_range.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/create_filter/date_range.test.ts @@ -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)]) } ); }; diff --git a/src/plugins/data/public/search/aggs/buckets/create_filter/filters.test.ts b/src/plugins/data/public/search/aggs/buckets/create_filter/filters.test.ts index 33ab1ce8186a..32ada8d57c76 100644 --- a/src/plugins/data/public/search/aggs/buckets/create_filter/filters.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/create_filter/filters.test.ts @@ -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', () => { + let aggTypesDependencies: FiltersBucketAggDependencies; + beforeEach(() => { - mockDataServices(); - }); + const { uiSettings } = coreMock.createSetup(); - const typesRegistry = mockAggTypesRegistry([ - getFiltersBucketAgg({ - uiSettings: coreMock.createSetup().uiSettings, - }), - ]); + aggTypesDependencies = { + uiSettings, + }; + }); const getAggConfigs = () => { const field = { @@ -65,7 +64,7 @@ describe('AggConfig Filters', () => { }, }, ], - { typesRegistry } + { typesRegistry: mockAggTypesRegistry([getFiltersBucketAgg(aggTypesDependencies)]) } ); }; it('should return a filters filter', () => { diff --git a/src/plugins/data/public/search/aggs/buckets/date_histogram.ts b/src/plugins/data/public/search/aggs/buckets/date_histogram.ts index d600b16f5676..7701f1bbcb4d 100644 --- a/src/plugins/data/public/search/aggs/buckets/date_histogram.ts +++ b/src/plugins/data/public/search/aggs/buckets/date_histogram.ts @@ -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({ - name: BUCKET_TYPES.DATE_HISTOGRAM, - title: i18n.translate('data.search.aggs.buckets.dateHistogramTitle', { - defaultMessage: 'Date Histogram', - }), - ordered: { - date: true, - }, - makeLabel(agg) { - let output: Record = {}; +export const getDateHistogramBucketAgg = ({ + uiSettings, + query, +}: DateHistogramBucketAggDependencies) => + new BucketAggType({ + name: BUCKET_TYPES.DATE_HISTOGRAM, + title: i18n.translate('data.search.aggs.buckets.dateHistogramTitle', { + defaultMessage: 'Date Histogram', + }), + ordered: { + date: true, + }, + makeLabel(agg) { + let output: Record = {}; - 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; + 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; + return { + buckets: { + configurable: true, + get() { + if (buckets) return buckets; - const { timefilter } = getQueryService().timefilter; - buckets = new TimeBuckets({ uiSettings }); - updateTimeBuckets(this, timefilter, buckets); + 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); + 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'); - } + if (!DateFieldFormat) { + throw new Error('Unable to retrieve Date Field Format'); + } - return new DateFieldFormat( + return new DateFieldFormat( + { + pattern: agg.buckets.getScaledDateFormat(), + }, + (key: string) => uiSettings.get(key) + ); + }, + params: [ { - pattern: agg.buckets.getScaledDateFormat(), + 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; + } + }, }, - (key: string) => getUiSettings().get(key) - ); - }, - params: [ - { - name: 'field', - type: 'field', - filterFieldTypes: KBN_FIELD_TYPES.DATE, - default(agg: IBucketDateHistogramAggConfig) { - return agg.getIndexPattern().timeFieldName; + { + name: 'timeRange', + default: null, + write: noop, }, - onChange(agg: IBucketDateHistogramAggConfig) { - if (_.get(agg, 'params.interval') === 'auto' && !agg.fieldIsTimeField()) { - delete agg.params.interval; - } + { + name: 'useNormalizedEsInterval', + default: true, + write: noop, }, - }, - { - 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'); - } + { + 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 }); + 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); + // 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 { 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; + 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 (isMetricAggType(type)) { + return type.isScalable(); + } + }); + if (all) { + output.metricScale = interval.scale; + output.metricScaleText = interval.preScaled.description; } - }); - 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) { - 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: '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: '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; + { + 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(), - }; + if (val.min != null || val.max != null) { + output.params.extended_bounds = { + min: moment(val.min).valueOf(), + max: moment(val.max).valueOf(), + }; - return; - } + return; + } + }, }, - }, - ], -}); + ], + }); diff --git a/src/plugins/data/public/search/aggs/buckets/date_range.test.ts b/src/plugins/data/public/search/aggs/buckets/date_range.test.ts index 03a453836e11..4ea550492fa0 100644 --- a/src/plugins/data/public/search/aggs/buckets/date_range.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/date_range.test.ts @@ -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', () => { + let aggTypesDependencies: DateRangeBucketAggDependencies; + beforeEach(() => { - mockDataServices(); - }); + const { uiSettings } = coreMock.createSetup(); - const typesRegistry = mockAggTypesRegistry([dateRangeBucketAgg]); + aggTypesDependencies = { + uiSettings, + }; + }); const getAggConfigs = (params: Record = {}, 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'); }); }); diff --git a/src/plugins/data/public/search/aggs/buckets/date_range.ts b/src/plugins/data/public/search/aggs/buckets/date_range.ts index 59e78af2d7b9..8133a47ec724 100644 --- a/src/plugins/data/public/search/aggs/buckets/date_range.ts +++ b/src/plugins/data/public/search/aggs/buckets/date_range.ts @@ -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'; }, - { - 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'); + params: [ + { + name: 'field', + type: 'field', + filterFieldTypes: KBN_FIELD_TYPES.DATE, + default(agg: IBucketAggConfig) { + return agg.getIndexPattern().timeFieldName; + }, + }, + { + 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 config = getUiSettings(); - const detectedTimezone = moment.tz.guess(); - const tzOffset = moment().format('Z'); - const isDefaultTimezone = config.isDefault('dateFormat:tz'); + 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 : config.get('dateFormat:tz'); - } - output.params.time_zone = tz; + tz = isDefaultTimezone ? detectedTimezone || tzOffset : uiSettings.get('dateFormat:tz'); + } + output.params.time_zone = tz; + }, }, - }, - ], -}); + ], + }); diff --git a/src/plugins/data/public/search/aggs/buckets/filters.ts b/src/plugins/data/public/search/aggs/buckets/filters.ts index 0ad28b8be213..8b9aca87f873 100644 --- a/src/plugins/data/public/search/aggs/buckets/filters.ts +++ b/src/plugins/data/public/search/aggs/buckets/filters.ts @@ -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 }) { }, ], }); -} diff --git a/src/plugins/data/public/search/aggs/buckets/geo_hash.test.ts b/src/plugins/data/public/search/aggs/buckets/geo_hash.test.ts index 09dd03c75915..408cdf22bcbc 100644 --- a/src/plugins/data/public/search/aggs/buckets/geo_hash.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/geo_hash.test.ts @@ -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) => { const indexPattern = { id: '1234', @@ -63,7 +61,7 @@ describe('Geohash Agg', () => { }, }, ], - { typesRegistry } + { typesRegistry: mockAggTypesRegistry() } ); }; diff --git a/src/plugins/data/public/search/aggs/buckets/histogram.test.ts b/src/plugins/data/public/search/aggs/buckets/histogram.test.ts index 07cf022dca83..c61b4ff37935 100644 --- a/src/plugins/data/public/search/aggs/buckets/histogram.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/histogram.test.ts @@ -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', () => { + let aggTypesDependencies: HistogramBucketAggDependencies; + beforeEach(() => { - mockDataServices(); - }); + const { uiSettings, notifications } = coreMock.createSetup(); - const typesRegistry = mockAggTypesRegistry([histogramBucketAgg]); + aggTypesDependencies = { + uiSettings, + notifications, + }; + }); const getAggConfigs = (params: Record) => { 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; beforeEach(() => { - histogramType = histogramBucketAgg; + histogramType = getHistogramBucketAgg(aggTypesDependencies); }); it('is ordered', () => { @@ -150,6 +158,14 @@ describe('Histogram Agg', () => { params?: Record, 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', () => { diff --git a/src/plugins/data/public/search/aggs/buckets/histogram.ts b/src/plugins/data/public/search/aggs/buckets/histogram.ts index 7ccd5ae4bf98..bbffc0912bf0 100644 --- a/src/plugins/data/public/search/aggs/buckets/histogram.ts +++ b/src/plugins/data/public/search/aggs/buckets/histogram.ts @@ -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({ - 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; +export const getHistogramBucketAgg = ({ + uiSettings, + notifications, +}: HistogramBucketAggDependencies) => + new BucketAggType({ + 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; + getAutoBounds: { + configurable: true, + value() { + return autoBounds; + }, }, - }, - }; - }, - 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'), - }); - }) - .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.', - }) - ); - }); + params: [ + { + name: 'field', + type: 'field', + filterFieldTypes: KBN_FIELD_TYPES.NUMBER, }, - 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; - - const config = getUiSettings(); - if (bars > config.get('histogram:maxBars')) { - const minInterval = range / config.get('histogram:maxBars'); - - // 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; + { + /* + * 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'), + }); + }) + .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; + + if (bars > uiSettings.get('histogram:maxBars')) { + const minInterval = range / uiSettings.get('histogram:maxBars'); + + // 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; - - 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; + 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; + } } - } - 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, - }, - ], -}); + ], + }); diff --git a/src/plugins/data/public/search/aggs/buckets/range.test.ts b/src/plugins/data/public/search/aggs/buckets/range.test.ts index d9e1af149524..bf3711543ae8 100644 --- a/src/plugins/data/public/search/aggs/buckets/range.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/range.test.ts @@ -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]) } ); }; diff --git a/src/plugins/data/public/search/aggs/buckets/significant_terms.test.ts b/src/plugins/data/public/search/aggs/buckets/significant_terms.test.ts index cee3ed506c29..1c221126c35e 100644 --- a/src/plugins/data/public/search/aggs/buckets/significant_terms.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/significant_terms.test.ts @@ -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 = {}) => { 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'); diff --git a/src/plugins/data/public/search/aggs/buckets/terms.test.ts b/src/plugins/data/public/search/aggs/buckets/terms.test.ts index 9a4f28afd3ed..280d78f6620b 100644 --- a/src/plugins/data/public/search/aggs/buckets/terms.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/terms.test.ts @@ -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 = {}) => { const indexPattern = { id: '1234', @@ -48,7 +47,7 @@ describe('Terms Agg', () => { type: BUCKET_TYPES.TERMS, }, ], - { typesRegistry } + { typesRegistry: mockAggTypesRegistry() } ); }; diff --git a/src/plugins/data/public/search/aggs/index.test.ts b/src/plugins/data/public/search/aggs/index.test.ts index b5dedc9d45e8..8c0e47763c29 100644 --- a/src/plugins/data/public/search/aggs/index.test.ts +++ b/src/plugins/data/public/search/aggs/index.test.ts @@ -22,24 +22,29 @@ import { getAggTypes } from './index'; import { isBucketAggType } from './buckets/_bucket_agg_type'; import { isMetricAggType } from './metrics/metric_agg_type'; +import { QueryStart } from '../../query'; -const aggTypes = getAggTypes({ uiSettings: coreMock.createStart().uiSettings }); +describe('AggTypesComponent', () => { + const core = coreMock.createSetup(); + const aggTypes = getAggTypes({ + uiSettings: core.uiSettings, + notifications: core.notifications, + query: {} as QueryStart, + }); -const bucketAggs = aggTypes.buckets; -const metricAggs = aggTypes.metrics; + const { buckets, metrics } = aggTypes; -describe('AggTypesComponent', () => { 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(); }); }); diff --git a/src/plugins/data/public/search/aggs/test_helpers/mock_agg_types_registry.ts b/src/plugins/data/public/search/aggs/test_helpers/mock_agg_types_registry.ts index 1ebd0ea29c9f..57d27b7da631 100644 --- a/src/plugins/data/public/search/aggs/test_helpers/mock_agg_types_registry.ts +++ b/src/plugins/data/public/search/aggs/test_helpers/mock_agg_types_registry.ts @@ -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 | 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)); } diff --git a/src/plugins/data/public/search/search_service.test.ts b/src/plugins/data/public/search/search_service.test.ts index 4bf027b07b83..19308dd387d3 100644 --- a/src/plugins/data/public/search/search_service.test.ts +++ b/src/plugins/data/public/search/search_service.test.ts @@ -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'); }); diff --git a/src/plugins/data/public/search/search_service.ts b/src/plugins/data/public/search/search_service.ts index 311a8a2fc6f6..dc1c99f76d59 100644 --- a/src/plugins/data/public/search/search_service.ts +++ b/src/plugins/data/public/search/search_service.ts @@ -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 { 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)); diff --git a/src/plugins/data/public/services.ts b/src/plugins/data/public/services.ts index 2af87d84b780..199ba17b3b81 100644 --- a/src/plugins/data/public/services.ts +++ b/src/plugins/data/public/services.ts @@ -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';