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..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
@@ -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 = {
@@ -138,46 +132,42 @@ const mockLibs = {
},
logger,
} as unknown as InfraBackendLibs;
+const alerts = new Map();
+let services: RuleExecutorServicesMock & LifecycleAlertServices;
-const alertsServices = alertsMock.createRuleExecutorServices();
-const services: RuleExecutorServicesMock &
- LifecycleAlertServices = {
- ...alertsServices,
- ...ruleRegistryMocks.createLifecycleAlertServices(alertsServices),
-};
-
-const alertInstances = new Map();
-
-services.alertFactory.create.mockImplementation((instanceID: string) => {
- const newAlertInstance: AlertTestInstance = {
- instance: alertsMock.createAlertFactory.create(),
- actionQueue: [],
- state: {},
+const setup = () => {
+ const alertsServices = alertsMock.createRuleExecutorServices();
+ services = {
+ ...alertsServices,
+ ...ruleRegistryMocks.createLifecycleAlertServices(alertsServices),
};
- const alertInstance: AlertTestInstance = persistAlertInstances
- ? alertInstances.get(instanceID) || newAlertInstance
- : newAlertInstance;
- alertInstances.set(instanceID, alertInstance);
+ 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: {},
+ };
+ });
- (alertInstance.instance.scheduleActions as jest.Mock).mockImplementation(
- (id: string, action: any) => {
- alertInstance.actionQueue.push({ id, action });
- 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);
}
- );
-
- return alertInstance.instance;
-});
+ });
+};
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);
@@ -194,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,
@@ -219,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': {
@@ -255,13 +260,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 +310,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..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';
@@ -19,9 +18,10 @@ import {
AlertInstanceContext as AlertContext,
AlertInstanceState as AlertState,
} from '@kbn/alerting-plugin/common';
-import { Alert, 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,
@@ -42,7 +42,6 @@ import {
stateToAlertMessage,
} from '../common/messages';
import {
- AdditionalContext,
createScopedLogger,
flattenAdditionalContext,
getContextForRecoveredAlerts,
@@ -59,99 +58,85 @@ 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,
- InventoryMetricThresholdRuleTypeState,
- InventoryMetricThresholdAlertState,
- InventoryMetricThresholdAlertContext,
- InventoryMetricThresholdAllowedActionGroups
- >(
- async ({
+export type InventoryMetricThresholdAlert = Omit<
+ ObservabilityMetricsAlert,
+ '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;
+ [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 {
- 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 logger = createScopedLogger(libs.logger, 'inventoryRule', {
+ alertId: ruleId,
+ executionId,
+ });
+
+ const esClient = services.scopedClusterClient.asCurrentUser;
+
+ const { savedObjectsClient, alertsClient } = services;
+
+ if (!alertsClient) {
+ throw new AlertsClientError();
+ }
+
+ 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,
});
- if (!params.filterQuery && params.filterQueryText) {
- try {
- const { fromKueryExpression } = await import('@kbn/es-query');
- fromKueryExpression(params.filterQueryText);
- } catch (e) {
- logger.error(e.message);
- const actionGroupId = 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, {
+ const indexedStartedAt = start ?? startedAt.toISOString();
+
+ alertsClient.setAlertData({
+ id: UNGROUPED_FACTORY_KEY,
+ payload: {
+ [ALERT_REASON]: reason,
+ },
+ context: {
alertDetailsUrl: await getAlertUrl(
- alertUuid,
+ uuid,
spaceId,
indexedStartedAt,
libs.alertsLocator,
@@ -170,181 +155,139 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) =
timestamp: indexedStartedAt,
spaceId,
}),
- });
+ },
+ });
- return { state: {} };
- }
+ 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,
- })
- )
+ }
+ 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
);
- 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) {
+ 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 = alertsClient.getAlertLimitValue();
+ 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],
- buildFiredAlertReason,
- nextState === AlertStates.WARNING
- )
+ 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 (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 actionGroupId =
- 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])
- );
-
- const evaluationValues = getEvaluationValues(results, group);
- const thresholds = getThresholds(criteria);
+ }
+ if (reason) {
+ const actionGroup =
+ nextState === AlertStates.WARNING ? WARNING_ACTIONS_ID : FIRED_ACTIONS_ID;
- const alert = alertFactory(
- group,
- reason,
- actionGroupId,
- additionalContext,
- evaluationValues,
- thresholds
- );
- const indexedStartedAt = getAlertStartedDate(group) ?? startedAt.toISOString();
- const alertUuid = getAlertUuid(group);
+ const additionalContext = results && results.length > 0 ? results[0][group].context : {};
+ additionalContext.tags = Array.from(
+ new Set([...(additionalContext.tags ?? []), ...ruleTags])
+ );
- scheduledActionsCount++;
+ const evaluationValues = getEvaluationValues(results, group);
+ const thresholds = getThresholds(criteria);
- const context = {
- alertDetailsUrl: await getAlertUrl(
- alertUuid,
- spaceId,
- indexedStartedAt,
- libs.alertsLocator,
- libs.basePath.publicBaseUrl
- ),
- alertState: stateToAlertMessage[nextState],
- group,
- reason,
- metric: mapToConditionsLookup(criteria, (c) => c.metric),
- 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,
- nodeType,
- timestamp: indexedStartedAt,
- spaceId,
- hostName: additionalContext?.host?.name,
- }),
- ...additionalContext,
- };
- alert.scheduleActions(actionGroupId, context);
- }
- }
+ const { uuid, start } = alertsClient.report({
+ id: group,
+ actionGroup,
+ });
- baseAlertFactory.alertLimit.setLimitReached(hasReachedLimit);
- const { getRecoveredAlerts } = services.alertFactory.done();
- const recoveredAlerts = getRecoveredAlerts();
+ const indexedStartedAt = start ?? startedAt.toISOString();
- 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;
- const additionalContext = getContextForRecoveredAlerts(alertHits);
- const originalActionGroup = getOriginalActionGroup(alertHits);
+ scheduledActionsCount++;
- alert.setContext({
+ 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,
@@ -353,19 +296,73 @@ 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_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);
+
+ const recoveredContext = {
+ 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,
+ };
+
+ alertsClient.setAlertData({
+ id: recoveredAlertId,
+ context: recoveredContext,
+ });
}
- );
+
+ 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/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 = (