mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[ML] Explain log rate spikes: Use DualBrush component to select WindowParameters 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.
This commit is contained in:
parent
89dd6cb91e
commit
00216fd0b7
14 changed files with 304 additions and 221 deletions
|
@ -12,4 +12,4 @@ export interface Refresh {
|
|||
timeRange?: { start: string; end: string };
|
||||
}
|
||||
|
||||
export const aiOpsRefresh$ = new Subject<Refresh>();
|
||||
export const aiopsRefresh$ = new Subject<Refresh>();
|
||||
|
|
|
@ -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;
|
||||
|
@ -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 = () => {
|
||||
|
|
|
@ -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,
|
||||
|
@ -19,9 +20,14 @@ 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 {
|
||||
|
@ -29,16 +35,22 @@ export interface DocumentCountChartPoint {
|
|||
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();
|
||||
|
@ -49,7 +61,8 @@ function getTimezone(uiSettings: IUiSettingsClient) {
|
|||
}
|
||||
}
|
||||
|
||||
export const DocumentCountChart: FC<Props> = ({
|
||||
export const DocumentCountChart: FC<DocumentCountChartProps> = ({
|
||||
brushSelectionUpdateHandler,
|
||||
width,
|
||||
chartPoints,
|
||||
timeRangeEarliest,
|
||||
|
@ -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,
|
||||
|
@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
@ -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}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -7,18 +7,6 @@
|
|||
|
||||
import React, { useEffect, FC } from 'react';
|
||||
|
||||
import {
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiHorizontalRule,
|
||||
EuiPageBody,
|
||||
EuiPageContentBody,
|
||||
EuiPageContentHeader,
|
||||
EuiPageContentHeaderSection,
|
||||
EuiSpacer,
|
||||
EuiTitle,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import type { DataView } from '@kbn/data-views-plugin/public';
|
||||
import { ProgressControls } from '@kbn/aiops-components';
|
||||
import { useFetchStream } from '@kbn/aiops-utils';
|
||||
|
@ -28,44 +16,38 @@ import { useAiOpsKibana } from '../../kibana_context';
|
|||
import { initialState, streamReducer } from '../../../common/api/stream_reducer';
|
||||
import type { ApiExplainLogRateSpikes } from '../../../common/api';
|
||||
import { SpikeAnalysisTable } from '../spike_analysis_table';
|
||||
import { FullTimeRangeSelector } from '../full_time_range_selector';
|
||||
import { DocumentCountContent } from '../document_count_content/document_count_content';
|
||||
import { DatePickerWrapper } from '../date_picker_wrapper';
|
||||
import { useData } from '../../hooks/use_data';
|
||||
import { useUrlState } from '../../hooks/url_state';
|
||||
|
||||
/**
|
||||
* ExplainLogRateSpikes props require a data view.
|
||||
*/
|
||||
export interface ExplainLogRateSpikesProps {
|
||||
interface ExplainLogRateSpikesProps {
|
||||
/** The data view to analyze. */
|
||||
dataView: DataView;
|
||||
/** Start timestamp filter */
|
||||
earliest: number;
|
||||
/** End timestamp filter */
|
||||
latest: number;
|
||||
/** Window parameters for the analysis */
|
||||
windowParameters: WindowParameters;
|
||||
}
|
||||
|
||||
export const ExplainLogRateSpikes: FC<ExplainLogRateSpikesProps> = ({
|
||||
dataView,
|
||||
earliest,
|
||||
latest,
|
||||
windowParameters,
|
||||
}) => {
|
||||
const { services } = useAiOpsKibana();
|
||||
const basePath = services.http?.basePath.get() ?? '';
|
||||
|
||||
const [globalState, setGlobalState] = useUrlState('_g');
|
||||
|
||||
const { docStats, timefilter } = useData(dataView, setGlobalState);
|
||||
|
||||
const { cancel, start, data, isRunning, error } = useFetchStream<
|
||||
ApiExplainLogRateSpikes,
|
||||
typeof basePath
|
||||
>(
|
||||
`${basePath}/internal/aiops/explain_log_rate_spikes`,
|
||||
{
|
||||
// TODO Consider actual user selected time ranges.
|
||||
// Since we already receive window parameters here,
|
||||
// we just set a maximum time range of 1970-2038 here.
|
||||
start: 0,
|
||||
end: 2147483647000,
|
||||
start: earliest,
|
||||
end: latest,
|
||||
// TODO Consider an optional Kuery.
|
||||
kuery: '',
|
||||
// TODO Handle data view without time fields.
|
||||
|
@ -76,100 +58,23 @@ export const ExplainLogRateSpikes: FC<ExplainLogRateSpikesProps> = ({
|
|||
{ reducer: streamReducer, initialState }
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (globalState?.time !== undefined) {
|
||||
timefilter.setTime({
|
||||
from: globalState.time.from,
|
||||
to: globalState.time.to,
|
||||
});
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [JSON.stringify(globalState?.time), timefilter]);
|
||||
|
||||
useEffect(() => {
|
||||
if (globalState?.refreshInterval !== undefined) {
|
||||
timefilter.setRefreshInterval(globalState.refreshInterval);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [JSON.stringify(globalState?.refreshInterval), timefilter]);
|
||||
|
||||
useEffect(() => {
|
||||
start();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
if (!dataView || !timefilter) return null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiPageBody data-test-subj="aiOpsIndexPage" paddingSize="none" panelled={false}>
|
||||
<EuiFlexGroup gutterSize="m">
|
||||
<EuiFlexItem>
|
||||
<EuiPageContentHeader className="aiOpsPageHeader">
|
||||
<EuiPageContentHeaderSection>
|
||||
<div className="aiOpsTitleHeader">
|
||||
<EuiTitle size={'s'}>
|
||||
<h2>{dataView.title}</h2>
|
||||
</EuiTitle>
|
||||
</div>
|
||||
</EuiPageContentHeaderSection>
|
||||
|
||||
<EuiFlexGroup
|
||||
alignItems="center"
|
||||
justifyContent="flexEnd"
|
||||
gutterSize="s"
|
||||
data-test-subj="aiOpsTimeRangeSelectorSection"
|
||||
>
|
||||
{dataView.timeFieldName !== undefined && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<FullTimeRangeSelector
|
||||
dataView={dataView}
|
||||
query={undefined}
|
||||
disabled={false}
|
||||
timefilter={timefilter}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
<EuiFlexItem grow={false}>
|
||||
<DatePickerWrapper />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPageContentHeader>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiHorizontalRule />
|
||||
<EuiPageContentBody>
|
||||
<EuiFlexGroup direction="column">
|
||||
{docStats?.totalCount !== undefined && (
|
||||
<EuiFlexItem>
|
||||
<DocumentCountContent
|
||||
documentCountStats={docStats.documentCountStats}
|
||||
totalCount={docStats.totalCount}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
<EuiFlexItem>
|
||||
<ProgressControls
|
||||
progress={data.loaded}
|
||||
progressMessage={data.loadingState ?? ''}
|
||||
isRunning={isRunning}
|
||||
onRefresh={start}
|
||||
onCancel={cancel}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
{data?.changePoints ? (
|
||||
<EuiFlexItem>
|
||||
<SpikeAnalysisTable
|
||||
changePointData={data.changePoints}
|
||||
loading={isRunning}
|
||||
error={error}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
) : null}
|
||||
</EuiFlexGroup>
|
||||
</EuiPageContentBody>
|
||||
</EuiPageBody>
|
||||
<ProgressControls
|
||||
progress={data.loaded}
|
||||
progressMessage={data.loadingState ?? ''}
|
||||
isRunning={isRunning}
|
||||
onRefresh={start}
|
||||
onCancel={cancel}
|
||||
/>
|
||||
{data?.changePoints ? (
|
||||
<SpikeAnalysisTable changePointData={data.changePoints} loading={isRunning} error={error} />
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -5,11 +5,27 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { FC, useCallback } from 'react';
|
||||
import React, { FC, useCallback, useEffect, useState } from 'react';
|
||||
import { parse, stringify } from 'query-string';
|
||||
import { isEqual } from 'lodash';
|
||||
import { encode } from 'rison-node';
|
||||
import { useHistory, useLocation } from 'react-router-dom';
|
||||
|
||||
import {
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiHorizontalRule,
|
||||
EuiPageBody,
|
||||
EuiPageContentBody,
|
||||
EuiPageContentHeader,
|
||||
EuiPageContentHeaderSection,
|
||||
EuiSpacer,
|
||||
EuiTitle,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import type { WindowParameters } from '@kbn/aiops-utils';
|
||||
import type { DataView } from '@kbn/data-views-plugin/public';
|
||||
|
||||
import {
|
||||
Accessor,
|
||||
Dictionary,
|
||||
|
@ -19,10 +35,51 @@ import {
|
|||
getNestedProperty,
|
||||
SetUrlState,
|
||||
} from '../../hooks/url_state';
|
||||
import { useData } from '../../hooks/use_data';
|
||||
import { useUrlState } from '../../hooks/url_state';
|
||||
|
||||
import { ExplainLogRateSpikes, ExplainLogRateSpikesProps } from './explain_log_rate_spikes';
|
||||
import { FullTimeRangeSelector } from '../full_time_range_selector';
|
||||
import { DocumentCountContent } from '../document_count_content/document_count_content';
|
||||
import { DatePickerWrapper } from '../date_picker_wrapper';
|
||||
|
||||
import { ExplainLogRateSpikes } from './explain_log_rate_spikes';
|
||||
|
||||
export interface ExplainLogRateSpikesWrapperProps {
|
||||
/** The data view to analyze. */
|
||||
dataView: DataView;
|
||||
}
|
||||
|
||||
export const ExplainLogRateSpikesWrapper: FC<ExplainLogRateSpikesWrapperProps> = ({ dataView }) => {
|
||||
const [globalState, setGlobalState] = useUrlState('_g');
|
||||
|
||||
const { docStats, timefilter } = useData(dataView, setGlobalState);
|
||||
const [windowParameters, setWindowParameters] = useState<WindowParameters | undefined>();
|
||||
|
||||
const activeBounds = timefilter.getActiveBounds();
|
||||
let earliest: number | undefined;
|
||||
let latest: number | undefined;
|
||||
if (activeBounds !== undefined) {
|
||||
earliest = activeBounds.min?.valueOf();
|
||||
latest = activeBounds.max?.valueOf();
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (globalState?.time !== undefined) {
|
||||
timefilter.setTime({
|
||||
from: globalState.time.from,
|
||||
to: globalState.time.to,
|
||||
});
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [JSON.stringify(globalState?.time), timefilter]);
|
||||
|
||||
useEffect(() => {
|
||||
if (globalState?.refreshInterval !== undefined) {
|
||||
timefilter.setRefreshInterval(globalState.refreshInterval);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [JSON.stringify(globalState?.refreshInterval), timefilter]);
|
||||
|
||||
export const ExplainLogRateSpikesWrapper: FC<ExplainLogRateSpikesProps> = (props) => {
|
||||
const history = useHistory();
|
||||
const { search: urlSearchString } = useLocation();
|
||||
|
||||
|
@ -88,9 +145,65 @@ export const ExplainLogRateSpikesWrapper: FC<ExplainLogRateSpikesProps> = (props
|
|||
[history, urlSearchString]
|
||||
);
|
||||
|
||||
if (!dataView || !timefilter) return null;
|
||||
|
||||
return (
|
||||
<UrlStateContextProvider value={{ searchString: urlSearchString, setUrlState }}>
|
||||
<ExplainLogRateSpikes {...props} />{' '}
|
||||
<EuiPageBody data-test-subj="aiopsIndexPage" paddingSize="none" panelled={false}>
|
||||
<EuiFlexGroup gutterSize="m">
|
||||
<EuiFlexItem>
|
||||
<EuiPageContentHeader className="aiopsPageHeader">
|
||||
<EuiPageContentHeaderSection>
|
||||
<div className="aiopsTitleHeader">
|
||||
<EuiTitle size={'s'}>
|
||||
<h2>{dataView.title}</h2>
|
||||
</EuiTitle>
|
||||
</div>
|
||||
</EuiPageContentHeaderSection>
|
||||
|
||||
<EuiFlexGroup
|
||||
alignItems="center"
|
||||
justifyContent="flexEnd"
|
||||
gutterSize="s"
|
||||
data-test-subj="aiopsTimeRangeSelectorSection"
|
||||
>
|
||||
{dataView.timeFieldName !== undefined && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<FullTimeRangeSelector
|
||||
dataView={dataView}
|
||||
query={undefined}
|
||||
disabled={false}
|
||||
timefilter={timefilter}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
<EuiFlexItem grow={false}>
|
||||
<DatePickerWrapper />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPageContentHeader>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiHorizontalRule />
|
||||
<EuiPageContentBody>
|
||||
{docStats?.totalCount !== undefined && (
|
||||
<DocumentCountContent
|
||||
brushSelectionUpdateHandler={setWindowParameters}
|
||||
documentCountStats={docStats.documentCountStats}
|
||||
totalCount={docStats.totalCount}
|
||||
/>
|
||||
)}
|
||||
<EuiSpacer size="m" />
|
||||
{earliest !== undefined && latest !== undefined && windowParameters !== undefined && (
|
||||
<ExplainLogRateSpikes
|
||||
dataView={dataView}
|
||||
earliest={earliest}
|
||||
latest={latest}
|
||||
windowParameters={windowParameters}
|
||||
/>
|
||||
)}
|
||||
</EuiPageContentBody>
|
||||
</EuiPageBody>
|
||||
</UrlStateContextProvider>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
export type { ExplainLogRateSpikesProps } from './explain_log_rate_spikes';
|
||||
export type { ExplainLogRateSpikesWrapperProps } from './explain_log_rate_spikes_wrapper';
|
||||
import { ExplainLogRateSpikesWrapper } from './explain_log_rate_spikes_wrapper';
|
||||
|
||||
// required for dynamic import using React.lazy()
|
||||
|
|
|
@ -117,6 +117,7 @@ export const SpikeAnalysisTable: FC<Props> = ({ changePointData, error, loading
|
|||
|
||||
return (
|
||||
<EuiBasicTable
|
||||
compressed
|
||||
columns={columns}
|
||||
items={pageOfItems ?? []}
|
||||
noItemsMessage={noDataText}
|
||||
|
|
|
@ -80,15 +80,15 @@ export function parseUrlState(search: string): Dictionary<any> {
|
|||
// This uses a context to be able to maintain only one instance
|
||||
// of the url state. It gets passed down with `UrlStateProvider`
|
||||
// and can be used via `useUrlState`.
|
||||
export const aiOpsUrlStateStore = createContext<UrlState>({
|
||||
export const aiopsUrlStateStore = createContext<UrlState>({
|
||||
searchString: '',
|
||||
setUrlState: () => {},
|
||||
});
|
||||
|
||||
export const { Provider } = aiOpsUrlStateStore;
|
||||
export const { Provider } = aiopsUrlStateStore;
|
||||
|
||||
export const useUrlState = (accessor: Accessor) => {
|
||||
const { searchString, setUrlState: setUrlStateContext } = useContext(aiOpsUrlStateStore);
|
||||
const { searchString, setUrlState: setUrlStateContext } = useContext(aiopsUrlStateStore);
|
||||
|
||||
const urlState = useMemo(() => {
|
||||
const fullUrlState = parseUrlState(searchString);
|
||||
|
|
|
@ -11,7 +11,7 @@ import { merge } from 'rxjs';
|
|||
import { UI_SETTINGS } from '@kbn/data-plugin/common';
|
||||
import { useAiOpsKibana } from '../kibana_context';
|
||||
import { useTimefilter } from './use_time_filter';
|
||||
import { aiOpsRefresh$ } from '../application/services/timefilter_refresh_service';
|
||||
import { aiopsRefresh$ } from '../application/services/timefilter_refresh_service';
|
||||
import { TimeBuckets } from '../../common/time_buckets';
|
||||
import { useDocumentCountStats } from './use_document_count_stats';
|
||||
import { Dictionary } from './url_state';
|
||||
|
@ -80,7 +80,7 @@ export const useData = (
|
|||
const timeUpdateSubscription = merge(
|
||||
timefilter.getTimeUpdate$(),
|
||||
timefilter.getAutoRefreshFetch$(),
|
||||
aiOpsRefresh$
|
||||
aiopsRefresh$
|
||||
).subscribe(() => {
|
||||
if (onUpdate) {
|
||||
onUpdate({
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
import { useCallback, useState } from 'react';
|
||||
import { useAiOpsKibana } from '../kibana_context';
|
||||
|
||||
export const AIOPS_FROZEN_TIER_PREFERENCE = 'aiOps.frozenDataTierPreference';
|
||||
export const AIOPS_FROZEN_TIER_PREFERENCE = 'aiop.frozenDataTierPreference';
|
||||
|
||||
export type AiOps = Partial<{
|
||||
[AIOPS_FROZEN_TIER_PREFERENCE]: 'exclude_frozen' | 'include_frozen';
|
||||
|
|
|
@ -13,6 +13,5 @@ export function plugin() {
|
|||
return new AiopsPlugin();
|
||||
}
|
||||
|
||||
export type { ExplainLogRateSpikesProps } from './components/explain_log_rate_spikes';
|
||||
export { ExplainLogRateSpikes } from './shared_lazy_components';
|
||||
export type { AiopsPluginSetup, AiopsPluginStart } from './types';
|
||||
|
|
|
@ -9,7 +9,7 @@ import React, { FC, Suspense } from 'react';
|
|||
|
||||
import { EuiErrorBoundary, EuiLoadingContent } from '@elastic/eui';
|
||||
|
||||
import type { ExplainLogRateSpikesProps } from './components/explain_log_rate_spikes';
|
||||
import type { ExplainLogRateSpikesWrapperProps } from './components/explain_log_rate_spikes';
|
||||
|
||||
const ExplainLogRateSpikesWrapperLazy = React.lazy(
|
||||
() => import('./components/explain_log_rate_spikes')
|
||||
|
@ -22,10 +22,10 @@ const LazyWrapper: FC = ({ children }) => (
|
|||
);
|
||||
|
||||
/**
|
||||
* Lazy-wrapped ExplainLogRateSpikes React component
|
||||
* @param {ExplainLogRateSpikesProps} props - properties specifying the data on which to run the analysis.
|
||||
* Lazy-wrapped ExplainLogRateSpikesWrapper React component
|
||||
* @param {ExplainLogRateSpikesWrapperProps} props - properties specifying the data on which to run the analysis.
|
||||
*/
|
||||
export const ExplainLogRateSpikes: FC<ExplainLogRateSpikesProps> = (props) => (
|
||||
export const ExplainLogRateSpikes: FC<ExplainLogRateSpikesWrapperProps> = (props) => (
|
||||
<LazyWrapper>
|
||||
<ExplainLogRateSpikesWrapperLazy {...props} />
|
||||
</LazyWrapper>
|
||||
|
|
|
@ -5,18 +5,14 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useEffect, useState, FC } from 'react';
|
||||
import React, { FC } from 'react';
|
||||
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { ExplainLogRateSpikes } from '@kbn/aiops-plugin/public';
|
||||
import { getWindowParameters } from '@kbn/aiops-utils';
|
||||
import type { WindowParameters } from '@kbn/aiops-utils';
|
||||
import { KBN_FIELD_TYPES } from '@kbn/data-plugin/public';
|
||||
|
||||
import { useMlContext } from '../contexts/ml';
|
||||
import { useMlKibana } from '../contexts/kibana';
|
||||
import { HelpMenu } from '../components/help_menu';
|
||||
import { ml } from '../services/ml_api_service';
|
||||
|
||||
import { MlPageHeader } from '../components/page_header';
|
||||
|
||||
|
@ -28,36 +24,6 @@ export const ExplainLogRateSpikesPage: FC = () => {
|
|||
const context = useMlContext();
|
||||
const dataView = context.currentDataView;
|
||||
|
||||
const [windowParameters, setWindowParameters] = useState<WindowParameters | undefined>();
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchWindowParameters() {
|
||||
if (dataView.timeFieldName) {
|
||||
const stats: Array<{
|
||||
data: Array<{ doc_count: number; key: number }>;
|
||||
stats: [number, number];
|
||||
}> = await ml.getVisualizerFieldHistograms({
|
||||
indexPattern: dataView.title,
|
||||
fields: [{ fieldName: dataView.timeFieldName, type: KBN_FIELD_TYPES.DATE }],
|
||||
query: { match_all: {} },
|
||||
samplerShardSize: -1,
|
||||
});
|
||||
|
||||
const peak = stats[0].data.reduce((p, c) => (c.doc_count >= p.doc_count ? c : p), {
|
||||
doc_count: 0,
|
||||
key: 0,
|
||||
});
|
||||
const peakTimestamp = Math.round(peak.key);
|
||||
|
||||
setWindowParameters(
|
||||
getWindowParameters(peakTimestamp, stats[0].stats[0], stats[0].stats[1])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fetchWindowParameters();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<MlPageHeader>
|
||||
|
@ -66,9 +32,7 @@ export const ExplainLogRateSpikesPage: FC = () => {
|
|||
defaultMessage="Explain log rate spikes"
|
||||
/>
|
||||
</MlPageHeader>
|
||||
{dataView.timeFieldName && windowParameters && (
|
||||
<ExplainLogRateSpikes dataView={dataView} windowParameters={windowParameters} />
|
||||
)}
|
||||
{dataView.timeFieldName && <ExplainLogRateSpikes dataView={dataView} />}
|
||||
<HelpMenu docLink={docLinks.links.ml.guide} />
|
||||
</>
|
||||
);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue