Skip to content

Commit

Permalink
[ML] Explain log rate spikes: Improve analysis workflow. (#137192)
Browse files Browse the repository at this point in the history
- Adds an empty prompt when there's no current selection to describe the necessary click action and some general feature description.
- Adds an option to clear the current selection and start from scratch.
- Adds badges to the brush areas to label them as Baseline and Deviation.
- Adds EuiPanel as wrappers for the chart and table sections. The distinct sections make it easier to identify to which context an action like the analysis refresh button refers.
  • Loading branch information
walterra authored Jul 26, 2022
1 parent 153b6b7 commit 97c0b32
Show file tree
Hide file tree
Showing 5 changed files with 193 additions and 45 deletions.
14 changes: 12 additions & 2 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 @@ -56,7 +56,7 @@ interface DualBrushProps {
windowParameters: WindowParameters;
min: number;
max: number;
onChange?: (windowParameters: WindowParameters) => void;
onChange?: (windowParameters: WindowParameters, windowPxParameters: WindowParameters) => void;
marginLeft: number;
width: number;
}
Expand Down Expand Up @@ -129,6 +129,12 @@ 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 @@ -141,6 +147,8 @@ export function DualBrush({

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

d3.select(this)
.transition()
Expand All @@ -158,6 +166,8 @@ export function DualBrush({

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

d3.select(this)
.transition()
Expand All @@ -172,7 +182,7 @@ export function DualBrush({
brushes.current[1].end = newWindowParameters.deviationMax;

if (onChange) {
onChange(newWindowParameters);
onChange(newWindowParameters, newBrushPx);
}
drawBrushes();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,10 @@ export const DualBrushAnnotation: FC<BrushAnnotationProps> = ({ id, min, max })
]}
id={`rect_brush_annotation_${id}`}
style={{
strokeWidth: 1,
strokeWidth: 0,
stroke: colors.lightShade,
fill: colors.lightShade,
opacity: 1,
opacity: 0.5,
}}
hideTooltips={true}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,10 @@ import {
XYChartElementEvent,
XYBrushEvent,
} from '@elastic/charts';
import { EuiBadge } from '@elastic/eui';

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';
Expand All @@ -37,14 +39,15 @@ export interface DocumentCountChartPoint {
}

interface DocumentCountChartProps {
brushSelectionUpdateHandler: (d: WindowParameters) => void;
brushSelectionUpdateHandler: (d: WindowParameters, force: boolean) => void;
width?: number;
chartPoints: DocumentCountChartPoint[];
chartPointsSplit?: DocumentCountChartPoint[];
timeRangeEarliest: number;
timeRangeLatest: number;
interval: number;
changePoint?: ChangePoint;
isBrushCleared: boolean;
}

const SPEC_ID = 'document_count';
Expand Down Expand Up @@ -73,6 +76,7 @@ export const DocumentCountChart: FC<DocumentCountChartProps> = ({
timeRangeLatest,
interval,
changePoint,
isBrushCleared,
}) => {
const {
services: { data, uiSettings, fieldFormats, charts },
Expand Down Expand Up @@ -187,7 +191,7 @@ export const DocumentCountChart: FC<DocumentCountChartProps> = ({
);
setOriginalWindowParameters(wp);
setWindowParameters(wp);
brushSelectionUpdateHandler(wp);
brushSelectionUpdateHandler(wp, true);
}
}
};
Expand All @@ -198,10 +202,21 @@ export const DocumentCountChart: FC<DocumentCountChartProps> = ({
WindowParameters | undefined
>();
const [windowParameters, setWindowParameters] = useState<WindowParameters | undefined>();
const [windowParametersAsPixels, setWindowParametersAsPixels] = useState<
WindowParameters | undefined
>();

useEffect(() => {
if (isBrushCleared && originalWindowParameters !== undefined) {
setOriginalWindowParameters(undefined);
setWindowParameters(undefined);
}
}, [isBrushCleared, originalWindowParameters]);

function onWindowParametersChange(wp: WindowParameters) {
function onWindowParametersChange(wp: WindowParameters, wpPx: WindowParameters) {
setWindowParameters(wp);
brushSelectionUpdateHandler(wp);
setWindowParametersAsPixels(wpPx);
brushSelectionUpdateHandler(wp, false);
}

const [mlBrushWidth, setMlBrushWidth] = useState<number>();
Expand All @@ -221,17 +236,56 @@ export const DocumentCountChart: FC<DocumentCountChartProps> = ({
<>
{isBrushVisible && (
<div className="aiopsHistogramBrushes">
<DualBrush
windowParameters={originalWindowParameters}
min={timeRangeEarliest}
max={timeRangeLatest + interval}
onChange={onWindowParametersChange}
marginLeft={mlBrushMarginLeft}
width={mlBrushWidth}
/>
<div
css={{
position: 'absolute',
'margin-left': `${
mlBrushMarginLeft + (windowParametersAsPixels?.baselineMin ?? 0)
}px`,
}}
>
<EuiBadge>
<FormattedMessage
id="xpack.aiops.documentCountChart.baselineBadgeContent"
defaultMessage="Baseline"
/>
</EuiBadge>
</div>
<div
css={{
position: 'absolute',
'margin-left': `${
mlBrushMarginLeft + (windowParametersAsPixels?.deviationMin ?? 0)
}px`,
}}
>
<EuiBadge>
<FormattedMessage
id="xpack.aiops.documentCountChart.deviationBadgeContent"
defaultMessage="Deviation"
/>
</EuiBadge>
</div>
<div
css={{
position: 'relative',
clear: 'both',
'padding-top': '20px',
'margin-bottom': '-4px',
}}
>
<DualBrush
windowParameters={originalWindowParameters}
min={timeRangeEarliest}
max={timeRangeLatest + interval}
onChange={onWindowParametersChange}
marginLeft={mlBrushMarginLeft}
width={mlBrushWidth}
/>
</div>
</div>
)}
<div style={{ width: width ?? '100%' }} data-test-subj="aiopsDocumentCountChart">
<div css={{ width: width ?? '100%' }} data-test-subj="aiopsDocumentCountChart">
<Chart
size={{
width: '100%',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,50 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React, { FC } from 'react';
import React, { useEffect, useState, FC } from 'react';

import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';

import { i18n } from '@kbn/i18n';
import type { WindowParameters } from '@kbn/aiops-utils';
import type { ChangePoint } from '@kbn/ml-agg-utils';

import { DocumentCountChart, DocumentCountChartPoint } from '../document_count_chart';
import { TotalCountHeader } from '../total_count_header';
import { DocumentCountStats } from '../../../get_document_stats';

const clearSelectionLabel = i18n.translate(
'xpack.aiops.documentCountContent.clearSelectionAriaLabel',
{
defaultMessage: 'Clear selection',
}
);

export interface DocumentCountContentProps {
brushSelectionUpdateHandler: (d: WindowParameters) => void;
clearSelectionHandler: () => void;
changePoint?: ChangePoint;
documentCountStats?: DocumentCountStats;
documentCountStatsSplit?: DocumentCountStats;
totalCount: number;
windowParameters?: WindowParameters;
}

export const DocumentCountContent: FC<DocumentCountContentProps> = ({
brushSelectionUpdateHandler,
clearSelectionHandler,
changePoint,
documentCountStats,
documentCountStatsSplit,
totalCount,
windowParameters,
}) => {
const [isBrushCleared, setIsBrushCleared] = useState(true);

useEffect(() => {
setIsBrushCleared(windowParameters === undefined);
}, [windowParameters]);

if (documentCountStats === undefined) {
return totalCount !== undefined ? <TotalCountHeader totalCount={totalCount} /> : null;
}
Expand All @@ -48,18 +68,48 @@ export const DocumentCountContent: FC<DocumentCountContentProps> = ({
chartPointsSplit = Object.entries(buckets).map(([time, value]) => ({ time: +time, value }));
}

function brushSelectionUpdate(d: WindowParameters, force: boolean) {
if (!isBrushCleared || force) {
brushSelectionUpdateHandler(d);
}
if (force) {
setIsBrushCleared(false);
}
}

function clearSelection() {
setIsBrushCleared(true);
clearSelectionHandler();
}

return (
<>
<TotalCountHeader totalCount={totalCount} />
<EuiFlexGroup gutterSize="xs">
<EuiFlexItem>
<TotalCountHeader totalCount={totalCount} />
</EuiFlexItem>
{!isBrushCleared && (
<EuiFlexItem grow={false}>
<EuiButtonEmpty
onClick={clearSelection}
size="xs"
data-test-sub="aiopsClearSelectionBadge"
>
{clearSelectionLabel}
</EuiButtonEmpty>
</EuiFlexItem>
)}
</EuiFlexGroup>
{documentCountStats.interval !== undefined && (
<DocumentCountChart
brushSelectionUpdateHandler={brushSelectionUpdateHandler}
brushSelectionUpdateHandler={brushSelectionUpdate}
chartPoints={chartPoints}
chartPointsSplit={chartPointsSplit}
timeRangeEarliest={timeRangeEarliest}
timeRangeLatest={timeRangeLatest}
interval={documentCountStats.interval}
changePoint={changePoint}
isBrushCleared={isBrushCleared}
/>
)}
</>
Expand Down
Loading

0 comments on commit 97c0b32

Please sign in to comment.