From eed9723c85e06184ceb5f527457d2842272cf01b Mon Sep 17 00:00:00 2001 From: Zacqary Adam Xeper Date: Thu, 5 Aug 2021 16:23:33 -0500 Subject: [PATCH] [Metrics UI] Add checkbox to optionally drop partial buckets (#107676) --- .../components/expression.tsx | 91 ++++++++++++++----- .../public/alerting/metric_threshold/types.ts | 1 + .../metric_threshold/lib/evaluate_alert.ts | 73 ++++++++------- .../preview_metric_threshold_alert.ts | 1 + 4 files changed, 112 insertions(+), 54 deletions(-) diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression.tsx b/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression.tsx index 8835a7cd55ce82..be0ecbb1dab657 100644 --- a/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression.tsx +++ b/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression.tsx @@ -17,6 +17,8 @@ import { EuiToolTip, EuiIcon, EuiFieldSearch, + EuiAccordion, + EuiPanel, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; @@ -259,6 +261,11 @@ export const Expressions: React.FC = (props) => { return alertParams.groupBy; }, [alertParams.groupBy]); + const areAllAggsRate = useMemo( + () => alertParams.criteria?.every((c) => c.aggType === Aggregators.RATE), + [alertParams.criteria] + ); + return ( <> @@ -323,27 +330,60 @@ export const Expressions: React.FC = (props) => { - - {i18n.translate('xpack.infra.metrics.alertFlyout.alertOnNoData', { - defaultMessage: "Alert me if there's no data", - })}{' '} - - - - - } - checked={alertParams.alertOnNoData} - onChange={(e) => setAlertParams('alertOnNoData', e.target.checked)} - /> - + + + + {i18n.translate('xpack.infra.metrics.alertFlyout.alertOnNoData', { + defaultMessage: "Alert me if there's no data", + })}{' '} + + + + + } + checked={alertParams.alertOnNoData} + onChange={(e) => setAlertParams('alertOnNoData', e.target.checked)} + /> + + {i18n.translate('xpack.infra.metrics.alertFlyout.shouldDropPartialBuckets', { + defaultMessage: 'Drop partial buckets when evaluating data', + })}{' '} + + + + + } + checked={areAllAggsRate || alertParams.shouldDropPartialBuckets} + disabled={areAllAggsRate} + onChange={(e) => setAlertParams('shouldDropPartialBuckets', e.target.checked)} + /> + + = (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} diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/types.ts b/x-pack/plugins/infra/public/alerting/metric_threshold/types.ts index fca4160199030b..a679579e57235a 100644 --- a/x-pack/plugins/infra/public/alerting/metric_threshold/types.ts +++ b/x-pack/plugins/infra/public/alerting/metric_threshold/types.ts @@ -61,4 +61,5 @@ export interface AlertParams { sourceId: string; filterQueryText?: string; alertOnNoData?: boolean; + shouldDropPartialBuckets?: boolean; } diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/evaluate_alert.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/evaluate_alert.ts index 88d135fd1681bb..6d99b6e2f5fe30 100644 --- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/evaluate_alert.ts +++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/evaluate_alert.ts @@ -45,6 +45,7 @@ export interface EvaluatedAlertParams { criteria: MetricExpressionParams[]; groupBy: string | undefined | string[]; filterQuery: string | undefined; + shouldDropPartialBuckets?: boolean; } export const evaluateAlert = ( @@ -53,7 +54,7 @@ export const evaluateAlert = { - const { criteria, groupBy, filterQuery } = params; + const { criteria, groupBy, filterQuery, shouldDropPartialBuckets } = params; return Promise.all( criteria.map(async (criterion) => { const currentValues = await getMetric( @@ -63,7 +64,8 @@ export const evaluateAlert = Promise> = async function ( esClient, params, @@ -111,7 +114,8 @@ const getMetric: ( timefield, groupBy, filterQuery, - timeframe + timeframe, + shouldDropPartialBuckets ) { const { aggType, timeSize, timeUnit } = params; const hasGroupBy = groupBy && groupBy.length; @@ -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 = ( @@ -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), }), {} ); @@ -182,7 +192,7 @@ const getMetric: ( [UNGROUPED_FACTORY_KEY]: getValuesFromAggregations( (result.aggregations! as unknown) as Aggregation, aggType, - { from, to, bucketSizeInMillis: intervalAsMS } + dropPartialBucketsOptions ), }; } catch (e) { @@ -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 } diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/preview_metric_threshold_alert.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/preview_metric_threshold_alert.ts index a4c207f4006d56..931b830875cdfa 100644 --- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/preview_metric_threshold_alert.ts +++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/preview_metric_threshold_alert.ts @@ -26,6 +26,7 @@ interface PreviewMetricThresholdAlertParams { criteria: MetricExpressionParams[]; groupBy: string | undefined | string[]; filterQuery: string | undefined; + shouldDropPartialBuckets?: boolean; }; config: InfraSource['configuration']; lookback: Unit;