[Alert details page] Using alert summary widget as the history chart (#181824)

Resolves #181475

## Summary

This PR uses the alert summary widget as a history chart, here is how
they look side by side:


![image](99907ee0-236e-473c-a605-0c265f5d53ca)

Now every rule's alert details page should have the history chart, and
the specific implementation for the following rules has been removed:
- APM Latency
- Log threshold
- Custom threshold
- SLO burn rate

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Maryam Saeidi 2024-05-16 10:08:11 +02:00 committed by GitHub
parent 5cfb994571
commit 520f19d0a2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
25 changed files with 256 additions and 1061 deletions

View file

@ -12,13 +12,10 @@ import {
ALERT_END,
ALERT_EVALUATION_THRESHOLD,
ALERT_EVALUATION_VALUE,
ALERT_INSTANCE_ID,
ALERT_RULE_TYPE_ID,
ALERT_RULE_UUID,
ALERT_START,
} from '@kbn/rule-data-utils';
import moment from 'moment';
import React, { useEffect, useMemo } from 'react';
import React, { useEffect } from 'react';
import { useKibana } from '@kbn/kibana-react-plugin/public';
import { getPaddedAlertTimeRange } from '@kbn/observability-get-padded-alert-time-range-util';
import { EuiCallOut } from '@elastic/eui';
@ -34,7 +31,6 @@ import { TimeRangeMetadataContextProvider } from '../../../../context/time_range
import { getComparisonChartTheme } from '../../../shared/time_comparison/get_comparison_chart_theme';
import FailedTransactionChart from './failed_transaction_chart';
import { getAggsTypeFromRule } from './helpers';
import { LatencyAlertsHistoryChart } from './latency_alerts_history_chart';
import LatencyChart from './latency_chart';
import ThroughputChart from './throughput_chart';
import { AlertDetailsAppSectionProps } from './types';
@ -125,12 +121,6 @@ export function AlertDetailsAppSection({
const latencyAggregationType = getAggsTypeFromRule(params.aggregationType);
const timeRange = getPaddedAlertTimeRange(alert.fields[ALERT_START]!, alert.fields[ALERT_END]);
const comparisonChartTheme = getComparisonChartTheme();
const historicalRange = useMemo(() => {
return {
start: moment().subtract(30, 'days').toISOString(),
end: moment().toISOString(),
};
}, []);
const { from, to } = timeRange;
if (!from || !to) {
@ -206,20 +196,6 @@ export function AlertDetailsAppSection({
/>
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<LatencyAlertsHistoryChart
ruleId={alert.fields[ALERT_RULE_UUID]}
alertInstanceId={alert.fields[ALERT_INSTANCE_ID]}
serviceName={serviceName}
start={historicalRange.start}
end={historicalRange.end}
transactionType={transactionType}
transactionName={transactionName}
latencyAggregationType={latencyAggregationType}
environment={environment}
timeZone={timeZone}
/>
</EuiFlexItem>
</ChartPointerEventContextProvider>
</TimeRangeMetadataContextProvider>
</EuiFlexGroup>

View file

@ -1,280 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { AnnotationDomainType, LineAnnotation, Position } from '@elastic/charts';
import {
EuiBadge,
EuiFlexGroup,
EuiFlexItem,
EuiIcon,
EuiLoadingSpinner,
EuiPanel,
EuiSpacer,
EuiText,
EuiTitle,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { convertTo } from '@kbn/observability-plugin/public';
import { AlertConsumers } from '@kbn/rule-data-utils';
import moment from 'moment';
import React, { useMemo } from 'react';
import { useAlertsHistory } from '@kbn/observability-alert-details';
import { useKibana } from '@kbn/kibana-react-plugin/public';
import { ApmDocumentType } from '../../../../../common/document_type';
import { LatencyAggregationType } from '../../../../../common/latency_aggregation_types';
import { getDurationFormatter } from '../../../../../common/utils/formatters';
import { useFetcher } from '../../../../hooks/use_fetcher';
import { usePreferredDataSourceAndBucketSize } from '../../../../hooks/use_preferred_data_source_and_bucket_size';
import { getLatencyChartSelector } from '../../../../selectors/latency_chart_selectors';
import { filterNil } from '../../../shared/charts/latency_chart';
import { TimeseriesChart } from '../../../shared/charts/timeseries_chart';
import {
getMaxY,
getResponseTimeTickFormatter,
} from '../../../shared/charts/transaction_charts/helper';
import { CHART_ANNOTATION_RED_COLOR } from './constants';
interface LatencyAlertsHistoryChartProps {
serviceName: string;
start: string;
end: string;
transactionType?: string;
transactionName?: string;
latencyAggregationType: LatencyAggregationType;
environment: string;
timeZone: string;
ruleId: string;
alertInstanceId?: string;
}
export function LatencyAlertsHistoryChart({
serviceName,
start,
end,
transactionType,
transactionName,
latencyAggregationType,
environment,
timeZone,
ruleId,
alertInstanceId,
}: LatencyAlertsHistoryChartProps) {
const preferred = usePreferredDataSourceAndBucketSize({
start,
end,
kuery: '',
numBuckets: 100,
// ServiceTransactionMetric does not have transactionName as a dimension, but it is faster than TransactionMetric
// We use TransactionMetric only when there is a transactionName
type: transactionName
? ApmDocumentType.TransactionMetric
: ApmDocumentType.ServiceTransactionMetric,
});
const { http, notifications } = useKibana().services;
const { data, status } = useFetcher(
(callApmApi) => {
if (serviceName && start && end && transactionType && latencyAggregationType && preferred) {
return callApmApi(`GET /internal/apm/services/{serviceName}/transactions/charts/latency`, {
params: {
path: { serviceName },
query: {
environment,
kuery: '',
start,
end,
transactionType,
transactionName,
latencyAggregationType,
bucketSizeInSeconds: preferred.bucketSizeInSeconds,
documentType: preferred.source.documentType,
rollupInterval: preferred.source.rollupInterval,
useDurationSummary:
preferred.source.hasDurationSummaryField &&
latencyAggregationType === LatencyAggregationType.avg,
},
},
});
}
},
[
end,
environment,
latencyAggregationType,
serviceName,
start,
transactionName,
transactionType,
preferred,
]
);
const memoizedData = useMemo(
() =>
getLatencyChartSelector({
latencyChart: data,
latencyAggregationType,
previousPeriodLabel: '',
}),
// It should only update when the data has changed
// eslint-disable-next-line react-hooks/exhaustive-deps
[data]
);
const { currentPeriod, previousPeriod } = memoizedData;
const timeseriesLatency = [currentPeriod, previousPeriod].filter(filterNil);
const latencyMaxY = getMaxY(timeseriesLatency);
const latencyFormatter = getDurationFormatter(latencyMaxY);
const {
data: { totalTriggeredAlerts, avgTimeToRecoverUS, histogramTriggeredAlerts },
isError,
isLoading,
} = useAlertsHistory({
http,
featureIds: [AlertConsumers.APM],
ruleId,
dateRange: { from: start, to: end },
instanceId: alertInstanceId,
});
if (isError) {
notifications?.toasts.addDanger({
title: i18n.translate('xpack.apm.alertDetails.latencyAlertHistoryChart.error.toastTitle', {
defaultMessage: 'Latency alerts history chart error',
}),
text: i18n.translate(
'xpack.apm.alertDetails.latencyAlertHistoryChart.error.toastDescription',
{
defaultMessage: `An error occurred when fetching latency alert history chart data for {serviceName}`,
values: { serviceName },
}
),
});
}
return (
<EuiPanel hasBorder={true}>
<EuiFlexGroup direction="column" gutterSize="none" responsive={false}>
<EuiFlexItem grow={false}>
<EuiTitle size="xs">
<h2>
{serviceName}
{i18n.translate('xpack.apm.latencyChartHistory.chartTitle', {
defaultMessage: ' latency alerts history',
})}
</h2>
</EuiTitle>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText size="s" color="subdued">
{i18n.translate('xpack.apm.latencyChartHistory.last30days', {
defaultMessage: 'Last 30 days',
})}
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer size="s" />
<EuiFlexGroup gutterSize="l">
<EuiFlexItem grow={false}>
<EuiFlexGroup gutterSize="xs" direction="column">
<EuiFlexItem grow={false}>
<EuiText color="danger">
<EuiTitle size="s">
<h3>
{isLoading ? <EuiLoadingSpinner size="s" /> : totalTriggeredAlerts || '-'}
</h3>
</EuiTitle>
</EuiText>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText size="s" color="subdued">
{i18n.translate('xpack.apm.latencyChartHistory.alertsTriggered', {
defaultMessage: 'Alerts triggered',
})}
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexGroup gutterSize="xs" direction="column">
<EuiFlexItem grow={false}>
<EuiText>
<EuiTitle size="s">
<h3>
{isLoading ? (
<EuiLoadingSpinner size="s" />
) : avgTimeToRecoverUS ? (
convertTo({
unit: 'minutes',
microseconds: avgTimeToRecoverUS,
extended: true,
}).formatted
) : (
'-'
)}
</h3>
</EuiTitle>
</EuiText>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText size="s" color="subdued">
{i18n.translate('xpack.apm.latencyChartHistory.avgTimeToRecover', {
defaultMessage: 'Avg time to recover',
})}
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexGroup>
<EuiSpacer size="s" />
<TimeseriesChart
id="latencyChart"
annotations={[
<LineAnnotation
id="annotations"
key={'annotationsAlertHistory'}
domainType={AnnotationDomainType.XDomain}
dataValues={
histogramTriggeredAlerts
?.filter((annotation) => annotation.doc_count > 0)
.map((annotation) => {
return {
dataValue: annotation.key,
header: String(annotation.doc_count),
details: moment(annotation.key_as_string).format('yyyy-MM-DD'),
};
}) || []
}
style={{
line: {
strokeWidth: 3,
stroke: CHART_ANNOTATION_RED_COLOR,
opacity: 1,
},
}}
marker={<EuiIcon type="warning" color={CHART_ANNOTATION_RED_COLOR} />}
markerBody={(annotationData) => (
<>
<EuiBadge color={CHART_ANNOTATION_RED_COLOR}>
<EuiText size="xs" color="white">
{annotationData.header}
</EuiText>
</EuiBadge>
<EuiSpacer size="xs" />
</>
)}
markerPosition={Position.Top}
/>,
]}
height={200}
comparisonEnabled={false}
offset={''}
fetchStatus={status}
timeseries={timeseriesLatency}
yLabelFormat={getResponseTimeTickFormatter(latencyFormatter)}
timeZone={timeZone}
/>
</EuiPanel>
);
}

View file

@ -1,205 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import moment from 'moment';
import React from 'react';
import { Rule } from '@kbn/alerting-plugin/common';
import {
EuiPanel,
EuiFlexGroup,
EuiFlexItem,
EuiTitle,
EuiText,
EuiSpacer,
EuiLoadingSpinner,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { convertTo } from '@kbn/observability-plugin/public';
import { AnnotationDomainType, LineAnnotation, Position } from '@elastic/charts';
import { EuiIcon, EuiBadge } from '@elastic/eui';
import { euiThemeVars } from '@kbn/ui-theme';
import { AlertConsumers } from '@kbn/rule-data-utils';
import DateMath from '@kbn/datemath';
import { useAlertsHistory } from '@kbn/observability-alert-details';
import { useKibanaContextForPlugin } from '../../../../../hooks/use_kibana';
import { type PartialCriterion } from '../../../../../../common/alerting/logs/log_threshold';
import { CriterionPreview } from '../../expression_editor/criterion_preview_chart';
import { PartialRuleParams } from '../../../../../../common/alerting/logs/log_threshold';
import type { Group } from '../types';
const LogsHistoryChart = ({
rule,
instanceId,
groups,
}: {
rule: Rule<PartialRuleParams>;
instanceId?: string;
groups?: Group[];
}) => {
const { http, notifications } = useKibanaContextForPlugin().services;
// Show the Logs History Chart ONLY if we have one criteria
// So always pull the first criteria
const criteria = rule.params.criteria[0];
const dateRange = {
from: 'now-30d',
to: 'now',
};
const executionTimeRange = {
gte: DateMath.parse(dateRange.from)!.valueOf(),
lte: DateMath.parse(dateRange.to, { roundUp: true })!.valueOf(),
buckets: 30,
};
const {
data: { histogramTriggeredAlerts, avgTimeToRecoverUS, totalTriggeredAlerts },
isLoading,
isError,
} = useAlertsHistory({
http,
featureIds: [AlertConsumers.LOGS],
ruleId: rule.id,
dateRange,
instanceId,
});
if (isError) {
notifications?.toasts.addDanger({
title: i18n.translate('xpack.infra.alertDetails.logsAlertHistoryChart.error.toastTitle', {
defaultMessage: 'Logs alerts history chart error',
}),
text: i18n.translate(
'xpack.infra.alertDetails.logsAlertHistoryChart.error.toastDescription',
{
defaultMessage: `An error occurred when fetching logs alert history chart data`,
}
),
});
}
const alertHistoryAnnotations =
histogramTriggeredAlerts
?.filter((annotation) => annotation.doc_count > 0)
.map((annotation) => {
return {
dataValue: annotation.key,
header: String(annotation.doc_count),
// Only the date(without time) is needed here, uiSettings don't provide that
details: moment(annotation.key_as_string).format('yyyy-MM-DD'),
};
}) || [];
return (
<EuiPanel hasBorder={true} data-test-subj="logsHistoryChartAlertDetails">
<EuiFlexGroup direction="column" gutterSize="none" responsive={false}>
<EuiFlexItem grow={false}>
<EuiTitle size="xs">
<h2>
{i18n.translate('xpack.infra.logs.alertDetails.chartHistory.chartTitle', {
defaultMessage: 'Logs threshold alerts history',
})}
</h2>
</EuiTitle>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText size="s" color="subdued">
{i18n.translate('xpack.infra.logs.alertDetails.chartHistory.last30days', {
defaultMessage: 'Last 30 days',
})}
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer size="s" />
<EuiFlexGroup gutterSize="l">
<EuiFlexItem grow={false}>
<EuiFlexGroup gutterSize="xs" direction="column">
<EuiFlexItem grow={false}>
<EuiText color="danger">
<EuiTitle size="s">
<h3>
{isLoading ? <EuiLoadingSpinner size="s" /> : totalTriggeredAlerts || '-'}
</h3>
</EuiTitle>
</EuiText>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText size="s" color="subdued">
{i18n.translate('xpack.infra.logs.alertDetails.chartHistory.alertsTriggered', {
defaultMessage: 'Alerts triggered',
})}
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexGroup gutterSize="xs" direction="column">
<EuiFlexItem grow={false}>
<EuiText>
<EuiTitle size="s">
<h3>
{isLoading ? (
<EuiLoadingSpinner size="s" />
) : avgTimeToRecoverUS ? (
convertTo({
unit: 'minutes',
microseconds: avgTimeToRecoverUS,
extended: true,
}).formatted
) : (
'-'
)}
</h3>
</EuiTitle>
</EuiText>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText size="s" color="subdued">
{i18n.translate('xpack.infra.logs.alertDetails.chartHistory.avgTimeToRecover', {
defaultMessage: 'Avg time to recover',
})}
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexGroup>
<EuiSpacer size="s" />
<CriterionPreview
annotations={[
<LineAnnotation
id="annotations"
key={'annotationsAlertHistory'}
domainType={AnnotationDomainType.XDomain}
dataValues={alertHistoryAnnotations}
style={{
line: {
strokeWidth: 3,
stroke: euiThemeVars.euiColorDangerText,
opacity: 1,
},
}}
marker={<EuiIcon type="warning" color="danger" />}
markerBody={(annotationData) => (
<>
<EuiBadge color="danger">
<EuiText size="xs" color="white">
{annotationData.header}
</EuiText>
</EuiBadge>
<EuiSpacer size="xs" />
</>
)}
markerPosition={Position.Top}
/>,
]}
ruleParams={{ ...rule.params, timeSize: 1, timeUnit: 'd' }}
logViewReference={rule.params.logView}
chartCriterion={criteria as PartialCriterion}
showThreshold={true}
executionTimeRange={executionTimeRange}
filterSeriesByGroupName={groups?.map((group) => group.value).join(', ')}
/>
</EuiPanel>
);
};
// eslint-disable-next-line import/no-default-export
export default LogsHistoryChart;

View file

@ -12,7 +12,6 @@ import {
ALERT_CONTEXT,
ALERT_END,
ALERT_EVALUATION_VALUE,
ALERT_INSTANCE_ID,
ALERT_START,
} from '@kbn/rule-data-utils';
import moment from 'moment';
@ -35,9 +34,7 @@ import { Threshold } from '../../../common/components/threshold';
import { LogRateAnalysis } from './components/log_rate_analysis';
import { LogThresholdCountChart, LogThresholdRatioChart } from './components/threhsold_chart';
import { useLicense } from '../../../../hooks/use_license';
import type { Group } from './types';
const LogsHistoryChart = React.lazy(() => import('./components/logs_history_chart'));
const formatThreshold = (threshold: number) => String(threshold);
const AlertDetailsAppSection = ({
@ -65,16 +62,6 @@ const AlertDetailsAppSection = ({
.filter(identity)
.join(' AND ')
: '';
const groups: Group[] | undefined = rule.params.groupBy
? rule.params.groupBy.flatMap((field) => {
const value: string = get(
alert.fields[ALERT_CONTEXT],
['groupByKeys', ...field.split('.')],
null
);
return value ? { field, value } : [];
})
: undefined;
const { derivedDataView } = useLogView({
initialLogViewReference: rule.params.logView,
@ -236,24 +223,6 @@ const AlertDetailsAppSection = ({
} else return null;
};
const getLogsHistoryChart = () => {
return (
rule &&
rule.params.criteria.length === 1 && (
<EuiFlexItem>
<LogsHistoryChart
rule={{
...rule,
params: { ...rule.params, timeSize: 12, timeUnit: 'h' },
}}
instanceId={alert.fields[ALERT_INSTANCE_ID]}
groups={groups}
/>
</EuiFlexItem>
)
);
};
const getLogRateAnalysisSection = () => {
return hasLicenseForLogRateAnalysis ? <LogRateAnalysis rule={rule} alert={alert} /> : null;
};
@ -263,7 +232,6 @@ const AlertDetailsAppSection = ({
{getLogRatioChart()}
{getLogCountChart()}
{getLogRateAnalysisSection()}
{getLogsHistoryChart()}
</EuiFlexGroup>
);
};

View file

@ -107,7 +107,7 @@ describe('AlertDetailsAppSection', () => {
it('should render rule and alert data', async () => {
const result = renderComponent();
expect((await result.findByTestId('thresholdAlertOverviewSection')).children.length).toBe(7);
expect((await result.findByTestId('thresholdAlertOverviewSection')).children.length).toBe(6);
expect(result.getByTestId('thresholdRule-2000-2500')).toBeTruthy();
});
@ -183,7 +183,7 @@ describe('AlertDetailsAppSection', () => {
{ ['kibana.alert.end']: '2023-03-28T14:40:00.000Z' }
);
expect(alertDetailsAppSectionComponent.getAllByTestId('RuleConditionChart').length).toBe(7);
expect(alertDetailsAppSectionComponent.getAllByTestId('RuleConditionChart').length).toBe(6);
expect(mockedRuleConditionChart.mock.calls[0]).toMatchSnapshot();
});

View file

@ -39,7 +39,6 @@ import type {
import moment from 'moment';
import { LOGS_EXPLORER_LOCATOR_ID, LogsExplorerLocatorParams } from '@kbn/deeplinks-observability';
import { TimeRange } from '@kbn/es-query';
import { AlertHistoryChart } from './alert_history';
import { useLicense } from '../../../../hooks/use_license';
import { useKibana } from '../../../../utils/kibana_react';
import { getGroupFilters } from '../../../../../common/custom_threshold_rule/helpers/get_group';
@ -293,7 +292,6 @@ export default function AlertDetailsAppSection({
{hasLogRateAnalysisLicense && (
<LogRateAnalysis alert={alert} dataView={dataView} rule={rule} services={services} />
)}
<AlertHistoryChart alert={alert} dataView={dataView} rule={rule} />
</EuiFlexGroup>
);
}

View file

@ -1,224 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import moment from 'moment';
import React, { useState } from 'react';
import { v4 as uuidv4 } from 'uuid';
import { RuleTypeParams } from '@kbn/alerting-plugin/common';
import { EventAnnotationConfig } from '@kbn/event-annotation-common';
import { DataView } from '@kbn/data-views-plugin/common';
import {
EuiPanel,
EuiFlexGroup,
EuiFlexItem,
EuiTitle,
EuiText,
EuiSpacer,
EuiLoadingSpinner,
useEuiTheme,
EuiSelect,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { ALERT_GROUP, ALERT_INSTANCE_ID, type AlertConsumers } from '@kbn/rule-data-utils';
import { useAlertsHistory } from '@kbn/observability-alert-details';
import { convertTo } from '../../../../../common/utils/formatters';
import { getGroupFilters } from '../../../../../common/custom_threshold_rule/helpers/get_group';
import { CustomMetricExpressionParams } from '../../../../../common/custom_threshold_rule/types';
import { useKibana } from '../../../../utils/kibana_react';
import { AlertParams } from '../../types';
import { RuleConditionChart } from '../rule_condition_chart/rule_condition_chart';
import { CustomThresholdAlert, CustomThresholdRule } from '../types';
import { generateChartTitleAndTooltip } from './helpers/generate_chart_title_and_tooltip';
const DEFAULT_INTERVAL = '1d';
const SERIES_TYPE = 'bar_stacked';
interface Props {
alert: CustomThresholdAlert;
rule: CustomThresholdRule;
dataView?: DataView;
}
const dateRange = {
from: 'now-30d',
to: 'now+1d',
};
export function AlertHistoryChart({ rule, dataView, alert }: Props) {
const { http, notifications } = useKibana().services;
const { euiTheme } = useEuiTheme();
const ruleParams = rule.params as RuleTypeParams & AlertParams;
const groups = alert.fields[ALERT_GROUP];
const instanceId = alert.fields[ALERT_INSTANCE_ID];
const featureIds = [rule.consumer as AlertConsumers];
const options = rule.params.criteria.map((criterion, index) => {
const { title, tooltip } = generateChartTitleAndTooltip(criterion, 27);
return {
text: title,
title: tooltip,
};
});
const [selectedCriterion, setSelectedCriterion] = useState<CustomMetricExpressionParams>(
rule.params.criteria[0]
);
const {
data: { histogramTriggeredAlerts, avgTimeToRecoverUS, totalTriggeredAlerts },
isLoading,
isError,
} = useAlertsHistory({
http,
featureIds,
ruleId: rule.id,
dateRange,
instanceId,
});
if (isError) {
notifications?.toasts.addDanger({
title: i18n.translate('xpack.observability.customThreshold.alertHistory.error.toastTitle', {
defaultMessage: 'Alerts history chart error',
}),
text: i18n.translate(
'xpack.observability.customThreshold.alertHistory.error.toastDescription',
{
defaultMessage: `An error occurred when fetching alert history chart data`,
}
),
});
}
const annotations: EventAnnotationConfig[] =
histogramTriggeredAlerts
?.filter((annotation) => annotation.doc_count > 0)
.map((annotation) => {
return {
type: 'manual',
id: uuidv4(),
label: String(annotation.doc_count),
key: {
type: 'point_in_time',
timestamp: moment(new Date(annotation.key_as_string!))
.startOf('day')
.add(12, 'h')
.toISOString(),
},
lineWidth: 2,
color: euiTheme.colors.danger,
icon: 'alert',
textVisibility: true,
};
}) || [];
return (
<EuiPanel hasBorder={true} data-test-subj="AlertDetails">
<EuiFlexGroup>
<EuiFlexGroup direction="column" gutterSize="none" responsive={false}>
<EuiFlexItem grow={false}>
<EuiTitle size="xs">
<h2>
{i18n.translate('xpack.observability.customThreshold.alertHistory.chartTitle', {
defaultMessage: 'Alerts history',
})}
</h2>
</EuiTitle>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText size="s" color="subdued">
{i18n.translate('xpack.observability.customThreshold.alertHistory.last30days', {
defaultMessage: 'Last 30 days',
})}
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
{rule.params.criteria.length > 1 && (
<EuiSelect
data-test-subj="o11yAlertHistoryChartSelect"
options={options}
onChange={(e) =>
setSelectedCriterion(
rule.params.criteria[
options.map((option) => option.text).indexOf(e.target.value) ?? 0
]
)
}
/>
)}
</EuiFlexGroup>
<EuiSpacer size="s" />
<EuiFlexGroup gutterSize="l">
<EuiFlexItem grow={false}>
<EuiFlexGroup gutterSize="xs" direction="column">
<EuiFlexItem grow={false}>
<EuiText color="danger">
<EuiTitle size="s">
<h3>
{isLoading ? <EuiLoadingSpinner size="s" /> : totalTriggeredAlerts || '-'}
</h3>
</EuiTitle>
</EuiText>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText size="s" color="subdued">
{i18n.translate(
'xpack.observability.customThreshold.alertHistory.alertsTriggered',
{
defaultMessage: 'Alerts triggered',
}
)}
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexGroup gutterSize="xs" direction="column">
<EuiFlexItem grow={false}>
<EuiText>
<EuiTitle size="s">
<h3>
{isLoading ? (
<EuiLoadingSpinner size="s" />
) : avgTimeToRecoverUS ? (
convertTo({
unit: 'minutes',
microseconds: avgTimeToRecoverUS,
extended: true,
}).formatted
) : (
'-'
)}
</h3>
</EuiTitle>
</EuiText>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText size="s" color="subdued">
{i18n.translate('xpack.observability.customThreshold.alertHistory.avgTimeToRecover', {
defaultMessage: 'Avg time to recover',
})}
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexGroup>
<EuiSpacer size="s" />
<RuleConditionChart
additionalFilters={getGroupFilters(groups)}
annotations={annotations}
chartOptions={{
// For alert details page, the series type needs to be changed to 'bar_stacked'
// due to https://github.com/elastic/elastic-charts/issues/2323
seriesType: SERIES_TYPE,
interval: DEFAULT_INTERVAL,
}}
dataView={dataView}
groupBy={ruleParams.groupBy}
metricExpression={selectedCriterion}
searchConfiguration={ruleParams.searchConfiguration}
timeRange={dateRange}
/>
</EuiPanel>
);
}

View file

@ -14,12 +14,12 @@ import {
type LogRateAnalysisType,
} from '@kbn/aiops-log-rate-analysis/log_rate_analysis_type';
import { LogRateAnalysisContent, type LogRateAnalysisResultsData } from '@kbn/aiops-plugin/public';
import { Rule } from '@kbn/alerting-plugin/common';
import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import type { Message } from '@kbn/observability-ai-assistant-plugin/public';
import { ALERT_END } from '@kbn/rule-data-utils';
import { Rule } from '@kbn/triggers-actions-ui-plugin/public';
import { CustomThresholdRuleTypeParams } from '../../types';
import { TopAlert } from '../../../..';
import { Color, colorTransformer } from '../../../../../common/custom_threshold_rule/color_palette';

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import { Rule } from '@kbn/alerting-plugin/common';
import type { Rule } from '@kbn/triggers-actions-ui-plugin/public';
import { TopAlert } from '../../..';
import { CustomThresholdAlertFields, CustomThresholdRuleTypeParams } from '../types';

View file

@ -16,12 +16,11 @@ export const buildCustomThresholdRule = (
rule: Partial<CustomThresholdRule> = {}
): CustomThresholdRule => {
return {
alertTypeId: 'metrics.alert.threshold',
ruleTypeId: 'metrics.alert.threshold',
createdBy: 'admin',
updatedBy: 'admin',
createdAt: new Date('2023-02-20T15:25:32.125Z'),
updatedAt: new Date('2023-03-02T16:24:41.177Z'),
apiKey: 'apiKey',
apiKeyOwner: 'admin',
notifyWhen: null,
muteAll: false,

View file

@ -53,7 +53,7 @@ const mockObservabilityAIAssistant = observabilityAIAssistantPluginMock.createSt
const mockKibana = () => {
useKibanaMock.mockReturnValue({
services: {
...kibanaStartMock.startContract(),
...kibanaStartMock.startContract().services,
cases: casesPluginMock.createStartContract(),
application: { currentAppId$: from('mockedApp') },
http: {
@ -63,9 +63,6 @@ const mockKibana = () => {
},
observabilityAIAssistant: mockObservabilityAIAssistant,
theme: {},
triggersActionsUi: {
ruleTypeRegistry,
},
},
});
};
@ -78,6 +75,7 @@ jest.mock('../../hooks/use_fetch_rule', () => {
rule: {
id: 'ruleId',
name: 'ruleName',
consumer: 'logs',
},
}),
};
@ -139,7 +137,7 @@ describe('Alert details', () => {
expect(alertDetails.queryByTestId('alertDetailsError')).toBeFalsy();
expect(alertDetails.queryByTestId('alertDetailsPageTitle')).toBeTruthy();
expect(alertDetails.queryByTestId('alertDetailsTabbedContent')).toBeTruthy();
expect(alertDetails.queryByTestId('alert-summary-container')).toBeTruthy();
expect(alertDetails.queryByTestId('alert-summary-container')).toBeFalsy();
expect(alertDetails.queryByTestId('overviewTab')).toBeTruthy();
expect(alertDetails.queryByTestId('metadataTab')).toBeTruthy();
});

View file

@ -44,7 +44,9 @@ import { observabilityFeatureId } from '../../../common';
import { paths } from '../../../common/locators/paths';
import { HeaderMenu } from '../overview/components/header_menu/header_menu';
import { AlertOverview } from '../../components/alert_overview/alert_overview';
import { CustomThresholdRule } from '../../components/custom_threshold/components/types';
import { AlertDetailContextualInsights } from './alert_details_contextual_insights';
import { AlertHistoryChart } from './components/alert_history';
interface AlertDetailsPathParams {
alertId: string;
@ -77,8 +79,9 @@ export function AlertDetails() {
const [ruleTypeModel, setRuleTypeModel] = useState<RuleTypeModel | null>(null);
const CasesContext = getCasesContext();
const userCasesPermissions = canUseCases([observabilityFeatureId]);
const ruleId = alertDetail?.formatted.fields[ALERT_RULE_UUID];
const { rule } = useFetchRule({
ruleId: alertDetail?.formatted.fields[ALERT_RULE_UUID],
ruleId,
});
const [summaryFields, setSummaryFields] = useState<AlertSummaryField[]>();
const [alertStatus, setAlertStatus] = useState<AlertStatus>();
@ -165,8 +168,8 @@ export function AlertDetails() {
const overviewTab = alertDetail ? (
AlertDetailsAppSection &&
/*
when feature flag is enabled, show alert details page with customized overview tab,
otherwise show default overview tab
when feature flag is enabled, show alert details page with customized overview tab,
otherwise show default overview tab
*/
isAlertDetailsEnabledPerApp(alertDetail.formatted, config) ? (
<>
@ -175,13 +178,20 @@ export function AlertDetails() {
<AlertDetailContextualInsights alert={alertDetail} />
<EuiSpacer size="l" />
{rule && alertDetail.formatted && (
<AlertDetailsAppSection
alert={alertDetail.formatted}
rule={rule}
timeZone={timeZone}
setAlertSummaryFields={setSummaryFields}
ruleLink={http.basePath.prepend(paths.observability.ruleDetails(rule.id))}
/>
<>
<AlertDetailsAppSection
alert={alertDetail.formatted}
rule={rule}
timeZone={timeZone}
setAlertSummaryFields={setSummaryFields}
ruleLink={http.basePath.prepend(paths.observability.ruleDetails(rule.id))}
/>
<EuiSpacer size="l" />
<AlertHistoryChart
alert={alertDetail.formatted}
rule={rule as unknown as CustomThresholdRule}
/>
</>
)}
</>
) : (
@ -278,7 +288,7 @@ export function getScreenDescription(alertDetail: AlertData) {
Use the following alert fields as background information for generating a response. Do not list them as bullet points in the response.
${Object.entries(getRelevantAlertFields(alertDetail))
.map(([key, value]) => `${key}: ${JSON.stringify(value)}`)
.join('\n')}
.join('\n')}
`);
}

View file

@ -0,0 +1,192 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React from 'react';
import { ALERTING_FEATURE_ID } from '@kbn/alerting-plugin/common';
import {
EuiPanel,
EuiFlexGroup,
EuiFlexItem,
EuiTitle,
EuiText,
EuiSpacer,
EuiLoadingSpinner,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { ALERT_INSTANCE_ID, ALERT_RULE_UUID, type AlertConsumers } from '@kbn/rule-data-utils';
import { useAlertsHistory } from '@kbn/observability-alert-details';
import type { Rule } from '@kbn/triggers-actions-ui-plugin/public';
import { convertTo } from '../../../../common/utils/formatters';
import { useFetchRuleTypes } from '../../../hooks/use_fetch_rule_types';
import { useGetFilteredRuleTypes } from '../../../hooks/use_get_filtered_rule_types';
import { useKibana } from '../../../utils/kibana_react';
import { TopAlert } from '../../..';
import { getDefaultAlertSummaryTimeRange } from '../../../utils/alert_summary_widget';
interface Props {
alert: TopAlert;
rule: Rule;
}
const dateRange = {
from: 'now-30d',
to: 'now+1d',
};
export function AlertHistoryChart({ rule, alert }: Props) {
const {
http,
notifications,
triggersActionsUi: { getAlertSummaryWidget: AlertSummaryWidget },
} = useKibana().services;
const instanceId = alert.fields[ALERT_INSTANCE_ID];
const filteredRuleTypes = useGetFilteredRuleTypes();
const { ruleTypes } = useFetchRuleTypes({
filterByRuleTypeIds: filteredRuleTypes,
});
const ruleType = ruleTypes?.find((type) => type.id === rule?.ruleTypeId);
const featureIds =
rule?.consumer === ALERTING_FEATURE_ID && ruleType?.producer
? [ruleType.producer as AlertConsumers]
: rule
? [rule.consumer as AlertConsumers]
: [];
const ruleId = alert.fields[ALERT_RULE_UUID];
const {
data: { avgTimeToRecoverUS, totalTriggeredAlerts },
isLoading,
isError,
} = useAlertsHistory({
http,
featureIds,
ruleId: rule.id,
dateRange,
instanceId,
});
if (isError) {
notifications?.toasts.addDanger({
title: i18n.translate('xpack.observability.alertDetailsPage.alertHistory.error.toastTitle', {
defaultMessage: 'Alerts history chart error',
}),
text: i18n.translate(
'xpack.observability.alertDetailsPage.alertHistory.error.toastDescription',
{
defaultMessage: `An error occurred when fetching alert history chart data`,
}
),
});
}
return (
<EuiPanel hasBorder={true} data-test-subj="AlertDetails">
<EuiFlexGroup direction="column" gutterSize="none" responsive={false}>
<EuiFlexItem grow={false}>
<EuiTitle size="xs">
<h2>
{i18n.translate('xpack.observability.alertDetailsPage.alertHistory.chartTitle', {
defaultMessage: 'Alerts history',
})}
</h2>
</EuiTitle>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText size="s" color="subdued">
{i18n.translate('xpack.observability.alertDetailsPage.alertHistory.last30days', {
defaultMessage: 'Last 30 days',
})}
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer size="s" />
<EuiFlexGroup gutterSize="l">
<EuiFlexItem grow={false}>
<EuiFlexGroup gutterSize="xs" direction="column">
<EuiFlexItem grow={false}>
<EuiText color="danger">
<EuiTitle size="s">
<h3>
{isLoading ? <EuiLoadingSpinner size="s" /> : totalTriggeredAlerts || '-'}
</h3>
</EuiTitle>
</EuiText>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText size="s" color="subdued">
{i18n.translate(
'xpack.observability.alertDetailsPage.alertHistory.alertsTriggered',
{
defaultMessage: 'Alerts triggered',
}
)}
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexGroup gutterSize="xs" direction="column">
<EuiFlexItem grow={false}>
<EuiText>
<EuiTitle size="s">
<h3>
{isLoading ? (
<EuiLoadingSpinner size="s" />
) : avgTimeToRecoverUS ? (
convertTo({
unit: 'minutes',
microseconds: avgTimeToRecoverUS,
extended: true,
}).formatted
) : (
'-'
)}
</h3>
</EuiTitle>
</EuiText>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText size="s" color="subdued">
{i18n.translate(
'xpack.observability.alertDetailsPage.alertHistory.avgTimeToRecover',
{
defaultMessage: 'Avg time to recover',
}
)}
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexGroup>
<EuiSpacer size="s" />
<AlertSummaryWidget
featureIds={featureIds}
timeRange={getDefaultAlertSummaryTimeRange()}
fullSize
hideStats
filter={{
bool: {
must: [
{
term: {
[ALERT_RULE_UUID]: ruleId,
},
},
...(instanceId && instanceId !== '*'
? [
{
term: {
[ALERT_INSTANCE_ID]: instanceId,
},
},
]
: []),
],
},
}}
/>
</EuiPanel>
);
}

View file

@ -10,7 +10,6 @@ import React, { useEffect } from 'react';
import { AlertSummaryField } from '@kbn/observability-plugin/public';
import { useKibana } from '../../../../utils/kibana_react';
import { useFetchSloDetails } from '../../../../hooks/use_fetch_slo_details';
import { AlertsHistoryPanel } from './components/alerts_history/alerts_history_panel';
import { ErrorRatePanel } from './components/error_rate/error_rate_panel';
import { CustomAlertDetailsPanel } from './components/custom_panels/custom_panels';
import { BurnRateAlert, BurnRateRule } from './types';
@ -69,7 +68,6 @@ export default function AlertDetailsAppSection({
<EuiFlexGroup direction="column" data-test-subj="overviewSection">
<ErrorRatePanel alert={alert} slo={slo} isLoading={isLoading} />
<CustomAlertDetailsPanel alert={alert} slo={slo} rule={rule} />
<AlertsHistoryPanel alert={alert} rule={rule} slo={slo} isLoading={isLoading} />
</EuiFlexGroup>
);
}

View file

@ -1,208 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import {
EuiFlexGroup,
EuiFlexItem,
EuiIcon,
EuiLink,
EuiLoadingChart,
EuiLoadingSpinner,
EuiPanel,
EuiStat,
EuiText,
EuiTextColor,
EuiTitle,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import { useAlertsHistory } from '@kbn/observability-alert-details';
import rison from '@kbn/rison';
import { ALERT_INSTANCE_ID, ALERT_RULE_PARAMETERS } from '@kbn/rule-data-utils';
import { GetSLOResponse } from '@kbn/slo-schema';
import moment from 'moment';
import React from 'react';
import { convertTo } from '@kbn/observability-plugin/public';
import { useKibana } from '../../../../../../utils/kibana_react';
import { WindowSchema } from '../../../../../../typings';
import { ErrorRateChart } from '../../../../error_rate_chart';
import { BurnRateAlert, BurnRateRule } from '../../types';
import { getActionGroupFromReason } from '../../utils/alert';
interface Props {
slo?: GetSLOResponse;
alert: BurnRateAlert;
rule: BurnRateRule;
isLoading: boolean;
}
export function AlertsHistoryPanel({ rule, slo, alert, isLoading }: Props) {
const {
services: { http },
} = useKibana();
const { isLoading: isAlertsHistoryLoading, data } = useAlertsHistory({
featureIds: ['slo'],
ruleId: rule.id,
dateRange: {
from: 'now-30d',
to: 'now',
},
http,
instanceId: alert.fields[ALERT_INSTANCE_ID],
});
const actionGroup = getActionGroupFromReason(alert.reason);
const actionGroupWindow = (
(alert.fields[ALERT_RULE_PARAMETERS]?.windows ?? []) as WindowSchema[]
).find((window: WindowSchema) => window.actionGroup === actionGroup);
const dataTimeRange = {
from: moment().subtract(30, 'day').toDate(),
to: new Date(),
};
function getAlertsLink() {
const kuery = `kibana.alert.rule.uuid:"${rule.id}"`;
return http.basePath.prepend(`/app/observability/alerts?_a=${rison.encode({ kuery })}`);
}
if (isLoading) {
return <EuiLoadingChart size="m" mono data-test-subj="loading" />;
}
if (!slo) {
return null;
}
return (
<EuiPanel paddingSize="m" color="transparent" hasBorder data-test-subj="alertsHistoryPanel">
<EuiFlexGroup direction="column" gutterSize="m">
<EuiFlexGroup direction="column" gutterSize="none">
<EuiFlexGroup direction="row" justifyContent="spaceBetween">
<EuiFlexItem grow={false}>
<EuiTitle size="xs">
<h2>
{i18n.translate(
'xpack.slo.burnRateRule.alertDetailsAppSection.alertsHistory.title',
{ defaultMessage: '{sloName} alerts history', values: { sloName: slo.name } }
)}
</h2>
</EuiTitle>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiLink color="text" href={getAlertsLink()} data-test-subj="alertsLink">
<EuiIcon type="sortRight" style={{ marginRight: '4px' }} />
<FormattedMessage
id="xpack.slo.burnRateRule.alertDetailsAppSection.alertsHistory.alertsLink"
defaultMessage="View alerts"
/>
</EuiLink>
</EuiFlexItem>
</EuiFlexGroup>
<EuiFlexItem grow={false}>
<EuiText size="s" color="subdued">
<span>
{i18n.translate(
'xpack.slo.burnRateRule.alertDetailsAppSection.alertsHistory.subtitle',
{
defaultMessage: 'Last 30 days',
}
)}
</span>
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
<EuiFlexGroup direction="row" gutterSize="m" justifyContent="flexStart">
<EuiFlexItem grow={false}>
<EuiStat
title={
isAlertsHistoryLoading ? (
<EuiLoadingSpinner size="s" />
) : data.totalTriggeredAlerts ? (
data.totalTriggeredAlerts
) : (
'-'
)
}
titleColor="danger"
titleSize="m"
textAlign="left"
isLoading={isLoading}
data-test-subj="alertsTriggeredStats"
reverse
description={
<EuiTextColor color="default">
<span>
{i18n.translate(
'xpack.slo.burnRateRule.alertDetailsAppSection.alertsHistory.triggeredAlertsStatsTitle',
{ defaultMessage: 'Alerts triggered' }
)}
</span>
</EuiTextColor>
}
/>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiStat
title={
isAlertsHistoryLoading ? (
<EuiLoadingSpinner size="s" />
) : data.avgTimeToRecoverUS ? (
convertTo({
unit: 'minutes',
microseconds: data.avgTimeToRecoverUS,
extended: true,
}).formatted
) : (
'-'
)
}
titleColor="default"
titleSize="m"
textAlign="left"
isLoading={isLoading}
data-test-subj="avgTimeToRecoverStat"
reverse
description={
<EuiTextColor color="default">
<span>
{i18n.translate(
'xpack.slo.burnRateRule.alertDetailsAppSection.alertsHistory.avgTimeToRecoverStatsTitle',
{ defaultMessage: 'Avg time to recover' }
)}
</span>
</EuiTextColor>
}
/>
</EuiFlexItem>
</EuiFlexGroup>
<EuiFlexGroup direction="row" gutterSize="m" justifyContent="flexStart">
<EuiFlexItem>
{isAlertsHistoryLoading ? (
<EuiLoadingSpinner size="s" />
) : (
<ErrorRateChart
slo={slo}
dataTimeRange={dataTimeRange}
threshold={actionGroupWindow!.burnRateThreshold}
annotations={data.histogramTriggeredAlerts
.filter((a) => a.doc_count > 0)
.map((a) => ({
date: new Date(a.key_as_string!),
total: a.doc_count,
}))}
showErrorRateAsLine
/>
)}
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexGroup>
</EuiPanel>
);
}

View file

@ -29,7 +29,6 @@
"@kbn/kibana-utils-plugin",
"@kbn/slo-schema",
"@kbn/alerting-plugin",
"@kbn/observability-alert-details",
"@kbn/rison",
"@kbn/embeddable-plugin",
"@kbn/lens-plugin",

View file

@ -8679,7 +8679,6 @@
"xpack.apm.agentExplorerInstanceTable.agentVersionColumnLabel.multipleVersions": "{versionsCount, plural, one {1 version} other {# versions}}",
"xpack.apm.agentExplorerInstanceTable.noServiceNodeName.tooltip.linkToDocs": "Vous pouvez configurer le nom du nœud de service via {seeDocs}.",
"xpack.apm.agentExplorerTable.agentVersionColumnLabel.multipleVersions": "{versionsCount, plural, one {1 version} other {# versions}}",
"xpack.apm.alertDetails.latencyAlertHistoryChart.error.toastDescription": "Une erreur s'est produite lors de la récupération des données graphiques de l'historique d'alertes de latence pour {serviceName}",
"xpack.apm.alerts.anomalySeverity.scoreDetailsDescription": "score {value} {value, select, critical {} other {et plus}}",
"xpack.apm.alerts.timeLabelForData": "Dernières {lookback} {timeLabel} de données, ({displayedGroups} groupes affichés sur {totalGroups}",
"xpack.apm.alertTypes.errorCount.reason": "Le nombre d'erreurs est {measured} au cours des derniers/dernières {interval} pour {group}. Alerte lorsque > {threshold}.",
@ -9056,7 +9055,6 @@
"xpack.apm.aggregatedTransactions.fallback.tooltip": "Cette page utilise les données d'événements de transactions lorsqu'aucun événement d'indicateur n'a été trouvé dans la plage temporelle actuelle, ou lorsqu'un filtre a été appliqué en fonction des champs indisponibles dans les documents des événements d'indicateurs.",
"xpack.apm.alertDetails.error.toastDescription": "Impossible de charger les graphiques de la page de détails dalerte. Veuillez essayer dactualiser la page si lalerte vient dêtre créée",
"xpack.apm.alertDetails.error.toastTitle": "Une erreur sest produite lors de lidentification de la plage temporelle de lalerte.",
"xpack.apm.alertDetails.latencyAlertHistoryChart.error.toastTitle": "Erreur du graphique dhistorique des alertes de latence",
"xpack.apm.alerting.fields.environment": "Environnement",
"xpack.apm.alerting.fields.error.group.id": "Clé du groupe d'erreurs",
"xpack.apm.alerting.fields.service": "Service",
@ -9535,10 +9533,6 @@
"xpack.apm.labs.description": "Essayez les fonctionnalités APM qui sont en version d'évaluation technique et en cours de progression.",
"xpack.apm.labs.feedbackButtonLabel": "Dites-nous ce que vous pensez !",
"xpack.apm.labs.reload": "Recharger pour appliquer les modifications",
"xpack.apm.latencyChartHistory.alertsTriggered": "Alertes déclenchées",
"xpack.apm.latencyChartHistory.avgTimeToRecover": "Temps moyen de récupération",
"xpack.apm.latencyChartHistory.chartTitle": " historique des alertes de latence",
"xpack.apm.latencyChartHistory.last30days": "30 derniers jours",
"xpack.apm.latencyCorrelations.licenseCheckText": "Pour utiliser les corrélations de latence, vous devez disposer d'une licence Elastic Platinum. Elle vous permettra de découvrir quels champs sont corrélés à de faibles performances.",
"xpack.apm.license.button": "Commencer l'essai",
"xpack.apm.license.title": "Commencer un essai gratuit de 30 jours",
@ -21189,8 +21183,6 @@
"xpack.infra.waffle.customMetrics.editMode.deleteAriaLabel": "Supprimer l'indicateur personnalisé pour {name}",
"xpack.infra.waffle.customMetrics.editMode.editButtonAriaLabel": "Modifier l'indicateur personnalisé pour {name}",
"xpack.infra.waffle.unableToSelectGroupErrorMessage": "Impossible de sélectionner les options de regroupement pour {nodeType}",
"xpack.infra.alertDetails.logsAlertHistoryChart.error.toastDescription": "Une erreur sest produite lors de la récupération des données graphiques de lhistorique dalertes de logs",
"xpack.infra.alertDetails.logsAlertHistoryChart.error.toastTitle": "Erreur graphique dans lhistorique dalertes de logs",
"xpack.infra.alerting.alertDropdownTitle": "Alertes et règles",
"xpack.infra.alerting.alertFlyout.groupBy.placeholder": "Rien (non groupé)",
"xpack.infra.alerting.alertFlyout.groupByLabel": "Regrouper par",
@ -21418,10 +21410,6 @@
"xpack.infra.logEntryExampleMessageHeaders.logColumnHeader.messageLabel": "Message",
"xpack.infra.logs.alertDetails.chart.ratioTitle": "Ratio de Requête A à Requête B",
"xpack.infra.logs.alertDetails.chartAnnotation.alertStarted": "Alerte démarrée",
"xpack.infra.logs.alertDetails.chartHistory.alertsTriggered": "Alertes déclenchées",
"xpack.infra.logs.alertDetails.chartHistory.avgTimeToRecover": "Temps moyen de récupération",
"xpack.infra.logs.alertDetails.chartHistory.chartTitle": "Historique des alertes de seuil de logs",
"xpack.infra.logs.alertDetails.chartHistory.last30days": "30 derniers jours",
"xpack.infra.logs.alertDetails.logRateAnalysis.sectionTitle": "Analyse du taux de log",
"xpack.infra.logs.alertDetails.logRateAnalysisTitle": "Causes possibles et résolutions",
"xpack.infra.logs.alertDropdown.inlineLogViewCreateAlertContent": "La création d'alertes n'est pas prise en charge avec les vues de log en ligne.",
@ -30032,12 +30020,6 @@
"xpack.observability.customThreshold.alertChartTitle": "Résultat de l'équation pour ",
"xpack.observability.customThreshold.alertDetails.logRateAnalysis.sectionTitle": "Analyse du taux de log",
"xpack.observability.customThreshold.alertDetails.logRateAnalysisTitle": "Causes possibles et résolutions",
"xpack.observability.customThreshold.alertHistory.alertsTriggered": "Alertes déclenchées",
"xpack.observability.customThreshold.alertHistory.avgTimeToRecover": "Temps moyen de récupération",
"xpack.observability.customThreshold.alertHistory.chartTitle": "Historique des alertes",
"xpack.observability.customThreshold.alertHistory.error.toastDescription": "Une erreur s'est produite lors de la récupération des données graphiques de l'historique des alertes",
"xpack.observability.customThreshold.alertHistory.error.toastTitle": "Erreur lors de la récupération des données graphiques de l'historique des alertes",
"xpack.observability.customThreshold.alertHistory.last30days": "30 derniers jours",
"xpack.observability.customThreshold.rule..charts.error_equation.description": "Vérifiez l'équation de la règle.",
"xpack.observability.customThreshold.rule..charts.error_equation.title": "Une erreur s'est produite lors de l'affichage du graphique",
"xpack.observability.customThreshold.rule..charts.errorMessage": "Oups, un problème est survenu",

View file

@ -8666,7 +8666,6 @@
"xpack.apm.agentExplorerInstanceTable.agentVersionColumnLabel.multipleVersions": "{versionsCount, plural, one {1バージョン} other {# バージョン}}",
"xpack.apm.agentExplorerInstanceTable.noServiceNodeName.tooltip.linkToDocs": "{seeDocs}を使用してサービスノード名を構成できます。",
"xpack.apm.agentExplorerTable.agentVersionColumnLabel.multipleVersions": "{versionsCount, plural, one {1バージョン} other {# バージョン}}",
"xpack.apm.alertDetails.latencyAlertHistoryChart.error.toastDescription": "{serviceName}のレイテンシアラート履歴グラフデータを取得するときに、エラーが発生しました。",
"xpack.apm.alerts.anomalySeverity.scoreDetailsDescription": "スコア {value} {value, select, critical {} other {以上}}",
"xpack.apm.alerts.timeLabelForData": "過去{lookback} {timeLabel}のデータ({displayedGroups}/{totalGroups}個のグループを表示)",
"xpack.apm.alertTypes.minimumWindowSize.description": "推奨される最小値は{sizeValue} {sizeUnit}です。これにより、アラートに評価する十分なデータがあることが保証されます。低すぎる値を選択した場合、アラートが想定通りに実行されない可能性があります。",
@ -9037,7 +9036,6 @@
"xpack.apm.aggregatedTransactions.fallback.tooltip": "メトリックイベントが現在の時間範囲にないか、メトリックイベントドキュメントにないフィールドに基づいてフィルターが適用されたため、このページはトランザクションイベントデータを使用しています。",
"xpack.apm.alertDetails.error.toastDescription": "アラート詳細ページのグラフを読み込めません。アラートが新しく作成された場合は、ページを更新してください",
"xpack.apm.alertDetails.error.toastTitle": "アラート時間範囲を特定するときにエラーが発生しました。",
"xpack.apm.alertDetails.latencyAlertHistoryChart.error.toastTitle": "レイテンシアラート履歴グラフエラー",
"xpack.apm.alerting.fields.environment": "環境",
"xpack.apm.alerting.fields.error.group.id": "エラーグループキー",
"xpack.apm.alerting.fields.service": "サービス",
@ -9516,10 +9514,6 @@
"xpack.apm.labs.description": "現在テクニカルプレビュー中のAPM機能をお試しください。",
"xpack.apm.labs.feedbackButtonLabel": "ご意見をお聞かせください。",
"xpack.apm.labs.reload": "変更を適用するには、再読み込みしてください",
"xpack.apm.latencyChartHistory.alertsTriggered": "アラートがトリガーされました",
"xpack.apm.latencyChartHistory.avgTimeToRecover": "回復までの平均時間",
"xpack.apm.latencyChartHistory.chartTitle": " レイテンシアラート履歴",
"xpack.apm.latencyChartHistory.last30days": "過去30日間",
"xpack.apm.latencyCorrelations.licenseCheckText": "遅延の相関関係を使用するには、Elastic Platinumライセンスのサブスクリプションが必要です。使用すると、パフォーマンスの低下に関連しているフィールドを検出できます。",
"xpack.apm.license.button": "トライアルを開始",
"xpack.apm.license.title": "無料の 30 日トライアルを開始",
@ -21152,8 +21146,6 @@
"xpack.infra.waffle.customMetrics.editMode.deleteAriaLabel": "{name} のカスタムメトリックを削除",
"xpack.infra.waffle.customMetrics.editMode.editButtonAriaLabel": "{name} のカスタムメトリックを編集",
"xpack.infra.waffle.unableToSelectGroupErrorMessage": "{nodeType} のオプションでグループを選択できません",
"xpack.infra.alertDetails.logsAlertHistoryChart.error.toastDescription": "ログアラート履歴グラフデータを取得するときに、エラーが発生しました。",
"xpack.infra.alertDetails.logsAlertHistoryChart.error.toastTitle": "ログアラート履歴グラフエラー",
"xpack.infra.alerting.alertDropdownTitle": "アラートとルール",
"xpack.infra.alerting.alertFlyout.groupBy.placeholder": "なし(グループなし)",
"xpack.infra.alerting.alertFlyout.groupByLabel": "グループ分けの条件",
@ -21392,10 +21384,6 @@
"xpack.infra.logEntryExampleMessageHeaders.logColumnHeader.messageLabel": "メッセージ",
"xpack.infra.logs.alertDetails.chart.ratioTitle": "クエリAとクエリBの比率",
"xpack.infra.logs.alertDetails.chartAnnotation.alertStarted": "アラートが開始しました",
"xpack.infra.logs.alertDetails.chartHistory.alertsTriggered": "アラートがトリガーされました",
"xpack.infra.logs.alertDetails.chartHistory.avgTimeToRecover": "回復までの平均時間",
"xpack.infra.logs.alertDetails.chartHistory.chartTitle": "ログしきい値アラート履歴",
"xpack.infra.logs.alertDetails.chartHistory.last30days": "過去30日間",
"xpack.infra.logs.alertDetails.logRateAnalysis.sectionTitle": "ログレート分析",
"xpack.infra.logs.alertDetails.logRateAnalysisTitle": "考えられる原因と修正方法",
"xpack.infra.logs.alertDropdown.inlineLogViewCreateAlertContent": "インラインログビューではアラートの作成がサポートされていません",
@ -30004,12 +29992,6 @@
"xpack.observability.customThreshold.alertChartTitle": "式の結果 ",
"xpack.observability.customThreshold.alertDetails.logRateAnalysis.sectionTitle": "ログレート分析",
"xpack.observability.customThreshold.alertDetails.logRateAnalysisTitle": "考えられる原因と修正方法",
"xpack.observability.customThreshold.alertHistory.alertsTriggered": "アラートがトリガーされました",
"xpack.observability.customThreshold.alertHistory.avgTimeToRecover": "回復までの平均時間",
"xpack.observability.customThreshold.alertHistory.chartTitle": "アラート履歴",
"xpack.observability.customThreshold.alertHistory.error.toastDescription": "アラート履歴グラフデータを取得するときに、エラーが発生しました",
"xpack.observability.customThreshold.alertHistory.error.toastTitle": "アラート履歴グラフエラー",
"xpack.observability.customThreshold.alertHistory.last30days": "過去30日間",
"xpack.observability.customThreshold.rule..charts.error_equation.description": "ルール式を確認してください。",
"xpack.observability.customThreshold.rule..charts.error_equation.title": "グラフの表示中にエラーが発生しました",
"xpack.observability.customThreshold.rule..charts.errorMessage": "問題が発生しました",

View file

@ -8682,7 +8682,6 @@
"xpack.apm.agentExplorerInstanceTable.agentVersionColumnLabel.multipleVersions": "{versionsCount, plural, one {1 个版本} other {# 个版本}}",
"xpack.apm.agentExplorerInstanceTable.noServiceNodeName.tooltip.linkToDocs": "您可以通过 {seeDocs} 配置服务节点名称。",
"xpack.apm.agentExplorerTable.agentVersionColumnLabel.multipleVersions": "{versionsCount, plural, one {1 个版本} other {# 个版本}}",
"xpack.apm.alertDetails.latencyAlertHistoryChart.error.toastDescription": "提取 {serviceName} 的延迟告警历史记录图表数据时出错",
"xpack.apm.alerts.anomalySeverity.scoreDetailsDescription": "分数 {value} {value, select, critical {} other {及以上}}",
"xpack.apm.alerts.timeLabelForData": "过去 {lookback} {timeLabel}的数据,显示{displayedGroups}/{totalGroups} 个组",
"xpack.apm.alertTypes.errorCount.reason": "对于 {group},过去 {interval}的错误计数为 {measured}。超出 {threshold} 时告警。",
@ -9059,7 +9058,6 @@
"xpack.apm.aggregatedTransactions.fallback.tooltip": "此页面正在使用事务事件数据,因为当前时间范围内未找到任何指标事件,或者已根据指标事件文档中不可用的字段应用了筛选。",
"xpack.apm.alertDetails.error.toastDescription": "无法加载告警详情页面的图表。如果告警为新建告警,请尝试刷新该页面",
"xpack.apm.alertDetails.error.toastTitle": "识别告警时间范围时出错。",
"xpack.apm.alertDetails.latencyAlertHistoryChart.error.toastTitle": "延迟告警历史记录图表错误",
"xpack.apm.alerting.fields.environment": "环境",
"xpack.apm.alerting.fields.error.group.id": "错误分组密钥",
"xpack.apm.alerting.fields.service": "服务",
@ -9538,10 +9536,6 @@
"xpack.apm.labs.description": "试用正处于技术预览状态和开发中的 APM 功能。",
"xpack.apm.labs.feedbackButtonLabel": "告诉我们您的看法!",
"xpack.apm.labs.reload": "重新加载以应用更改",
"xpack.apm.latencyChartHistory.alertsTriggered": "已触发告警",
"xpack.apm.latencyChartHistory.avgTimeToRecover": "恢复的平均时间",
"xpack.apm.latencyChartHistory.chartTitle": " 延迟告警历史记录",
"xpack.apm.latencyChartHistory.last30days": "过去 30 天",
"xpack.apm.latencyCorrelations.licenseCheckText": "要使用延迟相关性,必须订阅 Elastic 白金级许可证。使用相关性,将能够发现哪些字段与性能差相关。",
"xpack.apm.license.button": "开始试用",
"xpack.apm.license.title": "开始为期 30 天的免费试用",
@ -21195,8 +21189,6 @@
"xpack.infra.waffle.customMetrics.editMode.deleteAriaLabel": "删除 {name} 的定制指标",
"xpack.infra.waffle.customMetrics.editMode.editButtonAriaLabel": "编辑 {name} 的定制指标",
"xpack.infra.waffle.unableToSelectGroupErrorMessage": "无法选择 {nodeType} 的分组依据选项",
"xpack.infra.alertDetails.logsAlertHistoryChart.error.toastDescription": "提取日志告警历史记录图表数据时出错",
"xpack.infra.alertDetails.logsAlertHistoryChart.error.toastTitle": "日志告警历史记录图表错误",
"xpack.infra.alerting.alertDropdownTitle": "告警和规则",
"xpack.infra.alerting.alertFlyout.groupBy.placeholder": "无内容(未分组)",
"xpack.infra.alerting.alertFlyout.groupByLabel": "分组依据",
@ -21424,10 +21416,6 @@
"xpack.infra.logEntryExampleMessageHeaders.logColumnHeader.messageLabel": "消息",
"xpack.infra.logs.alertDetails.chart.ratioTitle": "查询 A 到查询 B 的比率",
"xpack.infra.logs.alertDetails.chartAnnotation.alertStarted": "已启动告警",
"xpack.infra.logs.alertDetails.chartHistory.alertsTriggered": "已触发告警",
"xpack.infra.logs.alertDetails.chartHistory.avgTimeToRecover": "恢复的平均时间",
"xpack.infra.logs.alertDetails.chartHistory.chartTitle": "日志阈值告警历史记录",
"xpack.infra.logs.alertDetails.chartHistory.last30days": "过去 30 天",
"xpack.infra.logs.alertDetails.logRateAnalysis.sectionTitle": "日志速率分析",
"xpack.infra.logs.alertDetails.logRateAnalysisTitle": "可能的原因和补救措施",
"xpack.infra.logs.alertDropdown.inlineLogViewCreateAlertContent": "不支持通过内联日志视图创建告警",
@ -30044,12 +30032,6 @@
"xpack.observability.customThreshold.alertChartTitle": "方程结果用于 ",
"xpack.observability.customThreshold.alertDetails.logRateAnalysis.sectionTitle": "日志速率分析",
"xpack.observability.customThreshold.alertDetails.logRateAnalysisTitle": "可能的原因和补救措施",
"xpack.observability.customThreshold.alertHistory.alertsTriggered": "已触发告警",
"xpack.observability.customThreshold.alertHistory.avgTimeToRecover": "恢复的平均时间",
"xpack.observability.customThreshold.alertHistory.chartTitle": "告警历史记录",
"xpack.observability.customThreshold.alertHistory.error.toastDescription": "提取告警历史记录图表数据时出错",
"xpack.observability.customThreshold.alertHistory.error.toastTitle": "告警历史记录图表错误",
"xpack.observability.customThreshold.alertHistory.last30days": "过去 30 天",
"xpack.observability.customThreshold.rule..charts.error_equation.description": "检查规则方程。",
"xpack.observability.customThreshold.rule..charts.error_equation.title": "渲染图表时出错",
"xpack.observability.customThreshold.rule..charts.errorMessage": "哇哦,出问题了",

View file

@ -24,6 +24,7 @@ export const AlertSummaryWidget = ({
onClick = () => {},
timeRange,
hideChart,
hideStats,
onLoaded,
dependencies: { charts },
}: AlertSummaryWidgetProps & AlertSummaryWidgetDependencies) => {
@ -63,6 +64,7 @@ export const AlertSummaryWidget = ({
dateFormat={timeRange.dateFormat}
recoveredAlertCount={recoveredAlertCount}
hideChart={hideChart}
hideStats={hideStats}
dependencyProps={dependencyProps}
/>
) : null

View file

@ -5,6 +5,7 @@
* 2.0.
*/
import { LIGHT_THEME } from '@elastic/charts';
import { action } from '@storybook/addon-actions';
import { AlertSummaryWidgetCompact as Component } from './alert_summary_widget_compact';
import { mockedAlertSummaryResponse, mockedChartProps } from '../../../mock/alert_summary_widget';
@ -20,5 +21,9 @@ export const Compact = {
chartProps: mockedChartProps,
timeRangeTitle: 'Last 30 days',
onClick: action('clicked'),
dependencyProps: {
baseTheme: LIGHT_THEME,
sparklineTheme: {},
},
},
};

View file

@ -5,6 +5,7 @@
* 2.0.
*/
import { LIGHT_THEME } from '@elastic/charts';
import { action } from '@storybook/addon-actions';
import { AlertSummaryWidgetFullSize as Component } from './alert_summary_widget_full_size';
import { mockedAlertSummaryResponse, mockedChartProps } from '../../../mock/alert_summary_widget';
@ -18,9 +19,13 @@ export const FullSize = {
args: {
...mockedAlertSummaryResponse,
hideChart: false,
hideStats: false,
chartProps: {
...mockedChartProps,
onBrushEnd: action('brushEvent'),
},
dependencyProps: {
baseTheme: LIGHT_THEME,
},
},
};

View file

@ -75,4 +75,15 @@ describe('AlertSummaryWidgetFullSize', () => {
alertSummaryWidget.queryByTestId('alertSummaryWidgetFullSizeChartContainer')
).not.toBeInTheDocument();
});
it('should render AlertSummaryWidgetFullSize without stats', async () => {
const alertSummaryWidget = renderComponent({
hideStats: true,
});
expect(alertSummaryWidget.queryByTestId('alertSummaryWidgetFullSize')).toBeTruthy();
expect(
alertSummaryWidget.queryByTestId('alertSummaryWidgetFullSizeStats')
).not.toBeInTheDocument();
});
});

View file

@ -30,6 +30,7 @@ export interface AlertSummaryWidgetFullSizeProps {
recoveredAlertCount: number;
dateFormat?: string;
hideChart?: boolean;
hideStats?: boolean;
dependencyProps: DependencyProps;
}
@ -40,6 +41,7 @@ export const AlertSummaryWidgetFullSize = ({
dateFormat,
recoveredAlertCount,
hideChart,
hideStats,
dependencyProps: { baseTheme },
}: AlertSummaryWidgetFullSizeProps) => {
const chartData = activeAlerts.map((alert) => alert.doc_count);
@ -55,12 +57,14 @@ export const AlertSummaryWidgetFullSize = ({
hasShadow={false}
paddingSize="none"
>
<EuiFlexItem>
<AlertCounts
activeAlertCount={activeAlertCount}
recoveredAlertCount={recoveredAlertCount}
/>
</EuiFlexItem>
{!hideStats && (
<EuiFlexItem data-test-subj="alertSummaryWidgetFullSizeStats">
<AlertCounts
activeAlertCount={activeAlertCount}
recoveredAlertCount={recoveredAlertCount}
/>
</EuiFlexItem>
)}
{!hideChart && (
<div data-test-subj="alertSummaryWidgetFullSizeChartContainer">
<EuiSpacer size="l" />

View file

@ -53,5 +53,6 @@ export interface AlertSummaryWidgetProps {
timeRange: AlertSummaryTimeRange;
chartProps?: ChartProps;
hideChart?: boolean;
hideStats?: boolean;
onLoaded?: (alertsCount?: AlertsCount) => void;
}