diff --git a/packages/grafana-data/src/types/navModel.ts b/packages/grafana-data/src/types/navModel.ts index 33905084342..63e3e7050f0 100644 --- a/packages/grafana-data/src/types/navModel.ts +++ b/packages/grafana-data/src/types/navModel.ts @@ -28,6 +28,8 @@ export interface NavLinkDTO { emptyMessageId?: string; // The ID of the plugin that registered the page (in case it was registered by a plugin, otherwise left empty) pluginId?: string; + // Whether the page is used to create a new resource. We may place these in a different position in the UI. + isCreateAction?: boolean; } export interface NavModelItem extends NavLinkDTO { diff --git a/pkg/services/navtree/models.go b/pkg/services/navtree/models.go index 00358200547..f71ceea0808 100644 --- a/pkg/services/navtree/models.go +++ b/pkg/services/navtree/models.go @@ -68,6 +68,7 @@ type NavLink struct { HighlightID string `json:"highlightId,omitempty"` EmptyMessageId string `json:"emptyMessageId,omitempty"` PluginID string `json:"pluginId,omitempty"` // (Optional) The ID of the plugin that registered nav link (e.g. as a standalone plugin page) + IsCreateAction bool `json:"isCreateAction,omitempty"` } func (node *NavLink) Sort() { diff --git a/pkg/services/navtree/navtreeimpl/navtree.go b/pkg/services/navtree/navtreeimpl/navtree.go index 94ed6c125b4..73d601b6ba3 100644 --- a/pkg/services/navtree/navtreeimpl/navtree.go +++ b/pkg/services/navtree/navtreeimpl/navtree.go @@ -408,13 +408,17 @@ func (s *ServiceImpl) buildDashboardNavLinks(c *models.ReqContext, hasEditPerm b dashboardChildNavs = append(dashboardChildNavs, &navtree.NavLink{ Text: "Divider", Divider: true, Id: "divider", HideFromTabs: true, }) + } + if hasEditPerm { if hasAccess(hasEditPermInAnyFolder, ac.EvalPermission(dashboards.ActionDashboardsCreate)) { dashboardChildNavs = append(dashboardChildNavs, &navtree.NavLink{ - Text: "New dashboard", Icon: "plus", Url: s.cfg.AppSubURL + "/dashboard/new", HideFromTabs: true, Id: "dashboards/new", ShowIconInNavbar: true, + Text: "New dashboard", Icon: "plus", Url: s.cfg.AppSubURL + "/dashboard/new", HideFromTabs: true, Id: "dashboards/new", ShowIconInNavbar: true, IsCreateAction: true, }) } + } + if hasEditPerm && !s.features.IsEnabled(featuremgmt.FlagTopnav) { if hasAccess(ac.ReqOrgAdminOrEditor, ac.EvalPermission(dashboards.ActionFoldersCreate)) { dashboardChildNavs = append(dashboardChildNavs, &navtree.NavLink{ Text: "New folder", SubTitle: "Create a new folder to organize your dashboards", Id: "dashboards/folder/new", @@ -498,13 +502,15 @@ func (s *ServiceImpl) buildAlertNavLinks(c *models.ReqContext, hasEditPerm bool) fallbackHasEditPerm := func(*models.ReqContext) bool { return hasEditPerm } if hasAccess(fallbackHasEditPerm, ac.EvalAny(ac.EvalPermission(ac.ActionAlertingRuleCreate), ac.EvalPermission(ac.ActionAlertingRuleExternalWrite))) { - alertChildNavs = append(alertChildNavs, &navtree.NavLink{ - Text: "Divider", Divider: true, Id: "divider", HideFromTabs: true, - }) + if !s.features.IsEnabled(featuremgmt.FlagTopnav) { + alertChildNavs = append(alertChildNavs, &navtree.NavLink{ + Text: "Divider", Divider: true, Id: "divider", HideFromTabs: true, + }) + } alertChildNavs = append(alertChildNavs, &navtree.NavLink{ Text: "New alert rule", SubTitle: "Create an alert rule", Id: "alert", - Icon: "plus", Url: s.cfg.AppSubURL + "/alerting/new", HideFromTabs: true, ShowIconInNavbar: true, + Icon: "plus", Url: s.cfg.AppSubURL + "/alerting/new", HideFromTabs: true, ShowIconInNavbar: true, IsCreateAction: true, }) } diff --git a/public/app/core/components/AppChrome/NavToolbarSeparator.tsx b/public/app/core/components/AppChrome/NavToolbarSeparator.tsx index 72b328ba2d9..36fe42e58b8 100644 --- a/public/app/core/components/AppChrome/NavToolbarSeparator.tsx +++ b/public/app/core/components/AppChrome/NavToolbarSeparator.tsx @@ -1,4 +1,4 @@ -import { css } from '@emotion/css'; +import { css, cx } from '@emotion/css'; import React from 'react'; import { GrafanaTheme2 } from '@grafana/data'; @@ -6,18 +6,19 @@ import { config } from '@grafana/runtime'; import { useStyles2 } from '@grafana/ui'; export interface Props { + className?: string; leftActionsSeparator?: boolean; } -export function NavToolbarSeparator({ leftActionsSeparator }: Props) { +export function NavToolbarSeparator({ className, leftActionsSeparator }: Props) { const styles = useStyles2(getStyles); if (leftActionsSeparator) { - return
; + return ; } if (config.featureToggles.topnav) { - return ; + return ; } return null; diff --git a/public/app/core/components/AppChrome/QuickAdd/QuickAdd.test.tsx b/public/app/core/components/AppChrome/QuickAdd/QuickAdd.test.tsx new file mode 100644 index 00000000000..dbf20988fde --- /dev/null +++ b/public/app/core/components/AppChrome/QuickAdd/QuickAdd.test.tsx @@ -0,0 +1,73 @@ +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import React from 'react'; +import { Provider } from 'react-redux'; + +import { NavModelItem, NavSection } from '@grafana/data'; +import { configureStore } from 'app/store/configureStore'; + +import { QuickAdd } from './QuickAdd'; + +const setup = () => { + const navBarTree: NavModelItem[] = [ + { + text: 'Section 1', + section: NavSection.Core, + id: 'section1', + url: 'section1', + children: [ + { text: 'New child 1', id: 'child1', url: 'section1/child1', isCreateAction: true }, + { text: 'Child2', id: 'child2', url: 'section1/child2' }, + ], + }, + { + text: 'Section 2', + id: 'section2', + section: NavSection.Config, + url: 'section2', + children: [{ text: 'New child 3', id: 'child3', url: 'section2/child3', isCreateAction: true }], + }, + ]; + + const store = configureStore({ navBarTree }); + + return render( +