"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.createLifecycleExecutor = void 0; var _Either = require("fp-ts/lib/Either"); var _uuid = require("uuid"); var _lodash = require("lodash"); var _lib = require("@kbn/alerting-plugin/server/lib"); var _alertingStateTypes = require("@kbn/alerting-state-types"); var _technical_rule_data_field_names = require("../../common/technical_rule_data_field_names"); var _fetch_existing_alerts = require("./fetch_existing_alerts"); var _get_common_alert_fields = require("./get_common_alert_fields"); var _get_updated_flapping_history = require("./get_updated_flapping_history"); var _fetch_alert_by_uuid = require("./fetch_alert_by_uuid"); var _get_alerts_for_notification = require("./get_alerts_for_notification"); /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ const createLifecycleExecutor = (logger, ruleDataClient) => wrappedExecutor => async options => { var _wrappedExecutorResul; const { services: { alertFactory, shouldWriteAlerts }, state: previousState, flappingSettings, maintenanceWindowIds, rule } = options; const ruleDataClientWriter = await ruleDataClient.getWriter(); const state = (0, _Either.getOrElse)(() => ({ wrapped: previousState, trackedAlerts: {}, trackedAlertsRecovered: {} }))((0, _alertingStateTypes.wrappedStateRt)().decode(previousState)); const commonRuleFields = (0, _get_common_alert_fields.getCommonAlertFields)(options); const currentAlerts = {}; const alertUuidMap = new Map(); const lifecycleAlertServices = { alertWithLifecycle: ({ id, fields }) => { currentAlerts[id] = fields; const alert = alertFactory.create(id); const uuid = alert.getUuid(); alertUuidMap.set(id, uuid); return alert; }, getAlertStartedDate: alertId => { var _state$trackedAlerts$, _state$trackedAlerts$2; return (_state$trackedAlerts$ = (_state$trackedAlerts$2 = state.trackedAlerts[alertId]) === null || _state$trackedAlerts$2 === void 0 ? void 0 : _state$trackedAlerts$2.started) !== null && _state$trackedAlerts$ !== void 0 ? _state$trackedAlerts$ : null; }, getAlertUuid: alertId => { const uuid = alertUuidMap.get(alertId); if (uuid) { return uuid; } const trackedAlert = state.trackedAlerts[alertId]; if (trackedAlert) { return trackedAlert.alertUuid; } const trackedRecoveredAlert = state.trackedAlertsRecovered[alertId]; if (trackedRecoveredAlert) { return trackedRecoveredAlert.alertUuid; } const alertInfo = `alert ${alertId} of rule ${rule.ruleTypeId}:${rule.id}`; logger.warn(`[Rule Registry] requesting uuid for ${alertInfo} which is not tracked, generating dynamically`); return (0, _uuid.v4)(); }, getAlertByAlertUuid: async alertUuid => { try { return await (0, _fetch_alert_by_uuid.fetchAlertByAlertUUID)(ruleDataClient, alertUuid); } catch (err) { return null; } } }; const wrappedExecutorResult = await wrappedExecutor({ ...options, state: state.wrapped != null ? state.wrapped : {}, services: { ...options.services, ...lifecycleAlertServices } }); const currentAlertIds = Object.keys(currentAlerts); const trackedAlertIds = Object.keys(state.trackedAlerts); const trackedAlertRecoveredIds = Object.keys(state.trackedAlertsRecovered); const newAlertIds = (0, _lodash.difference)(currentAlertIds, trackedAlertIds); const allAlertIds = [...new Set(currentAlertIds.concat(trackedAlertIds))]; const trackedAlertStates = Object.values(state.trackedAlerts); logger.debug(`[Rule Registry] Tracking ${allAlertIds.length} alerts (${newAlertIds.length} new, ${trackedAlertStates.length} previous)`); const trackedAlertsDataMap = {}; if (trackedAlertStates.length) { const result = await (0, _fetch_existing_alerts.fetchExistingAlerts)(ruleDataClient, trackedAlertStates, commonRuleFields); result.forEach(hit => { const alertInstanceId = hit._source ? hit._source[_technical_rule_data_field_names.ALERT_INSTANCE_ID] : void 0; if (alertInstanceId && hit._source) { trackedAlertsDataMap[alertInstanceId] = { indexName: hit._index, fields: hit._source }; } }); } const makeEventsDataMapFor = alertIds => alertIds.map(alertId => { var _alertData$fields$ALE, _currentAlertData$tag, _alertData$fields$TAG, _options$rule$tags; const alertData = trackedAlertsDataMap[alertId]; const currentAlertData = currentAlerts[alertId]; const trackedAlert = state.trackedAlerts[alertId]; if (!alertData) { logger.debug(`[Rule Registry] Could not find alert data for ${alertId}`); } const isNew = !trackedAlert; const isRecovered = !currentAlertData; const isActive = !isRecovered; const flappingHistory = (0, _get_updated_flapping_history.getUpdatedFlappingHistory)(flappingSettings, alertId, state, isNew, isRecovered, isActive, trackedAlertRecoveredIds); const { alertUuid, started, flapping, pendingRecoveredCount } = !isNew ? state.trackedAlerts[alertId] : { alertUuid: lifecycleAlertServices.getAlertUuid(alertId), started: commonRuleFields[_technical_rule_data_field_names.TIMESTAMP], flapping: state.trackedAlertsRecovered[alertId] ? state.trackedAlertsRecovered[alertId].flapping : false, pendingRecoveredCount: 0 }; const event = { ...(alertData === null || alertData === void 0 ? void 0 : alertData.fields), ...commonRuleFields, ...currentAlertData, [_technical_rule_data_field_names.ALERT_DURATION]: (options.startedAt.getTime() - new Date(started).getTime()) * 1000, [_technical_rule_data_field_names.ALERT_TIME_RANGE]: isRecovered ? { gte: started, lte: commonRuleFields[_technical_rule_data_field_names.TIMESTAMP] } : { gte: started }, [_technical_rule_data_field_names.ALERT_INSTANCE_ID]: alertId, [_technical_rule_data_field_names.ALERT_START]: started, [_technical_rule_data_field_names.ALERT_UUID]: alertUuid, [_technical_rule_data_field_names.ALERT_STATUS]: isRecovered ? _technical_rule_data_field_names.ALERT_STATUS_RECOVERED : _technical_rule_data_field_names.ALERT_STATUS_ACTIVE, [_technical_rule_data_field_names.ALERT_WORKFLOW_STATUS]: (_alertData$fields$ALE = alertData === null || alertData === void 0 ? void 0 : alertData.fields[_technical_rule_data_field_names.ALERT_WORKFLOW_STATUS]) !== null && _alertData$fields$ALE !== void 0 ? _alertData$fields$ALE : 'open', [_technical_rule_data_field_names.EVENT_KIND]: 'signal', [_technical_rule_data_field_names.EVENT_ACTION]: isNew ? 'open' : isActive ? 'active' : 'close', [_technical_rule_data_field_names.TAGS]: Array.from(new Set([...((_currentAlertData$tag = currentAlertData === null || currentAlertData === void 0 ? void 0 : currentAlertData.tags) !== null && _currentAlertData$tag !== void 0 ? _currentAlertData$tag : []), ...((_alertData$fields$TAG = alertData === null || alertData === void 0 ? void 0 : alertData.fields[_technical_rule_data_field_names.TAGS]) !== null && _alertData$fields$TAG !== void 0 ? _alertData$fields$TAG : []), ...((_options$rule$tags = options.rule.tags) !== null && _options$rule$tags !== void 0 ? _options$rule$tags : [])])), [_technical_rule_data_field_names.VERSION]: ruleDataClient.kibanaVersion, [_technical_rule_data_field_names.ALERT_FLAPPING]: flapping, ...(isRecovered ? { [_technical_rule_data_field_names.ALERT_END]: commonRuleFields[_technical_rule_data_field_names.TIMESTAMP] } : {}), ...(isNew && maintenanceWindowIds !== null && maintenanceWindowIds !== void 0 && maintenanceWindowIds.length ? { [_technical_rule_data_field_names.ALERT_MAINTENANCE_WINDOW_IDS]: maintenanceWindowIds } : {}) }; return { indexName: alertData === null || alertData === void 0 ? void 0 : alertData.indexName, event, flappingHistory, flapping, pendingRecoveredCount }; }); const trackedEventsToIndex = makeEventsDataMapFor(trackedAlertIds); const newEventsToIndex = makeEventsDataMapFor(newAlertIds); const trackedRecoveredEventsToIndex = makeEventsDataMapFor(trackedAlertRecoveredIds); const allEventsToIndex = [...(0, _get_alerts_for_notification.getAlertsForNotification)(flappingSettings, trackedEventsToIndex), ...newEventsToIndex]; // Only write alerts if: // - writing is enabled // AND // - rule execution has not been cancelled due to timeout // OR // - if execution has been cancelled due to timeout, if feature flags are configured to write alerts anyway const writeAlerts = ruleDataClient.isWriteEnabled() && shouldWriteAlerts(); if (allEventsToIndex.length > 0 && writeAlerts) { logger.debug(`[Rule Registry] Preparing to index ${allEventsToIndex.length} alerts.`); await ruleDataClientWriter.bulk({ body: allEventsToIndex.flatMap(({ event, indexName }) => [indexName ? { index: { _id: event[_technical_rule_data_field_names.ALERT_UUID], _index: indexName, require_alias: false } } : { index: { _id: event[_technical_rule_data_field_names.ALERT_UUID] } }, event]), refresh: 'wait_for' }); } else { logger.debug(`[Rule Registry] Not indexing ${allEventsToIndex.length} alerts because writing has been disabled.`); } const nextTrackedAlerts = Object.fromEntries(allEventsToIndex.filter(({ event }) => event[_technical_rule_data_field_names.ALERT_STATUS] !== _technical_rule_data_field_names.ALERT_STATUS_RECOVERED).map(({ event, flappingHistory, flapping: isCurrentlyFlapping, pendingRecoveredCount }) => { const alertId = event[_technical_rule_data_field_names.ALERT_INSTANCE_ID]; const alertUuid = event[_technical_rule_data_field_names.ALERT_UUID]; const started = new Date(event[_technical_rule_data_field_names.ALERT_START]).toISOString(); const flapping = (0, _lib.isFlapping)(flappingSettings, flappingHistory, isCurrentlyFlapping); return [alertId, { alertId, alertUuid, started, flappingHistory, flapping, pendingRecoveredCount }]; })); const nextTrackedAlertsRecovered = Object.fromEntries([...allEventsToIndex, ...trackedRecoveredEventsToIndex].filter(({ event, flappingHistory, flapping }) => // return recovered alerts if they are flapping or if the flapping array is not at capacity // this is a space saving effort that will stop tracking a recovered alert if it wasn't flapping and doesn't have state changes // in the last max capcity number of executions event[_technical_rule_data_field_names.ALERT_STATUS] === _technical_rule_data_field_names.ALERT_STATUS_RECOVERED && (flapping || flappingHistory.filter(f => f).length > 0)).map(({ event, flappingHistory, flapping: isCurrentlyFlapping, pendingRecoveredCount }) => { const alertId = event[_technical_rule_data_field_names.ALERT_INSTANCE_ID]; const alertUuid = event[_technical_rule_data_field_names.ALERT_UUID]; const started = new Date(event[_technical_rule_data_field_names.ALERT_START]).toISOString(); const flapping = (0, _lib.isFlapping)(flappingSettings, flappingHistory, isCurrentlyFlapping); return [alertId, { alertId, alertUuid, started, flappingHistory, flapping, pendingRecoveredCount }]; })); return { state: { wrapped: (_wrappedExecutorResul = wrappedExecutorResult === null || wrappedExecutorResult === void 0 ? void 0 : wrappedExecutorResult.state) !== null && _wrappedExecutorResul !== void 0 ? _wrappedExecutorResul : {}, trackedAlerts: writeAlerts ? nextTrackedAlerts : {}, trackedAlertsRecovered: writeAlerts ? nextTrackedAlertsRecovered : {} } }; }; exports.createLifecycleExecutor = createLifecycleExecutor;