mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[Metrics UI] Add checkbox to optionally drop partial buckets (#107676)
This commit is contained in:
parent
e9913264c7
commit
eed9723c85
4 changed files with 112 additions and 54 deletions
|
@ -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> = (props) => {
|
|||
return alertParams.groupBy;
|
||||
}, [alertParams.groupBy]);
|
||||
|
||||
const areAllAggsRate = useMemo(
|
||||
() => alertParams.criteria?.every((c) => c.aggType === Aggregators.RATE),
|
||||
[alertParams.criteria]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiSpacer size={'m'} />
|
||||
|
@ -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
|
||||
|
@ -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}
|
||||
|
|
|
@ -61,4 +61,5 @@ export interface AlertParams {
|
|||
sourceId: string;
|
||||
filterQueryText?: string;
|
||||
alertOnNoData?: boolean;
|
||||
shouldDropPartialBuckets?: boolean;
|
||||
}
|
||||
|
|
|
@ -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>(
|
||||
|
@ -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(
|
||||
|
@ -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;
|
||||
|
@ -103,7 +105,8 @@ 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,
|
||||
|
@ -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
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ interface PreviewMetricThresholdAlertParams {
|
|||
criteria: MetricExpressionParams[];
|
||||
groupBy: string | undefined | string[];
|
||||
filterQuery: string | undefined;
|
||||
shouldDropPartialBuckets?: boolean;
|
||||
};
|
||||
config: InfraSource['configuration'];
|
||||
lookback: Unit;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue