[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:
Walter Rafelsberger 2022-07-13 18:05:19 +02:00 committed by GitHub
parent 89dd6cb91e
commit 00216fd0b7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 304 additions and 221 deletions

View file

@ -12,4 +12,4 @@ export interface Refresh {
timeRange?: { start: string; end: string };
}
export const aiOpsRefresh$ = new Subject<Refresh>();
export const aiopsRefresh$ = new Subject<Refresh>();

View file

@ -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 = () => {

View file

@ -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>
</>
);
};

View file

@ -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}
/>
)}
</>
);
};

View file

@ -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}
</>
);
};

View file

@ -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>
);
};

View file

@ -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()

View file

@ -117,6 +117,7 @@ export const SpikeAnalysisTable: FC<Props> = ({ changePointData, error, loading
return (
<EuiBasicTable
compressed
columns={columns}
items={pageOfItems ?? []}
noItemsMessage={noDataText}

View file

@ -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);

View file

@ -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({

View file

@ -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';

View file

@ -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';

View file

@ -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>

View file

@ -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} />
</>
);