DashboardScene: Simplify controls a bit (#82908)

* DashboardScene: Simplify controls a bit

* update tests

* more test updates

* Update

* improvements

* Fix

* Fix merge

* Update

* update
pull/83086/head
Torkel Ödegaard 2 years ago committed by GitHub
parent 57499845c2
commit 6db2d1a411
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 8
      public/app/features/dashboard-scene/embedding/EmbeddedDashboard.tsx
  2. 8
      public/app/features/dashboard-scene/panel-edit/PanelEditorRenderer.tsx
  3. 82
      public/app/features/dashboard-scene/scene/DashboardControls.tsx
  4. 13
      public/app/features/dashboard-scene/scene/DashboardLinksControls.tsx
  5. 18
      public/app/features/dashboard-scene/scene/DashboardScene.test.tsx
  6. 2
      public/app/features/dashboard-scene/scene/DashboardScene.tsx
  7. 32
      public/app/features/dashboard-scene/scene/DashboardSceneRenderer.tsx
  8. 26
      public/app/features/dashboard-scene/serialization/transformSaveModelToScene.test.ts
  9. 24
      public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts
  10. 20
      public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.ts
  11. 23
      public/app/features/dashboard-scene/settings/DashboardLinksEditView.test.tsx
  12. 33
      public/app/features/dashboard-scene/settings/GeneralSettingsEditView.test.tsx
  13. 9
      public/app/features/dashboard-scene/settings/GeneralSettingsEditView.tsx
  14. 25
      public/app/features/dashboard-scene/utils/DashboardModelCompatibilityWrapper.test.ts
  15. 5
      public/app/features/dashboard-scene/utils/DashboardModelCompatibilityWrapper.ts
  16. 77
      public/app/features/dashboard-scene/utils/dashboardSceneGraph.test.ts
  17. 41
      public/app/features/dashboard-scene/utils/dashboardSceneGraph.ts

@ -65,13 +65,7 @@ function EmbeddedDashboardRenderer({ model, initialState, onStateChange }: Rende
return ( return (
<div className={styles.canvas}> <div className={styles.canvas}>
{controls && ( {controls && <controls.Component model={controls} />}
<div className={styles.controls}>
{controls.map((control) => (
<control.Component key={control.state.key} model={control} />
))}
</div>
)}
<div className={styles.body}> <div className={styles.body}>
<body.Component model={body} /> <body.Component model={body} />
</div> </div>

@ -37,13 +37,7 @@ export function PanelEditorRenderer({ model }: SceneComponentProps<PanelEditor>)
> >
<div className={styles.body}> <div className={styles.body}>
<div className={styles.canvasContent}> <div className={styles.canvasContent}>
{controls && ( {controls && <controls.Component model={controls} />}
<div className={styles.controls}>
{controls.map((control) => (
<control.Component key={control.state.key} model={control} />
))}
</div>
)}
<Splitter <Splitter
direction="column" direction="column"
primaryPaneStyles={{ minHeight: 0, paddingBottom: !dataPane ? 16 : 0 }} primaryPaneStyles={{ minHeight: 0, paddingBottom: !dataPane ? 16 : 0 }}

@ -1,41 +1,89 @@
import { css, cx } from '@emotion/css';
import React from 'react'; import React from 'react';
import { SceneObjectState, SceneObject, SceneObjectBase, SceneComponentProps } from '@grafana/scenes'; import { GrafanaTheme2 } from '@grafana/data';
import { Box, Stack } from '@grafana/ui'; import {
SceneObjectState,
SceneObject,
SceneObjectBase,
SceneComponentProps,
SceneTimePicker,
SceneRefreshPicker,
SceneDebugger,
} from '@grafana/scenes';
import { Box, Stack, useStyles2 } from '@grafana/ui';
import { getDashboardSceneFor } from '../utils/utils';
import { DashboardLinksControls } from './DashboardLinksControls'; import { DashboardLinksControls } from './DashboardLinksControls';
interface DashboardControlsState extends SceneObjectState { interface DashboardControlsState extends SceneObjectState {
variableControls: SceneObject[]; variableControls: SceneObject[];
timeControls: SceneObject[]; timePicker: SceneTimePicker;
linkControls: DashboardLinksControls; refreshPicker: SceneRefreshPicker;
hideTimeControls?: boolean; hideTimeControls?: boolean;
} }
export class DashboardControls extends SceneObjectBase<DashboardControlsState> { export class DashboardControls extends SceneObjectBase<DashboardControlsState> {
static Component = DashboardControlsRenderer; static Component = DashboardControlsRenderer;
public constructor(state: Partial<DashboardControlsState>) {
super({
variableControls: [],
timePicker: state.timePicker ?? new SceneTimePicker({}),
refreshPicker: state.refreshPicker ?? new SceneRefreshPicker({}),
...state,
});
}
} }
function DashboardControlsRenderer({ model }: SceneComponentProps<DashboardControls>) { function DashboardControlsRenderer({ model }: SceneComponentProps<DashboardControls>) {
const { variableControls, linkControls, timeControls, hideTimeControls } = model.useState(); const { variableControls, refreshPicker, timePicker, hideTimeControls } = model.useState();
const dashboard = getDashboardSceneFor(model);
const { links, meta, editPanel } = dashboard.useState();
const styles = useStyles2(getStyles);
const showDebugger = location.search.includes('scene-debugger');
return ( return (
<Stack <div className={cx(styles.controls, meta.isEmbedded && styles.embedded)}>
grow={1}
direction={{
md: 'row',
xs: 'column',
}}
>
<Stack grow={1} wrap={'wrap'}> <Stack grow={1} wrap={'wrap'}>
{variableControls.map((c) => ( {variableControls.map((c) => (
<c.Component model={c} key={c.state.key} /> <c.Component model={c} key={c.state.key} />
))} ))}
<Box grow={1} /> <Box grow={1} />
<linkControls.Component model={linkControls} /> {!editPanel && <DashboardLinksControls links={links} uid={dashboard.state.uid} />}
</Stack>
<Stack justifyContent={'flex-end'}>
{!hideTimeControls && timeControls.map((c) => <c.Component model={c} key={c.state.key} />)}
</Stack> </Stack>
</Stack> {!hideTimeControls && (
<Stack justifyContent={'flex-end'}>
<timePicker.Component model={timePicker} />
<refreshPicker.Component model={refreshPicker} />
</Stack>
)}
{showDebugger && <SceneDebugger scene={model} key={'scene-debugger'} />}
</div>
); );
} }
function getStyles(theme: GrafanaTheme2) {
return {
controls: css({
display: 'flex',
alignItems: 'flex-start',
gap: theme.spacing(1),
position: 'sticky',
top: 0,
background: theme.colors.background.canvas,
zIndex: theme.zIndex.activePanel,
padding: theme.spacing(2, 0),
width: '100%',
marginLeft: 'auto',
[theme.breakpoints.down('sm')]: {
flexDirection: 'column-reverse',
alignItems: 'stretch',
},
}),
embedded: css({
background: 'unset',
position: 'unset',
}),
};
}

@ -2,7 +2,6 @@ import React from 'react';
import { sanitizeUrl } from '@grafana/data/src/text/sanitize'; import { sanitizeUrl } from '@grafana/data/src/text/sanitize';
import { selectors } from '@grafana/e2e-selectors'; import { selectors } from '@grafana/e2e-selectors';
import { SceneComponentProps, SceneObjectBase, SceneObjectState } from '@grafana/scenes';
import { DashboardLink } from '@grafana/schema'; import { DashboardLink } from '@grafana/schema';
import { Tooltip } from '@grafana/ui'; import { Tooltip } from '@grafana/ui';
import { import {
@ -12,17 +11,13 @@ import {
import { getLinkSrv } from 'app/features/panel/panellinks/link_srv'; import { getLinkSrv } from 'app/features/panel/panellinks/link_srv';
import { LINK_ICON_MAP } from '../settings/links/utils'; import { LINK_ICON_MAP } from '../settings/links/utils';
import { getDashboardSceneFor } from '../utils/utils';
interface DashboardLinksControlsState extends SceneObjectState {} export interface Props {
links: DashboardLink[];
export class DashboardLinksControls extends SceneObjectBase<DashboardLinksControlsState> { uid?: string;
static Component = DashboardLinksControlsRenderer;
} }
function DashboardLinksControlsRenderer({ model }: SceneComponentProps<DashboardLinksControls>) { export function DashboardLinksControls({ links, uid }: Props) {
const { links, uid } = getDashboardSceneFor(model).useState();
if (!links || !uid) { if (!links || !uid) {
return null; return null;
} }

@ -3,7 +3,6 @@ import {
sceneGraph, sceneGraph,
SceneGridItem, SceneGridItem,
SceneGridLayout, SceneGridLayout,
SceneRefreshPicker,
SceneTimeRange, SceneTimeRange,
SceneQueryRunner, SceneQueryRunner,
SceneVariableSet, SceneVariableSet,
@ -22,7 +21,6 @@ import { dashboardSceneGraph } from '../utils/dashboardSceneGraph';
import { djb2Hash } from '../utils/djb2Hash'; import { djb2Hash } from '../utils/djb2Hash';
import { DashboardControls } from './DashboardControls'; import { DashboardControls } from './DashboardControls';
import { DashboardLinksControls } from './DashboardLinksControls';
import { DashboardScene, DashboardSceneState } from './DashboardScene'; import { DashboardScene, DashboardSceneState } from './DashboardScene';
jest.mock('../settings/version-history/HistorySrv'); jest.mock('../settings/version-history/HistorySrv');
@ -94,14 +92,14 @@ describe('DashboardScene', () => {
}); });
it('A change to time picker visibility settings should set isDirty true', () => { it('A change to time picker visibility settings should set isDirty true', () => {
const dashboardControls = dashboardSceneGraph.getDashboardControls(scene)!; const dashboardControls = scene.state.controls!;
const prevState = dashboardControls.state.hideTimeControls; const prevState = dashboardControls.state.hideTimeControls;
dashboardControls.setState({ hideTimeControls: true }); dashboardControls.setState({ hideTimeControls: true });
expect(scene.state.isDirty).toBe(true); expect(scene.state.isDirty).toBe(true);
scene.exitEditMode({ skipConfirm: true }); scene.exitEditMode({ skipConfirm: true });
expect(dashboardSceneGraph.getDashboardControls(scene)!.state.hideTimeControls).toEqual(prevState); expect(scene.state.controls!.state.hideTimeControls).toEqual(prevState);
}); });
it('A change to time zone should set isDirty true', () => { it('A change to time zone should set isDirty true', () => {
@ -217,17 +215,7 @@ function buildTestScene(overrides?: Partial<DashboardSceneState>) {
$timeRange: new SceneTimeRange({ $timeRange: new SceneTimeRange({
timeZone: 'browser', timeZone: 'browser',
}), }),
controls: [ controls: new DashboardControls({}),
new DashboardControls({
variableControls: [],
linkControls: new DashboardLinksControls({}),
timeControls: [
new SceneRefreshPicker({
intervals: ['1s'],
}),
],
}),
],
body: new SceneGridLayout({ body: new SceneGridLayout({
children: [ children: [
new SceneGridItem({ new SceneGridItem({

@ -75,7 +75,7 @@ export interface DashboardSceneState extends SceneObjectState {
/** NavToolbar actions */ /** NavToolbar actions */
actions?: SceneObject[]; actions?: SceneObject[];
/** Fixed row at the top of the canvas with for example variables and time range controls */ /** Fixed row at the top of the canvas with for example variables and time range controls */
controls?: SceneObject[]; controls?: DashboardControls;
/** True when editing */ /** True when editing */
isEditing?: boolean; isEditing?: boolean;
/** True when user made a change */ /** True when user made a change */

@ -3,7 +3,7 @@ import React from 'react';
import { useLocation } from 'react-router-dom'; import { useLocation } from 'react-router-dom';
import { GrafanaTheme2, PageLayoutType } from '@grafana/data'; import { GrafanaTheme2, PageLayoutType } from '@grafana/data';
import { SceneComponentProps, SceneDebugger } from '@grafana/scenes'; import { SceneComponentProps } from '@grafana/scenes';
import { CustomScrollbar, useStyles2 } from '@grafana/ui'; import { CustomScrollbar, useStyles2 } from '@grafana/ui';
import { Page } from 'app/core/components/Page/Page'; import { Page } from 'app/core/components/Page/Page';
import { getNavModel } from 'app/core/selectors/navModel'; import { getNavModel } from 'app/core/selectors/navModel';
@ -21,7 +21,6 @@ export function DashboardSceneRenderer({ model }: SceneComponentProps<DashboardS
const pageNav = model.getPageNav(location, navIndex); const pageNav = model.getPageNav(location, navIndex);
const bodyToRender = model.getBodyToRender(); const bodyToRender = model.getBodyToRender();
const navModel = getNavModel(navIndex, 'dashboards/browse'); const navModel = getNavModel(navIndex, 'dashboards/browse');
const showDebugger = location.search.includes('scene-debugger');
if (editview) { if (editview) {
return ( return (
@ -32,23 +31,11 @@ export function DashboardSceneRenderer({ model }: SceneComponentProps<DashboardS
); );
} }
const emptyState = ( const emptyState = <DashboardEmpty dashboard={model} canCreate={!!model.state.meta.canEdit} />;
<>
<div className={styles.controls}>{showDebugger && <SceneDebugger scene={model} key={'scene-debugger'} />}</div>
<DashboardEmpty dashboard={model} canCreate={!!model.state.meta.canEdit} />
</>
);
const withPanels = ( const withPanels = (
<> <>
{controls && ( {controls && <controls.Component model={controls} />}
<div className={styles.controls}>
{controls.map((control) => (
<control.Component key={control.state.key} model={control} />
))}
{showDebugger && <SceneDebugger scene={model} key={'scene-debugger'} />}
</div>
)}
<div className={cx(styles.body)}> <div className={cx(styles.body)}>
<bodyToRender.Component model={bodyToRender} /> <bodyToRender.Component model={bodyToRender} />
</div> </div>
@ -88,18 +75,5 @@ function getStyles(theme: GrafanaTheme2) {
gap: '8px', gap: '8px',
marginBottom: theme.spacing(2), marginBottom: theme.spacing(2),
}), }),
controls: css({
display: 'flex',
flexWrap: 'wrap',
alignItems: 'center',
gap: theme.spacing(1),
position: 'sticky',
top: 0,
background: theme.colors.background.canvas,
zIndex: theme.zIndex.activePanel,
padding: theme.spacing(2, 0),
marginLeft: 'auto',
}),
}; };
} }

@ -26,8 +26,6 @@ import {
SceneGridLayout, SceneGridLayout,
SceneGridRow, SceneGridRow,
SceneQueryRunner, SceneQueryRunner,
SceneRefreshPicker,
SceneTimePicker,
VizPanel, VizPanel,
} from '@grafana/scenes'; } from '@grafana/scenes';
import { import {
@ -44,7 +42,6 @@ import { SHARED_DASHBOARD_QUERY } from 'app/plugins/datasource/dashboard';
import { DASHBOARD_DATASOURCE_PLUGIN_ID } from 'app/plugins/datasource/dashboard/types'; import { DASHBOARD_DATASOURCE_PLUGIN_ID } from 'app/plugins/datasource/dashboard/types';
import { DashboardDataDTO } from 'app/types'; import { DashboardDataDTO } from 'app/types';
import { DashboardControls } from '../scene/DashboardControls';
import { PanelRepeaterGridItem } from '../scene/PanelRepeaterGridItem'; import { PanelRepeaterGridItem } from '../scene/PanelRepeaterGridItem';
import { PanelTimeRange } from '../scene/PanelTimeRange'; import { PanelTimeRange } from '../scene/PanelTimeRange';
import { RowRepeaterBehavior } from '../scene/RowRepeaterBehavior'; import { RowRepeaterBehavior } from '../scene/RowRepeaterBehavior';
@ -112,7 +109,7 @@ describe('transformSaveModelToScene', () => {
const oldModel = new DashboardModel(dash); const oldModel = new DashboardModel(dash);
const scene = createDashboardSceneFromDashboardModel(oldModel); const scene = createDashboardSceneFromDashboardModel(oldModel);
const dashboardControls = scene.state.controls![0] as DashboardControls; const dashboardControls = scene.state.controls!;
expect(scene.state.title).toBe('test'); expect(scene.state.title).toBe('test');
expect(scene.state.uid).toBe('test-uid'); expect(scene.state.uid).toBe('test-uid');
@ -127,13 +124,8 @@ describe('transformSaveModelToScene', () => {
expect(scene.state?.$variables?.getByName('constant')).toBeInstanceOf(ConstantVariable); expect(scene.state?.$variables?.getByName('constant')).toBeInstanceOf(ConstantVariable);
expect(scene.state?.$variables?.getByName('CoolFilters')).toBeInstanceOf(AdHocFiltersVariable); expect(scene.state?.$variables?.getByName('CoolFilters')).toBeInstanceOf(AdHocFiltersVariable);
expect(dashboardControls).toBeDefined(); expect(dashboardControls).toBeDefined();
expect(dashboardControls).toBeInstanceOf(DashboardControls);
expect(dashboardControls.state.timeControls).toHaveLength(2); expect(dashboardControls.state.refreshPicker.state.intervals).toEqual(defaultTimePickerConfig.refresh_intervals);
expect(dashboardControls.state.timeControls[0]).toBeInstanceOf(SceneTimePicker);
expect(dashboardControls.state.timeControls[1]).toBeInstanceOf(SceneRefreshPicker);
expect((dashboardControls.state.timeControls[1] as SceneRefreshPicker).state.intervals).toEqual(
defaultTimePickerConfig.refresh_intervals
);
expect(dashboardControls.state.hideTimeControls).toBe(true); expect(dashboardControls.state.hideTimeControls).toBe(true);
}); });
@ -1039,9 +1031,7 @@ describe('transformSaveModelToScene', () => {
const scene = transformSaveModelToScene({ dashboard: dashboard_to_load1 as any, meta: {} }); const scene = transformSaveModelToScene({ dashboard: dashboard_to_load1 as any, meta: {} });
expect(scene.state.$data).toBeInstanceOf(SceneDataLayers); expect(scene.state.$data).toBeInstanceOf(SceneDataLayers);
expect((scene.state.controls![0] as DashboardControls)!.state.variableControls[1]).toBeInstanceOf( expect(scene.state.controls!.state.variableControls[1]).toBeInstanceOf(SceneDataLayerControls);
SceneDataLayerControls
);
const dataLayers = scene.state.$data as SceneDataLayers; const dataLayers = scene.state.$data as SceneDataLayers;
expect(dataLayers.state.layers).toHaveLength(4); expect(dataLayers.state.layers).toHaveLength(4);
@ -1069,9 +1059,7 @@ describe('transformSaveModelToScene', () => {
const scene = transformSaveModelToScene({ dashboard: dashboard_to_load1 as any, meta: {} }); const scene = transformSaveModelToScene({ dashboard: dashboard_to_load1 as any, meta: {} });
expect(scene.state.$data).toBeInstanceOf(SceneDataLayers); expect(scene.state.$data).toBeInstanceOf(SceneDataLayers);
expect((scene.state.controls![0] as DashboardControls)!.state.variableControls[1]).toBeInstanceOf( expect(scene.state.controls!.state.variableControls[1]).toBeInstanceOf(SceneDataLayerControls);
SceneDataLayerControls
);
const dataLayers = scene.state.$data as SceneDataLayers; const dataLayers = scene.state.$data as SceneDataLayers;
expect(dataLayers.state.layers).toHaveLength(5); expect(dataLayers.state.layers).toHaveLength(5);
@ -1085,9 +1073,7 @@ describe('transformSaveModelToScene', () => {
const scene = transformSaveModelToScene({ dashboard: dashboard_to_load1 as any, meta: {} }); const scene = transformSaveModelToScene({ dashboard: dashboard_to_load1 as any, meta: {} });
expect(scene.state.$data).toBeInstanceOf(SceneDataLayers); expect(scene.state.$data).toBeInstanceOf(SceneDataLayers);
expect((scene.state.controls![0] as DashboardControls)!.state.variableControls[1]).toBeInstanceOf( expect(scene.state.controls!.state.variableControls[1]).toBeInstanceOf(SceneDataLayerControls);
SceneDataLayerControls
);
const dataLayers = scene.state.$data as SceneDataLayers; const dataLayers = scene.state.$data as SceneDataLayers;
expect(dataLayers.state.layers).toHaveLength(5); expect(dataLayers.state.layers).toHaveLength(5);

@ -36,7 +36,6 @@ import { DashboardDTO } from 'app/types';
import { AlertStatesDataLayer } from '../scene/AlertStatesDataLayer'; import { AlertStatesDataLayer } from '../scene/AlertStatesDataLayer';
import { DashboardAnnotationsDataLayer } from '../scene/DashboardAnnotationsDataLayer'; import { DashboardAnnotationsDataLayer } from '../scene/DashboardAnnotationsDataLayer';
import { DashboardControls } from '../scene/DashboardControls'; import { DashboardControls } from '../scene/DashboardControls';
import { DashboardLinksControls } from '../scene/DashboardLinksControls';
import { registerDashboardMacro } from '../scene/DashboardMacro'; import { registerDashboardMacro } from '../scene/DashboardMacro';
import { DashboardScene } from '../scene/DashboardScene'; import { DashboardScene } from '../scene/DashboardScene';
import { LibraryVizPanel } from '../scene/LibraryVizPanel'; import { LibraryVizPanel } from '../scene/LibraryVizPanel';
@ -268,21 +267,16 @@ export function createDashboardSceneFromDashboardModel(oldModel: DashboardModel)
layers, layers,
}) })
: undefined, : undefined,
controls: [ controls: new DashboardControls({
new DashboardControls({ variableControls: [new VariableValueSelectors({}), new SceneDataLayerControls()],
variableControls: [new VariableValueSelectors({}), new SceneDataLayerControls()], timePicker: new SceneTimePicker({}),
timeControls: [ refreshPicker: new SceneRefreshPicker({
new SceneTimePicker({}), refresh: oldModel.refresh,
new SceneRefreshPicker({ intervals: oldModel.timepicker.refresh_intervals,
refresh: oldModel.refresh, withText: true,
intervals: oldModel.timepicker.refresh_intervals,
withText: true,
}),
],
linkControls: new DashboardLinksControls({}),
hideTimeControls: oldModel.timepicker.hidden,
}), }),
], hideTimeControls: oldModel.timepicker.hidden,
}),
}); });
return dashboardScene; return dashboardScene;

@ -12,7 +12,6 @@ import {
SceneDataTransformer, SceneDataTransformer,
SceneVariableSet, SceneVariableSet,
LocalValueVariable, LocalValueVariable,
SceneRefreshPicker,
} from '@grafana/scenes'; } from '@grafana/scenes';
import { import {
AnnotationQuery, AnnotationQuery,
@ -34,7 +33,6 @@ import { getPanelDataFrames } from 'app/features/dashboard/components/HelpWizard
import { DASHBOARD_SCHEMA_VERSION } from 'app/features/dashboard/state/DashboardMigrator'; import { DASHBOARD_SCHEMA_VERSION } from 'app/features/dashboard/state/DashboardMigrator';
import { GrafanaQueryType } from 'app/plugins/datasource/grafana/types'; import { GrafanaQueryType } from 'app/plugins/datasource/grafana/types';
import { DashboardControls } from '../scene/DashboardControls';
import { DashboardScene } from '../scene/DashboardScene'; import { DashboardScene } from '../scene/DashboardScene';
import { LibraryVizPanel } from '../scene/LibraryVizPanel'; import { LibraryVizPanel } from '../scene/LibraryVizPanel';
import { PanelRepeaterGridItem } from '../scene/PanelRepeaterGridItem'; import { PanelRepeaterGridItem } from '../scene/PanelRepeaterGridItem';
@ -54,9 +52,6 @@ export function transformSceneToSaveModel(scene: DashboardScene, isSnapshot = fa
const variablesSet = state.$variables; const variablesSet = state.$variables;
const body = state.body; const body = state.body;
let refreshIntervals: string[] | undefined;
let hideTimePicker: boolean | undefined;
let panels: Panel[] = []; let panels: Panel[] = [];
let graphTooltip = defaultDashboard.graphTooltip; let graphTooltip = defaultDashboard.graphTooltip;
let variables: VariableModel[] = []; let variables: VariableModel[] = [];
@ -92,16 +87,7 @@ export function transformSceneToSaveModel(scene: DashboardScene, isSnapshot = fa
variables = sceneVariablesSetToVariables(variablesSet); variables = sceneVariablesSetToVariables(variablesSet);
} }
if (state.controls && state.controls[0] instanceof DashboardControls) { const controlsState = state.controls?.state;
hideTimePicker = state.controls[0].state.hideTimeControls;
const timeControls = state.controls[0].state.timeControls;
for (const control of timeControls) {
if (control instanceof SceneRefreshPicker && control.state.intervals) {
refreshIntervals = control.state.intervals;
}
}
}
if (state.$behaviors) { if (state.$behaviors) {
for (const behavior of state.$behaviors!) { for (const behavior of state.$behaviors!) {
@ -113,8 +99,8 @@ export function transformSceneToSaveModel(scene: DashboardScene, isSnapshot = fa
const timePickerWithoutDefaults = removeDefaults<TimePickerConfig>( const timePickerWithoutDefaults = removeDefaults<TimePickerConfig>(
{ {
refresh_intervals: refreshIntervals, refresh_intervals: controlsState?.refreshPicker.state.intervals,
hidden: hideTimePicker, hidden: controlsState?.hideTimeControls,
nowDelay: timeRange.UNSAFE_nowDelay, nowDelay: timeRange.UNSAFE_nowDelay,
}, },
defaultTimePickerConfig defaultTimePickerConfig

@ -2,18 +2,10 @@ import { render as RTLRender } from '@testing-library/react';
import React from 'react'; import React from 'react';
import { TestProvider } from 'test/helpers/TestProvider'; import { TestProvider } from 'test/helpers/TestProvider';
import { import { behaviors, SceneGridLayout, SceneGridItem, SceneTimeRange } from '@grafana/scenes';
behaviors,
SceneGridLayout,
SceneGridItem,
SceneRefreshPicker,
SceneTimeRange,
SceneTimePicker,
} from '@grafana/scenes';
import { DashboardCursorSync } from '@grafana/schema'; import { DashboardCursorSync } from '@grafana/schema';
import { DashboardControls } from '../scene/DashboardControls'; import { DashboardControls } from '../scene/DashboardControls';
import { DashboardLinksControls } from '../scene/DashboardLinksControls';
import { DashboardScene } from '../scene/DashboardScene'; import { DashboardScene } from '../scene/DashboardScene';
import { activateFullSceneTree } from '../utils/test-utils'; import { activateFullSceneTree } from '../utils/test-utils';
@ -225,18 +217,7 @@ async function buildTestScene() {
const dashboard = new DashboardScene({ const dashboard = new DashboardScene({
$timeRange: new SceneTimeRange({}), $timeRange: new SceneTimeRange({}),
$behaviors: [new behaviors.CursorSync({ sync: DashboardCursorSync.Off })], $behaviors: [new behaviors.CursorSync({ sync: DashboardCursorSync.Off })],
controls: [ controls: new DashboardControls({}),
new DashboardControls({
variableControls: [],
linkControls: new DashboardLinksControls({}),
timeControls: [
new SceneTimePicker({}),
new SceneRefreshPicker({
intervals: ['1s'],
}),
],
}),
],
title: 'hello', title: 'hello',
uid: 'dash-1', uid: 'dash-1',
meta: { meta: {

@ -1,15 +1,7 @@
import { import { behaviors, SceneGridLayout, SceneGridItem, SceneTimeRange } from '@grafana/scenes';
behaviors,
SceneGridLayout,
SceneGridItem,
SceneRefreshPicker,
SceneTimeRange,
SceneTimePicker,
} from '@grafana/scenes';
import { DashboardCursorSync } from '@grafana/schema'; import { DashboardCursorSync } from '@grafana/schema';
import { DashboardControls } from '../scene/DashboardControls'; import { DashboardControls } from '../scene/DashboardControls';
import { DashboardLinksControls } from '../scene/DashboardLinksControls';
import { DashboardScene } from '../scene/DashboardScene'; import { DashboardScene } from '../scene/DashboardScene';
import { activateFullSceneTree } from '../utils/test-utils'; import { activateFullSceneTree } from '../utils/test-utils';
@ -38,19 +30,9 @@ describe('GeneralSettingsEditView', () => {
expect(settings.getTimeRange()).toBe(dashboard.state.$timeRange); expect(settings.getTimeRange()).toBe(dashboard.state.$timeRange);
}); });
it('should return the dashboard refresh picker', () => {
expect(settings.getRefreshPicker()).toBe(
(dashboard.state?.controls?.[0] as DashboardControls)?.state?.timeControls?.[1]
);
});
it('should return the cursor sync', () => { it('should return the cursor sync', () => {
expect(settings.getCursorSync()).toBe(dashboard.state.$behaviors?.[0]); expect(settings.getCursorSync()).toBe(dashboard.state.$behaviors?.[0]);
}); });
it('should return the dashboard controls', () => {
expect(settings.getDashboardControls()).toBe(dashboard.state.controls?.[0]);
});
}); });
describe('Dashboard updates', () => { describe('Dashboard updates', () => {
@ -133,18 +115,7 @@ async function buildTestScene() {
const dashboard = new DashboardScene({ const dashboard = new DashboardScene({
$timeRange: new SceneTimeRange({}), $timeRange: new SceneTimeRange({}),
$behaviors: [new behaviors.CursorSync({ sync: DashboardCursorSync.Off })], $behaviors: [new behaviors.CursorSync({ sync: DashboardCursorSync.Off })],
controls: [ controls: new DashboardControls({}),
new DashboardControls({
variableControls: [],
linkControls: new DashboardLinksControls({}),
timeControls: [
new SceneTimePicker({}),
new SceneRefreshPicker({
intervals: ['1s'],
}),
],
}),
],
title: 'hello', title: 'hello',
uid: 'dash-1', uid: 'dash-1',
meta: { meta: {

@ -22,7 +22,6 @@ import { DeleteDashboardButton } from 'app/features/dashboard/components/DeleteD
import { DashboardScene } from '../scene/DashboardScene'; import { DashboardScene } from '../scene/DashboardScene';
import { NavToolbarActions } from '../scene/NavToolbarActions'; import { NavToolbarActions } from '../scene/NavToolbarActions';
import { dashboardSceneGraph } from '../utils/dashboardSceneGraph';
import { getDashboardSceneFor } from '../utils/utils'; import { getDashboardSceneFor } from '../utils/utils';
import { DashboardEditView, DashboardEditViewState, useDashboardEditPageNav } from './utils'; import { DashboardEditView, DashboardEditViewState, useDashboardEditPageNav } from './utils';
@ -61,7 +60,7 @@ export class GeneralSettingsEditView
} }
public getRefreshPicker() { public getRefreshPicker() {
return dashboardSceneGraph.getRefreshPicker(this._dashboard); return this.getDashboardControls().state.refreshPicker;
} }
public getCursorSync() { public getCursorSync() {
@ -75,7 +74,7 @@ export class GeneralSettingsEditView
} }
public getDashboardControls() { public getDashboardControls() {
return dashboardSceneGraph.getDashboardControls(this._dashboard); return this._dashboard.state.controls!;
} }
public onTitleChange = (value: string) => { public onTitleChange = (value: string) => {
@ -151,8 +150,8 @@ export class GeneralSettingsEditView
const { title, description, tags, meta, editable } = model.getDashboard().useState(); const { title, description, tags, meta, editable } = model.getDashboard().useState();
const { sync: graphTooltip } = model.getCursorSync()?.useState() || {}; const { sync: graphTooltip } = model.getCursorSync()?.useState() || {};
const { timeZone, weekStart, UNSAFE_nowDelay: nowDelay } = model.getTimeRange().useState(); const { timeZone, weekStart, UNSAFE_nowDelay: nowDelay } = model.getTimeRange().useState();
const { intervals } = model.getRefreshPicker()?.useState() || {}; const { intervals } = model.getRefreshPicker().useState();
const { hideTimeControls } = model.getDashboardControls()?.useState() || {}; const { hideTimeControls } = model.getDashboardControls().useState();
return ( return (
<Page navModel={navModel} pageNav={pageNav} layout={PageLayoutType.Standard}> <Page navModel={navModel} pageNav={pageNav} layout={PageLayoutType.Standard}>

@ -3,11 +3,9 @@ import {
behaviors, behaviors,
SceneGridItem, SceneGridItem,
SceneGridLayout, SceneGridLayout,
SceneRefreshPicker,
SceneQueryRunner, SceneQueryRunner,
SceneTimeRange, SceneTimeRange,
VizPanel, VizPanel,
SceneTimePicker,
SceneDataTransformer, SceneDataTransformer,
SceneDataLayers, SceneDataLayers,
} from '@grafana/scenes'; } from '@grafana/scenes';
@ -17,7 +15,6 @@ import { SHARED_DASHBOARD_QUERY } from 'app/plugins/datasource/dashboard';
import { AlertStatesDataLayer } from '../scene/AlertStatesDataLayer'; import { AlertStatesDataLayer } from '../scene/AlertStatesDataLayer';
import { DashboardAnnotationsDataLayer } from '../scene/DashboardAnnotationsDataLayer'; import { DashboardAnnotationsDataLayer } from '../scene/DashboardAnnotationsDataLayer';
import { DashboardControls } from '../scene/DashboardControls'; import { DashboardControls } from '../scene/DashboardControls';
import { DashboardLinksControls } from '../scene/DashboardLinksControls';
import { DashboardScene } from '../scene/DashboardScene'; import { DashboardScene } from '../scene/DashboardScene';
import { NEW_LINK } from '../settings/links/utils'; import { NEW_LINK } from '../settings/links/utils';
@ -37,7 +34,7 @@ describe('DashboardModelCompatibilityWrapper', () => {
expect(wrapper.time.from).toBe('now-6h'); expect(wrapper.time.from).toBe('now-6h');
expect(wrapper.timezone).toBe('America/New_York'); expect(wrapper.timezone).toBe('America/New_York');
expect(wrapper.weekStart).toBe('friday'); expect(wrapper.weekStart).toBe('friday');
expect(wrapper.timepicker.refresh_intervals).toEqual(['1s']); expect(wrapper.timepicker.refresh_intervals![0]).toEqual('5s');
expect(wrapper.timepicker.hidden).toEqual(true); expect(wrapper.timepicker.hidden).toEqual(true);
expect(wrapper.panels).toHaveLength(5); expect(wrapper.panels).toHaveLength(5);
@ -60,9 +57,7 @@ describe('DashboardModelCompatibilityWrapper', () => {
expect(wrapper.panels[3].datasource).toEqual({ uid: 'gdev-testdata', type: 'grafana-testdata-datasource' }); expect(wrapper.panels[3].datasource).toEqual({ uid: 'gdev-testdata', type: 'grafana-testdata-datasource' });
expect(wrapper.panels[4].datasource).toEqual({ uid: SHARED_DASHBOARD_QUERY, type: 'datasource' }); expect(wrapper.panels[4].datasource).toEqual({ uid: SHARED_DASHBOARD_QUERY, type: 'datasource' });
(scene.state.controls![0] as DashboardControls).setState({ scene.state.controls!.setState({ hideTimeControls: false });
hideTimeControls: false,
});
const wrapper2 = new DashboardModelCompatibilityWrapper(scene); const wrapper2 = new DashboardModelCompatibilityWrapper(scene);
expect(wrapper2.timepicker.hidden).toEqual(false); expect(wrapper2.timepicker.hidden).toEqual(false);
@ -175,19 +170,9 @@ function setup() {
}), }),
], ],
}), }),
controls: [ controls: new DashboardControls({
new DashboardControls({ hideTimeControls: true,
variableControls: [], }),
linkControls: new DashboardLinksControls({}),
timeControls: [
new SceneTimePicker({}),
new SceneRefreshPicker({
intervals: ['1s'],
}),
],
hideTimeControls: true,
}),
],
body: new SceneGridLayout({ body: new SceneGridLayout({
children: [ children: [
new SceneGridItem({ new SceneGridItem({

@ -17,7 +17,6 @@ import { DashboardScene } from '../scene/DashboardScene';
import { dataLayersToAnnotations } from '../serialization/dataLayersToAnnotations'; import { dataLayersToAnnotations } from '../serialization/dataLayersToAnnotations';
import { PanelModelCompatibilityWrapper } from './PanelModelCompatibilityWrapper'; import { PanelModelCompatibilityWrapper } from './PanelModelCompatibilityWrapper';
import { dashboardSceneGraph } from './dashboardSceneGraph';
import { findVizPanelByKey, getVizPanelKeyForPanelId } from './utils'; import { findVizPanelByKey, getVizPanelKeyForPanelId } from './utils';
/** /**
@ -68,8 +67,8 @@ export class DashboardModelCompatibilityWrapper {
public get timepicker() { public get timepicker() {
return { return {
refresh_intervals: dashboardSceneGraph.getRefreshPicker(this._scene)?.state.intervals, refresh_intervals: this._scene.state.controls!.state.refreshPicker.state.intervals,
hidden: dashboardSceneGraph.getDashboardControls(this._scene)?.state.hideTimeControls ?? false, hidden: this._scene.state.controls!.state.hideTimeControls ?? false,
}; };
} }

@ -4,8 +4,6 @@ import {
SceneGridLayout, SceneGridLayout,
SceneGridRow, SceneGridRow,
SceneQueryRunner, SceneQueryRunner,
SceneRefreshPicker,
SceneTimePicker,
SceneTimeRange, SceneTimeRange,
VizPanel, VizPanel,
} from '@grafana/scenes'; } from '@grafana/scenes';
@ -13,7 +11,6 @@ import {
import { AlertStatesDataLayer } from '../scene/AlertStatesDataLayer'; import { AlertStatesDataLayer } from '../scene/AlertStatesDataLayer';
import { DashboardAnnotationsDataLayer } from '../scene/DashboardAnnotationsDataLayer'; import { DashboardAnnotationsDataLayer } from '../scene/DashboardAnnotationsDataLayer';
import { DashboardControls } from '../scene/DashboardControls'; import { DashboardControls } from '../scene/DashboardControls';
import { DashboardLinksControls } from '../scene/DashboardLinksControls';
import { DashboardScene, DashboardSceneState } from '../scene/DashboardScene'; import { DashboardScene, DashboardSceneState } from '../scene/DashboardScene';
import { VizPanelLinks, VizPanelLinksMenu } from '../scene/PanelLinks'; import { VizPanelLinks, VizPanelLinksMenu } from '../scene/PanelLinks';
@ -21,67 +18,6 @@ import { dashboardSceneGraph } from './dashboardSceneGraph';
import { findVizPanelByKey } from './utils'; import { findVizPanelByKey } from './utils';
describe('dashboardSceneGraph', () => { describe('dashboardSceneGraph', () => {
describe('getTimePicker', () => {
it('should return null if no time picker', () => {
const scene = buildTestScene({
controls: [
new DashboardControls({
variableControls: [],
linkControls: new DashboardLinksControls({}),
timeControls: [],
}),
],
});
const timePicker = dashboardSceneGraph.getTimePicker(scene);
expect(timePicker).toBeNull();
});
it('should return time picker', () => {
const scene = buildTestScene();
const timePicker = dashboardSceneGraph.getTimePicker(scene);
expect(timePicker).not.toBeNull();
});
});
describe('getRefreshPicker', () => {
it('should return null if no refresh picker', () => {
const scene = buildTestScene({
controls: [
new DashboardControls({
variableControls: [],
linkControls: new DashboardLinksControls({}),
timeControls: [],
}),
],
});
const refreshPicker = dashboardSceneGraph.getRefreshPicker(scene);
expect(refreshPicker).toBeNull();
});
it('should return refresh picker', () => {
const scene = buildTestScene();
const refreshPicker = dashboardSceneGraph.getRefreshPicker(scene);
expect(refreshPicker).not.toBeNull();
});
});
describe('getDashboardControls', () => {
it('should return null if no dashboard controls', () => {
const scene = buildTestScene({ controls: [] });
const dashboardControls = dashboardSceneGraph.getDashboardControls(scene);
expect(dashboardControls).toBeNull();
});
it('should return dashboard controls', () => {
const scene = buildTestScene();
const dashboardControls = dashboardSceneGraph.getDashboardControls(scene);
expect(dashboardControls).not.toBeNull();
});
});
describe('getPanelLinks', () => { describe('getPanelLinks', () => {
it('should throw if no links object defined', () => { it('should throw if no links object defined', () => {
const scene = buildTestScene(); const scene = buildTestScene();
@ -155,18 +91,7 @@ function buildTestScene(overrides?: Partial<DashboardSceneState>) {
title: 'hello', title: 'hello',
uid: 'dash-1', uid: 'dash-1',
$timeRange: new SceneTimeRange({}), $timeRange: new SceneTimeRange({}),
controls: [ controls: new DashboardControls({}),
new DashboardControls({
variableControls: [],
linkControls: new DashboardLinksControls({}),
timeControls: [
new SceneTimePicker({}),
new SceneRefreshPicker({
intervals: ['1s'],
}),
],
}),
],
$data: new SceneDataLayers({ $data: new SceneDataLayers({
layers: [ layers: [
new DashboardAnnotationsDataLayer({ new DashboardAnnotationsDataLayer({

@ -1,48 +1,14 @@
import { import { VizPanel, SceneGridItem, SceneGridRow, SceneDataLayers, sceneGraph } from '@grafana/scenes';
SceneTimePicker,
SceneRefreshPicker,
VizPanel,
SceneGridItem,
SceneGridRow,
SceneDataLayers,
sceneGraph,
} from '@grafana/scenes';
import { DashboardControls } from '../scene/DashboardControls';
import { DashboardScene } from '../scene/DashboardScene'; import { DashboardScene } from '../scene/DashboardScene';
import { VizPanelLinks } from '../scene/PanelLinks'; import { VizPanelLinks } from '../scene/PanelLinks';
function getTimePicker(scene: DashboardScene) { function getTimePicker(scene: DashboardScene) {
const dashboardControls = getDashboardControls(scene); return scene.state.controls?.state.timePicker;
if (dashboardControls) {
const timePicker = dashboardControls.state.timeControls.find((c) => c instanceof SceneTimePicker);
if (timePicker && timePicker instanceof SceneTimePicker) {
return timePicker;
}
}
return null;
} }
function getRefreshPicker(scene: DashboardScene) { function getRefreshPicker(scene: DashboardScene) {
const dashboardControls = getDashboardControls(scene); return scene.state.controls?.state.refreshPicker;
if (dashboardControls) {
for (const control of dashboardControls.state.timeControls) {
if (control instanceof SceneRefreshPicker) {
return control;
}
}
}
return null;
}
function getDashboardControls(scene: DashboardScene) {
if (scene.state.controls?.[0] instanceof DashboardControls) {
return scene.state.controls[0];
}
return null;
} }
function getPanelLinks(panel: VizPanel) { function getPanelLinks(panel: VizPanel) {
@ -92,7 +58,6 @@ function getDataLayers(scene: DashboardScene): SceneDataLayers {
export const dashboardSceneGraph = { export const dashboardSceneGraph = {
getTimePicker, getTimePicker,
getRefreshPicker, getRefreshPicker,
getDashboardControls,
getPanelLinks, getPanelLinks,
getVizPanels, getVizPanels,
getDataLayers, getDataLayers,

Loading…
Cancel
Save