From ec08dcae074a283b6be7877fa177bf8ab2b66db9 Mon Sep 17 00:00:00 2001 From: Oliver Gupte Date: Sat, 22 Aug 2020 00:21:25 -0700 Subject: [PATCH 1/9] Closes #72636. Adds alerting integration for APM transaction duration anomalies. --- x-pack/plugins/apm/common/alert_types.ts | 19 +++ .../AlertIntegrations/index.tsx | 18 ++- .../components/app/ServiceDetails/index.tsx | 24 +-- .../SelectAnomalySeverity.tsx | 113 +++++++++++++ .../index.tsx | 153 ++++++++++++++++++ x-pack/plugins/apm/public/plugin.ts | 15 ++ .../server/lib/alerts/register_apm_alerts.ts | 8 + ...transaction_duration_anomaly_alert_type.ts | 136 ++++++++++++++++ .../get_ml_jobs_with_apm_group.ts | 10 +- .../apm/server/lib/helpers/setup_request.ts | 27 ++-- .../lib/service_map/get_service_anomalies.ts | 9 +- x-pack/plugins/apm/server/plugin.ts | 1 + .../providers/anomaly_detectors.ts | 8 +- 13 files changed, 512 insertions(+), 29 deletions(-) create mode 100644 x-pack/plugins/apm/public/components/shared/TransactionDurationAnomalyAlertTrigger/SelectAnomalySeverity.tsx create mode 100644 x-pack/plugins/apm/public/components/shared/TransactionDurationAnomalyAlertTrigger/index.tsx create mode 100644 x-pack/plugins/apm/server/lib/alerts/register_transaction_duration_anomaly_alert_type.ts diff --git a/x-pack/plugins/apm/common/alert_types.ts b/x-pack/plugins/apm/common/alert_types.ts index ad826a446d823..a1161354e04f4 100644 --- a/x-pack/plugins/apm/common/alert_types.ts +++ b/x-pack/plugins/apm/common/alert_types.ts @@ -9,6 +9,7 @@ import { i18n } from '@kbn/i18n'; export enum AlertType { ErrorRate = 'apm.error_rate', TransactionDuration = 'apm.transaction_duration', + TransactionDurationAnomaly = 'apm.transaction_duration_anomaly', } export const ALERT_TYPES_CONFIG = { @@ -45,6 +46,24 @@ export const ALERT_TYPES_CONFIG = { defaultActionGroupId: 'threshold_met', producer: 'apm', }, + [AlertType.TransactionDurationAnomaly]: { + name: i18n.translate('xpack.apm.transactionDurationAnomalyAlert.name', { + defaultMessage: 'Transaction duration anomaly', + }), + actionGroups: [ + { + id: 'threshold_met', + name: i18n.translate( + 'xpack.apm.transactionDurationAlert.thresholdMet', + { + defaultMessage: 'Threshold met', + } + ), + }, + ], + defaultActionGroupId: 'threshold_met', + producer: 'apm', + }, }; export const TRANSACTION_ALERT_AGGREGATION_TYPES = { diff --git a/x-pack/plugins/apm/public/components/app/ServiceDetails/AlertIntegrations/index.tsx b/x-pack/plugins/apm/public/components/app/ServiceDetails/AlertIntegrations/index.tsx index 80d5f739bea5a..2ac77a3810ecf 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceDetails/AlertIntegrations/index.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceDetails/AlertIntegrations/index.tsx @@ -35,10 +35,11 @@ const CREATE_THRESHOLD_ALERT_PANEL_ID = 'create_threshold'; interface Props { canReadAlerts: boolean; canSaveAlerts: boolean; + canReadAnomalies: boolean; } export function AlertIntegrations(props: Props) { - const { canSaveAlerts, canReadAlerts } = props; + const { canSaveAlerts, canReadAlerts, canReadAnomalies } = props; const plugin = useApmPluginContext(); @@ -105,6 +106,21 @@ export function AlertIntegrations(props: Props) { setAlertType(AlertType.TransactionDuration); }, }, + ...(canReadAnomalies + ? [ + { + name: i18n.translate( + 'xpack.apm.serviceDetails.alertsMenu.transactionDurationAnomaly', + { + defaultMessage: 'Transaction duration anomaly', + } + ), + onClick: () => { + setAlertType(AlertType.TransactionDurationAnomaly); + }, + }, + ] + : []), { name: i18n.translate( 'xpack.apm.serviceDetails.alertsMenu.errorRate', diff --git a/x-pack/plugins/apm/public/components/app/ServiceDetails/index.tsx b/x-pack/plugins/apm/public/components/app/ServiceDetails/index.tsx index 4488a962d0ba8..b5a4ca4799afd 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceDetails/index.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceDetails/index.tsx @@ -26,19 +26,18 @@ export function ServiceDetails({ tab }: Props) { const plugin = useApmPluginContext(); const { urlParams } = useUrlParams(); const { serviceName } = urlParams; - - const canReadAlerts = !!plugin.core.application.capabilities.apm[ - 'alerting:show' - ]; - const canSaveAlerts = !!plugin.core.application.capabilities.apm[ - 'alerting:save' - ]; + const capabilities = plugin.core.application.capabilities; + const canReadAlerts = !!capabilities.apm['alerting:show']; + const canSaveAlerts = !!capabilities.apm['alerting:save']; const isAlertingPluginEnabled = 'alerts' in plugin.plugins; - const isAlertingAvailable = isAlertingPluginEnabled && (canReadAlerts || canSaveAlerts); - - const { core } = useApmPluginContext(); + const isMlPluginEnabled = 'ml' in plugin.plugins; + const canReadAnomalies = !!( + isMlPluginEnabled && + capabilities.ml.canAccessML && + capabilities.ml.canGetJobs + ); const ADD_DATA_LABEL = i18n.translate('xpack.apm.addDataButtonLabel', { defaultMessage: 'Add data', @@ -58,12 +57,15 @@ export function ServiceDetails({ tab }: Props) { )} { + const { label, severity } = anomalyScoreSeverityMap[value]; + const defaultColor = theme.eui.euiColorMediumShade; + const color = getSeverityColor(theme, severity) || defaultColor; + return { + value: value.toString(10), + inputDisplay: ( + <> + + {label} + + + ), + dropdownDisplay: ( + <> + + {label} + + + +

+ +

