diff --git a/src/legacy/ui/public/agg_types/__tests__/buckets/_date_range.js b/src/legacy/ui/public/agg_types/__tests__/buckets/_date_range.js index 10ac4f3befeb7..94603dfa69a66 100644 --- a/src/legacy/ui/public/agg_types/__tests__/buckets/_date_range.js +++ b/src/legacy/ui/public/agg_types/__tests__/buckets/_date_range.js @@ -20,6 +20,7 @@ import { set } from 'lodash'; import expect from '@kbn/expect'; import sinon from 'sinon'; import ngMock from 'ng_mock'; +import { aggTypes } from '../..'; import AggParamWriterProvider from '../agg_param_writer'; import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; import chrome from '../../../chrome'; @@ -38,6 +39,15 @@ describe('date_range params', function () { timeField = indexPattern.timeFieldName; paramWriter = new AggParamWriter({ aggType: 'date_range' }); })); + + describe('getKey', () => { + const dateRange = aggTypes.buckets.find(agg => agg.name === 'date_range'); + it('should return object', () => { + const bucket = { from: 'from-date', to: 'to-date', key: 'from-dateto-date' }; + expect(dateRange.getKey(bucket)).to.equal({ from: 'from-date', to: 'to-date' }); + }); + }); + describe('time_zone', () => { beforeEach(() => { sinon.stub(config, 'get'); diff --git a/src/legacy/ui/public/agg_types/__tests__/buckets/create_filter/date_range.js b/src/legacy/ui/public/agg_types/__tests__/buckets/create_filter/date_range.js index 404eebf153dcc..3ba03f232428f 100644 --- a/src/legacy/ui/public/agg_types/__tests__/buckets/create_filter/date_range.js +++ b/src/legacy/ui/public/agg_types/__tests__/buckets/create_filter/date_range.js @@ -53,13 +53,15 @@ describe('AggConfig Filters', function () { }); const aggConfig = vis.aggs.byName('date_range')[0]; - const filter = createFilterDateRange(aggConfig, 'February 1st, 2015 to February 7th, 2015'); + const from = new Date('1 Feb 2015'); + const to = new Date('7 Feb 2015'); + const filter = createFilterDateRange(aggConfig, { from: from.valueOf(), to: to.valueOf() }); expect(filter).to.have.property('range'); expect(filter).to.have.property('meta'); expect(filter.meta).to.have.property('index', indexPattern.id); expect(filter.range).to.have.property('@timestamp'); - expect(filter.range['@timestamp']).to.have.property('gte', moment(new Date('1 Feb 2015')).toISOString()); - expect(filter.range['@timestamp']).to.have.property('lt', moment(new Date('7 Feb 2015')).toISOString()); + expect(filter.range['@timestamp']).to.have.property('gte', moment(from).toISOString()); + expect(filter.range['@timestamp']).to.have.property('lt', moment(to).toISOString()); }); }); }); diff --git a/src/legacy/ui/public/agg_types/__tests__/buckets/create_filter/ip_range.js b/src/legacy/ui/public/agg_types/__tests__/buckets/create_filter/ip_range.js index 2f7623d532d20..e29ebd689db20 100644 --- a/src/legacy/ui/public/agg_types/__tests__/buckets/create_filter/ip_range.js +++ b/src/legacy/ui/public/agg_types/__tests__/buckets/create_filter/ip_range.js @@ -55,7 +55,7 @@ describe('AggConfig Filters', function () { }); const aggConfig = vis.aggs.byName('ip_range')[0]; - const filter = createFilterIpRange(aggConfig, '0.0.0.0 to 1.1.1.1'); + const filter = createFilterIpRange(aggConfig, { type: 'fromTo', from: '0.0.0.0', to: '1.1.1.1' }); expect(filter).to.have.property('range'); expect(filter).to.have.property('meta'); expect(filter.meta).to.have.property('index', indexPattern.id); @@ -85,7 +85,7 @@ describe('AggConfig Filters', function () { }); const aggConfig = vis.aggs.byName('ip_range')[0]; - const filter = createFilterIpRange(aggConfig, '67.129.65.201/27'); + const filter = createFilterIpRange(aggConfig, { type: 'mask', mask: '67.129.65.201/27' }); expect(filter).to.have.property('range'); expect(filter).to.have.property('meta'); expect(filter.meta).to.have.property('index', indexPattern.id); diff --git a/src/legacy/ui/public/agg_types/buckets/create_filter/date_range.ts b/src/legacy/ui/public/agg_types/buckets/create_filter/date_range.ts index b9d6d63ea7793..cd4b0ffc215b0 100644 --- a/src/legacy/ui/public/agg_types/buckets/create_filter/date_range.ts +++ b/src/legacy/ui/public/agg_types/buckets/create_filter/date_range.ts @@ -18,19 +18,15 @@ */ import { buildRangeFilter, RangeFilterParams } from '@kbn/es-query'; -import { npSetup } from 'ui/new_platform'; +import moment from 'moment'; import { IBucketAggConfig } from '../_bucket_agg_type'; +import { DateRangeKey } from '../date_range'; -// @ts-ignore -import { dateRange } from '../../../utils/date_range'; - -export const createFilterDateRange = (agg: IBucketAggConfig, rangeString: string) => { - const range = dateRange.parse(rangeString, npSetup.core.uiSettings.get('dateFormat')); - +export const createFilterDateRange = (agg: IBucketAggConfig, { from, to }: DateRangeKey) => { const filter: RangeFilterParams = {}; - if (range.from) filter.gte = range.from.toISOString(); - if (range.to) filter.lt = range.to.toISOString(); - if (range.to && range.from) filter.format = 'strict_date_optional_time'; + if (from) filter.gte = moment(from).toISOString(); + if (to) filter.lt = moment(to).toISOString(); + if (to && from) filter.format = 'strict_date_optional_time'; return buildRangeFilter(agg.params.field, filter, agg.getIndexPattern()); }; diff --git a/src/legacy/ui/public/agg_types/buckets/create_filter/ip_range.ts b/src/legacy/ui/public/agg_types/buckets/create_filter/ip_range.ts index 182e87248dcc7..83769578725f2 100644 --- a/src/legacy/ui/public/agg_types/buckets/create_filter/ip_range.ts +++ b/src/legacy/ui/public/agg_types/buckets/create_filter/ip_range.ts @@ -20,18 +20,17 @@ import { buildRangeFilter, RangeFilterParams } from '@kbn/es-query'; import { CidrMask } from '../../../utils/cidr_mask'; import { IBucketAggConfig } from '../_bucket_agg_type'; +import { IpRangeKey } from '../ip_range'; -export const createFilterIpRange = (aggConfig: IBucketAggConfig, key: string) => { +export const createFilterIpRange = (aggConfig: IBucketAggConfig, key: IpRangeKey) => { let range: RangeFilterParams; - if (aggConfig.params.ipRangeType === 'mask') { - range = new CidrMask(key).getRange(); + if (key.type === 'mask') { + range = new CidrMask(key.mask).getRange(); } else { - const [from, to] = key.split(/\s+to\s+/); - range = { - from: from === '-Infinity' ? -Infinity : from, - to: to === 'Infinity' ? Infinity : to, + from: key.from ? key.from : -Infinity, + to: key.to ? key.to : Infinity, }; } diff --git a/src/legacy/ui/public/agg_types/buckets/date_range.ts b/src/legacy/ui/public/agg_types/buckets/date_range.ts index 336acb7ec7179..dd7f0cb972ae2 100644 --- a/src/legacy/ui/public/agg_types/buckets/date_range.ts +++ b/src/legacy/ui/public/agg_types/buckets/date_range.ts @@ -24,6 +24,7 @@ import { BUCKET_TYPES } from './bucket_agg_types'; import { BucketAggType } from './_bucket_agg_type'; import { createFilterDateRange } from './create_filter/date_range'; import { AggConfig } from '../agg_config'; +import { FieldFormat } from '../../../../../plugins/data/common/field_formats'; import { DateRangesParamEditor } from '../../vis/editors/default/controls/date_ranges'; // @ts-ignore @@ -36,16 +37,24 @@ const dateRangeTitle = i18n.translate('common.ui.aggTypes.buckets.dateRangeTitle defaultMessage: 'Date Range', }); +export interface DateRangeKey { + from: number; + to: number; +} + export const dateRangeBucketAgg = new BucketAggType({ name: BUCKET_TYPES.DATE_RANGE, title: dateRangeTitle, createFilter: createFilterDateRange, - getKey(bucket, key, agg) { - const formatter = agg.fieldOwnFormatter('text', fieldFormats.getDefaultInstance('date')); - return dateRange.toString(bucket, formatter); + getKey({ from, to }): DateRangeKey { + return { from, to }; }, - getFormat() { - return fieldFormats.getDefaultInstance('string'); + getFormat(agg) { + const formatter = agg.fieldOwnFormatter('text', fieldFormats.getDefaultInstance('date')); + const DateRangeFormat = FieldFormat.from(function(range: DateRangeKey) { + return dateRange.toString(range, formatter); + }); + return new DateRangeFormat(); }, makeLabel(aggConfig) { return aggConfig.getFieldDisplayName() + ' date ranges'; diff --git a/src/legacy/ui/public/agg_types/buckets/ip_range.ts b/src/legacy/ui/public/agg_types/buckets/ip_range.ts index 44e813453b302..bbc91b0768f6b 100644 --- a/src/legacy/ui/public/agg_types/buckets/ip_range.ts +++ b/src/legacy/ui/public/agg_types/buckets/ip_range.ts @@ -17,11 +17,15 @@ * under the License. */ -import { get, noop, map, omit, isNull } from 'lodash'; +import { noop, map, omit, isNull } from 'lodash'; import { i18n } from '@kbn/i18n'; import { BucketAggType, IBucketAggConfig } from './_bucket_agg_type'; import { IpRangeTypeParamEditor } from '../../vis/editors/default/controls/ip_range_type'; import { IpRangesParamEditor } from '../../vis/editors/default/controls/ip_ranges'; +// @ts-ignore +import { fieldFormats } from '../../registry/field_formats'; +import { FieldFormat } from '../../../../../plugins/data/common/field_formats'; +import { ipRange } from '../../utils/ip_range'; import { BUCKET_TYPES } from './bucket_agg_types'; // @ts-ignore @@ -32,16 +36,26 @@ const ipRangeTitle = i18n.translate('common.ui.aggTypes.buckets.ipRangeTitle', { defaultMessage: 'IPv4 Range', }); +export type IpRangeKey = + | { type: 'mask'; mask: string } + | { type: 'range'; from: string; to: string }; + export const ipRangeBucketAgg = new BucketAggType({ name: BUCKET_TYPES.IP_RANGE, title: ipRangeTitle, createFilter: createFilterIpRange, - getKey(bucket, key) { - if (key) return key; - const from = get(bucket, 'from', '-Infinity'); - const to = get(bucket, 'to', 'Infinity'); - - return `${from} to ${to}`; + getKey(bucket, key, agg): IpRangeKey { + if (agg.params.ipRangeType === 'mask') { + return { type: 'mask', mask: key }; + } + return { type: 'range', from: bucket.from, to: bucket.to }; + }, + getFormat(agg) { + const formatter = agg.fieldOwnFormatter('text', fieldFormats.getDefaultInstance('ip')); + const IpRangeFormat = FieldFormat.from(function(range: IpRangeKey) { + return ipRange.toString(range, formatter); + }); + return new IpRangeFormat(); }, makeLabel(aggConfig) { return i18n.translate('common.ui.aggTypes.buckets.ipRangeLabel', { diff --git a/src/legacy/ui/public/utils/date_range.js b/src/legacy/ui/public/utils/date_range.ts similarity index 52% rename from src/legacy/ui/public/utils/date_range.js rename to src/legacy/ui/public/utils/date_range.ts index 97b869b2bfedf..ca44183b8d68b 100644 --- a/src/legacy/ui/public/utils/date_range.js +++ b/src/legacy/ui/public/utils/date_range.ts @@ -17,28 +17,16 @@ * under the License. */ -import moment from 'moment'; +import { DateRangeKey } from '../agg_types/buckets/date_range'; export const dateRange = { - toString: function (range, format) { - if (!range.from) { - return 'Before ' + format(range.to); - } else if (!range.to) { - return 'After ' + format(range.from); + toString({ from, to }: DateRangeKey, format: (val: any) => string) { + if (!from) { + return 'Before ' + format(to); + } else if (!to) { + return 'After ' + format(from); } else { - return format(range.from) + ' to ' + format(range.to); + return format(from) + ' to ' + format(to); } }, - parse: function (rangeString, format) { - let chunks = rangeString.split(' to '); - if (chunks.length === 2) return { from: moment(chunks[0], format), to: moment(chunks[1], format) }; - - chunks = rangeString.split('Before '); - if (chunks.length === 2) return { to: moment(chunks[1], format) }; - - chunks = rangeString.split('After '); - if (chunks.length === 2) return { from: moment(chunks[1], format) }; - - throw new Error('Error attempting to parse date range: ' + rangeString); - } }; diff --git a/src/legacy/ui/public/utils/ip_range.ts b/src/legacy/ui/public/utils/ip_range.ts new file mode 100644 index 0000000000000..45ce21709d68c --- /dev/null +++ b/src/legacy/ui/public/utils/ip_range.ts @@ -0,0 +1,31 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { IpRangeKey } from '../agg_types/buckets/ip_range'; + +export const ipRange = { + toString(range: IpRangeKey, format: (val: any) => string) { + if (range.type === 'mask') { + return format(range.mask); + } + const from = range.from ? format(range.from) : '-Infinity'; + const to = range.to ? format(range.to) : 'Infinity'; + return `${from} to ${to}`; + }, +}; diff --git a/src/legacy/ui/public/visualize/loader/pipeline_helpers/utilities.ts b/src/legacy/ui/public/visualize/loader/pipeline_helpers/utilities.ts index 0f8761b715c0d..c12bd222663ae 100644 --- a/src/legacy/ui/public/visualize/loader/pipeline_helpers/utilities.ts +++ b/src/legacy/ui/public/visualize/loader/pipeline_helpers/utilities.ts @@ -28,6 +28,10 @@ import { tabifyGetColumns } from '../../../agg_response/tabify/_get_columns'; import chrome from '../../../chrome'; // @ts-ignore import { fieldFormats } from '../../../registry/field_formats'; +import { dateRange } from '../../../utils/date_range'; +import { ipRange } from '../../../utils/ip_range'; +import { DateRangeKey } from '../../../agg_types/buckets/date_range'; +import { IpRangeKey } from '../../../agg_types/buckets/ip_range'; interface TermsFieldFormatParams { otherBucketLabel: string; @@ -58,7 +62,8 @@ const getFieldFormat = (id: string | undefined, params: object = {}) => { export const createFormat = (agg: AggConfig): SerializedFieldFormat => { const format: SerializedFieldFormat = agg.params.field ? agg.params.field.format.toJSON() : {}; const formats: Record SerializedFieldFormat> = { - date_range: () => ({ id: 'string' }), + date_range: () => ({ id: 'date_range', params: format }), + ip_range: () => ({ id: 'ip_range', params: format }), percentile_ranks: () => ({ id: 'percent' }), count: () => ({ id: 'number' }), cardinality: () => ({ id: 'number' }), @@ -109,6 +114,20 @@ export const getFormat: FormatFactory = (mapping = {}) => { }); }); return new RangeFormat(); + } else if (id === 'date_range') { + const nestedFormatter = mapping.params as SerializedFieldFormat; + const DateRangeFormat = FieldFormat.from((range: DateRangeKey) => { + const format = getFieldFormat(nestedFormatter.id, nestedFormatter.params); + return dateRange.toString(range, format.convert.bind(format)); + }); + return new DateRangeFormat(); + } else if (id === 'ip_range') { + const nestedFormatter = mapping.params as SerializedFieldFormat; + const IpRangeFormat = FieldFormat.from((range: IpRangeKey) => { + const format = getFieldFormat(nestedFormatter.id, nestedFormatter.params); + return ipRange.toString(range, format.convert.bind(format)); + }); + return new IpRangeFormat(); } else if (isTermsFieldFormat(mapping) && mapping.params) { const params = mapping.params; return {