diff --git a/public/app/features/scenes/components/Scene.tsx b/public/app/features/scenes/components/Scene.tsx index 2848d2d2a8e..fe4731c13da 100644 --- a/public/app/features/scenes/components/Scene.tsx +++ b/public/app/features/scenes/components/Scene.tsx @@ -14,6 +14,7 @@ interface SceneState extends SceneObjectStatePlain { title: string; layout: SceneObject; actions?: SceneObject[]; + subMenu?: SceneObject; isEditing?: boolean; } @@ -33,7 +34,7 @@ export class Scene extends SceneObjectBase { } function SceneRenderer({ model }: SceneComponentProps) { - const { title, layout, actions = [], isEditing, $editor } = model.useState(); + const { title, layout, actions = [], isEditing, $editor, subMenu } = model.useState(); const toolbarActions = (actions ?? []).map((action) => ); @@ -55,9 +56,12 @@ function SceneRenderer({ model }: SceneComponentProps) { return ( -
- - {$editor && <$editor.Component model={$editor} isEditing={isEditing} />} +
+ {subMenu && } +
+ + {$editor && <$editor.Component model={$editor} isEditing={isEditing} />} +
); diff --git a/public/app/features/scenes/components/SceneCanvasText.tsx b/public/app/features/scenes/components/SceneCanvasText.tsx index aee61d0dc85..93b5e9a74a8 100644 --- a/public/app/features/scenes/components/SceneCanvasText.tsx +++ b/public/app/features/scenes/components/SceneCanvasText.tsx @@ -4,6 +4,7 @@ import { Field, Input } from '@grafana/ui'; import { SceneObjectBase } from '../core/SceneObjectBase'; import { SceneComponentProps, SceneLayoutChildState } from '../core/types'; +import { sceneTemplateInterpolator } from '../variables/sceneTemplateInterpolator'; export interface SceneCanvasTextState extends SceneLayoutChildState { text: string; @@ -13,8 +14,10 @@ export interface SceneCanvasTextState extends SceneLayoutChildState { export class SceneCanvasText extends SceneObjectBase { public static Editor = Editor; + public static Component = ({ model }: SceneComponentProps) => { const { text, fontSize = 20, align = 'left' } = model.useState(); + const textInterpolated = sceneTemplateInterpolator(text, model); const style: CSSProperties = { fontSize: fontSize, @@ -25,7 +28,7 @@ export class SceneCanvasText extends SceneObjectBase { justifyContent: align, }; - return
{text}
; + return
{textInterpolated}
; }; } diff --git a/public/app/features/scenes/components/SceneSubMenu.tsx b/public/app/features/scenes/components/SceneSubMenu.tsx new file mode 100644 index 00000000000..2563c2f2a37 --- /dev/null +++ b/public/app/features/scenes/components/SceneSubMenu.tsx @@ -0,0 +1,22 @@ +import React from 'react'; + +import { SceneObjectBase } from '../core/SceneObjectBase'; +import { SceneLayoutState, SceneComponentProps } from '../core/types'; + +interface SceneSubMenuState extends SceneLayoutState {} + +export class SceneSubMenu extends SceneObjectBase { + public static Component = SceneSubMenuRenderer; +} + +function SceneSubMenuRenderer({ model }: SceneComponentProps) { + const { children } = model.useState(); + + return ( +
+ {children.map((child) => ( + + ))} +
+ ); +} diff --git a/public/app/features/scenes/core/SceneObjectBase.test.ts b/public/app/features/scenes/core/SceneObjectBase.test.ts index 17c86014163..c8d3f138e76 100644 --- a/public/app/features/scenes/core/SceneObjectBase.test.ts +++ b/public/app/features/scenes/core/SceneObjectBase.test.ts @@ -1,4 +1,4 @@ -import { SceneVariableSet } from '../variables/SceneVariableSet'; +import { SceneVariableSet } from '../variables/sets/SceneVariableSet'; import { SceneDataNode } from './SceneDataNode'; import { SceneObjectBase } from './SceneObjectBase'; diff --git a/public/app/features/scenes/core/SceneObjectBase.tsx b/public/app/features/scenes/core/SceneObjectBase.tsx index d5871def18b..8c4e029e16d 100644 --- a/public/app/features/scenes/core/SceneObjectBase.tsx +++ b/public/app/features/scenes/core/SceneObjectBase.tsx @@ -9,7 +9,9 @@ import { SceneComponentWrapper } from './SceneComponentWrapper'; import { SceneObjectStateChangedEvent } from './events'; import { SceneDataState, SceneObject, SceneComponent, SceneEditor, SceneTimeRange, SceneObjectState } from './types'; -export abstract class SceneObjectBase implements SceneObject { +export abstract class SceneObjectBase + implements SceneObject +{ private _isActive = false; private _subject = new Subject(); private _state: TState; diff --git a/public/app/features/scenes/core/types.ts b/public/app/features/scenes/core/types.ts index 9791ba034e5..b531d6f95e6 100644 --- a/public/app/features/scenes/core/types.ts +++ b/public/app/features/scenes/core/types.ts @@ -114,6 +114,7 @@ export interface SceneEditor extends SceneObject { } export interface SceneTimeRangeState extends SceneObjectStatePlain, TimeRange {} + export interface SceneTimeRange extends SceneObject { onTimeRangeChange(timeRange: TimeRange): void; onIntervalChanged(interval: string): void; diff --git a/public/app/features/scenes/scenes/index.tsx b/public/app/features/scenes/scenes/index.tsx index c28184ab78d..0a1db6b2830 100644 --- a/public/app/features/scenes/scenes/index.tsx +++ b/public/app/features/scenes/scenes/index.tsx @@ -3,9 +3,10 @@ import { Scene } from '../components/Scene'; import { getFlexLayoutTest, getScenePanelRepeaterTest } from './demo'; import { getNestedScene } from './nested'; import { getSceneWithRows } from './sceneWithRows'; +import { getVariablesDemo } from './variablesDemo'; export function getScenes(): Scene[] { - return [getFlexLayoutTest(), getScenePanelRepeaterTest(), getNestedScene(), getSceneWithRows()]; + return [getFlexLayoutTest(), getScenePanelRepeaterTest(), getNestedScene(), getSceneWithRows(), getVariablesDemo()]; } const cache: Record = {}; diff --git a/public/app/features/scenes/scenes/variablesDemo.tsx b/public/app/features/scenes/scenes/variablesDemo.tsx new file mode 100644 index 00000000000..45ae1149d8c --- /dev/null +++ b/public/app/features/scenes/scenes/variablesDemo.tsx @@ -0,0 +1,63 @@ +import { getDefaultTimeRange } from '@grafana/data'; + +import { Scene } from '../components/Scene'; +import { SceneCanvasText } from '../components/SceneCanvasText'; +import { SceneFlexLayout } from '../components/SceneFlexLayout'; +import { SceneSubMenu } from '../components/SceneSubMenu'; +import { SceneTimePicker } from '../components/SceneTimePicker'; +import { SceneTimeRange } from '../core/SceneTimeRange'; +import { VariableValueSelectors } from '../variables/components/VariableValueSelectors'; +import { SceneVariableSet } from '../variables/sets/SceneVariableSet'; +import { TestVariable } from '../variables/variants/TestVariable'; + +export function getVariablesDemo(): Scene { + const scene = new Scene({ + title: 'Variables', + layout: new SceneFlexLayout({ + direction: 'row', + children: [ + new SceneCanvasText({ + text: 'Some text with a variable: ${server} - ${pod}', + fontSize: 40, + align: 'center', + }), + ], + }), + $variables: new SceneVariableSet({ + variables: [ + new TestVariable({ + name: 'server', + query: 'A.*', + value: 'server', + text: '', + delayMs: 1000, + options: [], + }), + new TestVariable({ + name: 'pod', + query: 'A.$server.*', + value: 'pod', + delayMs: 1000, + text: '', + options: [], + }), + new TestVariable({ + name: 'handler', + query: 'A.$server.$pod.*', + value: 'handler', + delayMs: 1000, + isMulti: true, + text: '', + options: [], + }), + ], + }), + $timeRange: new SceneTimeRange(getDefaultTimeRange()), + actions: [new SceneTimePicker({})], + subMenu: new SceneSubMenu({ + children: [new VariableValueSelectors({})], + }), + }); + + return scene; +} diff --git a/public/app/features/scenes/variables/components/VariableValueSelect.tsx b/public/app/features/scenes/variables/components/VariableValueSelect.tsx new file mode 100644 index 00000000000..b70dcc28920 --- /dev/null +++ b/public/app/features/scenes/variables/components/VariableValueSelect.tsx @@ -0,0 +1,39 @@ +import { isArray } from 'lodash'; +import React from 'react'; + +import { Select, MultiSelect } from '@grafana/ui'; + +import { SceneComponentProps } from '../../core/types'; +import { MultiValueVariable } from '../variants/MultiValueVariable'; + +export function VariableValueSelect({ model }: SceneComponentProps) { + const { value, key, loading, isMulti, options } = model.useState(); + + if (isMulti) { + return ( + + ); + } + + return ( +