Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
723da94
[Discover] Restore the draft query in the unified search
jughosta Jul 9, 2025
fa4012e
[Discover] Remove logs
jughosta Jul 9, 2025
8e1e27c
[Discover] Restore query in draft mode
jughosta Jul 10, 2025
6e83d4e
[Discover] Fix lint issues
jughosta Jul 10, 2025
6bbf4f4
[Discover] Update limits
jughosta Jul 10, 2025
ec56f17
[Discover] Change the approach
jughosta Jul 11, 2025
711d4e0
[CI] Auto-commit changed files from 'node scripts/yarn_deduplicate'
kibanamachine Jul 11, 2025
4725d58
[Discover] Clean up
jughosta Jul 11, 2025
f6d1eab
Merge remote-tracking branch 'origin/218509-restore-search-state' int…
jughosta Jul 11, 2025
dd19509
Merge remote-tracking branch 'upstream/main' into 218509-restore-sear…
jughosta Jul 14, 2025
06744f2
[Discover] Revert previous changes
jughosta Jul 14, 2025
b02c419
[Discover] Add a safeguard
jughosta Jul 15, 2025
1df0e81
Merge branch 'main' into 218509-restore-search-state
jughosta Jul 15, 2025
0c1778c
[Discover] Add tests
jughosta Jul 15, 2025
654ad4e
[Discover] Add tests for ES|QL mode
jughosta Jul 15, 2025
71a6985
[Discover] Exclude dates for non-time based data views
jughosta Jul 15, 2025
df05e7f
[Discover] Disable for normal mode
jughosta Jul 15, 2025
7e9d2e1
[Discover] Add unit tests
jughosta Jul 15, 2025
9066c70
[Discover] Add unit tests
jughosta Jul 15, 2025
2f6a365
Merge branch 'main' into 218509-restore-search-state
jughosta Jul 16, 2025
f9d88ff
Merge branch 'main' into 218509-restore-search-state
jughosta Jul 16, 2025
28b8086
Merge branch 'main' into 218509-restore-search-state
jughosta Jul 20, 2025
fe3f4fa
[Discover] Simplify
jughosta Jul 20, 2025
60eb5bc
[Discover] Update tests
jughosta Jul 21, 2025
d582bf1
[Discover] Simplify more
jughosta Jul 21, 2025
1e1093f
Merge branch 'main' into 218509-restore-search-state
jughosta Jul 21, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

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, UnifiedSearchDraft } 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';
Expand All @@ -25,10 +25,13 @@ import { ESQLToDataViewTransitionModal } from './esql_dataview_transition';
import {
internalStateActions,
useCurrentDataView,
useCurrentTabAction,
useCurrentTabSelector,
useDataViewsForPicker,
useInternalStateDispatch,
useInternalStateSelector,
} from '../../state_management/redux';
import { TABS_ENABLED } from '../../../../constants';

