From 73d0273c854909f0a359860d6b4eafb9520fdf69 Mon Sep 17 00:00:00 2001 From: Zacqary Adam Xeper Date: Tue, 30 Jun 2020 16:34:38 -0500 Subject: [PATCH] [Metrics UI] Add context.reason and alertOnNoData to Inventory alerts (#70260) --- .../metrics_and_groupby_toolbar_items.tsx | 2 +- .../infra/common/snapshot_metric_i18n.ts | 208 ++++++++++++++++++ .../inventory/components/expression.tsx | 28 ++- .../infra/public/alerting/inventory/index.ts | 6 +- .../components/toolbars/toolbar_wrapper.tsx | 203 ----------------- .../{metric_threshold => common}/messages.ts | 0 .../infra/server/lib/alerting/common/types.ts | 33 +++ .../evaluate_condition.ts | 17 +- .../inventory_metric_threshold_executor.ts | 71 ++++-- ...r_inventory_metric_threshold_alert_type.ts | 1 + .../inventory_metric_threshold/types.ts | 19 +- .../metric_threshold/lib/evaluate_alert.ts | 2 +- .../metric_threshold_executor.ts | 6 +- .../lib/alerting/metric_threshold/types.ts | 20 +- 14 files changed, 352 insertions(+), 264 deletions(-) create mode 100644 x-pack/plugins/infra/common/snapshot_metric_i18n.ts rename x-pack/plugins/infra/server/lib/alerting/{metric_threshold => common}/messages.ts (100%) create mode 100644 x-pack/plugins/infra/server/lib/alerting/common/types.ts diff --git a/x-pack/plugins/infra/common/inventory_models/shared/components/metrics_and_groupby_toolbar_items.tsx b/x-pack/plugins/infra/common/inventory_models/shared/components/metrics_and_groupby_toolbar_items.tsx index 9ddf422871d18c..a66421fe2fd0ee 100644 --- a/x-pack/plugins/infra/common/inventory_models/shared/components/metrics_and_groupby_toolbar_items.tsx +++ b/x-pack/plugins/infra/common/inventory_models/shared/components/metrics_and_groupby_toolbar_items.tsx @@ -6,6 +6,7 @@ import React, { useMemo } from 'react'; import { EuiFlexItem } from '@elastic/eui'; +import { toMetricOpt } from '../../../snapshot_metric_i18n'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { WaffleSortControls } from '../../../../public/pages/metrics/inventory_view/components/waffle/waffle_sort_controls'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths @@ -16,7 +17,6 @@ import { WaffleMetricControls } from '../../../../public/pages/metrics/inventory import { WaffleGroupByControls } from '../../../../public/pages/metrics/inventory_view/components/waffle/waffle_group_by_controls'; import { toGroupByOpt, - toMetricOpt, // eslint-disable-next-line @kbn/eslint/no-restricted-paths } from '../../../../public/pages/metrics/inventory_view/components/toolbars/toolbar_wrapper'; import { SnapshotMetricType } from '../../types'; diff --git a/x-pack/plugins/infra/common/snapshot_metric_i18n.ts b/x-pack/plugins/infra/common/snapshot_metric_i18n.ts new file mode 100644 index 00000000000000..412c60fd9a1a7a --- /dev/null +++ b/x-pack/plugins/infra/common/snapshot_metric_i18n.ts @@ -0,0 +1,208 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import { SnapshotMetricType } from './inventory_models/types'; + +const Translations = { + CPUUsage: i18n.translate('xpack.infra.waffle.metricOptions.cpuUsageText', { + defaultMessage: 'CPU usage', + }), + + MemoryUsage: i18n.translate('xpack.infra.waffle.metricOptions.memoryUsageText', { + defaultMessage: 'Memory usage', + }), + + InboundTraffic: i18n.translate('xpack.infra.waffle.metricOptions.inboundTrafficText', { + defaultMessage: 'Inbound traffic', + }), + + OutboundTraffic: i18n.translate('xpack.infra.waffle.metricOptions.outboundTrafficText', { + defaultMessage: 'Outbound traffic', + }), + + LogRate: i18n.translate('xpack.infra.waffle.metricOptions.hostLogRateText', { + defaultMessage: 'Log rate', + }), + + Load: i18n.translate('xpack.infra.waffle.metricOptions.loadText', { + defaultMessage: 'Load', + }), + + Count: i18n.translate('xpack.infra.waffle.metricOptions.countText', { + defaultMessage: 'Count', + }), + DiskIOReadBytes: i18n.translate('xpack.infra.waffle.metricOptions.diskIOReadBytes', { + defaultMessage: 'Disk Reads', + }), + DiskIOWriteBytes: i18n.translate('xpack.infra.waffle.metricOptions.diskIOWriteBytes', { + defaultMessage: 'Disk Writes', + }), + s3BucketSize: i18n.translate('xpack.infra.waffle.metricOptions.s3BucketSize', { + defaultMessage: 'Bucket Size', + }), + s3TotalRequests: i18n.translate('xpack.infra.waffle.metricOptions.s3TotalRequests', { + defaultMessage: 'Total Requests', + }), + s3NumberOfObjects: i18n.translate('xpack.infra.waffle.metricOptions.s3NumberOfObjects', { + defaultMessage: 'Number of Objects', + }), + s3DownloadBytes: i18n.translate('xpack.infra.waffle.metricOptions.s3DownloadBytes', { + defaultMessage: 'Downloads (Bytes)', + }), + s3UploadBytes: i18n.translate('xpack.infra.waffle.metricOptions.s3UploadBytes', { + defaultMessage: 'Uploads (Bytes)', + }), + rdsConnections: i18n.translate('xpack.infra.waffle.metricOptions.rdsConnections', { + defaultMessage: 'Connections', + }), + rdsQueriesExecuted: i18n.translate('xpack.infra.waffle.metricOptions.rdsQueriesExecuted', { + defaultMessage: 'Queries Executed', + }), + rdsActiveTransactions: i18n.translate('xpack.infra.waffle.metricOptions.rdsActiveTransactions', { + defaultMessage: 'Active Transactions', + }), + rdsLatency: i18n.translate('xpack.infra.waffle.metricOptions.rdsLatency', { + defaultMessage: 'Latency', + }), + sqsMessagesVisible: i18n.translate('xpack.infra.waffle.metricOptions.sqsMessagesVisible', { + defaultMessage: 'Messages Available', + }), + sqsMessagesDelayed: i18n.translate('xpack.infra.waffle.metricOptions.sqsMessagesDelayed', { + defaultMessage: 'Messages Delayed', + }), + sqsMessagesSent: i18n.translate('xpack.infra.waffle.metricOptions.sqsMessagesSent', { + defaultMessage: 'Messages Added', + }), + sqsMessagesEmpty: i18n.translate('xpack.infra.waffle.metricOptions.sqsMessagesEmpty', { + defaultMessage: 'Messages Returned Empty', + }), + sqsOldestMessage: i18n.translate('xpack.infra.waffle.metricOptions.sqsOldestMessage', { + defaultMessage: 'Oldest Message', + }), +}; + +export const toMetricOpt = ( + metric: SnapshotMetricType +): { text: string; value: SnapshotMetricType } | undefined => { + switch (metric) { + case 'cpu': + return { + text: Translations.CPUUsage, + value: 'cpu', + }; + case 'memory': + return { + text: Translations.MemoryUsage, + value: 'memory', + }; + case 'rx': + return { + text: Translations.InboundTraffic, + value: 'rx', + }; + case 'tx': + return { + text: Translations.OutboundTraffic, + value: 'tx', + }; + case 'logRate': + return { + text: Translations.LogRate, + value: 'logRate', + }; + case 'load': + return { + text: Translations.Load, + value: 'load', + }; + + case 'count': + return { + text: Translations.Count, + value: 'count', + }; + case 'diskIOReadBytes': + return { + text: Translations.DiskIOReadBytes, + value: 'diskIOReadBytes', + }; + case 'diskIOWriteBytes': + return { + text: Translations.DiskIOWriteBytes, + value: 'diskIOWriteBytes', + }; + case 's3BucketSize': + return { + text: Translations.s3BucketSize, + value: 's3BucketSize', + }; + case 's3TotalRequests': + return { + text: Translations.s3TotalRequests, + value: 's3TotalRequests', + }; + case 's3NumberOfObjects': + return { + text: Translations.s3NumberOfObjects, + value: 's3NumberOfObjects', + }; + case 's3DownloadBytes': + return { + text: Translations.s3DownloadBytes, + value: 's3DownloadBytes', + }; + case 's3UploadBytes': + return { + text: Translations.s3UploadBytes, + value: 's3UploadBytes', + }; + case 'rdsConnections': + return { + text: Translations.rdsConnections, + value: 'rdsConnections', + }; + case 'rdsQueriesExecuted': + return { + text: Translations.rdsQueriesExecuted, + value: 'rdsQueriesExecuted', + }; + case 'rdsActiveTransactions': + return { + text: Translations.rdsActiveTransactions, + value: 'rdsActiveTransactions', + }; + case 'rdsLatency': + return { + text: Translations.rdsLatency, + value: 'rdsLatency', + }; + case 'sqsMessagesVisible': + return { + text: Translations.sqsMessagesVisible, + value: 'sqsMessagesVisible', + }; + case 'sqsMessagesDelayed': + return { + text: Translations.sqsMessagesDelayed, + value: 'sqsMessagesDelayed', + }; + case 'sqsMessagesSent': + return { + text: Translations.sqsMessagesSent, + value: 'sqsMessagesSent', + }; + case 'sqsMessagesEmpty': + return { + text: Translations.sqsMessagesEmpty, + value: 'sqsMessagesEmpty', + }; + case 'sqsOldestMessage': + return { + text: Translations.sqsOldestMessage, + value: 'sqsOldestMessage', + }; + } +}; diff --git a/x-pack/plugins/infra/public/alerting/inventory/components/expression.tsx b/x-pack/plugins/infra/public/alerting/inventory/components/expression.tsx index 7df52ad56aef6a..d0b4045949d3e1 100644 --- a/x-pack/plugins/infra/public/alerting/inventory/components/expression.tsx +++ b/x-pack/plugins/infra/public/alerting/inventory/components/expression.tsx @@ -16,9 +16,13 @@ import { EuiFormRow, EuiButtonEmpty, EuiFieldSearch, + EuiCheckbox, + EuiToolTip, + EuiIcon, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; +import { toMetricOpt } from '../../../../common/snapshot_metric_i18n'; import { AlertPreview } from '../../common'; import { METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID } from '../../../../common/alerting/metrics'; import { @@ -38,7 +42,6 @@ import { AlertsContextValue } from '../../../../../triggers_actions_ui/public/ap // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { MetricsExplorerKueryBar } from '../../../pages/metrics/metrics_explorer/components/kuery_bar'; import { useSourceViaHttp } from '../../../containers/source/use_source_via_http'; -import { toMetricOpt } from '../../../pages/metrics/inventory_view/components/toolbars/toolbar_wrapper'; import { sqsMetricTypes } from '../../../../common/inventory_models/aws_sqs/toolbar_items'; import { ec2MetricTypes } from '../../../../common/inventory_models/aws_ec2/toolbar_items'; import { s3MetricTypes } from '../../../../common/inventory_models/aws_s3/toolbar_items'; @@ -73,6 +76,7 @@ interface Props { filterQuery?: string; filterQueryText?: string; sourceId?: string; + alertOnNoData?: boolean; }; alertInterval: string; alertsContext: AlertsContextValue; @@ -306,6 +310,28 @@ export const Expressions: React.FC = (props) => { + + + {i18n.translate('xpack.infra.metrics.alertFlyout.alertOnNoData', { + defaultMessage: "Alert me if there's no data", + })}{' '} + + + + + } + checked={alertParams.alertOnNoData} + onChange={(e) => setAlertParams('alertOnNoData', e.target.checked)} + /> + { ); }; -const ToolbarTranslations = { - CPUUsage: i18n.translate('xpack.infra.waffle.metricOptions.cpuUsageText', { - defaultMessage: 'CPU usage', - }), - - MemoryUsage: i18n.translate('xpack.infra.waffle.metricOptions.memoryUsageText', { - defaultMessage: 'Memory usage', - }), - - InboundTraffic: i18n.translate('xpack.infra.waffle.metricOptions.inboundTrafficText', { - defaultMessage: 'Inbound traffic', - }), - - OutboundTraffic: i18n.translate('xpack.infra.waffle.metricOptions.outboundTrafficText', { - defaultMessage: 'Outbound traffic', - }), - - LogRate: i18n.translate('xpack.infra.waffle.metricOptions.hostLogRateText', { - defaultMessage: 'Log rate', - }), - - Load: i18n.translate('xpack.infra.waffle.metricOptions.loadText', { - defaultMessage: 'Load', - }), - - Count: i18n.translate('xpack.infra.waffle.metricOptions.countText', { - defaultMessage: 'Count', - }), - DiskIOReadBytes: i18n.translate('xpack.infra.waffle.metricOptions.diskIOReadBytes', { - defaultMessage: 'Disk Reads', - }), - DiskIOWriteBytes: i18n.translate('xpack.infra.waffle.metricOptions.diskIOWriteBytes', { - defaultMessage: 'Disk Writes', - }), - s3BucketSize: i18n.translate('xpack.infra.waffle.metricOptions.s3BucketSize', { - defaultMessage: 'Bucket Size', - }), - s3TotalRequests: i18n.translate('xpack.infra.waffle.metricOptions.s3TotalRequests', { - defaultMessage: 'Total Requests', - }), - s3NumberOfObjects: i18n.translate('xpack.infra.waffle.metricOptions.s3NumberOfObjects', { - defaultMessage: 'Number of Objects', - }), - s3DownloadBytes: i18n.translate('xpack.infra.waffle.metricOptions.s3DownloadBytes', { - defaultMessage: 'Downloads (Bytes)', - }), - s3UploadBytes: i18n.translate('xpack.infra.waffle.metricOptions.s3UploadBytes', { - defaultMessage: 'Uploads (Bytes)', - }), - rdsConnections: i18n.translate('xpack.infra.waffle.metricOptions.rdsConnections', { - defaultMessage: 'Connections', - }), - rdsQueriesExecuted: i18n.translate('xpack.infra.waffle.metricOptions.rdsQueriesExecuted', { - defaultMessage: 'Queries Executed', - }), - rdsActiveTransactions: i18n.translate('xpack.infra.waffle.metricOptions.rdsActiveTransactions', { - defaultMessage: 'Active Transactions', - }), - rdsLatency: i18n.translate('xpack.infra.waffle.metricOptions.rdsLatency', { - defaultMessage: 'Latency', - }), - sqsMessagesVisible: i18n.translate('xpack.infra.waffle.metricOptions.sqsMessagesVisible', { - defaultMessage: 'Messages Available', - }), - sqsMessagesDelayed: i18n.translate('xpack.infra.waffle.metricOptions.sqsMessagesDelayed', { - defaultMessage: 'Messages Delayed', - }), - sqsMessagesSent: i18n.translate('xpack.infra.waffle.metricOptions.sqsMessagesSent', { - defaultMessage: 'Messages Added', - }), - sqsMessagesEmpty: i18n.translate('xpack.infra.waffle.metricOptions.sqsMessagesEmpty', { - defaultMessage: 'Messages Returned Empty', - }), - sqsOldestMessage: i18n.translate('xpack.infra.waffle.metricOptions.sqsOldestMessage', { - defaultMessage: 'Oldest Message', - }), -}; - export const toGroupByOpt = (field: string) => ({ text: fieldToName(field), field, }); - -export const toMetricOpt = ( - metric: SnapshotMetricType -): { text: string; value: SnapshotMetricType } | undefined => { - switch (metric) { - case 'cpu': - return { - text: ToolbarTranslations.CPUUsage, - value: 'cpu', - }; - case 'memory': - return { - text: ToolbarTranslations.MemoryUsage, - value: 'memory', - }; - case 'rx': - return { - text: ToolbarTranslations.InboundTraffic, - value: 'rx', - }; - case 'tx': - return { - text: ToolbarTranslations.OutboundTraffic, - value: 'tx', - }; - case 'logRate': - return { - text: ToolbarTranslations.LogRate, - value: 'logRate', - }; - case 'load': - return { - text: ToolbarTranslations.Load, - value: 'load', - }; - - case 'count': - return { - text: ToolbarTranslations.Count, - value: 'count', - }; - case 'diskIOReadBytes': - return { - text: ToolbarTranslations.DiskIOReadBytes, - value: 'diskIOReadBytes', - }; - case 'diskIOWriteBytes': - return { - text: ToolbarTranslations.DiskIOWriteBytes, - value: 'diskIOWriteBytes', - }; - case 's3BucketSize': - return { - text: ToolbarTranslations.s3BucketSize, - value: 's3BucketSize', - }; - case 's3TotalRequests': - return { - text: ToolbarTranslations.s3TotalRequests, - value: 's3TotalRequests', - }; - case 's3NumberOfObjects': - return { - text: ToolbarTranslations.s3NumberOfObjects, - value: 's3NumberOfObjects', - }; - case 's3DownloadBytes': - return { - text: ToolbarTranslations.s3DownloadBytes, - value: 's3DownloadBytes', - }; - case 's3UploadBytes': - return { - text: ToolbarTranslations.s3UploadBytes, - value: 's3UploadBytes', - }; - case 'rdsConnections': - return { - text: ToolbarTranslations.rdsConnections, - value: 'rdsConnections', - }; - case 'rdsQueriesExecuted': - return { - text: ToolbarTranslations.rdsQueriesExecuted, - value: 'rdsQueriesExecuted', - }; - case 'rdsActiveTransactions': - return { - text: ToolbarTranslations.rdsActiveTransactions, - value: 'rdsActiveTransactions', - }; - case 'rdsLatency': - return { - text: ToolbarTranslations.rdsLatency, - value: 'rdsLatency', - }; - case 'sqsMessagesVisible': - return { - text: ToolbarTranslations.sqsMessagesVisible, - value: 'sqsMessagesVisible', - }; - case 'sqsMessagesDelayed': - return { - text: ToolbarTranslations.sqsMessagesDelayed, - value: 'sqsMessagesDelayed', - }; - case 'sqsMessagesSent': - return { - text: ToolbarTranslations.sqsMessagesSent, - value: 'sqsMessagesSent', - }; - case 'sqsMessagesEmpty': - return { - text: ToolbarTranslations.sqsMessagesEmpty, - value: 'sqsMessagesEmpty', - }; - case 'sqsOldestMessage': - return { - text: ToolbarTranslations.sqsOldestMessage, - value: 'sqsOldestMessage', - }; - } -}; diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/messages.ts b/x-pack/plugins/infra/server/lib/alerting/common/messages.ts similarity index 100% rename from x-pack/plugins/infra/server/lib/alerting/metric_threshold/messages.ts rename to x-pack/plugins/infra/server/lib/alerting/common/messages.ts diff --git a/x-pack/plugins/infra/server/lib/alerting/common/types.ts b/x-pack/plugins/infra/server/lib/alerting/common/types.ts new file mode 100644 index 00000000000000..8bb087781dc7a8 --- /dev/null +++ b/x-pack/plugins/infra/server/lib/alerting/common/types.ts @@ -0,0 +1,33 @@ +/* + * 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. + */ + +export enum Comparator { + GT = '>', + LT = '<', + GT_OR_EQ = '>=', + LT_OR_EQ = '<=', + BETWEEN = 'between', + OUTSIDE_RANGE = 'outside', +} + +export enum Aggregators { + COUNT = 'count', + AVERAGE = 'avg', + SUM = 'sum', + MIN = 'min', + MAX = 'max', + RATE = 'rate', + CARDINALITY = 'cardinality', + P95 = 'p95', + P99 = 'p99', +} + +export enum AlertStates { + OK, + ALERT, + NO_DATA, + ERROR, +} diff --git a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/evaluate_condition.ts b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/evaluate_condition.ts index c55f50e229b698..ddc51d19021e74 100644 --- a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/evaluate_condition.ts +++ b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/evaluate_condition.ts @@ -17,13 +17,12 @@ import { InventoryItemType, SnapshotMetricType } from '../../../../common/invent import { InfraTimerangeInput } from '../../../../common/http_api/snapshot_api'; import { InfraSourceConfiguration } from '../../sources'; -interface ConditionResult { +type ConditionResult = InventoryMetricConditions & { shouldFire: boolean | boolean[]; - currentValue?: number | null; - metric: string; + currentValue: number; isNoData: boolean; isError: boolean; -} +}; export const evaluateCondition = async ( condition: InventoryMetricConditions, @@ -59,19 +58,25 @@ export const evaluateCondition = async ( const comparisonFunction = comparatorMap[comparator]; return mapValues(currentValues, (value) => ({ + ...condition, shouldFire: value !== undefined && value !== null && (Array.isArray(value) ? value.map((v) => comparisonFunction(Number(v), threshold)) : comparisonFunction(value, threshold)), - metric, isNoData: value === null, isError: value === undefined, - ...(!Array.isArray(value) ? { currentValue: value } : {}), + currentValue: getCurrentValue(value), })); }; +const getCurrentValue: (value: any) => number = (value) => { + if (Array.isArray(value)) return getCurrentValue(last(value)); + if (value !== null) return Number(value); + return NaN; +}; + const getData = async ( callCluster: AlertServices['callCluster'], nodeType: InventoryItemType, diff --git a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts index 9029b51568212b..445911878111f1 100644 --- a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts +++ b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts @@ -5,12 +5,20 @@ */ import { first, get } from 'lodash'; import { i18n } from '@kbn/i18n'; +import moment from 'moment'; +import { toMetricOpt } from '../../../../common/snapshot_metric_i18n'; import { AlertStates, InventoryMetricConditions } from './types'; import { AlertExecutorOptions } from '../../../../../alerts/server'; import { InventoryItemType, SnapshotMetricType } from '../../../../common/inventory_models/types'; import { InfraBackendLibs } from '../../infra_types'; import { METRIC_FORMATTERS } from '../../../../common/formatters/snapshot_metric_formats'; import { createFormatter } from '../../../../common/formatters'; +import { + buildErrorAlertReason, + buildFiredAlertReason, + buildNoDataAlertReason, + stateToAlertMessage, +} from '../common/messages'; import { evaluateCondition } from './evaluate_condition'; interface InventoryMetricThresholdParams { @@ -18,13 +26,20 @@ interface InventoryMetricThresholdParams { filterQuery: string | undefined; nodeType: InventoryItemType; sourceId?: string; + alertOnNoData?: boolean; } export const createInventoryMetricThresholdExecutor = ( libs: InfraBackendLibs, alertId: string ) => async ({ services, params }: AlertExecutorOptions) => { - const { criteria, filterQuery, sourceId, nodeType } = params as InventoryMetricThresholdParams; + const { + criteria, + filterQuery, + sourceId, + nodeType, + alertOnNoData, + } = params as InventoryMetricThresholdParams; const source = await libs.sources.getSourceConfiguration( services.savedObjectsClient, @@ -48,26 +63,56 @@ export const createInventoryMetricThresholdExecutor = ( const isNoData = results.some((result) => result[item].isNoData); const isError = results.some((result) => result[item].isError); - if (shouldAlertFire) { + const nextState = isError + ? AlertStates.ERROR + : isNoData + ? AlertStates.NO_DATA + : shouldAlertFire + ? AlertStates.ALERT + : AlertStates.OK; + + let reason; + if (nextState === AlertStates.ALERT) { + reason = results + .map((result) => { + if (!result[item]) return ''; + const resultWithVerboseMetricName = { + ...result[item], + metric: toMetricOpt(result[item].metric)?.text || result[item].metric, + }; + return buildFiredAlertReason(resultWithVerboseMetricName); + }) + .join('\n'); + } + if (alertOnNoData) { + if (nextState === AlertStates.NO_DATA) { + reason = results + .filter((result) => result[item].isNoData) + .map((result) => buildNoDataAlertReason(result[item])) + .join('\n'); + } else if (nextState === AlertStates.ERROR) { + reason = results + .filter((result) => result[item].isError) + .map((result) => buildErrorAlertReason(result[item].metric)) + .join('\n'); + } + } + if (reason) { alertInstance.scheduleActions(FIRED_ACTIONS.id, { group: item, - item, - valueOf: mapToConditionsLookup(results, (result) => + alertState: stateToAlertMessage[nextState], + reason, + timestamp: moment().toISOString(), + value: mapToConditionsLookup(results, (result) => formatMetric(result[item].metric, result[item].currentValue) ), - thresholdOf: mapToConditionsLookup(criteria, (c) => c.threshold), - metricOf: mapToConditionsLookup(criteria, (c) => c.metric), + threshold: mapToConditionsLookup(criteria, (c) => c.threshold), + metric: mapToConditionsLookup(criteria, (c) => c.metric), }); } alertInstance.replaceState({ - alertState: isError - ? AlertStates.ERROR - : isNoData - ? AlertStates.NO_DATA - : shouldAlertFire - ? AlertStates.ALERT - : AlertStates.OK, + alertState: nextState, }); } }; diff --git a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/register_inventory_metric_threshold_alert_type.ts b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/register_inventory_metric_threshold_alert_type.ts index e23dfed448c578..d7c4165d5a870d 100644 --- a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/register_inventory_metric_threshold_alert_type.ts +++ b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/register_inventory_metric_threshold_alert_type.ts @@ -35,6 +35,7 @@ export const registerMetricInventoryThresholdAlertType = (libs: InfraBackendLibs schema.string({ validate: validateIsStringElasticsearchJSONFilter }) ), sourceId: schema.string(), + alertOnNoData: schema.maybe(schema.boolean()), }, { unknowns: 'allow' } ), diff --git a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/types.ts b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/types.ts index ec1caad30a4d73..86c77e6d7459ac 100644 --- a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/types.ts +++ b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/types.ts @@ -5,24 +5,11 @@ */ import { Unit } from '@elastic/datemath'; import { SnapshotMetricType } from '../../../../common/inventory_models/types'; +import { Comparator, AlertStates } from '../common/types'; -export const METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID = 'metrics.alert.inventory.threshold'; - -export enum Comparator { - GT = '>', - LT = '<', - GT_OR_EQ = '>=', - LT_OR_EQ = '<=', - BETWEEN = 'between', - OUTSIDE_RANGE = 'outside', -} +export { Comparator, AlertStates }; -export enum AlertStates { - OK, - ALERT, - NO_DATA, - ERROR, -} +export const METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID = 'metrics.alert.inventory.threshold'; export interface InventoryMetricConditions { metric: SnapshotMetricType; diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/evaluate_alert.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/evaluate_alert.ts index 49b191c4e85c95..66318f3da01c6b 100644 --- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/evaluate_alert.ts +++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/evaluate_alert.ts @@ -15,8 +15,8 @@ import { InfraDatabaseSearchResponse } from '../../../adapters/framework/adapter import { createAfterKeyHandler } from '../../../../utils/create_afterkey_handler'; import { AlertServices, AlertExecutorOptions } from '../../../../../../alerts/server'; import { getAllCompositeData } from '../../../../utils/get_all_composite_data'; +import { DOCUMENT_COUNT_I18N } from '../../common/messages'; import { MetricExpressionParams, Comparator, Aggregators } from '../types'; -import { DOCUMENT_COUNT_I18N } from '../messages'; import { getElasticsearchMetricQuery } from './metric_query'; interface Aggregation { diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts index d80347595a3cad..5782277e4f4699 100644 --- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts +++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts @@ -8,14 +8,14 @@ import { i18n } from '@kbn/i18n'; import moment from 'moment'; import { AlertExecutorOptions } from '../../../../../alerts/server'; import { InfraBackendLibs } from '../../infra_types'; -import { AlertStates } from './types'; -import { evaluateAlert } from './lib/evaluate_alert'; import { buildErrorAlertReason, buildFiredAlertReason, buildNoDataAlertReason, stateToAlertMessage, -} from './messages'; +} from '../common/messages'; +import { AlertStates } from './types'; +import { evaluateAlert } from './lib/evaluate_alert'; export const createMetricThresholdExecutor = (libs: InfraBackendLibs, alertId: string) => async function (options: AlertExecutorOptions) { diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/types.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/types.ts index 48391925d99104..62ab94b7d8c837 100644 --- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/types.ts +++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/types.ts @@ -3,18 +3,11 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - import { Unit } from '@elastic/datemath'; -export const METRIC_THRESHOLD_ALERT_TYPE_ID = 'metrics.alert.threshold'; +import { Comparator, AlertStates } from '../common/types'; -export enum Comparator { - GT = '>', - LT = '<', - GT_OR_EQ = '>=', - LT_OR_EQ = '<=', - BETWEEN = 'between', - OUTSIDE_RANGE = 'outside', -} +export { Comparator, AlertStates }; +export const METRIC_THRESHOLD_ALERT_TYPE_ID = 'metrics.alert.threshold'; export enum Aggregators { COUNT = 'count', @@ -28,13 +21,6 @@ export enum Aggregators { P99 = 'p99', } -export enum AlertStates { - OK, - ALERT, - NO_DATA, - ERROR, -} - interface BaseMetricExpressionParams { timeSize: number; timeUnit: Unit;