Skip to content

Commit

Permalink
[Serverless] New UX for Global Search
Browse files Browse the repository at this point in the history
  • Loading branch information
tsullivan committed Jun 15, 2023
1 parent 74ae6d1 commit 9243ce4
Show file tree
Hide file tree
Showing 6 changed files with 131 additions and 79 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -52,6 +53,7 @@ describe('SearchBar', () => {

const basePathUrl = '/plugins/globalSearchBar/assets/';
const darkMode = false;
const chromeStyle$ = new BehaviorSubject<ChromeStyle>('classic');

beforeEach(() => {
applications = applicationServiceMock.createStartContract();
Expand Down Expand Up @@ -104,6 +106,7 @@ describe('SearchBar', () => {
navigateToUrl={applications.navigateToUrl}
basePathUrl={basePathUrl}
darkMode={darkMode}
chromeStyle$={chromeStyle$}
trackUiMetric={trackUiMetric}
/>
</IntlProvider>
Expand Down Expand Up @@ -136,6 +139,7 @@ describe('SearchBar', () => {
navigateToUrl={applications.navigateToUrl}
basePathUrl={basePathUrl}
darkMode={darkMode}
chromeStyle$={chromeStyle$}
trackUiMetric={trackUiMetric}
/>
</IntlProvider>
Expand Down Expand Up @@ -172,6 +176,7 @@ describe('SearchBar', () => {
navigateToUrl={applications.navigateToUrl}
basePathUrl={basePathUrl}
darkMode={darkMode}
chromeStyle$={chromeStyle$}
trackUiMetric={trackUiMetric}
/>
</IntlProvider>
Expand Down Expand Up @@ -203,6 +208,7 @@ describe('SearchBar', () => {
navigateToUrl={applications.navigateToUrl}
basePathUrl={basePathUrl}
darkMode={darkMode}
chromeStyle$={chromeStyle$}
trackUiMetric={trackUiMetric}
/>
</IntlProvider>
Expand All @@ -229,3 +235,5 @@ describe('SearchBar', () => {
expect(trackUiMetric).toHaveBeenCalledTimes(2);
});
});

// FIXME: add tests for chromeStyle === 'project'
78 changes: 67 additions & 11 deletions x-pack/plugins/global_search_bar/public/components/search_bar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
*/

import {
EuiButtonIcon,
EuiFlexGroup,
EuiFlexItem,
EuiFormLabel,
Expand All @@ -22,7 +23,9 @@ import type { GlobalSearchFindParams, GlobalSearchResult } from '@kbn/global-sea
import React, { FC, useCallback, useEffect, useRef, useState } from 'react';
import useDebounce from 'react-use/lib/useDebounce';
import useEvent from 'react-use/lib/useEvent';
import useLocalStorage from 'react-use/lib/useLocalStorage';
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';
Expand All @@ -46,15 +49,25 @@ const EmptyMessage = () => (
</EuiFlexGroup>
);

const GLOBAL_SEARCH_BAR_VISIBLE_KEY = 'GLOBAL_SEARCH_BAR_VISIBLE' as const;

export const SearchBar: FC<SearchBarProps> = ({
globalSearch,
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] = useLocalStorage(GLOBAL_SEARCH_BAR_VISIBLE_KEY, false);
const visibilityButtonRef = useRef<HTMLButtonElement | null>(null);

// General hooks
const [initialLoad, setInitialLoad] = useState(false);
const [searchValue, setSearchValue] = useState<string>('');
const [searchTerm, setSearchTerm] = useState<string>('');
Expand Down Expand Up @@ -178,14 +191,16 @@ export const SearchBar: FC<SearchBarProps> = ({
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(
Expand Down Expand Up @@ -244,6 +259,47 @@ export const SearchBar: FC<SearchBarProps> = ({

useEvent('keydown', onKeyDown);

if (chromeStyle === 'project' && !isVisible) {
const onShowSearch = () => {
setIsVisible(true);
};
return (
<EuiButtonIcon
buttonRef={visibilityButtonRef}
aria-label={i18nStrings.showSearchAriaText}
iconType="search"
color="text"
onClick={onShowSearch}
/>
);
}

const getAppendForChromeStyle = () => {
if (chromeStyle === 'project') {
return (
<EuiButtonIcon
aria-label={i18nStrings.closeSearchAriaText}
iconType="cross"
color="text"
onClick={() => {
setIsVisible(false);
}}
/>
);
}

if (showAppend) {
return (
<EuiFormLabel
title={keyboardShortcutTooltip}
css={{ fontFamily: euiTheme.font.familyCode }}
>
{isMac ? '⌘/' : '^/'}
</EuiFormLabel>
);
}
};

return (
<EuiSelectableTemplateSitewide
isPreFiltered
Expand All @@ -257,7 +313,14 @@ export const SearchBar: FC<SearchBarProps> = ({
value: searchValue,
onInput: (e: React.UIEvent<HTMLInputElement>) => setSearchValue(e.currentTarget.value),
'data-test-subj': 'nav-search-input',
inputRef: setSearchRef,
inputRef: (input) => {
setSearchRef(input);
if (chromeStyle === 'project' && input) {
// while the input ref is in flight, we set focus on it
// to autofocus the input when it appears
input.focus();
}
},
compressed: true,
'aria-label': i18nStrings.placeholderText,
placeholder: i18nStrings.placeholderText,
Expand All @@ -270,14 +333,7 @@ export const SearchBar: FC<SearchBarProps> = ({
setShowAppend(!searchValue.length);
},
fullWidth: true,
append: showAppend ? (
<EuiFormLabel
title={keyboardShortcutTooltip}
css={{ fontFamily: euiTheme.font.familyCode }}
>
{isMac ? '⌘/' : '^/'}
</EuiFormLabel>
) : undefined,
append: getAppendForChromeStyle(),
}}
emptyMessage={<EmptyMessage />}
noMatchesMessage={<NoMatchesMessage {...props} />}
Expand Down
3 changes: 3 additions & 0 deletions x-pack/plugins/global_search_bar/public/components/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand All @@ -18,4 +20,5 @@ export interface SearchBarProps {
taggingApi?: SavedObjectTaggingPluginStart;
basePathUrl: string;
darkMode: boolean;
chromeStyle$: Observable<ChromeStyle>;
}
100 changes: 39 additions & 61 deletions x-pack/plugins/global_search_bar/public/plugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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<CoreTheme>;
trackUiMetric: TrackUiMetricFn;
}) {
ReactDOM.render(
<KibanaThemeProvider theme$={theme$}>
<I18nProvider>
<SearchBar
globalSearch={globalSearch}
navigateToUrl={navigateToUrl}
taggingApi={savedObjectsTagging}
basePathUrl={basePathUrl}
darkMode={darkMode}
trackUiMetric={trackUiMetric}
/>
</I18nProvider>
</KibanaThemeProvider>,
container
);
mount: (container) => {
ReactDOM.render(
<KibanaThemeProvider theme$={theme.theme$}>
<I18nProvider>
<SearchBar
globalSearch={globalSearch}
navigateToUrl={application.navigateToUrl}
taggingApi={savedObjectsTagging}
basePathUrl={http.basePath.prepend('/plugins/globalSearchBar/assets/')}
darkMode={uiSettings.get('theme:darkMode')}
chromeStyle$={core.chrome.getChromeStyle$()}
trackUiMetric={trackUiMetric}
/>
</I18nProvider>
</KibanaThemeProvider>,
container
);

return () => ReactDOM.unmountComponentAtNode(container);
return () => ReactDOM.unmountComponentAtNode(container);
},
};
return navControl;
}
}
6 changes: 6 additions & 0 deletions x-pack/plugins/global_search_bar/public/strings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/global_search_bar/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"@kbn/kibana-react-plugin",
"@kbn/i18n",
"@kbn/saved-objects-tagging-oss-plugin",
"@kbn/core-chrome-browser",
],
"exclude": [
"target/**/*",
Expand Down

0 comments on commit 9243ce4

Please sign in to comment.