Skip to content

Commit

Permalink
Updates to APM alert annotations (#101106)
Browse files Browse the repository at this point in the history
Make alert annotations on the latency chart show a line annotation at the beginning and have a tooltip.
  • Loading branch information
smith authored Jun 8, 2021
1 parent b740640 commit fc8ca1d
Show file tree
Hide file tree
Showing 7 changed files with 1,068 additions and 34 deletions.
6 changes: 3 additions & 3 deletions x-pack/plugins/apm/public/application/application.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ describe('renderApp', () => {
const plugins = {
licensing: { license$: new Observable() },
triggersActionsUi: { actionTypeRegistry: {}, alertTypeRegistry: {} },
usageCollection: { reportUiCounter: () => {} },
data: {
query: {
timefilter: {
Expand All @@ -69,6 +68,8 @@ describe('renderApp', () => {
const embeddable = embeddablePluginMock.createStartContract();

const pluginsStart = ({
data,
embeddable,
observability: {
navigation: {
registerSections: () => jest.fn(),
Expand All @@ -83,8 +84,7 @@ describe('renderApp', () => {
getAddAlertFlyout: jest.fn(),
getEditAlertFlyout: jest.fn(),
},
data,
embeddable,
usageCollection: { reportUiCounter: () => {} },
} as unknown) as ApmPluginStartDeps;

jest.spyOn(window, 'scrollTo').mockReturnValueOnce(undefined);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { asRelativeDateTimeRange } from '../../../../../common/utils/formatters'
import { useTheme } from '../../../../hooks/use_theme';
import { AlertType } from '../../../../../common/alert_types';
import { getAlertAnnotations } from '../../../shared/charts/helper/get_alert_annotations';
import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context';

type ErrorDistributionAPIResponse = APIReturnType<'GET /api/apm/services/{serviceName}/errors/distribution'>;

Expand Down Expand Up @@ -64,8 +65,9 @@ export function ErrorDistribution({ distribution, title }: Props) {
const xMax = d3.max(buckets, (d) => d.x0);

const xFormatter = niceTimeFormatter([xMin, xMax]);

const { observabilityRuleTypeRegistry } = useApmPluginContext();
const { alerts } = useApmServiceContext();
const { getFormatter } = observabilityRuleTypeRegistry;

const tooltipProps: SettingsSpec['tooltip'] = {
headerFormatter: (tooltip: TooltipValue) => {
Expand Down Expand Up @@ -118,6 +120,8 @@ export function ErrorDistribution({ distribution, title }: Props) {
alerts: alerts?.filter(
(alert) => alert[RULE_ID]?.[0] === AlertType.ErrorCount
),
chartStartTime: buckets[0].x0,
getFormatter,
theme,
})}
</Chart>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
/*
* 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 { ALERT_SEVERITY_LEVEL } from '@kbn/rule-data-utils/target/technical_field_names';
import { ValuesType } from 'utility-types';
import { EuiTheme } from '../../../../../../../../src/plugins/kibana_react/common';
import { ObservabilityRuleTypeRegistry } from '../../../../../../observability/public';
import { APIReturnType } from '../../../../services/rest/createCallApmApi';
import { getAlertAnnotations } from './get_alert_annotations';

type Alert = ValuesType<
APIReturnType<'GET /api/apm/services/{serviceName}/alerts'>['alerts']
>;

const euiColorDanger = 'red';
const euiColorWarning = 'yellow';
const theme = ({
eui: { euiColorDanger, euiColorWarning },
} as unknown) as EuiTheme;
const alert: Alert = {
'rule.id': ['apm.transaction_duration'],
'kibana.rac.alert.evaluation.value': [2057657.39],
'service.name': ['frontend-rum'],
'rule.name': ['Latency threshold | frontend-rum'],
'kibana.rac.alert.duration.us': [62879000],
'kibana.rac.alert.status': ['open'],
tags: ['apm', 'service.name:frontend-rum'],
'transaction.type': ['page-load'],
'kibana.rac.alert.producer': ['apm'],
'kibana.rac.alert.uuid': ['af2ae371-df79-4fca-b0eb-a2dbd9478180'],
'rule.uuid': ['82e0ee40-c2f4-11eb-9a42-a9da66a1722f'],
'event.action': ['active'],
'@timestamp': ['2021-06-01T16:16:05.183Z'],
'kibana.rac.alert.id': ['apm.transaction_duration_All'],
'processor.event': ['transaction'],
'kibana.rac.alert.evaluation.threshold': [500000],
'kibana.rac.alert.start': ['2021-06-01T16:15:02.304Z'],
'event.kind': ['state'],
'rule.category': ['Latency threshold'],
};
const chartStartTime = new Date(
alert['kibana.rac.alert.start']![0] as string
).getTime();
const getFormatter: ObservabilityRuleTypeRegistry['getFormatter'] = () => () => ({
link: '/',
reason: 'a good reason',
});

describe('getAlertAnnotations', () => {
describe('with no alerts', () => {
it('returns an empty array', () => {
expect(
getAlertAnnotations({ alerts: [], chartStartTime, getFormatter, theme })
).toEqual([]);
});
});

describe('with an alert with an undefined severity', () => {
it('uses the danger color', () => {
expect(
getAlertAnnotations({
alerts: [alert],
chartStartTime,
getFormatter,
theme,
})![0].props.style.line.stroke
).toEqual(euiColorDanger);
});

it('says "Alert" in the header', () => {
expect(
getAlertAnnotations({
alerts: [alert],
chartStartTime,
getFormatter,
theme,
})![0].props.dataValues[0].header
).toEqual('Alert');
});

it('uses the reason in the annotation details', () => {
expect(
getAlertAnnotations({
alerts: [alert],
chartStartTime,
getFormatter,
theme,
})![0].props.dataValues[0].details
).toEqual('a good reason');
});

describe('with no formatter', () => {
it('uses the rule type', () => {
const getNoFormatter: ObservabilityRuleTypeRegistry['getFormatter'] = () =>
undefined;

expect(
getAlertAnnotations({
alerts: [alert],
chartStartTime,
getFormatter: getNoFormatter,
theme,
})![0].props.dataValues[0].details
).toEqual(alert['rule.name']![0]);
});
});

describe('when the alert start time is before the chart start time', () => {
it('uses the chart start time', () => {
const beforeChartStartTime = 1622565000000;

expect(
getAlertAnnotations({
alerts: [alert],
chartStartTime: beforeChartStartTime,
getFormatter,
theme,
})![0].props.dataValues[0].dataValue
).toEqual(beforeChartStartTime);
});
});
});

describe('with an alert with a warning severity', () => {
const warningAlert: Alert = {
...alert,
[ALERT_SEVERITY_LEVEL]: ['warning'],
};

it('uses the warning color', () => {
expect(
getAlertAnnotations({
alerts: [warningAlert],
chartStartTime,
getFormatter,
theme,
})![0].props.style.line.stroke
).toEqual(euiColorWarning);
});

it('says "Warning Alert" in the header', () => {
expect(
getAlertAnnotations({
alerts: [warningAlert],
chartStartTime,
getFormatter,
theme,
})![0].props.dataValues[0].header
).toEqual('Warning Alert');
});
});

describe('with an alert with a critical severity', () => {
const criticalAlert: Alert = {
...alert,
[ALERT_SEVERITY_LEVEL]: ['critical'],
};

it('uses the critical color', () => {
expect(
getAlertAnnotations({
alerts: [criticalAlert],
chartStartTime,
getFormatter,
theme,
})![0].props.style.line.stroke
).toEqual(euiColorDanger);
});

it('says "Critical Alert" in the header', () => {
expect(
getAlertAnnotations({
alerts: [criticalAlert],
chartStartTime,
getFormatter,
theme,
})![0].props.dataValues[0].header
).toEqual('Critical Alert');
});
});
});
Loading

0 comments on commit fc8ca1d

Please sign in to comment.