From 42efb14989b0b5542bc113da36f518a1e9d216a4 Mon Sep 17 00:00:00 2001 From: Joao Silva <100691367+JoaoSilvaGrafana@users.noreply.github.com> Date: Thu, 15 Aug 2024 13:35:20 +0100 Subject: [PATCH] Bookmarks: Move building logic to the Frontend (#91849) --- pkg/services/navtree/navtreeimpl/navtree.go | 37 +----- .../AppChrome/MegaMenu/MegaMenu.tsx | 22 +++- .../AppChrome/MegaMenu/utils.test.ts | 110 +++++++++++------- 3 files changed, 88 insertions(+), 81 deletions(-) diff --git a/pkg/services/navtree/navtreeimpl/navtree.go b/pkg/services/navtree/navtreeimpl/navtree.go index c63f6211180..c6268905661 100644 --- a/pkg/services/navtree/navtreeimpl/navtree.go +++ b/pkg/services/navtree/navtreeimpl/navtree.go @@ -169,14 +169,12 @@ func (s *ServiceImpl) GetNavTree(c *contextmodel.ReqContext, prefs *pref.Prefere } if s.features.IsEnabled(c.Req.Context(), featuremgmt.FlagPinNavItems) { - bookmarks := s.buildBookmarksNavLinks(prefs, treeRoot) - treeRoot.AddSection(&navtree.NavLink{ Text: "Bookmarks", Id: navtree.NavIDBookmarks, Icon: "bookmark", SortWeight: navtree.WeightBookmarks, - Children: bookmarks, + Children: []*navtree.NavLink{}, EmptyMessageId: "bookmarks-empty", Url: s.cfg.AppSubURL + "/bookmarks", }) @@ -335,39 +333,6 @@ func (s *ServiceImpl) buildStarredItemsNavLinks(c *contextmodel.ReqContext) ([]* return starredItemsChildNavs, nil } -func (s *ServiceImpl) buildBookmarksNavLinks(prefs *pref.Preference, treeRoot *navtree.NavTreeRoot) []*navtree.NavLink { - bookmarksChildNavs := []*navtree.NavLink{} - - bookmarkUrls := prefs.JSONData.Navbar.BookmarkUrls - - if len(bookmarkUrls) > 0 { - for _, url := range bookmarkUrls { - item := treeRoot.FindByURL(url) - if item != nil { - bookmarksChildNavs = append(bookmarksChildNavs, &navtree.NavLink{ - Id: item.Id, - Text: item.Text, - SubTitle: item.SubTitle, - Icon: item.Icon, - Img: item.Img, - Url: item.Url, - Target: item.Target, - HideFromTabs: item.HideFromTabs, - RoundIcon: item.RoundIcon, - IsSection: item.IsSection, - HighlightText: item.HighlightText, - HighlightID: item.HighlightID, - PluginID: item.PluginID, - IsCreateAction: item.IsCreateAction, - Keywords: item.Keywords, - ParentItem: &navtree.NavLink{Id: navtree.NavIDBookmarks}, - }) - } - } - } - - return bookmarksChildNavs -} func (s *ServiceImpl) buildDashboardNavLinks(c *contextmodel.ReqContext) []*navtree.NavLink { hasAccess := ac.HasAccess(s.accessControl, c) diff --git a/public/app/core/components/AppChrome/MegaMenu/MegaMenu.tsx b/public/app/core/components/AppChrome/MegaMenu/MegaMenu.tsx index ce7488d0d9b..d3bed574dcc 100644 --- a/public/app/core/components/AppChrome/MegaMenu/MegaMenu.tsx +++ b/public/app/core/components/AppChrome/MegaMenu/MegaMenu.tsx @@ -15,7 +15,7 @@ import { useDispatch, useSelector } from 'app/types'; import { MegaMenuItem } from './MegaMenuItem'; import { usePinnedItems } from './hooks'; -import { enrichWithInteractionTracking, getActiveItem } from './utils'; +import { enrichWithInteractionTracking, findByUrl, getActiveItem } from './utils'; export const MENU_WIDTH = '300px'; @@ -39,6 +39,26 @@ export const MegaMenu = memo( .filter((item) => item.id !== 'profile' && item.id !== 'help') .map((item) => enrichWithInteractionTracking(item, state.megaMenuDocked)); + if (config.featureToggles.pinNavItems) { + const bookmarksItem = findByUrl(navItems, '/bookmarks'); + if (bookmarksItem) { + // Add children to the bookmarks section + bookmarksItem.children = pinnedItems.reduce((acc: NavModelItem[], url) => { + const item = findByUrl(navItems, url); + if (!item) { + return acc; + } + acc.push({ + id: item.id, + text: item.text, + url: item.url, + parentItem: { id: 'bookmarks', text: 'Bookmarks' }, + }); + return acc; + }, []); + } + } + const activeItem = getActiveItem(navItems, state.sectionNav.node, location.pathname); const handleDockedMenu = () => { diff --git a/public/app/core/components/AppChrome/MegaMenu/utils.test.ts b/public/app/core/components/AppChrome/MegaMenu/utils.test.ts index e03c46022e7..0a99bffd6b3 100644 --- a/public/app/core/components/AppChrome/MegaMenu/utils.test.ts +++ b/public/app/core/components/AppChrome/MegaMenu/utils.test.ts @@ -1,7 +1,50 @@ import { NavModelItem } from '@grafana/data'; import { ContextSrv, setContextSrv } from 'app/core/services/context_srv'; -import { enrichHelpItem, getActiveItem } from './utils'; +import { enrichHelpItem, getActiveItem, findByUrl } from './utils'; + +const starredDashboardUid = 'foo'; +const mockNavTree: NavModelItem[] = [ + { + text: 'Item', + url: '/item', + id: 'item', + }, + { + text: 'Item with children', + url: '/itemWithChildren', + id: 'item-with-children', + children: [ + { + text: 'Child', + url: '/child', + id: 'child', + }, + ], + }, + { + text: 'Base', + url: '/', + id: 'home', + }, + { + text: 'Starred', + url: '/dashboards?starred', + id: 'starred', + children: [ + { + id: `starred/${starredDashboardUid}`, + text: 'Lazy Loading', + url: `/d/${starredDashboardUid}/some-name`, + }, + ], + }, + { + text: 'Dashboards', + url: '/dashboards', + id: 'dashboards', + }, +]; jest.mock('../../../app_events', () => ({ publish: jest.fn(), @@ -45,49 +88,6 @@ describe('enrichConfigItems', () => { }); describe('getActiveItem', () => { - const starredDashboardUid = 'foo'; - const mockNavTree: NavModelItem[] = [ - { - text: 'Item', - url: '/item', - id: 'item', - }, - { - text: 'Item with children', - url: '/itemWithChildren', - id: 'item-with-children', - children: [ - { - text: 'Child', - url: '/child', - id: 'child', - }, - ], - }, - { - text: 'Base', - url: '/', - id: 'home', - }, - { - text: 'Starred', - url: '/dashboards?starred', - id: 'starred', - children: [ - { - id: `starred/${starredDashboardUid}`, - text: 'Lazy Loading', - url: `/d/${starredDashboardUid}/some-name`, - }, - ], - }, - { - text: 'Dashboards', - url: '/dashboards', - id: 'dashboards', - }, - ]; - it('returns an exact match at the top level', () => { const mockPage: NavModelItem = { text: 'Some current page', @@ -124,3 +124,25 @@ describe('getActiveItem', () => { expect(getActiveItem(mockNavTree, mockPage, '/')?.id).toEqual('home'); }); }); + +describe('findByUrl', () => { + it('returns the correct item at the top level', () => { + expect(findByUrl(mockNavTree, '/item')).toEqual({ + text: 'Item', + url: '/item', + id: 'item', + }); + }); + + it('returns the correct child item', () => { + expect(findByUrl(mockNavTree, '/child')).toEqual({ + text: 'Child', + url: '/child', + id: 'child', + }); + }); + + it('returns null if no item found', () => { + expect(findByUrl(mockNavTree, '/no-item')).toBeNull(); + }); +});