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

WIP moving average metric aggregation #10034

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/core_plugins/metric_vis/public/metric_vis.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ function MetricVisProvider(Private) {
name: 'metric',
title: 'Metric',
min: 1,
aggFilter: ['!derivative'],
defaults: [
{ type: 'count', schema: 'metric' }
]
Expand Down
2 changes: 1 addition & 1 deletion src/core_plugins/tagcloud/public/tag_cloud_vis.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ visTypes.register(function TagCloudProvider(Private) {
title: 'Tag Size',
min: 1,
max: 1,
aggFilter: ['!std_dev', '!percentiles', '!percentile_ranks'],
aggFilter: ['!std_dev', '!percentiles', '!percentile_ranks', '!derivative'],
defaults: [
{ schema: 'metric', type: 'count' }
]
Expand Down
126 changes: 126 additions & 0 deletions src/ui/public/agg_types/__tests__/metrics/derivative.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import _ from 'lodash';
import expect from 'expect.js';
import ngMock from 'ng_mock';
import DerivativeProvider from 'ui/agg_types/metrics/derivative';
import VisProvider from 'ui/vis';
import StubbedIndexPattern from 'fixtures/stubbed_logstash_index_pattern';

describe('Derivative metric', function () {
let aggDsl;
let derivativeMetric;
let aggConfig;

function init(settings) {
ngMock.module('kibana');
ngMock.inject(function (Private) {
const Vis = Private(VisProvider);
const indexPattern = Private(StubbedIndexPattern);
derivativeMetric = Private(DerivativeProvider);

const params = settings || {
buckets_path: '1',
customMetric: null
};

const vis = new Vis(indexPattern, {
title: 'New Visualization',
type: 'metric',
params: {
fontSize: 60,
handleNoResults: true
},
aggs: [
{
id: '1',
type: 'count',
schema: 'metric'
},
{
id: '2',
type: 'derivative',
schema: 'metric',
params
}
],
listeners: {}
});

// Grab the aggConfig off the vis (we don't actually use the vis for anything else)
aggConfig = vis.aggs[1];
aggDsl = aggConfig.toDsl();
});
}

it('should return a label prefixed with Derivative of', function () {
init();
expect(derivativeMetric.makeLabel(aggConfig)).to.eql('Derivative of Count');
});

it('should return a label Derivative of max bytes', function () {
init({
buckets_path: 'custom',
customMetric: {
id:'1-orderAgg',
type: 'max',
params: { field: 'bytes' },
schema: 'orderAgg'
}
});
expect(derivativeMetric.makeLabel(aggConfig)).to.eql('Derivative of Max bytes');
});

it('should return a label prefixed with number of derivative', function () {
init({
buckets_path: 'custom',
customMetric: {
id:'2-orderAgg',
type: 'derivative',
params: {
buckets_path: 'custom',
customMetric: {
id:'2-orderAgg-orderAgg',
type: 'count',
schema: 'orderAgg'
}
},
schema: 'orderAgg'
}
});
expect(derivativeMetric.makeLabel(aggConfig)).to.eql('2. derivative of Count');
});

it('should set parent aggs', function () {
init({
buckets_path: 'custom',
customMetric: {
id:'2-orderAgg',
type: 'max',
params: { field: 'bytes' },
schema: 'orderAgg'
}
});
expect(aggDsl.parentAggs['2-orderAgg'].max.field).to.be('bytes');
});

it('should set nested parent aggs', function () {
init({
buckets_path: 'custom',
customMetric: {
id:'2-orderAgg',
type: 'derivative',
params: {
buckets_path: 'custom',
customMetric: {
id:'2-orderAgg-orderAgg',
type: 'max',
params: { field: 'bytes' },
schema: 'orderAgg'
}
},
schema: 'orderAgg'
}
});
expect(aggDsl.parentAggs['2-orderAgg'].derivative.buckets_path).to.be('2-orderAgg-orderAgg');
});

});
28 changes: 28 additions & 0 deletions src/ui/public/agg_types/controls/sub_agg.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<div ng-controller="aggParam.controller">
<div class="form-group">
<label>Metric</label>
<select
name="buckets_path"
ng-model="agg.params.buckets_path"
required
class="form-control">
<option
ng-repeat="respAgg in responseValueAggs track by respAgg.id"
value="{{respAgg.id}}"
ng-if="respAgg.type.name !== agg.type.name"
ng-disabled="rejectAgg(respAgg)"
ng-selected="agg.params.buckets_path === respAgg.id">
metric: {{safeMakeLabel(respAgg)}}
</option>
<option value="custom" ng-selected="agg.params.buckets_path === 'custom'">
Custom Metric
</option>
</select>
</div>
<ng-form name="customMetricForm" ng-if="agg.params.buckets_path === 'custom'" class="vis-editor-agg-order-agg">
<vis-editor-agg-params
agg="agg.params.customMetric"
group-name="'metrics'">
</vis-editor-agg-params>
</ng-form>
</div>
6 changes: 5 additions & 1 deletion src/ui/public/agg_types/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import AggTypesMetricsStdDeviationProvider from 'ui/agg_types/metrics/std_deviat
import AggTypesMetricsCardinalityProvider from 'ui/agg_types/metrics/cardinality';
import AggTypesMetricsPercentilesProvider from 'ui/agg_types/metrics/percentiles';
import AggTypesMetricsPercentileRanksProvider from 'ui/agg_types/metrics/percentile_ranks';
import AggTypesMetricsDerivativeProvider from 'ui/agg_types/metrics/derivative';
import AggTypesMetricsMovingAverageProvider from 'ui/agg_types/metrics/moving_avg';
import AggTypesBucketsDateHistogramProvider from 'ui/agg_types/buckets/date_histogram';
import AggTypesBucketsHistogramProvider from 'ui/agg_types/buckets/histogram';
import AggTypesBucketsRangeProvider from 'ui/agg_types/buckets/range';
Expand All @@ -34,7 +36,9 @@ export default function AggTypeService(Private) {
Private(AggTypesMetricsCardinalityProvider),
Private(AggTypesMetricsPercentilesProvider),
Private(AggTypesMetricsPercentileRanksProvider),
Private(AggTypesMetricsTopHitProvider)
Private(AggTypesMetricsTopHitProvider),
Private(AggTypesMetricsDerivativeProvider),
Private(AggTypesMetricsMovingAverageProvider),
],
buckets: [
Private(AggTypesBucketsDateHistogramProvider),
Expand Down
142 changes: 142 additions & 0 deletions src/ui/public/agg_types/metrics/derivative.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import AggTypesMetricsMetricAggTypeProvider from 'ui/agg_types/metrics/metric_agg_type';
import orderAggTemplate from 'ui/agg_types/controls/sub_agg.html';
import _ from 'lodash';
import $ from 'jquery';
import VisAggConfigProvider from 'ui/vis/agg_config';
import VisSchemasProvider from 'ui/vis/schemas';

export default function AggTypeMetricDerivativeProvider(Private) {
const DerivativeAggType = Private(AggTypesMetricsMetricAggTypeProvider);
const AggConfig = Private(VisAggConfigProvider);
const Schemas = Private(VisSchemasProvider);

const aggFilter = ['!top_hits', '!percentiles', '!median', '!std_dev'];
const orderAggSchema = (new Schemas([
{
group: 'none',
name: 'orderAgg',
title: 'Order Agg',
aggFilter: aggFilter
}
])).all[0];

return new DerivativeAggType({
name: 'derivative',
title: 'Derivative',
makeLabel: function (aggConfig) {
if (aggConfig.params.customMetric) {
let label = aggConfig.params.customMetric.makeLabel();
if (label.includes('Derivative of ')) {
label = '2. derivative of ' + label.substring('Derivative of '.length);
}
else if (label.includes('derivative of ')) {
label = (parseInt(label.substring(0, 1)) + 1) + label.substring(1);
}
else {
label = 'Derivative of ' + label;
}
return label;
}
const metric = aggConfig.vis.aggs.find(agg => agg.id === aggConfig.params.buckets_path);
return 'Derivative of ' + metric.makeLabel();
},
params: [
{
name: 'customMetric',
type: AggConfig,
default: null,
serialize: function (customMetric) {
return customMetric.toJSON();
},
deserialize: function (state, agg) {
return this.makeAgg(agg, state);
},
makeAgg: function (termsAgg, state) {
state = state || {};
state.schema = orderAggSchema;
const orderAgg = new AggConfig(termsAgg.vis, state);
orderAgg.id = termsAgg.id + '-orderAgg';
return orderAgg;
},
write: _.noop
},
{
name: 'buckets_path',
editor: orderAggTemplate,
controller: function ($scope, $element) {

$scope.safeMakeLabel = function (agg) {
try {
return agg.makeLabel();
} catch (e) {
return '- agg not valid -';
}
};

$scope.$watch('responseValueAggs', updateOrderAgg);
$scope.$watch('agg.params.buckets_path', updateOrderAgg);

$scope.$on('$destroy', function () {
if ($scope.aggForm && $scope.aggForm.agg) {
$scope.aggForm.agg.$setValidity('bucket', true);
}
});

// Returns true if the agg is not compatible with the terms bucket
$scope.rejectAgg = function (agg) {
// aggFilter elements all starts with a '!'
// so the index of agg.type.name in a filter is 1 if it is included
return Boolean(aggFilter.find((filter) => filter.indexOf(agg.type.name) === 1));
};

function checkBuckets() {
const buckets = $scope.vis.aggs.filter(agg => agg.schema.group === 'buckets');
const bucketIsHistogram = ['date_histogram', 'histogram'].includes(buckets[0].type.name);
const canUseDerivative = buckets.length === 1 && bucketIsHistogram;
if ($scope.aggForm.agg) $scope.aggForm.agg.$setValidity('bucket', canUseDerivative);
if (canUseDerivative) {
if (buckets[0].type.name === 'histogram') {
buckets[0].params.min_doc_count = 1;
}
else {
buckets[0].params.min_doc_count = 0;
}
}
}

function updateOrderAgg() {
const agg = $scope.agg;
const params = agg.params;
const bucketsPath = params.buckets_path;
const paramDef = agg.type.params.byName.customMetric;

checkBuckets();

// we aren't creating a custom aggConfig
if (bucketsPath !== 'custom') {
params.customMetric = null;
return;
}

params.customMetric = params.customMetric || paramDef.makeAgg(agg);
}
},
write: function (agg, output) {
const vis = agg.vis;
const orderAgg = agg.params.customMetric || vis.aggs.getResponseAggById(agg.params.buckets_path);

if (agg.params.customMetric && agg.params.customMetric.type.name !== 'count') {
output.parentAggs = (output.parentAggs || []).concat(orderAgg);
}

output.params = {};
if (orderAgg.type.name === 'count') {
output.params.buckets_path = '_count';
} else {
output.params.buckets_path = orderAgg.id;
}
}
}
]
});
}
Loading