"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.DefaultSummaryClient = void 0; var _sloSchema = require("@kbn/slo-schema"); var _moment = _interopRequireDefault(require("moment")); var _constants = require("../../assets/constants"); var _services = require("../../domain/services"); var _date_range = require("../../domain/services/date_range"); var _number = require("../../utils/number"); /* * 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. */ class DefaultSummaryClient { constructor(esClient) { this.esClient = esClient; } async fetchSummary(compositeSloList) { const dateRangeByCompositeSlo = compositeSloList.reduce((acc, compositeSlo) => { acc[compositeSlo.id] = (0, _date_range.toDateRange)(compositeSlo.timeWindow); return acc; }, {}); const searches = compositeSloList.flatMap(compositeSlo => [{ index: _constants.SLO_DESTINATION_INDEX_PATTERN }, generateSearchQuery(compositeSlo, dateRangeByCompositeSlo[compositeSlo.id])]); const summaryByCompositeSlo = {}; if (searches.length === 0) { return summaryByCompositeSlo; } const result = await this.esClient.msearch({ searches }); for (let i = 0; i < result.responses.length; i++) { var _aggregations$bySloId, _aggregations$bySloId2; const compositeSlo = compositeSloList[i]; // @ts-ignore const { aggregations = {} } = result.responses[i]; const buckets = (_aggregations$bySloId = aggregations === null || aggregations === void 0 ? void 0 : (_aggregations$bySloId2 = aggregations.bySloId) === null || _aggregations$bySloId2 === void 0 ? void 0 : _aggregations$bySloId2.buckets) !== null && _aggregations$bySloId !== void 0 ? _aggregations$bySloId : []; if (_sloSchema.calendarAlignedTimeWindowSchema.is(compositeSlo.timeWindow) && _sloSchema.timeslicesBudgetingMethodSchema.is(compositeSlo.budgetingMethod)) { let sliValue = 0; let totalWeights = 0; let maxSloTotalSlices = 0; for (const bucket of buckets) { const sourceSloId = bucket.key; const sourceSloGoodSlices = bucket.good.value; const sourceSloTotalSlices = bucket.total.value; maxSloTotalSlices = sourceSloTotalSlices > maxSloTotalSlices ? sourceSloTotalSlices : maxSloTotalSlices; const sourceSloSliValue = (0, _services.computeSLI)(sourceSloGoodSlices, sourceSloTotalSlices); const sourceSloWeight = compositeSlo.sources.find(source => source.id === sourceSloId).weight; // used to build the query, therefore exists totalWeights += sourceSloWeight; sliValue += sourceSloSliValue < 0 ? 0 : sourceSloWeight * sourceSloSliValue; } sliValue /= totalWeights === 0 ? 1 : totalWeights; const totalSlicesInCalendar = computeTotalSlicesFromDateRange(dateRangeByCompositeSlo[compositeSlo.id], compositeSlo.objective.timesliceWindow); const initialErrorBudget = 1 - compositeSlo.objective.target; const errorBudgetConsumed = (1 - sliValue) / initialErrorBudget * (maxSloTotalSlices / totalSlicesInCalendar); const errorBudget = (0, _services.toErrorBudget)(initialErrorBudget, errorBudgetConsumed); summaryByCompositeSlo[compositeSlo.id] = { sliValue: (0, _number.toHighPrecision)(sliValue), errorBudget, status: (0, _services.computeSummaryStatus)(compositeSlo, sliValue, errorBudget) }; } else { let sliValue = 0; let totalWeights = 0; for (const bucket of buckets) { const sourceSloId = bucket.key; const sourceSloGood = bucket.good.value; const sourceSloTotal = bucket.total.value; const sourceSloSliValue = (0, _services.computeSLI)(sourceSloGood, sourceSloTotal); const sourceSloWeight = compositeSlo.sources.find(source => source.id === sourceSloId).weight; // used to build the query, therefore exists totalWeights += sourceSloWeight; sliValue += sourceSloSliValue < 0 ? 0 : sourceSloWeight * sourceSloSliValue; } sliValue /= totalWeights === 0 ? 1 : totalWeights; const initialErrorBudget = 1 - compositeSlo.objective.target; const errorBudgetConsumed = (1 - sliValue) / initialErrorBudget; const errorBudget = (0, _services.toErrorBudget)(initialErrorBudget, errorBudgetConsumed, _sloSchema.calendarAlignedTimeWindowSchema.is(compositeSlo.timeWindow)); summaryByCompositeSlo[compositeSlo.id] = { sliValue: (0, _number.toHighPrecision)(sliValue), errorBudget, status: (0, _services.computeSummaryStatus)(compositeSlo, sliValue, errorBudget) }; } } return summaryByCompositeSlo; } } exports.DefaultSummaryClient = DefaultSummaryClient; function generateSearchQuery(compositeSlo, dateRange) { return { size: 0, query: { bool: { filter: [{ range: { '@timestamp': { gte: dateRange.from.toISOString(), lt: dateRange.to.toISOString() } } }], should: compositeSlo.sources.map(source => ({ bool: { must: [{ term: { 'slo.id': source.id } }, { term: { 'slo.revision': source.revision } }] } })), minimum_should_match: 1 } }, ...(_sloSchema.occurrencesBudgetingMethodSchema.is(compositeSlo.budgetingMethod) && { aggs: { bySloId: { terms: { field: 'slo.id' }, aggs: { good: { sum: { field: 'slo.numerator' } }, total: { sum: { field: 'slo.denominator' } } } } } }), ...(_sloSchema.timeslicesBudgetingMethodSchema.is(compositeSlo.budgetingMethod) && { aggs: { bySloId: { terms: { field: 'slo.id' }, aggs: { good: { sum: { field: 'slo.isGoodSlice' } }, total: { value_count: { field: 'slo.isGoodSlice' } } } } } }) }; } function computeTotalSlicesFromDateRange(dateRange, timesliceWindow) { const dateRangeDurationInUnit = (0, _moment.default)(dateRange.to).diff(dateRange.from, (0, _sloSchema.toMomentUnitOfTime)(timesliceWindow.unit)); return Math.ceil(dateRangeDurationInUnit / timesliceWindow.value); }