"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getCounts = void 0; exports.getExecutionsPerDayCount = getExecutionsPerDayCount; exports.getInUseTotalCount = getInUseTotalCount; exports.getTotalCount = getTotalCount; exports.replaceFirstAndLastDotSymbols = replaceFirstAndLastDotSymbols; var _parse_connector_type_bucket = require("./lib/parse_connector_type_bucket"); var _common = require("../../common"); /* * 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. */ async function getTotalCount(esClient, kibanaIndex, logger, inMemoryConnectors) { const scriptedMetric = { scripted_metric: { init_script: 'state.types = [:]', map_script: ` String actionType = doc['action.actionTypeId'].value; if (actionType =~ /\.gen-ai/) { String genAiActionType = actionType +"__"+ doc['apiProvider'].value; state.types.put(genAiActionType, state.types.containsKey(genAiActionType) ? state.types.get(genAiActionType) + 1 : 1); } else { state.types.put(actionType, state.types.containsKey(actionType) ? state.types.get(actionType) + 1 : 1); } `, // Combine script is executed per cluster, but we already have a key-value pair per cluster. // Despite docs that say this is optional, this script can't be blank. combine_script: 'return state', // Reduce script is executed across all clusters, so we need to add up all the total from each cluster // This also needs to account for having no data reduce_script: ` HashMap result = new HashMap(); HashMap combinedTypes = new HashMap(); for (state in states) { for (String type : state.types.keySet()) { int typeCount = combinedTypes.containsKey(type) ? combinedTypes.get(type) + state.types.get(type) : state.types.get(type); combinedTypes.put(type, typeCount); } } result.types = combinedTypes; return result; ` } }; try { var _searchResult$aggrega, _searchResult$aggrega2, _searchResult$aggrega3, _inMemoryConnectors$l; const searchResult = await esClient.search({ index: kibanaIndex, size: 0, runtime_mappings: { apiProvider: { type: 'keyword', script: { // add apiProvider to the doc so we can use it in the scripted_metric source: ` if (doc['action.actionTypeId'].value =~ /\.gen-ai/) { emit(params._source["action"]["config"]["apiProvider"]) } ` } } }, body: { query: { bool: { filter: [{ term: { type: 'action' } }] } }, aggs: { byActionTypeId: scriptedMetric } } }); const aggs = (_searchResult$aggrega = (_searchResult$aggrega2 = searchResult.aggregations) === null || _searchResult$aggrega2 === void 0 ? void 0 : (_searchResult$aggrega3 = _searchResult$aggrega2.byActionTypeId.value) === null || _searchResult$aggrega3 === void 0 ? void 0 : _searchResult$aggrega3.types) !== null && _searchResult$aggrega !== void 0 ? _searchResult$aggrega : {}; const { countGenAiProviderTypes, countByType } = getCounts(aggs); if (inMemoryConnectors && inMemoryConnectors.length) { for (const inMemoryConnector of inMemoryConnectors) { const actionTypeId = replaceFirstAndLastDotSymbols(inMemoryConnector.actionTypeId); countByType[actionTypeId] = countByType[actionTypeId] || 0; countByType[actionTypeId]++; } } const totals = Object.keys(aggs).reduce((total, key) => parseInt(aggs[key].toString(), 10) + total, 0) + ((_inMemoryConnectors$l = inMemoryConnectors === null || inMemoryConnectors === void 0 ? void 0 : inMemoryConnectors.length) !== null && _inMemoryConnectors$l !== void 0 ? _inMemoryConnectors$l : 0); return { hasErrors: false, countTotal: totals, countByType, countGenAiProviderTypes }; } catch (err) { const errorMessage = err && err.message ? err.message : err.toString(); logger.warn(`Error executing actions telemetry task: getTotalCount - ${JSON.stringify(err)}`); return { hasErrors: true, errorMessage, countTotal: 0, countByType: {}, countGenAiProviderTypes: {} }; } } async function getInUseTotalCount(esClient, kibanaIndex, logger, referenceType, inMemoryConnectors) { const getInMemoryActionScriptedMetric = actionRefPrefix => ({ scripted_metric: { init_script: 'state.actionRefs = new HashMap(); state.total = 0;', map_script: ` String actionRef = doc['alert.actions.actionRef'].value; String actionTypeId = doc['alert.actions.actionTypeId'].value; if (actionRef.startsWith('${actionRefPrefix}') && state.actionRefs[actionRef] === null) { HashMap map = new HashMap(); map.actionRef = actionRef; map.actionTypeId = actionTypeId; state.actionRefs[actionRef] = map; state.total++; } `, // Combine script is executed per cluster, but we already have a key-value pair per cluster. // Despite docs that say this is optional, this script can't be blank. combine_script: 'return state', // Reduce script is executed across all clusters, so we need to add up all the total from each cluster // This also needs to account for having no data reduce_script: ` Map actionRefs = [:]; long total = 0; for (state in states) { if (state !== null) { total += state.total; for (String k : state.actionRefs.keySet()) { actionRefs.put(k, state.actionRefs.get(k)); } } } Map result = new HashMap(); result.total = total; result.actionRefs = actionRefs; return result; ` } }); const scriptedMetric = { scripted_metric: { init_script: 'state.connectorIds = new HashMap(); state.total = 0;', map_script: ` String connectorId = doc['references.id'].value; String actionRef = doc['references.name'].value; if (state.connectorIds[connectorId] === null) { state.connectorIds[connectorId] = actionRef; state.total++; } `, // Combine script is executed per cluster, but we already have a key-value pair per cluster. // Despite docs that say this is optional, this script can't be blank. combine_script: 'return state', // Reduce script is executed across all clusters, so we need to add up all the total from each cluster // This also needs to account for having no data reduce_script: ` Map connectorIds = [:]; long total = 0; for (state in states) { if (state !== null) { total += state.total; for (String k : state.connectorIds.keySet()) { connectorIds.put(k, connectorIds.containsKey(k) ? connectorIds.get(k) + state.connectorIds.get(k) : state.connectorIds.get(k)); } } } Map result = new HashMap(); result.total = total; result.connectorIds = connectorIds; return result; ` } }; const preconfiguredActionsScriptedMetric = getInMemoryActionScriptedMetric('preconfigured:'); const systemActionsScriptedMetric = getInMemoryActionScriptedMetric('system_action:'); const mustQuery = [{ bool: { should: [{ nested: { path: 'references', query: { bool: { filter: { bool: { must: [{ term: { 'references.type': 'action' } }] } } } } } }, { nested: { path: 'alert.actions', query: { bool: { filter: { bool: { must: [{ prefix: { 'alert.actions.actionRef': { value: 'preconfigured:' } } }] } } } } } }, { nested: { path: 'alert.actions', query: { bool: { filter: { bool: { must: [{ prefix: { 'alert.actions.actionRef': { value: 'system_action:' } } }] } } } } } }] } }]; if (!!referenceType) { mustQuery.push({ term: { type: referenceType } }); } try { var _actionResults$aggreg, _actionResults$aggreg2, _actionResults$aggreg3, _actionResults$aggreg4, _actionResults$aggreg5, _preconfiguredActions, _systemActionsAggs$to, _aggs$connectorIds, _preconfiguredActions2, _systemActionsAggs$ac, _aggs$total; const actionResults = await esClient.search({ index: kibanaIndex, size: 0, body: { query: { bool: { filter: { bool: { must_not: { term: { type: 'action_task_params' } }, must: mustQuery } } } }, aggs: { refs: { nested: { path: 'references' }, aggs: { actionRefIds: scriptedMetric } }, preconfigured_actions: { nested: { path: 'alert.actions' }, aggs: { preconfiguredActionRefIds: preconfiguredActionsScriptedMetric } }, system_actions: { nested: { path: 'alert.actions' }, aggs: { systemActionRefIds: systemActionsScriptedMetric } } } } }); const aggs = (_actionResults$aggreg = actionResults.aggregations) === null || _actionResults$aggreg === void 0 ? void 0 : _actionResults$aggreg.refs.actionRefIds.value; const preconfiguredActionsAggs = (_actionResults$aggreg2 = actionResults.aggregations) === null || _actionResults$aggreg2 === void 0 ? void 0 : (_actionResults$aggreg3 = _actionResults$aggreg2.preconfigured_actions) === null || _actionResults$aggreg3 === void 0 ? void 0 : _actionResults$aggreg3.preconfiguredActionRefIds.value; const systemActionsAggs = (_actionResults$aggreg4 = actionResults.aggregations) === null || _actionResults$aggreg4 === void 0 ? void 0 : (_actionResults$aggreg5 = _actionResults$aggreg4.system_actions) === null || _actionResults$aggreg5 === void 0 ? void 0 : _actionResults$aggreg5.systemActionRefIds.value; const totalInMemoryActions = ((_preconfiguredActions = preconfiguredActionsAggs === null || preconfiguredActionsAggs === void 0 ? void 0 : preconfiguredActionsAggs.total) !== null && _preconfiguredActions !== void 0 ? _preconfiguredActions : 0) + ((_systemActionsAggs$to = systemActionsAggs === null || systemActionsAggs === void 0 ? void 0 : systemActionsAggs.total) !== null && _systemActionsAggs$to !== void 0 ? _systemActionsAggs$to : 0); const { hits: actions } = await esClient.search({ index: kibanaIndex, _source_includes: ['action', 'namespaces'], body: { query: { bool: { must: [{ term: { type: 'action' } }, { terms: { _id: Object.entries((_aggs$connectorIds = aggs === null || aggs === void 0 ? void 0 : aggs.connectorIds) !== null && _aggs$connectorIds !== void 0 ? _aggs$connectorIds : {}).map(([key]) => `action:${key}`) } }] } } } }); const countByActionTypeId = actions.hits.reduce((actionTypeCount, action) => { const actionSource = action._source; const alertTypeId = replaceFirstAndLastDotSymbols(actionSource.action.actionTypeId); const currentCount = actionTypeCount[alertTypeId] !== undefined ? actionTypeCount[alertTypeId] : 0; actionTypeCount[alertTypeId] = currentCount + 1; return actionTypeCount; }, {}); const namespacesList = actions.hits.reduce((_namespaces, action) => { var _action$_source$names, _action$_source; const namespaces = (_action$_source$names = (_action$_source = action._source) === null || _action$_source === void 0 ? void 0 : _action$_source.namespaces) !== null && _action$_source$names !== void 0 ? _action$_source$names : ['default']; namespaces.forEach(namespace => { if (!_namespaces.has(namespace)) { _namespaces.add(namespace); } }); return _namespaces; }, new Set()); const countEmailByService = actions.hits.filter(action => action._source.action.actionTypeId === '.email').reduce((emailServiceCount, action) => { var _action$config$servic, _action$config; const service = (_action$config$servic = (_action$config = action._source.action.config) === null || _action$config === void 0 ? void 0 : _action$config.service) !== null && _action$config$servic !== void 0 ? _action$config$servic : 'other'; const currentCount = emailServiceCount[service] !== undefined ? emailServiceCount[service] : 0; emailServiceCount[service] = currentCount + 1; return emailServiceCount; }, {}); let preconfiguredAlertHistoryConnectors = 0; const inMemoryActionsRefs = [...Object.values((_preconfiguredActions2 = preconfiguredActionsAggs === null || preconfiguredActionsAggs === void 0 ? void 0 : preconfiguredActionsAggs.actionRefs) !== null && _preconfiguredActions2 !== void 0 ? _preconfiguredActions2 : {}), ...Object.values((_systemActionsAggs$ac = systemActionsAggs === null || systemActionsAggs === void 0 ? void 0 : systemActionsAggs.actionRefs) !== null && _systemActionsAggs$ac !== void 0 ? _systemActionsAggs$ac : {})]; for (const { actionRef, actionTypeId: rawActionTypeId } of inMemoryActionsRefs) { const actionTypeId = replaceFirstAndLastDotSymbols(rawActionTypeId); countByActionTypeId[actionTypeId] = countByActionTypeId[actionTypeId] || 0; countByActionTypeId[actionTypeId]++; if (actionRef === `preconfigured:${_common.AlertHistoryEsIndexConnectorId}`) { preconfiguredAlertHistoryConnectors++; } if (inMemoryConnectors && actionTypeId === '__email') { var _inMemoryConnectors$f, _inMemoryConnectors$f2, _inMemoryConnectors$f3; const inMemoryConnectorId = actionRef.split(':')[1]; const service = (_inMemoryConnectors$f = (_inMemoryConnectors$f2 = inMemoryConnectors.find(connector => connector.id === inMemoryConnectorId)) === null || _inMemoryConnectors$f2 === void 0 ? void 0 : (_inMemoryConnectors$f3 = _inMemoryConnectors$f2.config) === null || _inMemoryConnectors$f3 === void 0 ? void 0 : _inMemoryConnectors$f3.service) !== null && _inMemoryConnectors$f !== void 0 ? _inMemoryConnectors$f : 'other'; const currentCount = countEmailByService[service] !== undefined ? countEmailByService[service] : 0; countEmailByService[service] = currentCount + 1; } } return { hasErrors: false, countTotal: ((_aggs$total = aggs === null || aggs === void 0 ? void 0 : aggs.total) !== null && _aggs$total !== void 0 ? _aggs$total : 0) + totalInMemoryActions, countByType: countByActionTypeId, countByAlertHistoryConnectorType: preconfiguredAlertHistoryConnectors, countEmailByService, countNamespaces: namespacesList.size }; } catch (err) { const errorMessage = err && err.message ? err.message : err.toString(); logger.warn(`Error executing actions telemetry task: getInUseTotalCount - ${JSON.stringify(err)}`); return { hasErrors: true, errorMessage, countTotal: 0, countByType: {}, countByAlertHistoryConnectorType: 0, countEmailByService: {}, countNamespaces: 0 }; } } const getCounts = aggs => { const countGenAiProviderTypes = {}; const countByType = Object.keys(aggs).reduce((obj, key) => { const genAiKey = '.gen-ai'; if (key.includes(genAiKey)) { const newKey = replaceFirstAndLastDotSymbols(genAiKey); if (obj[newKey] != null) { obj[newKey] = obj[newKey] + aggs[key]; } else { obj[newKey] = aggs[key]; } const genAiProvder = key.split(`${genAiKey}__`)[1]; if (countGenAiProviderTypes[genAiProvder] != null) { countGenAiProviderTypes[genAiProvder] = obj[genAiProvder] + aggs[key]; } else { countGenAiProviderTypes[genAiProvder] = aggs[key]; } return obj; } obj[replaceFirstAndLastDotSymbols(key)] = aggs[key]; return obj; }, {}); return { countByType, countGenAiProviderTypes }; }; exports.getCounts = getCounts; function replaceFirstAndLastDotSymbols(strToReplace) { const hasFirstSymbolDot = strToReplace.startsWith('.'); const appliedString = hasFirstSymbolDot ? strToReplace.replace('.', '__') : strToReplace; const hasLastSymbolDot = strToReplace.endsWith('.'); return hasLastSymbolDot ? `${appliedString.slice(0, -1)}__` : appliedString; } async function getExecutionsPerDayCount(esClient, eventLogIndex, logger) { const scriptedMetric = { scripted_metric: { init_script: 'state.connectorTypes = [:]; state.total = 0;', map_script: ` if (doc['kibana.saved_objects.type'].value == 'action') { String connectorType = doc['kibana.saved_objects.type_id'].value; state.connectorTypes.put(connectorType, state.connectorTypes.containsKey(connectorType) ? state.connectorTypes.get(connectorType) + 1 : 1); state.total++; } `, // Combine script is executed per cluster, but we already have a key-value pair per cluster. // Despite docs that say this is optional, this script can't be blank. combine_script: 'return state', // Reduce script is executed across all clusters, so we need to add up all the total from each cluster // This also needs to account for having no data reduce_script: ` Map connectorTypes = [:]; long total = 0; for (state in states) { if (state !== null) { total += state.total; for (String k : state.connectorTypes.keySet()) { connectorTypes.put(k, connectorTypes.containsKey(k) ? connectorTypes.get(k) + state.connectorTypes.get(k) : state.connectorTypes.get(k)); } } } Map result = new HashMap(); result.total = total; result.connectorTypes = connectorTypes; return result; ` } }; try { var _actionResults$aggreg6, _actionResults$aggreg7, _actionResults$aggreg8, _actionResults$aggreg9, _actionResults$aggreg10, _actionResults$aggreg11; const actionResults = await esClient.search({ index: eventLogIndex, size: 0, body: { query: { bool: { filter: { bool: { must: [{ term: { 'event.action': 'execute' } }, { term: { 'event.provider': 'actions' } }, { range: { '@timestamp': { gte: 'now-1d' } } }] } } } }, aggs: { totalExecutions: { nested: { path: 'kibana.saved_objects' }, aggs: { byConnectorTypeId: scriptedMetric } }, failedExecutions: { filter: { bool: { filter: [{ term: { 'event.outcome': 'failure' } }] } }, aggs: { refs: { nested: { path: 'kibana.saved_objects' }, aggs: { byConnectorTypeId: scriptedMetric } } } }, avgDuration: { avg: { field: 'event.duration' } }, avgDurationByType: { nested: { path: 'kibana.saved_objects' }, aggs: { actionSavedObjects: { filter: { term: { 'kibana.saved_objects.type': 'action' } }, aggs: { byTypeId: { terms: { field: 'kibana.saved_objects.type_id' }, aggs: { refs: { reverse_nested: {}, aggs: { avgDuration: { avg: { field: 'event.duration' } } } } } } } } } }, count_connector_types_by_action_run_outcome_per_day: { nested: { path: 'kibana.saved_objects' }, aggs: { actionSavedObjects: { filter: { term: { 'kibana.saved_objects.type': 'action' } }, aggs: { connector_types: { terms: { field: 'kibana.saved_objects.type_id' }, aggs: { outcome: { reverse_nested: {}, aggs: { count: { terms: { field: 'event.outcome' } } } } } } } } } } } } }); // @ts-expect-error aggegation type is not specified const aggsExecutions = (_actionResults$aggreg6 = actionResults.aggregations.totalExecutions) === null || _actionResults$aggreg6 === void 0 ? void 0 : _actionResults$aggreg6.byConnectorTypeId.value; // convert nanoseconds to milliseconds const aggsAvgExecutionTime = Math.round( // @ts-expect-error aggegation type is not specified actionResults.aggregations.avgDuration.value / (1000 * 1000)); const aggsFailedExecutions = // @ts-expect-error aggegation type is not specified (_actionResults$aggreg7 = actionResults.aggregations.failedExecutions) === null || _actionResults$aggreg7 === void 0 ? void 0 : (_actionResults$aggreg8 = _actionResults$aggreg7.refs) === null || _actionResults$aggreg8 === void 0 ? void 0 : _actionResults$aggreg8.byConnectorTypeId.value; const avgDurationByType = // @ts-expect-error aggegation type is not specified (_actionResults$aggreg9 = actionResults.aggregations.avgDurationByType) === null || _actionResults$aggreg9 === void 0 ? void 0 : (_actionResults$aggreg10 = _actionResults$aggreg9.actionSavedObjects) === null || _actionResults$aggreg10 === void 0 ? void 0 : (_actionResults$aggreg11 = _actionResults$aggreg10.byTypeId) === null || _actionResults$aggreg11 === void 0 ? void 0 : _actionResults$aggreg11.buckets; const avgExecutionTimeByType = avgDurationByType.reduce( // @ts-expect-error aggegation type is not specified (res, bucket) => { res[replaceFirstAndLastDotSymbols(bucket.key)] = bucket === null || bucket === void 0 ? void 0 : bucket.refs.avgDuration.value; return res; }, {}); const aggsCountConnectorTypeByActionRun = actionResults.aggregations; return { hasErrors: false, countTotal: aggsExecutions.total, countByType: Object.entries(aggsExecutions.connectorTypes).reduce((res, [key, value]) => { // @ts-expect-error aggegation type is not specified res[replaceFirstAndLastDotSymbols(key)] = value; return res; }, {}), countFailed: aggsFailedExecutions.total, countFailedByType: Object.entries(aggsFailedExecutions.connectorTypes).reduce((res, [key, value]) => { // @ts-expect-error aggegation type is not specified res[replaceFirstAndLastDotSymbols(key)] = value; return res; }, {}), avgExecutionTime: aggsAvgExecutionTime, avgExecutionTimeByType, countRunOutcomeByConnectorType: (0, _parse_connector_type_bucket.parseActionRunOutcomeByConnectorTypesBucket)(aggsCountConnectorTypeByActionRun.count_connector_types_by_action_run_outcome_per_day.actionSavedObjects.connector_types.buckets) }; } catch (err) { const errorMessage = err && err.message ? err.message : err.toString(); logger.warn(`Error executing actions telemetry task: getExecutionsPerDayCount - ${JSON.stringify(err)}`); return { hasErrors: true, errorMessage, countTotal: 0, countByType: {}, countFailed: 0, countFailedByType: {}, avgExecutionTime: 0, avgExecutionTimeByType: {}, countRunOutcomeByConnectorType: {} }; } }