NavTree: Make it possible to configure where in nav tree plugins live (#55484)
* NewIA: Plugin nav config * progress * Progress * Things are working * Add monitoring node * Add alerts and incidents * added experiment with standalone page * Refactoring by adding a type for navtree root * First test working * More tests * more tests * Progress on richer config and sorting * Sort weight working * Path config * Improving logic for not including admin or cfg nodes, making it the last step so that enterprise can add admin nodes without having to worry about the section not existing * fixed index routes * removed file * Fixes * Fixing tests * Fixing more tests and adding support for weight config * Updates * Remove unused fake * More fixes * Minor tweak * Minor fix * Can now control position using sortweight even when existing items have no sortweight * Added tests for frontend standalone page logic * more tests * Remove unused fake and fixed lint issue * Moving reading settings to navtree impl package * remove nav_id setting prefix * Remove old test file * Fix trailing newline * Fixed bug with adding nil node * fixing lint issue * remove some code we have to rethink * move read settings to PrivideService and switch to util.SplitString
This commit is contained in:
@@ -3,6 +3,7 @@ package navtreeimpl
|
||||
import (
|
||||
"path"
|
||||
"sort"
|
||||
"strconv"
|
||||
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
@@ -10,15 +11,17 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/navtree"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsettings"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
func (s *ServiceImpl) getAppLinks(c *models.ReqContext) ([]*navtree.NavLink, error) {
|
||||
func (s *ServiceImpl) addAppLinks(treeRoot *navtree.NavTreeRoot, c *models.ReqContext) error {
|
||||
topNavEnabled := s.features.IsEnabled(featuremgmt.FlagTopnav)
|
||||
hasAccess := ac.HasAccess(s.accessControl, c)
|
||||
appLinks := []*navtree.NavLink{}
|
||||
|
||||
pss, err := s.pluginSettings.GetPluginSettings(c.Req.Context(), &pluginsettings.GetArgs{OrgID: c.OrgID})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
|
||||
isPluginEnabled := func(plugin plugins.PluginDTO) bool {
|
||||
@@ -43,63 +46,8 @@ func (s *ServiceImpl) getAppLinks(c *models.ReqContext) ([]*navtree.NavLink, err
|
||||
continue
|
||||
}
|
||||
|
||||
appLink := &navtree.NavLink{
|
||||
Text: plugin.Name,
|
||||
Id: "plugin-page-" + plugin.ID,
|
||||
Img: plugin.Info.Logos.Small,
|
||||
Section: navtree.NavSectionPlugin,
|
||||
SortWeight: navtree.WeightPlugin,
|
||||
}
|
||||
|
||||
if s.features.IsEnabled(featuremgmt.FlagTopnav) {
|
||||
appLink.Url = s.cfg.AppSubURL + "/a/" + plugin.ID
|
||||
} else {
|
||||
appLink.Url = path.Join(s.cfg.AppSubURL, plugin.DefaultNavURL)
|
||||
}
|
||||
|
||||
for _, include := range plugin.Includes {
|
||||
if !c.HasUserRole(include.Role) {
|
||||
continue
|
||||
}
|
||||
|
||||
if include.Type == "page" && include.AddToNav {
|
||||
var link *navtree.NavLink
|
||||
if len(include.Path) > 0 {
|
||||
link = &navtree.NavLink{
|
||||
Url: s.cfg.AppSubURL + include.Path,
|
||||
Text: include.Name,
|
||||
}
|
||||
if include.DefaultNav && !s.features.IsEnabled(featuremgmt.FlagTopnav) {
|
||||
appLink.Url = link.Url // Overwrite the hardcoded page logic
|
||||
}
|
||||
} else {
|
||||
link = &navtree.NavLink{
|
||||
Url: s.cfg.AppSubURL + "/plugins/" + plugin.ID + "/page/" + include.Slug,
|
||||
Text: include.Name,
|
||||
}
|
||||
}
|
||||
link.Icon = include.Icon
|
||||
appLink.Children = append(appLink.Children, link)
|
||||
}
|
||||
|
||||
if include.Type == "dashboard" && include.AddToNav {
|
||||
dboardURL := include.DashboardURLPath()
|
||||
if dboardURL != "" {
|
||||
link := &navtree.NavLink{
|
||||
Url: path.Join(s.cfg.AppSubURL, dboardURL),
|
||||
Text: include.Name,
|
||||
}
|
||||
appLink.Children = append(appLink.Children, link)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(appLink.Children) > 0 {
|
||||
// If we only have one child and it's the app default nav then remove it from children
|
||||
if len(appLink.Children) == 1 && appLink.Children[0].Url == appLink.Url {
|
||||
appLink.Children = []*navtree.NavLink{}
|
||||
}
|
||||
appLinks = append(appLinks, appLink)
|
||||
if appNode := s.processAppPlugin(plugin, c, topNavEnabled, treeRoot); appNode != nil {
|
||||
appLinks = append(appLinks, appNode)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,5 +57,157 @@ func (s *ServiceImpl) getAppLinks(c *models.ReqContext) ([]*navtree.NavLink, err
|
||||
})
|
||||
}
|
||||
|
||||
return appLinks, nil
|
||||
if topNavEnabled {
|
||||
treeRoot.AddSection(&navtree.NavLink{
|
||||
Text: "Apps",
|
||||
Icon: "apps",
|
||||
Description: "App plugins that extend the Grafana experience",
|
||||
Id: "apps",
|
||||
Children: appLinks,
|
||||
Section: navtree.NavSectionCore,
|
||||
Url: s.cfg.AppSubURL + "/apps",
|
||||
})
|
||||
} else {
|
||||
for _, appLink := range appLinks {
|
||||
treeRoot.AddSection(appLink)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ServiceImpl) processAppPlugin(plugin plugins.PluginDTO, c *models.ReqContext, topNavEnabled bool, treeRoot *navtree.NavTreeRoot) *navtree.NavLink {
|
||||
appLink := &navtree.NavLink{
|
||||
Text: plugin.Name,
|
||||
Id: "plugin-page-" + plugin.ID,
|
||||
Img: plugin.Info.Logos.Small,
|
||||
Section: navtree.NavSectionPlugin,
|
||||
SortWeight: navtree.WeightPlugin,
|
||||
}
|
||||
|
||||
if s.features.IsEnabled(featuremgmt.FlagTopnav) {
|
||||
appLink.Url = s.cfg.AppSubURL + "/a/" + plugin.ID
|
||||
} else {
|
||||
appLink.Url = path.Join(s.cfg.AppSubURL, plugin.DefaultNavURL)
|
||||
}
|
||||
|
||||
for _, include := range plugin.Includes {
|
||||
if !c.HasUserRole(include.Role) {
|
||||
continue
|
||||
}
|
||||
|
||||
if include.Type == "page" && include.AddToNav {
|
||||
link := &navtree.NavLink{
|
||||
Text: include.Name,
|
||||
Icon: include.Icon,
|
||||
}
|
||||
|
||||
if len(include.Path) > 0 {
|
||||
link.Url = s.cfg.AppSubURL + include.Path
|
||||
if include.DefaultNav {
|
||||
appLink.Url = link.Url
|
||||
}
|
||||
} else {
|
||||
link.Url = s.cfg.AppSubURL + "/plugins/" + plugin.ID + "/page/" + include.Slug
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
} else {
|
||||
appLink.Children = append(appLink.Children, link)
|
||||
}
|
||||
}
|
||||
|
||||
if include.Type == "dashboard" && include.AddToNav {
|
||||
dboardURL := include.DashboardURLPath()
|
||||
if dboardURL != "" {
|
||||
link := &navtree.NavLink{
|
||||
Url: path.Join(s.cfg.AppSubURL, dboardURL),
|
||||
Text: include.Name,
|
||||
}
|
||||
appLink.Children = append(appLink.Children, link)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(appLink.Children) > 0 {
|
||||
// If we only have one child and it's the app default nav then remove it from children
|
||||
if len(appLink.Children) == 1 && appLink.Children[0].Url == appLink.Url {
|
||||
appLink.Children = []*navtree.NavLink{}
|
||||
}
|
||||
|
||||
alertingNode := treeRoot.FindById(navtree.NavIDAlerting)
|
||||
|
||||
if navConfig, hasOverride := s.navigationAppConfig[plugin.ID]; hasOverride && topNavEnabled {
|
||||
appLink.SortWeight = navConfig.SortWeight
|
||||
|
||||
if navNode := treeRoot.FindById(navConfig.SectionID); navNode != nil {
|
||||
navNode.Children = append(navNode.Children, appLink)
|
||||
} else {
|
||||
if navConfig.SectionID == navtree.NavIDMonitoring {
|
||||
treeRoot.AddSection(&navtree.NavLink{
|
||||
Text: "Monitoring",
|
||||
Id: navtree.NavIDMonitoring,
|
||||
Description: "Monitoring and infrastructure apps",
|
||||
Icon: "heart-rate",
|
||||
Section: navtree.NavSectionCore,
|
||||
Children: []*navtree.NavLink{appLink},
|
||||
Url: s.cfg.AppSubURL + "/monitoring",
|
||||
})
|
||||
} else if navConfig.SectionID == navtree.NavIDAlertsAndIncidents && alertingNode != nil {
|
||||
treeRoot.AddSection(&navtree.NavLink{
|
||||
Text: "Alerts & incidents",
|
||||
Id: navtree.NavIDAlertsAndIncidents,
|
||||
Description: "Alerting and incident management apps",
|
||||
Icon: "bell",
|
||||
Section: navtree.NavSectionCore,
|
||||
Children: []*navtree.NavLink{alertingNode, appLink},
|
||||
Url: s.cfg.AppSubURL + "/alerts-and-incidents",
|
||||
})
|
||||
treeRoot.RemoveSection(alertingNode)
|
||||
} else {
|
||||
s.log.Error("Plugin app nav id not found", "pluginId", plugin.ID, "navId", navConfig.SectionID)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return appLink
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ServiceImpl) readNavigationSettings() {
|
||||
s.navigationAppConfig = map[string]NavigationAppConfig{
|
||||
"grafana-k8s-app": {SectionID: navtree.NavIDMonitoring, SortWeight: 1},
|
||||
"grafana-synthetic-monitoring-app": {SectionID: navtree.NavIDMonitoring, SortWeight: 2},
|
||||
"grafana-oncall-app": {SectionID: navtree.NavIDAlertsAndIncidents, SortWeight: 1},
|
||||
"grafana-incident-app": {SectionID: navtree.NavIDAlertsAndIncidents, SortWeight: 2},
|
||||
"grafana-ml-app": {SectionID: navtree.NavIDAlertsAndIncidents, SortWeight: 3},
|
||||
}
|
||||
|
||||
s.navigationAppPathConfig = map[string]NavigationAppConfig{
|
||||
"/a/grafana-auth-app": {SectionID: navtree.NavIDCfg, SortWeight: 7},
|
||||
}
|
||||
|
||||
sec := s.cfg.Raw.Section("navigation.apps")
|
||||
|
||||
for _, key := range sec.Keys() {
|
||||
pluginId := key.Name()
|
||||
// Support <id> <weight> value
|
||||
values := util.SplitString(sec.Key(key.Name()).MustString(""))
|
||||
|
||||
appCfg := &NavigationAppConfig{SectionID: values[0]}
|
||||
if len(values) > 1 {
|
||||
if weight, err := strconv.ParseInt(values[1], 10, 64); err == nil {
|
||||
appCfg.SortWeight = weight
|
||||
}
|
||||
}
|
||||
|
||||
s.navigationAppConfig[pluginId] = *appCfg
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user