diff --git a/x-pack/plugins/data_visualizer/public/application/data_drift/data_drift_page.tsx b/x-pack/plugins/data_visualizer/public/application/data_drift/data_drift_page.tsx index 668ca7bf43f6e..86bb350849a33 100644 --- a/x-pack/plugins/data_visualizer/public/application/data_drift/data_drift_page.tsx +++ b/x-pack/plugins/data_visualizer/public/application/data_drift/data_drift_page.tsx @@ -22,7 +22,7 @@ import { } from '@elastic/eui'; import type { WindowParameters } from '@kbn/aiops-log-rate-analysis'; -import type { Filter, Query } from '@kbn/es-query'; +import { buildEsQuery, type Filter, type Query } from '@kbn/es-query'; import { useUrlState, usePageUrlState } from '@kbn/ml-url-state'; import type { DataSeriesDatum } from '@elastic/charts/dist/chart_types/xy_chart/utils/series'; import { useStorage } from '@kbn/ml-local-storage'; @@ -38,6 +38,7 @@ import { css } from '@emotion/react'; import type { SearchQueryLanguage } from '@kbn/ml-query-utils'; import { i18n } from '@kbn/i18n'; import { cloneDeep } from 'lodash'; +import { getEsQueryConfig } from '@kbn/data-plugin/common'; import type { SingleBrushWindowParameters } from './document_count_chart_single_brush/single_brush'; import type { InitialSettings } from './use_data_drift_result'; import { useDataDriftStateManagerContext } from './use_state_manager'; @@ -59,7 +60,11 @@ const dataViewTitleHeader = css({ minWidth: '300px', }); -export const PageHeader: FC = () => { +interface PageHeaderProps { + onRefresh: () => void; + needsUpdate: boolean; +} +export const PageHeader: FC = ({ onRefresh, needsUpdate }) => { const [, setGlobalState] = useUrlState('_g'); const { dataView } = useDataSource(); @@ -121,6 +126,8 @@ export const PageHeader: FC = () => { showRefresh={!hasValidTimeField} width="full" flexGroup={false} + onRefresh={onRefresh} + needsUpdate={needsUpdate} /> , ]} @@ -147,7 +154,7 @@ const isBarBetween = (start: number, end: number, min: number, max: number) => { }; export const DataDriftPage: FC = ({ initialSettings }) => { const { - services: { data: dataService }, + services: { data: dataService, uiSettings }, } = useDataVisualizerKibana(); const { dataView, savedSearch } = useDataSource(); @@ -174,6 +181,10 @@ export const DataDriftPage: FC = ({ initialSettings }) => { const [selectedSavedSearch, setSelectedSavedSearch] = useState(savedSearch); + const [localQueryString, setLocalQueryString] = useState( + dataComparisonListState.searchString + ); + useEffect(() => { if (savedSearch) { setSelectedSavedSearch(savedSearch); @@ -352,9 +363,46 @@ export const DataDriftPage: FC = ({ initialSettings }) => { ? getDataDriftDataLabel(COMPARISON_LABEL, initialSettings?.comparison) : getDataDriftDataLabel(COMPARISON_LABEL); + const onQueryChange = useCallback((query: Query['query'] | undefined) => { + setLocalQueryString(query); + }, []); + + const queryNeedsUpdate = useMemo( + () => localQueryString !== dataComparisonListState.searchString, + [dataComparisonListState.searchString, localQueryString] + ); + + const handleRefresh = useCallback(() => { + if (queryNeedsUpdate) { + const newQuery = buildEsQuery( + dataView, + { + query: localQueryString || '', + language: searchQueryLanguage, + }, + dataService?.query.filterManager.getFilters() ?? [], + uiSettings ? getEsQueryConfig(uiSettings) : undefined + ); + setDataComparisonListState({ + ...dataComparisonListState, + searchString: localQueryString, + searchQuery: newQuery, + }); + } + }, [ + queryNeedsUpdate, + dataView, + localQueryString, + searchQueryLanguage, + dataService?.query.filterManager, + uiSettings, + setDataComparisonListState, + dataComparisonListState, + ]); + return ( - + @@ -365,6 +413,7 @@ export const DataDriftPage: FC = ({ initialSettings }) => { searchQuery={searchQuery} searchQueryLanguage={searchQueryLanguage} setSearchParams={setSearchParams} + onQueryChange={onQueryChange} /> diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/index_data_visualizer_view/index_data_visualizer_view.tsx b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/index_data_visualizer_view/index_data_visualizer_view.tsx index e1aae26be3a9a..9e9aafb8b0e84 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/index_data_visualizer_view/index_data_visualizer_view.tsx +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/index_data_visualizer_view/index_data_visualizer_view.tsx @@ -154,6 +154,10 @@ export const IndexDataVisualizerView: FC = (dataVi dataVisualizerProps.currentSavedSearch ); + const [localQueryString, setLocalQueryString] = useState( + dataVisualizerListState.searchString + ); + const { currentDataView, currentSessionId, getAdditionalLinks } = dataVisualizerProps; const dataViewFields: DataViewField[] = currentDataView.fields; @@ -464,6 +468,43 @@ export const IndexDataVisualizerView: FC = (dataVi }, }); + const queryNeedsUpdate = useMemo( + () => (localQueryString !== dataVisualizerListState.searchString ? true : undefined), + [dataVisualizerListState.searchString, localQueryString] + ); + + const onQueryChange = useCallback((query: Query['query'] | undefined) => { + setLocalQueryString(query); + }, []); + + const handleRefresh = useCallback(() => { + if (queryNeedsUpdate) { + const newQuery = buildEsQuery( + currentDataView, + { + query: localQueryString || '', + language: searchQueryLanguage, + }, + data.query.filterManager.getFilters() ?? [], + uiSettings ? getEsQueryConfig(uiSettings) : undefined + ); + setDataVisualizerListState({ + ...dataVisualizerListState, + searchString: localQueryString, + searchQuery: newQuery, + }); + } + }, [ + queryNeedsUpdate, + currentDataView, + localQueryString, + searchQueryLanguage, + data.query.filterManager, + uiSettings, + setDataVisualizerListState, + dataVisualizerListState, + ]); + return ( = (dataVi isAutoRefreshOnly={!hasValidTimeField} showRefresh={!hasValidTimeField} width="full" + needsUpdate={queryNeedsUpdate} + onRefresh={handleRefresh} /> @@ -537,6 +580,7 @@ export const IndexDataVisualizerView: FC = (dataVi setVisibleFieldNames={setVisibleFieldNames} showEmptyFields={showEmptyFields} onAddFilter={onAddFilter} + onQueryChange={onQueryChange} /> {overallStats?.totalCount !== undefined && ( diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/search_panel/search_bar.tsx b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/search_panel/search_bar.tsx index 1e7efae2e4769..8c939c384b590 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/search_panel/search_bar.tsx +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/search_panel/search_bar.tsx @@ -8,11 +8,12 @@ import type { Filter, Query, TimeRange } from '@kbn/es-query'; import { buildEsQuery } from '@kbn/es-query'; import { i18n } from '@kbn/i18n'; -import React, { useEffect, useState } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import { isDefined } from '@kbn/ml-is-defined'; import type { DataView } from '@kbn/data-views-plugin/common'; import type { SearchQueryLanguage } from '@kbn/ml-query-utils'; import { getEsQueryConfig } from '@kbn/data-plugin/common'; +import { debounce } from 'lodash'; import { useDataVisualizerKibana } from '../../../kibana_context'; export const SearchPanelContent = ({ @@ -21,11 +22,13 @@ export const SearchPanelContent = ({ searchQueryLanguage, dataView, setSearchParams, + onQueryChange, }: { dataView: DataView; searchQuery: Query['query']; searchString: Query['query']; searchQueryLanguage: SearchQueryLanguage; + onQueryChange?: (query: Query['query'] | undefined) => void; setSearchParams({ searchQuery, searchString, @@ -77,6 +80,8 @@ export const SearchPanelContent = ({ uiSettings ? getEsQueryConfig(uiSettings) : undefined ); + // Additional call because the search bar doesn't call onQueryChange when the query is cleared from filters + onQueryChange?.(mergedQuery.query); setSearchParams({ searchQuery: combinedQuery, searchString: mergedQuery.query, @@ -93,6 +98,18 @@ export const SearchPanelContent = ({ } }; + // Debounce the onQueryChange to prevent race condition when filters are updated and both `onQuerySubmit` and `onQueryChange` are called. + const debouncedOnQueryChange = useCallback( + (inputQuery: Query['query'] | undefined) => { + const debouncedFunction = debounce((debouncedQuery: Query['query'] | undefined) => { + onQueryChange?.(debouncedQuery); + }, 100); + + return debouncedFunction(inputQuery); + }, + [onQueryChange] + ); + return ( } + onQueryChange={({ query }) => { + debouncedOnQueryChange?.(query?.query); + }} /> ); }; diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/search_panel/search_panel.tsx b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/search_panel/search_panel.tsx index 7a61119b82c9b..10dc040ef3701 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/search_panel/search_panel.tsx +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/search_panel/search_panel.tsx @@ -47,6 +47,7 @@ interface Props { }): void; showEmptyFields: boolean; onAddFilter?: (field: DataViewField | string, value: string, type: '+' | '-') => void; + onQueryChange?: (query: Query['query'] | undefined) => void; } export const SearchPanel: FC = ({ @@ -62,6 +63,7 @@ export const SearchPanel: FC = ({ visibleFieldNames, setSearchParams, showEmptyFields, + onQueryChange, }) => { const dvSearchPanelControls = css({ marginLeft: '0px !important', @@ -101,6 +103,7 @@ export const SearchPanel: FC = ({ searchString={searchString} searchQuery={searchQuery} searchQueryLanguage={searchQueryLanguage} + onQueryChange={onQueryChange} />