Skip to content

Commit

Permalink
[Metrics UI] Add checkbox to optionally drop partial buckets (#107676)
Browse files Browse the repository at this point in the history
  • Loading branch information
Zacqary committed Aug 5, 2021
1 parent e991326 commit eed9723
Show file tree
Hide file tree
Showing 4 changed files with 112 additions and 54 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import {
EuiToolTip,
EuiIcon,
EuiFieldSearch,
EuiAccordion,
EuiPanel,
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
Expand Down Expand Up @@ -259,6 +261,11 @@ export const Expressions: React.FC<Props> = (props) => {
return alertParams.groupBy;
}, [alertParams.groupBy]);

const areAllAggsRate = useMemo(
() => alertParams.criteria?.every((c) => c.aggType === Aggregators.RATE),
[alertParams.criteria]
);

return (
<>
<EuiSpacer size={'m'} />
Expand Down Expand Up @@ -323,27 +330,60 @@ export const Expressions: React.FC<Props> = (props) => {
</div>

<EuiSpacer size={'m'} />
<EuiCheckbox
id="metrics-alert-no-data-toggle"
label={
<>
{i18n.translate('xpack.infra.metrics.alertFlyout.alertOnNoData', {
defaultMessage: "Alert me if there's no data",
})}{' '}
<EuiToolTip
content={i18n.translate('xpack.infra.metrics.alertFlyout.noDataHelpText', {
defaultMessage:
'Enable this to trigger the action if the metric(s) do not report any data over the expected time period, or if the alert fails to query Elasticsearch',
})}
>
<EuiIcon type="questionInCircle" color="subdued" />
</EuiToolTip>
</>
}
checked={alertParams.alertOnNoData}
onChange={(e) => setAlertParams('alertOnNoData', e.target.checked)}
/>

<EuiAccordion
id="advanced-options-accordion"
buttonContent={i18n.translate('xpack.infra.metrics.alertFlyout.advancedOptions', {
defaultMessage: 'Advanced options',
})}
>
<EuiPanel color="subdued">
<EuiCheckbox
id="metrics-alert-no-data-toggle"
label={
<>
{i18n.translate('xpack.infra.metrics.alertFlyout.alertOnNoData', {
defaultMessage: "Alert me if there's no data",
})}{' '}
<EuiToolTip
content={i18n.translate('xpack.infra.metrics.alertFlyout.noDataHelpText', {
defaultMessage:
'Enable this to trigger the action if the metric(s) do not report any data over the expected time period, or if the alert fails to query Elasticsearch',
})}
>
<EuiIcon type="questionInCircle" color="subdued" />
</EuiToolTip>
</>
}
checked={alertParams.alertOnNoData}
onChange={(e) => setAlertParams('alertOnNoData', e.target.checked)}
/>
<EuiCheckbox
id="metrics-alert-partial-buckets-toggle"
label={
<>
{i18n.translate('xpack.infra.metrics.alertFlyout.shouldDropPartialBuckets', {
defaultMessage: 'Drop partial buckets when evaluating data',
})}{' '}
<EuiToolTip
content={i18n.translate(
'xpack.infra.metrics.alertFlyout.dropPartialBucketsHelpText',
{
defaultMessage:
"Enable this to drop the most recent bucket of evaluation data if it's less than {timeSize}{timeUnit}.",
values: { timeSize, timeUnit },
}
)}
>
<EuiIcon type="questionInCircle" color="subdued" />
</EuiToolTip>
</>
}
checked={areAllAggsRate || alertParams.shouldDropPartialBuckets}
disabled={areAllAggsRate}
onChange={(e) => setAlertParams('shouldDropPartialBuckets', e.target.checked)}
/>
</EuiPanel>
</EuiAccordion>
<EuiSpacer size={'m'} />

<EuiFormRow
Expand Down Expand Up @@ -400,7 +440,14 @@ export const Expressions: React.FC<Props> = (props) => {
alertThrottle={alertThrottle}
alertNotifyWhen={alertNotifyWhen}
alertType={METRIC_THRESHOLD_ALERT_TYPE_ID}
alertParams={pick(alertParams, 'criteria', 'groupBy', 'filterQuery', 'sourceId')}
alertParams={pick(
alertParams,
'criteria',
'groupBy',
'filterQuery',
'sourceId',
'shouldDropPartialBuckets'
)}
showNoDataResults={alertParams.alertOnNoData}
validate={validateMetricThreshold}
groupByDisplayName={groupByPreviewDisplayName}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,5 @@ export interface AlertParams {
sourceId: string;
filterQueryText?: string;
alertOnNoData?: boolean;
shouldDropPartialBuckets?: boolean;
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export interface EvaluatedAlertParams {
criteria: MetricExpressionParams[];
groupBy: string | undefined | string[];
filterQuery: string | undefined;
shouldDropPartialBuckets?: boolean;
}

export const evaluateAlert = <Params extends EvaluatedAlertParams = EvaluatedAlertParams>(
Expand All @@ -53,7 +54,7 @@ export const evaluateAlert = <Params extends EvaluatedAlertParams = EvaluatedAle
config: InfraSource['configuration'],
timeframe?: { start: number; end: number }
) => {
const { criteria, groupBy, filterQuery } = params;
const { criteria, groupBy, filterQuery, shouldDropPartialBuckets } = params;
return Promise.all(
criteria.map(async (criterion) => {
const currentValues = await getMetric(
Expand All @@ -63,7 +64,8 @@ export const evaluateAlert = <Params extends EvaluatedAlertParams = EvaluatedAle
config.fields.timestamp,
groupBy,
filterQuery,
timeframe
timeframe,
shouldDropPartialBuckets
);

const { threshold, warningThreshold, comparator, warningComparator } = criterion;
Expand Down Expand Up @@ -103,15 +105,17 @@ const getMetric: (
timefield: string,
groupBy: string | undefined | string[],
filterQuery: string | undefined,
timeframe?: { start: number; end: number }
timeframe?: { start: number; end: number },
shouldDropPartialBuckets?: boolean
) => Promise<Record<string, number[]>> = async function (
esClient,
params,
index,
timefield,
groupBy,
filterQuery,
timeframe
timeframe,
shouldDropPartialBuckets
) {
const { aggType, timeSize, timeUnit } = params;
const hasGroupBy = groupBy && groupBy.length;
Expand Down Expand Up @@ -143,6 +147,16 @@ const getMetric: (
filterQuery
);

const dropPartialBucketsOptions =
// Rate aggs always drop partial buckets; guard against this boolean being passed as false
shouldDropPartialBuckets || aggType === Aggregators.RATE
? {
from,
to,
bucketSizeInMillis: intervalAsMS,
}
: null;

try {
if (hasGroupBy) {
const bucketSelector = (
Expand All @@ -164,11 +178,7 @@ const getMetric: (
...result,
[Object.values(bucket.key)
.map((value) => value)
.join(', ')]: getValuesFromAggregations(bucket, aggType, {
from,
to,
bucketSizeInMillis: intervalAsMS,
}),
.join(', ')]: getValuesFromAggregations(bucket, aggType, dropPartialBucketsOptions),
}),
{}
);
Expand All @@ -182,7 +192,7 @@ const getMetric: (
[UNGROUPED_FACTORY_KEY]: getValuesFromAggregations(
(result.aggregations! as unknown) as Aggregation,
aggType,
{ from, to, bucketSizeInMillis: intervalAsMS }
dropPartialBucketsOptions
),
};
} catch (e) {
Expand Down Expand Up @@ -222,47 +232,46 @@ const dropPartialBuckets = ({ from, to, bucketSizeInMillis }: DropPartialBucketO
const getValuesFromAggregations = (
aggregations: Aggregation,
aggType: MetricExpressionParams['aggType'],
dropPartialBucketsOptions: DropPartialBucketOptions
dropPartialBucketsOptions: DropPartialBucketOptions | null
) => {
try {
const { buckets } = aggregations.aggregatedIntervals;
if (!buckets.length) return null; // No Data state

let mappedBuckets;

if (aggType === Aggregators.COUNT) {
return buckets.map((bucket) => ({
mappedBuckets = buckets.map((bucket) => ({
key: bucket.from_as_string,
value: bucket.doc_count,
}));
}
if (aggType === Aggregators.P95 || aggType === Aggregators.P99) {
return buckets.map((bucket) => {
} else if (aggType === Aggregators.P95 || aggType === Aggregators.P99) {
mappedBuckets = buckets.map((bucket) => {
const values = bucket.aggregatedValue?.values || [];
const firstValue = first(values);
if (!firstValue) return null;
return { key: bucket.from_as_string, value: firstValue.value };
});
}

if (aggType === Aggregators.AVERAGE) {
return buckets.map((bucket) => ({
} else if (aggType === Aggregators.AVERAGE) {
mappedBuckets = buckets.map((bucket) => ({
key: bucket.key_as_string ?? bucket.from_as_string,
value: bucket.aggregatedValue?.value ?? null,
}));
} else if (aggType === Aggregators.RATE) {
mappedBuckets = buckets.map((bucket) => ({
key: bucket.key_as_string ?? bucket.from_as_string,
value: bucket.aggregatedValue?.value ?? null,
}));
} else {
mappedBuckets = buckets.map((bucket) => ({
key: bucket.key_as_string ?? bucket.from_as_string,
value: bucket.aggregatedValue?.value ?? null,
}));
}

if (aggType === Aggregators.RATE) {
return buckets
.map((bucket) => ({
key: bucket.key_as_string ?? bucket.from_as_string,
value: bucket.aggregatedValue?.value ?? null,
}))
.filter(dropPartialBuckets(dropPartialBucketsOptions));
if (dropPartialBucketsOptions) {
return mappedBuckets.filter(dropPartialBuckets(dropPartialBucketsOptions));
}

return buckets.map((bucket) => ({
key: bucket.key_as_string ?? bucket.from_as_string,
value: bucket.aggregatedValue?.value ?? null,
}));
return mappedBuckets;
} catch (e) {
return NaN; // Error state
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ interface PreviewMetricThresholdAlertParams {
criteria: MetricExpressionParams[];
groupBy: string | undefined | string[];
filterQuery: string | undefined;
shouldDropPartialBuckets?: boolean;
};
config: InfraSource['configuration'];
lookback: Unit;
Expand Down

0 comments on commit eed9723

Please sign in to comment.