diff --git a/packages/grafana-data/src/types/icon.ts b/packages/grafana-data/src/types/icon.ts index d3dea16ff5c..64d4bdd7a88 100644 --- a/packages/grafana-data/src/types/icon.ts +++ b/packages/grafana-data/src/types/icon.ts @@ -139,6 +139,7 @@ export const availableIconsIndex = { 'gf-layout-simple': true, 'gf-logs': true, 'gf-ml': true, + 'gf-ml-alt': true, 'gf-movepane-left': true, 'gf-movepane-right': true, 'gf-portrait': true, diff --git a/pkg/services/navtree/models.go b/pkg/services/navtree/models.go index 97d7ab1112b..5af7d6dbeb7 100644 --- a/pkg/services/navtree/models.go +++ b/pkg/services/navtree/models.go @@ -19,6 +19,7 @@ const ( WeightDrilldown WeightAlerting WeightAlertsAndIncidents + WeightAIAndML WeightTestingAndSynthetics WeightMonitoring WeightCloudServiceProviders diff --git a/pkg/services/navtree/navtreeimpl/navtree.go b/pkg/services/navtree/navtreeimpl/navtree.go index fdc345a8026..0a1682b026c 100644 --- a/pkg/services/navtree/navtreeimpl/navtree.go +++ b/pkg/services/navtree/navtreeimpl/navtree.go @@ -7,6 +7,7 @@ import ( "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/infra/kvstore" "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/plugins" ac "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/apikey" "github.com/grafana/grafana/pkg/services/authn" @@ -17,6 +18,7 @@ import ( "github.com/grafana/grafana/pkg/services/licensing" "github.com/grafana/grafana/pkg/services/navtree" "github.com/grafana/grafana/pkg/services/org" + pc "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginaccesscontrol" "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginsettings" "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore" pref "github.com/grafana/grafana/pkg/services/preference" @@ -161,6 +163,10 @@ func (s *ServiceImpl) GetNavTree(c *contextmodel.ReqContext, prefs *pref.Prefere if alertingSection := s.buildAlertNavLinks(c); alertingSection != nil { treeRoot.AddSection(alertingSection) } + + if aimlSection := s.buildAIMLNavLinks(c); aimlSection != nil { + treeRoot.AddSection(aimlSection) + } } if connectionsSection := s.buildDataConnectionsNavLink(c); connectionsSection != nil { @@ -416,6 +422,80 @@ func (s *ServiceImpl) buildDashboardNavLinks(c *contextmodel.ReqContext) []*navt return dashboardChildNavs } +func (s *ServiceImpl) buildAIMLNavLinks(c *contextmodel.ReqContext) *navtree.NavLink { + hasAccess := ac.HasAccess(s.accessControl, c) + + pss, err := s.pluginSettings.GetPluginSettings(c.Req.Context(), &pluginsettings.GetArgs{OrgID: c.GetOrgID()}) + if err != nil { + s.log.Error("Failed to get plugin settings", "error", err) + return nil + } + + // Check if ML plugin is enabled + isMLPluginEnabled := false + for _, plugin := range s.pluginStore.Plugins(c.Req.Context(), plugins.TypeApp) { + if plugin.ID == "grafana-ml-app" { + // Check if plugin is enabled in settings + if plugin.AutoEnabled { + isMLPluginEnabled = true + break + } + for _, ps := range pss { + if ps.PluginID == plugin.ID && ps.Enabled { + isMLPluginEnabled = true + break + } + } + break + } + } + + // Return nil if plugin is not enabled + if !isMLPluginEnabled { + return nil + } + + // Check if user has access to the plugin + if !hasAccess(ac.EvalPermission(pc.ActionAppAccess, "grafana-ml-app")) { + return nil + } + + var aimlChildNavs []*navtree.NavLink + + aimlChildNavs = append(aimlChildNavs, &navtree.NavLink{ + Text: "Metric forecasting", + SubTitle: "Create a forecast", + Id: "ai-ml-metric-forecast", + Url: s.cfg.AppSubURL + "/a/grafana-ml-app/metric-forecast", + }) + + aimlChildNavs = append(aimlChildNavs, &navtree.NavLink{ + Text: "Outlier detection", + SubTitle: "Create an outlier detector", + Id: "ai-ml-outlier-detection", + Url: s.cfg.AppSubURL + "/a/grafana-ml-app/outlier-detector", + }) + + aimlChildNavs = append(aimlChildNavs, &navtree.NavLink{ + Text: "Sift investigations", + SubTitle: "View and create investigations", + Id: "ai-ml-sift-investigations", + Url: s.cfg.AppSubURL + "/a/grafana-ml-app/investigations", + }) + + var aimlNav = navtree.NavLink{ + Text: "AI & Machine Learning", + SubTitle: "Explore AI and machine learning features", + Id: "ai-ml-home", + Icon: "gf-ml-alt", + Children: aimlChildNavs, + SortWeight: navtree.WeightAIAndML, + Url: s.cfg.AppSubURL + "/a/grafana-ml-app/home", + } + + return &aimlNav +} + func (s *ServiceImpl) buildAlertNavLinks(c *contextmodel.ReqContext) *navtree.NavLink { hasAccess := ac.HasAccess(s.accessControl, c) var alertChildNavs []*navtree.NavLink diff --git a/public/img/icons/custom/gf-ml-alt.svg b/public/img/icons/custom/gf-ml-alt.svg new file mode 100644 index 00000000000..218d766af2e --- /dev/null +++ b/public/img/icons/custom/gf-ml-alt.svg @@ -0,0 +1 @@ +