From 703deeff0b4ffcc066cb2ee1fa3f7b007a5eae74 Mon Sep 17 00:00:00 2001 From: Dominik Prokop Date: Mon, 27 Nov 2023 09:32:23 +0100 Subject: [PATCH] DashboardScene: Add time picker keybindings (#78632) * grafana/data: Add time range zoom out util * Add keybindings for time range zoom out * TimeRangePicker: change the way absolute time range is detected * Depend on dashboard scene tructure rather * Revert "grafana/data: Add time range zoom out util" This reverts commit bc1602db57d9523324d0d2fbf2bda0f472cdcbf3. * Lint * Lint * dashboardSceneGraph tests --- .../grafana-data/src/datetime/rangeutil.ts | 2 +- .../DateTimePickers/TimeRangePicker.tsx | 4 +- .../scene/keyboardShortcuts.ts | 49 +++++++++++++++++++ .../utils/dashboardSceneGraph.test.ts | 39 +++++++++++++++ .../utils/dashboardSceneGraph.ts | 22 +++++++++ 5 files changed, 113 insertions(+), 3 deletions(-) create mode 100644 public/app/features/dashboard-scene/utils/dashboardSceneGraph.test.ts create mode 100644 public/app/features/dashboard-scene/utils/dashboardSceneGraph.ts diff --git a/packages/grafana-data/src/datetime/rangeutil.ts b/packages/grafana-data/src/datetime/rangeutil.ts index c4f514b6735..7bb575e5f41 100644 --- a/packages/grafana-data/src/datetime/rangeutil.ts +++ b/packages/grafana-data/src/datetime/rangeutil.ts @@ -214,7 +214,7 @@ export const convertRawToRange = ( return { from, to, raw: { from, to } }; }; -function isRelativeTime(v: DateTime | string) { +export function isRelativeTime(v: DateTime | string) { if (typeof v === 'string') { return v.indexOf('now') >= 0; } diff --git a/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker.tsx b/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker.tsx index 3c3b2d4b830..f64c4f426bf 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker.tsx @@ -5,7 +5,6 @@ import { useOverlay } from '@react-aria/overlays'; import React, { memo, createRef, useState, useEffect } from 'react'; import { - isDateTime, rangeUtil, GrafanaTheme2, dateTimeFormat, @@ -108,7 +107,8 @@ export function TimeRangePicker(props: TimeRangePickerProps) { const styles = useStyles2(getStyles); const { modalBackdrop } = useStyles2(getModalStyles); - const hasAbsolute = isDateTime(value.raw.from) || isDateTime(value.raw.to); + const hasAbsolute = !rangeUtil.isRelativeTime(value.raw.from) || !rangeUtil.isRelativeTime(value.raw.to); + const variant = isSynced ? 'active' : isOnCanvas ? 'canvas' : 'default'; const currentTimeRange = formattedRange(value, timeZone); diff --git a/public/app/features/dashboard-scene/scene/keyboardShortcuts.ts b/public/app/features/dashboard-scene/scene/keyboardShortcuts.ts index 0f306180db1..7dd6c02ca9e 100644 --- a/public/app/features/dashboard-scene/scene/keyboardShortcuts.ts +++ b/public/app/features/dashboard-scene/scene/keyboardShortcuts.ts @@ -4,6 +4,7 @@ import { OptionsWithLegend } from '@grafana/schema'; import { KeybindingSet } from 'app/core/services/KeybindingSet'; import { ShareModal } from '../sharing/ShareModal'; +import { dashboardSceneGraph } from '../utils/dashboardSceneGraph'; import { getDashboardUrl, getInspectUrl, getViewPanelUrl, tryGetExploreUrlForPanel } from '../utils/urlBuilders'; import { getPanelIdForVizPanel } from '../utils/utils'; @@ -79,6 +80,34 @@ export function setupKeyboardShortcuts(scene: DashboardScene) { onTrigger: () => sceneGraph.getTimeRange(scene).onRefresh(), }); + // Zoom out + keybindings.addBinding({ + key: 't z', + onTrigger: () => { + handleZoomOut(scene); + }, + }); + keybindings.addBinding({ + key: 'ctrl+z', + onTrigger: () => { + handleZoomOut(scene); + }, + }); + + keybindings.addBinding({ + key: 't left', + onTrigger: () => { + handleTimeRangeShift(scene, 'left'); + }, + }); + + keybindings.addBinding({ + key: 't right', + onTrigger: () => { + handleTimeRangeShift(scene, 'right'); + }, + }); + // Dashboard settings keybindings.addBinding({ key: 'd s', @@ -128,3 +157,23 @@ export function toggleVizPanelLegend(vizPanel: VizPanel) { function hasLegendOptions(optionsWithLegend: unknown): optionsWithLegend is OptionsWithLegend { return optionsWithLegend != null && typeof optionsWithLegend === 'object' && 'legend' in optionsWithLegend; } + +function handleZoomOut(scene: DashboardScene) { + const timePicker = dashboardSceneGraph.getTimePicker(scene); + timePicker?.onZoom(); +} + +function handleTimeRangeShift(scene: DashboardScene, direction: 'left' | 'right') { + const timePicker = dashboardSceneGraph.getTimePicker(scene); + + if (!timePicker) { + return; + } + + if (direction === 'left') { + timePicker.onMoveBackward(); + } + if (direction === 'right') { + timePicker.onMoveForward(); + } +} diff --git a/public/app/features/dashboard-scene/utils/dashboardSceneGraph.test.ts b/public/app/features/dashboard-scene/utils/dashboardSceneGraph.test.ts new file mode 100644 index 00000000000..4d6519c08ec --- /dev/null +++ b/public/app/features/dashboard-scene/utils/dashboardSceneGraph.test.ts @@ -0,0 +1,39 @@ +import { DashboardDataDTO } from 'app/types'; + +import dashboard_to_load from '../serialization/testfiles/dashboard_to_load1.json'; +import { transformSaveModelToScene } from '../serialization/transformSaveModelToScene'; + +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, + collapse: false, + refresh_intervals: [], + time_options: [], + }, + }; + + const scene = transformSaveModelToScene({ + dashboard: dashboard as unknown as DashboardDataDTO, + meta: {}, + }); + + const timePicker = dashboardSceneGraph.getTimePicker(scene); + expect(timePicker).toBeNull(); + }); + + it('should return time picker', () => { + const scene = transformSaveModelToScene({ + dashboard: dashboard_to_load as unknown as DashboardDataDTO, + meta: {}, + }); + const timePicker = dashboardSceneGraph.getTimePicker(scene); + expect(timePicker).not.toBeNull(); + }); + }); +}); diff --git a/public/app/features/dashboard-scene/utils/dashboardSceneGraph.ts b/public/app/features/dashboard-scene/utils/dashboardSceneGraph.ts new file mode 100644 index 00000000000..a223ca096ee --- /dev/null +++ b/public/app/features/dashboard-scene/utils/dashboardSceneGraph.ts @@ -0,0 +1,22 @@ +import { SceneTimePicker } from '@grafana/scenes'; + +import { DashboardControls } from '../scene/DashboardControls'; +import { DashboardScene } from '../scene/DashboardScene'; + +function getTimePicker(scene: DashboardScene) { + const controls = scene.state.controls; + + if (controls && controls[0] instanceof DashboardControls) { + const dashboardControls = controls[0]; + const timePicker = dashboardControls.state.timeControls.find((c) => c instanceof SceneTimePicker); + if (timePicker && timePicker instanceof SceneTimePicker) { + return timePicker; + } + } + + return null; +} + +export const dashboardSceneGraph = { + getTimePicker, +};