+
+ + ), + }; +}; + +interface Props { + onChange: (value: SeverityScore) => void; + value: SeverityScore; +} + +export function SelectAnomalySeverity({ onChange, value }: Props) { + const theme = useTheme(); + const options = ANOMALY_SCORES.map((anomalyScore) => + getOption(theme, anomalyScore) + ); + const [anomalyScore, setAnomalyScore] = useState(value); + + useEffect(() => { + setAnomalyScore(value); + }, [value]); + + return ( + { + const selectedAnomalyScore = parseInt( + selectedValue, + 10 + ) as SeverityScore; + setAnomalyScore(selectedAnomalyScore); + onChange(selectedAnomalyScore); + }} + /> + ); +} diff --git a/x-pack/plugins/apm/public/components/shared/TransactionDurationAnomalyAlertTrigger/index.tsx b/x-pack/plugins/apm/public/components/shared/TransactionDurationAnomalyAlertTrigger/index.tsx new file mode 100644 index 0000000000000..9df493a16680e --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/TransactionDurationAnomalyAlertTrigger/index.tsx @@ -0,0 +1,153 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { EuiText, EuiSelect, EuiExpression } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; +import { ALERT_TYPES_CONFIG } from '../../../../common/alert_types'; +import { ALL_OPTION, useEnvironments } from '../../../hooks/useEnvironments'; +import { useServiceTransactionTypes } from '../../../hooks/useServiceTransactionTypes'; +import { useUrlParams } from '../../../hooks/useUrlParams'; +import { ServiceAlertTrigger } from '../ServiceAlertTrigger'; +import { PopoverExpression } from '../ServiceAlertTrigger/PopoverExpression'; +import { SelectAnomalySeverity } from './SelectAnomalySeverity'; + +interface Params { + windowSize: number; + windowUnit: string; + serviceName: string; + transactionType: string; + environment: string; + anomalyScore: 0 | 25 | 50 | 75; +} + +interface Props { + alertParams: Params; + setAlertParams: (key: string, value: any) => void; + setAlertProperty: (key: string, value: any) => void; +} + +export function TransactionDurationAnomalyAlertTrigger(props: Props) { + const { setAlertParams, alertParams, setAlertProperty } = props; + const { urlParams } = useUrlParams(); + const transactionTypes = useServiceTransactionTypes(urlParams); + const { serviceName, start, end } = urlParams; + const { environmentOptions } = useEnvironments({ serviceName, start, end }); + + if (!transactionTypes.length || !serviceName) { + return null; + } + + const defaults: Params = { + windowSize: 15, + windowUnit: 'm', + transactionType: transactionTypes[0], + serviceName, + environment: urlParams.environment || ALL_OPTION.value, + anomalyScore: 75, + }; + + const params = { + ...defaults, + ...alertParams, + }; + + const fields = [ + +
{serviceName}
+ + } + />, + + + setAlertParams('environment', e.target.value as Params['environment']) + } + compressed + /> + , + + { + return { + text: key, + value: key, + }; + })} + onChange={(e) => + setAlertParams( + 'transactionType', + e.target.value as Params['transactionType'] + ) + } + compressed + /> + , + + { + setAlertParams('anomalyScore', value); + }} + /> + , + ]; + + return ( + + ); +} + +// Default export is required for React.lazy loading +// +// eslint-disable-next-line import/no-default-export +export default TransactionDurationAnomalyAlertTrigger; diff --git a/x-pack/plugins/apm/public/plugin.ts b/x-pack/plugins/apm/public/plugin.ts index cf729fe0ff301..9e6846de765d2 100644 --- a/x-pack/plugins/apm/public/plugin.ts +++ b/x-pack/plugins/apm/public/plugin.ts @@ -166,5 +166,20 @@ export class ApmPlugin implements Plugin { }), requiresAppContext: true, }); + + plugins.triggers_actions_ui.alertTypeRegistry.register({ + id: AlertType.TransactionDurationAnomaly, + name: i18n.translate('xpack.apm.alertTypes.transactionDurationAnomaly', { + defaultMessage: 'Transaction duration anomaly', + }), + iconClass: 'bell', + alertParamsExpression: lazy(() => + import('./components/shared/TransactionDurationAnomalyAlertTrigger') + ), + validate: () => ({ + errors: [], + }), + requiresAppContext: true, + }); } } diff --git a/x-pack/plugins/apm/server/lib/alerts/register_apm_alerts.ts b/x-pack/plugins/apm/server/lib/alerts/register_apm_alerts.ts index 4b8e9cf937a2b..44ca80143bcd9 100644 --- a/x-pack/plugins/apm/server/lib/alerts/register_apm_alerts.ts +++ b/x-pack/plugins/apm/server/lib/alerts/register_apm_alerts.ts @@ -8,12 +8,15 @@ import { Observable } from 'rxjs'; import { AlertingPlugin } from '../../../../alerts/server'; import { ActionsPlugin } from '../../../../actions/server'; import { registerTransactionDurationAlertType } from './register_transaction_duration_alert_type'; +import { registerTransactionDurationAnomalyAlertType } from './register_transaction_duration_anomaly_alert_type'; import { registerErrorRateAlertType } from './register_error_rate_alert_type'; import { APMConfig } from '../..'; +import { MlPluginSetup } from '../../../../ml/server'; interface Params { alerts: AlertingPlugin['setup']; actions: ActionsPlugin['setup']; + ml?: MlPluginSetup; config$: Observable; } @@ -22,6 +25,11 @@ export function registerApmAlerts(params: Params) { alerts: params.alerts, config$: params.config$, }); + registerTransactionDurationAnomalyAlertType({ + alerts: params.alerts, + ml: params.ml, + config$: params.config$, + }); registerErrorRateAlertType({ alerts: params.alerts, config$: params.config$, diff --git a/x-pack/plugins/apm/server/lib/alerts/register_transaction_duration_anomaly_alert_type.ts b/x-pack/plugins/apm/server/lib/alerts/register_transaction_duration_anomaly_alert_type.ts new file mode 100644 index 0000000000000..05facdfb7ac89 --- /dev/null +++ b/x-pack/plugins/apm/server/lib/alerts/register_transaction_duration_anomaly_alert_type.ts @@ -0,0 +1,136 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { schema, TypeOf } from '@kbn/config-schema'; +import { Observable } from 'rxjs'; +import { i18n } from '@kbn/i18n'; +import { KibanaRequest } from '../../../../../../src/core/server'; +import { AlertType, ALERT_TYPES_CONFIG } from '../../../common/alert_types'; +import { AlertingPlugin } from '../../../../alerts/server'; +import { APMConfig } from '../..'; +import { MlPluginSetup } from '../../../../ml/server'; +import { getMLJobIds } from '../service_map/get_service_anomalies'; + +interface RegisterAlertParams { + alerts: AlertingPlugin['setup']; + ml?: MlPluginSetup; + config$: Observable; +} + +const paramsSchema = schema.object({ + serviceName: schema.string(), + transactionType: schema.string(), + windowSize: schema.number(), + windowUnit: schema.string(), + environment: schema.string(), + anomalyScore: schema.number(), +}); + +const alertTypeConfig = + ALERT_TYPES_CONFIG[AlertType.TransactionDurationAnomaly]; + +export function registerTransactionDurationAnomalyAlertType({ + alerts, + ml, + config$, +}: RegisterAlertParams) { + alerts.registerType({ + id: AlertType.TransactionDurationAnomaly, + name: alertTypeConfig.name, + actionGroups: alertTypeConfig.actionGroups, + defaultActionGroupId: alertTypeConfig.defaultActionGroupId, + validate: { + params: paramsSchema, + }, + actionVariables: { + context: [ + { + description: i18n.translate( + 'xpack.apm.registerTransactionDurationAnomalyAlertType.variables.serviceName', + { + defaultMessage: 'Service name', + } + ), + name: 'serviceName', + }, + { + description: i18n.translate( + 'xpack.apm.registerTransactionDurationAnomalyAlertType.variables.transactionType', + { + defaultMessage: 'Transaction type', + } + ), + name: 'transactionType', + }, + ], + }, + producer: 'apm', + executor: async ({ services, params, state }) => { + if (!ml) { + return; + } + const alertParams = params as TypeOf; + const mlClient = services.getLegacyScopedClusterClient(ml.mlClient); + const request = { params: 'DummyKibanaRequest' } as KibanaRequest; + const { mlAnomalySearch } = ml.mlSystemProvider(mlClient, request); + const anomalyDetectors = ml.anomalyDetectorsProvider(mlClient, request); + + const mlJobIds = await getMLJobIds( + { anomalyDetectors }, + alertParams.environment + ); + const anomalySearchParams = { + body: { + size: 0, + query: { + bool: { + filter: [ + { term: { result_type: 'record' } }, + { terms: { job_id: mlJobIds } }, + { + range: { + timestamp: { + gte: `now-${alertParams.windowSize}${alertParams.windowUnit}`, + format: 'epoch_millis', + }, + }, + }, + { + term: { + partition_field_value: alertParams.serviceName, + }, + }, + { + range: { + record_score: { + gte: alertParams.anomalyScore, + }, + }, + }, + ], + }, + }, + }, + }; + + const response = ((await mlAnomalySearch( + anomalySearchParams + )) as unknown) as { hits: { total: { value: number } } }; + const hitCount = response.hits.total.value; + + if (hitCount > 0) { + const alertInstance = services.alertInstanceFactory( + AlertType.TransactionDurationAnomaly + ); + alertInstance.scheduleActions(alertTypeConfig.defaultActionGroupId, { + serviceName: alertParams.serviceName, + }); + } + + return {}; + }, + }); +} diff --git a/x-pack/plugins/apm/server/lib/anomaly_detection/get_ml_jobs_with_apm_group.ts b/x-pack/plugins/apm/server/lib/anomaly_detection/get_ml_jobs_with_apm_group.ts index 5c0a3d17648aa..8c8cb7e9e52d3 100644 --- a/x-pack/plugins/apm/server/lib/anomaly_detection/get_ml_jobs_with_apm_group.ts +++ b/x-pack/plugins/apm/server/lib/anomaly_detection/get_ml_jobs_with_apm_group.ts @@ -4,14 +4,18 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Setup } from '../helpers/setup_request'; +import { MlPluginSetup } from '../../../../ml/server'; import { APM_ML_JOB_GROUP } from './constants'; // returns ml jobs containing "apm" group // workaround: the ML api returns 404 when no jobs are found. This is handled so instead of throwing an empty response is returned -export async function getMlJobsWithAPMGroup(ml: NonNullable) { +export async function getMlJobsWithAPMGroup({ + anomalyDetectors, +}: { + anomalyDetectors: ReturnType; +}) { try { - return await ml.anomalyDetectors.jobs(APM_ML_JOB_GROUP); + return await anomalyDetectors.jobs(APM_ML_JOB_GROUP); } catch (e) { if (e.statusCode === 404) { return { count: 0, jobs: [] }; diff --git a/x-pack/plugins/apm/server/lib/helpers/setup_request.ts b/x-pack/plugins/apm/server/lib/helpers/setup_request.ts index ddad2eb2d22dc..a242a0adb6d4c 100644 --- a/x-pack/plugins/apm/server/lib/helpers/setup_request.ts +++ b/x-pack/plugins/apm/server/lib/helpers/setup_request.ts @@ -98,7 +98,11 @@ export async function setupRequest( context, request, }), - ml: getMlSetup(context, request), + ml: getMlSetup( + context.plugins.ml, + context.core.savedObjects.client, + request + ), config, }; @@ -110,20 +114,21 @@ export async function setupRequest( } as InferSetup; } -function getMlSetup(context: APMRequestHandlerContext, request: KibanaRequest) { - if (!context.plugins.ml) { +function getMlSetup( + ml: APMRequestHandlerContext['plugins']['ml'], + savedObjectsClient: APMRequestHandlerContext['core']['savedObjects']['client'], + request: KibanaRequest +) { + if (!ml) { return; } - const ml = context.plugins.ml; const mlClient = ml.mlClient.asScoped(request); + const mlSystem = ml.mlSystemProvider(mlClient, request); return { - mlSystem: ml.mlSystemProvider(mlClient, request), - anomalyDetectors: ml.anomalyDetectorsProvider(mlClient, request), - modules: ml.modulesProvider( - mlClient, - request, - context.core.savedObjects.client - ), mlClient, + mlSystem, + modules: ml.modulesProvider(mlClient, request, savedObjectsClient), + anomalyDetectors: ml.anomalyDetectorsProvider(mlClient, request), + mlAnomalySearch: mlSystem.mlAnomalySearch, }; } diff --git a/x-pack/plugins/apm/server/lib/service_map/get_service_anomalies.ts b/x-pack/plugins/apm/server/lib/service_map/get_service_anomalies.ts index 03716382af859..05df730b1b4bd 100644 --- a/x-pack/plugins/apm/server/lib/service_map/get_service_anomalies.ts +++ b/x-pack/plugins/apm/server/lib/service_map/get_service_anomalies.ts @@ -16,6 +16,8 @@ import { ML_ERRORS, } from '../../../common/anomaly_detection'; import { getMlJobsWithAPMGroup } from '../anomaly_detection/get_ml_jobs_with_apm_group'; +import { ENVIRONMENT_ALL } from '../../../common/environment_filter_values'; +import { MlPluginSetup } from '../../../../ml/server'; export const DEFAULT_ANOMALIES = { mlJobIds: [], serviceAnomalies: {} }; @@ -136,16 +138,19 @@ function transformResponseToServiceAnomalies( } export async function getMLJobIds( - ml: Required['ml'], + ml: { + anomalyDetectors: ReturnType; + }, environment?: string ) { const response = await getMlJobsWithAPMGroup(ml); + // to filter out legacy jobs we are filtering by the existence of `apm_ml_version` in `custom_settings` // and checking that it is compatable. const mlJobs = response.jobs.filter( (job) => (job.custom_settings?.job_tags?.apm_ml_version ?? 0) >= 2 ); - if (environment) { + if (environment && environment !== ENVIRONMENT_ALL) { const matchingMLJob = mlJobs.find( (job) => job.custom_settings?.job_tags?.environment === environment ); diff --git a/x-pack/plugins/apm/server/plugin.ts b/x-pack/plugins/apm/server/plugin.ts index deafda67b806d..71202c62e6f6c 100644 --- a/x-pack/plugins/apm/server/plugin.ts +++ b/x-pack/plugins/apm/server/plugin.ts @@ -83,6 +83,7 @@ export class APMPlugin implements Plugin { registerApmAlerts({ alerts: plugins.alerts, actions: plugins.actions, + ml: plugins.ml, config$: mergedConfig$, }); } diff --git a/x-pack/plugins/ml/server/shared_services/providers/anomaly_detectors.ts b/x-pack/plugins/ml/server/shared_services/providers/anomaly_detectors.ts index 1140af0b76404..603b4fba17adb 100644 --- a/x-pack/plugins/ml/server/shared_services/providers/anomaly_detectors.ts +++ b/x-pack/plugins/ml/server/shared_services/providers/anomaly_detectors.ts @@ -23,7 +23,13 @@ export function getAnomalyDetectorsProvider({ }: SharedServicesChecks): AnomalyDetectorsProvider { return { anomalyDetectorsProvider(mlClusterClient: ILegacyScopedClusterClient, request: KibanaRequest) { - const hasMlCapabilities = getHasMlCapabilities(request); + // APM is using this service in anomaly alert, kibana alerting doesn't provide request object + // So we are adding a dummy request for now + // TODO: Remove this once kibana alerting provides request object + const hasMlCapabilities = + request.params !== 'DummyKibanaRequest' + ? getHasMlCapabilities(request) + : (_caps: string[]) => Promise.resolve(); return { async jobs(jobId?: string) { isFullLicense(); From ac4bde15aafb56be577136a9afef8351199ce212 Mon Sep 17 00:00:00 2001 From: Oliver Gupte Date: Tue, 25 Aug 2020 23:24:55 -0700 Subject: [PATCH 2/9] Code review feedback --- .../SelectAnomalySeverity.tsx | 10 ++-------- .../TransactionDurationAnomalyAlertTrigger/index.tsx | 11 ++--------- ...egister_transaction_duration_anomaly_alert_type.ts | 2 +- .../anomaly_detection/get_anomaly_detection_jobs.ts | 2 +- .../anomaly_detection/get_ml_jobs_with_apm_group.ts | 8 +++----- .../server/lib/anomaly_detection/has_legacy_jobs.ts | 2 +- .../server/lib/service_map/get_service_anomalies.ts | 8 +++----- .../lib/transactions/charts/get_anomaly_data/index.ts | 5 ++++- 8 files changed, 17 insertions(+), 31 deletions(-) diff --git a/x-pack/plugins/apm/public/components/shared/TransactionDurationAnomalyAlertTrigger/SelectAnomalySeverity.tsx b/x-pack/plugins/apm/public/components/shared/TransactionDurationAnomalyAlertTrigger/SelectAnomalySeverity.tsx index c817d8391fb50..4d1500e5f16dd 100644 --- a/x-pack/plugins/apm/public/components/shared/TransactionDurationAnomalyAlertTrigger/SelectAnomalySeverity.tsx +++ b/x-pack/plugins/apm/public/components/shared/TransactionDurationAnomalyAlertTrigger/SelectAnomalySeverity.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useState, useEffect } from 'react'; +import React from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiHealth, EuiSpacer, EuiSuperSelect, EuiText } from '@elastic/eui'; @@ -88,24 +88,18 @@ export function SelectAnomalySeverity({ onChange, value }: Props) { const options = ANOMALY_SCORES.map((anomalyScore) => getOption(theme, anomalyScore) ); - const [anomalyScore, setAnomalyScore] = useState(value); - - useEffect(() => { - setAnomalyScore(value); - }, [value]); return ( { const selectedAnomalyScore = parseInt( selectedValue, 10 ) as SeverityScore; - setAnomalyScore(selectedAnomalyScore); onChange(selectedAnomalyScore); }} /> diff --git a/x-pack/plugins/apm/public/components/shared/TransactionDurationAnomalyAlertTrigger/index.tsx b/x-pack/plugins/apm/public/components/shared/TransactionDurationAnomalyAlertTrigger/index.tsx index 9df493a16680e..e1e5ddd258857 100644 --- a/x-pack/plugins/apm/public/components/shared/TransactionDurationAnomalyAlertTrigger/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/TransactionDurationAnomalyAlertTrigger/index.tsx @@ -84,9 +84,7 @@ export function TransactionDurationAnomalyAlertTrigger(props: Props) { - setAlertParams('environment', e.target.value as Params['environment']) - } + onChange={(e) => setAlertParams('environment', e.target.value)} compressed /> , @@ -107,12 +105,7 @@ export function TransactionDurationAnomalyAlertTrigger(props: Props) { value: key, }; })} - onChange={(e) => - setAlertParams( - 'transactionType', - e.target.value as Params['transactionType'] - ) - } + onChange={(e) => setAlertParams('transactionType', e.target.value)} compressed /> , diff --git a/x-pack/plugins/apm/server/lib/alerts/register_transaction_duration_anomaly_alert_type.ts b/x-pack/plugins/apm/server/lib/alerts/register_transaction_duration_anomaly_alert_type.ts index 05facdfb7ac89..3abc89c470b21 100644 --- a/x-pack/plugins/apm/server/lib/alerts/register_transaction_duration_anomaly_alert_type.ts +++ b/x-pack/plugins/apm/server/lib/alerts/register_transaction_duration_anomaly_alert_type.ts @@ -79,7 +79,7 @@ export function registerTransactionDurationAnomalyAlertType({ const anomalyDetectors = ml.anomalyDetectorsProvider(mlClient, request); const mlJobIds = await getMLJobIds( - { anomalyDetectors }, + anomalyDetectors, alertParams.environment ); const anomalySearchParams = { diff --git a/x-pack/plugins/apm/server/lib/anomaly_detection/get_anomaly_detection_jobs.ts b/x-pack/plugins/apm/server/lib/anomaly_detection/get_anomaly_detection_jobs.ts index 05f41cdfdffd4..ead0c79a02836 100644 --- a/x-pack/plugins/apm/server/lib/anomaly_detection/get_anomaly_detection_jobs.ts +++ b/x-pack/plugins/apm/server/lib/anomaly_detection/get_anomaly_detection_jobs.ts @@ -22,7 +22,7 @@ export async function getAnomalyDetectionJobs(setup: Setup, logger: Logger) { throw Boom.forbidden(ML_ERRORS.ML_NOT_AVAILABLE_IN_SPACE); } - const response = await getMlJobsWithAPMGroup(ml); + const response = await getMlJobsWithAPMGroup(ml.anomalyDetectors); return response.jobs .filter((job) => (job.custom_settings?.job_tags?.apm_ml_version ?? 0) >= 2) .map((job) => { diff --git a/x-pack/plugins/apm/server/lib/anomaly_detection/get_ml_jobs_with_apm_group.ts b/x-pack/plugins/apm/server/lib/anomaly_detection/get_ml_jobs_with_apm_group.ts index 8c8cb7e9e52d3..1c39892c3fd96 100644 --- a/x-pack/plugins/apm/server/lib/anomaly_detection/get_ml_jobs_with_apm_group.ts +++ b/x-pack/plugins/apm/server/lib/anomaly_detection/get_ml_jobs_with_apm_group.ts @@ -9,11 +9,9 @@ import { APM_ML_JOB_GROUP } from './constants'; // returns ml jobs containing "apm" group // workaround: the ML api returns 404 when no jobs are found. This is handled so instead of throwing an empty response is returned -export async function getMlJobsWithAPMGroup({ - anomalyDetectors, -}: { - anomalyDetectors: ReturnType; -}) { +export async function getMlJobsWithAPMGroup( + anomalyDetectors: ReturnType +) { try { return await anomalyDetectors.jobs(APM_ML_JOB_GROUP); } catch (e) { diff --git a/x-pack/plugins/apm/server/lib/anomaly_detection/has_legacy_jobs.ts b/x-pack/plugins/apm/server/lib/anomaly_detection/has_legacy_jobs.ts index ed66236726b9f..c1f346aa30e1f 100644 --- a/x-pack/plugins/apm/server/lib/anomaly_detection/has_legacy_jobs.ts +++ b/x-pack/plugins/apm/server/lib/anomaly_detection/has_legacy_jobs.ts @@ -23,7 +23,7 @@ export async function hasLegacyJobs(setup: Setup) { throw Boom.forbidden(ML_ERRORS.ML_NOT_AVAILABLE_IN_SPACE); } - const response = await getMlJobsWithAPMGroup(ml); + const response = await getMlJobsWithAPMGroup(ml.anomalyDetectors); return response.jobs.some( (job) => job.job_id.endsWith('high_mean_response_time') && diff --git a/x-pack/plugins/apm/server/lib/service_map/get_service_anomalies.ts b/x-pack/plugins/apm/server/lib/service_map/get_service_anomalies.ts index 05df730b1b4bd..1e0f941538ca3 100644 --- a/x-pack/plugins/apm/server/lib/service_map/get_service_anomalies.ts +++ b/x-pack/plugins/apm/server/lib/service_map/get_service_anomalies.ts @@ -45,7 +45,7 @@ export async function getServiceAnomalies({ throw Boom.forbidden(ML_ERRORS.ML_NOT_AVAILABLE_IN_SPACE); } - const mlJobIds = await getMLJobIds(ml, environment); + const mlJobIds = await getMLJobIds(ml.anomalyDetectors, environment); const params = { body: { size: 0, @@ -138,12 +138,10 @@ function transformResponseToServiceAnomalies( } export async function getMLJobIds( - ml: { - anomalyDetectors: ReturnType; - }, + anomalyDetectors: ReturnType, environment?: string ) { - const response = await getMlJobsWithAPMGroup(ml); + const response = await getMlJobsWithAPMGroup(anomalyDetectors); // to filter out legacy jobs we are filtering by the existence of `apm_ml_version` in `custom_settings` // and checking that it is compatable. diff --git a/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/index.ts b/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/index.ts index 072099bc9553c..596c3137ec19f 100644 --- a/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/index.ts +++ b/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/index.ts @@ -66,7 +66,10 @@ export async function getAnomalySeries({ let mlJobIds: string[] = []; try { - mlJobIds = await getMLJobIds(setup.ml, uiFilters.environment); + mlJobIds = await getMLJobIds( + setup.ml.anomalyDetectors, + uiFilters.environment + ); } catch (error) { logger.error(error); return; From dd31e8b16e3a05de3ad0055d0269a5368e546e8b Mon Sep 17 00:00:00 2001 From: Oliver Gupte Date: Tue, 25 Aug 2020 23:53:51 -0700 Subject: [PATCH 3/9] Display alert summary with the selected anomaly severity label instead of the anomaly score. --- .../PopoverExpression/index.tsx | 4 +-- .../SelectAnomalySeverity.tsx | 35 ++++++++++--------- .../index.tsx | 11 +++--- 3 files changed, 27 insertions(+), 23 deletions(-) diff --git a/x-pack/plugins/apm/public/components/shared/ServiceAlertTrigger/PopoverExpression/index.tsx b/x-pack/plugins/apm/public/components/shared/ServiceAlertTrigger/PopoverExpression/index.tsx index b07672eeaee06..33f61d61f989b 100644 --- a/x-pack/plugins/apm/public/components/shared/ServiceAlertTrigger/PopoverExpression/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/ServiceAlertTrigger/PopoverExpression/index.tsx @@ -8,8 +8,8 @@ import React, { useState } from 'react'; import { EuiExpression, EuiPopover } from '@elastic/eui'; interface Props { - title: string; - value: string; + title: React.ReactNode; + value: React.ReactNode; children?: React.ReactNode; } diff --git a/x-pack/plugins/apm/public/components/shared/TransactionDurationAnomalyAlertTrigger/SelectAnomalySeverity.tsx b/x-pack/plugins/apm/public/components/shared/TransactionDurationAnomalyAlertTrigger/SelectAnomalySeverity.tsx index 4d1500e5f16dd..fcbdb900368ea 100644 --- a/x-pack/plugins/apm/public/components/shared/TransactionDurationAnomalyAlertTrigger/SelectAnomalySeverity.tsx +++ b/x-pack/plugins/apm/public/components/shared/TransactionDurationAnomalyAlertTrigger/SelectAnomalySeverity.tsx @@ -10,7 +10,6 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { EuiHealth, EuiSpacer, EuiSuperSelect, EuiText } from '@elastic/eui'; import { getSeverityColor } from '../../app/ServiceMap/cytoscapeOptions'; import { useTheme } from '../../../hooks/useTheme'; -import { EuiTheme } from '../../../../../observability/public'; import { severity as Severity } from '../../app/ServiceMap/Popover/getSeverity'; type SeverityScore = 0 | 25 | 50 | 75; @@ -45,24 +44,29 @@ const anomalyScoreSeverityMap: { }, }; -const getOption = (theme: EuiTheme, value: SeverityScore) => { - const { label, severity } = anomalyScoreSeverityMap[value]; +export function AnomalySeverity({ + severityScore, +}: { + severityScore: SeverityScore; +}) { + const theme = useTheme(); + const { label, severity } = anomalyScoreSeverityMap[severityScore]; const defaultColor = theme.eui.euiColorMediumShade; const color = getSeverityColor(theme, severity) || defaultColor; + return ( + + {label} + + ); +} + +const getOption = (value: SeverityScore) => { return { value: value.toString(10), - inputDisplay: ( - <> - - {label} - - - ), + inputDisplay: , dropdownDisplay: ( <> - - {label} - +

