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

[ML] Add annotation markers to time series brush area to indicate annotations exist outside of selected range #81490

Merged
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
697b60d
[ML] Remove redundant console.log
qn895 Oct 20, 2020
5f0d791
[ML] Rename old getAnnotation observable to getAnnotations$, add new …
qn895 Oct 20, 2020
ae65629
[ML] Change loadAnnotations to load separately from loadForJobID, rem…
qn895 Oct 21, 2020
ab4b600
[ML] Add annotation markers in time series brush area
qn895 Oct 21, 2020
5f904a9
[ML] Remove chart point value change
qn895 Oct 21, 2020
8148479
[ML] Add annotation markers to the bottom of context chart
qn895 Oct 21, 2020
1016d68
[ML] make annotations match the width in the focus area
qn895 Oct 22, 2020
e95be94
[ML] Remove duplicate scss stylings
qn895 Oct 22, 2020
bca6913
[ML] Remove redundant code
qn895 Oct 22, 2020
26cb1c8
Merge branch 'master' into ml-add-annotation-markers-in-context-brush…
kibanamachine Oct 27, 2020
aff3ab7
Merge remote-tracking branch 'upstream/master' into ml-add-annotation…
qn895 Oct 27, 2020
7945876
Merge remote-tracking branch 'upstream/master' into ml-add-annotation…
qn895 Oct 28, 2020
e84846f
[ML] Show tooltip
qn895 Oct 28, 2020
149ef85
[ML] Replace ctx annotation margin
qn895 Oct 28, 2020
3c6cd28
[ML] Add extra check to make annotationData safer
qn895 Oct 28, 2020
17c7903
Merge remote-tracking branch 'upstream/master' into ml-add-annotation…
qn895 Oct 28, 2020
f2253c0
Merge upstream/master into ml-add-annotation-markers-in-context-bru…
qn895 Nov 2, 2020
e12c721
[ML] Hide annotation strip
qn895 Nov 2, 2020
b55dacc
[ML] Change interface TimeseriesChart to class
qn895 Nov 2, 2020
af773a4
[ML] Fix i18n
qn895 Nov 2, 2020
d20aa19
Merge remote-tracking branch 'upstream/master' into ml-add-annotation…
qn895 Nov 3, 2020
3fb8c08
[ML] Fix toast notif show if no annotation data
qn895 Nov 3, 2020
d54048a
Merge remote-tracking branch 'upstream/master' into ml-add-annotation…
qn895 Nov 4, 2020
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 @@ -98,7 +98,7 @@ class AnnotationsTableUI extends Component {
if (dataCounts.processed_record_count > 0) {
// Load annotations for the selected job.
ml.annotations
.getAnnotations({
.getAnnotations$({
jobIds: [job.job_id],
earliestMs: null,
latestMs: null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jest.mock('../../../services/ml_api_service', () => {
return {
ml: {
annotations: {
getAnnotations: jest.fn().mockReturnValue(mockAnnotations$),
getAnnotations$: jest.fn().mockReturnValue(mockAnnotations$),
},
},
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -392,7 +392,7 @@ export function loadAnnotationsTableData(selectedCells, selectedJobs, interval,

return new Promise((resolve) => {
ml.annotations
.getAnnotations({
.getAnnotations$({
jobIds,
earliestMs: timeRange.earliestMs,
latestMs: timeRange.latestMs,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ export class JobDetailsUI extends Component {
}

render() {
console.log('this.props', this.props);
const { job } = this.state;
const {
services: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { http, http$ } from '../http_service';
import { basePath } from './index';

export const annotations = {
getAnnotations(obj: {
getAnnotations$(obj: {
jobIds: string[];
earliestMs: number;
latestMs: number;
Expand All @@ -30,6 +30,23 @@ export const annotations = {
});
},

getAnnotations(obj: {
jobIds: string[];
earliestMs: number;
latestMs: number;
maxAnnotations: number;
fields: FieldToBucket[];
detectorIndex: number;
entities: any[];
}) {
const body = JSON.stringify(obj);
return http<GetAnnotationsResponse>({
path: `${basePath()}/annotations`,
method: 'POST',
body,
});
},

indexAnnotation(obj: Annotation) {
const body = JSON.stringify(obj);
return http<any>({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,25 @@ $mlAnnotationRectDefaultFillOpacity: 0.05;
.mlAnnotationHidden {
display: none;
}

// context annotation marker
.mlContextAnnotationRect {
stroke: $euiColorFullShade;
stroke-width: $mlAnnotationBorderWidth;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For what it's worth, there are 1px $euiBorderWidthThin and 2px $euiBorderWidthThick variables, if you're interested in using them here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for looking it over 🙏 I think I'll keep mlAnnotationBorderWidth here since it's used for other parts as well but good to know we do have those variables available.

stroke-opacity: $mlAnnotationRectDefaultStrokeOpacity;
transition: stroke-opacity $euiAnimSpeedFast;

fill: $euiColorFullShade;
fill-opacity: $mlAnnotationRectDefaultFillOpacity;
transition: fill-opacity $euiAnimSpeedFast;

shape-rendering: geometricPrecision;
}

.mlContextAnnotationRect-isBlur {
stroke-opacity: $mlAnnotationRectDefaultStrokeOpacity / 2;
transition: stroke-opacity $euiAnimSpeedFast;

fill-opacity: $mlAnnotationRectDefaultFillOpacity / 2;
transition: fill-opacity $euiAnimSpeedFast;
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { annotation$ } from '../../../services/annotations_service';
import { formatValue } from '../../../formatters/format_value';
import {
LINE_CHART_ANOMALY_RADIUS,
ANNOTATION_SYMBOL_HEIGHT,
MULTI_BUCKET_SYMBOL_SIZE,
SCHEDULED_EVENT_SYMBOL_HEIGHT,
drawLineChartDots,
Expand All @@ -48,11 +49,13 @@ import {
renderAnnotations,
highlightFocusChartAnnotation,
unhighlightFocusChartAnnotation,
ANNOTATION_MIN_WIDTH,
} from './timeseries_chart_annotations';

const focusZoomPanelHeight = 25;
const focusChartHeight = 310;
const focusHeight = focusZoomPanelHeight + focusChartHeight;
const annotationHeight = ANNOTATION_SYMBOL_HEIGHT + 4;
darnautov marked this conversation as resolved.
Show resolved Hide resolved
const contextChartHeight = 60;
const contextChartLineTopMargin = 3;
const chartSpacing = 25;
Expand Down Expand Up @@ -82,7 +85,13 @@ const anomalyGrayScale = d3.scale

function getSvgHeight() {
return (
focusHeight + contextChartHeight + swimlaneHeight + chartSpacing + margin.top + margin.bottom
focusHeight +
contextChartHeight +
swimlaneHeight +
annotationHeight +
chartSpacing +
margin.top +
margin.bottom
);
}

Expand Down Expand Up @@ -225,7 +234,12 @@ class TimeseriesChartIntl extends Component {
}

componentDidUpdate(prevProps) {
if (this.props.renderFocusChartOnly === false || prevProps.svgWidth !== this.props.svgWidth) {
if (
this.props.renderFocusChartOnly === false ||
prevProps.svgWidth !== this.props.svgWidth ||
prevProps.showAnnotations !== this.props.showAnnotations ||
prevProps.annotationData !== this.props.annotationData
) {
this.renderChart();
this.drawContextChartSelection();
}
Expand Down Expand Up @@ -367,7 +381,13 @@ class TimeseriesChartIntl extends Component {

// Draw each of the component elements.
createFocusChart(focus, this.vizWidth, focusHeight);
drawContextElements(context, this.vizWidth, contextChartHeight, swimlaneHeight);
drawContextElements(
context,
this.vizWidth,
contextChartHeight,
swimlaneHeight,
annotationHeight
);
}

contextChartInitialized = false;
Expand Down Expand Up @@ -947,8 +967,14 @@ class TimeseriesChartIntl extends Component {
}

drawContextElements(cxtGroup, cxtWidth, cxtChartHeight, swlHeight) {
const { bounds, contextChartData, contextForecastData, modelPlotEnabled } = this.props;

const {
bounds,
contextChartData,
contextForecastData,
modelPlotEnabled,
annotationData,
showAnnotations,
} = this.props;
const data = contextChartData;

this.contextXScale = d3.time
Expand Down Expand Up @@ -1065,6 +1091,57 @@ class TimeseriesChartIntl extends Component {
cxtGroup.append('path').datum(data).attr('class', 'values-line').attr('d', contextValuesLine);
drawLineChartDots(data, cxtGroup, contextValuesLine, 1);

// Add annotation markers to the context area
cxtGroup.append('g').classed('mlContextAnnotations', true);

const [contextXRangeStart, contextXRangeEnd] = this.contextXScale.range();
const ctxAnnotations = cxtGroup
.select('.mlContextAnnotations')
.selectAll('g.mlContextAnnotation')
.data(showAnnotations && annotationData ? annotationData : [], (d) => d._id || '');

ctxAnnotations.enter().append('g').classed('mlContextAnnotation', true);

const ctxAnnotationRects = ctxAnnotations
.selectAll('.mlContextAnnotationRect')
.data((d) => [d]);

ctxAnnotationRects
.enter()
.append('rect')
.attr('rx', 2)
.attr('ry', 2)
.classed('mlContextAnnotationRect', true);

ctxAnnotationRects
.attr('x', (d) => {
const date = moment(d.timestamp);
let xPos = this.contextXScale(date);

if (xPos - ANNOTATION_SYMBOL_HEIGHT <= contextXRangeStart) {
xPos = 0;
}
if (xPos + ANNOTATION_SYMBOL_HEIGHT >= contextXRangeEnd) {
xPos = contextXRangeEnd - ANNOTATION_SYMBOL_HEIGHT;
}

return xPos;
})
.attr('y', cxtChartHeight + swlHeight + 2)
.attr('height', ANNOTATION_SYMBOL_HEIGHT)
.attr('width', (d) => {
const s = this.contextXScale(moment(d.timestamp)) + 1;
const e =
darnautov marked this conversation as resolved.
Show resolved Hide resolved
typeof d.end_timestamp !== 'undefined'
? this.contextXScale(moment(d.end_timestamp)) - 1
: s + ANNOTATION_MIN_WIDTH;
const width = Math.max(ANNOTATION_MIN_WIDTH, e - s);
return width;
});

ctxAnnotations.classed('mlAnnotationHidden', !showAnnotations);
ctxAnnotationRects.exit().remove();

// Create the path elements for the forecast value line and bounds area.
if (contextForecastData !== undefined) {
cxtGroup
Expand Down Expand Up @@ -1120,7 +1197,7 @@ class TimeseriesChartIntl extends Component {
.call(brush)
.selectAll('rect')
.attr('y', -1)
.attr('height', contextChartHeight + swimlaneHeight + 1);
.attr('height', contextChartHeight + swimlaneHeight + annotationHeight + 1);

// move the left and right resize areas over to
// be under the handles
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ const ANNOTATION_DEFAULT_LEVEL = 1;
const ANNOTATION_LEVEL_HEIGHT = 28;
const ANNOTATION_UPPER_RECT_MARGIN = 0;
const ANNOTATION_UPPER_TEXT_MARGIN = -7;
const ANNOTATION_MIN_WIDTH = 2;
export const ANNOTATION_MIN_WIDTH = 2;
const ANNOTATION_RECT_BORDER_RADIUS = 2;
const ANNOTATION_TEXT_VERTICAL_OFFSET = 26;
const ANNOTATION_TEXT_RECT_VERTICAL_OFFSET = 12;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,10 @@ import {
import { getToastNotifications } from '../util/dependency_cache';
import { ResizeChecker } from '../../../../../../src/plugins/kibana_utils/public';

import { ANOMALIES_TABLE_DEFAULT_QUERY_SIZE } from '../../../common/constants/search';
import {
ANNOTATIONS_TABLE_DEFAULT_QUERY_SIZE,
ANOMALIES_TABLE_DEFAULT_QUERY_SIZE,
} from '../../../common/constants/search';
import {
isModelPlotEnabled,
isModelPlotChartableForDetector,
Expand Down Expand Up @@ -84,6 +87,7 @@ import {
} from './timeseriesexplorer_utils';
import { EMPTY_FIELD_VALUE_LABEL } from './components/entity_control/entity_control';
import { ANOMALY_DETECTION_DEFAULT_TIME_RANGE } from '../../../common/constants/settings';
import { extractErrorMessage } from '../../../common/util/errors';

// Used to indicate the chart is being plotted across
// all partition field values, where the cardinality of the field cannot be
Expand Down Expand Up @@ -128,6 +132,8 @@ function getTimeseriesexplorerDefaultState() {
dataNotChartable: false,
entitiesLoading: false,
entityValues: {},
annotationError: undefined,
annotationData: [],
focusAnnotationData: [],
focusAggregations: {},
focusAggregationInterval: {},
Expand Down Expand Up @@ -188,6 +194,7 @@ export class TimeSeriesExplorer extends React.Component {
this.resizeRef.current !== null ? this.resizeRef.current.offsetWidth - containerPadding : 0,
});
};
unmounted = false;

/**
* Subject for listening brush time range selection.
Expand Down Expand Up @@ -421,6 +428,29 @@ export class TimeSeriesExplorer extends React.Component {
);
};

/**
* Loads the full list of annotations for job without any aggs or time boundaries
* used to indicate existence of annotations that are beyond the selected time
* in the time series brush area
*/
loadAnnotations = async (jobId) => {
darnautov marked this conversation as resolved.
Show resolved Hide resolved
ml.annotations
.getAnnotations({
jobIds: [jobId],
earliestMs: null,
latestMs: null,
maxAnnotations: ANNOTATIONS_TABLE_DEFAULT_QUERY_SIZE,
})
.then((resp) => {
if (!this.unmounted && resp?.success === true) {
this.setState({ annotationData: resp.annotations[jobId] ?? [] });
}
})
darnautov marked this conversation as resolved.
Show resolved Hide resolved
.catch((error) => {
this.setState({ annotationData: [], annotationError: extractErrorMessage(error) });
});
};

/**
* Loads available entity values.
* @param {Array} entities - Entity controls configuration
Expand Down Expand Up @@ -999,6 +1029,7 @@ export class TimeSeriesExplorer extends React.Component {
previousProps.selectedForecastId !== this.props.selectedForecastId ||
previousProps.selectedJobId !== this.props.selectedJobId;
this.loadSingleMetricData(fullRefresh);
this.loadAnnotations(this.props.selectedJobId);
}

if (previousProps === undefined) {
Expand Down Expand Up @@ -1026,6 +1057,7 @@ export class TimeSeriesExplorer extends React.Component {
componentWillUnmount() {
this.subscriptions.unsubscribe();
this.resizeChecker.destroy();
this.unmounted = true;
}

render() {
Expand All @@ -1046,6 +1078,7 @@ export class TimeSeriesExplorer extends React.Component {
dataNotChartable,
entityValues,
focusAggregationInterval,
annotationData,
focusAnnotationError,
focusAnnotationData,
focusAggregations,
Expand Down Expand Up @@ -1077,6 +1110,7 @@ export class TimeSeriesExplorer extends React.Component {
contextForecastData,
contextAggregationInterval,
swimlaneData,
annotationData,
focusAnnotationData,
focusChartData,
focusForecastData,
Expand Down Expand Up @@ -1111,7 +1145,6 @@ export class TimeSeriesExplorer extends React.Component {
isEqual(this.previousChartProps.focusForecastData, chartProps.focusForecastData) &&
isEqual(this.previousChartProps.focusChartData, chartProps.focusChartData) &&
isEqual(this.previousChartProps.focusAnnotationData, chartProps.focusAnnotationData) &&
this.previousShowAnnotations === showAnnotations &&
this.previousShowForecast === showForecast &&
this.previousShowModelBounds === showModelBounds &&
this.props.previousRefresh === lastRefresh
Expand All @@ -1120,7 +1153,6 @@ export class TimeSeriesExplorer extends React.Component {
}

this.previousChartProps = chartProps;
this.previousShowAnnotations = showAnnotations;
this.previousShowForecast = showForecast;
this.previousShowModelBounds = showModelBounds;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ export function getFocusData(
),
// Query 4 - load any annotations for the selected job.
ml.annotations
.getAnnotations({
.getAnnotations$({
jobIds: [selectedJob.job_id],
earliestMs: searchBounds.min.valueOf(),
latestMs: searchBounds.max.valueOf(),
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/ml/public/application/util/chart_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { ML_PAGES } from '../../../common/constants/ml_url_generator';
export const LINE_CHART_ANOMALY_RADIUS = 7;
export const MULTI_BUCKET_SYMBOL_SIZE = 100; // In square pixels for use with d3 symbol.size
export const SCHEDULED_EVENT_SYMBOL_HEIGHT = 5;
export const ANNOTATION_SYMBOL_HEIGHT = 10;

const MAX_LABEL_WIDTH = 100;

Expand Down