GeneralSettings: hide time picker from settings using scenes (#79526)

This commit is contained in:
Ivan Ortega Alba 2023-12-15 11:52:34 +01:00 committed by GitHub
parent 8a6ea4bfad
commit 9fd3c9df55
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 225 additions and 103 deletions

View File

@ -11,6 +11,7 @@ interface DashboardControlsState extends SceneObjectState {
variableControls: SceneObject[];
timeControls: SceneObject[];
linkControls: DashboardLinksControls;
hideTimeControls?: boolean;
}
export class DashboardControls extends SceneObjectBase<DashboardControlsState> {
static Component = DashboardControlsRenderer;
@ -18,7 +19,7 @@ export class DashboardControls extends SceneObjectBase<DashboardControlsState> {
function DashboardControlsRenderer({ model }: SceneComponentProps<DashboardControls>) {
const dashboard = getDashboardSceneFor(model);
const { variableControls, linkControls, timeControls } = model.useState();
const { variableControls, linkControls, timeControls, hideTimeControls } = model.useState();
const { isEditing } = dashboard.useState();
return (
@ -40,9 +41,7 @@ function DashboardControlsRenderer({ model }: SceneComponentProps<DashboardContr
{isEditing && (
<ToolbarButton variant="canvas" icon="cog" tooltip="Dashboard settings" onClick={dashboard.onOpenSettings} />
)}
{timeControls.map((c) => (
<c.Component model={c} key={c.state.key} />
))}
{!hideTimeControls && timeControls.map((c) => <c.Component model={c} key={c.state.key} />)}
</Stack>
</Stack>
);

View File

@ -56,47 +56,55 @@ describe('DashboardScene', () => {
});
it.each`
prop | value
${'title'} | ${'new title'}
${'description'} | ${'new description'}
${'tags'} | ${['tag1', 'tag2']}
${'editable'} | ${false}
${'graphTooltip'} | ${1}
prop | value
${'title'} | ${'new title'}
${'description'} | ${'new description'}
${'tags'} | ${['tag3', 'tag4']}
${'editable'} | ${false}
`(
'A change to $prop should set isDirty true',
({ prop, value }: { prop: keyof DashboardSceneState; value: any }) => {
const prevState = scene.state[prop];
scene.setState({ [prop]: value });
expect(scene.state.isDirty).toBe(true);
// TODO: Discard doesn't restore the previous state
// scene.onDiscard();
// expect(scene.state[prop]).toBe(prevState);
scene.onDiscard();
expect(scene.state[prop]).toEqual(prevState);
}
);
// TODO: Make the dashboard to restore the defaults on discard
it.skip('A change to refresh picker interval settings should set isDirty true', () => {
it('A change to refresh picker interval settings should set isDirty true', () => {
const refreshPicker = dashboardSceneGraph.getRefreshPicker(scene)!;
const prevState = refreshPicker.state.intervals;
const prevState = [...refreshPicker.state.intervals!];
refreshPicker.setState({ intervals: ['10s'] });
expect(scene.state.isDirty).toBe(true);
scene.onDiscard();
expect(refreshPicker.state.intervals).toEqual(prevState);
expect(dashboardSceneGraph.getRefreshPicker(scene)!.state.intervals).toEqual(prevState);
});
// TODO: Make the dashboard to restore the defaults on discard
it.skip('A change to time zone should set isDirty true', () => {
const timeRange = scene.state.$timeRange!;
it('A change to time picker visibility settings should set isDirty true', () => {
const dashboardControls = dashboardSceneGraph.getDashboardControls(scene)!;
const prevState = dashboardControls.state.hideTimeControls;
dashboardControls.setState({ hideTimeControls: true });
expect(scene.state.isDirty).toBe(true);
scene.onDiscard();
expect(dashboardSceneGraph.getDashboardControls(scene)!.state.hideTimeControls).toEqual(prevState);
});
it('A change to time zone should set isDirty true', () => {
const timeRange = sceneGraph.getTimeRange(scene)!;
const prevState = timeRange.state.timeZone;
timeRange.setState({ timeZone: 'UTC' });
expect(scene.state.isDirty).toBe(true);
scene.onDiscard();
expect(timeRange.state.timeZone).toBe(prevState);
expect(sceneGraph.getTimeRange(scene)!.state.timeZone).toBe(prevState);
});
});
});
@ -148,7 +156,12 @@ function buildTestScene(overrides?: Partial<DashboardSceneState>) {
const scene = new DashboardScene({
title: 'hello',
uid: 'dash-1',
$timeRange: new SceneTimeRange({}),
description: 'hello description',
tags: ['tag1', 'tag2'],
editable: true,
$timeRange: new SceneTimeRange({
timeZone: 'browser',
}),
controls: [
new DashboardControls({
variableControls: [],

View File

@ -33,6 +33,7 @@ import { djb2Hash } from '../utils/djb2Hash';
import { getDashboardUrl } from '../utils/urlBuilders';
import { forceRenderChildren, getClosestVizPanel, getPanelIdForVizPanel, isPanelClone } from '../utils/utils';
import { DashboardControls } from './DashboardControls';
import { DashboardSceneUrlSync } from './DashboardSceneUrlSync';
import { ViewPanelScene } from './ViewPanelScene';
import { setupKeyboardShortcuts } from './keyboardShortcuts';
@ -249,6 +250,11 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> {
if (event.payload.changedObject instanceof SceneTimeRange) {
this.setIsDirty();
}
if (event.payload.changedObject instanceof DashboardControls) {
if (Object.prototype.hasOwnProperty.call(event.payload.partialUpdate, 'hideTimeControls')) {
this.setIsDirty();
}
}
}
);
}

View File

@ -558,18 +558,12 @@ exports[`transformSceneToSaveModel Given a simple scene with custom settings Sho
"to": "now",
},
"timepicker": {
"hidden": false,
"hidden": true,
"refresh_intervals": [
"5s",
"10s",
"30s",
"1m",
"5m",
"15m",
"30m",
"1h",
"2h",
"1d",
],
"time_options": [
"5m",

View File

@ -22,9 +22,18 @@ import {
SceneGridItem,
SceneGridLayout,
SceneGridRow,
SceneRefreshPicker,
SceneTimePicker,
VizPanel,
} from '@grafana/scenes';
import { DashboardCursorSync, defaultDashboard, Panel, RowPanel, VariableType } from '@grafana/schema';
import {
DashboardCursorSync,
defaultDashboard,
defaultTimePickerConfig,
Panel,
RowPanel,
VariableType,
} from '@grafana/schema';
import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
import { createPanelSaveModel } from 'app/features/dashboard/state/__fixtures__/dashboardFixtures';
import { SHARED_DASHBOARD_QUERY } from 'app/plugins/datasource/dashboard';
@ -58,6 +67,10 @@ describe('transformSaveModelToScene', () => {
weekStart: 'saturday',
fiscalYearStartMonth: 2,
timezone: 'America/New_York',
timepicker: {
...defaultTimePickerConfig,
hidden: true,
},
templating: {
list: [
{
@ -93,6 +106,7 @@ describe('transformSaveModelToScene', () => {
const oldModel = new DashboardModel(dash);
const scene = createDashboardSceneFromDashboardModel(oldModel);
const dashboardControls = scene.state.controls![0] as DashboardControls;
expect(scene.state.title).toBe('test');
expect(scene.state.uid).toBe('test-uid');
@ -100,13 +114,19 @@ describe('transformSaveModelToScene', () => {
expect(scene.state?.$timeRange?.state.fiscalYearStartMonth).toEqual(2);
expect(scene.state?.$timeRange?.state.timeZone).toEqual('America/New_York');
expect(scene.state?.$timeRange?.state.weekStart).toEqual('saturday');
expect(scene.state?.$variables?.state.variables).toHaveLength(1);
expect(scene.state.controls).toBeDefined();
expect(scene.state.controls![0]).toBeInstanceOf(DashboardControls);
expect((scene.state.controls![0] as DashboardControls).state.variableControls[1]).toBeInstanceOf(AdHocFilterSet);
expect(
((scene.state.controls![0] as DashboardControls).state.variableControls[1] as AdHocFilterSet).state.name
).toBe('CoolFilters');
expect(dashboardControls).toBeDefined();
expect(dashboardControls).toBeInstanceOf(DashboardControls);
expect(dashboardControls.state.variableControls[1]).toBeInstanceOf(AdHocFilterSet);
expect((dashboardControls.state.variableControls[1] as AdHocFilterSet).state.name).toBe('CoolFilters');
expect(dashboardControls.state.timeControls).toHaveLength(2);
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);
});
it('should apply cursor sync behavior', () => {

View File

@ -268,16 +268,15 @@ export function createDashboardSceneFromDashboardModel(oldModel: DashboardModel)
controls: [
new DashboardControls({
variableControls: [new VariableValueSelectors({}), ...filtersSets, new SceneDataLayerControls()],
timeControls: Boolean(oldModel.timepicker.hidden)
? []
: [
new SceneTimePicker({}),
new SceneRefreshPicker({
refresh: oldModel.refresh,
intervals: oldModel.timepicker.refresh_intervals,
}),
],
timeControls: [
new SceneTimePicker({}),
new SceneRefreshPicker({
refresh: oldModel.refresh,
intervals: oldModel.timepicker.refresh_intervals,
}),
],
linkControls: new DashboardLinksControls({}),
hideTimeControls: oldModel.timepicker.hidden,
}),
],
});

View File

@ -51,6 +51,7 @@ export function transformSceneToSaveModel(scene: DashboardScene, isSnapshot = fa
const variablesSet = state.$variables;
const body = state.body;
let refresh_intervals = defaultTimePickerConfig.refresh_intervals;
let hideTimePicker: boolean = defaultTimePickerConfig.hidden;
let panels: Panel[] = [];
let graphTooltip = defaultDashboard.graphTooltip;
let variables: VariableModel[] = [];
@ -87,6 +88,8 @@ export function transformSceneToSaveModel(scene: DashboardScene, isSnapshot = fa
}
if (state.controls && state.controls[0] instanceof DashboardControls) {
hideTimePicker = state.controls[0].state.hideTimeControls ?? hideTimePicker;
const timeControls = state.controls[0].state.timeControls;
for (const control of timeControls) {
if (control instanceof SceneRefreshPicker && control.state.intervals) {
@ -123,6 +126,7 @@ export function transformSceneToSaveModel(scene: DashboardScene, isSnapshot = fa
timepicker: {
...defaultTimePickerConfig,
refresh_intervals,
hidden: hideTimePicker,
},
panels,
annotations: {

View File

@ -1,4 +1,11 @@
import { behaviors, SceneGridLayout, SceneGridItem, SceneRefreshPicker, SceneTimeRange } from '@grafana/scenes';
import {
behaviors,
SceneGridLayout,
SceneGridItem,
SceneRefreshPicker,
SceneTimeRange,
SceneTimePicker,
} from '@grafana/scenes';
import { DashboardCursorSync } from '@grafana/schema';
import { DashboardControls } from '../scene/DashboardControls';
@ -33,13 +40,17 @@ describe('GeneralSettingsEditView', () => {
it('should return the dashboard refresh picker', () => {
expect(settings.getRefreshPicker()).toBe(
(dashboard.state?.controls?.[0] as DashboardControls)?.state?.timeControls?.[0]
(dashboard.state?.controls?.[0] as DashboardControls)?.state?.timeControls?.[1]
);
});
it('should return the cursor sync', () => {
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', () => {
@ -108,6 +119,12 @@ describe('GeneralSettingsEditView', () => {
expect(settings.getCursorSync()?.state.sync).toBe(DashboardCursorSync.Crosshair);
});
it('A change to time picker visiblity settings updates the dashboard state', () => {
settings.onHideTimePickerChange(true);
expect(settings.getDashboardControls()?.state.hideTimeControls).toBe(true);
});
});
});
@ -121,6 +138,7 @@ async function buildTestScene() {
variableControls: [],
linkControls: new DashboardLinksControls({}),
timeControls: [
new SceneTimePicker({}),
new SceneRefreshPicker({
intervals: ['1s'],
}),

View File

@ -1,7 +1,7 @@
import React, { ChangeEvent } from 'react';
import { PageLayoutType } from '@grafana/data';
import { behaviors, SceneComponentProps, SceneObjectBase, SceneTimePicker, sceneGraph } from '@grafana/scenes';
import { behaviors, SceneComponentProps, SceneObjectBase, sceneGraph } from '@grafana/scenes';
import { TimeZone } from '@grafana/schema';
import {
Box,
@ -20,7 +20,6 @@ import { t, Trans } from 'app/core/internationalization';
import { TimePickerSettings } from 'app/features/dashboard/components/DashboardSettings/TimePickerSettings';
import { DeleteDashboardButton } from 'app/features/dashboard/components/DeleteDashboard/DeleteDashboardButton';
import { DashboardControls } from '../scene/DashboardControls';
import { DashboardScene } from '../scene/DashboardScene';
import { NavToolbarActions } from '../scene/NavToolbarActions';
import { dashboardSceneGraph } from '../utils/dashboardSceneGraph';
@ -75,6 +74,10 @@ export class GeneralSettingsEditView
return;
}
public getDashboardControls() {
return dashboardSceneGraph.getDashboardControls(this._dashboard);
}
public onTitleChange = (value: string) => {
this._dashboard.setState({ title: value });
};
@ -126,16 +129,9 @@ export class GeneralSettingsEditView
};
public onHideTimePickerChange = (value: boolean) => {
if (this._dashboard.state.controls instanceof DashboardControls) {
for (const control of this._dashboard.state.controls.state.timeControls) {
if (control instanceof SceneTimePicker) {
control.setState({
// TODO: Control visibility from DashboardControls
// hidden: value,
});
}
}
}
this.getDashboardControls()?.setState({
hideTimeControls: value,
});
};
public onLiveNowChange = (value: boolean) => {
@ -152,6 +148,7 @@ export class GeneralSettingsEditView
const { sync: graphTooltip } = model.getCursorSync()?.useState() || {};
const { timeZone, weekStart } = model.getTimeRange().useState();
const { intervals } = model.getRefreshPicker()?.useState() || {};
const { hideTimeControls } = model.getDashboardControls()?.useState() || {};
return (
<Page navModel={navModel} pageNav={pageNav} layout={PageLayoutType.Standard}>
@ -232,8 +229,7 @@ export class GeneralSettingsEditView
onHideTimePickerChange={model.onHideTimePickerChange}
onLiveNowChange={model.onLiveNowChange}
refreshIntervals={intervals}
// TODO: Control visibility of time picker
// timePickerHidden={timepicker?.state?.hidden}
timePickerHidden={hideTimeControls}
// TODO: Implement this in dashboard scene
// nowDelay={timepicker.nowDelay || ''}
// TODO: Implement this in dashboard scene

View File

@ -34,7 +34,7 @@ describe('DashboardModelCompatibilityWrapper', () => {
expect(wrapper.timepicker.hidden).toEqual(true);
(scene.state.controls![0] as DashboardControls).setState({
timeControls: [new SceneTimePicker({})],
hideTimeControls: false,
});
const wrapper2 = new DashboardModelCompatibilityWrapper(scene);
@ -99,10 +99,12 @@ function setup() {
variableControls: [],
linkControls: new DashboardLinksControls({}),
timeControls: [
new SceneTimePicker({}),
new SceneRefreshPicker({
intervals: ['1s'],
}),
],
hideTimeControls: true,
}),
],
body: new SceneGridLayout({

View File

@ -63,7 +63,7 @@ export class DashboardModelCompatibilityWrapper {
public get timepicker() {
return {
refresh_intervals: dashboardSceneGraph.getRefreshPicker(this._scene)?.state.intervals,
hidden: !Boolean(dashboardSceneGraph.getTimePicker(this._scene)),
hidden: dashboardSceneGraph.getDashboardControls(this._scene)?.state.hideTimeControls ?? false,
};
}

View File

@ -1,25 +1,30 @@
import { DashboardDataDTO } from 'app/types';
import {
SceneGridItem,
SceneGridLayout,
SceneQueryRunner,
SceneRefreshPicker,
SceneTimePicker,
SceneTimeRange,
VizPanel,
} from '@grafana/scenes';
import dashboard_to_load from '../serialization/testfiles/dashboard_to_load1.json';
import { transformSaveModelToScene } from '../serialization/transformSaveModelToScene';
import { DashboardControls } from '../scene/DashboardControls';
import { DashboardLinksControls } from '../scene/DashboardLinksControls';
import { DashboardScene, DashboardSceneState } from '../scene/DashboardScene';
import { dashboardSceneGraph } from './dashboardSceneGraph';
describe('dashboardSceneGraph', () => {
describe('getTimePicker', () => {
it('should return null if no time picker', () => {
const dashboard: DashboardDataDTO = {
...(dashboard_to_load as unknown as DashboardDataDTO),
timepicker: {
hidden: true,
refresh_intervals: [],
time_options: [],
},
};
const scene = transformSaveModelToScene({
dashboard: dashboard as unknown as DashboardDataDTO,
meta: {},
const scene = buildTestScene({
controls: [
new DashboardControls({
variableControls: [],
linkControls: new DashboardLinksControls({}),
timeControls: [],
}),
],
});
const timePicker = dashboardSceneGraph.getTimePicker(scene);
@ -27,10 +32,7 @@ describe('dashboardSceneGraph', () => {
});
it('should return time picker', () => {
const scene = transformSaveModelToScene({
dashboard: dashboard_to_load as unknown as DashboardDataDTO,
meta: {},
});
const scene = buildTestScene();
const timePicker = dashboardSceneGraph.getTimePicker(scene);
expect(timePicker).not.toBeNull();
});
@ -38,18 +40,14 @@ describe('dashboardSceneGraph', () => {
describe('getRefreshPicker', () => {
it('should return null if no refresh picker', () => {
const dashboard: DashboardDataDTO = {
...(dashboard_to_load as unknown as DashboardDataDTO),
timepicker: {
hidden: true,
refresh_intervals: [],
time_options: [],
},
};
const scene = transformSaveModelToScene({
dashboard: dashboard as unknown as DashboardDataDTO,
meta: {},
const scene = buildTestScene({
controls: [
new DashboardControls({
variableControls: [],
linkControls: new DashboardLinksControls({}),
timeControls: [],
}),
],
});
const refreshPicker = dashboardSceneGraph.getRefreshPicker(scene);
@ -57,12 +55,76 @@ describe('dashboardSceneGraph', () => {
});
it('should return refresh picker', () => {
const scene = transformSaveModelToScene({
dashboard: dashboard_to_load as unknown as DashboardDataDTO,
meta: {},
});
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();
});
});
});
function buildTestScene(overrides?: Partial<DashboardSceneState>) {
const scene = new DashboardScene({
title: 'hello',
uid: 'dash-1',
$timeRange: new SceneTimeRange({}),
controls: [
new DashboardControls({
variableControls: [],
linkControls: new DashboardLinksControls({}),
timeControls: [
new SceneTimePicker({}),
new SceneRefreshPicker({
intervals: ['1s'],
}),
],
}),
],
body: new SceneGridLayout({
children: [
new SceneGridItem({
key: 'griditem-1',
x: 0,
body: new VizPanel({
title: 'Panel A',
key: 'panel-1',
pluginId: 'table',
$data: new SceneQueryRunner({ key: 'data-query-runner', queries: [{ refId: 'A' }] }),
}),
}),
new SceneGridItem({
body: new VizPanel({
title: 'Panel B',
key: 'panel-2',
pluginId: 'table',
}),
}),
new SceneGridItem({
body: new VizPanel({
title: 'Panel B',
key: 'panel-2-clone-1',
pluginId: 'table',
$data: new SceneQueryRunner({ key: 'data-query-runner2', queries: [{ refId: 'A' }] }),
}),
}),
],
}),
...overrides,
});
return scene;
}

View File

@ -4,10 +4,9 @@ import { DashboardControls } from '../scene/DashboardControls';
import { DashboardScene } from '../scene/DashboardScene';
function getTimePicker(scene: DashboardScene) {
const controls = scene.state.controls;
const dashboardControls = getDashboardControls(scene);
if (controls && controls[0] instanceof DashboardControls) {
const dashboardControls = controls[0];
if (dashboardControls) {
const timePicker = dashboardControls.state.timeControls.find((c) => c instanceof SceneTimePicker);
if (timePicker && timePicker instanceof SceneTimePicker) {
return timePicker;
@ -18,8 +17,10 @@ function getTimePicker(scene: DashboardScene) {
}
function getRefreshPicker(scene: DashboardScene) {
if (scene.state.controls?.[0] instanceof DashboardControls) {
for (const control of scene.state.controls[0].state.timeControls) {
const dashboardControls = getDashboardControls(scene);
if (dashboardControls) {
for (const control of dashboardControls.state.timeControls) {
if (control instanceof SceneRefreshPicker) {
return control;
}
@ -28,7 +29,15 @@ function getRefreshPicker(scene: DashboardScene) {
return null;
}
function getDashboardControls(scene: DashboardScene) {
if (scene.state.controls?.[0] instanceof DashboardControls) {
return scene.state.controls[0];
}
return null;
}
export const dashboardSceneGraph = {
getTimePicker,
getRefreshPicker,
getDashboardControls,
};