DashboardScene: Show dashboard not found view (#89342)

* DashboardScene: Show dashboard not found view

* Test fix

* Use correct selector
This commit is contained in:
Dominik Prokop 2024-06-19 13:33:29 +02:00 committed by GitHub
parent 8eabef1f91
commit d46df10d30
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 116 additions and 11 deletions

View File

@ -603,4 +603,7 @@ export const Components = {
headerOrderSwitch: 'data-testid header-order-switch',
headerPreviewSwitch: 'data-testid header-preview-switch',
},
EntityNotFound: {
container: 'data-testid entity-not-found',
},
};

View File

@ -2,6 +2,7 @@ import { css } from '@emotion/css';
import React from 'react';
import { GrafanaTheme2 } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { EmptyState, TextLink, useStyles2 } from '@grafana/ui';
export interface Props {
@ -15,7 +16,7 @@ export function EntityNotFound({ entity = 'Page' }: Props) {
const styles = useStyles2(getStyles);
return (
<div className={styles.container}>
<div className={styles.container} data-testid={selectors.components.EntityNotFound.container}>
<EmptyState message={`${entity} not found`} variant="not-found">
We&apos;re looking but can&apos;t seem to find this {entity.toLowerCase()}. Try returning{' '}
<TextLink href="/">home</TextLink> or seeking help on the{' '}

View File

@ -392,6 +392,10 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> {
public getPageNav(location: H.Location, navIndex: NavIndex) {
const { meta, viewPanelScene, editPanel } = this.state;
if (meta.dashboardNotFound) {
return { text: 'Not found' };
}
let pageNav: NavModelItem = {
text: this.state.title,
url: getDashboardUrl({

View File

@ -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();
});
});

View File

@ -7,6 +7,7 @@ import { selectors } from '@grafana/e2e-selectors';
import { SceneComponentProps } from '@grafana/scenes';
import { CustomScrollbar, useStyles2 } from '@grafana/ui';
import { Page } from 'app/core/components/Page/Page';
import { EntityNotFound } from 'app/core/components/PageNotFound/EntityNotFound';
import { getNavModel } from 'app/core/selectors/navModel';
import DashboardEmpty from 'app/features/dashboard/dashgrid/DashboardEmpty';
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 = (
<div className={cx(styles.body, !hasControls && styles.bodyWithoutControls)}>
<div className={cx(styles.body, !hasControls && styles.bodyWithoutControls)} key="dashboard-panels">
<bodyToRender.Component model={bodyToRender} />
</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 (
<Page navModel={navModel} pageNav={pageNav} layout={PageLayoutType.Custom}>
{editPanel && <editPanel.Component model={editPanel} />}
@ -55,7 +68,7 @@ export function DashboardSceneRenderer({ model }: SceneComponentProps<DashboardS
scopes && isScopesExpanded && styles.pageContainerWithScopesExpanded
)}
>
{scopes && <scopes.Component model={scopes} />}
{scopes && !meta.dashboardNotFound && <scopes.Component model={scopes} />}
<NavToolbarActions dashboard={model} />
{controls && hasControls && (
<div
@ -71,10 +84,7 @@ export function DashboardSceneRenderer({ model }: SceneComponentProps<DashboardS
className={styles.scrollbarContainer}
testId={selectors.pages.Dashboard.DashNav.scrollContainer}
>
<div className={cx(styles.canvasContent, isHomePage && styles.homePagePadding)}>
<>{isEmpty && emptyState}</>
{withPanels}
</div>
<div className={cx(styles.canvasContent, isHomePage && styles.homePagePadding)}>{body}</div>
</CustomScrollbar>
</div>
)}

View File

@ -8,6 +8,7 @@ import { selectors } from '@grafana/e2e-selectors';
import { config, locationService } from '@grafana/runtime';
import { SceneGridLayout, SceneQueryRunner, SceneTimeRange, UrlSyncContextProvider, VizPanel } from '@grafana/scenes';
import { playlistSrv } from 'app/features/playlist/PlaylistSrv';
import { DashboardMeta } from 'app/types';
import { buildPanelEditScene } from '../panel-edit/PanelEditor';
@ -163,9 +164,27 @@ describe('NavToolbarActions', () => {
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({
$timeRange: new SceneTimeRange({ from: 'now-6h', to: 'now' }),
meta: {
@ -177,6 +196,7 @@ function setup() {
canStar: true,
canAdmin: true,
canDelete: true,
...meta,
},
title: 'hello',
uid: 'dash-1',

View File

@ -139,7 +139,7 @@ export function ToolbarActions({ dashboard }: Props) {
toolbarActions.push({
group: 'icon-actions',
condition: meta.isSnapshot && !isEditing,
condition: meta.isSnapshot && !meta.dashboardNotFound && !isEditing,
render: () => (
<GoToSnapshotOriginButton
key="go-to-snapshot-origin"

View File

@ -92,7 +92,9 @@ export class DashboardLoaderSrv {
return result;
})
.catch(() => {
return this._dashboardLoadFailed('Not found', true);
const dash = this._dashboardLoadFailed('Not found', true);
dash.dashboard.uid = uid;
return dash;
});
} else {
throw new Error('Dashboard uid or slug required');