add event tracking to navigation (#49800)

This commit is contained in:
Ashley Harrison 2022-05-30 11:55:07 +01:00 committed by GitHub
parent 17d0133008
commit 309ad38fd6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 85 additions and 37 deletions

View File

@ -54,15 +54,14 @@ const NavBarItem = ({
const onNavigate = (item: NavModelItem) => {
const { url, target, onClick } = item;
if (!url) {
onClick?.();
return;
}
onClick?.();
if (!target && url.startsWith('/')) {
locationService.push(locationUtil.stripBaseFromUrl(url));
} else {
window.open(url, target);
if (url) {
if (!target && url.startsWith('/')) {
locationService.push(locationUtil.stripBaseFromUrl(url));
} else {
window.open(url, target);
}
}
};

View File

@ -7,6 +7,7 @@ import CSSTransition from 'react-transition-group/CSSTransition';
import { useLocalStorage } from 'react-use';
import { GrafanaTheme2, NavModelItem } from '@grafana/data';
import { reportInteraction } from '@grafana/runtime';
import { CollapsableSection, CustomScrollbar, Icon, IconButton, IconName, useStyles2, useTheme2 } from '@grafana/ui';
import { Branding } from '../../Branding/Branding';
@ -64,7 +65,14 @@ export function NavBarMenu({ activeItem, isOpen, navItems, onClose, setMenuAnima
variant="secondary"
/>
</div>
<NavBarToggle className={styles.menuCollapseIcon} isExpanded={isOpen} onClick={onClose} />
<NavBarToggle
className={styles.menuCollapseIcon}
isExpanded={isOpen}
onClick={() => {
reportInteraction('grafana_navigation_collapsed');
onClose();
}}
/>
<nav className={styles.content}>
<CustomScrollbar hideHorizontalTrack>
<ul className={styles.itemList}>

View File

@ -6,7 +6,7 @@ import { useSelector } from 'react-redux';
import { useLocation } from 'react-router-dom';
import { GrafanaTheme2, NavModelItem, NavSection } from '@grafana/data';
import { config, locationService } from '@grafana/runtime';
import { config, locationService, reportInteraction } from '@grafana/runtime';
import { Icon, IconName, useTheme2 } from '@grafana/ui';
import { Branding } from 'app/core/components/Branding/Branding';
import { getKioskMode } from 'app/core/navigation/kiosk';
@ -14,7 +14,14 @@ import { KioskMode, StoreState } from 'app/types';
import { OrgSwitcher } from '../../OrgSwitcher';
import { NavBarContext } from '../context';
import { enrichConfigItems, getActiveItem, isMatchOrChildMatch, isSearchActive, SEARCH_ITEM_ID } from '../utils';
import {
enrichConfigItems,
enrichWithInteractionTracking,
getActiveItem,
isMatchOrChildMatch,
isSearchActive,
SEARCH_ITEM_ID,
} from '../utils';
import NavBarItem from './NavBarItem';
import { NavBarItemWithoutMenu } from './NavBarItemWithoutMenu';
@ -27,21 +34,6 @@ const onOpenSearch = () => {
locationService.partial({ search: 'open' });
};
const searchItem: NavModelItem = {
id: SEARCH_ITEM_ID,
onClick: onOpenSearch,
text: 'Search dashboards',
icon: 'search',
};
// Here we need to hack in a "home" NavModelItem since this is constructed in the frontend
const homeItem: NavModelItem = {
id: 'home',
text: 'Home',
url: config.appSubUrl || '/',
icon: 'grafana',
};
export const NavBarNext = React.memo(() => {
const navBarTree = useSelector((state: StoreState) => state.navBarTree);
const theme = useTheme2();
@ -49,23 +41,50 @@ export const NavBarNext = React.memo(() => {
const location = useLocation();
const kiosk = getKioskMode();
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);
};
const navTree = cloneDeep(navBarTree);
navTree.unshift(homeItem);
const coreItems = navTree.filter((item) => item.section === NavSection.Core);
const pluginItems = navTree.filter((item) => item.section === NavSection.Plugin);
// Here we need to hack in a "home" and "search" NavModelItem since this is constructed in the frontend
const searchItem: NavModelItem = enrichWithInteractionTracking(
{
id: SEARCH_ITEM_ID,
onClick: onOpenSearch,
text: 'Search dashboards',
icon: 'search',
},
menuOpen
);
const homeItem: NavModelItem = enrichWithInteractionTracking(
{
id: 'home',
text: 'Home',
url: config.appSubUrl || '/',
icon: 'grafana',
},
menuOpen
);
const navTree = cloneDeep(navBarTree);
const coreItems = navTree
.filter((item) => item.section === NavSection.Core)
.map((item) => enrichWithInteractionTracking(item, menuOpen));
const pluginItems = navTree
.filter((item) => item.section === NavSection.Plugin)
.map((item) => enrichWithInteractionTracking(item, menuOpen));
const configItems = enrichConfigItems(
navTree.filter((item) => item.section === NavSection.Config),
location,
toggleSwitcherModal
);
).map((item) => enrichWithInteractionTracking(item, menuOpen));
const activeItem = isSearchActive(location) ? searchItem : getActiveItem(navTree, location.pathname);
const [menuOpen, setMenuOpen] = useState(false);
const [menuAnimationInProgress, setMenuAnimationInProgress] = useState(false);
const [menuIdOpen, setMenuIdOpen] = useState<string | undefined>(undefined);
if (kiosk !== KioskMode.Off) {
return null;
@ -87,16 +106,20 @@ export const NavBarNext = React.memo(() => {
<NavBarToggle
className={styles.menuExpandIcon}
isExpanded={menuOpen}
onClick={() => setMenuOpen(!menuOpen)}
onClick={() => {
reportInteraction('grafana_navigation_expanded');
setMenuOpen(true);
}}
/>
<NavBarMenuPortalContainer />
<NavBarItemWithoutMenu
elClassName={styles.grafanaLogoInner}
label="Home"
label={homeItem.text}
className={styles.grafanaLogo}
url={homeItem.url}
onClick={homeItem.onClick}
>
<Branding.MenuLogo />
</NavBarItemWithoutMenu>
@ -111,7 +134,7 @@ export const NavBarNext = React.memo(() => {
<NavBarItem
key={`${link.id}-${index}`}
isActive={isMatchOrChildMatch(link, activeItem)}
link={{ ...link, subTitle: undefined, onClick: undefined }}
link={{ ...link, subTitle: undefined }}
>
{link.icon && <Icon name={link.icon as IconName} size="xl" />}
{link.img && <img src={link.img} alt={`${link.text} logo`} />}

View File

@ -1,6 +1,7 @@
import { Location } from 'history';
import { NavModelItem, NavSection } from '@grafana/data';
import { reportInteraction } from '@grafana/runtime';
import { getConfig } from 'app/core/config';
import { contextSrv } from 'app/core/services/context_srv';
@ -59,6 +60,7 @@ export const enrichConfigItems = (
link.children = [
...getFooterLinks(),
{
id: 'keyboard-shortcuts',
text: 'Keyboard shortcuts',
icon: 'keyboard',
onClick: onOpenShortcuts,
@ -70,6 +72,7 @@ export const enrichConfigItems = (
link.children = [
...menuItems,
{
id: 'switch-organization',
text: 'Switch organization',
icon: 'arrow-random',
onClick: toggleOrgSwitcher,
@ -80,6 +83,21 @@ export const enrichConfigItems = (
return items;
};
export const enrichWithInteractionTracking = (item: NavModelItem, expandedState: boolean) => {
const onClick = item.onClick;
item.onClick = () => {
reportInteraction('grafana_navigation_item_clicked', {
path: item.url ?? item.id,
state: expandedState ? 'expanded' : 'collapsed',
});
onClick?.();
};
if (item.children) {
item.children = item.children.map((item) => enrichWithInteractionTracking(item, expandedState));
}
return item;
};
export const isMatchOrChildMatch = (itemToCheck: NavModelItem, searchItem?: NavModelItem) => {
return Boolean(itemToCheck === searchItem || itemToCheck.children?.some((child) => child === searchItem));
};