"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.ExecutionHandler = void 0; var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var _ruleDataUtils = require("@kbn/rule-data-utils"); var _server = require("@kbn/actions-plugin/server"); var _server2 = require("@kbn/task-manager-plugin/server"); var _lodash = require("lodash"); var _types = require("../types"); var _inject_action_params = require("./inject_action_params"); var _transform_action_params = require("./transform_action_params"); var _common = require("../../common"); var _rule_action_helper = require("./rule_action_helper"); /* * 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. */ var Reasons; (function (Reasons) { Reasons["MUTED"] = "muted"; Reasons["THROTTLED"] = "throttled"; Reasons["ACTION_GROUP_NOT_CHANGED"] = "actionGroupHasNotChanged"; })(Reasons || (Reasons = {})); class ExecutionHandler { constructor({ rule, ruleType, logger, alertingEventLogger, taskRunnerContext, taskInstance, ruleRunMetricsStore, apiKey, ruleConsumer, executionId, ruleLabel, previousStartedAt, actionsClient, maintenanceWindowIds, alertsClient }) { (0, _defineProperty2.default)(this, "logger", void 0); (0, _defineProperty2.default)(this, "alertingEventLogger", void 0); (0, _defineProperty2.default)(this, "rule", void 0); (0, _defineProperty2.default)(this, "ruleType", void 0); (0, _defineProperty2.default)(this, "taskRunnerContext", void 0); (0, _defineProperty2.default)(this, "taskInstance", void 0); (0, _defineProperty2.default)(this, "ruleRunMetricsStore", void 0); (0, _defineProperty2.default)(this, "apiKey", void 0); (0, _defineProperty2.default)(this, "ruleConsumer", void 0); (0, _defineProperty2.default)(this, "executionId", void 0); (0, _defineProperty2.default)(this, "ruleLabel", void 0); (0, _defineProperty2.default)(this, "ephemeralActionsToSchedule", void 0); (0, _defineProperty2.default)(this, "CHUNK_SIZE", 1000); (0, _defineProperty2.default)(this, "skippedAlerts", {}); (0, _defineProperty2.default)(this, "actionsClient", void 0); (0, _defineProperty2.default)(this, "ruleTypeActionGroups", void 0); (0, _defineProperty2.default)(this, "mutedAlertIdsSet", new Set()); (0, _defineProperty2.default)(this, "previousStartedAt", void 0); (0, _defineProperty2.default)(this, "maintenanceWindowIds", []); (0, _defineProperty2.default)(this, "alertsClient", void 0); this.logger = logger; this.alertingEventLogger = alertingEventLogger; this.rule = rule; this.ruleType = ruleType; this.taskRunnerContext = taskRunnerContext; this.taskInstance = taskInstance; this.ruleRunMetricsStore = ruleRunMetricsStore; this.apiKey = apiKey; this.ruleConsumer = ruleConsumer; this.executionId = executionId; this.ruleLabel = ruleLabel; this.actionsClient = actionsClient; this.ephemeralActionsToSchedule = taskRunnerContext.maxEphemeralActionsPerRule; this.ruleTypeActionGroups = new Map(ruleType.actionGroups.map(actionGroup => [actionGroup.id, actionGroup.name])); this.previousStartedAt = previousStartedAt; this.mutedAlertIdsSet = new Set(rule.mutedInstanceIds); this.maintenanceWindowIds = maintenanceWindowIds !== null && maintenanceWindowIds !== void 0 ? maintenanceWindowIds : []; this.alertsClient = alertsClient; } async run(alerts) { var _this$taskInstance$st; const throttledSummaryActions = (0, _rule_action_helper.getSummaryActionsFromTaskState)({ actions: this.rule.actions, summaryActions: (_this$taskInstance$st = this.taskInstance.state) === null || _this$taskInstance$st === void 0 ? void 0 : _this$taskInstance$st.summaryActions }); const executables = await this.generateExecutables(alerts, throttledSummaryActions); if (!!executables.length) { const { CHUNK_SIZE, logger, alertingEventLogger, ruleRunMetricsStore, taskRunnerContext: { actionsConfigMap, actionsPlugin }, taskInstance: { params: { spaceId, alertId: ruleId } } } = this; const logActions = []; const bulkActions = []; this.ruleRunMetricsStore.incrementNumberOfGeneratedActions(executables.length); for (const { action, alert, summarizedAlerts } of executables) { const { actionTypeId } = action; const actionGroup = action.group; ruleRunMetricsStore.incrementNumberOfGeneratedActionsByConnectorType(actionTypeId); if (ruleRunMetricsStore.hasReachedTheExecutableActionsLimit(actionsConfigMap)) { ruleRunMetricsStore.setTriggeredActionsStatusByConnectorType({ actionTypeId, status: _common.ActionsCompletion.PARTIAL }); logger.debug(`Rule "${this.rule.id}" skipped scheduling action "${action.id}" because the maximum number of allowed actions has been reached.`); break; } if (ruleRunMetricsStore.hasReachedTheExecutableActionsLimitByConnectorType({ actionTypeId, actionsConfigMap })) { if (!ruleRunMetricsStore.hasConnectorTypeReachedTheLimit(actionTypeId)) { logger.debug(`Rule "${this.rule.id}" skipped scheduling action "${action.id}" because the maximum number of allowed actions for connector type ${actionTypeId} has been reached.`); } ruleRunMetricsStore.setTriggeredActionsStatusByConnectorType({ actionTypeId, status: _common.ActionsCompletion.PARTIAL }); continue; } if (!this.isExecutableAction(action)) { this.logger.warn(`Rule "${this.taskInstance.params.alertId}" skipped scheduling action "${action.id}" because it is disabled`); continue; } ruleRunMetricsStore.incrementNumberOfTriggeredActions(); ruleRunMetricsStore.incrementNumberOfTriggeredActionsByConnectorType(actionTypeId); if (summarizedAlerts) { const { start, end } = (0, _rule_action_helper.getSummaryActionTimeBounds)(action, this.rule.schedule, this.previousStartedAt); const ruleUrl = this.buildRuleUrl(spaceId, start, end); const actionToRun = { ...action, params: (0, _inject_action_params.injectActionParams)({ actionTypeId, ruleUrl, actionParams: (0, _transform_action_params.transformSummaryActionParams)({ alerts: summarizedAlerts, rule: this.rule, ruleTypeId: this.ruleType.id, actionId: action.id, actionParams: action.params, spaceId, actionsPlugin, actionTypeId, kibanaBaseUrl: this.taskRunnerContext.kibanaBaseUrl, ruleUrl: ruleUrl === null || ruleUrl === void 0 ? void 0 : ruleUrl.absoluteUrl }) }) }; await this.actionRunOrAddToBulk({ enqueueOptions: this.getEnqueueOptions(actionToRun), bulkActions }); if ((0, _rule_action_helper.isActionOnInterval)(action)) { throttledSummaryActions[action.uuid] = { date: new Date() }; } logActions.push({ id: action.id, typeId: action.actionTypeId, alertSummary: { new: summarizedAlerts.new.count, ongoing: summarizedAlerts.ongoing.count, recovered: summarizedAlerts.recovered.count } }); } else { const ruleUrl = this.buildRuleUrl(spaceId); const actionToRun = { ...action, params: (0, _inject_action_params.injectActionParams)({ actionTypeId, ruleUrl, actionParams: (0, _transform_action_params.transformActionParams)({ actionsPlugin, alertId: ruleId, alertType: this.ruleType.id, actionTypeId, alertName: this.rule.name, spaceId, tags: this.rule.tags, alertInstanceId: alert.getId(), alertUuid: alert.getUuid(), alertActionGroup: actionGroup, alertActionGroupName: this.ruleTypeActionGroups.get(actionGroup), context: alert.getContext(), actionId: action.id, state: alert.getState(), kibanaBaseUrl: this.taskRunnerContext.kibanaBaseUrl, alertParams: this.rule.params, actionParams: action.params, flapping: alert.getFlapping(), ruleUrl: ruleUrl === null || ruleUrl === void 0 ? void 0 : ruleUrl.absoluteUrl }) }) }; await this.actionRunOrAddToBulk({ enqueueOptions: this.getEnqueueOptions(actionToRun), bulkActions }); logActions.push({ id: action.id, typeId: action.actionTypeId, alertId: alert.getId(), alertGroup: action.group }); if (!this.isRecoveredAlert(actionGroup)) { if ((0, _rule_action_helper.isActionOnInterval)(action)) { alert.updateLastScheduledActions(action.group, (0, _rule_action_helper.generateActionHash)(action), action.uuid); } else { alert.updateLastScheduledActions(action.group); } alert.unscheduleActions(); } } } if (!!bulkActions.length) { for (const c of (0, _lodash.chunk)(bulkActions, CHUNK_SIZE)) { await this.actionsClient.bulkEnqueueExecution(c); } } if (!!logActions.length) { for (const action of logActions) { alertingEventLogger.logAction(action); } } } return { throttledSummaryActions }; } logNumberOfFilteredAlerts({ numberOfAlerts = 0, numberOfSummarizedAlerts = 0, action }) { const count = numberOfAlerts - numberOfSummarizedAlerts; if (count > 0) { this.logger.debug(`(${count}) alert${count > 1 ? 's' : ''} ${count > 1 ? 'have' : 'has'} been filtered out for: ${action.actionTypeId}:${action.uuid}`); } } isAlertMuted(alertId) { const muted = this.mutedAlertIdsSet.has(alertId); if (muted) { if (!this.skippedAlerts[alertId] || this.skippedAlerts[alertId] && this.skippedAlerts[alertId].reason !== Reasons.MUTED) { this.logger.debug(`skipping scheduling of actions for '${alertId}' in rule ${this.ruleLabel}: rule is muted`); } this.skippedAlerts[alertId] = { reason: Reasons.MUTED }; return true; } return false; } isExecutableAction(action) { return this.taskRunnerContext.actionsPlugin.isActionExecutable(action.id, action.actionTypeId, { notifyUsage: true }); } isRecoveredAlert(actionGroup) { return actionGroup === this.ruleType.recoveryActionGroup.id; } isExecutableActiveAlert({ alert, action }) { var _action$frequency; const alertId = alert.getId(); const { rule, ruleLabel, logger } = this; const notifyWhen = ((_action$frequency = action.frequency) === null || _action$frequency === void 0 ? void 0 : _action$frequency.notifyWhen) || rule.notifyWhen; if (notifyWhen === 'onActionGroupChange' && !alert.scheduledActionGroupHasChanged()) { if (!this.skippedAlerts[alertId] || this.skippedAlerts[alertId] && this.skippedAlerts[alertId].reason !== Reasons.ACTION_GROUP_NOT_CHANGED) { logger.debug(`skipping scheduling of actions for '${alertId}' in rule ${ruleLabel}: alert is active but action group has not changed`); } this.skippedAlerts[alertId] = { reason: Reasons.ACTION_GROUP_NOT_CHANGED }; return false; } if (notifyWhen === 'onThrottleInterval') { var _action$frequency2, _action$frequency$thr, _rule$throttle; const throttled = (_action$frequency2 = action.frequency) !== null && _action$frequency2 !== void 0 && _action$frequency2.throttle ? alert.isThrottled({ throttle: (_action$frequency$thr = action.frequency.throttle) !== null && _action$frequency$thr !== void 0 ? _action$frequency$thr : null, actionHash: (0, _rule_action_helper.generateActionHash)(action), // generateActionHash must be removed once all the hash identifiers removed from the task state uuid: action.uuid }) : alert.isThrottled({ throttle: (_rule$throttle = rule.throttle) !== null && _rule$throttle !== void 0 ? _rule$throttle : null }); if (throttled) { if (!this.skippedAlerts[alertId] || this.skippedAlerts[alertId] && this.skippedAlerts[alertId].reason !== Reasons.THROTTLED) { logger.debug(`skipping scheduling of actions for '${alertId}' in rule ${ruleLabel}: rule is throttled`); } this.skippedAlerts[alertId] = { reason: Reasons.THROTTLED }; return false; } } return alert.hasScheduledActions(); } getActionGroup(alert) { var _alert$getScheduledAc; return ((_alert$getScheduledAc = alert.getScheduledActionOptions()) === null || _alert$getScheduledAc === void 0 ? void 0 : _alert$getScheduledAc.actionGroup) || this.ruleType.recoveryActionGroup.id; } buildRuleUrl(spaceId, start, end) { if (!this.taskRunnerContext.kibanaBaseUrl) { return; } const relativePath = this.ruleType.getViewInAppRelativeUrl ? this.ruleType.getViewInAppRelativeUrl({ rule: this.rule, start, end }) : `${_ruleDataUtils.triggersActionsRoute}${(0, _ruleDataUtils.getRuleDetailsRoute)(this.rule.id)}`; try { const basePathname = new URL(this.taskRunnerContext.kibanaBaseUrl).pathname; const basePathnamePrefix = basePathname !== '/' ? `${basePathname}` : ''; const spaceIdSegment = spaceId !== 'default' ? `/s/${spaceId}` : ''; const ruleUrl = new URL([basePathnamePrefix, spaceIdSegment, relativePath].join(''), this.taskRunnerContext.kibanaBaseUrl); return { absoluteUrl: ruleUrl.toString(), kibanaBaseUrl: this.taskRunnerContext.kibanaBaseUrl, basePathname: basePathnamePrefix, spaceIdSegment, relativePath }; } catch (error) { this.logger.debug(`Rule "${this.rule.id}" encountered an error while constructing the rule.url variable: ${error.message}`); return; } } getEnqueueOptions(action) { const { apiKey, ruleConsumer, executionId, taskInstance: { params: { spaceId, alertId: ruleId } } } = this; const namespace = spaceId === 'default' ? {} : { namespace: spaceId }; return { id: action.id, params: action.params, spaceId, apiKey: apiKey !== null && apiKey !== void 0 ? apiKey : null, consumer: ruleConsumer, source: (0, _server.asSavedObjectExecutionSource)({ id: ruleId, type: 'alert' }), executionId, relatedSavedObjects: [{ id: ruleId, type: 'alert', namespace: namespace.namespace, typeId: this.ruleType.id }] }; } async generateExecutables(alerts, throttledSummaryActions) { const executables = []; for (const action of this.rule.actions) { const alertsArray = Object.entries(alerts); let summarizedAlerts = null; if (this.shouldGetSummarizedAlerts({ action, throttledSummaryActions })) { summarizedAlerts = await this.getSummarizedAlerts({ action, spaceId: this.taskInstance.params.spaceId, ruleId: this.taskInstance.params.alertId }); if (!(0, _rule_action_helper.isSummaryActionOnInterval)(action)) { this.logNumberOfFilteredAlerts({ numberOfAlerts: alertsArray.length, numberOfSummarizedAlerts: summarizedAlerts.all.count, action }); } } // By doing that we are not cancelling the summary action but just waiting // for the window maintenance to be over before sending the summary action if ((0, _rule_action_helper.isSummaryAction)(action) && this.maintenanceWindowIds.length > 0) { this.logger.debug(`no scheduling of summary actions "${action.id}" for rule "${this.taskInstance.params.alertId}": has active maintenance windows ${this.maintenanceWindowIds.join()}.`); continue; } else if ((0, _rule_action_helper.isSummaryAction)(action)) { if (summarizedAlerts && summarizedAlerts.all.count !== 0) { executables.push({ action, summarizedAlerts }); } continue; } for (const [alertId, alert] of alertsArray) { if (alert.isFilteredOut(summarizedAlerts)) { continue; } if (alert.getMaintenanceWindowIds().length > 0) { this.logger.debug(`no scheduling of actions "${action.id}" for rule "${this.taskInstance.params.alertId}": has active maintenance windows ${alert.getMaintenanceWindowIds().join()}.`); continue; } const actionGroup = this.getActionGroup(alert); if (!this.ruleTypeActionGroups.has(actionGroup)) { this.logger.error(`Invalid action group "${actionGroup}" for rule "${this.ruleType.id}".`); continue; } if (action.group === actionGroup && !this.isAlertMuted(alertId)) { if (this.isRecoveredAlert(action.group) || this.isExecutableActiveAlert({ alert, action })) { executables.push({ action, alert }); } } } } return executables; } canGetSummarizedAlerts() { return !!this.ruleType.alerts && !!this.alertsClient.getSummarizedAlerts; } shouldGetSummarizedAlerts({ action, throttledSummaryActions }) { if (!this.canGetSummarizedAlerts()) { var _action$frequency3; if ((_action$frequency3 = action.frequency) !== null && _action$frequency3 !== void 0 && _action$frequency3.summary) { this.logger.error(`Skipping action "${action.id}" for rule "${this.rule.id}" because the rule type "${this.ruleType.name}" does not support alert-as-data.`); } return false; } // we fetch summarizedAlerts to filter alerts in memory as well if (!(0, _rule_action_helper.isSummaryAction)(action) && !action.alertsFilter) { return false; } if ((0, _rule_action_helper.isSummaryAction)(action) && (0, _rule_action_helper.isSummaryActionThrottled)({ action, throttledSummaryActions, logger: this.logger })) { return false; } return true; } async getSummarizedAlerts({ action, ruleId, spaceId }) { const optionsBase = { ruleId, spaceId, excludedAlertInstanceIds: this.rule.mutedInstanceIds, alertsFilter: action.alertsFilter }; let options; if ((0, _rule_action_helper.isActionOnInterval)(action)) { const throttleMills = (0, _types.parseDuration)(action.frequency.throttle); const start = new Date(Date.now() - throttleMills); options = { ...optionsBase, start, end: new Date() }; } else { options = { ...optionsBase, executionUuid: this.executionId }; } const alerts = await this.alertsClient.getSummarizedAlerts(options); const total = alerts.new.count + alerts.ongoing.count + alerts.recovered.count; return { ...alerts, all: { count: total, data: [...alerts.new.data, ...alerts.ongoing.data, ...alerts.recovered.data] } }; } async actionRunOrAddToBulk({ enqueueOptions, bulkActions }) { if (this.taskRunnerContext.supportsEphemeralTasks && this.ephemeralActionsToSchedule > 0) { this.ephemeralActionsToSchedule--; try { await this.actionsClient.ephemeralEnqueuedExecution(enqueueOptions); } catch (err) { if ((0, _server2.isEphemeralTaskRejectedDueToCapacityError)(err)) { bulkActions.push(enqueueOptions); } } } else { bulkActions.push(enqueueOptions); } } } exports.ExecutionHandler = ExecutionHandler;