Skip to content

Commit

Permalink
Add option to drop partial buckets from date_histogram visuals (#19979)
Browse files Browse the repository at this point in the history
* Add Drop Partials option to date histogram agg settings UI

* Add timeRange to aggOpts and parse in _response_writer

* Implement dropPartials method in TabifyBuckets

* Fixed a couple issues

* Fixed issue with undefined timeRange

* Use braces for conditionals
  • Loading branch information
blfrantz authored and timroes committed Aug 30, 2018
1 parent 34b5f22 commit 25761fb
Show file tree
Hide file tree
Showing 9 changed files with 177 additions and 6 deletions.
75 changes: 75 additions & 0 deletions src/ui/public/agg_response/tabify/__tests__/_buckets.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
});
});
29 changes: 29 additions & 0 deletions src/ui/public/agg_response/tabify/__tests__/_response_writer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 () {
Expand Down
38 changes: 35 additions & 3 deletions src/ui/public/agg_response/tabify/_buckets.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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) {
Expand Down Expand Up @@ -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 };
9 changes: 9 additions & 0 deletions src/ui/public/agg_response/tabify/_response_writer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/ui/public/agg_response/tabify/tabify.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
8 changes: 8 additions & 0 deletions src/ui/public/agg_types/buckets/date_histogram.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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',
Expand Down
11 changes: 11 additions & 0 deletions src/ui/public/agg_types/controls/drop_partials.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<div class="checkbox" ng-if="agg.params.field.name === agg.vis.indexPattern.timeFieldName">
<label>
<input ng-model="agg.params.drop_partials" type="checkbox">
Drop partial buckets
&nbsp;
<icon-tip
position="'right'"
content="'Remove buckets that span time outside the time range so the histogram doesn\'t start and end with incomplete buckets.'"
></icon-tip>
</label>
</div>
6 changes: 5 additions & 1 deletion src/ui/public/vis/response_handlers/basic.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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);
Expand Down
5 changes: 4 additions & 1 deletion src/ui/public/vis/response_handlers/tabify.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
Expand Down

0 comments on commit 25761fb

Please sign in to comment.