diff --git a/src/plugins/data/public/mocks.ts b/src/plugins/data/public/mocks.ts index 135b6121d1dd5..0cf97cdf8140e 100644 --- a/src/plugins/data/public/mocks.ts +++ b/src/plugins/data/public/mocks.ts @@ -60,7 +60,7 @@ const createStartContract = (): Start => { query: queryStartMock, ui: { IndexPatternSelect: jest.fn(), - SearchBar: jest.fn(), + SearchBar: jest.fn().mockReturnValue(null), }, indexPatterns: ({ createField: jest.fn(() => {}), diff --git a/x-pack/plugins/security_solution/public/common/components/search_bar/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/search_bar/index.test.tsx new file mode 100644 index 0000000000000..356456c777791 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/search_bar/index.test.tsx @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { mount } from 'enzyme'; + +import { InputsModelId } from '../../store/inputs/constants'; +import { SearchBarComponent } from '.'; +import { TestProviders } from '../../mock'; + +jest.mock('../../lib/kibana'); + +describe('SearchBarComponent', () => { + const props = { + id: 'global' as InputsModelId, + indexPattern: { + fields: [], + title: '', + }, + updateSearch: jest.fn(), + setSavedQuery: jest.fn(), + setSearchBarFilter: jest.fn(), + end: '', + start: '', + toStr: '', + fromStr: '', + isLoading: false, + filterQuery: { + query: '', + language: '', + }, + queries: [], + savedQuery: undefined, + }; + + it('calls setSearchBarFilter on mount', () => { + mount(, { wrappingComponent: TestProviders }); + + expect(props.setSearchBarFilter).toHaveBeenCalled(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/search_bar/index.tsx b/x-pack/plugins/security_solution/public/common/components/search_bar/index.tsx index de60bca73cedf..2dc44fd48e66d 100644 --- a/x-pack/plugins/security_solution/public/common/components/search_bar/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/search_bar/index.tsx @@ -33,7 +33,6 @@ import { filterQuerySelector, fromStrSelector, isLoadingSelector, - kindSelector, queriesSelector, savedQuerySelector, startSelector, @@ -44,6 +43,8 @@ import { networkActions } from '../../../network/store'; import { timelineActions } from '../../../timelines/store/timeline'; import { useKibana } from '../../lib/kibana'; +const APP_STATE_STORAGE_KEY = 'securitySolution.searchBar.appState'; + interface SiemSearchBarProps { id: InputsModelId; indexPattern: IIndexPattern; @@ -57,7 +58,7 @@ const SearchBarContainer = styled.div` } `; -const SearchBarComponent = memo( +export const SearchBarComponent = memo( ({ end, filterQuery, @@ -74,20 +75,27 @@ const SearchBarComponent = memo( updateSearch, dataTestSubj, }) => { - const { data } = useKibana().services; const { - timefilter: { timefilter }, - filterManager, - } = data.query; - - if (fromStr != null && toStr != null) { - timefilter.setTime({ from: fromStr, to: toStr }); - } else if (start != null && end != null) { - timefilter.setTime({ - from: new Date(start).toISOString(), - to: new Date(end).toISOString(), - }); - } + data: { + query: { + timefilter: { timefilter }, + filterManager, + }, + ui: { SearchBar }, + }, + storage, + } = useKibana().services; + + useEffect(() => { + if (fromStr != null && toStr != null) { + timefilter.setTime({ from: fromStr, to: toStr }); + } else if (start != null && end != null) { + timefilter.setTime({ + from: new Date(start).toISOString(), + to: new Date(end).toISOString(), + }); + } + }, [end, fromStr, start, timefilter, toStr]); const onQuerySubmit = useCallback( (payload: { dateRange: TimeRange; query?: Query }) => { @@ -135,8 +143,7 @@ const SearchBarComponent = memo( window.setTimeout(() => updateSearch(updateSearchBar), 0); }, - // eslint-disable-next-line react-hooks/exhaustive-deps - [id, end, filterQuery, fromStr, queries, start, toStr] + [id, toStr, end, fromStr, start, filterManager, filterQuery, queries, updateSearch] ); const onRefresh = useCallback( @@ -155,16 +162,14 @@ const SearchBarComponent = memo( queries.forEach((q) => q.refetch && (q.refetch as inputsModel.Refetch)()); } }, - // eslint-disable-next-line react-hooks/exhaustive-deps - [id, queries, filterManager] + [updateSearch, id, filterManager, queries] ); const onSaved = useCallback( (newSavedQuery: SavedQuery) => { setSavedQuery({ id, savedQuery: newSavedQuery }); }, - // eslint-disable-next-line react-hooks/exhaustive-deps - [id] + [id, setSavedQuery] ); const onSavedQueryUpdated = useCallback( @@ -200,8 +205,7 @@ const SearchBarComponent = memo( updateSearch(updateSearchBar); }, - // eslint-disable-next-line react-hooks/exhaustive-deps - [id, end, fromStr, start, toStr] + [id, toStr, end, fromStr, start, filterManager, updateSearch] ); const onClearSavedQuery = useCallback(() => { @@ -223,8 +227,16 @@ const SearchBarComponent = memo( filterManager, }); } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [id, end, filterManager, fromStr, start, toStr, savedQuery]); + }, [savedQuery, updateSearch, id, toStr, end, fromStr, start, filterManager]); + + const saveAppStateToStorage = useCallback( + (filters: Filter[]) => storage.set(APP_STATE_STORAGE_KEY, filters), + [storage] + ); + + const getAppStateFromStorage = useCallback(() => storage.get(APP_STATE_STORAGE_KEY) ?? [], [ + storage, + ]); useEffect(() => { let isSubscribed = true; @@ -234,6 +246,7 @@ const SearchBarComponent = memo( filterManager.getUpdates$().subscribe({ next: () => { if (isSubscribed) { + saveAppStateToStorage(filterManager.getAppFilters()); setSearchBarFilter({ id, filters: filterManager.getFilters(), @@ -243,16 +256,25 @@ const SearchBarComponent = memo( }) ); + // for the initial state + filterManager.setAppFilters(getAppStateFromStorage()); + setSearchBarFilter({ + id, + filters: filterManager.getFilters(), + }); + return () => { isSubscribed = false; subscriptions.unsubscribe(); }; // eslint-disable-next-line react-hooks/exhaustive-deps }, []); + const indexPatterns = useMemo(() => [indexPattern], [indexPattern]); + return ( - { const getEndSelector = endSelector(); const getFromStrSelector = fromStrSelector(); const getIsLoadingSelector = isLoadingSelector(); - const getKindSelector = kindSelector(); const getQueriesSelector = queriesSelector(); const getStartSelector = startSelector(); const getToStrSelector = toStrSelector(); @@ -292,7 +313,6 @@ const makeMapStateToProps = () => { fromStr: getFromStrSelector(inputsRange), filterQuery: getFilterQuerySelector(inputsRange), isLoading: getIsLoadingSelector(inputsRange), - kind: getKindSelector(inputsRange), queries: getQueriesSelector(inputsRange), savedQuery: getSavedQuerySelector(inputsRange), start: getStartSelector(inputsRange),