Skip to content

Commit

Permalink
[8.x] [ML] Anomaly explorer: Show Data Gaps and Connect Anomalous Poi…
Browse files Browse the repository at this point in the history
…nts on Single Metric Charts (elastic#194119) (elastic#194426)

# Backport

This will backport the following commits from `main` to `8.x`:
- [[ML] Anomaly explorer: Show Data Gaps and Connect Anomalous Points on
Single Metric Charts
(elastic#194119)](elastic#194119)

<!--- Backport version: 9.4.3 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Robert
Jaszczurek","email":"[email protected]"},"sourceCommit":{"committedDate":"2024-09-30T12:17:29Z","message":"[ML]
Anomaly explorer: Show Data Gaps and Connect Anomalous Points on Single
Metric Charts (elastic#194119)\n\n## Summary\r\n\r\nFix for
[elastic#193885](https://github.com/elastic/kibana/issues/193885)\r\n\r\nSingle
metric viewer:\r\n![Screenshot 2024-09-26 at 13
31\r\n35](https://github.com/user-attachments/assets/3576758e-9cb1-4c55-bd84-40f2095cc54f)\r\n\r\nAnomaly
explorer:\r\n| Before | After |\r\n| ------------- | -------------
|\r\n|\r\n![image](https://github.com/user-attachments/assets/89f48abd-26ac-4dd4-ab10-b1c8198f50ff)\r\n|
![Screenshot 2024-09-26 at 13
32\r\n05](https://github.com/user-attachments/assets/e3a6e1e3-6238-4c01-a372-fa0c25475fbd)\r\n|","sha":"32144fc0244566db557646ff35cc82c29734df72","branchLabelMapping":{"^v9.0.0$":"main","^v8.16.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:fix",":ml","Feature:Anomaly
Detection","v9.0.0","Team:ML","v8.16.0","backport:version"],"title":"[ML]
Anomaly explorer: Show Data Gaps and Connect Anomalous Points on Single
Metric
Charts","number":194119,"url":"https://github.com/elastic/kibana/pull/194119","mergeCommit":{"message":"[ML]
Anomaly explorer: Show Data Gaps and Connect Anomalous Points on Single
Metric Charts (elastic#194119)\n\n## Summary\r\n\r\nFix for
[elastic#193885](https://github.com/elastic/kibana/issues/193885)\r\n\r\nSingle
metric viewer:\r\n![Screenshot 2024-09-26 at 13
31\r\n35](https://github.com/user-attachments/assets/3576758e-9cb1-4c55-bd84-40f2095cc54f)\r\n\r\nAnomaly
explorer:\r\n| Before | After |\r\n| ------------- | -------------
|\r\n|\r\n![image](https://github.com/user-attachments/assets/89f48abd-26ac-4dd4-ab10-b1c8198f50ff)\r\n|
![Screenshot 2024-09-26 at 13
32\r\n05](https://github.com/user-attachments/assets/e3a6e1e3-6238-4c01-a372-fa0c25475fbd)\r\n|","sha":"32144fc0244566db557646ff35cc82c29734df72"}},"sourceBranch":"main","suggestedTargetBranches":["8.x"],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","branchLabelMappingKey":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/194119","number":194119,"mergeCommit":{"message":"[ML]
Anomaly explorer: Show Data Gaps and Connect Anomalous Points on Single
Metric Charts (elastic#194119)\n\n## Summary\r\n\r\nFix for
[elastic#193885](https://github.com/elastic/kibana/issues/193885)\r\n\r\nSingle
metric viewer:\r\n![Screenshot 2024-09-26 at 13
31\r\n35](https://github.com/user-attachments/assets/3576758e-9cb1-4c55-bd84-40f2095cc54f)\r\n\r\nAnomaly
explorer:\r\n| Before | After |\r\n| ------------- | -------------
|\r\n|\r\n![image](https://github.com/user-attachments/assets/89f48abd-26ac-4dd4-ab10-b1c8198f50ff)\r\n|
![Screenshot 2024-09-26 at 13
32\r\n05](https://github.com/user-attachments/assets/e3a6e1e3-6238-4c01-a372-fa0c25475fbd)\r\n|","sha":"32144fc0244566db557646ff35cc82c29734df72"}},{"branch":"8.x","label":"v8.16.0","branchLabelMappingKey":"^v8.16.0$","isSourceBranch":false,"state":"NOT_CREATED"}]}]
BACKPORT-->

Co-authored-by: Robert Jaszczurek <[email protected]>
  • Loading branch information
kibanamachine and rbrtj authored Sep 30, 2024
1 parent 5afb442 commit 9e1829e
Show file tree
Hide file tree
Showing 2 changed files with 39 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -368,14 +368,17 @@ export class ExplorerChartSingleMetric extends React.Component {
// These are used for displaying tooltips on mouseover.
// Don't render dots where value=null (data gaps, with no anomalies)
// or for multi-bucket anomalies.
// Except for scheduled events.
const dots = lineChartGroup
.append('g')
.attr('class', 'chart-markers')
.selectAll('.metric-value')
.data(
data.filter(
(d) =>
(d.value !== null || typeof d.anomalyScore === 'number') &&
(d.value !== null ||
typeof d.anomalyScore === 'number' ||
d.scheduledEvents !== undefined) &&
!showMultiBucketAnomalyMarker(d)
)
);
Expand Down Expand Up @@ -407,7 +410,11 @@ export class ExplorerChartSingleMetric extends React.Component {
// Update all dots to new positions.
dots
.attr('cx', (d) => lineChartXScale(d.date))
.attr('cy', (d) => lineChartYScale(d.value))
// Fallback with domain's min value if value is null
// To ensure event markers are rendered properly at the bottom of the chart
.attr('cy', (d) =>
lineChartYScale(d.value !== null ? d.value : lineChartYScale.domain()[0])
)
.attr('class', (d) => {
let markerClass = 'metric-value';
if (isAnomalyVisible(d)) {
Expand Down Expand Up @@ -470,7 +477,14 @@ export class ExplorerChartSingleMetric extends React.Component {
// Update all markers to new positions.
scheduledEventMarkers
.attr('x', (d) => lineChartXScale(d.date) - LINE_CHART_ANOMALY_RADIUS)
.attr('y', (d) => lineChartYScale(d.value) - SCHEDULED_EVENT_SYMBOL_HEIGHT / 2);
.attr(
'y',
(d) =>
// Fallback with domain's min value if value is null
// To ensure event markers are rendered properly at the bottom of the chart
lineChartYScale(d.value !== null ? d.value : lineChartYScale.domain()[0]) -
SCHEDULED_EVENT_SYMBOL_HEIGHT / 2
);
}

function showAnomalyPopover(marker, circle) {
Expand Down Expand Up @@ -596,7 +610,7 @@ export class ExplorerChartSingleMetric extends React.Component {
});
}
}
} else {
} else if (marker.value !== null) {
tooltipData.push({
label: i18n.translate(
'xpack.ml.explorer.singleMetricChart.valueWithoutAnomalyScoreLabel',
Expand Down
26 changes: 21 additions & 5 deletions x-pack/plugins/ml/server/models/results_service/anomaly_charts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1055,9 +1055,11 @@ export function anomalyChartsDataProvider(mlClient: MlClient, client: IScopedClu
// differently because of how the source data is structured.
// For rare chart values we are only interested wether a value is either `0` or not,
// `0` acts like a flag in the chart whether to display the dot/marker.
// All other charts (single metric, population) are metric based and with
// For single metric chart, we need to pass null values to display data gaps.
// All other charts are distribution based and with
// those a value of `null` acts as the flag to hide a data point.
if (
chartType === CHART_TYPE.SINGLE_METRIC ||
(chartType === CHART_TYPE.EVENT_DISTRIBUTION && value > 0) ||
(chartType !== CHART_TYPE.EVENT_DISTRIBUTION && value !== null)
) {
Expand All @@ -1079,6 +1081,7 @@ export function anomalyChartsDataProvider(mlClient: MlClient, client: IScopedClu
// Iterate through the anomaly records, adding anomalyScore properties
// to the chartData entries for anomalous buckets.
const chartDataForPointSearch = getChartDataForPointSearch(chartData, records[0], chartType);
let shouldSortChartData = false;
each(records, (record) => {
// Look for a chart point with the same time as the record.
// If none found, insert a point for anomalies due to a gap in the data.
Expand All @@ -1087,10 +1090,17 @@ export function anomalyChartsDataProvider(mlClient: MlClient, client: IScopedClu
if (chartPoint === undefined) {
chartPoint = { date: recordTime, value: null };
chartData.push(chartPoint);
shouldSortChartData = true;
}
if (chartPoint !== undefined) {
chartPoint.anomalyScore = record.record_score;

// If it is an empty chart point, set the value to the actual value
// To properly display the anomaly marker on the chart
if (chartPoint.value === null) {
chartPoint.value = Array.isArray(record.actual) ? record.actual[0] : record.actual;
}

if (record.actual !== undefined) {
chartPoint.actual = record.actual;
chartPoint.typical = record.typical;
Expand Down Expand Up @@ -1119,21 +1129,27 @@ export function anomalyChartsDataProvider(mlClient: MlClient, client: IScopedClu
}
});

// Chart data is sorted by default, but if we added points for anomalies,
// we need to sort again to ensure the points are in the correct order.
if (shouldSortChartData) {
chartData.sort((a, b) => a.date - b.date);
}

// Add a scheduledEvents property to any points in the chart data set
// which correspond to times of scheduled events for the job.
if (scheduledEvents !== undefined) {
each(scheduledEvents, (events, time) => {
const chartPoint = findChartPointForTime(chartDataForPointSearch, Number(time));
if (chartPoint !== undefined) {
chartPoint.scheduledEvents = events;
// We do not want to create additional points for single metric charts
// as it could break the chart.
} else if (chartType !== CHART_TYPE.SINGLE_METRIC) {
} else {
// If there's no underlying metric data point for the scheduled event,
// create a new chart point with a value of 0.
// Except for Single Metric Charts, where we want to create a point at the bottom of the chart.
// Which is not always `0`.
const eventChartPoint: ChartPoint = {
date: Number(time),
value: 0,
value: chartType === CHART_TYPE.SINGLE_METRIC ? null : 0,
entity: SCHEDULE_EVENT_MARKER_ENTITY,
scheduledEvents: events,
};
Expand Down

0 comments on commit 9e1829e

Please sign in to comment.