mirror of
https://github.com/grafana/grafana.git
synced 2025-02-14 01:23:32 -06:00
Refactoring and simplifying TopSearchBar (#55341)
This commit is contained in:
parent
80c0dc890a
commit
40bc140a9a
@ -1,18 +1,25 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { i18n } from '@lingui/core';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import React from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
|
||||
import { GrafanaTheme2, NavModelItem } from '@grafana/data';
|
||||
import { Menu, MenuItem, useStyles2 } from '@grafana/ui';
|
||||
|
||||
import menuItemTranslations from '../../NavBar/navBarItem-translations';
|
||||
import { enrichConfigItems, enrichWithInteractionTracking } from '../../NavBar/utils';
|
||||
|
||||
export interface TopNavBarMenuProps {
|
||||
node: NavModelItem;
|
||||
}
|
||||
|
||||
export function TopNavBarMenu({ node }: TopNavBarMenuProps) {
|
||||
export function TopNavBarMenu({ node: nodePlain }: TopNavBarMenuProps) {
|
||||
const styles = useStyles2(getStyles);
|
||||
const location = useLocation();
|
||||
const enriched = enrichConfigItems([cloneDeep(nodePlain)], location);
|
||||
const node = enrichWithInteractionTracking(enriched[0], false);
|
||||
|
||||
if (!node) {
|
||||
return null;
|
||||
}
|
||||
|
@ -1,51 +1,22 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import React, { useState } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import React from 'react';
|
||||
|
||||
import { GrafanaTheme2, NavSection } from '@grafana/data';
|
||||
import { locationService } from '@grafana/runtime';
|
||||
import { Dropdown, FilterInput, Icon, Tooltip, useStyles2 } from '@grafana/ui';
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { Dropdown, Icon, Tooltip, useStyles2 } from '@grafana/ui';
|
||||
import { contextSrv } from 'app/core/core';
|
||||
import { useSearchQuery } from 'app/features/search/hooks/useSearchQuery';
|
||||
import { useSelector } from 'app/types';
|
||||
|
||||
import { enrichConfigItems, enrichWithInteractionTracking } from '../NavBar/utils';
|
||||
import { OrgSwitcher } from '../OrgSwitcher';
|
||||
|
||||
import { TopNavBarMenu } from './TopBar/TopNavBarMenu';
|
||||
import { TopSearchBarInput } from './TopSearchBarInput';
|
||||
import { TOP_BAR_LEVEL_HEIGHT } from './types';
|
||||
|
||||
export function TopSearchBar() {
|
||||
const styles = useStyles2(getStyles);
|
||||
const location = useLocation();
|
||||
const { query, onQueryChange } = useSearchQuery({});
|
||||
const navBarTree = useSelector((state) => state.navBarTree);
|
||||
const navTree = cloneDeep(navBarTree);
|
||||
const [showSwitcherModal, setShowSwitcherModal] = useState(false);
|
||||
const toggleSwitcherModal = () => {
|
||||
setShowSwitcherModal(!showSwitcherModal);
|
||||
};
|
||||
const navIndex = useSelector((state) => state.navIndex);
|
||||
|
||||
const onOpenSearch = () => {
|
||||
locationService.partial({ search: 'open' });
|
||||
};
|
||||
const onSearchChange = (value: string) => {
|
||||
onQueryChange(value);
|
||||
if (value) {
|
||||
onOpenSearch();
|
||||
}
|
||||
};
|
||||
|
||||
const configItems = enrichConfigItems(
|
||||
navTree.filter((item) => item.section === NavSection.Config),
|
||||
location,
|
||||
toggleSwitcherModal
|
||||
).map((item) => enrichWithInteractionTracking(item, false));
|
||||
|
||||
const helpNode = configItems.find((item) => item.id === 'help');
|
||||
const profileNode = configItems.find((item) => item.id === 'profile');
|
||||
const signInNode = configItems.find((item) => item.id === 'signin');
|
||||
const helpNode = navIndex['help'];
|
||||
const profileNode = navIndex['profile'];
|
||||
const signInNode = navIndex['signin'];
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
@ -55,17 +26,11 @@ export function TopSearchBar() {
|
||||
</a>
|
||||
</div>
|
||||
<div className={styles.searchWrapper}>
|
||||
<FilterInput
|
||||
onClick={onOpenSearch}
|
||||
placeholder="Search Grafana"
|
||||
value={query.query ?? ''}
|
||||
onChange={onSearchChange}
|
||||
className={styles.searchInput}
|
||||
/>
|
||||
<TopSearchBarInput />
|
||||
</div>
|
||||
<div className={styles.actions}>
|
||||
{helpNode && (
|
||||
<Dropdown overlay={<TopNavBarMenu node={helpNode} />}>
|
||||
<Dropdown overlay={() => <TopNavBarMenu node={helpNode} />}>
|
||||
<button className={styles.actionItem}>
|
||||
<Icon name="question-circle" size="lg" />
|
||||
</button>
|
||||
@ -91,7 +56,6 @@ export function TopSearchBar() {
|
||||
</Dropdown>
|
||||
)}
|
||||
</div>
|
||||
{showSwitcherModal && <OrgSwitcher onDismiss={toggleSwitcherModal} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
28
public/app/core/components/AppChrome/TopSearchBarInput.tsx
Normal file
28
public/app/core/components/AppChrome/TopSearchBarInput.tsx
Normal file
@ -0,0 +1,28 @@
|
||||
import React from 'react';
|
||||
|
||||
import { locationService } from '@grafana/runtime';
|
||||
import { FilterInput } from '@grafana/ui';
|
||||
import { useSearchQuery } from 'app/features/search/hooks/useSearchQuery';
|
||||
|
||||
export function TopSearchBarInput() {
|
||||
const { query, onQueryChange } = useSearchQuery({});
|
||||
|
||||
const onOpenSearch = () => {
|
||||
locationService.partial({ search: 'open' });
|
||||
};
|
||||
|
||||
const onSearchChange = (value: string) => {
|
||||
onQueryChange(value);
|
||||
if (value) {
|
||||
onOpenSearch();
|
||||
}
|
||||
};
|
||||
return (
|
||||
<FilterInput
|
||||
onClick={onOpenSearch}
|
||||
placeholder="Search Grafana"
|
||||
value={query.query ?? ''}
|
||||
onChange={onSearchChange}
|
||||
/>
|
||||
);
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import React, { useState } from 'react';
|
||||
import React from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
|
||||
import { GrafanaTheme2, NavModelItem, NavSection } from '@grafana/data';
|
||||
@ -22,11 +22,6 @@ export const MegaMenu = React.memo<Props>(({ onClose, searchBarHidden }) => {
|
||||
const theme = useTheme2();
|
||||
const styles = getStyles(theme);
|
||||
const location = useLocation();
|
||||
const [showSwitcherModal, setShowSwitcherModal] = useState(false);
|
||||
|
||||
const toggleSwitcherModal = () => {
|
||||
setShowSwitcherModal(!showSwitcherModal);
|
||||
};
|
||||
|
||||
const homeItem: NavModelItem = enrichWithInteractionTracking(
|
||||
{
|
||||
@ -48,8 +43,7 @@ export const MegaMenu = React.memo<Props>(({ onClose, searchBarHidden }) => {
|
||||
.map((item) => enrichWithInteractionTracking(item, true));
|
||||
const configItems = enrichConfigItems(
|
||||
navTree.filter((item) => item.section === NavSection.Config && item && item.id !== 'help' && item.id !== 'profile'),
|
||||
location,
|
||||
toggleSwitcherModal
|
||||
location
|
||||
).map((item) => enrichWithInteractionTracking(item, true));
|
||||
|
||||
const activeItem = getActiveItem(navTree, location.pathname);
|
||||
|
@ -11,8 +11,6 @@ import { Icon, useTheme2, CustomScrollbar } from '@grafana/ui';
|
||||
import { getKioskMode } from 'app/core/navigation/kiosk';
|
||||
import { useSelector } from 'app/types';
|
||||
|
||||
import { OrgSwitcher } from '../OrgSwitcher';
|
||||
|
||||
import NavBarItem from './NavBarItem';
|
||||
import { NavBarItemIcon } from './NavBarItemIcon';
|
||||
import { NavBarItemWithoutMenu } from './NavBarItemWithoutMenu';
|
||||
@ -38,15 +36,10 @@ export const NavBar = React.memo(() => {
|
||||
const theme = useTheme2();
|
||||
const styles = getStyles(theme);
|
||||
const location = useLocation();
|
||||
const [showSwitcherModal, setShowSwitcherModal] = useState(false);
|
||||
const [menuOpen, setMenuOpen] = useState(false);
|
||||
const [menuAnimationInProgress, setMenuAnimationInProgress] = useState(false);
|
||||
const [menuIdOpen, setMenuIdOpen] = useState<string | undefined>(undefined);
|
||||
|
||||
const toggleSwitcherModal = () => {
|
||||
setShowSwitcherModal(!showSwitcherModal);
|
||||
};
|
||||
|
||||
// Here we need to hack in a "home" and "search" NavModelItem since this is constructed in the frontend
|
||||
const searchItem: NavModelItem = enrichWithInteractionTracking(
|
||||
{
|
||||
@ -78,8 +71,7 @@ export const NavBar = React.memo(() => {
|
||||
.map((item) => enrichWithInteractionTracking(item, menuOpen));
|
||||
const configItems = enrichConfigItems(
|
||||
navTree.filter((item) => item.section === NavSection.Config),
|
||||
location,
|
||||
toggleSwitcherModal
|
||||
location
|
||||
).map((item) => enrichWithInteractionTracking(item, menuOpen));
|
||||
|
||||
const activeItem = isSearchActive(location) ? searchItem : getActiveItem(navTree, location.pathname);
|
||||
@ -158,7 +150,6 @@ export const NavBar = React.memo(() => {
|
||||
</FocusScope>
|
||||
</NavBarContext.Provider>
|
||||
</nav>
|
||||
{showSwitcherModal && <OrgSwitcher onDismiss={toggleSwitcherModal} />}
|
||||
{(menuOpen || menuAnimationInProgress) && (
|
||||
<div className={styles.menuWrapper}>
|
||||
<NavBarMenu
|
||||
|
@ -62,7 +62,7 @@ describe('enrichConfigItems', () => {
|
||||
const contextSrv = new ContextSrv();
|
||||
contextSrv.user.isSignedIn = false;
|
||||
setContextSrv(contextSrv);
|
||||
const enrichedConfigItems = enrichConfigItems(mockItems, mockLocation, jest.fn());
|
||||
const enrichedConfigItems = enrichConfigItems(mockItems, mockLocation);
|
||||
const signInNode = enrichedConfigItems.find((item) => item.id === 'signin');
|
||||
expect(signInNode).toBeDefined();
|
||||
});
|
||||
@ -71,7 +71,7 @@ describe('enrichConfigItems', () => {
|
||||
const contextSrv = new ContextSrv();
|
||||
contextSrv.user.isSignedIn = true;
|
||||
setContextSrv(contextSrv);
|
||||
const enrichedConfigItems = enrichConfigItems(mockItems, mockLocation, jest.fn());
|
||||
const enrichedConfigItems = enrichConfigItems(mockItems, mockLocation);
|
||||
const signInNode = enrichedConfigItems.find((item) => item.id === 'signin');
|
||||
expect(signInNode).toBeDefined();
|
||||
});
|
||||
@ -80,7 +80,7 @@ describe('enrichConfigItems', () => {
|
||||
const contextSrv = new ContextSrv();
|
||||
contextSrv.user.orgCount = 1;
|
||||
setContextSrv(contextSrv);
|
||||
const enrichedConfigItems = enrichConfigItems(mockItems, mockLocation, jest.fn());
|
||||
const enrichedConfigItems = enrichConfigItems(mockItems, mockLocation);
|
||||
const profileNode = enrichedConfigItems.find((item) => item.id === 'profile');
|
||||
expect(profileNode!.children).toBeUndefined();
|
||||
});
|
||||
@ -89,7 +89,7 @@ describe('enrichConfigItems', () => {
|
||||
const contextSrv = new ContextSrv();
|
||||
contextSrv.user.orgCount = 2;
|
||||
setContextSrv(contextSrv);
|
||||
const enrichedConfigItems = enrichConfigItems(mockItems, mockLocation, jest.fn());
|
||||
const enrichedConfigItems = enrichConfigItems(mockItems, mockLocation);
|
||||
const profileNode = enrichedConfigItems.find((item) => item.id === 'profile');
|
||||
expect(profileNode!.children).toContainEqual(
|
||||
expect.objectContaining({
|
||||
@ -101,7 +101,7 @@ describe('enrichConfigItems', () => {
|
||||
it('enhances the help node with extra child links', () => {
|
||||
const contextSrv = new ContextSrv();
|
||||
setContextSrv(contextSrv);
|
||||
const enrichedConfigItems = enrichConfigItems(mockItems, mockLocation, jest.fn());
|
||||
const enrichedConfigItems = enrichConfigItems(mockItems, mockLocation);
|
||||
const helpNode = enrichedConfigItems.find((item) => item.id === 'help');
|
||||
expect(helpNode!.children).toContainEqual(
|
||||
expect.objectContaining({
|
||||
|
@ -8,6 +8,7 @@ import { contextSrv } from 'app/core/services/context_srv';
|
||||
import { ShowModalReactEvent } from '../../../types/events';
|
||||
import appEvents from '../../app_events';
|
||||
import { getFooterLinks } from '../Footer/Footer';
|
||||
import { OrgSwitcher } from '../OrgSwitcher';
|
||||
import { HelpModal } from '../help/HelpModal';
|
||||
|
||||
export const SEARCH_ITEM_ID = 'search';
|
||||
@ -22,21 +23,21 @@ export const getForcedLoginUrl = (url: string) => {
|
||||
return `${getConfig().appSubUrl}${url.split('?')[0]}?${queryParams.toString()}`;
|
||||
};
|
||||
|
||||
export const enrichConfigItems = (
|
||||
items: NavModelItem[],
|
||||
location: Location<unknown>,
|
||||
toggleOrgSwitcher: () => void
|
||||
) => {
|
||||
export const enrichConfigItems = (items: NavModelItem[], location: Location<unknown>) => {
|
||||
const { isSignedIn, user } = contextSrv;
|
||||
const onOpenShortcuts = () => {
|
||||
appEvents.publish(new ShowModalReactEvent({ component: HelpModal }));
|
||||
};
|
||||
|
||||
const onOpenOrgSwitcher = () => {
|
||||
appEvents.publish(new ShowModalReactEvent({ component: OrgSwitcher }));
|
||||
};
|
||||
|
||||
if (user && user.orgCount > 1) {
|
||||
const profileNode = items.find((bottomNavItem) => bottomNavItem.id === 'profile');
|
||||
if (profileNode) {
|
||||
profileNode.showOrgSwitcher = true;
|
||||
profileNode.subTitle = `Current Org.: ${user?.orgName}`;
|
||||
profileNode.subTitle = `Organization: ${user?.orgName}`;
|
||||
}
|
||||
}
|
||||
|
||||
@ -53,7 +54,7 @@ export const enrichConfigItems = (
|
||||
});
|
||||
}
|
||||
|
||||
items.forEach((link, index) => {
|
||||
items.forEach((link) => {
|
||||
let menuItems = link.children || [];
|
||||
|
||||
if (link.id === 'help') {
|
||||
@ -75,7 +76,7 @@ export const enrichConfigItems = (
|
||||
id: 'switch-organization',
|
||||
text: 'Switch organization',
|
||||
icon: 'arrow-random',
|
||||
onClick: toggleOrgSwitcher,
|
||||
onClick: onOpenOrgSwitcher,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user