"use strict";

var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.DEFAULT_ANOMALIES = void 0;
exports.getMLJobIds = getMLJobIds;
exports.getMLJobs = getMLJobs;
exports.getServiceAnomalies = getServiceAnomalies;
var _boom = _interopRequireDefault(require("@hapi/boom"));
var _lodash = require("lodash");
var _server = require("@kbn/observability-plugin/server");
var _anomaly_detection = require("../../../common/anomaly_detection");
var _environment_filter_values = require("../../../common/environment_filter_values");
var _service_health_status = require("../../../common/service_health_status");
var _transaction_types = require("../../../common/transaction_types");
var _with_apm_span = require("../../utils/with_apm_span");
var _get_ml_jobs_with_apm_group = require("../../lib/anomaly_detection/get_ml_jobs_with_apm_group");
var _apm_ml_anomaly_query = require("../../lib/anomaly_detection/apm_ml_anomaly_query");
var _apm_ml_detectors = require("../../../common/anomaly_detection/apm_ml_detectors");
/*
 * 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 DEFAULT_ANOMALIES = {
  mlJobIds: [],
  serviceAnomalies: []
};
exports.DEFAULT_ANOMALIES = DEFAULT_ANOMALIES;
async function getServiceAnomalies({
  mlClient,
  environment,
  start,
  end
}) {
  return (0, _with_apm_span.withApmSpan)('get_service_anomalies', async () => {
    var _typedAnomalyResponse, _typedAnomalyResponse2;
    if (!mlClient) {
      throw _boom.default.notImplemented(_anomaly_detection.ML_ERRORS.ML_NOT_AVAILABLE);
    }
    const params = {
      body: {
        size: 0,
        query: {
          bool: {
            filter: [...(0, _apm_ml_anomaly_query.apmMlAnomalyQuery)({
              detectorTypes: [_apm_ml_detectors.ApmMlDetectorType.txLatency]
            }), ...(0, _server.rangeQuery)(Math.min(end - 30 * 60 * 1000, start), end, 'timestamp'), {
              terms: {
                // Only retrieving anomalies for default transaction types
                by_field_value: _transaction_types.defaultTransactionTypes
              }
            }]
          }
        },
        aggs: {
          services: {
            composite: {
              size: 5000,
              sources: [{
                serviceName: {
                  terms: {
                    field: 'partition_field_value'
                  }
                }
              }, {
                jobId: {
                  terms: {
                    field: 'job_id'
                  }
                }
              }]
            },
            aggs: {
              metrics: {
                top_metrics: {
                  metrics: [{
                    field: 'actual'
                  }, {
                    field: 'by_field_value'
                  }, {
                    field: 'result_type'
                  }, {
                    field: 'record_score'
                  }],
                  sort: {
                    record_score: 'desc'
                  }
                }
              }
            }
          }
        }
      }
    };
    const [anomalyResponse, jobIds] = await Promise.all([
    // pass an empty array of job ids to anomaly search
    // so any validation is skipped
    (0, _with_apm_span.withApmSpan)('ml_anomaly_search', () => mlClient.mlSystem.mlAnomalySearch(params, [])), getMLJobIds(mlClient.anomalyDetectors, environment)]);
    const typedAnomalyResponse = anomalyResponse;
    const relevantBuckets = (0, _lodash.uniqBy)((0, _lodash.sortBy)( // make sure we only return data for jobs that are available in this space
    (_typedAnomalyResponse = (_typedAnomalyResponse2 = typedAnomalyResponse.aggregations) === null || _typedAnomalyResponse2 === void 0 ? void 0 : _typedAnomalyResponse2.services.buckets.filter(bucket => jobIds.includes(bucket.key.jobId))) !== null && _typedAnomalyResponse !== void 0 ? _typedAnomalyResponse : [],
    // sort by job ID in case there are multiple jobs for one service to
    // ensure consistent results
    bucket => bucket.key.jobId),
    // return one bucket per service
    bucket => bucket.key.serviceName);
    return {
      mlJobIds: jobIds,
      serviceAnomalies: relevantBuckets.map(bucket => {
        const metrics = bucket.metrics.top[0].metrics;
        const anomalyScore = metrics.result_type === 'record' && metrics.record_score ? metrics.record_score : 0;
        const severity = (0, _anomaly_detection.getSeverity)(anomalyScore);
        const healthStatus = (0, _service_health_status.getServiceHealthStatus)({
          severity
        });
        return {
          serviceName: bucket.key.serviceName,
          jobId: bucket.key.jobId,
          transactionType: metrics.by_field_value,
          actualValue: metrics.actual,
          anomalyScore,
          healthStatus
        };
      })
    };
  });
}
async function getMLJobs(anomalyDetectors, environment) {
  const jobs = await (0, _get_ml_jobs_with_apm_group.getMlJobsWithAPMGroup)(anomalyDetectors);

  // to filter out legacy jobs we are filtering by the existence of `apm_ml_version` in `custom_settings`
  // and checking that it is compatable.
  const mlJobs = jobs.filter(job => job.version >= 2);
  if (environment && environment !== _environment_filter_values.ENVIRONMENT_ALL.value) {
    const matchingMLJob = mlJobs.find(job => job.environment === environment);
    if (!matchingMLJob) {
      return [];
    }
    return [matchingMLJob];
  }
  return mlJobs;
}
async function getMLJobIds(anomalyDetectors, environment) {
  const mlJobs = await getMLJobs(anomalyDetectors, environment);
  return mlJobs.map(job => job.jobId);
}