diff --git a/.betterer.results b/.betterer.results index 4595a7489a6..40fec2dce99 100644 --- a/.betterer.results +++ b/.betterer.results @@ -2686,8 +2686,7 @@ exports[`better eslint`] = { [0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "0"] ], "public/app/features/datasources/components/ButtonRow.tsx:5381": [ - [0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "0"], - [0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "1"] + [0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "0"] ], "public/app/features/datasources/components/DataSourceReadOnlyMessage.tsx:5381": [ [0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "0"] diff --git a/public/app/app.ts b/public/app/app.ts index 8a8422f743d..1ea3967a202 100644 --- a/public/app/app.ts +++ b/public/app/app.ts @@ -51,7 +51,7 @@ import { AppWrapper } from './AppWrapper'; import appEvents from './core/app_events'; import { AppChromeService } from './core/components/AppChrome/AppChromeService'; import { getAllOptionEditors, getAllStandardFieldConfigs } from './core/components/OptionsUI/registry'; -import { PluginPage } from './core/components/PageNew/PluginPage'; +import { PluginPage } from './core/components/Page/PluginPage'; import { GrafanaContextType } from './core/context/GrafanaContext'; import { initializeI18n } from './core/internationalization'; import { interceptLinkClicks } from './core/navigation/patch/interceptLinkClicks'; diff --git a/public/app/core/components/AppChrome/AppChrome.test.tsx b/public/app/core/components/AppChrome/AppChrome.test.tsx index 35ed48715a5..92390c093f8 100644 --- a/public/app/core/components/AppChrome/AppChrome.test.tsx +++ b/public/app/core/components/AppChrome/AppChrome.test.tsx @@ -9,7 +9,7 @@ import { config } from '@grafana/runtime'; import { HOME_NAV_ID } from 'app/core/reducers/navModel'; import { DashboardQueryResult, getGrafanaSearcher, QueryResponse } from 'app/features/search/service'; -import { Page } from '../PageNew/Page'; +import { Page } from '../Page/Page'; import { AppChrome } from './AppChrome'; diff --git a/public/app/core/components/Page/Page.test.tsx b/public/app/core/components/Page/Page.test.tsx index afa5e7b99c8..e7962be5cf1 100644 --- a/public/app/core/components/Page/Page.test.tsx +++ b/public/app/core/components/Page/Page.test.tsx @@ -1,23 +1,28 @@ import { render, screen } from '@testing-library/react'; import React from 'react'; import { TestProvider } from 'test/helpers/TestProvider'; +import { getGrafanaContextMock } from 'test/mocks/getGrafanaContextMock'; -import { NavModelItem } from '@grafana/data'; +import { NavModelItem, PageLayoutType } from '@grafana/data'; import { config } from '@grafana/runtime'; +import { HOME_NAV_ID } from 'app/core/reducers/navModel'; import { Page } from './Page'; import { PageProps } from './types'; const pageNav: NavModelItem = { - text: 'Main title', + text: 'pageNav title', children: [ - { text: 'Child1', url: '1', active: true }, - { text: 'Child2', url: '2' }, + { text: 'pageNav child1', url: '1', active: true }, + { text: 'pageNav child2', url: '2' }, ], }; - const setup = (props: Partial) => { config.bootData.navTree = [ + { + id: HOME_NAV_ID, + text: 'Home', + }, { text: 'Section name', id: 'section', @@ -29,13 +34,17 @@ const setup = (props: Partial) => { }, ]; - return render( - + const context = getGrafanaContextMock(); + + const renderResult = render( +
Children
); + + return { renderResult, context }; }; describe('Render', () => { @@ -51,14 +60,25 @@ describe('Render', () => { it('should render header when pageNav supplied', async () => { setup({ pageNav }); - expect(screen.getByRole('heading', { name: 'Main title' })).toBeInTheDocument(); + expect(screen.getByRole('heading', { name: 'pageNav title' })).toBeInTheDocument(); expect(screen.getAllByRole('tab').length).toBe(2); }); - it('should get header nav model from redux navIndex', async () => { - setup({ navId: 'child1' }); + it('should update chrome with section, pageNav and layout', async () => { + const { context } = setup({ navId: 'child1', pageNav, layout: PageLayoutType.Canvas }); + expect(context.chrome.state.getValue().sectionNav.node.id).toBe('child1'); + expect(context.chrome.state.getValue().pageNav).toBe(pageNav); + expect(context.chrome.state.getValue().layout).toBe(PageLayoutType.Canvas); + }); - expect(screen.getByRole('heading', { name: 'Section name' })).toBeInTheDocument(); - expect(screen.getAllByRole('tab').length).toBe(2); + it('should update document title', async () => { + setup({ navId: 'child1', pageNav }); + expect(document.title).toBe('pageNav title - Child1 - Section name - Grafana'); + }); + + it('should not include hideFromBreadcrumb nodes in title', async () => { + pageNav.children![0].hideFromBreadcrumbs = true; + setup({ navId: 'child1', pageNav }); + expect(document.title).toBe('pageNav title - Child1 - Section name - Grafana'); }); }); diff --git a/public/app/core/components/Page/Page.tsx b/public/app/core/components/Page/Page.tsx index 9d5b594809b..df666b44ed9 100644 --- a/public/app/core/components/Page/Page.tsx +++ b/public/app/core/components/Page/Page.tsx @@ -1,35 +1,32 @@ // Libraries import { css, cx } from '@emotion/css'; -import React, { useEffect } from 'react'; +import React, { useLayoutEffect } from 'react'; import { GrafanaTheme2, PageLayoutType } from '@grafana/data'; -import { config } from '@grafana/runtime'; import { CustomScrollbar, useStyles2 } from '@grafana/ui'; import { useGrafana } from 'app/core/context/GrafanaContext'; -import { Footer } from '../Footer/Footer'; -import { PageHeader } from '../PageHeader/PageHeader'; -import { Page as NewPage } from '../PageNew/Page'; - import { PageContents } from './PageContents'; +import { PageHeader } from './PageHeader'; +import { PageTabs } from './PageTabs'; import { PageType } from './types'; import { usePageNav } from './usePageNav'; import { usePageTitle } from './usePageTitle'; -export const OldPage: PageType = ({ +export const Page: PageType = ({ navId, navModel: oldNavProp, pageNav, + renderTitle, + actions, + subTitle, children, className, - toolbar, - scrollRef, - scrollTop, - layout = PageLayoutType.Standard, - renderTitle, - subTitle, - actions, info, + layout = PageLayoutType.Standard, + toolbar, + scrollTop, + scrollRef, ...otherProps }) => { const styles = useStyles2(getStyles); @@ -38,48 +35,46 @@ export const OldPage: PageType = ({ usePageTitle(navModel, pageNav); - const pageHeaderNav = pageNav ?? navModel?.main; + const pageHeaderNav = pageNav ?? navModel?.node; - useEffect(() => { + // We use useLayoutEffect here to make sure that the chrome is updated before the page is rendered + // This prevents flickering sectionNav when going from dashbaord to settings for example + useLayoutEffect(() => { if (navModel) { - // This is needed for chrome to update it's chromeless state chrome.update({ sectionNav: navModel, + pageNav: pageNav, + layout: layout, }); - } else { - // Need to trigger a chrome state update for the route change to be processed - chrome.update({}); } - }, [navModel, chrome]); + }, [navModel, pageNav, chrome, layout]); return (
{layout === PageLayoutType.Standard && ( -
+
{pageHeaderNav && ( )} - {children} -
+ {pageNav && pageNav.children && } +
{children}
)} {layout === PageLayoutType.Canvas && ( - <> - {toolbar} -
- -
{children}
-
+ +
+ {toolbar} + {children}
- +
)} {layout === PageLayoutType.Custom && ( <> @@ -91,33 +86,46 @@ export const OldPage: PageType = ({ ); }; -OldPage.Contents = PageContents; +Page.Contents = PageContents; -export const Page: PageType = config.featureToggles.topnav ? NewPage : OldPage; +const getStyles = (theme: GrafanaTheme2) => { + return { + wrapper: css({ + label: 'page-wrapper', + height: '100%', + display: 'flex', + flex: '1 1 0', + flexDirection: 'column', + minHeight: 0, + }), + pageContent: css({ + label: 'page-content', + flexGrow: 1, + }), + pageInner: css({ + label: 'page-inner', + padding: theme.spacing(2), + borderRadius: theme.shape.borderRadius(1), + border: `1px solid ${theme.colors.border.weak}`, + borderBottom: 'none', + background: theme.colors.background.primary, + display: 'flex', + flexDirection: 'column', + flexGrow: 1, + margin: theme.spacing(0, 0, 0, 0), -const getStyles = (theme: GrafanaTheme2) => ({ - wrapper: css({ - width: '100%', - height: '100%', - display: 'flex', - flex: '1 1 0', - flexDirection: 'column', - minHeight: 0, - }), - scrollWrapper: css({ - width: '100%', - flexGrow: 1, - minHeight: 0, - display: 'flex', - }), - content: css({ - display: 'flex', - flexDirection: 'column', - padding: theme.spacing(0, 2, 2, 2), - flexBasis: '100%', - flexGrow: 1, - }), - contentWithoutToolbar: css({ - padding: theme.spacing(2), - }), -}); + [theme.breakpoints.up('md')]: { + margin: theme.spacing(2, 2, 0, 1), + padding: theme.spacing(3), + }, + }), + canvasContent: css({ + label: 'canvas-content', + display: 'flex', + flexDirection: 'column', + padding: theme.spacing(2), + flexBasis: '100%', + flexGrow: 1, + }), + }; +}; diff --git a/public/app/core/components/Page/PageContents.tsx b/public/app/core/components/Page/PageContents.tsx index 55ec042e789..1246443139a 100644 --- a/public/app/core/components/Page/PageContents.tsx +++ b/public/app/core/components/Page/PageContents.tsx @@ -1,8 +1,6 @@ // Libraries -import { cx } from '@emotion/css'; import React from 'react'; -// Components import PageLoader from '../PageLoader/PageLoader'; interface Props { @@ -12,5 +10,7 @@ interface Props { } export const PageContents = ({ isLoading, children, className }: Props) => { - return
{isLoading ? : children}
; + let content = className ?
{children}
: children; + + return <>{isLoading ? : content}; }; diff --git a/public/app/core/components/PageNew/PageHeader.tsx b/public/app/core/components/Page/PageHeader.tsx similarity index 98% rename from public/app/core/components/PageNew/PageHeader.tsx rename to public/app/core/components/Page/PageHeader.tsx index 55fcdd552d4..7e3b7aa3e8e 100644 --- a/public/app/core/components/PageNew/PageHeader.tsx +++ b/public/app/core/components/Page/PageHeader.tsx @@ -4,9 +4,10 @@ import React from 'react'; import { NavModelItem, GrafanaTheme2 } from '@grafana/data'; import { useStyles2 } from '@grafana/ui'; -import { PageInfoItem } from '../Page/types'; import { PageInfo } from '../PageInfo/PageInfo'; +import { PageInfoItem } from './types'; + export interface Props { navItem: NavModelItem; renderTitle?: (title: string) => React.ReactNode; diff --git a/public/app/core/components/PageNew/PageTabs.tsx b/public/app/core/components/Page/PageTabs.tsx similarity index 100% rename from public/app/core/components/PageNew/PageTabs.tsx rename to public/app/core/components/Page/PageTabs.tsx diff --git a/public/app/core/components/PageNew/PluginPage.tsx b/public/app/core/components/Page/PluginPage.tsx similarity index 94% rename from public/app/core/components/PageNew/PluginPage.tsx rename to public/app/core/components/Page/PluginPage.tsx index 2872786852a..694c6729fa5 100644 --- a/public/app/core/components/PageNew/PluginPage.tsx +++ b/public/app/core/components/Page/PluginPage.tsx @@ -3,7 +3,7 @@ import React, { useContext } from 'react'; import { PluginPageProps } from '@grafana/runtime'; import { PluginPageContext } from 'app/features/plugins/components/PluginPageContext'; -import { Page } from '../Page/Page'; +import { Page } from './Page'; export function PluginPage({ actions, children, info, pageNav, layout, renderTitle, subTitle }: PluginPageProps) { const context = useContext(PluginPageContext); diff --git a/public/app/core/components/PageNew/Page.test.tsx b/public/app/core/components/PageNew/Page.test.tsx deleted file mode 100644 index 53068a201de..00000000000 --- a/public/app/core/components/PageNew/Page.test.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import { render, screen } from '@testing-library/react'; -import React from 'react'; -import { TestProvider } from 'test/helpers/TestProvider'; -import { getGrafanaContextMock } from 'test/mocks/getGrafanaContextMock'; - -import { NavModelItem, PageLayoutType } from '@grafana/data'; -import { config } from '@grafana/runtime'; -import { HOME_NAV_ID } from 'app/core/reducers/navModel'; - -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 = [ - { - id: HOME_NAV_ID, - text: 'Home', - }, - { - text: 'Section name', - id: 'section', - url: 'section', - children: [ - { text: 'Child1', id: 'child1', url: 'section/child1' }, - { text: 'Child2', id: 'child2', url: 'section/child2' }, - ], - }, - ]; - - const context = getGrafanaContextMock(); - - const renderResult = render( - - -
Children
-
-
- ); - - return { renderResult, context }; -}; - -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 update chrome with section, pageNav and layout', async () => { - const { context } = setup({ navId: 'child1', pageNav, layout: PageLayoutType.Canvas }); - expect(context.chrome.state.getValue().sectionNav.node.id).toBe('child1'); - expect(context.chrome.state.getValue().pageNav).toBe(pageNav); - expect(context.chrome.state.getValue().layout).toBe(PageLayoutType.Canvas); - }); - - it('should update document title', async () => { - setup({ navId: 'child1', pageNav }); - expect(document.title).toBe('pageNav title - Child1 - Section name - Grafana'); - }); - - it('should not include hideFromBreadcrumb nodes in title', async () => { - pageNav.children![0].hideFromBreadcrumbs = true; - setup({ navId: 'child1', pageNav }); - expect(document.title).toBe('pageNav title - Child1 - Section name - Grafana'); - }); -}); diff --git a/public/app/core/components/PageNew/Page.tsx b/public/app/core/components/PageNew/Page.tsx deleted file mode 100644 index fd8e3c5f65e..00000000000 --- a/public/app/core/components/PageNew/Page.tsx +++ /dev/null @@ -1,132 +0,0 @@ -// Libraries -import { css, cx } from '@emotion/css'; -import React, { useLayoutEffect } from 'react'; - -import { GrafanaTheme2, PageLayoutType } from '@grafana/data'; -import { CustomScrollbar, useStyles2 } from '@grafana/ui'; -import { useGrafana } from 'app/core/context/GrafanaContext'; - -import { PageType } from '../Page/types'; -import { usePageNav } from '../Page/usePageNav'; -import { usePageTitle } from '../Page/usePageTitle'; - -import { PageContents } from './PageContents'; -import { PageHeader } from './PageHeader'; -import { PageTabs } from './PageTabs'; - -export const Page: PageType = ({ - navId, - navModel: oldNavProp, - pageNav, - renderTitle, - actions, - subTitle, - children, - className, - info, - layout = PageLayoutType.Standard, - toolbar, - scrollTop, - scrollRef, - ...otherProps -}) => { - const styles = useStyles2(getStyles); - const navModel = usePageNav(navId, oldNavProp); - const { chrome } = useGrafana(); - - usePageTitle(navModel, pageNav); - - const pageHeaderNav = pageNav ?? navModel?.node; - - // We use useLayoutEffect here to make sure that the chrome is updated before the page is rendered - // This prevents flickering sectionNav when going from dashbaord to settings for example - useLayoutEffect(() => { - if (navModel) { - chrome.update({ - sectionNav: navModel, - pageNav: pageNav, - layout: layout, - }); - } - }, [navModel, pageNav, chrome, layout]); - - return ( -
- {layout === PageLayoutType.Standard && ( - -
- {pageHeaderNav && ( - - )} - {pageNav && pageNav.children && } -
{children}
-
-
- )} - {layout === PageLayoutType.Canvas && ( - -
- {toolbar} - {children} -
-
- )} - {layout === PageLayoutType.Custom && ( - <> - {toolbar} - {children} - - )} -
- ); -}; - -Page.Contents = PageContents; - -const getStyles = (theme: GrafanaTheme2) => { - return { - wrapper: css({ - label: 'page-wrapper', - height: '100%', - display: 'flex', - flex: '1 1 0', - flexDirection: 'column', - minHeight: 0, - }), - pageContent: css({ - label: 'page-content', - flexGrow: 1, - }), - pageInner: css({ - label: 'page-inner', - padding: theme.spacing(2), - borderRadius: theme.shape.borderRadius(1), - border: `1px solid ${theme.colors.border.weak}`, - borderBottom: 'none', - background: theme.colors.background.primary, - display: 'flex', - flexDirection: 'column', - flexGrow: 1, - margin: theme.spacing(0, 0, 0, 0), - - [theme.breakpoints.up('md')]: { - margin: theme.spacing(2, 2, 0, 1), - padding: theme.spacing(3), - }, - }), - canvasContent: css({ - label: 'canvas-content', - display: 'flex', - flexDirection: 'column', - padding: theme.spacing(2), - flexBasis: '100%', - flexGrow: 1, - }), - }; -}; diff --git a/public/app/core/components/PageNew/PageContents.tsx b/public/app/core/components/PageNew/PageContents.tsx deleted file mode 100644 index 1246443139a..00000000000 --- a/public/app/core/components/PageNew/PageContents.tsx +++ /dev/null @@ -1,16 +0,0 @@ -// Libraries -import React from 'react'; - -import PageLoader from '../PageLoader/PageLoader'; - -interface Props { - isLoading?: boolean; - children: React.ReactNode; - className?: string; -} - -export const PageContents = ({ isLoading, children, className }: Props) => { - let content = className ?
{children}
: children; - - return <>{isLoading ? : content}; -}; diff --git a/public/app/features/connections/pages/EditDataSourcePage.tsx b/public/app/features/connections/pages/EditDataSourcePage.tsx index 2f66e87f6db..b27417e66ec 100644 --- a/public/app/features/connections/pages/EditDataSourcePage.tsx +++ b/public/app/features/connections/pages/EditDataSourcePage.tsx @@ -1,7 +1,6 @@ import * as React from 'react'; import { useLocation, useParams } from 'react-router-dom'; -import { config } from '@grafana/runtime'; import { Page } from 'app/core/components/Page/Page'; import { EditDataSource } from 'app/features/datasources/components/EditDataSource'; import { EditDataSourceActions } from 'app/features/datasources/components/EditDataSourceActions'; @@ -16,11 +15,7 @@ export function EditDataSourcePage() { const { navId, pageNav } = useDataSourceSettingsNav(); return ( - : undefined} - > + }> diff --git a/public/app/features/dashboard/components/DashboardPermissions/AccessControlDashboardPermissions.tsx b/public/app/features/dashboard/components/DashboardPermissions/AccessControlDashboardPermissions.tsx index 7fd8611b5e9..a06413a1338 100644 --- a/public/app/features/dashboard/components/DashboardPermissions/AccessControlDashboardPermissions.tsx +++ b/public/app/features/dashboard/components/DashboardPermissions/AccessControlDashboardPermissions.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { Permissions } from 'app/core/components/AccessControl'; -import { Page } from 'app/core/components/PageNew/Page'; +import { Page } from 'app/core/components/Page/Page'; import { contextSrv } from 'app/core/core'; import { AccessControlAction } from 'app/types'; diff --git a/public/app/features/dashboard/components/DashboardPermissions/DashboardPermissions.tsx b/public/app/features/dashboard/components/DashboardPermissions/DashboardPermissions.tsx index 3c6cd2ff0b0..89ba108750c 100644 --- a/public/app/features/dashboard/components/DashboardPermissions/DashboardPermissions.tsx +++ b/public/app/features/dashboard/components/DashboardPermissions/DashboardPermissions.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/PageNew/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/dashboard/components/DashboardSettings/AnnotationsSettings.tsx b/public/app/features/dashboard/components/DashboardSettings/AnnotationsSettings.tsx index 0c6157c976c..a4db2c16c52 100644 --- a/public/app/features/dashboard/components/DashboardSettings/AnnotationsSettings.tsx +++ b/public/app/features/dashboard/components/DashboardSettings/AnnotationsSettings.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { AnnotationQuery, getDataSourceRef, NavModelItem } from '@grafana/data'; import { getDataSourceSrv, locationService } from '@grafana/runtime'; -import { Page } from 'app/core/components/PageNew/Page'; +import { Page } from 'app/core/components/Page/Page'; import { DashboardModel } from '../../state'; import { AnnotationSettingsEdit, AnnotationSettingsList, newAnnotationName } from '../AnnotationSettings'; diff --git a/public/app/features/dashboard/components/DashboardSettings/DashboardSettings.tsx b/public/app/features/dashboard/components/DashboardSettings/DashboardSettings.tsx index 8ba6072b32c..624230aefd8 100644 --- a/public/app/features/dashboard/components/DashboardSettings/DashboardSettings.tsx +++ b/public/app/features/dashboard/components/DashboardSettings/DashboardSettings.tsx @@ -7,7 +7,7 @@ import { selectors } from '@grafana/e2e-selectors'; import { locationService } from '@grafana/runtime'; import { Button, ToolbarButtonRow } from '@grafana/ui'; import { AppChromeUpdate } from 'app/core/components/AppChrome/AppChromeUpdate'; -import { Page } from 'app/core/components/PageNew/Page'; +import { Page } from 'app/core/components/Page/Page'; import config from 'app/core/config'; import { contextSrv } from 'app/core/services/context_srv'; import { AccessControlAction } from 'app/types'; diff --git a/public/app/features/dashboard/components/DashboardSettings/GeneralSettings.tsx b/public/app/features/dashboard/components/DashboardSettings/GeneralSettings.tsx index 7e16893f6bc..c1f7b4697bf 100644 --- a/public/app/features/dashboard/components/DashboardSettings/GeneralSettings.tsx +++ b/public/app/features/dashboard/components/DashboardSettings/GeneralSettings.tsx @@ -3,7 +3,7 @@ import { connect, ConnectedProps } from 'react-redux'; import { TimeZone } from '@grafana/data'; import { CollapsableSection, Field, Input, RadioButtonGroup, TagsInput } from '@grafana/ui'; -import { Page } from 'app/core/components/PageNew/Page'; +import { Page } from 'app/core/components/Page/Page'; import { FolderPicker } from 'app/core/components/Select/FolderPicker'; import { updateTimeZoneDashboard, updateWeekStartDashboard } from 'app/features/dashboard/state/actions'; diff --git a/public/app/features/dashboard/components/DashboardSettings/JsonEditorSettings.tsx b/public/app/features/dashboard/components/DashboardSettings/JsonEditorSettings.tsx index 2f864ee0e30..9d0920a677e 100644 --- a/public/app/features/dashboard/components/DashboardSettings/JsonEditorSettings.tsx +++ b/public/app/features/dashboard/components/DashboardSettings/JsonEditorSettings.tsx @@ -3,7 +3,7 @@ import React, { useState } from 'react'; import { GrafanaTheme2 } from '@grafana/data'; import { Button, CodeEditor, useStyles2 } from '@grafana/ui'; -import { Page } from 'app/core/components/PageNew/Page'; +import { Page } from 'app/core/components/Page/Page'; import { dashboardWatcher } from 'app/features/live/dashboard/dashboardWatcher'; import { getDashboardSrv } from '../../services/DashboardSrv'; diff --git a/public/app/features/dashboard/components/DashboardSettings/LinksSettings.tsx b/public/app/features/dashboard/components/DashboardSettings/LinksSettings.tsx index d550f9789c1..10cd657c92f 100644 --- a/public/app/features/dashboard/components/DashboardSettings/LinksSettings.tsx +++ b/public/app/features/dashboard/components/DashboardSettings/LinksSettings.tsx @@ -2,7 +2,7 @@ import React, { useState } from 'react'; import { NavModelItem } from '@grafana/data'; import { locationService } from '@grafana/runtime'; -import { Page } from 'app/core/components/PageNew/Page'; +import { Page } from 'app/core/components/Page/Page'; import { LinkSettingsEdit, LinkSettingsList } from '../LinksSettings'; import { newLink } from '../LinksSettings/LinkSettingsEdit'; diff --git a/public/app/features/dashboard/components/DashboardSettings/VersionsSettings.tsx b/public/app/features/dashboard/components/DashboardSettings/VersionsSettings.tsx index 5068bc95b77..8af74edb60f 100644 --- a/public/app/features/dashboard/components/DashboardSettings/VersionsSettings.tsx +++ b/public/app/features/dashboard/components/DashboardSettings/VersionsSettings.tsx @@ -1,7 +1,7 @@ import React, { PureComponent } from 'react'; import { Spinner, HorizontalGroup } from '@grafana/ui'; -import { Page } from 'app/core/components/PageNew/Page'; +import { Page } from 'app/core/components/Page/Page'; import { historySrv, diff --git a/public/app/features/dashboard/containers/DashboardPage.test.tsx b/public/app/features/dashboard/containers/DashboardPage.test.tsx index d2abb8f84f1..0fe6cfef4a9 100644 --- a/public/app/features/dashboard/containers/DashboardPage.test.tsx +++ b/public/app/features/dashboard/containers/DashboardPage.test.tsx @@ -1,10 +1,12 @@ import { render, screen, waitFor } from '@testing-library/react'; +import { KBarProvider } from 'kbar'; import React from 'react'; import { Provider } from 'react-redux'; import { match, Router } from 'react-router-dom'; import { useEffectOnce } from 'react-use'; import { AutoSizerProps } from 'react-virtualized-auto-sizer'; import { mockToolkitActionCreator } from 'test/core/redux/mocks'; +import { TestProvider } from 'test/helpers/TestProvider'; import { getGrafanaContextMock } from 'test/mocks/getGrafanaContextMock'; import { createTheme } from '@grafana/data'; @@ -12,6 +14,7 @@ import { selectors } from '@grafana/e2e-selectors'; import { config, locationService, setDataSourceSrv } from '@grafana/runtime'; import { Dashboard } from '@grafana/schema'; import { notifyApp } from 'app/core/actions'; +import { AppChrome } from 'app/core/components/AppChrome/AppChrome'; import { GrafanaContext } from 'app/core/context/GrafanaContext'; import { getRouteComponentProps } from 'app/core/navigation/__mocks__/routeProps'; import { RouteDescriptor } from 'app/core/navigation/types'; @@ -58,6 +61,9 @@ jest.mock('app/core/core', () => ({ return { unsubscribe: () => {} }; }, }, + contextSrv: { + user: { orgId: 1 }, + }, })); jest.mock('react-virtualized-auto-sizer', () => { @@ -92,6 +98,10 @@ function setup(propOverrides?: Partial) { config.bootData.navTree = [ { text: 'Dashboards', id: 'dashboards/browse' }, { text: 'Home', id: HOME_NAV_ID }, + { + text: 'Help', + id: 'help', + }, ]; const store = configureStore(); @@ -177,6 +187,45 @@ describe('DashboardPage', () => { expect(document.title).toBe('My dashboard - Dashboards - Grafana'); }); }); + + it('only calls initDashboard once when wrapped in AppChrome', async () => { + const props: Props = { + ...getRouteComponentProps({ + match: { params: { slug: 'my-dash', uid: '11' } } as unknown as match, + route: { routeName: DashboardRoutes.Normal } as RouteDescriptor, + }), + navIndex: { + 'dashboards/browse': { + text: 'Dashboards', + id: 'dashboards/browse', + parentItem: { text: 'Home', id: HOME_NAV_ID }, + }, + [HOME_NAV_ID]: { text: 'Home', id: HOME_NAV_ID }, + }, + initPhase: DashboardInitPhase.Completed, + initError: null, + initDashboard: mockInitDashboard, + notifyApp: mockToolkitActionCreator(notifyApp), + cleanUpDashboardAndVariables: mockCleanUpDashboardAndVariables, + cancelVariables: jest.fn(), + templateVarsChangedInUrl: jest.fn(), + dashboard: getTestDashboard(), + theme: createTheme(), + }; + + render( + + + + + + + + ); + + await screen.findByText('My dashboard'); + expect(mockInitDashboard).toHaveBeenCalledTimes(1); + }); }); describe('When going into view mode', () => { diff --git a/public/app/features/datasources/components/ButtonRow.test.tsx b/public/app/features/datasources/components/ButtonRow.test.tsx index dc2208f5092..0d9147452bd 100644 --- a/public/app/features/datasources/components/ButtonRow.test.tsx +++ b/public/app/features/datasources/components/ButtonRow.test.tsx @@ -8,9 +8,7 @@ import { ButtonRow, Props } from './ButtonRow'; const setup = (propOverrides?: object) => { const props: Props = { canSave: false, - canDelete: false, onSubmit: jest.fn(), - onDelete: jest.fn(), onTest: jest.fn(), exploreUrl: '/explore', }; @@ -24,7 +22,7 @@ describe('', () => { it('should render component', () => { setup(); - expect(screen.getByRole('button', { name: selectors.pages.DataSource.delete })).toBeInTheDocument(); + expect(screen.getByRole('link', { name: 'Explore' })).toBeInTheDocument(); }); it('should render save & test', () => { setup({ canSave: true }); diff --git a/public/app/features/datasources/components/ButtonRow.tsx b/public/app/features/datasources/components/ButtonRow.tsx index d1c40f885be..9d581bd8cc4 100644 --- a/public/app/features/datasources/components/ButtonRow.tsx +++ b/public/app/features/datasources/components/ButtonRow.tsx @@ -1,7 +1,6 @@ import React from 'react'; import { selectors } from '@grafana/e2e-selectors'; -import { config } from '@grafana/runtime'; import { Button, LinkButton } from '@grafana/ui'; import { contextSrv } from 'app/core/core'; import { AccessControlAction } from 'app/types'; @@ -9,36 +8,18 @@ import { AccessControlAction } from 'app/types'; export interface Props { exploreUrl: string; canSave: boolean; - canDelete: boolean; - onDelete: () => void; onSubmit: (event: React.MouseEvent) => void; onTest: (event: React.MouseEvent) => void; } -export function ButtonRow({ canSave, canDelete, onDelete, onSubmit, onTest, exploreUrl }: Props) { +export function ButtonRow({ canSave, onSubmit, onTest, exploreUrl }: Props) { const canExploreDataSources = contextSrv.hasPermission(AccessControlAction.DataSourcesExplore); return (
- {!config.featureToggles.topnav && ( - - )} Explore - {!config.featureToggles.topnav && ( - - )} {canSave && (