Skip to content

Commit

Permalink
[ML] Log rate spike: support saved search (#136765)
Browse files Browse the repository at this point in the history
* add support for saved search

* move analysis table and progress to separate component

* fix types

* ensure filters from saved search show up correctly
  • Loading branch information
alvarezmelissa87 authored Jul 22, 2022
1 parent d5fdeef commit c3ac0a1
Show file tree
Hide file tree
Showing 6 changed files with 140 additions and 61 deletions.
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

0 comments on commit c3ac0a1

Please sign in to comment.