FolderPage: Improve folder page to work with new nav breadcrumbs and modify Dashboard page show path based breadcrumbs" (#52428)

* FolderPage: Progress on folder page navigation

* Dashboard to adapt breadcrumbs when opening dashboard from file storage

* add warning when topnav is not enabled

Co-authored-by: Ryan McKinley <ryantxu@gmail.com>
pull/51272/head
Torkel Ödegaard 3 years ago committed by GitHub
parent 79d92aa03e
commit b29045b57b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 70
      public/app/features/dashboard/containers/DashboardPage.tsx
  2. 76
      public/app/features/storage/StorageFolderPage.tsx
  3. 1
      public/app/features/storage/storage.ts

@ -12,9 +12,10 @@ import { PageLayoutType } from 'app/core/components/Page/types';
import { createErrorNotification } from 'app/core/copy/appNotification'; import { createErrorNotification } from 'app/core/copy/appNotification';
import { getKioskMode } from 'app/core/navigation/kiosk'; import { getKioskMode } from 'app/core/navigation/kiosk';
import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; import { GrafanaRouteComponentProps } from 'app/core/navigation/types';
import { PanelModel } from 'app/features/dashboard/state'; import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
import { dashboardWatcher } from 'app/features/live/dashboard/dashboardWatcher'; import { dashboardWatcher } from 'app/features/live/dashboard/dashboardWatcher';
import { KioskMode, StoreState } from 'app/types'; import { getPageNavFromSlug, getRootContentNavModel } from 'app/features/storage/StorageFolderPage';
import { DashboardRoutes, KioskMode, StoreState } from 'app/types';
import { PanelEditEnteredEvent, PanelEditExitedEvent } from 'app/types/events'; import { PanelEditEnteredEvent, PanelEditExitedEvent } from 'app/types/events';
import { cancelVariables, templateVarsChangedInUrl } from '../../variables/state/actions'; import { cancelVariables, templateVarsChangedInUrl } from '../../variables/state/actions';
@ -149,28 +150,7 @@ export class UnthemedDashboardPage extends PureComponent<Props, State> {
return; return;
} }
// Update page nav this.updatePageNav(dashboard);
if (!this.pageNav || dashboard.title !== this.pageNav.text) {
this.pageNav = {
text: dashboard.title,
url: locationUtil.getUrlForPartial(this.props.history.location, {
editview: null,
editPanel: null,
viewPanel: null,
}),
};
}
// Check if folder changed
if (
dashboard.meta.folderTitle &&
(!this.pageNav.parentItem || this.pageNav.parentItem.text !== dashboard.meta.folderTitle)
) {
this.pageNav.parentItem = {
text: dashboard.meta.folderTitle,
url: `/dashboards/f/${dashboard.meta.folderUid}`,
};
}
if ( if (
prevProps.match.params.uid !== match.params.uid || prevProps.match.params.uid !== match.params.uid ||
@ -339,6 +319,45 @@ export class UnthemedDashboardPage extends PureComponent<Props, State> {
return inspectPanel; return inspectPanel;
} }
updatePageNav(dashboard: DashboardModel) {
if (!this.pageNav || dashboard.title !== this.pageNav.text) {
this.pageNav = {
text: dashboard.title,
url: locationUtil.getUrlForPartial(this.props.history.location, {
editview: null,
editPanel: null,
viewPanel: null,
}),
};
}
// Check if folder changed
if (
dashboard.meta.folderTitle &&
(!this.pageNav.parentItem || this.pageNav.parentItem.text !== dashboard.meta.folderTitle)
) {
this.pageNav.parentItem = {
text: dashboard.meta.folderTitle,
url: `/dashboards/f/${dashboard.meta.folderUid}`,
};
}
if (this.props.route.routeName === DashboardRoutes.Path) {
const pageNav = getPageNavFromSlug(this.props.match.params.slug!);
if (pageNav?.parentItem) {
this.pageNav.parentItem = pageNav.parentItem;
}
}
}
getPageProps() {
if (this.props.route.routeName === DashboardRoutes.Path) {
return { navModel: getRootContentNavModel(), pageNav: this.pageNav };
} else {
return { navId: 'dashboards', pageNav: this.pageNav };
}
}
render() { render() {
const { dashboard, initError, queryParams, isPublic } = this.props; const { dashboard, initError, queryParams, isPublic } = this.props;
const { editPanel, viewPanel, updateScrollTop } = this.state; const { editPanel, viewPanel, updateScrollTop } = this.state;
@ -368,8 +387,7 @@ export class UnthemedDashboardPage extends PureComponent<Props, State> {
return ( return (
<Page <Page
navId="dashboards" {...this.getPageProps()}
pageNav={this.pageNav}
layout={PageLayoutType.Dashboard} layout={PageLayoutType.Dashboard}
toolbar={toolbar} toolbar={toolbar}
className={containerClassNames} className={containerClassNames}

@ -1,32 +1,24 @@
import { css } from '@emotion/css'; import React from 'react';
import React, { FC } from 'react';
import { useAsync } from 'react-use'; import { useAsync } from 'react-use';
import { DataFrame, GrafanaTheme2 } from '@grafana/data'; import { DataFrame, NavModel, NavModelItem } from '@grafana/data';
import { Card, Icon, Spinner, useStyles2 } from '@grafana/ui'; import { config } from '@grafana/runtime';
import { Alert, Card, Icon, Spinner } from '@grafana/ui';
import { Page } from 'app/core/components/Page/Page';
import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; import { GrafanaRouteComponentProps } from 'app/core/navigation/types';
import { getGrafanaStorage } from './storage'; import { getGrafanaStorage } from './storage';
export interface Props extends GrafanaRouteComponentProps<{ slug: string }> {} export interface Props extends GrafanaRouteComponentProps<{ slug: string }> {}
export const StorageFolderPage: FC<Props> = (props) => { export function StorageFolderPage(props: Props) {
const slug = props.match.params.slug; const slug = props.match.params.slug ?? '';
const styles = useStyles2(getStyles);
const listing = useAsync((): Promise<DataFrame | undefined> => { const listing = useAsync((): Promise<DataFrame | undefined> => {
return getGrafanaStorage().list(slug); return getGrafanaStorage().list(slug);
}, [slug]); }, [slug]);
let base = document.location.pathname; const childRoot = slug.length > 0 ? `g/${slug}/` : 'g/';
if (!base.endsWith('/')) { const pageNav = getPageNavFromSlug(slug);
base += '/';
}
let parent = '';
const idx = base.lastIndexOf('/', base.length - 2);
if (idx > 0) {
parent = base.substring(0, idx);
}
const renderListing = () => { const renderListing = () => {
if (listing.value) { if (listing.value) {
@ -35,8 +27,10 @@ export const StorageFolderPage: FC<Props> = (props) => {
let name = item; let name = item;
const isFolder = name.indexOf('.') < 0; const isFolder = name.indexOf('.') < 0;
const isDash = !isFolder && name.endsWith('.json'); const isDash = !isFolder && name.endsWith('.json');
const url = `${childRoot}${name}`;
return ( return (
<Card key={name} href={isFolder || isDash ? base + name : undefined}> <Card key={name} href={isFolder || isDash ? url : undefined}>
<Card.Heading>{name}</Card.Heading> <Card.Heading>{name}</Card.Heading>
<Card.Figure> <Card.Figure>
<Icon name={isFolder ? 'folder' : isDash ? 'gf-grid' : 'file-alt'} size="sm" /> <Icon name={isFolder ? 'folder' : isDash ? 'gf-grid' : 'file-alt'} size="sm" />
@ -51,29 +45,37 @@ export const StorageFolderPage: FC<Props> = (props) => {
return <div>?</div>; return <div>?</div>;
}; };
const navModel = getRootContentNavModel();
return ( return (
<div className={styles.wrapper}> <Page navModel={navModel} pageNav={pageNav}>
{slug?.length > 0 && ( {!config.featureToggles.topnav && (
<> <div>
<h1>{slug}</h1> <Alert title="Enable the topnav feature toggle">This page is designed assuming topnav is enabled</Alert>
<Card href={parent}> </div>
<Card.Heading>{parent}</Card.Heading>
<Card.Figure>
<Icon name="arrow-left" size="sm" />
</Card.Figure>
</Card>
<br />
</>
)} )}
{renderListing()} {renderListing()}
</div> </Page>
); );
}; }
export function getPageNavFromSlug(slug: string) {
const parts = slug.split('/');
let pageNavs: NavModelItem[] = [];
let url = 'g';
let lastPageNav: NavModelItem | undefined;
for (let i = 0; i < parts.length; i++) {
url += `/${parts[i]}`;
pageNavs.push({ text: parts[i], url, parentItem: lastPageNav });
lastPageNav = pageNavs[pageNavs.length - 1];
}
return lastPageNav;
}
const getStyles = (theme: GrafanaTheme2) => ({ export function getRootContentNavModel(): NavModel {
wrapper: css` return { main: { text: 'C:' }, node: { text: 'Content', url: '/g' } };
margin: 50px; }
`,
});
export default StorageFolderPage; export default StorageFolderPage;

@ -123,6 +123,7 @@ class SimpleStorage implements GrafanaStorage {
if (!path.endsWith('.json')) { if (!path.endsWith('.json')) {
path += '.json'; path += '.json';
} }
const result = await backendSrv.get(`/api/storage/read/${path}`); const result = await backendSrv.get(`/api/storage/read/${path}`);
result.uid = path; result.uid = path;
delete result.id; // Saved with the dev dashboards! delete result.id; // Saved with the dev dashboards!

Loading…
Cancel
Save