mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
DashboardScene: Show dashboard not found view (#89342)
* DashboardScene: Show dashboard not found view * Test fix * Use correct selector
This commit is contained in:
parent
8eabef1f91
commit
d46df10d30
@ -603,4 +603,7 @@ export const Components = {
|
|||||||
headerOrderSwitch: 'data-testid header-order-switch',
|
headerOrderSwitch: 'data-testid header-order-switch',
|
||||||
headerPreviewSwitch: 'data-testid header-preview-switch',
|
headerPreviewSwitch: 'data-testid header-preview-switch',
|
||||||
},
|
},
|
||||||
|
EntityNotFound: {
|
||||||
|
container: 'data-testid entity-not-found',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
@ -2,6 +2,7 @@ import { css } from '@emotion/css';
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { GrafanaTheme2 } from '@grafana/data';
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
|
import { selectors } from '@grafana/e2e-selectors';
|
||||||
import { EmptyState, TextLink, useStyles2 } from '@grafana/ui';
|
import { EmptyState, TextLink, useStyles2 } from '@grafana/ui';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
@ -15,7 +16,7 @@ export function EntityNotFound({ entity = 'Page' }: Props) {
|
|||||||
const styles = useStyles2(getStyles);
|
const styles = useStyles2(getStyles);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.container}>
|
<div className={styles.container} data-testid={selectors.components.EntityNotFound.container}>
|
||||||
<EmptyState message={`${entity} not found`} variant="not-found">
|
<EmptyState message={`${entity} not found`} variant="not-found">
|
||||||
We're looking but can't seem to find this {entity.toLowerCase()}. Try returning{' '}
|
We're looking but can't seem to find this {entity.toLowerCase()}. Try returning{' '}
|
||||||
<TextLink href="/">home</TextLink> or seeking help on the{' '}
|
<TextLink href="/">home</TextLink> or seeking help on the{' '}
|
||||||
|
@ -392,6 +392,10 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> {
|
|||||||
public getPageNav(location: H.Location, navIndex: NavIndex) {
|
public getPageNav(location: H.Location, navIndex: NavIndex) {
|
||||||
const { meta, viewPanelScene, editPanel } = this.state;
|
const { meta, viewPanelScene, editPanel } = this.state;
|
||||||
|
|
||||||
|
if (meta.dashboardNotFound) {
|
||||||
|
return { text: 'Not found' };
|
||||||
|
}
|
||||||
|
|
||||||
let pageNav: NavModelItem = {
|
let pageNav: NavModelItem = {
|
||||||
text: this.state.title,
|
text: this.state.title,
|
||||||
url: getDashboardUrl({
|
url: getDashboardUrl({
|
||||||
|
@ -0,0 +1,65 @@
|
|||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import React from 'react';
|
||||||
|
import { Provider } from 'react-redux';
|
||||||
|
import { Router } from 'react-router';
|
||||||
|
import { getGrafanaContextMock } from 'test/mocks/getGrafanaContextMock';
|
||||||
|
|
||||||
|
import { selectors } from '@grafana/e2e-selectors';
|
||||||
|
import { locationService } from '@grafana/runtime';
|
||||||
|
import { GrafanaContext } from 'app/core/context/GrafanaContext';
|
||||||
|
import { configureStore } from 'app/store/configureStore';
|
||||||
|
|
||||||
|
import { transformSaveModelToScene } from '../serialization/transformSaveModelToScene';
|
||||||
|
|
||||||
|
describe('DashboardSceneRenderer', () => {
|
||||||
|
it('should render Not Found notice when dashboard is not found', async () => {
|
||||||
|
const scene = transformSaveModelToScene({
|
||||||
|
meta: {
|
||||||
|
isSnapshot: true,
|
||||||
|
dashboardNotFound: true,
|
||||||
|
canStar: false,
|
||||||
|
canDelete: false,
|
||||||
|
canSave: false,
|
||||||
|
canEdit: false,
|
||||||
|
canShare: false,
|
||||||
|
},
|
||||||
|
dashboard: {
|
||||||
|
title: 'Not found',
|
||||||
|
uid: 'uid',
|
||||||
|
schemaVersion: 0,
|
||||||
|
// Disabling build in annotations to avoid mocking Grafana data source
|
||||||
|
annotations: {
|
||||||
|
list: [
|
||||||
|
{
|
||||||
|
builtIn: 1,
|
||||||
|
datasource: {
|
||||||
|
type: 'grafana',
|
||||||
|
uid: '-- Grafana --',
|
||||||
|
},
|
||||||
|
enable: false,
|
||||||
|
hide: true,
|
||||||
|
iconColor: 'rgba(0, 211, 255, 1)',
|
||||||
|
name: 'Annotations & Alerts',
|
||||||
|
type: 'dashboard',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const store = configureStore({});
|
||||||
|
const context = getGrafanaContextMock();
|
||||||
|
|
||||||
|
render(
|
||||||
|
<GrafanaContext.Provider value={context}>
|
||||||
|
<Provider store={store}>
|
||||||
|
<Router history={locationService.getHistory()}>
|
||||||
|
<scene.Component model={scene} />
|
||||||
|
</Router>
|
||||||
|
</Provider>
|
||||||
|
</GrafanaContext.Provider>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(await screen.findByTestId(selectors.components.EntityNotFound.container)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
@ -7,6 +7,7 @@ import { selectors } from '@grafana/e2e-selectors';
|
|||||||
import { SceneComponentProps } from '@grafana/scenes';
|
import { SceneComponentProps } from '@grafana/scenes';
|
||||||
import { CustomScrollbar, useStyles2 } from '@grafana/ui';
|
import { CustomScrollbar, useStyles2 } from '@grafana/ui';
|
||||||
import { Page } from 'app/core/components/Page/Page';
|
import { Page } from 'app/core/components/Page/Page';
|
||||||
|
import { EntityNotFound } from 'app/core/components/PageNotFound/EntityNotFound';
|
||||||
import { getNavModel } from 'app/core/selectors/navModel';
|
import { getNavModel } from 'app/core/selectors/navModel';
|
||||||
import DashboardEmpty from 'app/features/dashboard/dashgrid/DashboardEmpty';
|
import DashboardEmpty from 'app/features/dashboard/dashgrid/DashboardEmpty';
|
||||||
import { useSelector } from 'app/types';
|
import { useSelector } from 'app/types';
|
||||||
@ -35,14 +36,26 @@ export function DashboardSceneRenderer({ model }: SceneComponentProps<DashboardS
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const emptyState = <DashboardEmpty dashboard={model} canCreate={!!model.state.meta.canEdit} />;
|
const emptyState = (
|
||||||
|
<DashboardEmpty dashboard={model} canCreate={!!model.state.meta.canEdit} key="dashboard-empty-state" />
|
||||||
|
);
|
||||||
|
|
||||||
const withPanels = (
|
const withPanels = (
|
||||||
<div className={cx(styles.body, !hasControls && styles.bodyWithoutControls)}>
|
<div className={cx(styles.body, !hasControls && styles.bodyWithoutControls)} key="dashboard-panels">
|
||||||
<bodyToRender.Component model={bodyToRender} />
|
<bodyToRender.Component model={bodyToRender} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const notFound = meta.dashboardNotFound && <EntityNotFound entity="Dashboard" key="dashboard-not-found" />;
|
||||||
|
|
||||||
|
let body = [withPanels];
|
||||||
|
|
||||||
|
if (notFound) {
|
||||||
|
body = [notFound];
|
||||||
|
} else if (isEmpty) {
|
||||||
|
body = [emptyState, withPanels];
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page navModel={navModel} pageNav={pageNav} layout={PageLayoutType.Custom}>
|
<Page navModel={navModel} pageNav={pageNav} layout={PageLayoutType.Custom}>
|
||||||
{editPanel && <editPanel.Component model={editPanel} />}
|
{editPanel && <editPanel.Component model={editPanel} />}
|
||||||
@ -55,7 +68,7 @@ export function DashboardSceneRenderer({ model }: SceneComponentProps<DashboardS
|
|||||||
scopes && isScopesExpanded && styles.pageContainerWithScopesExpanded
|
scopes && isScopesExpanded && styles.pageContainerWithScopesExpanded
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{scopes && <scopes.Component model={scopes} />}
|
{scopes && !meta.dashboardNotFound && <scopes.Component model={scopes} />}
|
||||||
<NavToolbarActions dashboard={model} />
|
<NavToolbarActions dashboard={model} />
|
||||||
{controls && hasControls && (
|
{controls && hasControls && (
|
||||||
<div
|
<div
|
||||||
@ -71,10 +84,7 @@ export function DashboardSceneRenderer({ model }: SceneComponentProps<DashboardS
|
|||||||
className={styles.scrollbarContainer}
|
className={styles.scrollbarContainer}
|
||||||
testId={selectors.pages.Dashboard.DashNav.scrollContainer}
|
testId={selectors.pages.Dashboard.DashNav.scrollContainer}
|
||||||
>
|
>
|
||||||
<div className={cx(styles.canvasContent, isHomePage && styles.homePagePadding)}>
|
<div className={cx(styles.canvasContent, isHomePage && styles.homePagePadding)}>{body}</div>
|
||||||
<>{isEmpty && emptyState}</>
|
|
||||||
{withPanels}
|
|
||||||
</div>
|
|
||||||
</CustomScrollbar>
|
</CustomScrollbar>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
@ -8,6 +8,7 @@ import { selectors } from '@grafana/e2e-selectors';
|
|||||||
import { config, locationService } from '@grafana/runtime';
|
import { config, locationService } from '@grafana/runtime';
|
||||||
import { SceneGridLayout, SceneQueryRunner, SceneTimeRange, UrlSyncContextProvider, VizPanel } from '@grafana/scenes';
|
import { SceneGridLayout, SceneQueryRunner, SceneTimeRange, UrlSyncContextProvider, VizPanel } from '@grafana/scenes';
|
||||||
import { playlistSrv } from 'app/features/playlist/PlaylistSrv';
|
import { playlistSrv } from 'app/features/playlist/PlaylistSrv';
|
||||||
|
import { DashboardMeta } from 'app/types';
|
||||||
|
|
||||||
import { buildPanelEditScene } from '../panel-edit/PanelEditor';
|
import { buildPanelEditScene } from '../panel-edit/PanelEditor';
|
||||||
|
|
||||||
@ -163,9 +164,27 @@ describe('NavToolbarActions', () => {
|
|||||||
expect(newShareButton).toBeInTheDocument();
|
expect(newShareButton).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Snapshot', () => {
|
||||||
|
it('should show link button when is a snapshot', () => {
|
||||||
|
setup({
|
||||||
|
isSnapshot: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(screen.queryByTestId('button-snapshot')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
it('should not show link button when is not found dashboard', () => {
|
||||||
|
setup({
|
||||||
|
isSnapshot: true,
|
||||||
|
dashboardNotFound: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(screen.queryByTestId('button-snapshot')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function setup() {
|
function setup(meta?: DashboardMeta) {
|
||||||
const dashboard = new DashboardScene({
|
const dashboard = new DashboardScene({
|
||||||
$timeRange: new SceneTimeRange({ from: 'now-6h', to: 'now' }),
|
$timeRange: new SceneTimeRange({ from: 'now-6h', to: 'now' }),
|
||||||
meta: {
|
meta: {
|
||||||
@ -177,6 +196,7 @@ function setup() {
|
|||||||
canStar: true,
|
canStar: true,
|
||||||
canAdmin: true,
|
canAdmin: true,
|
||||||
canDelete: true,
|
canDelete: true,
|
||||||
|
...meta,
|
||||||
},
|
},
|
||||||
title: 'hello',
|
title: 'hello',
|
||||||
uid: 'dash-1',
|
uid: 'dash-1',
|
||||||
|
@ -139,7 +139,7 @@ export function ToolbarActions({ dashboard }: Props) {
|
|||||||
|
|
||||||
toolbarActions.push({
|
toolbarActions.push({
|
||||||
group: 'icon-actions',
|
group: 'icon-actions',
|
||||||
condition: meta.isSnapshot && !isEditing,
|
condition: meta.isSnapshot && !meta.dashboardNotFound && !isEditing,
|
||||||
render: () => (
|
render: () => (
|
||||||
<GoToSnapshotOriginButton
|
<GoToSnapshotOriginButton
|
||||||
key="go-to-snapshot-origin"
|
key="go-to-snapshot-origin"
|
||||||
|
@ -92,7 +92,9 @@ export class DashboardLoaderSrv {
|
|||||||
return result;
|
return result;
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
return this._dashboardLoadFailed('Not found', true);
|
const dash = this._dashboardLoadFailed('Not found', true);
|
||||||
|
dash.dashboard.uid = uid;
|
||||||
|
return dash;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Dashboard uid or slug required');
|
throw new Error('Dashboard uid or slug required');
|
||||||
|
Loading…
Reference in New Issue
Block a user