@@ -84,10 +88,7 @@ interface Props { } export function SelectAnomalySeverity({ onChange, value }: Props) { - const theme = useTheme(); - const options = ANOMALY_SCORES.map((anomalyScore) => - getOption(theme, anomalyScore) - ); + const options = ANOMALY_SCORES.map((anomalyScore) => getOption(anomalyScore)); return ( , } title={i18n.translate( - 'xpack.apm.transactionDurationAnomalyAlertTrigger.anomalyScore', + 'xpack.apm.transactionDurationAnomalyAlertTrigger.anomalySeverity', { - defaultMessage: 'Has anomaly score', + defaultMessage: 'Has anomaly with severity', } )} > From 94c3b48fb0f38921c0c0449c470c0adf52402ce2 Mon Sep 17 00:00:00 2001 From: Oliver Gupte Date: Wed, 26 Aug 2020 00:40:34 -0700 Subject: [PATCH 4/9] - refactored ALL_OPTION and NOT_DEFINED_OPTION to be shared from common/environment_filter_values - utilize getEnvironmentLabel in the alerting trigger components and added support for the 'All' label --- .../apm/common/environment_filter_values.ts | 24 ++++++++++++++++--- .../shared/EnvironmentFilter/index.tsx | 11 +++------ .../shared/ErrorRateAlertTrigger/index.tsx | 14 +++++------ .../TransactionDurationAlertTrigger/index.tsx | 14 +++++------ .../index.tsx | 14 +++++------ .../apm/public/hooks/useEnvironments.tsx | 10 +------- 6 files changed, 46 insertions(+), 41 deletions(-) diff --git a/x-pack/plugins/apm/common/environment_filter_values.ts b/x-pack/plugins/apm/common/environment_filter_values.ts index 38b6f480ca3d3..6d89b6b16f764 100644 --- a/x-pack/plugins/apm/common/environment_filter_values.ts +++ b/x-pack/plugins/apm/common/environment_filter_values.ts @@ -9,11 +9,29 @@ import { i18n } from '@kbn/i18n'; export const ENVIRONMENT_ALL = 'ENVIRONMENT_ALL'; export const ENVIRONMENT_NOT_DEFINED = 'ENVIRONMENT_NOT_DEFINED'; +const ENVIRONMENT_ALL_LABEL = i18n.translate('xpack.apm.environment.allLabel', { + defaultMessage: 'All', +}); +const ENVIRONMENT_NOT_DEFINED_LABEL = i18n.translate( + 'xpack.apm.filter.environment.notDefinedLabel', + { defaultMessage: 'Not defined' } +); + +export const ALL_OPTION = { + value: ENVIRONMENT_ALL, + text: ENVIRONMENT_ALL_LABEL, +}; +export const NOT_DEFINED_OPTION = { + value: ENVIRONMENT_NOT_DEFINED, + text: ENVIRONMENT_NOT_DEFINED_LABEL, +}; + export function getEnvironmentLabel(environment: string) { + if (environment === ENVIRONMENT_ALL) { + return ENVIRONMENT_ALL_LABEL; + } if (environment === ENVIRONMENT_NOT_DEFINED) { - return i18n.translate('xpack.apm.filter.environment.notDefinedLabel', { - defaultMessage: 'Not defined', - }); + return ENVIRONMENT_NOT_DEFINED_LABEL; } return environment; } diff --git a/x-pack/plugins/apm/public/components/shared/EnvironmentFilter/index.tsx b/x-pack/plugins/apm/public/components/shared/EnvironmentFilter/index.tsx index 1490ca42679b9..7a694dee17710 100644 --- a/x-pack/plugins/apm/public/components/shared/EnvironmentFilter/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/EnvironmentFilter/index.tsx @@ -12,10 +12,12 @@ import { useUrlParams } from '../../../hooks/useUrlParams'; import { history } from '../../../utils/history'; import { fromQuery, toQuery } from '../Links/url_helpers'; import { + ALL_OPTION, + NOT_DEFINED_OPTION, ENVIRONMENT_ALL, ENVIRONMENT_NOT_DEFINED, } from '../../../../common/environment_filter_values'; -import { useEnvironments, ALL_OPTION } from '../../../hooks/useEnvironments'; +import { useEnvironments } from '../../../hooks/useEnvironments'; function updateEnvironmentUrl( location: ReturnType, @@ -32,13 +34,6 @@ function updateEnvironmentUrl( }); } -const NOT_DEFINED_OPTION = { - value: ENVIRONMENT_NOT_DEFINED, - text: i18n.translate('xpack.apm.filter.environment.notDefinedLabel', { - defaultMessage: 'Not defined', - }), -}; - const SEPARATOR_OPTION = { text: `- ${i18n.translate( 'xpack.apm.filter.environment.selectEnvironmentLabel', diff --git a/x-pack/plugins/apm/public/components/shared/ErrorRateAlertTrigger/index.tsx b/x-pack/plugins/apm/public/components/shared/ErrorRateAlertTrigger/index.tsx index b457fb1092bc6..c8a174fa698fa 100644 --- a/x-pack/plugins/apm/public/components/shared/ErrorRateAlertTrigger/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/ErrorRateAlertTrigger/index.tsx @@ -12,8 +12,12 @@ import { ForLastExpression } from '../../../../../triggers_actions_ui/public'; import { ALERT_TYPES_CONFIG } from '../../../../common/alert_types'; import { ServiceAlertTrigger } from '../ServiceAlertTrigger'; import { PopoverExpression } from '../ServiceAlertTrigger/PopoverExpression'; -import { useEnvironments, ALL_OPTION } from '../../../hooks/useEnvironments'; +import { useEnvironments } from '../../../hooks/useEnvironments'; import { useUrlParams } from '../../../hooks/useUrlParams'; +import { + ENVIRONMENT_ALL, + getEnvironmentLabel, +} from '../../../../common/environment_filter_values'; export interface ErrorRateAlertTriggerParams { windowSize: number; @@ -39,7 +43,7 @@ export function ErrorRateAlertTrigger(props: Props) { threshold: 25, windowSize: 1, windowUnit: 'm', - environment: ALL_OPTION.value, + environment: ENVIRONMENT_ALL, }; const params = { @@ -51,11 +55,7 @@ export function ErrorRateAlertTrigger(props: Props) { const fields = [ , env !== ENVIRONMENT_NOT_DEFINED) From c798812f5d65c6b45fa7abab12a065ab5228a5b1 Mon Sep 17 00:00:00 2001 From: Oliver Gupte Date: Wed, 26 Aug 2020 15:41:03 -0700 Subject: [PATCH 5/9] refactor get_all_environments to minimize exports and be more consistent and clean --- .../apm/common/environment_filter_values.ts | 42 +++++++++---------- .../shared/EnvironmentFilter/index.tsx | 14 +++---- .../shared/ErrorRateAlertTrigger/index.tsx | 2 +- .../TransactionDurationAlertTrigger/index.tsx | 2 +- .../index.tsx | 2 +- .../apm/public/hooks/useEnvironments.tsx | 6 +-- .../alerts/register_error_rate_alert_type.ts | 2 +- ...egister_transaction_duration_alert_type.ts | 2 +- .../lib/environments/get_all_environments.ts | 2 +- .../get_environment_ui_filter_es.test.ts | 2 +- .../get_environment_ui_filter_es.ts | 2 +- .../lib/service_map/get_service_anomalies.ts | 2 +- .../server/lib/ui_filters/get_environments.ts | 2 +- .../translations/translations/ja-JP.json | 2 +- .../translations/translations/zh-CN.json | 2 +- 15 files changed, 41 insertions(+), 45 deletions(-) diff --git a/x-pack/plugins/apm/common/environment_filter_values.ts b/x-pack/plugins/apm/common/environment_filter_values.ts index 6d89b6b16f764..e231f37a170ed 100644 --- a/x-pack/plugins/apm/common/environment_filter_values.ts +++ b/x-pack/plugins/apm/common/environment_filter_values.ts @@ -6,32 +6,30 @@ import { i18n } from '@kbn/i18n'; -export const ENVIRONMENT_ALL = 'ENVIRONMENT_ALL'; -export const ENVIRONMENT_NOT_DEFINED = 'ENVIRONMENT_NOT_DEFINED'; +const ENVIRONMENT_ALL_VALUE = 'ENVIRONMENT_ALL'; +const ENVIRONMENT_NOT_DEFINED_VALUE = 'ENVIRONMENT_NOT_DEFINED'; -const ENVIRONMENT_ALL_LABEL = i18n.translate('xpack.apm.environment.allLabel', { - defaultMessage: 'All', -}); -const ENVIRONMENT_NOT_DEFINED_LABEL = i18n.translate( - 'xpack.apm.filter.environment.notDefinedLabel', - { defaultMessage: 'Not defined' } -); +const environmentLabels: Record = { + [ENVIRONMENT_ALL_VALUE]: i18n.translate( + 'xpack.apm.filter.environment.allLabel', + { defaultMessage: 'All' } + ), + [ENVIRONMENT_NOT_DEFINED_VALUE]: i18n.translate( + 'xpack.apm.filter.environment.notDefinedLabel', + { defaultMessage: 'Not defined' } + ), +}; -export const ALL_OPTION = { - value: ENVIRONMENT_ALL, - text: ENVIRONMENT_ALL_LABEL, +export const ENVIRONMENT_ALL = { + value: ENVIRONMENT_ALL_VALUE, + text: environmentLabels[ENVIRONMENT_ALL_VALUE], }; -export const NOT_DEFINED_OPTION = { - value: ENVIRONMENT_NOT_DEFINED, - text: ENVIRONMENT_NOT_DEFINED_LABEL, + +export const ENVIRONMENT_NOT_DEFINED = { + value: ENVIRONMENT_NOT_DEFINED_VALUE, + text: environmentLabels[ENVIRONMENT_NOT_DEFINED_VALUE], }; export function getEnvironmentLabel(environment: string) { - if (environment === ENVIRONMENT_ALL) { - return ENVIRONMENT_ALL_LABEL; - } - if (environment === ENVIRONMENT_NOT_DEFINED) { - return ENVIRONMENT_NOT_DEFINED_LABEL; - } - return environment; + return environmentLabels[environment] || environment; } diff --git a/x-pack/plugins/apm/public/components/shared/EnvironmentFilter/index.tsx b/x-pack/plugins/apm/public/components/shared/EnvironmentFilter/index.tsx index 7a694dee17710..24ebb6bc8bd5f 100644 --- a/x-pack/plugins/apm/public/components/shared/EnvironmentFilter/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/EnvironmentFilter/index.tsx @@ -12,8 +12,6 @@ import { useUrlParams } from '../../../hooks/useUrlParams'; import { history } from '../../../utils/history'; import { fromQuery, toQuery } from '../Links/url_helpers'; import { - ALL_OPTION, - NOT_DEFINED_OPTION, ENVIRONMENT_ALL, ENVIRONMENT_NOT_DEFINED, } from '../../../../common/environment_filter_values'; @@ -24,7 +22,7 @@ function updateEnvironmentUrl( environment?: string ) { const nextEnvironmentQueryParam = - environment !== ENVIRONMENT_ALL ? environment : undefined; + environment !== ENVIRONMENT_ALL.value ? environment : undefined; history.push({ ...location, search: fromQuery({ @@ -44,16 +42,16 @@ const SEPARATOR_OPTION = { function getOptions(environments: string[]) { const environmentOptions = environments - .filter((env) => env !== ENVIRONMENT_NOT_DEFINED) + .filter((env) => env !== ENVIRONMENT_NOT_DEFINED.value) .map((environment) => ({ value: environment, text: environment, })); return [ - ALL_OPTION, - ...(environments.includes(ENVIRONMENT_NOT_DEFINED) - ? [NOT_DEFINED_OPTION] + ENVIRONMENT_ALL, + ...(environments.includes(ENVIRONMENT_NOT_DEFINED.value) + ? [ENVIRONMENT_NOT_DEFINED] : []), ...(environmentOptions.length > 0 ? [SEPARATOR_OPTION] : []), ...environmentOptions, @@ -78,7 +76,7 @@ export function EnvironmentFilter() { defaultMessage: 'environment', })} options={getOptions(environments)} - value={environment || ENVIRONMENT_ALL} + value={environment || ENVIRONMENT_ALL.value} onChange={(event) => { updateEnvironmentUrl(location, event.target.value); }} diff --git a/x-pack/plugins/apm/public/components/shared/ErrorRateAlertTrigger/index.tsx b/x-pack/plugins/apm/public/components/shared/ErrorRateAlertTrigger/index.tsx index c8a174fa698fa..4737233090347 100644 --- a/x-pack/plugins/apm/public/components/shared/ErrorRateAlertTrigger/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/ErrorRateAlertTrigger/index.tsx @@ -43,7 +43,7 @@ export function ErrorRateAlertTrigger(props: Props) { threshold: 25, windowSize: 1, windowUnit: 'm', - environment: ENVIRONMENT_ALL, + environment: ENVIRONMENT_ALL.value, }; const params = { diff --git a/x-pack/plugins/apm/public/components/shared/TransactionDurationAlertTrigger/index.tsx b/x-pack/plugins/apm/public/components/shared/TransactionDurationAlertTrigger/index.tsx index ad2d0480e9a44..def385ed357db 100644 --- a/x-pack/plugins/apm/public/components/shared/TransactionDurationAlertTrigger/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/TransactionDurationAlertTrigger/index.tsx @@ -58,7 +58,7 @@ export function TransactionDurationAlertTrigger(props: Props) { windowSize: 5, windowUnit: 'm', transactionType: transactionTypes[0], - environment: ENVIRONMENT_ALL, + environment: ENVIRONMENT_ALL.value, }; const params = { diff --git a/x-pack/plugins/apm/public/components/shared/TransactionDurationAnomalyAlertTrigger/index.tsx b/x-pack/plugins/apm/public/components/shared/TransactionDurationAnomalyAlertTrigger/index.tsx index 7ddb3b9a41afc..bb5af0620576e 100644 --- a/x-pack/plugins/apm/public/components/shared/TransactionDurationAnomalyAlertTrigger/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/TransactionDurationAnomalyAlertTrigger/index.tsx @@ -52,7 +52,7 @@ export function TransactionDurationAnomalyAlertTrigger(props: Props) { windowUnit: 'm', transactionType: transactionTypes[0], serviceName, - environment: urlParams.environment || ENVIRONMENT_ALL, + environment: urlParams.environment || ENVIRONMENT_ALL.value, anomalyScore: 75, }; diff --git a/x-pack/plugins/apm/public/hooks/useEnvironments.tsx b/x-pack/plugins/apm/public/hooks/useEnvironments.tsx index 6bbd5b6442bdd..9e01dde274ff7 100644 --- a/x-pack/plugins/apm/public/hooks/useEnvironments.tsx +++ b/x-pack/plugins/apm/public/hooks/useEnvironments.tsx @@ -7,20 +7,20 @@ import { useMemo } from 'react'; import { useFetcher } from './useFetcher'; import { - ALL_OPTION, + ENVIRONMENT_ALL, ENVIRONMENT_NOT_DEFINED, } from '../../common/environment_filter_values'; import { callApmApi } from '../services/rest/createCallApmApi'; function getEnvironmentOptions(environments: string[]) { const environmentOptions = environments - .filter((env) => env !== ENVIRONMENT_NOT_DEFINED) + .filter((env) => env !== ENVIRONMENT_NOT_DEFINED.value) .map((environment) => ({ value: environment, text: environment, })); - return [ALL_OPTION, ...environmentOptions]; + return [ENVIRONMENT_ALL, ...environmentOptions]; } export function useEnvironments({ diff --git a/x-pack/plugins/apm/server/lib/alerts/register_error_rate_alert_type.ts b/x-pack/plugins/apm/server/lib/alerts/register_error_rate_alert_type.ts index 53843b7f7412b..61e3dfee420a5 100644 --- a/x-pack/plugins/apm/server/lib/alerts/register_error_rate_alert_type.ts +++ b/x-pack/plugins/apm/server/lib/alerts/register_error_rate_alert_type.ts @@ -75,7 +75,7 @@ export function registerErrorRateAlertType({ }); const environmentTerm = - alertParams.environment === ENVIRONMENT_ALL + alertParams.environment === ENVIRONMENT_ALL.value ? [] : [{ term: { [SERVICE_ENVIRONMENT]: alertParams.environment } }]; diff --git a/x-pack/plugins/apm/server/lib/alerts/register_transaction_duration_alert_type.ts b/x-pack/plugins/apm/server/lib/alerts/register_transaction_duration_alert_type.ts index a922457b14cea..ead28c325692d 100644 --- a/x-pack/plugins/apm/server/lib/alerts/register_transaction_duration_alert_type.ts +++ b/x-pack/plugins/apm/server/lib/alerts/register_transaction_duration_alert_type.ts @@ -89,7 +89,7 @@ export function registerTransactionDurationAlertType({ }); const environmentTerm = - alertParams.environment === ENVIRONMENT_ALL + alertParams.environment === ENVIRONMENT_ALL.value ? [] : [{ term: { [SERVICE_ENVIRONMENT]: alertParams.environment } }]; diff --git a/x-pack/plugins/apm/server/lib/environments/get_all_environments.ts b/x-pack/plugins/apm/server/lib/environments/get_all_environments.ts index 423b87cb78c3c..29aaa98169fa5 100644 --- a/x-pack/plugins/apm/server/lib/environments/get_all_environments.ts +++ b/x-pack/plugins/apm/server/lib/environments/get_all_environments.ts @@ -48,7 +48,7 @@ export async function getAllEnvironments({ terms: { field: SERVICE_ENVIRONMENT, size: 100, - missing: includeMissing ? ENVIRONMENT_NOT_DEFINED : undefined, + missing: includeMissing ? ENVIRONMENT_NOT_DEFINED.value : undefined, }, }, }, diff --git a/x-pack/plugins/apm/server/lib/helpers/convert_ui_filters/__test__/get_environment_ui_filter_es.test.ts b/x-pack/plugins/apm/server/lib/helpers/convert_ui_filters/__test__/get_environment_ui_filter_es.test.ts index 800f809727eb6..a319bba1eabe1 100644 --- a/x-pack/plugins/apm/server/lib/helpers/convert_ui_filters/__test__/get_environment_ui_filter_es.test.ts +++ b/x-pack/plugins/apm/server/lib/helpers/convert_ui_filters/__test__/get_environment_ui_filter_es.test.ts @@ -21,7 +21,7 @@ describe('getEnvironmentUiFilterES', () => { }); it('should create a filter for missing service environments', () => { - const uiFilterES = getEnvironmentUiFilterES(ENVIRONMENT_NOT_DEFINED); + const uiFilterES = getEnvironmentUiFilterES(ENVIRONMENT_NOT_DEFINED.value); expect(uiFilterES).toHaveLength(1); expect(uiFilterES[0]).toHaveProperty( ['bool', 'must_not', 'exists', 'field'], diff --git a/x-pack/plugins/apm/server/lib/helpers/convert_ui_filters/get_environment_ui_filter_es.ts b/x-pack/plugins/apm/server/lib/helpers/convert_ui_filters/get_environment_ui_filter_es.ts index 87bc8dc968373..6ff98a9be75f9 100644 --- a/x-pack/plugins/apm/server/lib/helpers/convert_ui_filters/get_environment_ui_filter_es.ts +++ b/x-pack/plugins/apm/server/lib/helpers/convert_ui_filters/get_environment_ui_filter_es.ts @@ -12,7 +12,7 @@ export function getEnvironmentUiFilterES(environment?: string): ESFilter[] { if (!environment) { return []; } - if (environment === ENVIRONMENT_NOT_DEFINED) { + if (environment === ENVIRONMENT_NOT_DEFINED.value) { return [{ bool: { must_not: { exists: { field: SERVICE_ENVIRONMENT } } } }]; } return [{ term: { [SERVICE_ENVIRONMENT]: environment } }]; diff --git a/x-pack/plugins/apm/server/lib/service_map/get_service_anomalies.ts b/x-pack/plugins/apm/server/lib/service_map/get_service_anomalies.ts index 1e0f941538ca3..ec274d20b6005 100644 --- a/x-pack/plugins/apm/server/lib/service_map/get_service_anomalies.ts +++ b/x-pack/plugins/apm/server/lib/service_map/get_service_anomalies.ts @@ -148,7 +148,7 @@ export async function getMLJobIds( const mlJobs = response.jobs.filter( (job) => (job.custom_settings?.job_tags?.apm_ml_version ?? 0) >= 2 ); - if (environment && environment !== ENVIRONMENT_ALL) { + if (environment && environment !== ENVIRONMENT_ALL.value) { const matchingMLJob = mlJobs.find( (job) => job.custom_settings?.job_tags?.environment === environment ); diff --git a/x-pack/plugins/apm/server/lib/ui_filters/get_environments.ts b/x-pack/plugins/apm/server/lib/ui_filters/get_environments.ts index 98f00bf8e6555..7d3af4caa2ca3 100644 --- a/x-pack/plugins/apm/server/lib/ui_filters/get_environments.ts +++ b/x-pack/plugins/apm/server/lib/ui_filters/get_environments.ts @@ -47,7 +47,7 @@ export async function getEnvironments( environments: { terms: { field: SERVICE_ENVIRONMENT, - missing: ENVIRONMENT_NOT_DEFINED, + missing: ENVIRONMENT_NOT_DEFINED.value, }, }, }, diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 0b51c00475d7e..0852cdd430364 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -4774,7 +4774,6 @@ "xpack.apm.customLink.empty": "カスタムリンクが見つかりません。独自のカスタムリンク、たとえば特定のダッシュボードまたは外部リンクへのリンクをセットアップします。", "xpack.apm.emptyMessage.noDataFoundDescription": "別の時間範囲を試すか検索フィルターをリセットしてください。", "xpack.apm.emptyMessage.noDataFoundLabel": "データが見つかりません。", - "xpack.apm.environment.allLabel": "すべて", "xpack.apm.error.prompt.body": "詳細はブラウザの開発者コンソールをご確認ください。", "xpack.apm.error.prompt.title": "申し訳ございませんが、エラーが発生しました :(", "xpack.apm.errorGroupDetails.avgLabel": "平均", @@ -4812,6 +4811,7 @@ "xpack.apm.fetcher.error.title": "リソースの取得中にエラーが発生しました", "xpack.apm.fetcher.error.url": "URL", "xpack.apm.filter.environment.label": "環境", + "xpack.apm.filter.environment.allLabel": "すべて", "xpack.apm.filter.environment.notDefinedLabel": "未定義", "xpack.apm.filter.environment.selectEnvironmentLabel": "環境を選択", "xpack.apm.formatters.hoursTimeUnitLabel": "h", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index d520f63fe7484..3e58cad233d0c 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -4775,7 +4775,6 @@ "xpack.apm.customLink.empty": "未找到定制链接。设置自己的定制链接,如特定仪表板的链接或外部链接。", "xpack.apm.emptyMessage.noDataFoundDescription": "尝试其他时间范围或重置搜索筛选。", "xpack.apm.emptyMessage.noDataFoundLabel": "未找到任何数据", - "xpack.apm.environment.allLabel": "全部", "xpack.apm.error.prompt.body": "有关详情,请查看您的浏览器开发者控制台。", "xpack.apm.error.prompt.title": "抱歉,发生错误 :(", "xpack.apm.errorGroupDetails.avgLabel": "平均", @@ -4813,6 +4812,7 @@ "xpack.apm.fetcher.error.title": "提取资源时出错", "xpack.apm.fetcher.error.url": "URL", "xpack.apm.filter.environment.label": "环境", + "xpack.apm.filter.environment.allLabel": "全部", "xpack.apm.filter.environment.notDefinedLabel": "未定义", "xpack.apm.filter.environment.selectEnvironmentLabel": "选择环境", "xpack.apm.formatters.hoursTimeUnitLabel": "h", From d19427c67b2a33393c9ea370ebece4e159411a32 Mon Sep 17 00:00:00 2001 From: Oliver Gupte Date: Wed, 26 Aug 2020 16:50:56 -0700 Subject: [PATCH 6/9] - Reorg the alerts menu for different alert types (threshold/anomaly) - default environment alert settings to the selected filter --- .../AlertIntegrations/index.tsx | 76 ++++++++++--------- .../shared/ErrorRateAlertTrigger/index.tsx | 2 +- .../TransactionDurationAlertTrigger/index.tsx | 2 +- 3 files changed, 41 insertions(+), 39 deletions(-) diff --git a/x-pack/plugins/apm/public/components/app/ServiceDetails/AlertIntegrations/index.tsx b/x-pack/plugins/apm/public/components/app/ServiceDetails/AlertIntegrations/index.tsx index 2ac77a3810ecf..c106ea6e37a7e 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceDetails/AlertIntegrations/index.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceDetails/AlertIntegrations/index.tsx @@ -18,19 +18,28 @@ import { useApmPluginContext } from '../../../../hooks/useApmPluginContext'; const alertLabel = i18n.translate( 'xpack.apm.serviceDetails.alertsMenu.alerts', - { - defaultMessage: 'Alerts', - } + { defaultMessage: 'Alerts' } +); +const transactionDurationLabel = i18n.translate( + 'xpack.apm.serviceDetails.alertsMenu.transactionDuration', + { defaultMessage: 'Transaction duration' } +); +const errorRateLabel = i18n.translate( + 'xpack.apm.serviceDetails.alertsMenu.errorRate', + { defaultMessage: 'Error rate' } ); - const createThresholdAlertLabel = i18n.translate( 'xpack.apm.serviceDetails.alertsMenu.createThresholdAlert', - { - defaultMessage: 'Create threshold alert', - } + { defaultMessage: 'Create threshold alert' } +); +const createAnomalyAlertAlertLabel = i18n.translate( + 'xpack.apm.serviceDetails.alertsMenu.createAnomalyAlert', + { defaultMessage: 'Create anomaly alert' } ); -const CREATE_THRESHOLD_ALERT_PANEL_ID = 'create_threshold'; +const CREATE_TRANSACTION_DURATION_ALERT_PANEL_ID = + 'create_transaction_duration'; +const CREATE_ERROR_RATE_ALERT_PANEL_ID = 'create_error_rate'; interface Props { canReadAlerts: boolean; @@ -53,9 +62,7 @@ export function AlertIntegrations(props: Props) { iconSide="right" onClick={() => setPopoverOpen(true)} > - {i18n.translate('xpack.apm.serviceDetails.alertsMenu.alerts', { - defaultMessage: 'Alerts', - })} + {alertLabel} ); @@ -67,10 +74,10 @@ export function AlertIntegrations(props: Props) { ...(canSaveAlerts ? [ { - name: createThresholdAlertLabel, - panel: CREATE_THRESHOLD_ALERT_PANEL_ID, - icon: 'bell', + name: transactionDurationLabel, + panel: CREATE_TRANSACTION_DURATION_ALERT_PANEL_ID, }, + { name: errorRateLabel, panel: CREATE_ERROR_RATE_ALERT_PANEL_ID }, ] : []), ...(canReadAlerts @@ -78,9 +85,7 @@ export function AlertIntegrations(props: Props) { { name: i18n.translate( 'xpack.apm.serviceDetails.alertsMenu.viewActiveAlerts', - { - defaultMessage: 'View active alerts', - } + { defaultMessage: 'View active alerts' } ), href: plugin.core.http.basePath.prepend( '/app/management/insightsAndAlerting/triggersActions/alerts' @@ -92,44 +97,41 @@ export function AlertIntegrations(props: Props) { ], }, { - id: CREATE_THRESHOLD_ALERT_PANEL_ID, - title: createThresholdAlertLabel, + id: CREATE_TRANSACTION_DURATION_ALERT_PANEL_ID, + title: transactionDurationLabel, items: [ { - name: i18n.translate( - 'xpack.apm.serviceDetails.alertsMenu.transactionDuration', - { - defaultMessage: 'Transaction duration', - } - ), + icon: 'bell', + name: createThresholdAlertLabel, onClick: () => { setAlertType(AlertType.TransactionDuration); + setPopoverOpen(false); }, }, ...(canReadAnomalies ? [ { - name: i18n.translate( - 'xpack.apm.serviceDetails.alertsMenu.transactionDurationAnomaly', - { - defaultMessage: 'Transaction duration anomaly', - } - ), + icon: 'bell', + name: createAnomalyAlertAlertLabel, onClick: () => { setAlertType(AlertType.TransactionDurationAnomaly); + setPopoverOpen(false); }, }, ] : []), + ], + }, + { + id: CREATE_ERROR_RATE_ALERT_PANEL_ID, + title: errorRateLabel, + items: [ { - name: i18n.translate( - 'xpack.apm.serviceDetails.alertsMenu.errorRate', - { - defaultMessage: 'Error rate', - } - ), + icon: 'bell', + name: createThresholdAlertLabel, onClick: () => { setAlertType(AlertType.ErrorRate); + setPopoverOpen(false); }, }, ], diff --git a/x-pack/plugins/apm/public/components/shared/ErrorRateAlertTrigger/index.tsx b/x-pack/plugins/apm/public/components/shared/ErrorRateAlertTrigger/index.tsx index 4737233090347..7344839795955 100644 --- a/x-pack/plugins/apm/public/components/shared/ErrorRateAlertTrigger/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/ErrorRateAlertTrigger/index.tsx @@ -43,7 +43,7 @@ export function ErrorRateAlertTrigger(props: Props) { threshold: 25, windowSize: 1, windowUnit: 'm', - environment: ENVIRONMENT_ALL.value, + environment: urlParams.environment || ENVIRONMENT_ALL.value, }; const params = { diff --git a/x-pack/plugins/apm/public/components/shared/TransactionDurationAlertTrigger/index.tsx b/x-pack/plugins/apm/public/components/shared/TransactionDurationAlertTrigger/index.tsx index def385ed357db..ba12b11c9527d 100644 --- a/x-pack/plugins/apm/public/components/shared/TransactionDurationAlertTrigger/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/TransactionDurationAlertTrigger/index.tsx @@ -58,7 +58,7 @@ export function TransactionDurationAlertTrigger(props: Props) { windowSize: 5, windowUnit: 'm', transactionType: transactionTypes[0], - environment: ENVIRONMENT_ALL.value, + environment: urlParams.environment || ENVIRONMENT_ALL.value, }; const params = { From b1f3b255b5a6aa7e49e024fd72f69daaa4a06bd8 Mon Sep 17 00:00:00 2001 From: Oliver Gupte Date: Wed, 26 Aug 2020 17:11:09 -0700 Subject: [PATCH 7/9] - Filters default transaction type to only those supported in the APM anomaly detection jobs - Removes Service name and transaction type from the set of expressions in the alerting setup --- .../index.tsx | 47 ++++--------------- 1 file changed, 10 insertions(+), 37 deletions(-) diff --git a/x-pack/plugins/apm/public/components/shared/TransactionDurationAnomalyAlertTrigger/index.tsx b/x-pack/plugins/apm/public/components/shared/TransactionDurationAnomalyAlertTrigger/index.tsx index bb5af0620576e..d4731d7ea2fd3 100644 --- a/x-pack/plugins/apm/public/components/shared/TransactionDurationAnomalyAlertTrigger/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/TransactionDurationAnomalyAlertTrigger/index.tsx @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { EuiText, EuiSelect, EuiExpression } from '@elastic/eui'; +import { EuiSelect } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; import { ALERT_TYPES_CONFIG } from '../../../../common/alert_types'; @@ -20,6 +20,10 @@ import { ENVIRONMENT_ALL, getEnvironmentLabel, } from '../../../../common/environment_filter_values'; +import { + TRANSACTION_PAGE_LOAD, + TRANSACTION_REQUEST, +} from '../../../../common/transaction_types'; interface Params { windowSize: number; @@ -42,15 +46,18 @@ export function TransactionDurationAnomalyAlertTrigger(props: Props) { const transactionTypes = useServiceTransactionTypes(urlParams); const { serviceName, start, end } = urlParams; const { environmentOptions } = useEnvironments({ serviceName, start, end }); + const supportedTransactionTypes = transactionTypes.filter((transactionType) => + [TRANSACTION_PAGE_LOAD, TRANSACTION_REQUEST].includes(transactionType) + ); - if (!transactionTypes.length || !serviceName) { + if (!supportedTransactionTypes.length || !serviceName) { return null; } const defaults: Params = { windowSize: 15, windowUnit: 'm', - transactionType: transactionTypes[0], + transactionType: supportedTransactionTypes[0], // 'page-load' for RUM, 'request' otherwise serviceName, environment: urlParams.environment || ENVIRONMENT_ALL.value, anomalyScore: 75, @@ -62,19 +69,6 @@ export function TransactionDurationAnomalyAlertTrigger(props: Props) { }; const fields = [ - -

{serviceName}
-
- } - />, , - - { - return { - text: key, - value: key, - }; - })} - onChange={(e) => setAlertParams('transactionType', e.target.value)} - compressed - /> - , } title={i18n.translate( From 7ec51559fc09f572707979fd23b39ba42c231f74 Mon Sep 17 00:00:00 2001 From: Oliver Gupte Date: Thu, 27 Aug 2020 01:13:01 -0700 Subject: [PATCH 8/9] - remove bell icon from alerts menu --- .../components/app/ServiceDetails/AlertIntegrations/index.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/x-pack/plugins/apm/public/components/app/ServiceDetails/AlertIntegrations/index.tsx b/x-pack/plugins/apm/public/components/app/ServiceDetails/AlertIntegrations/index.tsx index c106ea6e37a7e..27c4a37e09c00 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceDetails/AlertIntegrations/index.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceDetails/AlertIntegrations/index.tsx @@ -101,7 +101,6 @@ export function AlertIntegrations(props: Props) { title: transactionDurationLabel, items: [ { - icon: 'bell', name: createThresholdAlertLabel, onClick: () => { setAlertType(AlertType.TransactionDuration); @@ -111,7 +110,6 @@ export function AlertIntegrations(props: Props) { ...(canReadAnomalies ? [ { - icon: 'bell', name: createAnomalyAlertAlertLabel, onClick: () => { setAlertType(AlertType.TransactionDurationAnomaly); @@ -127,7 +125,6 @@ export function AlertIntegrations(props: Props) { title: errorRateLabel, items: [ { - icon: 'bell', name: createThresholdAlertLabel, onClick: () => { setAlertType(AlertType.ErrorRate); From 9d3a35a8bb3fb62d61bc23f125350c76b37841c0 Mon Sep 17 00:00:00 2001 From: Oliver Gupte Date: Thu, 27 Aug 2020 14:33:14 -0700 Subject: [PATCH 9/9] Adds target service back into the anomaly alert setup as a ready-only expression --- .../TransactionDurationAnomalyAlertTrigger/index.tsx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/apm/public/components/shared/TransactionDurationAnomalyAlertTrigger/index.tsx b/x-pack/plugins/apm/public/components/shared/TransactionDurationAnomalyAlertTrigger/index.tsx index d4731d7ea2fd3..911c51013a844 100644 --- a/x-pack/plugins/apm/public/components/shared/TransactionDurationAnomalyAlertTrigger/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/TransactionDurationAnomalyAlertTrigger/index.tsx @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { EuiSelect } from '@elastic/eui'; +import { EuiExpression, EuiSelect } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; import { ALERT_TYPES_CONFIG } from '../../../../common/alert_types'; @@ -69,6 +69,15 @@ export function TransactionDurationAnomalyAlertTrigger(props: Props) { }; const fields = [ + ,