diff --git a/.betterer.results b/.betterer.results index d5ce121e6fb84..63f534ff5a4f1 100644 --- a/.betterer.results +++ b/.betterer.results @@ -3105,7 +3105,7 @@ exports[`better eslint`] = { [35, 46, 3, "Unexpected any. Specify a different type.", "193409811"], [37, 26, 3, "Unexpected any. Specify a different type.", "193409811"] ], - "public/app/angular/services/nav_model_srv.ts:1949768361": [ + "public/app/angular/services/nav_model_srv.ts:356083867": [ [57, 39, 3, "Unexpected any. Specify a different type.", "193409811"] ], "public/app/angular/services/ng_react.ts:1219930949": [ @@ -3331,15 +3331,21 @@ exports[`better eslint`] = { "public/app/core/components/OptionsUI/strings.tsx:1826340875": [ [24, 16, 42, "Do not use any type assertions.", "3301854123"] ], - "public/app/core/components/PageHeader/PageHeader.test.tsx:3755271251": [ - [19, 32, 10, "Do not use any type assertions.", "883420344"], - [19, 39, 3, "Unexpected any. Specify a different type.", "193409811"], - [39, 32, 10, "Do not use any type assertions.", "883420344"], - [39, 39, 3, "Unexpected any. Specify a different type.", "193409811"] + "public/app/core/components/PageHeader/PageHeader.test.tsx:4036541205": [ + [16, 34, 10, "Do not use any type assertions.", "883420344"], + [16, 41, 3, "Unexpected any. Specify a different type.", "193409811"], + [33, 34, 10, "Do not use any type assertions.", "883420344"], + [33, 41, 3, "Unexpected any. Specify a different type.", "193409811"] ], - "public/app/core/components/PageHeader/PageHeader.tsx:952684291": [ + "public/app/core/components/PageHeader/PageHeader.tsx:503869166": [ [65, 22, 22, "Do not use any type assertions.", "4054086100"], - [104, 34, 21, "Do not use any type assertions.", "2401004021"] + [102, 34, 21, "Do not use any type assertions.", "2401004021"] + ], + "public/app/core/components/PageNew/PageTabs.tsx:3261576880": [ + [23, 22, 22, "Do not use any type assertions.", "4054086100"] + ], + "public/app/core/components/PageNew/SectionNav.tsx:3562461661": [ + [20, 34, 21, "Do not use any type assertions.", "2401004021"] ], "public/app/core/components/PanelTypeFilter/PanelTypeFilter.tsx:1614517181": [ [41, 24, 3, "Unexpected any. Specify a different type.", "193409811"], @@ -3549,7 +3555,7 @@ exports[`better eslint`] = { "public/app/core/reducers/appNotification.ts:735507530": [ [77, 35, 3, "Unexpected any. Specify a different type.", "193409811"] ], - "public/app/core/reducers/navModel.ts:2486277874": [ + "public/app/core/reducers/navModel.ts:3755863968": [ [7, 20, 41, "Do not use any type assertions.", "680953089"] ], "public/app/core/reducers/processsAclItems.ts:2776156466": [ @@ -3959,23 +3965,23 @@ exports[`better eslint`] = { [26, 5, 29, "Do not use any type assertions.", "2635161824"], [67, 9, 60, "Do not use any type assertions.", "738572730"] ], - "public/app/features/admin/AdminListOrgsPage.tsx:1232501301": [ + "public/app/features/admin/AdminListOrgsPage.tsx:851621779": [ [22, 32, 3, "Unexpected any. Specify a different type.", "193409811"] ], - "public/app/features/admin/AdminSettings.tsx:876474391": [ + "public/app/features/admin/AdminSettings.tsx:3612709745": [ [18, 10, 63, "Do not use any type assertions.", "1950730127"] ], "public/app/features/admin/OrgRolePicker.tsx:2826049686": [ [23, 34, 20, "Do not use any type assertions.", "3568020359"] ], - "public/app/features/admin/UserListAdminPage.tsx:3454473979": [ + "public/app/features/admin/UserListAdminPage.tsx:1874540957": [ [27, 21, 3, "Unexpected any. Specify a different type.", "193409811"] ], "public/app/features/admin/UserProfile.tsx:2121737967": [ [205, 24, 3, "Unexpected any. Specify a different type.", "193409811"], [302, 10, 3, "Unexpected any. Specify a different type.", "193409811"] ], - "public/app/features/admin/ldap/LdapPage.tsx:1724804732": [ + "public/app/features/admin/ldap/LdapPage.tsx:2036572570": [ [73, 19, 3, "Unexpected any. Specify a different type.", "193409811"] ], "public/app/features/admin/state/reducers.test.ts:3463255684": [ @@ -3994,7 +4000,7 @@ exports[`better eslint`] = { [57, 34, 39, "Do not use any type assertions.", "935682756"], [57, 70, 3, "Unexpected any. Specify a different type.", "193409811"] ], - "public/app/features/alerting/AlertRuleList.tsx:474918126": [ + "public/app/features/alerting/AlertRuleList.tsx:3637063496": [ [134, 24, 17, "Do not use any type assertions.", "2518695225"], [137, 58, 17, "Do not use any type assertions.", "2518695225"] ], @@ -4044,7 +4050,7 @@ exports[`better eslint`] = { [402, 51, 3, "Unexpected any. Specify a different type.", "193409811"], [468, 34, 3, "Unexpected any. Specify a different type.", "193409811"] ], - "public/app/features/alerting/EditNotificationChannelPage.tsx:3397946803": [ + "public/app/features/alerting/EditNotificationChannelPage.tsx:3335986965": [ [21, 23, 3, "Unexpected any. Specify a different type.", "193409811"] ], "public/app/features/alerting/StateHistory.tsx:3418297987": [ @@ -4578,7 +4584,7 @@ exports[`better eslint`] = { "public/app/features/canvas/runtime/root.tsx:2845174121": [ [24, 19, 36, "Do not use any type assertions.", "1413431594"] ], - "public/app/features/canvas/runtime/scene.tsx:4285479205": [ + "public/app/features/canvas/runtime/scene.tsx:4221384430": [ [155, 61, 25, "Do not use any type assertions.", "588553285"], [160, 42, 25, "Do not use any type assertions.", "588553285"] ], @@ -4645,7 +4651,7 @@ exports[`better eslint`] = { [198, 29, 13, "Do not use any type assertions.", "2146830713"], [217, 32, 3, "Unexpected any. Specify a different type.", "193409811"] ], - "public/app/features/dashboard/components/DashNav/DashNav.tsx:1533394562": [ + "public/app/features/dashboard/components/DashNav/DashNav.tsx:2724792053": [ [67, 87, 3, "Unexpected any. Specify a different type.", "193409811"] ], "public/app/features/dashboard/components/DashNav/DashNavTimeControls.test.tsx:3825334541": [ @@ -5014,7 +5020,7 @@ exports[`better eslint`] = { [264, 15, 56, "Do not use any type assertions.", "2674630592"], [266, 13, 3, "Unexpected any. Specify a different type.", "193409811"] ], - "public/app/features/dashboard/containers/DashboardPage.tsx:3173260528": [ + "public/app/features/dashboard/containers/DashboardPage.tsx:3836168909": [ [108, 36, 40, "Do not use any type assertions.", "2547843745"], [108, 73, 3, "Unexpected any. Specify a different type.", "193409811"], [144, 32, 40, "Do not use any type assertions.", "2547843745"], @@ -5438,14 +5444,14 @@ exports[`better eslint`] = { [15, 16, 24, "Do not use any type assertions.", "3405802617"], [21, 11, 21, "Do not use any type assertions.", "3218407483"] ], - "public/app/features/datasources/DataSourceDashboards.tsx:3932611556": [ + "public/app/features/datasources/DataSourceDashboards.tsx:2085374850": [ [49, 16, 3, "Unexpected any. Specify a different type.", "193409811"] ], "public/app/features/datasources/DataSourcesListPage.test.tsx:368182366": [ [19, 17, 26, "Do not use any type assertions.", "3994646623"], [22, 14, 126, "Do not use any type assertions.", "1409469672"] ], - "public/app/features/datasources/DataSourcesListPage.tsx:247845505": [ + "public/app/features/datasources/DataSourcesListPage.tsx:2606271463": [ [44, 14, 22, "Do not use any type assertions.", "2167463294"] ], "public/app/features/datasources/__mocks__/dataSourcesMocks.ts:1217473343": [ @@ -6154,7 +6160,7 @@ exports[`better eslint`] = { "public/app/features/live/pages/AddNewRule.tsx:2593231672": [ [59, 16, 3, "Unexpected any. Specify a different type.", "193409811"] ], - "public/app/features/live/pages/PipelineAdminPage.tsx:952820098": [ + "public/app/features/live/pages/PipelineAdminPage.tsx:87558436": [ [13, 51, 3, "Unexpected any. Specify a different type.", "193409811"] ], "public/app/features/live/pages/PipelineTable.tsx:894975337": [ @@ -6181,7 +6187,7 @@ exports[`better eslint`] = { "public/app/features/live/pages/utils.ts:270086979": [ [19, 37, 3, "Unexpected any. Specify a different type.", "193409811"] ], - "public/app/features/manage-dashboards/DashboardImportPage.tsx:1780804260": [ + "public/app/features/manage-dashboards/DashboardImportPage.tsx:1590008002": [ [81, 19, 3, "Unexpected any. Specify a different type.", "193409811"], [82, 25, 3, "Unexpected any. Specify a different type.", "193409811"] ], @@ -6482,7 +6488,7 @@ exports[`better eslint`] = { [68, 90, 12, "Do not use any type assertions.", "3603862904"], [68, 99, 3, "Unexpected any. Specify a different type.", "193409811"] ], - "public/app/features/plugins/components/AppRootPage.tsx:1453161637": [ + "public/app/features/plugins/components/AppRootPage.tsx:3831200387": [ [109, 21, 34, "Do not use any type assertions.", "462520373"] ], "public/app/features/plugins/datasource_srv.ts:3002426838": [ @@ -6763,7 +6769,7 @@ exports[`better eslint`] = { [12, 13, 3, "Unexpected any. Specify a different type.", "193409811"], [41, 9, 43, "Do not use any type assertions.", "4109129169"] ], - "public/app/features/sandbox/TestStuffPage.tsx:3698683147": [ + "public/app/features/sandbox/TestStuffPage.tsx:1454935587": [ [134, 30, 29, "Do not use any type assertions.", "3195381622"] ], "public/app/features/search/components/DashboardSearch.test.tsx:3245889886": [ @@ -6921,22 +6927,24 @@ exports[`better eslint`] = { [246, 28, 17, "Do not use any type assertions.", "1811834489"], [251, 5, 29, "Do not use any type assertions.", "4135357902"] ], - "public/app/features/serviceaccounts/ServiceAccountPage.test.tsx:3979425477": [ - [33, 20, 23, "Do not use any type assertions.", "499357842"], - [44, 13, 9, "Do not use any type assertions.", "3692209159"], - [44, 19, 3, "Unexpected any. Specify a different type.", "193409811"], - [45, 14, 9, "Do not use any type assertions.", "3692209159"], - [45, 20, 3, "Unexpected any. Specify a different type.", "193409811"], - [47, 11, 9, "Do not use any type assertions.", "3692209159"], - [47, 17, 3, "Unexpected any. Specify a different type.", "193409811"] + "public/app/features/serviceaccounts/ServiceAccountPage.test.tsx:3401231959": [ + [25, 20, 23, "Do not use any type assertions.", "499357842"], + [36, 13, 9, "Do not use any type assertions.", "3692209159"], + [36, 19, 3, "Unexpected any. Specify a different type.", "193409811"], + [37, 14, 9, "Do not use any type assertions.", "3692209159"], + [37, 20, 3, "Unexpected any. Specify a different type.", "193409811"], + [39, 11, 9, "Do not use any type assertions.", "3692209159"], + [39, 17, 3, "Unexpected any. Specify a different type.", "193409811"] ], "public/app/features/serviceaccounts/state/reducers.ts:816482675": [ [13, 18, 23, "Do not use any type assertions.", "499357842"], [15, 10, 14, "Do not use any type assertions.", "2767045528"], [43, 19, 25, "Do not use any type assertions.", "1122467828"] ], - "public/app/features/teams/CreateTeam.test.tsx:136903941": [ - [30, 14, 34, "Do not use any type assertions.", "463003776"] + "public/app/features/teams/CreateTeam.test.tsx:933968426": [ + [16, 14, 42, "Do not use any type assertions.", "2370189818"], + [16, 14, 28, "Do not use any type assertions.", "3912429083"], + [18, 5, 3, "Unexpected any. Specify a different type.", "193409811"] ], "public/app/features/teams/TeamGroupSync.test.tsx:2647720693": [ [12, 12, 17, "Do not use any type assertions.", "2242876437"] @@ -6945,12 +6953,11 @@ exports[`better eslint`] = { [59, 32, 3, "Unexpected any. Specify a different type.", "193409811"], [63, 23, 3, "Unexpected any. Specify a different type.", "193409811"] ], - "public/app/features/teams/TeamList.test.tsx:2054736348": [ - [22, 14, 123, "Do not use any type assertions.", "695951401"], - [30, 11, 12, "Do not use any type assertions.", "1079483306"], - [40, 18, 59, "Do not use any type assertions.", "25768610"], - [67, 24, 77, "Do not use any type assertions.", "361803129"], - [84, 24, 77, "Do not use any type assertions.", "1295202722"] + "public/app/features/teams/TeamList.test.tsx:3686781523": [ + [21, 11, 12, "Do not use any type assertions.", "1079483306"], + [31, 18, 59, "Do not use any type assertions.", "25768610"], + [58, 24, 77, "Do not use any type assertions.", "361803129"], + [75, 24, 77, "Do not use any type assertions.", "1295202722"] ], "public/app/features/teams/TeamMemberRow.tsx:607934811": [ [41, 18, 20, "Do not use any type assertions.", "3089389535"] @@ -6962,16 +6969,15 @@ exports[`better eslint`] = { "public/app/features/teams/TeamMembers.tsx:4139991775": [ [19, 32, 3, "Unexpected any. Specify a different type.", "193409811"] ], - "public/app/features/teams/TeamPages.test.tsx:1955453068": [ + "public/app/features/teams/TeamPages.test.tsx:648699836": [ [63, 13, 86, "Do not use any type assertions.", "2945747797"], [68, 11, 3, "Unexpected any. Specify a different type.", "193409811"], - [70, 14, 34, "Do not use any type assertions.", "463003776"], [75, 10, 10, "Do not use any type assertions.", "1584692172"], [76, 13, 18, "Do not use any type assertions.", "2776323642"], [79, 18, 88, "Do not use any type assertions.", "2247670218"], [138, 20, 95, "Do not use any type assertions.", "4068964415"] ], - "public/app/features/teams/TeamPages.tsx:3881385980": [ + "public/app/features/teams/TeamPages.tsx:342488801": [ [52, 43, 18, "Do not use any type assertions.", "373851894"] ], "public/app/features/teams/state/reducers.ts:1856799550": [ @@ -7177,10 +7183,9 @@ exports[`better eslint`] = { "public/app/features/users/UsersActionBar.tsx:1795710611": [ [66, 32, 3, "Unexpected any. Specify a different type.", "193409811"] ], - "public/app/features/users/UsersListPage.test.tsx:4284943401": [ - [27, 14, 119, "Do not use any type assertions.", "1781051572"], - [35, 11, 15, "Do not use any type assertions.", "2789568508"], - [36, 14, 15, "Do not use any type assertions.", "3505843099"] + "public/app/features/users/UsersListPage.test.tsx:761605339": [ + [26, 11, 15, "Do not use any type assertions.", "2789568508"], + [27, 14, 15, "Do not use any type assertions.", "3505843099"] ], "public/app/features/users/UsersTable.test.tsx:2179189208": [ [19, 11, 15, "Do not use any type assertions.", "2789568508"] @@ -10977,7 +10982,7 @@ exports[`better eslint`] = { [41, 64, 3, "Unexpected any. Specify a different type.", "193409811"], [41, 69, 3, "Unexpected any. Specify a different type.", "193409811"] ], - "public/app/plugins/panel/canvas/editor/LayerElementListEditor.tsx:374041207": [ + "public/app/plugins/panel/canvas/editor/LayerElementListEditor.tsx:2123096129": [ [21, 33, 3, "Unexpected any. Specify a different type.", "193409811"], [40, 30, 44, "Do not use any type assertions.", "712744497"] ], diff --git a/packages/grafana-data/src/types/navModel.ts b/packages/grafana-data/src/types/navModel.ts index 58f7926152808..453e1a1f57267 100644 --- a/packages/grafana-data/src/types/navModel.ts +++ b/packages/grafana-data/src/types/navModel.ts @@ -56,10 +56,6 @@ export interface NavModel { * This is the current active tab/navigation. */ node: NavModelItem; - /** - * Describes breadcrumbs that are used in places such as data source settings., folder page and plugins page. - */ - breadcrumbs?: NavModelItem[]; } export interface NavModelBreadcrumb { diff --git a/packages/grafana-ui/src/components/Tabs/VerticalTab.tsx b/packages/grafana-ui/src/components/Tabs/VerticalTab.tsx new file mode 100644 index 0000000000000..c1fdeccff2b98 --- /dev/null +++ b/packages/grafana-ui/src/components/Tabs/VerticalTab.tsx @@ -0,0 +1,96 @@ +import { css, cx } from '@emotion/css'; +import React from 'react'; + +import { GrafanaTheme2 } from '@grafana/data'; +import { selectors } from '@grafana/e2e-selectors'; + +import { useStyles2 } from '../../themes/ThemeContext'; +import { Icon } from '../Icon/Icon'; + +import { Counter } from './Counter'; +import { TabProps } from './Tab'; + +export const VerticalTab = React.forwardRef( + ({ label, active, icon, counter, className, suffix: Suffix, onChangeTab, href, ...otherProps }, ref) => { + const tabsStyles = useStyles2(getTabStyles); + const content = () => ( + <> + {icon && } + {label} + {typeof counter === 'number' && } + {Suffix && } + + ); + + const linkClass = cx(tabsStyles.link, active && tabsStyles.activeStyle); + + return ( +
  • + + {content()} + +
  • + ); + } +); + +VerticalTab.displayName = 'Tab'; + +const getTabStyles = (theme: GrafanaTheme2) => { + return { + item: css` + list-style: none; + margin-right: ${theme.spacing(2)}; + position: relative; + display: block; + margin-bottom: 4px; + `, + link: css` + padding: 6px 12px; + display: block; + height: 100%; + cursor: pointer; + + color: ${theme.colors.text.primary}; + + svg { + margin-right: ${theme.spacing(1)}; + } + + &:hover, + &:focus { + text-decoration: underline; + } + `, + activeStyle: css` + label: activeTabStyle; + color: ${theme.colors.text.maxContrast}; + font-weight: 500; + overflow: hidden; + + &::before { + display: block; + content: ' '; + position: absolute; + left: 0; + width: 4px; + bottom: 0; + top: 0; + border-radius: 2px; + background-image: linear-gradient(0deg, #f05a28 30%, #fbca0a 99%); + } + `, + suffix: css` + margin-left: ${theme.spacing(1)}; + `, + }; +}; diff --git a/packages/grafana-ui/src/components/index.ts b/packages/grafana-ui/src/components/index.ts index 721d749c863c5..e9dd8b85ada22 100644 --- a/packages/grafana-ui/src/components/index.ts +++ b/packages/grafana-ui/src/components/index.ts @@ -79,6 +79,7 @@ export { TableCellDisplayMode, TableSortByFieldState } from './Table/types'; export { TableInputCSV } from './TableInputCSV/TableInputCSV'; export { TabsBar } from './Tabs/TabsBar'; export { Tab } from './Tabs/Tab'; +export { VerticalTab } from './Tabs/VerticalTab'; export { TabContent } from './Tabs/TabContent'; export { Counter } from './Tabs/Counter'; diff --git a/public/app/angular/angular_wrappers.ts b/public/app/angular/angular_wrappers.ts index 15159553cd740..30a63a5af02a6 100644 --- a/public/app/angular/angular_wrappers.ts +++ b/public/app/angular/angular_wrappers.ts @@ -17,7 +17,7 @@ import { QueryEditor as CloudMonitoringQueryEditor } from 'app/plugins/datasourc import EmptyListCTA from '../core/components/EmptyListCTA/EmptyListCTA'; import { Footer } from '../core/components/Footer/Footer'; -import PageHeader from '../core/components/PageHeader/PageHeader'; +import { PageHeader } from '../core/components/PageHeader/PageHeader'; import { MetricSelect } from '../core/components/Select/MetricSelect'; import { TagFilter } from '../core/components/TagFilter/TagFilter'; import { HelpModal } from '../core/components/help/HelpModal'; diff --git a/public/app/angular/services/nav_model_srv.ts b/public/app/angular/services/nav_model_srv.ts index 73506625130d6..bf8790e1b3b21 100644 --- a/public/app/angular/services/nav_model_srv.ts +++ b/public/app/angular/services/nav_model_srv.ts @@ -71,7 +71,6 @@ export function getWarningNav(text: string, subTitle?: string): NavModel { icon: 'exclamation-triangle', }; return { - breadcrumbs: [node], node: node, main: node, }; diff --git a/public/app/core/components/ErrorPage/ErrorPage.tsx b/public/app/core/components/ErrorPage/ErrorPage.tsx index 8fe3e4c645ed9..25e77912f6c44 100644 --- a/public/app/core/components/ErrorPage/ErrorPage.tsx +++ b/public/app/core/components/ErrorPage/ErrorPage.tsx @@ -7,7 +7,7 @@ import { Icon } from '@grafana/ui'; import { getNavModel } from 'app/core/selectors/navModel'; import { StoreState } from 'app/types'; -import Page from '../Page/Page'; +import { Page } from '../Page/Page'; interface ConnectedProps { navModel: NavModel; diff --git a/public/app/core/components/Page/Page.test.tsx b/public/app/core/components/Page/Page.test.tsx new file mode 100644 index 0000000000000..3ac2e7edcff24 --- /dev/null +++ b/public/app/core/components/Page/Page.test.tsx @@ -0,0 +1,67 @@ +import { render, screen } from '@testing-library/react'; +import React from 'react'; +import { Provider } from 'react-redux'; + +import { NavModelItem } from '@grafana/data'; +import { config } from '@grafana/runtime'; +import { configureStore } from 'app/store/configureStore'; + +import { Page } from './Page'; +import { PageProps } from './types'; + +const pageNav: NavModelItem = { + text: 'Main title', + children: [ + { text: 'Child1', url: '1', active: true }, + { text: 'Child2', url: '2' }, + ], +}; + +const setup = (props: Partial) => { + config.bootData.navTree = [ + { + text: 'Section name', + id: 'section', + url: 'section', + children: [ + { text: 'Child1', id: 'child1', url: 'section/child1' }, + { text: 'Child2', id: 'child2', url: 'section/child2' }, + ], + }, + ]; + + const store = configureStore(); + + return render( + + +
    Children
    +
    +
    + ); +}; + +describe('Render', () => { + it('should render component with emtpy Page container', async () => { + setup({}); + const children = await screen.findByTestId('page-children'); + expect(children).toBeInTheDocument(); + + const pageHeader = screen.queryByRole('heading'); + expect(pageHeader).not.toBeInTheDocument(); + }); + + it('should render header when pageNav supplied', async () => { + setup({ pageNav }); + + expect(screen.getByRole('heading', { name: 'Main title' })).toBeInTheDocument(); + expect(screen.getAllByRole('tab').length).toBe(2); + }); + + it('should get header nav model from redux navIndex', async () => { + setup({ navId: 'child1' }); + + expect(screen.getByRole('heading', { name: 'Section name' })).toBeInTheDocument(); + expect(screen.getAllByRole('tab').length).toBe(2); + }); +}); diff --git a/public/app/core/components/Page/Page.tsx b/public/app/core/components/Page/Page.tsx index 121b9ba1548c5..643c9ee281e58 100644 --- a/public/app/core/components/Page/Page.tsx +++ b/public/app/core/components/Page/Page.tsx @@ -1,45 +1,33 @@ // Libraries import { css, cx } from '@emotion/css'; -import React, { FC, HTMLAttributes, useEffect } from 'react'; +import React from 'react'; -import { GrafanaTheme2, NavModel } from '@grafana/data'; +import { GrafanaTheme2 } from '@grafana/data'; +import { config } from '@grafana/runtime'; import { CustomScrollbar, useStyles2 } from '@grafana/ui'; -import { getTitleFromNavModel } from 'app/core/selectors/navModel'; -// Components -import { Branding } from '../Branding/Branding'; import { Footer } from '../Footer/Footer'; -import PageHeader from '../PageHeader/PageHeader'; +import { PageHeader } from '../PageHeader/PageHeader'; +import { Page as NewPage } from '../PageNew/Page'; import { PageContents } from './PageContents'; +import { PageType } from './types'; +import { usePageNav } from './usePageNav'; +import { usePageTitle } from './usePageTitle'; -interface Props extends HTMLAttributes { - children: React.ReactNode; - navModel?: NavModel; -} - -export interface PageType extends FC { - Header: typeof PageHeader; - Contents: typeof PageContents; -} - -export const Page: PageType = ({ navModel, children, className, ...otherProps }) => { +export const OldPage: PageType = ({ navId, navModel: oldNavProp, pageNav, children, className, ...otherProps }) => { const styles = useStyles2(getStyles); + const navModel = usePageNav(navId, oldNavProp); + + usePageTitle(navModel, pageNav); - useEffect(() => { - if (navModel) { - const title = getTitleFromNavModel(navModel); - document.title = title ? `${title} - ${Branding.AppTitle}` : Branding.AppTitle; - } else { - document.title = Branding.AppTitle; - } - }, [navModel]); + const pageHeaderNav = pageNav ?? navModel?.main; return (
    - {navModel && } + {pageHeaderNav && } {children}
    @@ -48,12 +36,12 @@ export const Page: PageType = ({ navModel, children, className, ...otherProps }) ); }; -Page.Header = PageHeader; -Page.Contents = PageContents; +OldPage.Header = PageHeader; +OldPage.Contents = PageContents; -export default Page; +export const Page: PageType = config.featureToggles.topnav ? NewPage : OldPage; -const getStyles = (theme: GrafanaTheme2) => ({ +const getStyles = (_: GrafanaTheme2) => ({ wrapper: css` width: 100%; flex-grow: 1; diff --git a/public/app/core/components/Page/types.ts b/public/app/core/components/Page/types.ts new file mode 100644 index 0000000000000..bc8340f22fcd3 --- /dev/null +++ b/public/app/core/components/Page/types.ts @@ -0,0 +1,19 @@ +import { FC, HTMLAttributes } from 'react'; + +import { NavModel, NavModelItem } from '@grafana/data'; + +import { PageHeader } from '../PageHeader/PageHeader'; + +import { PageContents } from './PageContents'; + +export interface PageProps extends HTMLAttributes { + children: React.ReactNode; + navId?: string; + navModel?: NavModel; + pageNav?: NavModelItem; +} + +export interface PageType extends FC { + Header: typeof PageHeader; + Contents: typeof PageContents; +} diff --git a/public/app/core/components/Page/usePageNav.ts b/public/app/core/components/Page/usePageNav.ts new file mode 100644 index 0000000000000..e78a5d17aa65b --- /dev/null +++ b/public/app/core/components/Page/usePageNav.ts @@ -0,0 +1,29 @@ +import { useSelector } from 'react-redux'; +import { createSelector } from 'reselect'; + +import { NavModel } from '@grafana/data'; +import { getNavModel } from 'app/core/selectors/navModel'; +import { store } from 'app/store/store'; +import { StoreState } from 'app/types'; + +export function usePageNav(navId?: string, oldProp?: NavModel): NavModel | undefined { + if (oldProp) { + return oldProp; + } + + if (!navId) { + return; + } + + // Page component is used in so many tests, this simplifies not having to initialize a full redux store + if (!store) { + return; + } + + // eslint-disable-next-line react-hooks/rules-of-hooks + return useSelector(createSelector(getNavIndex, (navIndex) => getNavModel(navIndex, navId ?? 'home'))); +} + +function getNavIndex(store: StoreState) { + return store.navIndex; +} diff --git a/public/app/core/components/Page/usePageTitle.ts b/public/app/core/components/Page/usePageTitle.ts new file mode 100644 index 0000000000000..b7512bb7b463c --- /dev/null +++ b/public/app/core/components/Page/usePageTitle.ts @@ -0,0 +1,32 @@ +import { useEffect } from 'react'; + +import { NavModel, NavModelItem } from '@grafana/data'; + +import { Branding } from '../Branding/Branding'; + +export function usePageTitle(navModel?: NavModel, pageNav?: NavModelItem) { + useEffect(() => { + const parts: string[] = []; + + if (pageNav) { + if (pageNav.children) { + const activePage = pageNav.children.find((x) => x.active); + if (activePage) { + parts.push(activePage.text); + } + } + parts.push(pageNav.text); + } + + if (navModel) { + if (navModel.node !== navModel.main) { + parts.push(navModel.node.text); + } + parts.push(navModel.main.text); + } + + parts.push(Branding.AppTitle); + + document.title = parts.join(' - '); + }, [navModel, pageNav]); +} diff --git a/public/app/core/components/PageHeader/PageHeader.test.tsx b/public/app/core/components/PageHeader/PageHeader.test.tsx index 2965d00714260..3e5af5ee1f9f3 100644 --- a/public/app/core/components/PageHeader/PageHeader.test.tsx +++ b/public/app/core/components/PageHeader/PageHeader.test.tsx @@ -1,23 +1,20 @@ import { render, screen } from '@testing-library/react'; import React from 'react'; -import PageHeader from './PageHeader'; +import { PageHeader } from './PageHeader'; describe('PageHeader', () => { describe('when the nav tree has a node with a title', () => { it('should render the title', async () => { const nav = { - main: { - icon: 'folder-open', - id: 'node', - subTitle: 'node subtitle', - url: '', - text: 'node', - }, - node: {}, + icon: 'folder-open', + id: 'node', + subTitle: 'node subtitle', + url: '', + text: 'node', }; - render(); + render(); expect(screen.getByRole('heading', { name: 'node' })).toBeInTheDocument(); }); @@ -26,18 +23,15 @@ describe('PageHeader', () => { describe('when the nav tree has a node with breadcrumbs and a title', () => { it('should render the title with breadcrumbs first and then title last', async () => { const nav = { - main: { - icon: 'folder-open', - id: 'child', - subTitle: 'child subtitle', - url: '', - text: 'child', - breadcrumbs: [{ title: 'Parent', url: 'parentUrl' }], - }, - node: {}, + icon: 'folder-open', + id: 'child', + subTitle: 'child subtitle', + url: '', + text: 'child', + breadcrumbs: [{ title: 'Parent', url: 'parentUrl' }], }; - render(); + render(); expect(screen.getByRole('heading', { name: 'Parent / child' })).toBeInTheDocument(); expect(screen.getByRole('link', { name: 'Parent' })).toBeInTheDocument(); diff --git a/public/app/core/components/PageHeader/PageHeader.tsx b/public/app/core/components/PageHeader/PageHeader.tsx index 116ff8186dde6..45fe58f14743a 100644 --- a/public/app/core/components/PageHeader/PageHeader.tsx +++ b/public/app/core/components/PageHeader/PageHeader.tsx @@ -1,14 +1,14 @@ import { css } from '@emotion/css'; import React, { FC } from 'react'; -import { NavModel, NavModelItem, NavModelBreadcrumb, GrafanaTheme2 } from '@grafana/data'; +import { NavModelItem, NavModelBreadcrumb, GrafanaTheme2 } from '@grafana/data'; import { Tab, TabsBar, Icon, IconName, useStyles2 } from '@grafana/ui'; import { PanelHeaderMenuItem } from 'app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenuItem'; import { ProBadge } from '../Upgrade/ProBadge'; export interface Props { - model: NavModel; + navItem: NavModelItem; } const SelectNav = ({ children, customCss }: { children: NavModelItem[]; customCss: string }) => { @@ -75,21 +75,19 @@ const Navigation = ({ children }: { children: NavModelItem[] }) => { ); }; -export const PageHeader: FC = ({ model }) => { +export const PageHeader: FC = ({ navItem: model }) => { const styles = useStyles2(getStyles); if (!model) { return null; } - const main = model.main; - const children = main.children; return (
    - {renderHeaderTitle(main)} - {children && children.length && {children}} + {renderHeaderTitle(model)} + {model.children && model.children.length > 0 && {model.children}}
    @@ -157,5 +155,3 @@ const getStyles = (theme: GrafanaTheme2) => ({ background: ${theme.colors.background.canvas}; `, }); - -export default PageHeader; diff --git a/public/app/core/components/PageNew/Page.test.tsx b/public/app/core/components/PageNew/Page.test.tsx new file mode 100644 index 0000000000000..a28ecec2b7df3 --- /dev/null +++ b/public/app/core/components/PageNew/Page.test.tsx @@ -0,0 +1,79 @@ +import { render, screen } from '@testing-library/react'; +import React from 'react'; +import { Provider } from 'react-redux'; + +import { NavModelItem } from '@grafana/data'; +import { config } from '@grafana/runtime'; +import { configureStore } from 'app/store/configureStore'; + +import { PageProps } from '../Page/types'; + +import { Page } from './Page'; + +const pageNav: NavModelItem = { + text: 'pageNav title', + children: [ + { text: 'pageNav child1', url: '1', active: true }, + { text: 'pageNav child2', url: '2' }, + ], +}; + +const setup = (props: Partial) => { + config.bootData.navTree = [ + { + text: 'Section name', + id: 'section', + url: 'section', + children: [ + { text: 'Child1', id: 'child1', url: 'section/child1' }, + { text: 'Child2', id: 'child2', url: 'section/child2' }, + ], + }, + ]; + + const store = configureStore(); + + return render( + + +
    Children
    +
    +
    + ); +}; + +describe('Render', () => { + it('should render component with emtpy Page container', async () => { + setup({}); + const children = await screen.findByTestId('page-children'); + expect(children).toBeInTheDocument(); + + const pageHeader = screen.queryByRole('heading'); + expect(pageHeader).not.toBeInTheDocument(); + }); + + it('should render header when pageNav supplied', async () => { + setup({ pageNav }); + + expect(screen.getByRole('heading', { name: 'pageNav title' })).toBeInTheDocument(); + expect(screen.getAllByRole('tab').length).toBe(2); + }); + + it('should render section nav model based on navId', async () => { + setup({ navId: 'child1' }); + + expect(screen.getByRole('heading', { name: 'Section name' })).toBeInTheDocument(); + expect(screen.getByRole('heading', { name: 'Child1' })).toBeInTheDocument(); + expect(screen.getByRole('tab', { name: 'Tab Child1' })).toBeInTheDocument(); + expect(screen.getAllByRole('tab').length).toBe(2); + }); + + it('should render section nav model based on navId and item page nav', async () => { + setup({ navId: 'child1', pageNav }); + + expect(screen.getByRole('heading', { name: 'Section name' })).toBeInTheDocument(); + expect(screen.getByRole('heading', { name: 'pageNav title' })).toBeInTheDocument(); + expect(screen.getByRole('tab', { name: 'Tab Child1' })).toBeInTheDocument(); + expect(screen.getByRole('tab', { name: 'Tab pageNav child1' })).toBeInTheDocument(); + }); +}); diff --git a/public/app/core/components/PageNew/Page.tsx b/public/app/core/components/PageNew/Page.tsx new file mode 100644 index 0000000000000..ed3424b15426a --- /dev/null +++ b/public/app/core/components/PageNew/Page.tsx @@ -0,0 +1,89 @@ +// Libraries +import { css, cx } from '@emotion/css'; +import React from 'react'; + +import { GrafanaTheme2 } from '@grafana/data'; +import { CustomScrollbar, useStyles2 } from '@grafana/ui'; + +// Components +import { Footer } from '../Footer/Footer'; +import { PageType } from '../Page/types'; +import { usePageNav } from '../Page/usePageNav'; +import { usePageTitle } from '../Page/usePageTitle'; +import { TopNavUpdate } from '../TopNav/TopNavUpdate'; + +import { PageContents } from './PageContents'; +import { PageHeader } from './PageHeader'; +import { PageTabs } from './PageTabs'; +import { SectionNav } from './SectionNav'; + +export const Page: PageType = ({ navId, navModel: oldNavProp, pageNav, children, className, ...otherProps }) => { + const styles = useStyles2(getStyles); + const navModel = usePageNav(navId, oldNavProp); + + usePageTitle(navModel, pageNav); + + const pageHeaderNav = pageNav ?? navModel?.node; + + return ( +
    +
    + {navModel && navModel.main.children && } +
    + +
    + {pageHeaderNav && } + {pageNav && pageNav.children && } + {children} +
    +
    + +
    +
    + {/** Update the breadcrumbs with item level nav */} + {pageNav && } +
    + ); +}; + +Page.Header = PageHeader; +Page.Contents = PageContents; + +const getStyles = (theme: GrafanaTheme2) => { + const shadow = theme.isDark + ? `0 0.6px 1.5px -1px rgb(0 0 0),0 2px 4px -1px rgb(0 0 0 / 40%),0 5px 10px -1px rgb(0 0 0 / 23%)` + : '0 0.6px 1.5px -1px rgb(0 0 0 / 8%),0 2px 4px rgb(0 0 0 / 6%),0 5px 10px -1px rgb(0 0 0 / 5%)'; + + return { + wrapper: css` + height: 100%; + display: flex; + flex: 1 1 0; + flex-direction: column; + min-height: 0; + `, + panes: css({ + display: 'flex', + height: '100%', + width: '100%', + flexGrow: 1, + minHeight: 0, + flexDirection: 'column', + [theme.breakpoints.up('md')]: { + flexDirection: 'row', + }, + }), + pageContent: css({ + flexGrow: 1, + }), + pageInner: css({ + padding: theme.spacing(3), + boxShadow: shadow, + background: theme.colors.background.primary, + margin: theme.spacing(2, 2, 2, 1), + display: 'flex', + flexDirection: 'column', + flexGrow: 1, + }), + }; +}; diff --git a/public/app/core/components/PageNew/PageContents.tsx b/public/app/core/components/PageNew/PageContents.tsx new file mode 100644 index 0000000000000..85a04fa295aa6 --- /dev/null +++ b/public/app/core/components/PageNew/PageContents.tsx @@ -0,0 +1,14 @@ +// Libraries +import React, { FC } from 'react'; + +import PageLoader from '../PageLoader/PageLoader'; + +interface Props { + isLoading?: boolean; + children: React.ReactNode; + className?: string; +} + +export const PageContents: FC = ({ isLoading, children }) => { + return <>{isLoading ? : children}; +}; diff --git a/public/app/core/components/PageNew/PageHeader.tsx b/public/app/core/components/PageNew/PageHeader.tsx new file mode 100644 index 0000000000000..1879563c90e58 --- /dev/null +++ b/public/app/core/components/PageNew/PageHeader.tsx @@ -0,0 +1,43 @@ +import { css } from '@emotion/css'; +import React from 'react'; + +import { NavModelItem, GrafanaTheme2 } from '@grafana/data'; +import { useStyles2 } from '@grafana/ui'; + +export interface Props { + navItem: NavModelItem; +} + +export function PageHeader({ navItem }: Props) { + const styles = useStyles2(getStyles); + + return ( + <> +

    + {navItem.img && {`logo} + {navItem.text} +

    + {navItem.subTitle &&
    {navItem.subTitle}
    } + + ); +} + +const getStyles = (theme: GrafanaTheme2) => { + return { + pageTitle: css({ + display: 'flex', + marginBottom: theme.spacing(3), + }), + pageSubTitle: css({ + marginBottom: theme.spacing(2), + position: 'relative', + top: theme.spacing(-1), + color: theme.colors.text.secondary, + }), + pageImg: css({ + width: '32px', + height: '32px', + marginRight: theme.spacing(2), + }), + }; +}; diff --git a/public/app/core/components/PageNew/PageTabs.tsx b/public/app/core/components/PageNew/PageTabs.tsx new file mode 100644 index 0000000000000..d3fb3a73c9ba6 --- /dev/null +++ b/public/app/core/components/PageNew/PageTabs.tsx @@ -0,0 +1,42 @@ +import { css } from '@emotion/css'; +import React from 'react'; + +import { NavModelItem, GrafanaTheme2 } from '@grafana/data'; +import { IconName, useStyles2, TabsBar, Tab } from '@grafana/ui'; + +export interface Props { + navItem: NavModelItem; +} + +export function PageTabs({ navItem }: Props) { + const styles = useStyles2(getStyles); + + return ( +
    + + {navItem.children!.map((child, index) => { + return ( + !child.hideFromTabs && ( + + ) + ); + })} + +
    + ); +} + +const getStyles = (theme: GrafanaTheme2) => { + return { + tabsWrapper: css({ + paddingBottom: theme.spacing(3), + }), + }; +}; diff --git a/public/app/core/components/PageNew/SectionNav.tsx b/public/app/core/components/PageNew/SectionNav.tsx new file mode 100644 index 0000000000000..3570de4294f4c --- /dev/null +++ b/public/app/core/components/PageNew/SectionNav.tsx @@ -0,0 +1,92 @@ +import { css } from '@emotion/css'; +import React from 'react'; + +import { NavModel, GrafanaTheme2 } from '@grafana/data'; +import { IconName, useStyles2, Icon, VerticalTab } from '@grafana/ui'; + +export interface Props { + model: NavModel; +} + +export function SectionNav(props: Props) { + const styles = useStyles2(getStyles); + + const main = props.model.main; + const directChildren = props.model.main.children!.filter((x) => !x.hideFromTabs && !x.children); + const nestedItems = props.model.main.children!.filter((x) => x.children && x.children.length); + + return ( + + ); +} + +const getStyles = (theme: GrafanaTheme2) => { + return { + nav: css({ + display: 'flex', + flexDirection: 'column', + background: theme.colors.background.canvas, + padding: theme.spacing(3, 2), + flexShrink: 0, + [theme.breakpoints.up('md')]: { + width: '250px', + }, + }), + sectionName: css({ + display: 'flex', + gap: theme.spacing(1), + padding: theme.spacing(0.5, 0, 3, 0.25), + fontSize: theme.typography.h4.fontSize, + margin: 0, + }), + items: css({ + // paddingLeft: '9px', + }), + subSection: css({ + padding: theme.spacing(3, 0, 1, 1), + fontWeight: 500, + fontSize: '16px', + }), + }; +}; diff --git a/public/app/core/components/TopNav/Breadcrumbs.tsx b/public/app/core/components/TopNav/Breadcrumbs.tsx index bdfad00315ef1..ad8f8d2cb3a46 100644 --- a/public/app/core/components/TopNav/Breadcrumbs.tsx +++ b/public/app/core/components/TopNav/Breadcrumbs.tsx @@ -8,7 +8,7 @@ import { TopNavProps } from './TopNavUpdate'; export interface Props extends TopNavProps { sectionNav: NavModelItem; - subNav?: NavModelItem; + pageNav?: NavModelItem; } export interface Breadcrumb { @@ -17,9 +17,9 @@ export interface Breadcrumb { href?: string; } -export function Breadcrumbs({ sectionNav, subNav }: Props) { +export function Breadcrumbs({ sectionNav, pageNav }: Props) { const styles = useStyles2(getStyles); - const crumbs: Breadcrumb[] = [{ icon: 'home', href: '/' }]; + const crumbs: Breadcrumb[] = [{ icon: 'home-alt', href: '/' }]; function addCrumbs(node: NavModelItem) { if (node.parentItem) { @@ -31,8 +31,8 @@ export function Breadcrumbs({ sectionNav, subNav }: Props) { addCrumbs(sectionNav); - if (subNav) { - addCrumbs(subNav); + if (pageNav) { + addCrumbs(pageNav); } return ( diff --git a/public/app/core/components/TopNav/NavToolbar.tsx b/public/app/core/components/TopNav/NavToolbar.tsx index afe8474d3d198..2a135a78b8119 100644 --- a/public/app/core/components/TopNav/NavToolbar.tsx +++ b/public/app/core/components/TopNav/NavToolbar.tsx @@ -12,10 +12,9 @@ export interface Props extends TopNavProps { onToggleSearchBar(): void; searchBarHidden?: boolean; sectionNav: NavModelItem; - subNav?: NavModelItem; } -export function NavToolbar({ actions, onToggleSearchBar, searchBarHidden, sectionNav, subNav }: Props) { +export function NavToolbar({ actions, onToggleSearchBar, searchBarHidden, sectionNav, pageNav }: Props) { const styles = useStyles2(getStyles); return ( @@ -23,7 +22,7 @@ export function NavToolbar({ actions, onToggleSearchBar, searchBarHidden, sectio
    {}} />
    - +
    {actions} diff --git a/public/app/core/components/TopNav/TopNavPage.tsx b/public/app/core/components/TopNav/TopNavPage.tsx index 5924ad6948522..9db6cd296e776 100644 --- a/public/app/core/components/TopNav/TopNavPage.tsx +++ b/public/app/core/components/TopNav/TopNavPage.tsx @@ -36,6 +36,7 @@ export function TopNavPage({ children, navId }: Props) { searchBarHidden={searchBarHidden} onToggleSearchBar={toggleSearchBar} sectionNav={navModel.node} + pageNav={props.pageNav} />
    {children}
    @@ -60,11 +61,11 @@ const getStyles = (theme: GrafanaTheme2) => { }), content: css({ display: 'flex', - paddingTop: TOP_BAR_LEVEL_HEIGHT * 2 + 16, + paddingTop: TOP_BAR_LEVEL_HEIGHT * 2, flexGrow: 1, }), contentNoSearchBar: css({ - paddingTop: TOP_BAR_LEVEL_HEIGHT + 16, + paddingTop: TOP_BAR_LEVEL_HEIGHT, }), topNav: css({ display: 'flex', diff --git a/public/app/core/components/TopNav/TopNavUpdate.tsx b/public/app/core/components/TopNav/TopNavUpdate.tsx index c16ba7a275508..5420415d145e5 100644 --- a/public/app/core/components/TopNav/TopNavUpdate.tsx +++ b/public/app/core/components/TopNav/TopNavUpdate.tsx @@ -4,7 +4,7 @@ import { Subject } from 'rxjs'; import { NavModelItem } from '@grafana/data'; export interface TopNavProps { - subNav?: NavModelItem; + pageNav?: NavModelItem; actions?: React.ReactNode; } diff --git a/public/app/core/reducers/navModel.ts b/public/app/core/reducers/navModel.ts index bdb256bfe1a9f..6be4673248da5 100644 --- a/public/app/core/reducers/navModel.ts +++ b/public/app/core/reducers/navModel.ts @@ -32,7 +32,6 @@ function buildWarningNav(text: string, subTitle?: string): NavModel { icon: 'exclamation-triangle', }; return { - breadcrumbs: [node], node: node, main: node, }; diff --git a/public/app/features/admin/AdminEditOrgPage.tsx b/public/app/features/admin/AdminEditOrgPage.tsx index 44bc10e013409..8ab6d856d77c6 100644 --- a/public/app/features/admin/AdminEditOrgPage.tsx +++ b/public/app/features/admin/AdminEditOrgPage.tsx @@ -6,7 +6,7 @@ import { useAsyncFn } from 'react-use'; import { UrlQueryValue } from '@grafana/data'; import { getBackendSrv } from '@grafana/runtime'; import { Form, Field, Input, Button, Legend, Alert } from '@grafana/ui'; -import Page from 'app/core/components/Page/Page'; +import { Page } from 'app/core/components/Page/Page'; import { contextSrv } from 'app/core/core'; import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; import { getNavModel } from 'app/core/selectors/navModel'; diff --git a/public/app/features/admin/AdminListOrgsPage.tsx b/public/app/features/admin/AdminListOrgsPage.tsx index 6fff515aa0908..94757ff371569 100644 --- a/public/app/features/admin/AdminListOrgsPage.tsx +++ b/public/app/features/admin/AdminListOrgsPage.tsx @@ -4,7 +4,7 @@ import useAsyncFn from 'react-use/lib/useAsyncFn'; import { getBackendSrv } from '@grafana/runtime'; import { LinkButton } from '@grafana/ui'; -import Page from 'app/core/components/Page/Page'; +import { Page } from 'app/core/components/Page/Page'; import { getNavModel } from 'app/core/selectors/navModel'; import { contextSrv } from 'app/core/services/context_srv'; import { AccessControlAction } from 'app/types'; diff --git a/public/app/features/admin/AdminSettings.tsx b/public/app/features/admin/AdminSettings.tsx index e2e2a0c7ecbf3..f0b90e6e2b8b3 100644 --- a/public/app/features/admin/AdminSettings.tsx +++ b/public/app/features/admin/AdminSettings.tsx @@ -4,7 +4,7 @@ import { useAsync } from 'react-use'; import { NavModel } from '@grafana/data'; import { getBackendSrv } from '@grafana/runtime'; -import Page from 'app/core/components/Page/Page'; +import { Page } from 'app/core/components/Page/Page'; import { getNavModel } from 'app/core/selectors/navModel'; import { StoreState } from 'app/types'; diff --git a/public/app/features/admin/UpgradePage.tsx b/public/app/features/admin/UpgradePage.tsx index 3678cb244c633..07b93187d285e 100644 --- a/public/app/features/admin/UpgradePage.tsx +++ b/public/app/features/admin/UpgradePage.tsx @@ -4,8 +4,8 @@ import { connect } from 'react-redux'; import { GrafanaTheme2, NavModel } from '@grafana/data'; import { LinkButton, useStyles2 } from '@grafana/ui'; +import { Page } from 'app/core/components/Page/Page'; -import Page from '../../core/components/Page/Page'; import { getNavModel } from '../../core/selectors/navModel'; import { StoreState } from '../../types'; diff --git a/public/app/features/admin/UserAdminPage.tsx b/public/app/features/admin/UserAdminPage.tsx index 264e9ce943ae6..14865464d1e81 100644 --- a/public/app/features/admin/UserAdminPage.tsx +++ b/public/app/features/admin/UserAdminPage.tsx @@ -3,7 +3,7 @@ import { connect, ConnectedProps } from 'react-redux'; import { NavModel } from '@grafana/data'; import { featureEnabled } from '@grafana/runtime'; -import Page from 'app/core/components/Page/Page'; +import { Page } from 'app/core/components/Page/Page'; import { contextSrv } from 'app/core/core'; import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; import { getNavModel } from 'app/core/selectors/navModel'; diff --git a/public/app/features/admin/UserCreatePage.tsx b/public/app/features/admin/UserCreatePage.tsx index b98e82a3e6a79..e68a20eeebf2f 100644 --- a/public/app/features/admin/UserCreatePage.tsx +++ b/public/app/features/admin/UserCreatePage.tsx @@ -5,7 +5,7 @@ import { useHistory } from 'react-router-dom'; import { NavModel } from '@grafana/data'; import { getBackendSrv } from '@grafana/runtime'; import { Form, Button, Input, Field } from '@grafana/ui'; -import Page from 'app/core/components/Page/Page'; +import { Page } from 'app/core/components/Page/Page'; import { getNavModel } from '../../core/selectors/navModel'; import { StoreState } from '../../types'; diff --git a/public/app/features/admin/UserListAdminPage.tsx b/public/app/features/admin/UserListAdminPage.tsx index 7fa5809ae4f37..f7435d397df21 100644 --- a/public/app/features/admin/UserListAdminPage.tsx +++ b/public/app/features/admin/UserListAdminPage.tsx @@ -13,7 +13,7 @@ import { useStyles2, FilterInput, } from '@grafana/ui'; -import Page from 'app/core/components/Page/Page'; +import { Page } from 'app/core/components/Page/Page'; import { TagBadge } from 'app/core/components/TagFilter/TagBadge'; import { contextSrv } from 'app/core/core'; diff --git a/public/app/features/admin/ldap/LdapPage.tsx b/public/app/features/admin/ldap/LdapPage.tsx index 28adbdc13ad9b..f0959e6f70e89 100644 --- a/public/app/features/admin/ldap/LdapPage.tsx +++ b/public/app/features/admin/ldap/LdapPage.tsx @@ -5,7 +5,7 @@ import { NavModel } from '@grafana/data'; import { featureEnabled } from '@grafana/runtime'; import { Alert, Button, LegacyForms } from '@grafana/ui'; const { FormField } = LegacyForms; -import Page from 'app/core/components/Page/Page'; +import { Page } from 'app/core/components/Page/Page'; import { contextSrv } from 'app/core/core'; import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; import { getNavModel } from 'app/core/selectors/navModel'; diff --git a/public/app/features/alerting/AlertRuleList.tsx b/public/app/features/alerting/AlertRuleList.tsx index 10de691e571f5..a19d2ab7bbd2b 100644 --- a/public/app/features/alerting/AlertRuleList.tsx +++ b/public/app/features/alerting/AlertRuleList.tsx @@ -5,7 +5,7 @@ import { SelectableValue } from '@grafana/data'; import { config, locationService } from '@grafana/runtime'; import { Button, FilterInput, LinkButton, Select, VerticalGroup } from '@grafana/ui'; import appEvents from 'app/core/app_events'; -import Page from 'app/core/components/Page/Page'; +import { Page } from 'app/core/components/Page/Page'; import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; import { getNavModel } from 'app/core/selectors/navModel'; import { AlertRule, StoreState } from 'app/types'; diff --git a/public/app/features/alerting/EditNotificationChannelPage.tsx b/public/app/features/alerting/EditNotificationChannelPage.tsx index 45aba248222ce..7303f7ccfd36e 100644 --- a/public/app/features/alerting/EditNotificationChannelPage.tsx +++ b/public/app/features/alerting/EditNotificationChannelPage.tsx @@ -4,7 +4,7 @@ import { MapDispatchToProps, MapStateToProps } from 'react-redux'; import { NavModel } from '@grafana/data'; import { config } from '@grafana/runtime'; import { Form, Spinner } from '@grafana/ui'; -import Page from 'app/core/components/Page/Page'; +import { Page } from 'app/core/components/Page/Page'; import { connectWithCleanUp } from 'app/core/components/connectWithCleanUp'; import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; import { getNavModel } from 'app/core/selectors/navModel'; diff --git a/public/app/features/alerting/FeatureTogglePage.tsx b/public/app/features/alerting/FeatureTogglePage.tsx index 63f62796f9403..e6def5ec3f581 100644 --- a/public/app/features/alerting/FeatureTogglePage.tsx +++ b/public/app/features/alerting/FeatureTogglePage.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import Page from 'app/core/components/Page/Page'; +import { Page } from 'app/core/components/Page/Page'; import { useNavModel } from 'app/core/hooks/useNavModel'; export default function FeatureTogglePage() { diff --git a/public/app/features/alerting/NewNotificationChannelPage.tsx b/public/app/features/alerting/NewNotificationChannelPage.tsx index 418029fb2e56b..93d6ca43865ce 100644 --- a/public/app/features/alerting/NewNotificationChannelPage.tsx +++ b/public/app/features/alerting/NewNotificationChannelPage.tsx @@ -3,7 +3,7 @@ import { connect, ConnectedProps } from 'react-redux'; import { config } from '@grafana/runtime'; import { Form } from '@grafana/ui'; -import Page from 'app/core/components/Page/Page'; +import { Page } from 'app/core/components/Page/Page'; import { getNavModel } from 'app/core/selectors/navModel'; import { NotificationChannelDTO, StoreState } from '../../types'; diff --git a/public/app/features/alerting/NotificationsListPage.tsx b/public/app/features/alerting/NotificationsListPage.tsx index 9a5e5b3f4eb08..6f11e260aac02 100644 --- a/public/app/features/alerting/NotificationsListPage.tsx +++ b/public/app/features/alerting/NotificationsListPage.tsx @@ -4,7 +4,7 @@ import { useAsyncFn } from 'react-use'; import { getBackendSrv } from '@grafana/runtime'; import { HorizontalGroup, Button, LinkButton } from '@grafana/ui'; import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA'; -import Page from 'app/core/components/Page/Page'; +import { Page } from 'app/core/components/Page/Page'; import { appEvents } from 'app/core/core'; import { useNavModel } from 'app/core/hooks/useNavModel'; import { AlertNotification } from 'app/types/alerting'; diff --git a/public/app/features/alerting/unified/RuleEditor.tsx b/public/app/features/alerting/unified/RuleEditor.tsx index 57d66fc319f4d..e550148abbea0 100644 --- a/public/app/features/alerting/unified/RuleEditor.tsx +++ b/public/app/features/alerting/unified/RuleEditor.tsx @@ -5,7 +5,7 @@ import { useAsync } from 'react-use'; import { GrafanaTheme2 } from '@grafana/data'; import { Alert, LinkButton, LoadingPlaceholder, useStyles2, withErrorBoundary } from '@grafana/ui'; -import Page from 'app/core/components/Page/Page'; +import { Page } from 'app/core/components/Page/Page'; import { useCleanup } from 'app/core/hooks/useCleanup'; import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; import { RuleIdentifier } from 'app/types/unified-alerting'; diff --git a/public/app/features/alerting/unified/components/AlertingPageWrapper.tsx b/public/app/features/alerting/unified/components/AlertingPageWrapper.tsx index 21a7826139e90..bb87e4a7a4ead 100644 --- a/public/app/features/alerting/unified/components/AlertingPageWrapper.tsx +++ b/public/app/features/alerting/unified/components/AlertingPageWrapper.tsx @@ -1,7 +1,7 @@ import React, { FC } from 'react'; import { useSelector } from 'react-redux'; -import Page from 'app/core/components/Page/Page'; +import { Page } from 'app/core/components/Page/Page'; import { getNavModel } from 'app/core/selectors/navModel'; import { StoreState } from 'app/types/store'; diff --git a/public/app/features/api-keys/ApiKeysPage.tsx b/public/app/features/api-keys/ApiKeysPage.tsx index 9df08efd22e25..4cb20857620c3 100644 --- a/public/app/features/api-keys/ApiKeysPage.tsx +++ b/public/app/features/api-keys/ApiKeysPage.tsx @@ -6,7 +6,7 @@ import { rangeUtil } from '@grafana/data'; import { InlineField, InlineSwitch, VerticalGroup } from '@grafana/ui'; import appEvents from 'app/core/app_events'; import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA'; -import Page from 'app/core/components/Page/Page'; +import { Page } from 'app/core/components/Page/Page'; import config from 'app/core/config'; import { contextSrv } from 'app/core/core'; import { getNavModel } from 'app/core/selectors/navModel'; diff --git a/public/app/features/dashboard/components/DashNav/DashNav.tsx b/public/app/features/dashboard/components/DashNav/DashNav.tsx index 09101d40d5ad0..8a82a48a83911 100644 --- a/public/app/features/dashboard/components/DashNav/DashNav.tsx +++ b/public/app/features/dashboard/components/DashNav/DashNav.tsx @@ -278,7 +278,7 @@ export const DashNav = React.memo((props) => { if (config.featureToggles.topnav) { return ( } /> ); diff --git a/public/app/features/dashboard/containers/DashboardPage.tsx b/public/app/features/dashboard/containers/DashboardPage.tsx index d32eb5c181796..017e6adc8a180 100644 --- a/public/app/features/dashboard/containers/DashboardPage.tsx +++ b/public/app/features/dashboard/containers/DashboardPage.tsx @@ -5,7 +5,7 @@ import { connect, ConnectedProps } from 'react-redux'; import { GrafanaTheme2, TimeRange } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; -import { locationService } from '@grafana/runtime'; +import { config, locationService } from '@grafana/runtime'; import { CustomScrollbar, stylesFactory, Themeable2, withTheme2 } from '@grafana/ui'; import { notifyApp } from 'app/core/actions'; import { Branding } from 'app/core/components/Branding/Branding'; @@ -387,7 +387,8 @@ export class UnthemedDashboardPage extends PureComponent { * Styles */ export const getStyles = stylesFactory((theme: GrafanaTheme2, kioskMode: KioskMode) => { - const contentPadding = kioskMode !== KioskMode.Full ? theme.spacing(0, 2, 2) : theme.spacing(2); + const contentPadding = + kioskMode === KioskMode.Full || config.featureToggles.topnav ? theme.spacing(2) : theme.spacing(0, 2, 2); return { dashboardContainer: css` width: 100%; diff --git a/public/app/features/datasources/DataSourceDashboards.tsx b/public/app/features/datasources/DataSourceDashboards.tsx index 9673716ddc379..9033a94ecc6ce 100644 --- a/public/app/features/datasources/DataSourceDashboards.tsx +++ b/public/app/features/datasources/DataSourceDashboards.tsx @@ -1,7 +1,7 @@ import React, { PureComponent } from 'react'; import { connect, ConnectedProps } from 'react-redux'; -import Page from 'app/core/components/Page/Page'; +import { Page } from 'app/core/components/Page/Page'; import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; import { getNavModel } from 'app/core/selectors/navModel'; import { PluginDashboard, StoreState } from 'app/types'; diff --git a/public/app/features/datasources/DataSourcesListPage.tsx b/public/app/features/datasources/DataSourcesListPage.tsx index f03e7ede1af5a..4535c5b0f769f 100644 --- a/public/app/features/datasources/DataSourcesListPage.tsx +++ b/public/app/features/datasources/DataSourcesListPage.tsx @@ -3,7 +3,7 @@ import { connect, ConnectedProps } from 'react-redux'; import { IconName } from '@grafana/ui'; import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA'; -import Page from 'app/core/components/Page/Page'; +import { Page } from 'app/core/components/Page/Page'; import PageActionBar from 'app/core/components/PageActionBar/PageActionBar'; import { contextSrv } from 'app/core/core'; import { getNavModel } from 'app/core/selectors/navModel'; diff --git a/public/app/features/datasources/NewDataSourcePage.tsx b/public/app/features/datasources/NewDataSourcePage.tsx index b7943e8af90bf..2f226c6978432 100644 --- a/public/app/features/datasources/NewDataSourcePage.tsx +++ b/public/app/features/datasources/NewDataSourcePage.tsx @@ -5,7 +5,7 @@ import { connect, ConnectedProps } from 'react-redux'; import { DataSourcePluginMeta, GrafanaTheme2, NavModel } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; import { Card, LinkButton, List, PluginSignatureBadge, FilterInput, useStyles2 } from '@grafana/ui'; -import Page from 'app/core/components/Page/Page'; +import { Page } from 'app/core/components/Page/Page'; import { StoreState } from 'app/types'; import { PluginsErrorsInfo } from '../plugins/components/PluginsErrorsInfo'; diff --git a/public/app/features/datasources/settings/DataSourceSettingsPage.tsx b/public/app/features/datasources/settings/DataSourceSettingsPage.tsx index 68e2a4f2384f1..3630fb0d48fcd 100644 --- a/public/app/features/datasources/settings/DataSourceSettingsPage.tsx +++ b/public/app/features/datasources/settings/DataSourceSettingsPage.tsx @@ -6,7 +6,7 @@ import { selectors } from '@grafana/e2e-selectors'; import { Alert, Button } from '@grafana/ui'; import { cleanUpAction } from 'app/core/actions/cleanUp'; import appEvents from 'app/core/app_events'; -import Page from 'app/core/components/Page/Page'; +import { Page } from 'app/core/components/Page/Page'; import { contextSrv } from 'app/core/core'; import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; import { getNavModel } from 'app/core/selectors/navModel'; diff --git a/public/app/features/explore/FeatureTogglePage.tsx b/public/app/features/explore/FeatureTogglePage.tsx index 54a00691aaa85..a10df73f18379 100644 --- a/public/app/features/explore/FeatureTogglePage.tsx +++ b/public/app/features/explore/FeatureTogglePage.tsx @@ -3,7 +3,7 @@ import React from 'react'; import { GrafanaTheme2 } from '@grafana/data'; import { useStyles2 } from '@grafana/ui'; -import Page from 'app/core/components/Page/Page'; +import { Page } from 'app/core/components/Page/Page'; export default function FeatureTogglePage() { const styles = useStyles2( diff --git a/public/app/features/folders/AccessControlFolderPermissions.tsx b/public/app/features/folders/AccessControlFolderPermissions.tsx index 7e5e1b4d3e50c..325b69eb406a7 100644 --- a/public/app/features/folders/AccessControlFolderPermissions.tsx +++ b/public/app/features/folders/AccessControlFolderPermissions.tsx @@ -2,7 +2,7 @@ import React, { useEffect } from 'react'; import { connect, ConnectedProps } from 'react-redux'; import { Permissions } from 'app/core/components/AccessControl'; -import Page from 'app/core/components/Page/Page'; +import { Page } from 'app/core/components/Page/Page'; import { contextSrv } from 'app/core/core'; import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; import { getNavModel } from 'app/core/selectors/navModel'; diff --git a/public/app/features/folders/FolderAlerting.tsx b/public/app/features/folders/FolderAlerting.tsx index 939676a751c88..25785b74949bc 100644 --- a/public/app/features/folders/FolderAlerting.tsx +++ b/public/app/features/folders/FolderAlerting.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { useAsync } from 'react-use'; -import Page from 'app/core/components/Page/Page'; +import { Page } from 'app/core/components/Page/Page'; import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; import { getNavModel } from 'app/core/selectors/navModel'; import { StoreState } from 'app/types'; diff --git a/public/app/features/folders/FolderLibraryPanelsPage.tsx b/public/app/features/folders/FolderLibraryPanelsPage.tsx index aa27c268d76ce..a645f34bb5122 100644 --- a/public/app/features/folders/FolderLibraryPanelsPage.tsx +++ b/public/app/features/folders/FolderLibraryPanelsPage.tsx @@ -2,7 +2,8 @@ import React, { useState } from 'react'; import { connect, ConnectedProps } from 'react-redux'; import { useAsync } from 'react-use'; -import Page from '../../core/components/Page/Page'; +import { Page } from 'app/core/components/Page/Page'; + import { GrafanaRouteComponentProps } from '../../core/navigation/types'; import { getNavModel } from '../../core/selectors/navModel'; import { StoreState } from '../../types'; diff --git a/public/app/features/folders/FolderPermissions.tsx b/public/app/features/folders/FolderPermissions.tsx index ed69935fa4fb0..59f9263af8796 100644 --- a/public/app/features/folders/FolderPermissions.tsx +++ b/public/app/features/folders/FolderPermissions.tsx @@ -3,7 +3,7 @@ import { connect, ConnectedProps } from 'react-redux'; import { Tooltip, Icon, Button } from '@grafana/ui'; import { SlideDown } from 'app/core/components/Animations/SlideDown'; -import Page from 'app/core/components/Page/Page'; +import { Page } from 'app/core/components/Page/Page'; import AddPermission from 'app/core/components/PermissionList/AddPermission'; import PermissionList from 'app/core/components/PermissionList/PermissionList'; import PermissionsInfo from 'app/core/components/PermissionList/PermissionsInfo'; diff --git a/public/app/features/folders/FolderSettingsPage.tsx b/public/app/features/folders/FolderSettingsPage.tsx index 16cf9a0392a42..ecb47bb0de206 100644 --- a/public/app/features/folders/FolderSettingsPage.tsx +++ b/public/app/features/folders/FolderSettingsPage.tsx @@ -4,7 +4,7 @@ import { connect, ConnectedProps } from 'react-redux'; import { Button, LegacyForms } from '@grafana/ui'; const { Input } = LegacyForms; import appEvents from 'app/core/app_events'; -import Page from 'app/core/components/Page/Page'; +import { Page } from 'app/core/components/Page/Page'; import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; import { getNavModel } from 'app/core/selectors/navModel'; import { StoreState } from 'app/types'; diff --git a/public/app/features/folders/__snapshots__/FolderSettingsPage.test.tsx.snap b/public/app/features/folders/__snapshots__/FolderSettingsPage.test.tsx.snap index 32186afa57aa4..1a334028040ed 100644 --- a/public/app/features/folders/__snapshots__/FolderSettingsPage.test.tsx.snap +++ b/public/app/features/folders/__snapshots__/FolderSettingsPage.test.tsx.snap @@ -1,7 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Render should enable save button 1`] = ` -
    - + `; exports[`Render should render component 1`] = ` - - + `; diff --git a/public/app/features/folders/components/NewDashboardsFolder.tsx b/public/app/features/folders/components/NewDashboardsFolder.tsx index 72617c8fbfe72..cd387a798179e 100644 --- a/public/app/features/folders/components/NewDashboardsFolder.tsx +++ b/public/app/features/folders/components/NewDashboardsFolder.tsx @@ -2,7 +2,7 @@ import React, { PureComponent } from 'react'; import { connect, ConnectedProps } from 'react-redux'; import { Button, Input, Form, Field } from '@grafana/ui'; -import Page from 'app/core/components/Page/Page'; +import { Page } from 'app/core/components/Page/Page'; import { getNavModel } from 'app/core/selectors/navModel'; import { StoreState } from 'app/types'; diff --git a/public/app/features/invites/SignupInvited.tsx b/public/app/features/invites/SignupInvited.tsx index 4d862d6377dd5..d12da280e070c 100644 --- a/public/app/features/invites/SignupInvited.tsx +++ b/public/app/features/invites/SignupInvited.tsx @@ -3,7 +3,7 @@ import { useAsync } from 'react-use'; import { getBackendSrv } from '@grafana/runtime'; import { Button, Field, Form, Input } from '@grafana/ui'; -import Page from 'app/core/components/Page/Page'; +import { Page } from 'app/core/components/Page/Page'; import { getConfig } from 'app/core/config'; import { contextSrv } from 'app/core/core'; import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; diff --git a/public/app/features/library-panels/LibraryPanelsPage.tsx b/public/app/features/library-panels/LibraryPanelsPage.tsx index 09342b0e5cfb1..af435231e7ab2 100644 --- a/public/app/features/library-panels/LibraryPanelsPage.tsx +++ b/public/app/features/library-panels/LibraryPanelsPage.tsx @@ -1,7 +1,8 @@ import React, { FC, useState } from 'react'; import { connect, ConnectedProps } from 'react-redux'; -import Page from '../../core/components/Page/Page'; +import { Page } from 'app/core/components/Page/Page'; + import { GrafanaRouteComponentProps } from '../../core/navigation/types'; import { getNavModel } from '../../core/selectors/navModel'; import { StoreState } from '../../types'; diff --git a/public/app/features/live/pages/CloudAdminPage.tsx b/public/app/features/live/pages/CloudAdminPage.tsx index fe3face9db2cb..449c4c06c3388 100644 --- a/public/app/features/live/pages/CloudAdminPage.tsx +++ b/public/app/features/live/pages/CloudAdminPage.tsx @@ -4,7 +4,7 @@ import React, { useEffect, useState } from 'react'; import { GrafanaTheme } from '@grafana/data'; import { getBackendSrv } from '@grafana/runtime'; import { useStyles } from '@grafana/ui'; -import Page from 'app/core/components/Page/Page'; +import { Page } from 'app/core/components/Page/Page'; import { useNavModel } from 'app/core/hooks/useNavModel'; import { GrafanaCloudBackend } from './types'; diff --git a/public/app/features/live/pages/FeatureTogglePage.tsx b/public/app/features/live/pages/FeatureTogglePage.tsx index a1d3e635c5ac2..35b19509a05ff 100644 --- a/public/app/features/live/pages/FeatureTogglePage.tsx +++ b/public/app/features/live/pages/FeatureTogglePage.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import Page from 'app/core/components/Page/Page'; +import { Page } from 'app/core/components/Page/Page'; import { useNavModel } from 'app/core/hooks/useNavModel'; export default function FeatureTogglePage() { diff --git a/public/app/features/live/pages/LiveStatusPage.tsx b/public/app/features/live/pages/LiveStatusPage.tsx index 569853591af08..485aacfe8d52b 100644 --- a/public/app/features/live/pages/LiveStatusPage.tsx +++ b/public/app/features/live/pages/LiveStatusPage.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import Page from 'app/core/components/Page/Page'; +import { Page } from 'app/core/components/Page/Page'; import { useNavModel } from 'app/core/hooks/useNavModel'; export default function CloudAdminPage() { diff --git a/public/app/features/live/pages/PipelineAdminPage.tsx b/public/app/features/live/pages/PipelineAdminPage.tsx index 2face908dc6e1..7d33b38b59582 100644 --- a/public/app/features/live/pages/PipelineAdminPage.tsx +++ b/public/app/features/live/pages/PipelineAdminPage.tsx @@ -2,7 +2,7 @@ import React, { useEffect, useState, ChangeEvent } from 'react'; import { getBackendSrv } from '@grafana/runtime'; import { Input } from '@grafana/ui'; -import Page from 'app/core/components/Page/Page'; +import { Page } from 'app/core/components/Page/Page'; import { useNavModel } from 'app/core/hooks/useNavModel'; import { AddNewRule } from './AddNewRule'; diff --git a/public/app/features/manage-dashboards/DashboardImportPage.tsx b/public/app/features/manage-dashboards/DashboardImportPage.tsx index 1085441536a6f..365a7eabd1b95 100644 --- a/public/app/features/manage-dashboards/DashboardImportPage.tsx +++ b/public/app/features/manage-dashboards/DashboardImportPage.tsx @@ -20,7 +20,7 @@ import { withTheme2, } from '@grafana/ui'; import appEvents from 'app/core/app_events'; -import Page from 'app/core/components/Page/Page'; +import { Page } from 'app/core/components/Page/Page'; import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; import { getNavModel } from 'app/core/selectors/navModel'; import { StoreState } from 'app/types'; diff --git a/public/app/features/manage-dashboards/SnapshotListPage.tsx b/public/app/features/manage-dashboards/SnapshotListPage.tsx index 5aa91777a345c..ae16793ca0f1a 100644 --- a/public/app/features/manage-dashboards/SnapshotListPage.tsx +++ b/public/app/features/manage-dashboards/SnapshotListPage.tsx @@ -2,7 +2,7 @@ import React, { FC } from 'react'; import { MapStateToProps, connect } from 'react-redux'; import { NavModel } from '@grafana/data'; -import Page from 'app/core/components/Page/Page'; +import { Page } from 'app/core/components/Page/Page'; import { getNavModel } from 'app/core/selectors/navModel'; import { StoreState } from 'app/types'; diff --git a/public/app/features/notifications/NotificationsPage.tsx b/public/app/features/notifications/NotificationsPage.tsx index 04b6b192a2350..22c64301384c3 100644 --- a/public/app/features/notifications/NotificationsPage.tsx +++ b/public/app/features/notifications/NotificationsPage.tsx @@ -1,7 +1,8 @@ import React from 'react'; import { connect, ConnectedProps } from 'react-redux'; -import Page from '../../core/components/Page/Page'; +import { Page } from 'app/core/components/Page/Page'; + import { GrafanaRouteComponentProps } from '../../core/navigation/types'; import { getNavModel } from '../../core/selectors/navModel'; import { StoreState } from '../../types'; diff --git a/public/app/features/org/NewOrgPage.tsx b/public/app/features/org/NewOrgPage.tsx index 34d2619110bd6..6a4fc75a49084 100644 --- a/public/app/features/org/NewOrgPage.tsx +++ b/public/app/features/org/NewOrgPage.tsx @@ -2,7 +2,7 @@ import React, { FC } from 'react'; import { connect, ConnectedProps } from 'react-redux'; import { Button, Input, Field, Form } from '@grafana/ui'; -import Page from 'app/core/components/Page/Page'; +import { Page } from 'app/core/components/Page/Page'; import { getConfig } from 'app/core/config'; import { StoreState } from 'app/types'; diff --git a/public/app/features/org/OrgDetailsPage.tsx b/public/app/features/org/OrgDetailsPage.tsx index 5059ad5dd2578..57c21a83a63ed 100644 --- a/public/app/features/org/OrgDetailsPage.tsx +++ b/public/app/features/org/OrgDetailsPage.tsx @@ -3,7 +3,7 @@ import { connect } from 'react-redux'; import { NavModel } from '@grafana/data'; import { VerticalGroup } from '@grafana/ui'; -import Page from 'app/core/components/Page/Page'; +import { Page } from 'app/core/components/Page/Page'; import SharedPreferences from 'app/core/components/SharedPreferences/SharedPreferences'; import { contextSrv } from 'app/core/core'; import { getNavModel } from 'app/core/selectors/navModel'; diff --git a/public/app/features/org/SelectOrgPage.tsx b/public/app/features/org/SelectOrgPage.tsx index 273f699cc65a9..2d39f619074a7 100644 --- a/public/app/features/org/SelectOrgPage.tsx +++ b/public/app/features/org/SelectOrgPage.tsx @@ -4,7 +4,7 @@ import { useEffectOnce } from 'react-use'; import { config } from '@grafana/runtime'; import { Button, HorizontalGroup } from '@grafana/ui'; -import Page from 'app/core/components/Page/Page'; +import { Page } from 'app/core/components/Page/Page'; import { StoreState, UserOrg } from 'app/types'; import { getUserOrganizations, setUserOrganization } from './state/actions'; diff --git a/public/app/features/org/UserInvitePage.tsx b/public/app/features/org/UserInvitePage.tsx index 8afa065a0b7b3..097ead15a32a0 100644 --- a/public/app/features/org/UserInvitePage.tsx +++ b/public/app/features/org/UserInvitePage.tsx @@ -2,7 +2,7 @@ import React, { FC } from 'react'; import { connect } from 'react-redux'; import { NavModel } from '@grafana/data'; -import Page from 'app/core/components/Page/Page'; +import { Page } from 'app/core/components/Page/Page'; import { contextSrv } from 'app/core/core'; import { getNavModel } from 'app/core/selectors/navModel'; import { StoreState } from 'app/types/store'; diff --git a/public/app/features/playlist/PlaylistEditPage.tsx b/public/app/features/playlist/PlaylistEditPage.tsx index c98ba7ee00d3f..24eb413ef12fa 100644 --- a/public/app/features/playlist/PlaylistEditPage.tsx +++ b/public/app/features/playlist/PlaylistEditPage.tsx @@ -4,7 +4,7 @@ import { connect, MapStateToProps } from 'react-redux'; import { NavModel } from '@grafana/data'; import { locationService } from '@grafana/runtime'; import { useStyles2 } from '@grafana/ui'; -import Page from 'app/core/components/Page/Page'; +import { Page } from 'app/core/components/Page/Page'; import { getNavModel } from 'app/core/selectors/navModel'; import { StoreState } from 'app/types'; diff --git a/public/app/features/playlist/PlaylistNewPage.tsx b/public/app/features/playlist/PlaylistNewPage.tsx index c08d288ec998a..e7ef84960b85f 100644 --- a/public/app/features/playlist/PlaylistNewPage.tsx +++ b/public/app/features/playlist/PlaylistNewPage.tsx @@ -4,7 +4,7 @@ import { connect, MapStateToProps } from 'react-redux'; import { NavModel } from '@grafana/data'; import { locationService } from '@grafana/runtime'; import { useStyles2 } from '@grafana/ui'; -import Page from 'app/core/components/Page/Page'; +import { Page } from 'app/core/components/Page/Page'; import { getNavModel } from 'app/core/selectors/navModel'; import { StoreState } from 'app/types'; diff --git a/public/app/features/playlist/PlaylistPage.tsx b/public/app/features/playlist/PlaylistPage.tsx index 62e85085d2f44..e8a997f02320f 100644 --- a/public/app/features/playlist/PlaylistPage.tsx +++ b/public/app/features/playlist/PlaylistPage.tsx @@ -4,7 +4,7 @@ import { useDebounce } from 'react-use'; import { NavModel } from '@grafana/data'; import { ConfirmModal } from '@grafana/ui'; -import Page from 'app/core/components/Page/Page'; +import { Page } from 'app/core/components/Page/Page'; import PageActionBar from 'app/core/components/PageActionBar/PageActionBar'; import { getNavModel } from 'app/core/selectors/navModel'; import { contextSrv } from 'app/core/services/context_srv'; diff --git a/public/app/features/plugins/admin/routes.ts b/public/app/features/plugins/admin/routes.ts index 02a54c1c6c8aa..53a573ccbf453 100644 --- a/public/app/features/plugins/admin/routes.ts +++ b/public/app/features/plugins/admin/routes.ts @@ -7,16 +7,19 @@ import { PluginAdminRoutes } from './types'; const DEFAULT_ROUTES = [ { path: '/plugins', + navId: 'plugins', routeName: PluginAdminRoutes.Home, component: SafeDynamicImport(() => import(/* webpackChunkName: "PluginListPage" */ './pages/Browse')), }, { path: '/plugins/browse', + navId: 'plugins', routeName: PluginAdminRoutes.Browse, component: SafeDynamicImport(() => import(/* webpackChunkName: "PluginListPage" */ './pages/Browse')), }, { path: '/plugins/:pluginId/', + navId: 'plugins', routeName: PluginAdminRoutes.Details, component: SafeDynamicImport(() => import(/* webpackChunkName: "PluginPage" */ './pages/PluginDetails')), }, @@ -25,16 +28,19 @@ const DEFAULT_ROUTES = [ const ADMIN_ROUTES = [ { path: '/admin/plugins', + navId: 'admin-plugins', routeName: PluginAdminRoutes.HomeAdmin, component: SafeDynamicImport(() => import(/* webpackChunkName: "PluginListPage" */ './pages/Browse')), }, { path: '/admin/plugins/browse', + navId: 'admin-plugins', routeName: PluginAdminRoutes.BrowseAdmin, component: SafeDynamicImport(() => import(/* webpackChunkName: "PluginListPage" */ './pages/Browse')), }, { path: '/admin/plugins/:pluginId/', + navId: 'admin-plugins', routeName: PluginAdminRoutes.DetailsAdmin, component: SafeDynamicImport(() => import(/* webpackChunkName: "PluginPage" */ './pages/PluginDetails')), }, diff --git a/public/app/features/plugins/components/AppPluginLoader.tsx b/public/app/features/plugins/components/AppPluginLoader.tsx index fe26327693098..37516513f1a0f 100644 --- a/public/app/features/plugins/components/AppPluginLoader.tsx +++ b/public/app/features/plugins/components/AppPluginLoader.tsx @@ -3,7 +3,7 @@ import { useLocation, useParams } from 'react-router-dom'; import { NavModel } from '@grafana/data'; import { getWarningNav } from 'app/angular/services/nav_model_srv'; -import Page from 'app/core/components/Page/Page'; +import { Page } from 'app/core/components/Page/Page'; import PageLoader from 'app/core/components/PageLoader/PageLoader'; import { useImportAppPlugin } from '../hooks/useImportAppPlugin'; @@ -23,13 +23,13 @@ export const AppPluginLoader = ({ id, basePath }: AppPluginLoaderProps) => { const { pathname } = useLocation(); if (error) { - return ; + return ; } return ( <> {loading && } - {nav && } + {nav && } {!loading && plugin && plugin.root && ( ({ getBackendSrv: () => ({ post: postMock, @@ -26,6 +27,7 @@ jest.mock('@grafana/runtime', () => ({ licenseUrl: '', }, appSubUrl: '', + featureToggles: {}, }, })); @@ -52,7 +54,7 @@ const setup = (propOverrides: Partial) => { Object.assign(props, propOverrides); - render(); + render(); }; describe('ServiceAccountCreatePage tests', () => { diff --git a/public/app/features/serviceaccounts/ServiceAccountCreatePage.tsx b/public/app/features/serviceaccounts/ServiceAccountCreatePage.tsx index dc6bf19741d96..5d2faca95f765 100644 --- a/public/app/features/serviceaccounts/ServiceAccountCreatePage.tsx +++ b/public/app/features/serviceaccounts/ServiceAccountCreatePage.tsx @@ -1,34 +1,24 @@ import React, { useCallback, useEffect, useState } from 'react'; -import { connect } from 'react-redux'; import { useHistory } from 'react-router-dom'; -import { NavModel } from '@grafana/data'; import { getBackendSrv } from '@grafana/runtime'; import { Form, Button, Input, Field } from '@grafana/ui'; -import Page from 'app/core/components/Page/Page'; +import { Page } from 'app/core/components/Page/Page'; import { UserRolePicker } from 'app/core/components/RolePicker/UserRolePicker'; import { fetchBuiltinRoles, fetchRoleOptions, updateUserRoles } from 'app/core/components/RolePicker/api'; import { contextSrv } from 'app/core/core'; import { AccessControlAction, OrgRole, Role, ServiceAccountCreateApiResponse, ServiceAccountDTO } from 'app/types'; -import { getNavModel } from '../../core/selectors/navModel'; -import { StoreState } from '../../types'; import { OrgRolePicker } from '../admin/OrgRolePicker'; -export interface Props { - navModel: NavModel; -} - -const mapStateToProps = (state: StoreState) => ({ - navModel: getNavModel(state.navIndex, 'serviceaccounts'), -}); +export interface Props {} const createServiceAccount = async (sa: ServiceAccountDTO) => getBackendSrv().post('/api/serviceaccounts/', sa); const updateServiceAccount = async (id: number, sa: ServiceAccountDTO) => getBackendSrv().patch(`/api/serviceaccounts/${id}`, sa); -export const ServiceAccountCreatePageUnconnected = ({ navModel }: Props): JSX.Element => { +export const ServiceAccountCreatePage = ({}: Props): JSX.Element => { const [roleOptions, setRoleOptions] = useState([]); const [builtinRoles, setBuiltinRoles] = useState<{ [key: string]: Role[] }>({}); const [pendingRoles, setPendingRoles] = useState([]); @@ -109,7 +99,7 @@ export const ServiceAccountCreatePageUnconnected = ({ navModel }: Props): JSX.El }; return ( - +

    Create service account

    @@ -152,4 +142,4 @@ export const ServiceAccountCreatePageUnconnected = ({ navModel }: Props): JSX.El ); }; -export default connect(mapStateToProps)(ServiceAccountCreatePageUnconnected); +export default ServiceAccountCreatePage; diff --git a/public/app/features/serviceaccounts/ServiceAccountPage.test.tsx b/public/app/features/serviceaccounts/ServiceAccountPage.test.tsx index 142b404550722..d13d484cdc498 100644 --- a/public/app/features/serviceaccounts/ServiceAccountPage.test.tsx +++ b/public/app/features/serviceaccounts/ServiceAccountPage.test.tsx @@ -23,14 +23,6 @@ const setup = (propOverrides: Partial) => { const updateServiceAccountMock = jest.fn(); const props: Props = { - navModel: { - main: { - text: 'Configuration', - }, - node: { - text: 'Service accounts', - }, - }, serviceAccount: {} as ServiceAccountDTO, tokens: [], builtInRoles: {}, diff --git a/public/app/features/serviceaccounts/ServiceAccountPage.tsx b/public/app/features/serviceaccounts/ServiceAccountPage.tsx index 82fed275471d7..75d4d9add8726 100644 --- a/public/app/features/serviceaccounts/ServiceAccountPage.tsx +++ b/public/app/features/serviceaccounts/ServiceAccountPage.tsx @@ -2,12 +2,11 @@ import { css } from '@emotion/css'; import React, { useEffect, useState } from 'react'; import { connect, ConnectedProps } from 'react-redux'; -import { getTimeZone, GrafanaTheme2, NavModel } from '@grafana/data'; +import { getTimeZone, GrafanaTheme2 } from '@grafana/data'; import { Button, ConfirmModal, IconButton, useStyles2 } from '@grafana/ui'; -import Page from 'app/core/components/Page/Page'; +import { Page } from 'app/core/components/Page/Page'; import { contextSrv } from 'app/core/core'; import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; -import { getNavModel } from 'app/core/selectors/navModel'; import { AccessControlAction, ApiKey, Role, ServiceAccountDTO, StoreState } from 'app/types'; import { CreateTokenModal, ServiceAccountToken } from './components/CreateTokenModal'; @@ -24,7 +23,6 @@ import { } from './state/actionsServiceAccountPage'; interface OwnProps extends GrafanaRouteComponentProps<{ id: string }> { - navModel: NavModel; serviceAccount?: ServiceAccountDTO; tokens: ApiKey[]; isLoading: boolean; @@ -34,7 +32,6 @@ interface OwnProps extends GrafanaRouteComponentProps<{ id: string }> { function mapStateToProps(state: StoreState) { return { - navModel: getNavModel(state.navIndex, 'serviceaccounts'), serviceAccount: state.serviceAccountProfile.serviceAccount, tokens: state.serviceAccountProfile.tokens, isLoading: state.serviceAccountProfile.isLoading, @@ -58,7 +55,6 @@ const connector = connect(mapStateToProps, mapDispatchToProps); export type Props = OwnProps & ConnectedProps; export const ServiceAccountPageUnconnected = ({ - navModel, match, serviceAccount, tokens, @@ -131,7 +127,7 @@ export const ServiceAccountPageUnconnected = ({ }; return ( - + {serviceAccount && (
    diff --git a/public/app/features/serviceaccounts/ServiceAccountsListPage.test.tsx b/public/app/features/serviceaccounts/ServiceAccountsListPage.test.tsx index e2222f4f62890..59a66ade42a66 100644 --- a/public/app/features/serviceaccounts/ServiceAccountsListPage.test.tsx +++ b/public/app/features/serviceaccounts/ServiceAccountsListPage.test.tsx @@ -26,14 +26,6 @@ const setup = (propOverrides: Partial) => { const getApiKeysMigrationInfoMock = jest.fn(); const closeApiKeysMigrationInfoMock = jest.fn(); const props: Props = { - navModel: { - main: { - text: 'Configuration', - }, - node: { - text: 'Service accounts', - }, - }, builtInRoles: {}, isLoading: false, page: 0, diff --git a/public/app/features/serviceaccounts/ServiceAccountsListPage.tsx b/public/app/features/serviceaccounts/ServiceAccountsListPage.tsx index 6043c9b5e8ec7..1d87856501a2d 100644 --- a/public/app/features/serviceaccounts/ServiceAccountsListPage.tsx +++ b/public/app/features/serviceaccounts/ServiceAccountsListPage.tsx @@ -6,10 +6,9 @@ import { connect, ConnectedProps } from 'react-redux'; import { GrafanaTheme2, OrgRole } from '@grafana/data'; import { Alert, ConfirmModal, FilterInput, Icon, LinkButton, RadioButtonGroup, Tooltip, useStyles2 } from '@grafana/ui'; import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA'; -import Page from 'app/core/components/Page/Page'; +import { Page } from 'app/core/components/Page/Page'; import PageLoader from 'app/core/components/PageLoader/PageLoader'; import { contextSrv } from 'app/core/core'; -import { getNavModel } from 'app/core/selectors/navModel'; import { StoreState, ServiceAccountDTO, AccessControlAction, ServiceAccountStateFilter } from 'app/types'; import { CreateTokenModal, ServiceAccountToken } from './components/CreateTokenModal'; @@ -33,7 +32,6 @@ export type Props = OwnProps & ConnectedProps; function mapStateToProps(state: StoreState) { return { - navModel: getNavModel(state.navIndex, 'serviceaccounts'), ...state.serviceAccounts, }; } @@ -54,7 +52,6 @@ const mapDispatchToProps = { const connector = connect(mapStateToProps, mapDispatchToProps); export const ServiceAccountsListPageUnconnected = ({ - navModel, serviceAccounts, isLoading, roleOptions, @@ -169,7 +166,7 @@ export const ServiceAccountsListPageUnconnected = ({ }; return ( - + {apiKeysMigrated && showApiKeysMigrationInfo && ( { jest.clearAllMocks(); @@ -14,23 +14,12 @@ const mockPost = jest.fn(() => { return Promise.resolve({}); }); -jest.mock('@grafana/runtime', () => ({ - getBackendSrv: () => { - return { - post: mockPost, - }; - }, - config: { - buildInfo: {}, - licenseInfo: {}, - }, -})); +setBackendSrv({ + post: mockPost, +} as any as BackendSrv); const setup = () => { - const props: Props = { - navModel: { node: {}, main: {} } as NavModel, - }; - return render(); + return render(); }; describe('Create team', () => { diff --git a/public/app/features/teams/CreateTeam.tsx b/public/app/features/teams/CreateTeam.tsx index 1c35b134e4bd1..b0fabfcef5f2c 100644 --- a/public/app/features/teams/CreateTeam.tsx +++ b/public/app/features/teams/CreateTeam.tsx @@ -1,24 +1,16 @@ import React, { PureComponent } from 'react'; -import { connect } from 'react-redux'; -import { NavModel } from '@grafana/data'; import { getBackendSrv, locationService } from '@grafana/runtime'; import { Button, Form, Field, Input, FieldSet } from '@grafana/ui'; -import Page from 'app/core/components/Page/Page'; +import { Page } from 'app/core/components/Page/Page'; import { contextSrv } from 'app/core/core'; -import { getNavModel } from 'app/core/selectors/navModel'; -import { StoreState } from 'app/types'; - -export interface Props { - navModel: NavModel; -} interface TeamDTO { name: string; email: string; } -export class CreateTeam extends PureComponent { +export class CreateTeam extends PureComponent { create = async (formModel: TeamDTO) => { const result = await getBackendSrv().post('/api/teams', formModel); if (result.teamId) { @@ -27,10 +19,8 @@ export class CreateTeam extends PureComponent { } }; render() { - const { navModel } = this.props; - return ( - + {({ register, errors }) => ( @@ -58,10 +48,4 @@ export class CreateTeam extends PureComponent { } } -function mapStateToProps(state: StoreState) { - return { - navModel: getNavModel(state.navIndex, 'teams'), - }; -} - -export default connect(mapStateToProps)(CreateTeam); +export default CreateTeam; diff --git a/public/app/features/teams/TeamList.test.tsx b/public/app/features/teams/TeamList.test.tsx index 9575b0c1ed079..e81bd56ef65c2 100644 --- a/public/app/features/teams/TeamList.test.tsx +++ b/public/app/features/teams/TeamList.test.tsx @@ -3,7 +3,6 @@ import userEvent from '@testing-library/user-event'; import React from 'react'; import { mockToolkitActionCreator } from 'test/core/redux/mocks'; -import { NavModel } from '@grafana/data'; import { contextSrv, User } from 'app/core/services/context_srv'; import { OrgRole, Team } from '../../types'; @@ -20,14 +19,6 @@ jest.mock('app/core/config', () => { const setup = (propOverrides?: object) => { const props: Props = { - navModel: { - main: { - text: 'Configuration', - }, - node: { - text: 'Team List', - }, - } as NavModel, teams: [] as Team[], loadTeams: jest.fn(), deleteTeam: jest.fn(), diff --git a/public/app/features/teams/TeamList.tsx b/public/app/features/teams/TeamList.tsx index 7f2cda0992b0d..e744fdd799379 100644 --- a/public/app/features/teams/TeamList.tsx +++ b/public/app/features/teams/TeamList.tsx @@ -1,13 +1,11 @@ import React, { PureComponent } from 'react'; -import { NavModel } from '@grafana/data'; import { DeleteButton, LinkButton, FilterInput, VerticalGroup, HorizontalGroup, Pagination } from '@grafana/ui'; import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA'; -import Page from 'app/core/components/Page/Page'; +import { Page } from 'app/core/components/Page/Page'; import { TeamRolePicker } from 'app/core/components/RolePicker/TeamRolePicker'; import { fetchRoleOptions } from 'app/core/components/RolePicker/api'; import { config } from 'app/core/config'; -import { getNavModel } from 'app/core/selectors/navModel'; import { contextSrv, User } from 'app/core/services/context_srv'; import { AccessControlAction, Role, StoreState, Team } from 'app/types'; @@ -20,7 +18,6 @@ import { getSearchQuery, getTeams, getTeamsCount, getTeamsSearchPage, isPermissi const pageLimit = 30; export interface Props { - navModel: NavModel; teams: Team[]; searchQuery: string; searchPage: number; @@ -224,10 +221,10 @@ export class TeamList extends PureComponent { } render() { - const { hasFetched, navModel } = this.props; + const { hasFetched } = this.props; return ( - + {this.renderList()} ); @@ -236,7 +233,6 @@ export class TeamList extends PureComponent { function mapStateToProps(state: StoreState) { return { - navModel: getNavModel(state.navIndex, 'teams'), teams: getTeams(state.teams), searchQuery: getSearchQuery(state.teams), searchPage: getTeamsSearchPage(state.teams), diff --git a/public/app/features/teams/TeamPages.test.tsx b/public/app/features/teams/TeamPages.test.tsx index 12d024e0fb94c..7184ec3931bd3 100644 --- a/public/app/features/teams/TeamPages.test.tsx +++ b/public/app/features/teams/TeamPages.test.tsx @@ -2,7 +2,7 @@ import { render, screen } from '@testing-library/react'; import React from 'react'; import { Provider } from 'react-redux'; -import { NavModel, createTheme } from '@grafana/data'; +import { createTheme } from '@grafana/data'; import { getRouteComponentProps } from 'app/core/navigation/__mocks__/routeProps'; import { User } from 'app/core/services/context_srv'; import { configureStore } from 'app/store/configureStore'; @@ -68,7 +68,7 @@ const setup = (propOverrides?: object) => { }, } as any, }), - navModel: { node: {}, main: {} } as NavModel, + pageNav: { text: 'Cool team ' }, teamId: 1, loadTeam: jest.fn(), loadTeamMembers: jest.fn(), diff --git a/public/app/features/teams/TeamPages.tsx b/public/app/features/teams/TeamPages.tsx index f72b7f0b53285..6c0968237c6fe 100644 --- a/public/app/features/teams/TeamPages.tsx +++ b/public/app/features/teams/TeamPages.tsx @@ -2,10 +2,10 @@ import { includes } from 'lodash'; import React, { PureComponent } from 'react'; import { connect, ConnectedProps } from 'react-redux'; -import { NavModel } from '@grafana/data'; +import { NavModelItem } from '@grafana/data'; import { featureEnabled } from '@grafana/runtime'; import { Themeable2, withTheme2 } from '@grafana/ui'; -import Page from 'app/core/components/Page/Page'; +import { Page } from 'app/core/components/Page/Page'; import { UpgradeBox } from 'app/core/components/Upgrade/UpgradeBox'; import config from 'app/core/config'; import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; @@ -51,11 +51,11 @@ function mapStateToProps(state: StoreState, props: OwnProps) { } const pageName = props.match.params.page ?? defaultPage; const teamLoadingNav = getTeamLoadingNav(pageName as string); - const navModel = getNavModel(state.navIndex, `team-${pageName}-${teamId}`, teamLoadingNav); + const pageNav = getNavModel(state.navIndex, `team-${pageName}-${teamId}`, teamLoadingNav).main; const members = getTeamMembers(state.team); return { - navModel, + pageNav, teamId: teamId, pageName: pageName, team, @@ -118,20 +118,20 @@ export class TeamPages extends PureComponent { return text1.toLocaleLowerCase() === text2.toLocaleLowerCase(); }; - hideTabsFromNonTeamAdmin = (navModel: NavModel, isSignedInUserTeamAdmin: boolean) => { + hideTabsFromNonTeamAdmin = (pageNav: NavModelItem, isSignedInUserTeamAdmin: boolean) => { if (contextSrv.accessControlEnabled()) { - return navModel; + return pageNav; } - if (!isSignedInUserTeamAdmin && navModel.main && navModel.main.children) { - navModel.main.children + if (!isSignedInUserTeamAdmin && pageNav && pageNav.children) { + pageNav.children .filter((navItem) => !this.textsAreEqual(navItem.text, PageTypes.Members)) .map((navItem) => { navItem.hideFromTabs = true; }); } - return navModel; + return pageNav; }; renderPage(isSignedInUserTeamAdmin: boolean): React.ReactNode { @@ -183,11 +183,11 @@ export class TeamPages extends PureComponent { } render() { - const { team, navModel, members, editorsCanAdmin, signedInUser } = this.props; + const { team, pageNav, members, editorsCanAdmin, signedInUser } = this.props; const isTeamAdmin = isSignedInUserTeamAdmin({ members, editorsCanAdmin, signedInUser }); return ( - + {team && Object.keys(team).length !== 0 && this.renderPage(isTeamAdmin)} diff --git a/public/app/features/teams/TeamPermissions.tsx b/public/app/features/teams/TeamPermissions.tsx index 51a17bc388ed8..2d4d23386e8a1 100644 --- a/public/app/features/teams/TeamPermissions.tsx +++ b/public/app/features/teams/TeamPermissions.tsx @@ -18,7 +18,7 @@ const TeamPermissions = (props: TeamPermissionsProps) => { return ( ({ const setup = (propOverrides?: object) => { const store = configureStore(); const props: Props = { - navModel: { - main: { - text: 'Configuration', - }, - node: { - text: 'Users', - }, - } as NavModel, users: [] as OrgUser[], invitees: [] as Invitee[], searchQuery: '', diff --git a/public/app/features/users/UsersListPage.tsx b/public/app/features/users/UsersListPage.tsx index 6205a86a7c193..7cab296441d39 100644 --- a/public/app/features/users/UsersListPage.tsx +++ b/public/app/features/users/UsersListPage.tsx @@ -3,9 +3,8 @@ import { connect, ConnectedProps } from 'react-redux'; import { renderMarkdown } from '@grafana/data'; import { HorizontalGroup, Pagination, VerticalGroup } from '@grafana/ui'; -import Page from 'app/core/components/Page/Page'; +import { Page } from 'app/core/components/Page/Page'; import { contextSrv } from 'app/core/core'; -import { getNavModel } from 'app/core/selectors/navModel'; import { OrgUser, OrgRole, StoreState } from 'app/types'; import InviteesTable from '../invites/InviteesTable'; @@ -21,7 +20,6 @@ import { getUsers, getUsersSearchQuery, getUsersSearchPage } from './state/selec function mapStateToProps(state: StoreState) { const searchQuery = getUsersSearchQuery(state.users); return { - navModel: getNavModel(state.navIndex, 'users'), users: getUsers(state.users), searchQuery: getUsersSearchQuery(state.users), searchPage: getUsersSearchPage(state.users), @@ -125,11 +123,11 @@ export class UsersListPage extends PureComponent { } render() { - const { navModel, hasFetched } = this.props; + const { hasFetched } = this.props; const externalUserMngInfoHtml = this.externalUserMngInfoHtml; return ( - + <> diff --git a/public/app/routes/routes.tsx b/public/app/routes/routes.tsx index c3a59ffeb045d..9344f6297b2e1 100644 --- a/public/app/routes/routes.tsx +++ b/public/app/routes/routes.tsx @@ -162,6 +162,7 @@ export function getAppRoutes(): RouteDescriptor[] { { path: '/explore', pageClass: 'page-explore', + navId: 'explore', roles: () => contextSrv.evaluatePermission( () => (config.viewersCanEdit ? [] : ['Editor', 'Admin']), @@ -183,12 +184,14 @@ export function getAppRoutes(): RouteDescriptor[] { }, { path: '/org', + navId: 'org-settings', component: SafeDynamicImport( () => import(/* webpackChunkName: "OrgDetailsPage" */ '../features/org/OrgDetailsPage') ), }, { path: '/org/new', + navId: 'global-orgs', component: SafeDynamicImport(() => import(/* webpackChunkName: "NewOrgPage" */ 'app/features/org/NewOrgPage')), }, { @@ -214,6 +217,7 @@ export function getAppRoutes(): RouteDescriptor[] { }, { path: '/org/serviceaccounts', + navId: 'serviceaccounts', roles: () => contextSrv.evaluatePermission(() => ['Admin'], [AccessControlAction.ServiceAccountsRead]), component: SafeDynamicImport( () => @@ -222,6 +226,7 @@ export function getAppRoutes(): RouteDescriptor[] { }, { path: '/org/serviceaccounts/create', + navId: 'serviceaccounts', component: SafeDynamicImport( () => import( @@ -231,6 +236,7 @@ export function getAppRoutes(): RouteDescriptor[] { }, { path: '/org/serviceaccounts/:id', + navId: 'serviceaccounts', component: ServiceAccountPage, }, { @@ -245,6 +251,7 @@ export function getAppRoutes(): RouteDescriptor[] { }, { path: '/org/teams/new', + navId: 'teams', roles: () => contextSrv.evaluatePermission( () => (config.editorsCanAdmin ? ['Editor', 'Admin'] : ['Admin']), @@ -254,6 +261,7 @@ export function getAppRoutes(): RouteDescriptor[] { }, { path: '/org/teams/edit/:id/:page?', + navId: 'teams', roles: () => contextSrv.evaluatePermission( () => (config.editorsCanAdmin ? ['Editor', 'Admin'] : ['Admin']), @@ -270,6 +278,7 @@ export function getAppRoutes(): RouteDescriptor[] { }, { path: '/admin/settings', + navId: 'server-settings', component: SafeDynamicImport( () => import(/* webpackChunkName: "AdminSettings" */ 'app/features/admin/AdminSettings') ),