Skip to content

Commit

Permalink
[ML] Explain log rate spikes: Use DualBrush component to select Windo…
Browse files Browse the repository at this point in the history
…wParameters for analysis. (#136255)

- Adds the DualBrush component to DocumentCountChart to allow the user to select WindowParameters for the explain log rate spikes analysis.
- VIEW_MODE is for now hard coded to brush. In a follow up we will allow a user to switch between zoom mode for navigation and brush mode for selection of WindowParameters.
- The auto-generation of WindowParameters has been removed from the wrapping code in the ML plugin.
- urlState related code has been moved from ExplainLogRateSpikes to ExplainLogRateSpikesWrapper.
- The analysis results table style has been changed to compressed.
  • Loading branch information
walterra authored Jul 13, 2022
1 parent 89dd6cb commit 00216fd
Show file tree
Hide file tree
Showing 14 changed files with 304 additions and 221 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@ export interface Refresh {
timeRange?: { start: string; end: string };
}

export const aiOpsRefresh$ = new Subject<Refresh>();
export const aiopsRefresh$ = new Subject<Refresh>();
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { TimeHistoryContract, UI_SETTINGS } from '@kbn/data-plugin/public';

import { useUrlState } from '../../hooks/url_state';
import { useAiOpsKibana } from '../../kibana_context';
import { aiOpsRefresh$ } from '../../application/services/timefilter_refresh_service';
import { aiopsRefresh$ } from '../../application/services/timefilter_refresh_service';

interface TimePickerQuickRange {
from: string;
Expand Down Expand Up @@ -47,7 +47,7 @@ function getRecentlyUsedRangesFactory(timeHistory: TimeHistoryContract) {
}

function updateLastRefresh(timeRange: OnRefreshProps) {
aiOpsRefresh$.next({ lastRefresh: Date.now(), timeRange });
aiopsRefresh$.next({ lastRefresh: Date.now(), timeRange });
}

export const DatePickerWrapper: FC = () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@
* 2.0.
*/

import React, { FC, useCallback, useMemo } from 'react';
import { i18n } from '@kbn/i18n';
import React, { FC, useCallback, useEffect, useMemo, useState } from 'react';
import moment from 'moment';

import {
Axis,
BarSeries,
Expand All @@ -19,26 +20,37 @@ import {
XYChartElementEvent,
XYBrushEvent,
} from '@elastic/charts';
import moment from 'moment';

import { i18n } from '@kbn/i18n';
import { IUiSettingsClient } from '@kbn/core/public';
import { DualBrush, DualBrushAnnotation } from '@kbn/aiops-components';
import { getWindowParameters } from '@kbn/aiops-utils';
import type { WindowParameters } from '@kbn/aiops-utils';
import { MULTILAYER_TIME_AXIS_STYLE } from '@kbn/charts-plugin/common';

import { useAiOpsKibana } from '../../../kibana_context';

export interface DocumentCountChartPoint {
time: number | string;
value: number;
}

interface Props {
interface DocumentCountChartProps {
brushSelectionUpdateHandler: (d: WindowParameters) => void;
width?: number;
chartPoints: DocumentCountChartPoint[];
timeRangeEarliest: number;
timeRangeLatest: number;
interval?: number;
interval: number;
}

const SPEC_ID = 'document_count';

enum VIEW_MODE {
ZOOM = 'zoom',
BRUSH = 'brush',
}

function getTimezone(uiSettings: IUiSettingsClient) {
if (uiSettings.isDefault('dateFormat:tz')) {
const detectedTimezone = moment.tz.guess();
Expand All @@ -49,7 +61,8 @@ function getTimezone(uiSettings: IUiSettingsClient) {
}
}

export const DocumentCountChart: FC<Props> = ({
export const DocumentCountChart: FC<DocumentCountChartProps> = ({
brushSelectionUpdateHandler,
width,
chartPoints,
timeRangeEarliest,
Expand All @@ -70,6 +83,9 @@ export const DocumentCountChart: FC<Props> = ({
defaultMessage: 'document count',
});

// TODO Let user choose between ZOOM and BRUSH mode.
const [viewMode] = useState<VIEW_MODE>(VIEW_MODE.BRUSH);

const xDomain = {
min: timeRangeEarliest,
max: timeRangeLatest,
Expand Down Expand Up @@ -117,46 +133,120 @@ export const DocumentCountChart: FC<Props> = ({
from: startRange,
to: startRange + interval,
};
timefilterUpdateHandler(range);

if (viewMode === VIEW_MODE.ZOOM) {
timefilterUpdateHandler(range);
} else {
if (
typeof startRange === 'number' &&
originalWindowParameters === undefined &&
windowParameters === undefined &&
adjustedChartPoints !== undefined
) {
const wp = getWindowParameters(
startRange + interval / 2,
xDomain.min,
xDomain.max + interval
);
setOriginalWindowParameters(wp);
setWindowParameters(wp);
brushSelectionUpdateHandler(wp);
}
}
};

const timeZone = getTimezone(uiSettings);

const [originalWindowParameters, setOriginalWindowParameters] = useState<
WindowParameters | undefined
>();
const [windowParameters, setWindowParameters] = useState<WindowParameters | undefined>();

function onWindowParametersChange(wp: WindowParameters) {
setWindowParameters(wp);
brushSelectionUpdateHandler(wp);
}

const [mlBrushWidth, setMlBrushWidth] = useState<number>();
const [mlBrushMarginLeft, setMlBrushMarginLeft] = useState<number>();

useEffect(() => {
if (viewMode !== VIEW_MODE.BRUSH) {
setOriginalWindowParameters(undefined);
setWindowParameters(undefined);
}
}, [viewMode]);

const isBrushVisible =
originalWindowParameters && mlBrushMarginLeft && mlBrushWidth && mlBrushWidth > 0;

return (
<div style={{ width: width ?? '100%' }} data-test-subj="aiopsDocumentCountChart">
<Chart
size={{
width: '100%',
height: 120,
}}
>
<Settings
xDomain={xDomain}
onBrushEnd={onBrushEnd as BrushEndListener}
onElementClick={onElementClick}
theme={chartTheme}
baseTheme={chartBaseTheme}
/>
<Axis
id="bottom"
position={Position.Bottom}
showOverlappingTicks={true}
tickFormat={(value) => xAxisFormatter.convert(value)}
timeAxisLayerCount={useLegacyTimeAxis ? 0 : 2}
style={useLegacyTimeAxis ? {} : MULTILAYER_TIME_AXIS_STYLE}
/>
<Axis id="left" position={Position.Left} />
<BarSeries
id={SPEC_ID}
name={seriesName}
xScaleType={ScaleType.Time}
yScaleType={ScaleType.Linear}
xAccessor="time"
yAccessors={['value']}
data={adjustedChartPoints}
timeZone={timeZone}
/>
</Chart>
</div>
<>
{isBrushVisible && (
<div className="aiopsHistogramBrushes">
<DualBrush
windowParameters={originalWindowParameters}
min={timeRangeEarliest}
max={timeRangeLatest + interval}
onChange={onWindowParametersChange}
marginLeft={mlBrushMarginLeft}
width={mlBrushWidth}
/>
</div>
)}
<div style={{ width: width ?? '100%' }} data-test-subj="aiopsDocumentCountChart">
<Chart
size={{
width: '100%',
height: 120,
}}
>
<Settings
xDomain={xDomain}
onBrushEnd={viewMode !== VIEW_MODE.BRUSH ? (onBrushEnd as BrushEndListener) : undefined}
onElementClick={onElementClick}
onProjectionAreaChange={({ projection }) => {
setMlBrushMarginLeft(projection.left);
setMlBrushWidth(projection.width);
}}
theme={chartTheme}
baseTheme={chartBaseTheme}
/>
<Axis
id="bottom"
position={Position.Bottom}
showOverlappingTicks={true}
tickFormat={(value) => xAxisFormatter.convert(value)}
timeAxisLayerCount={useLegacyTimeAxis ? 0 : 2}
style={useLegacyTimeAxis ? {} : MULTILAYER_TIME_AXIS_STYLE}
/>
<Axis id="left" position={Position.Left} />
<BarSeries
id={SPEC_ID}
name={seriesName}
xScaleType={ScaleType.Time}
yScaleType={ScaleType.Linear}
xAccessor="time"
yAccessors={['value']}
data={adjustedChartPoints}
timeZone={timeZone}
/>
{windowParameters && (
<>
<DualBrushAnnotation
id="aiopsBaseline"
min={windowParameters.baselineMin}
max={windowParameters.baselineMax - interval}
/>
<DualBrushAnnotation
id="aiopsDeviation"
min={windowParameters.deviationMin}
max={windowParameters.deviationMax - interval}
/>
</>
)}
</Chart>
</div>
</>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,24 @@
* 2.0.
*/
import React, { FC } from 'react';

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

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

export interface Props {
export interface DocumentCountContentProps {
brushSelectionUpdateHandler: (d: WindowParameters) => void;
documentCountStats?: DocumentCountStats;
totalCount: number;
}

export const DocumentCountContent: FC<Props> = ({ documentCountStats, totalCount }) => {
export const DocumentCountContent: FC<DocumentCountContentProps> = ({
brushSelectionUpdateHandler,
documentCountStats,
totalCount,
}) => {
if (documentCountStats === undefined) {
return totalCount !== undefined ? <TotalCountHeader totalCount={totalCount} /> : null;
}
Expand All @@ -32,12 +40,15 @@ export const DocumentCountContent: FC<Props> = ({ documentCountStats, totalCount
return (
<>
<TotalCountHeader totalCount={totalCount} />
<DocumentCountChart
chartPoints={chartPoints}
timeRangeEarliest={timeRangeEarliest}
timeRangeLatest={timeRangeLatest}
interval={documentCountStats.interval}
/>
{documentCountStats.interval !== undefined && (
<DocumentCountChart
brushSelectionUpdateHandler={brushSelectionUpdateHandler}
chartPoints={chartPoints}
timeRangeEarliest={timeRangeEarliest}
timeRangeLatest={timeRangeLatest}
interval={documentCountStats.interval}
/>
)}
</>
);
};
Loading

0 comments on commit 00216fd

Please sign in to comment.