export interface DiscoverTopNavProps {
savedQuery?: string;
Expand Down Expand Up @@ -211,6 +214,19 @@ export const DiscoverTopNav = ({
[searchBarCustomization?.CustomSearchBar, navigation.ui.AggregateQueryTopNavMenu]
);

const searchDraftUiState = useCurrentTabSelector((state) => state.uiState.searchDraft);
const setSearchDraftUiState = useCurrentTabAction(internalStateActions.setSearchDraftUiState);
const onDraftChange = useCallback(
(newSearchDraftUiState: UnifiedSearchDraft | undefined) => {
dispatch(
setSearchDraftUiState({
searchDraftUiState: newSearchDraftUiState,
})
);
},
[dispatch, setSearchDraftUiState]
);

const shouldHideDefaultDataviewPicker =
!!searchBarCustomization?.CustomDataViewPicker || !!searchBarCustomization?.hideDataViewPicker;

Expand Down Expand Up @@ -248,6 +264,8 @@ export const DiscoverTopNav = ({
) : undefined
}
onESQLDocsFlyoutVisibilityChanged={onESQLDocsFlyoutVisibilityChanged}
draft={searchDraftUiState}
onDraftChange={TABS_ENABLED ? onDraftChange : undefined}
/>
{isESQLToDataViewTransitionModalVisible && (
<ESQLToDataViewTransitionModal onClose={onESQLToDataViewTransitionModalClose} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,14 @@ export const internalStateSlice = createSlice({
withTab(state, action, (tab) => {
tab.uiState.layout = action.payload.layoutUiState;
}),

setSearchDraftUiState: (
state,
action: TabAction<{ searchDraftUiState: Partial<TabState['uiState']['searchDraft']> }>
) =>
withTab(state, action, (tab) => {
tab.uiState.searchDraft = action.payload.searchDraftUiState;
}),
},
extraReducers: (builder) => {
builder.addCase(loadDataViewList.fulfilled, (state, action) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 { 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';
Expand Down Expand Up @@ -80,6 +81,7 @@ export interface TabState extends TabItem {
dataGrid?: Partial<UnifiedDataTableRestorableState>;
fieldList?: Partial<UnifiedFieldListRestorableState>;
layout?: Partial<DiscoverLayoutRestorableState>;
searchDraft?: Partial<UnifiedSearchDraft>;
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export type {
UnifiedSearchPublicPluginStart,
UnifiedSearchPluginSetup,
IUnifiedSearchPluginServices,
UnifiedSearchDraft,
} from './types';
export type { FilterItemsProps } from './filter_bar';
export type { DataViewPickerProps } from './dataview_picker';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(undefined);
});
});
});

describe('SharingMetaFields', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -146,6 +146,7 @@ export interface QueryBarTopRowProps<QT extends Query | AggregateQuery = Query>
onRefreshChange?: (options: { isPaused: boolean; refreshInterval: number }) => void;
onSubmit: (payload: { dateRange: TimeRange; query?: Query | QT }) => void;
onCancel?: () => void;
onDraftChange?: (draft: UnifiedSearchDraft | undefined) => void;
placeholder?: string;
prepend?: React.ComponentProps<typeof EuiFieldText>['prepend'];
query?: Query | QT;
Expand Down Expand Up @@ -445,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,
}
: undefined,
[
onDraftChange,
draftIsDirty,
draftQuery,
draftDateRangeFrom,
draftDateRangeTo,
showDatePicker,
]
);

useEffect(() => {
onDraftChange?.(draft);
}, [onDraftChange, draft]);

function shouldRenderQueryInput(): boolean {
return Boolean(showQueryInput && props.query && storage);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,8 @@ export function createSearchBar({
onCancel={props.onCancel}
filters={filters}
query={query}
draft={props.draft}
onDraftChange={props.onDraftChange}
onFiltersUpdated={defaultFiltersUpdated(data.query, props.onFiltersUpdated)}
onRefreshChange={
!props.isAutoRefreshDisabled
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -409,4 +409,50 @@ describe('SearchBar', () => {
});
});
});

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);
});

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);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -96,6 +96,9 @@ export interface SearchBarOwnProps<QT extends AggregateQuery | Query = Query> {
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<QT>['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
Expand Down Expand Up @@ -251,22 +254,46 @@ export class SearchBarUI<QT extends (Query | AggregateQuery) | Query = Query> ex
return nextState;
}

private prefillWithInitialDraftState = (state: SearchBarState<QT>): SearchBarState<QT> => {
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<QT>['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:

Each app doesn't have to maintain its own "draft" value if it wants to put off updating the query in app state
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.query ? { ...this.props.query } : undefined,
dateRangeFrom: get(this.props, 'dateRangeFrom', 'now-15m'),
dateRangeTo: get(this.props, 'dateRangeTo', 'now'),
} as SearchBarState<QT>;
} as SearchBarState<QT>);

public isDirty = () => {
if (!this.props.showDatePicker && this.state.query && this.props.query) {
Expand Down Expand Up @@ -636,6 +663,7 @@ export class SearchBarUI<QT extends (Query | AggregateQuery) | Query = Query> 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
Expand Down
6 changes: 6 additions & 0 deletions src/platform/plugins/shared/unified_search/public/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ import type { IndexPatternSelectProps, QueryStringInputProps, StatefulSearchBarP
import type { FiltersBuilderProps } from './filters_builder/filters_builder';
import { StatefulSearchBarDeps } from './search_bar/create_search_bar';

export interface UnifiedSearchDraft {
query?: AggregateQuery | Query;
dateRangeFrom?: string;
dateRangeTo?: string;
}

export interface UnifiedSearchSetupDependencies {
uiActions: UiActionsSetup;
data: DataPublicPluginStart;
Expand Down
Loading