From 723da946fff58f743ea2d71e85e5f18be8410901 Mon Sep 17 00:00:00 2001 From: Julia Rechkunova Date: Wed, 9 Jul 2025 19:09:51 +0200 Subject: [PATCH 01/19] [Discover] Restore the draft query in the unified search --- .../shared/kbn-restorable-state/kibana.jsonc | 2 +- .../components/top_nav/discover_topnav.tsx | 45 +++++++++++++++++-- .../state_management/redux/actions/tabs.ts | 11 ++++- .../state_management/redux/internal_state.ts | 8 ++++ .../main/state_management/redux/types.ts | 2 + .../shared/unified_search/public/index.ts | 1 + .../shared/unified_search/public/plugin.ts | 2 + .../query_string_input/query_bar_top_row.tsx | 4 ++ .../shared/unified_search/public/types.ts | 3 ++ .../shared/unified_search/restorable_state.ts | 18 ++++++++ 10 files changed, 89 insertions(+), 7 deletions(-) create mode 100644 src/platform/plugins/shared/unified_search/restorable_state.ts diff --git a/src/platform/packages/shared/kbn-restorable-state/kibana.jsonc b/src/platform/packages/shared/kbn-restorable-state/kibana.jsonc index 8d8867786e646..4a9224cc27739 100644 --- a/src/platform/packages/shared/kbn-restorable-state/kibana.jsonc +++ b/src/platform/packages/shared/kbn-restorable-state/kibana.jsonc @@ -1,5 +1,5 @@ { - "type": "shared-browser", + "type": "shared-common", "id": "@kbn/restorable-state", "owner": "@elastic/kibana-data-discovery", "group": "platform", diff --git a/src/platform/plugins/shared/discover/public/application/main/components/top_nav/discover_topnav.tsx b/src/platform/plugins/shared/discover/public/application/main/components/top_nav/discover_topnav.tsx index eeeda7cb01908..3bc5088865fe0 100644 --- a/src/platform/plugins/shared/discover/public/application/main/components/top_nav/discover_topnav.tsx +++ b/src/platform/plugins/shared/discover/public/application/main/components/top_nav/discover_topnav.tsx @@ -9,7 +9,10 @@ import React, { useCallback, useEffect, useMemo, useRef } from 'react'; import { DataViewType } from '@kbn/data-views-plugin/public'; -import type { DataViewPickerProps } from '@kbn/unified-search-plugin/public'; +import type { + DataViewPickerProps, + UnifiedSearchRestorableState, +} from '@kbn/unified-search-plugin/public'; import { DiscoverFlyouts, dismissAllFlyoutsExceptFor } from '@kbn/discover-utils'; import type { EuiHeaderLinksProps } from '@elastic/eui'; import { useSavedSearchInitial } from '../../state_management/discover_state_provider'; @@ -25,6 +28,8 @@ import { ESQLToDataViewTransitionModal } from './esql_dataview_transition'; import { internalStateActions, useCurrentDataView, + useCurrentTabAction, + useCurrentTabSelector, useDataViewsForPicker, useInternalStateDispatch, useInternalStateSelector, @@ -51,7 +56,14 @@ export const DiscoverTopNav = ({ }: DiscoverTopNavProps) => { const dispatch = useInternalStateDispatch(); const services = useDiscoverServices(); - const { dataViewEditor, navigation, dataViewFieldEditor, data, setHeaderActionMenu } = services; + const { + dataViewEditor, + navigation, + dataViewFieldEditor, + data, + setHeaderActionMenu, + unifiedSearch, + } = services; const query = useAppStateSelector((state) => state.query); const { savedDataViews, managedDataViews, adHocDataViews } = useDataViewsForPicker(); const dataView = useCurrentDataView(); @@ -205,12 +217,35 @@ export const DiscoverTopNav = ({ }, []); const searchBarCustomization = useDiscoverCustomization('search_bar'); + const unifiedSearchWithRestorableState = unifiedSearch.ui.withRestorableState; const SearchBar = useMemo( - () => searchBarCustomization?.CustomSearchBar ?? navigation.ui.AggregateQueryTopNavMenu, - [searchBarCustomization?.CustomSearchBar, navigation.ui.AggregateQueryTopNavMenu] + () => + searchBarCustomization?.CustomSearchBar ?? + unifiedSearchWithRestorableState(navigation.ui.AggregateQueryTopNavMenu), + [ + searchBarCustomization?.CustomSearchBar, + unifiedSearchWithRestorableState, + navigation.ui.AggregateQueryTopNavMenu, + ] ); + const searchUiState = useCurrentTabSelector((state) => state.uiState.search); + const setSearchUiState = useCurrentTabAction(internalStateActions.setSearchUiState); + const onInitialStateChange = useCallback( + (newSearchUiState: Partial) => { + console.log('onInitialStateChange', newSearchUiState); + dispatch( + setSearchUiState({ + searchUiState: newSearchUiState, + }) + ); + }, + [dispatch, setSearchUiState] + ); + + console.log('searchUiState', searchUiState); + const shouldHideDefaultDataviewPicker = !!searchBarCustomization?.CustomDataViewPicker || !!searchBarCustomization?.hideDataViewPicker; @@ -248,6 +283,8 @@ export const DiscoverTopNav = ({ ) : undefined } onESQLDocsFlyoutVisibilityChanged={onESQLDocsFlyoutVisibilityChanged} + initialState={searchUiState} + onInitialStateChange={onInitialStateChange} /> {isESQLToDataViewTransitionModalVisible && ( diff --git a/src/platform/plugins/shared/discover/public/application/main/state_management/redux/actions/tabs.ts b/src/platform/plugins/shared/discover/public/application/main/state_management/redux/actions/tabs.ts index ff3f166c099ba..5126892253e06 100644 --- a/src/platform/plugins/shared/discover/public/application/main/state_management/redux/actions/tabs.ts +++ b/src/platform/plugins/shared/discover/public/application/main/state_management/redux/actions/tabs.ts @@ -8,7 +8,7 @@ */ import type { TabbedContentState } from '@kbn/unified-tabs/src/components/tabbed_content/tabbed_content'; -import { cloneDeep, differenceBy, omit, pick } from 'lodash'; +import { cloneDeep, differenceBy, omit, pick, isEqual } from 'lodash'; import type { QueryState } from '@kbn/data-plugin/common'; import type { TabState } from '../types'; import { selectAllTabs, selectRecentlyClosedTabs, selectTab } from '../selectors'; @@ -138,6 +138,13 @@ export const updateTabs: InternalStateThunkActionCreator<[TabbedContentState], P } = nextTab.lastPersistedGlobalState; const appState = nextTabStateContainer.appState.getState(); const { filters: appFilters, query } = appState; + const lastEnteredQuery = nextTab.uiState.search?.query; + const nextQuery = + lastEnteredQuery && !isEqual(lastEnteredQuery, query) ? lastEnteredQuery : query; + + if (!isEqual(nextQuery, query)) { + nextTabStateContainer.appState.update({ query: nextQuery }); + } await urlStateStorage.set(GLOBAL_STATE_URL_KEY, { time: timeRange, @@ -153,7 +160,7 @@ export const updateTabs: InternalStateThunkActionCreator<[TabbedContentState], P services.filterManager.setGlobalFilters(cloneDeep(globalFilters ?? [])); services.filterManager.setAppFilters(cloneDeep(appFilters ?? [])); services.data.query.queryString.setQuery( - query ?? services.data.query.queryString.getDefaultQuery() + nextQuery ?? services.data.query.queryString.getDefaultQuery() ); nextTabStateContainer.actions.initializeAndSync(); diff --git a/src/platform/plugins/shared/discover/public/application/main/state_management/redux/internal_state.ts b/src/platform/plugins/shared/discover/public/application/main/state_management/redux/internal_state.ts index 172a184939dab..84982c71c67b4 100644 --- a/src/platform/plugins/shared/discover/public/application/main/state_management/redux/internal_state.ts +++ b/src/platform/plugins/shared/discover/public/application/main/state_management/redux/internal_state.ts @@ -245,6 +245,14 @@ export const internalStateSlice = createSlice({ withTab(state, action, (tab) => { tab.uiState.layout = action.payload.layoutUiState; }), + + setSearchUiState: ( + state, + action: TabAction<{ searchUiState: Partial }> + ) => + withTab(state, action, (tab) => { + tab.uiState.search = action.payload.searchUiState; + }), }, extraReducers: (builder) => { builder.addCase(loadDataViewList.fulfilled, (state, action) => { diff --git a/src/platform/plugins/shared/discover/public/application/main/state_management/redux/types.ts b/src/platform/plugins/shared/discover/public/application/main/state_management/redux/types.ts index f9b201faa130d..fb9e5c8376ea6 100644 --- a/src/platform/plugins/shared/discover/public/application/main/state_management/redux/types.ts +++ b/src/platform/plugins/shared/discover/public/application/main/state_management/redux/types.ts @@ -13,6 +13,7 @@ import type { DataTableRecord } from '@kbn/discover-utils'; import type { Filter, TimeRange } from '@kbn/es-query'; import type { UnifiedDataTableRestorableState } from '@kbn/unified-data-table'; import type { UnifiedFieldListRestorableState } from '@kbn/unified-field-list'; +import type { UnifiedSearchRestorableState } from '@kbn/unified-search-plugin/public'; import type { UnifiedHistogramVisContext } from '@kbn/unified-histogram'; import type { TabItem } from '@kbn/unified-tabs'; import type { DiscoverAppState } from '../discover_app_state_container'; @@ -80,6 +81,7 @@ export interface TabState extends TabItem { dataGrid?: Partial; fieldList?: Partial; layout?: Partial; + search?: Partial; }; } diff --git a/src/platform/plugins/shared/unified_search/public/index.ts b/src/platform/plugins/shared/unified_search/public/index.ts index c90a01b775883..52087f6d91383 100755 --- a/src/platform/plugins/shared/unified_search/public/index.ts +++ b/src/platform/plugins/shared/unified_search/public/index.ts @@ -25,6 +25,7 @@ export type { UnifiedSearchPublicPluginStart, UnifiedSearchPluginSetup, IUnifiedSearchPluginServices, + UnifiedSearchRestorableState, } from './types'; export type { FilterItemsProps } from './filter_bar'; export type { DataViewPickerProps } from './dataview_picker'; diff --git a/src/platform/plugins/shared/unified_search/public/plugin.ts b/src/platform/plugins/shared/unified_search/public/plugin.ts index b8a23ded72170..9256327930e9f 100755 --- a/src/platform/plugins/shared/unified_search/public/plugin.ts +++ b/src/platform/plugins/shared/unified_search/public/plugin.ts @@ -27,6 +27,7 @@ import type { } from './types'; import { ACTION_GLOBAL_APPLY_FILTER, UPDATE_FILTER_REFERENCES_ACTION } from './actions/constants'; import { FiltersBuilderLazy } from './filters_builder'; +import { withRestorableState } from '../restorable_state'; export class UnifiedSearchPublicPlugin implements Plugin @@ -123,6 +124,7 @@ export class UnifiedSearchPublicPlugin autocomplete: autocompleteStart, }, }), + withRestorableState, }, autocomplete: autocompleteStart, }; diff --git a/src/platform/plugins/shared/unified_search/public/query_string_input/query_bar_top_row.tsx b/src/platform/plugins/shared/unified_search/public/query_string_input/query_bar_top_row.tsx index c88701ea75e98..319949b7c6119 100644 --- a/src/platform/plugins/shared/unified_search/public/query_string_input/query_bar_top_row.tsx +++ b/src/platform/plugins/shared/unified_search/public/query_string_input/query_bar_top_row.tsx @@ -59,6 +59,7 @@ import type { SuggestionsAbstraction, SuggestionsListSize, } from '../typeahead/suggestions_component'; +import { useRestorableRef } from '../../restorable_state'; export const strings = { getNeedsUpdatingLabel: () => @@ -230,6 +231,9 @@ export const QueryBarTopRow = React.memo( function QueryBarTopRow( props: QueryBarTopRowProps ) { + const restorableQueryRef = useRestorableRef('query', props.query); + restorableQueryRef.current = props.query; + const isMobile = useIsWithinBreakpoints(['xs', 's']); const [isXXLarge, setIsXXLarge] = useState(false); const submitButtonStyle: QueryBarTopRowProps['submitButtonStyle'] = diff --git a/src/platform/plugins/shared/unified_search/public/types.ts b/src/platform/plugins/shared/unified_search/public/types.ts index a058b06bbafd2..3b1e7293b3818 100755 --- a/src/platform/plugins/shared/unified_search/public/types.ts +++ b/src/platform/plugins/shared/unified_search/public/types.ts @@ -22,6 +22,8 @@ import { AutocompleteSetup, AutocompleteStart } from './autocomplete'; import type { IndexPatternSelectProps, QueryStringInputProps, StatefulSearchBarProps } from '.'; import type { FiltersBuilderProps } from './filters_builder/filters_builder'; import { StatefulSearchBarDeps } from './search_bar/create_search_bar'; +import { withRestorableState } from '../restorable_state'; +export type { UnifiedSearchRestorableState } from '../restorable_state'; export interface UnifiedSearchSetupDependencies { uiActions: UiActionsSetup; @@ -55,6 +57,7 @@ export interface UnifiedSearchPublicPluginStartUi { AggregateQuerySearchBar: AggQuerySearchBarComp; FiltersBuilderLazy: React.ComponentType; QueryStringInput: React.ComponentType>; + withRestorableState: typeof withRestorableState; } /** diff --git a/src/platform/plugins/shared/unified_search/restorable_state.ts b/src/platform/plugins/shared/unified_search/restorable_state.ts new file mode 100644 index 0000000000000..7b8f188a73ba1 --- /dev/null +++ b/src/platform/plugins/shared/unified_search/restorable_state.ts @@ -0,0 +1,18 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { createRestorableStateProvider } from '@kbn/restorable-state'; +import type { Query, AggregateQuery } from '@kbn/es-query'; + +export interface UnifiedSearchRestorableState { + query: AggregateQuery | Query | undefined; +} + +export const { withRestorableState, useRestorableRef } = + createRestorableStateProvider(); From fa4012e7a89bf30a26994470b466f2e1bcf8760a Mon Sep 17 00:00:00 2001 From: Julia Rechkunova Date: Wed, 9 Jul 2025 19:11:32 +0200 Subject: [PATCH 02/19] [Discover] Remove logs --- .../application/main/components/top_nav/discover_topnav.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/platform/plugins/shared/discover/public/application/main/components/top_nav/discover_topnav.tsx b/src/platform/plugins/shared/discover/public/application/main/components/top_nav/discover_topnav.tsx index 3bc5088865fe0..30e93a2acb99a 100644 --- a/src/platform/plugins/shared/discover/public/application/main/components/top_nav/discover_topnav.tsx +++ b/src/platform/plugins/shared/discover/public/application/main/components/top_nav/discover_topnav.tsx @@ -234,7 +234,7 @@ export const DiscoverTopNav = ({ const setSearchUiState = useCurrentTabAction(internalStateActions.setSearchUiState); const onInitialStateChange = useCallback( (newSearchUiState: Partial) => { - console.log('onInitialStateChange', newSearchUiState); + // console.log('onInitialStateChange', newSearchUiState); dispatch( setSearchUiState({ searchUiState: newSearchUiState, @@ -244,7 +244,7 @@ export const DiscoverTopNav = ({ [dispatch, setSearchUiState] ); - console.log('searchUiState', searchUiState); + // console.log('searchUiState', searchUiState); const shouldHideDefaultDataviewPicker = !!searchBarCustomization?.CustomDataViewPicker || !!searchBarCustomization?.hideDataViewPicker; From 8e1e27c8ba78f4b68ebb621c9156f6d75b4ddea9 Mon Sep 17 00:00:00 2001 From: Julia Rechkunova Date: Thu, 10 Jul 2025 17:44:39 +0200 Subject: [PATCH 03/19] [Discover] Restore query in draft mode --- .../components/top_nav/discover_topnav.tsx | 23 ++++++++++++++++++- .../state_management/redux/actions/tabs.ts | 11 ++------- .../query_string_input/query_bar_top_row.tsx | 7 +++++- .../public/search_bar/create_search_bar.tsx | 1 + .../public/search_bar/search_bar.tsx | 19 ++++++++++++--- .../shared/unified_search/restorable_state.ts | 2 ++ 6 files changed, 49 insertions(+), 14 deletions(-) diff --git a/src/platform/plugins/shared/discover/public/application/main/components/top_nav/discover_topnav.tsx b/src/platform/plugins/shared/discover/public/application/main/components/top_nav/discover_topnav.tsx index 30e93a2acb99a..5d0ddd299b112 100644 --- a/src/platform/plugins/shared/discover/public/application/main/components/top_nav/discover_topnav.tsx +++ b/src/platform/plugins/shared/discover/public/application/main/components/top_nav/discover_topnav.tsx @@ -7,12 +7,13 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import React, { useCallback, useEffect, useMemo, useRef } from 'react'; +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { DataViewType } from '@kbn/data-views-plugin/public'; import type { DataViewPickerProps, UnifiedSearchRestorableState, } from '@kbn/unified-search-plugin/public'; +import { isEqual } from 'lodash'; import { DiscoverFlyouts, dismissAllFlyoutsExceptFor } from '@kbn/discover-utils'; import type { EuiHeaderLinksProps } from '@elastic/eui'; import { useSavedSearchInitial } from '../../state_management/discover_state_provider'; @@ -244,7 +245,26 @@ export const DiscoverTopNav = ({ [dispatch, setSearchUiState] ); + const [draft] = useState(() => { + const draftState: Partial = {}; + + if (searchUiState?.query && !isEqual(searchUiState.query, query)) { + draftState.query = searchUiState.query; + } + + if (searchUiState?.dateRangeFrom) { + draftState.dateRangeFrom = searchUiState.dateRangeFrom; + } + + if (searchUiState?.dateRangeTo) { + draftState.dateRangeTo = searchUiState.dateRangeTo; + } + + return Object.keys(draftState).length > 0 ? draftState : undefined; + }); + // console.log('searchUiState', searchUiState); + // console.log('draft', draft); const shouldHideDefaultDataviewPicker = !!searchBarCustomization?.CustomDataViewPicker || !!searchBarCustomization?.hideDataViewPicker; @@ -259,6 +279,7 @@ export const DiscoverTopNav = ({ onCancel={onCancelClick} isLoading={isLoading} onSavedQueryIdChange={updateSavedQueryId} + draft={draft} query={query} savedQueryId={savedQuery} screenTitle={savedSearch.title} diff --git a/src/platform/plugins/shared/discover/public/application/main/state_management/redux/actions/tabs.ts b/src/platform/plugins/shared/discover/public/application/main/state_management/redux/actions/tabs.ts index 5126892253e06..ff3f166c099ba 100644 --- a/src/platform/plugins/shared/discover/public/application/main/state_management/redux/actions/tabs.ts +++ b/src/platform/plugins/shared/discover/public/application/main/state_management/redux/actions/tabs.ts @@ -8,7 +8,7 @@ */ import type { TabbedContentState } from '@kbn/unified-tabs/src/components/tabbed_content/tabbed_content'; -import { cloneDeep, differenceBy, omit, pick, isEqual } from 'lodash'; +import { cloneDeep, differenceBy, omit, pick } from 'lodash'; import type { QueryState } from '@kbn/data-plugin/common'; import type { TabState } from '../types'; import { selectAllTabs, selectRecentlyClosedTabs, selectTab } from '../selectors'; @@ -138,13 +138,6 @@ export const updateTabs: InternalStateThunkActionCreator<[TabbedContentState], P } = nextTab.lastPersistedGlobalState; const appState = nextTabStateContainer.appState.getState(); const { filters: appFilters, query } = appState; - const lastEnteredQuery = nextTab.uiState.search?.query; - const nextQuery = - lastEnteredQuery && !isEqual(lastEnteredQuery, query) ? lastEnteredQuery : query; - - if (!isEqual(nextQuery, query)) { - nextTabStateContainer.appState.update({ query: nextQuery }); - } await urlStateStorage.set(GLOBAL_STATE_URL_KEY, { time: timeRange, @@ -160,7 +153,7 @@ export const updateTabs: InternalStateThunkActionCreator<[TabbedContentState], P services.filterManager.setGlobalFilters(cloneDeep(globalFilters ?? [])); services.filterManager.setAppFilters(cloneDeep(appFilters ?? [])); services.data.query.queryString.setQuery( - nextQuery ?? services.data.query.queryString.getDefaultQuery() + query ?? services.data.query.queryString.getDefaultQuery() ); nextTabStateContainer.actions.initializeAndSync(); diff --git a/src/platform/plugins/shared/unified_search/public/query_string_input/query_bar_top_row.tsx b/src/platform/plugins/shared/unified_search/public/query_string_input/query_bar_top_row.tsx index 319949b7c6119..faa24628798a1 100644 --- a/src/platform/plugins/shared/unified_search/public/query_string_input/query_bar_top_row.tsx +++ b/src/platform/plugins/shared/unified_search/public/query_string_input/query_bar_top_row.tsx @@ -231,8 +231,13 @@ export const QueryBarTopRow = React.memo( function QueryBarTopRow( props: QueryBarTopRowProps ) { + const isDirty = props.isDirty; const restorableQueryRef = useRestorableRef('query', props.query); - restorableQueryRef.current = props.query; + restorableQueryRef.current = isDirty ? props.query : undefined; + const restorableDateRangeFromRef = useRestorableRef('dateRangeFrom', props.dateRangeFrom); + restorableDateRangeFromRef.current = isDirty ? props.dateRangeFrom : undefined; + const restorableDateRangeToRef = useRestorableRef('dateRangeTo', props.dateRangeTo); + restorableDateRangeToRef.current = isDirty ? props.dateRangeTo : undefined; const isMobile = useIsWithinBreakpoints(['xs', 's']); const [isXXLarge, setIsXXLarge] = useState(false); diff --git a/src/platform/plugins/shared/unified_search/public/search_bar/create_search_bar.tsx b/src/platform/plugins/shared/unified_search/public/search_bar/create_search_bar.tsx index 4f78697438dbf..76ffc578d9812 100644 --- a/src/platform/plugins/shared/unified_search/public/search_bar/create_search_bar.tsx +++ b/src/platform/plugins/shared/unified_search/public/search_bar/create_search_bar.tsx @@ -245,6 +245,7 @@ export function createSearchBar({ onCancel={props.onCancel} filters={filters} query={query} + draft={props.draft} onFiltersUpdated={defaultFiltersUpdated(data.query, props.onFiltersUpdated)} onRefreshChange={ !props.isAutoRefreshDisabled diff --git a/src/platform/plugins/shared/unified_search/public/search_bar/search_bar.tsx b/src/platform/plugins/shared/unified_search/public/search_bar/search_bar.tsx index 0e63835151a68..9ed1667f34870 100644 --- a/src/platform/plugins/shared/unified_search/public/search_bar/search_bar.tsx +++ b/src/platform/plugins/shared/unified_search/public/search_bar/search_bar.tsx @@ -86,6 +86,12 @@ export interface SearchBarOwnProps { dateRangeTo?: string; // Query bar - should be in SearchBarInjectedDeps query?: QT | Query; + // To initialize with a predefined query which has not been submitted yet (in dirty state) + draft?: { + query?: QT | Query; + dateRangeFrom?: string; + dateRangeTo?: string; + }; // Show when user has privileges to save. See `canShowSavedQuery(...)` lib. showSaveQuery?: boolean; // Show the controls to save and load saved queries @@ -263,9 +269,13 @@ export class SearchBarUI ex openQueryBarMenu: false, showSavedQueryPopover: false, currentProps: this.props, - query: this.props.query ? { ...this.props.query } : undefined, - dateRangeFrom: get(this.props, 'dateRangeFrom', 'now-15m'), - dateRangeTo: get(this.props, 'dateRangeTo', 'now'), + query: this.props.draft?.query + ? { ...this.props.draft.query } + : this.props.query + ? { ...this.props.query } + : undefined, + dateRangeFrom: this.props.draft?.dateRangeFrom || get(this.props, 'dateRangeFrom', 'now-15m'), + dateRangeTo: this.props.draft?.dateRangeTo || get(this.props, 'dateRangeTo', 'now'), } as SearchBarState; public isDirty = () => { @@ -493,6 +503,9 @@ export class SearchBarUI ex } public render() { + // console.log('this.state.query', this.state.query); + // console.log('this.props.query', this.props.query); + // console.log('this.state.currentProps.query', this.state.currentProps?.query); const { theme, query } = this.props; const isESQLQuery = isOfAggregateQueryType(query); const isScreenshotMode = this.props.isScreenshotMode === true; diff --git a/src/platform/plugins/shared/unified_search/restorable_state.ts b/src/platform/plugins/shared/unified_search/restorable_state.ts index 7b8f188a73ba1..16a53751d5859 100644 --- a/src/platform/plugins/shared/unified_search/restorable_state.ts +++ b/src/platform/plugins/shared/unified_search/restorable_state.ts @@ -12,6 +12,8 @@ import type { Query, AggregateQuery } from '@kbn/es-query'; export interface UnifiedSearchRestorableState { query: AggregateQuery | Query | undefined; + dateRangeFrom: string | undefined; + dateRangeTo: string | undefined; } export const { withRestorableState, useRestorableRef } = From 6e83d4e336052d060efe84d55c0cc4a543d559d9 Mon Sep 17 00:00:00 2001 From: Julia Rechkunova Date: Thu, 10 Jul 2025 17:54:13 +0200 Subject: [PATCH 04/19] [Discover] Fix lint issues --- .../plugins/shared/unified_search/public/mocks/mocks.ts | 1 + src/platform/plugins/shared/unified_search/public/plugin.ts | 2 +- .../public/query_string_input/query_bar_top_row.tsx | 2 +- .../shared/unified_search/{ => public}/restorable_state.ts | 0 src/platform/plugins/shared/unified_search/public/types.ts | 4 ++-- src/platform/plugins/shared/unified_search/tsconfig.json | 1 + 6 files changed, 6 insertions(+), 4 deletions(-) rename src/platform/plugins/shared/unified_search/{ => public}/restorable_state.ts (100%) diff --git a/src/platform/plugins/shared/unified_search/public/mocks/mocks.ts b/src/platform/plugins/shared/unified_search/public/mocks/mocks.ts index 2fde687a327a9..c03f57c0117ff 100644 --- a/src/platform/plugins/shared/unified_search/public/mocks/mocks.ts +++ b/src/platform/plugins/shared/unified_search/public/mocks/mocks.ts @@ -40,6 +40,7 @@ const createStartContract = (): Start => { AggregateQuerySearchBar: jest.fn().mockReturnValue(null), FiltersBuilderLazy: jest.fn(), QueryStringInput: jest.fn().mockReturnValue('QueryStringInput'), + withRestorableState: jest.fn(), }, }; }; diff --git a/src/platform/plugins/shared/unified_search/public/plugin.ts b/src/platform/plugins/shared/unified_search/public/plugin.ts index 9256327930e9f..654dda4f8e4dd 100755 --- a/src/platform/plugins/shared/unified_search/public/plugin.ts +++ b/src/platform/plugins/shared/unified_search/public/plugin.ts @@ -27,7 +27,7 @@ import type { } from './types'; import { ACTION_GLOBAL_APPLY_FILTER, UPDATE_FILTER_REFERENCES_ACTION } from './actions/constants'; import { FiltersBuilderLazy } from './filters_builder'; -import { withRestorableState } from '../restorable_state'; +import { withRestorableState } from './restorable_state'; export class UnifiedSearchPublicPlugin implements Plugin diff --git a/src/platform/plugins/shared/unified_search/public/query_string_input/query_bar_top_row.tsx b/src/platform/plugins/shared/unified_search/public/query_string_input/query_bar_top_row.tsx index faa24628798a1..eaf3ce377076f 100644 --- a/src/platform/plugins/shared/unified_search/public/query_string_input/query_bar_top_row.tsx +++ b/src/platform/plugins/shared/unified_search/public/query_string_input/query_bar_top_row.tsx @@ -59,7 +59,7 @@ import type { SuggestionsAbstraction, SuggestionsListSize, } from '../typeahead/suggestions_component'; -import { useRestorableRef } from '../../restorable_state'; +import { useRestorableRef } from '../restorable_state'; export const strings = { getNeedsUpdatingLabel: () => diff --git a/src/platform/plugins/shared/unified_search/restorable_state.ts b/src/platform/plugins/shared/unified_search/public/restorable_state.ts similarity index 100% rename from src/platform/plugins/shared/unified_search/restorable_state.ts rename to src/platform/plugins/shared/unified_search/public/restorable_state.ts diff --git a/src/platform/plugins/shared/unified_search/public/types.ts b/src/platform/plugins/shared/unified_search/public/types.ts index 3b1e7293b3818..0789aef266453 100755 --- a/src/platform/plugins/shared/unified_search/public/types.ts +++ b/src/platform/plugins/shared/unified_search/public/types.ts @@ -22,8 +22,8 @@ import { AutocompleteSetup, AutocompleteStart } from './autocomplete'; import type { IndexPatternSelectProps, QueryStringInputProps, StatefulSearchBarProps } from '.'; import type { FiltersBuilderProps } from './filters_builder/filters_builder'; import { StatefulSearchBarDeps } from './search_bar/create_search_bar'; -import { withRestorableState } from '../restorable_state'; -export type { UnifiedSearchRestorableState } from '../restorable_state'; +import { withRestorableState } from './restorable_state'; +export type { UnifiedSearchRestorableState } from './restorable_state'; export interface UnifiedSearchSetupDependencies { uiActions: UiActionsSetup; diff --git a/src/platform/plugins/shared/unified_search/tsconfig.json b/src/platform/plugins/shared/unified_search/tsconfig.json index 1059ae409de4f..066c4aa633e26 100644 --- a/src/platform/plugins/shared/unified_search/tsconfig.json +++ b/src/platform/plugins/shared/unified_search/tsconfig.json @@ -52,6 +52,7 @@ "@kbn/esql-types", "@kbn/aiops-utils", "@kbn/esql-ast", + "@kbn/restorable-state", ], "exclude": [ "target/**/*", From 6bbf4f45c1412421b06e206c115ea72e01f37341 Mon Sep 17 00:00:00 2001 From: Julia Rechkunova Date: Thu, 10 Jul 2025 18:49:08 +0200 Subject: [PATCH 05/19] [Discover] Update limits --- packages/kbn-optimizer/limits.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index cb6698ae7f4a1..94628e75e1be7 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -169,7 +169,7 @@ pageLoadAssetSize: uiActions: 35121 uiActionsEnhanced: 38494 unifiedDocViewer: 25099 - unifiedSearch: 23000 + unifiedSearch: 25200 upgradeAssistant: 81241 uptime: 60000 urlDrilldown: 30063 From ec56f172a84bde33677bd7bc6631e52a46e3a090 Mon Sep 17 00:00:00 2001 From: Julia Rechkunova Date: Fri, 11 Jul 2025 15:35:47 +0200 Subject: [PATCH 06/19] [Discover] Change the approach --- .../components/top_nav/discover_topnav.tsx | 72 +++++++------------ .../state_management/redux/internal_state.ts | 6 +- .../main/state_management/redux/types.ts | 4 +- .../shared/unified_search/public/index.ts | 2 +- .../shared/unified_search/public/plugin.ts | 2 - .../query_string_input/query_bar_top_row.tsx | 33 ++++++--- .../unified_search/public/restorable_state.ts | 20 ------ .../public/search_bar/create_search_bar.tsx | 1 + .../public/search_bar/search_bar.tsx | 15 ++-- .../shared/unified_search/public/types.ts | 9 ++- 10 files changed, 69 insertions(+), 95 deletions(-) delete mode 100644 src/platform/plugins/shared/unified_search/public/restorable_state.ts diff --git a/src/platform/plugins/shared/discover/public/application/main/components/top_nav/discover_topnav.tsx b/src/platform/plugins/shared/discover/public/application/main/components/top_nav/discover_topnav.tsx index 5d0ddd299b112..ad2fbbd7126eb 100644 --- a/src/platform/plugins/shared/discover/public/application/main/components/top_nav/discover_topnav.tsx +++ b/src/platform/plugins/shared/discover/public/application/main/components/top_nav/discover_topnav.tsx @@ -7,12 +7,9 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useRef } from 'react'; import { DataViewType } from '@kbn/data-views-plugin/public'; -import type { - DataViewPickerProps, - UnifiedSearchRestorableState, -} from '@kbn/unified-search-plugin/public'; +import type { DataViewPickerProps, UnifiedSearchDraft } from '@kbn/unified-search-plugin/public'; import { isEqual } from 'lodash'; import { DiscoverFlyouts, dismissAllFlyoutsExceptFor } from '@kbn/discover-utils'; import type { EuiHeaderLinksProps } from '@elastic/eui'; @@ -57,14 +54,7 @@ export const DiscoverTopNav = ({ }: DiscoverTopNavProps) => { const dispatch = useInternalStateDispatch(); const services = useDiscoverServices(); - const { - dataViewEditor, - navigation, - dataViewFieldEditor, - data, - setHeaderActionMenu, - unifiedSearch, - } = services; + const { dataViewEditor, navigation, dataViewFieldEditor, data, setHeaderActionMenu } = services; const query = useAppStateSelector((state) => state.query); const { savedDataViews, managedDataViews, adHocDataViews } = useDataViewsForPicker(); const dataView = useCurrentDataView(); @@ -218,53 +208,46 @@ export const DiscoverTopNav = ({ }, []); const searchBarCustomization = useDiscoverCustomization('search_bar'); - const unifiedSearchWithRestorableState = unifiedSearch.ui.withRestorableState; const SearchBar = useMemo( - () => - searchBarCustomization?.CustomSearchBar ?? - unifiedSearchWithRestorableState(navigation.ui.AggregateQueryTopNavMenu), - [ - searchBarCustomization?.CustomSearchBar, - unifiedSearchWithRestorableState, - navigation.ui.AggregateQueryTopNavMenu, - ] + () => searchBarCustomization?.CustomSearchBar ?? navigation.ui.AggregateQueryTopNavMenu, + [searchBarCustomization?.CustomSearchBar, navigation.ui.AggregateQueryTopNavMenu] ); - const searchUiState = useCurrentTabSelector((state) => state.uiState.search); - const setSearchUiState = useCurrentTabAction(internalStateActions.setSearchUiState); - const onInitialStateChange = useCallback( - (newSearchUiState: Partial) => { - // console.log('onInitialStateChange', newSearchUiState); + const searchDraft = useCurrentTabSelector((state) => state.uiState.searchDraft); + const setSearchDraftUiState = useCurrentTabAction(internalStateActions.setSearchDraftUiState); + const onDraftChange = useCallback( + (draft: UnifiedSearchDraft | null) => { dispatch( - setSearchUiState({ - searchUiState: newSearchUiState, + setSearchDraftUiState({ + searchDraftUiState: { + query: draft?.query ?? undefined, + dateRangeFrom: draft?.dateRangeFrom ?? undefined, + dateRangeTo: draft?.dateRangeTo ?? undefined, + }, }) ); }, - [dispatch, setSearchUiState] + [dispatch, setSearchDraftUiState] ); - const [draft] = useState(() => { - const draftState: Partial = {}; + const draft = useMemo(() => { + const draftState: Partial = {}; - if (searchUiState?.query && !isEqual(searchUiState.query, query)) { - draftState.query = searchUiState.query; + if (searchDraft?.query && !isEqual(searchDraft.query, query)) { + draftState.query = searchDraft.query; } - if (searchUiState?.dateRangeFrom) { - draftState.dateRangeFrom = searchUiState.dateRangeFrom; + if (searchDraft?.dateRangeFrom) { + draftState.dateRangeFrom = searchDraft.dateRangeFrom; } - if (searchUiState?.dateRangeTo) { - draftState.dateRangeTo = searchUiState.dateRangeTo; + if (searchDraft?.dateRangeTo) { + draftState.dateRangeTo = searchDraft.dateRangeTo; } return Object.keys(draftState).length > 0 ? draftState : undefined; - }); - - // console.log('searchUiState', searchUiState); - // console.log('draft', draft); + }, [searchDraft, query]); const shouldHideDefaultDataviewPicker = !!searchBarCustomization?.CustomDataViewPicker || !!searchBarCustomization?.hideDataViewPicker; @@ -279,7 +262,6 @@ export const DiscoverTopNav = ({ onCancel={onCancelClick} isLoading={isLoading} onSavedQueryIdChange={updateSavedQueryId} - draft={draft} query={query} savedQueryId={savedQuery} screenTitle={savedSearch.title} @@ -304,8 +286,8 @@ export const DiscoverTopNav = ({ ) : undefined } onESQLDocsFlyoutVisibilityChanged={onESQLDocsFlyoutVisibilityChanged} - initialState={searchUiState} - onInitialStateChange={onInitialStateChange} + draft={draft} + onDraftChange={onDraftChange} /> {isESQLToDataViewTransitionModalVisible && ( diff --git a/src/platform/plugins/shared/discover/public/application/main/state_management/redux/internal_state.ts b/src/platform/plugins/shared/discover/public/application/main/state_management/redux/internal_state.ts index 84982c71c67b4..5566c8732994f 100644 --- a/src/platform/plugins/shared/discover/public/application/main/state_management/redux/internal_state.ts +++ b/src/platform/plugins/shared/discover/public/application/main/state_management/redux/internal_state.ts @@ -246,12 +246,12 @@ export const internalStateSlice = createSlice({ tab.uiState.layout = action.payload.layoutUiState; }), - setSearchUiState: ( + setSearchDraftUiState: ( state, - action: TabAction<{ searchUiState: Partial }> + action: TabAction<{ searchDraftUiState: Partial }> ) => withTab(state, action, (tab) => { - tab.uiState.search = action.payload.searchUiState; + tab.uiState.searchDraft = action.payload.searchDraftUiState; }), }, extraReducers: (builder) => { diff --git a/src/platform/plugins/shared/discover/public/application/main/state_management/redux/types.ts b/src/platform/plugins/shared/discover/public/application/main/state_management/redux/types.ts index fb9e5c8376ea6..d22e17c1f9e0a 100644 --- a/src/platform/plugins/shared/discover/public/application/main/state_management/redux/types.ts +++ b/src/platform/plugins/shared/discover/public/application/main/state_management/redux/types.ts @@ -13,7 +13,7 @@ import type { DataTableRecord } from '@kbn/discover-utils'; import type { Filter, TimeRange } from '@kbn/es-query'; import type { UnifiedDataTableRestorableState } from '@kbn/unified-data-table'; import type { UnifiedFieldListRestorableState } from '@kbn/unified-field-list'; -import type { UnifiedSearchRestorableState } from '@kbn/unified-search-plugin/public'; +import type { UnifiedSearchDraft } from '@kbn/unified-search-plugin/public'; import type { UnifiedHistogramVisContext } from '@kbn/unified-histogram'; import type { TabItem } from '@kbn/unified-tabs'; import type { DiscoverAppState } from '../discover_app_state_container'; @@ -81,7 +81,7 @@ export interface TabState extends TabItem { dataGrid?: Partial; fieldList?: Partial; layout?: Partial; - search?: Partial; + searchDraft?: Partial; }; } diff --git a/src/platform/plugins/shared/unified_search/public/index.ts b/src/platform/plugins/shared/unified_search/public/index.ts index 52087f6d91383..b2becea0eedfd 100755 --- a/src/platform/plugins/shared/unified_search/public/index.ts +++ b/src/platform/plugins/shared/unified_search/public/index.ts @@ -25,7 +25,7 @@ export type { UnifiedSearchPublicPluginStart, UnifiedSearchPluginSetup, IUnifiedSearchPluginServices, - UnifiedSearchRestorableState, + UnifiedSearchDraft, } from './types'; export type { FilterItemsProps } from './filter_bar'; export type { DataViewPickerProps } from './dataview_picker'; diff --git a/src/platform/plugins/shared/unified_search/public/plugin.ts b/src/platform/plugins/shared/unified_search/public/plugin.ts index 654dda4f8e4dd..b8a23ded72170 100755 --- a/src/platform/plugins/shared/unified_search/public/plugin.ts +++ b/src/platform/plugins/shared/unified_search/public/plugin.ts @@ -27,7 +27,6 @@ import type { } from './types'; import { ACTION_GLOBAL_APPLY_FILTER, UPDATE_FILTER_REFERENCES_ACTION } from './actions/constants'; import { FiltersBuilderLazy } from './filters_builder'; -import { withRestorableState } from './restorable_state'; export class UnifiedSearchPublicPlugin implements Plugin @@ -124,7 +123,6 @@ export class UnifiedSearchPublicPlugin autocomplete: autocompleteStart, }, }), - withRestorableState, }, autocomplete: autocompleteStart, }; diff --git a/src/platform/plugins/shared/unified_search/public/query_string_input/query_bar_top_row.tsx b/src/platform/plugins/shared/unified_search/public/query_string_input/query_bar_top_row.tsx index eaf3ce377076f..67c9d26a4439e 100644 --- a/src/platform/plugins/shared/unified_search/public/query_string_input/query_bar_top_row.tsx +++ b/src/platform/plugins/shared/unified_search/public/query_string_input/query_bar_top_row.tsx @@ -46,7 +46,7 @@ import type { DataView } from '@kbn/data-views-plugin/public'; import type { PersistedLog } from '@kbn/data-plugin/public'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import { UI_SETTINGS } from '@kbn/data-plugin/common'; -import type { IUnifiedSearchPluginServices } from '../types'; +import type { IUnifiedSearchPluginServices, UnifiedSearchDraft } from '../types'; import { QueryStringInput } from './query_string_input'; import { NoDataPopover } from './no_data_popover'; import { shallowEqual } from '../utils/shallow_equal'; @@ -59,7 +59,6 @@ import type { SuggestionsAbstraction, SuggestionsListSize, } from '../typeahead/suggestions_component'; -import { useRestorableRef } from '../restorable_state'; export const strings = { getNeedsUpdatingLabel: () => @@ -147,6 +146,7 @@ export interface QueryBarTopRowProps onRefreshChange?: (options: { isPaused: boolean; refreshInterval: number }) => void; onSubmit: (payload: { dateRange: TimeRange; query?: Query | QT }) => void; onCancel?: () => void; + onDraftChange?: (draft: UnifiedSearchDraft | null) => void; placeholder?: string; prepend?: React.ComponentProps['prepend']; query?: Query | QT; @@ -231,13 +231,28 @@ export const QueryBarTopRow = React.memo( function QueryBarTopRow( props: QueryBarTopRowProps ) { - const isDirty = props.isDirty; - const restorableQueryRef = useRestorableRef('query', props.query); - restorableQueryRef.current = isDirty ? props.query : undefined; - const restorableDateRangeFromRef = useRestorableRef('dateRangeFrom', props.dateRangeFrom); - restorableDateRangeFromRef.current = isDirty ? props.dateRangeFrom : undefined; - const restorableDateRangeToRef = useRestorableRef('dateRangeTo', props.dateRangeTo); - restorableDateRangeToRef.current = isDirty ? props.dateRangeTo : undefined; + const { + onDraftChange, + isDirty: draftIsDirty, + query: draftQuery, + dateRangeFrom: draftDateRangeFrom, + dateRangeTo: draftDateRangeTo, + } = props; + const draft = useMemo( + () => + onDraftChange && draftIsDirty + ? { + query: draftQuery, + dateRangeFrom: draftDateRangeFrom, + dateRangeTo: draftDateRangeTo, + } + : null, + [onDraftChange, draftIsDirty, draftQuery, draftDateRangeFrom, draftDateRangeTo] + ); + + useEffect(() => { + onDraftChange?.(draft); + }, [onDraftChange, draft]); const isMobile = useIsWithinBreakpoints(['xs', 's']); const [isXXLarge, setIsXXLarge] = useState(false); diff --git a/src/platform/plugins/shared/unified_search/public/restorable_state.ts b/src/platform/plugins/shared/unified_search/public/restorable_state.ts deleted file mode 100644 index 16a53751d5859..0000000000000 --- a/src/platform/plugins/shared/unified_search/public/restorable_state.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* - * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import { createRestorableStateProvider } from '@kbn/restorable-state'; -import type { Query, AggregateQuery } from '@kbn/es-query'; - -export interface UnifiedSearchRestorableState { - query: AggregateQuery | Query | undefined; - dateRangeFrom: string | undefined; - dateRangeTo: string | undefined; -} - -export const { withRestorableState, useRestorableRef } = - createRestorableStateProvider(); diff --git a/src/platform/plugins/shared/unified_search/public/search_bar/create_search_bar.tsx b/src/platform/plugins/shared/unified_search/public/search_bar/create_search_bar.tsx index 76ffc578d9812..4828df89f8f72 100644 --- a/src/platform/plugins/shared/unified_search/public/search_bar/create_search_bar.tsx +++ b/src/platform/plugins/shared/unified_search/public/search_bar/create_search_bar.tsx @@ -246,6 +246,7 @@ export function createSearchBar({ filters={filters} query={query} draft={props.draft} + onDraftChange={props.onDraftChange} onFiltersUpdated={defaultFiltersUpdated(data.query, props.onFiltersUpdated)} onRefreshChange={ !props.isAutoRefreshDisabled diff --git a/src/platform/plugins/shared/unified_search/public/search_bar/search_bar.tsx b/src/platform/plugins/shared/unified_search/public/search_bar/search_bar.tsx index 9ed1667f34870..bcc2c86734f0b 100644 --- a/src/platform/plugins/shared/unified_search/public/search_bar/search_bar.tsx +++ b/src/platform/plugins/shared/unified_search/public/search_bar/search_bar.tsx @@ -36,7 +36,7 @@ import { DataView } from '@kbn/data-views-plugin/public'; import { i18n } from '@kbn/i18n'; import { AdditionalQueryBarMenuItems } from '../query_string_input/query_bar_menu_panels'; -import type { IUnifiedSearchPluginServices } from '../types'; +import type { IUnifiedSearchPluginServices, UnifiedSearchDraft } from '../types'; import { SavedQueryMeta, SaveQueryForm } from '../saved_query_form'; import { SavedQueryManagementList } from '../saved_query_management'; import { QueryBarMenu, QueryBarMenuProps } from '../query_string_input/query_bar_menu'; @@ -86,12 +86,6 @@ export interface SearchBarOwnProps { dateRangeTo?: string; // Query bar - should be in SearchBarInjectedDeps query?: QT | Query; - // To initialize with a predefined query which has not been submitted yet (in dirty state) - draft?: { - query?: QT | Query; - dateRangeFrom?: string; - dateRangeTo?: string; - }; // Show when user has privileges to save. See `canShowSavedQuery(...)` lib. showSaveQuery?: boolean; // Show the controls to save and load saved queries @@ -102,6 +96,9 @@ export interface SearchBarOwnProps { payload: { dateRange: TimeRange; query?: QT | Query }, isUpdate?: boolean ) => void; + // To initialize with a predefined query which has not been submitted yet (in dirty state) + draft?: UnifiedSearchDraft; + onDraftChange?: QueryBarTopRowProps['onDraftChange']; // User has saved the current state as a saved query onSaved?: (savedQuery: SavedQuery) => void; // User has modified the saved query, your app should persist the update @@ -503,9 +500,6 @@ export class SearchBarUI ex } public render() { - // console.log('this.state.query', this.state.query); - // console.log('this.props.query', this.props.query); - // console.log('this.state.currentProps.query', this.state.currentProps?.query); const { theme, query } = this.props; const isESQLQuery = isOfAggregateQueryType(query); const isScreenshotMode = this.props.isScreenshotMode === true; @@ -649,6 +643,7 @@ export class SearchBarUI ex onRefreshChange={this.props.onRefreshChange} onCancel={this.props.onCancel} onChange={this.onQueryBarChange} + onDraftChange={this.props.onDraftChange} isDirty={this.isDirty()} customSubmitButton={ this.props.customSubmitButton ? this.props.customSubmitButton : undefined diff --git a/src/platform/plugins/shared/unified_search/public/types.ts b/src/platform/plugins/shared/unified_search/public/types.ts index 0789aef266453..f53c943ef3ddf 100755 --- a/src/platform/plugins/shared/unified_search/public/types.ts +++ b/src/platform/plugins/shared/unified_search/public/types.ts @@ -22,8 +22,12 @@ import { AutocompleteSetup, AutocompleteStart } from './autocomplete'; import type { IndexPatternSelectProps, QueryStringInputProps, StatefulSearchBarProps } from '.'; import type { FiltersBuilderProps } from './filters_builder/filters_builder'; import { StatefulSearchBarDeps } from './search_bar/create_search_bar'; -import { withRestorableState } from './restorable_state'; -export type { UnifiedSearchRestorableState } from './restorable_state'; + +export interface UnifiedSearchDraft { + query?: AggregateQuery | Query; + dateRangeFrom?: string; + dateRangeTo?: string; +} export interface UnifiedSearchSetupDependencies { uiActions: UiActionsSetup; @@ -57,7 +61,6 @@ export interface UnifiedSearchPublicPluginStartUi { AggregateQuerySearchBar: AggQuerySearchBarComp; FiltersBuilderLazy: React.ComponentType; QueryStringInput: React.ComponentType>; - withRestorableState: typeof withRestorableState; } /** From 711d4e0fde7d0196ab12deb88f95402e0bc6adea Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Fri, 11 Jul 2025 13:47:35 +0000 Subject: [PATCH 07/19] [CI] Auto-commit changed files from 'node scripts/yarn_deduplicate' --- src/platform/plugins/shared/unified_search/tsconfig.json | 1 - 1 file changed, 1 deletion(-) diff --git a/src/platform/plugins/shared/unified_search/tsconfig.json b/src/platform/plugins/shared/unified_search/tsconfig.json index 066c4aa633e26..1059ae409de4f 100644 --- a/src/platform/plugins/shared/unified_search/tsconfig.json +++ b/src/platform/plugins/shared/unified_search/tsconfig.json @@ -52,7 +52,6 @@ "@kbn/esql-types", "@kbn/aiops-utils", "@kbn/esql-ast", - "@kbn/restorable-state", ], "exclude": [ "target/**/*", From 4725d58055b0a7038938371c346e93c3e602e0d8 Mon Sep 17 00:00:00 2001 From: Julia Rechkunova Date: Fri, 11 Jul 2025 16:21:17 +0200 Subject: [PATCH 08/19] [Discover] Clean up --- src/platform/plugins/shared/unified_search/public/mocks/mocks.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/platform/plugins/shared/unified_search/public/mocks/mocks.ts b/src/platform/plugins/shared/unified_search/public/mocks/mocks.ts index c03f57c0117ff..2fde687a327a9 100644 --- a/src/platform/plugins/shared/unified_search/public/mocks/mocks.ts +++ b/src/platform/plugins/shared/unified_search/public/mocks/mocks.ts @@ -40,7 +40,6 @@ const createStartContract = (): Start => { AggregateQuerySearchBar: jest.fn().mockReturnValue(null), FiltersBuilderLazy: jest.fn(), QueryStringInput: jest.fn().mockReturnValue('QueryStringInput'), - withRestorableState: jest.fn(), }, }; }; From 06744f2b8ea93d2f2996cbc036184f6a9a45e316 Mon Sep 17 00:00:00 2001 From: Julia Rechkunova Date: Mon, 14 Jul 2025 15:40:59 +0200 Subject: [PATCH 09/19] [Discover] Revert previous changes --- src/platform/packages/shared/kbn-restorable-state/kibana.jsonc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/packages/shared/kbn-restorable-state/kibana.jsonc b/src/platform/packages/shared/kbn-restorable-state/kibana.jsonc index 4a9224cc27739..8d8867786e646 100644 --- a/src/platform/packages/shared/kbn-restorable-state/kibana.jsonc +++ b/src/platform/packages/shared/kbn-restorable-state/kibana.jsonc @@ -1,5 +1,5 @@ { - "type": "shared-common", + "type": "shared-browser", "id": "@kbn/restorable-state", "owner": "@elastic/kibana-data-discovery", "group": "platform", From b02c419e85743c6138357d3b0ca9ef2e218aef31 Mon Sep 17 00:00:00 2001 From: Julia Rechkunova Date: Tue, 15 Jul 2025 12:09:58 +0200 Subject: [PATCH 10/19] [Discover] Add a safeguard --- .../components/top_nav/discover_topnav.tsx | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/platform/plugins/shared/discover/public/application/main/components/top_nav/discover_topnav.tsx b/src/platform/plugins/shared/discover/public/application/main/components/top_nav/discover_topnav.tsx index ad2fbbd7126eb..f1344c8e0d811 100644 --- a/src/platform/plugins/shared/discover/public/application/main/components/top_nav/discover_topnav.tsx +++ b/src/platform/plugins/shared/discover/public/application/main/components/top_nav/discover_topnav.tsx @@ -13,6 +13,7 @@ import type { DataViewPickerProps, UnifiedSearchDraft } from '@kbn/unified-searc import { isEqual } from 'lodash'; import { DiscoverFlyouts, dismissAllFlyoutsExceptFor } from '@kbn/discover-utils'; import type { EuiHeaderLinksProps } from '@elastic/eui'; +import { isOfAggregateQueryType } from '@kbn/es-query'; import { useSavedSearchInitial } from '../../state_management/discover_state_provider'; import { ESQL_TRANSITION_MODAL_KEY } from '../../../../../common/constants'; import { useDiscoverServices } from '../../../../hooks/use_discover_services'; @@ -214,7 +215,7 @@ export const DiscoverTopNav = ({ [searchBarCustomization?.CustomSearchBar, navigation.ui.AggregateQueryTopNavMenu] ); - const searchDraft = useCurrentTabSelector((state) => state.uiState.searchDraft); + const searchDraftUiState = useCurrentTabSelector((state) => state.uiState.searchDraft); const setSearchDraftUiState = useCurrentTabAction(internalStateActions.setSearchDraftUiState); const onDraftChange = useCallback( (draft: UnifiedSearchDraft | null) => { @@ -234,20 +235,24 @@ export const DiscoverTopNav = ({ const draft = useMemo(() => { const draftState: Partial = {}; - if (searchDraft?.query && !isEqual(searchDraft.query, query)) { - draftState.query = searchDraft.query; + if (searchDraftUiState?.query && !isEqual(searchDraftUiState.query, query)) { + if (isOfAggregateQueryType(searchDraftUiState.query) !== isOfAggregateQueryType(query)) { + return undefined; // don't use the draft when query type is different + } + + draftState.query = searchDraftUiState.query; } - if (searchDraft?.dateRangeFrom) { - draftState.dateRangeFrom = searchDraft.dateRangeFrom; + if (searchDraftUiState?.dateRangeFrom) { + draftState.dateRangeFrom = searchDraftUiState.dateRangeFrom; } - if (searchDraft?.dateRangeTo) { - draftState.dateRangeTo = searchDraft.dateRangeTo; + if (searchDraftUiState?.dateRangeTo) { + draftState.dateRangeTo = searchDraftUiState.dateRangeTo; } return Object.keys(draftState).length > 0 ? draftState : undefined; - }, [searchDraft, query]); + }, [searchDraftUiState, query]); const shouldHideDefaultDataviewPicker = !!searchBarCustomization?.CustomDataViewPicker || !!searchBarCustomization?.hideDataViewPicker; From 0c1778cf41552a18492a331c4a71c06ff5d5722c Mon Sep 17 00:00:00 2001 From: Julia Rechkunova Date: Tue, 15 Jul 2025 15:11:05 +0200 Subject: [PATCH 11/19] [Discover] Add tests --- .../apps/discover/tabs/_restorable_state.ts | 58 +++++++++++++++++++ .../functional/page_objects/unified_tabs.ts | 6 ++ 2 files changed, 64 insertions(+) diff --git a/src/platform/test/functional/apps/discover/tabs/_restorable_state.ts b/src/platform/test/functional/apps/discover/tabs/_restorable_state.ts index a4495cefd418c..9303bdf471473 100644 --- a/src/platform/test/functional/apps/discover/tabs/_restorable_state.ts +++ b/src/platform/test/functional/apps/discover/tabs/_restorable_state.ts @@ -22,6 +22,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const retry = getService('retry'); const testSubjects = getService('testSubjects'); const dataGrid = getService('dataGrid'); + const queryBar = getService('queryBar'); describe('tabs restorable state', function () { describe('sidebar', function () { @@ -214,5 +215,62 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await expectState(true, 'By line'); }); }); + + describe('search bar', function () { + it('should restore the search bar state', async () => { + const expectState = async (query: string, isDirty: boolean) => { + await retry.try(async () => { + expect(await queryBar.getQueryString()).to.be(query); + }); + expect(await testSubjects.getAttribute('querySubmitButton', 'aria-label')).to.be( + isDirty ? 'Needs updating' : 'Refresh query' + ); + }; + + const draftQuery0 = 'jpg'; + await expectState('', false); + await queryBar.setQuery(draftQuery0); + await expectState(draftQuery0, true); + + await unifiedTabs.createNewTab(); + await discover.waitUntilTabIsLoaded(); + await expectState('', false); + + const draftQuery2 = 'png'; + await unifiedTabs.createNewTab(); + await discover.waitUntilTabIsLoaded(); + await expectState('', false); + await queryBar.setQuery(draftQuery2); + await expectState(draftQuery2, true); + + await unifiedTabs.selectTab(0); + await discover.waitUntilTabIsLoaded(); + await expectState(draftQuery0, true); + expect(await discover.getHitCount()).to.be('14,004'); + await queryBar.clickQuerySubmitButton(); + await discover.waitUntilTabIsLoaded(); + await expectState(draftQuery0, false); + expect(await discover.getHitCount()).to.be('11,829'); + + await unifiedTabs.selectTab(1); + await discover.waitUntilTabIsLoaded(); + await expectState('', false); + expect(await discover.getHitCount()).to.be('14,004'); + + await unifiedTabs.selectTab(2); + await discover.waitUntilTabIsLoaded(); + await expectState(draftQuery2, true); + expect(await discover.getHitCount()).to.be('14,004'); + await queryBar.clickQuerySubmitButton(); + await discover.waitUntilTabIsLoaded(); + await expectState(draftQuery2, false); + expect(await discover.getHitCount()).to.be('1,373'); + + await unifiedTabs.selectTab(0); + await discover.waitUntilTabIsLoaded(); + await expectState(draftQuery0, false); + expect(await discover.getHitCount()).to.be('11,829'); + }); + }); }); } diff --git a/src/platform/test/functional/page_objects/unified_tabs.ts b/src/platform/test/functional/page_objects/unified_tabs.ts index d54cf3b4bb136..23fadb270881a 100644 --- a/src/platform/test/functional/page_objects/unified_tabs.ts +++ b/src/platform/test/functional/page_objects/unified_tabs.ts @@ -59,6 +59,10 @@ export class UnifiedTabsPageObject extends FtrService { return numberOfTabs.length; } + public async hideTabPreview() { + await this.testSubjects.moveMouseTo('breadcrumbs'); + } + public async createNewTab() { const numberOfTabs = await this.getNumberOfTabs(); await this.testSubjects.click('unifiedTabs_tabsBar_newTabBtn'); @@ -69,6 +73,7 @@ export class UnifiedTabsPageObject extends FtrService { (await this.getSelectedTab())?.index === newNumberOfTabs - 1 ); }); + await this.hideTabPreview(); } public async selectTab(index: number) { @@ -80,6 +85,7 @@ export class UnifiedTabsPageObject extends FtrService { await this.retry.waitFor('the selected tab to change', async () => { return (await this.getSelectedTab())?.index === index; }); + await this.hideTabPreview(); } public async closeTab(index: number) { From 654ad4ea5589362fa26b53658f3ca1c7c8ca37d3 Mon Sep 17 00:00:00 2001 From: Julia Rechkunova Date: Tue, 15 Jul 2025 16:18:34 +0200 Subject: [PATCH 12/19] [Discover] Add tests for ES|QL mode --- .../apps/discover/tabs/_restorable_state.ts | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/src/platform/test/functional/apps/discover/tabs/_restorable_state.ts b/src/platform/test/functional/apps/discover/tabs/_restorable_state.ts index 9303bdf471473..e6f7dc575632d 100644 --- a/src/platform/test/functional/apps/discover/tabs/_restorable_state.ts +++ b/src/platform/test/functional/apps/discover/tabs/_restorable_state.ts @@ -23,6 +23,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const testSubjects = getService('testSubjects'); const dataGrid = getService('dataGrid'); const queryBar = getService('queryBar'); + const monacoEditor = getService('monacoEditor'); describe('tabs restorable state', function () { describe('sidebar', function () { @@ -271,6 +272,69 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await expectState(draftQuery0, false); expect(await discover.getHitCount()).to.be('11,829'); }); + + it('should restore the search bar state in ES|QL mode', async () => { + await discover.selectTextBaseLang(); + await discover.waitUntilTabIsLoaded(); + const defaultQuery = 'FROM logstash-* | LIMIT 10'; + + const expectState = async (query: string, isDirty: boolean) => { + await retry.try(async () => { + expect(await monacoEditor.getCodeEditorValue()).to.be(query); + }); + expect(await testSubjects.getAttribute('querySubmitButton', 'aria-label')).to.be( + isDirty ? 'Run query' : 'Refresh query' + ); + }; + + const draftQuery0 = 'from logstash-* | sort @timestamp desc | limit 50'; + await expectState(defaultQuery, false); + await monacoEditor.setCodeEditorValue(draftQuery0); + await expectState(draftQuery0, true); + + await unifiedTabs.createNewTab(); + await discover.waitUntilTabIsLoaded(); + await discover.selectTextBaseLang(); + await discover.waitUntilTabIsLoaded(); + await expectState(defaultQuery, false); + + const draftQuery2 = 'from logstash-* | sort @timestamp desc | limit 150'; + await unifiedTabs.createNewTab(); + await discover.waitUntilTabIsLoaded(); + await discover.selectTextBaseLang(); + await discover.waitUntilTabIsLoaded(); + await expectState(defaultQuery, false); + await monacoEditor.setCodeEditorValue(draftQuery2); + await expectState(draftQuery2, true); + + await unifiedTabs.selectTab(0); + await discover.waitUntilTabIsLoaded(); + await expectState(draftQuery0, true); + expect(await discover.getHitCount()).to.be('10'); + await queryBar.clickQuerySubmitButton(); + await discover.waitUntilTabIsLoaded(); + await expectState(draftQuery0, false); + expect(await discover.getHitCount()).to.be('50'); + + await unifiedTabs.selectTab(1); + await discover.waitUntilTabIsLoaded(); + await expectState(defaultQuery, false); + expect(await discover.getHitCount()).to.be('10'); + + await unifiedTabs.selectTab(2); + await discover.waitUntilTabIsLoaded(); + await expectState(draftQuery2, true); + expect(await discover.getHitCount()).to.be('10'); + await queryBar.clickQuerySubmitButton(); + await discover.waitUntilTabIsLoaded(); + await expectState(draftQuery2, false); + expect(await discover.getHitCount()).to.be('150'); + + await unifiedTabs.selectTab(0); + await discover.waitUntilTabIsLoaded(); + await expectState(draftQuery0, false); + expect(await discover.getHitCount()).to.be('50'); + }); }); }); } From 71a6985f672104208efa1404022f6e2df5635b6a Mon Sep 17 00:00:00 2001 From: Julia Rechkunova Date: Tue, 15 Jul 2025 16:57:33 +0200 Subject: [PATCH 13/19] [Discover] Exclude dates for non-time based data views --- .../query_string_input/query_bar_top_row.tsx | 53 +++++++++++-------- 1 file changed, 30 insertions(+), 23 deletions(-) diff --git a/src/platform/plugins/shared/unified_search/public/query_string_input/query_bar_top_row.tsx b/src/platform/plugins/shared/unified_search/public/query_string_input/query_bar_top_row.tsx index 67c9d26a4439e..ebe9aeddbab7f 100644 --- a/src/platform/plugins/shared/unified_search/public/query_string_input/query_bar_top_row.tsx +++ b/src/platform/plugins/shared/unified_search/public/query_string_input/query_bar_top_row.tsx @@ -231,29 +231,6 @@ export const QueryBarTopRow = React.memo( function QueryBarTopRow( props: QueryBarTopRowProps ) { - const { - onDraftChange, - isDirty: draftIsDirty, - query: draftQuery, - dateRangeFrom: draftDateRangeFrom, - dateRangeTo: draftDateRangeTo, - } = props; - const draft = useMemo( - () => - onDraftChange && draftIsDirty - ? { - query: draftQuery, - dateRangeFrom: draftDateRangeFrom, - dateRangeTo: draftDateRangeTo, - } - : null, - [onDraftChange, draftIsDirty, draftQuery, draftDateRangeFrom, draftDateRangeTo] - ); - - useEffect(() => { - onDraftChange?.(draft); - }, [onDraftChange, draft]); - const isMobile = useIsWithinBreakpoints(['xs', 's']); const [isXXLarge, setIsXXLarge] = useState(false); const submitButtonStyle: QueryBarTopRowProps['submitButtonStyle'] = @@ -469,6 +446,36 @@ export const QueryBarTopRow = React.memo( [onSubmit] ); + const { + onDraftChange, + isDirty: draftIsDirty, + query: draftQuery, + dateRangeFrom: draftDateRangeFrom, + dateRangeTo: draftDateRangeTo, + } = props; + const draft = useMemo( + () => + onDraftChange && draftIsDirty + ? { + query: draftQuery, + dateRangeFrom: showDatePicker ? draftDateRangeFrom : undefined, + dateRangeTo: showDatePicker ? draftDateRangeTo : undefined, + } + : null, + [ + onDraftChange, + draftIsDirty, + draftQuery, + draftDateRangeFrom, + draftDateRangeTo, + showDatePicker, + ] + ); + + useEffect(() => { + onDraftChange?.(draft); + }, [onDraftChange, draft]); + function shouldRenderQueryInput(): boolean { return Boolean(showQueryInput && props.query && storage); } From df05e7f61b0d55a0670435f11ba1a151d1fbfe7e Mon Sep 17 00:00:00 2001 From: Julia Rechkunova Date: Tue, 15 Jul 2025 17:01:38 +0200 Subject: [PATCH 14/19] [Discover] Disable for normal mode --- .../application/main/components/top_nav/discover_topnav.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/platform/plugins/shared/discover/public/application/main/components/top_nav/discover_topnav.tsx b/src/platform/plugins/shared/discover/public/application/main/components/top_nav/discover_topnav.tsx index f1344c8e0d811..9bc941f06c324 100644 --- a/src/platform/plugins/shared/discover/public/application/main/components/top_nav/discover_topnav.tsx +++ b/src/platform/plugins/shared/discover/public/application/main/components/top_nav/discover_topnav.tsx @@ -33,6 +33,7 @@ import { useInternalStateDispatch, useInternalStateSelector, } from '../../state_management/redux'; +import { TABS_ENABLED } from '../../../../constants'; export interface DiscoverTopNavProps { savedQuery?: string; @@ -292,7 +293,7 @@ export const DiscoverTopNav = ({ } onESQLDocsFlyoutVisibilityChanged={onESQLDocsFlyoutVisibilityChanged} draft={draft} - onDraftChange={onDraftChange} + onDraftChange={TABS_ENABLED ? onDraftChange : undefined} /> {isESQLToDataViewTransitionModalVisible && ( From 7e9d2e11bda1ea40227fc0cf76a7e68a050df297 Mon Sep 17 00:00:00 2001 From: Julia Rechkunova Date: Tue, 15 Jul 2025 17:11:38 +0200 Subject: [PATCH 15/19] [Discover] Add unit tests --- .../query_bar_top_row.test.tsx | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/src/platform/plugins/shared/unified_search/public/query_string_input/query_bar_top_row.test.tsx b/src/platform/plugins/shared/unified_search/public/query_string_input/query_bar_top_row.test.tsx index 51eb987838bee..aa6159cbd61b9 100644 --- a/src/platform/plugins/shared/unified_search/public/query_string_input/query_bar_top_row.test.tsx +++ b/src/platform/plugins/shared/unified_search/public/query_string_input/query_bar_top_row.test.tsx @@ -404,6 +404,70 @@ describe('QueryBarTopRowTopRow', () => { expect(component.find(CANCEL_BUTTON_SELECTOR).length).toBe(0); }); + + describe('draft', () => { + it('Should call onDraftChange when in dirty state', () => { + const onDraftChange = jest.fn(); + const state = { + query: kqlQuery, + dateRangeFrom: 'now-7d', + dateRangeTo: 'now', + }; + const { getByText } = render( + wrapQueryBarTopRowInContext({ + isDirty: true, + onDraftChange, + ...state, + }) + ); + + expect(getByText(kqlQuery.query)).toBeInTheDocument(); + expect(onDraftChange).toHaveBeenCalledWith(state); + }); + + it('Should call onDraftChange when in dirty state and no date picker', () => { + const onDraftChange = jest.fn(); + const state = { + query: kqlQuery, + dateRangeFrom: 'now-7d', + dateRangeTo: 'now', + }; + const { getByText } = render( + wrapQueryBarTopRowInContext({ + isDirty: true, + showDatePicker: false, + onDraftChange, + ...state, + }) + ); + + expect(getByText(kqlQuery.query)).toBeInTheDocument(); + expect(onDraftChange).toHaveBeenCalledWith({ + query: state.query, + dateRangeFrom: undefined, + dateRangeTo: undefined, + }); + }); + + it('Should call onDraftChange with empty draft when in normal state', () => { + const onDraftChange = jest.fn(); + const state = { + query: kqlQuery, + dateRangeFrom: 'now-7d', + dateRangeTo: 'now', + }; + const { getByText } = render( + wrapQueryBarTopRowInContext({ + isDirty: false, + onDraftChange, + ...state, + }) + ); + + expect(getByText(kqlQuery.query)).toBeInTheDocument(); + expect(onDraftChange).toHaveBeenCalledWith(null); + }); + }); }); describe('SharingMetaFields', () => { From 9066c70918afeba048d763b50e3b8c32e73e502c Mon Sep 17 00:00:00 2001 From: Julia Rechkunova Date: Tue, 15 Jul 2025 17:27:08 +0200 Subject: [PATCH 16/19] [Discover] Add unit tests --- .../public/search_bar/search_bar.test.tsx | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/platform/plugins/shared/unified_search/public/search_bar/search_bar.test.tsx b/src/platform/plugins/shared/unified_search/public/search_bar/search_bar.test.tsx index 70181439bf713..7d036618b10f0 100644 --- a/src/platform/plugins/shared/unified_search/public/search_bar/search_bar.test.tsx +++ b/src/platform/plugins/shared/unified_search/public/search_bar/search_bar.test.tsx @@ -409,4 +409,26 @@ describe('SearchBar', () => { }); }); }); + + it('Should prefill with the draft query if provided', () => { + const draft = { + query: { language: 'kuery', query: 'test_draft' }, + dateRangeFrom: 'now-30m', + dateRangeTo: 'now-10m', + }; + const onDraftChange = jest.fn(); + const component = mount( + wrapSearchBarInContext({ + indexPatterns: [stubIndexPattern], + query: kqlQuery, + dateRangeTo: 'now', + dateRangeFrom: 'now-15m', + draft, + onDraftChange, + }) + ); + + expect(onDraftChange).toHaveBeenCalledWith(draft); + expect(component.find('textarea').prop('value')).toEqual(draft.query.query); + }); }); From fe3f4faf62abafe13c66d25bda79d5782f140835 Mon Sep 17 00:00:00 2001 From: Julia Rechkunova Date: Sun, 20 Jul 2025 16:31:41 +0200 Subject: [PATCH 17/19] [Discover] Simplify --- .../components/top_nav/discover_topnav.tsx | 33 +++++-------------- .../query_string_input/query_bar_top_row.tsx | 4 +-- 2 files changed, 11 insertions(+), 26 deletions(-) diff --git a/src/platform/plugins/shared/discover/public/application/main/components/top_nav/discover_topnav.tsx b/src/platform/plugins/shared/discover/public/application/main/components/top_nav/discover_topnav.tsx index 9bc941f06c324..6b5d6992cc81d 100644 --- a/src/platform/plugins/shared/discover/public/application/main/components/top_nav/discover_topnav.tsx +++ b/src/platform/plugins/shared/discover/public/application/main/components/top_nav/discover_topnav.tsx @@ -10,7 +10,6 @@ import React, { useCallback, useEffect, useMemo, useRef } from 'react'; import { DataViewType } from '@kbn/data-views-plugin/public'; import type { DataViewPickerProps, UnifiedSearchDraft } from '@kbn/unified-search-plugin/public'; -import { isEqual } from 'lodash'; import { DiscoverFlyouts, dismissAllFlyoutsExceptFor } from '@kbn/discover-utils'; import type { EuiHeaderLinksProps } from '@elastic/eui'; import { isOfAggregateQueryType } from '@kbn/es-query'; @@ -219,14 +218,10 @@ export const DiscoverTopNav = ({ const searchDraftUiState = useCurrentTabSelector((state) => state.uiState.searchDraft); const setSearchDraftUiState = useCurrentTabAction(internalStateActions.setSearchDraftUiState); const onDraftChange = useCallback( - (draft: UnifiedSearchDraft | null) => { + (newSearchDraftUiState: UnifiedSearchDraft | undefined) => { dispatch( setSearchDraftUiState({ - searchDraftUiState: { - query: draft?.query ?? undefined, - dateRangeFrom: draft?.dateRangeFrom ?? undefined, - dateRangeTo: draft?.dateRangeTo ?? undefined, - }, + searchDraftUiState: newSearchDraftUiState, }) ); }, @@ -234,25 +229,15 @@ export const DiscoverTopNav = ({ ); const draft = useMemo(() => { - const draftState: Partial = {}; - - if (searchDraftUiState?.query && !isEqual(searchDraftUiState.query, query)) { - if (isOfAggregateQueryType(searchDraftUiState.query) !== isOfAggregateQueryType(query)) { - return undefined; // don't use the draft when query type is different - } - - draftState.query = searchDraftUiState.query; - } - - if (searchDraftUiState?.dateRangeFrom) { - draftState.dateRangeFrom = searchDraftUiState.dateRangeFrom; - } - - if (searchDraftUiState?.dateRangeTo) { - draftState.dateRangeTo = searchDraftUiState.dateRangeTo; + if ( + searchDraftUiState?.query && + isOfAggregateQueryType(searchDraftUiState.query) !== isOfAggregateQueryType(query) + ) { + // safeguard against query type mismatch + return undefined; } - return Object.keys(draftState).length > 0 ? draftState : undefined; + return searchDraftUiState; }, [searchDraftUiState, query]); const shouldHideDefaultDataviewPicker = diff --git a/src/platform/plugins/shared/unified_search/public/query_string_input/query_bar_top_row.tsx b/src/platform/plugins/shared/unified_search/public/query_string_input/query_bar_top_row.tsx index ebe9aeddbab7f..1c6f7f566f656 100644 --- a/src/platform/plugins/shared/unified_search/public/query_string_input/query_bar_top_row.tsx +++ b/src/platform/plugins/shared/unified_search/public/query_string_input/query_bar_top_row.tsx @@ -146,7 +146,7 @@ export interface QueryBarTopRowProps onRefreshChange?: (options: { isPaused: boolean; refreshInterval: number }) => void; onSubmit: (payload: { dateRange: TimeRange; query?: Query | QT }) => void; onCancel?: () => void; - onDraftChange?: (draft: UnifiedSearchDraft | null) => void; + onDraftChange?: (draft: UnifiedSearchDraft | undefined) => void; placeholder?: string; prepend?: React.ComponentProps['prepend']; query?: Query | QT; @@ -461,7 +461,7 @@ export const QueryBarTopRow = React.memo( dateRangeFrom: showDatePicker ? draftDateRangeFrom : undefined, dateRangeTo: showDatePicker ? draftDateRangeTo : undefined, } - : null, + : undefined, [ onDraftChange, draftIsDirty, From 60eb5bcc40d8b6deca8aa00b2794c2cbd64240f8 Mon Sep 17 00:00:00 2001 From: Julia Rechkunova Date: Mon, 21 Jul 2025 10:30:14 +0200 Subject: [PATCH 18/19] [Discover] Update tests --- .../public/query_string_input/query_bar_top_row.test.tsx | 8 ++++---- .../unified_search/public/search_bar/search_bar.test.tsx | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/platform/plugins/shared/unified_search/public/query_string_input/query_bar_top_row.test.tsx b/src/platform/plugins/shared/unified_search/public/query_string_input/query_bar_top_row.test.tsx index aa6159cbd61b9..1e2a1e45d8c7e 100644 --- a/src/platform/plugins/shared/unified_search/public/query_string_input/query_bar_top_row.test.tsx +++ b/src/platform/plugins/shared/unified_search/public/query_string_input/query_bar_top_row.test.tsx @@ -406,7 +406,7 @@ describe('QueryBarTopRowTopRow', () => { }); describe('draft', () => { - it('Should call onDraftChange when in dirty state', () => { + it('should call onDraftChange when in dirty state', () => { const onDraftChange = jest.fn(); const state = { query: kqlQuery, @@ -425,7 +425,7 @@ describe('QueryBarTopRowTopRow', () => { expect(onDraftChange).toHaveBeenCalledWith(state); }); - it('Should call onDraftChange when in dirty state and no date picker', () => { + it('should call onDraftChange when in dirty state and no date picker', () => { const onDraftChange = jest.fn(); const state = { query: kqlQuery, @@ -449,7 +449,7 @@ describe('QueryBarTopRowTopRow', () => { }); }); - it('Should call onDraftChange with empty draft when in normal state', () => { + it('should call onDraftChange with empty draft when in normal state', () => { const onDraftChange = jest.fn(); const state = { query: kqlQuery, @@ -465,7 +465,7 @@ describe('QueryBarTopRowTopRow', () => { ); expect(getByText(kqlQuery.query)).toBeInTheDocument(); - expect(onDraftChange).toHaveBeenCalledWith(null); + expect(onDraftChange).toHaveBeenCalledWith(undefined); }); }); }); diff --git a/src/platform/plugins/shared/unified_search/public/search_bar/search_bar.test.tsx b/src/platform/plugins/shared/unified_search/public/search_bar/search_bar.test.tsx index 7d036618b10f0..07ff55b1dcbaa 100644 --- a/src/platform/plugins/shared/unified_search/public/search_bar/search_bar.test.tsx +++ b/src/platform/plugins/shared/unified_search/public/search_bar/search_bar.test.tsx @@ -410,7 +410,7 @@ describe('SearchBar', () => { }); }); - it('Should prefill with the draft query if provided', () => { + it('should prefill with the draft query if provided', () => { const draft = { query: { language: 'kuery', query: 'test_draft' }, dateRangeFrom: 'now-30m', From d582bf12b9090a3ab189456e00c085ce5bd67690 Mon Sep 17 00:00:00 2001 From: Julia Rechkunova Date: Mon, 21 Jul 2025 11:00:42 +0200 Subject: [PATCH 19/19] [Discover] Simplify more --- .../components/top_nav/discover_topnav.tsx | 15 +---- .../public/search_bar/search_bar.test.tsx | 62 +++++++++++++------ .../public/search_bar/search_bar.tsx | 38 +++++++++--- 3 files changed, 73 insertions(+), 42 deletions(-) diff --git a/src/platform/plugins/shared/discover/public/application/main/components/top_nav/discover_topnav.tsx b/src/platform/plugins/shared/discover/public/application/main/components/top_nav/discover_topnav.tsx index 6b5d6992cc81d..a2bfad25d94f6 100644 --- a/src/platform/plugins/shared/discover/public/application/main/components/top_nav/discover_topnav.tsx +++ b/src/platform/plugins/shared/discover/public/application/main/components/top_nav/discover_topnav.tsx @@ -12,7 +12,6 @@ import { DataViewType } from '@kbn/data-views-plugin/public'; import type { DataViewPickerProps, UnifiedSearchDraft } from '@kbn/unified-search-plugin/public'; import { DiscoverFlyouts, dismissAllFlyoutsExceptFor } from '@kbn/discover-utils'; import type { EuiHeaderLinksProps } from '@elastic/eui'; -import { isOfAggregateQueryType } from '@kbn/es-query'; import { useSavedSearchInitial } from '../../state_management/discover_state_provider'; import { ESQL_TRANSITION_MODAL_KEY } from '../../../../../common/constants'; import { useDiscoverServices } from '../../../../hooks/use_discover_services'; @@ -228,18 +227,6 @@ export const DiscoverTopNav = ({ [dispatch, setSearchDraftUiState] ); - const draft = useMemo(() => { - if ( - searchDraftUiState?.query && - isOfAggregateQueryType(searchDraftUiState.query) !== isOfAggregateQueryType(query) - ) { - // safeguard against query type mismatch - return undefined; - } - - return searchDraftUiState; - }, [searchDraftUiState, query]); - const shouldHideDefaultDataviewPicker = !!searchBarCustomization?.CustomDataViewPicker || !!searchBarCustomization?.hideDataViewPicker; @@ -277,7 +264,7 @@ export const DiscoverTopNav = ({ ) : undefined } onESQLDocsFlyoutVisibilityChanged={onESQLDocsFlyoutVisibilityChanged} - draft={draft} + draft={searchDraftUiState} onDraftChange={TABS_ENABLED ? onDraftChange : undefined} /> {isESQLToDataViewTransitionModalVisible && ( diff --git a/src/platform/plugins/shared/unified_search/public/search_bar/search_bar.test.tsx b/src/platform/plugins/shared/unified_search/public/search_bar/search_bar.test.tsx index 07ff55b1dcbaa..c49ea257fd93f 100644 --- a/src/platform/plugins/shared/unified_search/public/search_bar/search_bar.test.tsx +++ b/src/platform/plugins/shared/unified_search/public/search_bar/search_bar.test.tsx @@ -410,25 +410,49 @@ describe('SearchBar', () => { }); }); - it('should prefill with the draft query if provided', () => { - const draft = { - query: { language: 'kuery', query: 'test_draft' }, - dateRangeFrom: 'now-30m', - dateRangeTo: 'now-10m', - }; - const onDraftChange = jest.fn(); - const component = mount( - wrapSearchBarInContext({ - indexPatterns: [stubIndexPattern], - query: kqlQuery, - dateRangeTo: 'now', - dateRangeFrom: 'now-15m', - draft, - onDraftChange, - }) - ); + describe('draft', () => { + it('should prefill with the draft query if provided', () => { + const draft = { + query: { language: 'kuery', query: 'test_draft' }, + dateRangeFrom: 'now-30m', + dateRangeTo: 'now-10m', + }; + const onDraftChange = jest.fn(); + const component = mount( + wrapSearchBarInContext({ + indexPatterns: [stubIndexPattern], + query: kqlQuery, + dateRangeTo: 'now', + dateRangeFrom: 'now-15m', + draft, + onDraftChange, + }) + ); + + expect(onDraftChange).toHaveBeenCalledWith(draft); + expect(component.find('textarea').prop('value')).toEqual(draft.query.query); + }); - expect(onDraftChange).toHaveBeenCalledWith(draft); - expect(component.find('textarea').prop('value')).toEqual(draft.query.query); + it('should check for query type mismatch', () => { + const draft = { + query: esqlQuery, + dateRangeFrom: 'now-30m', + dateRangeTo: 'now-10m', + }; + const onDraftChange = jest.fn(); + const component = mount( + wrapSearchBarInContext({ + indexPatterns: [stubIndexPattern], + query: kqlQuery, + dateRangeTo: 'now', + dateRangeFrom: 'now-15m', + draft, + onDraftChange, + }) + ); + + expect(onDraftChange).toHaveBeenCalledWith(undefined); + expect(component.find('textarea').prop('value')).toEqual(kqlQuery.query); + }); }); }); diff --git a/src/platform/plugins/shared/unified_search/public/search_bar/search_bar.tsx b/src/platform/plugins/shared/unified_search/public/search_bar/search_bar.tsx index bcc2c86734f0b..5da81213f2307 100644 --- a/src/platform/plugins/shared/unified_search/public/search_bar/search_bar.tsx +++ b/src/platform/plugins/shared/unified_search/public/search_bar/search_bar.tsx @@ -254,6 +254,30 @@ export class SearchBarUI ex return nextState; } + private prefillWithInitialDraftState = (state: SearchBarState): SearchBarState => { + const { draft, query } = this.props; + + if (!draft) { + return state; + } + + if ( + query && + draft.query && + isOfAggregateQueryType(query) !== isOfAggregateQueryType(draft.query) + ) { + // safeguard against query type mismatch + return state; + } + + return { + ...state, + query: draft.query ? ({ ...draft.query } as SearchBarState['query']) : state.query, + dateRangeFrom: draft.dateRangeFrom || state.dateRangeFrom, + dateRangeTo: draft.dateRangeTo || state.dateRangeTo, + }; + }; + /* Keep the "draft" value in local state until the user actually submits the query. There are a couple advantages: @@ -261,19 +285,15 @@ export class SearchBarUI ex until the user manually submits their changes. Some apps have watches on the query value in app state so we don't want to trigger those on every keypress. */ - public state = { + public state = this.prefillWithInitialDraftState({ isFiltersVisible: true, openQueryBarMenu: false, showSavedQueryPopover: false, currentProps: this.props, - query: this.props.draft?.query - ? { ...this.props.draft.query } - : this.props.query - ? { ...this.props.query } - : undefined, - dateRangeFrom: this.props.draft?.dateRangeFrom || get(this.props, 'dateRangeFrom', 'now-15m'), - dateRangeTo: this.props.draft?.dateRangeTo || get(this.props, 'dateRangeTo', 'now'), - } as SearchBarState; + query: this.props.query ? { ...this.props.query } : undefined, + dateRangeFrom: get(this.props, 'dateRangeFrom', 'now-15m'), + dateRangeTo: get(this.props, 'dateRangeTo', 'now'), + } as SearchBarState); public isDirty = () => { if (!this.props.showDatePicker && this.state.query && this.props.query) {