mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 01:13:23 -04:00
[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|
|---|---|
||
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:
parent
c531d0c8da
commit
f2e439670c
7 changed files with 87 additions and 82 deletions
|
@ -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', () => {
|
||||
|
|
|
@ -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 =
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}",
|
||||
|
|
|
@ -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}",
|
||||
|
|
|
@ -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}",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue