diff --git a/packages/kbn-alerts-ui-shared/src/alert_lifecycle_status_badge/index.tsx b/packages/kbn-alerts-ui-shared/src/alert_lifecycle_status_badge/index.tsx index 35519fba7dd1e..7d06bc45b5c03 100644 --- a/packages/kbn-alerts-ui-shared/src/alert_lifecycle_status_badge/index.tsx +++ b/packages/kbn-alerts-ui-shared/src/alert_lifecycle_status_badge/index.tsx @@ -9,7 +9,7 @@ import React, { memo } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiBadge, EuiBadgeProps } from '@elastic/eui'; -import { AlertStatus, ALERT_STATUS_RECOVERED } from '@kbn/rule-data-utils'; +import { AlertStatus, ALERT_STATUS_RECOVERED, ALERT_STATUS_UNTRACKED } from '@kbn/rule-data-utils'; export interface AlertLifecycleStatusBadgeProps { alertStatus: AlertStatus; @@ -37,15 +37,27 @@ const FLAPPING_LABEL = i18n.translate( } ); +const UNTRACKED_LABEL = i18n.translate( + 'alertsUIShared.components.alertLifecycleStatusBadge.untrackedLabel', + { + defaultMessage: 'Untracked', + } +); + interface BadgeProps { label: string; color: string; + isDisabled?: boolean; iconProps?: { iconType: EuiBadgeProps['iconType']; }; } const getBadgeProps = (alertStatus: AlertStatus, flapping: boolean | undefined): BadgeProps => { + if (alertStatus === ALERT_STATUS_UNTRACKED) { + return { label: UNTRACKED_LABEL, color: 'default', isDisabled: true }; + } + // Prefer recovered over flapping if (alertStatus === ALERT_STATUS_RECOVERED) { return { @@ -82,10 +94,15 @@ export const AlertLifecycleStatusBadge = memo((props: AlertLifecycleStatusBadgeP const castedFlapping = castFlapping(flapping); - const { label, color, iconProps } = getBadgeProps(alertStatus, castedFlapping); + const { label, color, iconProps, isDisabled } = getBadgeProps(alertStatus, castedFlapping); return ( - + {label} ); diff --git a/packages/kbn-rule-data-utils/src/alerts_as_data_status.ts b/packages/kbn-rule-data-utils/src/alerts_as_data_status.ts index cb36ce339e79a..d7e1ca921e23d 100644 --- a/packages/kbn-rule-data-utils/src/alerts_as_data_status.ts +++ b/packages/kbn-rule-data-utils/src/alerts_as_data_status.ts @@ -8,5 +8,9 @@ export const ALERT_STATUS_ACTIVE = 'active'; export const ALERT_STATUS_RECOVERED = 'recovered'; +export const ALERT_STATUS_UNTRACKED = 'untracked'; -export type AlertStatus = typeof ALERT_STATUS_ACTIVE | typeof ALERT_STATUS_RECOVERED; +export type AlertStatus = + | typeof ALERT_STATUS_ACTIVE + | typeof ALERT_STATUS_RECOVERED + | typeof ALERT_STATUS_UNTRACKED; diff --git a/x-pack/plugins/alerting/common/alert_summary.ts b/x-pack/plugins/alerting/common/alert_summary.ts index ed6cf325e20b1..d982f60e82bec 100644 --- a/x-pack/plugins/alerting/common/alert_summary.ts +++ b/x-pack/plugins/alerting/common/alert_summary.ts @@ -40,4 +40,5 @@ export interface AlertStatus { activeStartDate?: string; flapping: boolean; maintenanceWindowIds?: string[]; + tracked: boolean; } diff --git a/x-pack/plugins/alerting/server/alerts_client/alerts_client.test.ts b/x-pack/plugins/alerting/server/alerts_client/alerts_client.test.ts index 2b9a1e0cf0b84..174e30178638c 100644 --- a/x-pack/plugins/alerting/server/alerts_client/alerts_client.test.ts +++ b/x-pack/plugins/alerting/server/alerts_client/alerts_client.test.ts @@ -3026,6 +3026,53 @@ describe('Alerts Client', () => { expect(recoveredAlert.hit).toBeUndefined(); }); }); + + describe('setAlertStatusToUntracked()', () => { + test('should call updateByQuery on provided ruleIds', async () => { + const alertsClient = new AlertsClient<{}, {}, {}, 'default', 'recovered'>( + alertsClientParams + ); + + const opts = { + maxAlerts, + ruleLabel: `test: rule-name`, + flappingSettings: DEFAULT_FLAPPING_SETTINGS, + activeAlertsFromState: {}, + recoveredAlertsFromState: {}, + }; + await alertsClient.initializeExecution(opts); + + await alertsClient.setAlertStatusToUntracked(['test-index'], ['test-rule']); + + expect(clusterClient.updateByQuery).toHaveBeenCalledTimes(1); + }); + + test('should retry updateByQuery on failure', async () => { + clusterClient.updateByQuery.mockResponseOnce({ + total: 10, + updated: 8, + }); + const alertsClient = new AlertsClient<{}, {}, {}, 'default', 'recovered'>( + alertsClientParams + ); + + const opts = { + maxAlerts, + ruleLabel: `test: rule-name`, + flappingSettings: DEFAULT_FLAPPING_SETTINGS, + activeAlertsFromState: {}, + recoveredAlertsFromState: {}, + }; + await alertsClient.initializeExecution(opts); + + await alertsClient.setAlertStatusToUntracked(['test-index'], ['test-rule']); + + expect(clusterClient.updateByQuery).toHaveBeenCalledTimes(2); + expect(logger.warn).toHaveBeenCalledWith( + 'Attempt 1: Failed to untrack 2 of 10; indices test-index, ruleIds test-rule' + ); + }); + }); }); } }); diff --git a/x-pack/plugins/alerting/server/alerts_client/alerts_client.ts b/x-pack/plugins/alerting/server/alerts_client/alerts_client.ts index 90fbba5969de8..0122aff79818f 100644 --- a/x-pack/plugins/alerting/server/alerts_client/alerts_client.ts +++ b/x-pack/plugins/alerting/server/alerts_client/alerts_client.ts @@ -6,7 +6,13 @@ */ import { ElasticsearchClient } from '@kbn/core/server'; -import { ALERT_RULE_UUID, ALERT_UUID } from '@kbn/rule-data-utils'; +import { + ALERT_RULE_UUID, + ALERT_STATUS, + ALERT_STATUS_UNTRACKED, + ALERT_STATUS_ACTIVE, + ALERT_UUID, +} from '@kbn/rule-data-utils'; import { chunk, flatMap, isEmpty, keys } from 'lodash'; import { SearchRequest } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type { Alert } from '@kbn/alerts-as-data-utils'; @@ -198,6 +204,51 @@ export class AlertsClient< return { hits, total }; } + public async setAlertStatusToUntracked(indices: string[], ruleIds: string[]) { + const esClient = await this.options.elasticsearchClientPromise; + const terms: Array<{ term: Record }> = ruleIds.map((ruleId) => ({ + term: { + [ALERT_RULE_UUID]: { value: ruleId }, + }, + })); + terms.push({ + term: { + [ALERT_STATUS]: { value: ALERT_STATUS_ACTIVE }, + }, + }); + + try { + // Retry this updateByQuery up to 3 times to make sure the number of documents + // updated equals the number of documents matched + for (let retryCount = 0; retryCount < 3; retryCount++) { + const response = await esClient.updateByQuery({ + index: indices, + allow_no_indices: true, + body: { + conflicts: 'proceed', + script: { + source: UNTRACK_UPDATE_PAINLESS_SCRIPT, + lang: 'painless', + }, + query: { + bool: { + must: terms, + }, + }, + }, + }); + if (response.total === response.updated) break; + this.options.logger.warn( + `Attempt ${retryCount + 1}: Failed to untrack ${ + (response.total ?? 0) - (response.updated ?? 0) + } of ${response.total}; indices ${indices}, ruleIds ${ruleIds}` + ); + } + } catch (err) { + this.options.logger.error(`Error marking ${ruleIds} as untracked - ${err.message}`); + } + } + public report( alert: ReportedAlert< AlertData, @@ -562,3 +613,11 @@ export class AlertsClient< return this._isUsingDataStreams; } } + +const UNTRACK_UPDATE_PAINLESS_SCRIPT = ` +// Certain rule types don't flatten their AAD values, apply the ALERT_STATUS key to them directly +if (!ctx._source.containsKey('${ALERT_STATUS}') || ctx._source['${ALERT_STATUS}'].empty) { + ctx._source.${ALERT_STATUS} = '${ALERT_STATUS_UNTRACKED}'; +} else { + ctx._source['${ALERT_STATUS}'] = '${ALERT_STATUS_UNTRACKED}' +}`; diff --git a/x-pack/plugins/alerting/server/alerts_client/legacy_alerts_client.ts b/x-pack/plugins/alerting/server/alerts_client/legacy_alerts_client.ts index b7c17ee9579a8..26f7171a39686 100644 --- a/x-pack/plugins/alerting/server/alerts_client/legacy_alerts_client.ts +++ b/x-pack/plugins/alerting/server/alerts_client/legacy_alerts_client.ts @@ -232,4 +232,8 @@ export class LegacyAlertsClient< } public async persistAlerts() {} + + public async setAlertStatusToUntracked() { + return; + } } diff --git a/x-pack/plugins/alerting/server/alerts_client/types.ts b/x-pack/plugins/alerting/server/alerts_client/types.ts index eccd381bc2a5c..94adde2892623 100644 --- a/x-pack/plugins/alerting/server/alerts_client/types.ts +++ b/x-pack/plugins/alerting/server/alerts_client/types.ts @@ -63,6 +63,7 @@ export interface IAlertsClient< alertsToReturn: Record; recoveredAlertsToReturn: Record; }; + setAlertStatusToUntracked(indices: string[], ruleIds: string[]): Promise; factory(): PublicAlertFactory< State, Context, diff --git a/x-pack/plugins/alerting/server/application/rule/methods/aggregate/aggregate_rules.test.ts b/x-pack/plugins/alerting/server/application/rule/methods/aggregate/aggregate_rules.test.ts index 9bdac512cd28c..fd9daa3cb7356 100644 --- a/x-pack/plugins/alerting/server/application/rule/methods/aggregate/aggregate_rules.test.ts +++ b/x-pack/plugins/alerting/server/application/rule/methods/aggregate/aggregate_rules.test.ts @@ -56,6 +56,8 @@ const rulesClientParams: jest.Mocked = { kibanaVersion, isAuthenticationTypeAPIKey: jest.fn(), getAuthenticationAPIKey: jest.fn(), + getAlertIndicesAlias: jest.fn(), + alertsService: null, maxScheduledPerMinute: 1000, internalSavedObjectsRepository, }; diff --git a/x-pack/plugins/alerting/server/application/rule/methods/bulk_delete/bulk_delete_rules.test.ts b/x-pack/plugins/alerting/server/application/rule/methods/bulk_delete/bulk_delete_rules.test.ts index c33cd867a1be8..611aca68c81c3 100644 --- a/x-pack/plugins/alerting/server/application/rule/methods/bulk_delete/bulk_delete_rules.test.ts +++ b/x-pack/plugins/alerting/server/application/rule/methods/bulk_delete/bulk_delete_rules.test.ts @@ -79,6 +79,8 @@ const rulesClientParams: jest.Mocked = { minimumScheduleInterval: { value: '1m', enforce: false }, isAuthenticationTypeAPIKey: jest.fn(), getAuthenticationAPIKey: jest.fn(), + getAlertIndicesAlias: jest.fn(), + alertsService: null, }; const getBulkOperationStatusErrorResponse = (statusCode: number) => ({ diff --git a/x-pack/plugins/alerting/server/application/rule/methods/bulk_edit/bulk_edit_rules.test.ts b/x-pack/plugins/alerting/server/application/rule/methods/bulk_edit/bulk_edit_rules.test.ts index 1a2faed2e0e66..e5d7e0c8db43a 100644 --- a/x-pack/plugins/alerting/server/application/rule/methods/bulk_edit/bulk_edit_rules.test.ts +++ b/x-pack/plugins/alerting/server/application/rule/methods/bulk_edit/bulk_edit_rules.test.ts @@ -102,6 +102,8 @@ const rulesClientParams: jest.Mocked = { minimumScheduleInterval: { value: '1m', enforce: false }, isAuthenticationTypeAPIKey: isAuthenticationTypeApiKeyMock, getAuthenticationAPIKey: getAuthenticationApiKeyMock, + getAlertIndicesAlias: jest.fn(), + alertsService: null, }; const paramsModifier = jest.fn(); diff --git a/x-pack/plugins/alerting/server/application/rule/methods/create/create_rule.test.ts b/x-pack/plugins/alerting/server/application/rule/methods/create/create_rule.test.ts index 2d7746f715a05..41ca041e89a0b 100644 --- a/x-pack/plugins/alerting/server/application/rule/methods/create/create_rule.test.ts +++ b/x-pack/plugins/alerting/server/application/rule/methods/create/create_rule.test.ts @@ -82,6 +82,8 @@ const rulesClientParams: jest.Mocked = { minimumScheduleInterval: { value: '1m', enforce: false }, isAuthenticationTypeAPIKey: jest.fn(), getAuthenticationAPIKey: jest.fn(), + getAlertIndicesAlias: jest.fn(), + alertsService: null, }; beforeEach(() => { diff --git a/x-pack/plugins/alerting/server/application/rule/methods/get_schedule_frequency/get_schedule_frequency.test.ts b/x-pack/plugins/alerting/server/application/rule/methods/get_schedule_frequency/get_schedule_frequency.test.ts index cbd1476e111a1..d23e4b3a7dd54 100644 --- a/x-pack/plugins/alerting/server/application/rule/methods/get_schedule_frequency/get_schedule_frequency.test.ts +++ b/x-pack/plugins/alerting/server/application/rule/methods/get_schedule_frequency/get_schedule_frequency.test.ts @@ -53,6 +53,8 @@ const rulesClientParams: jest.Mocked = { minimumScheduleInterval: { value: '1m', enforce: false }, isAuthenticationTypeAPIKey: jest.fn(), getAuthenticationAPIKey: jest.fn(), + getAlertIndicesAlias: jest.fn(), + alertsService: null, }; const getMockAggregationResult = ( diff --git a/x-pack/plugins/alerting/server/lib/alert_summary_from_event_log.test.ts b/x-pack/plugins/alerting/server/lib/alert_summary_from_event_log.test.ts index 45f8d84fd4a98..a4bdc2f88bfa0 100644 --- a/x-pack/plugins/alerting/server/lib/alert_summary_from_event_log.test.ts +++ b/x-pack/plugins/alerting/server/lib/alert_summary_from_event_log.test.ts @@ -127,6 +127,7 @@ describe('alertSummaryFromEventLog', () => { "flapping": false, "muted": true, "status": "OK", + "tracked": true, "uuid": undefined, }, "alert-2": Object { @@ -135,6 +136,7 @@ describe('alertSummaryFromEventLog', () => { "flapping": false, "muted": true, "status": "OK", + "tracked": true, "uuid": undefined, }, }, @@ -241,6 +243,7 @@ describe('alertSummaryFromEventLog', () => { "flapping": false, "muted": false, "status": "OK", + "tracked": true, "uuid": "uuid-1", }, }, @@ -283,6 +286,7 @@ describe('alertSummaryFromEventLog', () => { "flapping": false, "muted": false, "status": "OK", + "tracked": true, "uuid": "uuid-1", }, }, @@ -324,6 +328,7 @@ describe('alertSummaryFromEventLog', () => { "flapping": false, "muted": false, "status": "OK", + "tracked": true, "uuid": "uuid-1", }, }, @@ -366,6 +371,7 @@ describe('alertSummaryFromEventLog', () => { "flapping": false, "muted": false, "status": "Active", + "tracked": true, "uuid": "uuid-1", }, }, @@ -408,6 +414,7 @@ describe('alertSummaryFromEventLog', () => { "flapping": false, "muted": false, "status": "Active", + "tracked": true, "uuid": "uuid-1", }, }, @@ -450,6 +457,7 @@ describe('alertSummaryFromEventLog', () => { "flapping": false, "muted": false, "status": "Active", + "tracked": true, "uuid": "uuid-1", }, }, @@ -490,6 +498,7 @@ describe('alertSummaryFromEventLog', () => { "flapping": false, "muted": false, "status": "Active", + "tracked": true, "uuid": "uuid-1", }, }, @@ -534,6 +543,7 @@ describe('alertSummaryFromEventLog', () => { "flapping": false, "muted": true, "status": "Active", + "tracked": true, "uuid": "uuid-1", }, "alert-2": Object { @@ -542,6 +552,7 @@ describe('alertSummaryFromEventLog', () => { "flapping": false, "muted": true, "status": "OK", + "tracked": true, "uuid": "uuid-2", }, }, @@ -593,6 +604,7 @@ describe('alertSummaryFromEventLog', () => { "flapping": false, "muted": false, "status": "Active", + "tracked": true, "uuid": "uuid-1", }, "alert-2": Object { @@ -601,6 +613,7 @@ describe('alertSummaryFromEventLog', () => { "flapping": false, "muted": false, "status": "OK", + "tracked": true, "uuid": "uuid-2", }, }, @@ -639,6 +652,7 @@ describe('alertSummaryFromEventLog', () => { "flapping": true, "muted": false, "status": "Active", + "tracked": true, "uuid": "uuid-1", }, }, diff --git a/x-pack/plugins/alerting/server/lib/alert_summary_from_event_log.ts b/x-pack/plugins/alerting/server/lib/alert_summary_from_event_log.ts index d316b1b5bf01c..e20b92370bbc6 100644 --- a/x-pack/plugins/alerting/server/lib/alert_summary_from_event_log.ts +++ b/x-pack/plugins/alerting/server/lib/alert_summary_from_event_log.ts @@ -105,6 +105,8 @@ export function alertSummaryFromEventLog(params: AlertSummaryFromEventLogParams) status.activeStartDate = undefined; status.actionGroupId = undefined; } + + status.tracked = action !== EVENT_LOG_ACTIONS.untrackedInstance; } for (const event of executionEvents.reverse()) { @@ -169,6 +171,7 @@ function getAlertStatus( actionGroupId: undefined, activeStartDate: undefined, flapping: false, + tracked: true, }; alerts.set(alertId, status); return status; diff --git a/x-pack/plugins/alerting/server/plugin.ts b/x-pack/plugins/alerting/server/plugin.ts index e2e98347d88fe..e9590f883cc53 100644 --- a/x-pack/plugins/alerting/server/plugin.ts +++ b/x-pack/plugins/alerting/server/plugin.ts @@ -111,6 +111,7 @@ export const EVENT_LOG_ACTIONS = { recoveredInstance: 'recovered-instance', activeInstance: 'active-instance', executeTimeout: 'execute-timeout', + untrackedInstance: 'untracked-instance', }; export const LEGACY_EVENT_LOG_ACTIONS = { resolvedInstance: 'resolved-instance', @@ -494,6 +495,8 @@ export class AlertingPlugin { eventLogger: this.eventLogger, minimumScheduleInterval: this.config.rules.minimumScheduleInterval, maxScheduledPerMinute: this.config.rules.maxScheduledPerMinute, + getAlertIndicesAlias: createGetAlertIndicesAliasFn(this.ruleTypeRegistry!), + alertsService: this.alertsService, }); rulesSettingsClientFactory.initialize({ diff --git a/x-pack/plugins/alerting/server/rules_client/lib/create_new_api_key_set.test.ts b/x-pack/plugins/alerting/server/rules_client/lib/create_new_api_key_set.test.ts index 0548c5f7fc783..ae5bf3f759194 100644 --- a/x-pack/plugins/alerting/server/rules_client/lib/create_new_api_key_set.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/lib/create_new_api_key_set.test.ts @@ -52,6 +52,8 @@ const rulesClientParams: jest.Mocked = { fieldsToExcludeFromPublicApi: [], isAuthenticationTypeAPIKey: jest.fn(), getAuthenticationAPIKey: jest.fn(), + getAlertIndicesAlias: jest.fn(), + alertsService: null, }; const username = 'test'; diff --git a/x-pack/plugins/alerting/server/rules_client/lib/index.ts b/x-pack/plugins/alerting/server/rules_client/lib/index.ts index 0dcaa31fe51a1..ab1f33df01c10 100644 --- a/x-pack/plugins/alerting/server/rules_client/lib/index.ts +++ b/x-pack/plugins/alerting/server/rules_client/lib/index.ts @@ -14,7 +14,7 @@ export { getAuthorizationFilter } from './get_authorization_filter'; export { checkAuthorizationAndGetTotal } from './check_authorization_and_get_total'; export { scheduleTask } from './schedule_task'; export { createNewAPIKeySet } from './create_new_api_key_set'; -export { recoverRuleAlerts } from './recover_rule_alerts'; +export { untrackRuleAlerts } from './untrack_rule_alerts'; export { migrateLegacyActions } from './siem_legacy_actions/migrate_legacy_actions'; export { formatLegacyActions } from './siem_legacy_actions/format_legacy_actions'; export { addGeneratedActionValues } from './add_generated_action_values'; diff --git a/x-pack/plugins/alerting/server/rules_client/lib/recover_rule_alerts.ts b/x-pack/plugins/alerting/server/rules_client/lib/untrack_rule_alerts.ts similarity index 51% rename from x-pack/plugins/alerting/server/rules_client/lib/recover_rule_alerts.ts rename to x-pack/plugins/alerting/server/rules_client/lib/untrack_rule_alerts.ts index 9648f23dc05d2..7eaee0e468e1c 100644 --- a/x-pack/plugins/alerting/server/rules_client/lib/recover_rule_alerts.ts +++ b/x-pack/plugins/alerting/server/rules_client/lib/untrack_rule_alerts.ts @@ -15,40 +15,50 @@ import { EVENT_LOG_ACTIONS } from '../../plugin'; import { createAlertEventLogRecordObject } from '../../lib/create_alert_event_log_record_object'; import { RulesClientContext } from '../types'; -export const recoverRuleAlerts = async ( +export const untrackRuleAlerts = async ( context: RulesClientContext, id: string, attributes: RawRule ) => { - return withSpan({ name: 'recoverRuleAlerts', type: 'rules' }, async () => { + return withSpan({ name: 'untrackRuleAlerts', type: 'rules' }, async () => { if (!context.eventLogger || !attributes.scheduledTaskId) return; try { - const { state } = taskInstanceToAlertTaskInstance( + const taskInstance = taskInstanceToAlertTaskInstance( await context.taskManager.get(attributes.scheduledTaskId), attributes as unknown as SanitizedRule ); - const recoveredAlerts = mapValues, Alert>( + const { state } = taskInstance; + + const untrackedAlerts = mapValues, Alert>( state.alertInstances ?? {}, (rawAlertInstance, alertId) => new Alert(alertId, rawAlertInstance) ); - const recoveredAlertIds = Object.keys(recoveredAlerts); - for (const alertId of recoveredAlertIds) { - const { group: actionGroup } = recoveredAlerts[alertId].getLastScheduledActions() ?? {}; - const instanceState = recoveredAlerts[alertId].getState(); - const message = `instance '${alertId}' has recovered due to the rule was disabled`; - const alertUuid = recoveredAlerts[alertId].getUuid(); + const untrackedAlertIds = Object.keys(untrackedAlerts); + + const ruleType = context.ruleTypeRegistry.get(attributes.alertTypeId); + + const { autoRecoverAlerts: isLifecycleAlert } = ruleType; + + // Untrack Stack alerts + // TODO: Replace this loop with an Alerts As Data implmentation when Stack Rules use Alerts As Data + // instead of the Kibana Event Log + for (const alertId of untrackedAlertIds) { + const { group: actionGroup } = untrackedAlerts[alertId].getLastScheduledActions() ?? {}; + const instanceState = untrackedAlerts[alertId].getState(); + const message = `instance '${alertId}' has been untracked because the rule was disabled`; + const alertUuid = untrackedAlerts[alertId].getUuid(); const event = createAlertEventLogRecordObject({ ruleId: id, ruleName: attributes.name, ruleRevision: attributes.revision, - ruleType: context.ruleTypeRegistry.get(attributes.alertTypeId), + ruleType, consumer: attributes.consumer, instanceId: alertId, alertUuid, - action: EVENT_LOG_ACTIONS.recoveredInstance, + action: EVENT_LOG_ACTIONS.untrackedInstance, message, state: instanceState, group: actionGroup, @@ -65,10 +75,32 @@ export const recoverRuleAlerts = async ( }); context.eventLogger.logEvent(event); } + + // Untrack Lifecycle alerts (Alerts As Data-enabled) + if (isLifecycleAlert) { + const alertsClient = await context.alertsService?.createAlertsClient({ + namespace: context.namespace!, + rule: { + id, + name: attributes.name, + consumer: attributes.consumer, + revision: attributes.revision, + spaceId: context.spaceId, + tags: attributes.tags, + parameters: attributes.parameters, + executionId: '', + }, + ruleType, + logger: context.logger, + }); + if (!alertsClient) throw new Error('Could not create alertsClient'); + const indices = context.getAlertIndicesAlias([ruleType.id], context.spaceId); + await alertsClient.setAlertStatusToUntracked(indices, [id]); + } } catch (error) { // this should not block the rest of the disable process context.logger.warn( - `rulesClient.disable('${id}') - Could not write recovery events - ${error.message}` + `rulesClient.disable('${id}') - Could not write untrack events - ${error.message}` ); } }); diff --git a/x-pack/plugins/alerting/server/rules_client/methods/bulk_disable.ts b/x-pack/plugins/alerting/server/rules_client/methods/bulk_disable.ts index 30b8b1eb01bba..d409d69f8a6ac 100644 --- a/x-pack/plugins/alerting/server/rules_client/methods/bulk_disable.ts +++ b/x-pack/plugins/alerting/server/rules_client/methods/bulk_disable.ts @@ -22,7 +22,7 @@ import { getAuthorizationFilter, checkAuthorizationAndGetTotal, getAlertFromRaw, - recoverRuleAlerts, + untrackRuleAlerts, updateMeta, migrateLegacyActions, } from '../lib'; @@ -58,11 +58,12 @@ export const bulkDisableRules = async (context: RulesClientContext, options: Bul }) ); - const [taskIdsToDisable, taskIdsToDelete] = accListSpecificForBulkOperation; + const [taskIdsToDisable, taskIdsToDelete, taskIdsToClearState] = accListSpecificForBulkOperation; await Promise.allSettled([ tryToDisableTasks({ taskIdsToDisable, + taskIdsToClearState, logger: context.logger, taskManager: context.taskManager, }), @@ -114,7 +115,7 @@ const bulkDisableRulesWithOCC = async ( for await (const response of rulesFinder.find()) { await pMap(response.saved_objects, async (rule) => { try { - await recoverRuleAlerts(context, rule.id, rule.attributes); + await untrackRuleAlerts(context, rule.id, rule.attributes); if (rule.attributes.name) { ruleNameToRuleIdMapping[rule.id] = rule.attributes.name; @@ -193,6 +194,7 @@ const bulkDisableRulesWithOCC = async ( const taskIdsToDisable: string[] = []; const taskIdsToDelete: string[] = []; + const taskIdsToClearState: string[] = []; const disabledRules: Array> = []; result.saved_objects.forEach((rule) => { @@ -202,6 +204,12 @@ const bulkDisableRulesWithOCC = async ( taskIdsToDelete.push(rule.attributes.scheduledTaskId); } else { taskIdsToDisable.push(rule.attributes.scheduledTaskId); + if (rule.attributes.alertTypeId) { + const { autoRecoverAlerts: isLifecycleAlert } = context.ruleTypeRegistry.get( + rule.attributes.alertTypeId + ); + if (isLifecycleAlert) taskIdsToClearState.push(rule.attributes.scheduledTaskId); + } } } disabledRules.push(rule); @@ -221,23 +229,28 @@ const bulkDisableRulesWithOCC = async ( errors, // TODO: delete the casting when we do versioning of bulk disable api rules: disabledRules as Array>, - accListSpecificForBulkOperation: [taskIdsToDisable, taskIdsToDelete], + accListSpecificForBulkOperation: [taskIdsToDisable, taskIdsToDelete, taskIdsToClearState], }; }; const tryToDisableTasks = async ({ taskIdsToDisable, + taskIdsToClearState, logger, taskManager, }: { taskIdsToDisable: string[]; + taskIdsToClearState: string[]; logger: Logger; taskManager: TaskManagerStartContract; }) => { return await withSpan({ name: 'taskManager.bulkDisable', type: 'rules' }, async () => { if (taskIdsToDisable.length > 0) { try { - const resultFromDisablingTasks = await taskManager.bulkDisable(taskIdsToDisable); + const resultFromDisablingTasks = await taskManager.bulkDisable( + taskIdsToDisable, + taskIdsToClearState + ); if (resultFromDisablingTasks.tasks.length) { logger.debug( `Successfully disabled schedules for underlying tasks: ${resultFromDisablingTasks.tasks diff --git a/x-pack/plugins/alerting/server/rules_client/methods/disable.ts b/x-pack/plugins/alerting/server/rules_client/methods/disable.ts index 1382b7d14b111..88ffd510e8800 100644 --- a/x-pack/plugins/alerting/server/rules_client/methods/disable.ts +++ b/x-pack/plugins/alerting/server/rules_client/methods/disable.ts @@ -11,7 +11,7 @@ import { WriteOperations, AlertingAuthorizationEntity } from '../../authorizatio import { retryIfConflicts } from '../../lib/retry_if_conflicts'; import { ruleAuditEvent, RuleAuditAction } from '../common/audit_events'; import { RulesClientContext } from '../types'; -import { recoverRuleAlerts, updateMeta, migrateLegacyActions } from '../lib'; +import { untrackRuleAlerts, updateMeta, migrateLegacyActions } from '../lib'; export async function disable(context: RulesClientContext, { id }: { id: string }): Promise { return await retryIfConflicts( @@ -43,7 +43,7 @@ async function disableWithOCC(context: RulesClientContext, { id }: { id: string references = alert.references; } - await recoverRuleAlerts(context, id, attributes); + await untrackRuleAlerts(context, id, attributes); try { await context.authorization.ensureAuthorized({ @@ -102,6 +102,9 @@ async function disableWithOCC(context: RulesClientContext, { id }: { id: string : {}), } ); + const { autoRecoverAlerts: isLifecycleAlert } = context.ruleTypeRegistry.get( + attributes.alertTypeId + ); // If the scheduledTaskId does not match the rule id, we should // remove the task, otherwise mark the task as disabled @@ -109,7 +112,10 @@ async function disableWithOCC(context: RulesClientContext, { id }: { id: string if (attributes.scheduledTaskId !== id) { await context.taskManager.removeIfExists(attributes.scheduledTaskId); } else { - await context.taskManager.bulkDisable([attributes.scheduledTaskId]); + await context.taskManager.bulkDisable( + [attributes.scheduledTaskId], + Boolean(isLifecycleAlert) + ); } } } diff --git a/x-pack/plugins/alerting/server/rules_client/tests/bulk_disable.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/bulk_disable.test.ts index c4d7e6eb4f755..966a87c695d10 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/bulk_disable.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/bulk_disable.test.ts @@ -87,6 +87,8 @@ const rulesClientParams: jest.Mocked = { minimumScheduleInterval: { value: '1m', enforce: false }, isAuthenticationTypeAPIKey: jest.fn(), getAuthenticationAPIKey: jest.fn(), + getAlertIndicesAlias: jest.fn(), + alertsService: null, }; beforeEach(() => { @@ -251,7 +253,7 @@ describe('bulkDisableRules', () => { expect(unsecuredSavedObjectsClient.bulkCreate).toHaveBeenCalledTimes(4); expect(taskManager.bulkDisable).toHaveBeenCalledTimes(1); - expect(taskManager.bulkDisable).toHaveBeenCalledWith(['id1']); + expect(taskManager.bulkDisable).toHaveBeenCalledWith(['id1'], []); expect(result).toStrictEqual({ errors: [{ message: 'UPS', rule: { id: 'id2', name: 'fakeName' }, status: 409 }], rules: [returnedDisabledRule1], @@ -388,7 +390,7 @@ describe('bulkDisableRules', () => { await rulesClient.bulkDisableRules({ filter: 'fake_filter' }); expect(taskManager.bulkDisable).toHaveBeenCalledTimes(1); - expect(taskManager.bulkDisable).toHaveBeenCalledWith(['id1', 'id2']); + expect(taskManager.bulkDisable).toHaveBeenCalledWith(['id1', 'id2'], []); expect(logger.debug).toBeCalledTimes(1); expect(logger.debug).toBeCalledWith( @@ -451,7 +453,7 @@ describe('bulkDisableRules', () => { await rulesClient.bulkDisableRules({ filter: 'fake_filter' }); expect(taskManager.bulkDisable).toHaveBeenCalledTimes(1); - expect(taskManager.bulkDisable).toHaveBeenCalledWith(['id1']); + expect(taskManager.bulkDisable).toHaveBeenCalledWith(['id1'], []); expect(logger.debug).toBeCalledTimes(1); expect(logger.debug).toBeCalledWith( @@ -477,7 +479,7 @@ describe('bulkDisableRules', () => { await rulesClient.bulkDisableRules({ filter: 'fake_filter' }); expect(taskManager.bulkDisable).toHaveBeenCalledTimes(1); - expect(taskManager.bulkDisable).toHaveBeenCalledWith(['id1']); + expect(taskManager.bulkDisable).toHaveBeenCalledWith(['id1'], []); }); test('should not throw an error if taskManager.bulkDisable throw an error', async () => { @@ -611,7 +613,7 @@ describe('bulkDisableRules', () => { expect(logger.warn).toHaveBeenCalledTimes(2); expect(logger.warn).toHaveBeenLastCalledWith( - "rulesClient.disable('id2') - Could not write recovery events - UPS" + "rulesClient.disable('id2') - Could not write untrack events - UPS" ); }); }); diff --git a/x-pack/plugins/alerting/server/rules_client/tests/bulk_enable.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/bulk_enable.test.ts index acba28bf20ac6..af03c5908daff 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/bulk_enable.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/bulk_enable.test.ts @@ -82,6 +82,8 @@ const rulesClientParams: jest.Mocked = { minimumScheduleInterval: { value: '1m', enforce: false }, isAuthenticationTypeAPIKey: jest.fn(), getAuthenticationAPIKey: jest.fn(), + getAlertIndicesAlias: jest.fn(), + alertsService: null, }; beforeEach(() => { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/clear_expired_snoozes.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/clear_expired_snoozes.test.ts index efebe91a90272..b64f192045511 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/clear_expired_snoozes.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/clear_expired_snoozes.test.ts @@ -69,6 +69,8 @@ const rulesClientParams: jest.Mocked = { eventLogger, isAuthenticationTypeAPIKey: jest.fn(), getAuthenticationAPIKey: jest.fn(), + getAlertIndicesAlias: jest.fn(), + alertsService: null, }; describe('clearExpiredSnoozes()', () => { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/delete.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/delete.test.ts index ece860c63c30e..a4e581414744a 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/delete.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/delete.test.ts @@ -71,6 +71,8 @@ const rulesClientParams: jest.Mocked = { auditLogger, isAuthenticationTypeAPIKey: jest.fn(), getAuthenticationAPIKey: jest.fn(), + getAlertIndicesAlias: jest.fn(), + alertsService: null, }; beforeEach(() => { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/disable.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/disable.test.ts index 23b23b607cd04..ca6c242539a9a 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/disable.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/disable.test.ts @@ -73,6 +73,8 @@ const rulesClientParams: jest.Mocked = { eventLogger, isAuthenticationTypeAPIKey: jest.fn(), getAuthenticationAPIKey: jest.fn(), + getAlertIndicesAlias: jest.fn(), + alertsService: null, }; beforeEach(() => { @@ -255,11 +257,11 @@ describe('disable()', () => { version: '123', } ); - expect(taskManager.bulkDisable).toHaveBeenCalledWith(['1']); + expect(taskManager.bulkDisable).toHaveBeenCalledWith(['1'], false); expect(taskManager.removeIfExists).not.toHaveBeenCalledWith(); }); - test('disables the rule with calling event log to "recover" the alert instances from the task state', async () => { + test('disables the rule with calling event log to untrack the alert instances from the task state', async () => { const scheduledTaskId = '1'; taskManager.get.mockResolvedValue({ id: scheduledTaskId, @@ -329,13 +331,13 @@ describe('disable()', () => { version: '123', } ); - expect(taskManager.bulkDisable).toHaveBeenCalledWith(['1']); + expect(taskManager.bulkDisable).toHaveBeenCalledWith(['1'], false); expect(taskManager.removeIfExists).not.toHaveBeenCalledWith(); expect(eventLogger.logEvent).toHaveBeenCalledTimes(1); expect(eventLogger.logEvent.mock.calls[0][0]).toStrictEqual({ event: { - action: 'recovered-instance', + action: 'untracked-instance', category: ['alerts'], kind: 'alert', }, @@ -363,7 +365,7 @@ describe('disable()', () => { ], space_ids: ['default'], }, - message: "instance '1' has recovered due to the rule was disabled", + message: "instance '1' has been untracked because the rule was disabled", rule: { category: '123', id: '1', @@ -373,7 +375,7 @@ describe('disable()', () => { }); }); - test('disables the rule even if unable to retrieve task manager doc to generate recovery event log events', async () => { + test('disables the rule even if unable to retrieve task manager doc to generate untrack event log events', async () => { taskManager.get.mockRejectedValueOnce(new Error('Fail')); await rulesClient.disable({ id: '1' }); expect(unsecuredSavedObjectsClient.get).not.toHaveBeenCalled(); @@ -414,12 +416,12 @@ describe('disable()', () => { version: '123', } ); - expect(taskManager.bulkDisable).toHaveBeenCalledWith(['1']); + expect(taskManager.bulkDisable).toHaveBeenCalledWith(['1'], false); expect(taskManager.removeIfExists).not.toHaveBeenCalledWith(); expect(eventLogger.logEvent).toHaveBeenCalledTimes(0); expect(rulesClientParams.logger.warn).toHaveBeenCalledWith( - `rulesClient.disable('1') - Could not write recovery events - Fail` + `rulesClient.disable('1') - Could not write untrack events - Fail` ); }); @@ -459,7 +461,7 @@ describe('disable()', () => { version: '123', } ); - expect(taskManager.bulkDisable).toHaveBeenCalledWith(['1']); + expect(taskManager.bulkDisable).toHaveBeenCalledWith(['1'], false); expect(taskManager.removeIfExists).not.toHaveBeenCalledWith(); }); diff --git a/x-pack/plugins/alerting/server/rules_client/tests/enable.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/enable.test.ts index e7e09c55a7920..9ab3a919f01a5 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/enable.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/enable.test.ts @@ -70,6 +70,8 @@ const rulesClientParams: jest.Mocked = { auditLogger, isAuthenticationTypeAPIKey: jest.fn(), getAuthenticationAPIKey: jest.fn(), + getAlertIndicesAlias: jest.fn(), + alertsService: null, }; setGlobalDate(); diff --git a/x-pack/plugins/alerting/server/rules_client/tests/find.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/find.test.ts index 3d697b08dc2f1..e7e737b635f28 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/find.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/find.test.ts @@ -63,6 +63,8 @@ const rulesClientParams: jest.Mocked = { kibanaVersion, isAuthenticationTypeAPIKey: jest.fn(), getAuthenticationAPIKey: jest.fn(), + getAlertIndicesAlias: jest.fn(), + alertsService: null, }; beforeEach(() => { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/get.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/get.test.ts index 36a5510a03a3b..5e98fcfee3721 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/get.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/get.test.ts @@ -60,6 +60,8 @@ const rulesClientParams: jest.Mocked = { kibanaVersion, isAuthenticationTypeAPIKey: jest.fn(), getAuthenticationAPIKey: jest.fn(), + getAlertIndicesAlias: jest.fn(), + alertsService: null, }; beforeEach(() => { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/get_action_error_log.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/get_action_error_log.test.ts index d8069721c4a5c..0fe46a07e4e7f 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/get_action_error_log.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/get_action_error_log.test.ts @@ -59,6 +59,8 @@ const rulesClientParams: jest.Mocked = { auditLogger, isAuthenticationTypeAPIKey: jest.fn(), getAuthenticationAPIKey: jest.fn(), + getAlertIndicesAlias: jest.fn(), + alertsService: null, }; beforeEach(() => { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/get_alert_state.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/get_alert_state.test.ts index 644cc6190f605..dee5cb2ab9a81 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/get_alert_state.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/get_alert_state.test.ts @@ -51,6 +51,8 @@ const rulesClientParams: jest.Mocked = { kibanaVersion, isAuthenticationTypeAPIKey: jest.fn(), getAuthenticationAPIKey: jest.fn(), + getAlertIndicesAlias: jest.fn(), + alertsService: null, }; beforeEach(() => { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/get_alert_summary.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/get_alert_summary.test.ts index 68160306d3424..1521dfdefced3 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/get_alert_summary.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/get_alert_summary.test.ts @@ -57,6 +57,8 @@ const rulesClientParams: jest.Mocked = { kibanaVersion, isAuthenticationTypeAPIKey: jest.fn(), getAuthenticationAPIKey: jest.fn(), + getAlertIndicesAlias: jest.fn(), + alertsService: null, }; beforeEach(() => { @@ -170,6 +172,7 @@ describe('getAlertSummary()', () => { "flapping": true, "muted": false, "status": "Active", + "tracked": true, "uuid": "uuid-1", }, "alert-muted-no-activity": Object { @@ -178,6 +181,7 @@ describe('getAlertSummary()', () => { "flapping": false, "muted": true, "status": "OK", + "tracked": true, "uuid": undefined, }, "alert-previously-active": Object { @@ -186,6 +190,7 @@ describe('getAlertSummary()', () => { "flapping": false, "muted": false, "status": "OK", + "tracked": true, "uuid": "uuid-2", }, }, diff --git a/x-pack/plugins/alerting/server/rules_client/tests/get_execution_log.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/get_execution_log.test.ts index 0704a0e7afe16..f1d2dbcacf8fd 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/get_execution_log.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/get_execution_log.test.ts @@ -60,6 +60,8 @@ const rulesClientParams: jest.Mocked = { auditLogger, isAuthenticationTypeAPIKey: jest.fn(), getAuthenticationAPIKey: jest.fn(), + getAlertIndicesAlias: jest.fn(), + alertsService: null, }; beforeEach(() => { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/get_tags.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/get_tags.test.ts index 6d55fe7f40725..54971f47a3b09 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/get_tags.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/get_tags.test.ts @@ -54,6 +54,8 @@ const rulesClientParams: jest.Mocked = { kibanaVersion, isAuthenticationTypeAPIKey: jest.fn(), getAuthenticationAPIKey: jest.fn(), + getAlertIndicesAlias: jest.fn(), + alertsService: null, }; const listedTypes = new Set([ diff --git a/x-pack/plugins/alerting/server/rules_client/tests/list_rule_types.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/list_rule_types.test.ts index 09aed8b28bfb4..0b95247b60adf 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/list_rule_types.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/list_rule_types.test.ts @@ -55,6 +55,8 @@ const rulesClientParams: jest.Mocked = { kibanaVersion, isAuthenticationTypeAPIKey: jest.fn(), getAuthenticationAPIKey: jest.fn(), + getAlertIndicesAlias: jest.fn(), + alertsService: null, }; beforeEach(() => { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/mute_all.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/mute_all.test.ts index 7466d7405fa24..2b6e2793aa648 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/mute_all.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/mute_all.test.ts @@ -51,6 +51,8 @@ const rulesClientParams: jest.Mocked = { kibanaVersion, isAuthenticationTypeAPIKey: jest.fn(), getAuthenticationAPIKey: jest.fn(), + getAlertIndicesAlias: jest.fn(), + alertsService: null, }; beforeEach(() => { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/mute_instance.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/mute_instance.test.ts index 2dedc7f92f38d..4d7f1a52699cc 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/mute_instance.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/mute_instance.test.ts @@ -51,6 +51,8 @@ const rulesClientParams: jest.Mocked = { kibanaVersion, isAuthenticationTypeAPIKey: jest.fn(), getAuthenticationAPIKey: jest.fn(), + getAlertIndicesAlias: jest.fn(), + alertsService: null, }; beforeEach(() => { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/resolve.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/resolve.test.ts index 5dc753f5d4ade..dd3ccaea5b67c 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/resolve.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/resolve.test.ts @@ -60,6 +60,8 @@ const rulesClientParams: jest.Mocked = { kibanaVersion, isAuthenticationTypeAPIKey: jest.fn(), getAuthenticationAPIKey: jest.fn(), + getAlertIndicesAlias: jest.fn(), + alertsService: null, }; beforeEach(() => { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/run_soon.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/run_soon.test.ts index 20bbd2fb72f1a..9040096eba8b0 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/run_soon.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/run_soon.test.ts @@ -53,6 +53,8 @@ const rulesClientParams: jest.Mocked = { auditLogger, isAuthenticationTypeAPIKey: jest.fn(), getAuthenticationAPIKey: jest.fn(), + getAlertIndicesAlias: jest.fn(), + alertsService: null, }; setGlobalDate(); diff --git a/x-pack/plugins/alerting/server/rules_client/tests/unmute_all.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/unmute_all.test.ts index ecdea250916be..c8cb134b129d7 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/unmute_all.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/unmute_all.test.ts @@ -51,6 +51,8 @@ const rulesClientParams: jest.Mocked = { kibanaVersion, isAuthenticationTypeAPIKey: jest.fn(), getAuthenticationAPIKey: jest.fn(), + getAlertIndicesAlias: jest.fn(), + alertsService: null, }; beforeEach(() => { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/unmute_instance.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/unmute_instance.test.ts index 4b030bb7eb2b5..4df037b8728f0 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/unmute_instance.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/unmute_instance.test.ts @@ -51,6 +51,8 @@ const rulesClientParams: jest.Mocked = { kibanaVersion, isAuthenticationTypeAPIKey: jest.fn(), getAuthenticationAPIKey: jest.fn(), + getAlertIndicesAlias: jest.fn(), + alertsService: null, }; beforeEach(() => { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/update.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/update.test.ts index cd97ea0ac1bbd..8b275d12cfa0a 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/update.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/update.test.ts @@ -90,6 +90,8 @@ const rulesClientParams: jest.Mocked = { minimumScheduleInterval: { value: '1m', enforce: false }, isAuthenticationTypeAPIKey: jest.fn(), getAuthenticationAPIKey: jest.fn(), + getAlertIndicesAlias: jest.fn(), + alertsService: null, }; beforeEach(() => { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/update_api_key.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/update_api_key.test.ts index cc5d4e32511bc..f9cd570afa430 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/update_api_key.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/update_api_key.test.ts @@ -58,6 +58,8 @@ const rulesClientParams: jest.Mocked = { auditLogger, isAuthenticationTypeAPIKey: jest.fn(), getAuthenticationAPIKey: jest.fn(), + getAlertIndicesAlias: jest.fn(), + alertsService: null, }; beforeEach(() => { diff --git a/x-pack/plugins/alerting/server/rules_client/types.ts b/x-pack/plugins/alerting/server/rules_client/types.ts index 87ae594976246..7428f81529f20 100644 --- a/x-pack/plugins/alerting/server/rules_client/types.ts +++ b/x-pack/plugins/alerting/server/rules_client/types.ts @@ -32,6 +32,8 @@ import { } from '../types'; import { AlertingAuthorization } from '../authorization'; import { AlertingRulesConfig } from '../config'; +import { GetAlertIndicesAlias } from '../lib'; +import { AlertsService } from '../alerts_service'; export type { BulkEditOperation, @@ -74,6 +76,8 @@ export interface RulesClientContext { readonly fieldsToExcludeFromPublicApi: Array; readonly isAuthenticationTypeAPIKey: () => boolean; readonly getAuthenticationAPIKey: (name: string) => CreateAPIKeyResult; + readonly getAlertIndicesAlias: GetAlertIndicesAlias; + readonly alertsService: AlertsService | null; } export type NormalizedAlertAction = Omit; diff --git a/x-pack/plugins/alerting/server/rules_client_conflict_retries.test.ts b/x-pack/plugins/alerting/server/rules_client_conflict_retries.test.ts index c2c1f73bbe5fb..036fa1c98ff8f 100644 --- a/x-pack/plugins/alerting/server/rules_client_conflict_retries.test.ts +++ b/x-pack/plugins/alerting/server/rules_client_conflict_retries.test.ts @@ -66,6 +66,8 @@ const rulesClientParams: jest.Mocked = { minimumScheduleInterval: { value: '1m', enforce: false }, isAuthenticationTypeAPIKey: jest.fn(), getAuthenticationAPIKey: jest.fn(), + getAlertIndicesAlias: jest.fn(), + alertsService: null, }; // this suite consists of two suites running tests against mutable RulesClient APIs: diff --git a/x-pack/plugins/alerting/server/rules_client_factory.test.ts b/x-pack/plugins/alerting/server/rules_client_factory.test.ts index 6bb4f945d5430..9f1a4acb12420 100644 --- a/x-pack/plugins/alerting/server/rules_client_factory.test.ts +++ b/x-pack/plugins/alerting/server/rules_client_factory.test.ts @@ -46,6 +46,8 @@ const rulesClientFactoryParams: jest.Mocked = { ruleTypeRegistry: ruleTypeRegistryMock.create(), getSpaceId: jest.fn(), spaceIdToNamespace: jest.fn(), + getAlertIndicesAlias: jest.fn(), + alertsService: null, maxScheduledPerMinute: 10000, minimumScheduleInterval: { value: '1m', enforce: false }, internalSavedObjectsRepository, @@ -112,6 +114,8 @@ test('creates a rules client with proper constructor arguments when security is minimumScheduleInterval: { value: '1m', enforce: false }, isAuthenticationTypeAPIKey: expect.any(Function), getAuthenticationAPIKey: expect.any(Function), + getAlertIndicesAlias: expect.any(Function), + alertsService: null, }); }); @@ -154,6 +158,8 @@ test('creates a rules client with proper constructor arguments', async () => { minimumScheduleInterval: { value: '1m', enforce: false }, isAuthenticationTypeAPIKey: expect.any(Function), getAuthenticationAPIKey: expect.any(Function), + getAlertIndicesAlias: expect.any(Function), + alertsService: null, }); }); diff --git a/x-pack/plugins/alerting/server/rules_client_factory.ts b/x-pack/plugins/alerting/server/rules_client_factory.ts index e0b9de5dc53e2..6f2930429256e 100644 --- a/x-pack/plugins/alerting/server/rules_client_factory.ts +++ b/x-pack/plugins/alerting/server/rules_client_factory.ts @@ -26,6 +26,8 @@ import { RuleTypeRegistry, SpaceIdToNamespaceFunction } from './types'; import { RulesClient } from './rules_client'; import { AlertingAuthorizationClientFactory } from './alerting_authorization_client_factory'; import { AlertingRulesConfig } from './config'; +import { GetAlertIndicesAlias } from './lib'; +import { AlertsService } from './alerts_service/alerts_service'; export interface RulesClientFactoryOpts { logger: Logger; taskManager: TaskManagerStartContract; @@ -43,6 +45,8 @@ export interface RulesClientFactoryOpts { eventLogger?: IEventLogger; minimumScheduleInterval: AlertingRulesConfig['minimumScheduleInterval']; maxScheduledPerMinute: AlertingRulesConfig['maxScheduledPerMinute']; + getAlertIndicesAlias: GetAlertIndicesAlias; + alertsService: AlertsService | null; } export class RulesClientFactory { @@ -63,6 +67,8 @@ export class RulesClientFactory { private eventLogger?: IEventLogger; private minimumScheduleInterval!: AlertingRulesConfig['minimumScheduleInterval']; private maxScheduledPerMinute!: AlertingRulesConfig['maxScheduledPerMinute']; + private getAlertIndicesAlias!: GetAlertIndicesAlias; + private alertsService!: AlertsService | null; public initialize(options: RulesClientFactoryOpts) { if (this.isInitialized) { @@ -85,6 +91,8 @@ export class RulesClientFactory { this.eventLogger = options.eventLogger; this.minimumScheduleInterval = options.minimumScheduleInterval; this.maxScheduledPerMinute = options.maxScheduledPerMinute; + this.getAlertIndicesAlias = options.getAlertIndicesAlias; + this.alertsService = options.alertsService; } public create(request: KibanaRequest, savedObjects: SavedObjectsServiceStart): RulesClient { @@ -113,6 +121,8 @@ export class RulesClientFactory { internalSavedObjectsRepository: this.internalSavedObjectsRepository, encryptedSavedObjectsClient: this.encryptedSavedObjectsClient, auditLogger: securityPluginSetup?.audit.asScoped(request), + getAlertIndicesAlias: this.getAlertIndicesAlias, + alertsService: this.alertsService, async getUserName() { if (!securityPluginStart) { return null; diff --git a/x-pack/plugins/infra/public/common/alerts/constants.ts b/x-pack/plugins/infra/public/common/alerts/constants.ts index 8c71ba66e86e8..950825f511286 100644 --- a/x-pack/plugins/infra/public/common/alerts/constants.ts +++ b/x-pack/plugins/infra/public/common/alerts/constants.ts @@ -6,7 +6,12 @@ */ import { i18n } from '@kbn/i18n'; -import { ALERT_STATUS, ALERT_STATUS_ACTIVE, ALERT_STATUS_RECOVERED } from '@kbn/rule-data-utils'; +import { + ALERT_STATUS, + ALERT_STATUS_ACTIVE, + ALERT_STATUS_RECOVERED, + ALERT_STATUS_UNTRACKED, +} from '@kbn/rule-data-utils'; import type { AlertStatusFilter } from './types'; export const ALERT_STATUS_ALL = 'all'; @@ -46,9 +51,24 @@ export const RECOVERED_ALERTS: AlertStatusFilter = { }), }; +export const UNTRACKED_ALERTS: AlertStatusFilter = { + status: ALERT_STATUS_UNTRACKED, + query: { + term: { + [ALERT_STATUS]: { + value: ALERT_STATUS_UNTRACKED, + }, + }, + }, + label: i18n.translate('xpack.infra.hostsViewPage.tabs.alerts.alertStatusFilter.untracked', { + defaultMessage: 'Untracked', + }), +}; + export const ALERT_STATUS_QUERY = { [ACTIVE_ALERTS.status]: ACTIVE_ALERTS.query, [RECOVERED_ALERTS.status]: RECOVERED_ALERTS.query, + [UNTRACKED_ALERTS.status]: UNTRACKED_ALERTS.query, }; export const ALERTS_DOC_HREF = diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/alerts/alerts_status_filter.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/alerts/alerts_status_filter.tsx index 9298b2e420642..255273b5cd513 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/alerts/alerts_status_filter.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/alerts/alerts_status_filter.tsx @@ -13,6 +13,7 @@ import { ACTIVE_ALERTS, ALL_ALERTS, RECOVERED_ALERTS, + UNTRACKED_ALERTS, } from '../../../../../../common/alerts/constants'; export interface AlertStatusFilterProps { status: AlertStatus; @@ -38,6 +39,12 @@ const options: EuiButtonGroupOptionProps[] = [ value: RECOVERED_ALERTS.query, 'data-test-subj': 'hostsView-alert-status-filter-recovered-button', }, + { + id: UNTRACKED_ALERTS.status, + label: UNTRACKED_ALERTS.label, + value: UNTRACKED_ALERTS.query, + 'data-test-subj': 'hostsView-alert-status-filter-untracked-button', + }, ]; export function AlertsStatusFilter({ status, onChange }: AlertStatusFilterProps) { diff --git a/x-pack/plugins/observability/common/typings.ts b/x-pack/plugins/observability/common/typings.ts index ce2fdb7460688..dfddf89992c19 100644 --- a/x-pack/plugins/observability/common/typings.ts +++ b/x-pack/plugins/observability/common/typings.ts @@ -6,7 +6,11 @@ */ import * as t from 'io-ts'; -import { ALERT_STATUS_ACTIVE, ALERT_STATUS_RECOVERED } from '@kbn/rule-data-utils'; +import { + ALERT_STATUS_ACTIVE, + ALERT_STATUS_RECOVERED, + ALERT_STATUS_UNTRACKED, +} from '@kbn/rule-data-utils'; import { ALERT_STATUS_ALL } from './constants'; export type Maybe = T | null | undefined; @@ -29,6 +33,7 @@ export interface ApmIndicesConfig { export type AlertStatus = | typeof ALERT_STATUS_ACTIVE | typeof ALERT_STATUS_RECOVERED + | typeof ALERT_STATUS_UNTRACKED | typeof ALERT_STATUS_ALL; export interface AlertStatusFilter { diff --git a/x-pack/plugins/observability/public/components/alert_search_bar/components/alerts_status_filter.tsx b/x-pack/plugins/observability/public/components/alert_search_bar/components/alerts_status_filter.tsx index 73cdfeceade3e..dbfca389e0bcc 100644 --- a/x-pack/plugins/observability/public/components/alert_search_bar/components/alerts_status_filter.tsx +++ b/x-pack/plugins/observability/public/components/alert_search_bar/components/alerts_status_filter.tsx @@ -8,7 +8,7 @@ import { EuiButtonGroup, EuiButtonGroupOptionProps } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; -import { ALL_ALERTS, ACTIVE_ALERTS, RECOVERED_ALERTS } from '../constants'; +import { ALL_ALERTS, ACTIVE_ALERTS, RECOVERED_ALERTS, UNTRACKED_ALERTS } from '../constants'; import { AlertStatusFilterProps } from '../types'; import { AlertStatus } from '../../../../common/typings'; @@ -31,6 +31,12 @@ const options: EuiButtonGroupOptionProps[] = [ value: RECOVERED_ALERTS.query, 'data-test-subj': 'alert-status-filter-recovered-button', }, + { + id: UNTRACKED_ALERTS.status, + label: UNTRACKED_ALERTS.label, + value: UNTRACKED_ALERTS.query, + 'data-test-subj': 'alert-status-filter-untracked-button', + }, ]; export function AlertsStatusFilter({ status, onChange }: AlertStatusFilterProps) { diff --git a/x-pack/plugins/observability/public/components/alert_search_bar/constants.ts b/x-pack/plugins/observability/public/components/alert_search_bar/constants.ts index c067baafe28f1..85ea6464d5ac0 100644 --- a/x-pack/plugins/observability/public/components/alert_search_bar/constants.ts +++ b/x-pack/plugins/observability/public/components/alert_search_bar/constants.ts @@ -7,7 +7,12 @@ import { Query } from '@kbn/es-query'; import { i18n } from '@kbn/i18n'; -import { ALERT_STATUS_ACTIVE, ALERT_STATUS_RECOVERED, ALERT_STATUS } from '@kbn/rule-data-utils'; +import { + ALERT_STATUS_ACTIVE, + ALERT_STATUS_RECOVERED, + ALERT_STATUS_UNTRACKED, + ALERT_STATUS, +} from '@kbn/rule-data-utils'; import { AlertStatusFilter } from '../../../common/typings'; import { ALERT_STATUS_ALL } from '../../../common/constants'; @@ -38,7 +43,16 @@ export const RECOVERED_ALERTS: AlertStatusFilter = { }), }; +export const UNTRACKED_ALERTS: AlertStatusFilter = { + status: ALERT_STATUS_UNTRACKED, + query: `${ALERT_STATUS}: "${ALERT_STATUS_UNTRACKED}"`, + label: i18n.translate('xpack.observability.alerts.alertStatusFilter.untracked', { + defaultMessage: 'Untracked', + }), +}; + export const ALERT_STATUS_QUERY = { [ACTIVE_ALERTS.status]: ACTIVE_ALERTS.query, [RECOVERED_ALERTS.status]: RECOVERED_ALERTS.query, + [UNTRACKED_ALERTS.status]: UNTRACKED_ALERTS.query, }; diff --git a/x-pack/plugins/observability/public/components/alerts_flyout/alerts_flyout_body.tsx b/x-pack/plugins/observability/public/components/alerts_flyout/alerts_flyout_body.tsx index 297ee793289f4..735d38d0604d0 100644 --- a/x-pack/plugins/observability/public/components/alerts_flyout/alerts_flyout_body.tsx +++ b/x-pack/plugins/observability/public/components/alerts_flyout/alerts_flyout_body.tsx @@ -16,6 +16,7 @@ import { EuiFlyoutBody, } from '@elastic/eui'; import { + AlertStatus, ALERT_DURATION, ALERT_EVALUATION_THRESHOLD, ALERT_EVALUATION_VALUE, @@ -23,8 +24,7 @@ import { ALERT_RULE_CATEGORY, ALERT_RULE_TYPE_ID, ALERT_RULE_UUID, - ALERT_STATUS_ACTIVE, - ALERT_STATUS_RECOVERED, + ALERT_STATUS, } from '@kbn/rule-data-utils'; import { i18n } from '@kbn/i18n'; import { AlertLifecycleStatusBadge } from '@kbn/alerts-ui-shared'; @@ -64,7 +64,7 @@ export function AlertsFlyoutBody({ alert, id: pageId }: FlyoutProps) { }), description: ( ), diff --git a/x-pack/plugins/task_manager/server/task_scheduling.ts b/x-pack/plugins/task_manager/server/task_scheduling.ts index 85c346d52da05..5ee848c716bec 100644 --- a/x-pack/plugins/task_manager/server/task_scheduling.ts +++ b/x-pack/plugins/task_manager/server/task_scheduling.ts @@ -152,13 +152,20 @@ export class TaskScheduling { return await this.store.bulkSchedule(modifiedTasks); } - public async bulkDisable(taskIds: string[]) { + public async bulkDisable(taskIds: string[], clearStateIdsOrBoolean?: string[] | boolean) { return await retryableBulkUpdate({ taskIds, store: this.store, getTasks: async (ids) => await this.bulkGetTasksHelper(ids), filter: (task) => !!task.enabled, - map: (task) => ({ ...task, enabled: false }), + map: (task) => ({ + ...task, + enabled: false, + ...((Array.isArray(clearStateIdsOrBoolean) && clearStateIdsOrBoolean.includes(task.id)) || + clearStateIdsOrBoolean === true + ? { state: {} } + : {}), + }), validate: false, }); } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/rule_summary.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/rule_summary.test.ts index 889f2634eacc9..19455dc9e9c58 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/rule_summary.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/rule_summary.test.ts @@ -19,6 +19,7 @@ describe('loadRuleSummary', () => { flapping: true, status: 'OK', muted: false, + tracked: true, }, }, consumer: 'alerts', @@ -47,6 +48,7 @@ describe('loadRuleSummary', () => { flapping: true, status: 'OK', muted: false, + tracked: true, }, }, consumer: 'alerts', diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule.test.tsx index 7f49f8872f109..fd0d18552191c 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule.test.tsx @@ -128,12 +128,14 @@ describe('rules', () => { muted: false, actionGroupId: 'default', flapping: false, + tracked: true, }, second_rule: { status: 'Active', muted: false, actionGroupId: 'action group id unknown', flapping: false, + tracked: true, }, }, }); @@ -192,11 +194,13 @@ describe('rules', () => { status: 'OK', muted: false, flapping: false, + tracked: true, }, ['us-east']: { status: 'OK', muted: false, flapping: false, + tracked: true, }, }; @@ -228,8 +232,8 @@ describe('rules', () => { mutedInstanceIds: ['us-west', 'us-east'], }); const ruleType = mockRuleType(); - const ruleUsWest: AlertStatus = { status: 'OK', muted: false, flapping: false }; - const ruleUsEast: AlertStatus = { status: 'OK', muted: false, flapping: false }; + const ruleUsWest: AlertStatus = { status: 'OK', muted: false, flapping: false, tracked: true }; + const ruleUsEast: AlertStatus = { status: 'OK', muted: false, flapping: false, tracked: true }; const wrapper = mountWithIntl( { status: 'OK', muted: false, flapping: false, + tracked: true, }, 'us-east': { status: 'OK', muted: false, flapping: false, + tracked: true, }, }, })} @@ -275,6 +281,7 @@ describe('alertToListItem', () => { activeStartDate: fake2MinutesAgo.toISOString(), actionGroupId: 'testing', flapping: false, + tracked: true, }; expect(alertToListItem(fakeNow.getTime(), 'id', alert)).toEqual({ @@ -285,6 +292,7 @@ describe('alertToListItem', () => { sortPriority: 0, duration: fakeNow.getTime() - fake2MinutesAgo.getTime(), isMuted: false, + tracked: true, }); }); @@ -295,6 +303,7 @@ describe('alertToListItem', () => { muted: false, activeStartDate: fake2MinutesAgo.toISOString(), flapping: false, + tracked: true, }; expect(alertToListItem(fakeNow.getTime(), 'id', alert)).toEqual({ @@ -305,6 +314,7 @@ describe('alertToListItem', () => { sortPriority: 0, duration: fakeNow.getTime() - fake2MinutesAgo.getTime(), isMuted: false, + tracked: true, }); }); @@ -316,6 +326,7 @@ describe('alertToListItem', () => { activeStartDate: fake2MinutesAgo.toISOString(), actionGroupId: 'default', flapping: false, + tracked: true, }; expect(alertToListItem(fakeNow.getTime(), 'id', alert)).toEqual({ @@ -326,6 +337,7 @@ describe('alertToListItem', () => { sortPriority: 0, duration: fakeNow.getTime() - fake2MinutesAgo.getTime(), isMuted: true, + tracked: true, }); }); @@ -335,6 +347,7 @@ describe('alertToListItem', () => { muted: false, actionGroupId: 'default', flapping: false, + tracked: true, }; expect(alertToListItem(fakeNow.getTime(), 'id', alert)).toEqual({ @@ -345,6 +358,7 @@ describe('alertToListItem', () => { duration: 0, sortPriority: 0, isMuted: false, + tracked: true, }); }); @@ -354,6 +368,7 @@ describe('alertToListItem', () => { muted: true, actionGroupId: 'default', flapping: false, + tracked: true, }; expect(alertToListItem(fakeNow.getTime(), 'id', alert)).toEqual({ alert: 'id', @@ -363,6 +378,7 @@ describe('alertToListItem', () => { duration: 0, sortPriority: 1, isMuted: true, + tracked: true, }); }); }); @@ -457,12 +473,14 @@ describe('tabbed content', () => { muted: false, actionGroupId: 'default', flapping: false, + tracked: true, }, second_rule: { status: 'Active', muted: false, actionGroupId: 'action group id unknown', flapping: false, + tracked: true, }, }, }); @@ -544,6 +562,7 @@ function mockRuleSummary(overloads: Partial = {}): RuleSummary { muted: false, actionGroupId: 'testActionGroup', flapping: false, + tracked: true, }, }, executionDuration: { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule.tsx index 38b43114452ef..7a5d12aad5e93 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule.tsx @@ -190,6 +190,7 @@ export function alertToListItem( const start = alert?.activeStartDate ? new Date(alert.activeStartDate) : undefined; const duration = start ? durationEpoch - start.valueOf() : 0; const sortPriority = getSortPriorityByStatus(alert?.status); + const tracked = !!alert?.tracked; return { alert: alertId, status, @@ -198,6 +199,7 @@ export function alertToListItem( isMuted, sortPriority, flapping: alert.flapping, + tracked, ...(alert.maintenanceWindowIds ? { maintenanceWindowIds: alert.maintenanceWindowIds } : {}), }; } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_alert_list.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_alert_list.tsx index 616f17a2ee685..05dedf3067418 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_alert_list.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_alert_list.tsx @@ -10,7 +10,12 @@ import moment, { Duration } from 'moment'; import { padStart, chunk } from 'lodash'; import { EuiBasicTable, EuiToolTip } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { AlertStatus, ALERT_STATUS_ACTIVE, ALERT_STATUS_RECOVERED } from '@kbn/rule-data-utils'; +import { + AlertStatus, + ALERT_STATUS_ACTIVE, + ALERT_STATUS_RECOVERED, + ALERT_STATUS_UNTRACKED, +} from '@kbn/rule-data-utils'; import { AlertStatusValues, MaintenanceWindow } from '@kbn/alerting-plugin/common'; import { DEFAULT_SEARCH_PAGE_SIZE } from '../../../constants'; import { Pagination } from '../../../../types'; @@ -20,7 +25,13 @@ import { AlertLifecycleStatusBadge } from '../../../components/alert_lifecycle_s import { useBulkGetMaintenanceWindows } from '../../alerts_table/hooks/use_bulk_get_maintenance_windows'; import { MaintenanceWindowBaseCell } from '../../alerts_table/maintenance_windows/cell'; -export const getConvertedAlertStatus = (status: AlertStatusValues): AlertStatus => { +export const getConvertedAlertStatus = ( + status: AlertStatusValues, + alert: AlertListItem +): AlertStatus => { + if (!alert.tracked) { + return ALERT_STATUS_UNTRACKED; + } if (status === 'Active') { return ALERT_STATUS_ACTIVE; } @@ -151,7 +162,7 @@ export const RuleAlertList = (props: RuleAlertListProps) => { ), width: '15%', render: (value: AlertStatusValues, alert: AlertListItem) => { - const convertedStatus = getConvertedAlertStatus(value); + const convertedStatus = getConvertedAlertStatus(value, alert); return ( ); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_route.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_route.test.tsx index 1826894ef70f9..7e660a30f7ec9 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_route.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_route.test.tsx @@ -170,6 +170,7 @@ function mockRuleSummary(overloads: Partial = {}): any { status: 'OK', muted: false, flapping: false, + tracked: true, }, }, executionDuration: { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/test_helpers.ts b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/test_helpers.ts index d72e381c170ab..01ea94fdea8df 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/test_helpers.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/test_helpers.ts @@ -104,6 +104,7 @@ export function mockRuleSummary(overloads: Partial = {}): RuleSumma muted: false, actionGroupId: 'testActionGroup', flapping: false, + tracked: true, }, }, executionDuration: { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/types.ts b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/types.ts index 3de1f6f464c88..6b5239702e567 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/types.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/types.ts @@ -15,6 +15,7 @@ export interface AlertListItem { sortPriority: number; flapping: boolean; maintenanceWindowIds?: string[]; + tracked: boolean; } export interface RefreshToken { diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/disable.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/disable.ts index c4620416f4bd6..39eaa216e7e04 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/disable.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/disable.ts @@ -95,7 +95,7 @@ export default function createDisableRuleTests({ getService }: FtrProviderContex }); }); - it('should create recovered-instance events for all alerts', async () => { + it('should create untracked-instance events for all alerts', async () => { const { body: createdRule } = await supertest .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') @@ -138,7 +138,7 @@ export default function createDisableRuleTests({ getService }: FtrProviderContex provider: 'alerting', actions: new Map([ // make sure the counts of the # of events per type are as expected - ['recovered-instance', { equal: 2 }], + ['untracked-instance', { equal: 2 }], ]), }); }); @@ -151,7 +151,7 @@ export default function createDisableRuleTests({ getService }: FtrProviderContex savedObjects: [ { type: 'alert', id: ruleId, rel: 'primary', type_id: 'test.cumulative-firing' }, ], - message: "instance 'instance-0' has recovered due to the rule was disabled", + message: "instance 'instance-0' has been untracked because the rule was disabled", shouldHaveEventEnd: false, shouldHaveTask: false, ruleTypeId: createdRule.rule_type_id, diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/event_log.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/event_log.ts index 2aff79b8997b8..2662ba69f3025 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/event_log.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/event_log.ts @@ -24,6 +24,7 @@ const InstanceActions = new Set([ 'new-instance', 'active-instance', 'recovered-instance', + 'untracked-instance', ]); // eslint-disable-next-line import/no-default-export diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/get_alert_summary.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/get_alert_summary.ts index 6a825be98d327..87546a8649ce3 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/get_alert_summary.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/get_alert_summary.ts @@ -183,6 +183,7 @@ export default function createGetAlertSummaryTests({ getService }: FtrProviderCo status: 'OK', muted: true, flapping: false, + tracked: true, }, }); }); @@ -248,11 +249,13 @@ export default function createGetAlertSummaryTests({ getService }: FtrProviderCo actionGroupId: 'default', activeStartDate: actualAlerts.alertA.activeStartDate, flapping: false, + tracked: true, }, alertB: { status: 'OK', muted: false, flapping: false, + tracked: true, }, alertC: { status: 'Active', @@ -260,11 +263,13 @@ export default function createGetAlertSummaryTests({ getService }: FtrProviderCo actionGroupId: 'default', activeStartDate: actualAlerts.alertC.activeStartDate, flapping: false, + tracked: true, }, alertD: { status: 'OK', muted: true, flapping: false, + tracked: true, }, }; expect(actualAlerts).to.eql(expectedAlerts); @@ -332,12 +337,14 @@ export default function createGetAlertSummaryTests({ getService }: FtrProviderCo actionGroupId: 'default', activeStartDate: actualAlerts.alertA.activeStartDate, flapping: false, + tracked: true, maintenanceWindowIds: [createdMaintenanceWindow.id], }, alertB: { status: 'OK', muted: false, flapping: false, + tracked: true, maintenanceWindowIds: [createdMaintenanceWindow.id], }, alertC: { @@ -346,12 +353,14 @@ export default function createGetAlertSummaryTests({ getService }: FtrProviderCo actionGroupId: 'default', activeStartDate: actualAlerts.alertC.activeStartDate, flapping: false, + tracked: true, maintenanceWindowIds: [createdMaintenanceWindow.id], }, alertD: { status: 'OK', muted: true, flapping: false, + tracked: true, }, }; expect(actualAlerts).to.eql(expectedAlerts); @@ -398,11 +407,13 @@ export default function createGetAlertSummaryTests({ getService }: FtrProviderCo actionGroupId: 'default', activeStartDate: actualAlerts.alertA.activeStartDate, flapping: false, + tracked: true, }, alertB: { status: 'OK', muted: false, flapping: false, + tracked: true, }, alertC: { status: 'Active', @@ -410,11 +421,13 @@ export default function createGetAlertSummaryTests({ getService }: FtrProviderCo actionGroupId: 'default', activeStartDate: actualAlerts.alertC.activeStartDate, flapping: false, + tracked: true, }, alertD: { status: 'OK', muted: true, flapping: false, + tracked: true, }, }; expect(actualAlerts).to.eql(expectedAlerts);