diff --git a/x-pack/plugins/global_search_bar/public/components/search_bar.test.tsx b/x-pack/plugins/global_search_bar/public/components/search_bar.test.tsx
index dca9d00f4ca29..aa5d42ad2a0d7 100644
--- a/x-pack/plugins/global_search_bar/public/components/search_bar.test.tsx
+++ b/x-pack/plugins/global_search_bar/public/components/search_bar.test.tsx
@@ -5,16 +5,17 @@
* 2.0.
*/
-import React from 'react';
-import { act, render, screen, fireEvent } from '@testing-library/react';
-import { of, BehaviorSubject } from 'rxjs';
-import { filter, map } from 'rxjs/operators';
+import type { ChromeStyle } from '@kbn/core-chrome-browser';
import { applicationServiceMock } from '@kbn/core/public/mocks';
-import { globalSearchPluginMock } from '@kbn/global-search-plugin/public/mocks';
import { GlobalSearchBatchedResults, GlobalSearchResult } from '@kbn/global-search-plugin/public';
-import { SearchBar } from './search_bar';
+import { globalSearchPluginMock } from '@kbn/global-search-plugin/public/mocks';
import { __IntlProvider as IntlProvider } from '@kbn/i18n-react';
-import { TrackUiMetricFn } from '../types';
+import { act, fireEvent, render, screen } from '@testing-library/react';
+import React from 'react';
+import { BehaviorSubject, of } from 'rxjs';
+import { filter, map } from 'rxjs/operators';
+import type { TrackUiMetricFn } from '../types';
+import { SearchBar } from './search_bar';
jest.mock(
'react-virtualized-auto-sizer',
@@ -87,145 +88,210 @@ describe('SearchBar', () => {
expect(await screen.findAllByTestId('nav-search-option')).toHaveLength(list.length);
};
- it('correctly filters and sorts results', async () => {
- searchService.find
- .mockReturnValueOnce(
- of(
- createBatch('Discover', 'Canvas'),
- createBatch({ id: 'Visualize', type: 'test' }, 'Graph')
- )
- )
- .mockReturnValueOnce(of(createBatch('Discover', { id: 'My Dashboard', type: 'test' })));
-
- render(
-
-
-
- );
-
- expect(searchService.find).toHaveBeenCalledTimes(0);
-
- await focusAndUpdate();
-
- expect(searchService.find).toHaveBeenCalledTimes(1);
- expect(searchService.find).toHaveBeenCalledWith({}, {});
- await assertSearchResults(['Canvas • Kibana', 'Discover • Kibana', 'Graph • Kibana']);
-
- simulateTypeChar('d');
-
- await assertSearchResults(['Discover • Kibana', 'My Dashboard • Test']);
- expect(searchService.find).toHaveBeenCalledTimes(2);
- expect(searchService.find).toHaveBeenLastCalledWith({ term: 'd' }, {});
-
- expect(trackUiMetric).nthCalledWith(1, 'count', 'search_focus');
- expect(trackUiMetric).nthCalledWith(2, 'count', 'search_request');
- expect(trackUiMetric).toHaveBeenCalledTimes(2);
- });
+ describe('chromeStyle: classic', () => {
+ const chromeStyle$ = of('classic');
- it('supports keyboard shortcuts', async () => {
- render(
-
-
-
- );
- act(() => {
- fireEvent.keyDown(window, { key: '/', ctrlKey: true, metaKey: true });
+ it('correctly filters and sorts results', async () => {
+ searchService.find
+ .mockReturnValueOnce(
+ of(
+ createBatch('Discover', 'Canvas'),
+ createBatch({ id: 'Visualize', type: 'test' }, 'Graph')
+ )
+ )
+ .mockReturnValueOnce(of(createBatch('Discover', { id: 'My Dashboard', type: 'test' })));
+
+ render(
+
+
+
+ );
+
+ expect(searchService.find).toHaveBeenCalledTimes(0);
+
+ await focusAndUpdate();
+
+ expect(searchService.find).toHaveBeenCalledTimes(1);
+ expect(searchService.find).toHaveBeenCalledWith({}, {});
+ await assertSearchResults(['Canvas • Kibana', 'Discover • Kibana', 'Graph • Kibana']);
+
+ simulateTypeChar('d');
+
+ await assertSearchResults(['Discover • Kibana', 'My Dashboard • Test']);
+ expect(searchService.find).toHaveBeenCalledTimes(2);
+ expect(searchService.find).toHaveBeenLastCalledWith({ term: 'd' }, {});
+
+ expect(trackUiMetric).nthCalledWith(1, 'count', 'search_focus');
+ expect(trackUiMetric).nthCalledWith(2, 'count', 'search_request');
+ expect(trackUiMetric).toHaveBeenCalledTimes(2);
});
- const inputElement = await screen.findByTestId('nav-search-input');
-
- expect(document.activeElement).toEqual(inputElement);
+ it('supports keyboard shortcuts', async () => {
+ render(
+
+
+
+ );
+ act(() => {
+ fireEvent.keyDown(window, { key: '/', ctrlKey: true, metaKey: true });
+ });
+
+ const inputElement = await screen.findByTestId('nav-search-input');
+
+ expect(document.activeElement).toEqual(inputElement);
+
+ expect(trackUiMetric).nthCalledWith(1, 'count', 'shortcut_used');
+ expect(trackUiMetric).nthCalledWith(2, 'count', 'search_focus');
+ expect(trackUiMetric).toHaveBeenCalledTimes(2);
+ });
- expect(trackUiMetric).nthCalledWith(1, 'count', 'shortcut_used');
- expect(trackUiMetric).nthCalledWith(2, 'count', 'search_focus');
- expect(trackUiMetric).toHaveBeenCalledTimes(2);
- });
+ it('only display results from the last search', async () => {
+ const firstSearchTrigger = new BehaviorSubject(false);
+ const firstSearch = firstSearchTrigger.pipe(
+ filter((event) => event),
+ map(() => {
+ return createBatch('Discover', 'Canvas');
+ })
+ );
+ const secondSearch = of(createBatch('Visualize', 'Map'));
+
+ searchService.find.mockReturnValueOnce(firstSearch).mockReturnValueOnce(secondSearch);
+
+ render(
+
+
+
+ );
+
+ await focusAndUpdate();
+
+ expect(searchService.find).toHaveBeenCalledTimes(1);
+ //
+ simulateTypeChar('d');
+ await assertSearchResults(['Visualize • Kibana', 'Map • Kibana']);
+
+ firstSearchTrigger.next(true);
+
+ update();
+
+ await assertSearchResults(['Visualize • Kibana', 'Map • Kibana']);
+ });
- it('only display results from the last search', async () => {
- const firstSearchTrigger = new BehaviorSubject(false);
- const firstSearch = firstSearchTrigger.pipe(
- filter((event) => event),
- map(() => {
- return createBatch('Discover', 'Canvas');
- })
- );
- const secondSearch = of(createBatch('Visualize', 'Map'));
-
- searchService.find.mockReturnValueOnce(firstSearch).mockReturnValueOnce(secondSearch);
-
- render(
-
-
-
- );
-
- await focusAndUpdate();
-
- expect(searchService.find).toHaveBeenCalledTimes(1);
- //
- simulateTypeChar('d');
- await assertSearchResults(['Visualize • Kibana', 'Map • Kibana']);
-
- firstSearchTrigger.next(true);
-
- update();
-
- await assertSearchResults(['Visualize • Kibana', 'Map • Kibana']);
+ it('tracks the application navigated to', async () => {
+ searchService.find.mockReturnValueOnce(
+ of(createBatch('Discover', { id: 'My Dashboard', type: 'test' }))
+ );
+
+ render(
+
+
+
+ );
+
+ expect(searchService.find).toHaveBeenCalledTimes(0);
+
+ await focusAndUpdate();
+
+ expect(searchService.find).toHaveBeenCalledTimes(1);
+ expect(searchService.find).toHaveBeenCalledWith({}, {});
+ await assertSearchResults(['Discover • Kibana']);
+
+ const navSearchOptionToClick = await screen.findByTestId('nav-search-option');
+ act(() => {
+ fireEvent.click(navSearchOptionToClick);
+ });
+
+ expect(trackUiMetric).nthCalledWith(1, 'count', 'search_focus');
+ expect(trackUiMetric).nthCalledWith(2, 'click', [
+ 'user_navigated_to_application',
+ 'user_navigated_to_application_discover',
+ ]);
+ expect(trackUiMetric).toHaveBeenCalledTimes(2);
+ });
});
- it('tracks the application navigated to', async () => {
- searchService.find.mockReturnValueOnce(
- of(createBatch('Discover', { id: 'My Dashboard', type: 'test' }))
- );
-
- render(
-
-
-
- );
-
- expect(searchService.find).toHaveBeenCalledTimes(0);
-
- await focusAndUpdate();
-
- expect(searchService.find).toHaveBeenCalledTimes(1);
- expect(searchService.find).toHaveBeenCalledWith({}, {});
- await assertSearchResults(['Discover • Kibana']);
-
- const navSearchOptionToClick = await screen.findByTestId('nav-search-option');
- act(() => {
- fireEvent.click(navSearchOptionToClick);
+ describe('chromeStyle: project', () => {
+ const chromeStyle$ = of('project');
+
+ it('supports keyboard shortcuts', async () => {
+ render(
+
+
+
+ );
+
+ act(() => {
+ fireEvent.keyDown(window, { key: '/', ctrlKey: true, metaKey: true });
+ });
+
+ const inputElement = await screen.findByTestId('nav-search-input');
+
+ expect(document.activeElement).toEqual(inputElement);
+
+ fireEvent.click(await screen.findByTestId('nav-search-conceal'));
+ expect(screen.queryAllByTestId('nav-search-input')).toHaveLength(0);
+
+ expect(trackUiMetric).nthCalledWith(1, 'count', 'shortcut_used');
+ expect(trackUiMetric).nthCalledWith(2, 'count', 'search_focus');
+ expect(trackUiMetric).toHaveBeenCalledTimes(2);
});
- expect(trackUiMetric).nthCalledWith(1, 'count', 'search_focus');
- expect(trackUiMetric).nthCalledWith(2, 'click', [
- 'user_navigated_to_application',
- 'user_navigated_to_application_discover',
- ]);
- expect(trackUiMetric).toHaveBeenCalledTimes(2);
+ it('supports show/hide', async () => {
+ render(
+
+
+
+ );
+
+ fireEvent.click(await screen.findByTestId('nav-search-reveal'));
+ expect(await screen.findByTestId('nav-search-input')).toBeVisible();
+
+ fireEvent.click(await screen.findByTestId('nav-search-conceal'));
+ expect(screen.queryAllByTestId('nav-search-input')).toHaveLength(0);
+
+ expect(trackUiMetric).nthCalledWith(1, 'count', 'search_focus');
+ });
});
});
diff --git a/x-pack/plugins/global_search_bar/public/components/search_bar.tsx b/x-pack/plugins/global_search_bar/public/components/search_bar.tsx
index f454359e636df..72659515425c5 100644
--- a/x-pack/plugins/global_search_bar/public/components/search_bar.tsx
+++ b/x-pack/plugins/global_search_bar/public/components/search_bar.tsx
@@ -6,6 +6,7 @@
*/
import {
+ EuiButtonIcon,
EuiFlexGroup,
EuiFlexItem,
EuiFormLabel,
@@ -23,6 +24,7 @@ import React, { FC, useCallback, useEffect, useRef, useState } from 'react';
import useDebounce from 'react-use/lib/useDebounce';
import useEvent from 'react-use/lib/useEvent';
import useMountedState from 'react-use/lib/useMountedState';
+import useObservable from 'react-use/lib/useObservable';
import { Subscription } from 'rxjs';
import { blurEvent, CLICK_METRIC, COUNT_METRIC, getClickMetric, isMac, sort } from '.';
import { resultToOption, suggestionToOption } from '../lib';
@@ -51,10 +53,18 @@ export const SearchBar: FC = ({
taggingApi,
navigateToUrl,
trackUiMetric,
+ chromeStyle$,
...props
}) => {
const isMounted = useMountedState();
const { euiTheme } = useEuiTheme();
+ const chromeStyle = useObservable(chromeStyle$);
+
+ // These hooks are used when on chromeStyle set to 'project'
+ const [isVisible, setIsVisible] = useState(false);
+ const visibilityButtonRef = useRef(null);
+
+ // General hooks
const [initialLoad, setInitialLoad] = useState(false);
const [searchValue, setSearchValue] = useState('');
const [searchTerm, setSearchTerm] = useState('');
@@ -178,14 +188,16 @@ export const SearchBar: FC = ({
if (event.key === '/' && (isMac ? event.metaKey : event.ctrlKey)) {
event.preventDefault();
trackUiMetric(METRIC_TYPE.COUNT, COUNT_METRIC.SHORTCUT_USED);
- if (searchRef) {
+ if (chromeStyle === 'project' && !isVisible) {
+ visibilityButtonRef.current?.click();
+ } else if (searchRef) {
searchRef.focus();
} else if (buttonRef) {
(buttonRef.children[0] as HTMLButtonElement).click();
}
}
},
- [buttonRef, searchRef, trackUiMetric]
+ [chromeStyle, isVisible, buttonRef, searchRef, trackUiMetric]
);
const onChange = useCallback(
@@ -244,6 +256,49 @@ export const SearchBar: FC = ({
useEvent('keydown', onKeyDown);
+ if (chromeStyle === 'project' && !isVisible) {
+ const onShowSearch = () => {
+ setIsVisible(true);
+ };
+ return (
+
+ );
+ }
+
+ const getAppendForChromeStyle = () => {
+ if (chromeStyle === 'project') {
+ return (
+ {
+ setIsVisible(false);
+ }}
+ />
+ );
+ }
+
+ if (showAppend) {
+ return (
+
+ {isMac ? '⌘/' : '^/'}
+
+ );
+ }
+ };
+
return (
= ({
singleSelection={true}
renderOption={(option) => euiSelectableTemplateSitewideRenderOptions(option, searchTerm)}
searchProps={{
+ autoFocus: chromeStyle === 'project',
value: searchValue,
onInput: (e: React.UIEvent) => setSearchValue(e.currentTarget.value),
'data-test-subj': 'nav-search-input',
@@ -270,14 +326,7 @@ export const SearchBar: FC = ({
setShowAppend(!searchValue.length);
},
fullWidth: true,
- append: showAppend ? (
-
- {isMac ? '⌘/' : '^/'}
-
- ) : undefined,
+ append: getAppendForChromeStyle(),
}}
emptyMessage={}
noMatchesMessage={}
diff --git a/x-pack/plugins/global_search_bar/public/components/types.ts b/x-pack/plugins/global_search_bar/public/components/types.ts
index 88546cda3f80f..968b904e3fa38 100644
--- a/x-pack/plugins/global_search_bar/public/components/types.ts
+++ b/x-pack/plugins/global_search_bar/public/components/types.ts
@@ -5,9 +5,11 @@
* 2.0.
*/
+import { ChromeStyle } from '@kbn/core-chrome-browser';
import type { ApplicationStart } from '@kbn/core/public';
import type { GlobalSearchPluginStart } from '@kbn/global-search-plugin/public';
import type { SavedObjectTaggingPluginStart } from '@kbn/saved-objects-tagging-plugin/public';
+import { Observable } from 'rxjs';
import { TrackUiMetricFn } from '../types';
/* @internal */
@@ -18,4 +20,5 @@ export interface SearchBarProps {
taggingApi?: SavedObjectTaggingPluginStart;
basePathUrl: string;
darkMode: boolean;
+ chromeStyle$: Observable;
}
diff --git a/x-pack/plugins/global_search_bar/public/plugin.tsx b/x-pack/plugins/global_search_bar/public/plugin.tsx
index 239ad78f67c6a..7be3d9227a99b 100644
--- a/x-pack/plugins/global_search_bar/public/plugin.tsx
+++ b/x-pack/plugins/global_search_bar/public/plugin.tsx
@@ -5,15 +5,14 @@
* 2.0.
*/
-import React from 'react';
-import ReactDOM from 'react-dom';
-import { Observable } from 'rxjs';
+import { ChromeNavControl, CoreStart, Plugin } from '@kbn/core/public';
+import { GlobalSearchPluginStart } from '@kbn/global-search-plugin/public';
import { I18nProvider } from '@kbn/i18n-react';
-import { ApplicationStart, CoreTheme, CoreStart, Plugin } from '@kbn/core/public';
import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
-import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public';
-import { GlobalSearchPluginStart } from '@kbn/global-search-plugin/public';
import { SavedObjectTaggingPluginStart } from '@kbn/saved-objects-tagging-plugin/public';
+import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public';
+import React from 'react';
+import ReactDOM from 'react-dom';
import { SearchBar } from './components/search_bar';
import { TrackUiMetricFn } from './types';
@@ -28,69 +27,48 @@ export class GlobalSearchBarPlugin implements Plugin<{}, {}> {
return {};
}
- public start(
- core: CoreStart,
- { globalSearch, savedObjectsTagging, usageCollection }: GlobalSearchBarPluginStartDeps
- ) {
+ public start(core: CoreStart, startDeps: GlobalSearchBarPluginStartDeps) {
+ core.chrome.navControls.registerCenter(this.getNavControl({ core, ...startDeps }));
+ return {};
+ }
+
+ private getNavControl(deps: { core: CoreStart } & GlobalSearchBarPluginStartDeps) {
+ const { core, globalSearch, savedObjectsTagging, usageCollection } = deps;
+ const { application, http, theme, uiSettings } = core;
+
let trackUiMetric: TrackUiMetricFn = () => {};
if (usageCollection) {
trackUiMetric = (...args) => {
+ // track UI Counter metrics
usageCollection.reportUiCounter('global_search_bar', ...args);
+
+ // TODO track EBT metrics using core.analytics
};
}
- core.chrome.navControls.registerCenter({
+ const navControl: ChromeNavControl = {
order: 1000,
- mount: (container) =>
- this.mount({
- container,
- globalSearch,
- savedObjectsTagging,
- navigateToUrl: core.application.navigateToUrl,
- basePathUrl: core.http.basePath.prepend('/plugins/globalSearchBar/assets/'),
- darkMode: core.uiSettings.get('theme:darkMode'),
- theme$: core.theme.theme$,
- trackUiMetric,
- }),
- });
- return {};
- }
-
- private mount({
- container,
- globalSearch,
- savedObjectsTagging,
- navigateToUrl,
- basePathUrl,
- darkMode,
- theme$,
- trackUiMetric,
- }: {
- container: HTMLElement;
- globalSearch: GlobalSearchPluginStart;
- savedObjectsTagging?: SavedObjectTaggingPluginStart;
- navigateToUrl: ApplicationStart['navigateToUrl'];
- basePathUrl: string;
- darkMode: boolean;
- theme$: Observable;
- trackUiMetric: TrackUiMetricFn;
- }) {
- ReactDOM.render(
-
-
-
-
- ,
- container
- );
+ mount: (container) => {
+ ReactDOM.render(
+
+
+
+
+ ,
+ container
+ );
- return () => ReactDOM.unmountComponentAtNode(container);
+ return () => ReactDOM.unmountComponentAtNode(container);
+ },
+ };
+ return navControl;
}
}
diff --git a/x-pack/plugins/global_search_bar/public/strings.ts b/x-pack/plugins/global_search_bar/public/strings.ts
index ce599ff3fefd5..c50d7f5792d88 100644
--- a/x-pack/plugins/global_search_bar/public/strings.ts
+++ b/x-pack/plugins/global_search_bar/public/strings.ts
@@ -14,6 +14,12 @@ export const i18nStrings = {
popoverButton: i18n.translate('xpack.globalSearchBar.searchBar.mobileSearchButtonAriaLabel', {
defaultMessage: 'Site-wide search',
}),
+ showSearchAriaText: i18n.translate('xpack.globalSearchBar.searchBar.showSearchAriaText', {
+ defaultMessage: 'Show search bar',
+ }),
+ closeSearchAriaText: i18n.translate('xpack.globalSearchBar.searchBar.closeSearchAriaText', {
+ defaultMessage: 'Close search bar',
+ }),
keyboardShortcutTooltip: {
prefix: i18n.translate('xpack.globalSearchBar.searchBar.shortcutTooltip.description', {
defaultMessage: 'Keyboard shortcut',
diff --git a/x-pack/plugins/global_search_bar/tsconfig.json b/x-pack/plugins/global_search_bar/tsconfig.json
index e8cc744d9a0df..daa4baf21a718 100644
--- a/x-pack/plugins/global_search_bar/tsconfig.json
+++ b/x-pack/plugins/global_search_bar/tsconfig.json
@@ -14,6 +14,7 @@
"@kbn/kibana-react-plugin",
"@kbn/i18n",
"@kbn/saved-objects-tagging-oss-plugin",
+ "@kbn/core-chrome-browser",
],
"exclude": [
"target/**/*",