Skip to content

Commit

Permalink
[ML] Explain log rate spikes: Fix data out of date when brush selecti…
Browse files Browse the repository at this point in the history
…on changes (#137791) (#138009)

* update run analysis button content when selection changges

* fix brush overlap causing endless rerender

* [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix'

* fix resize triggering rerun analysis prompt

* [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix'

* add comments to getSnappedWindowParameters function

* use memo instead of using component state

* [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix'

* fix eslint error and simplify usememo callback

* [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix'

Co-authored-by: kibanamachine <[email protected]>
(cherry picked from commit b17579a)

Co-authored-by: Melissa Alvarez <[email protected]>
  • Loading branch information
kibanamachine and alvarezmelissa87 authored Aug 3, 2022
1 parent 4a7a4d6 commit 77678c4
Show file tree
Hide file tree
Showing 6 changed files with 185 additions and 35 deletions.
78 changes: 55 additions & 23 deletions x-pack/packages/ml/aiops_components/src/dual_brush/dual_brush.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import * as d3Scale from 'd3-scale';
import * as d3Selection from 'd3-selection';
import * as d3Transition from 'd3-transition';

import { getSnappedWindowParameters } from '@kbn/aiops-utils';
import type { WindowParameters } from '@kbn/aiops-utils';

import './dual_brush.scss';
Expand Down Expand Up @@ -58,6 +59,7 @@ interface DualBrushProps {
max: number;
onChange?: (windowParameters: WindowParameters, windowPxParameters: WindowParameters) => void;
marginLeft: number;
snapTimestamps?: number[];
width: number;
}

Expand All @@ -67,6 +69,7 @@ export function DualBrush({
max,
onChange,
marginLeft,
snapTimestamps,
width,
}: DualBrushProps) {
const d3BrushContainer = useRef(null);
Expand Down Expand Up @@ -129,12 +132,6 @@ export function DualBrush({
deviationMin: px2ts(deviationSelection[0]),
deviationMax: px2ts(deviationSelection[1]),
};
const newBrushPx = {
baselineMin: baselineSelection[0],
baselineMax: baselineSelection[1],
deviationMin: deviationSelection[0],
deviationMax: deviationSelection[1],
};

if (
id === 'deviation' &&
Expand All @@ -147,14 +144,6 @@ export function DualBrush({

newWindowParameters.deviationMin = px2ts(newDeviationMin);
newWindowParameters.deviationMax = px2ts(newDeviationMax);
newBrushPx.deviationMin = newDeviationMin;
newBrushPx.deviationMax = newDeviationMax;

d3.select(this)
.transition()
.duration(200)
// @ts-expect-error call doesn't allow the brush move function
.call(brushes.current[1].brush.move, [newDeviationMin, newDeviationMax]);
} else if (
id === 'baseline' &&
deviationSelection &&
Expand All @@ -166,23 +155,56 @@ export function DualBrush({

newWindowParameters.baselineMin = px2ts(newBaselineMin);
newWindowParameters.baselineMax = px2ts(newBaselineMax);
newBrushPx.baselineMin = newBaselineMin;
newBrushPx.baselineMax = newBaselineMax;
}

const snappedWindowParameters = snapTimestamps
? getSnappedWindowParameters(newWindowParameters, snapTimestamps)
: newWindowParameters;

const newBrushPx = {
baselineMin: x(snappedWindowParameters.baselineMin) ?? 0,
baselineMax: x(snappedWindowParameters.baselineMax) ?? 0,
deviationMin: x(snappedWindowParameters.deviationMin) ?? 0,
deviationMax: x(snappedWindowParameters.deviationMax) ?? 0,
};

if (
id === 'baseline' &&
(baselineSelection[0] !== newBrushPx.baselineMin ||
baselineSelection[1] !== newBrushPx.baselineMax)
) {
d3.select(this)
.transition()
.duration(200)
// @ts-expect-error call doesn't allow the brush move function
.call(brushes.current[0].brush.move, [
newBrushPx.baselineMin,
newBrushPx.baselineMax,
]);
}

if (
id === 'deviation' &&
(deviationSelection[0] !== newBrushPx.deviationMin ||
deviationSelection[1] !== newBrushPx.deviationMax)
) {
d3.select(this)
.transition()
.duration(200)
// @ts-expect-error call doesn't allow the brush move function
.call(brushes.current[0].brush.move, [newBaselineMin, newBaselineMax]);
.call(brushes.current[1].brush.move, [
newBrushPx.deviationMin,
newBrushPx.deviationMax,
]);
}

brushes.current[0].start = newWindowParameters.baselineMin;
brushes.current[0].end = newWindowParameters.baselineMax;
brushes.current[1].start = newWindowParameters.deviationMin;
brushes.current[1].end = newWindowParameters.deviationMax;
brushes.current[0].start = snappedWindowParameters.baselineMin;
brushes.current[0].end = snappedWindowParameters.baselineMax;
brushes.current[1].start = snappedWindowParameters.deviationMin;
brushes.current[1].end = snappedWindowParameters.deviationMax;

if (onChange) {
onChange(newWindowParameters, newBrushPx);
onChange(snappedWindowParameters, newBrushPx);
}
drawBrushes();
}
Expand Down Expand Up @@ -255,7 +277,17 @@ export function DualBrush({

drawBrushes();
}
}, [min, max, width, baselineMin, baselineMax, deviationMin, deviationMax, onChange]);
}, [
min,
max,
width,
baselineMin,
baselineMax,
deviationMin,
deviationMax,
snapTimestamps,
onChange,
]);

return (
<>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,14 @@
* 2.0.
*/

import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiProgress, EuiText } from '@elastic/eui';
import {
EuiButton,
EuiFlexGroup,
EuiFlexItem,
EuiIconTip,
EuiProgress,
EuiText,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import React from 'react';
Expand All @@ -19,6 +26,7 @@ interface ProgressControlProps {
onRefresh: () => void;
onCancel: () => void;
isRunning: boolean;
shouldRerunAnalysis: boolean;
}

export function ProgressControls({
Expand All @@ -27,6 +35,7 @@ export function ProgressControls({
onRefresh,
onCancel,
isRunning,
shouldRerunAnalysis,
}: ProgressControlProps) {
return (
<EuiFlexGroup>
Expand Down Expand Up @@ -56,11 +65,34 @@ export function ProgressControls({
</EuiFlexItem>
<EuiFlexItem grow={false}>
{!isRunning && (
<EuiButton size="s" onClick={onRefresh}>
<FormattedMessage
id="xpack.aiops.rerunAnalysisButtonTitle"
defaultMessage="Rerun analysis"
/>
<EuiButton
size="s"
onClick={onRefresh}
color={shouldRerunAnalysis ? 'warning' : 'primary'}
>
<EuiFlexGroup>
<EuiFlexItem>
<FormattedMessage
id="xpack.aiops.rerunAnalysisButtonTitle"
defaultMessage="Rerun analysis"
/>
</EuiFlexItem>
{shouldRerunAnalysis && (
<>
<EuiFlexItem>
<EuiIconTip
aria-label="Warning"
type="alert"
color="warning"
content={i18n.translate('xpack.aiops.rerunAnalysisTooltipContent', {
defaultMessage:
'Analysis data may be out of date due to selection update. Rerun analysis.',
})}
/>
</EuiFlexItem>
</>
)}
</EuiFlexGroup>
</EuiButton>
)}
{isRunning && (
Expand Down
60 changes: 60 additions & 0 deletions x-pack/packages/ml/aiops_utils/src/get_window_parameters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,63 @@ export const getWindowParameters = (
deviationMax: Math.round(deviationMax),
};
};

/**
*
* Converts window paramaters from the brushes to “snap” the brushes to the chart histogram bar width and ensure timestamps
* correspond to bucket timestamps
*
* @param windowParameters time range definition for baseline and deviation to be used by spike log analysis
* @param snapTimestamps time range definition that always corresponds to histogram bucket timestamps
* @returns WindowParameters
*/
export const getSnappedWindowParameters = (
windowParameters: WindowParameters,
snapTimestamps: number[]
): WindowParameters => {
const snappedBaselineMin = snapTimestamps.reduce((pts, cts) => {
if (
Math.abs(cts - windowParameters.baselineMin) < Math.abs(pts - windowParameters.baselineMin)
) {
return cts;
}
return pts;
}, snapTimestamps[0]);
const baselineMaxTimestamps = snapTimestamps.filter((ts) => ts > snappedBaselineMin);

const snappedBaselineMax = baselineMaxTimestamps.reduce((pts, cts) => {
if (
Math.abs(cts - windowParameters.baselineMax) < Math.abs(pts - windowParameters.baselineMax)
) {
return cts;
}
return pts;
}, baselineMaxTimestamps[0]);
const deviationMinTss = baselineMaxTimestamps.filter((ts) => ts > snappedBaselineMax);

const snappedDeviationMin = deviationMinTss.reduce((pts, cts) => {
if (
Math.abs(cts - windowParameters.deviationMin) < Math.abs(pts - windowParameters.deviationMin)
) {
return cts;
}
return pts;
}, deviationMinTss[0]);
const deviationMaxTss = deviationMinTss.filter((ts) => ts > snappedDeviationMin);

const snappedDeviationMax = deviationMaxTss.reduce((pts, cts) => {
if (
Math.abs(cts - windowParameters.deviationMax) < Math.abs(pts - windowParameters.deviationMax)
) {
return cts;
}
return pts;
}, deviationMaxTss[0]);

return {
baselineMin: snappedBaselineMin,
baselineMax: snappedBaselineMax,
deviationMin: snappedDeviationMin,
deviationMax: snappedDeviationMax,
};
};
2 changes: 1 addition & 1 deletion x-pack/packages/ml/aiops_utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* 2.0.
*/

export { getWindowParameters } from './get_window_parameters';
export { getSnappedWindowParameters, getWindowParameters } from './get_window_parameters';
export type { WindowParameters } from './get_window_parameters';
export { streamFactory } from './stream_factory';
export { useFetchStream } from './use_fetch_stream';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import { IUiSettingsClient } from '@kbn/core/public';
import { DualBrush, DualBrushAnnotation } from '@kbn/aiops-components';
import { getWindowParameters } from '@kbn/aiops-utils';
import { getSnappedWindowParameters, getWindowParameters } from '@kbn/aiops-utils';
import type { WindowParameters } from '@kbn/aiops-utils';
import { MULTILAYER_TIME_AXIS_STYLE } from '@kbn/charts-plugin/common';
import type { ChangePoint } from '@kbn/ml-agg-utils';
Expand Down Expand Up @@ -148,6 +148,14 @@ export const DocumentCountChart: FC<DocumentCountChartProps> = ({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [chartPointsSplit, timeRangeEarliest, timeRangeLatest, interval]);

const snapTimestamps = useMemo(() => {
return adjustedChartPoints
.map((d) => d.time)
.filter(function (arg: unknown): arg is number {
return typeof arg === 'number';
});
}, [adjustedChartPoints]);

const timefilterUpdateHandler = useCallback(
(ranges: { from: number; to: number }) => {
data.query.timefilter.timefilter.setTime({
Expand Down Expand Up @@ -189,9 +197,10 @@ export const DocumentCountChart: FC<DocumentCountChartProps> = ({
xDomain.min,
xDomain.max + interval
);
setOriginalWindowParameters(wp);
setWindowParameters(wp);
brushSelectionUpdateHandler(wp, true);
const wpSnap = getSnappedWindowParameters(wp, snapTimestamps);
setOriginalWindowParameters(wpSnap);
setWindowParameters(wpSnap);
brushSelectionUpdateHandler(wpSnap, true);
}
}
};
Expand Down Expand Up @@ -280,6 +289,7 @@ export const DocumentCountChart: FC<DocumentCountChartProps> = ({
max={timeRangeLatest + interval}
onChange={onWindowParametersChange}
marginLeft={mlBrushMarginLeft}
snapTimestamps={snapTimestamps}
width={mlBrushWidth}
/>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
* 2.0.
*/

import React, { useEffect, FC } from 'react';
import React, { useEffect, useMemo, useState, FC } from 'react';
import { isEqual } from 'lodash';

import { EuiEmptyPrompt } from '@elastic/eui';

Expand Down Expand Up @@ -54,6 +55,10 @@ export const ExplainLogRateSpikesAnalysis: FC<ExplainLogRateSpikesAnalysisProps>
const { services } = useAiOpsKibana();
const basePath = services.http?.basePath.get() ?? '';

const [currentAnalysisWindowParameters, setCurrentAnalysisWindowParameters] = useState<
WindowParameters | undefined
>();

const { cancel, start, data, isRunning, error } = useFetchStream<
ApiExplainLogRateSpikes,
typeof basePath
Expand All @@ -72,6 +77,7 @@ export const ExplainLogRateSpikesAnalysis: FC<ExplainLogRateSpikesAnalysisProps>
);

useEffect(() => {
setCurrentAnalysisWindowParameters(windowParameters);
start();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
Expand All @@ -85,9 +91,18 @@ export const ExplainLogRateSpikesAnalysis: FC<ExplainLogRateSpikesAnalysisProps>
if (onSelectedChangePoint) {
onSelectedChangePoint(null);
}

setCurrentAnalysisWindowParameters(windowParameters);
start();
}

const shouldRerunAnalysis = useMemo(
() =>
currentAnalysisWindowParameters !== undefined &&
!isEqual(currentAnalysisWindowParameters, windowParameters),
[currentAnalysisWindowParameters, windowParameters]
);

const showSpikeAnalysisTable = data?.changePoints.length > 0;

return (
Expand All @@ -98,6 +113,7 @@ export const ExplainLogRateSpikesAnalysis: FC<ExplainLogRateSpikesAnalysisProps>
isRunning={isRunning}
onRefresh={startHandler}
onCancel={cancel}
shouldRerunAnalysis={shouldRerunAnalysis}
/>
{!isRunning && !showSpikeAnalysisTable && (
<EuiEmptyPrompt
Expand Down

0 comments on commit 77678c4

Please sign in to comment.