diff --git a/x-pack/plugins/ml/common/util/job_utils.test.ts b/x-pack/plugins/ml/common/util/job_utils.test.ts index 1ea70c0c19b4e..b0d4d583c619b 100644 --- a/x-pack/plugins/ml/common/util/job_utils.test.ts +++ b/x-pack/plugins/ml/common/util/job_utils.test.ts @@ -281,6 +281,7 @@ describe('ML - job utils', () => { expect(isSourceDataChartableForDetector(job, 22)).toBe(true); expect(isSourceDataChartableForDetector(job, 23)).toBe(true); expect(isSourceDataChartableForDetector(job, 24)).toBe(true); + expect(isSourceDataChartableForDetector(job, 36)).toBe(true); expect(isSourceDataChartableForDetector(job, 37)).toBe(true); }); @@ -296,7 +297,6 @@ describe('ML - job utils', () => { expect(isSourceDataChartableForDetector(job, 33)).toBe(false); expect(isSourceDataChartableForDetector(job, 34)).toBe(false); expect(isSourceDataChartableForDetector(job, 35)).toBe(false); - expect(isSourceDataChartableForDetector(job, 36)).toBe(false); }); }); diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container_service.js b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container_service.js index 97b19d40de477..f9d8fa6726d72 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container_service.js +++ b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container_service.js @@ -84,6 +84,7 @@ export const anomalyDataChange = function ( ); const seriesConfigs = recordsToPlot.map(buildConfig); + const seriesConfigsNoGeoData = []; // initialize the charts with loading indicators data.seriesToPlot = seriesConfigs.map((config) => ({ @@ -116,10 +117,10 @@ export const anomalyDataChange = function ( loading: false, mapData: records, }); + } else { + seriesConfigsNoGeoData.push(config); } } - - data.seriesToPlot = mapData; } // Calculate the time range of the charts, which is a function of the chart width and max job bucket span. @@ -306,26 +307,26 @@ export const anomalyDataChange = function ( // TODO - if query returns no results e.g. source data has been deleted, // display a message saying 'No data between earliest/latest'. const seriesPromises = []; - seriesConfigs.forEach((seriesConfig) => { - if (!seriesConfig.detectorLabel.includes(ML_JOB_AGGREGATION.LAT_LONG)) { - seriesPromises.push( - Promise.all([ - getMetricData(seriesConfig, chartRange), - getRecordsForCriteria(seriesConfig, chartRange), - getScheduledEvents(seriesConfig, chartRange), - getEventDistribution(seriesConfig, chartRange), - ]) - ); - } + // Use seriesConfigs list without geo data config so indices match up after seriesPromises are resolved and we map through the responses + const seriesCongifsForPromises = hasGeoData ? seriesConfigsNoGeoData : seriesConfigs; + seriesCongifsForPromises.forEach((seriesConfig) => { + seriesPromises.push( + Promise.all([ + getMetricData(seriesConfig, chartRange), + getRecordsForCriteria(seriesConfig, chartRange), + getScheduledEvents(seriesConfig, chartRange), + getEventDistribution(seriesConfig, chartRange), + ]) + ); }); function processChartData(response, seriesIndex) { const metricData = response[0].results; const records = response[1].records; - const jobId = seriesConfigs[seriesIndex].jobId; + const jobId = seriesCongifsForPromises[seriesIndex].jobId; const scheduledEvents = response[2].events[jobId]; const eventDistribution = response[3]; - const chartType = getChartType(seriesConfigs[seriesIndex]); + const chartType = getChartType(seriesCongifsForPromises[seriesIndex]); // Sort records in ascending time order matching up with chart data records.sort((recordA, recordB) => { @@ -450,24 +451,20 @@ export const anomalyDataChange = function ( ); const overallChartLimits = chartLimits(allDataPoints); - data.seriesToPlot = response - .map((d, i) => { - if (!seriesConfigs[i].detectorLabel.includes(ML_JOB_AGGREGATION.LAT_LONG)) { - return { - ...seriesConfigs[i], - loading: false, - chartData: processedData[i], - plotEarliest: chartRange.min, - plotLatest: chartRange.max, - selectedEarliest: selectedEarliestMs, - selectedLatest: selectedLatestMs, - chartLimits: USE_OVERALL_CHART_LIMITS - ? overallChartLimits - : chartLimits(processedData[i]), - }; - } - }) - .filter((value) => value !== undefined); + data.seriesToPlot = response.map((d, i) => { + return { + ...seriesCongifsForPromises[i], + loading: false, + chartData: processedData[i], + plotEarliest: chartRange.min, + plotLatest: chartRange.max, + selectedEarliest: selectedEarliestMs, + selectedLatest: selectedLatestMs, + chartLimits: USE_OVERALL_CHART_LIMITS + ? overallChartLimits + : chartLimits(processedData[i]), + }; + }); if (mapData.length) { // push map data in if it's available diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_charts/map_config.ts b/x-pack/plugins/ml/public/application/explorer/explorer_charts/map_config.ts index e1fda4256bff6..a9ca873d84a56 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer_charts/map_config.ts +++ b/x-pack/plugins/ml/public/application/explorer/explorer_charts/map_config.ts @@ -4,8 +4,28 @@ * you may not use this file except in compliance with the Elastic License. */ +import { FIELD_ORIGIN, STYLE_TYPE } from '../../../../../maps/common/constants'; + const FEATURE = 'Feature'; const POINT = 'Point'; +const SEVERITY_COLOR_RAMP = [ + { + stop: 0, + color: '#8BC8FB', + }, + { + stop: 25, + color: '#FDEC25', + }, + { + stop: 50, + color: '#FBA740', + }, + { + stop: 75, + color: '#FE5050', + }, +]; function getAnomalyFeatures(anomalies: any[], type: 'actual_point' | 'typical_point') { const anomalyFeatures = []; @@ -90,6 +110,12 @@ export const getMLAnomaliesActualLayer = (anomalies: any) => { sourceDescriptor: { id: 'b7486535-171b-4d3b-bb2e-33c1a0a2854d', type: 'GEOJSON_FILE', + __fields: [ + { + name: 'record_score', + type: 'number', + }, + ], __featureCollection: { features: getAnomalyFeatures(anomalies, 'actual_point'), type: 'FeatureCollection', @@ -100,9 +126,14 @@ export const getMLAnomaliesActualLayer = (anomalies: any) => { type: 'VECTOR', properties: { fillColor: { - type: 'STATIC', + type: STYLE_TYPE.DYNAMIC, options: { - color: '#FF0000', + customColorRamp: SEVERITY_COLOR_RAMP, + field: { + name: 'record_score', + origin: FIELD_ORIGIN.SOURCE, + }, + useCustomColorRamp: true, }, }, lineColor: { diff --git a/x-pack/plugins/ml/public/application/util/chart_utils.js b/x-pack/plugins/ml/public/application/util/chart_utils.js index 25e7adf49e969..799187cc37dfd 100644 --- a/x-pack/plugins/ml/public/application/util/chart_utils.js +++ b/x-pack/plugins/ml/public/application/util/chart_utils.js @@ -176,6 +176,11 @@ const POPULATION_DISTRIBUTION_ENABLED = true; // get the chart type based on its configuration export function getChartType(config) { let chartType = CHART_TYPE.SINGLE_METRIC; + + if (config.functionDescription === 'lat_long' || config.mapData !== undefined) { + return CHART_TYPE.GEO_MAP; + } + if ( EVENT_DISTRIBUTION_ENABLED && config.functionDescription === 'rare' && @@ -189,8 +194,6 @@ export function getChartType(config) { config.metricFunction !== null // Event distribution chart relies on the ML function mapping to an ES aggregation ) { chartType = CHART_TYPE.POPULATION_DISTRIBUTION; - } else if (config.functionDescription === 'lat_long') { - chartType = CHART_TYPE.GEO_MAP; } if (