"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.extractAggId = void 0; exports.toExpression = toExpression; var _lodash = require("lodash"); var _seedrandom = _interopRequireDefault(require("seedrandom")); var _public = require("@kbn/data-plugin/public"); var _common = require("@kbn/data-plugin/common"); var _public2 = require("@kbn/expressions-plugin/public"); var _operations = require("./operations"); var _helpers = require("./operations/definitions/helpers"); var _dedupe_aggs = require("./dedupe_aggs"); var _time_shift_utils = require("./time_shift_utils"); var _utils = require("./utils"); /* * 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. */ // esAggs column ID manipulation functions const extractAggId = id => id.split('.')[0].split('-')[2]; exports.extractAggId = extractAggId; const updatePositionIndex = (currentId, newIndex) => { const [fullId, percentile] = currentId.split('.'); const idParts = fullId.split('-'); idParts[1] = String(newIndex); return idParts.join('-') + (percentile ? `.${percentile}` : ''); }; function getExpressionForLayer(layer, indexPattern, uiSettings, dateRange, nowInstant, searchSessionId) { const { columnOrder } = layer; if (columnOrder.length === 0 || !indexPattern) { return null; } const columns = { ...layer.columns }; // make sure the columns are in topological order const sortedColumns = sortedReferences(columnOrder.map(colId => [colId, columns[colId]])); sortedColumns.forEach(columnId => { const column = columns[columnId]; const rootDef = _operations.operationDefinitionMap[column.operationType]; if ('references' in column && rootDef.filterable && column.filter) { // inherit filter to all referenced operations function setFilterForAllReferences(currentColumn) { if (!('references' in currentColumn)) return; currentColumn.references.forEach(referenceColumnId => { let referencedColumn = columns[referenceColumnId]; const hasFilter = referencedColumn.filter; const referenceDef = _operations.operationDefinitionMap[column.operationType]; if (referenceDef.filterable && !hasFilter) { referencedColumn = { ...referencedColumn, filter: column.filter }; columns[referenceColumnId] = referencedColumn; } if (!hasFilter) { // only push through the current filter if the current level doesn't have its own setFilterForAllReferences(referencedColumn); } }); } setFilterForAllReferences(column); } if ('references' in column && rootDef.shiftable && column.timeShift) { // inherit time shift to all referenced operations function setTimeShiftForAllReferences(currentColumn) { if (!('references' in currentColumn)) return; currentColumn.references.forEach(referenceColumnId => { let referencedColumn = columns[referenceColumnId]; const hasShift = referencedColumn.timeShift; const referenceDef = _operations.operationDefinitionMap[column.operationType]; if (referenceDef.shiftable && !hasShift) { referencedColumn = { ...referencedColumn, timeShift: column.timeShift }; columns[referenceColumnId] = referencedColumn; } if (!hasShift) { // only push through the current time shift if the current level doesn't have its own setTimeShiftForAllReferences(referencedColumn); } }); } setTimeShiftForAllReferences(column); } }); const columnEntries = columnOrder.map(colId => [colId, columns[colId]]); const [referenceEntries, esAggEntries] = (0, _lodash.partition)(columnEntries, ([, col]) => { var _operationDefinitionM, _operationDefinitionM2; return ((_operationDefinitionM = _operations.operationDefinitionMap[col.operationType]) === null || _operationDefinitionM === void 0 ? void 0 : _operationDefinitionM.input) === 'fullReference' || ((_operationDefinitionM2 = _operations.operationDefinitionMap[col.operationType]) === null || _operationDefinitionM2 === void 0 ? void 0 : _operationDefinitionM2.input) === 'managedReference'; }); const firstDateHistogramColumn = columnEntries.find(([, col]) => col.operationType === 'date_histogram'); const hasDateHistogram = Boolean(firstDateHistogramColumn); if (referenceEntries.length || esAggEntries.length) { let aggs = []; const expressions = []; const histogramBarsTarget = uiSettings.get(_public.UI_SETTINGS.HISTOGRAM_BAR_TARGET); sortedReferences(referenceEntries).forEach(colId => { const col = columns[colId]; const def = _operations.operationDefinitionMap[col.operationType]; if (def.input === 'fullReference' || def.input === 'managedReference') { expressions.push(...def.toExpression(layer, colId, indexPattern, { dateRange, now: nowInstant, targetBars: histogramBarsTarget })); } }); const orderedColumnIds = esAggEntries.map(([colId]) => colId); let esAggsIdMap = {}; const aggExpressionToEsAggsIdMap = new Map(); esAggEntries.forEach(([colId, col], index) => { const def = _operations.operationDefinitionMap[col.operationType]; if (def.input !== 'fullReference' && def.input !== 'managedReference') { var _col$filter; const aggId = String(index); const wrapInFilter = Boolean(def.filterable && ((_col$filter = col.filter) === null || _col$filter === void 0 ? void 0 : _col$filter.query)); const wrapInTimeFilter = def.canReduceTimeRange && !hasDateHistogram && col.reducedTimeRange && indexPattern.timeFieldName; let aggAst = def.toEsAggsFn({ ...col, timeShift: (0, _time_shift_utils.resolveTimeShift)(col.timeShift, dateRange, histogramBarsTarget, hasDateHistogram) }, wrapInFilter || wrapInTimeFilter ? `${aggId}-metric` : aggId, indexPattern, layer, uiSettings, orderedColumnIds, _operations.operationDefinitionMap); if (wrapInFilter || wrapInTimeFilter) { aggAst = (0, _public2.buildExpressionFunction)('aggFilteredMetric', { id: String(index), enabled: true, schema: 'metric', customBucket: (0, _public2.buildExpression)([(0, _public2.buildExpressionFunction)('aggFilter', { id: `${index}-filter`, enabled: true, schema: 'bucket', filter: col.filter && (0, _common.queryToAst)(col.filter), timeWindow: wrapInTimeFilter ? col.reducedTimeRange : undefined, timeShift: (0, _time_shift_utils.resolveTimeShift)(col.timeShift, dateRange, histogramBarsTarget, hasDateHistogram) })]), customMetric: (0, _public2.buildExpression)({ type: 'expression', chain: [aggAst] }), timeShift: (0, _time_shift_utils.resolveTimeShift)(col.timeShift, dateRange, histogramBarsTarget, hasDateHistogram) }).toAst(); } const expressionBuilder = (0, _public2.buildExpression)({ type: 'expression', chain: [aggAst] }); aggs.push(expressionBuilder); const esAggsId = window.ELASTIC_LENS_DELAY_SECONDS ? `col-${index + (col.isBucketed ? 0 : 1)}-${aggId}` : `col-${index}-${aggId}`; esAggsIdMap[esAggsId] = [{ ...col, id: colId, label: col.customLabel ? col.label : _operations.operationDefinitionMap[col.operationType].getDefaultLabel(col, indexPattern, layer.columns) }]; aggExpressionToEsAggsIdMap.set(expressionBuilder, esAggsId); } }); if (window.ELASTIC_LENS_DELAY_SECONDS) { aggs.push((0, _public2.buildExpression)({ type: 'expression', chain: [(0, _public2.buildExpressionFunction)('aggShardDelay', { id: 'the-delay', enabled: true, schema: 'metric', delay: `${window.ELASTIC_LENS_DELAY_SECONDS}s` }).toAst()] })); } const allOperations = (0, _lodash.uniq)(esAggEntries.map(([_, column]) => _operations.operationDefinitionMap[column.operationType])); // De-duplicate aggs for supported operations const dedupedResult = (0, _dedupe_aggs.dedupeAggs)(aggs, esAggsIdMap, aggExpressionToEsAggsIdMap, allOperations); aggs = dedupedResult.aggs; esAggsIdMap = dedupedResult.esAggsIdMap; // Apply any operation-specific custom optimizations allOperations.forEach(operation => { var _operation$optimizeEs; const optimizeAggs = (_operation$optimizeEs = operation.optimizeEsAggs) === null || _operation$optimizeEs === void 0 ? void 0 : _operation$optimizeEs.bind(operation); if (optimizeAggs) { const { aggs: newAggs, esAggsIdMap: newIdMap } = optimizeAggs(aggs, esAggsIdMap, aggExpressionToEsAggsIdMap); aggs = newAggs; esAggsIdMap = newIdMap; } }); /* Update ID mappings with new agg array positions. Given this esAggs-ID-to-original-column map after percentile (for example) optimization: col-0-0: column1 col-?-1.34: column2 (34th percentile) col-2-2: column3 col-?-1.98: column4 (98th percentile) and this array of aggs 0: { id: 0 } 1: { id: 2 } 2: { id: 1 } We need to update the anticipated agg indicies to match the aggs array: col-0-0: column1 col-2-1.34: column2 (34th percentile) col-1-2: column3 col-3-3.98: column4 (98th percentile) */ const updatedEsAggsIdMap = {}; let counter = 0; const esAggsIds = Object.keys(esAggsIdMap); aggs.forEach(builder => { var _builder$functions$0$; const esAggId = (_builder$functions$0$ = builder.functions[0].getArgument('id')) === null || _builder$functions$0$ === void 0 ? void 0 : _builder$functions$0$[0]; const matchingEsAggColumnIds = esAggsIds.filter(id => extractAggId(id) === esAggId); matchingEsAggColumnIds.forEach(currentId => { const currentColumn = esAggsIdMap[currentId][0]; const aggIndex = window.ELASTIC_LENS_DELAY_SECONDS ? counter + (currentColumn.isBucketed ? 0 : 1) : counter; const newId = updatePositionIndex(currentId, aggIndex); updatedEsAggsIdMap[newId] = esAggsIdMap[currentId]; counter++; }); }); const columnsWithFormatters = columnEntries.filter(([, col]) => { var _col$params, _col$params2; return (0, _helpers.isColumnOfType)('range', col) && ((_col$params = col.params) === null || _col$params === void 0 ? void 0 : _col$params.parentFormat) || (0, _helpers.isColumnFormatted)(col) && ((_col$params2 = col.params) === null || _col$params2 === void 0 ? void 0 : _col$params2.format); }); const formatterOverrides = columnsWithFormatters.map(([id, col]) => { var _format$params; // TODO: improve the type handling here const parentFormat = 'parentFormat' in col.params ? col.params.parentFormat : undefined; const format = col.params.format; const base = { type: 'function', function: 'lens_format_column', arguments: { format: format ? [format.id] : [''], columnId: [id], decimals: typeof (format === null || format === void 0 ? void 0 : (_format$params = format.params) === null || _format$params === void 0 ? void 0 : _format$params.decimals) === 'number' ? [format.params.decimals] : [], suffix: format !== null && format !== void 0 && format.params && 'suffix' in format.params && format.params.suffix ? [format.params.suffix] : [], compact: format !== null && format !== void 0 && format.params && 'compact' in format.params && format.params.compact ? [format.params.compact] : [], pattern: format !== null && format !== void 0 && format.params && 'pattern' in format.params && format.params.pattern ? [format.params.pattern] : [], fromUnit: format !== null && format !== void 0 && format.params && 'fromUnit' in format.params && format.params.fromUnit ? [format.params.fromUnit] : [], toUnit: format !== null && format !== void 0 && format.params && 'toUnit' in format.params && format.params.toUnit ? [format.params.toUnit] : [], parentFormat: parentFormat ? [JSON.stringify(parentFormat)] : [] } }; return base; }); const columnsWithTimeScale = columnEntries.filter(([, col]) => col.timeScale && _operations.operationDefinitionMap[col.operationType].timeScalingMode && _operations.operationDefinitionMap[col.operationType].timeScalingMode !== 'disabled'); const timeScaleFunctions = columnsWithTimeScale.flatMap(([id, col]) => { const scalingCall = { type: 'function', function: 'lens_time_scale', arguments: { dateColumnId: firstDateHistogramColumn !== null && firstDateHistogramColumn !== void 0 && firstDateHistogramColumn.length ? [firstDateHistogramColumn[0]] : [], inputColumnId: [id], outputColumnId: [id], outputColumnName: [col.customLabel ? col.label : _operations.operationDefinitionMap[col.operationType].getDefaultLabel(col, indexPattern, layer.columns)], targetUnit: [col.timeScale], reducedTimeRange: col.reducedTimeRange ? [col.reducedTimeRange] : [] } }; const formatCall = { type: 'function', function: 'lens_format_column', arguments: { format: [''], columnId: [id], parentFormat: [JSON.stringify({ id: 'suffix', params: { unit: col.timeScale } })] } }; return [scalingCall, formatCall]; }); if (esAggEntries.length === 0) { return { type: 'expression', chain: [{ type: 'function', function: 'createTable', arguments: { ids: [], names: [], rowCount: [1] } }, ...expressions, ...formatterOverrides, ...timeScaleFunctions] }; } const allDateHistogramFields = Object.values(columns).map(column => (0, _helpers.isColumnOfType)('date_histogram', column) && !column.params.ignoreTimeRange ? column.sourceField : null).filter(field => Boolean(field)); return { type: 'expression', chain: [{ type: 'function', function: 'kibana', arguments: {} }, (0, _public2.buildExpressionFunction)('esaggs', { index: (0, _public2.buildExpression)([(0, _public2.buildExpressionFunction)('indexPatternLoad', { id: indexPattern.id })]), aggs, metricsAtAllLevels: false, partialRows: false, timeFields: allDateHistogramFields, probability: (0, _utils.getSamplingValue)(layer), samplerSeed: (0, _seedrandom.default)(searchSessionId).int32(), ignoreGlobalFilters: Boolean(layer.ignoreGlobalFilters) }).toAst(), { type: 'function', function: 'lens_map_to_columns', arguments: { idMap: [JSON.stringify(updatedEsAggsIdMap)] } }, ...expressions, ...formatterOverrides, ...timeScaleFunctions] }; } return null; } // Topologically sorts references so that we can execute them in sequence function sortedReferences(columns) { const allNodes = {}; columns.forEach(([id, col]) => { allNodes[id] = 'references' in col ? col.references : []; }); // remove real metric references columns.forEach(([id]) => { allNodes[id] = allNodes[id].filter(refId => !!allNodes[refId]); }); const ordered = []; while (ordered.length < columns.length) { Object.keys(allNodes).forEach(id => { if (allNodes[id].length === 0) { ordered.push(id); delete allNodes[id]; Object.keys(allNodes).forEach(k => { allNodes[k] = allNodes[k].filter(i => i !== id); }); } }); } return ordered; } function toExpression(state, layerId, indexPatterns, uiSettings, dateRange, nowInstant, searchSessionId) { if (state.layers[layerId]) { return getExpressionForLayer(state.layers[layerId], indexPatterns[state.layers[layerId].indexPatternId], uiSettings, dateRange, nowInstant, searchSessionId); } return null; }