+ {controls && (
+
+ {controls.map((control) => (
+
+ ))}
+
+ )}
+
);
}
+
+function getStyles(theme: GrafanaTheme2) {
+ return {
+ body: css({
+ flexGrow: 1,
+ display: 'flex',
+ gap: '8px',
+ }),
+ controls: css({
+ display: 'flex',
+ gap: theme.spacing(1),
+ alignItems: 'center',
+ }),
+ };
+}
diff --git a/public/app/features/scenes/dashboard/DashboardsLoader.test.ts b/public/app/features/scenes/dashboard/DashboardsLoader.test.ts
index 63e672ace35..8684e5d2b3a 100644
--- a/public/app/features/scenes/dashboard/DashboardsLoader.test.ts
+++ b/public/app/features/scenes/dashboard/DashboardsLoader.test.ts
@@ -135,7 +135,7 @@ describe('DashboardLoader', () => {
expect(scene.state.uid).toBe('test-uid');
expect(scene.state?.$timeRange?.state.value.raw).toEqual(dash.time);
expect(scene.state?.$variables?.state.variables).toHaveLength(1);
- expect(scene.state.subMenu).toBeDefined();
+ expect(scene.state.controls).toBeDefined();
});
});
@@ -162,9 +162,10 @@ describe('DashboardLoader', () => {
const oldModel = new DashboardModel(dashboard);
const scene = createDashboardSceneFromDashboardModel(oldModel);
+ const body = scene.state.body as SceneGridLayout;
- expect(scene.state.body.state.children).toHaveLength(1);
- const rowScene = scene.state.body.state.children[0] as SceneGridRow;
+ expect(body.state.children).toHaveLength(1);
+ const rowScene = body.state.children[0] as SceneGridRow;
expect(rowScene).toBeInstanceOf(SceneGridRow);
expect(rowScene.state.title).toEqual(row.title);
expect(rowScene.state.placement?.y).toEqual(row.gridPos!.y);
@@ -226,16 +227,17 @@ describe('DashboardLoader', () => {
const oldModel = new DashboardModel(dashboard);
const scene = createDashboardSceneFromDashboardModel(oldModel);
+ const body = scene.state.body as SceneGridLayout;
- expect(scene.state.body.state.children).toHaveLength(3);
- expect(scene.state.body).toBeInstanceOf(SceneGridLayout);
+ expect(body.state.children).toHaveLength(3);
+ expect(body).toBeInstanceOf(SceneGridLayout);
// Panel out of row
- expect(scene.state.body.state.children[0]).toBeInstanceOf(VizPanel);
- const panelOutOfRowVizPanel = scene.state.body.state.children[0] as VizPanel;
+ expect(body.state.children[0]).toBeInstanceOf(VizPanel);
+ const panelOutOfRowVizPanel = body.state.children[0] as VizPanel;
expect(panelOutOfRowVizPanel.state.title).toBe(panelOutOfRow.title);
// Row with panel
- expect(scene.state.body.state.children[1]).toBeInstanceOf(SceneGridRow);
- const rowWithPanelsScene = scene.state.body.state.children[1] as SceneGridRow;
+ expect(body.state.children[1]).toBeInstanceOf(SceneGridRow);
+ const rowWithPanelsScene = body.state.children[1] as SceneGridRow;
expect(rowWithPanelsScene.state.title).toBe(rowWithPanel.title);
expect(rowWithPanelsScene.state.children).toHaveLength(1);
// Panel within row
@@ -243,8 +245,8 @@ describe('DashboardLoader', () => {
const panelInRowVizPanel = rowWithPanelsScene.state.children[0] as VizPanel;
expect(panelInRowVizPanel.state.title).toBe(panelInRow.title);
// Empty row
- expect(scene.state.body.state.children[2]).toBeInstanceOf(SceneGridRow);
- const emptyRowScene = scene.state.body.state.children[2] as SceneGridRow;
+ expect(body.state.children[2]).toBeInstanceOf(SceneGridRow);
+ const emptyRowScene = body.state.children[2] as SceneGridRow;
expect(emptyRowScene.state.title).toBe(emptyRow.title);
expect(emptyRowScene.state.children).toHaveLength(0);
});
diff --git a/public/app/features/scenes/dashboard/DashboardsLoader.ts b/public/app/features/scenes/dashboard/DashboardsLoader.ts
index d2bcd4de2a7..bd19241d56e 100644
--- a/public/app/features/scenes/dashboard/DashboardsLoader.ts
+++ b/public/app/features/scenes/dashboard/DashboardsLoader.ts
@@ -13,7 +13,6 @@ import {
SceneTimeRange,
SceneObject,
SceneQueryRunner,
- SceneSubMenu,
SceneVariableSet,
VariableValueSelectors,
SceneVariable,
@@ -150,7 +149,6 @@ export function createSceneObjectsForPanels(oldPanels: PanelModel[]): SceneObjec
}
export function createDashboardSceneFromDashboardModel(oldModel: DashboardModel) {
- let subMenu: SceneSubMenu | undefined = undefined;
let variables: SceneVariableSet | undefined = undefined;
if (oldModel.templating.list.length) {
@@ -166,9 +164,7 @@ export function createDashboardSceneFromDashboardModel(oldModel: DashboardModel)
// TODO: Remove filter
// Added temporarily to allow skipping non-compatible variables
.filter((v): v is SceneVariable => Boolean(v));
- subMenu = new SceneSubMenu({
- children: [new VariableValueSelectors({})],
- });
+
variables = new SceneVariableSet({
variables: variableObjects,
});
@@ -183,7 +179,9 @@ export function createDashboardSceneFromDashboardModel(oldModel: DashboardModel)
$timeRange: new SceneTimeRange(oldModel.time),
actions: [new SceneTimePicker({})],
$variables: variables,
- subMenu,
+ ...(variables && {
+ controls: [new VariableValueSelectors({})],
+ }),
});
}
diff --git a/public/app/features/scenes/scenes/demo.tsx b/public/app/features/scenes/scenes/demo.tsx
index a8c3c861987..489e3088a23 100644
--- a/public/app/features/scenes/scenes/demo.tsx
+++ b/public/app/features/scenes/scenes/demo.tsx
@@ -2,21 +2,21 @@ import {
SceneFlexLayout,
SceneTimeRange,
SceneTimePicker,
- ScenePanelRepeater,
+ SceneByFrameRepeater,
VizPanel,
SceneCanvasText,
SceneToolbarInput,
- EmbeddedScene,
+ SceneDataNode,
} from '@grafana/scenes';
import { panelBuilders } from '../builders/panelBuilders';
-import { Scene } from '../components/Scene';
+import { DashboardScene } from '../dashboard/DashboardScene';
import { SceneEditManager } from '../editor/SceneEditManager';
import { getQueryRunnerWithRandomWalkQuery } from './queries';
-export function getFlexLayoutTest(standalone: boolean): Scene | EmbeddedScene {
- const state = {
+export function getFlexLayoutTest(): DashboardScene {
+ return new DashboardScene({
title: 'Flex layout test',
body: new SceneFlexLayout({
direction: 'row',
@@ -62,47 +62,53 @@ export function getFlexLayoutTest(standalone: boolean): Scene | EmbeddedScene {
$timeRange: new SceneTimeRange(),
$data: getQueryRunnerWithRandomWalkQuery(),
actions: [new SceneTimePicker({})],
- };
-
- return standalone ? new Scene(state) : new EmbeddedScene(state);
+ });
}
-export function getScenePanelRepeaterTest(standalone: boolean): Scene | EmbeddedScene {
+export function getScenePanelRepeaterTest(): DashboardScene {
const queryRunner = getQueryRunnerWithRandomWalkQuery({
seriesCount: 2,
alias: '__server_names',
scenarioId: 'random_walk',
});
- const state = {
+ return new DashboardScene({
title: 'Panel repeater test',
- body: new ScenePanelRepeater({
- layout: new SceneFlexLayout({
+ body: new SceneByFrameRepeater({
+ body: new SceneFlexLayout({
direction: 'column',
- children: [
- new SceneFlexLayout({
- direction: 'row',
- placement: { minHeight: 200 },
- children: [
- new VizPanel({
- pluginId: 'timeseries',
- title: 'Title',
- options: {
- legend: { displayMode: 'hidden' },
- },
- }),
- new VizPanel({
- placement: { width: 300 },
- pluginId: 'stat',
- fieldConfig: { defaults: { displayName: 'Last' }, overrides: [] },
- options: {
- graphMode: 'none',
- },
- }),
- ],
- }),
- ],
+ children: [],
}),
+ getLayoutChild: (data, frame, frameIndex) => {
+ return new SceneFlexLayout({
+ key: `panel-${frameIndex}`,
+ $data: new SceneDataNode({
+ data: {
+ ...data,
+ series: [frame],
+ },
+ }),
+ direction: 'row',
+ placement: { minHeight: 200 },
+ children: [
+ new VizPanel({
+ pluginId: 'timeseries',
+ title: 'Title',
+ options: {
+ legend: { displayMode: 'hidden' },
+ },
+ }),
+ new VizPanel({
+ placement: { width: 300 },
+ pluginId: 'stat',
+ fieldConfig: { defaults: { displayName: 'Last' }, overrides: [] },
+ options: {
+ graphMode: 'none',
+ },
+ }),
+ ],
+ });
+ },
}),
$editor: new SceneEditManager({}),
$timeRange: new SceneTimeRange(),
@@ -124,7 +130,5 @@ export function getScenePanelRepeaterTest(standalone: boolean): Scene | Embedded
}),
new SceneTimePicker({}),
],
- };
-
- 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 6cbfce18580..4b7e85e9aa3 100644
--- a/public/app/features/scenes/scenes/grid.tsx
+++ b/public/app/features/scenes/scenes/grid.tsx
@@ -1,19 +1,12 @@
-import {
- VizPanel,
- SceneTimePicker,
- SceneFlexLayout,
- SceneGridLayout,
- SceneTimeRange,
- EmbeddedScene,
-} from '@grafana/scenes';
+import { VizPanel, SceneTimePicker, SceneFlexLayout, SceneGridLayout, SceneTimeRange } from '@grafana/scenes';
-import { Scene } from '../components/Scene';
+import { DashboardScene } from '../dashboard/DashboardScene';
import { SceneEditManager } from '../editor/SceneEditManager';
import { getQueryRunnerWithRandomWalkQuery } from './queries';
-export function getGridLayoutTest(standalone: boolean): Scene | EmbeddedScene {
- const state = {
+export function getGridLayoutTest(): DashboardScene {
+ return new DashboardScene({
title: 'Grid layout test',
body: new SceneGridLayout({
children: [
@@ -58,7 +51,5 @@ export function getGridLayoutTest(standalone: boolean): Scene | EmbeddedScene {
$timeRange: new SceneTimeRange(),
$data: getQueryRunnerWithRandomWalkQuery(),
actions: [new SceneTimePicker({})],
- };
-
- 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 dc856427bb2..052f5ee9903 100644
--- a/public/app/features/scenes/scenes/gridMultiTimeRange.tsx
+++ b/public/app/features/scenes/scenes/gridMultiTimeRange.tsx
@@ -1,25 +1,18 @@
-import {
- VizPanel,
- SceneGridRow,
- SceneTimePicker,
- SceneGridLayout,
- SceneTimeRange,
- EmbeddedScene,
-} from '@grafana/scenes';
+import { VizPanel, SceneGridRow, SceneTimePicker, SceneGridLayout, SceneTimeRange } from '@grafana/scenes';
-import { Scene } from '../components/Scene';
+import { DashboardScene } from '../dashboard/DashboardScene';
import { SceneEditManager } from '../editor/SceneEditManager';
import { getQueryRunnerWithRandomWalkQuery } from './queries';
-export function getGridWithMultipleTimeRanges(standalone: boolean): Scene | EmbeddedScene {
+export function getGridWithMultipleTimeRanges(): DashboardScene {
const globalTimeRange = new SceneTimeRange();
const row1TimeRange = new SceneTimeRange({
from: 'now-1y',
to: 'now',
});
- const state = {
+ return new DashboardScene({
title: 'Grid with rows and different queries and time ranges',
body: new SceneGridLayout({
children: [
@@ -66,7 +59,5 @@ export function getGridWithMultipleTimeRanges(standalone: boolean): Scene | Embe
$timeRange: globalTimeRange,
$data: getQueryRunnerWithRandomWalkQuery(),
actions: [new SceneTimePicker({})],
- };
-
- 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 bfd49bc6e00..76cb257b203 100644
--- a/public/app/features/scenes/scenes/gridMultiple.tsx
+++ b/public/app/features/scenes/scenes/gridMultiple.tsx
@@ -1,19 +1,12 @@
-import {
- VizPanel,
- SceneTimePicker,
- SceneFlexLayout,
- SceneGridLayout,
- SceneTimeRange,
- EmbeddedScene,
-} from '@grafana/scenes';
+import { VizPanel, SceneTimePicker, SceneFlexLayout, SceneGridLayout, SceneTimeRange } from '@grafana/scenes';
-import { Scene } from '../components/Scene';
+import { DashboardScene } from '../dashboard/DashboardScene';
import { SceneEditManager } from '../editor/SceneEditManager';
import { getQueryRunnerWithRandomWalkQuery } from './queries';
-export function getMultipleGridLayoutTest(standalone: boolean): Scene | EmbeddedScene {
- const state = {
+export function getMultipleGridLayoutTest(): DashboardScene {
+ return new DashboardScene({
title: 'Multiple grid layouts test',
body: new SceneFlexLayout({
children: [
@@ -98,7 +91,5 @@ export function getMultipleGridLayoutTest(standalone: boolean): Scene | Embedded
$timeRange: new SceneTimeRange(),
$data: getQueryRunnerWithRandomWalkQuery(),
actions: [new SceneTimePicker({})],
- };
-
- 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 683d118932b..051c539c61a 100644
--- a/public/app/features/scenes/scenes/gridWithMultipleData.tsx
+++ b/public/app/features/scenes/scenes/gridWithMultipleData.tsx
@@ -1,19 +1,12 @@
-import {
- VizPanel,
- SceneGridRow,
- SceneTimePicker,
- SceneGridLayout,
- SceneTimeRange,
- EmbeddedScene,
-} from '@grafana/scenes';
+import { VizPanel, SceneGridRow, SceneTimePicker, SceneGridLayout, SceneTimeRange } from '@grafana/scenes';
-import { Scene } from '../components/Scene';
+import { DashboardScene } from '../dashboard/DashboardScene';
import { SceneEditManager } from '../editor/SceneEditManager';
import { getQueryRunnerWithRandomWalkQuery } from './queries';
-export function getGridWithMultipleData(standalone: boolean): Scene | EmbeddedScene {
- const state = {
+export function getGridWithMultipleData(): DashboardScene {
+ return new DashboardScene({
title: 'Grid with rows and different queries',
body: new SceneGridLayout({
children: [
@@ -93,7 +86,5 @@ export function getGridWithMultipleData(standalone: boolean): Scene | EmbeddedSc
$timeRange: new SceneTimeRange(),
$data: getQueryRunnerWithRandomWalkQuery(),
actions: [new SceneTimePicker({})],
- };
-
- 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 d3739040722..8f3c7f09500 100644
--- a/public/app/features/scenes/scenes/gridWithRow.tsx
+++ b/public/app/features/scenes/scenes/gridWithRow.tsx
@@ -1,19 +1,12 @@
-import {
- VizPanel,
- SceneGridLayout,
- SceneGridRow,
- SceneTimePicker,
- SceneTimeRange,
- EmbeddedScene,
-} from '@grafana/scenes';
+import { VizPanel, SceneGridLayout, SceneGridRow, SceneTimePicker, SceneTimeRange } from '@grafana/scenes';
-import { Scene } from '../components/Scene';
+import { DashboardScene } from '../dashboard/DashboardScene';
import { SceneEditManager } from '../editor/SceneEditManager';
import { getQueryRunnerWithRandomWalkQuery } from './queries';
-export function getGridWithRowLayoutTest(standalone: boolean): Scene | EmbeddedScene {
- const state = {
+export function getGridWithRowLayoutTest(): DashboardScene {
+ return new DashboardScene({
title: 'Grid with row layout test',
body: new SceneGridLayout({
children: [
@@ -76,7 +69,5 @@ export function getGridWithRowLayoutTest(standalone: boolean): Scene | EmbeddedS
$timeRange: new SceneTimeRange(),
$data: getQueryRunnerWithRandomWalkQuery(),
actions: [new SceneTimePicker({})],
- };
-
- return standalone ? new Scene(state) : new EmbeddedScene(state);
+ });
}
diff --git a/public/app/features/scenes/scenes/gridWithRows.tsx b/public/app/features/scenes/scenes/gridWithRows.tsx
index c3bde62bb1f..ad1c871d35d 100644
--- a/public/app/features/scenes/scenes/gridWithRows.tsx
+++ b/public/app/features/scenes/scenes/gridWithRows.tsx
@@ -7,12 +7,11 @@ import {
SceneTimeRange,
} from '@grafana/scenes';
-import { Scene } from '../components/Scene';
-import { SceneEditManager } from '../editor/SceneEditManager';
+import { DashboardScene } from '../dashboard/DashboardScene';
import { getQueryRunnerWithRandomWalkQuery } from './queries';
-export function getGridWithRowsTest(): Scene {
+export function getGridWithRowsTest(): DashboardScene {
const panel = new VizPanel({
pluginId: 'timeseries',
title: 'Fill height',
@@ -77,12 +76,12 @@ export function getGridWithRowsTest(): Scene {
}),
],
});
- const scene = new Scene({
+
+ const scene = new DashboardScene({
title: 'Grid rows test',
body: new SceneGridLayout({
children: [cell1, cell2, row1, row2],
}),
- $editor: new SceneEditManager({}),
$timeRange: new SceneTimeRange(),
$data: getQueryRunnerWithRandomWalkQuery(),
actions: [new SceneTimePicker({})],
diff --git a/public/app/features/scenes/scenes/index.tsx b/public/app/features/scenes/scenes/index.tsx
index bdb94daebe4..f398ff1a2ae 100644
--- a/public/app/features/scenes/scenes/index.tsx
+++ b/public/app/features/scenes/scenes/index.tsx
@@ -1,6 +1,4 @@
-import { EmbeddedScene, SceneObjectBase, SceneState } from '@grafana/scenes';
-
-import { Scene } from '../components/Scene';
+import { DashboardScene } from '../dashboard/DashboardScene';
import { getFlexLayoutTest, getScenePanelRepeaterTest } from './demo';
import { getGridLayoutTest } from './grid';
@@ -16,7 +14,7 @@ import { getVariablesDemo, getVariablesDemoWithAll } from './variablesDemo';
interface SceneDef {
title: string;
- getScene: (standalone: boolean) => Scene | EmbeddedScene;
+ getScene: () => DashboardScene;
}
export function getScenes(): SceneDef[] {
return [
@@ -36,20 +34,18 @@ export function getScenes(): SceneDef[] {
];
}
-const cache: Record
}> = {};
+const cache: Record = {};
-export function getSceneByTitle(title: string, standalone = true) {
+export function getSceneByTitle(title: string) {
if (cache[title]) {
- if (cache[title].standalone === standalone) {
- return cache[title].scene;
- }
+ return cache[title];
}
const scene = getScenes().find((x) => x.title === title);
if (scene) {
- cache[title] = { scene: scene.getScene(standalone), standalone };
+ cache[title] = scene.getScene();
}
- return cache[title].scene;
+ return cache[title];
}
diff --git a/public/app/features/scenes/scenes/nested.tsx b/public/app/features/scenes/scenes/nested.tsx
index 9243a1c1abc..7ac0056e3e8 100644
--- a/public/app/features/scenes/scenes/nested.tsx
+++ b/public/app/features/scenes/scenes/nested.tsx
@@ -1,18 +1,11 @@
-import {
- VizPanel,
- NestedScene,
- SceneTimePicker,
- SceneFlexLayout,
- SceneTimeRange,
- EmbeddedScene,
-} from '@grafana/scenes';
+import { VizPanel, NestedScene, SceneTimePicker, SceneFlexLayout, SceneTimeRange } from '@grafana/scenes';
-import { Scene } from '../components/Scene';
+import { DashboardScene } from '../dashboard/DashboardScene';
import { getQueryRunnerWithRandomWalkQuery } from './queries';
-export function getNestedScene(standalone: boolean): Scene | EmbeddedScene {
- const state = {
+export function getNestedScene(): DashboardScene {
+ return new DashboardScene({
title: 'Nested Scene demo',
body: new SceneFlexLayout({
direction: 'column',
@@ -28,9 +21,7 @@ export function getNestedScene(standalone: boolean): Scene | EmbeddedScene {
$timeRange: new SceneTimeRange(),
$data: getQueryRunnerWithRandomWalkQuery(),
actions: [new SceneTimePicker({})],
- };
-
- return standalone ? new Scene(state) : new EmbeddedScene(state);
+ });
}
export function getInnerScene(title: string) {
diff --git a/public/app/features/scenes/scenes/queryVariableDemo.tsx b/public/app/features/scenes/scenes/queryVariableDemo.tsx
index 4eb211d8d59..69fd91ded75 100644
--- a/public/app/features/scenes/scenes/queryVariableDemo.tsx
+++ b/public/app/features/scenes/scenes/queryVariableDemo.tsx
@@ -1,7 +1,6 @@
import { VariableRefresh } from '@grafana/data';
import {
SceneCanvasText,
- SceneSubMenu,
SceneTimePicker,
SceneFlexLayout,
SceneTimeRange,
@@ -10,13 +9,12 @@ import {
CustomVariable,
DataSourceVariable,
QueryVariable,
- EmbeddedScene,
} from '@grafana/scenes';
-import { Scene } from '../components/Scene';
+import { DashboardScene } from '../dashboard/DashboardScene';
-export function getQueryVariableDemo(standalone: boolean): Scene | EmbeddedScene {
- const state = {
+export function getQueryVariableDemo(): DashboardScene {
+ return new DashboardScene({
title: 'Query variable',
$variables: new SceneVariableSet({
variables: [
@@ -65,10 +63,6 @@ export function getQueryVariableDemo(standalone: boolean): Scene | EmbeddedScene
}),
$timeRange: new SceneTimeRange(),
actions: [new SceneTimePicker({})],
- subMenu: new SceneSubMenu({
- children: [new VariableValueSelectors({})],
- }),
- };
-
- return standalone ? new Scene(state) : new EmbeddedScene(state);
+ controls: [new VariableValueSelectors({})],
+ });
}
diff --git a/public/app/features/scenes/scenes/sceneWithRows.tsx b/public/app/features/scenes/scenes/sceneWithRows.tsx
index c7d5d7f4f1f..51e4352129b 100644
--- a/public/app/features/scenes/scenes/sceneWithRows.tsx
+++ b/public/app/features/scenes/scenes/sceneWithRows.tsx
@@ -1,19 +1,12 @@
-import {
- VizPanel,
- NestedScene,
- SceneTimePicker,
- SceneFlexLayout,
- SceneTimeRange,
- EmbeddedScene,
-} from '@grafana/scenes';
+import { VizPanel, NestedScene, SceneTimePicker, SceneFlexLayout, SceneTimeRange } from '@grafana/scenes';
-import { Scene } from '../components/Scene';
+import { DashboardScene } from '../dashboard/DashboardScene';
import { SceneEditManager } from '../editor/SceneEditManager';
import { getQueryRunnerWithRandomWalkQuery } from './queries';
-export function getSceneWithRows(standalone: boolean): Scene | EmbeddedScene {
- const state = {
+export function getSceneWithRows(): DashboardScene {
+ return new DashboardScene({
title: 'Scene with rows',
body: new SceneFlexLayout({
direction: 'column',
@@ -60,7 +53,5 @@ export function getSceneWithRows(standalone: boolean): Scene | EmbeddedScene {
$timeRange: new SceneTimeRange(),
$data: getQueryRunnerWithRandomWalkQuery(),
actions: [new SceneTimePicker({})],
- };
-
- return standalone ? new Scene(state) : new EmbeddedScene(state);
+ });
}
diff --git a/public/app/features/scenes/scenes/transformations.tsx b/public/app/features/scenes/scenes/transformations.tsx
index 739f88c820f..d8c5a08f335 100644
--- a/public/app/features/scenes/scenes/transformations.tsx
+++ b/public/app/features/scenes/scenes/transformations.tsx
@@ -1,19 +1,11 @@
-import {
- SceneTimePicker,
- SceneFlexLayout,
- VizPanel,
- SceneDataTransformer,
- SceneTimeRange,
- EmbeddedScene,
-} from '@grafana/scenes';
+import { SceneTimePicker, SceneFlexLayout, VizPanel, SceneDataTransformer, SceneTimeRange } from '@grafana/scenes';
-import { Scene } from '../components/Scene';
-import { SceneEditManager } from '../editor/SceneEditManager';
+import { DashboardScene } from '../dashboard/DashboardScene';
import { getQueryRunnerWithRandomWalkQuery } from './queries';
-export function getTransformationsDemo(standalone: boolean): Scene | EmbeddedScene {
- const state = {
+export function getTransformationsDemo(): DashboardScene {
+ return new DashboardScene({
title: 'Transformations demo',
body: new SceneFlexLayout({
direction: 'row',
@@ -63,11 +55,8 @@ export function getTransformationsDemo(standalone: boolean): Scene | EmbeddedSce
}),
],
}),
- $editor: new SceneEditManager({}),
$timeRange: new SceneTimeRange(),
$data: getQueryRunnerWithRandomWalkQuery(),
actions: [new SceneTimePicker({})],
- };
-
- 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 14547e4302d..aae86008f13 100644
--- a/public/app/features/scenes/scenes/variablesDemo.tsx
+++ b/public/app/features/scenes/scenes/variablesDemo.tsx
@@ -1,7 +1,6 @@
import {
VizPanel,
SceneCanvasText,
- SceneSubMenu,
SceneTimePicker,
SceneFlexLayout,
SceneTimeRange,
@@ -10,15 +9,14 @@ import {
CustomVariable,
DataSourceVariable,
TestVariable,
- EmbeddedScene,
} from '@grafana/scenes';
-import { Scene } from '../components/Scene';
+import { DashboardScene } from '../dashboard/DashboardScene';
import { getQueryRunnerWithRandomWalkQuery } from './queries';
-export function getVariablesDemo(standalone: boolean): Scene | EmbeddedScene {
- const state = {
+export function getVariablesDemo(): DashboardScene {
+ return new DashboardScene({
title: 'Variables',
$variables: new SceneVariableSet({
variables: [
@@ -82,16 +80,12 @@ export function getVariablesDemo(standalone: boolean): Scene | EmbeddedScene {
}),
$timeRange: new SceneTimeRange(),
actions: [new SceneTimePicker({})],
- subMenu: new SceneSubMenu({
- children: [new VariableValueSelectors({})],
- }),
- };
-
- return standalone ? new Scene(state) : new EmbeddedScene(state);
+ controls: [new VariableValueSelectors({})],
+ });
}
-export function getVariablesDemoWithAll(): Scene {
- const scene = new Scene({
+export function getVariablesDemoWithAll(): DashboardScene {
+ return new DashboardScene({
title: 'Variables with All values',
$variables: new SceneVariableSet({
variables: [
@@ -153,10 +147,6 @@ export function getVariablesDemoWithAll(): Scene {
}),
$timeRange: new SceneTimeRange(),
actions: [new SceneTimePicker({})],
- subMenu: new SceneSubMenu({
- children: [new VariableValueSelectors({})],
- }),
+ controls: [new VariableValueSelectors({})],
});
-
- return scene;
}
diff --git a/public/app/features/search/page/components/SearchView.test.tsx b/public/app/features/search/page/components/SearchView.test.tsx
index cb3f230c736..8c594887eca 100644
--- a/public/app/features/search/page/components/SearchView.test.tsx
+++ b/public/app/features/search/page/components/SearchView.test.tsx
@@ -105,6 +105,46 @@ describe('SearchView', () => {
expect(screen.getByRole('button', { name: 'Clear search and filters' })).toBeInTheDocument();
});
+ it('shows an empty state if no starred dashboard returned', async () => {
+ jest.spyOn(getGrafanaSearcher(), 'search').mockResolvedValue({
+ ...mockSearchResult,
+ totalRows: 0,
+ view: new DataFrameView({ fields: [], length: 0 }),
+ });
+
+ setup(undefined, { starred: true });
+
+ await waitFor(() => expect(screen.queryByText('No results found for your query.')).toBeInTheDocument());
+ expect(screen.getByRole('button', { name: 'Clear search and filters' })).toBeInTheDocument();
+ });
+
+ it('shows empty folder cta for empty folder', async () => {
+ jest.spyOn(getGrafanaSearcher(), 'search').mockResolvedValue({
+ ...mockSearchResult,
+ totalRows: 0,
+ view: new DataFrameView({ fields: [], length: 0 }),
+ });
+
+ setup(
+ {
+ folderDTO: {
+ id: 1,
+ uid: 'abc',
+ title: 'morning coffee',
+ url: '/morningcoffee',
+ version: 1,
+ canSave: true,
+ canEdit: true,
+ canAdmin: true,
+ canDelete: true,
+ },
+ },
+ undefined
+ );
+
+ await waitFor(() => expect(screen.queryByText("This folder doesn't have any dashboards yet")).toBeInTheDocument());
+ });
+
describe('include panels', () => {
it('should be enabled when layout is list', async () => {
config.featureToggles.panelTitleSearch = true;
diff --git a/public/app/features/search/page/components/SearchView.tsx b/public/app/features/search/page/components/SearchView.tsx
index 5a03908f1df..dbde3e3ad22 100644
--- a/public/app/features/search/page/components/SearchView.tsx
+++ b/public/app/features/search/page/components/SearchView.tsx
@@ -159,7 +159,7 @@ export const SearchView = ({ showManage, folderDTO, hidePseudoFolders, keyboardE
);
};
- if (folderDTO && !state.loading && !state.result?.totalRows && !state.query.length) {
+ if (folderDTO && !state.loading && !state.result?.totalRows && !stateManager.hasSearchFilters()) {
return (
{
store.set(SEARCH_PANELS_LOCAL_STORAGE_KEY, includePanels);
};
+ hasSearchFilters() {
+ return this.state.query || this.state.tag.length || this.state.starred;
+ }
+
getSearchQuery() {
const q: SearchQuery = {
query: this.state.query,
diff --git a/public/app/features/support-bundles/SupportBundles.tsx b/public/app/features/support-bundles/SupportBundles.tsx
index 0e75a1c27a6..a2a6fa98f72 100644
--- a/public/app/features/support-bundles/SupportBundles.tsx
+++ b/public/app/features/support-bundles/SupportBundles.tsx
@@ -1,10 +1,13 @@
import React, { useEffect } from 'react';
-import { useAsyncFn } from 'react-use';
+import { connect, ConnectedProps } from 'react-redux';
import { dateTimeFormat } from '@grafana/data';
-import { config, getBackendSrv } from '@grafana/runtime';
-import { LinkButton } from '@grafana/ui';
+import { config } from '@grafana/runtime';
+import { LinkButton, Spinner, IconButton } from '@grafana/ui';
import { Page } from 'app/core/components/Page/Page';
+import { StoreState } from 'app/types';
+
+import { loadBundles, removeBundle, checkBundles } from './state/actions';
const subTitle = (
@@ -13,39 +16,48 @@ const subTitle = (
);
-const newButton = (
+const NewBundleButton = (
New support bundle
);
-type SupportBundleState = 'complete' | 'error' | 'timeout' | 'pending';
-
-interface SupportBundle {
- uid: string;
- state: SupportBundleState;
- creator: string;
- createdAt: number;
- expiresAt: number;
-}
-
-const getBundles = () => {
- return getBackendSrv().get('/api/support-bundles');
+const mapStateToProps = (state: StoreState) => {
+ return {
+ supportBundles: state.supportBundles.supportBundles,
+ isLoading: state.supportBundles.isLoading,
+ };
};
-function SupportBundles() {
- const [bundlesState, fetchBundles] = useAsyncFn(getBundles, []);
+const mapDispatchToProps = {
+ loadBundles,
+ removeBundle,
+ checkBundles,
+};
+
+const connector = connect(mapStateToProps, mapDispatchToProps);
+
+type Props = ConnectedProps;
+
+const SupportBundlesUnconnected = ({ supportBundles, isLoading, loadBundles, removeBundle, checkBundles }: Props) => {
+ const isPending = supportBundles.some((b) => b.state === 'pending');
useEffect(() => {
- fetchBundles();
- }, [fetchBundles]);
+ loadBundles();
+ }, [loadBundles]);
- const actions = config.featureToggles.topnav ? newButton : undefined;
+ useEffect(() => {
+ if (isPending) {
+ checkBundles();
+ }
+ });
+
+ const actions = config.featureToggles.topnav ? NewBundleButton : undefined;
return (
-
- {!config.featureToggles.topnav && newButton}
+
+ {!config.featureToggles.topnav && NewBundleButton}
@@ -53,25 +65,31 @@ function SupportBundles() {
| Created on |
Requested by |
Expires |
+ |
+ |
|
- {bundlesState?.value?.map((b) => (
-
- | {dateTimeFormat(b.createdAt * 1000)} |
- {b.creator} |
- {dateTimeFormat(b.expiresAt * 1000)} |
+ {supportBundles?.map((bundle) => (
+
+ | {dateTimeFormat(bundle.createdAt * 1000)} |
+ {bundle.creator} |
+ {dateTimeFormat(bundle.expiresAt * 1000)} |
+ {bundle.state === 'pending' && } |
Download
|
+
+ removeBundle(bundle.uid)} name="trash-alt" variant="destructive" />
+ |
))}
@@ -79,6 +97,6 @@ function SupportBundles() {
);
-}
+};
-export default SupportBundles;
+export default connector(SupportBundlesUnconnected);
diff --git a/public/app/features/support-bundles/SupportBundlesCreate.tsx b/public/app/features/support-bundles/SupportBundlesCreate.tsx
index 3ecbc3212f6..d8070c1c096 100644
--- a/public/app/features/support-bundles/SupportBundlesCreate.tsx
+++ b/public/app/features/support-bundles/SupportBundlesCreate.tsx
@@ -1,29 +1,11 @@
-import React, { useCallback, useEffect, useState } from 'react';
-import { useAsyncFn } from 'react-use';
+import React, { useEffect } from 'react';
+import { connect, ConnectedProps } from 'react-redux';
-import { getBackendSrv, locationService } from '@grafana/runtime';
-import { Form, Button, Field, Checkbox } from '@grafana/ui';
+import { Form, Button, Field, Checkbox, LinkButton, HorizontalGroup, Alert } from '@grafana/ui';
import { Page } from 'app/core/components/Page/Page';
+import { StoreState } from 'app/types';
-// move to types
-export interface SupportBundleCreateRequest {
- collectors: string[];
-}
-
-export interface SupportBundleCollector {
- uid: string;
- displayName: string;
- description: string;
- includedByDefault: boolean;
- default: boolean;
-}
-
-export interface Props {}
-
-const createSupportBundle = async (data: SupportBundleCreateRequest) => {
- const result = await getBackendSrv().post('/api/support-bundles', data);
- return result;
-};
+import { loadSupportBundleCollectors, createSupportBundle } from './state/actions';
const subTitle = (
@@ -31,50 +13,60 @@ const subTitle = (
);
-export const SupportBundlesCreate = ({}: Props): JSX.Element => {
- const onSubmit = useCallback(async (data) => {
- try {
- const selectedLabelsArray = Object.keys(data).filter((key) => data[key]);
- const response = await createSupportBundle({ collectors: selectedLabelsArray });
- console.info(response);
- } catch (e) {
- console.error(e);
- }
+const mapStateToProps = (state: StoreState) => {
+ return {
+ collectors: state.supportBundles.supportBundleCollectors,
+ isLoading: state.supportBundles.createBundlePageLoading,
+ loadCollectorsError: state.supportBundles.loadBundlesError,
+ createBundleError: state.supportBundles.createBundleError,
+ };
+};
- locationService.push('/admin/support-bundles');
- }, []);
+const mapDispatchToProps = {
+ loadSupportBundleCollectors,
+ createSupportBundle,
+};
- const [components, setComponents] = useState([]);
- // populate components from the backend
- const populateComponents = async () => {
- return await getBackendSrv().get('/api/support-bundles/collectors');
+const connector = connect(mapStateToProps, mapDispatchToProps);
+
+type Props = ConnectedProps;
+
+export const SupportBundlesCreateUnconnected = ({
+ collectors,
+ isLoading,
+ loadCollectorsError,
+ createBundleError,
+ loadSupportBundleCollectors,
+ createSupportBundle,
+}: Props): JSX.Element => {
+ const onSubmit = (data: Record) => {
+ const selectedLabelsArray = Object.keys(data).filter((key) => data[key]);
+ createSupportBundle({ collectors: selectedLabelsArray });
};
- const [state, fetchComponents] = useAsyncFn(populateComponents);
useEffect(() => {
- fetchComponents().then((res) => {
- setComponents(res);
- });
- }, [fetchComponents]);
+ loadSupportBundleCollectors();
+ }, [loadSupportBundleCollectors]);
// turn components into a uuid -> enabled map
- const values: Record = components.reduce((acc, curr) => {
+ const values: Record = collectors.reduce((acc, curr) => {
return { ...acc, [curr.uid]: curr.default };
}, {});
return (
-
+
Create support bundle
- {state.error && {state.error}
}
- {!!components.length && (
+ {loadCollectorsError && }
+ {createBundleError && }
+ {!!collectors.length && (