Skip to content

Commit

Permalink
[AO] Add alertDetailsUrl to APM rule types (#158657)
Browse files Browse the repository at this point in the history
Resolves #158268

## Summary

In this PR, I've added `alertDetailsUrl` as an action variable to the
APM rule types. Also, the related functional tests were updated to
ensure the URL was generated correctly.

|Action|Result|
|---|---|

|![image](https://github.com/elastic/kibana/assets/12370520/680c575a-8bbc-4ead-9a74-d92d25780fcf)|![image](https://github.com/elastic/kibana/assets/12370520/c3b4fd1e-8ffd-43ab-8d58-130d70cc7767)|

#### Filtered alert
<img
src="https://github.com/elastic/kibana/assets/12370520/edd01a7c-2f8e-4bba-92f9-46471d8acb2a"
width="800"/>

Note that, for the APM Latency threshold, we already had this variable
pointing to the newly implemented alert details page.

## 🧪 How to test
-  Create an APM rule (besides the Latency threshold rule)
- Create an action and add `context.alertDetailsUrl` variable in the
message
- After an alert is created, check the action message. The link should
land on the `Alerts` page filtered for that specific alert instance

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
  • Loading branch information
maryam-saeidi and kibanamachine authored Jun 2, 2023
1 parent 3ddd74a commit cec0a15
Show file tree
Hide file tree
Showing 22 changed files with 325 additions and 190 deletions.
2 changes: 2 additions & 0 deletions x-pack/plugins/apm/server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { isEmpty, mapValues } from 'lodash';
import { Dataset } from '@kbn/rule-registry-plugin/server';
import { UI_SETTINGS } from '@kbn/data-plugin/common';
import { mappingFromFieldMap } from '@kbn/alerting-plugin/common';
import { alertsLocatorID } from '@kbn/observability-plugin/common';
import { APMConfig, APM_SERVER_FEATURE_ID } from '.';
import { APM_FEATURE, registerFeaturesUsage } from './feature';
import {
Expand Down Expand Up @@ -187,6 +188,7 @@ export class APMPlugin
ml: plugins.ml,
observability: plugins.observability,
ruleDataClient,
alertsLocator: plugins.share.url.locators.get(alertsLocatorID),
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
* 2.0.
*/

import type { AlertsLocatorParams } from '@kbn/observability-plugin/common';
import { LocatorPublic } from '@kbn/share-plugin/common';
import { Observable } from 'rxjs';
import { IBasePath, Logger } from '@kbn/core/server';
import {
Expand Down Expand Up @@ -89,6 +91,7 @@ export interface RegisterRuleDependencies {
ml?: MlPluginSetup;
observability: ObservabilityPluginSetup;
ruleDataClient: IRuleDataClient;
alertsLocator?: LocatorPublic<AlertsLocatorParams>;
}

export function registerApmRuleTypes(dependencies: RegisterRuleDependencies) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ describe('Transaction duration anomaly alert', () => {
'critical anomaly with a score of 80 was detected in the last 5 mins for foo.',
viewInAppUrl:
'http://localhost:5601/eyr/app/apm/services/foo?transactionType=type-foo&environment=development',
alertDetailsUrl: 'mockedAlertsLocator > getLocation',
});
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWith
import { KibanaRequest } from '@kbn/core/server';
import datemath from '@kbn/datemath';
import type { ESSearchResponse } from '@kbn/es-types';
import { ProcessorEvent } from '@kbn/observability-plugin/common';
import { getAlertUrl, ProcessorEvent } from '@kbn/observability-plugin/common';
import { termQuery } from '@kbn/observability-plugin/server';
import {
ALERT_EVALUATION_THRESHOLD,
Expand All @@ -18,6 +18,7 @@ import {
} from '@kbn/rule-data-utils';
import { createLifecycleRuleTypeFactory } from '@kbn/rule-registry-plugin/server';
import { addSpaceIdToPath } from '@kbn/spaces-plugin/common';
import { asyncForEach } from '@kbn/std';
import { compact } from 'lodash';
import { getSeverity } from '../../../../../common/anomaly_detection';
import {
Expand Down Expand Up @@ -55,6 +56,7 @@ const ruleTypeConfig = RULE_TYPES_CONFIG[ApmRuleType.Anomaly];

export function registerAnomalyRuleType({
alerting,
alertsLocator,
basePath,
config$,
logger,
Expand All @@ -75,6 +77,7 @@ export function registerAnomalyRuleType({
validate: { params: anomalyParamsSchema },
actionVariables: {
context: [
apmActionVariables.alertDetailsUrl,
apmActionVariables.environment,
apmActionVariables.reason,
apmActionVariables.serviceName,
Expand All @@ -87,12 +90,17 @@ export function registerAnomalyRuleType({
producer: 'apm',
minimumLicenseRequired: 'basic',
isExportable: true,
executor: async ({ params, services, spaceId }) => {
executor: async ({ params, services, spaceId, startedAt }) => {
if (!ml) {
return { state: {} };
}

const { savedObjectsClient, scopedClusterClient } = services;
const {
getAlertUuid,
getAlertStartedDate,
savedObjectsClient,
scopedClusterClient,
} = services;

const ruleParams = params;
const request = {} as KibanaRequest;
Expand Down Expand Up @@ -231,7 +239,7 @@ export function registerAnomalyRuleType({
anomaly ? anomaly.score >= threshold : false
) ?? [];

for (const anomaly of compact(anomalies)) {
await asyncForEach(compact(anomalies), async (anomaly) => {
const {
serviceName,
environment,
Expand Down Expand Up @@ -261,7 +269,7 @@ export function registerAnomalyRuleType({
windowUnit: params.windowUnit,
});

const id = [
const alertId = [
ApmRuleType.Anomaly,
serviceName,
environment,
Expand All @@ -270,43 +278,53 @@ export function registerAnomalyRuleType({
.filter((name) => name)
.join('_');

const alert = services.alertWithLifecycle({
id: alertId,
fields: {
[SERVICE_NAME]: serviceName,
...getEnvironmentEsField(environment),
[TRANSACTION_TYPE]: transactionType,
[PROCESSOR_EVENT]: ProcessorEvent.transaction,
[ALERT_SEVERITY]: severityLevel,
[ALERT_EVALUATION_VALUE]: score,
[ALERT_EVALUATION_THRESHOLD]: threshold,
[ALERT_REASON]: reasonMessage,
...eventSourceFields,
},
});

const relativeViewInAppUrl = getAlertUrlTransaction(
serviceName,
getEnvironmentEsField(environment)?.[SERVICE_ENVIRONMENT],
transactionType
);

const viewInAppUrl = addSpaceIdToPath(
basePath.publicBaseUrl,
spaceId,
relativeViewInAppUrl
);
const indexedStartedAt =
getAlertStartedDate(alertId) ?? startedAt.toISOString();
const alertUuid = getAlertUuid(alertId);
const alertDetailsUrl = await getAlertUrl(
alertUuid,
spaceId,
indexedStartedAt,
alertsLocator,
basePath.publicBaseUrl
);

services
.alertWithLifecycle({
id,
fields: {
[SERVICE_NAME]: serviceName,
...getEnvironmentEsField(environment),
[TRANSACTION_TYPE]: transactionType,
[PROCESSOR_EVENT]: ProcessorEvent.transaction,
[ALERT_SEVERITY]: severityLevel,
[ALERT_EVALUATION_VALUE]: score,
[ALERT_EVALUATION_THRESHOLD]: threshold,
[ALERT_REASON]: reasonMessage,
...eventSourceFields,
},
})
.scheduleActions(ruleTypeConfig.defaultActionGroupId, {
environment: getEnvironmentLabel(environment),
reason: reasonMessage,
serviceName,
threshold: selectedOption?.label,
transactionType,
triggerValue: severityLevel,
viewInAppUrl,
});
}
alert.scheduleActions(ruleTypeConfig.defaultActionGroupId, {
alertDetailsUrl,
environment: getEnvironmentLabel(environment),
reason: reasonMessage,
serviceName,
threshold: selectedOption?.label,
transactionType,
triggerValue: severityLevel,
viewInAppUrl,
});
});

return { state: {} };
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ describe('Error count alert', () => {
interval: '5 mins',
viewInAppUrl:
'http://localhost:5601/eyr/app/apm/services/foo/errors?environment=env-foo',
alertDetailsUrl: 'mockedAlertsLocator > getLocation',
});
expect(scheduleActions).toHaveBeenCalledWith('threshold_met', {
serviceName: 'foo',
Expand All @@ -158,6 +159,7 @@ describe('Error count alert', () => {
interval: '5 mins',
viewInAppUrl:
'http://localhost:5601/eyr/app/apm/services/foo/errors?environment=env-foo-2',
alertDetailsUrl: 'mockedAlertsLocator > getLocation',
});
expect(scheduleActions).toHaveBeenCalledWith('threshold_met', {
serviceName: 'bar',
Expand All @@ -169,6 +171,7 @@ describe('Error count alert', () => {
interval: '5 mins',
viewInAppUrl:
'http://localhost:5601/eyr/app/apm/services/bar/errors?environment=env-bar',
alertDetailsUrl: 'mockedAlertsLocator > getLocation',
});
});

Expand Down Expand Up @@ -246,6 +249,7 @@ describe('Error count alert', () => {
interval: '5 mins',
viewInAppUrl:
'http://localhost:5601/eyr/app/apm/services/foo/errors?environment=env-foo',
alertDetailsUrl: 'mockedAlertsLocator > getLocation',
transactionName: 'tx-name-foo',
});
expect(scheduleActions).toHaveBeenCalledWith('threshold_met', {
Expand All @@ -258,6 +262,7 @@ describe('Error count alert', () => {
interval: '5 mins',
viewInAppUrl:
'http://localhost:5601/eyr/app/apm/services/foo/errors?environment=env-foo-2',
alertDetailsUrl: 'mockedAlertsLocator > getLocation',
transactionName: 'tx-name-foo-2',
});
expect(scheduleActions).toHaveBeenCalledWith('threshold_met', {
Expand All @@ -270,6 +275,7 @@ describe('Error count alert', () => {
interval: '5 mins',
viewInAppUrl:
'http://localhost:5601/eyr/app/apm/services/bar/errors?environment=env-bar',
alertDetailsUrl: 'mockedAlertsLocator > getLocation',
transactionName: 'tx-name-bar',
});
});
Expand Down Expand Up @@ -348,6 +354,7 @@ describe('Error count alert', () => {
interval: '5 mins',
viewInAppUrl:
'http://localhost:5601/eyr/app/apm/services/foo/errors?environment=env-foo',
alertDetailsUrl: 'mockedAlertsLocator > getLocation',
errorGroupingKey: 'error-key-foo',
});
expect(scheduleActions).toHaveBeenCalledWith('threshold_met', {
Expand All @@ -360,6 +367,7 @@ describe('Error count alert', () => {
interval: '5 mins',
viewInAppUrl:
'http://localhost:5601/eyr/app/apm/services/foo/errors?environment=env-foo-2',
alertDetailsUrl: 'mockedAlertsLocator > getLocation',
errorGroupingKey: 'error-key-foo-2',
});
expect(scheduleActions).toHaveBeenCalledWith('threshold_met', {
Expand All @@ -372,6 +380,7 @@ describe('Error count alert', () => {
interval: '5 mins',
viewInAppUrl:
'http://localhost:5601/eyr/app/apm/services/bar/errors?environment=env-bar',
alertDetailsUrl: 'mockedAlertsLocator > getLocation',
errorGroupingKey: 'error-key-bar',
});
});
Expand Down Expand Up @@ -446,6 +455,7 @@ describe('Error count alert', () => {
interval: '5 mins',
viewInAppUrl:
'http://localhost:5601/eyr/app/apm/services/foo/errors?environment=env-foo',
alertDetailsUrl: 'mockedAlertsLocator > getLocation',
});
expect(scheduleActions).toHaveBeenCalledWith('threshold_met', {
serviceName: 'foo',
Expand All @@ -457,6 +467,7 @@ describe('Error count alert', () => {
interval: '5 mins',
viewInAppUrl:
'http://localhost:5601/eyr/app/apm/services/foo/errors?environment=env-foo-2',
alertDetailsUrl: 'mockedAlertsLocator > getLocation',
});
expect(scheduleActions).toHaveBeenCalledWith('threshold_met', {
serviceName: 'bar',
Expand All @@ -468,6 +479,7 @@ describe('Error count alert', () => {
interval: '5 mins',
viewInAppUrl:
'http://localhost:5601/eyr/app/apm/services/bar/errors?environment=env-bar',
alertDetailsUrl: 'mockedAlertsLocator > getLocation',
});
});

Expand Down Expand Up @@ -545,6 +557,7 @@ describe('Error count alert', () => {
interval: '5 mins',
viewInAppUrl:
'http://localhost:5601/eyr/app/apm/services/foo/errors?environment=ENVIRONMENT_ALL',
alertDetailsUrl: 'mockedAlertsLocator > getLocation',
});
expect(scheduleActions).toHaveBeenCalledWith('threshold_met', {
serviceName: 'foo',
Expand All @@ -556,6 +569,7 @@ describe('Error count alert', () => {
interval: '5 mins',
viewInAppUrl:
'http://localhost:5601/eyr/app/apm/services/foo/errors?environment=ENVIRONMENT_ALL',
alertDetailsUrl: 'mockedAlertsLocator > getLocation',
});
expect(scheduleActions).toHaveBeenCalledWith('threshold_met', {
serviceName: 'bar',
Expand All @@ -567,6 +581,7 @@ describe('Error count alert', () => {
interval: '5 mins',
viewInAppUrl:
'http://localhost:5601/eyr/app/apm/services/bar/errors?environment=env-bar',
alertDetailsUrl: 'mockedAlertsLocator > getLocation',
});
});
});
Loading

0 comments on commit cec0a15

Please sign in to comment.