From 59bfb44a508a9af31e9d171155d6b87c0cb0b71e Mon Sep 17 00:00:00 2001 From: Josh Hunt Date: Fri, 24 Oct 2025 17:42:42 +0100 Subject: [PATCH] FS: Apply versionString in help menu in frontend (#112958) Set version in help menu in the frontend instead --- pkg/api/index.go | 1 - pkg/services/navtree/models.go | 8 --- .../AppChrome/MegaMenu/utils.test.ts | 49 ++++++++++++++----- .../components/AppChrome/MegaMenu/utils.ts | 26 ++++++---- .../AppChrome/TopBar/useHelpNode.tsx | 15 ++++-- .../commandPalette/actions/staticActions.ts | 4 +- 6 files changed, 66 insertions(+), 37 deletions(-) diff --git a/pkg/api/index.go b/pkg/api/index.go index 765a40c4688..1effea38164 100644 --- a/pkg/api/index.go +++ b/pkg/api/index.go @@ -216,7 +216,6 @@ func (hs *HTTPServer) setIndexViewData(c *contextmodel.ReqContext) (*dtos.IndexV hs.HooksService.RunIndexDataHooks(&data, c) data.NavTree.ApplyCostManagementIA() - data.NavTree.ApplyHelpVersion(data.Settings.BuildInfo.VersionString) // RunIndexDataHooks can modify the version string data.NavTree.Sort() return &data, nil diff --git a/pkg/services/navtree/models.go b/pkg/services/navtree/models.go index 51bbdf261b1..15418e04b59 100644 --- a/pkg/services/navtree/models.go +++ b/pkg/services/navtree/models.go @@ -158,14 +158,6 @@ func Sort(nodes []*NavLink) { } } -func (root *NavTreeRoot) ApplyHelpVersion(version string) { - helpNode := root.FindById("help") - - if helpNode != nil { - helpNode.SubTitle = version - } -} - func (root *NavTreeRoot) ApplyCostManagementIA() { orgAdminNode := root.FindById(NavIDCfg) var costManagementApp *NavLink diff --git a/public/app/core/components/AppChrome/MegaMenu/utils.test.ts b/public/app/core/components/AppChrome/MegaMenu/utils.test.ts index ea5a289bd95..e66c7c923c8 100644 --- a/public/app/core/components/AppChrome/MegaMenu/utils.test.ts +++ b/public/app/core/components/AppChrome/MegaMenu/utils.test.ts @@ -1,7 +1,10 @@ +import { cloneDeep } from 'lodash'; + import { NavModelItem } from '@grafana/data'; +import { config } from '@grafana/runtime'; import { ContextSrv, setContextSrv } from 'app/core/services/context_srv'; -import { enrichHelpItem, getActiveItem, findByUrl } from './utils'; +import { getEnrichedHelpItem, getActiveItem, findByUrl } from './utils'; const starredDashboardUid = 'foo'; const mockNavTree: NavModelItem[] = [ @@ -67,40 +70,62 @@ jest.mock('../../../app_events', () => ({ })); describe('enrichConfigItems', () => { - let mockHelpNode: NavModelItem; + let mockHelpNode: NavModelItem = { + id: 'help', + text: 'Help', + }; + let originalBuildInfo = { ...config.buildInfo }; - beforeEach(() => { - mockHelpNode = { - id: 'help', - text: 'Help', - }; + beforeAll(() => { + config.buildInfo.versionString = '9.0.0-test'; + }); + + afterAll(() => { + config.buildInfo = originalBuildInfo; }); it('enhances the help node with extra child links', () => { const contextSrv = new ContextSrv(); setContextSrv(contextSrv); - const helpNode = enrichHelpItem(mockHelpNode); - expect(helpNode!.children).toContainEqual( + const helpNode = getEnrichedHelpItem(mockHelpNode); + expect(helpNode.children).toContainEqual( expect.objectContaining({ text: 'Documentation', }) ); - expect(helpNode!.children).toContainEqual( + expect(helpNode.children).toContainEqual( expect.objectContaining({ text: 'Support', }) ); - expect(helpNode!.children).toContainEqual( + expect(helpNode.children).toContainEqual( expect.objectContaining({ text: 'Community', }) ); - expect(helpNode!.children).toContainEqual( + expect(helpNode.children).toContainEqual( expect.objectContaining({ text: 'Keyboard shortcuts', }) ); }); + + it('adds the version string as subtitle', () => { + const helpNode = getEnrichedHelpItem(mockHelpNode); + expect(helpNode.subTitle).toBe(config.buildInfo.versionString); + }); + + it("doesn't mutate the original help node", () => { + const originalHelpNode = cloneDeep(mockHelpNode); + const newHelpNode = getEnrichedHelpItem(mockHelpNode); + + // The mockHelpNode should remain deeply equal to the clone we made of it + expect(mockHelpNode).toEqual(originalHelpNode); + + // The new node should have a different identity than the original + expect(newHelpNode).not.toBe(mockHelpNode); + expect(newHelpNode.children).not.toBe(mockHelpNode.children); + }); }); describe('getActiveItem', () => { diff --git a/public/app/core/components/AppChrome/MegaMenu/utils.ts b/public/app/core/components/AppChrome/MegaMenu/utils.ts index 9996db02879..473e76ec834 100644 --- a/public/app/core/components/AppChrome/MegaMenu/utils.ts +++ b/public/app/core/components/AppChrome/MegaMenu/utils.ts @@ -13,14 +13,21 @@ import { HelpModal } from '../../help/HelpModal'; import { DOCK_MENU_BUTTON_ID, MEGA_MENU_HEADER_TOGGLE_ID } from './MegaMenuHeader'; -export const enrichHelpItem = (helpItem: NavModelItem) => { +const emitOpenShortcutsModal = () => { + appEvents.publish(new ShowModalReactEvent({ component: HelpModal })); +}; + +export const getEnrichedHelpItem = (helpItem: NavModelItem): NavModelItem => { let menuItems = helpItem.children || []; - if (helpItem.id === 'help') { - const onOpenShortcuts = () => { - appEvents.publish(new ShowModalReactEvent({ component: HelpModal })); - }; - helpItem.children = [ + if (helpItem.id !== 'help') { + return helpItem; + } + + return { + ...helpItem, + subTitle: config.buildInfo.versionString, + children: [ ...menuItems, ...getFooterLinks(), ...getEditionAndUpdateLinks(), @@ -28,11 +35,10 @@ export const enrichHelpItem = (helpItem: NavModelItem) => { id: 'keyboard-shortcuts', text: t('nav.help/keyboard-shortcuts', 'Keyboard shortcuts'), icon: 'keyboard', - onClick: onOpenShortcuts, + onClick: emitOpenShortcutsModal, }, - ]; - } - return helpItem; + ], + }; }; export const enrichWithInteractionTracking = ( diff --git a/public/app/core/components/AppChrome/TopBar/useHelpNode.tsx b/public/app/core/components/AppChrome/TopBar/useHelpNode.tsx index 52beedffb56..fe9490520c2 100644 --- a/public/app/core/components/AppChrome/TopBar/useHelpNode.tsx +++ b/public/app/core/components/AppChrome/TopBar/useHelpNode.tsx @@ -1,11 +1,18 @@ import { cloneDeep } from 'lodash'; +import { useMemo } from 'react'; +import { NavModelItem } from '@grafana/data'; import { useSelector } from 'app/types/store'; -import { enrichHelpItem } from '../MegaMenu/utils'; +import { getEnrichedHelpItem } from '../MegaMenu/utils'; -export function useHelpNode() { +export function useHelpNode(): NavModelItem | undefined { const navIndex = useSelector((state) => state.navIndex); - const helpNode = cloneDeep(navIndex['help']); - return helpNode ? enrichHelpItem(helpNode) : undefined; + + const helpNode = useMemo(() => { + const helpNode = cloneDeep(navIndex['help']); + return helpNode ? getEnrichedHelpItem(helpNode) : undefined; + }, [navIndex]); + + return helpNode; } diff --git a/public/app/features/commandPalette/actions/staticActions.ts b/public/app/features/commandPalette/actions/staticActions.ts index b70c5ee4f1c..597e749a6cc 100644 --- a/public/app/features/commandPalette/actions/staticActions.ts +++ b/public/app/features/commandPalette/actions/staticActions.ts @@ -2,7 +2,7 @@ import { useMemo } from 'react'; import { NavModelItem } from '@grafana/data'; import { t } from '@grafana/i18n'; -import { enrichHelpItem } from 'app/core/components/AppChrome/MegaMenu/utils'; +import { getEnrichedHelpItem } from 'app/core/components/AppChrome/MegaMenu/utils'; import { shouldRenderInviteUserButton, performInviteUserClick, @@ -25,7 +25,7 @@ function navTreeToActions(navTree: NavModelItem[], parents: NavModelItem[] = []) for (let navItem of navTree) { // help node needs enriching with the frontend links if (navItem.id === 'help') { - navItem = enrichHelpItem({ ...navItem }); + navItem = getEnrichedHelpItem({ ...navItem }); delete navItem.url; } const { url, target, text, isCreateAction, children, onClick, keywords } = navItem;