From 9969d88e962b8ceb858c0a1cbe1cca038812cee7 Mon Sep 17 00:00:00 2001 From: mgiota Date: Thu, 15 Jul 2021 00:18:01 +0200 Subject: [PATCH 01/16] WIP: register inventory metric threshold as lifecycle rule --- x-pack/plugins/alerting/server/plugin.ts | 5 + .../infra/common/alerting/metrics/index.ts | 1 + .../common/alerting/metrics/rule_data.ts | 25 ++ .../infra/common/alerting/metrics/types.ts | 2 +- .../infra/public/alerting/inventory/index.ts | 9 +- .../inventory/rule_data_formatters.ts | 72 +++++ x-pack/plugins/infra/public/plugin.ts | 7 +- .../inventory_metric_threshold_executor.ts | 305 ++++++++++-------- ...r_inventory_metric_threshold_alert_type.ts | 102 +++--- .../lib/alerting/register_alert_types.ts | 3 +- 10 files changed, 337 insertions(+), 194 deletions(-) create mode 100644 x-pack/plugins/infra/common/alerting/metrics/rule_data.ts create mode 100644 x-pack/plugins/infra/public/alerting/inventory/rule_data_formatters.ts diff --git a/x-pack/plugins/alerting/server/plugin.ts b/x-pack/plugins/alerting/server/plugin.ts index b906983017ff6..17658e1ffb249 100644 --- a/x-pack/plugins/alerting/server/plugin.ts +++ b/x-pack/plugins/alerting/server/plugin.ts @@ -84,6 +84,11 @@ export const LEGACY_EVENT_LOG_ACTIONS = { resolvedInstance: 'resolved-instance', }; +// export interface InventoryMetricPluginSetupContract +// extends Omit { +// ActionGroupIds: InventoryMetricThresholdAllowedActionGroups; +// } + export interface PluginSetupContract { registerType< Params extends AlertTypeParams = AlertTypeParams, diff --git a/x-pack/plugins/infra/common/alerting/metrics/index.ts b/x-pack/plugins/infra/common/alerting/metrics/index.ts index 2c66638711cd0..2c968b6f41bfa 100644 --- a/x-pack/plugins/infra/common/alerting/metrics/index.ts +++ b/x-pack/plugins/infra/common/alerting/metrics/index.ts @@ -6,6 +6,7 @@ */ export * from './types'; +export * from './rule_data'; export const INFRA_ALERT_PREVIEW_PATH = '/api/infra/alerting/preview'; export const TOO_MANY_BUCKETS_PREVIEW_EXCEPTION = 'TOO_MANY_BUCKETS_PREVIEW_EXCEPTION'; diff --git a/x-pack/plugins/infra/common/alerting/metrics/rule_data.ts b/x-pack/plugins/infra/common/alerting/metrics/rule_data.ts new file mode 100644 index 0000000000000..9d3d0c9d65dec --- /dev/null +++ b/x-pack/plugins/infra/common/alerting/metrics/rule_data.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { jsonRt } from '@kbn/io-ts-utils/target/json_rt'; +import * as rt from 'io-ts'; +import { + baseAlertRequestParamsRT as inventoryMetricThresholdAlertParamsRT, + METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID, +} from './types'; + +export const serializedParamsKey = 'serialized_params'; + +export const inventoryMetricThresholdRuleDataNamespace = METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID; +export const inventoryMetricThresholdRuleDataSerializedParamsKey = `${inventoryMetricThresholdRuleDataNamespace}.${serializedParamsKey}` as const; + +// TODO change name (threshold) +export const inventoryMetricRuleDataRT = rt.type({ + [inventoryMetricThresholdRuleDataSerializedParamsKey]: rt.array( + jsonRt.pipe(inventoryMetricThresholdAlertParamsRT) + ), +}); diff --git a/x-pack/plugins/infra/common/alerting/metrics/types.ts b/x-pack/plugins/infra/common/alerting/metrics/types.ts index 94ec40dd2847e..9c4a21125f836 100644 --- a/x-pack/plugins/infra/common/alerting/metrics/types.ts +++ b/x-pack/plugins/infra/common/alerting/metrics/types.ts @@ -57,7 +57,7 @@ export interface MetricAnomalyParams { } // Alert Preview API -const baseAlertRequestParamsRT = rt.intersection([ +export const baseAlertRequestParamsRT = rt.intersection([ rt.partial({ filterQuery: rt.union([rt.string, rt.undefined]), sourceId: rt.string, diff --git a/x-pack/plugins/infra/public/alerting/inventory/index.ts b/x-pack/plugins/infra/public/alerting/inventory/index.ts index b66bd94553d3c..7d370c7106cb7 100644 --- a/x-pack/plugins/infra/public/alerting/inventory/index.ts +++ b/x-pack/plugins/infra/public/alerting/inventory/index.ts @@ -12,16 +12,18 @@ import { METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID, // eslint-disable-next-line @kbn/eslint/no-restricted-paths } from '../../../server/lib/alerting/inventory_metric_threshold/types'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { AlertTypeModel } from '../../../../triggers_actions_ui/public/types'; + +import { ObservabilityRuleTypeModel } from '../../../../observability/public'; + import { AlertTypeParams } from '../../../../alerting/common'; import { validateMetricThreshold } from './components/validation'; +import { formatReason } from './rule_data_formatters'; interface InventoryMetricAlertTypeParams extends AlertTypeParams { criteria: InventoryMetricConditions[]; } -export function createInventoryMetricAlertType(): AlertTypeModel { +export function createInventoryMetricAlertType(): ObservabilityRuleTypeModel { return { id: METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID, description: i18n.translate('xpack.infra.metrics.inventory.alertFlyout.alertDescription', { @@ -44,5 +46,6 @@ Reason: } ), requiresAppContext: false, + format: formatReason, }; } diff --git a/x-pack/plugins/infra/public/alerting/inventory/rule_data_formatters.ts b/x-pack/plugins/infra/public/alerting/inventory/rule_data_formatters.ts new file mode 100644 index 0000000000000..3e7bbfad5ce12 --- /dev/null +++ b/x-pack/plugins/infra/public/alerting/inventory/rule_data_formatters.ts @@ -0,0 +1,72 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +import { + ALERT_EVALUATION_THRESHOLD, + ALERT_EVALUATION_VALUE, + ALERT_ID, + ALERT_START, +} from '@kbn/rule-data-utils'; +import { modifyUrl } from '@kbn/std'; +import { fold } from 'fp-ts/lib/Either'; +import { pipe } from 'fp-ts/lib/function'; +import { ObservabilityRuleTypeFormatter } from '../../../../observability/public'; +import { + ComparatorToi18nMap, + logThresholdRuleDataRT, + logThresholdRuleDataSerializedParamsKey, + ratioAlertParamsRT, +} from '../../../common/alerting/logs/log_threshold'; + +export const formatReason: ObservabilityRuleTypeFormatter = ({ fields }) => { + const reason = pipe( + logThresholdRuleDataRT.decode(fields), + fold( + () => + i18n.translate('xpack.infra.logs.alerting.threshold.unknownReasonDescription', { + defaultMessage: 'unknown reason', + }), + (logThresholdRuleData) => { + const params = logThresholdRuleData[logThresholdRuleDataSerializedParamsKey][0]; + + const actualCount = fields[ALERT_EVALUATION_VALUE]; + const groupName = fields[ALERT_ID]; + const isGrouped = (params.groupBy?.length ?? 0) > 0; + const thresholdCount = fields[ALERT_EVALUATION_THRESHOLD]; + const translatedComparator = ComparatorToi18nMap[params.count.comparator]; + + return i18n.translate('xpack.infra.logs.alerting.threshold.ratioAlertReasonDescription', { + defaultMessage: + '{isGrouped, select, true{{groupName}: } false{}}The log entries ratio is {actualCount} ({translatedComparator} {thresholdCount}).', + values: { + actualCount, + translatedComparator, + groupName, + isGrouped, + thresholdCount, + }, + }); + } + ) + ); + + const alertStartDate = fields[ALERT_START]; + const timestamp = alertStartDate != null ? new Date(alertStartDate).valueOf() : null; + const link = modifyUrl('/app/logs/link-to/default/logs', ({ query, ...otherUrlParts }) => ({ + ...otherUrlParts, + query: { + ...query, + ...(timestamp != null ? { time: `${timestamp}` } : {}), + }, + })); + + return { + reason, + link, // TODO: refactor to URL generators + }; +}; diff --git a/x-pack/plugins/infra/public/plugin.ts b/x-pack/plugins/infra/public/plugin.ts index d5951d9ec9915..0eaeea60c63bf 100644 --- a/x-pack/plugins/infra/public/plugin.ts +++ b/x-pack/plugins/infra/public/plugin.ts @@ -34,12 +34,15 @@ export class Plugin implements InfraClientPluginClass { registerFeatures(pluginsSetup.home); } - pluginsSetup.triggersActionsUi.alertTypeRegistry.register(createInventoryMetricAlertType()); + pluginsSetup.observability.observabilityRuleTypeRegistry.register( + createInventoryMetricAlertType() + ); + pluginsSetup.observability.observabilityRuleTypeRegistry.register( createLogThresholdAlertType() ); - pluginsSetup.triggersActionsUi.alertTypeRegistry.register(createMetricThresholdAlertType()); + pluginsSetup.triggersActionsUi.alertTypeRegistry.register(createMetricThresholdAlertType()); pluginsSetup.observability.dashboard.register({ appName: 'infra_logs', hasData: getLogsHasDataFetcher(core.getStartServices), 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 7a890ac14482a..137496ddd9c99 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 @@ -7,17 +7,25 @@ import { first, get, last } from 'lodash'; import { i18n } from '@kbn/i18n'; +import { ALERT_EVALUATION_THRESHOLD, ALERT_EVALUATION_VALUE } from '@kbn/rule-data-utils'; import moment from 'moment'; import { getCustomMetricLabel } from '../../../../common/formatters/get_custom_metric_label'; import { toMetricOpt } from '../../../../common/snapshot_metric_i18n'; import { AlertStates, InventoryMetricConditions } from './types'; import { + inventoryMetricRuleDataRT, + inventoryMetricThresholdRuleDataSerializedParamsKey, +} from '../../../../common/alerting/metrics'; +import { + AlertTypeParams, + AlertTypeState, + ActionGroupIdsOf, ActionGroup, AlertInstanceContext, AlertInstanceState, RecoveredActionGroup, } from '../../../../../alerting/common'; -import { AlertExecutorOptions } from '../../../../../alerting/server'; +import { AlertInstance } from '../../../../../alerting/server'; import { InventoryItemType, SnapshotMetricType } from '../../../../common/inventory_models/types'; import { InfraBackendLibs } from '../../infra_types'; import { METRIC_FORMATTERS } from '../../../../common/formatters/snapshot_metric_formats'; @@ -40,144 +48,175 @@ interface InventoryMetricThresholdParams { alertOnNoData?: boolean; } -export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) => async ({ - services, - params, -}: AlertExecutorOptions< - /** - * TODO: Remove this use of `any` by utilizing a proper type - */ - Record, - Record, - AlertInstanceState, - AlertInstanceContext, - InventoryMetricThresholdAllowedActionGroups ->) => { - const { - criteria, - filterQuery, - sourceId, - nodeType, - alertOnNoData, - } = params as InventoryMetricThresholdParams; - - if (criteria.length === 0) throw new Error('Cannot execute an alert with 0 conditions'); - - const source = await libs.sources.getSourceConfiguration( - services.savedObjectsClient, - sourceId || 'default' - ); - - const logQueryFields = await libs - .getLogQueryFields( - sourceId || 'default', - services.savedObjectsClient, - services.scopedClusterClient.asCurrentUser - ) - .catch(() => undefined); - - const compositeSize = libs.configuration.inventory.compositeSize; - - const results = await Promise.all( - criteria.map((condition) => - evaluateCondition({ - condition, - nodeType, - source, - logQueryFields, - esClient: services.scopedClusterClient.asCurrentUser, - compositeSize, - filterQuery, - }) - ) - ); - - const inventoryItems = Object.keys(first(results)!); - for (const item of inventoryItems) { - // AND logic; all criteria must be across the threshold - const shouldAlertFire = results.every((result) => - // Grab the result of the most recent bucket - last(result[item].shouldFire) +export type InventoryMetricThresholdActionGroups = ActionGroupIdsOf< + typeof FIRED_ACTIONS | typeof WARNING_ACTIONS | typeof RecoveredActionGroup +>; +export type InventoryMetricThresholdAlertTypeParams = InventoryMetricThresholdParams; +export type InventoryMetricThresholdAlertTypeState = AlertTypeState; // no specific state used +export type InventoryMetricThresholdAlertInstanceState = AlertInstanceState; // no specific state used +export type InventoryMetricThresholdAlertInstanceContext = AlertInstanceContext; // no specific instance context used + +type InventoryMetricThresholdAlertInstance = AlertInstance< + InventoryMetricThresholdAlertInstanceState, + InventoryMetricThresholdAlertInstanceContext, + InventoryMetricThresholdActionGroups +>; +type InventoryMetricThresholdAlertInstanceFactory = ( + id: string, + threshold: number, + value: number +) => InventoryMetricThresholdAlertInstance; + +export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) => { + libs.metricsRules.createLifecycleRuleExecutor< + InventoryMetricThresholdAlertTypeParams, + InventoryMetricThresholdAlertTypeState, + InventoryMetricThresholdAlertInstanceState, + InventoryMetricThresholdAlertInstanceContext, + InventoryMetricThresholdActionGroups + >(async ({ services, params }) => { + const { + criteria, + filterQuery, + sourceId, + nodeType, + alertOnNoData, + } = params as InventoryMetricThresholdParams; + if (criteria.length === 0) throw new Error('Cannot execute an alert with 0 conditions'); + + const { alertWithLifecycle, savedObjectsClient } = services; + const alertInstanceFactory: InventoryMetricThresholdAlertInstanceFactory = ( + id, + threshold, + value + ) => + alertWithLifecycle({ + id, + fields: { + [ALERT_EVALUATION_THRESHOLD]: threshold, + [ALERT_EVALUATION_VALUE]: value, + ...inventoryMetricRuleDataRT.encode({ + [inventoryMetricThresholdRuleDataSerializedParamsKey]: [params], + }), + }, + }); + + const source = await libs.sources.getSourceConfiguration( + savedObjectsClient, + sourceId || 'default' ); - const shouldAlertWarn = results.every((result) => last(result[item].shouldWarn)); - - // AND logic; because we need to evaluate all criteria, if one of them reports no data then the - // whole alert is in a No Data/Error state - const isNoData = results.some((result) => last(result[item].isNoData)); - const isError = results.some((result) => result[item].isError); - - const nextState = isError - ? AlertStates.ERROR - : isNoData - ? AlertStates.NO_DATA - : shouldAlertFire - ? AlertStates.ALERT - : shouldAlertWarn - ? AlertStates.WARNING - : AlertStates.OK; - - let reason; - if (nextState === AlertStates.ALERT || nextState === AlertStates.WARNING) { - reason = results - .map((result) => - buildReasonWithVerboseMetricName( - result[item], - buildFiredAlertReason, - nextState === AlertStates.WARNING - ) - ) - .join('\n'); - /* - * Custom recovery actions aren't yet available in the alerting framework - * Uncomment the code below once they've been implemented - * Reference: https://github.com/elastic/kibana/issues/87048 - */ - // } else if (nextState === AlertStates.OK && prevState?.alertState === AlertStates.ALERT) { - // reason = results - // .map((result) => buildReasonWithVerboseMetricName(result[item], buildRecoveredAlertReason)) - // .join('\n'); - } - if (alertOnNoData) { - if (nextState === AlertStates.NO_DATA) { - reason = results - .filter((result) => result[item].isNoData) - .map((result) => buildReasonWithVerboseMetricName(result[item], buildNoDataAlertReason)) - .join('\n'); - } else if (nextState === AlertStates.ERROR) { + + const logQueryFields = await libs + .getLogQueryFields( + sourceId || 'default', + services.savedObjectsClient, + services.scopedClusterClient.asCurrentUser + ) + .catch(() => undefined); + + const compositeSize = libs.configuration.inventory.compositeSize; + + const results = await Promise.all( + criteria.map((condition) => + evaluateCondition({ + condition, + nodeType, + source, + logQueryFields, + esClient: services.scopedClusterClient.asCurrentUser, + compositeSize, + filterQuery, + }) + ) + ); + + const inventoryItems = Object.keys(first(results)!); + for (const item of inventoryItems) { + // AND logic; all criteria must be across the threshold + const shouldAlertFire = results.every((result) => + // Grab the result of the most recent bucket + last(result[item].shouldFire) + ); + const shouldAlertWarn = results.every((result) => last(result[item].shouldWarn)); + + // AND logic; because we need to evaluate all criteria, if one of them reports no data then the + // whole alert is in a No Data/Error state + const isNoData = results.some((result) => last(result[item].isNoData)); + const isError = results.some((result) => result[item].isError); + + const nextState = isError + ? AlertStates.ERROR + : isNoData + ? AlertStates.NO_DATA + : shouldAlertFire + ? AlertStates.ALERT + : shouldAlertWarn + ? AlertStates.WARNING + : AlertStates.OK; + + let reason; + if (nextState === AlertStates.ALERT || nextState === AlertStates.WARNING) { reason = results - .filter((result) => result[item].isError) - .map((result) => buildReasonWithVerboseMetricName(result[item], buildErrorAlertReason)) + .map((result) => + buildReasonWithVerboseMetricName( + result[item], + buildFiredAlertReason, + nextState === AlertStates.WARNING + ) + ) .join('\n'); - } - } - if (reason) { - const actionGroupId = - nextState === AlertStates.OK - ? RecoveredActionGroup.id - : nextState === AlertStates.WARNING - ? WARNING_ACTIONS.id - : FIRED_ACTIONS.id; - const alertInstance = services.alertInstanceFactory(`${item}`); - alertInstance.scheduleActions( - /** - * TODO: We're lying to the compiler here as explicitly calling `scheduleActions` on - * the RecoveredActionGroup isn't allowed + /* + * Custom recovery actions aren't yet available in the alerting framework + * Uncomment the code below once they've been implemented + * Reference: https://github.com/elastic/kibana/issues/87048 */ - (actionGroupId as unknown) as InventoryMetricThresholdAllowedActionGroups, - { - group: item, - alertState: stateToAlertMessage[nextState], - reason, - timestamp: moment().toISOString(), - value: mapToConditionsLookup(results, (result) => - formatMetric(result[item].metric, result[item].currentValue) - ), - threshold: mapToConditionsLookup(criteria, (c) => c.threshold), - metric: mapToConditionsLookup(criteria, (c) => c.metric), + // } else if (nextState === AlertStates.OK && prevState?.alertState === AlertStates.ALERT) { + // reason = results + // .map((result) => buildReasonWithVerboseMetricName(result[item], buildRecoveredAlertReason)) + // .join('\n'); + } + if (alertOnNoData) { + if (nextState === AlertStates.NO_DATA) { + reason = results + .filter((result) => result[item].isNoData) + .map((result) => buildReasonWithVerboseMetricName(result[item], buildNoDataAlertReason)) + .join('\n'); + } else if (nextState === AlertStates.ERROR) { + reason = results + .filter((result) => result[item].isError) + .map((result) => buildReasonWithVerboseMetricName(result[item], buildErrorAlertReason)) + .join('\n'); } - ); + } + if (reason) { + const actionGroupId = + nextState === AlertStates.OK + ? RecoveredActionGroup.id + : nextState === AlertStates.WARNING + ? WARNING_ACTIONS.id + : FIRED_ACTIONS.id; + const alertInstance = services.alertInstanceFactory(`${item}`); + alertInstance.scheduleActions( + /** + * TODO: We're lying to the compiler here as explicitly calling `scheduleActions` on + * the RecoveredActionGroup isn't allowed + */ + (actionGroupId as unknown) as InventoryMetricThresholdAllowedActionGroups, + { + group: item, + alertState: stateToAlertMessage[nextState], + reason, + timestamp: moment().toISOString(), + value: mapToConditionsLookup(results, (result) => + formatMetric(result[item].metric, result[item].currentValue) + ), + threshold: mapToConditionsLookup(criteria, (c) => c.threshold), + metric: mapToConditionsLookup(criteria, (c) => c.metric), + } + ); + } } - } + }); }; const buildReasonWithVerboseMetricName = ( 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 a2d8e522c7c8d..1ec7746fcec3e 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 @@ -7,12 +7,7 @@ import { schema } from '@kbn/config-schema'; import { i18n } from '@kbn/i18n'; -import { - AlertType, - AlertInstanceState, - AlertInstanceContext, - ActionGroupIdsOf, -} from '../../../../../alerting/server'; +import { ActionGroupIdsOf, PluginSetupContract } from '../../../../../alerting/server'; import { createInventoryMetricThresholdExecutor, FIRED_ACTIONS, @@ -55,51 +50,52 @@ export type InventoryMetricThresholdAllowedActionGroups = ActionGroupIdsOf< typeof FIRED_ACTIONS | typeof WARNING_ACTIONS >; -export const registerMetricInventoryThresholdAlertType = ( - libs: InfraBackendLibs -): AlertType< - /** - * TODO: Remove this use of `any` by utilizing a proper type - */ - Record, - Record, - AlertInstanceState, - AlertInstanceContext, - InventoryMetricThresholdAllowedActionGroups -> => ({ - id: METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID, - name: i18n.translate('xpack.infra.metrics.inventory.alertName', { - defaultMessage: 'Inventory', - }), - validate: { - params: schema.object( - { - criteria: schema.arrayOf(condition), - nodeType: schema.string(), - filterQuery: schema.maybe( - schema.string({ validate: validateIsStringElasticsearchJSONFilter }) - ), - sourceId: schema.string(), - alertOnNoData: schema.maybe(schema.boolean()), - }, - { unknowns: 'allow' } - ), - }, - defaultActionGroupId: FIRED_ACTIONS_ID, - actionGroups: [FIRED_ACTIONS, WARNING_ACTIONS], - producer: 'infrastructure', - minimumLicenseRequired: 'basic', - isExportable: true, - executor: createInventoryMetricThresholdExecutor(libs), - actionVariables: { - context: [ - { name: 'group', description: groupActionVariableDescription }, - { name: 'alertState', description: alertStateActionVariableDescription }, - { name: 'reason', description: reasonActionVariableDescription }, - { name: 'timestamp', description: timestampActionVariableDescription }, - { name: 'value', description: valueActionVariableDescription }, - { name: 'metric', description: metricActionVariableDescription }, - { name: 'threshold', description: thresholdActionVariableDescription }, - ], +export interface InventoryMetricPluginSetupContract + extends Omit { + ActionGroupIds: InventoryMetricThresholdAllowedActionGroups; +} + +export async function registerMetricInventoryThresholdAlertType( + alertingPlugin: Omit & { + ActionGroupIds: InventoryMetricThresholdAllowedActionGroups; }, -}); + libs: InfraBackendLibs +) { + alertingPlugin.registerType({ + id: METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID, + name: i18n.translate('xpack.infra.metrics.inventory.alertName', { + defaultMessage: 'Inventory', + }), + validate: { + params: schema.object( + { + criteria: schema.arrayOf(condition), + nodeType: schema.string(), + filterQuery: schema.maybe( + schema.string({ validate: validateIsStringElasticsearchJSONFilter }) + ), + sourceId: schema.string(), + alertOnNoData: schema.maybe(schema.boolean()), + }, + { unknowns: 'allow' } + ), + }, + defaultActionGroupId: FIRED_ACTIONS_ID, + actionGroups: [FIRED_ACTIONS, WARNING_ACTIONS], + producer: 'infrastructure', + minimumLicenseRequired: 'basic', + isExportable: true, + executor: createInventoryMetricThresholdExecutor(libs), + actionVariables: { + context: [ + { name: 'group', description: groupActionVariableDescription }, + { name: 'alertState', description: alertStateActionVariableDescription }, + { name: 'reason', description: reasonActionVariableDescription }, + { name: 'timestamp', description: timestampActionVariableDescription }, + { name: 'value', description: valueActionVariableDescription }, + { name: 'metric', description: metricActionVariableDescription }, + { name: 'threshold', description: thresholdActionVariableDescription }, + ], + }, + }); +} diff --git a/x-pack/plugins/infra/server/lib/alerting/register_alert_types.ts b/x-pack/plugins/infra/server/lib/alerting/register_alert_types.ts index b5e6f714de77e..d7df2afd8038b 100644 --- a/x-pack/plugins/infra/server/lib/alerting/register_alert_types.ts +++ b/x-pack/plugins/infra/server/lib/alerting/register_alert_types.ts @@ -21,10 +21,9 @@ const registerAlertTypes = ( ) => { if (alertingPlugin) { alertingPlugin.registerType(registerMetricThresholdAlertType(libs)); - alertingPlugin.registerType(registerMetricInventoryThresholdAlertType(libs)); alertingPlugin.registerType(registerMetricAnomalyAlertType(libs, ml)); - const registerFns = [registerLogThresholdAlertType]; + const registerFns = [registerLogThresholdAlertType, registerMetricInventoryThresholdAlertType]; registerFns.forEach((fn) => { fn(alertingPlugin, libs); }); From 430b12871073f37d042bb9fb3c79fb1309ef1ca1 Mon Sep 17 00:00:00 2001 From: mgiota Date: Fri, 16 Jul 2021 12:52:09 +0200 Subject: [PATCH 02/16] fix inventory executor error --- .../inventory_metric_threshold_executor.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) 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 137496ddd9c99..cfcf8bdd671f1 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 @@ -18,14 +18,13 @@ import { } from '../../../../common/alerting/metrics'; import { AlertTypeParams, - AlertTypeState, ActionGroupIdsOf, ActionGroup, AlertInstanceContext, AlertInstanceState, RecoveredActionGroup, } from '../../../../../alerting/common'; -import { AlertInstance } from '../../../../../alerting/server'; +import { AlertInstance, AlertTypeState } from '../../../../../alerting/server'; import { InventoryItemType, SnapshotMetricType } from '../../../../common/inventory_models/types'; import { InfraBackendLibs } from '../../infra_types'; import { METRIC_FORMATTERS } from '../../../../common/formatters/snapshot_metric_formats'; @@ -49,9 +48,9 @@ interface InventoryMetricThresholdParams { } export type InventoryMetricThresholdActionGroups = ActionGroupIdsOf< - typeof FIRED_ACTIONS | typeof WARNING_ACTIONS | typeof RecoveredActionGroup + typeof FIRED_ACTIONS | typeof WARNING_ACTIONS >; -export type InventoryMetricThresholdAlertTypeParams = InventoryMetricThresholdParams; +export type InventoryMetricThresholdAlertTypeParams = Record; export type InventoryMetricThresholdAlertTypeState = AlertTypeState; // no specific state used export type InventoryMetricThresholdAlertInstanceState = AlertInstanceState; // no specific state used export type InventoryMetricThresholdAlertInstanceContext = AlertInstanceContext; // no specific instance context used @@ -67,7 +66,7 @@ type InventoryMetricThresholdAlertInstanceFactory = ( value: number ) => InventoryMetricThresholdAlertInstance; -export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) => { +export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) => libs.metricsRules.createLifecycleRuleExecutor< InventoryMetricThresholdAlertTypeParams, InventoryMetricThresholdAlertTypeState, @@ -217,7 +216,6 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) = } } }); -}; const buildReasonWithVerboseMetricName = ( resultItem: any, From f719f0d25fdd258a310c3a490bcb0f0c8e7a17ea Mon Sep 17 00:00:00 2001 From: mgiota Date: Fri, 16 Jul 2021 17:49:54 +0200 Subject: [PATCH 03/16] save alerts into ES --- .../inventory_metric_threshold_executor.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) 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 cfcf8bdd671f1..78928795768c3 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 @@ -94,9 +94,10 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) = fields: { [ALERT_EVALUATION_THRESHOLD]: threshold, [ALERT_EVALUATION_VALUE]: value, - ...inventoryMetricRuleDataRT.encode({ - [inventoryMetricThresholdRuleDataSerializedParamsKey]: [params], - }), + // TEMP, serialized params need to be fixed + // ...inventoryMetricRuleDataRT.encode({ + // [inventoryMetricThresholdRuleDataSerializedParamsKey]: [params], + // }), }, }); @@ -194,7 +195,9 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) = : nextState === AlertStates.WARNING ? WARNING_ACTIONS.id : FIRED_ACTIONS.id; - const alertInstance = services.alertInstanceFactory(`${item}`); + const threshold = 20; // TEMP + const value = 30; // TEMP + const alertInstance = alertInstanceFactory(`${item}`, threshold, value); alertInstance.scheduleActions( /** * TODO: We're lying to the compiler here as explicitly calling `scheduleActions` on From 6d4c1d46d7963c4059804781357f9ec9de04ad3e Mon Sep 17 00:00:00 2001 From: mgiota Date: Mon, 19 Jul 2021 08:28:46 +0200 Subject: [PATCH 04/16] temp --- .../infra/common/alerting/metrics/types.ts | 37 +++++++++++++++++++ .../inventory/rule_data_formatters.ts | 2 +- 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/infra/common/alerting/metrics/types.ts b/x-pack/plugins/infra/common/alerting/metrics/types.ts index 9c4a21125f836..d70306a35f63d 100644 --- a/x-pack/plugins/infra/common/alerting/metrics/types.ts +++ b/x-pack/plugins/infra/common/alerting/metrics/types.ts @@ -56,6 +56,27 @@ export interface MetricAnomalyParams { influencerFilter: rt.TypeOf | undefined; } +export interface InventoryMetricConditions { + // metric: SnapshotMetricType; + timeSize: number; + // timeUnit: Unit; + sourceId?: string; + threshold: number[]; + comparator: Comparator; + // customMetric?: SnapshotCustomMetricInput; + warningThreshold?: number[]; + warningComparator?: Comparator; +} +export type InventoryItemType = rt.TypeOf; + +export interface InventoryMetricThresholdAlertParams { + criteria: InventoryMetricConditions[]; + filterQuery: string | undefined; + nodeType: InventoryItemType; + sourceId?: string; + alertOnNoData?: boolean; +} + // Alert Preview API export const baseAlertRequestParamsRT = rt.intersection([ rt.partial({ @@ -80,6 +101,22 @@ export const baseAlertRequestParamsRT = rt.intersection([ }), ]); +// export const inventoryMetricThresholdAlertParamsRT = rt.intersection([ +// baseAlertRequestParamsRT, +// rt.type({ +// nodeType: ItemTypeRT, +// alertType: rt.literal(METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID), +// criteria: rt.array(rt.any), +// filterQuery: rt.union([rt.string, rt.undefined]), +// // nodeType: InventoryItemType; +// sourceId?: rt.string, +// alertOnNoData?: rt.boolean, +// }) +// ]); + +// export const alertParamsRT = inventoryMetricThresholdAlertParamsRT; +// export const AlertParams = rt.TypeOf; + const metricThresholdAlertPreviewRequestParamsRT = rt.intersection([ baseAlertRequestParamsRT, rt.partial({ diff --git a/x-pack/plugins/infra/public/alerting/inventory/rule_data_formatters.ts b/x-pack/plugins/infra/public/alerting/inventory/rule_data_formatters.ts index 3e7bbfad5ce12..dea944c259093 100644 --- a/x-pack/plugins/infra/public/alerting/inventory/rule_data_formatters.ts +++ b/x-pack/plugins/infra/public/alerting/inventory/rule_data_formatters.ts @@ -20,7 +20,6 @@ import { ComparatorToi18nMap, logThresholdRuleDataRT, logThresholdRuleDataSerializedParamsKey, - ratioAlertParamsRT, } from '../../../common/alerting/logs/log_threshold'; export const formatReason: ObservabilityRuleTypeFormatter = ({ fields }) => { @@ -32,6 +31,7 @@ export const formatReason: ObservabilityRuleTypeFormatter = ({ fields }) => { defaultMessage: 'unknown reason', }), (logThresholdRuleData) => { + console.log(logThresholdRuleData, 'rule_data'); const params = logThresholdRuleData[logThresholdRuleDataSerializedParamsKey][0]; const actualCount = fields[ALERT_EVALUATION_VALUE]; From 43ac40e26097818e8be94f16529c8ff946e9277c Mon Sep 17 00:00:00 2001 From: mgiota Date: Mon, 19 Jul 2021 22:27:52 +0200 Subject: [PATCH 05/16] basic format reason for inventory threshold --- .../infra/common/alerting/metrics/types.ts | 37 -------------- .../inventory/rule_data_formatters.ts | 50 ++++--------------- .../inventory_metric_threshold_executor.ts | 30 +++++------ 3 files changed, 25 insertions(+), 92 deletions(-) diff --git a/x-pack/plugins/infra/common/alerting/metrics/types.ts b/x-pack/plugins/infra/common/alerting/metrics/types.ts index d70306a35f63d..9c4a21125f836 100644 --- a/x-pack/plugins/infra/common/alerting/metrics/types.ts +++ b/x-pack/plugins/infra/common/alerting/metrics/types.ts @@ -56,27 +56,6 @@ export interface MetricAnomalyParams { influencerFilter: rt.TypeOf | undefined; } -export interface InventoryMetricConditions { - // metric: SnapshotMetricType; - timeSize: number; - // timeUnit: Unit; - sourceId?: string; - threshold: number[]; - comparator: Comparator; - // customMetric?: SnapshotCustomMetricInput; - warningThreshold?: number[]; - warningComparator?: Comparator; -} -export type InventoryItemType = rt.TypeOf; - -export interface InventoryMetricThresholdAlertParams { - criteria: InventoryMetricConditions[]; - filterQuery: string | undefined; - nodeType: InventoryItemType; - sourceId?: string; - alertOnNoData?: boolean; -} - // Alert Preview API export const baseAlertRequestParamsRT = rt.intersection([ rt.partial({ @@ -101,22 +80,6 @@ export const baseAlertRequestParamsRT = rt.intersection([ }), ]); -// export const inventoryMetricThresholdAlertParamsRT = rt.intersection([ -// baseAlertRequestParamsRT, -// rt.type({ -// nodeType: ItemTypeRT, -// alertType: rt.literal(METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID), -// criteria: rt.array(rt.any), -// filterQuery: rt.union([rt.string, rt.undefined]), -// // nodeType: InventoryItemType; -// sourceId?: rt.string, -// alertOnNoData?: rt.boolean, -// }) -// ]); - -// export const alertParamsRT = inventoryMetricThresholdAlertParamsRT; -// export const AlertParams = rt.TypeOf; - const metricThresholdAlertPreviewRequestParamsRT = rt.intersection([ baseAlertRequestParamsRT, rt.partial({ diff --git a/x-pack/plugins/infra/public/alerting/inventory/rule_data_formatters.ts b/x-pack/plugins/infra/public/alerting/inventory/rule_data_formatters.ts index dea944c259093..89f9c7a8ea6e1 100644 --- a/x-pack/plugins/infra/public/alerting/inventory/rule_data_formatters.ts +++ b/x-pack/plugins/infra/public/alerting/inventory/rule_data_formatters.ts @@ -9,55 +9,25 @@ import { i18n } from '@kbn/i18n'; import { ALERT_EVALUATION_THRESHOLD, ALERT_EVALUATION_VALUE, - ALERT_ID, ALERT_START, } from '@kbn/rule-data-utils'; import { modifyUrl } from '@kbn/std'; -import { fold } from 'fp-ts/lib/Either'; -import { pipe } from 'fp-ts/lib/function'; import { ObservabilityRuleTypeFormatter } from '../../../../observability/public'; -import { - ComparatorToi18nMap, - logThresholdRuleDataRT, - logThresholdRuleDataSerializedParamsKey, -} from '../../../common/alerting/logs/log_threshold'; export const formatReason: ObservabilityRuleTypeFormatter = ({ fields }) => { - const reason = pipe( - logThresholdRuleDataRT.decode(fields), - fold( - () => - i18n.translate('xpack.infra.logs.alerting.threshold.unknownReasonDescription', { - defaultMessage: 'unknown reason', - }), - (logThresholdRuleData) => { - console.log(logThresholdRuleData, 'rule_data'); - const params = logThresholdRuleData[logThresholdRuleDataSerializedParamsKey][0]; - - const actualCount = fields[ALERT_EVALUATION_VALUE]; - const groupName = fields[ALERT_ID]; - const isGrouped = (params.groupBy?.length ?? 0) > 0; - const thresholdCount = fields[ALERT_EVALUATION_THRESHOLD]; - const translatedComparator = ComparatorToi18nMap[params.count.comparator]; - - return i18n.translate('xpack.infra.logs.alerting.threshold.ratioAlertReasonDescription', { - defaultMessage: - '{isGrouped, select, true{{groupName}: } false{}}The log entries ratio is {actualCount} ({translatedComparator} {thresholdCount}).', - values: { - actualCount, - translatedComparator, - groupName, - isGrouped, - thresholdCount, - }, - }); - } - ) - ); + const actualCount = fields[ALERT_EVALUATION_VALUE]; + const thresholdCount = fields[ALERT_EVALUATION_THRESHOLD]; + const reason = i18n.translate('xpack.infra.logs.alerting.threshold.ratioAlertReasonDescription', { + defaultMessage: 'Current value is {actualCount} (threshold of {thresholdCount}).', + values: { + actualCount, + thresholdCount, + }, + }); const alertStartDate = fields[ALERT_START]; const timestamp = alertStartDate != null ? new Date(alertStartDate).valueOf() : null; - const link = modifyUrl('/app/logs/link-to/default/logs', ({ query, ...otherUrlParts }) => ({ + const link = modifyUrl('/app/metrics/link-to/default/metrics', ({ query, ...otherUrlParts }) => ({ ...otherUrlParts, query: { ...query, 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 78928795768c3..e27807b45756d 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 @@ -62,8 +62,8 @@ type InventoryMetricThresholdAlertInstance = AlertInstance< >; type InventoryMetricThresholdAlertInstanceFactory = ( id: string, - threshold: number, - value: number + threshold: number | undefined, + value: number | undefined ) => InventoryMetricThresholdAlertInstance; export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) => @@ -82,7 +82,6 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) = alertOnNoData, } = params as InventoryMetricThresholdParams; if (criteria.length === 0) throw new Error('Cannot execute an alert with 0 conditions'); - const { alertWithLifecycle, savedObjectsClient } = services; const alertInstanceFactory: InventoryMetricThresholdAlertInstanceFactory = ( id, @@ -94,10 +93,6 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) = fields: { [ALERT_EVALUATION_THRESHOLD]: threshold, [ALERT_EVALUATION_VALUE]: value, - // TEMP, serialized params need to be fixed - // ...inventoryMetricRuleDataRT.encode({ - // [inventoryMetricThresholdRuleDataSerializedParamsKey]: [params], - // }), }, }); @@ -153,17 +148,24 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) = : shouldAlertWarn ? AlertStates.WARNING : AlertStates.OK; - let reason; + let threshold; + let value; if (nextState === AlertStates.ALERT || nextState === AlertStates.WARNING) { reason = results - .map((result) => - buildReasonWithVerboseMetricName( - result[item], + .map((result) => { + const resultItem = result[item]; + value = resultItem.currentValue; + threshold = + nextState === AlertStates.WARNING + ? resultItem.warningThreshold! + : resultItem.threshold; + return buildReasonWithVerboseMetricName( + resultItem, buildFiredAlertReason, nextState === AlertStates.WARNING - ) - ) + ); + }) .join('\n'); /* * Custom recovery actions aren't yet available in the alerting framework @@ -195,8 +197,6 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) = : nextState === AlertStates.WARNING ? WARNING_ACTIONS.id : FIRED_ACTIONS.id; - const threshold = 20; // TEMP - const value = 30; // TEMP const alertInstance = alertInstanceFactory(`${item}`, threshold, value); alertInstance.scheduleActions( /** From c9778db1b7e69b884ed9c9fffa93d5e86eac7c39 Mon Sep 17 00:00:00 2001 From: mgiota Date: Mon, 19 Jul 2021 22:55:48 +0200 Subject: [PATCH 06/16] clean up, fix i18n error and temporarily remove types --- x-pack/plugins/alerting/server/plugin.ts | 5 ----- .../infra/common/alerting/metrics/rule_data.ts | 1 - .../alerting/inventory/rule_data_formatters.ts | 2 +- .../inventory_metric_threshold_executor.ts | 10 ++++------ ...ister_inventory_metric_threshold_alert_type.ts | 15 ++------------- 5 files changed, 7 insertions(+), 26 deletions(-) diff --git a/x-pack/plugins/alerting/server/plugin.ts b/x-pack/plugins/alerting/server/plugin.ts index 17658e1ffb249..b906983017ff6 100644 --- a/x-pack/plugins/alerting/server/plugin.ts +++ b/x-pack/plugins/alerting/server/plugin.ts @@ -84,11 +84,6 @@ export const LEGACY_EVENT_LOG_ACTIONS = { resolvedInstance: 'resolved-instance', }; -// export interface InventoryMetricPluginSetupContract -// extends Omit { -// ActionGroupIds: InventoryMetricThresholdAllowedActionGroups; -// } - export interface PluginSetupContract { registerType< Params extends AlertTypeParams = AlertTypeParams, diff --git a/x-pack/plugins/infra/common/alerting/metrics/rule_data.ts b/x-pack/plugins/infra/common/alerting/metrics/rule_data.ts index 9d3d0c9d65dec..5f8fe57a96331 100644 --- a/x-pack/plugins/infra/common/alerting/metrics/rule_data.ts +++ b/x-pack/plugins/infra/common/alerting/metrics/rule_data.ts @@ -17,7 +17,6 @@ export const serializedParamsKey = 'serialized_params'; export const inventoryMetricThresholdRuleDataNamespace = METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID; export const inventoryMetricThresholdRuleDataSerializedParamsKey = `${inventoryMetricThresholdRuleDataNamespace}.${serializedParamsKey}` as const; -// TODO change name (threshold) export const inventoryMetricRuleDataRT = rt.type({ [inventoryMetricThresholdRuleDataSerializedParamsKey]: rt.array( jsonRt.pipe(inventoryMetricThresholdAlertParamsRT) diff --git a/x-pack/plugins/infra/public/alerting/inventory/rule_data_formatters.ts b/x-pack/plugins/infra/public/alerting/inventory/rule_data_formatters.ts index 89f9c7a8ea6e1..45382a6f3a680 100644 --- a/x-pack/plugins/infra/public/alerting/inventory/rule_data_formatters.ts +++ b/x-pack/plugins/infra/public/alerting/inventory/rule_data_formatters.ts @@ -17,7 +17,7 @@ import { ObservabilityRuleTypeFormatter } from '../../../../observability/public export const formatReason: ObservabilityRuleTypeFormatter = ({ fields }) => { const actualCount = fields[ALERT_EVALUATION_VALUE]; const thresholdCount = fields[ALERT_EVALUATION_THRESHOLD]; - const reason = i18n.translate('xpack.infra.logs.alerting.threshold.ratioAlertReasonDescription', { + const reason = i18n.translate('xpack.infra.metrics.alerting.inventory.alertReasonDescription', { defaultMessage: 'Current value is {actualCount} (threshold of {thresholdCount}).', values: { actualCount, 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 e27807b45756d..3ac269fb8b1ae 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 @@ -13,11 +13,6 @@ import { getCustomMetricLabel } from '../../../../common/formatters/get_custom_m import { toMetricOpt } from '../../../../common/snapshot_metric_i18n'; import { AlertStates, InventoryMetricConditions } from './types'; import { - inventoryMetricRuleDataRT, - inventoryMetricThresholdRuleDataSerializedParamsKey, -} from '../../../../common/alerting/metrics'; -import { - AlertTypeParams, ActionGroupIdsOf, ActionGroup, AlertInstanceContext, @@ -37,7 +32,6 @@ import { stateToAlertMessage, } from '../common/messages'; import { evaluateCondition } from './evaluate_condition'; -import { InventoryMetricThresholdAllowedActionGroups } from './register_inventory_metric_threshold_alert_type'; interface InventoryMetricThresholdParams { criteria: InventoryMetricConditions[]; @@ -47,6 +41,10 @@ interface InventoryMetricThresholdParams { alertOnNoData?: boolean; } +type InventoryMetricThresholdAllowedActionGroups = ActionGroupIdsOf< + typeof FIRED_ACTIONS | typeof WARNING_ACTIONS +>; + export type InventoryMetricThresholdActionGroups = ActionGroupIdsOf< typeof FIRED_ACTIONS | typeof WARNING_ACTIONS >; 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 1ec7746fcec3e..5d516f3591419 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 @@ -7,7 +7,7 @@ import { schema } from '@kbn/config-schema'; import { i18n } from '@kbn/i18n'; -import { ActionGroupIdsOf, PluginSetupContract } from '../../../../../alerting/server'; +import { PluginSetupContract } from '../../../../../alerting/server'; import { createInventoryMetricThresholdExecutor, FIRED_ACTIONS, @@ -46,19 +46,8 @@ const condition = schema.object({ ), }); -export type InventoryMetricThresholdAllowedActionGroups = ActionGroupIdsOf< - typeof FIRED_ACTIONS | typeof WARNING_ACTIONS ->; - -export interface InventoryMetricPluginSetupContract - extends Omit { - ActionGroupIds: InventoryMetricThresholdAllowedActionGroups; -} - export async function registerMetricInventoryThresholdAlertType( - alertingPlugin: Omit & { - ActionGroupIds: InventoryMetricThresholdAllowedActionGroups; - }, + alertingPlugin: PluginSetupContract, libs: InfraBackendLibs ) { alertingPlugin.registerType({ From 615b8fd89ecd203ac1440b71a0c54f15d7a5bd07 Mon Sep 17 00:00:00 2001 From: mgiota Date: Tue, 20 Jul 2021 21:56:02 +0200 Subject: [PATCH 07/16] delete serialized params --- .../infra/common/alerting/metrics/index.ts | 1 - .../common/alerting/metrics/rule_data.ts | 24 ------------------- 2 files changed, 25 deletions(-) delete mode 100644 x-pack/plugins/infra/common/alerting/metrics/rule_data.ts diff --git a/x-pack/plugins/infra/common/alerting/metrics/index.ts b/x-pack/plugins/infra/common/alerting/metrics/index.ts index 2c968b6f41bfa..2c66638711cd0 100644 --- a/x-pack/plugins/infra/common/alerting/metrics/index.ts +++ b/x-pack/plugins/infra/common/alerting/metrics/index.ts @@ -6,7 +6,6 @@ */ export * from './types'; -export * from './rule_data'; export const INFRA_ALERT_PREVIEW_PATH = '/api/infra/alerting/preview'; export const TOO_MANY_BUCKETS_PREVIEW_EXCEPTION = 'TOO_MANY_BUCKETS_PREVIEW_EXCEPTION'; diff --git a/x-pack/plugins/infra/common/alerting/metrics/rule_data.ts b/x-pack/plugins/infra/common/alerting/metrics/rule_data.ts deleted file mode 100644 index 5f8fe57a96331..0000000000000 --- a/x-pack/plugins/infra/common/alerting/metrics/rule_data.ts +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { jsonRt } from '@kbn/io-ts-utils/target/json_rt'; -import * as rt from 'io-ts'; -import { - baseAlertRequestParamsRT as inventoryMetricThresholdAlertParamsRT, - METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID, -} from './types'; - -export const serializedParamsKey = 'serialized_params'; - -export const inventoryMetricThresholdRuleDataNamespace = METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID; -export const inventoryMetricThresholdRuleDataSerializedParamsKey = `${inventoryMetricThresholdRuleDataNamespace}.${serializedParamsKey}` as const; - -export const inventoryMetricRuleDataRT = rt.type({ - [inventoryMetricThresholdRuleDataSerializedParamsKey]: rt.array( - jsonRt.pipe(inventoryMetricThresholdAlertParamsRT) - ), -}); From 322fccab0935b7dbcb3469bae14641f4f43c3cab Mon Sep 17 00:00:00 2001 From: mgiota Date: Tue, 20 Jul 2021 22:10:04 +0200 Subject: [PATCH 08/16] include group name in the reason --- .../infra/public/alerting/inventory/rule_data_formatters.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/infra/public/alerting/inventory/rule_data_formatters.ts b/x-pack/plugins/infra/public/alerting/inventory/rule_data_formatters.ts index 45382a6f3a680..3b98dcee633a2 100644 --- a/x-pack/plugins/infra/public/alerting/inventory/rule_data_formatters.ts +++ b/x-pack/plugins/infra/public/alerting/inventory/rule_data_formatters.ts @@ -10,6 +10,7 @@ import { ALERT_EVALUATION_THRESHOLD, ALERT_EVALUATION_VALUE, ALERT_START, + ALERT_ID, } from '@kbn/rule-data-utils'; import { modifyUrl } from '@kbn/std'; import { ObservabilityRuleTypeFormatter } from '../../../../observability/public'; @@ -17,11 +18,14 @@ import { ObservabilityRuleTypeFormatter } from '../../../../observability/public export const formatReason: ObservabilityRuleTypeFormatter = ({ fields }) => { const actualCount = fields[ALERT_EVALUATION_VALUE]; const thresholdCount = fields[ALERT_EVALUATION_THRESHOLD]; + const groupName = fields[ALERT_ID]; const reason = i18n.translate('xpack.infra.metrics.alerting.inventory.alertReasonDescription', { - defaultMessage: 'Current value is {actualCount} (threshold of {thresholdCount}).', + defaultMessage: + 'Current value is {actualCount} (threshold of {thresholdCount}) for {groupName}.', values: { actualCount, thresholdCount, + groupName, }, }); From 844313a51c2a85e9bca0b0a4c09a396a14b2d476 Mon Sep 17 00:00:00 2001 From: mgiota Date: Tue, 20 Jul 2021 22:10:34 +0200 Subject: [PATCH 09/16] cleanup --- .../inventory_metric_threshold_executor.ts | 3 --- 1 file changed, 3 deletions(-) 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 3ac269fb8b1ae..94c2697349e62 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 @@ -45,9 +45,6 @@ type InventoryMetricThresholdAllowedActionGroups = ActionGroupIdsOf< typeof FIRED_ACTIONS | typeof WARNING_ACTIONS >; -export type InventoryMetricThresholdActionGroups = ActionGroupIdsOf< - typeof FIRED_ACTIONS | typeof WARNING_ACTIONS ->; export type InventoryMetricThresholdAlertTypeParams = Record; export type InventoryMetricThresholdAlertTypeState = AlertTypeState; // no specific state used export type InventoryMetricThresholdAlertInstanceState = AlertInstanceState; // no specific state used From 9238414960c1c88cfecb61152a702d54096ff386 Mon Sep 17 00:00:00 2001 From: mgiota Date: Wed, 21 Jul 2021 14:14:16 +0200 Subject: [PATCH 10/16] link to default metrics page --- .../alerting/inventory/rule_data_formatters.ts | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/x-pack/plugins/infra/public/alerting/inventory/rule_data_formatters.ts b/x-pack/plugins/infra/public/alerting/inventory/rule_data_formatters.ts index 3b98dcee633a2..6a7b11818019c 100644 --- a/x-pack/plugins/infra/public/alerting/inventory/rule_data_formatters.ts +++ b/x-pack/plugins/infra/public/alerting/inventory/rule_data_formatters.ts @@ -29,18 +29,10 @@ export const formatReason: ObservabilityRuleTypeFormatter = ({ fields }) => { }, }); - const alertStartDate = fields[ALERT_START]; - const timestamp = alertStartDate != null ? new Date(alertStartDate).valueOf() : null; - const link = modifyUrl('/app/metrics/link-to/default/metrics', ({ query, ...otherUrlParts }) => ({ - ...otherUrlParts, - query: { - ...query, - ...(timestamp != null ? { time: `${timestamp}` } : {}), - }, - })); + const link = '/app/metrics/inventory'; return { reason, - link, // TODO: refactor to URL generators + link, }; }; From 323a2a65603504ad1b56db7527f36a687c8f52b3 Mon Sep 17 00:00:00 2001 From: mgiota Date: Wed, 21 Jul 2021 14:29:06 +0200 Subject: [PATCH 11/16] grab the value and threshold for the inventory item --- .../inventory_metric_threshold_executor.ts | 48 +++++++++++-------- 1 file changed, 27 insertions(+), 21 deletions(-) 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 94c2697349e62..3c47e5bed8c38 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 @@ -53,7 +53,7 @@ export type InventoryMetricThresholdAlertInstanceContext = AlertInstanceContext; type InventoryMetricThresholdAlertInstance = AlertInstance< InventoryMetricThresholdAlertInstanceState, InventoryMetricThresholdAlertInstanceContext, - InventoryMetricThresholdActionGroups + InventoryMetricThresholdAllowedActionGroups >; type InventoryMetricThresholdAlertInstanceFactory = ( id: string, @@ -61,13 +61,18 @@ type InventoryMetricThresholdAlertInstanceFactory = ( value: number | undefined ) => InventoryMetricThresholdAlertInstance; +interface AlertData { + threshold: number | undefined; + value: number | undefined; +} + export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) => libs.metricsRules.createLifecycleRuleExecutor< InventoryMetricThresholdAlertTypeParams, InventoryMetricThresholdAlertTypeState, InventoryMetricThresholdAlertInstanceState, InventoryMetricThresholdAlertInstanceContext, - InventoryMetricThresholdActionGroups + InventoryMetricThresholdAllowedActionGroups >(async ({ services, params }) => { const { criteria, @@ -105,7 +110,6 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) = .catch(() => undefined); const compositeSize = libs.configuration.inventory.compositeSize; - const results = await Promise.all( criteria.map((condition) => evaluateCondition({ @@ -119,14 +123,14 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) = }) ) ); - const inventoryItems = Object.keys(first(results)!); + for (const item of inventoryItems) { // AND logic; all criteria must be across the threshold - const shouldAlertFire = results.every((result) => + const shouldAlertFire = results.every((result) => { // Grab the result of the most recent bucket - last(result[item].shouldFire) - ); + return last(result[item].shouldFire); + }); const shouldAlertWarn = results.every((result) => last(result[item].shouldWarn)); // AND logic; because we need to evaluate all criteria, if one of them reports no data then the @@ -144,23 +148,15 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) = ? AlertStates.WARNING : AlertStates.OK; let reason; - let threshold; - let value; if (nextState === AlertStates.ALERT || nextState === AlertStates.WARNING) { reason = results - .map((result) => { - const resultItem = result[item]; - value = resultItem.currentValue; - threshold = - nextState === AlertStates.WARNING - ? resultItem.warningThreshold! - : resultItem.threshold; - return buildReasonWithVerboseMetricName( - resultItem, + .map((result) => + buildReasonWithVerboseMetricName( + result[item], buildFiredAlertReason, nextState === AlertStates.WARNING - ); - }) + ) + ) .join('\n'); /* * Custom recovery actions aren't yet available in the alerting framework @@ -192,7 +188,8 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) = : nextState === AlertStates.WARNING ? WARNING_ACTIONS.id : FIRED_ACTIONS.id; - const alertInstance = alertInstanceFactory(`${item}`, threshold, value); + const alertData = buildAlertData(first(results)![item], nextState); + const alertInstance = alertInstanceFactory(`${item}`, alertData.threshold, alertData.value); alertInstance.scheduleActions( /** * TODO: We're lying to the compiler here as explicitly calling `scheduleActions` on @@ -235,6 +232,15 @@ const buildReasonWithVerboseMetricName = ( return buildReason(resultWithVerboseMetricName); }; +const buildAlertData = (inventoryItem: any, nextState: any): AlertData => { + const threshold = + nextState === AlertStates.WARNING ? inventoryItem.warningThreshold! : resultItem.threshold; + return { + value: inventoryItem.currentValue, + threshold, + }; +}; + const mapToConditionsLookup = ( list: any[], mapFn: (value: any, index: number, array: any[]) => unknown From 944c3dc1042cfa4a11f5f4a666c08b3d2a86ffda Mon Sep 17 00:00:00 2001 From: mgiota Date: Wed, 21 Jul 2021 15:13:21 +0200 Subject: [PATCH 12/16] fix typo --- .../inventory_metric_threshold_executor.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 3c47e5bed8c38..cb6cac16a4547 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 @@ -234,7 +234,7 @@ const buildReasonWithVerboseMetricName = ( const buildAlertData = (inventoryItem: any, nextState: any): AlertData => { const threshold = - nextState === AlertStates.WARNING ? inventoryItem.warningThreshold! : resultItem.threshold; + nextState === AlertStates.WARNING ? inventoryItem.warningThreshold! : inventoryItem.threshold; return { value: inventoryItem.currentValue, threshold, From 5b703c2281143f58e22304477f968daa4d4372a6 Mon Sep 17 00:00:00 2001 From: mgiota Date: Wed, 21 Jul 2021 15:52:22 +0200 Subject: [PATCH 13/16] fix check types --- .../public/alerting/inventory/rule_data_formatters.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/x-pack/plugins/infra/public/alerting/inventory/rule_data_formatters.ts b/x-pack/plugins/infra/public/alerting/inventory/rule_data_formatters.ts index 6a7b11818019c..a31d2b625c1da 100644 --- a/x-pack/plugins/infra/public/alerting/inventory/rule_data_formatters.ts +++ b/x-pack/plugins/infra/public/alerting/inventory/rule_data_formatters.ts @@ -6,13 +6,7 @@ */ import { i18n } from '@kbn/i18n'; -import { - ALERT_EVALUATION_THRESHOLD, - ALERT_EVALUATION_VALUE, - ALERT_START, - ALERT_ID, -} from '@kbn/rule-data-utils'; -import { modifyUrl } from '@kbn/std'; +import { ALERT_EVALUATION_THRESHOLD, ALERT_EVALUATION_VALUE, ALERT_ID } from '@kbn/rule-data-utils'; import { ObservabilityRuleTypeFormatter } from '../../../../observability/public'; export const formatReason: ObservabilityRuleTypeFormatter = ({ fields }) => { From 94955dd8103142e194933e4bd525fdff8c0e8dc6 Mon Sep 17 00:00:00 2001 From: mgiota Date: Wed, 21 Jul 2021 17:47:06 +0200 Subject: [PATCH 14/16] remove threshold and currentValue, the reason field will contain this info for combined conditions --- .../inventory_metric_threshold_executor.ts | 35 ++++--------------- 1 file changed, 6 insertions(+), 29 deletions(-) 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 cb6cac16a4547..025bc54e11cc9 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 @@ -7,7 +7,6 @@ import { first, get, last } from 'lodash'; import { i18n } from '@kbn/i18n'; -import { ALERT_EVALUATION_THRESHOLD, ALERT_EVALUATION_VALUE } from '@kbn/rule-data-utils'; import moment from 'moment'; import { getCustomMetricLabel } from '../../../../common/formatters/get_custom_metric_label'; import { toMetricOpt } from '../../../../common/snapshot_metric_i18n'; @@ -57,15 +56,10 @@ type InventoryMetricThresholdAlertInstance = AlertInstance< >; type InventoryMetricThresholdAlertInstanceFactory = ( id: string, - threshold: number | undefined, - value: number | undefined + threshold?: number | undefined, + value?: number | undefined ) => InventoryMetricThresholdAlertInstance; -interface AlertData { - threshold: number | undefined; - value: number | undefined; -} - export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) => libs.metricsRules.createLifecycleRuleExecutor< InventoryMetricThresholdAlertTypeParams, @@ -83,17 +77,10 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) = } = params as InventoryMetricThresholdParams; if (criteria.length === 0) throw new Error('Cannot execute an alert with 0 conditions'); const { alertWithLifecycle, savedObjectsClient } = services; - const alertInstanceFactory: InventoryMetricThresholdAlertInstanceFactory = ( - id, - threshold, - value - ) => + const alertInstanceFactory: InventoryMetricThresholdAlertInstanceFactory = (id) => alertWithLifecycle({ id, - fields: { - [ALERT_EVALUATION_THRESHOLD]: threshold, - [ALERT_EVALUATION_VALUE]: value, - }, + fields: {}, }); const source = await libs.sources.getSourceConfiguration( @@ -124,7 +111,6 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) = ) ); const inventoryItems = Object.keys(first(results)!); - for (const item of inventoryItems) { // AND logic; all criteria must be across the threshold const shouldAlertFire = results.every((result) => { @@ -188,8 +174,8 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) = : nextState === AlertStates.WARNING ? WARNING_ACTIONS.id : FIRED_ACTIONS.id; - const alertData = buildAlertData(first(results)![item], nextState); - const alertInstance = alertInstanceFactory(`${item}`, alertData.threshold, alertData.value); + + const alertInstance = alertInstanceFactory(`${item}`); alertInstance.scheduleActions( /** * TODO: We're lying to the compiler here as explicitly calling `scheduleActions` on @@ -232,15 +218,6 @@ const buildReasonWithVerboseMetricName = ( return buildReason(resultWithVerboseMetricName); }; -const buildAlertData = (inventoryItem: any, nextState: any): AlertData => { - const threshold = - nextState === AlertStates.WARNING ? inventoryItem.warningThreshold! : inventoryItem.threshold; - return { - value: inventoryItem.currentValue, - threshold, - }; -}; - const mapToConditionsLookup = ( list: any[], mapFn: (value: any, index: number, array: any[]) => unknown From 1f3ce107bc965a0c2edc7960cb24420ec2368039 Mon Sep 17 00:00:00 2001 From: mgiota Date: Wed, 21 Jul 2021 18:00:53 +0200 Subject: [PATCH 15/16] remove thereshold and value from the reason, soon will be replaced by indexed reason field --- .../public/alerting/inventory/rule_data_formatters.ts | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/x-pack/plugins/infra/public/alerting/inventory/rule_data_formatters.ts b/x-pack/plugins/infra/public/alerting/inventory/rule_data_formatters.ts index a31d2b625c1da..1d8414d6abd23 100644 --- a/x-pack/plugins/infra/public/alerting/inventory/rule_data_formatters.ts +++ b/x-pack/plugins/infra/public/alerting/inventory/rule_data_formatters.ts @@ -6,19 +6,14 @@ */ import { i18n } from '@kbn/i18n'; -import { ALERT_EVALUATION_THRESHOLD, ALERT_EVALUATION_VALUE, ALERT_ID } from '@kbn/rule-data-utils'; +import { ALERT_ID } from '@kbn/rule-data-utils'; import { ObservabilityRuleTypeFormatter } from '../../../../observability/public'; export const formatReason: ObservabilityRuleTypeFormatter = ({ fields }) => { - const actualCount = fields[ALERT_EVALUATION_VALUE]; - const thresholdCount = fields[ALERT_EVALUATION_THRESHOLD]; const groupName = fields[ALERT_ID]; const reason = i18n.translate('xpack.infra.metrics.alerting.inventory.alertReasonDescription', { - defaultMessage: - 'Current value is {actualCount} (threshold of {thresholdCount}) for {groupName}.', + defaultMessage: 'Inventory alert for {groupName}.', // TEMP reason message, will be deleted once we index the reason field values: { - actualCount, - thresholdCount, groupName, }, }); From db0ed1d7c8d7380e038f897c844cd48c72f0591c Mon Sep 17 00:00:00 2001 From: mgiota Date: Wed, 21 Jul 2021 18:03:14 +0200 Subject: [PATCH 16/16] remove unnecessary export --- x-pack/plugins/infra/common/alerting/metrics/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/infra/common/alerting/metrics/types.ts b/x-pack/plugins/infra/common/alerting/metrics/types.ts index 9c4a21125f836..94ec40dd2847e 100644 --- a/x-pack/plugins/infra/common/alerting/metrics/types.ts +++ b/x-pack/plugins/infra/common/alerting/metrics/types.ts @@ -57,7 +57,7 @@ export interface MetricAnomalyParams { } // Alert Preview API -export const baseAlertRequestParamsRT = rt.intersection([ +const baseAlertRequestParamsRT = rt.intersection([ rt.partial({ filterQuery: rt.union([rt.string, rt.undefined]), sourceId: rt.string,