[Alert details page] Fix metric threshold chart time range for bigger lookback windows (#184409)

Fixes #183625
Fixes #183130

## Summary

This PR fixes #183130 by removing `Last 1 minute` subtitle in the alert
details pages of the metric and custom threshold rules.

|Before|After|
|---|---|

|![image](8a8a98fa-4c6e-479e-a3f4-9856a57272bb)|

Also, it adjusts the chart time range by extending it at least 20 times
the rule execution interval to avoid showing no data for bigger
intervals such as 1 hour or 1 day.
This commit is contained in:
Maryam Saeidi 2024-06-03 14:10:17 +02:00 committed by GitHub
parent c531d0c8da
commit f2e439670c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 87 additions and 82 deletions

View file

@ -20,18 +20,27 @@ describe('getPaddedAlertTimeRange', () => {
'Duration 4 hour, time range will be extended it with 30 minutes from each side',
'2023-03-28T04:15:32.660Z',
'2023-03-28T08:15:32.660Z',
undefined,
{ from: '2023-03-28T03:45:32.660Z', to: '2023-03-28T08:45:32.660Z' },
],
[
'Duration 5 minutes, time range will be extended it with 20 minutes from each side',
'2023-03-28T08:22:33.660Z',
'2023-03-28T08:27:33.660Z',
undefined,
{ from: '2023-03-28T08:02:33.660Z', to: '2023-03-28T08:47:33.660Z' },
],
[
'Duration 5 minutes with 1 day lookBack, time range will be extended it with 20 days from each side',
'2023-01-28T22:22:33.660Z',
'2023-01-28T23:27:33.660Z',
{ size: 1, unit: 'd' },
{ from: '2023-01-08T22:22:33.660Z', to: '2023-02-17T23:27:33.660Z' },
],
];
it.each(testData)('%s', (_, start, end, output) => {
expect(getPaddedAlertTimeRange(start, end)).toEqual(output);
it.each(testData)('%s', (_, start, end, lookBackWindow, output) => {
expect(getPaddedAlertTimeRange(start, end, lookBackWindow)).toEqual(output);
});
describe('active alert', () => {

View file

@ -12,13 +12,32 @@ export interface TimeRange {
to: string;
}
export const getPaddedAlertTimeRange = (alertStart: string, alertEnd?: string): TimeRange => {
export const getPaddedAlertTimeRange = (
alertStart: string,
alertEnd?: string,
lookBackWindow?: {
size: number;
unit: 's' | 'm' | 'h' | 'd';
}
): TimeRange => {
const alertDuration = moment.duration(moment(alertEnd).diff(moment(alertStart)));
const now = moment().toISOString();
const durationMs =
// If alert duration is less than 160 min, we use 20 minute buffer
// Otherwise, we use 8 times alert duration
const defaultDurationMs =
alertDuration.asMinutes() < 160
? moment.duration(20, 'minutes').asMilliseconds()
: alertDuration.asMilliseconds() / 8;
// To ensure the alert time range at least covers 20 times lookback window,
// we compare lookBackDurationMs and defaultDurationMs to use any of those that is longer
const lookBackDurationMs =
lookBackWindow &&
moment.duration(lookBackWindow.size * 20, lookBackWindow.unit).asMilliseconds();
const durationMs =
lookBackDurationMs && lookBackDurationMs - defaultDurationMs > 0
? lookBackDurationMs
: defaultDurationMs;
const from = moment(alertStart).subtract(durationMs, 'millisecond').toISOString();
const to =

View file

@ -6,7 +6,6 @@
*/
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import React, { useEffect } from 'react';
import moment from 'moment';
import {
@ -15,7 +14,6 @@ import {
EuiLink,
EuiPanel,
EuiSpacer,
EuiText,
EuiTitle,
useEuiTheme,
} from '@elastic/eui';
@ -31,7 +29,6 @@ import { Rule } from '@kbn/alerting-plugin/common';
import { AlertAnnotation, AlertActiveTimeRangeAnnotation } from '@kbn/observability-alert-details';
import { getPaddedAlertTimeRange } from '@kbn/observability-get-padded-alert-time-range-util';
import { metricValueFormatter } from '../../../../common/alerting/metrics/metric_value_formatter';
import { TIME_LABELS } from '../../common/criterion_preview_chart/criterion_preview_chart';
import { Threshold } from '../../common/components/threshold';
import { withSourceProvider } from '../../../containers/metrics_source';
import { generateUniqueKey } from '../lib/generate_unique_key';
@ -88,7 +85,6 @@ export function AlertDetailsAppSection({
const chartProps = {
baseTheme: charts.theme.useChartsBaseTheme(),
};
const timeRange = getPaddedAlertTimeRange(alert.fields[ALERT_START]!, alert.fields[ALERT_END]);
const alertEnd = alert.fields[ALERT_END] ? moment(alert.fields[ALERT_END]).valueOf() : undefined;
const annotations = [
<AlertAnnotation
@ -140,61 +136,61 @@ export function AlertDetailsAppSection({
return !!rule.params.criteria ? (
<EuiFlexGroup direction="column" data-test-subj="metricThresholdAppSection">
{rule.params.criteria.map((criterion, index) => (
<EuiFlexItem key={generateUniqueKey(criterion)}>
<EuiPanel hasBorder hasShadow={false}>
<EuiTitle size="xs">
<h4>
{criterion.aggType.toUpperCase()}{' '}
{'metric' in criterion ? criterion.metric : undefined}
</h4>
</EuiTitle>
<EuiText size="s" color="subdued">
<FormattedMessage
id="xpack.infra.metrics.alertDetailsAppSection.criterion.subtitle"
defaultMessage="Last {lookback} {timeLabel}"
values={{
lookback: criterion.timeSize,
timeLabel: TIME_LABELS[criterion.timeUnit as keyof typeof TIME_LABELS],
}}
/>
</EuiText>
<EuiSpacer size="s" />
<EuiFlexGroup>
<EuiFlexItem style={{ minHeight: 150, minWidth: 160 }} grow={1}>
<Threshold
chartProps={chartProps}
id={`threshold-${generateUniqueKey(criterion)}`}
threshold={criterion.threshold[0]}
value={alert.fields[ALERT_EVALUATION_VALUES]![index]}
valueFormatter={(d) =>
metricValueFormatter(d, 'metric' in criterion ? criterion.metric : undefined)
}
title={i18n.translate(
'xpack.infra.metrics.alertDetailsAppSection.thresholdTitle',
{
defaultMessage: 'Threshold breached',
{rule.params.criteria.map((criterion, index) => {
const timeRange = getPaddedAlertTimeRange(
alert.fields[ALERT_START]!,
alert.fields[ALERT_END],
{
size: criterion.timeSize,
unit: criterion.timeUnit,
}
);
return (
<EuiFlexItem key={generateUniqueKey(criterion)}>
<EuiPanel hasBorder hasShadow={false}>
<EuiTitle size="xs">
<h4>
{criterion.aggType.toUpperCase()}{' '}
{'metric' in criterion ? criterion.metric : undefined}
</h4>
</EuiTitle>
<EuiSpacer size="m" />
<EuiFlexGroup>
<EuiFlexItem style={{ minHeight: 150, minWidth: 160 }} grow={1}>
<Threshold
chartProps={chartProps}
id={`threshold-${generateUniqueKey(criterion)}`}
threshold={criterion.threshold[0]}
value={alert.fields[ALERT_EVALUATION_VALUES]![index]}
valueFormatter={(d) =>
metricValueFormatter(d, 'metric' in criterion ? criterion.metric : undefined)
}
)}
comparator={criterion.comparator}
/>
</EuiFlexItem>
<EuiFlexItem grow={5}>
<ExpressionChart
annotations={annotations}
chartType={MetricsExplorerChartType.line}
expression={criterion}
filterQuery={rule.params.filterQueryText}
groupBy={rule.params.groupBy}
groupInstance={groupInstance}
hideTitle
timeRange={timeRange}
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiPanel>
</EuiFlexItem>
))}
title={i18n.translate(
'xpack.infra.metrics.alertDetailsAppSection.thresholdTitle',
{
defaultMessage: 'Threshold breached',
}
)}
comparator={criterion.comparator}
/>
</EuiFlexItem>
<EuiFlexItem grow={5}>
<ExpressionChart
annotations={annotations}
chartType={MetricsExplorerChartType.line}
expression={criterion}
filterQuery={rule.params.filterQueryText}
groupBy={rule.params.groupBy}
groupInstance={groupInstance}
hideTitle
timeRange={timeRange}
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiPanel>
</EuiFlexItem>
);
})}
</EuiFlexGroup>
) : null;
}

View file

@ -7,7 +7,6 @@
import chroma from 'chroma-js';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import React, { useEffect, useState } from 'react';
import {
EuiFlexGroup,
@ -15,7 +14,6 @@ import {
EuiLink,
EuiPanel,
EuiSpacer,
EuiText,
EuiTitle,
EuiToolTip,
useEuiTheme,
@ -45,7 +43,6 @@ import { getGroupFilters } from '../../../../../common/custom_threshold_rule/hel
import { metricValueFormatter } from '../../../../../common/custom_threshold_rule/metric_value_formatter';
import { AlertSummaryField } from '../../../..';
import { AlertParams } from '../../types';
import { TIME_LABELS } from '../criterion_preview_chart/criterion_preview_chart';
import { Threshold } from '../custom_threshold';
import { CustomThresholdRule, CustomThresholdAlert } from '../types';
import { LogRateAnalysis } from './log_rate_analysis';
@ -236,17 +233,7 @@ export default function AlertDetailsAppSection({
<h4 data-test-subj={`chartTitle-${index}`}>{chartTitleAndTooltip[index].title}</h4>
</EuiTitle>
</EuiToolTip>
<EuiText size="s" color="subdued">
<FormattedMessage
id="xpack.observability.customThreshold.rule.alertDetailsAppSection.criterion.subtitle"
defaultMessage="Last {lookback} {timeLabel}"
values={{
lookback: criterion.timeSize,
timeLabel: TIME_LABELS[criterion.timeUnit as keyof typeof TIME_LABELS],
}}
/>
</EuiText>
<EuiSpacer size="s" />
<EuiSpacer size="m" />
<EuiFlexGroup>
<EuiFlexItem style={{ minHeight: 150, minWidth: 160 }} grow={1}>
<Threshold

View file

@ -21002,7 +21002,6 @@
"xpack.infra.logSourceConfiguration.missingMessageFieldErrorMessage": "La vue de données doit contenir un champ {messageField}.",
"xpack.infra.logSourceErrorPage.savedObjectNotFoundErrorMessage": "Impossible de localiser ce {savedObjectType} : {savedObjectId}",
"xpack.infra.metadataEmbeddable.errorMessage": "Une erreur s'est produite lors du chargement des données. Essayez de {refetch} et d'ouvrir à nouveau les détails de l'hôte.",
"xpack.infra.metrics.alertDetailsAppSection.criterion.subtitle": "Dernier {lookback} {timeLabel}",
"xpack.infra.metrics.alertFlyout.alertPerRedundantFilterError": "Il est possible que cette règle signale {matchedGroups} moins que prévu, car la requête de filtre comporte une correspondance pour {groupCount, plural, one {ce champ} other {ces champs}}. Pour en savoir plus, veuillez consulter {filteringAndGroupingLink}.",
"xpack.infra.metrics.alertFlyout.customEquationEditor.aggregationLabel": "{name} de l'agrégation",
"xpack.infra.metrics.alertFlyout.customEquationEditor.fieldLabel": "{name} du champ",
@ -29762,7 +29761,6 @@
"xpack.observability.customThreshold.rule.aggregators.p99": "99e centile de {metric}",
"xpack.observability.customThreshold.rule.aggregators.rate": "Taux de {metric}",
"xpack.observability.customThreshold.rule.aggregators.sum": "Somme de {metric}",
"xpack.observability.customThreshold.rule.alertDetailsAppSection.criterion.subtitle": "Dernier {lookback} {timeLabel}",
"xpack.observability.customThreshold.rule.alertDetailsAppSection.summaryField.moreTags": "+{number} de plus",
"xpack.observability.customThreshold.rule.alertFlyout.alertPerRedundantFilterError": "Il est possible que cette règle signale {matchedGroups} moins que prévu, car la requête de filtre comporte une correspondance pour {groupCount, plural, one {ce champ} other {ces champs}}. Pour en savoir plus, veuillez consulter {filteringAndGroupingLink}.",
"xpack.observability.customThreshold.rule.alertFlyout.condition": "Condition {conditionNumber}",

View file

@ -20971,7 +20971,6 @@
"xpack.infra.logSourceConfiguration.missingMessageFieldErrorMessage": "データビューには{messageField}フィールドが必要です。",
"xpack.infra.logSourceErrorPage.savedObjectNotFoundErrorMessage": "{savedObjectType}{savedObjectId}が見つかりませんでした",
"xpack.infra.metadataEmbeddable.errorMessage": "データの読み込みエラーが発生しました。{refetch}し、ホスト詳細をもう一度開いてください。",
"xpack.infra.metrics.alertDetailsAppSection.criterion.subtitle": "過去{lookback} {timeLabel}",
"xpack.infra.metrics.alertFlyout.alertPerRedundantFilterError": "このルールは想定未満の{matchedGroups}に対してアラートを通知できます。フィルタークエリには{groupCount, plural, one {このフィールド} other {これらのフィールド}}の完全一致が含まれるためです。詳細については、{filteringAndGroupingLink}を参照してください。",
"xpack.infra.metrics.alertFlyout.customEquationEditor.aggregationLabel": "集約{name}",
"xpack.infra.metrics.alertFlyout.customEquationEditor.fieldLabel": "フィールド{name}",
@ -29735,7 +29734,6 @@
"xpack.observability.customThreshold.rule.aggregators.p99": "{metric}の99パーセンタイル",
"xpack.observability.customThreshold.rule.aggregators.rate": "{metric}の比率",
"xpack.observability.customThreshold.rule.aggregators.sum": "{metric}の合計",
"xpack.observability.customThreshold.rule.alertDetailsAppSection.criterion.subtitle": "過去{lookback} {timeLabel}",
"xpack.observability.customThreshold.rule.alertDetailsAppSection.summaryField.moreTags": "その他{number}",
"xpack.observability.customThreshold.rule.alertFlyout.alertPerRedundantFilterError": "このルールは想定未満の{matchedGroups}に対してアラートを通知できます。フィルタークエリには{groupCount, plural, one {このフィールド} other {これらのフィールド}}の完全一致が含まれるためです。詳細については、{filteringAndGroupingLink}を参照してください。",
"xpack.observability.customThreshold.rule.alertFlyout.condition": "条件{conditionNumber}",

View file

@ -21010,7 +21010,6 @@
"xpack.infra.logSourceConfiguration.missingMessageFieldErrorMessage": "数据视图必须包含 {messageField} 字段。",
"xpack.infra.logSourceErrorPage.savedObjectNotFoundErrorMessage": "无法找到该{savedObjectType}{savedObjectId}",
"xpack.infra.metadataEmbeddable.errorMessage": "加载数据时出错。尝试{refetch}并再次打开主机详情。",
"xpack.infra.metrics.alertDetailsAppSection.criterion.subtitle": "过去 {lookback} {timeLabel}",
"xpack.infra.metrics.alertFlyout.alertPerRedundantFilterError": "此规则可能针对低于预期的 {matchedGroups} 告警,因为筛选查询包含{groupCount, plural, one {此字段} other {这些字段}}的匹配项。有关更多信息,请参阅 {filteringAndGroupingLink}。",
"xpack.infra.metrics.alertFlyout.customEquationEditor.aggregationLabel": "聚合 {name}",
"xpack.infra.metrics.alertFlyout.customEquationEditor.fieldLabel": "字段 {name}",
@ -29775,7 +29774,6 @@
"xpack.observability.customThreshold.rule.aggregators.p99": "{metric} 的第 99 个百分位",
"xpack.observability.customThreshold.rule.aggregators.rate": "{metric} 的比率",
"xpack.observability.customThreshold.rule.aggregators.sum": "{metric} 的总和",
"xpack.observability.customThreshold.rule.alertDetailsAppSection.criterion.subtitle": "过去 {lookback} {timeLabel}",
"xpack.observability.customThreshold.rule.alertDetailsAppSection.summaryField.moreTags": "+ 另外 {number} 个",
"xpack.observability.customThreshold.rule.alertFlyout.alertPerRedundantFilterError": "此规则可能针对低于预期的 {matchedGroups} 告警,因为筛选查询包含{groupCount, plural, one {此字段} other {这些字段}}的匹配项。有关更多信息,请参阅 {filteringAndGroupingLink}。",
"xpack.observability.customThreshold.rule.alertFlyout.condition": "条件 {conditionNumber}",