Skip to content

Commit

Permalink
[RAC] Populate Observability alerts table with data from alerts indic…
Browse files Browse the repository at this point in the history
…es (elastic#96692)

* Set up Observability rule APIs

* Populate alerts table with data from API

* Move field map types/utils to common

* Format reason/link in alert type

* Format reason/link in alert type

* Fix issues with tsconfigs

* Storybook cleanup for example alerts

* Use `MemoryRouter` in the stories and `useHistory` in the component to get the history
* Replace examples with ones from "real" data
* Use `() => {}` instead of `jest.fn()` in mock registry data

* Store/display evaluations, add active/recovered badge

* Some more story fixes

* Decode rule data with type from owning registry

* Use transaction type/environment in link to app

* Fix type issues

* Fix API tests

* Undo changes in task_runner.ts

* Remove Mutable<> wrappers for field map

* Remove logger.debug calls in alerting es client

* Add API test for recovery of alerts

* Revert changes to src/core/server/http/router

* Use type imports where possible

* Update limits

* Set limit to 100kb

Co-authored-by: Nathan L Smith <[email protected]>
  • Loading branch information
dgieselaar and smith committed Apr 18, 2021
1 parent 031750d commit b9e7afc
Show file tree
Hide file tree
Showing 110 changed files with 3,379 additions and 4,430 deletions.
3 changes: 3 additions & 0 deletions packages/kbn-io-ts-utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,6 @@
export { jsonRt } from './json_rt';
export { mergeRt } from './merge_rt';
export { strictKeysRt } from './strict_keys_rt';
export { isoToEpochRt } from './iso_to_epoch_rt';
export { toNumberRt } from './to_number_rt';
export { toBooleanRt } from './to_boolean_rt';
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
/*
* 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.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import { isoToEpochRt } from './index';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
/*
* 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.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import * as t from 'io-ts';
Expand All @@ -17,9 +18,7 @@ export const isoToEpochRt = new t.Type<number, string, unknown>(
(input, context) =>
either.chain(t.string.validate(input, context), (str) => {
const epochDate = new Date(str).getTime();
return isNaN(epochDate)
? t.failure(input, context)
: t.success(epochDate);
return isNaN(epochDate) ? t.failure(input, context) : t.success(epochDate);
}),
(output) => new Date(output).toISOString()
);
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
/*
* 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.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import * as t from 'io-ts';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
/*
* 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.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import * as t from 'io-ts';
Expand Down
1 change: 1 addition & 0 deletions packages/kbn-optimizer/limits.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ pageLoadAssetSize:
remoteClusters: 51327
reporting: 183418
rollup: 97204
ruleRegistry: 100000
savedObjects: 108518
savedObjectsManagement: 101836
savedObjectsTagging: 59482
Expand Down
25 changes: 25 additions & 0 deletions x-pack/plugins/apm/common/rules.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* 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.
*/

const plainApmRuleRegistrySettings = {
name: 'apm',
fieldMap: {
'service.environment': {
type: 'keyword',
},
'transaction.type': {
type: 'keyword',
},
'processor.event': {
type: 'keyword',
},
},
} as const;

type APMRuleRegistrySettings = typeof plainApmRuleRegistrySettings;

export const apmRuleRegistrySettings: APMRuleRegistrySettings = plainApmRuleRegistrySettings;
115 changes: 106 additions & 9 deletions x-pack/plugins/apm/public/components/alerting/register_apm_alerts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,39 @@

import { i18n } from '@kbn/i18n';
import { lazy } from 'react';
import { format } from 'url';
import { ENVIRONMENT_ALL } from '../../../common/environment_filter_values';
import { asDuration, asPercent } from '../../../common/utils/formatters';
import { AlertType } from '../../../common/alert_types';
import { ApmPluginStartDeps } from '../../plugin';
import { ApmRuleRegistry } from '../../plugin';

export function registerApmAlerts(
alertTypeRegistry: ApmPluginStartDeps['triggersActionsUi']['alertTypeRegistry']
) {
alertTypeRegistry.register({
export function registerApmAlerts(apmRuleRegistry: ApmRuleRegistry) {
apmRuleRegistry.registerType({
id: AlertType.ErrorCount,
description: i18n.translate('xpack.apm.alertTypes.errorCount.description', {
defaultMessage:
'Alert when the number of errors in a service exceeds a defined threshold.',
}),
format: ({ alert }) => {
return {
reason: i18n.translate('xpack.apm.alertTypes.errorCount.reason', {
defaultMessage: `Error count is greater than {threshold} (current value is {measured}) for {serviceName}`,
values: {
threshold: alert['kibana.observability.evaluation.threshold'],
measured: alert['kibana.observability.evaluation.value'],
serviceName: alert['service.name']!,
},
}),
link: format({
pathname: `/app/apm/services/${alert['service.name']!}`,
query: {
...(alert['service.environment']
? { environment: alert['service.environment'] }
: { environment: ENVIRONMENT_ALL.value }),
},
}),
};
},
iconClass: 'bell',
documentationUrl(docLinks) {
return `${docLinks.ELASTIC_WEBSITE_URL}guide/en/kibana/${docLinks.DOC_LINK_VERSION}/apm-alerts.html`;
Expand All @@ -41,7 +62,7 @@ export function registerApmAlerts(
),
});

alertTypeRegistry.register({
apmRuleRegistry.registerType({
id: AlertType.TransactionDuration,
description: i18n.translate(
'xpack.apm.alertTypes.transactionDuration.description',
Expand All @@ -50,6 +71,32 @@ export function registerApmAlerts(
'Alert when the latency of a specific transaction type in a service exceeds a defined threshold.',
}
),
format: ({ alert }) => ({
reason: i18n.translate(
'xpack.apm.alertTypes.transactionDuration.reason',
{
defaultMessage: `Latency is above {threshold} (current value is {measured}) for {serviceName}`,
values: {
threshold: asDuration(
alert['kibana.observability.evaluation.threshold']
),
measured: asDuration(
alert['kibana.observability.evaluation.value']
),
serviceName: alert['service.name']!,
},
}
),
link: format({
pathname: `/app/apm/services/${alert['service.name']!}`,
query: {
transactionType: alert['transaction.type']!,
...(alert['service.environment']
? { environment: alert['service.environment'] }
: { environment: ENVIRONMENT_ALL.value }),
},
}),
}),
iconClass: 'bell',
documentationUrl(docLinks) {
return `${docLinks.ELASTIC_WEBSITE_URL}guide/en/kibana/${docLinks.DOC_LINK_VERSION}/apm-alerts.html`;
Expand All @@ -75,7 +122,7 @@ export function registerApmAlerts(
),
});

alertTypeRegistry.register({
apmRuleRegistry.registerType({
id: AlertType.TransactionErrorRate,
description: i18n.translate(
'xpack.apm.alertTypes.transactionErrorRate.description',
Expand All @@ -84,6 +131,34 @@ export function registerApmAlerts(
'Alert when the rate of transaction errors in a service exceeds a defined threshold.',
}
),
format: ({ alert }) => ({
reason: i18n.translate(
'xpack.apm.alertTypes.transactionErrorRate.reason',
{
defaultMessage: `Transaction error rate is greater than {threshold} (current value is {measured}) for {serviceName}`,
values: {
threshold: asPercent(
alert['kibana.observability.evaluation.threshold'],
100
),
measured: asPercent(
alert['kibana.observability.evaluation.value'],
100
),
serviceName: alert['service.name']!,
},
}
),
link: format({
pathname: `/app/apm/services/${alert['service.name']!}`,
query: {
transactionType: alert['transaction.type']!,
...(alert['service.environment']
? { environment: alert['service.environment'] }
: { environment: ENVIRONMENT_ALL.value }),
},
}),
}),
iconClass: 'bell',
documentationUrl(docLinks) {
return `${docLinks.ELASTIC_WEBSITE_URL}guide/en/kibana/${docLinks.DOC_LINK_VERSION}/apm-alerts.html`;
Expand All @@ -109,14 +184,36 @@ export function registerApmAlerts(
),
});

alertTypeRegistry.register({
apmRuleRegistry.registerType({
id: AlertType.TransactionDurationAnomaly,
description: i18n.translate(
'xpack.apm.alertTypes.transactionDurationAnomaly.description',
{
defaultMessage: 'Alert when the latency of a service is abnormal.',
}
),
format: ({ alert }) => ({
reason: i18n.translate(
'xpack.apm.alertTypes.transactionDurationAnomaly.reason',
{
defaultMessage: `{severityLevel} anomaly detected for {serviceName} (score was {measured})`,
values: {
serviceName: alert['service.name'],
severityLevel: alert['kibana.rac.alert.severity.level'],
measured: alert['kibana.observability.evaluation.value'],
},
}
),
link: format({
pathname: `/app/apm/services/${alert['service.name']!}`,
query: {
transactionType: alert['transaction.type']!,
...(alert['service.environment']
? { environment: alert['service.environment'] }
: { environment: ENVIRONMENT_ALL.value }),
},
}),
}),
iconClass: 'bell',
documentationUrl(docLinks) {
return `${docLinks.ELASTIC_WEBSITE_URL}guide/en/kibana/${docLinks.DOC_LINK_VERSION}/apm-alerts.html`;
Expand All @@ -137,7 +234,7 @@ export function registerApmAlerts(
- Type: \\{\\{context.transactionType\\}\\}
- Environment: \\{\\{context.environment\\}\\}
- Severity threshold: \\{\\{context.threshold\\}\\}
- Severity value: \\{\\{context.thresholdValue\\}\\}
- Severity value: \\{\\{context.triggerValue\\}\\}
`,
}
),
Expand Down
20 changes: 17 additions & 3 deletions x-pack/plugins/apm/public/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import { ConfigSchema } from '.';
import {
FetchDataParams,
FormatterRuleRegistry,
HasDataParams,
ObservabilityPublicSetup,
} from '../../observability/public';
Expand Down Expand Up @@ -40,8 +41,11 @@ import { EmbeddableStart } from '../../../../src/plugins/embeddable/public';
import { registerApmAlerts } from './components/alerting/register_apm_alerts';
import { MlPluginSetup, MlPluginStart } from '../../ml/public';
import { MapsStartApi } from '../../maps/public';
import { apmRuleRegistrySettings } from '../common/rules';

export type ApmPluginSetup = ReturnType<ApmPlugin['setup']>;
export type ApmRuleRegistry = ApmPluginSetup['ruleRegistry'];

export type ApmPluginSetup = void;
export type ApmPluginStart = void;

export interface ApmPluginSetupDeps {
Expand All @@ -52,7 +56,7 @@ export interface ApmPluginSetupDeps {
home?: HomePublicPluginSetup;
licensing: LicensingPluginSetup;
triggersActionsUi: TriggersAndActionsUIPublicPluginSetup;
observability?: ObservabilityPublicSetup;
observability: ObservabilityPublicSetup;
}

export interface ApmPluginStartDeps {
Expand Down Expand Up @@ -156,6 +160,13 @@ export class ApmPlugin implements Plugin<ApmPluginSetup, ApmPluginStart> {
},
});

const apmRuleRegistry = plugins.observability.ruleRegistry.create({
...apmRuleRegistrySettings,
ctor: FormatterRuleRegistry,
});

registerApmAlerts(apmRuleRegistry);

core.application.register({
id: 'ux',
title: 'User Experience',
Expand Down Expand Up @@ -196,9 +207,12 @@ export class ApmPlugin implements Plugin<ApmPluginSetup, ApmPluginStart> {
);
},
});

return {
ruleRegistry: apmRuleRegistry,
};
}
public start(core: CoreStart, plugins: ApmPluginStartDeps) {
toggleAppLinkInNav(core, this.initializerContext.config.get());
registerApmAlerts(plugins.triggersActionsUi.alertTypeRegistry);
}
}
12 changes: 9 additions & 3 deletions x-pack/plugins/apm/scripts/optimize-tsconfig/optimize.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,14 @@ async function setIgnoreChanges() {
}
}

async function deleteApmTsConfig() {
await unlink(path.resolve(kibanaRoot, 'x-pack/plugins/apm', 'tsconfig.json'));
async function deleteTsConfigs() {
const toDelete = ['apm', 'observability', 'rule_registry'];

for (const app of toDelete) {
await unlink(
path.resolve(kibanaRoot, 'x-pack/plugins', app, 'tsconfig.json')
);
}
}

async function optimizeTsConfig() {
Expand All @@ -98,7 +104,7 @@ async function optimizeTsConfig() {

await addApmFilesToTestTsConfig();

await deleteApmTsConfig();
await deleteTsConfigs();

await setIgnoreChanges();
// eslint-disable-next-line no-console
Expand Down
2 changes: 2 additions & 0 deletions x-pack/plugins/apm/scripts/optimize-tsconfig/paths.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ const filesToIgnore = [
path.resolve(kibanaRoot, 'tsconfig.json'),
path.resolve(kibanaRoot, 'tsconfig.base.json'),
path.resolve(kibanaRoot, 'x-pack/plugins/apm', 'tsconfig.json'),
path.resolve(kibanaRoot, 'x-pack/plugins/observability', 'tsconfig.json'),
path.resolve(kibanaRoot, 'x-pack/plugins/rule_registry', 'tsconfig.json'),
path.resolve(kibanaRoot, 'x-pack/test', 'tsconfig.json'),
];

Expand Down
11 changes: 7 additions & 4 deletions x-pack/plugins/apm/server/lib/alerts/alerting_es_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,17 @@ import {
} from '../../../../../../typings/elasticsearch';
import { AlertServices } from '../../../../alerting/server';

export async function alertingEsClient<TParams extends ESSearchRequest>(
export async function alertingEsClient<TParams extends ESSearchRequest>({
scopedClusterClient,
params,
}: {
scopedClusterClient: AlertServices<
never,
never,
never
>['scopedClusterClient'],
params: TParams
): Promise<ESSearchResponse<unknown, TParams>> {
>['scopedClusterClient'];
params: TParams;
}): Promise<ESSearchResponse<unknown, TParams>> {
const response = await scopedClusterClient.asCurrentUser.search({
...params,
ignore_unavailable: true,
Expand Down
Loading

0 comments on commit b9e7afc

Please sign in to comment.