mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[ML] Explain log rate spikes: Fix data out of date when brush selection changes (#137791)
* 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 <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
8231e0f47f
commit
b17579afa3
6 changed files with 185 additions and 35 deletions
|
@ -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';
|
||||
|
@ -58,6 +59,7 @@ interface DualBrushProps {
|
|||
max: number;
|
||||
onChange?: (windowParameters: WindowParameters, windowPxParameters: WindowParameters) => void;
|
||||
marginLeft: number;
|
||||
snapTimestamps?: number[];
|
||||
width: number;
|
||||
}
|
||||
|
||||
|
@ -67,6 +69,7 @@ export function DualBrush({
|
|||
max,
|
||||
onChange,
|
||||
marginLeft,
|
||||
snapTimestamps,
|
||||
width,
|
||||
}: DualBrushProps) {
|
||||
const d3BrushContainer = useRef(null);
|
||||
|
@ -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' &&
|
||||
|
@ -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 &&
|
||||
|
@ -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, [newBaselineMin, newBaselineMax]);
|
||||
.call(brushes.current[0].brush.move, [
|
||||
newBrushPx.baselineMin,
|
||||
newBrushPx.baselineMax,
|
||||
]);
|
||||
}
|
||||
|
||||
brushes.current[0].start = newWindowParameters.baselineMin;
|
||||
brushes.current[0].end = newWindowParameters.baselineMax;
|
||||
brushes.current[1].start = newWindowParameters.deviationMin;
|
||||
brushes.current[1].end = newWindowParameters.deviationMax;
|
||||
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[1].brush.move, [
|
||||
newBrushPx.deviationMin,
|
||||
newBrushPx.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();
|
||||
}
|
||||
|
@ -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 (
|
||||
<>
|
||||
|
|
|
@ -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';
|
||||
|
@ -19,6 +26,7 @@ interface ProgressControlProps {
|
|||
onRefresh: () => void;
|
||||
onCancel: () => void;
|
||||
isRunning: boolean;
|
||||
shouldRerunAnalysis: boolean;
|
||||
}
|
||||
|
||||
export function ProgressControls({
|
||||
|
@ -27,6 +35,7 @@ export function ProgressControls({
|
|||
onRefresh,
|
||||
onCancel,
|
||||
isRunning,
|
||||
shouldRerunAnalysis,
|
||||
}: ProgressControlProps) {
|
||||
return (
|
||||
<EuiFlexGroup>
|
||||
|
@ -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 && (
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
@ -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({
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -280,6 +289,7 @@ export const DocumentCountChart: FC<DocumentCountChartProps> = ({
|
|||
max={timeRangeLatest + interval}
|
||||
onChange={onWindowParametersChange}
|
||||
marginLeft={mlBrushMarginLeft}
|
||||
snapTimestamps={snapTimestamps}
|
||||
width={mlBrushWidth}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
@ -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
|
||||
|
@ -72,6 +77,7 @@ export const ExplainLogRateSpikesAnalysis: FC<ExplainLogRateSpikesAnalysisProps>
|
|||
);
|
||||
|
||||
useEffect(() => {
|
||||
setCurrentAnalysisWindowParameters(windowParameters);
|
||||
start();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
@ -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 (
|
||||
|
@ -98,6 +113,7 @@ export const ExplainLogRateSpikesAnalysis: FC<ExplainLogRateSpikesAnalysisProps>
|
|||
isRunning={isRunning}
|
||||
onRefresh={startHandler}
|
||||
onCancel={cancel}
|
||||
shouldRerunAnalysis={shouldRerunAnalysis}
|
||||
/>
|
||||
{!isRunning && !showSpikeAnalysisTable && (
|
||||
<EuiEmptyPrompt
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue