"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.TYPICAL_STYLE = exports.ML_ANOMALY_LAYERS = exports.CUSTOM_COLOR_RAMP = exports.ACTUAL_STYLE = void 0; exports.getInitialAnomaliesLayers = getInitialAnomaliesLayers; exports.getInitialSourceIndexFieldLayers = getInitialSourceIndexFieldLayers; exports.getResultsForJobId = getResultsForJobId; var _eui = require("@elastic/eui"); var _common = require("@kbn/maps-plugin/common"); var _esQuery = require("@kbn/es-query"); var _mlAnomalyUtils = require("@kbn/ml-anomaly-utils"); var _mlDateUtils = require("@kbn/ml-date-utils"); var _mlQueryUtils = require("@kbn/ml-query-utils"); var _group_color_utils = require("../../common/util/group_color_utils"); var _get_index_pattern = require("../application/explorer/reducers/explorer_reducer/get_index_pattern"); var _anomaly_source = require("./anomaly_source"); /* * 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 ML_ANOMALY_LAYERS = { TYPICAL: 'typical', ACTUAL: 'actual', TYPICAL_TO_ACTUAL: 'typical to actual' }; exports.ML_ANOMALY_LAYERS = ML_ANOMALY_LAYERS; const CUSTOM_COLOR_RAMP = { type: _common.STYLE_TYPE.DYNAMIC, options: { customColorRamp: _mlAnomalyUtils.ML_SEVERITY_COLOR_RAMP, field: { name: 'record_score', origin: _common.FIELD_ORIGIN.SOURCE }, useCustomColorRamp: true } }; exports.CUSTOM_COLOR_RAMP = CUSTOM_COLOR_RAMP; const ACTUAL_STYLE = { type: 'VECTOR', properties: { fillColor: CUSTOM_COLOR_RAMP, lineColor: CUSTOM_COLOR_RAMP }, isTimeAware: false }; exports.ACTUAL_STYLE = ACTUAL_STYLE; const TYPICAL_STYLE = { type: 'VECTOR', properties: { fillColor: { type: 'STATIC', options: { color: '#98A2B2' } }, lineColor: { type: 'STATIC', options: { color: '#fff' } }, lineWidth: { type: 'STATIC', options: { size: 2 } }, iconSize: { type: 'STATIC', options: { size: 6 } } } }; exports.TYPICAL_STYLE = TYPICAL_STYLE; // Must reverse coordinates here. Map expects [lon, lat] - anomalies are stored as [lat, lon] for lat_lon jobs function getCoordinates(latLonString) { return latLonString.split(',').map(coordinate => Number(coordinate)).reverse(); } function getInitialAnomaliesLayers(jobId) { const initialLayers = []; for (const layer in ML_ANOMALY_LAYERS) { if (ML_ANOMALY_LAYERS.hasOwnProperty(layer)) { initialLayers.push({ id: (0, _eui.htmlIdGenerator)()(), type: _common.LAYER_TYPE.GEOJSON_VECTOR, sourceDescriptor: _anomaly_source.AnomalySource.createDescriptor({ jobId, typicalActual: ML_ANOMALY_LAYERS[layer] }), style: ML_ANOMALY_LAYERS[layer] === ML_ANOMALY_LAYERS.TYPICAL ? TYPICAL_STYLE : ACTUAL_STYLE }); } } return initialLayers; } function getInitialSourceIndexFieldLayers(sourceIndexWithGeoFields) { const initialLayers = []; for (const index in sourceIndexWithGeoFields) { if (sourceIndexWithGeoFields.hasOwnProperty(index)) { const { dataViewId, geoFields } = sourceIndexWithGeoFields[index]; geoFields.forEach(geoField => { const color = (0, _group_color_utils.tabColor)(geoField); initialLayers.push({ id: (0, _eui.htmlIdGenerator)()(), type: _common.LAYER_TYPE.GEOJSON_VECTOR, style: { type: 'VECTOR', properties: { fillColor: { type: 'STATIC', options: { color } }, lineColor: { type: 'STATIC', options: { color } } } }, sourceDescriptor: { id: (0, _eui.htmlIdGenerator)()(), type: _common.SOURCE_TYPES.ES_SEARCH, tooltipProperties: [geoField], label: index, indexPatternId: dataViewId, geoField, scalingType: _common.SCALING_TYPES.MVT } }); }); } } return initialLayers; } async function getResultsForJobId(mlResultsService, jobId, locationType, searchFilters) { var _resp; const { query, timeFilters } = searchFilters; const hasQuery = query && query.query !== ''; let queryFilter; // @ts-ignore missing properties from ExplorerJob - those fields aren't required for this const indexPattern = (0, _get_index_pattern.getIndexPattern)([{ id: jobId }]); if (hasQuery && query.language === _mlQueryUtils.SEARCH_QUERY_LANGUAGE.KUERY) { queryFilter = (0, _esQuery.toElasticsearchQuery)((0, _esQuery.fromKueryExpression)(query.query), indexPattern); } else if (hasQuery && (query === null || query === void 0 ? void 0 : query.language) === _mlQueryUtils.SEARCH_QUERY_LANGUAGE.LUCENE) { queryFilter = (0, _esQuery.luceneStringToDsl)(query.query); } const must = [{ term: { job_id: jobId } }, { term: { result_type: 'record' } }]; let bool = { must }; if (queryFilter && queryFilter.bool) { bool = { ...bool, ...queryFilter.bool }; } else if (queryFilter) { // @ts-ignore push doesn't exist on type QueryDslQueryContainer | QueryDslQueryContainer[] | undefined bool.must.push(queryFilter); } // Query to look for the highest scoring anomaly. const body = { query: { bool }, size: 1000, _source: { excludes: [] } }; if (timeFilters) { const timerange = { range: { timestamp: { gte: `${timeFilters.from}`, lte: timeFilters.to } } }; must.push(timerange); } let resp = null; try { resp = await mlResultsService.anomalySearch({ body }, [jobId]); } catch (error) { // search may fail if the job doesn't already exist // ignore this error as the outer function call will raise a toast } const features = ((_resp = resp) === null || _resp === void 0 ? void 0 : _resp.hits.hits.map(({ _source }) => { const geoResults = _source.geo_results; const actual = geoResults && geoResults.actual_point ? getCoordinates(geoResults.actual_point) : [0, 0]; const typical = geoResults && geoResults.typical_point ? getCoordinates(geoResults.typical_point) : [0, 0]; let geometry; if (locationType === ML_ANOMALY_LAYERS.TYPICAL || locationType === ML_ANOMALY_LAYERS.ACTUAL) { geometry = { type: 'Point', coordinates: locationType === ML_ANOMALY_LAYERS.TYPICAL ? typical : actual }; } else { geometry = { type: 'LineString', coordinates: [typical, actual] }; } const splitFields = { ...(_source.partition_field_name ? { [_source.partition_field_name]: _source.partition_field_value } : {}), ...(_source.by_field_name ? { [_source.by_field_name]: _source.by_field_value } : {}), ...(_source.over_field_name ? { [_source.over_field_name]: _source.over_field_value } : {}) }; const splitFieldKeys = Object.keys(splitFields); const influencers = _source.influencers ? _source.influencers.filter(({ influencer_field_name: name, influencer_field_values: values }) => { // remove influencers without values and influencers on partition fields return values.length && !splitFieldKeys.includes(name); }) : []; return { type: 'Feature', geometry, properties: { actual, typical, fieldName: _source.field_name, functionDescription: _source.function_description, timestamp: (0, _mlDateUtils.formatHumanReadableDateTimeSeconds)(_source.timestamp), record_score: Math.floor(_source.record_score), ...(Object.keys(splitFields).length > 0 ? splitFields : {}), ...(influencers.length ? { influencers } : {}) } }; })) || []; return { type: 'FeatureCollection', features }; }