Skip to content

Commit

Permalink
[TSVB] Add Positive Rate to Aggregations (elastic#59843)
Browse files Browse the repository at this point in the history
* [TSVB] Add Rate to Aggregations

* Fixing i18n labels

* Changing from rate to growth_rate; adding message to aggregation form;

* Change units to scale; change free text to combobox

* Fixing placeholder

* Fixing i18n label

* Changing from Growth Rate to Positive Rate

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
  • Loading branch information
simianhacker and elasticmachine committed Apr 9, 2020
1 parent 225312e commit 6b3b515
Show file tree
Hide file tree
Showing 10 changed files with 410 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,12 @@ const metricAggs = [
}),
value: 'filter_ratio',
},
{
label: i18n.translate('visTypeTimeseries.aggSelect.metricsAggs.positiveRateLabel', {
defaultMessage: 'Positive Rate',
}),
value: 'positive_rate',
},
{
label: i18n.translate('visTypeTimeseries.aggSelect.metricsAggs.maxLabel', {
defaultMessage: 'Max',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
/*
* 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 PropTypes from 'prop-types';
import React from 'react';
import { AggSelect } from './agg_select';
import { FieldSelect } from './field_select';
import { AggRow } from './agg_row';
import { createChangeHandler } from '../lib/create_change_handler';
import { createSelectHandler } from '../lib/create_select_handler';
import {
htmlIdGenerator,
EuiFlexGroup,
EuiFlexItem,
EuiFormLabel,
EuiFormRow,
EuiSpacer,
EuiText,
EuiLink,
EuiComboBox,
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
import { KBN_FIELD_TYPES } from '../../../../../../plugins/data/public';

const UNIT_OPTIONS = [
{
label: i18n.translate('visTypeTimeseries.units.auto', { defaultMessage: 'auto' }),
value: '',
},
{
label: i18n.translate('visTypeTimeseries.units.perMillisecond', {
defaultMessage: 'per millisecond',
}),
value: '1ms',
},
{
label: i18n.translate('visTypeTimeseries.units.perSecond', { defaultMessage: 'per second' }),
value: '1s',
},
{
label: i18n.translate('visTypeTimeseries.units.perMinute', { defaultMessage: 'per minute' }),
value: '1m',
},
{
label: i18n.translate('visTypeTimeseries.units.perHour', { defaultMessage: 'per hour' }),
value: '1h',
},
{
label: i18n.translate('visTypeTimeseries.units.perDay', { defaultMessage: 'per day' }),
value: '1d',
},
];

export const PositiveRateAgg = props => {
const defaults = { unit: '' };
const model = { ...defaults, ...props.model };

const handleChange = createChangeHandler(props.onChange, model);
const handleSelectChange = createSelectHandler(handleChange);

const htmlId = htmlIdGenerator();
const indexPattern =
(props.series.override_index_pattern && props.series.series_index_pattern) ||
props.panel.index_pattern;

const selectedUnitOptions = UNIT_OPTIONS.filter(o => o.value === model.unit);

return (
<AggRow
disableDelete={props.disableDelete}
model={props.model}
onAdd={props.onAdd}
onDelete={props.onDelete}
siblings={props.siblings}
dragHandleProps={props.dragHandleProps}
>
<EuiFlexGroup gutterSize="s">
<EuiFlexItem>
<EuiFormLabel htmlFor={htmlId('aggregation')}>
<FormattedMessage
id="visTypeTimeseries.positiveRate.aggregationLabel"
defaultMessage="Aggregation"
/>
</EuiFormLabel>
<EuiSpacer size="xs" />
<AggSelect
id={htmlId('aggregation')}
panelType={props.panel.type}
siblings={props.siblings}
value={model.type}
onChange={handleSelectChange('type')}
fullWidth
/>
</EuiFlexItem>
<EuiFlexItem>
<EuiFormRow
id={htmlId('field')}
label={
<FormattedMessage
id="visTypeTimeseries.postiveRate.fieldLabel"
defaultMessage="Field"
/>
}
fullWidth
>
<FieldSelect
fields={props.fields}
type={model.type}
restrict={KBN_FIELD_TYPES.NUMBER}
indexPattern={indexPattern}
value={model.field}
onChange={handleSelectChange('field')}
uiRestrictions={props.uiRestrictions}
fullWidth
/>
</EuiFormRow>
</EuiFlexItem>
<EuiFlexItem>
<EuiFormRow
id={htmlId('units')}
label={
<FormattedMessage
id="visTypeTimeseries.positiveRate.unitsLabel"
defaultMessage="Scale"
/>
}
fullWidth
>
<EuiComboBox
placeholder={i18n.translate('visTypeTimeseries.positiveRate.unitSelectPlaceholder', {
defaultMessage: 'Select scale...',
})}
options={UNIT_OPTIONS}
onChange={handleSelectChange('unit')}
singleSelection={{ asPlainText: true }}
selectedOptions={selectedUnitOptions}
/>
</EuiFormRow>
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer size="s" />
<EuiText size="xs" color="subdued">
<p>
<FormattedMessage
id="visTypeTimeseries.positiveRate.helpText"
defaultMessage="This aggregation should only be applied to {link}, it is a shortcut for applying max, derivative and positive only to a field."
values={{
link: (
<EuiLink href="https://en.wikipedia.org/wiki/Monotonic_function" target="_BLANK">
<FormattedMessage
id="visTypeTimeseries.positiveRate.helpTextLink"
defaultMessage="monotonically increasing numbers"
/>
</EuiLink>
),
}}
/>
</p>
</EuiText>
</AggRow>
);
};

PositiveRateAgg.propTypes = {
disableDelete: PropTypes.bool,
fields: PropTypes.object,
model: PropTypes.object,
onAdd: PropTypes.func,
onChange: PropTypes.func,
onDelete: PropTypes.func,
panel: PropTypes.object,
series: PropTypes.object,
siblings: PropTypes.array,
};
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import { PercentileRankAgg } from '../aggs/percentile_rank';
import { Static } from '../aggs/static';
import { MathAgg } from '../aggs/math';
import { TopHitAgg } from '../aggs/top_hit';
import { PositiveRateAgg } from '../aggs/positive_rate';

export const aggToComponent = {
count: StandardAgg,
Expand Down Expand Up @@ -65,4 +66,5 @@ export const aggToComponent = {
static: Static,
math: MathAgg,
top_hit: TopHitAgg,
positive_rate: PositiveRateAgg,
};
3 changes: 3 additions & 0 deletions src/plugins/vis_type_timeseries/common/agg_lookup.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,9 @@ export const lookup = {
defaultMessage: 'Static Value',
}),
top_hit: i18n.translate('visTypeTimeseries.aggLookup.topHitLabel', { defaultMessage: 'Top Hit' }),
positive_rate: i18n.translate('visTypeTimeseries.aggLookup.positiveRateLabel', {
defaultMessage: 'Positive Rate',
}),
};

const pipeline = [
Expand Down
6 changes: 6 additions & 0 deletions src/plugins/vis_type_timeseries/common/calculate_label.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,12 @@ export function calculateLabel(metric, metrics) {
defaultMessage: 'Filter Ratio',
});
}
if (metric.type === 'positive_rate') {
return i18n.translate('visTypeTimeseries.calculateLabel.positiveRateLabel', {
defaultMessage: 'Positive Rate of {field}',
values: { field: metric.field },
});
}
if (metric.type === 'static') {
return i18n.translate('visTypeTimeseries.calculateLabel.staticValueLabel', {
defaultMessage: 'Static Value of {metricValue}',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { dateHistogram } from './date_histogram';
import { metricBuckets } from './metric_buckets';
import { siblingBuckets } from './sibling_buckets';
import { ratios as filterRatios } from './filter_ratios';
import { positiveRate } from './positive_rate';
import { normalizeQuery } from './normalize_query';

export const processors = [
Expand All @@ -38,5 +39,6 @@ export const processors = [
metricBuckets,
siblingBuckets,
filterRatios,
positiveRate,
normalizeQuery,
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* 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 { getBucketSize } from '../../helpers/get_bucket_size';
import { getIntervalAndTimefield } from '../../get_interval_and_timefield';
import { bucketTransform } from '../../helpers/bucket_transform';
import { set } from 'lodash';

export const filter = metric => metric.type === 'positive_rate';

export const createPositiveRate = (doc, intervalString, aggRoot) => metric => {
const maxFn = bucketTransform.max;
const derivativeFn = bucketTransform.derivative;
const positiveOnlyFn = bucketTransform.positive_only;

const maxMetric = { id: `${metric.id}-positive-rate-max`, type: 'max', field: metric.field };
const derivativeMetric = {
id: `${metric.id}-positive-rate-derivative`,
type: 'derivative',
field: `${metric.id}-positive-rate-max`,
unit: metric.unit,
};
const positiveOnlyMetric = {
id: metric.id,
type: 'positive_only',
field: `${metric.id}-positive-rate-derivative`,
};

const fakeSeriesMetrics = [maxMetric, derivativeMetric, positiveOnlyMetric];

const maxBucket = maxFn(maxMetric, fakeSeriesMetrics, intervalString);
const derivativeBucket = derivativeFn(derivativeMetric, fakeSeriesMetrics, intervalString);
const positiveOnlyBucket = positiveOnlyFn(positiveOnlyMetric, fakeSeriesMetrics, intervalString);

set(doc, `${aggRoot}.timeseries.aggs.${metric.id}-positive-rate-max`, maxBucket);
set(doc, `${aggRoot}.timeseries.aggs.${metric.id}-positive-rate-derivative`, derivativeBucket);
set(doc, `${aggRoot}.timeseries.aggs.${metric.id}`, positiveOnlyBucket);
};

export function positiveRate(req, panel, series, esQueryConfig, indexPatternObject, capabilities) {
return next => doc => {
const { interval } = getIntervalAndTimefield(panel, series, indexPatternObject);
const { intervalString } = getBucketSize(req, interval, capabilities);
if (series.metrics.some(filter)) {
series.metrics
.filter(filter)
.forEach(createPositiveRate(doc, intervalString, `aggs.${series.id}.aggs`));
return next(doc);
}
return next(doc);
};
}
Loading

0 comments on commit 6b3b515

Please sign in to comment.