Skip to content

Commit

Permalink
[Alert details page] Update source and status bar (elastic#194842)
Browse files Browse the repository at this point in the history
Closes elastic#187110
Closes elastic#187111
Closes elastic#153834

## Summary

This PR changes how we show source and alert status information, as
shown below:


![image](https://github.com/user-attachments/assets/7ab12e3a-f2e4-4bf8-8b47-955e2e1bb47f)

Also, for the APM Latency rule type, we now have a threshold component
similar to other rules. For the SLO burn rate rule, the SLO link is
added to the source list.

|Rule type|Screenshot|
|---|---|
|APM
Latency|![image](https://github.com/user-attachments/assets/9846570a-46db-46a7-b3a0-96a1e63b58ad)|
|SLO burn
rate|![image](https://github.com/user-attachments/assets/e0042a8a-e1c0-4dc0-a909-73140e8a0b21)|

---------

Co-authored-by: kibanamachine <[email protected]>
  • Loading branch information
maryam-saeidi and kibanamachine authored Oct 4, 2024
1 parent ccd0e17 commit 1a54aab
Show file tree
Hide file tree
Showing 37 changed files with 489 additions and 483 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,11 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { SettingsSpec } from '@elastic/charts';

export const DEFAULT_DATE_FORMAT = 'HH:mm:ss';
export const CHART_ANNOTATION_RED_COLOR = '#BD271E';
export const CHART_SETTINGS: Partial<SettingsSpec> = {
showLegend: false,
};
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import { UI_SETTINGS } from '@kbn/data-plugin/public';
import { Theme } from '@elastic/charts';
import { AlertActiveTimeRangeAnnotation, AlertAnnotation } from '@kbn/observability-alert-details';
import { useKibana } from '@kbn/kibana-react-plugin/public';
import { DEFAULT_DATE_FORMAT } from './constants';
import { CHART_SETTINGS, DEFAULT_DATE_FORMAT } from './constants';
import { useFetcher } from '../../../../hooks/use_fetcher';
import { ChartType } from '../../../shared/charts/helper/get_timeseries_color';
import * as get_timeseries_color from '../../../shared/charts/helper/get_timeseries_color';
Expand Down Expand Up @@ -226,6 +226,7 @@ function FailedTransactionChart({
comparisonEnabled={false}
customTheme={comparisonChartTheme}
timeZone={timeZone}
settings={CHART_SETTINGS}
/>
</EuiPanel>
</EuiFlexItem>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,22 @@
* 2.0.
*/

import React from 'react';
import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui';
import { COMPARATORS } from '@kbn/alerting-comparators';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import { formatAlertEvaluationValue } from '@kbn/observability-plugin/public';
import { useKibana } from '@kbn/kibana-react-plugin/public';
import { formatAlertEvaluationValue, Threshold } from '@kbn/observability-plugin/public';
import { useChartThemes } from '@kbn/observability-shared-plugin/public';
import { getPaddedAlertTimeRange } from '@kbn/observability-get-padded-alert-time-range-util';
import {
ALERT_END,
ALERT_EVALUATION_THRESHOLD,
ALERT_EVALUATION_VALUE,
ALERT_RULE_TYPE_ID,
ALERT_START,
} from '@kbn/rule-data-utils';
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';
import { CoreStart } from '@kbn/core/public';
import {
Expand All @@ -36,12 +39,7 @@ import ThroughputChart from './throughput_chart';
import { AlertDetailsAppSectionProps } from './types';
import { createCallApmApi } from '../../../../services/rest/create_call_apm_api';

export function AlertDetailsAppSection({
rule,
alert,
timeZone,
setAlertSummaryFields,
}: AlertDetailsAppSectionProps) {
export function AlertDetailsAppSection({ rule, alert, timeZone }: AlertDetailsAppSectionProps) {
const { services } = useKibana();
createCallApmApi(services as CoreStart);

Expand All @@ -54,42 +52,25 @@ export function AlertDetailsAppSection({
const transactionName = alert.fields[TRANSACTION_NAME];
const transactionType = alert.fields[TRANSACTION_TYPE];

useEffect(() => {
const alertSummaryFields = [
{
label: (
<FormattedMessage
id="xpack.apm.pages.alertDetails.alertSummary.actualValue"
defaultMessage="Actual value"
/>
),
value: formatAlertEvaluationValue(alertRuleTypeId, alertEvaluationValue),
},
{
label: (
<FormattedMessage
id="xpack.apm.pages.alertDetails.alertSummary.expectedValue"
defaultMessage="Expected value"
/>
),
value: formatAlertEvaluationValue(alertRuleTypeId, alertEvaluationThreshold),
},
];
setAlertSummaryFields(alertSummaryFields);
}, [
alertRuleTypeId,
alertEvaluationValue,
alertEvaluationThreshold,
environment,
serviceName,
transactionName,
setAlertSummaryFields,
]);

const params = rule.params;
const latencyAggregationType = getAggsTypeFromRule(params.aggregationType);
const timeRange = getPaddedAlertTimeRange(alert.fields[ALERT_START]!, alert.fields[ALERT_END]);
const comparisonChartTheme = getComparisonChartTheme();
const chartThemes = useChartThemes();
const thresholdComponent =
alertEvaluationValue && alertEvaluationThreshold ? (
<Threshold
chartProps={chartThemes}
id="latency-threshold"
threshold={[alertEvaluationThreshold]}
value={alertEvaluationValue}
valueFormatter={(d: number) => String(formatAlertEvaluationValue(alertRuleTypeId, d))}
title={i18n.translate('xpack.apm.alertDetails.thresholdTitle', {
defaultMessage: 'Threshold breached',
})}
comparator={COMPARATORS.GREATER_THAN}
/>
) : undefined;

const { from, to } = timeRange;
if (!from || !to) {
Expand Down Expand Up @@ -138,6 +119,7 @@ export function AlertDetailsAppSection({
latencyAggregationType={latencyAggregationType}
comparisonEnabled={false}
offset={''}
threshold={thresholdComponent}
/>
<EuiSpacer size="s" />
<EuiFlexGroup direction="row" gutterSize="s">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import { Theme } from '@elastic/charts';
import { RecursivePartial } from '@elastic/eui';
import React, { useMemo } from 'react';
import React, { useMemo, ReactElement } from 'react';
import { EuiFlexItem, EuiPanel, EuiFlexGroup, EuiTitle } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { BoolQuery } from '@kbn/es-query';
Expand Down Expand Up @@ -38,7 +38,7 @@ import { LatencyAggregationType } from '../../../../../common/latency_aggregatio
import { isLatencyThresholdRuleType } from './helpers';
import { ApmDocumentType } from '../../../../../common/document_type';
import { usePreferredDataSourceAndBucketSize } from '../../../../hooks/use_preferred_data_source_and_bucket_size';
import { DEFAULT_DATE_FORMAT } from './constants';
import { CHART_SETTINGS, DEFAULT_DATE_FORMAT } from './constants';
import { TransactionTypeSelect } from './transaction_type_select';
import { ViewInAPMButton } from './view_in_apm_button';

Expand All @@ -61,6 +61,7 @@ function LatencyChart({
customAlertEvaluationThreshold,
kuery = '',
filters,
threshold,
}: {
alert: TopAlert;
transactionType: string;
Expand All @@ -78,6 +79,7 @@ function LatencyChart({
offset: string;
timeZone: string;
customAlertEvaluationThreshold?: number;
threshold?: ReactElement;
kuery?: string;
filters?: BoolQuery;
}) {
Expand Down Expand Up @@ -245,18 +247,28 @@ function LatencyChart({
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
<TimeseriesChart
id="latencyChart"
annotations={getLatencyChartAdditionalData()}
height={200}
comparisonEnabled={comparisonEnabled}
offset={offset}
fetchStatus={status}
customTheme={comparisonChartTheme}
timeseries={timeseriesLatency}
yLabelFormat={getResponseTimeTickFormatter(latencyFormatter)}
timeZone={timeZone}
/>
<EuiFlexGroup direction="row" gutterSize="m">
{!!threshold && (
<EuiFlexItem style={{ minWidth: 180 }} grow={1}>
{threshold}
</EuiFlexItem>
)}
<EuiFlexItem grow={!!threshold ? 5 : undefined}>
<TimeseriesChart
id="latencyChart"
annotations={getLatencyChartAdditionalData()}
height={200}
comparisonEnabled={comparisonEnabled}
offset={offset}
fetchStatus={status}
customTheme={comparisonChartTheme}
timeseries={timeseriesLatency}
yLabelFormat={getResponseTimeTickFormatter(latencyFormatter)}
timeZone={timeZone}
settings={CHART_SETTINGS}
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiPanel>
</EuiFlexItem>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
EuiIconTip,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { CHART_SETTINGS } from './constants';

import { ChartType, getTimeSeriesColor } from '../../../shared/charts/helper/get_timeseries_color';
import { useFetcher } from '../../../../hooks/use_fetcher';
Expand Down Expand Up @@ -190,6 +191,7 @@ function ThroughputChart({
timeseries={timeseriesThroughput}
yLabelFormat={asExactTransactionRate}
timeZone={timeZone}
settings={CHART_SETTINGS}
/>
</EuiPanel>
</EuiFlexItem>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
*/

import type { Rule } from '@kbn/alerting-plugin/common';
import type { TopAlert, AlertSummaryField } from '@kbn/observability-plugin/public';
import type { TopAlert } from '@kbn/observability-plugin/public';
import type { TIME_UNITS } from '@kbn/triggers-actions-ui-plugin/public';
import type {
SERVICE_NAME,
Expand All @@ -28,5 +28,4 @@ export interface AlertDetailsAppSectionProps {
[SERVICE_ENVIRONMENT]: string;
}>;
timeZone: string;
setAlertSummaryFields: React.Dispatch<React.SetStateAction<AlertSummaryField[] | undefined>>;
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
XYBrushEvent,
XYChartSeriesIdentifier,
Tooltip,
SettingsSpec,
} from '@elastic/charts';
import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiSpacer } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
Expand All @@ -46,11 +47,13 @@ const END_ZONE_LABEL = i18n.translate('xpack.apm.timeseries.endzone', {
defaultMessage:
'The selected time range does not include this entire bucket. It might contain partial data.',
});

interface TimeseriesChartProps extends TimeseriesChartWithContextProps {
comparisonEnabled: boolean;
offset?: string;
timeZone: string;
annotations?: Array<ReactElement<typeof RectAnnotation | typeof LineAnnotation>>;
settings?: Partial<SettingsSpec>;
}
export function TimeseriesChart({
id,
Expand All @@ -68,6 +71,7 @@ export function TimeseriesChart({
offset,
timeZone,
annotations,
settings,
}: TimeseriesChartProps) {
const history = useHistory();
const { chartRef, updatePointerEvent } = useChartPointerEventContext();
Expand Down Expand Up @@ -186,6 +190,7 @@ export function TimeseriesChart({
}
}}
locale={i18n.getLocale()}
{...settings}
/>
<Axis
id="x-axis"
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/observability_solution/apm/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@
"@kbn/aiops-log-rate-analysis",
"@kbn/router-utils",
"@kbn/react-hooks",
"@kbn/alerting-comparators",
],
"exclude": ["target/**/*"]
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,10 @@
*/

import { Rule } from '@kbn/alerting-plugin/common';
import { AlertSummaryField, TopAlert } from '@kbn/observability-plugin/public';
import { TopAlert } from '@kbn/observability-plugin/public';
import { PartialRuleParams } from '../../../../../common/alerting/logs/log_threshold';

export interface AlertDetailsAppSectionProps {
rule: Rule<PartialRuleParams>;
alert: TopAlert<Record<string, any>>;
setAlertSummaryFields: React.Dispatch<React.SetStateAction<AlertSummaryField[] | undefined>>;
}
Original file line number Diff line number Diff line change
Expand Up @@ -69,15 +69,13 @@ jest.mock('../../../hooks/use_kibana', () => ({

describe('AlertDetailsAppSection', () => {
const queryClient = new QueryClient();
const mockedSetAlertSummaryFields = jest.fn();
const renderComponent = () => {
return render(
<IntlProvider locale="en">
<QueryClientProvider client={queryClient}>
<AlertDetailsAppSection
alert={buildMetricThresholdAlert()}
rule={buildMetricThresholdRule()}
setAlertSummaryFields={mockedSetAlertSummaryFields}
/>
</QueryClientProvider>
</IntlProvider>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
} from '@elastic/eui';
import chroma from 'chroma-js';

import { AlertSummaryField, RuleConditionChart, TopAlert } from '@kbn/observability-plugin/public';
import { RuleConditionChart, TopAlert } from '@kbn/observability-plugin/public';
import { ALERT_END, ALERT_START, ALERT_EVALUATION_VALUES, ALERT_GROUP } from '@kbn/rule-data-utils';
import { Rule, RuleTypeParams } from '@kbn/alerting-plugin/common';
import { getPaddedAlertTimeRange } from '@kbn/observability-get-padded-alert-time-range-util';
Expand Down Expand Up @@ -57,10 +57,9 @@ export type MetricThresholdAlert = TopAlert<MetricThresholdAlertField>;
interface AppSectionProps {
alert: MetricThresholdAlert;
rule: MetricThresholdRule;
setAlertSummaryFields: React.Dispatch<React.SetStateAction<AlertSummaryField[] | undefined>>;
}

export function AlertDetailsAppSection({ alert, rule, setAlertSummaryFields }: AppSectionProps) {
export function AlertDetailsAppSection({ alert, rule }: AppSectionProps) {
const { charts } = useKibanaContextForPlugin().services;
const { euiTheme } = useEuiTheme();
const groups = alert.fields[ALERT_GROUP];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,27 +8,33 @@
import { ALERT_GROUP_FIELD, ALERT_GROUP_VALUE, ALERT_GROUP } from '@kbn/rule-data-utils';
import { TopAlert } from '../../typings/alerts';
import { apmSources, infraSources } from './get_alert_source_links';
import { Group } from '../../../common/typings';

interface AlertFields {
[key: string]: any;
}

export const getSources = (alert: TopAlert) => {
const isGroup = (item: Group | undefined): item is Group => {
return !!item;
};

export const getSources = (alert: TopAlert): Group[] => {
// when `kibana.alert.group` is not flattened (for alert detail pages)
if (alert.fields[ALERT_GROUP]) return alert.fields[ALERT_GROUP];
if (alert.fields[ALERT_GROUP]) return alert.fields[ALERT_GROUP] as Group[];

// when `kibana.alert.group` is flattened (for alert flyout)
const groupsFromGroupFields = alert.fields[ALERT_GROUP_FIELD]?.map((field, index) => {
const values = alert.fields[ALERT_GROUP_VALUE];
if (values?.length && values[index]) {
return { field, value: values[index] };
const group: Group = { field, value: values[index] };
return group;
}
});
}).filter(isGroup);

if (groupsFromGroupFields?.length) return groupsFromGroupFields;

// Not all rules has group.fields, in that case we search in the alert fields.
const matchedSources: Array<{ field: string; value: any }> = [];
const matchedSources: Group[] = [];
const ALL_SOURCES = [...infraSources, ...apmSources];
const alertFields = alert.fields as AlertFields;
ALL_SOURCES.forEach((source: string) => {
Expand Down
Loading

0 comments on commit 1a54aab

Please sign in to comment.