Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add option to drop partial buckets from date_histogram visuals #19979

Merged
merged 8 commits into from
Aug 30, 2018
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