From b3078c4dfde3138b74e84746064977a911477982 Mon Sep 17 00:00:00 2001 From: "opensearch-trigger-bot[bot]" <98922864+opensearch-trigger-bot[bot]@users.noreply.github.com> Date: Thu, 15 Aug 2024 11:36:08 -0700 Subject: [PATCH] Refactor search bar & filters to conditionally render new look with application header (#7687) (#7719) * Refactor search bar & filters to conditionally render with new application header * add more test coverage * address comments * Changeset file for PR #7687 created/updated --------- (cherry picked from commit 97ddd8a7e846c9f65b25d1f392b423d0b0cf6691) Signed-off-by: Zhongnan Su Signed-off-by: github-actions[bot] Co-authored-by: github-actions[bot] Co-authored-by: opensearch-changeset-bot[bot] <154024398+opensearch-changeset-bot[bot]@users.noreply.github.com> --- changelogs/fragments/7687.yml | 2 + src/plugins/data/common/constants.ts | 1 + .../ui/filter_bar/_global_filter_group.scss | 9 + .../data/public/ui/filter_bar/filter_bar.tsx | 74 +--- .../ui/filter_bar/filter_options.test.tsx | 188 ++++++++ .../public/ui/filter_bar/filter_options.tsx | 400 ++++++++++++++---- ...d_query_management_component.test.tsx.snap | 68 --- .../saved_query_management_component.test.tsx | 55 ++- .../saved_query_management_component.tsx | 293 ++++++------- .../data/public/ui/search_bar/search_bar.tsx | 41 +- 10 files changed, 731 insertions(+), 400 deletions(-) create mode 100644 changelogs/fragments/7687.yml create mode 100644 src/plugins/data/public/ui/filter_bar/filter_options.test.tsx delete mode 100644 src/plugins/data/public/ui/saved_query_management/__snapshots__/saved_query_management_component.test.tsx.snap diff --git a/changelogs/fragments/7687.yml b/changelogs/fragments/7687.yml new file mode 100644 index 000000000000..4577e74e4d4d --- /dev/null +++ b/changelogs/fragments/7687.yml @@ -0,0 +1,2 @@ +refactor: +- Refactor search bar & filters to conditionally render new look with application header ([#7687](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7687)) \ No newline at end of file diff --git a/src/plugins/data/common/constants.ts b/src/plugins/data/common/constants.ts index 863877322ad9..1de7ae5df034 100644 --- a/src/plugins/data/common/constants.ts +++ b/src/plugins/data/common/constants.ts @@ -63,4 +63,5 @@ export const UI_SETTINGS = { QUERY_ENHANCEMENTS_ENABLED: 'query:enhancements:enabled', QUERY_DATAFRAME_HYDRATION_STRATEGY: 'query:dataframe:hydrationStrategy', SEARCH_QUERY_LANGUAGE_BLOCKLIST: 'search:queryLanguageBlocklist', + NEW_HOME_PAGE: 'home:useNewHomePage', } as const; diff --git a/src/plugins/data/public/ui/filter_bar/_global_filter_group.scss b/src/plugins/data/public/ui/filter_bar/_global_filter_group.scss index b3bf36830ab7..96e6e4d6c97c 100644 --- a/src/plugins/data/public/ui/filter_bar/_global_filter_group.scss +++ b/src/plugins/data/public/ui/filter_bar/_global_filter_group.scss @@ -55,3 +55,12 @@ margin-top: $euiSize * -1; } } + +.globalFilterGroup__removeAllFilters { + color: $euiColorDangerText; +} + +.globalFilterGroup__filterPrefix { + padding-top: $euiSizeS; + padding-right: $euiSizeS; +} diff --git a/src/plugins/data/public/ui/filter_bar/filter_bar.tsx b/src/plugins/data/public/ui/filter_bar/filter_bar.tsx index 964b1fe82fb7..431fd72c065f 100644 --- a/src/plugins/data/public/ui/filter_bar/filter_bar.tsx +++ b/src/plugins/data/public/ui/filter_bar/filter_bar.tsx @@ -35,6 +35,7 @@ import { EuiFlexItem, EuiPopover, EuiResizeObserver, + EuiText, } from '@elastic/eui'; import { FormattedMessage, InjectedIntl, injectI18n } from '@osd/i18n/react'; import classNames from 'classnames'; @@ -43,20 +44,10 @@ import { stringify } from '@osd/std'; import { FilterEditor } from './filter_editor'; import { FilterItem } from './filter_item'; -import { FilterOptions } from './filter_options'; import { useOpenSearchDashboards } from '../../../../opensearch_dashboards_react/public'; import { IIndexPattern } from '../..'; -import { - buildEmptyFilter, - Filter, - enableFilter, - disableFilter, - pinFilter, - toggleFilterDisabled, - toggleFilterNegated, - unpinFilter, - UI_SETTINGS, -} from '../../../common'; +import { buildEmptyFilter, Filter, UI_SETTINGS } from '../../../common'; +import { FilterOptions } from './filter_options'; interface Props { filters: Filter[]; @@ -74,6 +65,7 @@ function FilterBarUI(props: Props) { const [filterWidth, setFilterWidth] = useState(maxFilterWidth); const uiSettings = opensearchDashboards.services.uiSettings; + const useNewHeader = Boolean(uiSettings!.get(UI_SETTINGS.NEW_HOME_PAGE)); if (!uiSettings) return null; function onFiltersUpdated(filters: Filter[]) { @@ -177,41 +169,10 @@ function FilterBarUI(props: Props) { onFiltersUpdated(filters); } - function onEnableAll() { - const filters = props.filters.map(enableFilter); - onFiltersUpdated(filters); - } - - function onDisableAll() { - const filters = props.filters.map(disableFilter); - onFiltersUpdated(filters); - } - - function onPinAll() { - const filters = props.filters.map(pinFilter); - onFiltersUpdated(filters); - } - - function onUnpinAll() { - const filters = props.filters.map(unpinFilter); - onFiltersUpdated(filters); - } - - function onToggleAllNegated() { - const filters = props.filters.map(toggleFilterNegated); - onFiltersUpdated(filters); - } - - function onToggleAllDisabled() { - const filters = props.filters.map(toggleFilterDisabled); - onFiltersUpdated(filters); - } - - function onRemoveAll() { - onFiltersUpdated([]); - } - const classes = classNames('globalFilterBar', props.className); + const filterBarPrefixText = i18n.translate('data.search.filterBar.filterBarPrefixText', { + defaultMessage: 'Filters: ', + }); return ( - + {useNewHeader ? ( + + {filterBarPrefixText}: + + ) : ( + + )} diff --git a/src/plugins/data/public/ui/filter_bar/filter_options.test.tsx b/src/plugins/data/public/ui/filter_bar/filter_options.test.tsx new file mode 100644 index 000000000000..f48e744528e9 --- /dev/null +++ b/src/plugins/data/public/ui/filter_bar/filter_options.test.tsx @@ -0,0 +1,188 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from 'react'; +import { FilterOptions } from './filter_options'; +import { SavedQueryAttributes } from '../../query'; +import { mountWithIntl } from 'test_utils/enzyme_helpers'; +import { Query } from 'src/plugins/data/common'; +import { useOpenSearchDashboards } from '../../../../opensearch_dashboards_react/public'; + +// Mock useOpenSearchDashboards hook +jest.mock('../../../../opensearch_dashboards_react/public', () => ({ + useOpenSearchDashboards: jest.fn(), + withOpenSearchDashboards: (Component: any) => (props: any) => , +})); + +const mockProps = () => ({ + savedQueryService: { + saveQuery: jest.fn(), + getAllSavedQueries: jest.fn(), + findSavedQueries: jest.fn().mockResolvedValue({ total: 0, queries: [] }), + getSavedQuery: jest.fn(), + deleteSavedQuery: jest.fn(), + getSavedQueryCount: jest.fn(), + }, + onSave: jest.fn(), + onSaveAsNew: jest.fn(), + onLoad: jest.fn(), + onClearSavedQuery: jest.fn(), + onFiltersUpdated: jest.fn(), + showSaveQuery: true, + loadedSavedQuery: { + id: '1', + attributes: { + name: 'Test Query', + title: '', + description: '', + query: { query: '', language: 'kuery' } as Query, + } as SavedQueryAttributes, + }, + filters: [ + { + meta: { + alias: null, + disabled: false, + negate: false, + }, + }, + ], + indexPatterns: [], + useSaveQueryMenu: false, +}); + +describe('Filter options menu', () => { + beforeEach(() => { + // Mocking `uiSettings.get` to return true for `useNewHeader` + (useOpenSearchDashboards as jest.Mock).mockReturnValue({ + services: { + uiSettings: { + get: jest.fn((key) => { + if (key === 'home:useNewHomePage') { + return true; + } + return false; + }), + }, + }, + }); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('render menu panel', () => { + const wrapper = mountWithIntl(); + const button = wrapper.find('[data-test-subj="showFilterActions"]').at(0); + + button.simulate('click'); + expect(wrapper.find('[data-test-subj="filter-options-menu-panel"]').exists()).toBeTruthy(); + }); + + it("render filter options with 'Add filter' button", () => { + const wrapper = mountWithIntl(); + const button = wrapper.find('[data-test-subj="showFilterActions"]').at(0); + button.simulate('click'); + wrapper.update(); + const addFilterButton = wrapper.find('[data-test-subj="addFilters"]').at(0); + addFilterButton.simulate('click'); + expect(wrapper.find('[data-test-subj="add-filter-panel"]').exists()).toBeTruthy(); + }); + + it("render filter options with 'Save Query' button", () => { + const wrapper = mountWithIntl(); + const button = wrapper.find('[data-test-subj="showFilterActions"]').at(0); + button.simulate('click'); + wrapper.update(); + const saveQueryButton = wrapper + .find('[data-test-subj="saved-query-management-save-button"]') + .at(0); + expect(saveQueryButton.exists()).toBeTruthy(); + saveQueryButton.simulate('click'); + expect(wrapper.find('[data-test-subj="save-query-panel"]').exists()).toBeTruthy(); + }); + + it('should call onFiltersUpdated when enable all filters button is clicked', () => { + const props = mockProps(); + const wrapper = mountWithIntl(); + const button = wrapper.find('[data-test-subj="showFilterActions"]').at(0); + button.simulate('click'); + wrapper.update(); + const enableAllFiltersButton = wrapper.find('[data-test-subj="enableAllFilters"]').at(0); + enableAllFiltersButton.simulate('click'); + expect(props.onFiltersUpdated).toHaveBeenCalled(); + }); + + it('should call onFiltersUpdated when disable all filters button is clicked', () => { + const props = mockProps(); + const wrapper = mountWithIntl(); + const button = wrapper.find('[data-test-subj="showFilterActions"]').at(0); + button.simulate('click'); + wrapper.update(); + const disableAllFiltersButton = wrapper.find('[data-test-subj="disableAllFilters"]').at(0); + disableAllFiltersButton.simulate('click'); + expect(props.onFiltersUpdated).toHaveBeenCalled(); + }); + + it('should call onFiltersUpdated when pin all filters button is clicked', () => { + const props = mockProps(); + const wrapper = mountWithIntl(); + const button = wrapper.find('[data-test-subj="showFilterActions"]').at(0); + button.simulate('click'); + wrapper.update(); + const pinAllFiltersButton = wrapper.find('[data-test-subj="pinAllFilters"]').at(0); + pinAllFiltersButton.simulate('click'); + expect(props.onFiltersUpdated).toHaveBeenCalled(); + }); + + it('should call onFiltersUpdated when unpin all filters button is clicked', () => { + const props = mockProps(); + const wrapper = mountWithIntl(); + const button = wrapper.find('[data-test-subj="showFilterActions"]').at(0); + button.simulate('click'); + wrapper.update(); + const unpinAllFiltersButton = wrapper.find('[data-test-subj="unpinAllFilters"]').at(0); + unpinAllFiltersButton.simulate('click'); + expect(props.onFiltersUpdated).toHaveBeenCalled(); + }); + + it('should call onFiltersUpdated when Invert all filters button is clicked', () => { + const props = mockProps(); + const wrapper = mountWithIntl(); + const button = wrapper.find('[data-test-subj="showFilterActions"]').at(0); + button.simulate('click'); + wrapper.update(); + const invertAllFiltersButton = wrapper + .find('[data-test-subj="invertInclusionAllFilters"]') + .at(0); + invertAllFiltersButton.simulate('click'); + expect(props.onFiltersUpdated).toHaveBeenCalled(); + }); + + it('should call onFiltersUpdated when Invert enabled/disabled filters button is clicked', () => { + const props = mockProps(); + const wrapper = mountWithIntl(); + const button = wrapper.find('[data-test-subj="showFilterActions"]').at(0); + button.simulate('click'); + wrapper.update(); + const invertEnabledDisabledFiltersButton = wrapper + .find('[data-test-subj="invertEnableDisableAllFilters"]') + .at(0); + invertEnabledDisabledFiltersButton.simulate('click'); + expect(props.onFiltersUpdated).toHaveBeenCalled(); + }); + + it('should call onFiltersUpdated when remove all filters button is clicked', () => { + const props = mockProps(); + const wrapper = mountWithIntl(); + const button = wrapper.find('[data-test-subj="showFilterActions"]').at(0); + button.simulate('click'); + wrapper.update(); + const removeAllFiltersButton = wrapper.find('[data-test-subj="removeAllFilters"]').at(0); + removeAllFiltersButton.simulate('click'); + expect(props.onFiltersUpdated).toHaveBeenCalled(); + }); +}); diff --git a/src/plugins/data/public/ui/filter_bar/filter_options.tsx b/src/plugins/data/public/ui/filter_bar/filter_options.tsx index b61bc1804dc3..82c2be66875d 100644 --- a/src/plugins/data/public/ui/filter_bar/filter_options.tsx +++ b/src/plugins/data/public/ui/filter_bar/filter_options.tsx @@ -29,177 +29,401 @@ */ import { - EuiSmallButtonIcon, EuiContextMenu, EuiPopover, - EuiPopoverTitle, EuiToolTip, + EuiButton, + EuiPopoverFooter, + EuiFlexGroup, + EuiFlexItem, + EuiSmallButtonEmpty, + EuiIcon, + EuiResizeObserver, + EuiContextMenuPanel, } from '@elastic/eui'; -import { FormattedMessage, InjectedIntl, injectI18n } from '@osd/i18n/react'; -import { Component } from 'react'; +import { stringify } from '@osd/std'; +import { InjectedIntl, injectI18n } from '@osd/i18n/react'; +import { i18n } from '@osd/i18n'; import React from 'react'; +import { + buildEmptyFilter, + Filter, + enableFilter, + disableFilter, + pinFilter, + toggleFilterDisabled, + toggleFilterNegated, + unpinFilter, + UI_SETTINGS, + IIndexPattern, +} from '../../../common'; +import { FilterEditor } from './filter_editor'; +import { useOpenSearchDashboards } from '../../../../opensearch_dashboards_react/public'; +import { SavedQueryManagementComponent } from '../saved_query_management'; +import { SavedQuery, SavedQueryService } from '../../query'; interface Props { - onEnableAll: () => void; - onDisableAll: () => void; - onPinAll: () => void; - onUnpinAll: () => void; - onToggleAllNegated: () => void; - onToggleAllDisabled: () => void; - onRemoveAll: () => void; intl: InjectedIntl; + filters: Filter[]; + indexPatterns: IIndexPattern[]; + savedQueryService: SavedQueryService; + // Show when user has privileges to save + showSaveQuery?: boolean; + onSave: () => void; + onSaveAsNew: () => void; + onLoad: (savedQuery: SavedQuery) => void; + onClearSavedQuery: () => void; + onFiltersUpdated?: (filters: Filter[]) => void; + loadedSavedQuery?: SavedQuery; + useSaveQueryMenu: boolean; } +const maxFilterWidth = 600; -interface State { - isPopoverOpen: boolean; -} +const FilterOptionsUI = (props: Props) => { + const [isPopoverOpen, setIsPopoverOpen] = React.useState(false); + const [renderedComponent, setRenderedComponent] = React.useState('menu'); + const [filterWidth, setFilterWidth] = React.useState(maxFilterWidth); + const [showSaveQueryButton, setShowSaveQueryButton] = React.useState(true); + const opensearchDashboards = useOpenSearchDashboards(); + const uiSettings = opensearchDashboards.services.uiSettings; + const isPinned = uiSettings!.get(UI_SETTINGS.FILTERS_PINNED_BY_DEFAULT); + const useNewHeader = Boolean(uiSettings!.get(UI_SETTINGS.NEW_HOME_PAGE)); + const [indexPattern] = props.indexPatterns; + const index = indexPattern && indexPattern.id; + const newFilter = buildEmptyFilter(isPinned, index); -class FilterOptionsUI extends Component { - public state: State = { - isPopoverOpen: false, + const togglePopover = () => { + setRenderedComponent('menu'); + setShowSaveQueryButton(true); + setIsPopoverOpen((prevState) => !prevState); }; - public togglePopover = () => { - this.setState((prevState) => ({ - isPopoverOpen: !prevState.isPopoverOpen, - })); + const closePopover = () => { + setIsPopoverOpen(false); }; - public closePopover = () => { - this.setState({ isPopoverOpen: false }); + function onFiltersUpdated(filters: Filter[]) { + if (props.onFiltersUpdated) { + props.onFiltersUpdated(filters); + } + } + + function onEnableAll() { + const filters = props.filters.map(enableFilter); + onFiltersUpdated(filters); + } + + function onDisableAll() { + const filters = props.filters.map(disableFilter); + onFiltersUpdated(filters); + } + + function onPinAll() { + const filters = props.filters.map(pinFilter); + onFiltersUpdated(filters); + } + + function onUnpinAll() { + const filters = props.filters.map(unpinFilter); + onFiltersUpdated(filters); + } + + function onToggleAllNegated() { + const filters = props.filters.map(toggleFilterNegated); + onFiltersUpdated(filters); + } + + function onToggleAllDisabled() { + const filters = props.filters.map(toggleFilterDisabled); + onFiltersUpdated(filters); + } + + function onRemoveAll() { + onFiltersUpdated([]); + } + + function onAdd(filter: Filter) { + setIsPopoverOpen(false); + const filters = [...props.filters, filter]; + onFiltersUpdated(filters); + } + + function onResize(dimensions: { height: number; width: number }) { + setFilterWidth(dimensions.width); + } + + const addFilterPanelItem = { + name: props.intl.formatMessage({ + id: 'data.filter.options.addFiltersButtonLabel', + defaultMessage: 'Add filters', + }), + icon: 'plusInCircle', + onClick: () => { + setRenderedComponent('addFilter'); + setShowSaveQueryButton(false); + }, + 'data-test-subj': 'addFilters', + disabled: false, }; - public render() { - const panelTree = { + const disableMenuOption = props.filters.length === 0 && useNewHeader; + + const panelTree = [ + { id: 0, + title: 'Filters', items: [ { - name: this.props.intl.formatMessage({ + name: props.intl.formatMessage({ id: 'data.filter.options.enableAllFiltersButtonLabel', defaultMessage: 'Enable all', }), icon: 'eye', onClick: () => { - this.closePopover(); - this.props.onEnableAll(); + closePopover(); + onEnableAll(); }, 'data-test-subj': 'enableAllFilters', + disabled: disableMenuOption, }, { - name: this.props.intl.formatMessage({ + name: props.intl.formatMessage({ id: 'data.filter.options.disableAllFiltersButtonLabel', defaultMessage: 'Disable all', }), icon: 'eyeClosed', onClick: () => { - this.closePopover(); - this.props.onDisableAll(); + closePopover(); + onDisableAll(); }, 'data-test-subj': 'disableAllFilters', + disabled: disableMenuOption, }, { - name: this.props.intl.formatMessage({ + name: props.intl.formatMessage({ id: 'data.filter.options.pinAllFiltersButtonLabel', defaultMessage: 'Pin all', }), icon: 'pin', onClick: () => { - this.closePopover(); - this.props.onPinAll(); + closePopover(); + onPinAll(); }, 'data-test-subj': 'pinAllFilters', + disabled: disableMenuOption, }, { - name: this.props.intl.formatMessage({ + name: props.intl.formatMessage({ id: 'data.filter.options.unpinAllFiltersButtonLabel', defaultMessage: 'Unpin all', }), icon: 'pin', onClick: () => { - this.closePopover(); - this.props.onUnpinAll(); + closePopover(); + onUnpinAll(); }, 'data-test-subj': 'unpinAllFilters', + disabled: disableMenuOption, }, { - name: this.props.intl.formatMessage({ + name: props.intl.formatMessage({ id: 'data.filter.options.invertNegatedFiltersButtonLabel', defaultMessage: 'Invert inclusion', }), icon: 'invert', onClick: () => { - this.closePopover(); - this.props.onToggleAllNegated(); + closePopover(); + onToggleAllNegated(); }, 'data-test-subj': 'invertInclusionAllFilters', + disabled: disableMenuOption, }, { - name: this.props.intl.formatMessage({ + name: props.intl.formatMessage({ id: 'data.filter.options.invertDisabledFiltersButtonLabel', defaultMessage: 'Invert enabled/disabled', }), icon: 'eye', onClick: () => { - this.closePopover(); - this.props.onToggleAllDisabled(); + closePopover(); + onToggleAllDisabled(); }, 'data-test-subj': 'invertEnableDisableAllFilters', + disabled: disableMenuOption, }, { - name: this.props.intl.formatMessage({ + name: props.intl.formatMessage({ id: 'data.filter.options.deleteAllFiltersButtonLabel', defaultMessage: 'Remove all', }), icon: 'trash', onClick: () => { - this.closePopover(); - this.props.onRemoveAll(); + closePopover(); + onRemoveAll(); }, 'data-test-subj': 'removeAllFilters', + disabled: disableMenuOption, + className: useNewHeader ? 'globalFilterGroup__removeAllFilters' : '', }, ], - }; - - return ( - - - - } - anchorPosition="rightUp" - panelPaddingSize="none" - repositionOnScroll - > - - - - - - ); + }, + ]; + + const handleSave = () => { + if (props.onSave) { + props.onSave(); + } + setIsPopoverOpen(false); + }; + + const saveQueryPanel = ( + { + setIsPopoverOpen(false); + }} + key={'savedQueryManagement'} + />, + ]} + data-test-subj="save-query-panel" + /> + ); + + const menuPanel = ( + + ); + const addFilterPanel = ( + + {(resizeRef) => ( +
+ + setIsPopoverOpen(false)} + key={stringify(newFilter)} + /> + +
+ )} + , + ]} + data-test-subj="add-filter-panel" + /> + ); + const renderComponent = () => { + switch (renderedComponent) { + case 'menu': + return menuPanel; + case 'addFilter': + return addFilterPanel; + case 'saveQuery': + return saveQueryPanel; + } + }; + + if (useNewHeader) { + panelTree[0].items.unshift(addFilterPanelItem); } -} + + const label = i18n.translate('data.search.searchBar.savedQueryPopoverButtonText', { + defaultMessage: 'See saved queries', + }); + + const savedQueryPopoverButton = ( + + + + ); + + const filterPopoverButton = ( + + + + {useNewHeader && } + + + ); + + return ( + + {useNewHeader ? renderComponent() : props.useSaveQueryMenu ? saveQueryPanel : menuPanel} + {useNewHeader && showSaveQueryButton && ( + + + + { + setRenderedComponent('saveQuery'); + setShowSaveQueryButton(false); + }} + > + {i18n.translate('data.search.searchBar.savedQueryPopoverSaveButtonText', { + defaultMessage: 'Save query', + })} + + + + + )} + + ); +}; export const FilterOptions = injectI18n(FilterOptionsUI); diff --git a/src/plugins/data/public/ui/saved_query_management/__snapshots__/saved_query_management_component.test.tsx.snap b/src/plugins/data/public/ui/saved_query_management/__snapshots__/saved_query_management_component.test.tsx.snap deleted file mode 100644 index 4656759e60de..000000000000 --- a/src/plugins/data/public/ui/saved_query_management/__snapshots__/saved_query_management_component.test.tsx.snap +++ /dev/null @@ -1,68 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Saved query management component has a popover button 1`] = ` - - - - } - closePopover={[Function]} - display="inlineBlock" - hasArrow={true} - id="savedQueryPopover" - isOpen={false} - ownFocus={true} - panelPaddingSize="none" - repositionOnScroll={true} -> -
- - Saved Queries - - -

