From d4e013fd44f0915d9f1ae6abda504f66fe4e2fc3 Mon Sep 17 00:00:00 2001 From: Emil Tullstedt Date: Fri, 15 Nov 2019 09:28:55 +0100 Subject: [PATCH] NavLinks: Make ordering in navigation configurable (#20382) The ordering of links in the navigation bar is currently based the order of the slice containing the navigation tree. Since Grafana supports adding more links to the navigation bar with `RunIndexDataHooks` which runs at the very end of the function this means that any link registered through a hook will be placed last in the slice and be displayed last in the menu. With this PR the ordering can be specified with a weight which allows for placing links created by extensions in a more intuitive place where applicable. Stable sorting is used to ensure that the current FIFO ordering is preserved when either no weight is set or two items shares the same weight. --- pkg/api/dtos/index.go | 18 ++++++++++ pkg/api/index.go | 78 +++++++++++++++++++++++++------------------ 2 files changed, 64 insertions(+), 32 deletions(-) diff --git a/pkg/api/dtos/index.go b/pkg/api/dtos/index.go index bd3ac76eec8..4aa50bc72f6 100644 --- a/pkg/api/dtos/index.go +++ b/pkg/api/dtos/index.go @@ -22,6 +22,23 @@ type PluginCss struct { Dark string `json:"dark"` } +const ( + // These weights may be used by an extension to reliably place + // itself in relation to a particular item in the menu. The weights + // are negative to ensure that the default items are placed above + // any items with default weight. + + WeightCreate = (iota - 20) * 100 + WeightDashboard + WeightExplore + WeightProfile + WeightAlerting + WeightPlugin + WeightConfig + WeightAdmin + WeightHelp +) + type NavLink struct { Id string `json:"id,omitempty"` Text string `json:"text,omitempty"` @@ -31,6 +48,7 @@ type NavLink struct { Img string `json:"img,omitempty"` Url string `json:"url,omitempty"` Target string `json:"target,omitempty"` + SortWeight int64 `json:"sortWeight,omitempty"` Divider bool `json:"divider,omitempty"` HideFromMenu bool `json:"hideFromMenu,omitempty"` HideFromTabs bool `json:"hideFromTabs,omitempty"` diff --git a/pkg/api/index.go b/pkg/api/index.go index 69ac9d60a46..678d42a6dcb 100644 --- a/pkg/api/index.go +++ b/pkg/api/index.go @@ -2,6 +2,7 @@ package api import ( "fmt" + "sort" "strings" "github.com/grafana/grafana/pkg/api/dtos" @@ -115,11 +116,12 @@ func (hs *HTTPServer) setIndexViewData(c *m.ReqContext) (*dtos.IndexViewData, er children = append(children, &dtos.NavLink{Text: "Import", SubTitle: "Import dashboard from file or Grafana.com", Id: "import", Icon: "gicon gicon-dashboard-import", Url: setting.AppSubUrl + "/dashboard/import"}) data.NavTree = append(data.NavTree, &dtos.NavLink{ - Text: "Create", - Id: "create", - Icon: "fa fa-fw fa-plus", - Url: setting.AppSubUrl + "/dashboard/new", - Children: children, + Text: "Create", + Id: "create", + Icon: "fa fa-fw fa-plus", + Url: setting.AppSubUrl + "/dashboard/new", + Children: children, + SortWeight: dtos.WeightCreate, }) } @@ -132,21 +134,23 @@ func (hs *HTTPServer) setIndexViewData(c *m.ReqContext) (*dtos.IndexViewData, er } data.NavTree = append(data.NavTree, &dtos.NavLink{ - Text: "Dashboards", - Id: "dashboards", - SubTitle: "Manage dashboards & folders", - Icon: "gicon gicon-dashboard", - Url: setting.AppSubUrl + "/", - Children: dashboardChildNavs, + Text: "Dashboards", + Id: "dashboards", + SubTitle: "Manage dashboards & folders", + Icon: "gicon gicon-dashboard", + Url: setting.AppSubUrl + "/", + SortWeight: dtos.WeightDashboard, + Children: dashboardChildNavs, }) if setting.ExploreEnabled && (c.OrgRole == m.ROLE_ADMIN || c.OrgRole == m.ROLE_EDITOR || setting.ViewersCanEdit) { data.NavTree = append(data.NavTree, &dtos.NavLink{ - Text: "Explore", - Id: "explore", - SubTitle: "Explore your data", - Icon: "gicon gicon-explore", - Url: setting.AppSubUrl + "/explore", + Text: "Explore", + Id: "explore", + SubTitle: "Explore your data", + Icon: "gicon gicon-explore", + SortWeight: dtos.WeightExplore, + Url: setting.AppSubUrl + "/explore", }) } @@ -163,6 +167,7 @@ func (hs *HTTPServer) setIndexViewData(c *m.ReqContext) (*dtos.IndexViewData, er Img: data.User.GravatarUrl, Url: setting.AppSubUrl + "/profile", HideFromMenu: true, + SortWeight: dtos.WeightProfile, Children: []*dtos.NavLink{ {Text: "Preferences", Id: "profile-settings", Url: setting.AppSubUrl + "/profile", Icon: "gicon gicon-preferences"}, {Text: "Change Password", Id: "change-password", Url: setting.AppSubUrl + "/profile/password", Icon: "fa fa-fw fa-lock", HideFromMenu: true}, @@ -186,12 +191,13 @@ func (hs *HTTPServer) setIndexViewData(c *m.ReqContext) (*dtos.IndexViewData, er } data.NavTree = append(data.NavTree, &dtos.NavLink{ - Text: "Alerting", - SubTitle: "Alert rules & notifications", - Id: "alerting", - Icon: "gicon gicon-alert", - Url: setting.AppSubUrl + "/alerting/list", - Children: alertChildNavs, + Text: "Alerting", + SubTitle: "Alert rules & notifications", + Id: "alerting", + Icon: "gicon gicon-alert", + Url: setting.AppSubUrl + "/alerting/list", + Children: alertChildNavs, + SortWeight: dtos.WeightAlerting, }) } @@ -203,10 +209,11 @@ func (hs *HTTPServer) setIndexViewData(c *m.ReqContext) (*dtos.IndexViewData, er for _, plugin := range enabledPlugins.Apps { if plugin.Pinned { appLink := &dtos.NavLink{ - Text: plugin.Name, - Id: "plugin-page-" + plugin.Id, - Url: plugin.DefaultNavUrl, - Img: plugin.Info.Logos.Small, + Text: plugin.Name, + Id: "plugin-page-" + plugin.Id, + Url: plugin.DefaultNavUrl, + Img: plugin.Info.Logos.Small, + SortWeight: dtos.WeightPlugin, } for _, include := range plugin.Includes { @@ -297,12 +304,13 @@ func (hs *HTTPServer) setIndexViewData(c *m.ReqContext) (*dtos.IndexViewData, er } data.NavTree = append(data.NavTree, &dtos.NavLink{ - Id: "cfg", - Text: "Configuration", - SubTitle: "Organization: " + c.OrgName, - Icon: "gicon gicon-cog", - Url: configNodes[0].Url, - Children: configNodes, + Id: "cfg", + Text: "Configuration", + SubTitle: "Organization: " + c.OrgName, + Icon: "gicon gicon-cog", + Url: configNodes[0].Url, + SortWeight: dtos.WeightConfig, + Children: configNodes, }) if c.IsGrafanaAdmin { @@ -326,6 +334,7 @@ func (hs *HTTPServer) setIndexViewData(c *m.ReqContext) (*dtos.IndexViewData, er Id: "admin", Icon: "gicon gicon-shield", Url: setting.AppSubUrl + "/admin/users", + SortWeight: dtos.WeightAdmin, Children: adminNavLinks, }) } @@ -337,6 +346,7 @@ func (hs *HTTPServer) setIndexViewData(c *m.ReqContext) (*dtos.IndexViewData, er Url: "#", Icon: "gicon gicon-question", HideFromMenu: true, + SortWeight: dtos.WeightHelp, Children: []*dtos.NavLink{ {Text: "Keyboard shortcuts", Url: "/shortcuts", Icon: "fa fa-fw fa-keyboard-o", Target: "_self"}, {Text: "Community site", Url: "http://community.grafana.com", Icon: "fa fa-fw fa-comment", Target: "_blank"}, @@ -345,6 +355,10 @@ func (hs *HTTPServer) setIndexViewData(c *m.ReqContext) (*dtos.IndexViewData, er }) hs.HooksService.RunIndexDataHooks(&data) + + sort.SliceStable(data.NavTree, func(i, j int) bool { + return data.NavTree[i].SortWeight < data.NavTree[j].SortWeight + }) return &data, nil }