mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[ML] Log rate spike: support saved search (#136765)
* add support for saved search * move analysis table and progress to separate component * fix types * ensure filters from saved search show up correctly
This commit is contained in:
parent
d5fdeefb1c
commit
c3ac0a163a
6 changed files with 140 additions and 61 deletions
|
@ -182,10 +182,9 @@ export function getEsQueryFromSavedSearch({
|
|||
};
|
||||
}
|
||||
|
||||
// TODO: support saved search
|
||||
// If saved search is an json object with the original query and filter
|
||||
// retrieve the parsed query and filter
|
||||
const savedSearchData = undefined; // getQueryFromSavedSearchObject(savedSearch);
|
||||
const savedSearchData = getQueryFromSavedSearchObject(savedSearch);
|
||||
|
||||
// If no saved search available, use user's query and filters
|
||||
if (!savedSearchData && userQuery) {
|
||||
|
|
|
@ -18,23 +18,20 @@ import {
|
|||
} from '@elastic/eui';
|
||||
|
||||
import type { DataView } from '@kbn/data-views-plugin/public';
|
||||
import { ProgressControls } from '@kbn/aiops-components';
|
||||
import { useFetchStream } from '@kbn/aiops-utils';
|
||||
import type { WindowParameters } from '@kbn/aiops-utils';
|
||||
import { Filter, Query } from '@kbn/es-query';
|
||||
import { SavedSearch } from '@kbn/discover-plugin/public';
|
||||
|
||||
import { useAiOpsKibana } from '../../kibana_context';
|
||||
import { initialState, streamReducer } from '../../../common/api/stream_reducer';
|
||||
import type { ApiExplainLogRateSpikes } from '../../../common/api';
|
||||
import { SearchQueryLanguage } from '../../application/utils/search_utils';
|
||||
import { SearchQueryLanguage, SavedSearchSavedObject } from '../../application/utils/search_utils';
|
||||
import { useUrlState, usePageUrlState, AppStateKey } from '../../hooks/url_state';
|
||||
import { useData } from '../../hooks/use_data';
|
||||
import { SpikeAnalysisTable } from '../spike_analysis_table';
|
||||
import { restorableDefaults } from './explain_log_rate_spikes_wrapper';
|
||||
import { FullTimeRangeSelector } from '../full_time_range_selector';
|
||||
import { DocumentCountContent } from '../document_count_content/document_count_content';
|
||||
import { DatePickerWrapper } from '../date_picker_wrapper';
|
||||
import { SearchPanel } from '../search_panel';
|
||||
import { ExplainLogRateSpikesAnalysis } from './explain_log_rate_spikes_analysis';
|
||||
|
||||
/**
|
||||
* ExplainLogRateSpikes props require a data view.
|
||||
|
@ -42,16 +39,25 @@ import { SearchPanel } from '../search_panel';
|
|||
interface ExplainLogRateSpikesProps {
|
||||
/** The data view to analyze. */
|
||||
dataView: DataView;
|
||||
/** The saved search to analyze. */
|
||||
savedSearch: SavedSearch | SavedSearchSavedObject | null;
|
||||
}
|
||||
|
||||
export const ExplainLogRateSpikes: FC<ExplainLogRateSpikesProps> = ({ dataView }) => {
|
||||
export const ExplainLogRateSpikes: FC<ExplainLogRateSpikesProps> = ({ dataView, savedSearch }) => {
|
||||
const { services } = useAiOpsKibana();
|
||||
const { http, data: dataService } = services;
|
||||
const basePath = http?.basePath.get() ?? '';
|
||||
const { data: dataService } = services;
|
||||
|
||||
const [aiopsListState, setAiopsListState] = usePageUrlState(AppStateKey, restorableDefaults);
|
||||
const [globalState, setGlobalState] = useUrlState('_g');
|
||||
|
||||
const [currentSavedSearch, setCurrentSavedSearch] = useState(savedSearch);
|
||||
|
||||
useEffect(() => {
|
||||
if (savedSearch) {
|
||||
setCurrentSavedSearch(savedSearch);
|
||||
}
|
||||
}, [savedSearch]);
|
||||
|
||||
const setSearchParams = useCallback(
|
||||
(searchParams: {
|
||||
searchQuery: Query['query'];
|
||||
|
@ -59,6 +65,12 @@ export const ExplainLogRateSpikes: FC<ExplainLogRateSpikesProps> = ({ dataView }
|
|||
queryLanguage: SearchQueryLanguage;
|
||||
filters: Filter[];
|
||||
}) => {
|
||||
// When the user loads saved search and then clear or modify the query
|
||||
// we should remove the saved search and replace it with the index pattern id
|
||||
if (currentSavedSearch !== null) {
|
||||
setCurrentSavedSearch(null);
|
||||
}
|
||||
|
||||
setAiopsListState({
|
||||
...aiopsListState,
|
||||
searchQuery: searchParams.searchQuery,
|
||||
|
@ -67,11 +79,11 @@ export const ExplainLogRateSpikes: FC<ExplainLogRateSpikesProps> = ({ dataView }
|
|||
filters: searchParams.filters,
|
||||
});
|
||||
},
|
||||
[aiopsListState, setAiopsListState]
|
||||
[currentSavedSearch, aiopsListState, setAiopsListState]
|
||||
);
|
||||
|
||||
const { docStats, timefilter, earliest, latest, searchQueryLanguage, searchString, searchQuery } =
|
||||
useData(dataView, aiopsListState, setGlobalState);
|
||||
useData({ currentDataView: dataView, currentSavedSearch }, aiopsListState, setGlobalState);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
|
@ -104,36 +116,11 @@ export const ExplainLogRateSpikes: FC<ExplainLogRateSpikesProps> = ({ dataView }
|
|||
useEffect(() => {
|
||||
// Update data query manager if input string is updated
|
||||
dataService?.query.queryString.setQuery({
|
||||
query: searchString,
|
||||
query: searchString ?? '',
|
||||
language: searchQueryLanguage,
|
||||
});
|
||||
}, [dataService, searchQueryLanguage, searchString]);
|
||||
|
||||
const { cancel, start, data, isRunning, error } = useFetchStream<
|
||||
ApiExplainLogRateSpikes,
|
||||
typeof basePath
|
||||
>(
|
||||
`${basePath}/internal/aiops/explain_log_rate_spikes`,
|
||||
{
|
||||
// @ts-ignore unexpected type
|
||||
start: earliest,
|
||||
// @ts-ignore unexpected type
|
||||
end: latest,
|
||||
// TODO Consider an optional Kuery.
|
||||
kuery: '',
|
||||
// TODO Handle data view without time fields.
|
||||
timeFieldName: dataView.timeFieldName ?? '',
|
||||
index: dataView.title,
|
||||
...windowParameters,
|
||||
},
|
||||
{ reducer: streamReducer, initialState }
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
start();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiFlexGroup gutterSize="m">
|
||||
|
@ -176,7 +163,7 @@ export const ExplainLogRateSpikes: FC<ExplainLogRateSpikesProps> = ({ dataView }
|
|||
<EuiFlexItem>
|
||||
<SearchPanel
|
||||
dataView={dataView}
|
||||
searchString={searchString}
|
||||
searchString={searchString ?? ''}
|
||||
searchQuery={searchQuery}
|
||||
searchQueryLanguage={searchQueryLanguage}
|
||||
setSearchParams={setSearchParams}
|
||||
|
@ -194,20 +181,12 @@ export const ExplainLogRateSpikes: FC<ExplainLogRateSpikesProps> = ({ dataView }
|
|||
<EuiSpacer size="m" />
|
||||
{earliest !== undefined && latest !== undefined && windowParameters !== undefined && (
|
||||
<EuiFlexItem>
|
||||
<ProgressControls
|
||||
progress={data.loaded}
|
||||
progressMessage={data.loadingState ?? ''}
|
||||
isRunning={isRunning}
|
||||
onRefresh={start}
|
||||
onCancel={cancel}
|
||||
<ExplainLogRateSpikesAnalysis
|
||||
dataView={dataView}
|
||||
earliest={earliest}
|
||||
latest={latest}
|
||||
windowParameters={windowParameters}
|
||||
/>
|
||||
{data?.changePoints ? (
|
||||
<SpikeAnalysisTable
|
||||
changePointData={data.changePoints}
|
||||
loading={isRunning}
|
||||
error={error}
|
||||
/>
|
||||
) : null}
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* 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 React, { useEffect, FC } from 'react';
|
||||
import type { DataView } from '@kbn/data-views-plugin/public';
|
||||
import { ProgressControls } from '@kbn/aiops-components';
|
||||
import { useFetchStream } from '@kbn/aiops-utils';
|
||||
import type { WindowParameters } from '@kbn/aiops-utils';
|
||||
|
||||
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';
|
||||
|
||||
/**
|
||||
* ExplainLogRateSpikes props require a data view.
|
||||
*/
|
||||
interface ExplainLogRateSpikesAnalysisProps {
|
||||
/** 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 ExplainLogRateSpikesAnalysis: FC<ExplainLogRateSpikesAnalysisProps> = ({
|
||||
dataView,
|
||||
earliest,
|
||||
latest,
|
||||
windowParameters,
|
||||
}) => {
|
||||
const { services } = useAiOpsKibana();
|
||||
const basePath = services.http?.basePath.get() ?? '';
|
||||
|
||||
const { cancel, start, data, isRunning, error } = useFetchStream<
|
||||
ApiExplainLogRateSpikes,
|
||||
typeof basePath
|
||||
>(
|
||||
`${basePath}/internal/aiops/explain_log_rate_spikes`,
|
||||
{
|
||||
start: earliest,
|
||||
end: latest,
|
||||
// TODO Consider an optional Kuery.
|
||||
kuery: '',
|
||||
// TODO Handle data view without time fields.
|
||||
timeFieldName: dataView.timeFieldName ?? '',
|
||||
index: dataView.title,
|
||||
...windowParameters,
|
||||
},
|
||||
{ reducer: streamReducer, initialState }
|
||||
);
|
||||
useEffect(() => {
|
||||
start();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<ProgressControls
|
||||
progress={data.loaded}
|
||||
progressMessage={data.loadingState ?? ''}
|
||||
isRunning={isRunning}
|
||||
onRefresh={start}
|
||||
onCancel={cancel}
|
||||
/>
|
||||
{data?.changePoints ? (
|
||||
<SpikeAnalysisTable changePointData={data.changePoints} loading={isRunning} error={error} />
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -12,12 +12,17 @@ import { parse, stringify } from 'query-string';
|
|||
import { isEqual } from 'lodash';
|
||||
import { encode } from 'rison-node';
|
||||
import { useHistory, useLocation } from 'react-router-dom';
|
||||
import { SavedSearch } from '@kbn/discover-plugin/public';
|
||||
|
||||
import { EuiPageBody } from '@elastic/eui';
|
||||
import { DataView } from '@kbn/data-views-plugin/public';
|
||||
|
||||
import { ExplainLogRateSpikes } from './explain_log_rate_spikes';
|
||||
import { SEARCH_QUERY_LANGUAGE, SearchQueryLanguage } from '../../application/utils/search_utils';
|
||||
import {
|
||||
SEARCH_QUERY_LANGUAGE,
|
||||
SearchQueryLanguage,
|
||||
SavedSearchSavedObject,
|
||||
} from '../../application/utils/search_utils';
|
||||
import { useAiOpsKibana } from '../../kibana_context';
|
||||
import {
|
||||
Accessor,
|
||||
|
@ -32,6 +37,8 @@ import {
|
|||
export interface ExplainLogRateSpikesWrapperProps {
|
||||
/** The data view to analyze. */
|
||||
dataView: DataView;
|
||||
/** The saved search to analyze. */
|
||||
savedSearch: SavedSearch | SavedSearchSavedObject | null;
|
||||
}
|
||||
|
||||
const defaultSearchQuery = {
|
||||
|
@ -57,7 +64,10 @@ export const getDefaultAiOpsListState = (
|
|||
|
||||
export const restorableDefaults = getDefaultAiOpsListState();
|
||||
|
||||
export const ExplainLogRateSpikesWrapper: FC<ExplainLogRateSpikesWrapperProps> = ({ dataView }) => {
|
||||
export const ExplainLogRateSpikesWrapper: FC<ExplainLogRateSpikesWrapperProps> = ({
|
||||
dataView,
|
||||
savedSearch,
|
||||
}) => {
|
||||
const { services } = useAiOpsKibana();
|
||||
const { notifications } = services;
|
||||
const { toasts } = notifications;
|
||||
|
@ -149,7 +159,7 @@ export const ExplainLogRateSpikesWrapper: FC<ExplainLogRateSpikesWrapperProps> =
|
|||
return (
|
||||
<UrlStateContextProvider value={{ searchString: urlSearchString, setUrlState }}>
|
||||
<EuiPageBody data-test-subj="aiopsIndexPage" paddingSize="none" panelled={false}>
|
||||
<ExplainLogRateSpikes dataView={dataView} />
|
||||
<ExplainLogRateSpikes dataView={dataView} savedSearch={savedSearch} />
|
||||
</EuiPageBody>
|
||||
</UrlStateContextProvider>
|
||||
);
|
||||
|
|
|
@ -9,6 +9,7 @@ import { useEffect, useMemo, useState } from 'react'; // useCallback, useRef
|
|||
import type { DataView } from '@kbn/data-views-plugin/public';
|
||||
import { merge } from 'rxjs';
|
||||
import { UI_SETTINGS } from '@kbn/data-plugin/common';
|
||||
import { SavedSearch } from '@kbn/discover-plugin/public';
|
||||
import { useAiOpsKibana } from '../kibana_context';
|
||||
import { useTimefilter } from './use_time_filter';
|
||||
import { aiopsRefresh$ } from '../application/services/timefilter_refresh_service';
|
||||
|
@ -16,16 +17,22 @@ import { TimeBuckets } from '../../common/time_buckets';
|
|||
import { useDocumentCountStats } from './use_document_count_stats';
|
||||
import { Dictionary } from './url_state';
|
||||
import { DocumentStatsSearchStrategyParams } from '../get_document_stats';
|
||||
import { getEsQueryFromSavedSearch } from '../application/utils/search_utils';
|
||||
import {
|
||||
getEsQueryFromSavedSearch,
|
||||
SavedSearchSavedObject,
|
||||
} from '../application/utils/search_utils';
|
||||
import { AiOpsIndexBasedAppState } from '../components/explain_log_rate_spikes/explain_log_rate_spikes_wrapper';
|
||||
|
||||
export const useData = (
|
||||
currentDataView: DataView,
|
||||
{
|
||||
currentDataView,
|
||||
currentSavedSearch,
|
||||
}: { currentDataView: DataView; currentSavedSearch: SavedSearch | SavedSearchSavedObject | null },
|
||||
aiopsListState: AiOpsIndexBasedAppState,
|
||||
onUpdate: (params: Dictionary<unknown>) => void
|
||||
) => {
|
||||
const { services } = useAiOpsKibana();
|
||||
const { uiSettings } = services;
|
||||
const { uiSettings, data } = services;
|
||||
const [lastRefresh, setLastRefresh] = useState(0);
|
||||
const [fieldStatsRequest, setFieldStatsRequest] = useState<
|
||||
DocumentStatsSearchStrategyParams | undefined
|
||||
|
@ -36,7 +43,8 @@ export const useData = (
|
|||
const searchData = getEsQueryFromSavedSearch({
|
||||
dataView: currentDataView,
|
||||
uiSettings,
|
||||
savedSearch: undefined,
|
||||
savedSearch: currentSavedSearch,
|
||||
filterManager: data.query.filterManager,
|
||||
});
|
||||
|
||||
if (searchData === undefined || aiopsListState.searchString !== '') {
|
||||
|
@ -57,6 +65,7 @@ export const useData = (
|
|||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [
|
||||
currentSavedSearch?.id,
|
||||
currentDataView.id,
|
||||
aiopsListState.searchString,
|
||||
aiopsListState.searchQueryLanguage,
|
||||
|
|
|
@ -23,6 +23,7 @@ export const ExplainLogRateSpikesPage: FC = () => {
|
|||
|
||||
const context = useMlContext();
|
||||
const dataView = context.currentDataView;
|
||||
const savedSearch = context.currentSavedSearch;
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -32,7 +33,9 @@ export const ExplainLogRateSpikesPage: FC = () => {
|
|||
defaultMessage="Explain log rate spikes"
|
||||
/>
|
||||
</MlPageHeader>
|
||||
{dataView.timeFieldName && <ExplainLogRateSpikes dataView={dataView} />}
|
||||
{dataView.timeFieldName && (
|
||||
<ExplainLogRateSpikes dataView={dataView} savedSearch={savedSearch} />
|
||||
)}
|
||||
<HelpMenu docLink={docLinks.links.ml.guide} />
|
||||
</>
|
||||
);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue