Scenes: Basics for rendering scenes as an embedded page (#60098)

* Basics for rendering scenes as an embedded page

* Render submenu in embedded scene

* EmbeddedScene alternative for embedding within Grafana page
This commit is contained in:
Dominik Prokop 2022-12-12 01:25:28 -08:00 committed by GitHub
parent 1a8d0e2736
commit b58cecc788
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 146 additions and 69 deletions

View File

@ -0,0 +1,31 @@
// Libraries
import React, { FC } from 'react';
import { NavModelItem } from '@grafana/data';
import { Page } from 'app/core/components/Page/Page';
import { GrafanaRouteComponentProps } from 'app/core/navigation/types';
import { getSceneByTitle } from './scenes';
export interface Props extends GrafanaRouteComponentProps<{ name: string }> {}
export const SceneEmbeddedPage: FC<Props> = (props) => {
const scene = getSceneByTitle(props.match.params.name, false);
if (!scene) {
return <h2>Scene not found</h2>;
}
const pageNav: NavModelItem = {
text: scene.state.title,
};
return (
<Page navId="scenes" pageNav={pageNav}>
<Page.Contents>
<scene.Component model={scene} />
</Page.Contents>
</Page>
);
};
export default SceneEmbeddedPage;

View File

@ -3,7 +3,7 @@ import React, { FC } from 'react';
import { useAsync } from 'react-use';
import { Stack } from '@grafana/experimental';
import { Card } from '@grafana/ui';
import { Card, LinkButton } from '@grafana/ui';
import { Page } from 'app/core/components/Page/Page';
// Types
@ -26,8 +26,16 @@ export const SceneListPage: FC<Props> = ({}) => {
<h5>Test scenes</h5>
<Stack direction="column" gap={0}>
{scenes.map((scene) => (
<Card href={`/scenes/${scene.state.title}`} key={scene.state.title}>
<Card.Heading>{scene.state.title}</Card.Heading>
<Card key={scene.title}>
<Card.Heading>{scene.title}</Card.Heading>
<Card.Actions>
<LinkButton size="sm" href={`/scenes/${scene.title}`}>
Open as standalone scene
</LinkButton>
<LinkButton size="sm" variant="secondary" href={`/scenes/embedded/${scene.title}`}>
Open as embedded scene
</LinkButton>
</Card.Actions>
</Card>
))}
</Stack>

View File

@ -8,7 +8,7 @@ import { getSceneByTitle } from './scenes';
export interface Props extends GrafanaRouteComponentProps<{ name: string }> {}
export const ScenePage: FC<Props> = (props) => {
const scene = getSceneByTitle(props.match.params.name);
const scene = getSceneByTitle(props.match.params.name, true);
if (!scene) {
return <h2>Scene not found</h2>;

View File

@ -34,6 +34,30 @@ export class Scene extends SceneObjectBase<SceneState> {
}
}
export class EmbeddedScene extends Scene {
public static Component = EmbeddedSceneRenderer;
}
function EmbeddedSceneRenderer({ model }: SceneComponentProps<Scene>) {
const { layout, isEditing, subMenu } = model.useState();
return (
<div
style={{
flexGrow: 1,
display: 'flex',
gap: '8px',
overflow: 'auto',
minHeight: '100%',
flexDirection: 'column',
}}
>
{subMenu && <subMenu.Component model={subMenu} />}
<div style={{ flexGrow: 1, display: 'flex', gap: '8px', overflow: 'auto' }}>
<layout.Component model={layout} isEditing={isEditing} />
</div>
</div>
);
}
function SceneRenderer({ model }: SceneComponentProps<Scene>) {
const { title, layout, actions = [], isEditing, $editor, subMenu } = model.useState();

View File

@ -7,13 +7,14 @@ import {
SceneFlexLayout,
VizPanel,
} from '../components';
import { EmbeddedScene } from '../components/Scene';
import { SceneTimeRange } from '../core/SceneTimeRange';
import { SceneEditManager } from '../editor/SceneEditManager';
import { getQueryRunnerWithRandomWalkQuery } from './queries';
export function getFlexLayoutTest(): Scene {
const scene = new Scene({
export function getFlexLayoutTest(standalone: boolean): Scene {
const state = {
title: 'Flex layout test',
layout: new SceneFlexLayout({
direction: 'row',
@ -54,19 +55,19 @@ export function getFlexLayoutTest(): Scene {
$timeRange: new SceneTimeRange(),
$data: getQueryRunnerWithRandomWalkQuery(),
actions: [new SceneTimePicker({})],
});
};
return scene;
return standalone ? new Scene(state) : new EmbeddedScene(state);
}
export function getScenePanelRepeaterTest(): Scene {
export function getScenePanelRepeaterTest(standalone: boolean): Scene {
const queryRunner = getQueryRunnerWithRandomWalkQuery({
seriesCount: 2,
alias: '__server_names',
scenarioId: 'random_walk',
});
const scene = new Scene({
const state = {
title: 'Panel repeater test',
layout: new ScenePanelRepeater({
layout: new SceneFlexLayout({
@ -116,7 +117,7 @@ export function getScenePanelRepeaterTest(): Scene {
}),
new SceneTimePicker({}),
],
});
};
return scene;
return standalone ? new Scene(state) : new EmbeddedScene(state);
}

View File

@ -1,5 +1,5 @@
import { VizPanel } from '../components';
import { Scene } from '../components/Scene';
import { EmbeddedScene, Scene } from '../components/Scene';
import { SceneTimePicker } from '../components/SceneTimePicker';
import { SceneFlexLayout } from '../components/layout/SceneFlexLayout';
import { SceneGridLayout } from '../components/layout/SceneGridLayout';
@ -8,8 +8,8 @@ import { SceneEditManager } from '../editor/SceneEditManager';
import { getQueryRunnerWithRandomWalkQuery } from './queries';
export function getGridLayoutTest(): Scene {
const scene = new Scene({
export function getGridLayoutTest(standalone: boolean): Scene {
const state = {
title: 'Grid layout test',
layout: new SceneGridLayout({
children: [
@ -58,7 +58,7 @@ export function getGridLayoutTest(): Scene {
$timeRange: new SceneTimeRange(),
$data: getQueryRunnerWithRandomWalkQuery(),
actions: [new SceneTimePicker({})],
});
};
return scene;
return standalone ? new Scene(state) : new EmbeddedScene(state);
}

View File

@ -1,5 +1,5 @@
import { VizPanel, SceneGridRow } from '../components';
import { Scene } from '../components/Scene';
import { EmbeddedScene, Scene } from '../components/Scene';
import { SceneTimePicker } from '../components/SceneTimePicker';
import { SceneGridLayout } from '../components/layout/SceneGridLayout';
import { SceneTimeRange } from '../core/SceneTimeRange';
@ -7,14 +7,14 @@ import { SceneEditManager } from '../editor/SceneEditManager';
import { getQueryRunnerWithRandomWalkQuery } from './queries';
export function getGridWithMultipleTimeRanges(): Scene {
export function getGridWithMultipleTimeRanges(standalone: boolean): Scene {
const globalTimeRange = new SceneTimeRange();
const row1TimeRange = new SceneTimeRange({
from: 'now-1y',
to: 'now',
});
const scene = new Scene({
const state = {
title: 'Grid with rows and different queries and time ranges',
layout: new SceneGridLayout({
children: [
@ -65,7 +65,7 @@ export function getGridWithMultipleTimeRanges(): Scene {
$timeRange: globalTimeRange,
$data: getQueryRunnerWithRandomWalkQuery(),
actions: [new SceneTimePicker({})],
});
};
return scene;
return standalone ? new Scene(state) : new EmbeddedScene(state);
}

View File

@ -1,5 +1,5 @@
import { VizPanel } from '../components';
import { Scene } from '../components/Scene';
import { EmbeddedScene, Scene } from '../components/Scene';
import { SceneTimePicker } from '../components/SceneTimePicker';
import { SceneFlexLayout } from '../components/layout/SceneFlexLayout';
import { SceneGridLayout } from '../components/layout/SceneGridLayout';
@ -8,8 +8,8 @@ import { SceneEditManager } from '../editor/SceneEditManager';
import { getQueryRunnerWithRandomWalkQuery } from './queries';
export function getMultipleGridLayoutTest(): Scene {
const scene = new Scene({
export function getMultipleGridLayoutTest(standalone: boolean): Scene {
const state = {
title: 'Multiple grid layouts test',
layout: new SceneFlexLayout({
children: [
@ -102,7 +102,7 @@ export function getMultipleGridLayoutTest(): Scene {
$timeRange: new SceneTimeRange(),
$data: getQueryRunnerWithRandomWalkQuery(),
actions: [new SceneTimePicker({})],
});
};
return scene;
return standalone ? new Scene(state) : new EmbeddedScene(state);
}

View File

@ -1,5 +1,5 @@
import { VizPanel, SceneGridRow } from '../components';
import { Scene } from '../components/Scene';
import { EmbeddedScene, Scene } from '../components/Scene';
import { SceneTimePicker } from '../components/SceneTimePicker';
import { SceneGridLayout } from '../components/layout/SceneGridLayout';
import { SceneTimeRange } from '../core/SceneTimeRange';
@ -7,8 +7,8 @@ import { SceneEditManager } from '../editor/SceneEditManager';
import { getQueryRunnerWithRandomWalkQuery } from './queries';
export function getGridWithMultipleData(): Scene {
const scene = new Scene({
export function getGridWithMultipleData(standalone: boolean): Scene {
const state = {
title: 'Grid with rows and different queries',
layout: new SceneGridLayout({
children: [
@ -96,7 +96,7 @@ export function getGridWithMultipleData(): Scene {
$timeRange: new SceneTimeRange(),
$data: getQueryRunnerWithRandomWalkQuery(),
actions: [new SceneTimePicker({})],
});
};
return scene;
return standalone ? new Scene(state) : new EmbeddedScene(state);
}

View File

@ -1,13 +1,13 @@
import { VizPanel, SceneGridLayout, SceneGridRow } from '../components';
import { Scene } from '../components/Scene';
import { EmbeddedScene, Scene } from '../components/Scene';
import { SceneTimePicker } from '../components/SceneTimePicker';
import { SceneTimeRange } from '../core/SceneTimeRange';
import { SceneEditManager } from '../editor/SceneEditManager';
import { getQueryRunnerWithRandomWalkQuery } from './queries';
export function getGridWithRowLayoutTest(): Scene {
const scene = new Scene({
export function getGridWithRowLayoutTest(standalone: boolean): Scene {
const state = {
title: 'Grid with row layout test',
layout: new SceneGridLayout({
children: [
@ -78,7 +78,7 @@ export function getGridWithRowLayoutTest(): Scene {
$timeRange: new SceneTimeRange(),
$data: getQueryRunnerWithRandomWalkQuery(),
actions: [new SceneTimePicker({})],
});
};
return scene;
return standalone ? new Scene(state) : new EmbeddedScene(state);
}

View File

@ -10,32 +10,39 @@ import { getNestedScene } from './nested';
import { getSceneWithRows } from './sceneWithRows';
import { getVariablesDemo } from './variablesDemo';
export function getScenes(): Scene[] {
interface SceneDef {
title: string;
getScene: (standalone: boolean) => Scene;
}
export function getScenes(): SceneDef[] {
return [
getFlexLayoutTest(),
getScenePanelRepeaterTest(),
getNestedScene(),
getSceneWithRows(),
getGridLayoutTest(),
getGridWithRowLayoutTest(),
getGridWithMultipleData(),
getGridWithMultipleTimeRanges(),
getMultipleGridLayoutTest(),
getVariablesDemo(),
{ title: 'Flex layout test', getScene: getFlexLayoutTest },
{ title: 'Panel repeater test', getScene: getScenePanelRepeaterTest },
{ title: 'Nested Scene demo', getScene: getNestedScene },
{ title: 'Scene with rows', getScene: getSceneWithRows },
{ title: 'Grid layout test', getScene: getGridLayoutTest },
{ title: 'Grid with row layout test', getScene: getGridWithRowLayoutTest },
{ title: 'Grid with rows and different queries', getScene: getGridWithMultipleData },
{ title: 'Grid with rows and different queries and time ranges', getScene: getGridWithMultipleTimeRanges },
{ title: 'Multiple grid layouts test', getScene: getMultipleGridLayoutTest },
{ title: 'Variables', getScene: getVariablesDemo },
];
}
const cache: Record<string, Scene> = {};
const cache: Record<string, { standalone: boolean; scene: Scene }> = {};
export function getSceneByTitle(title: string) {
export function getSceneByTitle(title: string, standalone = true) {
if (cache[title]) {
return cache[title];
if (cache[title].standalone === standalone) {
return cache[title].scene;
}
}
const scene = getScenes().find((x) => x.state.title === title);
const scene = getScenes().find((x) => x.title === title);
if (scene) {
cache[title] = scene;
cache[title] = { scene: scene.getScene(standalone), standalone };
}
return scene;
return cache[title].scene;
}

View File

@ -1,14 +1,14 @@
import { VizPanel } from '../components';
import { NestedScene } from '../components/NestedScene';
import { Scene } from '../components/Scene';
import { EmbeddedScene, Scene } from '../components/Scene';
import { SceneTimePicker } from '../components/SceneTimePicker';
import { SceneFlexLayout } from '../components/layout/SceneFlexLayout';
import { SceneTimeRange } from '../core/SceneTimeRange';
import { getQueryRunnerWithRandomWalkQuery } from './queries';
export function getNestedScene(): Scene {
const scene = new Scene({
export function getNestedScene(standalone: boolean): Scene {
const state = {
title: 'Nested Scene demo',
layout: new SceneFlexLayout({
direction: 'column',
@ -24,9 +24,9 @@ export function getNestedScene(): Scene {
$timeRange: new SceneTimeRange(),
$data: getQueryRunnerWithRandomWalkQuery(),
actions: [new SceneTimePicker({})],
});
};
return scene;
return standalone ? new Scene(state) : new EmbeddedScene(state);
}
export function getInnerScene(title: string) {

View File

@ -1,6 +1,6 @@
import { VizPanel } from '../components';
import { NestedScene } from '../components/NestedScene';
import { Scene } from '../components/Scene';
import { EmbeddedScene, Scene } from '../components/Scene';
import { SceneTimePicker } from '../components/SceneTimePicker';
import { SceneFlexLayout } from '../components/layout/SceneFlexLayout';
import { SceneTimeRange } from '../core/SceneTimeRange';
@ -8,8 +8,8 @@ import { SceneEditManager } from '../editor/SceneEditManager';
import { getQueryRunnerWithRandomWalkQuery } from './queries';
export function getSceneWithRows(): Scene {
const scene = new Scene({
export function getSceneWithRows(standalone: boolean): Scene {
const state = {
title: 'Scene with rows',
layout: new SceneFlexLayout({
direction: 'column',
@ -56,7 +56,7 @@ export function getSceneWithRows(): Scene {
$timeRange: new SceneTimeRange(),
$data: getQueryRunnerWithRandomWalkQuery(),
actions: [new SceneTimePicker({})],
});
};
return scene;
return standalone ? new Scene(state) : new EmbeddedScene(state);
}

View File

@ -1,5 +1,5 @@
import { VizPanel } from '../components';
import { Scene } from '../components/Scene';
import { EmbeddedScene, Scene } from '../components/Scene';
import { SceneCanvasText } from '../components/SceneCanvasText';
import { SceneSubMenu } from '../components/SceneSubMenu';
import { SceneTimePicker } from '../components/SceneTimePicker';
@ -13,8 +13,8 @@ import { TestVariable } from '../variables/variants/TestVariable';
import { getQueryRunnerWithRandomWalkQuery } from './queries';
export function getVariablesDemo(): Scene {
const scene = new Scene({
export function getVariablesDemo(standalone: boolean): Scene {
const state = {
title: 'Variables',
$variables: new SceneVariableSet({
variables: [
@ -81,7 +81,7 @@ export function getVariablesDemo(): Scene {
subMenu: new SceneSubMenu({
children: [new VariableValueSelectors({})],
}),
});
};
return scene;
return standalone ? new Scene(state) : new EmbeddedScene(state);
}

View File

@ -553,6 +553,12 @@ export function getDynamicDashboardRoutes(cfg = config): RouteDescriptor[] {
() => import(/* webpackChunkName: "scenes"*/ 'app/features/scenes/dashboard/DashboardScenePage')
),
},
{
path: '/scenes/embedded/:name',
component: SafeDynamicImport(
() => import(/* webpackChunkName: "scenes"*/ 'app/features/scenes/SceneEmbeddedPage')
),
},
{
path: '/scenes/:name',
component: SafeDynamicImport(() => import(/* webpackChunkName: "scenes"*/ 'app/features/scenes/ScenePage')),