[ML] Fixes position of Single Metric markers for long agg intervals (#22055) (#22063)

This commit is contained in:
Pete Harverson 2018-08-16 15:30:54 +01:00 committed by GitHub
parent 8fa2c7bafe
commit 0218afc961
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 63 additions and 21 deletions

View file

@ -30,7 +30,7 @@ import {
import { TimeBuckets } from 'ui/time_buckets';
import { mlAnomaliesTableService } from 'plugins/ml/components/anomalies_table/anomalies_table_service';
import ContextChartMask from 'plugins/ml/timeseriesexplorer/context_chart_mask';
import { findNearestChartPointToTime } from 'plugins/ml/timeseriesexplorer/timeseriesexplorer_utils';
import { findChartPointForAnomalyTime } from 'plugins/ml/timeseriesexplorer/timeseriesexplorer_utils';
import { mlEscape } from 'plugins/ml/util/string_utils';
import { mlFieldFormatService } from 'plugins/ml/services/field_format_service';
@ -1017,11 +1017,11 @@ module.directive('mlTimeseriesChart', function (
function highlightFocusChartAnomaly(record) {
// Highlights the anomaly marker in the focus chart corresponding to the specified record.
// Find the anomaly marker which is closest in time to the source record.
// Find the anomaly marker which corresponds to the time of the anomaly record.
// Depending on the way the chart is aggregated, there may not be
// a point at exactly the same time as the record being highlighted.
const anomalyTime = record.source.timestamp;
const markerToSelect = findNearestChartPointToTime(scope.focusChartData, anomalyTime);
const markerToSelect = findChartPointForAnomalyTime(scope.focusChartData, anomalyTime);
// Render an additional highlighted anomaly marker on the focus chart.
// TODO - plot anomaly markers for cases where there is an anomaly due

View file

@ -98,14 +98,15 @@ export function processDataForFocusAnomalies(
anomalyRecords,
timeFieldName) {
// Iterate through the anomaly records, adding anomalyScore properties
// Iterate through the anomaly records, adding anomalyScore, function,
// actual and typical properties, plus causes info if applicable,
// to the chartData entries for anomalous buckets.
_.each(anomalyRecords, (record) => {
// Look for a chart point with the same time as the record.
// If none found, find closest time in chartData set.
const recordTime = record[timeFieldName];
let chartPoint = findNearestChartPointToTime(chartData, recordTime);
let chartPoint = findChartPointForAnomalyTime(chartData, recordTime);
// TODO - handle case where there is an anomaly due to the absence of data
// and there is no model plot.
@ -121,23 +122,28 @@ export function processDataForFocusAnomalies(
if (chartPoint !== undefined) {
// If chart aggregation interval > bucket span, there may be more than
// one anomaly record in the interval, so get max record anomalyScore.
chartPoint.anomalyScore = Math.max(_.get(chartPoint, 'anomalyScore', 0), record.record_score);
chartPoint.function = record.function;
// one anomaly record in the interval, so use the properties from
// the record with the highest anomalyScore.
const recordScore = record.record_score;
const pointScore = chartPoint.anomalyScore;
if (pointScore === undefined || pointScore < recordScore) {
chartPoint.anomalyScore = recordScore;
chartPoint.function = record.function;
if (_.has(record, 'actual')) {
chartPoint.actual = record.actual;
chartPoint.typical = record.typical;
} else {
const causes = _.get(record, 'causes', []);
if (causes.length > 0) {
chartPoint.byFieldName = record.by_field_name;
chartPoint.numberOfCauses = causes.length;
if (causes.length === 1) {
// If only a single cause, copy actual and typical values to the top level.
const cause = _.first(record.causes);
chartPoint.actual = cause.actual;
chartPoint.typical = cause.typical;
if (_.has(record, 'actual')) {
chartPoint.actual = record.actual;
chartPoint.typical = record.typical;
} else {
const causes = _.get(record, 'causes', []);
if (causes.length > 0) {
chartPoint.byFieldName = record.by_field_name;
chartPoint.numberOfCauses = causes.length;
if (causes.length === 1) {
// If only a single cause, copy actual and typical values to the top level.
const cause = _.first(record.causes);
chartPoint.actual = cause.actual;
chartPoint.typical = cause.typical;
}
}
}
}
@ -165,6 +171,7 @@ export function processScheduledEventsForChart(chartData, scheduledEvents) {
return chartData;
}
// Finds the chart point which is closest in time to the specified time.
export function findNearestChartPointToTime(chartData, time) {
let chartPoint;
if(chartData === undefined) {
@ -198,6 +205,41 @@ export function findNearestChartPointToTime(chartData, time) {
} else {
foundItem = previousItem;
}
break;
}
}
chartPoint = foundItem;
}
return chartPoint;
}
// Finds the chart point which corresponds to an anomaly with the
// specified time.
export function findChartPointForAnomalyTime(chartData, anomalyTime) {
let chartPoint;
if(chartData === undefined) {
return chartPoint;
}
for (let i = 0; i < chartData.length; i++) {
if (chartData[i].date.getTime() === anomalyTime) {
chartPoint = chartData[i];
break;
}
}
if (chartPoint === undefined) {
// Find the time of the point which falls immediately before the
// time of the anomaly. This is the start of the chart 'bucket'
// which contains the anomalous bucket.
let foundItem;
for (let i = 0; i < chartData.length; i++) {
const itemTime = chartData[i].date.getTime();
if (itemTime > anomalyTime) {
foundItem = (i > 0) ? chartData[i - 1] : chartData[0];
break;
}
}