diff --git a/src/ui/public/agg_response/tabify/__tests__/_buckets.js b/src/ui/public/agg_response/tabify/__tests__/_buckets.js index 785a62ff82657e..8be13c1ad6c129 100644 --- a/src/ui/public/agg_response/tabify/__tests__/_buckets.js +++ b/src/ui/public/agg_response/tabify/__tests__/_buckets.js @@ -80,4 +80,79 @@ describe('Buckets wrapper', function () { expect(buckets).to.have.length(1); }); }); + + describe('drop_partial option', function () { + const aggResp = { + buckets: [ + { key: 0, value: {} }, + { key: 100, value: {} }, + { key: 200, value: {} }, + { key: 300, value: {} } + ] + }; + + it('drops partial buckets when enabled', function () { + const aggParams = { + drop_partials: true, + field: { + name: 'date' + } + }; + const timeRange = { + gte: 150, + lte: 350, + name: 'date' + }; + const buckets = new TabifyBuckets(aggResp, aggParams, timeRange); + expect(buckets).to.have.length(1); + }); + + it('keeps partial buckets when disabled', function () { + const aggParams = { + drop_partials: false, + field: { + name: 'date' + } + }; + const timeRange = { + gte: 150, + lte: 350, + name: 'date' + }; + const buckets = new TabifyBuckets(aggResp, aggParams, timeRange); + expect(buckets).to.have.length(4); + }); + + it('keeps aligned buckets when enabled', function () { + const aggParams = { + drop_partials: true, + field: { + name: 'date' + } + }; + const timeRange = { + gte: 100, + lte: 400, + name: 'date' + }; + const buckets = new TabifyBuckets(aggResp, aggParams, timeRange); + expect(buckets).to.have.length(3); + }); + + it('does not drop buckets for non-timerange fields', function () { + const aggParams = { + drop_partials: true, + field: { + name: 'other_time' + } + }; + const timeRange = { + gte: 150, + lte: 350, + name: 'date' + }; + const buckets = new TabifyBuckets(aggResp, aggParams, timeRange); + expect(buckets).to.have.length(4); + }); + }); }); diff --git a/src/ui/public/agg_response/tabify/__tests__/_response_writer.js b/src/ui/public/agg_response/tabify/__tests__/_response_writer.js index 3835e4ceb33985..d9e04af7253db6 100644 --- a/src/ui/public/agg_response/tabify/__tests__/_response_writer.js +++ b/src/ui/public/agg_response/tabify/__tests__/_response_writer.js @@ -97,6 +97,35 @@ describe('TabbedAggResponseWriter class', function () { expect(writer.splitStack).to.have.length(1); expect(writer.splitStack[0]).to.be(writer.root); }); + + describe('sets timeRange', function () { + it('to the first nested object\'s range', function () { + const vis = new Vis(indexPattern, { type: 'histogram', aggs: [] }); + const range = { + gte: 0, + lte: 100 + }; + + const writer = new TabbedAggResponseWriter(vis.getAggConfig(), { + timeRange: { + '@timestamp': range + } + }); + + expect(writer.timeRange.gte).to.be(range.gte); + expect(writer.timeRange.lte).to.be(range.lte); + expect(writer.timeRange.name).to.be('@timestamp'); + }); + + it('to undefined if no nested object', function () { + const vis = new Vis(indexPattern, { type: 'histogram', aggs: [] }); + + const writer = new TabbedAggResponseWriter(vis.getAggConfig(), { + timeRange: {} + }); + expect(writer).to.have.property('timeRange', undefined); + }); + }); }); describe('', function () { diff --git a/src/ui/public/agg_response/tabify/_buckets.js b/src/ui/public/agg_response/tabify/_buckets.js index 6beadefe5e5b91..846536b9831f94 100644 --- a/src/ui/public/agg_response/tabify/_buckets.js +++ b/src/ui/public/agg_response/tabify/_buckets.js @@ -19,7 +19,7 @@ import _ from 'lodash'; -function TabifyBuckets(aggResp, aggParams) { +function TabifyBuckets(aggResp, aggParams, timeRange) { if (_.has(aggResp, 'buckets')) { this.buckets = aggResp.buckets; } else if (aggResp) { @@ -38,7 +38,12 @@ function TabifyBuckets(aggResp, aggParams) { this.length = this.buckets.length; } - if (this.length && aggParams) this._orderBucketsAccordingToParams(aggParams); + if (this.length && aggParams) { + this._orderBucketsAccordingToParams(aggParams); + if (aggParams.drop_partials) { + this._dropPartials(aggParams, timeRange); + } + } } TabifyBuckets.prototype.forEach = function (fn) { @@ -75,10 +80,37 @@ TabifyBuckets.prototype._orderBucketsAccordingToParams = function (params) { ranges = params.ipRangeType === 'mask' ? ranges.mask : ranges.fromTo; } this.buckets = ranges.map(range => { - if (range.mask) return this.buckets.find(el => el.key === range.mask); + if (range.mask) { + return this.buckets.find(el => el.key === range.mask); + } return this.buckets.find(el => this._isRangeEqual(el, range)); }); } }; +// dropPartials should only be called if the aggParam setting is enabled, +// and the agg field is the same as the Time Range. +TabifyBuckets.prototype._dropPartials = function (params, timeRange) { + if (!timeRange || + this.buckets.length <= 1 || + this.objectMode || + params.field.name !== timeRange.name) { + return; + } + + const interval = this.buckets[1].key - this.buckets[0].key; + + this.buckets = this.buckets.filter(bucket => { + if (bucket.key < timeRange.gte) { + return false; + } + if (bucket.key + interval > timeRange.lte) { + return false; + } + return true; + }); + + this.length = this.buckets.length; +}; + export { TabifyBuckets }; diff --git a/src/ui/public/agg_response/tabify/_response_writer.js b/src/ui/public/agg_response/tabify/_response_writer.js index 1d0bd1c23863cb..0b6d4cb947466f 100644 --- a/src/ui/public/agg_response/tabify/_response_writer.js +++ b/src/ui/public/agg_response/tabify/_response_writer.js @@ -70,6 +70,15 @@ function TabbedAggResponseWriter(aggs, opts) { this.root = new TabifyTableGroup(); this.acrStack = []; this.splitStack = [this.root]; + + // Extract the time range object if provided + if (this.opts.timeRange) { + const timeRangeKey = Object.keys(this.opts.timeRange)[0]; + this.timeRange = this.opts.timeRange[timeRangeKey]; + if (this.timeRange) { + this.timeRange.name = timeRangeKey; + } + } } /** diff --git a/src/ui/public/agg_response/tabify/tabify.js b/src/ui/public/agg_response/tabify/tabify.js index 05ec560d5214a3..7e91670d83fb3a 100644 --- a/src/ui/public/agg_response/tabify/tabify.js +++ b/src/ui/public/agg_response/tabify/tabify.js @@ -49,7 +49,7 @@ function collectBucket(write, bucket, key, aggScale) { switch (agg.type.type) { case 'buckets': - const buckets = new TabifyBuckets(bucket[agg.id], agg.params); + const buckets = new TabifyBuckets(bucket[agg.id], agg.params, write.timeRange); if (buckets.length) { const splitting = write.canSplit && agg.schema.name === 'split'; if (splitting) { diff --git a/src/ui/public/agg_types/buckets/date_histogram.js b/src/ui/public/agg_types/buckets/date_histogram.js index c9c26689f3d539..39cd82e126b6cc 100644 --- a/src/ui/public/agg_types/buckets/date_histogram.js +++ b/src/ui/public/agg_types/buckets/date_histogram.js @@ -28,6 +28,7 @@ import { TimeBuckets } from '../../time_buckets'; import { createFilterDateHistogram } from './create_filter/date_histogram'; import { intervalOptions } from './_interval_options'; import intervalTemplate from '../controls/time_interval.html'; +import dropPartialTemplate from '../controls/drop_partials.html'; const config = chrome.getUiSettingsClient(); const detectedTimezone = tzDetect.determine().name(); @@ -147,6 +148,13 @@ export const dateHistogramBucketAgg = new BucketAggType({ return isDefaultTimezone ? detectedTimezone || tzOffset : config.get('dateFormat:tz'); }, }, + { + name: 'drop_partials', + default: false, + write: _.noop, + editor: dropPartialTemplate, + }, + { name: 'customInterval', default: '2h', diff --git a/src/ui/public/agg_types/controls/drop_partials.html b/src/ui/public/agg_types/controls/drop_partials.html new file mode 100644 index 00000000000000..3b3713db22f871 --- /dev/null +++ b/src/ui/public/agg_types/controls/drop_partials.html @@ -0,0 +1,11 @@ +
+ +
diff --git a/src/ui/public/vis/response_handlers/basic.js b/src/ui/public/vis/response_handlers/basic.js index d8ce9942a88983..42625fade7b595 100644 --- a/src/ui/public/vis/response_handlers/basic.js +++ b/src/ui/public/vis/response_handlers/basic.js @@ -19,6 +19,7 @@ import { AggResponseIndexProvider } from '../../agg_response'; import { TabifyTable } from '../../agg_response/tabify/_table'; +import { getTime } from 'ui/timefilter/get_time'; import { VisResponseHandlersRegistryProvider } from '../../registry/vis_response_handlers'; @@ -71,10 +72,13 @@ const BasicResponseHandlerProvider = function (Private) { resolve(aggResponse.hierarchical(vis, response)); } + const time = getTime(vis.indexPattern, vis.filters.timeRange); + const tableGroup = aggResponse.tabify(vis.getAggConfig(), response, { canSplit: true, asAggConfigResults: true, - isHierarchical: vis.isHierarchical() + isHierarchical: vis.isHierarchical(), + timeRange: time ? time.range : undefined }); let converted = convertTableGroup(vis, tableGroup); diff --git a/src/ui/public/vis/response_handlers/tabify.js b/src/ui/public/vis/response_handlers/tabify.js index 1b3cc3a1c33465..6a74fd2dbd728e 100644 --- a/src/ui/public/vis/response_handlers/tabify.js +++ b/src/ui/public/vis/response_handlers/tabify.js @@ -20,6 +20,7 @@ import _ from 'lodash'; import { AggResponseIndexProvider } from '../../agg_response'; import { VisResponseHandlersRegistryProvider } from '../../registry/vis_response_handlers'; +import { getTime } from 'ui/timefilter/get_time'; const TabifyResponseHandlerProvider = function (Private) { const aggResponse = Private(AggResponseIndexProvider); @@ -28,11 +29,13 @@ const TabifyResponseHandlerProvider = function (Private) { name: 'tabify', handler: function (vis, response) { return new Promise((resolve) => { + const time = getTime(vis.indexPattern, vis.filters.timeRange); const tableGroup = aggResponse.tabify(vis.getAggConfig(), response, { canSplit: true, asAggConfigResults: _.get(vis, 'type.responseHandlerConfig.asAggConfigResults', false), - isHierarchical: vis.isHierarchical() + isHierarchical: vis.isHierarchical(), + timeRange: time ? time.range : undefined }); resolve(tableGroup);