Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ML] Log rate spike: support saved search #136765

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,47 +18,59 @@ 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.
*/
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'];
searchString: Query['query'];
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,
Expand All @@ -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 () => {
Expand Down Expand Up @@ -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">
Expand Down Expand Up @@ -176,7 +163,7 @@ export const ExplainLogRateSpikes: FC<ExplainLogRateSpikesProps> = ({ dataView }
<EuiFlexItem>
<SearchPanel
dataView={dataView}
searchString={searchString}
searchString={searchString ?? ''}
searchQuery={searchQuery}
searchQueryLanguage={searchQueryLanguage}
setSearchParams={setSearchParams}
Expand All @@ -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>
Expand Down
Original file line number Diff line number Diff line change
@@ -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}
</>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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 = {
Expand All @@ -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;
Expand Down Expand Up @@ -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>
);
Expand Down
17 changes: 13 additions & 4 deletions x-pack/plugins/aiops/public/hooks/use_data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,30 @@ 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';
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
Expand All @@ -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 !== '') {
Expand All @@ -57,6 +65,7 @@ export const useData = (
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
currentSavedSearch?.id,
currentDataView.id,
aiopsListState.searchString,
aiopsListState.searchQueryLanguage,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export const ExplainLogRateSpikesPage: FC = () => {

const context = useMlContext();
const dataView = context.currentDataView;
const savedSearch = context.currentSavedSearch;

return (
<>
Expand All @@ -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} />
</>
);
Expand Down