Refactoring and simplifying TopSearchBar (#55341)

This commit is contained in:
Torkel Ödegaard 2022-09-20 18:57:38 +02:00 committed by GitHub
parent 80c0dc890a
commit 40bc140a9a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 63 additions and 78 deletions

View File

@ -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;
}

View File

@ -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>
);
}

View 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}
/>
);
}

View File

@ -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);

View File

@ -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

View File

@ -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({

View File

@ -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,
},
];
}