- There are no saved queries. Save query text and filters that you want to use again. -

-
- - - - - - - -
-
-`; diff --git a/src/plugins/data/public/ui/saved_query_management/saved_query_management_component.test.tsx b/src/plugins/data/public/ui/saved_query_management/saved_query_management_component.test.tsx index a7cdb6c57bbb..8b92609d419a 100644 --- a/src/plugins/data/public/ui/saved_query_management/saved_query_management_component.test.tsx +++ b/src/plugins/data/public/ui/saved_query_management/saved_query_management_component.test.tsx @@ -6,6 +6,8 @@ import React from 'react'; import { SavedQueryManagementComponent } from './saved_query_management_component'; import { shallowWithIntl } from 'test_utils/enzyme_helpers'; +import { Query } from 'src/plugins/data/common'; +import { SavedQueryAttributes } from '../../query'; const mockProps = () => ({ savedQueryService: { @@ -20,15 +22,54 @@ const mockProps = () => ({ onSaveAsNew: jest.fn(), onLoad: jest.fn(), onClearSavedQuery: jest.fn(), + closeMenuPopover: jest.fn(), + showSaveQuery: true, + loadedSavedQuery: { + id: '1', + attributes: { + name: 'Test Query', + title: '', + description: '', + query: { query: '', language: 'kuery' } as Query, + } as SavedQueryAttributes, + }, }); describe('Saved query management component', () => { - it('has a popover button', () => { - const props = { - ...mockProps(), - }; - const component = shallowWithIntl(); - const savedQueryPopoverButton = component.find('#savedQueryPopover'); - expect(savedQueryPopoverButton).toMatchSnapshot(); + it('should render without errors', () => { + const props = mockProps(); + const wrapper = shallowWithIntl(); + expect(wrapper.exists()).toBe(true); + }); + + it('should call onSave when save button is clicked', () => { + const props = mockProps(); + const wrapper = shallowWithIntl(); + const saveButton = wrapper + .find('[data-test-subj="saved-query-management-save-changes-button"]') + .at(0); + saveButton.simulate('click'); + expect(props.onSave).toHaveBeenCalled(); + expect(props.closeMenuPopover).toHaveBeenCalled(); + }); + + it('should call onSaveAsNew when save as new button is clicked', () => { + const props = mockProps(); + const wrapper = shallowWithIntl(); + const saveAsNewButton = wrapper + .find('[data-test-subj="saved-query-management-save-as-new-button"]') + .at(0); + saveAsNewButton.simulate('click'); + expect(props.onSaveAsNew).toHaveBeenCalled(); + }); + + it('should call onClearSavedQuery when clear saved query button is clicked', () => { + const props = mockProps(); + const wrapper = shallowWithIntl(); + const clearSavedQueryButton = wrapper.find( + '[data-test-subj="saved-query-management-clear-button"]' + ); + clearSavedQueryButton.simulate('click'); + expect(props.onClearSavedQuery).toHaveBeenCalled(); }); }); diff --git a/src/plugins/data/public/ui/saved_query_management/saved_query_management_component.tsx b/src/plugins/data/public/ui/saved_query_management/saved_query_management_component.tsx index 8504ec858b0d..3048554fe2fc 100644 --- a/src/plugins/data/public/ui/saved_query_management/saved_query_management_component.tsx +++ b/src/plugins/data/public/ui/saved_query_management/saved_query_management_component.tsx @@ -29,10 +29,8 @@ */ import { - EuiPopover, EuiPopoverTitle, EuiPopoverFooter, - EuiSmallButtonEmpty, EuiButtonEmpty, EuiButton, EuiFlexGroup, @@ -41,7 +39,6 @@ import { EuiPagination, EuiText, EuiSpacer, - EuiIcon, } from '@elastic/eui'; import { i18n } from '@osd/i18n'; @@ -59,6 +56,7 @@ interface Props { onSaveAsNew: () => void; onLoad: (savedQuery: SavedQuery) => void; onClearSavedQuery: () => void; + closeMenuPopover: () => void; } export function SavedQueryManagementComponent({ @@ -69,8 +67,8 @@ export function SavedQueryManagementComponent({ onLoad, onClearSavedQuery, savedQueryService, + closeMenuPopover, }: Props) { - const [isOpen, setIsOpen] = useState(false); const [savedQueries, setSavedQueries] = useState([] as SavedQuery[]); const [count, setTotalCount] = useState(0); const [activePage, setActivePage] = useState(0); @@ -95,16 +93,12 @@ export function SavedQueryManagementComponent({ setTotalCount(savedQueryCount); setSavedQueries(sortedSavedQueryItems); }; - if (isOpen) { - fetchCountAndSavedQueries(); - } - }, [isOpen, activePage, savedQueryService]); - - const handleTogglePopover = useCallback(() => setIsOpen((currentState) => !currentState), [ - setIsOpen, - ]); + fetchCountAndSavedQueries(); + }, [activePage, savedQueryService]); - const handleClosePopover = useCallback(() => setIsOpen(false), []); + const handleClosePopover = useCallback(() => { + closeMenuPopover(); + }, [closeMenuPopover]); const handleSave = useCallback(() => { handleClosePopover(); @@ -170,21 +164,6 @@ export function SavedQueryManagementComponent({ const goToPage = (pageNumber: number) => { setActivePage(pageNumber); }; - const label = i18n.translate('data.search.searchBar.savedQueryPopoverButtonText', { - defaultMessage: 'See saved queries', - }); - - const savedQueryPopoverButton = ( - - - - ); const savedQueryRows = () => { const savedQueriesWithoutCurrent = savedQueries.filter((savedQuery) => { @@ -208,150 +187,130 @@ export function SavedQueryManagementComponent({ }; return ( - - -
+ + {savedQueryPopoverTitleText} + + {savedQueries.length > 0 ? ( + + +

{savedQueryDescriptionText}

+
+
+ + {savedQueryRows()} + +
+ +
+ ) : ( + + +

{noSavedQueriesDescriptionText}

+
+ +
+ )} + + - - {savedQueryPopoverTitleText} - - {savedQueries.length > 0 ? ( + {showSaveQuery && loadedSavedQuery && ( - -

{savedQueryDescriptionText}

-
-
- + - {savedQueryRows()} - -
- -
- ) : ( - - -

{noSavedQueriesDescriptionText}

-
- + {i18n.translate('data.search.searchBar.savedQueryPopoverSaveChangesButtonText', { + defaultMessage: 'Save changes', + })} + + + + + {i18n.translate('data.search.searchBar.savedQueryPopoverSaveAsNewButtonText', { + defaultMessage: 'Save as new', + })} + +
)} - - - {showSaveQuery && loadedSavedQuery && ( - - - - {i18n.translate( - 'data.search.searchBar.savedQueryPopoverSaveChangesButtonText', - { - defaultMessage: 'Save changes', - } - )} - - - - - {i18n.translate( - 'data.search.searchBar.savedQueryPopoverSaveAsNewButtonText', - { - defaultMessage: 'Save as new', - } - )} - - - - )} - {showSaveQuery && !loadedSavedQuery && ( - - - {i18n.translate('data.search.searchBar.savedQueryPopoverSaveButtonText', { - defaultMessage: 'Save current query', - })} - - - )} - - - {loadedSavedQuery && ( - - {i18n.translate('data.search.searchBar.savedQueryPopoverClearButtonText', { - defaultMessage: 'Clear', - })} - + {showSaveQuery && !loadedSavedQuery && ( + + - - -
-
-
+ data-test-subj="saved-query-management-save-button" + > + {i18n.translate('data.search.searchBar.savedQueryPopoverSaveButtonText', { + defaultMessage: 'Save current query', + })} + +
+ )} + + + {loadedSavedQuery && ( + + {i18n.translate('data.search.searchBar.savedQueryPopoverClearButtonText', { + defaultMessage: 'Clear', + })} + + )} + +
+ + ); } diff --git a/src/plugins/data/public/ui/search_bar/search_bar.tsx b/src/plugins/data/public/ui/search_bar/search_bar.tsx index 3a83af415f59..3db269074015 100644 --- a/src/plugins/data/public/ui/search_bar/search_bar.tsx +++ b/src/plugins/data/public/ui/search_bar/search_bar.tsx @@ -33,7 +33,6 @@ import classNames from 'classnames'; import { compact, get, isEqual } from 'lodash'; import React, { Component } from 'react'; import ResizeObserver from 'resize-observer-polyfill'; -import { DataSource } from '../..'; import { OpenSearchDashboardsReactContextValue, withOpenSearchDashboards, @@ -45,8 +44,8 @@ import { FilterBar } from '../filter_bar/filter_bar'; import { QueryEditorTopRow } from '../query_editor'; import QueryBarTopRow from '../query_string_input/query_bar_top_row'; import { SavedQueryMeta, SaveQueryForm } from '../saved_query_form'; -import { SavedQueryManagementComponent } from '../saved_query_management'; import { Settings } from '../types'; +import { FilterOptions } from '../filter_bar/filter_options'; interface SearchBarInjectedDeps { opensearchDashboards: OpenSearchDashboardsReactContextValue; @@ -123,6 +122,7 @@ class SearchBarUI extends Component { private savedQueryService = this.services.data.query.savedQueries; public filterBarRef: Element | null = null; public filterBarWrapperRef: Element | null = null; + private useNewHeader = Boolean(this.services.uiSettings.get(UI_SETTINGS.NEW_HOME_PAGE)); public static getDerivedStateFromProps(nextProps: SearchBarProps, prevState: State) { if (isEqual(prevState.currentProps, nextProps)) { @@ -233,6 +233,7 @@ class SearchBarUI extends Component { return ( this.props.showFilterBar && this.props.filters && + (!this.useNewHeader || this.props.filters.length > 0) && this.props.indexPatterns && compact(this.props.indexPatterns).length > 0 && (this.props.settings?.getQueryEnhancements(this.state.query?.language!)?.searchBar @@ -412,17 +413,27 @@ class SearchBarUI extends Component { ); this.props.settings?.setUserQueryEnhancementsEnabled(isEnhancementsEnabledOverride); - const savedQueryManagement = this.state.query && this.props.onClearSavedQuery && ( - - ); + const searchBarMenu = (useSaveQueryMenu: boolean = false) => { + return ( + this.state.query && + this.props.onClearSavedQuery && ( + + ) + ); + }; let filterBar; if (this.shouldRenderFilterBar()) { @@ -464,7 +475,7 @@ class SearchBarUI extends Component { onSubmit={this.onQueryBarSubmit} indexPatterns={this.props.indexPatterns} isLoading={this.props.isLoading} - prepend={this.props.showFilterBar ? savedQueryManagement : undefined} + prepend={this.props.showFilterBar ? searchBarMenu(!this.useNewHeader) : undefined} showDatePicker={this.props.showDatePicker} dateRangeFrom={this.state.dateRangeFrom} dateRangeTo={this.state.dateRangeTo} @@ -498,7 +509,7 @@ class SearchBarUI extends Component { onSubmit={this.onQueryBarSubmit} indexPatterns={this.props.indexPatterns} isLoading={this.props.isLoading} - prepend={this.props.showFilterBar ? savedQueryManagement : undefined} + prepend={this.props.showFilterBar ? searchBarMenu(!this.useNewHeader) : undefined} showDatePicker={this.props.showDatePicker} dateRangeFrom={this.state.dateRangeFrom} dateRangeTo={this.state.dateRangeTo}