Skip to content

Commit

Permalink
Merge branch 'main' into rule-url-variable
Browse files Browse the repository at this point in the history
  • Loading branch information
jonathan-buttner authored Nov 14, 2022
2 parents 3dd3236 + 53bab70 commit b51f3f5
Show file tree
Hide file tree
Showing 28 changed files with 609 additions and 247 deletions.
7 changes: 4 additions & 3 deletions x-pack/plugins/apm/server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,12 +190,13 @@ export class APMPlugin

if (plugins.alerting) {
registerApmRuleTypes({
ruleDataClient,
alerting: plugins.alerting,
ml: plugins.ml,
basePath: core.http.basePath,
config$,
logger: this.logger!.get('rule'),
basePath: core.http.basePath,
ml: plugins.ml,
observability: plugins.observability,
ruleDataClient,
});
}

Expand Down
66 changes: 38 additions & 28 deletions x-pack/plugins/apm/server/routes/alerts/action_variables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,15 @@
import { i18n } from '@kbn/i18n';

export const apmActionVariables = {
serviceName: {
description: i18n.translate(
'xpack.apm.alerts.action_variables.serviceName',
{ defaultMessage: 'The service the alert is created for' }
),
name: 'serviceName' as const,
},
transactionType: {
alertDetailsUrl: {
description: i18n.translate(
'xpack.apm.alerts.action_variables.transactionType',
{ defaultMessage: 'The transaction type the alert is created for' }
'xpack.apm.alerts.action_variables.alertDetailsUrl',
{
defaultMessage:
'Link to the view within Elastic that shows further details and context surrounding this alert',
}
),
name: 'transactionType' as const,
name: 'alertDetailsUrl' as const,
},
environment: {
description: i18n.translate(
Expand All @@ -29,23 +25,6 @@ export const apmActionVariables = {
),
name: 'environment' as const,
},
threshold: {
description: i18n.translate('xpack.apm.alerts.action_variables.threshold', {
defaultMessage:
'Any trigger value above this value will cause the alert to fire',
}),
name: 'threshold' as const,
},
triggerValue: {
description: i18n.translate(
'xpack.apm.alerts.action_variables.triggerValue',
{
defaultMessage:
'The value that breached the threshold and triggered the alert',
}
),
name: 'triggerValue' as const,
},
interval: {
description: i18n.translate(
'xpack.apm.alerts.action_variables.intervalSize',
Expand All @@ -65,6 +44,37 @@ export const apmActionVariables = {
),
name: 'reason' as const,
},
serviceName: {
description: i18n.translate(
'xpack.apm.alerts.action_variables.serviceName',
{ defaultMessage: 'The service the alert is created for' }
),
name: 'serviceName' as const,
},
threshold: {
description: i18n.translate('xpack.apm.alerts.action_variables.threshold', {
defaultMessage:
'Any trigger value above this value will cause the alert to fire',
}),
name: 'threshold' as const,
},
transactionType: {
description: i18n.translate(
'xpack.apm.alerts.action_variables.transactionType',
{ defaultMessage: 'The transaction type the alert is created for' }
),
name: 'transactionType' as const,
},
triggerValue: {
description: i18n.translate(
'xpack.apm.alerts.action_variables.triggerValue',
{
defaultMessage:
'The value that breached the threshold and triggered the alert',
}
),
name: 'triggerValue' as const,
},
viewInAppUrl: {
description: i18n.translate(
'xpack.apm.alerts.action_variables.viewInAppUrl',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import { Observable } from 'rxjs';
import { IBasePath, Logger } from '@kbn/core/server';
import { PluginSetupContract as AlertingPluginSetupContract } from '@kbn/alerting-plugin/server';
import { ObservabilityPluginSetup } from '@kbn/observability-plugin/server';
import { IRuleDataClient } from '@kbn/rule-registry-plugin/server';
import { MlPluginSetup } from '@kbn/ml-plugin/server';
import { registerTransactionDurationRuleType } from './rule_types/transaction_duration/register_transaction_duration_rule_type';
Expand All @@ -17,12 +18,13 @@ import { APMConfig } from '../..';
import { registerTransactionErrorRateRuleType } from './rule_types/transaction_error_rate/register_transaction_error_rate_rule_type';

export interface RegisterRuleDependencies {
ruleDataClient: IRuleDataClient;
ml?: MlPluginSetup;
alerting: AlertingPluginSetupContract;
basePath: IBasePath;
config$: Observable<APMConfig>;
logger: Logger;
basePath: IBasePath;
ml?: MlPluginSetup;
observability: ObservabilityPluginSetup;
ruleDataClient: IRuleDataClient;
}

export function registerApmRuleTypes(dependencies: RegisterRuleDependencies) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,9 @@ describe('Transaction duration anomaly alert', () => {
);

expect(scheduleActions).toHaveBeenCalledWith('threshold_met', {
alertDetailsUrl: expect.stringContaining(
'http://localhost:5601/eyr/app/observability/alerts/'
),
serviceName: 'foo',
transactionType: 'type-foo',
environment: 'development',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,23 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import datemath from '@kbn/datemath';
import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { schema } from '@kbn/config-schema';
import { KibanaRequest } from '@kbn/core/server';
import datemath from '@kbn/datemath';
import type { ESSearchResponse } from '@kbn/es-types';
import { getAlertDetailsUrl } from '@kbn/infra-plugin/server/lib/alerting/common/utils';
import { ProcessorEvent } from '@kbn/observability-plugin/common';
import { termQuery } from '@kbn/observability-plugin/server';
import {
ALERT_EVALUATION_THRESHOLD,
ALERT_EVALUATION_VALUE,
ALERT_REASON,
ALERT_SEVERITY,
} from '@kbn/rule-data-utils';
import { compact } from 'lodash';
import type { ESSearchResponse } from '@kbn/es-types';
import { KibanaRequest } from '@kbn/core/server';
import { termQuery } from '@kbn/observability-plugin/server';
import { createLifecycleRuleTypeFactory } from '@kbn/rule-registry-plugin/server';
import { ProcessorEvent } from '@kbn/observability-plugin/common';
import {
ApmRuleType,
RULE_TYPES_CONFIG,
ANOMALY_ALERT_SEVERITY_TYPES,
formatAnomalyReason,
} from '../../../../../common/rules/apm_rule_types';
import { addSpaceIdToPath } from '@kbn/spaces-plugin/common';
import { compact } from 'lodash';
import { getSeverity } from '../../../../../common/anomaly_detection';
import {
ApmMlDetectorType,
Expand All @@ -41,6 +37,12 @@ import {
getEnvironmentLabel,
} from '../../../../../common/environment_filter_values';
import { ANOMALY_SEVERITY } from '../../../../../common/ml_constants';
import {
ANOMALY_ALERT_SEVERITY_TYPES,
ApmRuleType,
formatAnomalyReason,
RULE_TYPES_CONFIG,
} from '../../../../../common/rules/apm_rule_types';
import { asMutableArray } from '../../../../../common/utils/as_mutable_array';
import { getAlertUrlTransaction } from '../../../../../common/utils/formatters';
import { getMLJobs } from '../../../service_map/get_service_anomalies';
Expand All @@ -65,12 +67,13 @@ const paramsSchema = schema.object({
const ruleTypeConfig = RULE_TYPES_CONFIG[ApmRuleType.Anomaly];

export function registerAnomalyRuleType({
logger,
ruleDataClient,
config$,
alerting,
ml,
basePath,
config$,
logger,
ml,
observability,
ruleDataClient,
}: RegisterRuleDependencies) {
const createLifecycleRuleType = createLifecycleRuleTypeFactory({
logger,
Expand All @@ -88,32 +91,38 @@ export function registerAnomalyRuleType({
},
actionVariables: {
context: [
apmActionVariables.serviceName,
apmActionVariables.transactionType,
...(observability.getAlertDetailsConfig()?.apm.enabled
? [apmActionVariables.alertDetailsUrl]
: []),
apmActionVariables.environment,
apmActionVariables.reason,
apmActionVariables.serviceName,
apmActionVariables.threshold,
apmActionVariables.transactionType,
apmActionVariables.triggerValue,
apmActionVariables.reason,
apmActionVariables.viewInAppUrl,
],
},
producer: 'apm',
minimumLicenseRequired: 'basic',
isExportable: true,
executor: async ({ services, params }) => {
executor: async ({ params, services, spaceId }) => {
if (!ml) {
return {};
}

const { savedObjectsClient, scopedClusterClient, getAlertUuid } =
services;

const ruleParams = params;
const request = {} as KibanaRequest;
const { mlAnomalySearch } = ml.mlSystemProvider(
request,
services.savedObjectsClient
savedObjectsClient
);
const anomalyDetectors = ml.anomalyDetectorsProvider(
request,
services.savedObjectsClient
savedObjectsClient
);

const mlJobs = await getMLJobs(
Expand Down Expand Up @@ -254,8 +263,8 @@ export function registerAnomalyRuleType({

const eventSourceFields = await getServiceGroupFieldsForAnomaly({
config$,
scopedClusterClient: services.scopedClusterClient,
savedObjectsClient: services.savedObjectsClient,
scopedClusterClient,
savedObjectsClient,
serviceName,
environment,
transactionType,
Expand All @@ -272,28 +281,38 @@ export function registerAnomalyRuleType({
windowUnit: params.windowUnit,
});

const id = [
ApmRuleType.Anomaly,
serviceName,
environment,
transactionType,
]
.filter((name) => name)
.join('_');

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

const viewInAppUrl = basePath.publicBaseUrl
? new URL(
basePath.prepend(relativeViewInAppUrl),
basePath.publicBaseUrl
).toString()
: relativeViewInAppUrl;
const viewInAppUrl = addSpaceIdToPath(
basePath.publicBaseUrl,
spaceId,
relativeViewInAppUrl
);

const alertUuid = getAlertUuid(id);

const alertDetailsUrl = getAlertDetailsUrl(
basePath,
spaceId,
alertUuid
);

services
.alertWithLifecycle({
id: [
ApmRuleType.Anomaly,
serviceName,
environment,
transactionType,
]
.filter((name) => name)
.join('_'),
id,
fields: {
[SERVICE_NAME]: serviceName,
...getEnvironmentEsField(environment),
Expand All @@ -307,12 +326,13 @@ export function registerAnomalyRuleType({
},
})
.scheduleActions(ruleTypeConfig.defaultActionGroupId, {
serviceName,
transactionType,
alertDetailsUrl,
environment: getEnvironmentLabel(environment),
reason: reasonMessage,
serviceName,
threshold: selectedOption?.label,
transactionType,
triggerValue: severityLevel,
reason: reasonMessage,
viewInAppUrl,
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,9 @@ describe('Error count alert', () => {
expect(scheduleActions).toHaveBeenCalledTimes(3);

expect(scheduleActions).toHaveBeenCalledWith('threshold_met', {
alertDetailsUrl: expect.stringContaining(
'http://localhost:5601/eyr/app/observability/alerts/'
),
serviceName: 'foo',
environment: 'env-foo',
threshold: 2,
Expand All @@ -148,6 +151,9 @@ describe('Error count alert', () => {
'http://localhost:5601/eyr/app/apm/services/foo/errors?environment=env-foo',
});
expect(scheduleActions).toHaveBeenCalledWith('threshold_met', {
alertDetailsUrl: expect.stringContaining(
'http://localhost:5601/eyr/app/observability/alerts/'
),
serviceName: 'foo',
environment: 'env-foo-2',
threshold: 2,
Expand All @@ -158,6 +164,9 @@ describe('Error count alert', () => {
'http://localhost:5601/eyr/app/apm/services/foo/errors?environment=env-foo-2',
});
expect(scheduleActions).toHaveBeenCalledWith('threshold_met', {
alertDetailsUrl: expect.stringContaining(
'http://localhost:5601/eyr/app/observability/alerts/'
),
serviceName: 'bar',
environment: 'env-bar',
reason: 'Error count is 3 in the last 5 mins for bar. Alert when > 2.',
Expand Down
Loading

0 comments on commit b51f3f5

Please sign in to comment.