Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[8.x] [ML] Anomaly Explorer: Display markers for scheduled events in distribution type anomaly charts (#192377) #193118

Merged
merged 1 commit into from
Sep 17, 2024
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
2 changes: 2 additions & 0 deletions x-pack/plugins/ml/common/constants/charts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,5 @@ export const CHART_TYPE = {
} as const;

export type ChartType = (typeof CHART_TYPE)[keyof typeof CHART_TYPE];

export const SCHEDULE_EVENT_MARKER_ENTITY = 'schedule_event_marker_entity';
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import { CHART_TYPE } from '../explorer_constants';
import { CHART_HEIGHT, TRANSPARENT_BACKGROUND } from './constants';
import { filter } from 'rxjs';
import { drawCursor } from './utils/draw_anomaly_explorer_charts_cursor';
import { SCHEDULE_EVENT_MARKER_ENTITY } from '../../../../common/constants/charts';

const CONTENT_WRAPPER_HEIGHT = 215;
const SCHEDULED_EVENT_MARKER_HEIGHT = 5;
Expand Down Expand Up @@ -158,12 +159,29 @@ export class ExplorerChartDistribution extends React.Component {
.key((d) => d.entity)
.entries(chartData)
.sort((a, b) => {
// To display calendar event markers we populate the chart with fake data points.
// If a category has fake data points, it should be sorted to the end.
const aHasFakeData = a.values.some((d) => d.entity === SCHEDULE_EVENT_MARKER_ENTITY);
const bHasFakeData = b.values.some((d) => d.entity === SCHEDULE_EVENT_MARKER_ENTITY);

if (aHasFakeData && !bHasFakeData) {
return 1;
}

if (bHasFakeData && !aHasFakeData) {
return -1;
}

return b.values.length - a.values.length;
})
.filter((d, i) => {
// only filter for rare charts
if (chartType === CHART_TYPE.EVENT_DISTRIBUTION) {
return i < categoryLimit || d.key === highlight;
return (
i < categoryLimit ||
d.key === highlight ||
d.values.some((d) => d.entity === SCHEDULE_EVENT_MARKER_ENTITY)
);
}
return true;
})
Expand Down Expand Up @@ -373,7 +391,8 @@ export class ExplorerChartDistribution extends React.Component {
.orient('left')
.innerTickSize(0)
.outerTickSize(0)
.tickPadding(10);
.tickPadding(10)
.tickFormat((d) => (d === SCHEDULE_EVENT_MARKER_ENTITY ? null : d));

if (fieldFormat !== undefined) {
yAxis.tickFormat((d) => fieldFormat.convert(d, 'text'));
Expand Down Expand Up @@ -518,7 +537,8 @@ export class ExplorerChartDistribution extends React.Component {
const tooltipData = [{ label: formattedDate }];
const seriesKey = config.detectorLabel;

if (marker.entity !== undefined) {
// Hide entity for scheduled events with mocked value.
if (marker.entity !== undefined && marker.entity !== SCHEDULE_EVENT_MARKER_ENTITY) {
tooltipData.push({
label: i18n.translate('xpack.ml.explorer.distributionChart.entityLabel', {
defaultMessage: 'entity',
Expand Down Expand Up @@ -590,7 +610,11 @@ export class ExplorerChartDistribution extends React.Component {
});
}
}
} else if (chartType !== CHART_TYPE.EVENT_DISTRIBUTION) {
} else if (
chartType !== CHART_TYPE.EVENT_DISTRIBUTION &&
// Hide value for scheduled events with mocked value.
marker.entity !== SCHEDULE_EVENT_MARKER_ENTITY
) {
tooltipData.push({
label: i18n.translate(
'xpack.ml.explorer.distributionChart.valueWithoutAnomalyScoreLabel',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ import { parseInterval } from '../../../common/util/parse_interval';
import { getDatafeedAggregations } from '../../../common/util/datafeed_utils';
import { findAggField } from '../../../common/util/validation_utils';
import type { ChartType } from '../../../common/constants/charts';
import { CHART_TYPE } from '../../../common/constants/charts';
import { CHART_TYPE, SCHEDULE_EVENT_MARKER_ENTITY } from '../../../common/constants/charts';
import { getChartType } from '../../../common/util/chart_utils';
import type { MlJob } from '../..';

Expand Down Expand Up @@ -1124,9 +1124,19 @@ export function anomalyChartsDataProvider(mlClient: MlClient, client: IScopedClu
each(scheduledEvents, (events, time) => {
const chartPoint = findChartPointForTime(chartDataForPointSearch, Number(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;
// 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) {
// If there's no underlying metric data point for the scheduled event,
// create a new chart point with a value of 0.
const eventChartPoint: ChartPoint = {
date: Number(time),
value: 0,
entity: SCHEDULE_EVENT_MARKER_ENTITY,
scheduledEvents: events,
};
chartData.push(eventChartPoint);
}
});
}
Expand Down