"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 _create_execute_function = require("@kbn/actions-plugin/server/create_execute_function");
var _alertingStateTypes = require("@kbn/alerting-state-types");
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 = [];
      let bulkActionsResponse = [];
      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: _alertingStateTypes.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: _alertingStateTypes.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().toISOString()
            };
          }
          logActions[action.id] = {
            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[action.id] = {
            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)) {
          const response = await this.actionsClient.bulkEnqueueExecution(c);
          if (response.errors) {
            bulkActionsResponse = bulkActionsResponse.concat(response.items.filter(i => i.response === _create_execute_function.ExecutionResponseType.QUEUED_ACTIONS_LIMIT_ERROR));
          }
        }
      }
      if (!!bulkActionsResponse.length) {
        for (const r of bulkActionsResponse) {
          if (r.response === _create_execute_function.ExecutionResponseType.QUEUED_ACTIONS_LIMIT_ERROR) {
            ruleRunMetricsStore.setHasReachedQueuedActionsLimit(true);
            ruleRunMetricsStore.decrementNumberOfTriggeredActions();
            ruleRunMetricsStore.decrementNumberOfTriggeredActionsByConnectorType(r.actionTypeId);
            ruleRunMetricsStore.setTriggeredActionsStatusByConnectorType({
              actionTypeId: r.actionTypeId,
              status: _alertingStateTypes.ActionsCompletion.PARTIAL
            });
            logger.debug(`Rule "${this.rule.id}" skipped scheduling action "${r.id}" because the maximum number of queued actions has been reached.`);
            delete logActions[r.id];
          }
        }
      }
      const logActionsValues = Object.values(logActions);
      if (!!logActionsValues.length) {
        for (const action of logActionsValues) {
          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
      }],
      actionTypeId: action.actionTypeId
    };
  }
  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) {
        var _action$frequency3;
        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;
        }

        // only actions with notifyWhen set to "on status change" should return
        // notifications for flapping pending recovered alerts
        if (alert.getPendingRecoveredCount() > 0 && ((_action$frequency3 = action.frequency) === null || _action$frequency3 === void 0 ? void 0 : _action$frequency3.notifyWhen) !== _common.RuleNotifyWhen.CHANGE) {
          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$frequency4;
      if ((_action$frequency4 = action.frequency) !== null && _action$frequency4 !== void 0 && _action$frequency4.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;