Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,6 @@
stroke-width: 1px;
stroke: $euiColorDarkShade;
fill: $euiColorLightShade;
pointer-events: none;
}

.forecast {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -667,15 +667,17 @@ class TimeseriesChartIntl extends Component {
return d.lower;
}
}
return metricValue;
// metricValue is undefined for scheduled events when there is no source data.
return metricValue || 0;
});
yMax = d3.max(combinedData, (d) => {
let metricValue = d.value;
if (metricValue === null && d.anomalyScore !== undefined && d.actual !== undefined) {
// If an anomaly coincides with a gap in the data, use the anomaly actual value.
metricValue = Array.isArray(d.actual) ? d.actual[0] : d.actual;
}
return d.upper !== undefined ? Math.max(metricValue, d.upper) : metricValue;
// metricValue is undefined for scheduled events when there is no source data.
return d.upper !== undefined ? Math.max(metricValue, d.upper) : metricValue || 0;
});

if (yMax === yMin) {
Expand All @@ -701,6 +703,7 @@ class TimeseriesChartIntl extends Component {
// TODO needs revisiting to be a more robust normalization
yMax += Math.abs(yMax - yMin) * ((maxLevel + 1) / 5);
}

this.focusYScale.domain([yMin, yMax]);
} else {
// Display 10 unlabelled ticks.
Expand Down Expand Up @@ -835,6 +838,10 @@ class TimeseriesChartIntl extends Component {
scheduledEventMarkers
.enter()
.append('rect')
.on('mouseover', function (d) {
showFocusChartTooltip(d, this);
})
.on('mouseout', () => hideFocusChartTooltip())
.attr('width', LINE_CHART_ANOMALY_RADIUS * 2)
.attr('height', SCHEDULED_EVENT_SYMBOL_HEIGHT)
.attr('class', 'scheduled-event-marker')
Expand All @@ -844,7 +851,10 @@ class TimeseriesChartIntl extends Component {
// Update all markers to new positions.
scheduledEventMarkers
.attr('x', (d) => this.focusXScale(d.date) - LINE_CHART_ANOMALY_RADIUS)
.attr('y', (d) => this.focusYScale(d.value) - 3);
.attr('y', (d) => {
const focusYValue = this.focusYScale(d.value);
return isNaN(focusYValue) ? -focusHeight - 3 : focusYValue - 3;
});

// Plot any forecast data in scope.
if (focusForecastData !== undefined) {
Expand Down Expand Up @@ -1652,7 +1662,7 @@ class TimeseriesChartIntl extends Component {
valueAccessor: 'prediction',
});
} else {
if (marker.value !== undefined) {
if (marker.value !== undefined && marker.value !== null) {
tooltipData.push({
label: i18n.translate(
'xpack.ml.timeSeriesExplorer.timeSeriesChart.withoutAnomalyScore.valueLabel',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,11 @@ export function getFocusData(
modelPlotEnabled,
functionDescription
);
focusChartData = processScheduledEventsForChart(focusChartData, scheduledEvents);
focusChartData = processScheduledEventsForChart(
focusChartData,
scheduledEvents,
focusAggregationInterval
);

const refreshFocusData: FocusData = {
scheduledEvents,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@ export function processDataForFocusAnomalies(
functionDescription: any
): any;

export function processScheduledEventsForChart(chartData: any, scheduledEvents: any): any;
export function processScheduledEventsForChart(
chartData: any,
scheduledEvents: any,
aggregationInterval: any
): any;

export function findNearestChartPointToTime(chartData: any, time: any): any;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,16 +205,44 @@ export function processDataForFocusAnomalies(

// Adds a scheduledEvents property to any points in the chart data set
// which correspond to times of scheduled events for the job.
export function processScheduledEventsForChart(chartData, scheduledEvents) {
export function processScheduledEventsForChart(chartData, scheduledEvents, aggregationInterval) {
if (scheduledEvents !== undefined) {
const timesToAddPointsFor = [];

// Iterate through the scheduled events making sure we have a chart point for each event.
const intervalMs = aggregationInterval.asMilliseconds();
let lastChartDataPointTime = undefined;
if (chartData !== undefined && chartData.length > 0) {
lastChartDataPointTime = chartData[chartData.length - 1].date.getTime();
}

// In case there's no chart data/sparse data during these scheduled events
// ensure we add chart points at every aggregation interval for these scheduled events.
let sortRequired = false;
each(scheduledEvents, (events, time) => {
const chartPoint = findNearestChartPointToTime(chartData, time);
if (chartPoint !== undefined) {
// Note if the scheduled event coincides with an absence of the underlying metric data,
// we don't worry about plotting the event.
chartPoint.scheduledEvents = events;
const exactChartPoint = findChartPointForScheduledEvent(chartData, +time);

if (exactChartPoint !== undefined) {
exactChartPoint.scheduledEvents = events;
} else {
const timeToAdd = Math.floor(time / intervalMs) * intervalMs;
if (timesToAddPointsFor.indexOf(timeToAdd) === -1 && timeToAdd !== lastChartDataPointTime) {
const pointToAdd = {
date: new Date(timeToAdd),
value: null,
scheduledEvents: events,
};

chartData.push(pointToAdd);
sortRequired = true;
}
}
});

// Sort chart data by time if extra points were added at the end of the array for scheduled events.
if (sortRequired === true) {
chartData.sort((a, b) => a.date.getTime() - b.date.getTime());
}
}

return chartData;
Expand All @@ -240,12 +268,12 @@ export function findNearestChartPointToTime(chartData, time) {
// grab the current and previous items and compare the time differences
let foundItem;
for (let i = 0; i < chartData.length; i++) {
const itemTime = chartData[i].date.getTime();
const itemTime = chartData[i]?.date?.getTime();
if (itemTime > time) {
const item = chartData[i];
const previousItem = chartData[i - 1];

const diff1 = Math.abs(time - previousItem.date.getTime());
const diff1 = Math.abs(time - previousItem?.date?.getTime());
const diff2 = Math.abs(time - itemTime);

// foundItem should be the item with a date closest to bucketTime
Expand Down Expand Up @@ -300,6 +328,22 @@ export function findChartPointForAnomalyTime(chartData, anomalyTime, aggregation
return chartPoint;
}

export function findChartPointForScheduledEvent(chartData, eventTime) {
let chartPoint;
if (chartData === undefined) {
return chartPoint;
}

for (let i = 0; i < chartData.length; i++) {
if (chartData[i].date.getTime() === eventTime) {
chartPoint = chartData[i];
break;
}
}

return chartPoint;
}

export function calculateAggregationInterval(bounds, bucketsTarget, jobs, selectedJob) {
// Aggregation interval used in queries should be a function of the time span of the chart
// and the bucket span of the selected job(s).
Expand Down