From b58cecc7884c3337274eb266b1ec10fe287a65c9 Mon Sep 17 00:00:00 2001 From: Dominik Prokop Date: Mon, 12 Dec 2022 01:25:28 -0800 Subject: [PATCH] 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 --- .../app/features/scenes/SceneEmbeddedPage.tsx | 31 ++++++++++++++ public/app/features/scenes/SceneListPage.tsx | 14 +++++-- public/app/features/scenes/ScenePage.tsx | 2 +- .../app/features/scenes/components/Scene.tsx | 24 +++++++++++ public/app/features/scenes/scenes/demo.tsx | 17 ++++---- public/app/features/scenes/scenes/grid.tsx | 10 ++--- .../scenes/scenes/gridMultiTimeRange.tsx | 10 ++--- .../features/scenes/scenes/gridMultiple.tsx | 10 ++--- .../scenes/scenes/gridWithMultipleData.tsx | 10 ++--- .../features/scenes/scenes/gridWithRow.tsx | 10 ++--- public/app/features/scenes/scenes/index.tsx | 41 +++++++++++-------- public/app/features/scenes/scenes/nested.tsx | 10 ++--- .../features/scenes/scenes/sceneWithRows.tsx | 10 ++--- .../features/scenes/scenes/variablesDemo.tsx | 10 ++--- public/app/routes/routes.tsx | 6 +++ 15 files changed, 146 insertions(+), 69 deletions(-) create mode 100644 public/app/features/scenes/SceneEmbeddedPage.tsx diff --git a/public/app/features/scenes/SceneEmbeddedPage.tsx b/public/app/features/scenes/SceneEmbeddedPage.tsx new file mode 100644 index 00000000000..8b1939a94af --- /dev/null +++ b/public/app/features/scenes/SceneEmbeddedPage.tsx @@ -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) => { + const scene = getSceneByTitle(props.match.params.name, false); + + if (!scene) { + return

Scene not found

; + } + + const pageNav: NavModelItem = { + text: scene.state.title, + }; + return ( + + + + + + ); +}; + +export default SceneEmbeddedPage; diff --git a/public/app/features/scenes/SceneListPage.tsx b/public/app/features/scenes/SceneListPage.tsx index 59f48c9ab7c..0590acce524 100644 --- a/public/app/features/scenes/SceneListPage.tsx +++ b/public/app/features/scenes/SceneListPage.tsx @@ -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 = ({}) => {
Test scenes
{scenes.map((scene) => ( - - {scene.state.title} + + {scene.title} + + + Open as standalone scene + + + Open as embedded scene + + ))} diff --git a/public/app/features/scenes/ScenePage.tsx b/public/app/features/scenes/ScenePage.tsx index c8a0fbbdfdc..9c17614d150 100644 --- a/public/app/features/scenes/ScenePage.tsx +++ b/public/app/features/scenes/ScenePage.tsx @@ -8,7 +8,7 @@ import { getSceneByTitle } from './scenes'; export interface Props extends GrafanaRouteComponentProps<{ name: string }> {} export const ScenePage: FC = (props) => { - const scene = getSceneByTitle(props.match.params.name); + const scene = getSceneByTitle(props.match.params.name, true); if (!scene) { return

Scene not found

; diff --git a/public/app/features/scenes/components/Scene.tsx b/public/app/features/scenes/components/Scene.tsx index 88b28df0619..e2aba208bca 100644 --- a/public/app/features/scenes/components/Scene.tsx +++ b/public/app/features/scenes/components/Scene.tsx @@ -34,6 +34,30 @@ export class Scene extends SceneObjectBase { } } +export class EmbeddedScene extends Scene { + public static Component = EmbeddedSceneRenderer; +} + +function EmbeddedSceneRenderer({ model }: SceneComponentProps) { + const { layout, isEditing, subMenu } = model.useState(); + return ( +
+ {subMenu && } +
+ +
+
+ ); +} function SceneRenderer({ model }: SceneComponentProps) { const { title, layout, actions = [], isEditing, $editor, subMenu } = model.useState(); diff --git a/public/app/features/scenes/scenes/demo.tsx b/public/app/features/scenes/scenes/demo.tsx index bbc6bbc09f2..98793e5fe6b 100644 --- a/public/app/features/scenes/scenes/demo.tsx +++ b/public/app/features/scenes/scenes/demo.tsx @@ -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); } diff --git a/public/app/features/scenes/scenes/grid.tsx b/public/app/features/scenes/scenes/grid.tsx index 3516ff8969c..5137742e2e0 100644 --- a/public/app/features/scenes/scenes/grid.tsx +++ b/public/app/features/scenes/scenes/grid.tsx @@ -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); } diff --git a/public/app/features/scenes/scenes/gridMultiTimeRange.tsx b/public/app/features/scenes/scenes/gridMultiTimeRange.tsx index 169339a3156..f9d0124baf4 100644 --- a/public/app/features/scenes/scenes/gridMultiTimeRange.tsx +++ b/public/app/features/scenes/scenes/gridMultiTimeRange.tsx @@ -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); } diff --git a/public/app/features/scenes/scenes/gridMultiple.tsx b/public/app/features/scenes/scenes/gridMultiple.tsx index b16e2eb6bde..0cacfdb7087 100644 --- a/public/app/features/scenes/scenes/gridMultiple.tsx +++ b/public/app/features/scenes/scenes/gridMultiple.tsx @@ -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); } diff --git a/public/app/features/scenes/scenes/gridWithMultipleData.tsx b/public/app/features/scenes/scenes/gridWithMultipleData.tsx index 5d2e1a893ab..879fc6c5b30 100644 --- a/public/app/features/scenes/scenes/gridWithMultipleData.tsx +++ b/public/app/features/scenes/scenes/gridWithMultipleData.tsx @@ -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); } diff --git a/public/app/features/scenes/scenes/gridWithRow.tsx b/public/app/features/scenes/scenes/gridWithRow.tsx index d333ee167fb..539f3b0e17c 100644 --- a/public/app/features/scenes/scenes/gridWithRow.tsx +++ b/public/app/features/scenes/scenes/gridWithRow.tsx @@ -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); } diff --git a/public/app/features/scenes/scenes/index.tsx b/public/app/features/scenes/scenes/index.tsx index d8900a858b2..94b2c479334 100644 --- a/public/app/features/scenes/scenes/index.tsx +++ b/public/app/features/scenes/scenes/index.tsx @@ -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 = {}; +const cache: Record = {}; -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; } diff --git a/public/app/features/scenes/scenes/nested.tsx b/public/app/features/scenes/scenes/nested.tsx index 05fb096b339..cd298a80ab0 100644 --- a/public/app/features/scenes/scenes/nested.tsx +++ b/public/app/features/scenes/scenes/nested.tsx @@ -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) { diff --git a/public/app/features/scenes/scenes/sceneWithRows.tsx b/public/app/features/scenes/scenes/sceneWithRows.tsx index 6548103f91c..6ae6111157e 100644 --- a/public/app/features/scenes/scenes/sceneWithRows.tsx +++ b/public/app/features/scenes/scenes/sceneWithRows.tsx @@ -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); } diff --git a/public/app/features/scenes/scenes/variablesDemo.tsx b/public/app/features/scenes/scenes/variablesDemo.tsx index 2e71cf55865..b5280456127 100644 --- a/public/app/features/scenes/scenes/variablesDemo.tsx +++ b/public/app/features/scenes/scenes/variablesDemo.tsx @@ -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); } diff --git a/public/app/routes/routes.tsx b/public/app/routes/routes.tsx index a75042077fe..f8960581ad9 100644 --- a/public/app/routes/routes.tsx +++ b/public/app/routes/routes.tsx @@ -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')),