PageLayouts: Updates dashboard section routes with navId (#52175)

* First stab at new page layouts behind feature toggle

* Simplifying PageHeader

* Progress on a new model that can more easily support new and old page layouts

* Progress

* rename folder

* Progress

* Minor change

* fixes

* Fixing tests

* Make breadcrumbs work

* Add tests for old Page component

* Adding tests for new Page component and behavior

* fixing page header test

* Fixed test

* Moving user profile routes to navId

* PageLayouts: Updates dashboards routes with navId

* added missing navId

* AppChrome outside route

* Renaming folder

* Minor fix

* Updated

* Fixing StoragePage

* Updated

* Updating translation ids

* Updated snapshot

* update nav translation ids (yes this is confusing)

Co-authored-by: Ashley Harrison <ashley.harrison@grafana.com>
Co-authored-by: joshhunt <josh@trtr.co>
This commit is contained in:
Torkel Ödegaard 2022-07-20 17:26:52 +02:00 committed by GitHub
parent 320262c3db
commit 77f7e8dafc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 80 additions and 218 deletions

View File

@ -83,7 +83,7 @@ exports[`no enzyme tests`] = {
"public/app/features/dimensions/editors/ThresholdsEditor/ThresholdsEditor.test.tsx:4164297658": [
[0, 17, 13, "RegExp match", "2409514259"]
],
"public/app/features/folders/FolderSettingsPage.test.tsx:1109052730": [
"public/app/features/folders/FolderSettingsPage.test.tsx:1208063654": [
[0, 19, 13, "RegExp match", "2409514259"]
],
"public/app/plugins/datasource/cloudwatch/components/ConfigEditor.test.tsx:4057721851": [

View File

@ -443,23 +443,23 @@ func (hs *HTTPServer) buildDashboardNavLinks(c *models.ReqContext, hasEditPerm b
dashboardChildNavs := []*dtos.NavLink{}
dashboardChildNavs = append(dashboardChildNavs, &dtos.NavLink{
Text: "Browse", Id: "manage-dashboards", Url: hs.Cfg.AppSubURL + "/dashboards", Icon: "sitemap",
Text: "Browse", Id: "dashboards/browse", Url: hs.Cfg.AppSubURL + "/dashboards", Icon: "sitemap",
})
dashboardChildNavs = append(dashboardChildNavs, &dtos.NavLink{
Text: "Playlists", Id: "playlists", Url: hs.Cfg.AppSubURL + "/playlists", Icon: "presentation-play",
Text: "Playlists", Id: "dashboards/playlists", Url: hs.Cfg.AppSubURL + "/playlists", Icon: "presentation-play",
})
if c.IsSignedIn {
dashboardChildNavs = append(dashboardChildNavs, &dtos.NavLink{
Text: "Snapshots",
Id: "snapshots",
Id: "dashboards/snapshots",
Url: hs.Cfg.AppSubURL + "/dashboard/snapshots",
Icon: "camera",
})
dashboardChildNavs = append(dashboardChildNavs, &dtos.NavLink{
Text: "Library panels",
Id: "library-panels",
Id: "dashboards/library-panels",
Url: hs.Cfg.AppSubURL + "/library-panels",
Icon: "library-panel",
})
@ -481,20 +481,20 @@ func (hs *HTTPServer) buildDashboardNavLinks(c *models.ReqContext, hasEditPerm b
if hasAccess(hasEditPermInAnyFolder, ac.EvalPermission(dashboards.ActionDashboardsCreate)) {
dashboardChildNavs = append(dashboardChildNavs, &dtos.NavLink{
Text: "New dashboard", Icon: "plus", Url: hs.Cfg.AppSubURL + "/dashboard/new", HideFromTabs: true, Id: "new-dashboard", ShowIconInNavbar: true,
Text: "New dashboard", Icon: "plus", Url: hs.Cfg.AppSubURL + "/dashboard/new", HideFromTabs: true, Id: "dashboards/new", ShowIconInNavbar: true,
})
}
if hasAccess(ac.ReqOrgAdminOrEditor, ac.EvalPermission(dashboards.ActionFoldersCreate)) {
dashboardChildNavs = append(dashboardChildNavs, &dtos.NavLink{
Text: "New folder", SubTitle: "Create a new folder to organize your dashboards", Id: "new-folder",
Text: "New folder", SubTitle: "Create a new folder to organize your dashboards", Id: "dashboards/folder/new",
Icon: "plus", Url: hs.Cfg.AppSubURL + "/dashboards/folder/new", HideFromTabs: true, ShowIconInNavbar: true,
})
}
if hasAccess(hasEditPermInAnyFolder, ac.EvalPermission(dashboards.ActionDashboardsCreate)) {
dashboardChildNavs = append(dashboardChildNavs, &dtos.NavLink{
Text: "Import", SubTitle: "Import dashboard from file or Grafana.com", Id: "import", Icon: "plus",
Text: "Import", SubTitle: "Import dashboard from file or Grafana.com", Id: "dashboards/import", Icon: "plus",
Url: hs.Cfg.AppSubURL + "/dashboard/import", HideFromTabs: true, ShowIconInNavbar: true,
})
}

View File

@ -4,7 +4,10 @@ import { defineMessage } from '@lingui/macro';
// Maps the ID of the nav item to a translated phrase to later pass to <Trans />
// Because the navigation content is dynamic (defined in the backend), we can not use
// the normal inline message definition method.
// Keys MUST match the ID of the navigation item, defined in the backend.
// The keys of the TRANSLATED_MENU_ITEMS object (NOT the id inside the defineMessage function)
// must match the ID of the navigation item, as defined in the backend nav model
// see pkg/api/index.go
const TRANSLATED_MENU_ITEMS: Record<string, MessageDescriptor> = {
home: defineMessage({ id: 'nav.home', message: 'Home' }),
@ -18,12 +21,12 @@ const TRANSLATED_MENU_ITEMS: Record<string, MessageDescriptor> = {
starred: defineMessage({ id: 'nav.starred', message: 'Starred' }),
'starred-empty': defineMessage({ id: 'nav.starred-empty', message: 'Your starred dashboards will appear here' }),
dashboards: defineMessage({ id: 'nav.dashboards', message: 'Dashboards' }),
'manage-dashboards': defineMessage({ id: 'nav.manage-dashboards', message: 'Browse' }),
playlists: defineMessage({ id: 'nav.playlists', message: 'Playlists' }),
snapshots: defineMessage({ id: 'nav.snapshots', message: 'Snapshots' }),
'library-panels': defineMessage({ id: 'nav.library-panels', message: 'Library panels' }),
'new-dashboard': defineMessage({ id: 'nav.new-dashboard', message: 'New dashboard' }),
'new-folder': defineMessage({ id: 'nav.new-folder', message: 'New folder' }),
'dashboards/browse': defineMessage({ id: 'nav.manage-dashboards', message: 'Browse' }),
'dashboards/playlists': defineMessage({ id: 'nav.playlists', message: 'Playlists' }),
'dashboards/snapshots': defineMessage({ id: 'nav.snapshots', message: 'Snapshots' }),
'dashboards/library-panels': defineMessage({ id: 'nav.library-panels', message: 'Library panels' }),
'dashboards/new': defineMessage({ id: 'nav.new-dashboard', message: 'New dashboard' }),
'dashboards/folder/new': defineMessage({ id: 'nav.new-folder', message: 'New folder' }),
explore: defineMessage({ id: 'nav.explore', message: 'Explore' }),

View File

@ -17,7 +17,7 @@ function mapStateToProps(state: StoreState, props: RouteProps) {
const uid = props.match.params.uid;
return {
uid: uid,
navModel: getNavModel(state.navIndex, `folder-permissions-${uid}`, getLoadingNav(1)),
pageNav: getNavModel(state.navIndex, `folder-permissions-${uid}`, getLoadingNav(1)),
};
}
@ -28,7 +28,7 @@ const mapDispatchToProps = {
const connector = connect(mapStateToProps, mapDispatchToProps);
export type Props = ConnectedProps<typeof connector>;
export const AccessControlFolderPermissions = ({ uid, getFolderByUid, navModel }: Props) => {
export const AccessControlFolderPermissions = ({ uid, getFolderByUid, pageNav }: Props) => {
useEffect(() => {
getFolderByUid(uid);
}, [getFolderByUid, uid]);
@ -36,7 +36,7 @@ export const AccessControlFolderPermissions = ({ uid, getFolderByUid, navModel }
const canSetPermissions = contextSrv.hasPermission(AccessControlAction.FoldersPermissionsWrite);
return (
<Page navModel={navModel}>
<Page navId="dashboards/browse" pageNav={pageNav.main}>
<Page.Contents>
<Permissions resource="folders" resourceId={uid} canSetPermissions={canSetPermissions} />
</Page.Contents>

View File

@ -20,12 +20,12 @@ const FolderAlerting = ({ match }: OwnProps) => {
const folder = useSelector((state: StoreState) => state.folder);
const uid = match.params.uid;
const navModel = getNavModel(navIndex, `folder-alerting-${uid}`, getLoadingNav(1));
const pageNav = getNavModel(navIndex, `folder-alerting-${uid}`, getLoadingNav(1));
const { loading } = useAsync(async () => dispatch(getFolderByUid(uid)), [getFolderByUid, uid]);
return (
<Page navModel={navModel}>
<Page navId="dashboards/browse" pageNav={pageNav.main}>
<Page.Contents isLoading={loading}>
<AlertsFolderView folder={folder} />
</Page.Contents>

View File

@ -19,7 +19,7 @@ export interface OwnProps extends GrafanaRouteComponentProps<{ uid: string }> {}
const mapStateToProps = (state: StoreState, props: OwnProps) => {
const uid = props.match.params.uid;
return {
navModel: getNavModel(state.navIndex, `folder-library-panels-${uid}`, getLoadingNav(1)),
pageNav: getNavModel(state.navIndex, `folder-library-panels-${uid}`, getLoadingNav(1)),
folderUid: uid,
folder: state.folder,
};
@ -33,12 +33,12 @@ const connector = connect(mapStateToProps, mapDispatchToProps);
export type Props = OwnProps & ConnectedProps<typeof connector>;
export function FolderLibraryPanelsPage({ navModel, getFolderByUid, folderUid, folder }: Props): JSX.Element {
export function FolderLibraryPanelsPage({ pageNav, getFolderByUid, folderUid, folder }: Props): JSX.Element {
const { loading } = useAsync(async () => await getFolderByUid(folderUid), [getFolderByUid, folderUid]);
const [selected, setSelected] = useState<LibraryElementDTO | undefined>(undefined);
return (
<Page navModel={navModel}>
<Page navId="dashboards/browse" pageNav={pageNav.main}>
<Page.Contents isLoading={loading}>
<LibraryPanelsSearch
onClick={setSelected}

View File

@ -26,7 +26,7 @@ export interface OwnProps extends GrafanaRouteComponentProps<{ uid: string }> {}
const mapStateToProps = (state: StoreState, props: OwnProps) => {
const uid = props.match.params.uid;
return {
navModel: getNavModel(state.navIndex, `folder-permissions-${uid}`, getLoadingNav(1)),
pageNav: getNavModel(state.navIndex, `folder-permissions-${uid}`, getLoadingNav(1)),
folderUid: uid,
folder: state.folder,
};
@ -83,12 +83,12 @@ export class FolderPermissions extends PureComponent<Props, State> {
};
render() {
const { navModel, folder } = this.props;
const { pageNav, folder } = this.props;
const { isAdding } = this.state;
if (folder.id === 0) {
return (
<Page navModel={navModel}>
<Page navId="dashboards/browse" pageNav={pageNav.main}>
<Page.Contents isLoading={true}>
<span />
</Page.Contents>
@ -99,7 +99,7 @@ export class FolderPermissions extends PureComponent<Props, State> {
const folderInfo = { title: folder.title, url: folder.url, id: folder.id };
return (
<Page navModel={navModel}>
<Page navId="browse" pageNav={pageNav.main}>
<Page.Contents>
<div className="page-action-bar">
<h3 className="page-sub-heading">Folder Permissions</h3>

View File

@ -11,7 +11,7 @@ import { setFolderTitle } from './state/reducers';
const setup = (propOverrides?: object) => {
const props: Props = {
...getRouteComponentProps(),
navModel: {} as NavModel,
pageNav: {} as NavModel,
folderUid: '1234',
folder: {
id: 0,

View File

@ -20,7 +20,7 @@ export interface OwnProps extends GrafanaRouteComponentProps<{ uid: string }> {}
const mapStateToProps = (state: StoreState, props: OwnProps) => {
const uid = props.match.params.uid;
return {
navModel: getNavModel(state.navIndex, `folder-settings-${uid}`, getLoadingNav(2)),
pageNav: getNavModel(state.navIndex, `folder-settings-${uid}`, getLoadingNav(2)),
folderUid: uid,
folder: state.folder,
};
@ -84,10 +84,10 @@ export class FolderSettingsPage extends PureComponent<Props, State> {
};
render() {
const { navModel, folder } = this.props;
const { pageNav, folder } = this.props;
return (
<Page navModel={navModel}>
<Page navId="dashboards/browse" pageNav={pageNav.main}>
<Page.Contents isLoading={this.state.isLoading}>
<h3 className="page-sub-heading">Folder settings</h3>

View File

@ -2,7 +2,7 @@
exports[`Render should enable save button 1`] = `
<OldPage
navModel={Object {}}
navId="dashboards/browse"
>
<PageContents
isLoading={false}
@ -59,7 +59,7 @@ exports[`Render should enable save button 1`] = `
exports[`Render should render component 1`] = `
<OldPage
navModel={Object {}}
navId="dashboards/browse"
>
<PageContents
isLoading={false}

View File

@ -3,21 +3,15 @@ import { connect, ConnectedProps } from 'react-redux';
import { Button, Input, Form, Field } from '@grafana/ui';
import { Page } from 'app/core/components/Page/Page';
import { getNavModel } from 'app/core/selectors/navModel';
import { StoreState } from 'app/types';
import { validationSrv } from '../../manage-dashboards/services/ValidationSrv';
import { createNewFolder } from '../state/actions';
const mapStateToProps = (state: StoreState) => ({
navModel: getNavModel(state.navIndex, 'manage-dashboards'),
});
const mapDispatchToProps = {
createNewFolder,
};
const connector = connect(mapStateToProps, mapDispatchToProps);
const connector = connect(null, mapDispatchToProps);
interface OwnProps {}
@ -47,7 +41,7 @@ export class NewDashboardsFolder extends PureComponent<Props> {
render() {
return (
<Page navModel={this.props.navModel}>
<Page navId="dashboards/folder/new">
<Page.Contents>
<h3>New dashboard folder</h3>
<Form defaultValues={initialFormModel} onSubmit={this.onSubmit}>

View File

@ -1,31 +1,16 @@
import React, { FC, useState } from 'react';
import { connect, ConnectedProps } from 'react-redux';
import React, { useState } from 'react';
import { Page } from 'app/core/components/Page/Page';
import { GrafanaRouteComponentProps } from '../../core/navigation/types';
import { getNavModel } from '../../core/selectors/navModel';
import { StoreState } from '../../types';
import { LibraryPanelsSearch } from './components/LibraryPanelsSearch/LibraryPanelsSearch';
import { OpenLibraryPanelModal } from './components/OpenLibraryPanelModal/OpenLibraryPanelModal';
import { LibraryElementDTO } from './types';
const mapStateToProps = (state: StoreState) => ({
navModel: getNavModel(state.navIndex, 'library-panels'),
});
const connector = connect(mapStateToProps, undefined);
interface OwnProps extends GrafanaRouteComponentProps {}
type Props = OwnProps & ConnectedProps<typeof connector>;
export const LibraryPanelsPage: FC<Props> = ({ navModel }) => {
export const LibraryPanelsPage = () => {
const [selected, setSelected] = useState<LibraryElementDTO | undefined>(undefined);
return (
<Page navModel={navModel}>
<Page navId="dashboards/library-panels">
<Page.Contents>
<LibraryPanelsSearch onClick={setSelected} showSecondaryActions showSort showPanelFilter showFolderFilter />
{selected ? <OpenLibraryPanelModal onDismiss={() => setSelected(undefined)} libraryPanel={selected} /> : null}
@ -34,4 +19,4 @@ export const LibraryPanelsPage: FC<Props> = ({ navModel }) => {
);
};
export default connect(mapStateToProps)(LibraryPanelsPage);
export default LibraryPanelsPage;

View File

@ -22,7 +22,6 @@ import {
import appEvents from 'app/core/app_events';
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';
import { cleanUpAction } from '../../core/actions/cleanUp';
@ -40,7 +39,6 @@ type OwnProps = Themeable2 & GrafanaRouteComponentProps<{}, DashboardImportPageR
const IMPORT_STARTED_EVENT_NAME = 'dashboard_import_loaded';
const mapStateToProps = (state: StoreState) => ({
navModel: getNavModel(state.navIndex, 'import', undefined, true),
loadingState: state.importDashboard.state,
});
@ -189,10 +187,10 @@ class UnthemedDashboardImport extends PureComponent<Props> {
}
render() {
const { loadingState, navModel } = this.props;
const { loadingState } = this.props;
return (
<Page navModel={navModel}>
<Page navId="dashboards/import">
<Page.Contents>
{loadingState === LoadingState.Loading && (
<VerticalGroup justify="center">

View File

@ -1,23 +1,12 @@
import React, { FC } from 'react';
import { MapStateToProps, connect } from 'react-redux';
import React from 'react';
import { NavModel } from '@grafana/data';
import { Page } from 'app/core/components/Page/Page';
import { getNavModel } from 'app/core/selectors/navModel';
import { StoreState } from 'app/types';
import { GrafanaRouteComponentProps } from '../../core/navigation/types';
import { SnapshotListTable } from './components/SnapshotListTable';
interface ConnectedProps {
navModel: NavModel;
}
interface Props extends ConnectedProps, GrafanaRouteComponentProps {}
export const SnapshotListPage: FC<Props> = ({ navModel, location }) => {
export const SnapshotListPage = ({}) => {
return (
<Page navModel={navModel}>
<Page navId="dashboards/snapshots">
<Page.Contents>
<SnapshotListTable />
</Page.Contents>
@ -25,8 +14,4 @@ export const SnapshotListPage: FC<Props> = ({ navModel, location }) => {
);
};
const mapStateToProps: MapStateToProps<ConnectedProps, {}, StoreState> = (state: StoreState) => ({
navModel: getNavModel(state.navIndex, 'snapshots'),
});
export default connect(mapStateToProps)(SnapshotListPage);
export default SnapshotListPage;

View File

@ -27,10 +27,6 @@ async function getTestContext({ name, interval, items, uid }: Partial<Playlist>
const match: any = { params: { uid: 'foo' } };
const location: any = {};
const history: any = {};
const navModel: any = {
node: {},
main: {},
};
const getMock = jest.spyOn(backendSrv, 'get');
const putMock = jest.spyOn(backendSrv, 'put');
getMock.mockResolvedValue({
@ -40,14 +36,7 @@ async function getTestContext({ name, interval, items, uid }: Partial<Playlist>
uid: 'foo',
});
const { rerender } = render(
<PlaylistEditPage
queryParams={queryParams}
route={route}
match={match}
location={location}
history={history}
navModel={navModel}
/>
<PlaylistEditPage queryParams={queryParams} route={route} match={match} location={location} history={history} />
);
await waitFor(() => expect(getMock).toHaveBeenCalledTimes(1));

View File

@ -1,12 +1,8 @@
import React, { FC } from 'react';
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 { getNavModel } from 'app/core/selectors/navModel';
import { StoreState } from 'app/types';
import { GrafanaRouteComponentProps } from '../../core/navigation/types';
@ -16,17 +12,13 @@ import { getPlaylistStyles } from './styles';
import { Playlist } from './types';
import { usePlaylist } from './usePlaylist';
interface ConnectedProps {
navModel: NavModel;
}
export interface RouteParams {
uid: string;
}
interface Props extends ConnectedProps, GrafanaRouteComponentProps<RouteParams> {}
interface Props extends GrafanaRouteComponentProps<RouteParams> {}
export const PlaylistEditPage: FC<Props> = ({ navModel, match }) => {
export const PlaylistEditPage: FC<Props> = ({ match }) => {
const styles = useStyles2(getPlaylistStyles);
const { playlist, loading } = usePlaylist(match.params.uid);
const onSubmit = async (playlist: Playlist) => {
@ -35,7 +27,7 @@ export const PlaylistEditPage: FC<Props> = ({ navModel, match }) => {
};
return (
<Page navModel={navModel}>
<Page navId="dashboards/playlists">
<Page.Contents isLoading={loading}>
<h3 className={styles.subHeading}>Edit playlist</h3>
@ -50,8 +42,4 @@ export const PlaylistEditPage: FC<Props> = ({ navModel, match }) => {
);
};
const mapStateToProps: MapStateToProps<ConnectedProps, {}, StoreState> = (state: StoreState) => ({
navModel: getNavModel(state.navIndex, 'playlists'),
});
export default connect(mapStateToProps)(PlaylistEditPage);
export default PlaylistEditPage;

View File

@ -23,7 +23,7 @@ export const PlaylistForm: FC<PlaylistFormProps> = ({ onSubmit, playlist }) => {
const { name, interval, items: propItems } = playlist;
const { items, addById, addByTag, deleteItem, moveDown, moveUp } = usePlaylistItems(propItems);
return (
<>
<div>
<Form onSubmit={(list: Playlist) => onSubmit({ ...list, items })} validateOn={'onBlur'}>
{({ register, errors }) => {
const isDisabled = items.length === 0 || Object.keys(errors).length > 0;
@ -81,6 +81,6 @@ export const PlaylistForm: FC<PlaylistFormProps> = ({ onSubmit, playlist }) => {
);
}}
</Form>
</>
</div>
);
};

View File

@ -30,26 +30,9 @@ jest.mock('../../core/components/TagFilter/TagFilter', () => ({
function getTestContext({ name, interval, items }: Partial<Playlist> = {}) {
jest.clearAllMocks();
const playlist = { name, items, interval } as unknown as Playlist;
const queryParams = {};
const route: any = {};
const match: any = {};
const location: any = {};
const history: any = {};
const navModel: any = {
node: {},
main: {},
};
const backendSrvMock = jest.spyOn(backendSrv, 'post');
const { rerender } = render(
<PlaylistNewPage
queryParams={queryParams}
route={route}
match={match}
location={location}
history={history}
navModel={navModel}
/>
);
const { rerender } = render(<PlaylistNewPage />);
return { playlist, rerender, backendSrvMock };
}

View File

@ -1,14 +1,8 @@
import React, { FC } from 'react';
import { connect, MapStateToProps } from 'react-redux';
import React from 'react';
import { NavModel } from '@grafana/data';
import { locationService } from '@grafana/runtime';
import { useStyles2 } from '@grafana/ui';
import { Page } from 'app/core/components/Page/Page';
import { getNavModel } from 'app/core/selectors/navModel';
import { StoreState } from 'app/types';
import { GrafanaRouteComponentProps } from '../../core/navigation/types';
import { PlaylistForm } from './PlaylistForm';
import { createPlaylist } from './api';
@ -16,13 +10,7 @@ import { getPlaylistStyles } from './styles';
import { Playlist } from './types';
import { usePlaylist } from './usePlaylist';
interface ConnectedProps {
navModel: NavModel;
}
interface Props extends ConnectedProps, GrafanaRouteComponentProps {}
export const PlaylistNewPage: FC<Props> = ({ navModel }) => {
export const PlaylistNewPage = () => {
const styles = useStyles2(getPlaylistStyles);
const { playlist, loading } = usePlaylist();
const onSubmit = async (playlist: Playlist) => {
@ -31,7 +19,7 @@ export const PlaylistNewPage: FC<Props> = ({ navModel }) => {
};
return (
<Page navModel={navModel}>
<Page navId="dashboards/playlists">
<Page.Contents isLoading={loading}>
<h3 className={styles.subHeading}>New Playlist</h3>
@ -46,8 +34,4 @@ export const PlaylistNewPage: FC<Props> = ({ navModel }) => {
);
};
const mapStateToProps: MapStateToProps<ConnectedProps, {}, StoreState> = (state: StoreState) => ({
navModel: getNavModel(state.navIndex, 'playlists'),
});
export default connect(mapStateToProps)(PlaylistNewPage);
export default PlaylistNewPage;

View File

@ -3,9 +3,7 @@ import React from 'react';
import { contextSrv } from 'app/core/services/context_srv';
import { locationService } from '../../../../packages/grafana-runtime/src';
import { PlaylistPage, PlaylistPageProps } from './PlaylistPage';
import { PlaylistPage } from './PlaylistPage';
const fnMock = jest.fn();
@ -22,29 +20,8 @@ jest.mock('app/core/services/context_srv', () => ({
},
}));
function getTestContext(propOverrides?: object) {
const props: PlaylistPageProps = {
navModel: {
main: {
text: 'Playlist',
},
node: {
text: 'playlist',
},
},
route: {
path: '/playlists',
component: jest.fn(),
},
queryParams: { state: 'ok' },
match: { params: { name: 'playlist', sourceName: 'test playlist' }, isExact: false, url: 'asdf', path: '' },
history: locationService.getHistory(),
location: { pathname: '', hash: '', search: '', state: '' },
};
Object.assign(props, propOverrides);
return render(<PlaylistPage {...props} />);
function getTestContext() {
return render(<PlaylistPage />);
}
describe('PlaylistPage', () => {

View File

@ -1,17 +1,12 @@
import React, { FC, useState } from 'react';
import { connect, MapStateToProps } from 'react-redux';
import React, { useState } from 'react';
import { useDebounce } from 'react-use';
import { NavModel } from '@grafana/data';
import { ConfirmModal } from '@grafana/ui';
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';
import { StoreState } from 'app/types';
import EmptyListCTA from '../../core/components/EmptyListCTA/EmptyListCTA';
import { GrafanaRouteComponentProps } from '../../core/navigation/types';
import { EmptyQueryListBanner } from './EmptyQueryListBanner';
import { PlaylistPageList } from './PlaylistPageList';
@ -19,12 +14,7 @@ import { StartModal } from './StartModal';
import { deletePlaylist, getAllPlaylist } from './api';
import { PlaylistDTO } from './types';
interface ConnectedProps {
navModel: NavModel;
}
export interface PlaylistPageProps extends ConnectedProps, GrafanaRouteComponentProps {}
export const PlaylistPage: FC<PlaylistPageProps> = ({ navModel }) => {
export const PlaylistPage = () => {
const [searchQuery, setSearchQuery] = useState('');
const [debouncedSearchQuery, setDebouncedSearchQuery] = useState(searchQuery);
const [hasFetched, setHasFetched] = useState(false);
@ -76,7 +66,7 @@ export const PlaylistPage: FC<PlaylistPageProps> = ({ navModel }) => {
const showSearch = playlists.length > 0 || searchQuery.length > 0 || debouncedSearchQuery.length > 0;
return (
<Page navModel={navModel}>
<Page navId="dashboards/playlists">
<Page.Contents isLoading={!hasFetched}>
{showSearch && (
<PageActionBar
@ -112,8 +102,4 @@ export const PlaylistPage: FC<PlaylistPageProps> = ({ navModel }) => {
);
};
const mapStateToProps: MapStateToProps<ConnectedProps, {}, StoreState> = (state: StoreState) => ({
navModel: getNavModel(state.navIndex, 'playlists'),
});
export default connect(mapStateToProps)(PlaylistPage);
export default PlaylistPage;

View File

@ -1,13 +1,11 @@
import { css } from '@emotion/css';
import React, { FC, memo } from 'react';
import { connect, MapStateToProps } from 'react-redux';
import { useAsync } from 'react-use';
import { NavModel, locationUtil } from '@grafana/data';
import { locationUtil, NavModelItem } from '@grafana/data';
import { locationService } from '@grafana/runtime';
import { Page } from 'app/core/components/Page/Page';
import { getNavModel } from 'app/core/selectors/navModel';
import { FolderDTO, StoreState } from 'app/types';
import { FolderDTO } from 'app/types';
import { GrafanaRouteComponentProps } from '../../../core/navigation/types';
import { loadFolderPage } from '../loaders';
@ -19,17 +17,14 @@ export interface DashboardListPageRouteParams {
slug?: string;
}
interface DashboardListPageConnectedProps {
navModel: NavModel;
}
interface Props extends GrafanaRouteComponentProps<DashboardListPageRouteParams>, DashboardListPageConnectedProps {}
interface Props extends GrafanaRouteComponentProps<DashboardListPageRouteParams> {}
export const DashboardListPage: FC<Props> = memo(({ navModel, match, location }) => {
const { loading, value } = useAsync<() => Promise<{ folder?: FolderDTO; pageNavModel: NavModel }>>(() => {
export const DashboardListPage: FC<Props> = memo(({ match, location }) => {
const { loading, value } = useAsync<() => Promise<{ folder?: FolderDTO; pageNav?: NavModelItem }>>(() => {
const uid = match.params.uid;
const url = location.pathname;
if (!uid || !url.startsWith('/dashboards')) {
return Promise.resolve({ pageNavModel: navModel });
return Promise.resolve({});
}
return loadFolderPage(uid!).then(({ folder, folderNav }) => {
@ -39,12 +34,12 @@ export const DashboardListPage: FC<Props> = memo(({ navModel, match, location })
locationService.push(path);
}
return { folder, pageNavModel: { ...navModel, main: folderNav } };
return { folder, pageNav: folderNav };
});
}, [match.params.uid]);
return (
<Page navModel={value?.pageNavModel ?? navModel}>
<Page navId="dashboards/browse" pageNav={value?.pageNav}>
<Page.Contents
isLoading={loading}
className={css`
@ -61,10 +56,4 @@ export const DashboardListPage: FC<Props> = memo(({ navModel, match, location })
DashboardListPage.displayName = 'DashboardListPage';
const mapStateToProps: MapStateToProps<DashboardListPageConnectedProps, {}, StoreState> = (state) => {
return {
navModel: getNavModel(state.navIndex, 'manage-dashboards'),
};
};
export default connect(mapStateToProps)(DashboardListPage);
export default DashboardListPage;

View File

@ -51,8 +51,6 @@ export function getAppRoutes(): RouteDescriptor[] {
path: '/dashboard/new',
pageClass: 'page-dashboard',
routeName: DashboardRoutes.New,
// TODO[Router]
//roles: () => (contextSrv.hasEditPermissionInFolders ? [contextSrv.user.orgRole] : ['Admin']),
component: SafeDynamicImport(
() => import(/* webpackChunkName: "DashboardPage" */ '../features/dashboard/containers/DashboardPage')
),
@ -61,6 +59,7 @@ export function getAppRoutes(): RouteDescriptor[] {
path: '/d-solo/:uid/:slug',
pageClass: 'dashboard-solo',
routeName: DashboardRoutes.Normal,
chromeless: true,
component: SafeDynamicImport(
() => import(/* webpackChunkName: "SoloPanelPage" */ '../features/dashboard/containers/SoloPanelPage')
),
@ -70,6 +69,7 @@ export function getAppRoutes(): RouteDescriptor[] {
path: '/dashboard-solo/:type/:slug',
pageClass: 'dashboard-solo',
routeName: DashboardRoutes.Normal,
chromeless: true,
component: SafeDynamicImport(
() => import(/* webpackChunkName: "SoloPanelPage" */ '../features/dashboard/containers/SoloPanelPage')
),
@ -78,6 +78,7 @@ export function getAppRoutes(): RouteDescriptor[] {
path: '/d-solo/:uid',
pageClass: 'dashboard-solo',
routeName: DashboardRoutes.Normal,
chromeless: true,
component: SafeDynamicImport(
() => import(/* webpackChunkName: "SoloPanelPage" */ '../features/dashboard/containers/SoloPanelPage')
),