From 376f4b0cc76d24dbfab8fc13cad7ec30abbcd2be Mon Sep 17 00:00:00 2001 From: Levente Balogh Date: Thu, 3 Nov 2022 21:19:42 +0100 Subject: [PATCH] Navigation: Add `pluginId` to standalone plugin page NavLinks (#57769) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(Navigation): add `pluginId` to NavLink and override sibling navlinks with the same URL * test replacing page from plugin * chore: fix go lint issues * fix(NavLink): change `PluginId` to `PluginID` Co-authored-by: Torkel Ödegaard * fix(NavLink): make the `PluginId` -> `PluginID` change everywhere * chore(navModel.ts): update explanatory comment for `pluginId` Co-authored-by: Miklós Tolnai Co-authored-by: Torkel Ödegaard --- packages/grafana-data/src/types/navModel.ts | 2 + pkg/services/navtree/models.go | 1 + pkg/services/navtree/navtreeimpl/applinks.go | 32 ++++++++++++--- .../navtree/navtreeimpl/applinks_test.go | 41 ++++++++++++++++++- 4 files changed, 70 insertions(+), 6 deletions(-) diff --git a/packages/grafana-data/src/types/navModel.ts b/packages/grafana-data/src/types/navModel.ts index 2b2e1ceece6..dce924e8d9b 100644 --- a/packages/grafana-data/src/types/navModel.ts +++ b/packages/grafana-data/src/types/navModel.ts @@ -26,6 +26,8 @@ export interface NavLinkDTO { children?: NavLinkDTO[]; highlightText?: string; emptyMessageId?: string; + // The ID of the plugin that registered the page (in case it was registered by a plugin, otherwise left empty) + pluginId?: string; } export interface NavModelItem extends NavLinkDTO { diff --git a/pkg/services/navtree/models.go b/pkg/services/navtree/models.go index dc579ffbe29..00358200547 100644 --- a/pkg/services/navtree/models.go +++ b/pkg/services/navtree/models.go @@ -67,6 +67,7 @@ type NavLink struct { HighlightText string `json:"highlightText,omitempty"` HighlightID string `json:"highlightId,omitempty"` EmptyMessageId string `json:"emptyMessageId,omitempty"` + PluginID string `json:"pluginId,omitempty"` // (Optional) The ID of the plugin that registered nav link (e.g. as a standalone plugin page) } func (node *NavLink) Sort() { diff --git a/pkg/services/navtree/navtreeimpl/applinks.go b/pkg/services/navtree/navtreeimpl/applinks.go index e8bab53a813..b67bd8f0208 100644 --- a/pkg/services/navtree/navtreeimpl/applinks.go +++ b/pkg/services/navtree/navtreeimpl/applinks.go @@ -72,6 +72,7 @@ func (s *ServiceImpl) processAppPlugin(plugin plugins.PluginDTO, c *models.ReqCo Section: navtree.NavSectionPlugin, SortWeight: navtree.WeightPlugin, IsSection: true, + PluginID: plugin.ID, } if topNavEnabled { @@ -87,8 +88,9 @@ func (s *ServiceImpl) processAppPlugin(plugin plugins.PluginDTO, c *models.ReqCo if include.Type == "page" && include.AddToNav { link := &navtree.NavLink{ - Text: include.Name, - Icon: include.Icon, + Text: include.Name, + Icon: include.Icon, + PluginID: plugin.ID, } if len(include.Path) > 0 { @@ -100,11 +102,30 @@ func (s *ServiceImpl) processAppPlugin(plugin plugins.PluginDTO, c *models.ReqCo link.Url = s.cfg.AppSubURL + "/plugins/" + plugin.ID + "/page/" + include.Slug } + // Register standalone plugin pages to certain sections using the Grafana config if pathConfig, ok := s.navigationAppPathConfig[include.Path]; ok { if sectionForPage := treeRoot.FindById(pathConfig.SectionID); sectionForPage != nil { link.Id = "standalone-plugin-page-" + include.Path link.SortWeight = pathConfig.SortWeight - sectionForPage.Children = append(sectionForPage.Children, link) + + // Check if the section already has a page with the same URL, and in that case override it + // (This only happens if it is explicitly set by `navigation.app_standalone_pages` in the INI config) + isOverridingCorePage := false + for _, child := range sectionForPage.Children { + if child.Url == link.Url { + child.Id = link.Id + child.SortWeight = link.SortWeight + child.PluginID = link.PluginID + child.Children = []*navtree.NavLink{} + isOverridingCorePage = true + break + } + } + + // Append the page to the section + if !isOverridingCorePage { + sectionForPage.Children = append(sectionForPage.Children, link) + } } } else { appLink.Children = append(appLink.Children, link) @@ -115,8 +136,9 @@ func (s *ServiceImpl) processAppPlugin(plugin plugins.PluginDTO, c *models.ReqCo dboardURL := include.DashboardURLPath() if dboardURL != "" { link := &navtree.NavLink{ - Url: path.Join(s.cfg.AppSubURL, dboardURL), - Text: include.Name, + Url: path.Join(s.cfg.AppSubURL, dboardURL), + Text: include.Name, + PluginID: plugin.ID, } appLink.Children = append(appLink.Children, link) } diff --git a/pkg/services/navtree/navtreeimpl/applinks_test.go b/pkg/services/navtree/navtreeimpl/applinks_test.go index d6397d680d7..82b791090ad 100644 --- a/pkg/services/navtree/navtreeimpl/applinks_test.go +++ b/pkg/services/navtree/navtreeimpl/applinks_test.go @@ -65,9 +65,27 @@ func TestAddAppLinks(t *testing.T) { }, } + testApp3 := plugins.PluginDTO{ + JSONData: plugins.JSONData{ + ID: "test-app3", + Name: "Test app3 name", + Type: plugins.App, + Includes: []*plugins.Includes{ + { + Name: "Hello", + Path: "/connections/connect-data", + Type: "page", + AddToNav: true, + DefaultNav: true, + }, + }, + }, + } + pluginSettings := pluginsettings.FakePluginSettings{Plugins: map[string]*pluginsettings.DTO{ testApp1.ID: {ID: 0, OrgID: 1, PluginID: testApp1.ID, PluginVersion: "1.0.0", Enabled: true}, testApp2.ID: {ID: 0, OrgID: 1, PluginID: testApp2.ID, PluginVersion: "1.0.0", Enabled: true}, + testApp3.ID: {ID: 0, OrgID: 1, PluginID: testApp3.ID, PluginVersion: "1.0.0", Enabled: true}, }} service := ServiceImpl{ @@ -77,7 +95,7 @@ func TestAddAppLinks(t *testing.T) { pluginSettings: &pluginSettings, features: featuremgmt.WithFeatures(), pluginStore: plugins.FakePluginStore{ - PluginList: []plugins.PluginDTO{testApp1, testApp2}, + PluginList: []plugins.PluginDTO{testApp1, testApp2, testApp3}, }, } @@ -172,6 +190,27 @@ func TestAddAppLinks(t *testing.T) { require.Equal(t, "Test app2 name", treeRoot.Children[0].Children[0].Text) require.Equal(t, "Test app1 name", treeRoot.Children[0].Children[1].Text) }) + + t.Run("Should replace page from plugin", func(t *testing.T) { + service.features = featuremgmt.WithFeatures(featuremgmt.FlagTopnav, featuremgmt.FlagDataConnectionsConsole) + service.navigationAppPathConfig = map[string]NavigationAppConfig{ + "/connections/connect-data": {SectionID: "connections"}, + } + + treeRoot := navtree.NavTreeRoot{} + treeRoot.AddSection(service.buildDataConnectionsNavLink(reqCtx)) + require.Equal(t, "Connections", treeRoot.Children[0].Text) + require.Equal(t, "Connect Data", treeRoot.Children[0].Children[1].Text) + require.Equal(t, "connections-connect-data", treeRoot.Children[0].Children[1].Id) + require.Equal(t, "", treeRoot.Children[0].Children[1].PluginID) + + err := service.addAppLinks(&treeRoot, reqCtx) + require.NoError(t, err) + require.Equal(t, "Connections", treeRoot.Children[0].Text) + require.Equal(t, "Connect Data", treeRoot.Children[0].Children[1].Text) + require.Equal(t, "standalone-plugin-page-/connections/connect-data", treeRoot.Children[0].Children[1].Id) + require.Equal(t, "test-app3", treeRoot.Children[0].Children[1].PluginID) + }) } func TestReadingNavigationSettings(t *testing.T) {