diff --git a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md index 11b23a99d28..553de14518a 100644 --- a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md +++ b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md @@ -152,6 +152,7 @@ Experimental features might be changed or removed without prior notice. | `awsDatasourcesNewFormStyling` | Applies new form styling for configuration and query editors in AWS plugins | | `cachingOptimizeSerializationMemoryUsage` | If enabled, the caching backend gradually serializes query responses for the cache, comparing against the configured `[caching]max_value_mb` value as it goes. This can can help prevent Grafana from running out of memory while attempting to cache very large query responses. | | `pluginsInstrumentationStatusSource` | Include a status source label for plugin request metrics and logs | +| `costManagementUi` | Toggles the display of the cost management ui plugin | ## Development feature toggles diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index 6ffe31a2fd8..e4b4ea317d2 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -146,4 +146,5 @@ export interface FeatureToggles { cachingOptimizeSerializationMemoryUsage?: boolean; panelTitleSearchInV1?: boolean; pluginsInstrumentationStatusSource?: boolean; + costManagementUi?: boolean; } diff --git a/pkg/services/featuremgmt/codeowners.go b/pkg/services/featuremgmt/codeowners.go index 1be0dd2be23..2a555a2906e 100644 --- a/pkg/services/featuremgmt/codeowners.go +++ b/pkg/services/featuremgmt/codeowners.go @@ -26,4 +26,5 @@ const ( grafanaOperatorExperienceSquad codeowner = "@grafana/grafana-operator-experience-squad" enterpriseDatasourcesSquad codeowner = "@grafana/enterprise-datasources" grafanaSharingSquad codeowner = "@grafana/sharing-squad" + grafanaDatabasesFrontend codeowner = "@grafana/databases-frontend" ) diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index b38fa7616c1..7d1a0491755 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -897,5 +897,12 @@ var ( Stage: FeatureStageExperimental, Owner: grafanaPluginsPlatformSquad, }, + { + Name: "costManagementUi", + Description: "Toggles the display of the cost management ui plugin", + Stage: FeatureStageExperimental, + FrontendOnly: false, + Owner: grafanaDatabasesFrontend, + }, } ) diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index 50facefe1ad..3b336f4891e 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -127,3 +127,4 @@ awsDatasourcesNewFormStyling,experimental,@grafana/aws-datasources,false,false,f cachingOptimizeSerializationMemoryUsage,experimental,@grafana/grafana-operator-experience-squad,false,false,false,false panelTitleSearchInV1,experimental,@grafana/backend-platform,true,false,false,false pluginsInstrumentationStatusSource,experimental,@grafana/plugins-platform-backend,false,false,false,false +costManagementUi,experimental,@grafana/databases-frontend,false,false,false,false diff --git a/pkg/services/featuremgmt/toggles_gen.go b/pkg/services/featuremgmt/toggles_gen.go index c5a1e8ced1b..dc2756aec2a 100644 --- a/pkg/services/featuremgmt/toggles_gen.go +++ b/pkg/services/featuremgmt/toggles_gen.go @@ -518,4 +518,8 @@ const ( // FlagPluginsInstrumentationStatusSource // Include a status source label for plugin request metrics and logs FlagPluginsInstrumentationStatusSource = "pluginsInstrumentationStatusSource" + + // FlagCostManagementUi + // Toggles the display of the cost management ui plugin + FlagCostManagementUi = "costManagementUi" ) diff --git a/pkg/services/navtree/models.go b/pkg/services/navtree/models.go index 1054d87802e..800cc62d56a 100644 --- a/pkg/services/navtree/models.go +++ b/pkg/services/navtree/models.go @@ -90,7 +90,9 @@ func (root *NavTreeRoot) RemoveSection(node *NavLink) { func (root *NavTreeRoot) FindById(id string) *NavLink { return FindById(root.Children, id) } - +func (root *NavTreeRoot) FindByURL(url string) *NavLink { + return FindByURL(root.Children, url) +} func (root *NavTreeRoot) Sort() { Sort(root.Children) } @@ -192,6 +194,26 @@ func (root *NavTreeRoot) ApplyAdminIA(navAdminSubsectionsEnabled bool) { authenticationNode.IsSection = true adminNodeLinks = append(adminNodeLinks, authenticationNode) } + + costManagementNode := root.FindById("plugin-page-grafana-costmanagementui-app") + + if costManagementNode != nil { + adminNodeLinks = append(adminNodeLinks, costManagementNode) + } + + costManagementMetricsNode := root.FindByURL("/a/grafana-costmanagementui-app/metrics") + adaptiveMetricsNode := root.FindById("plugin-page-grafana-adaptive-metrics-app") + + if costManagementMetricsNode != nil && adaptiveMetricsNode != nil { + costManagementMetricsNode.Children = append(costManagementMetricsNode.Children, adaptiveMetricsNode) + } + + costManagementLogsNode := root.FindByURL("/a/grafana-costmanagementui-app/logs") + logVolumeExplorerNode := root.FindById("plugin-page-grafana-logvolumeexplorer-app") + + if costManagementLogsNode != nil && logVolumeExplorerNode != nil { + costManagementLogsNode.Children = append(costManagementLogsNode.Children, logVolumeExplorerNode) + } } else { adminNodeLinks = AppendIfNotNil(adminNodeLinks, root.FindById("datasources")) adminNodeLinks = AppendIfNotNil(adminNodeLinks, root.FindById("plugins")) @@ -245,3 +267,17 @@ func FindById(nodes []*NavLink, id string) *NavLink { return nil } + +func FindByURL(nodes []*NavLink, url string) *NavLink { + for _, child := range nodes { + if child.Url == url { + return child + } else if len(child.Children) > 0 { + if found := FindByURL(child.Children, url); found != nil { + return found + } + } + } + + return nil +} diff --git a/pkg/services/navtree/models_test.go b/pkg/services/navtree/models_test.go index dfdc5560c70..0d6df4cc648 100644 --- a/pkg/services/navtree/models_test.go +++ b/pkg/services/navtree/models_test.go @@ -33,4 +33,15 @@ func TestNavTreeRoot(t *testing.T) { require.Equal(t, "1", treeRoot.Children[0].Id) require.Equal(t, "4", treeRoot.Children[1].Id) }) + t.Run("FindByURL is able to find a navItem by url", func(t *testing.T) { + treeRoot := NavTreeRoot{ + Children: []*NavLink{ + {Id: "1", Url: "/"}, + {Id: "2", Url: "/org"}, + {Id: "3", Url: "/org/users"}, + }, + } + require.Equal(t, "2", treeRoot.FindByURL("/org").Id) + require.Equal(t, "3", treeRoot.FindByURL("/org/users").Id) + }) } diff --git a/pkg/services/navtree/navtreeimpl/applinks.go b/pkg/services/navtree/navtreeimpl/applinks.go index fe1ac0e994c..15f3f2f8b4a 100644 --- a/pkg/services/navtree/navtreeimpl/applinks.go +++ b/pkg/services/navtree/navtreeimpl/applinks.go @@ -262,10 +262,18 @@ func (s *ServiceImpl) readNavigationSettings() { "grafana-incident-app": {SectionID: navtree.NavIDAlertsAndIncidents, SortWeight: 2, Text: "Incidents"}, "grafana-ml-app": {SectionID: navtree.NavIDAlertsAndIncidents, SortWeight: 3, Text: "Machine Learning"}, "grafana-cloud-link-app": {SectionID: navtree.NavIDCfg}, + "grafana-costmanagementui-app": {SectionID: navtree.NavIDCfg, Text: "Cost management"}, "grafana-easystart-app": {SectionID: navtree.NavIDRoot, SortWeight: navtree.WeightApps + 1, Text: "Connections", Icon: "adjust-circle"}, "k6-app": {SectionID: navtree.NavIDRoot, SortWeight: navtree.WeightAlertsAndIncidents + 1, Text: "Performance testing", Icon: "k6"}, } + if s.features.IsEnabled(featuremgmt.FlagNavAdminSubsections) && s.features.IsEnabled(featuremgmt.FlagCostManagementUi) { + // if cost management is enabled we want to nest adaptive metrics and log volume explorer under that plugin + // in the admin section + s.navigationAppConfig["grafana-adaptive-metrics-app"] = NavigationAppConfig{SectionID: navtree.NavIDCfg} + s.navigationAppConfig["grafana-logvolumeexplorer-app"] = NavigationAppConfig{SectionID: navtree.NavIDCfg} + } + s.navigationAppPathConfig = map[string]NavigationAppConfig{ "/a/grafana-auth-app": {SectionID: navtree.NavIDCfg, SortWeight: 7}, } diff --git a/pkg/services/navtree/navtreeimpl/applinks_test.go b/pkg/services/navtree/navtreeimpl/applinks_test.go index 6d19b8cf67b..a877638fd6b 100644 --- a/pkg/services/navtree/navtreeimpl/applinks_test.go +++ b/pkg/services/navtree/navtreeimpl/applinks_test.go @@ -360,7 +360,8 @@ func TestAddAppLinks(t *testing.T) { func TestReadingNavigationSettings(t *testing.T) { t.Run("Should include defaults", func(t *testing.T) { service := ServiceImpl{ - cfg: setting.NewCfg(), + cfg: setting.NewCfg(), + features: featuremgmt.WithFeatures(), } _, _ = service.cfg.Raw.NewSection("navigation.app_sections") @@ -371,7 +372,8 @@ func TestReadingNavigationSettings(t *testing.T) { t.Run("Can add additional overrides via ini system", func(t *testing.T) { service := ServiceImpl{ - cfg: setting.NewCfg(), + cfg: setting.NewCfg(), + features: featuremgmt.WithFeatures(), } appSections, _ := service.cfg.Raw.NewSection("navigation.app_sections")