diff --git a/.betterer.results b/.betterer.results index c904362ac2d..77c376299b1 100644 --- a/.betterer.results +++ b/.betterer.results @@ -1537,11 +1537,6 @@ exports[`better eslint`] = { [0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "0"], [0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "1"] ], - "public/app/core/components/NavBar/NavBarItemMenuTrigger.tsx:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"], - [0, 0, 0, "Do not use any type assertions.", "1"], - [0, 0, 0, "Do not use any type assertions.", "2"] - ], "public/app/core/components/OptionsUI/registry.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"], diff --git a/conf/defaults.ini b/conf/defaults.ini index 322bf3a89bf..ecd4f941f7a 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -1462,7 +1462,6 @@ index_update_interval = 10s # Move an app plugin referenced by its id (including all its pages) to a specific navigation section -# Dependencies: needs the `topnav` feature to be enabled # Format: =
[navigation.app_sections] diff --git a/conf/sample.ini b/conf/sample.ini index 2a7a562c5c9..29331b68d77 100644 --- a/conf/sample.ini +++ b/conf/sample.ini @@ -1392,7 +1392,6 @@ ;enable_custom_baselayers = true # Move an app plugin referenced by its id (including all its pages) to a specific navigation section -# Dependencies: needs the `topnav` feature to be enabled [navigation.app_sections] # The following will move an app plugin with the id of `my-app-id` under the `starred` section # my-app-id = admin diff --git a/e2e/cloud-plugins-suite/azure-monitor.spec.ts b/e2e/cloud-plugins-suite/azure-monitor.spec.ts index 37ced38c1d3..71a9e5c4819 100644 --- a/e2e/cloud-plugins-suite/azure-monitor.spec.ts +++ b/e2e/cloud-plugins-suite/azure-monitor.spec.ts @@ -2,7 +2,6 @@ import { load } from 'js-yaml'; import { v4 as uuidv4 } from 'uuid'; import { e2e } from '@grafana/e2e'; -import { GrafanaBootConfig } from '@grafana/runtime'; import { selectors } from '../../public/app/plugins/datasource/azuremonitor/e2e/selectors'; import { @@ -97,15 +96,7 @@ const addAzureMonitorVariable = ( break; } e2e.pages.Dashboard.Settings.Variables.Edit.General.submitButton().click(); - e2e() - .window() - .then((win: Cypress.AUTWindow & { grafanaBootData: GrafanaBootConfig['bootData'] }) => { - if (win.grafanaBootData.settings.featureToggles.topnav) { - e2e.pages.Dashboard.Settings.Actions.close().click(); - } else { - e2e.components.PageToolbar.item('Go Back').click(); - } - }); + e2e.pages.Dashboard.Settings.Actions.close().click(); }; e2e.scenario({ diff --git a/e2e/dashboards-suite/new-constant-variable.spec.ts b/e2e/dashboards-suite/new-constant-variable.spec.ts index c595e49c692..69ac02bff28 100644 --- a/e2e/dashboards-suite/new-constant-variable.spec.ts +++ b/e2e/dashboards-suite/new-constant-variable.spec.ts @@ -1,5 +1,4 @@ import { e2e } from '@grafana/e2e'; -import { GrafanaBootConfig } from '@grafana/runtime'; const PAGE_UNDER_TEST = 'kVi2Gex7z/test-variable-output'; const DASHBOARD_NAME = 'Test variable output'; @@ -24,15 +23,7 @@ describe('Variables - Constant', () => { // Navigate back to the homepage and change the selected variable value e2e.pages.Dashboard.Settings.Variables.Edit.General.submitButton().click(); - e2e() - .window() - .then((win: Cypress.AUTWindow & { grafanaBootData: GrafanaBootConfig['bootData'] }) => { - if (win.grafanaBootData.settings.featureToggles.topnav) { - e2e.pages.Dashboard.Settings.Actions.close().click(); - } else { - e2e.components.BackButton.backArrow().click({ force: true }); - } - }); + e2e.pages.Dashboard.Settings.Actions.close().click(); e2e.components.RefreshPicker.runButtonV2().click(); // Assert it was rendered diff --git a/e2e/dashboards-suite/new-custom-variable.spec.ts b/e2e/dashboards-suite/new-custom-variable.spec.ts index 79b8a139a28..91873ce27f8 100644 --- a/e2e/dashboards-suite/new-custom-variable.spec.ts +++ b/e2e/dashboards-suite/new-custom-variable.spec.ts @@ -1,5 +1,4 @@ import { e2e } from '@grafana/e2e'; -import { GrafanaBootConfig } from '@grafana/runtime'; const PAGE_UNDER_TEST = 'kVi2Gex7z/test-variable-output'; const DASHBOARD_NAME = 'Test variable output'; @@ -33,15 +32,7 @@ describe('Variables - Custom', () => { // Navigate back to the homepage and change the selected variable value e2e.pages.Dashboard.Settings.Variables.Edit.General.submitButton().click(); - e2e() - .window() - .then((win: Cypress.AUTWindow & { grafanaBootData: GrafanaBootConfig['bootData'] }) => { - if (win.grafanaBootData.settings.featureToggles.topnav) { - e2e.pages.Dashboard.Settings.Actions.close().click(); - } else { - e2e.components.BackButton.backArrow().click({ force: true }); - } - }); + e2e.pages.Dashboard.Settings.Actions.close().click(); e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownValueLinkTexts('one').click(); e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownOptionTexts('two').click(); @@ -66,15 +57,7 @@ describe('Variables - Custom', () => { // Navigate back to the homepage and change the selected variable value e2e.pages.Dashboard.Settings.Variables.Edit.General.submitButton().click(); - e2e() - .window() - .then((win: Cypress.AUTWindow & { grafanaBootData: GrafanaBootConfig['bootData'] }) => { - if (win.grafanaBootData.settings.featureToggles.topnav) { - e2e.pages.Dashboard.Settings.Actions.close().click(); - } else { - e2e.components.BackButton.backArrow().click({ force: true }); - } - }); + e2e.pages.Dashboard.Settings.Actions.close().click(); e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownValueLinkTexts('One').click(); e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownOptionTexts('Two').click(); diff --git a/e2e/dashboards-suite/new-datasource-variable.spec.ts b/e2e/dashboards-suite/new-datasource-variable.spec.ts index 27aaa3c15fd..1008347464e 100644 --- a/e2e/dashboards-suite/new-datasource-variable.spec.ts +++ b/e2e/dashboards-suite/new-datasource-variable.spec.ts @@ -1,5 +1,4 @@ import { e2e } from '@grafana/e2e'; -import { GrafanaBootConfig } from '@grafana/runtime'; const PAGE_UNDER_TEST = 'kVi2Gex7z/test-variable-output'; const DASHBOARD_NAME = 'Test variable output'; @@ -32,15 +31,7 @@ describe('Variables - Datasource', () => { // Navigate back to the homepage and change the selected variable value e2e.pages.Dashboard.Settings.Variables.Edit.General.submitButton().click(); - e2e() - .window() - .then((win: Cypress.AUTWindow & { grafanaBootData: GrafanaBootConfig['bootData'] }) => { - if (win.grafanaBootData.settings.featureToggles.topnav) { - e2e.pages.Dashboard.Settings.Actions.close().click(); - } else { - e2e.components.BackButton.backArrow().click({ force: true }); - } - }); + e2e.pages.Dashboard.Settings.Actions.close().click(); e2e.components.RefreshPicker.runButtonV2().click(); e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownValueLinkTexts('gdev-prometheus').click(); diff --git a/e2e/dashboards-suite/new-interval-variable.spec.ts b/e2e/dashboards-suite/new-interval-variable.spec.ts index 6e4eeda55af..d091d62a010 100644 --- a/e2e/dashboards-suite/new-interval-variable.spec.ts +++ b/e2e/dashboards-suite/new-interval-variable.spec.ts @@ -1,5 +1,4 @@ import { e2e } from '@grafana/e2e'; -import { GrafanaBootConfig } from '@grafana/runtime'; const PAGE_UNDER_TEST = 'kVi2Gex7z/test-variable-output'; const DASHBOARD_NAME = 'Test variable output'; @@ -34,15 +33,7 @@ describe('Variables - Interval', () => { // Navigate back to the homepage and change the selected variable value e2e.pages.Dashboard.Settings.Variables.Edit.General.submitButton().click(); - e2e() - .window() - .then((win: Cypress.AUTWindow & { grafanaBootData: GrafanaBootConfig['bootData'] }) => { - if (win.grafanaBootData.settings.featureToggles.topnav) { - e2e.pages.Dashboard.Settings.Actions.close().click(); - } else { - e2e.components.BackButton.backArrow().click({ force: true }); - } - }); + e2e.pages.Dashboard.Settings.Actions.close().click(); e2e.components.RefreshPicker.runButtonV2().click(); e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownValueLinkTexts('10s').click(); diff --git a/e2e/dashboards-suite/new-query-variable.spec.ts b/e2e/dashboards-suite/new-query-variable.spec.ts index 307f3da7b6c..2bae25ad442 100644 --- a/e2e/dashboards-suite/new-query-variable.spec.ts +++ b/e2e/dashboards-suite/new-query-variable.spec.ts @@ -1,5 +1,4 @@ import { e2e } from '@grafana/e2e'; -import { GrafanaBootConfig } from '@grafana/runtime'; const PAGE_UNDER_TEST = '-Y-tnEDWk/templating-nested-template-variables'; const DASHBOARD_NAME = 'Templating - Nested Template Variables'; @@ -106,15 +105,7 @@ describe('Variables - Query - Add variable', () => { e2e.pages.Dashboard.Settings.Variables.Edit.General.submitButton().scrollIntoView().should('be.visible').click(); - e2e() - .window() - .then((win: Cypress.AUTWindow & { grafanaBootData: GrafanaBootConfig['bootData'] }) => { - if (win.grafanaBootData.settings.featureToggles.topnav) { - e2e.pages.Dashboard.Settings.Actions.close().click(); - } else { - e2e.components.BackButton.backArrow().click({ force: true }); - } - }); + e2e.pages.Dashboard.Settings.Actions.close().click(); e2e.pages.Dashboard.SubMenu.submenuItemLabels('a label').should('be.visible'); e2e.pages.Dashboard.SubMenu.submenuItem() @@ -179,15 +170,7 @@ describe('Variables - Query - Add variable', () => { e2e.pages.Dashboard.Settings.Variables.Edit.General.submitButton().scrollIntoView().should('be.visible').click(); - e2e() - .window() - .then((win: Cypress.AUTWindow & { grafanaBootData: GrafanaBootConfig['bootData'] }) => { - if (win.grafanaBootData.settings.featureToggles.topnav) { - e2e.pages.Dashboard.Settings.Actions.close().click(); - } else { - e2e.components.BackButton.backArrow().click({ force: true }); - } - }); + e2e.pages.Dashboard.Settings.Actions.close().click(); e2e.pages.Dashboard.SubMenu.submenuItemLabels('a label').should('be.visible'); e2e.pages.Dashboard.SubMenu.submenuItem() diff --git a/e2e/dashboards-suite/new-text-box-variable.spec.ts b/e2e/dashboards-suite/new-text-box-variable.spec.ts index 9bd24f16157..20df50bec16 100644 --- a/e2e/dashboards-suite/new-text-box-variable.spec.ts +++ b/e2e/dashboards-suite/new-text-box-variable.spec.ts @@ -1,5 +1,4 @@ import { e2e } from '@grafana/e2e'; -import { GrafanaBootConfig } from '@grafana/runtime'; const PAGE_UNDER_TEST = 'kVi2Gex7z/test-variable-output'; const DASHBOARD_NAME = 'Test variable output'; @@ -24,15 +23,7 @@ describe('Variables - Text box', () => { // Navigate back to the homepage and change the selected variable value e2e.pages.Dashboard.Settings.Variables.Edit.General.submitButton().click(); - e2e() - .window() - .then((win: Cypress.AUTWindow & { grafanaBootData: GrafanaBootConfig['bootData'] }) => { - if (win.grafanaBootData.settings.featureToggles.topnav) { - e2e.pages.Dashboard.Settings.Actions.close().click(); - } else { - e2e.components.BackButton.backArrow().click({ force: true }); - } - }); + e2e.pages.Dashboard.Settings.Actions.close().click(); e2e().get('#var-VariableUnderTest').clear().type('dog-cat').blur(); // Assert it was rendered diff --git a/e2e/dashboards-suite/set-options-from-ui.spec.ts b/e2e/dashboards-suite/set-options-from-ui.spec.ts index c6d61d78c1d..f9333cd2837 100644 --- a/e2e/dashboards-suite/set-options-from-ui.spec.ts +++ b/e2e/dashboards-suite/set-options-from-ui.spec.ts @@ -1,5 +1,4 @@ import { e2e } from '@grafana/e2e'; -import { GrafanaBootConfig } from '@grafana/runtime'; const PAGE_UNDER_TEST = '-Y-tnEDWk/templating-nested-template-variables'; @@ -12,15 +11,7 @@ describe('Variables - Set options from ui', () => { e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownOptionTexts('A').should('be.visible').click(); e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownOptionTexts('B').should('be.visible').click(); - e2e() - .window() - .then((win: Cypress.AUTWindow & { grafanaBootData: GrafanaBootConfig['bootData'] }) => { - if (win.grafanaBootData.settings.featureToggles.topnav) { - e2e.components.NavToolbar.container().click(); - } else { - e2e.components.PageToolbar.container().click(); - } - }); + e2e.components.NavToolbar.container().click(); e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownValueLinkTexts('B').scrollIntoView().should('be.visible'); @@ -72,15 +63,7 @@ describe('Variables - Set options from ui', () => { e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownValueLinkTexts('A').should('be.visible').click(); e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownOptionTexts('B').should('be.visible').click(); - e2e() - .window() - .then((win: Cypress.AUTWindow & { grafanaBootData: GrafanaBootConfig['bootData'] }) => { - if (win.grafanaBootData.settings.featureToggles.topnav) { - e2e.components.NavToolbar.container().click(); - } else { - e2e.components.PageToolbar.container().click(); - } - }); + e2e.components.NavToolbar.container().click(); e2e().wait('@query'); @@ -130,15 +113,7 @@ describe('Variables - Set options from ui', () => { e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownValueLinkTexts('A + B').should('be.visible').click(); e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownOptionTexts('A').should('be.visible').click(); - e2e() - .window() - .then((win: Cypress.AUTWindow & { grafanaBootData: GrafanaBootConfig['bootData'] }) => { - if (win.grafanaBootData.settings.featureToggles.topnav) { - e2e.components.NavToolbar.container().click(); - } else { - e2e.components.PageToolbar.container().click(); - } - }); + e2e.components.NavToolbar.container().click(); e2e().wait('@query'); diff --git a/e2e/dashboards-suite/templating-dashboard-links-and-variables.spec.ts b/e2e/dashboards-suite/templating-dashboard-links-and-variables.spec.ts index 272da3a5916..19757379ef2 100644 --- a/e2e/dashboards-suite/templating-dashboard-links-and-variables.spec.ts +++ b/e2e/dashboards-suite/templating-dashboard-links-and-variables.spec.ts @@ -1,5 +1,4 @@ import { e2e } from '@grafana/e2e'; -import { GrafanaBootConfig } from '@grafana/runtime'; e2e.scenario({ describeName: 'Templating', @@ -47,15 +46,7 @@ e2e.scenario({ e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownOptionTexts('p2').should('be.visible').click(); - e2e() - .window() - .then((win: Cypress.AUTWindow & { grafanaBootData: GrafanaBootConfig['bootData'] }) => { - if (win.grafanaBootData.settings.featureToggles.topnav) { - e2e.components.NavToolbar.container().click(); - } else { - e2e.components.PageToolbar.container().click(); - } - }); + e2e.components.NavToolbar.container().click(); e2e.components.DashboardLinks.dropDown() .scrollIntoView() .should('be.visible') diff --git a/package.json b/package.json index 6d395b02e71..ac9b69310d3 100644 --- a/package.json +++ b/package.json @@ -384,7 +384,6 @@ "react-popper-tooltip": "4.4.2", "react-redux": "7.2.6", "react-resizable": "3.0.4", - "react-reverse-portal": "2.1.1", "react-router-dom": "5.3.3", "react-select": "5.7.0", "react-split-pane": "0.1.92", diff --git a/packages/grafana-ui/src/components/ToolbarButton/ToolbarButton.tsx b/packages/grafana-ui/src/components/ToolbarButton/ToolbarButton.tsx index 19519613225..4a5f7e6394f 100644 --- a/packages/grafana-ui/src/components/ToolbarButton/ToolbarButton.tsx +++ b/packages/grafana-ui/src/components/ToolbarButton/ToolbarButton.tsx @@ -194,7 +194,7 @@ const getStyles = (theme: GrafanaTheme2) => { } } `, - default: theme.flags.topnav ? defaultTopNav : defaultOld, + default: defaultTopNav, canvas: defaultOld, active: css` color: ${theme.v1.palette.orangeDark}; diff --git a/pkg/api/api.go b/pkg/api/api.go index fa3d844f63d..e93bc997e7f 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -104,12 +104,7 @@ func (hs *HTTPServer) registerRoutes() { r.Get("/configuration", reqGrafanaAdmin, hs.Index) r.Get("/admin", reqOrgAdmin, hs.Index) r.Get("/admin/settings", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionSettingsRead)), hs.Index) - // Show the combined users page for org admins if topnav is enabled - if hs.Features.IsEnabled(featuremgmt.FlagTopnav) { - r.Get("/admin/users", authorize(reqSignedIn, ac.EvalAny(ac.EvalPermission(ac.ActionOrgUsersRead), ac.EvalPermission(ac.ActionUsersRead, ac.ScopeGlobalUsersAll))), hs.Index) - } else { - r.Get("/admin/users", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionUsersRead, ac.ScopeGlobalUsersAll)), hs.Index) - } + r.Get("/admin/users", authorize(reqSignedIn, ac.EvalAny(ac.EvalPermission(ac.ActionOrgUsersRead), ac.EvalPermission(ac.ActionUsersRead, ac.ScopeGlobalUsersAll))), hs.Index) r.Get("/admin/users/create", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionUsersCreate)), hs.Index) r.Get("/admin/users/edit/:id", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionUsersRead)), hs.Index) r.Get("/admin/orgs", authorizeInOrg(reqGrafanaAdmin, ac.UseGlobalOrg, ac.OrgsAccessEvaluator), hs.Index) diff --git a/pkg/api/index.go b/pkg/api/index.go index 469655292f4..16cdd4557fd 100644 --- a/pkg/api/index.go +++ b/pkg/api/index.go @@ -166,8 +166,8 @@ func (hs *HTTPServer) setIndexViewData(c *contextmodel.ReqContext) (*dtos.IndexV hs.HooksService.RunIndexDataHooks(&data, c) - // This will remove empty cfg or admin sections and move sections around if topnav is enabled - data.NavTree.RemoveEmptySectionsAndApplyNewInformationArchitecture(hs.Features.IsEnabled(featuremgmt.FlagTopnav)) + // This will remove empty cfg or admin sections and move sections around + data.NavTree.RemoveEmptySectionsAndApplyNewInformationArchitecture() data.NavTree.Sort() return &data, nil diff --git a/pkg/services/licensing/oss.go b/pkg/services/licensing/oss.go index 8139d63542d..c01caac6bab 100644 --- a/pkg/services/licensing/oss.go +++ b/pkg/services/licensing/oss.go @@ -59,15 +59,7 @@ func ProvideService(cfg *setting.Cfg, hooksService *hooks.HooksService) *OSSLice return } - var adminNodeID string - - if cfg.IsFeatureToggleEnabled("topnav") { - adminNodeID = navtree.NavIDCfg - } else { - adminNodeID = navtree.NavIDAdmin - } - - if adminNode := indexData.NavTree.FindById(adminNodeID); adminNode != nil { + if adminNode := indexData.NavTree.FindById(navtree.NavIDCfg); adminNode != nil { adminNode.Children = append(adminNode.Children, &navtree.NavLink{ Text: "Stats and license", Id: "upgrading", diff --git a/pkg/services/navtree/models.go b/pkg/services/navtree/models.go index 30f796f7919..1d0df6eedaf 100644 --- a/pkg/services/navtree/models.go +++ b/pkg/services/navtree/models.go @@ -100,7 +100,7 @@ func (root *NavTreeRoot) FindById(id string) *NavLink { return FindById(root.Children, id) } -func (root *NavTreeRoot) RemoveEmptySectionsAndApplyNewInformationArchitecture(topNavEnabled bool) { +func (root *NavTreeRoot) RemoveEmptySectionsAndApplyNewInformationArchitecture() { // Remove server admin node if it has no children or set the url to first child if node := root.FindById(NavIDAdmin); node != nil { if len(node.Children) == 0 { @@ -110,31 +110,26 @@ func (root *NavTreeRoot) RemoveEmptySectionsAndApplyNewInformationArchitecture(t } } - if topNavEnabled { - ApplyAdminIA(root) + ApplyAdminIA(root) - // Move reports into dashboards - if reports := root.FindById(NavIDReporting); reports != nil { - if dashboards := root.FindById(NavIDDashboards); dashboards != nil { - reports.SortWeight = 0 - dashboards.Children = append(dashboards.Children, reports) - root.RemoveSection(reports) - } - } - - // Change id of dashboards + // Move reports into dashboards + if reports := root.FindById(NavIDReporting); reports != nil { if dashboards := root.FindById(NavIDDashboards); dashboards != nil { - dashboards.Id = "dashboards/browse" + reports.SortWeight = 0 + dashboards.Children = append(dashboards.Children, reports) + root.RemoveSection(reports) } } - // Remove top level cfg / administration node if it has no children (needs to be after topnav new info archicture logic above that moves server admin into it) - // Remove server admin node if it has no children or set the url to first child + // Change id of dashboards + if dashboards := root.FindById(NavIDDashboards); dashboards != nil { + dashboards.Id = "dashboards/browse" + } + + // Remove top level cfg / administration node if it has no children if node := root.FindById(NavIDCfg); node != nil { if len(node.Children) == 0 { root.RemoveSection(node) - } else if !topNavEnabled { - node.Url = node.Children[0].Url } } diff --git a/pkg/services/navtree/models_test.go b/pkg/services/navtree/models_test.go index c76938469a0..06ded8cd6da 100644 --- a/pkg/services/navtree/models_test.go +++ b/pkg/services/navtree/models_test.go @@ -15,25 +15,12 @@ func TestNavTreeRoot(t *testing.T) { }, } - treeRoot.RemoveEmptySectionsAndApplyNewInformationArchitecture(false) + treeRoot.RemoveEmptySectionsAndApplyNewInformationArchitecture() require.Equal(t, 0, len(treeRoot.Children)) }) - t.Run("Should not remove admin sections when they have children", func(t *testing.T) { - treeRoot := NavTreeRoot{ - Children: []*NavLink{ - {Id: NavIDCfg, Children: []*NavLink{{Id: "child"}}}, - {Id: NavIDAdmin, Children: []*NavLink{{Id: "child"}}}, - }, - } - - treeRoot.RemoveEmptySectionsAndApplyNewInformationArchitecture(false) - - require.Equal(t, 2, len(treeRoot.Children)) - }) - - t.Run("Should create 3 new sections in the Admin node when topnav is enabled", func(t *testing.T) { + t.Run("Should create 3 new sections in the Admin node", func(t *testing.T) { treeRoot := NavTreeRoot{ Children: []*NavLink{ {Id: NavIDCfg}, @@ -41,7 +28,7 @@ func TestNavTreeRoot(t *testing.T) { }, } - treeRoot.RemoveEmptySectionsAndApplyNewInformationArchitecture(true) + treeRoot.RemoveEmptySectionsAndApplyNewInformationArchitecture() require.Equal(t, "Administration", treeRoot.Children[0].Text) }) @@ -54,7 +41,7 @@ func TestNavTreeRoot(t *testing.T) { }, } - treeRoot.RemoveEmptySectionsAndApplyNewInformationArchitecture(true) + treeRoot.RemoveEmptySectionsAndApplyNewInformationArchitecture() require.Equal(t, NavIDReporting, treeRoot.Children[0].Children[0].Id) }) diff --git a/pkg/services/navtree/navtreeimpl/admin.go b/pkg/services/navtree/navtreeimpl/admin.go index 3babb95bc0c..65a156c9cdd 100644 --- a/pkg/services/navtree/navtreeimpl/admin.go +++ b/pkg/services/navtree/navtreeimpl/admin.go @@ -36,18 +36,6 @@ func (s *ServiceImpl) getOrgAdminNode(c *contextmodel.ReqContext) (*navtree.NavL }) } - if !s.features.IsEnabled(featuremgmt.FlagTopnav) { - if hasAccess(ac.ReqOrgAdmin, ac.EvalPermission(ac.ActionOrgUsersRead)) { - configNodes = append(configNodes, &navtree.NavLink{ - 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", @@ -122,18 +110,10 @@ func (s *ServiceImpl) getServerAdminNode(c *contextmodel.ReqContext) *navtree.Na orgsAccessEvaluator := ac.EvalPermission(ac.ActionOrgsRead) adminNavLinks := []*navtree.NavLink{} - if s.features.IsEnabled(featuremgmt.FlagTopnav) { - if hasAccess(ac.ReqSignedIn, ac.EvalAny(ac.EvalPermission(ac.ActionOrgUsersRead), ac.EvalPermission(ac.ActionUsersRead, ac.ScopeGlobalUsersAll))) { - adminNavLinks = append(adminNavLinks, &navtree.NavLink{ - Text: "Users", SubTitle: "Manage users in Grafana", Id: "global-users", Url: s.cfg.AppSubURL + "/admin/users", Icon: "user", - }) - } - } else { - if hasAccess(ac.ReqGrafanaAdmin, ac.EvalPermission(ac.ActionUsersRead, ac.ScopeGlobalUsersAll)) { - adminNavLinks = append(adminNavLinks, &navtree.NavLink{ - Text: "Users", SubTitle: "Manage and create users across the whole Grafana server", Id: "global-users", Url: s.cfg.AppSubURL + "/admin/users", Icon: "user", - }) - } + if hasAccess(ac.ReqSignedIn, ac.EvalAny(ac.EvalPermission(ac.ActionOrgUsersRead), ac.EvalPermission(ac.ActionUsersRead, ac.ScopeGlobalUsersAll))) { + adminNavLinks = append(adminNavLinks, &navtree.NavLink{ + Text: "Users", SubTitle: "Manage users in Grafana", Id: "global-users", Url: s.cfg.AppSubURL + "/admin/users", Icon: "user", + }) } authConfigUIAvailable := s.license.FeatureEnabled("saml") && s.features.IsEnabled(featuremgmt.FlagAuthenticationConfigUI) diff --git a/pkg/services/navtree/navtreeimpl/applinks.go b/pkg/services/navtree/navtreeimpl/applinks.go index d147cf2ace9..362e7c82161 100644 --- a/pkg/services/navtree/navtreeimpl/applinks.go +++ b/pkg/services/navtree/navtreeimpl/applinks.go @@ -16,7 +16,6 @@ import ( ) func (s *ServiceImpl) addAppLinks(treeRoot *navtree.NavTreeRoot, c *contextmodel.ReqContext) error { - topNavEnabled := s.features.IsEnabled(featuremgmt.FlagTopnav) hasAccess := ac.HasAccess(s.accessControl, c) appLinks := []*navtree.NavLink{} @@ -47,7 +46,7 @@ func (s *ServiceImpl) addAppLinks(treeRoot *navtree.NavTreeRoot, c *contextmodel continue } - if appNode := s.processAppPlugin(plugin, c, topNavEnabled, treeRoot); appNode != nil { + if appNode := s.processAppPlugin(plugin, c, treeRoot); appNode != nil { appLinks = append(appLinks, appNode) } } @@ -65,7 +64,7 @@ func (s *ServiceImpl) addAppLinks(treeRoot *navtree.NavTreeRoot, c *contextmodel return nil } -func (s *ServiceImpl) processAppPlugin(plugin plugins.PluginDTO, c *contextmodel.ReqContext, topNavEnabled bool, treeRoot *navtree.NavTreeRoot) *navtree.NavLink { +func (s *ServiceImpl) processAppPlugin(plugin plugins.PluginDTO, c *contextmodel.ReqContext, treeRoot *navtree.NavTreeRoot) *navtree.NavLink { hasAccessToInclude := s.hasAccessToInclude(c, plugin.ID) appLink := &navtree.NavLink{ Text: plugin.Name, @@ -76,12 +75,7 @@ func (s *ServiceImpl) processAppPlugin(plugin plugins.PluginDTO, c *contextmodel SortWeight: navtree.WeightPlugin, IsSection: true, PluginID: plugin.ID, - } - - if topNavEnabled { - appLink.Url = s.cfg.AppSubURL + "/a/" + plugin.ID - } else { - appLink.Url = path.Join(s.cfg.AppSubURL, plugin.DefaultNavURL) + Url: s.cfg.AppSubURL + "/a/" + plugin.ID, } for _, include := range plugin.Includes { @@ -159,10 +153,6 @@ func (s *ServiceImpl) processAppPlugin(plugin plugins.PluginDTO, c *contextmodel appLink.Children = []*navtree.NavLink{} } - if !topNavEnabled { - return appLink - } - // Remove default nav child childrenWithoutDefault := []*navtree.NavLink{} for _, child := range appLink.Children { diff --git a/pkg/services/navtree/navtreeimpl/applinks_test.go b/pkg/services/navtree/navtreeimpl/applinks_test.go index ac937d601e3..4ca7c92eea9 100644 --- a/pkg/services/navtree/navtreeimpl/applinks_test.go +++ b/pkg/services/navtree/navtreeimpl/applinks_test.go @@ -119,17 +119,7 @@ func TestAddAppLinks(t *testing.T) { }, } - t.Run("Should add enabled apps with pages", func(t *testing.T) { - treeRoot := navtree.NavTreeRoot{} - err := service.addAppLinks(&treeRoot, reqCtx) - require.NoError(t, err) - require.Equal(t, "Test app1 name", treeRoot.Children[0].Text) - require.Equal(t, "/a/test-app1/catalog", treeRoot.Children[0].Url) - require.Equal(t, "/a/test-app1/page2", treeRoot.Children[0].Children[1].Url) - }) - - t.Run("Should move apps to Apps category when topnav is enabled", func(t *testing.T) { - service.features = featuremgmt.WithFeatures(featuremgmt.FlagTopnav) + t.Run("Should move apps to Apps category", func(t *testing.T) { treeRoot := navtree.NavTreeRoot{} err := service.addAppLinks(&treeRoot, reqCtx) require.NoError(t, err) @@ -141,8 +131,17 @@ func TestAddAppLinks(t *testing.T) { require.Equal(t, testApp1.Name, appsNode.Children[0].Text) }) - t.Run("Should remove the default nav child (DefaultNav=true) when topnav is enabled and should set its URL to the plugin nav root", func(t *testing.T) { - service.features = featuremgmt.WithFeatures(featuremgmt.FlagTopnav) + t.Run("Should add enabled apps with pages", func(t *testing.T) { + treeRoot := navtree.NavTreeRoot{} + err := service.addAppLinks(&treeRoot, reqCtx) + require.NoError(t, err) + appsNode := treeRoot.FindById(navtree.NavIDApps) + require.Equal(t, "Test app1 name", appsNode.Children[0].Text) + require.Equal(t, "/a/test-app1/catalog", appsNode.Children[0].Url) + require.Equal(t, "/a/test-app1/page2", appsNode.Children[0].Children[0].Url) + }) + + t.Run("Should remove the default nav child (DefaultNav=true) and should set its URL to the plugin nav root", func(t *testing.T) { treeRoot := navtree.NavTreeRoot{} err := service.addAppLinks(&treeRoot, reqCtx) require.NoError(t, err) @@ -155,7 +154,6 @@ func TestAddAppLinks(t *testing.T) { // This can be done by using `[navigation.app_sections]` in the INI config t.Run("Should move apps that have root nav id configured to the root", func(t *testing.T) { - service.features = featuremgmt.WithFeatures(featuremgmt.FlagTopnav) service.navigationAppConfig = map[string]NavigationAppConfig{ "test-app1": {SectionID: navtree.NavIDRoot}, } @@ -179,7 +177,6 @@ func TestAddAppLinks(t *testing.T) { // This can be done by using `[navigation.app_sections]` in the INI config t.Run("Should move apps that have specific nav id configured to correct section", func(t *testing.T) { - service.features = featuremgmt.WithFeatures(featuremgmt.FlagTopnav) service.navigationAppConfig = map[string]NavigationAppConfig{ "test-app1": {SectionID: navtree.NavIDAdmin}, } @@ -207,7 +204,6 @@ func TestAddAppLinks(t *testing.T) { }) t.Run("Should only add a 'Monitoring' section if a plugin exists that wants to live there", func(t *testing.T) { - service.features = featuremgmt.WithFeatures(featuremgmt.FlagTopnav) service.navigationAppConfig = map[string]NavigationAppConfig{} // Check if the Monitoring section is not there if no apps try to register to it @@ -231,7 +227,6 @@ func TestAddAppLinks(t *testing.T) { }) t.Run("Should add a 'Alerts and Incidents' section if a plugin exists that wants to live there", func(t *testing.T) { - service.features = featuremgmt.WithFeatures(featuremgmt.FlagTopnav) service.navigationAppConfig = map[string]NavigationAppConfig{} // Check if the 'Alerts and Incidents' section is not there if no apps try to register to it @@ -257,7 +252,6 @@ func TestAddAppLinks(t *testing.T) { }) t.Run("Should add a 'Alerts and Incidents' section if a plugin exists that wants to live there even without an alerting node", func(t *testing.T) { - service.features = featuremgmt.WithFeatures(featuremgmt.FlagTopnav) service.navigationAppConfig = map[string]NavigationAppConfig{} // Check if the 'Alerts and Incidents' section is not there if no apps try to register to it @@ -281,7 +275,6 @@ func TestAddAppLinks(t *testing.T) { }) t.Run("Should be able to control app sort order with SortWeight (smaller SortWeight displayed first)", func(t *testing.T) { - service.features = featuremgmt.WithFeatures(featuremgmt.FlagTopnav) service.navigationAppConfig = map[string]NavigationAppConfig{ "test-app2": {SectionID: navtree.NavIDMonitoring, SortWeight: 2}, "test-app1": {SectionID: navtree.NavIDMonitoring, SortWeight: 3}, @@ -300,7 +293,7 @@ func TestAddAppLinks(t *testing.T) { }) t.Run("Should replace page from plugin", func(t *testing.T) { - service.features = featuremgmt.WithFeatures(featuremgmt.FlagTopnav, featuremgmt.FlagDataConnectionsConsole) + service.features = featuremgmt.WithFeatures(featuremgmt.FlagDataConnectionsConsole) service.navigationAppConfig = map[string]NavigationAppConfig{} service.navigationAppPathConfig = map[string]NavigationAppConfig{ "/connections/connect-data": {SectionID: "connections"}, @@ -340,7 +333,7 @@ func TestAddAppLinks(t *testing.T) { }) t.Run("Should not register pages under the app plugin section unless AddToNav=true", func(t *testing.T) { - service.features = featuremgmt.WithFeatures(featuremgmt.FlagTopnav, featuremgmt.FlagDataConnectionsConsole) + service.features = featuremgmt.WithFeatures(featuremgmt.FlagDataConnectionsConsole) service.navigationAppPathConfig = map[string]NavigationAppConfig{} // We don't configure it as a standalone plugin page treeRoot := navtree.NavTreeRoot{} @@ -466,11 +459,12 @@ func TestAddAppLinksAccessControl(t *testing.T) { err := service.addAppLinks(&treeRoot, reqCtx) require.NoError(t, err) - require.Len(t, treeRoot.Children, 1) - require.Equal(t, "Test app1 name", treeRoot.Children[0].Text) - require.Len(t, treeRoot.Children[0].Children, 2) - require.Equal(t, "/a/test-app1/catalog", treeRoot.Children[0].Children[0].Url) - require.Equal(t, "/a/test-app1/page2", treeRoot.Children[0].Children[1].Url) + appsNode := treeRoot.FindById(navtree.NavIDApps) + require.Len(t, appsNode.Children, 1) + require.Equal(t, "Test app1 name", appsNode.Children[0].Text) + require.Equal(t, "/a/test-app1/catalog", appsNode.Children[0].Url) + require.Len(t, appsNode.Children[0].Children, 1) + require.Equal(t, "/a/test-app1/page2", appsNode.Children[0].Children[0].Url) }) t.Run("Should add one include when the user is a viewer", func(t *testing.T) { treeRoot := navtree.NavTreeRoot{} @@ -481,10 +475,11 @@ func TestAddAppLinksAccessControl(t *testing.T) { err := service.addAppLinks(&treeRoot, reqCtx) require.NoError(t, err) - require.Len(t, treeRoot.Children, 1) - require.Equal(t, "Test app1 name", treeRoot.Children[0].Text) - require.Len(t, treeRoot.Children[0].Children, 1) - require.Equal(t, "/a/test-app1/page2", treeRoot.Children[0].Children[0].Url) + appsNode := treeRoot.FindById(navtree.NavIDApps) + require.Len(t, appsNode.Children, 1) + require.Equal(t, "Test app1 name", appsNode.Children[0].Text) + require.Len(t, appsNode.Children[0].Children, 1) + require.Equal(t, "/a/test-app1/page2", appsNode.Children[0].Children[0].Url) }) t.Run("Should add both includes when the user is a viewer with catalog read", func(t *testing.T) { treeRoot := navtree.NavTreeRoot{} @@ -496,11 +491,12 @@ func TestAddAppLinksAccessControl(t *testing.T) { err := service.addAppLinks(&treeRoot, reqCtx) require.NoError(t, err) - require.Len(t, treeRoot.Children, 1) - require.Equal(t, "Test app1 name", treeRoot.Children[0].Text) - require.Len(t, treeRoot.Children[0].Children, 2) - require.Equal(t, "/a/test-app1/catalog", treeRoot.Children[0].Children[0].Url) - require.Equal(t, "/a/test-app1/page2", treeRoot.Children[0].Children[1].Url) + appsNode := treeRoot.FindById(navtree.NavIDApps) + require.Len(t, appsNode.Children, 1) + require.Equal(t, "Test app1 name", appsNode.Children[0].Text) + require.Equal(t, "/a/test-app1/catalog", appsNode.Children[0].Url) + require.Len(t, appsNode.Children[0].Children, 1) + require.Equal(t, "/a/test-app1/page2", appsNode.Children[0].Children[0].Url) }) t.Run("Should add one include when the user is an editor without catalog read", func(t *testing.T) { treeRoot := navtree.NavTreeRoot{} @@ -512,9 +508,10 @@ func TestAddAppLinksAccessControl(t *testing.T) { err := service.addAppLinks(&treeRoot, reqCtx) require.NoError(t, err) - require.Len(t, treeRoot.Children, 1) - require.Equal(t, "Test app1 name", treeRoot.Children[0].Text) - require.Len(t, treeRoot.Children[0].Children, 1) - require.Equal(t, "/a/test-app1/page2", treeRoot.Children[0].Children[0].Url) + appsNode := treeRoot.FindById(navtree.NavIDApps) + require.Len(t, appsNode.Children, 1) + require.Equal(t, "Test app1 name", appsNode.Children[0].Text) + require.Len(t, appsNode.Children[0].Children, 1) + require.Equal(t, "/a/test-app1/page2", appsNode.Children[0].Children[0].Url) }) } diff --git a/pkg/services/navtree/navtreeimpl/navtree.go b/pkg/services/navtree/navtreeimpl/navtree.go index 9d1de401bd2..881378a54c7 100644 --- a/pkg/services/navtree/navtreeimpl/navtree.go +++ b/pkg/services/navtree/navtreeimpl/navtree.go @@ -195,9 +195,6 @@ func (s *ServiceImpl) getHomeNode(c *contextmodel.ReqContext, prefs *pref.Prefer Section: navtree.NavSectionCore, SortWeight: navtree.WeightHome, } - if !s.features.IsEnabled(featuremgmt.FlagTopnav) { - homeNode.HideFromMenu = true - } return homeNode } @@ -345,12 +342,6 @@ func (s *ServiceImpl) buildDashboardNavLinks(c *contextmodel.ReqContext, hasEdit dashboardChildNavs := []*navtree.NavLink{} - if !s.features.IsEnabled(featuremgmt.FlagTopnav) { - dashboardChildNavs = append(dashboardChildNavs, &navtree.NavLink{ - Text: "Browse", Id: navtree.NavIDDashboardsBrowse, Url: s.cfg.AppSubURL + "/dashboards", Icon: "sitemap", - }) - } - dashboardChildNavs = append(dashboardChildNavs, &navtree.NavLink{ Text: "Playlists", SubTitle: "Groups of dashboards that are displayed in a sequence", Id: "dashboards/playlists", Url: s.cfg.AppSubURL + "/playlists", Icon: "presentation-play", }) @@ -393,12 +384,6 @@ func (s *ServiceImpl) buildDashboardNavLinks(c *contextmodel.ReqContext, hasEdit }) } - if hasEditPerm && !s.features.IsEnabled(featuremgmt.FlagTopnav) { - 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{ @@ -412,15 +397,6 @@ func (s *ServiceImpl) buildDashboardNavLinks(c *contextmodel.ReqContext, hasEdit } } - 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", - Icon: "plus", Url: s.cfg.AppSubURL + "/dashboards/folder/new", HideFromTabs: true, ShowIconInNavbar: true, - }) - } - } - return dashboardChildNavs } @@ -445,12 +421,7 @@ func (s *ServiceImpl) buildLegacyAlertNavLinks(c *contextmodel.ReqContext) *navt Children: alertChildNavs, Section: navtree.NavSectionCore, SortWeight: navtree.WeightAlerting, - } - - if s.features.IsEnabled(featuremgmt.FlagTopnav) { - alertNav.Url = s.cfg.AppSubURL + "/alerting" - } else { - alertNav.Url = s.cfg.AppSubURL + "/alerting/list" + Url: s.cfg.AppSubURL + "/alerting", } return &alertNav @@ -460,15 +431,6 @@ func (s *ServiceImpl) buildAlertNavLinks(c *contextmodel.ReqContext, hasEditPerm hasAccess := ac.HasAccess(s.accessControl, c) var alertChildNavs []*navtree.NavLink - if !s.features.IsEnabled(featuremgmt.FlagTopnav) { - alertChildNavs = append(alertChildNavs, &navtree.NavLink{ - Text: "Home", - Id: "alert-home", - Url: s.cfg.AppSubURL + "/alerting/home", - Icon: "home", - }) - } - if hasAccess(ac.ReqViewer, ac.EvalAny(ac.EvalPermission(ac.ActionAlertingRuleRead), ac.EvalPermission(ac.ActionAlertingRuleExternalRead))) { alertChildNavs = append(alertChildNavs, &navtree.NavLink{ Text: "Alert rules", SubTitle: "Rules that determine whether an alert will fire", Id: "alert-list", Url: s.cfg.AppSubURL + "/alerting/list", Icon: "list-ul", @@ -498,12 +460,6 @@ func (s *ServiceImpl) buildAlertNavLinks(c *contextmodel.ReqContext, hasEditPerm fallbackHasEditPerm := func(*contextmodel.ReqContext) bool { return hasEditPerm } if hasAccess(fallbackHasEditPerm, ac.EvalAny(ac.EvalPermission(ac.ActionAlertingRuleCreate), ac.EvalPermission(ac.ActionAlertingRuleExternalWrite))) { - 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: "Create alert rule", SubTitle: "Create an alert rule", Id: "alert", Icon: "plus", Url: s.cfg.AppSubURL + "/alerting/new", HideFromTabs: true, ShowIconInNavbar: true, IsCreateAction: true, @@ -519,12 +475,7 @@ func (s *ServiceImpl) buildAlertNavLinks(c *contextmodel.ReqContext, hasEditPerm Children: alertChildNavs, Section: navtree.NavSectionCore, SortWeight: navtree.WeightAlerting, - } - - if s.features.IsEnabled(featuremgmt.FlagTopnav) { - alertNav.Url = s.cfg.AppSubURL + "/alerting" - } else { - alertNav.Url = s.cfg.AppSubURL + "/alerting/home" + Url: s.cfg.AppSubURL + "/alerting", } return &alertNav diff --git a/public/app/core/components/AppChrome/AppChrome.tsx b/public/app/core/components/AppChrome/AppChrome.tsx index 6e2f3f9ad06..829ca792805 100644 --- a/public/app/core/components/AppChrome/AppChrome.tsx +++ b/public/app/core/components/AppChrome/AppChrome.tsx @@ -2,18 +2,14 @@ import { css, cx } from '@emotion/css'; import React, { PropsWithChildren } from 'react'; import { GrafanaTheme2 } from '@grafana/data'; -import { config } from '@grafana/runtime'; import { useStyles2 } from '@grafana/ui'; import { useGrafana } from 'app/core/context/GrafanaContext'; import { CommandPalette } from 'app/features/commandPalette/CommandPalette'; -import { SearchWrapper } from 'app/features/search'; import { KioskMode } from 'app/types'; -import { MegaMenu } from '../MegaMenu/MegaMenu'; -import { NavBar } from '../NavBar/NavBar'; - -import { NavToolbar } from './NavToolbar'; -import { TopSearchBar } from './TopSearchBar'; +import { MegaMenu } from './MegaMenu/MegaMenu'; +import { NavToolbar } from './NavToolbar/NavToolbar'; +import { TopSearchBar } from './TopBar/TopSearchBar'; import { TOP_BAR_LEVEL_HEIGHT } from './types'; export interface Props extends PropsWithChildren<{}> {} @@ -23,21 +19,6 @@ export function AppChrome({ children }: Props) { const { chrome } = useGrafana(); const state = chrome.useState(); - if (!config.featureToggles.topnav) { - return ( - <> - {!state.chromeless && ( - <> - - - - - )} -
{children}
- - ); - } - const searchBarHidden = state.searchBarHidden || state.kioskMode === KioskMode.TV; const contentClass = cx({ diff --git a/public/app/core/components/MegaMenu/MegaMenu.test.tsx b/public/app/core/components/AppChrome/MegaMenu/MegaMenu.test.tsx similarity index 95% rename from public/app/core/components/MegaMenu/MegaMenu.test.tsx rename to public/app/core/components/AppChrome/MegaMenu/MegaMenu.test.tsx index b88f7f087c9..49f7d60176a 100644 --- a/public/app/core/components/MegaMenu/MegaMenu.test.tsx +++ b/public/app/core/components/AppChrome/MegaMenu/MegaMenu.test.tsx @@ -6,7 +6,7 @@ import { getGrafanaContextMock } from 'test/mocks/getGrafanaContextMock'; import { NavModelItem, NavSection } from '@grafana/data'; import { locationService } from '@grafana/runtime'; -import { TestProvider } from '../../../../test/helpers/TestProvider'; +import { TestProvider } from '../../../../../test/helpers/TestProvider'; import { MegaMenu } from './MegaMenu'; diff --git a/public/app/core/components/MegaMenu/MegaMenu.tsx b/public/app/core/components/AppChrome/MegaMenu/MegaMenu.tsx similarity index 97% rename from public/app/core/components/MegaMenu/MegaMenu.tsx rename to public/app/core/components/AppChrome/MegaMenu/MegaMenu.tsx index b9a4cef37c7..607a5beabd3 100644 --- a/public/app/core/components/MegaMenu/MegaMenu.tsx +++ b/public/app/core/components/AppChrome/MegaMenu/MegaMenu.tsx @@ -7,9 +7,8 @@ import { GrafanaTheme2, NavSection } from '@grafana/data'; import { useTheme2 } from '@grafana/ui'; import { useSelector } from 'app/types'; -import { enrichConfigItems, enrichWithInteractionTracking, getActiveItem } from '../NavBar/utils'; - import { NavBarMenu } from './NavBarMenu'; +import { enrichConfigItems, enrichWithInteractionTracking, getActiveItem } from './utils'; export interface Props { onClose: () => void; diff --git a/public/app/core/components/NavBar/NavBarItemIcon.tsx b/public/app/core/components/AppChrome/MegaMenu/NavBarItemIcon.tsx similarity index 95% rename from public/app/core/components/NavBar/NavBarItemIcon.tsx rename to public/app/core/components/AppChrome/MegaMenu/NavBarItemIcon.tsx index f3b8d3c2707..e180e74d3aa 100644 --- a/public/app/core/components/NavBar/NavBarItemIcon.tsx +++ b/public/app/core/components/AppChrome/MegaMenu/NavBarItemIcon.tsx @@ -4,7 +4,7 @@ import React from 'react'; import { GrafanaTheme2, NavModelItem } from '@grafana/data'; import { Icon, toIconName, useTheme2 } from '@grafana/ui'; -import { Branding } from '../Branding/Branding'; +import { Branding } from '../../Branding/Branding'; interface NavBarItemIconProps { link: NavModelItem; diff --git a/public/app/core/components/MegaMenu/NavBarMenu.tsx b/public/app/core/components/AppChrome/MegaMenu/NavBarMenu.tsx similarity index 99% rename from public/app/core/components/MegaMenu/NavBarMenu.tsx rename to public/app/core/components/AppChrome/MegaMenu/NavBarMenu.tsx index 0522c1eefa3..e09a76e0302 100644 --- a/public/app/core/components/MegaMenu/NavBarMenu.tsx +++ b/public/app/core/components/AppChrome/MegaMenu/NavBarMenu.tsx @@ -9,7 +9,7 @@ import { GrafanaTheme2, NavModelItem } from '@grafana/data'; import { CustomScrollbar, Icon, IconButton, useTheme2 } from '@grafana/ui'; import { useGrafana } from 'app/core/context/GrafanaContext'; -import { TOP_BAR_LEVEL_HEIGHT } from '../AppChrome/types'; +import { TOP_BAR_LEVEL_HEIGHT } from '../types'; import { NavBarMenuItemWrapper } from './NavBarMenuItemWrapper'; diff --git a/public/app/core/components/MegaMenu/NavBarMenuItem.tsx b/public/app/core/components/AppChrome/MegaMenu/NavBarMenuItem.tsx similarity index 100% rename from public/app/core/components/MegaMenu/NavBarMenuItem.tsx rename to public/app/core/components/AppChrome/MegaMenu/NavBarMenuItem.tsx diff --git a/public/app/core/components/MegaMenu/NavBarMenuItemWrapper.tsx b/public/app/core/components/AppChrome/MegaMenu/NavBarMenuItemWrapper.tsx similarity index 98% rename from public/app/core/components/MegaMenu/NavBarMenuItemWrapper.tsx rename to public/app/core/components/AppChrome/MegaMenu/NavBarMenuItemWrapper.tsx index 2501c0341d7..ac5deb8cd9c 100644 --- a/public/app/core/components/MegaMenu/NavBarMenuItemWrapper.tsx +++ b/public/app/core/components/AppChrome/MegaMenu/NavBarMenuItemWrapper.tsx @@ -4,10 +4,9 @@ import React from 'react'; import { GrafanaTheme2, NavModelItem } from '@grafana/data'; import { toIconName, useStyles2 } from '@grafana/ui'; -import { isMatchOrChildMatch } from '../NavBar/utils'; - import { NavBarMenuItem } from './NavBarMenuItem'; import { NavBarMenuSection } from './NavBarMenuSection'; +import { isMatchOrChildMatch } from './utils'; export function NavBarMenuItemWrapper({ link, diff --git a/public/app/core/components/MegaMenu/NavBarMenuSection.tsx b/public/app/core/components/AppChrome/MegaMenu/NavBarMenuSection.tsx similarity index 94% rename from public/app/core/components/MegaMenu/NavBarMenuSection.tsx rename to public/app/core/components/AppChrome/MegaMenu/NavBarMenuSection.tsx index a5569873fc1..433ee3c886e 100644 --- a/public/app/core/components/MegaMenu/NavBarMenuSection.tsx +++ b/public/app/core/components/AppChrome/MegaMenu/NavBarMenuSection.tsx @@ -5,11 +5,10 @@ import { useLocalStorage } from 'react-use'; import { GrafanaTheme2, NavModelItem } from '@grafana/data'; import { Button, Icon, useStyles2 } from '@grafana/ui'; -import { NavBarItemIcon } from '../NavBar/NavBarItemIcon'; -import { NavFeatureHighlight } from '../NavBar/NavFeatureHighlight'; -import { hasChildMatch } from '../NavBar/utils'; - +import { NavBarItemIcon } from './NavBarItemIcon'; import { NavBarMenuItem } from './NavBarMenuItem'; +import { NavFeatureHighlight } from './NavFeatureHighlight'; +import { hasChildMatch } from './utils'; export function NavBarMenuSection({ link, diff --git a/public/app/core/components/NavBar/NavFeatureHighlight.tsx b/public/app/core/components/AppChrome/MegaMenu/NavFeatureHighlight.tsx similarity index 100% rename from public/app/core/components/NavBar/NavFeatureHighlight.tsx rename to public/app/core/components/AppChrome/MegaMenu/NavFeatureHighlight.tsx diff --git a/public/app/core/components/NavBar/navBarItem-translations.ts b/public/app/core/components/AppChrome/MegaMenu/navBarItem-translations.ts similarity index 88% rename from public/app/core/components/NavBar/navBarItem-translations.ts rename to public/app/core/components/AppChrome/MegaMenu/navBarItem-translations.ts index fa61f68e466..a7e3602411a 100644 --- a/public/app/core/components/NavBar/navBarItem-translations.ts +++ b/public/app/core/components/AppChrome/MegaMenu/navBarItem-translations.ts @@ -1,4 +1,3 @@ -import { config } from '@grafana/runtime'; import { t } from 'app/core/internationalization'; // Maps the ID of the nav item to a translated phrase to later pass to @@ -30,9 +29,7 @@ export function getNavTitle(navId: string | undefined) { case 'dashboards': return t('nav.dashboards.title', 'Dashboards'); case 'dashboards/browse': - return config.featureToggles.topnav - ? t('nav.dashboards.title', 'Dashboards') - : t('nav.manage-dashboards.title', 'Browse'); + return t('nav.dashboards.title', 'Dashboards'); case 'dashboards/playlists': return t('nav.playlists.title', 'Playlists'); case 'dashboards/snapshots': @@ -72,9 +69,7 @@ export function getNavTitle(navId: string | undefined) { case 'alerting-admin': return t('nav.alerting-admin.title', 'Admin'); case 'cfg': - return config.featureToggles.topnav - ? t('nav.config.title', 'Administration') - : t('nav.config.titleBeforeTopnav', 'Configuration'); + return t('nav.config.title', 'Administration'); case 'datasources': return t('nav.datasources.title', 'Data sources'); case 'correlations': @@ -86,9 +81,7 @@ export function getNavTitle(navId: string | undefined) { case 'plugins': return t('nav.plugins.title', 'Plugins'); case 'org-settings': - return config.featureToggles.topnav - ? t('nav.org-settings.title', 'Default preferences') - : t('nav.org-settings.titleBeforeTopnav', 'Preferences'); + return t('nav.org-settings.title', 'Default preferences'); case 'apikeys': return t('nav.api-keys.title', 'API keys'); case 'serviceaccounts': @@ -98,9 +91,7 @@ export function getNavTitle(navId: string | undefined) { case 'support-bundles': return t('nav.support-bundles.title', 'Support bundles'); case 'global-users': - return config.featureToggles.topnav - ? t('nav.global-users.title', 'Users') - : t('nav.global-users.titleBeforeTopnav', 'Users'); + return t('nav.global-users.title', 'Users'); case 'global-orgs': return t('nav.global-orgs.title', 'Organizations'); case 'server-settings': @@ -137,9 +128,7 @@ export function getNavSubTitle(navId: string | undefined) { case 'dashboards': return t('nav.dashboards.subtitle', 'Create and manage dashboards to visualize your data'); case 'dashboards/browse': - return config.featureToggles.topnav - ? t('nav.dashboards.subtitle', 'Create and manage dashboards to visualize your data') - : undefined; + return t('nav.dashboards.subtitle', 'Create and manage dashboards to visualize your data'); case 'manage-folder': return t('nav.manage-folder.subtitle', 'Manage folder dashboards and permissions'); case 'dashboards/playlists': @@ -193,12 +182,10 @@ export function getNavSubTitle(navId: string | undefined) { case 'support-bundles': return t('nav.support-bundles.subtitle', 'Download support bundles'); case 'admin': - return config.featureToggles.topnav - ? t( - 'nav.admin.subtitle', - 'Manage server-wide settings and access to resources such as organizations, users, and licenses' - ) - : undefined; + return t( + 'nav.admin.subtitle', + 'Manage server-wide settings and access to resources such as organizations, users, and licenses' + ); case 'apps': return t('nav.apps.subtitle', 'App plugins that extend the Grafana experience'); case 'monitoring': diff --git a/public/app/core/components/NavBar/utils.test.ts b/public/app/core/components/AppChrome/MegaMenu/utils.test.ts similarity index 68% rename from public/app/core/components/NavBar/utils.test.ts rename to public/app/core/components/AppChrome/MegaMenu/utils.test.ts index 3bce87818c9..e9ea4590c67 100644 --- a/public/app/core/components/NavBar/utils.test.ts +++ b/public/app/core/components/AppChrome/MegaMenu/utils.test.ts @@ -3,9 +3,9 @@ import { Location } from 'history'; import { GrafanaConfig, locationUtil, NavModelItem } from '@grafana/data'; import { ContextSrv, setContextSrv } from 'app/core/services/context_srv'; -import { enrichConfigItems, getActiveItem, isMatchOrChildMatch, isSearchActive } from './utils'; +import { enrichConfigItems, getActiveItem, isMatchOrChildMatch } from './utils'; -jest.mock('../../app_events', () => ({ +jest.mock('../../../app_events', () => ({ publish: jest.fn(), })); @@ -33,46 +33,6 @@ describe('enrichConfigItems', () => { ]; }); - it('does not add a sign in item if a user signed in', () => { - const contextSrv = new ContextSrv(); - contextSrv.user.isSignedIn = false; - setContextSrv(contextSrv); - const enrichedConfigItems = enrichConfigItems(mockItems, mockLocation); - const signInNode = enrichedConfigItems.find((item) => item.id === 'sign-in'); - expect(signInNode).toBeDefined(); - }); - - it('adds a sign in item if a user is not signed in', () => { - const contextSrv = new ContextSrv(); - contextSrv.user.isSignedIn = true; - setContextSrv(contextSrv); - const enrichedConfigItems = enrichConfigItems(mockItems, mockLocation); - const signInNode = enrichedConfigItems.find((item) => item.id === 'sign-in'); - expect(signInNode).toBeDefined(); - }); - - it('does not add an org switcher to the profile node if there is 1 org', () => { - const contextSrv = new ContextSrv(); - contextSrv.user.orgCount = 1; - setContextSrv(contextSrv); - const enrichedConfigItems = enrichConfigItems(mockItems, mockLocation); - const profileNode = enrichedConfigItems.find((item) => item.id === 'profile'); - expect(profileNode!.children).toBeUndefined(); - }); - - it('adds an org switcher to the profile node if there is more than 1 org', () => { - const contextSrv = new ContextSrv(); - contextSrv.user.orgCount = 2; - setContextSrv(contextSrv); - const enrichedConfigItems = enrichConfigItems(mockItems, mockLocation); - const profileNode = enrichedConfigItems.find((item) => item.id === 'profile'); - expect(profileNode!.children).toContainEqual( - expect.objectContaining({ - text: 'Switch organization', - }) - ); - }); - it('enhances the help node with extra child links', () => { const contextSrv = new ContextSrv(); setContextSrv(contextSrv); @@ -241,25 +201,3 @@ describe('getActiveItem', () => { }); }); }); - -describe('isSearchActive', () => { - it('returns true if the search query parameter is "open"', () => { - const mockLocation = { - hash: '', - pathname: '/', - search: '?search=open', - state: '', - }; - expect(isSearchActive(mockLocation)).toBe(true); - }); - - it('returns false if the search query parameter is missing', () => { - const mockLocation = { - hash: '', - pathname: '/', - search: '', - state: '', - }; - expect(isSearchActive(mockLocation)).toBe(false); - }); -}); diff --git a/public/app/core/components/NavBar/utils.ts b/public/app/core/components/AppChrome/MegaMenu/utils.ts similarity index 68% rename from public/app/core/components/NavBar/utils.ts rename to public/app/core/components/AppChrome/MegaMenu/utils.ts index 92cc50e577e..2127d76eba6 100644 --- a/public/app/core/components/NavBar/utils.ts +++ b/public/app/core/components/AppChrome/MegaMenu/utils.ts @@ -1,52 +1,19 @@ import { Location } from 'history'; -import { locationUtil, NavModelItem, NavSection } from '@grafana/data'; +import { locationUtil, NavModelItem } from '@grafana/data'; import { config, reportInteraction } from '@grafana/runtime'; import { t } from 'app/core/internationalization'; -import { contextSrv } from 'app/core/services/context_srv'; -import { ShowModalReactEvent } from '../../../types/events'; -import appEvents from '../../app_events'; -import { getFooterLinks } from '../Footer/Footer'; -import { OrgSwitcher } from '../OrgSwitcher'; -import { HelpModal } from '../help/HelpModal'; - -export const SEARCH_ITEM_ID = 'search'; -export const NAV_MENU_PORTAL_CONTAINER_ID = 'navbar-menu-portal-container'; - -export const getNavMenuPortalContainer = () => document.getElementById(NAV_MENU_PORTAL_CONTAINER_ID) ?? document.body; +import { ShowModalReactEvent } from '../../../../types/events'; +import appEvents from '../../../app_events'; +import { getFooterLinks } from '../../Footer/Footer'; +import { HelpModal } from '../../help/HelpModal'; export const enrichConfigItems = (items: NavModelItem[], location: Location) => { - const { isSignedIn, user } = contextSrv; const onOpenShortcuts = () => { appEvents.publish(new ShowModalReactEvent({ component: HelpModal })); }; - const onOpenOrgSwitcher = () => { - appEvents.publish(new ShowModalReactEvent({ component: OrgSwitcher })); - }; - - if (!config.featureToggles.topnav && user && user.orgCount > 1) { - const profileNode = items.find((bottomNavItem) => bottomNavItem.id === 'profile'); - if (profileNode) { - profileNode.showOrgSwitcher = true; - profileNode.subTitle = `Organization: ${user?.orgName}`; - } - } - - if (!isSignedIn && !config.featureToggles.topnav) { - const loginUrl = locationUtil.getUrlForPartial(location, { forceLogin: 'true' }); - - items.unshift({ - icon: 'signout', - id: 'sign-in', - section: NavSection.Config, - target: '_self', - text: t('nav.sign-in', 'Sign in'), - url: loginUrl, - }); - } - items.forEach((link) => { let menuItems = link.children || []; @@ -63,18 +30,6 @@ export const enrichConfigItems = (items: NavModelItem[], location: Location) => { - const query = new URLSearchParams(location.search); - return query.get('search') === 'open'; -}; - -export function getNavModelItemKey(item: NavModelItem) { - return item.id ?? item.text; -} - export function getEditionAndUpdateLinks(): NavModelItem[] { const { buildInfo, licenseInfo } = config; const stateInfo = licenseInfo.stateInfo ? ` (${licenseInfo.stateInfo})` : ''; diff --git a/public/app/core/components/AppChrome/NavToolbar.tsx b/public/app/core/components/AppChrome/NavToolbar/NavToolbar.tsx similarity index 94% rename from public/app/core/components/AppChrome/NavToolbar.tsx rename to public/app/core/components/AppChrome/NavToolbar/NavToolbar.tsx index 9a63160c6a0..fc7f789ca75 100644 --- a/public/app/core/components/AppChrome/NavToolbar.tsx +++ b/public/app/core/components/AppChrome/NavToolbar/NavToolbar.tsx @@ -8,11 +8,11 @@ import { t } from 'app/core/internationalization'; import { HOME_NAV_ID } from 'app/core/reducers/navModel'; import { useSelector } from 'app/types'; -import { Breadcrumbs } from '../Breadcrumbs/Breadcrumbs'; -import { buildBreadcrumbs } from '../Breadcrumbs/utils'; +import { Breadcrumbs } from '../../Breadcrumbs/Breadcrumbs'; +import { buildBreadcrumbs } from '../../Breadcrumbs/utils'; +import { TOP_BAR_LEVEL_HEIGHT } from '../types'; import { NavToolbarSeparator } from './NavToolbarSeparator'; -import { TOP_BAR_LEVEL_HEIGHT } from './types'; export interface Props { onToggleSearchBar(): void; diff --git a/public/app/core/components/AppChrome/NavToolbarSeparator.tsx b/public/app/core/components/AppChrome/NavToolbar/NavToolbarSeparator.tsx similarity index 82% rename from public/app/core/components/AppChrome/NavToolbarSeparator.tsx rename to public/app/core/components/AppChrome/NavToolbar/NavToolbarSeparator.tsx index 36fe42e58b8..e801acd5d6c 100644 --- a/public/app/core/components/AppChrome/NavToolbarSeparator.tsx +++ b/public/app/core/components/AppChrome/NavToolbar/NavToolbarSeparator.tsx @@ -2,7 +2,6 @@ import { css, cx } from '@emotion/css'; import React from 'react'; import { GrafanaTheme2 } from '@grafana/data'; -import { config } from '@grafana/runtime'; import { useStyles2 } from '@grafana/ui'; export interface Props { @@ -17,11 +16,7 @@ export function NavToolbarSeparator({ className, leftActionsSeparator }: Props) return
; } - if (config.featureToggles.topnav) { - return
; - } - - return null; + return
; } const getStyles = (theme: GrafanaTheme2) => { diff --git a/public/app/core/components/AppChrome/Organization/OrganizationPicker.tsx b/public/app/core/components/AppChrome/OrganizationSwitcher/OrganizationPicker.tsx similarity index 100% rename from public/app/core/components/AppChrome/Organization/OrganizationPicker.tsx rename to public/app/core/components/AppChrome/OrganizationSwitcher/OrganizationPicker.tsx diff --git a/public/app/core/components/AppChrome/Organization/OrganizationSelect.tsx b/public/app/core/components/AppChrome/OrganizationSwitcher/OrganizationSelect.tsx similarity index 100% rename from public/app/core/components/AppChrome/Organization/OrganizationSelect.tsx rename to public/app/core/components/AppChrome/OrganizationSwitcher/OrganizationSelect.tsx diff --git a/public/app/core/components/AppChrome/Organization/OrganizationSwitcher.test.tsx b/public/app/core/components/AppChrome/OrganizationSwitcher/OrganizationSwitcher.test.tsx similarity index 100% rename from public/app/core/components/AppChrome/Organization/OrganizationSwitcher.test.tsx rename to public/app/core/components/AppChrome/OrganizationSwitcher/OrganizationSwitcher.test.tsx diff --git a/public/app/core/components/AppChrome/Organization/OrganizationSwitcher.tsx b/public/app/core/components/AppChrome/OrganizationSwitcher/OrganizationSwitcher.tsx similarity index 100% rename from public/app/core/components/AppChrome/Organization/OrganizationSwitcher.tsx rename to public/app/core/components/AppChrome/OrganizationSwitcher/OrganizationSwitcher.tsx diff --git a/public/app/core/components/AppChrome/Organization/types.ts b/public/app/core/components/AppChrome/OrganizationSwitcher/types.ts similarity index 100% rename from public/app/core/components/AppChrome/Organization/types.ts rename to public/app/core/components/AppChrome/OrganizationSwitcher/types.ts diff --git a/public/app/core/components/AppChrome/QuickAdd/QuickAdd.tsx b/public/app/core/components/AppChrome/QuickAdd/QuickAdd.tsx index c322b4c3875..65a9b92e498 100644 --- a/public/app/core/components/AppChrome/QuickAdd/QuickAdd.tsx +++ b/public/app/core/components/AppChrome/QuickAdd/QuickAdd.tsx @@ -7,7 +7,7 @@ import { Menu, Dropdown, useStyles2, useTheme2, ToolbarButton } from '@grafana/u import { useMediaQueryChange } from 'app/core/hooks/useMediaQueryChange'; import { useSelector } from 'app/types'; -import { NavToolbarSeparator } from '../NavToolbarSeparator'; +import { NavToolbarSeparator } from '../NavToolbar/NavToolbarSeparator'; import { findCreateActions } from './utils'; diff --git a/public/app/core/components/AppChrome/TopBar/TopNavBarMenu.tsx b/public/app/core/components/AppChrome/TopBar/TopNavBarMenu.tsx index b04e2a062a3..d885fd83f3c 100644 --- a/public/app/core/components/AppChrome/TopBar/TopNavBarMenu.tsx +++ b/public/app/core/components/AppChrome/TopBar/TopNavBarMenu.tsx @@ -6,7 +6,7 @@ import { useLocation } from 'react-router-dom'; import { GrafanaTheme2, NavModelItem } from '@grafana/data'; import { Menu, MenuItem, useStyles2 } from '@grafana/ui'; -import { enrichConfigItems, enrichWithInteractionTracking } from '../../NavBar/utils'; +import { enrichConfigItems, enrichWithInteractionTracking } from '../MegaMenu/utils'; export interface TopNavBarMenuProps { node: NavModelItem; diff --git a/public/app/core/components/AppChrome/TopSearchBar.tsx b/public/app/core/components/AppChrome/TopBar/TopSearchBar.tsx similarity index 87% rename from public/app/core/components/AppChrome/TopSearchBar.tsx rename to public/app/core/components/AppChrome/TopBar/TopSearchBar.tsx index 7038efdfccd..6bca6daa1a1 100644 --- a/public/app/core/components/AppChrome/TopSearchBar.tsx +++ b/public/app/core/components/AppChrome/TopBar/TopSearchBar.tsx @@ -8,16 +8,16 @@ import { config } from 'app/core/config'; import { contextSrv } from 'app/core/core'; import { useSelector } from 'app/types'; -import { Branding } from '../Branding/Branding'; +import { Branding } from '../../Branding/Branding'; +import { NewsContainer } from '../News/NewsContainer'; +import { OrganizationSwitcher } from '../OrganizationSwitcher/OrganizationSwitcher'; +import { QuickAdd } from '../QuickAdd/QuickAdd'; +import { TOP_BAR_LEVEL_HEIGHT } from '../types'; -import { NewsContainer } from './News/NewsContainer'; -import { OrganizationSwitcher } from './Organization/OrganizationSwitcher'; -import { QuickAdd } from './QuickAdd/QuickAdd'; -import { SignInLink } from './TopBar/SignInLink'; -import { TopNavBarMenu } from './TopBar/TopNavBarMenu'; -import { TopSearchBarSection } from './TopBar/TopSearchBarSection'; +import { SignInLink } from './SignInLink'; +import { TopNavBarMenu } from './TopNavBarMenu'; import { TopSearchBarCommandPaletteTrigger } from './TopSearchBarCommandPaletteTrigger'; -import { TOP_BAR_LEVEL_HEIGHT } from './types'; +import { TopSearchBarSection } from './TopSearchBarSection'; export const TopSearchBar = React.memo(function TopSearchBar() { const styles = useStyles2(getStyles); diff --git a/public/app/core/components/AppChrome/TopSearchBarCommandPaletteTrigger.tsx b/public/app/core/components/AppChrome/TopBar/TopSearchBarCommandPaletteTrigger.tsx similarity index 100% rename from public/app/core/components/AppChrome/TopSearchBarCommandPaletteTrigger.tsx rename to public/app/core/components/AppChrome/TopBar/TopSearchBarCommandPaletteTrigger.tsx diff --git a/public/app/core/components/NavBar/NavBar.test.tsx b/public/app/core/components/NavBar/NavBar.test.tsx deleted file mode 100644 index a4ffe1ea4ca..00000000000 --- a/public/app/core/components/NavBar/NavBar.test.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import { act, render, screen } from '@testing-library/react'; -import React from 'react'; - -import { locationService } from '@grafana/runtime'; - -import { TestProvider } from '../../../../test/helpers/TestProvider'; - -import { NavBar } from './NavBar'; - -jest.mock('app/core/services/context_srv', () => ({ - contextSrv: { - sidemenu: true, - user: {}, - isSignedIn: false, - isGrafanaAdmin: false, - isEditor: false, - hasEditPermissionFolders: false, - }, -})); - -const setup = () => { - return render( - - - - ); -}; - -describe('NavBar', () => { - it('should render component', async () => { - setup(); - const sidemenu = await screen.findByTestId('sidemenu'); - expect(sidemenu).toBeInTheDocument(); - }); - - it('should not render when in kiosk mode is tv', async () => { - setup(); - - act(() => locationService.partial({ kiosk: 'tv' })); - const sidemenu = screen.queryByTestId('sidemenu'); - expect(sidemenu).not.toBeInTheDocument(); - }); - - it('should not render when in kiosk mode is full', async () => { - setup(); - - act(() => locationService.partial({ kiosk: '1' })); - const sidemenu = screen.queryByTestId('sidemenu'); - expect(sidemenu).not.toBeInTheDocument(); - }); -}); diff --git a/public/app/core/components/NavBar/NavBar.tsx b/public/app/core/components/NavBar/NavBar.tsx deleted file mode 100644 index a3700c5f788..00000000000 --- a/public/app/core/components/NavBar/NavBar.tsx +++ /dev/null @@ -1,296 +0,0 @@ -import { css, cx } from '@emotion/css'; -import { FocusScope } from '@react-aria/focus'; -import { Location as HistoryLocation } from 'history'; -import { cloneDeep } from 'lodash'; -import React, { useState } from 'react'; -import { useLocation } from 'react-router-dom'; - -import { GrafanaTheme2, locationUtil, NavModelItem, NavSection, textUtil } from '@grafana/data'; -import { config, locationSearchToObject, locationService, reportInteraction } from '@grafana/runtime'; -import { useTheme2, CustomScrollbar, IconButton } from '@grafana/ui'; -import { getKioskMode } from 'app/core/navigation/kiosk'; -import { useSelector } from 'app/types'; - -import NavBarItem from './NavBarItem'; -import { NavBarItemIcon } from './NavBarItemIcon'; -import { NavBarItemWithoutMenu } from './NavBarItemWithoutMenu'; -import { NavBarMenu } from './NavBarMenu'; -import { NavBarMenuPortalContainer } from './NavBarMenuPortalContainer'; -import { NavBarToggle } from './NavBarToggle'; -import { NavBarContext } from './context'; -import { - enrichConfigItems, - enrichWithInteractionTracking, - getActiveItem, - isMatchOrChildMatch, - isSearchActive, - SEARCH_ITEM_ID, -} from './utils'; - -const onOpenSearch = () => { - locationService.partial({ search: 'open' }); -}; - -export const NavBar = React.memo(() => { - const navBarTree = useSelector((state) => state.navBarTree); - const theme = useTheme2(); - const styles = getStyles(theme); - const location = useLocation(); - const [menuOpen, setMenuOpen] = useState(false); - const [menuAnimationInProgress, setMenuAnimationInProgress] = useState(false); - const [menuIdOpen, setMenuIdOpen] = useState(undefined); - - // Here we need to hack in a "home" and "search" NavModelItem since this is constructed in the frontend - const searchItem: NavModelItem = enrichWithInteractionTracking( - { - id: SEARCH_ITEM_ID, - onClick: onOpenSearch, - text: 'Search dashboards', - icon: 'search', - }, - menuOpen - ); - - let homeUrl = config.appSubUrl || '/'; - if (!config.bootData.user.isSignedIn && !config.anonymousEnabled) { - homeUrl = textUtil.sanitizeUrl(locationUtil.getUrlForPartial(location, { forceLogin: 'true' })); - } - - const homeItem: NavModelItem = enrichWithInteractionTracking( - { - id: 'home', - text: 'Home', - url: homeUrl, - icon: 'grafana', - }, - menuOpen - ); - - const navTree = cloneDeep(navBarTree).filter((item) => item.hideFromMenu !== true); - - const coreItems = navTree - .filter((item) => item.section === NavSection.Core) - .map((item) => enrichWithInteractionTracking(item, menuOpen)); - const pluginItems = navTree - .filter((item) => item.section === NavSection.Plugin) - .map((item) => enrichWithInteractionTracking(item, menuOpen)); - const configItems = enrichConfigItems( - navTree.filter((item) => item.section === NavSection.Config), - location - ).map((item) => enrichWithInteractionTracking(item, menuOpen)); - - const activeItem = isSearchActive(location) ? searchItem : getActiveItem(navTree, location.pathname); - - if (shouldHideNavBar(location)) { - return null; - } - - return ( -
- - {(menuOpen || menuAnimationInProgress) && ( -
- setMenuOpen(false)} - /> -
- )} -
- ); -}); - -function shouldHideNavBar(location: HistoryLocation) { - const queryParams = locationSearchToObject(location.search); - - if (getKioskMode(queryParams)) { - return true; - } - - // Temporary, can be removed after topnav is made permanent - if ((location.pathname.indexOf('/d/') === 0 && queryParams.editview) || queryParams.editPanel) { - return true; - } - - return false; -} - -NavBar.displayName = 'NavBar'; - -const getStyles = (theme: GrafanaTheme2) => ({ - navWrapper: css({ - position: 'relative', - display: 'flex', - }), - sidemenu: css({ - label: 'sidemenu', - display: 'flex', - flexDirection: 'column', - backgroundColor: theme.colors.background.primary, - zIndex: theme.zIndex.sidemenu, - padding: `${theme.spacing(1)} 0`, - position: 'relative', - width: theme.components.sidemenu.width, - borderRight: `1px solid ${theme.colors.border.weak}`, - - [theme.breakpoints.down('md')]: { - height: theme.spacing(7), - position: 'fixed', - paddingTop: '0px', - backgroundColor: 'inherit', - borderRight: 0, - }, - }), - mobileSidemenuLogo: css({ - alignItems: 'center', - display: 'flex', - flexDirection: 'row', - justifyContent: 'space-between', - padding: theme.spacing(2), - - [theme.breakpoints.up('md')]: { - display: 'none', - }, - }), - itemList: css({ - backgroundColor: 'inherit', - display: 'flex', - flexDirection: 'column', - height: '100%', - - [theme.breakpoints.down('md')]: { - visibility: 'hidden', - }, - }), - grafanaLogo: css({ - alignItems: 'stretch', - display: 'flex', - flexShrink: 0, - height: theme.spacing(6), - justifyContent: 'stretch', - - [theme.breakpoints.down('md')]: { - visibility: 'hidden', - }, - }), - grafanaLogoInner: css({ - alignItems: 'center', - display: 'flex', - height: '100%', - justifyContent: 'center', - width: '100%', - - '> div': { - height: 'auto', - width: 'auto', - }, - }), - search: css({ - display: 'none', - marginTop: 0, - - [theme.breakpoints.up('md')]: { - display: 'grid', - }, - }), - verticalSpacer: css({ - marginTop: 'auto', - }), - hideFromMobile: css({ - [theme.breakpoints.down('md')]: { - display: 'none', - }, - }), - menuWrapper: css({ - position: 'fixed', - display: 'grid', - gridAutoFlow: 'column', - height: '100%', - zIndex: theme.zIndex.sidemenu, - }), - menuExpandIcon: css({ - position: 'absolute', - top: '43px', - right: '0px', - transform: `translateX(50%)`, - }), - menuPortalContainer: css({ - zIndex: theme.zIndex.sidemenu, - }), -}); diff --git a/public/app/core/components/NavBar/NavBarItem.test.tsx b/public/app/core/components/NavBar/NavBarItem.test.tsx deleted file mode 100644 index fe89f225b39..00000000000 --- a/public/app/core/components/NavBar/NavBarItem.test.tsx +++ /dev/null @@ -1,247 +0,0 @@ -import { render, screen, waitFor } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import React from 'react'; -import { BrowserRouter } from 'react-router-dom'; - -import { locationUtil } from '@grafana/data'; -import { config, LocationService, setLocationService } from '@grafana/runtime'; - -// Need to mock createBrowserHistory here to avoid errors -jest.mock('history', () => ({ - ...jest.requireActual('history'), - createBrowserHistory: () => ({ - listen: jest.fn(), - location: {}, - createHref: jest.fn(), - }), -})); - -import NavBarItem, { Props } from './NavBarItem'; -import { NavBarContext } from './context'; - -const onClickMock = jest.fn(); -const setMenuIdOpenMock = jest.fn(); -const defaults: Props = { - link: { - text: 'Parent Node', - onClick: onClickMock, - children: [ - { text: 'Child Node 1', onClick: onClickMock, children: [] }, - { text: 'Child Node 2', onClick: onClickMock, children: [] }, - ], - id: 'MY_NAV_ID', - }, -}; - -async function getTestContext(overrides: Partial = {}, subUrl = '', isMenuOpen = false) { - jest.clearAllMocks(); - config.appSubUrl = subUrl; - locationUtil.initialize({ config, getTimeRangeForUrl: jest.fn(), getVariablesUrlParams: jest.fn() }); - const pushMock = jest.fn(); - const locationService = { push: pushMock } as unknown as LocationService; - setLocationService(locationService); - const props = { ...defaults, ...overrides }; - - const { rerender } = render( - - - - - - ); - - // Need to click this first to set the correct selection range - // see https://github.com/testing-library/user-event/issues/901#issuecomment-1087192424 - await userEvent.click(document.body); - return { rerender, pushMock }; -} - -describe('NavBarItem', () => { - describe('when url property is not set', () => { - it('then it renders the menu trigger as a button', async () => { - await getTestContext(); - - expect(screen.getAllByRole('button')).toHaveLength(1); - }); - - describe('and clicking on the menu trigger button', () => { - it('then the onClick handler should be called', async () => { - await getTestContext(); - - await userEvent.click(screen.getByRole('button')); - expect(onClickMock).toHaveBeenCalledTimes(1); - }); - }); - - describe('and hovering over the menu trigger button', () => { - it('then the menuIdOpen should be set correctly', async () => { - await getTestContext(); - - await userEvent.hover(screen.getByRole('button')); - expect(setMenuIdOpenMock).toHaveBeenCalledWith(defaults.link.id); - }); - }); - - describe('and tabbing to the menu trigger button', () => { - it('then the menuIdOpen should be set correctly', async () => { - await getTestContext(); - - await userEvent.tab(); - expect(setMenuIdOpenMock).toHaveBeenCalledWith(defaults.link.id); - }); - }); - - it('shows the menu when the correct menuIdOpen is set', async () => { - await getTestContext(undefined, undefined, true); - - expect(screen.getByText('Parent Node')).toBeInTheDocument(); - expect(screen.getByText('Child Node 1')).toBeInTheDocument(); - expect(screen.getByText('Child Node 2')).toBeInTheDocument(); - }); - - describe('and pressing arrow right on the menu trigger button', () => { - it('then the correct menu item should receive focus', async () => { - await getTestContext(undefined, undefined, true); - - await userEvent.tab(); - expect(screen.getAllByRole('menuitem')).toHaveLength(3); - expect(screen.getByRole('menuitem', { name: 'Parent Node' })).toHaveAttribute('tabIndex', '-1'); - expect(screen.getAllByRole('menuitem')[1]).toHaveAttribute('tabIndex', '-1'); - expect(screen.getAllByRole('menuitem')[2]).toHaveAttribute('tabIndex', '-1'); - - await userEvent.keyboard('{ArrowRight}'); - expect(screen.getAllByRole('menuitem')).toHaveLength(3); - expect(screen.getAllByRole('menuitem')[0]).toHaveAttribute('tabIndex', '0'); - expect(screen.getAllByRole('menuitem')[1]).toHaveAttribute('tabIndex', '-1'); - expect(screen.getAllByRole('menuitem')[2]).toHaveAttribute('tabIndex', '-1'); - }); - }); - }); - - describe('when url property is set', () => { - it('then it renders the menu trigger as a link', async () => { - await getTestContext({ link: { ...defaults.link, url: 'https://www.grafana.com' } }); - - expect(screen.getAllByRole('link')).toHaveLength(1); - expect(screen.getByRole('link')).toHaveAttribute('href', 'https://www.grafana.com'); - }); - - describe('and hovering over the menu trigger link', () => { - it('sets the correct menuIdOpen', async () => { - await getTestContext({ link: { ...defaults.link, url: 'https://www.grafana.com' } }); - - await userEvent.hover(screen.getByRole('link')); - - expect(setMenuIdOpenMock).toHaveBeenCalledWith(defaults.link.id); - }); - }); - - describe('and tabbing to the menu trigger link', () => { - it('sets the correct menuIdOpen', async () => { - await getTestContext({ link: { ...defaults.link, url: 'https://www.grafana.com' } }); - - await userEvent.tab(); - - expect(setMenuIdOpenMock).toHaveBeenCalledWith(defaults.link.id); - }); - }); - - it('shows the menu when the correct menuIdOpen is set', async () => { - await getTestContext({ link: { ...defaults.link, url: 'https://www.grafana.com' } }, undefined, true); - - expect(screen.getByText('Parent Node')).toBeInTheDocument(); - expect(screen.getByText('Child Node 1')).toBeInTheDocument(); - expect(screen.getByText('Child Node 2')).toBeInTheDocument(); - }); - - describe('and pressing arrow right on the menu trigger link', () => { - it('then the correct menu item should receive focus', async () => { - await getTestContext({ link: { ...defaults.link, url: 'https://www.grafana.com' } }, undefined, true); - - await userEvent.tab(); - expect(screen.getAllByRole('link')[0]).toHaveFocus(); - expect(screen.getAllByRole('menuitem')).toHaveLength(3); - expect(screen.getAllByRole('menuitem')[0]).toHaveAttribute('tabIndex', '-1'); - expect(screen.getAllByRole('menuitem')[1]).toHaveAttribute('tabIndex', '-1'); - expect(screen.getAllByRole('menuitem')[2]).toHaveAttribute('tabIndex', '-1'); - - await userEvent.keyboard('{ArrowRight}'); - expect(screen.getAllByRole('link')[0]).not.toHaveFocus(); - expect(screen.getAllByRole('menuitem')).toHaveLength(3); - expect(screen.getAllByRole('menuitem')[0]).toHaveAttribute('tabIndex', '0'); - expect(screen.getAllByRole('menuitem')[1]).toHaveAttribute('tabIndex', '-1'); - expect(screen.getAllByRole('menuitem')[2]).toHaveAttribute('tabIndex', '-1'); - }); - }); - - describe('and pressing arrow left on a menu item', () => { - it('then the nav bar item should receive focus', async () => { - await getTestContext({ link: { ...defaults.link, url: 'https://www.grafana.com' } }, undefined, true); - - await userEvent.tab(); - await userEvent.keyboard('{ArrowRight}'); - expect(screen.getAllByRole('link')[0]).not.toHaveFocus(); - expect(screen.getAllByRole('menuitem')).toHaveLength(3); - expect(screen.getAllByRole('menuitem')[0]).toHaveAttribute('tabIndex', '0'); - expect(screen.getAllByRole('menuitem')[1]).toHaveAttribute('tabIndex', '-1'); - expect(screen.getAllByRole('menuitem')[2]).toHaveAttribute('tabIndex', '-1'); - - await userEvent.keyboard('{ArrowLeft}'); - expect(screen.getAllByRole('link')[0]).toHaveFocus(); - expect(screen.getAllByRole('menuitem')).toHaveLength(3); - expect(screen.getAllByRole('menuitem')[0]).toHaveAttribute('tabIndex', '-1'); - expect(screen.getAllByRole('menuitem')[1]).toHaveAttribute('tabIndex', '-1'); - expect(screen.getAllByRole('menuitem')[2]).toHaveAttribute('tabIndex', '-1'); - }); - }); - - describe('when appSubUrl is configured and user clicks on menuitem link', () => { - it('then location service should be called with correct url', async () => { - const { pushMock } = await getTestContext( - { - link: { - ...defaults.link, - url: 'https://www.grafana.com', - children: [{ text: 'New', url: '/grafana/dashboard/new', children: [] }], - }, - }, - '/grafana', - true - ); - - await userEvent.click(screen.getByText('New')); - await waitFor(() => { - expect(pushMock).toHaveBeenCalledTimes(1); - expect(pushMock).toHaveBeenCalledWith('/dashboard/new'); - }); - }); - }); - - describe('when appSubUrl is not configured and user clicks on menuitem link', () => { - it('then location service should be called with correct url', async () => { - const { pushMock } = await getTestContext( - { - link: { - ...defaults.link, - url: 'https://www.grafana.com', - children: [{ text: 'New', url: '/grafana/dashboard/new', children: [] }], - }, - }, - undefined, - true - ); - - await userEvent.click(screen.getByText('New')); - await waitFor(() => { - expect(pushMock).toHaveBeenCalledTimes(1); - expect(pushMock).toHaveBeenCalledWith('/grafana/dashboard/new'); - }); - }); - }); - }); -}); diff --git a/public/app/core/components/NavBar/NavBarItem.tsx b/public/app/core/components/NavBar/NavBarItem.tsx deleted file mode 100644 index 906840fb9d8..00000000000 --- a/public/app/core/components/NavBar/NavBarItem.tsx +++ /dev/null @@ -1,115 +0,0 @@ -import { css, cx } from '@emotion/css'; -import { Item } from '@react-stately/collections'; -import React from 'react'; - -import { GrafanaTheme2, locationUtil, NavMenuItemType, NavModelItem } from '@grafana/data'; -import { locationService } from '@grafana/runtime'; -import { toIconName, useTheme2 } from '@grafana/ui'; - -import { NavBarItemMenu } from './NavBarItemMenu'; -import { NavBarItemMenuTrigger } from './NavBarItemMenuTrigger'; -import { getNavBarItemWithoutMenuStyles } from './NavBarItemWithoutMenu'; -import { NavBarMenuItem } from './NavBarMenuItem'; -import { useNavBarContext } from './context'; -import { getNavModelItemKey } from './utils'; - -export interface Props { - isActive?: boolean; - className?: string; - reverseMenuDirection?: boolean; - link: NavModelItem; -} - -const NavBarItem = ({ isActive = false, className, reverseMenuDirection = false, link }: Props) => { - const theme = useTheme2(); - const menuItems = link.children ?? []; - const { menuIdOpen } = useNavBarContext(); - - // Spreading `menuItems` here as otherwise we'd be mutating props - const menuItemsSorted = reverseMenuDirection ? [...menuItems].reverse() : menuItems; - const filteredItems = menuItemsSorted - .filter((item) => !item.hideFromMenu) - .map((i) => ({ ...i, menuItemType: NavMenuItemType.Item })); - const adjustHeightForBorder = filteredItems.length === 0; - const styles = getStyles(theme, adjustHeightForBorder, isActive); - const section: NavModelItem = { - ...link, - children: filteredItems, - menuItemType: NavMenuItemType.Section, - }; - const items: NavModelItem[] = [section].concat(filteredItems); - - const onNavigate = (item: NavModelItem) => { - const { url, target, onClick } = item; - onClick?.(); - - if (url) { - if (!target && url.startsWith('/')) { - locationService.push(locationUtil.stripBaseFromUrl(url)); - } else { - window.open(url, target); - } - } - }; - - return ( -
  • - - - {(item: NavModelItem) => { - const isSection = item.menuItemType === NavMenuItemType.Section; - const iconName = item.icon ? toIconName(item.icon) : undefined; - const icon = item.showIconInNavbar && !isSection ? iconName : undefined; - - return ( - - - - ); - }} - - -
  • - ); -}; - -export default NavBarItem; - -const getStyles = (theme: GrafanaTheme2, adjustHeightForBorder: boolean, isActive?: boolean) => ({ - ...getNavBarItemWithoutMenuStyles(theme, isActive), - containerHover: css({ - backgroundColor: theme.colors.action.hover, - color: theme.colors.text.primary, - }), - primaryText: css({ - color: theme.colors.text.primary, - }), - header: css({ - height: `calc(${theme.spacing(6)} - ${adjustHeightForBorder ? 2 : 1}px)`, - fontSize: theme.typography.h4.fontSize, - fontWeight: theme.typography.h4.fontWeight, - padding: `${theme.spacing(1)} ${theme.spacing(2)}`, - whiteSpace: 'nowrap', - width: '100%', - }), -}); diff --git a/public/app/core/components/NavBar/NavBarItemMenu.tsx b/public/app/core/components/NavBar/NavBarItemMenu.tsx deleted file mode 100644 index caeda7e2f8e..00000000000 --- a/public/app/core/components/NavBar/NavBarItemMenu.tsx +++ /dev/null @@ -1,120 +0,0 @@ -import { css } from '@emotion/css'; -import { useMenu } from '@react-aria/menu'; -import { mergeProps } from '@react-aria/utils'; -import { useTreeState } from '@react-stately/tree'; -import { SpectrumMenuProps } from '@react-types/menu'; -import React, { ReactElement, useEffect, useRef } from 'react'; - -import { GrafanaTheme2, NavMenuItemType, NavModelItem } from '@grafana/data'; -import { CustomScrollbar, useTheme2 } from '@grafana/ui'; - -import { NavBarItemMenuItem } from './NavBarItemMenuItem'; -import { useNavBarItemMenuContext } from './context'; -import { getNavModelItemKey } from './utils'; - -export interface NavBarItemMenuProps extends SpectrumMenuProps { - onNavigate: (item: NavModelItem) => void; - adjustHeightForBorder: boolean; - reverseMenuDirection?: boolean; -} - -export function NavBarItemMenu(props: NavBarItemMenuProps): ReactElement | null { - const { reverseMenuDirection, adjustHeightForBorder, disabledKeys, onNavigate, ...rest } = props; - const contextProps = useNavBarItemMenuContext(); - const completeProps = { - ...mergeProps(contextProps, rest), - }; - const { menuHasFocus, menuProps: contextMenuProps = {} } = contextProps; - const theme = useTheme2(); - const styles = getStyles(theme, reverseMenuDirection); - const state = useTreeState({ ...rest, disabledKeys }); - const ref = useRef(null); - const { menuProps } = useMenu(completeProps, { ...state }, ref); - const allItems = [...state.collection]; - const items = allItems.filter((item) => item.value.menuItemType === NavMenuItemType.Item); - const section = allItems.find((item) => item.value.menuItemType === NavMenuItemType.Section); - - useEffect(() => { - if (menuHasFocus && !state.selectionManager.isFocused) { - state.selectionManager.setFocusedKey(section?.key ?? ''); - state.selectionManager.setFocused(true); - } else if (!menuHasFocus) { - state.selectionManager.setFocused(false); - state.selectionManager.setFocusedKey(''); - state.selectionManager.clearSelection(); - } - }, [menuHasFocus, state.selectionManager, reverseMenuDirection, section?.key]); - - if (!section) { - return null; - } - - const menuSubTitle = section.value.subTitle; - - const headerComponent = ; - - const itemComponents = items.map((item) => ( - - )); - - if (itemComponents.length === 0 && section.value.emptyMessage) { - itemComponents.push( -
    - {section.value.emptyMessage} -
    - ); - } - - const subTitleComponent = menuSubTitle && ( -
  • - {menuSubTitle} -
  • - ); - - const contents = [itemComponents, subTitleComponent]; - const contentComponent = ( - - {reverseMenuDirection ? contents.reverse() : contents} - - ); - - const menu = [headerComponent, contentComponent]; - - return ( -
      - {reverseMenuDirection ? menu.reverse() : menu} -
    - ); -} - -function getStyles(theme: GrafanaTheme2, reverseDirection?: boolean) { - return { - menu: css` - background-color: ${theme.colors.background.primary}; - border: 1px solid ${theme.components.panel.borderColor}; - box-shadow: ${theme.shadows.z3}; - display: flex; - flex-direction: column; - list-style: none; - max-height: 400px; - max-width: 300px; - min-width: 140px; - transition: ${theme.transitions.create('opacity')}; - z-index: ${theme.zIndex.sidemenu}; - `, - subtitle: css` - background-color: transparent; - border-${reverseDirection ? 'bottom' : 'top'}: 1px solid ${theme.colors.border.weak}; - color: ${theme.colors.text.secondary}; - font-size: ${theme.typography.bodySmall.fontSize}; - font-weight: ${theme.typography.bodySmall.fontWeight}; - padding: ${theme.spacing(1)} ${theme.spacing(2)} ${theme.spacing(1)}; - text-align: left; - white-space: nowrap; - `, - emptyMessage: css` - font-style: italic; - padding: ${theme.spacing(0.5, 2)}; - `, - }; -} diff --git a/public/app/core/components/NavBar/NavBarItemMenuItem.tsx b/public/app/core/components/NavBar/NavBarItemMenuItem.tsx deleted file mode 100644 index 5ad0f769bbc..00000000000 --- a/public/app/core/components/NavBar/NavBarItemMenuItem.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import { css } from '@emotion/css'; -import { useFocus, useKeyboard } from '@react-aria/interactions'; -import { useMenuItem } from '@react-aria/menu'; -import { mergeProps } from '@react-aria/utils'; -import { TreeState } from '@react-stately/tree'; -import { Node } from '@react-types/shared'; -import React, { ReactElement, useRef, useState } from 'react'; - -import { GrafanaTheme2, NavModelItem } from '@grafana/data'; -import { useTheme2 } from '@grafana/ui'; - -import { useNavBarItemMenuContext, useNavBarContext } from './context'; - -export interface NavBarItemMenuItemProps { - item: Node; - state: TreeState; - onNavigate: (item: NavModelItem) => void; -} - -export function NavBarItemMenuItem({ item, state, onNavigate }: NavBarItemMenuItemProps): ReactElement { - const { onClose, onLeft } = useNavBarItemMenuContext(); - const { setMenuIdOpen } = useNavBarContext(); - const { key, rendered } = item; - const ref = useRef(null); - const isDisabled = state.disabledKeys.has(key); - - // style to the focused menu item - const [isFocused, setFocused] = useState(false); - const { focusProps } = useFocus({ onFocusChange: setFocused, isDisabled }); - const theme = useTheme2(); - const isSection = item.value.menuItemType === 'section'; - const styles = getStyles(theme, isFocused, isSection); - const onAction = () => { - setMenuIdOpen(undefined); - onNavigate(item.value); - onClose(); - }; - - let { menuItemProps } = useMenuItem( - { - isDisabled, - 'aria-label': item['aria-label'], - key, - closeOnSelect: true, - onClose, - onAction, - }, - state, - ref - ); - - const { keyboardProps } = useKeyboard({ - onKeyDown: (e) => { - if (e.key === 'ArrowLeft') { - onLeft(); - } - e.continuePropagation(); - }, - }); - - return ( - <> -
  • - {rendered} -
  • - - ); -} - -function getStyles(theme: GrafanaTheme2, isFocused: boolean, isSection: boolean) { - let backgroundColor = 'transparent'; - if (isFocused) { - backgroundColor = theme.colors.action.hover; - } else if (isSection) { - backgroundColor = theme.colors.background.secondary; - } - return { - menuItem: css` - background-color: ${backgroundColor}; - color: ${theme.colors.text.primary}; - - &:focus-visible { - background-color: ${theme.colors.action.hover}; - box-shadow: none; - color: ${theme.colors.text.primary}; - outline: 2px solid ${theme.colors.primary.main}; - outline-offset: -2px; - transition: none; - } - `, - }; -} diff --git a/public/app/core/components/NavBar/NavBarItemMenuTrigger.tsx b/public/app/core/components/NavBar/NavBarItemMenuTrigger.tsx deleted file mode 100644 index f9f22171407..00000000000 --- a/public/app/core/components/NavBar/NavBarItemMenuTrigger.tsx +++ /dev/null @@ -1,254 +0,0 @@ -import { css, cx } from '@emotion/css'; -import { useButton } from '@react-aria/button'; -import { useDialog } from '@react-aria/dialog'; -import { FocusScope } from '@react-aria/focus'; -import { useFocusWithin, useHover, useKeyboard } from '@react-aria/interactions'; -import { useMenuTrigger } from '@react-aria/menu'; -import { DismissButton, OverlayContainer, useOverlay, useOverlayPosition } from '@react-aria/overlays'; -import { useMenuTriggerState } from '@react-stately/menu'; -import { MenuTriggerProps } from '@react-types/menu'; -import React, { ReactElement, useEffect, useState } from 'react'; - -import { GrafanaTheme2, NavModelItem } from '@grafana/data'; -import { reportExperimentView } from '@grafana/runtime'; -import { Link, useTheme2 } from '@grafana/ui'; - -import { NavBarItemIcon } from './NavBarItemIcon'; -import { getNavMenuPortalContainer } from './NavBarMenuPortalContainer'; -import { NavFeatureHighlight } from './NavFeatureHighlight'; -import { NavBarItemMenuContext, useNavBarContext } from './context'; - -export interface NavBarItemMenuTriggerProps extends MenuTriggerProps { - children: ReactElement; - item: NavModelItem; - isActive?: boolean; - label: string; - reverseMenuDirection: boolean; -} - -export function NavBarItemMenuTrigger(props: NavBarItemMenuTriggerProps): ReactElement { - const { item, isActive, label, children: menu, reverseMenuDirection, ...rest } = props; - const [menuHasFocus, setMenuHasFocus] = useState(false); - const { menuIdOpen, setMenuIdOpen } = useNavBarContext(); - const theme = useTheme2(); - const styles = getStyles(theme, isActive); - - // Create state based on the incoming props - const state = useMenuTriggerState({ ...rest }); - - // Get props for the menu trigger and menu elements - const ref = React.useRef(null); - const { menuTriggerProps, menuProps } = useMenuTrigger({}, state, ref); - - useEffect(() => { - if (item.highlightId) { - reportExperimentView(`feature-highlights-${item.highlightId}-nav`, 'test', ''); - } - }, [item.highlightId]); - - const { hoverProps } = useHover({ - onHoverChange: (isHovering) => { - if (isHovering) { - state.open(); - setMenuIdOpen(item.id); - } else { - state.close(); - setMenuIdOpen(undefined); - } - }, - }); - - useEffect(() => { - // close the menu when changing submenus - if (menuIdOpen !== item.id) { - state.close(); - setMenuHasFocus(false); - } else { - state.open(); - } - }, [menuIdOpen, state, item.id]); - - const { keyboardProps } = useKeyboard({ - onKeyDown: (e) => { - switch (e.key) { - case 'ArrowRight': - if (!state.isOpen) { - state.open(); - setMenuIdOpen(item.id); - } - setMenuHasFocus(true); - break; - case 'Tab': - setMenuIdOpen(undefined); - break; - default: - break; - } - }, - }); - - // Get props for the button based on the trigger props from useMenuTrigger - const { buttonProps } = useButton(menuTriggerProps, ref); - const Wrapper = item.highlightText ? NavFeatureHighlight : React.Fragment; - const itemContent = ( - - - - - - ); - let element = ( - - ); - - if (item?.url) { - element = - !item.target && item.url.startsWith('/') ? ( - } - href={item.url} - target={item.target} - onClick={item?.onClick} - className={styles.element} - aria-label={label} - > - {itemContent} - - ) : ( - } - className={styles.element} - aria-label={label} - > - {itemContent} - - ); - } - - const overlayRef = React.useRef(null); - const { dialogProps } = useDialog({}, overlayRef); - const { overlayProps } = useOverlay( - { - onClose: () => { - state.close(); - setMenuIdOpen(undefined); - }, - isOpen: state.isOpen, - isDismissable: true, - }, - overlayRef - ); - - let { overlayProps: overlayPositionProps } = useOverlayPosition({ - targetRef: ref, - overlayRef, - placement: reverseMenuDirection ? 'right bottom' : 'right top', - isOpen: state.isOpen, - }); - - const { focusWithinProps } = useFocusWithin({ - onFocusWithin: (e) => { - if (e.target.id === ref.current?.id) { - // If focussing on the trigger itself, set the menu id that is open - setMenuIdOpen(item.id); - state.open(); - } - e.target.scrollIntoView?.({ - block: 'nearest', - }); - }, - onBlurWithin: (e) => { - if (e.target?.getAttribute('role') === 'menuitem' && !overlayRef.current?.contains(e.relatedTarget)) { - // If it is blurring from a menuitem to an element outside the current overlay - // close the menu that is open - setMenuIdOpen(undefined); - } - }, - }); - - return ( -
    - {element} - {state.isOpen && ( - - state.close(), - onLeft: () => { - setMenuHasFocus(false); - ref.current?.focus(); - }, - }} - > - -
    - state.close()} /> - {menu} - state.close()} /> -
    -
    -
    -
    - )} -
    - ); -} - -const getStyles = (theme: GrafanaTheme2, isActive?: boolean) => ({ - element: css({ - backgroundColor: 'transparent', - border: 'none', - color: 'inherit', - display: 'grid', - padding: 0, - placeContent: 'center', - height: theme.spacing(6), - width: theme.spacing(7), - - '&::before': { - display: isActive ? 'block' : 'none', - content: '" "', - position: 'absolute', - left: theme.spacing(1), - top: theme.spacing(1.5), - bottom: theme.spacing(1.5), - width: theme.spacing(0.5), - borderRadius: theme.shape.borderRadius(1), - backgroundImage: theme.colors.gradients.brandVertical, - }, - - '&:focus-visible': { - backgroundColor: theme.colors.action.hover, - boxShadow: 'none', - color: theme.colors.text.primary, - outline: `${theme.shape.borderRadius(1)} solid ${theme.colors.primary.main}`, - outlineOffset: `-${theme.shape.borderRadius(1)}`, - transition: 'none', - }, - }), - icon: css({ - height: '100%', - width: '100%', - }), -}); diff --git a/public/app/core/components/NavBar/NavBarItemWithoutMenu.tsx b/public/app/core/components/NavBar/NavBarItemWithoutMenu.tsx deleted file mode 100644 index e956b825bfd..00000000000 --- a/public/app/core/components/NavBar/NavBarItemWithoutMenu.tsx +++ /dev/null @@ -1,117 +0,0 @@ -import { css, cx } from '@emotion/css'; -import React, { ReactNode } from 'react'; - -import { GrafanaTheme2 } from '@grafana/data'; -import { Link, useTheme2 } from '@grafana/ui'; - -import { NavFeatureHighlight } from './NavFeatureHighlight'; - -export interface NavBarItemWithoutMenuProps { - label: string; - children: ReactNode; - className?: string; - elClassName?: string; - url?: string; - target?: string; - isActive?: boolean; - onClick?: () => void; - highlightText?: string; -} - -export function NavBarItemWithoutMenu({ - label, - children, - url, - target, - isActive = false, - onClick, - highlightText, - className, - elClassName, -}: NavBarItemWithoutMenuProps) { - const theme = useTheme2(); - const styles = getNavBarItemWithoutMenuStyles(theme, isActive); - - const content = highlightText ? ( - -
    {children}
    -
    - ) : ( -
    {children}
    - ); - - const elStyle = cx(styles.element, elClassName); - - const renderContents = () => { - if (!url) { - return ( - - ); - } else if (!target && url.startsWith('/')) { - return ( - - {content} - - ); - } else { - return ( - - {content} - - ); - } - }; - - return
    {renderContents()}
    ; -} - -export function getNavBarItemWithoutMenuStyles(theme: GrafanaTheme2, isActive?: boolean) { - return { - container: css({ - position: 'relative', - color: isActive ? theme.colors.text.primary : theme.colors.text.secondary, - display: 'grid', - - '&:hover': { - backgroundColor: theme.colors.action.hover, - color: theme.colors.text.primary, - }, - }), - element: css({ - backgroundColor: 'transparent', - border: 'none', - color: 'inherit', - display: 'block', - padding: 0, - overflowWrap: 'anywhere', - - '&::before': { - display: isActive ? 'block' : 'none', - content: "' '", - position: 'absolute', - left: theme.spacing(1), - top: theme.spacing(1.5), - bottom: theme.spacing(1.5), - width: theme.spacing(0.5), - borderRadius: theme.shape.borderRadius(1), - backgroundImage: theme.colors.gradients.brandVertical, - }, - - '&:focus-visible': { - backgroundColor: theme.colors.action.hover, - boxShadow: 'none', - color: theme.colors.text.primary, - outline: `${theme.shape.borderRadius(1)} solid ${theme.colors.primary.main}`, - outlineOffset: `-${theme.shape.borderRadius(1)}`, - transition: 'none', - }, - }), - - icon: css({ - height: '100%', - width: '100%', - }), - }; -} diff --git a/public/app/core/components/NavBar/NavBarMenu.test.tsx b/public/app/core/components/NavBar/NavBarMenu.test.tsx deleted file mode 100644 index 14670f47ccd..00000000000 --- a/public/app/core/components/NavBar/NavBarMenu.test.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { screen } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import React from 'react'; -import { render } from 'test/redux-rtl'; - -import { NavModelItem } from '@grafana/data'; - -import { NavBarMenu } from './NavBarMenu'; - -// don't care about interaction tracking in our unit tests -jest.mock('@grafana/runtime', () => ({ - ...jest.requireActual('@grafana/runtime'), - reportInteraction: jest.fn(), -})); - -describe('NavBarMenu', () => { - const mockOnClose = jest.fn(); - const mockNavItems: NavModelItem[] = []; - const mockSetMenuAnimationInProgress = jest.fn(); - - beforeEach(() => { - render( - - ); - }); - - it('should render the component', () => { - const sidemenu = screen.getByTestId('navbarmenu'); - expect(sidemenu).toBeInTheDocument(); - }); - - it('has a close button', () => { - const closeButton = screen.getAllByRole('button', { name: 'Close navigation menu' }); - // this is for mobile, will be hidden with display: none; on desktop - expect(closeButton[0]).toBeInTheDocument(); - // this is for desktop, will be hidden with display: none; on mobile - expect(closeButton[1]).toBeInTheDocument(); - }); - - it('clicking the close button calls the onClose callback', async () => { - const closeButton = screen.getAllByRole('button', { name: 'Close navigation menu' }); - expect(closeButton[0]).toBeInTheDocument(); - expect(closeButton[1]).toBeInTheDocument(); - await userEvent.click(closeButton[0]); - expect(mockOnClose).toHaveBeenCalled(); - await userEvent.click(closeButton[1]); - expect(mockOnClose).toHaveBeenCalled(); - }); -}); diff --git a/public/app/core/components/NavBar/NavBarMenu.tsx b/public/app/core/components/NavBar/NavBarMenu.tsx deleted file mode 100644 index d39ab6a4680..00000000000 --- a/public/app/core/components/NavBar/NavBarMenu.tsx +++ /dev/null @@ -1,467 +0,0 @@ -import { css, cx } from '@emotion/css'; -import { useDialog } from '@react-aria/dialog'; -import { FocusScope } from '@react-aria/focus'; -import { OverlayContainer, useOverlay } from '@react-aria/overlays'; -import React, { useRef } from 'react'; -import CSSTransition from 'react-transition-group/CSSTransition'; -import { useLocalStorage } from 'react-use'; - -import { GrafanaTheme2, NavModelItem } from '@grafana/data'; -import { reportInteraction } from '@grafana/runtime'; -import { CollapsableSection, CustomScrollbar, Icon, IconButton, toIconName, useStyles2, useTheme2 } from '@grafana/ui'; - -import { NavBarItemIcon } from './NavBarItemIcon'; -import { NavBarItemWithoutMenu } from './NavBarItemWithoutMenu'; -import { NavBarMenuItem } from './NavBarMenuItem'; -import { NavBarToggle } from './NavBarToggle'; -import { NavFeatureHighlight } from './NavFeatureHighlight'; -import { isMatchOrChildMatch } from './utils'; - -const MENU_WIDTH = '350px'; - -export interface Props { - activeItem?: NavModelItem; - isOpen: boolean; - navItems: NavModelItem[]; - setMenuAnimationInProgress: (isInProgress: boolean) => void; - onClose: () => void; -} - -export function NavBarMenu({ activeItem, isOpen, navItems, onClose, setMenuAnimationInProgress }: Props) { - const theme = useTheme2(); - const styles = getStyles(theme); - const ANIMATION_DURATION = theme.transitions.duration.standard; - const animStyles = getAnimStyles(theme, ANIMATION_DURATION); - const ref = useRef(null); - const backdropRef = useRef(null); - const { dialogProps } = useDialog({}, ref); - - const { overlayProps, underlayProps } = useOverlay( - { - isDismissable: true, - isOpen, - onClose, - }, - ref - ); - - return ( - - - setMenuAnimationInProgress(true)} - onExited={() => setMenuAnimationInProgress(false)} - appear={isOpen} - in={isOpen} - classNames={animStyles.overlay} - timeout={ANIMATION_DURATION} - > -
    -
    - - -
    - { - reportInteraction('grafana_navigation_collapsed'); - onClose(); - }} - /> - -
    -
    -
    - -
    - - - ); -} - -NavBarMenu.displayName = 'NavBarMenu'; - -const getStyles = (theme: GrafanaTheme2) => ({ - backdrop: css({ - backdropFilter: 'blur(1px)', - backgroundColor: theme.components.overlay.background, - bottom: 0, - left: 0, - position: 'fixed', - right: 0, - top: 0, - zIndex: theme.zIndex.modalBackdrop, - }), - container: css({ - display: 'flex', - bottom: 0, - flexDirection: 'column', - left: 0, - paddingTop: theme.spacing(1), - marginRight: theme.spacing(1.5), - right: 0, - zIndex: theme.zIndex.modal, - position: 'fixed', - top: 0, - boxSizing: 'content-box', - [theme.breakpoints.up('md')]: { - borderRight: `1px solid ${theme.colors.border.weak}`, - right: 'unset', - }, - }), - content: css({ - display: 'flex', - flexDirection: 'column', - overflow: 'auto', - }), - mobileHeader: css({ - borderBottom: `1px solid ${theme.colors.border.weak}`, - display: 'flex', - justifyContent: 'space-between', - padding: theme.spacing(1, 2, 2), - [theme.breakpoints.up('md')]: { - display: 'none', - }, - }), - itemList: css({ - display: 'grid', - gridAutoRows: `minmax(${theme.spacing(6)}, auto)`, - minWidth: MENU_WIDTH, - }), - menuCollapseIcon: css({ - position: 'absolute', - top: '43px', - right: '0px', - transform: `translateX(50%)`, - }), -}); - -const getAnimStyles = (theme: GrafanaTheme2, animationDuration: number) => { - const commonTransition = { - transitionDuration: `${animationDuration}ms`, - transitionTimingFunction: theme.transitions.easing.easeInOut, - [theme.breakpoints.down('md')]: { - overflow: 'hidden', - }, - }; - - const overlayTransition = { - ...commonTransition, - transitionProperty: 'background-color, box-shadow, width', - // this is needed to prevent a horizontal scrollbar during the animation on firefox - '.scrollbar-view': { - overflow: 'hidden !important', - }, - }; - - const backdropTransition = { - ...commonTransition, - transitionProperty: 'opacity', - }; - - const overlayOpen = { - backgroundColor: theme.colors.background.canvas, - boxShadow: theme.shadows.z3, - width: '100%', - [theme.breakpoints.up('md')]: { - width: MENU_WIDTH, - }, - }; - - const overlayClosed = { - boxShadow: 'none', - width: 0, - [theme.breakpoints.up('md')]: { - backgroundColor: theme.colors.background.primary, - width: theme.spacing(7), - }, - }; - - const backdropOpen = { - opacity: 1, - }; - - const backdropClosed = { - opacity: 0, - }; - - return { - backdrop: { - appear: css(backdropClosed), - appearActive: css(backdropTransition, backdropOpen), - appearDone: css(backdropOpen), - exit: css(backdropOpen), - exitActive: css(backdropTransition, backdropClosed), - }, - overlay: { - appear: css(overlayClosed), - appearActive: css(overlayTransition, overlayOpen), - appearDone: css(overlayOpen), - exit: css(overlayOpen), - exitActive: css(overlayTransition, overlayClosed), - }, - }; -}; - -export function NavItem({ - link, - activeItem, - onClose, -}: { - link: NavModelItem; - activeItem?: NavModelItem; - onClose: () => void; -}) { - const styles = useStyles2(getNavItemStyles); - - if (linkHasChildren(link)) { - return ( - -
      - {link.children.map((childLink) => { - const icon = childLink.icon ? toIconName(childLink.icon) : undefined; - return ( - !childLink.divider && ( - { - childLink.onClick?.(); - onClose(); - }} - styleOverrides={styles.item} - target={childLink.target} - text={childLink.text} - url={childLink.url} - isMobile={true} - /> - ) - ); - })} -
    -
    - ); - } else if (link.emptyMessage) { - return ( - -
      -
      {link.emptyMessage}
      -
    -
    - ); - } else { - const FeatureHighlightWrapper = link.highlightText ? NavFeatureHighlight : React.Fragment; - return ( -
  • - { - link.onClick?.(); - onClose(); - }} - isActive={link === activeItem} - > -
    -
    - - - -
    - {link.text} -
    -
    -
  • - ); - } -} - -const getNavItemStyles = (theme: GrafanaTheme2) => ({ - children: css({ - display: 'flex', - flexDirection: 'column', - }), - item: css({ - padding: `${theme.spacing(1)} ${theme.spacing(1.5)}`, - width: `calc(100% - ${theme.spacing(3)})`, - '&::before': { - display: 'none', - }, - }), - flex: css({ - display: 'flex', - }), - itemWithoutMenu: css({ - position: 'relative', - placeItems: 'inherit', - justifyContent: 'start', - display: 'flex', - flexGrow: 1, - alignItems: 'center', - }), - fullWidth: css({ - height: '100%', - width: '100%', - }), - iconContainer: css({ - display: 'flex', - placeContent: 'center', - }), - itemWithoutMenuContent: css({ - display: 'grid', - gridAutoFlow: 'column', - gridTemplateColumns: `${theme.spacing(7)} auto`, - alignItems: 'center', - height: '100%', - }), - linkText: css({ - fontSize: theme.typography.pxToRem(14), - justifySelf: 'start', - padding: theme.spacing(0.5, 4.25, 0.5, 0.5), - }), - emptyMessage: css({ - color: theme.colors.text.secondary, - fontStyle: 'italic', - padding: theme.spacing(1, 1.5), - }), -}); - -function CollapsibleNavItem({ - link, - isActive, - children, - className, - onClose, -}: { - link: NavModelItem; - isActive?: boolean; - children: React.ReactNode; - className?: string; - onClose: () => void; -}) { - const styles = useStyles2(getCollapsibleStyles); - const [sectionExpanded, setSectionExpanded] = useLocalStorage(`grafana.navigation.expanded[${link.text}]`, false); - const FeatureHighlightWrapper = link.highlightText ? NavFeatureHighlight : React.Fragment; - - return ( -
  • - { - link.onClick?.(); - onClose(); - }} - className={styles.collapsibleMenuItem} - elClassName={styles.collapsibleIcon} - > - - - - -
    - setSectionExpanded(isOpen)} - className={styles.collapseWrapper} - contentClassName={styles.collapseContent} - label={ -
    - {link.text} -
    - } - > - {children} -
    -
    -
  • - ); -} - -const getCollapsibleStyles = (theme: GrafanaTheme2) => ({ - menuItem: css({ - position: 'relative', - display: 'grid', - gridAutoFlow: 'column', - gridTemplateColumns: `${theme.spacing(7)} minmax(calc(${MENU_WIDTH} - ${theme.spacing(7)}), auto)`, - }), - collapsibleMenuItem: css({ - height: theme.spacing(6), - width: theme.spacing(7), - display: 'grid', - }), - collapsibleIcon: css({ - display: 'grid', - placeContent: 'center', - }), - collapsibleSectionWrapper: css({ - display: 'flex', - flexGrow: 1, - alignSelf: 'start', - flexDirection: 'column', - }), - collapseWrapper: css({ - paddingLeft: theme.spacing(0.5), - paddingRight: theme.spacing(4.25), - minHeight: theme.spacing(6), - overflowWrap: 'anywhere', - alignItems: 'center', - color: theme.colors.text.secondary, - '&:hover, &:focus-within': { - backgroundColor: theme.colors.action.hover, - color: theme.colors.text.primary, - }, - '&:focus-within': { - boxShadow: 'none', - outline: `2px solid ${theme.colors.primary.main}`, - outlineOffset: '-2px', - transition: 'none', - }, - }), - collapseContent: css({ - padding: 0, - }), - labelWrapper: css({ - fontSize: '15px', - }), - primary: css({ - color: theme.colors.text.primary, - }), - linkText: css({ - fontSize: theme.typography.pxToRem(14), - justifySelf: 'start', - }), -}); - -function linkHasChildren(link: NavModelItem): link is NavModelItem & { children: NavModelItem[] } { - return Boolean(link.children && link.children.length > 0); -} diff --git a/public/app/core/components/NavBar/NavBarMenuItem.test.tsx b/public/app/core/components/NavBar/NavBarMenuItem.test.tsx deleted file mode 100644 index 42e308bc047..00000000000 --- a/public/app/core/components/NavBar/NavBarMenuItem.test.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import { render, screen } from '@testing-library/react'; -import React from 'react'; -import { BrowserRouter } from 'react-router-dom'; - -import { NavBarMenuItem } from './NavBarMenuItem'; - -describe('NavBarMenuItem', () => { - const mockText = 'MyChildItem'; - const mockUrl = '/route'; - const mockIcon = 'home-alt'; - - it('displays the text', () => { - render(); - const text = screen.getByText(mockText); - expect(text).toBeInTheDocument(); - }); - - it('attaches the url to the text if provided', () => { - render( - - - - ); - const link = screen.getByRole('link', { name: mockText }); - expect(link).toBeInTheDocument(); - expect(link).toHaveAttribute('href', mockUrl); - }); - - it('displays an icon if a valid icon is provided', () => { - render(); - const icon = screen.getByTestId('dropdown-child-icon'); - expect(icon).toBeInTheDocument(); - }); - - it('displays an external link icon if the target is _blank', () => { - render(); - const icon = screen.getByTestId('external-link-icon'); - expect(icon).toBeInTheDocument(); - }); - - it('displays a divider instead when isDivider is true', () => { - render(); - - // Check the divider is shown - const divider = screen.getByTestId('dropdown-child-divider'); - expect(divider).toBeInTheDocument(); - - // Check nothing else is rendered - const text = screen.queryByText(mockText); - const icon = screen.queryByTestId('dropdown-child-icon'); - const link = screen.queryByRole('link', { name: mockText }); - expect(text).not.toBeInTheDocument(); - expect(icon).not.toBeInTheDocument(); - expect(link).not.toBeInTheDocument(); - }); -}); diff --git a/public/app/core/components/NavBar/NavBarMenuItem.tsx b/public/app/core/components/NavBar/NavBarMenuItem.tsx deleted file mode 100644 index 173f1bb2f1a..00000000000 --- a/public/app/core/components/NavBar/NavBarMenuItem.tsx +++ /dev/null @@ -1,153 +0,0 @@ -import { css, cx } from '@emotion/css'; -import React from 'react'; - -import { GrafanaTheme2 } from '@grafana/data'; -import { Icon, IconName, Link, useTheme2 } from '@grafana/ui'; - -export interface Props { - icon?: IconName; - isActive?: boolean; - isDivider?: boolean; - onClick?: () => void; - styleOverrides?: string; - target?: HTMLAnchorElement['target']; - text: React.ReactNode; - url?: string; - adjustHeightForBorder?: boolean; - isMobile?: boolean; -} - -export function NavBarMenuItem({ - icon, - isActive, - isDivider, - onClick, - styleOverrides, - target, - text, - url, - isMobile = false, -}: Props) { - const theme = useTheme2(); - const styles = getStyles(theme, isActive); - const elStyle = cx(styles.element, styleOverrides); - - const linkContent = ( -
    - {icon && } - -
    {text}
    - - {target === '_blank' && ( - - )} -
    - ); - - let element = ( - - ); - - if (url) { - element = - !target && url.startsWith('/') ? ( - - {linkContent} - - ) : ( - - {linkContent} - - ); - } - - if (isMobile) { - return isDivider ? ( -
    - ) : ( -
  • {element}
  • - ); - } - - return isDivider ? ( -
    - ) : ( -
    {element}
    - ); -} - -NavBarMenuItem.displayName = 'NavBarMenuItem'; - -const getStyles = (theme: GrafanaTheme2, isActive: Props['isActive']) => ({ - linkContent: css({ - alignItems: 'center', - display: 'flex', - gap: '0.5rem', - width: '100%', - }), - linkText: css({ - textOverflow: 'ellipsis', - overflow: 'hidden', - whiteSpace: 'nowrap', - }), - externalLinkIcon: css({ - color: theme.colors.text.secondary, - gridColumnStart: 3, - }), - element: css({ - alignItems: 'center', - background: 'none', - border: 'none', - color: isActive ? theme.colors.text.primary : theme.colors.text.secondary, - display: 'flex', - flex: 1, - fontSize: 'inherit', - height: '100%', - overflowWrap: 'anywhere', - padding: theme.spacing(0.5, 2), - textAlign: 'left', - width: '100%', - '&:hover, &:focus-visible': { - backgroundColor: theme.colors.action.hover, - color: theme.colors.text.primary, - }, - '&:focus-visible': { - boxShadow: 'none', - outline: `2px solid ${theme.colors.primary.main}`, - outlineOffset: '-2px', - transition: 'none', - }, - '&::before': { - display: isActive ? 'block' : 'none', - content: '" "', - position: 'absolute', - left: 0, - top: 0, - bottom: 0, - width: theme.spacing(0.5), - borderRadius: theme.shape.borderRadius(1), - backgroundImage: theme.colors.gradients.brandVertical, - }, - }), - listItem: css({ - position: 'relative', - display: 'flex', - alignItems: 'center', - - '&:hover, &:focus-within': { - color: theme.colors.text.primary, - - '> *:first-child::after': { - backgroundColor: theme.colors.action.hover, - }, - }, - }), - divider: css({ - borderBottom: `1px solid ${theme.colors.border.weak}`, - height: '1px', - margin: `${theme.spacing(1)} 0`, - overflow: 'hidden', - }), -}); diff --git a/public/app/core/components/NavBar/NavBarMenuPortalContainer.tsx b/public/app/core/components/NavBar/NavBarMenuPortalContainer.tsx deleted file mode 100644 index 5d51ff0af89..00000000000 --- a/public/app/core/components/NavBar/NavBarMenuPortalContainer.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { css } from '@emotion/css'; -import React from 'react'; - -import { GrafanaTheme2 } from '@grafana/data'; -import { useTheme2 } from '@grafana/ui'; - -const NAV_MENU_PORTAL_CONTAINER_ID = 'navbar-menu-portal-container'; - -export const getNavMenuPortalContainer = () => document.getElementById(NAV_MENU_PORTAL_CONTAINER_ID) ?? document.body; - -export const NavBarMenuPortalContainer = () => { - const theme = useTheme2(); - const styles = getStyles(theme); - return
    ; -}; - -NavBarMenuPortalContainer.displayName = 'NavBarMenuPortalContainer'; - -const getStyles = (theme: GrafanaTheme2) => ({ - menuPortalContainer: css({ - left: 0, - position: 'fixed', - right: 0, - top: 0, - zIndex: theme.zIndex.sidemenu, - }), -}); diff --git a/public/app/core/components/NavBar/NavBarToggle.tsx b/public/app/core/components/NavBar/NavBarToggle.tsx deleted file mode 100644 index c913464e4b4..00000000000 --- a/public/app/core/components/NavBar/NavBarToggle.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { css } from '@emotion/css'; -import classnames from 'classnames'; -import React from 'react'; - -import { GrafanaTheme2 } from '@grafana/data'; -import { IconButton, useTheme2 } from '@grafana/ui'; - -export interface Props { - className?: string; - isExpanded: boolean; - onClick: () => void; -} - -export const NavBarToggle = ({ className, isExpanded, onClick }: Props) => { - const theme = useTheme2(); - const styles = getStyles(theme); - - return ( - - ); -}; - -NavBarToggle.displayName = 'NavBarToggle'; - -const getStyles = (theme: GrafanaTheme2) => ({ - icon: css({ - backgroundColor: theme.colors.background.secondary, - border: `1px solid ${theme.colors.border.weak}`, - borderRadius: '50%', - marginRight: 0, - zIndex: theme.zIndex.sidemenu + 1, - - [theme.breakpoints.down('md')]: { - display: 'none', - }, - }), -}); diff --git a/public/app/core/components/NavBar/context.tsx b/public/app/core/components/NavBar/context.tsx deleted file mode 100644 index e7baf632b4c..00000000000 --- a/public/app/core/components/NavBar/context.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { createContext, HTMLAttributes, useContext } from 'react'; - -export interface NavBarItemMenuContextProps { - menuHasFocus: boolean; - onClose: () => void; - onLeft: () => void; - menuProps?: HTMLAttributes; -} - -export const NavBarItemMenuContext = createContext({ - menuHasFocus: false, - onClose: () => undefined, - onLeft: () => undefined, -}); - -export function useNavBarItemMenuContext(): NavBarItemMenuContextProps { - return useContext(NavBarItemMenuContext); -} - -export interface NavBarContextProps { - menuIdOpen: string | undefined; - setMenuIdOpen: (id: string | undefined) => void; -} - -export const NavBarContext = createContext({ - menuIdOpen: undefined, - setMenuIdOpen: () => undefined, -}); - -export function useNavBarContext(): NavBarContextProps { - return useContext(NavBarContext); -} diff --git a/public/app/core/components/AppChrome/NavLandingPage.test.tsx b/public/app/core/components/NavLandingPage/NavLandingPage.test.tsx similarity index 100% rename from public/app/core/components/AppChrome/NavLandingPage.test.tsx rename to public/app/core/components/NavLandingPage/NavLandingPage.test.tsx diff --git a/public/app/core/components/AppChrome/NavLandingPage.tsx b/public/app/core/components/NavLandingPage/NavLandingPage.tsx similarity index 100% rename from public/app/core/components/AppChrome/NavLandingPage.tsx rename to public/app/core/components/NavLandingPage/NavLandingPage.tsx diff --git a/public/app/core/components/AppChrome/NavLandingPageCard.test.tsx b/public/app/core/components/NavLandingPage/NavLandingPageCard.test.tsx similarity index 100% rename from public/app/core/components/AppChrome/NavLandingPageCard.test.tsx rename to public/app/core/components/NavLandingPage/NavLandingPageCard.test.tsx diff --git a/public/app/core/components/AppChrome/NavLandingPageCard.tsx b/public/app/core/components/NavLandingPage/NavLandingPageCard.tsx similarity index 100% rename from public/app/core/components/AppChrome/NavLandingPageCard.tsx rename to public/app/core/components/NavLandingPage/NavLandingPageCard.tsx diff --git a/public/app/core/components/Page/OldNavOnly.tsx b/public/app/core/components/Page/OldNavOnly.tsx deleted file mode 100644 index 62a97548086..00000000000 --- a/public/app/core/components/Page/OldNavOnly.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import React from 'react'; - -interface Props { - children: React.ReactNode; -} - -/** Remove after topnav feature toggle is removed */ -export function OldNavOnly({ children }: Props): React.ReactElement | null { - return <>{children}; -} diff --git a/public/app/core/components/Page/Page.tsx b/public/app/core/components/Page/Page.tsx index 65f96065503..4931ade57c4 100644 --- a/public/app/core/components/Page/Page.tsx +++ b/public/app/core/components/Page/Page.tsx @@ -11,7 +11,6 @@ import { Footer } from '../Footer/Footer'; import { PageHeader } from '../PageHeader/PageHeader'; import { Page as NewPage } from '../PageNew/Page'; -import { OldNavOnly } from './OldNavOnly'; import { PageContents } from './PageContents'; import { PageType } from './types'; import { usePageNav } from './usePageNav'; @@ -93,7 +92,6 @@ export const OldPage: PageType = ({ }; OldPage.Contents = PageContents; -OldPage.OldNavOnly = OldNavOnly; export const Page: PageType = config.featureToggles.topnav ? NewPage : OldPage; diff --git a/public/app/core/components/Page/types.ts b/public/app/core/components/Page/types.ts index 1cbf15c54d4..64b8ba7ba0d 100644 --- a/public/app/core/components/Page/types.ts +++ b/public/app/core/components/Page/types.ts @@ -2,7 +2,6 @@ import React, { FC, HTMLAttributes, RefCallback } from 'react'; import { NavModel, NavModelItem, PageLayoutType } from '@grafana/data'; -import { OldNavOnly } from './OldNavOnly'; import { PageContents } from './PageContents'; export interface PageProps extends HTMLAttributes { @@ -34,6 +33,5 @@ export interface PageInfoItem { } export interface PageType extends FC { - OldNavOnly: typeof OldNavOnly; Contents: typeof PageContents; } diff --git a/public/app/core/components/PageNew/Page.tsx b/public/app/core/components/PageNew/Page.tsx index 73a09f82603..85993de8d13 100644 --- a/public/app/core/components/PageNew/Page.tsx +++ b/public/app/core/components/PageNew/Page.tsx @@ -92,10 +92,6 @@ export const Page: PageType = ({ Page.Contents = PageContents; -Page.OldNavOnly = function OldNavOnly() { - return null; -}; - const getStyles = (theme: GrafanaTheme2) => { return { wrapper: css({ diff --git a/public/app/core/components/help/HelpModal.tsx b/public/app/core/components/help/HelpModal.tsx index fa7fdf37530..c05a3cfa736 100644 --- a/public/app/core/components/help/HelpModal.tsx +++ b/public/app/core/components/help/HelpModal.tsx @@ -3,7 +3,6 @@ import React, { useMemo } from 'react'; import { GrafanaTheme2 } from '@grafana/data'; import { Modal, useStyles2 } from '@grafana/ui'; -import { config } from 'app/core/config'; import { getModKey } from 'app/core/utils/browser'; const getShortcuts = (modKey: string) => { @@ -12,14 +11,7 @@ const getShortcuts = (modKey: string) => { { keys: ['g', 'h'], description: 'Go to Home Dashboard' }, { keys: ['g', 'e'], description: 'Go to Explore' }, { keys: ['g', 'p'], description: 'Go to Profile' }, - - ...(config.featureToggles.topnav - ? [{ keys: [`${modKey} + k`], description: 'Open search' }] - : [ - { keys: ['s', 'o'], description: 'Open search' }, - { keys: [`${modKey} + k`], description: 'Open command palette' }, - ]), - + { keys: [`${modKey} + k`], description: 'Open search' }, { keys: ['esc'], description: 'Exit edit/setting views' }, { keys: ['h'], description: 'Show all keyboard shortcuts' }, { keys: ['c', 't'], description: 'Change theme' }, diff --git a/public/app/core/reducers/navBarTree.ts b/public/app/core/reducers/navBarTree.ts index d5fba3cd666..57c9922c508 100644 --- a/public/app/core/reducers/navBarTree.ts +++ b/public/app/core/reducers/navBarTree.ts @@ -3,7 +3,7 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; import { NavModelItem } from '@grafana/data'; import { config } from '@grafana/runtime'; -import { getNavSubTitle, getNavTitle } from '../components/NavBar/navBarItem-translations'; +import { getNavSubTitle, getNavTitle } from '../components/AppChrome/MegaMenu/navBarItem-translations'; export const initialState: NavModelItem[] = config.bootData?.navTree ?? []; diff --git a/public/app/core/reducers/navModel.ts b/public/app/core/reducers/navModel.ts index 18f0699d461..e289681699c 100644 --- a/public/app/core/reducers/navModel.ts +++ b/public/app/core/reducers/navModel.ts @@ -4,7 +4,7 @@ import { cloneDeep } from 'lodash'; import { NavIndex, NavModel, NavModelItem } from '@grafana/data'; import config from 'app/core/config'; -import { getNavSubTitle, getNavTitle } from '../components/NavBar/navBarItem-translations'; +import { getNavSubTitle, getNavTitle } from '../components/AppChrome/MegaMenu/navBarItem-translations'; export const HOME_NAV_ID = 'home'; diff --git a/public/app/core/services/keybindingSrv.ts b/public/app/core/services/keybindingSrv.ts index b88e4126c0f..0cd96bbd712 100644 --- a/public/app/core/services/keybindingSrv.ts +++ b/public/app/core/services/keybindingSrv.ts @@ -3,7 +3,7 @@ import Mousetrap from 'mousetrap'; import 'mousetrap-global-bind'; import 'mousetrap/plugins/global-bind/mousetrap-global-bind'; import { LegacyGraphHoverClearEvent, locationUtil } from '@grafana/data'; -import { config, LocationService } from '@grafana/runtime'; +import { LocationService } from '@grafana/runtime'; import appEvents from 'app/core/app_events'; import { getExploreUrl } from 'app/core/utils/explore'; import { SaveDashboardDrawer } from 'app/features/dashboard/components/SaveDashboard/SaveDashboardDrawer'; @@ -41,10 +41,6 @@ export class KeybindingSrv { this.bind('g a', this.openAlerting); this.bind('g p', this.goToProfile); this.bind('g e', this.goToExplore); - if (!config.featureToggles.topnav) { - this.bind('s o', this.openSearch); - this.bind('f', this.openSearch); - } this.bind('t a', this.makeAbsoluteTime); this.bind('esc', this.exit); this.bindGlobalEsc(); @@ -52,10 +48,6 @@ export class KeybindingSrv { this.bind('c t', () => toggleTheme(false)); this.bind('c r', () => toggleTheme(true)); - - if (process.env.NODE_ENV === 'development') { - this.bind('t n', () => this.toggleNav()); - } } bindGlobalEsc() { @@ -88,18 +80,6 @@ export class KeybindingSrv { this.exit(); } - toggleNav() { - window.location.href = - config.appSubUrl + - locationUtil.getUrlForPartial(this.locationService.getLocation(), { - '__feature.topnav': (!config.featureToggles.topnav).toString(), - }); - } - - private openSearch() { - this.locationService.partial({ search: 'open' }); - } - private closeSearch() { this.locationService.partial({ search: null }); } diff --git a/public/app/features/alerting/routes.tsx b/public/app/features/alerting/routes.tsx index 8924059bbd5..ea1a6f90859 100644 --- a/public/app/features/alerting/routes.tsx +++ b/public/app/features/alerting/routes.tsx @@ -1,10 +1,9 @@ import { uniq } from 'lodash'; import React from 'react'; -import { Redirect } from 'react-router-dom'; import { OrgRole } from '@grafana/data'; -import { NavLandingPage } from 'app/core/components/AppChrome/NavLandingPage'; import { SafeDynamicImport } from 'app/core/components/DynamicImports/SafeDynamicImport'; +import { NavLandingPage } from 'app/core/components/NavLandingPage/NavLandingPage'; import { config } from 'app/core/config'; import { RouteDescriptor } from 'app/core/navigation/types'; import { AccessControlAction } from 'app/types'; @@ -17,8 +16,7 @@ const legacyRoutes: RouteDescriptor[] = [ ...commonRoutes, { path: '/alerting', - component: () => - config.featureToggles.topnav ? : , + component: () => , }, { path: '/alerting/list', @@ -90,19 +88,12 @@ const legacyRoutes: RouteDescriptor[] = [ const unifiedRoutes: RouteDescriptor[] = [ ...commonRoutes, - config.featureToggles.topnav - ? { - path: '/alerting', - component: SafeDynamicImport( - () => import(/* webpackChunkName: "AlertingHome" */ 'app/features/alerting/unified/Home') - ), - } - : { - path: '/alerting/home', - component: SafeDynamicImport( - () => import(/* webpackChunkName: "AlertingHome" */ 'app/features/alerting/unified/Home') - ), - }, + { + path: '/alerting', + component: SafeDynamicImport( + () => import(/* webpackChunkName: "AlertingHome" */ 'app/features/alerting/unified/Home') + ), + }, { path: '/alerting/list', roles: evaluateAccess( diff --git a/public/app/features/alerting/unified/Home.tsx b/public/app/features/alerting/unified/Home.tsx index 700155f65ac..96f0fec3c0e 100644 --- a/public/app/features/alerting/unified/Home.tsx +++ b/public/app/features/alerting/unified/Home.tsx @@ -4,7 +4,6 @@ import SVG from 'react-inlinesvg'; import { GrafanaTheme2 } from '@grafana/data'; import { Stack } from '@grafana/experimental'; -import { config } from '@grafana/runtime'; import { Icon, useStyles2, useTheme2 } from '@grafana/ui'; import { AlertingPageWrapper } from './components/AlertingPageWrapper'; @@ -14,7 +13,7 @@ export default function Home() { const styles = useStyles2(getWelcomePageStyles); return ( - +
    diff --git a/public/app/features/connections/Connections.tsx b/public/app/features/connections/Connections.tsx index e4346e3eea0..1a66576c825 100644 --- a/public/app/features/connections/Connections.tsx +++ b/public/app/features/connections/Connections.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import { Redirect, Route, Switch } from 'react-router-dom'; -import { NavLandingPage } from 'app/core/components/AppChrome/NavLandingPage'; +import { NavLandingPage } from 'app/core/components/NavLandingPage/NavLandingPage'; import { DataSourcesRoutesContext } from 'app/features/datasources/state'; import { StoreState, useSelector } from 'app/types'; diff --git a/public/app/features/connections/pages/DataSourcesListPage.tsx b/public/app/features/connections/pages/DataSourcesListPage.tsx index 47508f26fb6..394fc16ab2a 100644 --- a/public/app/features/connections/pages/DataSourcesListPage.tsx +++ b/public/app/features/connections/pages/DataSourcesListPage.tsx @@ -1,6 +1,5 @@ import * as React from 'react'; -import { config } from '@grafana/runtime'; import { Page } from 'app/core/components/Page/Page'; import { DataSourceAddButton } from 'app/features/datasources/components/DataSourceAddButton'; import { DataSourcesList } from 'app/features/datasources/components/DataSourcesList'; @@ -10,7 +9,7 @@ import { StoreState, useSelector } from 'app/types'; export function DataSourcesListPage() { const dataSourcesCount = useSelector(({ dataSources }: StoreState) => getDataSourcesCount(dataSources)); - const actions = config.featureToggles.topnav && dataSourcesCount > 0 ? : undefined; + const actions = dataSourcesCount > 0 ? : undefined; return ( diff --git a/public/app/features/correlations/CorrelationsPage.test.tsx b/public/app/features/correlations/CorrelationsPage.test.tsx index 4de653007fc..abfdc10a78c 100644 --- a/public/app/features/correlations/CorrelationsPage.test.tsx +++ b/public/app/features/correlations/CorrelationsPage.test.tsx @@ -178,13 +178,6 @@ jest.mock('@grafana/runtime', () => { return { ...runtime, - config: { - ...runtime.config, - featureToggles: { - ...runtime.config.featureToggles, - topnav: true, - }, - }, reportInteraction: (...args: Parameters) => { mocks.reportInteraction(...args); }, diff --git a/public/app/features/correlations/CorrelationsPage.tsx b/public/app/features/correlations/CorrelationsPage.tsx index f04d6d9e1dc..db075c534ea 100644 --- a/public/app/features/correlations/CorrelationsPage.tsx +++ b/public/app/features/correlations/CorrelationsPage.tsx @@ -8,7 +8,6 @@ import { Badge, Button, DeleteButton, - HorizontalGroup, LoadingPlaceholder, useStyles2, Alert, @@ -158,15 +157,6 @@ export default function CorrelationsPage() { actions={addButton} > -
    - - -

    Define how data living in different data sources relates to each other.

    -
    - {addButton} -
    -
    -
    {!data && get.loading && (
    diff --git a/public/app/features/dashboard/components/DashNav/DashNav.test.tsx b/public/app/features/dashboard/components/DashNav/DashNav.test.tsx deleted file mode 100644 index c9ee0d49456..00000000000 --- a/public/app/features/dashboard/components/DashNav/DashNav.test.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { act, render, screen, waitFor } from '@testing-library/react'; -import React from 'react'; -import { Provider } from 'react-redux'; -import { Router } from 'react-router-dom'; - -import { locationService } from '@grafana/runtime/src'; -import { GrafanaContext } from 'app/core/context/GrafanaContext'; - -import { getGrafanaContextMock } from '../../../../../test/mocks/getGrafanaContextMock'; -import { setStarred } from '../../../../core/reducers/navBarTree'; -import { configureStore } from '../../../../store/configureStore'; -import { updateTimeZoneForSession } from '../../../profile/state/reducers'; -import { createDashboardModelFixture } from '../../state/__fixtures__/dashboardFixtures'; - -import { DashNav } from './DashNav'; - -describe('Public dashboard title tag', () => { - it('will be rendered when publicDashboardEnabled set to true in dashboard meta', async () => { - let dashboard = createDashboardModelFixture({}, { publicDashboardEnabled: false }); - - const store = configureStore(); - const context = getGrafanaContextMock(); - const props = { - setStarred: jest.fn() as unknown as typeof setStarred, - updateTimeZoneForSession: jest.fn() as unknown as typeof updateTimeZoneForSession, - }; - - render( - - - - {}} - title="test" - /> - - - - ); - - const publicTag = screen.queryByText('Public'); - expect(publicTag).not.toBeInTheDocument(); - - act(() => { - dashboard.updateMeta({ publicDashboardEnabled: true }); - }); - - await waitFor(() => screen.getByText('Public')); - }); -}); diff --git a/public/app/features/dashboard/components/DashNav/DashNav.tsx b/public/app/features/dashboard/components/DashNav/DashNav.tsx index d4bc3c21672..3da75e21590 100644 --- a/public/app/features/dashboard/components/DashNav/DashNav.tsx +++ b/public/app/features/dashboard/components/DashNav/DashNav.tsx @@ -3,14 +3,13 @@ import React, { FC, ReactNode, useContext, useEffect } from 'react'; import { connect, ConnectedProps } from 'react-redux'; import { useLocation } from 'react-router-dom'; -import { locationUtil, textUtil } from '@grafana/data'; +import { textUtil } from '@grafana/data'; import { selectors as e2eSelectors } from '@grafana/e2e-selectors/src'; import { locationService } from '@grafana/runtime'; import { ButtonGroup, ModalsController, ToolbarButton, - PageToolbar, useForceUpdate, Tag, ToolbarButtonRow, @@ -18,9 +17,8 @@ import { ConfirmModal, } from '@grafana/ui'; import { AppChromeUpdate } from 'app/core/components/AppChrome/AppChromeUpdate'; -import { NavToolbarSeparator } from 'app/core/components/AppChrome/NavToolbarSeparator'; +import { NavToolbarSeparator } from 'app/core/components/AppChrome/NavToolbar/NavToolbarSeparator'; import config from 'app/core/config'; -import { useGrafana } from 'app/core/context/GrafanaContext'; import { useAppNotification } from 'app/core/copy/appNotification'; import { appEvents } from 'app/core/core'; import { useBusEvent } from 'app/core/hooks/useBusEvent'; @@ -79,8 +77,9 @@ export function addCustomRightAction(content: DashNavButtonModel) { type Props = OwnProps & ConnectedProps; export const DashNav = React.memo((props) => { + // this ensures the component rerenders when the location changes + useLocation(); const forceUpdate = useForceUpdate(); - const { chrome } = useGrafana(); const { showModal, hideModal } = useContext(ModalsContext); // We don't really care about the event payload here only that it triggeres a re-render of this component @@ -136,14 +135,6 @@ export const DashNav = React.memo((props) => { }); }; - const onClose = () => { - locationService.partial({ viewPanel: null }); - }; - - const onToggleTVMode = () => { - chrome.onToggleKioskMode(); - }; - const onOpenSettings = () => { locationService.partial({ editview: 'settings' }); }; @@ -287,21 +278,13 @@ export const DashNav = React.memo((props) => { const { snapshot } = dashboard; const snapshotUrl = snapshot && snapshot.originalUrl; const buttons: ReactNode[] = []; - const tvButton = config.featureToggles.topnav ? null : ( - - ); if (isPlaylistRunning()) { return [renderPlaylistControls(), renderTimeControls()]; } if (kioskMode === KioskMode.TV) { - return [renderTimeControls(), tvButton]; + return [renderTimeControls()]; } if (canEdit && !isFullscreen) { @@ -364,7 +347,6 @@ export const DashNav = React.memo((props) => { addCustomContent(customRightActions, buttons); buttons.push(renderTimeControls()); - buttons.push(tvButton); if (config.featureToggles.scenes) { buttons.push( @@ -379,39 +361,16 @@ export const DashNav = React.memo((props) => { return buttons; }; - const { isFullscreen, title, folderTitle } = props; - // this ensures the component rerenders when the location changes - const location = useLocation(); - const titleHref = locationUtil.getUrlForPartial(location, { search: 'open' }); - const parentHref = locationUtil.getUrlForPartial(location, { search: 'open', query: 'folder:current' }); - const onGoBack = isFullscreen ? onClose : undefined; - - if (config.featureToggles.topnav) { - return ( - - {renderLeftActions()} - - {renderRightActions()} - - } - /> - ); - } - return ( - - {renderRightActions()} - + + {renderLeftActions()} + + {renderRightActions()} + + } + /> ); }); diff --git a/public/app/features/dashboard/components/DashboardSettings/DashboardSettings.tsx b/public/app/features/dashboard/components/DashboardSettings/DashboardSettings.tsx index f69223b37c9..8ba6072b32c 100644 --- a/public/app/features/dashboard/components/DashboardSettings/DashboardSettings.tsx +++ b/public/app/features/dashboard/components/DashboardSettings/DashboardSettings.tsx @@ -5,7 +5,7 @@ import { useLocation } from 'react-router-dom'; import { locationUtil, NavModel, NavModelItem } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; import { locationService } from '@grafana/runtime'; -import { Button, PageToolbar, ToolbarButtonRow } from '@grafana/ui'; +import { Button, ToolbarButtonRow } from '@grafana/ui'; import { AppChromeUpdate } from 'app/core/components/AppChrome/AppChromeUpdate'; import { Page } from 'app/core/components/PageNew/Page'; import config from 'app/core/config'; @@ -49,28 +49,25 @@ export function DashboardSettings({ dashboard, editview, pageNav, sectionNav }: dashboard.meta.hasUnsavedFolderChange = false; }; - const folderTitle = dashboard.meta.folderTitle; const currentPage = pages.find((page) => page.id === editview) ?? pages[0]; const canSaveAs = contextSrv.hasEditPermissionInFolders; const canSave = dashboard.meta.canSave; const location = useLocation(); const editIndex = getEditIndex(location); const subSectionNav = getSectionNav(pageNav, sectionNav, pages, currentPage, location); - const size = config.featureToggles.topnav ? 'sm' : 'md'; + const size = 'sm'; const actions = [ - config.featureToggles.topnav && ( - - ), + , canSaveAs && ( - {!config.featureToggles.topnav ? ( - - {actions} - - ) : ( - {actions}} /> - )} + {actions}} /> ); diff --git a/public/app/features/dashboard/components/PanelEditor/PanelEditor.tsx b/public/app/features/dashboard/components/PanelEditor/PanelEditor.tsx index ee23bce4524..de45e2a6f12 100644 --- a/public/app/features/dashboard/components/PanelEditor/PanelEditor.tsx +++ b/public/app/features/dashboard/components/PanelEditor/PanelEditor.tsx @@ -7,13 +7,12 @@ import { Subscription } from 'rxjs'; import { FieldConfigSource, GrafanaTheme2, NavModel, NavModelItem, PageLayoutType } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; import { Stack } from '@grafana/experimental'; -import { config, locationService } from '@grafana/runtime'; +import { locationService } from '@grafana/runtime'; import { Button, HorizontalGroup, InlineSwitch, ModalsController, - PageToolbar, RadioButtonGroup, stylesFactory, Themeable2, @@ -322,7 +321,7 @@ export class PanelEditorUnconnected extends PureComponent { } renderEditorActions() { - const size = config.featureToggles.topnav ? 'sm' : 'md'; + const size = 'sm'; let editorActions = [