mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[ML] [AIOps] Log Rate Analysis: Adds support to restore baseline/deviation from url state on page refresh. (#171398)
Support to restore baseline/deviation time ranges from url state on full page refresh. Also updates functional tests to include a full page refresh after the first analysis run for each dataset.
This commit is contained in:
parent
d5fc9b0314
commit
19e97f35a7
12 changed files with 162 additions and 54 deletions
|
@ -15,25 +15,13 @@ import { isPopulatedObject } from '@kbn/ml-is-populated-object';
|
|||
* @typedef {WindowParameters}
|
||||
*/
|
||||
export interface WindowParameters {
|
||||
/**
|
||||
* Baseline minimum value
|
||||
* @type {number}
|
||||
*/
|
||||
/** Baseline minimum value */
|
||||
baselineMin: number;
|
||||
/**
|
||||
* Baseline maximum value
|
||||
* @type {number}
|
||||
*/
|
||||
/** Baseline maximum value */
|
||||
baselineMax: number;
|
||||
/**
|
||||
* Deviation minimum value
|
||||
* @type {number}
|
||||
*/
|
||||
/** Deviation minimum value */
|
||||
deviationMin: number;
|
||||
/**
|
||||
* Deviation maximum value
|
||||
* @type {number}
|
||||
*/
|
||||
/** Deviation maximum value */
|
||||
deviationMax: number;
|
||||
}
|
||||
|
||||
|
|
|
@ -38,21 +38,3 @@ export const getDefaultAiOpsListState = (
|
|||
filters: [],
|
||||
...overrides,
|
||||
});
|
||||
|
||||
export interface LogCategorizationPageUrlState {
|
||||
pageKey: 'logCategorization';
|
||||
pageUrlState: LogCategorizationAppState;
|
||||
}
|
||||
|
||||
export interface LogCategorizationAppState extends AiOpsFullIndexBasedAppState {
|
||||
field: string | undefined;
|
||||
}
|
||||
|
||||
export const getDefaultLogCategorizationAppState = (
|
||||
overrides?: Partial<LogCategorizationAppState>
|
||||
): LogCategorizationAppState => {
|
||||
return {
|
||||
field: undefined,
|
||||
...getDefaultAiOpsListState(overrides),
|
||||
};
|
||||
};
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { getDefaultAiOpsListState, type AiOpsFullIndexBasedAppState } from './common';
|
||||
|
||||
export interface LogCategorizationPageUrlState {
|
||||
pageKey: 'logCategorization';
|
||||
pageUrlState: LogCategorizationAppState;
|
||||
}
|
||||
|
||||
export interface LogCategorizationAppState extends AiOpsFullIndexBasedAppState {
|
||||
field: string | undefined;
|
||||
}
|
||||
|
||||
export const getDefaultLogCategorizationAppState = (
|
||||
overrides?: Partial<LogCategorizationAppState>
|
||||
): LogCategorizationAppState => {
|
||||
return {
|
||||
field: undefined,
|
||||
...getDefaultAiOpsListState(overrides),
|
||||
};
|
||||
};
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { WindowParameters } from '@kbn/aiops-utils';
|
||||
|
||||
import { getDefaultAiOpsListState, type AiOpsFullIndexBasedAppState } from './common';
|
||||
|
||||
export interface LogRateAnalysisPageUrlState {
|
||||
pageKey: 'logRateAnalysis';
|
||||
pageUrlState: LogRateAnalysisAppState;
|
||||
}
|
||||
/**
|
||||
* To avoid long urls, we store the window parameters in the url state not with
|
||||
* their full parameters names but with abbrevations. `windowParametersToAppState` and
|
||||
* `appStateToWindowParameters` are used to transform the data structure.
|
||||
*/
|
||||
export interface LogRateAnalysisAppState extends AiOpsFullIndexBasedAppState {
|
||||
/** Window parameters */
|
||||
wp?: {
|
||||
/** Baseline minimum value */
|
||||
bMin: number;
|
||||
/** Baseline maximum value */
|
||||
bMax: number;
|
||||
/** Deviation minimum value */
|
||||
dMin: number;
|
||||
/** Deviation maximum value */
|
||||
dMax: number;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms a full window parameters object to the abbreviated url state version.
|
||||
*/
|
||||
export const windowParametersToAppState = (wp?: WindowParameters): LogRateAnalysisAppState['wp'] =>
|
||||
wp && {
|
||||
bMin: wp.baselineMin,
|
||||
bMax: wp.baselineMax,
|
||||
dMin: wp.deviationMin,
|
||||
dMax: wp.deviationMax,
|
||||
};
|
||||
|
||||
/**
|
||||
* Transforms an abbreviated url state version of window parameters to its full version.
|
||||
*/
|
||||
export const appStateToWindowParameters = (
|
||||
wp: LogRateAnalysisAppState['wp']
|
||||
): WindowParameters | undefined =>
|
||||
wp && {
|
||||
baselineMin: wp.bMin,
|
||||
baselineMax: wp.bMax,
|
||||
deviationMin: wp.dMin,
|
||||
deviationMax: wp.dMax,
|
||||
};
|
||||
|
||||
export const getDefaultLogRateAnalysisAppState = (
|
||||
overrides?: Partial<LogRateAnalysisAppState>
|
||||
): LogRateAnalysisAppState => {
|
||||
return {
|
||||
wp: undefined,
|
||||
...getDefaultAiOpsListState(overrides),
|
||||
};
|
||||
};
|
|
@ -30,7 +30,7 @@ import type {
|
|||
} from '../../../../common/api/log_categorization/types';
|
||||
|
||||
import { useEuiTheme } from '../../../hooks/use_eui_theme';
|
||||
import type { LogCategorizationAppState } from '../../../application/utils/url_state';
|
||||
import type { LogCategorizationAppState } from '../../../application/url_state/log_pattern_analysis';
|
||||
|
||||
import { MiniHistogram } from '../../mini_histogram';
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ import type { Category, SparkLinesPerCategory } from '../../../common/api/log_ca
|
|||
import {
|
||||
type LogCategorizationPageUrlState,
|
||||
getDefaultLogCategorizationAppState,
|
||||
} from '../../application/utils/url_state';
|
||||
} from '../../application/url_state/log_pattern_analysis';
|
||||
import { createMergedEsQuery } from '../../application/utils/search_utils';
|
||||
import { useData } from '../../hooks/use_data';
|
||||
import { useSearch } from '../../hooks/use_search';
|
||||
|
|
|
@ -37,7 +37,7 @@ import { useAiopsAppContext } from '../../hooks/use_aiops_app_context';
|
|||
import {
|
||||
getDefaultLogCategorizationAppState,
|
||||
type LogCategorizationPageUrlState,
|
||||
} from '../../application/utils/url_state';
|
||||
} from '../../application/url_state/log_pattern_analysis';
|
||||
|
||||
import { SearchPanel } from '../search_panel';
|
||||
import { PageHeader } from '../page_header';
|
||||
|
|
|
@ -15,7 +15,7 @@ import type { Filter } from '@kbn/es-query';
|
|||
import { getCategoryQuery } from '../../../common/api/log_categorization/get_category_query';
|
||||
import type { Category } from '../../../common/api/log_categorization/types';
|
||||
|
||||
import type { AiOpsIndexBasedAppState } from '../../application/utils/url_state';
|
||||
import type { AiOpsIndexBasedAppState } from '../../application/url_state/common';
|
||||
import { useAiopsAppContext } from '../../hooks/use_aiops_app_context';
|
||||
|
||||
export const QUERY_MODE = {
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import { isEqual } from 'lodash';
|
||||
import React, { useEffect, useMemo, useState, type FC } from 'react';
|
||||
import React, { useEffect, useMemo, useRef, useState, type FC } from 'react';
|
||||
import { EuiEmptyPrompt, EuiHorizontalRule, EuiPanel } from '@elastic/eui';
|
||||
import type { Moment } from 'moment';
|
||||
|
||||
|
@ -76,6 +76,8 @@ export interface LogRateAnalysisContentProps {
|
|||
barHighlightColorOverride?: string;
|
||||
/** Optional callback that exposes data of the completed analysis */
|
||||
onAnalysisCompleted?: (d: LogRateAnalysisResultsData) => void;
|
||||
/** Optional callback that exposes current window parameters */
|
||||
onWindowParametersChange?: (wp?: WindowParameters) => void;
|
||||
/** Identifier to indicate the plugin utilizing the component */
|
||||
embeddingOrigin: string;
|
||||
}
|
||||
|
@ -90,6 +92,7 @@ export const LogRateAnalysisContent: FC<LogRateAnalysisContentProps> = ({
|
|||
barColorOverride,
|
||||
barHighlightColorOverride,
|
||||
onAnalysisCompleted,
|
||||
onWindowParametersChange,
|
||||
embeddingOrigin,
|
||||
}) => {
|
||||
const [windowParameters, setWindowParameters] = useState<WindowParameters | undefined>();
|
||||
|
@ -105,6 +108,28 @@ export const LogRateAnalysisContent: FC<LogRateAnalysisContentProps> = ({
|
|||
setIsBrushCleared(windowParameters === undefined);
|
||||
}, [windowParameters]);
|
||||
|
||||
// Window parameters stored in the url state use this components
|
||||
// `initialAnalysisStart` prop to set the initial params restore from url state.
|
||||
// To avoid a loop with window parameters being passed around on load,
|
||||
// the following ref and useEffect are used to check wether it's safe to call
|
||||
// the `onWindowParametersChange` callback.
|
||||
const windowParametersTouched = useRef(false);
|
||||
useEffect(() => {
|
||||
// Don't continue if window parameters were not touched yet.
|
||||
// Because they can be reset to `undefined` at a later stage again when a user
|
||||
// clears the selections, we cannot rely solely on checking if they are
|
||||
// `undefined`, we need the additional ref to update on the first change.
|
||||
if (!windowParametersTouched.current && windowParameters === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
windowParametersTouched.current = true;
|
||||
|
||||
if (onWindowParametersChange) {
|
||||
onWindowParametersChange(windowParameters);
|
||||
}
|
||||
}, [onWindowParametersChange, windowParameters]);
|
||||
|
||||
// Checks if `esSearchQuery` is the default empty query passed on from the search bar
|
||||
// and if that's the case fall back to a simpler match all query.
|
||||
const searchQuery = useMemo(
|
||||
|
|
|
@ -6,22 +6,26 @@
|
|||
*/
|
||||
|
||||
import React, { useCallback, useEffect, useState, FC } from 'react';
|
||||
import { isEqual } from 'lodash';
|
||||
|
||||
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiPageBody, EuiPageSection, EuiSpacer } from '@elastic/eui';
|
||||
|
||||
import { Filter, FilterStateStore, Query } from '@kbn/es-query';
|
||||
import { useUrlState, usePageUrlState } from '@kbn/ml-url-state';
|
||||
|
||||
import type { SearchQueryLanguage } from '@kbn/ml-query-utils';
|
||||
import type { WindowParameters } from '@kbn/aiops-utils';
|
||||
|
||||
import { useDataSource } from '../../hooks/use_data_source';
|
||||
import { useAiopsAppContext } from '../../hooks/use_aiops_app_context';
|
||||
import { useData } from '../../hooks/use_data';
|
||||
import { useSearch } from '../../hooks/use_search';
|
||||
import {
|
||||
getDefaultAiOpsListState,
|
||||
type AiOpsPageUrlState,
|
||||
} from '../../application/utils/url_state';
|
||||
getDefaultLogRateAnalysisAppState,
|
||||
appStateToWindowParameters,
|
||||
windowParametersToAppState,
|
||||
type LogRateAnalysisPageUrlState,
|
||||
} from '../../application/url_state/log_rate_analysis';
|
||||
import { AIOPS_TELEMETRY_ID } from '../../../common/constants';
|
||||
|
||||
import { SearchPanel } from '../search_panel';
|
||||
|
@ -40,9 +44,9 @@ export const LogRateAnalysisPage: FC<Props> = ({ stickyHistogram }) => {
|
|||
const { currentSelectedSignificantItem, currentSelectedGroup } =
|
||||
useLogRateAnalysisResultsTableRowContext();
|
||||
|
||||
const [aiopsListState, setAiopsListState] = usePageUrlState<AiOpsPageUrlState>(
|
||||
'AIOPS_INDEX_VIEWER',
|
||||
getDefaultAiOpsListState()
|
||||
const [stateFromUrl, setUrlState] = usePageUrlState<LogRateAnalysisPageUrlState>(
|
||||
'logRateAnalysis',
|
||||
getDefaultLogRateAnalysisAppState()
|
||||
);
|
||||
const [globalState, setGlobalState] = useUrlState('_g');
|
||||
|
||||
|
@ -67,20 +71,20 @@ export const LogRateAnalysisPage: FC<Props> = ({ stickyHistogram }) => {
|
|||
setSelectedSavedSearch(null);
|
||||
}
|
||||
|
||||
setAiopsListState({
|
||||
...aiopsListState,
|
||||
setUrlState({
|
||||
...stateFromUrl,
|
||||
searchQuery: searchParams.searchQuery,
|
||||
searchString: searchParams.searchString,
|
||||
searchQueryLanguage: searchParams.queryLanguage,
|
||||
filters: searchParams.filters,
|
||||
});
|
||||
},
|
||||
[selectedSavedSearch, aiopsListState, setAiopsListState]
|
||||
[selectedSavedSearch, stateFromUrl, setUrlState]
|
||||
);
|
||||
|
||||
const { searchQueryLanguage, searchString, searchQuery } = useSearch(
|
||||
{ dataView, savedSearch },
|
||||
aiopsListState
|
||||
stateFromUrl
|
||||
);
|
||||
|
||||
const { timefilter } = useData(
|
||||
|
@ -132,6 +136,14 @@ export const LogRateAnalysisPage: FC<Props> = ({ stickyHistogram }) => {
|
|||
});
|
||||
}, [dataService, searchQueryLanguage, searchString]);
|
||||
|
||||
const onWindowParametersHandler = (wp?: WindowParameters) => {
|
||||
if (!isEqual(wp, stateFromUrl.wp)) {
|
||||
setUrlState({
|
||||
wp: windowParametersToAppState(wp),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<EuiPageBody data-test-subj="aiopsLogRateAnalysisPage" paddingSize="none" panelled={false}>
|
||||
<PageHeader />
|
||||
|
@ -148,11 +160,13 @@ export const LogRateAnalysisPage: FC<Props> = ({ stickyHistogram }) => {
|
|||
/>
|
||||
</EuiFlexItem>
|
||||
<LogRateAnalysisContent
|
||||
initialAnalysisStart={appStateToWindowParameters(stateFromUrl.wp)}
|
||||
dataView={dataView}
|
||||
setGlobalState={setGlobalState}
|
||||
esSearchQuery={searchQuery}
|
||||
stickyHistogram={stickyHistogram}
|
||||
embeddingOrigin={AIOPS_TELEMETRY_ID.AIOPS_DEFAULT_SOURCE}
|
||||
esSearchQuery={searchQuery}
|
||||
onWindowParametersChange={onWindowParametersHandler}
|
||||
setGlobalState={setGlobalState}
|
||||
stickyHistogram={stickyHistogram}
|
||||
/>
|
||||
</EuiFlexGroup>
|
||||
</EuiPageSection>
|
||||
|
|
|
@ -11,7 +11,7 @@ import type { DataView } from '@kbn/data-views-plugin/public';
|
|||
import type { SavedSearch } from '@kbn/saved-search-plugin/public';
|
||||
|
||||
import { getEsQueryFromSavedSearch } from '../application/utils/search_utils';
|
||||
import type { AiOpsIndexBasedAppState } from '../application/utils/url_state';
|
||||
import type { AiOpsIndexBasedAppState } from '../application/url_state/common';
|
||||
import { useAiopsAppContext } from './use_aiops_app_context';
|
||||
|
||||
export const useSearch = (
|
||||
|
|
|
@ -15,6 +15,7 @@ import { logRateAnalysisTestData } from './log_rate_analysis_test_data';
|
|||
|
||||
export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
||||
const PageObjects = getPageObjects(['common', 'console', 'header', 'home', 'security']);
|
||||
const browser = getService('browser');
|
||||
const elasticChart = getService('elasticChart');
|
||||
const aiops = getService('aiops');
|
||||
|
||||
|
@ -147,6 +148,12 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
|||
await aiops.logRateAnalysisPage.clickRerunAnalysisButton(true);
|
||||
}
|
||||
|
||||
// Wait for the analysis to finish
|
||||
await aiops.logRateAnalysisPage.assertAnalysisComplete(testData.analysisType);
|
||||
|
||||
// At this stage the baseline and deviation brush position should be stored in
|
||||
// the url state and a full browser refresh should restore the analysis.
|
||||
await browser.refresh();
|
||||
await aiops.logRateAnalysisPage.assertAnalysisComplete(testData.analysisType);
|
||||
|
||||
// The group switch should be disabled by default
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue