Navigation: remove description from the backend navmodel and use subTitle instead (#56155)

* remove description from the backend navmodel and use subTitle instead

* only add admin subtitle in topnav
This commit is contained in:
Ashley Harrison 2022-10-03 10:09:32 +01:00 committed by GitHub
parent f7de253cdd
commit 3e688ecf7d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 149 additions and 152 deletions

View File

@ -44,7 +44,6 @@ const (
type NavLink struct {
Id string `json:"id,omitempty"`
Text string `json:"text"`
Description string `json:"description,omitempty"`
Section string `json:"section,omitempty"`
SubTitle string `json:"subTitle,omitempty"`
Icon string `json:"icon,omitempty"` // Available icons can be browsed in Storybook: https://developers.grafana.com/ui/latest/index.html?path=/story/docs-overview-icon--icons-overview

View File

@ -18,62 +18,62 @@ func (s *ServiceImpl) getOrgAdminNode(c *models.ReqContext) (*navtree.NavLink, e
hasAccess := ac.HasAccess(s.accessControl, c)
if hasAccess(ac.ReqOrgAdmin, datasources.ConfigurationPageAccess) {
configNodes = append(configNodes, &navtree.NavLink{
Text: "Data sources",
Icon: "database",
Description: "Add and configure data sources",
Id: "datasources",
Url: s.cfg.AppSubURL + "/datasources",
Text: "Data sources",
Icon: "database",
SubTitle: "Add and configure data sources",
Id: "datasources",
Url: s.cfg.AppSubURL + "/datasources",
})
}
if s.features.IsEnabled(featuremgmt.FlagCorrelations) && hasAccess(ac.ReqOrgAdmin, correlations.ConfigurationPageAccess) {
configNodes = append(configNodes, &navtree.NavLink{
Text: "Correlations",
Icon: "gf-glue",
Description: "Add and configure correlations",
Id: "correlations",
Url: s.cfg.AppSubURL + "/datasources/correlations",
Text: "Correlations",
Icon: "gf-glue",
SubTitle: "Add and configure correlations",
Id: "correlations",
Url: s.cfg.AppSubURL + "/datasources/correlations",
})
}
if hasAccess(ac.ReqOrgAdmin, ac.EvalPermission(ac.ActionOrgUsersRead)) {
configNodes = append(configNodes, &navtree.NavLink{
Text: "Users",
Id: "users",
Description: "Invite and assign roles to users",
Icon: "user",
Url: s.cfg.AppSubURL + "/org/users",
Text: "Users",
Id: "users",
SubTitle: "Invite and assign roles to users",
Icon: "user",
Url: s.cfg.AppSubURL + "/org/users",
})
}
if hasAccess(s.ReqCanAdminTeams, ac.TeamsAccessEvaluator) {
configNodes = append(configNodes, &navtree.NavLink{
Text: "Teams",
Id: "teams",
Description: "Groups of users that have common dashboard and permission needs",
Icon: "users-alt",
Url: s.cfg.AppSubURL + "/org/teams",
Text: "Teams",
Id: "teams",
SubTitle: "Groups of users that have common dashboard and permission needs",
Icon: "users-alt",
Url: s.cfg.AppSubURL + "/org/teams",
})
}
// FIXME: while we don't have a permissions for listing plugins the legacy check has to stay as a default
if plugins.ReqCanAdminPlugins(s.cfg)(c) || hasAccess(plugins.ReqCanAdminPlugins(s.cfg), plugins.AdminAccessEvaluator) {
configNodes = append(configNodes, &navtree.NavLink{
Text: "Plugins",
Id: "plugins",
Description: "Extend the Grafana experience with plugins",
Icon: "plug",
Url: s.cfg.AppSubURL + "/plugins",
Text: "Plugins",
Id: "plugins",
SubTitle: "Extend the Grafana experience with plugins",
Icon: "plug",
Url: s.cfg.AppSubURL + "/plugins",
})
}
if hasAccess(ac.ReqOrgAdmin, ac.OrgPreferencesAccessEvaluator) {
configNodes = append(configNodes, &navtree.NavLink{
Text: "Preferences",
Id: "org-settings",
Description: "Manage preferences across an organization",
Icon: "sliders-v-alt",
Url: s.cfg.AppSubURL + "/org",
Text: "Preferences",
Id: "org-settings",
SubTitle: "Manage preferences across an organization",
Icon: "sliders-v-alt",
Url: s.cfg.AppSubURL + "/org",
})
}
@ -86,21 +86,21 @@ func (s *ServiceImpl) getOrgAdminNode(c *models.ReqContext) (*navtree.NavLink, e
apiKeysHidden := hideApiKeys == "1" && len(apiKeys) == 0
if hasAccess(ac.ReqOrgAdmin, ac.ApiKeyAccessEvaluator) && !apiKeysHidden {
configNodes = append(configNodes, &navtree.NavLink{
Text: "API keys",
Id: "apikeys",
Description: "Manage and create API keys that are used to interact with Grafana HTTP APIs",
Icon: "key-skeleton-alt",
Url: s.cfg.AppSubURL + "/org/apikeys",
Text: "API keys",
Id: "apikeys",
SubTitle: "Manage and create API keys that are used to interact with Grafana HTTP APIs",
Icon: "key-skeleton-alt",
Url: s.cfg.AppSubURL + "/org/apikeys",
})
}
if enableServiceAccount(s, c) {
configNodes = append(configNodes, &navtree.NavLink{
Text: "Service accounts",
Id: "serviceaccounts",
Description: "Use service accounts to run automated workloads in Grafana",
Icon: "gf-service-account",
Url: s.cfg.AppSubURL + "/org/serviceaccounts",
Text: "Service accounts",
Id: "serviceaccounts",
SubTitle: "Use service accounts to run automated workloads in Grafana",
Icon: "gf-service-account",
Url: s.cfg.AppSubURL + "/org/serviceaccounts",
})
}
@ -125,29 +125,29 @@ func (s *ServiceImpl) getServerAdminNode(c *models.ReqContext) *navtree.NavLink
if hasAccess(ac.ReqGrafanaAdmin, ac.EvalPermission(ac.ActionUsersRead, ac.ScopeGlobalUsersAll)) {
adminNavLinks = append(adminNavLinks, &navtree.NavLink{
Text: "Users", Description: "Manage and create users across the whole Grafana server", Id: "global-users", Url: s.cfg.AppSubURL + "/admin/users", Icon: "user",
Text: "Users", SubTitle: "Manage and create users across the whole Grafana server", Id: "global-users", Url: s.cfg.AppSubURL + "/admin/users", Icon: "user",
})
}
if hasGlobalAccess(ac.ReqGrafanaAdmin, orgsAccessEvaluator) {
adminNavLinks = append(adminNavLinks, &navtree.NavLink{
Text: "Organizations", Description: "Isolated instances of Grafana running on the same server", Id: "global-orgs", Url: s.cfg.AppSubURL + "/admin/orgs", Icon: "building",
Text: "Organizations", SubTitle: "Isolated instances of Grafana running on the same server", Id: "global-orgs", Url: s.cfg.AppSubURL + "/admin/orgs", Icon: "building",
})
}
if hasAccess(ac.ReqGrafanaAdmin, ac.EvalPermission(ac.ActionSettingsRead)) {
adminNavLinks = append(adminNavLinks, &navtree.NavLink{
Text: "Settings", Description: "View the settings defined in your Grafana config", Id: "server-settings", Url: s.cfg.AppSubURL + "/admin/settings", Icon: "sliders-v-alt",
Text: "Settings", SubTitle: "View the settings defined in your Grafana config", Id: "server-settings", Url: s.cfg.AppSubURL + "/admin/settings", Icon: "sliders-v-alt",
})
}
if hasAccess(ac.ReqGrafanaAdmin, ac.EvalPermission(ac.ActionSettingsRead)) && s.features.IsEnabled(featuremgmt.FlagStorage) {
adminNavLinks = append(adminNavLinks, &navtree.NavLink{
Text: "Storage",
Id: "storage",
Description: "Manage file storage",
Icon: "cube",
Url: s.cfg.AppSubURL + "/admin/storage",
Text: "Storage",
Id: "storage",
SubTitle: "Manage file storage",
Icon: "cube",
Url: s.cfg.AppSubURL + "/admin/storage",
})
}
@ -158,13 +158,16 @@ func (s *ServiceImpl) getServerAdminNode(c *models.ReqContext) *navtree.NavLink
}
adminNode := &navtree.NavLink{
Text: "Server admin",
Description: "Manage server-wide settings and access to resources such as organizations, users, and licenses",
Id: navtree.NavIDAdmin,
Icon: "shield",
SortWeight: navtree.WeightAdmin,
Section: navtree.NavSectionConfig,
Children: adminNavLinks,
Text: "Server admin",
Id: navtree.NavIDAdmin,
Icon: "shield",
SortWeight: navtree.WeightAdmin,
Section: navtree.NavSectionConfig,
Children: adminNavLinks,
}
if s.cfg.IsFeatureToggleEnabled(featuremgmt.FlagTopnav) {
adminNode.SubTitle = "Manage server-wide settings and access to resources such as organizations, users, and licenses"
}
if len(adminNavLinks) > 0 {

View File

@ -59,13 +59,13 @@ func (s *ServiceImpl) addAppLinks(treeRoot *navtree.NavTreeRoot, c *models.ReqCo
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",
Text: "Apps",
Icon: "apps",
SubTitle: "App plugins that extend the Grafana experience",
Id: "apps",
Children: appLinks,
Section: navtree.NavSectionCore,
Url: s.cfg.AppSubURL + "/apps",
})
} else {
for _, appLink := range appLinks {
@ -150,23 +150,23 @@ func (s *ServiceImpl) processAppPlugin(plugin plugins.PluginDTO, c *models.ReqCo
} 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",
Text: "Monitoring",
Id: navtree.NavIDMonitoring,
SubTitle: "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",
Text: "Alerts & incidents",
Id: navtree.NavIDAlertsAndIncidents,
SubTitle: "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 {

View File

@ -88,15 +88,14 @@ func (s *ServiceImpl) GetNavTree(c *models.ReqContext, hasEditPerm bool, prefs *
dashboardChildLinks := s.buildDashboardNavLinks(c, hasEditPerm)
dashboardLink := &navtree.NavLink{
Text: "Dashboards",
Id: navtree.NavIDDashboards,
Description: "Create and manage dashboards to visualize your data",
SubTitle: "Manage dashboards and folders",
Icon: "apps",
Url: s.cfg.AppSubURL + "/dashboards",
SortWeight: navtree.WeightDashboard,
Section: navtree.NavSectionCore,
Children: dashboardChildLinks,
Text: "Dashboards",
Id: navtree.NavIDDashboards,
SubTitle: "Create and manage dashboards to visualize your data",
Icon: "apps",
Url: s.cfg.AppSubURL + "/dashboards",
SortWeight: navtree.WeightDashboard,
Section: navtree.NavSectionCore,
Children: dashboardChildLinks,
}
treeRoot.AddSection(dashboardLink)
@ -318,24 +317,24 @@ func (s *ServiceImpl) buildDashboardNavLinks(c *models.ReqContext, hasEditPerm b
}
dashboardChildNavs = append(dashboardChildNavs, &navtree.NavLink{
Text: "Playlists", Description: "Groups of dashboards that are displayed in a sequence", Id: "dashboards/playlists", Url: s.cfg.AppSubURL + "/playlists", Icon: "presentation-play",
Text: "Playlists", SubTitle: "Groups of dashboards that are displayed in a sequence", Id: "dashboards/playlists", Url: s.cfg.AppSubURL + "/playlists", Icon: "presentation-play",
})
if c.IsSignedIn {
dashboardChildNavs = append(dashboardChildNavs, &navtree.NavLink{
Text: "Snapshots",
Description: "Interactive, publically available, point-in-time representations of dashboards",
Id: "dashboards/snapshots",
Url: s.cfg.AppSubURL + "/dashboard/snapshots",
Icon: "camera",
Text: "Snapshots",
SubTitle: "Interactive, publically available, point-in-time representations of dashboards",
Id: "dashboards/snapshots",
Url: s.cfg.AppSubURL + "/dashboard/snapshots",
Icon: "camera",
})
dashboardChildNavs = append(dashboardChildNavs, &navtree.NavLink{
Text: "Library panels",
Description: "Reusable panels that can be added to multiple dashboards",
Id: "dashboards/library-panels",
Url: s.cfg.AppSubURL + "/library-panels",
Icon: "library-panel",
Text: "Library panels",
SubTitle: "Reusable panels that can be added to multiple dashboards",
Id: "dashboards/library-panels",
Url: s.cfg.AppSubURL + "/library-panels",
Icon: "library-panel",
})
}
@ -391,14 +390,13 @@ func (s *ServiceImpl) buildLegacyAlertNavLinks(c *models.ReqContext) *navtree.Na
}
var alertNav = navtree.NavLink{
Text: "Alerting",
Description: "Learn about problems in your systems moments after they occur",
SubTitle: "Alert rules and notifications",
Id: "alerting-legacy",
Icon: "bell",
Children: alertChildNavs,
Section: navtree.NavSectionCore,
SortWeight: navtree.WeightAlerting,
Text: "Alerting",
SubTitle: "Learn about problems in your systems moments after they occur",
Id: "alerting-legacy",
Icon: "bell",
Children: alertChildNavs,
Section: navtree.NavSectionCore,
SortWeight: navtree.WeightAlerting,
}
if s.features.IsEnabled(featuremgmt.FlagTopnav) {
@ -416,21 +414,21 @@ func (s *ServiceImpl) buildAlertNavLinks(c *models.ReqContext, hasEditPerm bool)
if hasAccess(ac.ReqViewer, ac.EvalAny(ac.EvalPermission(ac.ActionAlertingRuleRead), ac.EvalPermission(ac.ActionAlertingRuleExternalRead))) {
alertChildNavs = append(alertChildNavs, &navtree.NavLink{
Text: "Alert rules", Description: "Rules that determine whether an alert will fire", Id: "alert-list", Url: s.cfg.AppSubURL + "/alerting/list", Icon: "list-ul",
Text: "Alert rules", SubTitle: "Rules that determine whether an alert will fire", Id: "alert-list", Url: s.cfg.AppSubURL + "/alerting/list", Icon: "list-ul",
})
}
if hasAccess(ac.ReqOrgAdminOrEditor, ac.EvalAny(ac.EvalPermission(ac.ActionAlertingNotificationsRead), ac.EvalPermission(ac.ActionAlertingNotificationsExternalRead))) {
alertChildNavs = append(alertChildNavs, &navtree.NavLink{
Text: "Contact points", Description: "Decide how your contacts are notified when an alert fires", Id: "receivers", Url: s.cfg.AppSubURL + "/alerting/notifications",
Icon: "comment-alt-share", SubTitle: "Manage the settings of your contact points",
Text: "Contact points", SubTitle: "Decide how your contacts are notified when an alert fires", Id: "receivers", Url: s.cfg.AppSubURL + "/alerting/notifications",
Icon: "comment-alt-share",
})
alertChildNavs = append(alertChildNavs, &navtree.NavLink{Text: "Notification policies", Description: "Determine how alerts are routed to contact points", Id: "am-routes", Url: s.cfg.AppSubURL + "/alerting/routes", Icon: "sitemap"})
alertChildNavs = append(alertChildNavs, &navtree.NavLink{Text: "Notification policies", SubTitle: "Determine how alerts are routed to contact points", Id: "am-routes", Url: s.cfg.AppSubURL + "/alerting/routes", Icon: "sitemap"})
}
if hasAccess(ac.ReqViewer, ac.EvalAny(ac.EvalPermission(ac.ActionAlertingInstanceRead), ac.EvalPermission(ac.ActionAlertingInstancesExternalRead))) {
alertChildNavs = append(alertChildNavs, &navtree.NavLink{Text: "Silences", Description: "Stop notifications from one or more alerting rules", Id: "silences", Url: s.cfg.AppSubURL + "/alerting/silences", Icon: "bell-slash"})
alertChildNavs = append(alertChildNavs, &navtree.NavLink{Text: "Alert groups", Description: "See grouped alerts from an Alertmanager instance", Id: "groups", Url: s.cfg.AppSubURL + "/alerting/groups", Icon: "layer-group"})
alertChildNavs = append(alertChildNavs, &navtree.NavLink{Text: "Silences", SubTitle: "Stop notifications from one or more alerting rules", Id: "silences", Url: s.cfg.AppSubURL + "/alerting/silences", Icon: "bell-slash"})
alertChildNavs = append(alertChildNavs, &navtree.NavLink{Text: "Alert groups", SubTitle: "See grouped alerts from an Alertmanager instance", Id: "groups", Url: s.cfg.AppSubURL + "/alerting/groups", Icon: "layer-group"})
}
if c.OrgRole == org.RoleAdmin {
@ -455,14 +453,13 @@ func (s *ServiceImpl) buildAlertNavLinks(c *models.ReqContext, hasEditPerm bool)
if len(alertChildNavs) > 0 {
var alertNav = navtree.NavLink{
Text: "Alerting",
Description: "Learn about problems in your systems moments after they occur",
SubTitle: "Alert rules and notifications",
Id: navtree.NavIDAlerting,
Icon: "bell",
Children: alertChildNavs,
Section: navtree.NavSectionCore,
SortWeight: navtree.WeightAlerting,
Text: "Alerting",
SubTitle: "Learn about problems in your systems moments after they occur",
Id: navtree.NavIDAlerting,
Icon: "bell",
Children: alertChildNavs,
Section: navtree.NavSectionCore,
SortWeight: navtree.WeightAlerting,
}
if s.features.IsEnabled(featuremgmt.FlagTopnav) {
@ -485,27 +482,27 @@ func (s *ServiceImpl) buildDataConnectionsNavLink(c *models.ReqContext) *navtree
baseUrl := s.cfg.AppSubURL + "/" + baseId
children = append(children, &navtree.NavLink{
Id: baseId + "-datasources",
Text: "Data sources",
Icon: "database",
Description: "Add and configure data sources",
Url: baseUrl + "/datasources",
Id: baseId + "-datasources",
Text: "Data sources",
Icon: "database",
SubTitle: "Add and configure data sources",
Url: baseUrl + "/datasources",
})
children = append(children, &navtree.NavLink{
Id: baseId + "-plugins",
Text: "Plugins",
Icon: "plug",
Description: "Manage plugins",
Url: baseUrl + "/plugins",
Id: baseId + "-plugins",
Text: "Plugins",
Icon: "plug",
SubTitle: "Manage plugins",
Url: baseUrl + "/plugins",
})
children = append(children, &navtree.NavLink{
Id: baseId + "-cloud-integrations",
Text: "Cloud integrations",
Icon: "bolt",
Description: "Manage your cloud integrations",
Url: baseUrl + "/cloud-integrations",
Id: baseId + "-cloud-integrations",
Text: "Cloud integrations",
Icon: "bolt",
SubTitle: "Manage your cloud integrations",
Url: baseUrl + "/cloud-integrations",
})
navLink = &navtree.NavLink{

View File

@ -14,26 +14,26 @@ describe('NavLandingPage', () => {
const mockSectionSubtitle = 'Section subtitle';
const mockChild1 = {
text: 'Child 1',
description: 'Child 1 description',
subTitle: 'Child 1 subTitle',
id: 'child1',
url: 'mock-section-url/child1',
};
const mockChild2 = {
text: 'Child 2',
description: 'Child 2 description',
subTitle: 'Child 2 subTitle',
id: 'child2',
url: 'mock-section-url/child2',
};
const mockChild3 = {
text: 'Child 3',
id: 'child3',
description: 'Child 3 subtitle',
subTitle: 'Child 3 subtitle',
url: 'mock-section-url/child3',
hideFromTabs: true,
children: [
{
text: 'Child 3.1',
description: 'Child 3.1 description',
subTitle: 'Child 3.1 subTitle',
id: 'child3.1',
url: 'mock-section-url/child3/child3.1',
},
@ -75,10 +75,10 @@ describe('NavLandingPage', () => {
expect(screen.getByRole('link', { name: mockChild2.text })).toBeInTheDocument();
});
it('renders the description for each direct child', () => {
it('renders the subTitle for each direct child', () => {
setup();
expect(screen.getByText(mockChild1.description)).toBeInTheDocument();
expect(screen.getByText(mockChild2.description)).toBeInTheDocument();
expect(screen.getByText(mockChild1.subTitle)).toBeInTheDocument();
expect(screen.getByText(mockChild2.subTitle)).toBeInTheDocument();
});
it('renders the heading for nested sections', () => {
@ -86,9 +86,9 @@ describe('NavLandingPage', () => {
expect(screen.getByRole('heading', { name: mockChild3.text })).toBeInTheDocument();
});
it('renders the description for a nested section', () => {
it('renders the subTitle for a nested section', () => {
setup();
expect(screen.getByText(mockChild3.description)).toBeInTheDocument();
expect(screen.getByText(mockChild3.subTitle)).toBeInTheDocument();
});
it('renders a link for a nested child', () => {
@ -96,8 +96,8 @@ describe('NavLandingPage', () => {
expect(screen.getByRole('link', { name: mockChild3.children[0].text })).toBeInTheDocument();
});
it('renders the description for a nested child', () => {
it('renders the subTitle for a nested child', () => {
setup();
expect(screen.getByText(mockChild3.children[0].description)).toBeInTheDocument();
expect(screen.getByText(mockChild3.children[0].subTitle)).toBeInTheDocument();
});
});

View File

@ -27,7 +27,7 @@ export function NavLandingPage({ navId }: Props) {
{directChildren?.map((child) => (
<NavLandingPageCard
key={child.id}
description={child.description}
description={child.subTitle}
text={child.text}
url={child.url ?? ''}
/>
@ -36,15 +36,13 @@ export function NavLandingPage({ navId }: Props) {
)}
{nestedChildren?.map((child) => (
<section key={child.id}>
<div style={{ display: 'flex', alignItems: 'center' }}>
<h2 className={styles.nestedTitle}>{child.text}</h2>
</div>
<div className={styles.nestedDescription}>{child.description}</div>
<h2 className={styles.nestedTitle}>{child.text}</h2>
<div className={styles.nestedDescription}>{child.subTitle}</div>
<div className={styles.grid}>
{child.children?.map((child) => (
<NavLandingPageCard
key={child.id}
description={child.description}
description={child.subTitle}
text={child.text}
url={child.url ?? ''}
/>

View File

@ -11,7 +11,7 @@ export interface Props {
export function PageHeader({ navItem, subTitle }: Props) {
const styles = useStyles2(getStyles);
const sub = subTitle ?? navItem.description;
const sub = subTitle ?? navItem.subTitle;
return (
<>