From 42455eea84df4c955567e59516c942e6caad32b9 Mon Sep 17 00:00:00 2001 From: Ersin Erdal Date: Tue, 12 Mar 2024 15:07:23 +0100 Subject: [PATCH 1/8] Onboard Inventory Metric Threshold rule type with FAAD --- ...nventory_metric_threshold_executor.test.ts | 61 +++---- .../inventory_metric_threshold_executor.ts | 165 ++++++++---------- .../lib/alerting/register_rule_types.ts | 5 +- 3 files changed, 101 insertions(+), 130 deletions(-) diff --git a/x-pack/plugins/observability_solution/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.test.ts b/x-pack/plugins/observability_solution/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.test.ts index bd6350ba59dd0..28853194befe1 100644 --- a/x-pack/plugins/observability_solution/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.test.ts +++ b/x-pack/plugins/observability_solution/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.test.ts @@ -9,11 +9,7 @@ import { AlertInstanceContext as AlertContext, AlertInstanceState as AlertState, } from '@kbn/alerting-plugin/server'; -import { - AlertInstanceMock, - RuleExecutorServicesMock, - alertsMock, -} from '@kbn/alerting-plugin/server/mocks'; +import { RuleExecutorServicesMock, alertsMock } from '@kbn/alerting-plugin/server/mocks'; import { LifecycleAlertServices } from '@kbn/rule-registry-plugin/server'; import { ruleRegistryMocks } from '@kbn/rule-registry-plugin/server/mocks'; import { createLifecycleRuleExecutorMock } from '@kbn/rule-registry-plugin/server/utils/create_lifecycle_rule_executor_mock'; @@ -34,13 +30,11 @@ import { logsSharedPluginMock } from '@kbn/logs-shared-plugin/server/mocks'; jest.mock('./evaluate_condition', () => ({ evaluateCondition: jest.fn() })); interface AlertTestInstance { - instance: AlertInstanceMock; - actionQueue: any[]; - state: any; + actionGroup: string; + payload: any[]; + context: any[]; } -const persistAlertInstances = false; - const fakeLogger = (msg: string, meta?: Meta) => {}; const logger = { @@ -146,38 +140,33 @@ const services: RuleExecutorServicesMock & ...ruleRegistryMocks.createLifecycleAlertServices(alertsServices), }; -const alertInstances = new Map(); +const alerts = new Map(); -services.alertFactory.create.mockImplementation((instanceID: string) => { - const newAlertInstance: AlertTestInstance = { - instance: alertsMock.createAlertFactory.create(), - actionQueue: [], - state: {}, +services.alertsClient.report.mockImplementation((params: any) => { + alerts.set(params.id, { actionGroup: params.actionGroup, context: [], payload: [] }); + return { + uuid: `uuid-${params.id}`, + start: new Date().toISOString(), + alertDoc: {}, }; +}); - const alertInstance: AlertTestInstance = persistAlertInstances - ? alertInstances.get(instanceID) || newAlertInstance - : newAlertInstance; - alertInstances.set(instanceID, alertInstance); - - (alertInstance.instance.scheduleActions as jest.Mock).mockImplementation( - (id: string, action: any) => { - alertInstance.actionQueue.push({ id, action }); - return alertInstance.instance; - } - ); - - return alertInstance.instance; +services.alertsClient.setAlertData.mockImplementation((params: any) => { + const alert = alerts.get(params.id); + if (alert) { + alert.payload.push(params.payload); + alert.context.push(params.context); + } }); function mostRecentAction(id: string) { - const instance = alertInstances.get(id); + const instance = alerts.get(id); if (!instance) return undefined; - return instance.actionQueue.pop(); + return instance.context.pop(); } function clearInstances() { - alertInstances.clear(); + alerts.clear(); } const executor = createInventoryMetricThresholdExecutor(mockLibs); @@ -255,13 +244,13 @@ describe('The inventory threshold alert type', () => { }, }); await execute(Comparator.GT, [0.75]); - expect(mostRecentAction(instanceIdA).action.tags).toStrictEqual([ + expect(mostRecentAction(instanceIdA).tags).toStrictEqual([ 'host-01_tag1', 'host-01_tag2', 'ruleTag1', 'ruleTag2', ]); - expect(mostRecentAction(instanceIdB).action.tags).toStrictEqual([ + expect(mostRecentAction(instanceIdB).tags).toStrictEqual([ 'host-02_tag1', 'host-02_tag2', 'ruleTag1', @@ -305,8 +294,8 @@ describe('The inventory threshold alert type', () => { }, }); await execute(Comparator.GT, [0.75]); - expect(mostRecentAction(instanceIdA).action.tags).toStrictEqual(['ruleTag1', 'ruleTag2']); - expect(mostRecentAction(instanceIdB).action.tags).toStrictEqual(['ruleTag1', 'ruleTag2']); + expect(mostRecentAction(instanceIdA).tags).toStrictEqual(['ruleTag1', 'ruleTag2']); + expect(mostRecentAction(instanceIdB).tags).toStrictEqual(['ruleTag1', 'ruleTag2']); }); test('should call evaluation query with delay', async () => { diff --git a/x-pack/plugins/observability_solution/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts b/x-pack/plugins/observability_solution/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts index 7f4159f39c525..2abe2cfb04023 100644 --- a/x-pack/plugins/observability_solution/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts +++ b/x-pack/plugins/observability_solution/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts @@ -19,7 +19,7 @@ import { AlertInstanceContext as AlertContext, AlertInstanceState as AlertState, } from '@kbn/alerting-plugin/common'; -import { Alert, RuleTypeState } from '@kbn/alerting-plugin/server'; +import { AlertsClientError, RuleTypeState } from '@kbn/alerting-plugin/server'; import { getAlertUrl } from '@kbn/observability-plugin/common'; import { SnapshotMetricType } from '@kbn/metrics-data-access-plugin/common'; import { getOriginalActionGroup } from '../../../utils/get_original_action_group'; @@ -42,7 +42,6 @@ import { stateToAlertMessage, } from '../common/messages'; import { - AdditionalContext, createScopedLogger, flattenAdditionalContext, getContextForRecoveredAlerts, @@ -59,26 +58,10 @@ type InventoryMetricThresholdAllowedActionGroups = ActionGroupIdsOf< export const FIRED_ACTIONS_ID = 'metrics.inventory_threshold.fired'; export const WARNING_ACTIONS_ID = 'metrics.inventory_threshold.warning'; -type InventoryThrehsoldActionGroup = typeof FIRED_ACTIONS_ID | typeof WARNING_ACTIONS_ID; - export type InventoryMetricThresholdRuleTypeState = RuleTypeState; // no specific state used export type InventoryMetricThresholdAlertState = AlertState; // no specific state used export type InventoryMetricThresholdAlertContext = AlertContext; // no specific instance context used -type InventoryMetricThresholdAlert = Alert< - InventoryMetricThresholdAlertState, - InventoryMetricThresholdAlertContext, - InventoryMetricThresholdAllowedActionGroups ->; -type InventoryMetricThresholdAlertFactory = ( - id: string, - reason: string, - actionGroup: InventoryThrehsoldActionGroup, - additionalContext?: AdditionalContext | null, - evaluationValues?: Array, - thresholds?: Array -) => InventoryMetricThresholdAlert; - export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) => libs.metricsRules.createLifecycleRuleExecutor< InventoryMetricThresholdParams & Record, @@ -109,32 +92,11 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) = const esClient = services.scopedClusterClient.asCurrentUser; - const { - alertWithLifecycle, - savedObjectsClient, - getAlertStartedDate, - getAlertUuid, - getAlertByAlertUuid, - alertFactory: baseAlertFactory, - } = services; - const alertFactory: InventoryMetricThresholdAlertFactory = ( - id, - reason, - actionGroup, - additionalContext, - evaluationValues, - thresholds - ) => - alertWithLifecycle({ - id, - fields: { - [ALERT_REASON]: reason, - [ALERT_ACTION_GROUP]: actionGroup, - [ALERT_EVALUATION_VALUES]: evaluationValues, - [ALERT_EVALUATION_THRESHOLD]: thresholds, - ...flattenAdditionalContext(additionalContext), - }, - }); + const { savedObjectsClient, alertFactory: baseAlertFactory, alertsClient } = services; + + if (!alertsClient) { + throw new AlertsClientError(); + } if (!params.filterQuery && params.filterQueryText) { try { @@ -142,34 +104,45 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) = fromKueryExpression(params.filterQueryText); } catch (e) { logger.error(e.message); - const actionGroupId = FIRED_ACTIONS.id; // Change this to an Error action group when able + + const actionGroup = FIRED_ACTIONS.id; // Change this to an Error action group when able, const reason = buildInvalidQueryAlertReason(params.filterQueryText); - const alert = alertFactory(UNGROUPED_FACTORY_KEY, reason, actionGroupId); - const indexedStartedAt = - getAlertStartedDate(UNGROUPED_FACTORY_KEY) ?? startedAt.toISOString(); - const alertUuid = getAlertUuid(UNGROUPED_FACTORY_KEY); - alert.scheduleActions(actionGroupId, { - alertDetailsUrl: await getAlertUrl( - alertUuid, - spaceId, - indexedStartedAt, - libs.alertsLocator, - libs.basePath.publicBaseUrl - ), - alertState: stateToAlertMessage[AlertStates.ERROR], - group: UNGROUPED_FACTORY_KEY, - metric: mapToConditionsLookup(criteria, (c) => c.metric), - reason, - timestamp: startedAt.toISOString(), - value: null, - viewInAppUrl: getInventoryViewInAppUrlWithSpaceId({ - basePath: libs.basePath, - criteria, - nodeType, - timestamp: indexedStartedAt, - spaceId, - }), + const { uuid, start } = alertsClient.report({ + id: UNGROUPED_FACTORY_KEY, + actionGroup, + }); + + const indexedStartedAt = start ?? startedAt.toISOString(); + + alertsClient.setAlertData({ + id: UNGROUPED_FACTORY_KEY, + payload: { + [ALERT_REASON]: reason, + [ALERT_ACTION_GROUP]: actionGroup, + }, + context: { + alertDetailsUrl: await getAlertUrl( + uuid, + spaceId, + indexedStartedAt, + libs.alertsLocator, + libs.basePath.publicBaseUrl + ), + alertState: stateToAlertMessage[AlertStates.ERROR], + group: UNGROUPED_FACTORY_KEY, + metric: mapToConditionsLookup(criteria, (c) => c.metric), + reason, + timestamp: startedAt.toISOString(), + value: null, + viewInAppUrl: getInventoryViewInAppUrlWithSpaceId({ + basePath: libs.basePath, + criteria, + nodeType, + timestamp: indexedStartedAt, + spaceId, + }), + }, }); return { state: {} }; @@ -265,7 +238,7 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) = } } if (reason) { - const actionGroupId = + const actionGroup = nextState === AlertStates.WARNING ? WARNING_ACTIONS_ID : FIRED_ACTIONS_ID; const additionalContext = results && results.length > 0 ? results[0][group].context : {}; @@ -276,22 +249,18 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) = const evaluationValues = getEvaluationValues(results, group); const thresholds = getThresholds(criteria); - const alert = alertFactory( - group, - reason, - actionGroupId, - additionalContext, - evaluationValues, - thresholds - ); - const indexedStartedAt = getAlertStartedDate(group) ?? startedAt.toISOString(); - const alertUuid = getAlertUuid(group); + const { uuid, start } = alertsClient.report({ + id: group, + actionGroup, + }); + + const indexedStartedAt = start ?? startedAt.toISOString(); scheduledActionsCount++; const context = { alertDetailsUrl: await getAlertUrl( - alertUuid, + uuid, spaceId, indexedStartedAt, libs.alertsLocator, @@ -316,23 +285,35 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) = }), ...additionalContext, }; - alert.scheduleActions(actionGroupId, context); + + const payload = { + [ALERT_REASON]: reason, + [ALERT_ACTION_GROUP]: actionGroup, + [ALERT_EVALUATION_VALUES]: evaluationValues, + [ALERT_EVALUATION_THRESHOLD]: thresholds, + ...flattenAdditionalContext(additionalContext), + }; + + alertsClient.setAlertData({ + id: group, + payload, + context, + }); } } - baseAlertFactory.alertLimit.setLimitReached(hasReachedLimit); - const { getRecoveredAlerts } = services.alertFactory.done(); - const recoveredAlerts = getRecoveredAlerts(); + alertsClient.setAlertLimitReached(hasReachedLimit); + const recoveredAlerts = alertsClient?.getRecoveredAlerts() ?? []; - for (const alert of recoveredAlerts) { - const recoveredAlertId = alert.getId(); - const indexedStartedAt = getAlertStartedDate(recoveredAlertId) ?? startedAt.toISOString(); - const alertUuid = getAlertUuid(recoveredAlertId); - const alertHits = alertUuid ? await getAlertByAlertUuid(alertUuid) : undefined; + for (const recoveredAlert of recoveredAlerts) { + const recoveredAlertId = recoveredAlert.alert.getId(); + const indexedStartedAt = recoveredAlert.alert.getStart() ?? startedAt.toISOString(); + const alertUuid = recoveredAlert.alert.getUuid(); + const alertHits = recoveredAlert.hit; const additionalContext = getContextForRecoveredAlerts(alertHits); const originalActionGroup = getOriginalActionGroup(alertHits); - alert.setContext({ + recoveredAlert.alert.setContext({ alertDetailsUrl: await getAlertUrl( alertUuid, spaceId, diff --git a/x-pack/plugins/observability_solution/infra/server/lib/alerting/register_rule_types.ts b/x-pack/plugins/observability_solution/infra/server/lib/alerting/register_rule_types.ts index c26d92d5291c0..cbafba9f831f1 100644 --- a/x-pack/plugins/observability_solution/infra/server/lib/alerting/register_rule_types.ts +++ b/x-pack/plugins/observability_solution/infra/server/lib/alerting/register_rule_types.ts @@ -6,7 +6,8 @@ */ import { legacyExperimentalFieldMap } from '@kbn/alerts-as-data-utils'; -import { type IRuleTypeAlerts, PluginSetupContract } from '@kbn/alerting-plugin/server'; +import type { IRuleTypeAlerts } from '@kbn/alerting-plugin/server'; +import { PluginSetupContract } from '@kbn/alerting-plugin/server'; import { registerMetricThresholdRuleType } from './metric_threshold/register_metric_threshold_rule_type'; import { registerInventoryThresholdRuleType } from './inventory_metric_threshold/register_inventory_metric_threshold_rule_type'; import { registerLogThresholdRuleType } from './log_threshold/register_log_threshold_rule_type'; @@ -29,7 +30,7 @@ export const MetricsRulesTypeAlertDefinition: IRuleTypeAlerts = { mappings: { fieldMap: legacyExperimentalFieldMap }, useEcs: true, useLegacyAlerts: true, - shouldWrite: false, + shouldWrite: true, }; const registerRuleTypes = ( From 8bbcacf01bf94077a63f51285c881e43988ee37a Mon Sep 17 00:00:00 2001 From: Ersin Erdal Date: Wed, 13 Mar 2024 15:46:11 +0100 Subject: [PATCH 2/8] Add a unit test --- ...nventory_metric_threshold_executor.test.ts | 60 ++++++++++++------- .../inventory_metric_threshold_executor.ts | 2 +- 2 files changed, 39 insertions(+), 23 deletions(-) diff --git a/x-pack/plugins/observability_solution/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.test.ts b/x-pack/plugins/observability_solution/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.test.ts index 28853194befe1..b0ffe0b354b13 100644 --- a/x-pack/plugins/observability_solution/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.test.ts +++ b/x-pack/plugins/observability_solution/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.test.ts @@ -132,32 +132,33 @@ const mockLibs = { }, logger, } as unknown as InfraBackendLibs; - -const alertsServices = alertsMock.createRuleExecutorServices(); -const services: RuleExecutorServicesMock & - LifecycleAlertServices = { - ...alertsServices, - ...ruleRegistryMocks.createLifecycleAlertServices(alertsServices), -}; - const alerts = new Map(); +let services: RuleExecutorServicesMock & LifecycleAlertServices; -services.alertsClient.report.mockImplementation((params: any) => { - alerts.set(params.id, { actionGroup: params.actionGroup, context: [], payload: [] }); - return { - uuid: `uuid-${params.id}`, - start: new Date().toISOString(), - alertDoc: {}, +const setup = () => { + const alertsServices = alertsMock.createRuleExecutorServices(); + services = { + ...alertsServices, + ...ruleRegistryMocks.createLifecycleAlertServices(alertsServices), }; -}); -services.alertsClient.setAlertData.mockImplementation((params: any) => { - const alert = alerts.get(params.id); - if (alert) { - alert.payload.push(params.payload); - alert.context.push(params.context); - } -}); + services.alertsClient.report.mockImplementation((params: any) => { + alerts.set(params.id, { actionGroup: params.actionGroup, context: [], payload: [] }); + return { + uuid: `uuid-${params.id}`, + start: new Date().toISOString(), + alertDoc: {}, + }; + }); + + services.alertsClient.setAlertData.mockImplementation((params: any) => { + const alert = alerts.get(params.id); + if (alert) { + alert.payload.push(params.payload); + alert.context.push(params.context); + } + }); +}; function mostRecentAction(id: string) { const instance = alerts.get(id); @@ -183,6 +184,9 @@ const baseCriterion = { describe('The inventory threshold alert type', () => { describe('querying with Hosts and rule tags', () => { afterAll(() => clearInstances()); + + setup(); + const execute = (comparator: Comparator, threshold: number[], options?: any) => executor({ ...mockOptions, @@ -208,6 +212,18 @@ describe('The inventory threshold alert type', () => { const instanceIdA = 'host-01'; const instanceIdB = 'host-02'; + test('throws error when alertsClient is null', async () => { + try { + services.alertsClient = null; + await execute(Comparator.GT, [0.75]); + } catch (e) { + expect(e).toMatchInlineSnapshot( + '[Error: Expected alertsClient not to be null! There may have been an issue installing alert resources.]' + ); + setup(); + } + }); + test('when tags are present in the source, rule tags and source tags are combined in alert context', async () => { setEvaluationResults({ 'host-01': { diff --git a/x-pack/plugins/observability_solution/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts b/x-pack/plugins/observability_solution/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts index 2abe2cfb04023..f5937823d0e82 100644 --- a/x-pack/plugins/observability_solution/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts +++ b/x-pack/plugins/observability_solution/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts @@ -303,7 +303,7 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) = } alertsClient.setAlertLimitReached(hasReachedLimit); - const recoveredAlerts = alertsClient?.getRecoveredAlerts() ?? []; + const recoveredAlerts = alertsClient.getRecoveredAlerts() ?? []; for (const recoveredAlert of recoveredAlerts) { const recoveredAlertId = recoveredAlert.alert.getId(); From 3574f816382674341f2ebeefd010bdde3a6145e3 Mon Sep 17 00:00:00 2001 From: Ersin Erdal Date: Wed, 13 Mar 2024 18:50:02 +0100 Subject: [PATCH 3/8] fix should write to scope only inventory rule type --- .../register_inventory_metric_threshold_rule_type.ts | 12 ++++++++++-- .../infra/server/lib/alerting/register_rule_types.ts | 2 +- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/observability_solution/infra/server/lib/alerting/inventory_metric_threshold/register_inventory_metric_threshold_rule_type.ts b/x-pack/plugins/observability_solution/infra/server/lib/alerting/inventory_metric_threshold/register_inventory_metric_threshold_rule_type.ts index d1fbef7a7095e..3b5c158f7c1f5 100644 --- a/x-pack/plugins/observability_solution/infra/server/lib/alerting/inventory_metric_threshold/register_inventory_metric_threshold_rule_type.ts +++ b/x-pack/plugins/observability_solution/infra/server/lib/alerting/inventory_metric_threshold/register_inventory_metric_threshold_rule_type.ts @@ -8,7 +8,11 @@ import { schema, Type } from '@kbn/config-schema'; import { i18n } from '@kbn/i18n'; import { DEFAULT_APP_CATEGORIES } from '@kbn/core/server'; -import { GetViewInAppRelativeUrlFnOpts, PluginSetupContract } from '@kbn/alerting-plugin/server'; +import { + GetViewInAppRelativeUrlFnOpts, + IRuleTypeAlerts, + PluginSetupContract, +} from '@kbn/alerting-plugin/server'; import { observabilityPaths } from '@kbn/observability-plugin/common'; import { TimeUnitChar } from '@kbn/observability-plugin/common/utils/formatters/duration'; import { @@ -16,6 +20,7 @@ import { SnapshotMetricType, SnapshotMetricTypeKeys, } from '@kbn/metrics-data-access-plugin/common'; +import { MetricThresholdAlert } from '../metric_threshold/metric_threshold_executor'; import type { InfraConfig } from '../../../../common/plugin_config_types'; import { Comparator, @@ -162,7 +167,10 @@ export async function registerInventoryThresholdRuleType( }, ], }, - alerts: MetricsRulesTypeAlertDefinition, + alerts: { + ...MetricsRulesTypeAlertDefinition, + shouldWrite: true, + } as IRuleTypeAlerts, fieldsForAAD: O11Y_AAD_FIELDS, getViewInAppRelativeUrl: ({ rule }: GetViewInAppRelativeUrlFnOpts<{}>) => observabilityPaths.ruleDetails(rule.id), diff --git a/x-pack/plugins/observability_solution/infra/server/lib/alerting/register_rule_types.ts b/x-pack/plugins/observability_solution/infra/server/lib/alerting/register_rule_types.ts index cbafba9f831f1..56e5cefed08a6 100644 --- a/x-pack/plugins/observability_solution/infra/server/lib/alerting/register_rule_types.ts +++ b/x-pack/plugins/observability_solution/infra/server/lib/alerting/register_rule_types.ts @@ -30,7 +30,7 @@ export const MetricsRulesTypeAlertDefinition: IRuleTypeAlerts = { mappings: { fieldMap: legacyExperimentalFieldMap }, useEcs: true, useLegacyAlerts: true, - shouldWrite: true, + shouldWrite: false, }; const registerRuleTypes = ( From e83ea2e8ff612aa35bdc083c87b53c5148e62e83 Mon Sep 17 00:00:00 2001 From: Ersin Erdal Date: Wed, 13 Mar 2024 18:53:12 +0100 Subject: [PATCH 4/8] revert changes on register_rule_types.ts --- .../infra/server/lib/alerting/register_rule_types.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/x-pack/plugins/observability_solution/infra/server/lib/alerting/register_rule_types.ts b/x-pack/plugins/observability_solution/infra/server/lib/alerting/register_rule_types.ts index 56e5cefed08a6..c26d92d5291c0 100644 --- a/x-pack/plugins/observability_solution/infra/server/lib/alerting/register_rule_types.ts +++ b/x-pack/plugins/observability_solution/infra/server/lib/alerting/register_rule_types.ts @@ -6,8 +6,7 @@ */ import { legacyExperimentalFieldMap } from '@kbn/alerts-as-data-utils'; -import type { IRuleTypeAlerts } from '@kbn/alerting-plugin/server'; -import { PluginSetupContract } from '@kbn/alerting-plugin/server'; +import { type IRuleTypeAlerts, PluginSetupContract } from '@kbn/alerting-plugin/server'; import { registerMetricThresholdRuleType } from './metric_threshold/register_metric_threshold_rule_type'; import { registerInventoryThresholdRuleType } from './inventory_metric_threshold/register_inventory_metric_threshold_rule_type'; import { registerLogThresholdRuleType } from './log_threshold/register_log_threshold_rule_type'; From 5c5315fcc75980893c85ee8050794d5707795eac Mon Sep 17 00:00:00 2001 From: Ersin Erdal Date: Wed, 13 Mar 2024 19:58:00 +0100 Subject: [PATCH 5/8] remove createLifecycleRuleExecutor --- .../inventory_metric_threshold_executor.ts | 464 +++++++++--------- ...er_inventory_metric_threshold_rule_type.ts | 4 +- 2 files changed, 241 insertions(+), 227 deletions(-) diff --git a/x-pack/plugins/observability_solution/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts b/x-pack/plugins/observability_solution/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts index f5937823d0e82..aa4f3b5a5e82f 100644 --- a/x-pack/plugins/observability_solution/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts +++ b/x-pack/plugins/observability_solution/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts @@ -19,9 +19,10 @@ import { AlertInstanceContext as AlertContext, AlertInstanceState as AlertState, } from '@kbn/alerting-plugin/common'; -import { AlertsClientError, RuleTypeState } from '@kbn/alerting-plugin/server'; +import { AlertsClientError, RuleExecutorOptions, RuleTypeState } from '@kbn/alerting-plugin/server'; import { getAlertUrl } from '@kbn/observability-plugin/common'; import { SnapshotMetricType } from '@kbn/metrics-data-access-plugin/common'; +import { ObservabilityMetricsAlert } from '@kbn/alerts-as-data-utils'; import { getOriginalActionGroup } from '../../../utils/get_original_action_group'; import { AlertStates, @@ -62,203 +63,80 @@ export type InventoryMetricThresholdRuleTypeState = RuleTypeState; // no specifi export type InventoryMetricThresholdAlertState = AlertState; // no specific state used export type InventoryMetricThresholdAlertContext = AlertContext; // no specific instance context used -export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) => - libs.metricsRules.createLifecycleRuleExecutor< - InventoryMetricThresholdParams & Record, - InventoryMetricThresholdRuleTypeState, - InventoryMetricThresholdAlertState, - InventoryMetricThresholdAlertContext, - InventoryMetricThresholdAllowedActionGroups - >( - async ({ +export type InventoryMetricThresholdAlert = Omit< + ObservabilityMetricsAlert, + 'kibana.alert.evaluation.values' +> & { + // Defining a custom type for this because the schema generation script doesn't allow explicit null values + [ALERT_EVALUATION_VALUES]?: Array; + [ALERT_EVALUATION_THRESHOLD]?: Array; +}; + +export const createInventoryMetricThresholdExecutor = + (libs: InfraBackendLibs) => + async ( + options: RuleExecutorOptions< + InventoryMetricThresholdParams & Record, + InventoryMetricThresholdRuleTypeState, + InventoryMetricThresholdAlertState, + InventoryMetricThresholdAlertContext, + InventoryMetricThresholdAllowedActionGroups, + InventoryMetricThresholdAlert + > + ) => { + const { services, params, + startedAt, executionId, spaceId, - startedAt, rule: { id: ruleId, tags: ruleTags }, getTimeRange, - }) => { - const startTime = Date.now(); + } = options; - const { criteria, filterQuery, sourceId = 'default', nodeType, alertOnNoData } = params; + const startTime = Date.now(); - if (criteria.length === 0) throw new Error('Cannot execute an alert with 0 conditions'); + const { criteria, filterQuery, sourceId = 'default', nodeType, alertOnNoData } = params; - const logger = createScopedLogger(libs.logger, 'inventoryRule', { - alertId: ruleId, - executionId, - }); + if (criteria.length === 0) throw new Error('Cannot execute an alert with 0 conditions'); - const esClient = services.scopedClusterClient.asCurrentUser; - - const { savedObjectsClient, alertFactory: baseAlertFactory, alertsClient } = services; - - if (!alertsClient) { - throw new AlertsClientError(); - } + const logger = createScopedLogger(libs.logger, 'inventoryRule', { + alertId: ruleId, + executionId, + }); - if (!params.filterQuery && params.filterQueryText) { - try { - const { fromKueryExpression } = await import('@kbn/es-query'); - fromKueryExpression(params.filterQueryText); - } catch (e) { - logger.error(e.message); - - const actionGroup = FIRED_ACTIONS.id; // Change this to an Error action group when able, - const reason = buildInvalidQueryAlertReason(params.filterQueryText); - - const { uuid, start } = alertsClient.report({ - id: UNGROUPED_FACTORY_KEY, - actionGroup, - }); - - const indexedStartedAt = start ?? startedAt.toISOString(); - - alertsClient.setAlertData({ - id: UNGROUPED_FACTORY_KEY, - payload: { - [ALERT_REASON]: reason, - [ALERT_ACTION_GROUP]: actionGroup, - }, - context: { - alertDetailsUrl: await getAlertUrl( - uuid, - spaceId, - indexedStartedAt, - libs.alertsLocator, - libs.basePath.publicBaseUrl - ), - alertState: stateToAlertMessage[AlertStates.ERROR], - group: UNGROUPED_FACTORY_KEY, - metric: mapToConditionsLookup(criteria, (c) => c.metric), - reason, - timestamp: startedAt.toISOString(), - value: null, - viewInAppUrl: getInventoryViewInAppUrlWithSpaceId({ - basePath: libs.basePath, - criteria, - nodeType, - timestamp: indexedStartedAt, - spaceId, - }), - }, - }); - - return { state: {} }; - } - } - const source = await libs.sources.getSourceConfiguration(savedObjectsClient, sourceId); - - const [, { logsShared }] = await libs.getStartServices(); - const logQueryFields: LogQueryFields | undefined = await logsShared.logViews - .getClient(savedObjectsClient, esClient) - .getResolvedLogView({ - type: 'log-view-reference', - logViewId: sourceId, - }) - .then( - ({ indices }) => ({ indexPattern: indices }), - () => undefined - ); + const esClient = services.scopedClusterClient.asCurrentUser; - const compositeSize = libs.configuration.alerting.inventory_threshold.group_by_page_size; - const { dateEnd } = getTimeRange(); - const results = await Promise.all( - criteria.map((condition) => - evaluateCondition({ - compositeSize, - condition, - esClient, - executionTimestamp: new Date(dateEnd), - filterQuery, - logger, - logQueryFields, - nodeType, - source, - }) - ) - ); + const { savedObjectsClient, alertFactory: baseAlertFactory, alertsClient } = services; - let scheduledActionsCount = 0; - const alertLimit = baseAlertFactory.alertLimit.getValue(); - let hasReachedLimit = false; - const inventoryItems = Object.keys(first(results)!); - for (const group of inventoryItems) { - if (scheduledActionsCount >= alertLimit) { - // need to set this so that warning is displayed in the UI and in the logs - hasReachedLimit = true; - break; // once limit is reached, we break out of the loop and don't schedule any more alerts - } - // AND logic; all criteria must be across the threshold - const shouldAlertFire = results.every((result) => result[group]?.shouldFire); - const shouldAlertWarn = results.every((result) => result[group]?.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) => result[group]?.isNoData); - const isError = results.some((result) => result[group]?.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( - group, - result[group], - buildFiredAlertReason, - nextState === AlertStates.WARNING - ) - ) - .join('\n'); - } - if (alertOnNoData) { - if (nextState === AlertStates.NO_DATA) { - reason = results - .filter((result) => result[group].isNoData) - .map((result) => - buildReasonWithVerboseMetricName(group, result[group], buildNoDataAlertReason) - ) - .join('\n'); - } else if (nextState === AlertStates.ERROR) { - reason = results - .filter((result) => result[group].isError) - .map((result) => - buildReasonWithVerboseMetricName(group, result[group], buildErrorAlertReason) - ) - .join('\n'); - } - } - if (reason) { - const actionGroup = - nextState === AlertStates.WARNING ? WARNING_ACTIONS_ID : FIRED_ACTIONS_ID; - - const additionalContext = results && results.length > 0 ? results[0][group].context : {}; - additionalContext.tags = Array.from( - new Set([...(additionalContext.tags ?? []), ...ruleTags]) - ); + if (!alertsClient) { + throw new AlertsClientError(); + } - const evaluationValues = getEvaluationValues(results, group); - const thresholds = getThresholds(criteria); + if (!params.filterQuery && params.filterQueryText) { + try { + const { fromKueryExpression } = await import('@kbn/es-query'); + fromKueryExpression(params.filterQueryText); + } catch (e) { + logger.error(e.message); - const { uuid, start } = alertsClient.report({ - id: group, - actionGroup, - }); + const actionGroup = FIRED_ACTIONS.id; // Change this to an Error action group when able, + const reason = buildInvalidQueryAlertReason(params.filterQueryText); - const indexedStartedAt = start ?? startedAt.toISOString(); + const { uuid, start } = alertsClient.report({ + id: UNGROUPED_FACTORY_KEY, + actionGroup, + }); - scheduledActionsCount++; + const indexedStartedAt = start ?? startedAt.toISOString(); - const context = { + alertsClient.setAlertData({ + id: UNGROUPED_FACTORY_KEY, + payload: { + [ALERT_REASON]: reason, + [ALERT_ACTION_GROUP]: actionGroup, + }, + context: { alertDetailsUrl: await getAlertUrl( uuid, spaceId, @@ -266,66 +144,152 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) = libs.alertsLocator, libs.basePath.publicBaseUrl ), - alertState: stateToAlertMessage[nextState], - group, - reason, + alertState: stateToAlertMessage[AlertStates.ERROR], + group: UNGROUPED_FACTORY_KEY, metric: mapToConditionsLookup(criteria, (c) => c.metric), + reason, timestamp: startedAt.toISOString(), - threshold: mapToConditionsLookup(criteria, (c) => c.threshold), - value: mapToConditionsLookup(results, (result) => - formatMetric(result[group].metric, result[group].currentValue) - ), + value: null, viewInAppUrl: getInventoryViewInAppUrlWithSpaceId({ basePath: libs.basePath, criteria, nodeType, timestamp: indexedStartedAt, spaceId, - hostName: additionalContext?.host?.name, }), - ...additionalContext, - }; + }, + }); - const payload = { - [ALERT_REASON]: reason, - [ALERT_ACTION_GROUP]: actionGroup, - [ALERT_EVALUATION_VALUES]: evaluationValues, - [ALERT_EVALUATION_THRESHOLD]: thresholds, - ...flattenAdditionalContext(additionalContext), - }; - - alertsClient.setAlertData({ - id: group, - payload, - context, - }); + return { state: {} }; + } + } + const source = await libs.sources.getSourceConfiguration(savedObjectsClient, sourceId); + + const [, { logsShared }] = await libs.getStartServices(); + const logQueryFields: LogQueryFields | undefined = await logsShared.logViews + .getClient(savedObjectsClient, esClient) + .getResolvedLogView({ + type: 'log-view-reference', + logViewId: sourceId, + }) + .then( + ({ indices }) => ({ indexPattern: indices }), + () => undefined + ); + + const compositeSize = libs.configuration.alerting.inventory_threshold.group_by_page_size; + const { dateEnd } = getTimeRange(); + const results = await Promise.all( + criteria.map((condition) => + evaluateCondition({ + compositeSize, + condition, + esClient, + executionTimestamp: new Date(dateEnd), + filterQuery, + logger, + logQueryFields, + nodeType, + source, + }) + ) + ); + + let scheduledActionsCount = 0; + const alertLimit = baseAlertFactory.alertLimit.getValue(); + let hasReachedLimit = false; + const inventoryItems = Object.keys(first(results)!); + for (const group of inventoryItems) { + if (scheduledActionsCount >= alertLimit) { + // need to set this so that warning is displayed in the UI and in the logs + hasReachedLimit = true; + break; // once limit is reached, we break out of the loop and don't schedule any more alerts + } + // AND logic; all criteria must be across the threshold + const shouldAlertFire = results.every((result) => result[group]?.shouldFire); + const shouldAlertWarn = results.every((result) => result[group]?.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) => result[group]?.isNoData); + const isError = results.some((result) => result[group]?.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( + group, + result[group], + buildFiredAlertReason, + nextState === AlertStates.WARNING + ) + ) + .join('\n'); + } + if (alertOnNoData) { + if (nextState === AlertStates.NO_DATA) { + reason = results + .filter((result) => result[group].isNoData) + .map((result) => + buildReasonWithVerboseMetricName(group, result[group], buildNoDataAlertReason) + ) + .join('\n'); + } else if (nextState === AlertStates.ERROR) { + reason = results + .filter((result) => result[group].isError) + .map((result) => + buildReasonWithVerboseMetricName(group, result[group], buildErrorAlertReason) + ) + .join('\n'); } } + if (reason) { + const actionGroup = + nextState === AlertStates.WARNING ? WARNING_ACTIONS_ID : FIRED_ACTIONS_ID; + + const additionalContext = results && results.length > 0 ? results[0][group].context : {}; + additionalContext.tags = Array.from( + new Set([...(additionalContext.tags ?? []), ...ruleTags]) + ); - alertsClient.setAlertLimitReached(hasReachedLimit); - const recoveredAlerts = alertsClient.getRecoveredAlerts() ?? []; + const evaluationValues = getEvaluationValues(results, group); + const thresholds = getThresholds(criteria); - for (const recoveredAlert of recoveredAlerts) { - const recoveredAlertId = recoveredAlert.alert.getId(); - const indexedStartedAt = recoveredAlert.alert.getStart() ?? startedAt.toISOString(); - const alertUuid = recoveredAlert.alert.getUuid(); - const alertHits = recoveredAlert.hit; - const additionalContext = getContextForRecoveredAlerts(alertHits); - const originalActionGroup = getOriginalActionGroup(alertHits); + const { uuid, start } = alertsClient.report({ + id: group, + actionGroup, + }); + + const indexedStartedAt = start ?? startedAt.toISOString(); - recoveredAlert.alert.setContext({ + scheduledActionsCount++; + + const context = { alertDetailsUrl: await getAlertUrl( - alertUuid, + uuid, spaceId, indexedStartedAt, libs.alertsLocator, libs.basePath.publicBaseUrl ), - alertState: stateToAlertMessage[AlertStates.OK], - group: recoveredAlertId, + alertState: stateToAlertMessage[nextState], + group, + reason, metric: mapToConditionsLookup(criteria, (c) => c.metric), - threshold: mapToConditionsLookup(criteria, (c) => c.threshold), timestamp: startedAt.toISOString(), + threshold: mapToConditionsLookup(criteria, (c) => c.threshold), + value: mapToConditionsLookup(results, (result) => + formatMetric(result[group].metric, result[group].currentValue) + ), viewInAppUrl: getInventoryViewInAppUrlWithSpaceId({ basePath: libs.basePath, criteria, @@ -334,19 +298,69 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) = spaceId, hostName: additionalContext?.host?.name, }), - originalAlertState: translateActionGroupToAlertState(originalActionGroup), - originalAlertStateWasALERT: originalActionGroup === FIRED_ACTIONS_ID, - originalAlertStateWasWARNING: originalActionGroup === WARNING_ACTIONS_ID, ...additionalContext, + }; + + const payload = { + [ALERT_REASON]: reason, + [ALERT_ACTION_GROUP]: actionGroup, + [ALERT_EVALUATION_VALUES]: evaluationValues, + [ALERT_EVALUATION_THRESHOLD]: thresholds, + ...flattenAdditionalContext(additionalContext), + }; + + alertsClient.setAlertData({ + id: group, + payload, + context, }); } + } - const stopTime = Date.now(); - logger.debug(`Scheduled ${scheduledActionsCount} actions in ${stopTime - startTime}ms`); - - return { state: {} }; + alertsClient.setAlertLimitReached(hasReachedLimit); + const recoveredAlerts = alertsClient.getRecoveredAlerts() ?? []; + + for (const recoveredAlert of recoveredAlerts) { + const recoveredAlertId = recoveredAlert.alert.getId(); + const indexedStartedAt = recoveredAlert.alert.getStart() ?? startedAt.toISOString(); + const alertUuid = recoveredAlert.alert.getUuid(); + const alertHits = recoveredAlert.hit; + const additionalContext = getContextForRecoveredAlerts(alertHits); + const originalActionGroup = getOriginalActionGroup(alertHits); + + recoveredAlert.alert.setContext({ + alertDetailsUrl: await getAlertUrl( + alertUuid, + spaceId, + indexedStartedAt, + libs.alertsLocator, + libs.basePath.publicBaseUrl + ), + alertState: stateToAlertMessage[AlertStates.OK], + group: recoveredAlertId, + metric: mapToConditionsLookup(criteria, (c) => c.metric), + threshold: mapToConditionsLookup(criteria, (c) => c.threshold), + timestamp: startedAt.toISOString(), + viewInAppUrl: getInventoryViewInAppUrlWithSpaceId({ + basePath: libs.basePath, + criteria, + nodeType, + timestamp: indexedStartedAt, + spaceId, + hostName: additionalContext?.host?.name, + }), + originalAlertState: translateActionGroupToAlertState(originalActionGroup), + originalAlertStateWasALERT: originalActionGroup === FIRED_ACTIONS_ID, + originalAlertStateWasWARNING: originalActionGroup === WARNING_ACTIONS_ID, + ...additionalContext, + }); } - ); + + const stopTime = Date.now(); + logger.debug(`Scheduled ${scheduledActionsCount} actions in ${stopTime - startTime}ms`); + + return { state: {} }; + }; const formatThreshold = (metric: SnapshotMetricType, value: number | number[]) => { const metricFormatter = get(METRIC_FORMATTERS, metric, METRIC_FORMATTERS.count); diff --git a/x-pack/plugins/observability_solution/infra/server/lib/alerting/inventory_metric_threshold/register_inventory_metric_threshold_rule_type.ts b/x-pack/plugins/observability_solution/infra/server/lib/alerting/inventory_metric_threshold/register_inventory_metric_threshold_rule_type.ts index 3b5c158f7c1f5..40070909d8d73 100644 --- a/x-pack/plugins/observability_solution/infra/server/lib/alerting/inventory_metric_threshold/register_inventory_metric_threshold_rule_type.ts +++ b/x-pack/plugins/observability_solution/infra/server/lib/alerting/inventory_metric_threshold/register_inventory_metric_threshold_rule_type.ts @@ -20,7 +20,6 @@ import { SnapshotMetricType, SnapshotMetricTypeKeys, } from '@kbn/metrics-data-access-plugin/common'; -import { MetricThresholdAlert } from '../metric_threshold/metric_threshold_executor'; import type { InfraConfig } from '../../../../common/plugin_config_types'; import { Comparator, @@ -54,6 +53,7 @@ import { createInventoryMetricThresholdExecutor, FIRED_ACTIONS, FIRED_ACTIONS_ID, + InventoryMetricThresholdAlert, WARNING_ACTIONS, } from './inventory_metric_threshold_executor'; import { MetricsRulesTypeAlertDefinition } from '../register_rule_types'; @@ -170,7 +170,7 @@ export async function registerInventoryThresholdRuleType( alerts: { ...MetricsRulesTypeAlertDefinition, shouldWrite: true, - } as IRuleTypeAlerts, + } as IRuleTypeAlerts, fieldsForAAD: O11Y_AAD_FIELDS, getViewInAppRelativeUrl: ({ rule }: GetViewInAppRelativeUrlFnOpts<{}>) => observabilityPaths.ruleDetails(rule.id), From c8d3f2e45d87608097ab11b081df94be99dab79c Mon Sep 17 00:00:00 2001 From: Ersin Erdal Date: Mon, 18 Mar 2024 13:57:12 +0100 Subject: [PATCH 6/8] omit threshold --- .../inventory_metric_threshold_executor.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/observability_solution/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts b/x-pack/plugins/observability_solution/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts index aa4f3b5a5e82f..084dec17add67 100644 --- a/x-pack/plugins/observability_solution/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts +++ b/x-pack/plugins/observability_solution/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts @@ -65,7 +65,7 @@ export type InventoryMetricThresholdAlertContext = AlertContext; // no specific export type InventoryMetricThresholdAlert = Omit< ObservabilityMetricsAlert, - 'kibana.alert.evaluation.values' + 'kibana.alert.evaluation.values' | 'kibana.alert.evaluation.threshold' > & { // Defining a custom type for this because the schema generation script doesn't allow explicit null values [ALERT_EVALUATION_VALUES]?: Array; From 9c114c29d8a2fae663c1587e4d2c495a07212bea Mon Sep 17 00:00:00 2001 From: Ersin Erdal Date: Mon, 18 Mar 2024 16:14:41 +0100 Subject: [PATCH 7/8] Fix shouldWrite --- .../inventory_metric_threshold_executor.ts | 8 ++++++-- .../register_inventory_metric_threshold_rule_type.ts | 12 ++---------- .../register_metric_threshold_rule_type.ts | 7 +------ .../infra/server/lib/alerting/register_rule_types.ts | 5 +++-- 4 files changed, 12 insertions(+), 20 deletions(-) diff --git a/x-pack/plugins/observability_solution/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts b/x-pack/plugins/observability_solution/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts index 084dec17add67..e426f48d749a8 100644 --- a/x-pack/plugins/observability_solution/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts +++ b/x-pack/plugins/observability_solution/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts @@ -303,7 +303,6 @@ export const createInventoryMetricThresholdExecutor = const payload = { [ALERT_REASON]: reason, - [ALERT_ACTION_GROUP]: actionGroup, [ALERT_EVALUATION_VALUES]: evaluationValues, [ALERT_EVALUATION_THRESHOLD]: thresholds, ...flattenAdditionalContext(additionalContext), @@ -328,7 +327,7 @@ export const createInventoryMetricThresholdExecutor = const additionalContext = getContextForRecoveredAlerts(alertHits); const originalActionGroup = getOriginalActionGroup(alertHits); - recoveredAlert.alert.setContext({ + const recoveredContext = { alertDetailsUrl: await getAlertUrl( alertUuid, spaceId, @@ -353,6 +352,11 @@ export const createInventoryMetricThresholdExecutor = originalAlertStateWasALERT: originalActionGroup === FIRED_ACTIONS_ID, originalAlertStateWasWARNING: originalActionGroup === WARNING_ACTIONS_ID, ...additionalContext, + }; + + alertsClient.setAlertData({ + id: recoveredAlertId, + context: recoveredContext, }); } diff --git a/x-pack/plugins/observability_solution/infra/server/lib/alerting/inventory_metric_threshold/register_inventory_metric_threshold_rule_type.ts b/x-pack/plugins/observability_solution/infra/server/lib/alerting/inventory_metric_threshold/register_inventory_metric_threshold_rule_type.ts index 40070909d8d73..d1fbef7a7095e 100644 --- a/x-pack/plugins/observability_solution/infra/server/lib/alerting/inventory_metric_threshold/register_inventory_metric_threshold_rule_type.ts +++ b/x-pack/plugins/observability_solution/infra/server/lib/alerting/inventory_metric_threshold/register_inventory_metric_threshold_rule_type.ts @@ -8,11 +8,7 @@ import { schema, Type } from '@kbn/config-schema'; import { i18n } from '@kbn/i18n'; import { DEFAULT_APP_CATEGORIES } from '@kbn/core/server'; -import { - GetViewInAppRelativeUrlFnOpts, - IRuleTypeAlerts, - PluginSetupContract, -} from '@kbn/alerting-plugin/server'; +import { GetViewInAppRelativeUrlFnOpts, PluginSetupContract } from '@kbn/alerting-plugin/server'; import { observabilityPaths } from '@kbn/observability-plugin/common'; import { TimeUnitChar } from '@kbn/observability-plugin/common/utils/formatters/duration'; import { @@ -53,7 +49,6 @@ import { createInventoryMetricThresholdExecutor, FIRED_ACTIONS, FIRED_ACTIONS_ID, - InventoryMetricThresholdAlert, WARNING_ACTIONS, } from './inventory_metric_threshold_executor'; import { MetricsRulesTypeAlertDefinition } from '../register_rule_types'; @@ -167,10 +162,7 @@ export async function registerInventoryThresholdRuleType( }, ], }, - alerts: { - ...MetricsRulesTypeAlertDefinition, - shouldWrite: true, - } as IRuleTypeAlerts, + alerts: MetricsRulesTypeAlertDefinition, fieldsForAAD: O11Y_AAD_FIELDS, getViewInAppRelativeUrl: ({ rule }: GetViewInAppRelativeUrlFnOpts<{}>) => observabilityPaths.ruleDetails(rule.id), diff --git a/x-pack/plugins/observability_solution/infra/server/lib/alerting/metric_threshold/register_metric_threshold_rule_type.ts b/x-pack/plugins/observability_solution/infra/server/lib/alerting/metric_threshold/register_metric_threshold_rule_type.ts index 108bf49102393..e7ea693a0e74d 100644 --- a/x-pack/plugins/observability_solution/infra/server/lib/alerting/metric_threshold/register_metric_threshold_rule_type.ts +++ b/x-pack/plugins/observability_solution/infra/server/lib/alerting/metric_threshold/register_metric_threshold_rule_type.ts @@ -11,7 +11,6 @@ import { i18n } from '@kbn/i18n'; import { ActionGroupIdsOf } from '@kbn/alerting-plugin/common'; import { GetViewInAppRelativeUrlFnOpts, - IRuleTypeAlerts, PluginSetupContract, RuleType, } from '@kbn/alerting-plugin/server'; @@ -45,7 +44,6 @@ import { FIRED_ACTIONS, WARNING_ACTIONS, NO_DATA_ACTIONS, - MetricThresholdAlert, } from './metric_threshold_executor'; import { MetricsRulesTypeAlertDefinition } from '../register_rule_types'; import { O11Y_AAD_FIELDS } from '../../../../common/constants'; @@ -199,10 +197,7 @@ export async function registerMetricThresholdRuleType( }, category: DEFAULT_APP_CATEGORIES.observability.id, producer: 'infrastructure', - alerts: { - ...MetricsRulesTypeAlertDefinition, - shouldWrite: true, - } as IRuleTypeAlerts, + alerts: MetricsRulesTypeAlertDefinition, getViewInAppRelativeUrl: ({ rule }: GetViewInAppRelativeUrlFnOpts<{}>) => observabilityPaths.ruleDetails(rule.id), }); diff --git a/x-pack/plugins/observability_solution/infra/server/lib/alerting/register_rule_types.ts b/x-pack/plugins/observability_solution/infra/server/lib/alerting/register_rule_types.ts index c26d92d5291c0..40eb1fb5c3e89 100644 --- a/x-pack/plugins/observability_solution/infra/server/lib/alerting/register_rule_types.ts +++ b/x-pack/plugins/observability_solution/infra/server/lib/alerting/register_rule_types.ts @@ -12,6 +12,7 @@ import { registerInventoryThresholdRuleType } from './inventory_metric_threshold import { registerLogThresholdRuleType } from './log_threshold/register_log_threshold_rule_type'; import { InfraBackendLibs } from '../infra_types'; import type { InfraConfig } from '../../types'; +import { MetricThresholdAlert } from './metric_threshold/metric_threshold_executor'; export const LOGS_RULES_ALERT_CONTEXT = 'observability.logs'; // Defines which alerts-as-data index logs rules will use @@ -24,12 +25,12 @@ export const LogsRulesTypeAlertDefinition: IRuleTypeAlerts = { export const METRICS_RULES_ALERT_CONTEXT = 'observability.metrics'; // Defines which alerts-as-data index metrics rules will use -export const MetricsRulesTypeAlertDefinition: IRuleTypeAlerts = { +export const MetricsRulesTypeAlertDefinition: IRuleTypeAlerts = { context: METRICS_RULES_ALERT_CONTEXT, mappings: { fieldMap: legacyExperimentalFieldMap }, useEcs: true, useLegacyAlerts: true, - shouldWrite: false, + shouldWrite: true, }; const registerRuleTypes = ( From 12a9d24ab0e592e02db6c0258d281bbe50e4bf13 Mon Sep 17 00:00:00 2001 From: Ersin Erdal Date: Tue, 19 Mar 2024 17:14:22 +0100 Subject: [PATCH 8/8] Fix nits --- .../inventory_metric_threshold_executor.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/observability_solution/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts b/x-pack/plugins/observability_solution/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts index e426f48d749a8..7124ce9db597f 100644 --- a/x-pack/plugins/observability_solution/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts +++ b/x-pack/plugins/observability_solution/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts @@ -8,7 +8,6 @@ import { i18n } from '@kbn/i18n'; import { ALERT_REASON, - ALERT_ACTION_GROUP, ALERT_EVALUATION_VALUES, ALERT_EVALUATION_THRESHOLD, } from '@kbn/rule-data-utils'; @@ -107,7 +106,7 @@ export const createInventoryMetricThresholdExecutor = const esClient = services.scopedClusterClient.asCurrentUser; - const { savedObjectsClient, alertFactory: baseAlertFactory, alertsClient } = services; + const { savedObjectsClient, alertsClient } = services; if (!alertsClient) { throw new AlertsClientError(); @@ -134,7 +133,6 @@ export const createInventoryMetricThresholdExecutor = id: UNGROUPED_FACTORY_KEY, payload: { [ALERT_REASON]: reason, - [ALERT_ACTION_GROUP]: actionGroup, }, context: { alertDetailsUrl: await getAlertUrl( @@ -196,7 +194,7 @@ export const createInventoryMetricThresholdExecutor = ); let scheduledActionsCount = 0; - const alertLimit = baseAlertFactory.alertLimit.getValue(); + const alertLimit = alertsClient.getAlertLimitValue(); let hasReachedLimit = false; const inventoryItems = Object.keys(first(results)!); for (const group of inventoryItems) {