diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index 1f166051adc..65f523fc212 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -1233,6 +1233,10 @@ export interface FeatureToggles { */ onlyStoreActionSets?: boolean; /** + * Enables a new panel time settings drawer + */ + panelTimeSettings?: boolean; + /** * Enable template dashboards */ dashboardTemplates?: boolean; diff --git a/packages/grafana-ui/src/components/PanelChrome/TitleItem.tsx b/packages/grafana-ui/src/components/PanelChrome/TitleItem.tsx index 6a2a8adaa2d..18fee0039ad 100644 --- a/packages/grafana-ui/src/components/PanelChrome/TitleItem.tsx +++ b/packages/grafana-ui/src/components/PanelChrome/TitleItem.tsx @@ -39,7 +39,13 @@ export const TitleItem = forwardRef( ); } else if (onClick) { return ( - ); @@ -59,7 +65,6 @@ const getStyles = (theme: GrafanaTheme2) => { const item = css({ color: `${theme.colors.text.secondary}`, label: 'panel-header-item', - cursor: 'auto', border: 'none', borderRadius: `${theme.shape.radius.default}`, padding: `${theme.spacing(0, 1)}`, @@ -84,5 +89,6 @@ const getStyles = (theme: GrafanaTheme2) => { return { item, linkItem: cx(item, css({ cursor: 'pointer' })), + buttonItem: cx(item, css({ cursor: 'pointer' })), }; }; diff --git a/packages/grafana-ui/src/options/builder/index.ts b/packages/grafana-ui/src/options/builder/index.ts index 98b376794ee..42dd18e4850 100644 --- a/packages/grafana-ui/src/options/builder/index.ts +++ b/packages/grafana-ui/src/options/builder/index.ts @@ -4,4 +4,3 @@ export * from './legend'; export { addTooltipOptions } from './tooltip'; export * from './text'; export * from './stacking'; -export { addTimeCompareOption } from './timeCompare'; diff --git a/packages/grafana-ui/src/options/builder/timeCompare.tsx b/packages/grafana-ui/src/options/builder/timeCompare.tsx deleted file mode 100644 index 222856ff9f4..00000000000 --- a/packages/grafana-ui/src/options/builder/timeCompare.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { PanelOptionsEditorBuilder } from '@grafana/data'; -import { TimeCompareOptions } from '@grafana/schema'; - -/** - * Adds a generic time comparison option to the panel options editor. - * Can be used by any panel that supports time comparison. - */ -export function addTimeCompareOption( - builder: PanelOptionsEditorBuilder, - defaultValue = false -) { - builder.addBooleanSwitch({ - path: 'timeCompare', - name: 'Show comparison selector', - category: ['Time Comparison'], - description: 'Enables the comparison selector, so you can compare data between two time ranges', - defaultValue, - }); -} diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index edbc06b0029..43c3490359f 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -2133,6 +2133,17 @@ var ( Owner: identityAccessTeam, Expression: "true", }, + { + Name: "panelTimeSettings", + Description: "Enables a new panel time settings drawer", + FrontendOnly: false, + Stage: FeatureStageExperimental, + Owner: grafanaDashboardsSquad, + RequiresRestart: false, + AllowSelfServe: false, + HideFromDocs: false, + HideFromAdminPage: false, + }, { Name: "dashboardTemplates", Description: "Enable template dashboards", diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index 9a51e087cd6..35943dad6e2 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -274,4 +274,5 @@ newGauge,experimental,@grafana/dataviz-squad,false,false,true preventPanelChromeOverflow,preview,@grafana/grafana-frontend-platform,false,false,true pluginStoreServiceLoading,experimental,@grafana/plugins-platform-backend,false,false,false onlyStoreActionSets,GA,@grafana/identity-access-team,false,false,false +panelTimeSettings,experimental,@grafana/dashboards-squad,false,false,false dashboardTemplates,experimental,@grafana/sharing-squad,false,false,false diff --git a/pkg/services/featuremgmt/toggles_gen.go b/pkg/services/featuremgmt/toggles_gen.go index 13e47ce210e..fd328f487ac 100644 --- a/pkg/services/featuremgmt/toggles_gen.go +++ b/pkg/services/featuremgmt/toggles_gen.go @@ -1106,6 +1106,10 @@ const ( // When storing dashboard and folder resource permissions, only store action sets and not the full list of underlying permission FlagOnlyStoreActionSets = "onlyStoreActionSets" + // FlagPanelTimeSettings + // Enables a new panel time settings drawer + FlagPanelTimeSettings = "panelTimeSettings" + // FlagDashboardTemplates // Enable template dashboards FlagDashboardTemplates = "dashboardTemplates" diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json index 583c41b5d42..ec67bd4bce9 100644 --- a/pkg/services/featuremgmt/toggles_gen.json +++ b/pkg/services/featuremgmt/toggles_gen.json @@ -2886,6 +2886,18 @@ "expression": "true" } }, + { + "metadata": { + "name": "panelTimeSettings", + "resourceVersion": "1761555646368", + "creationTimestamp": "2025-10-27T09:00:46Z" + }, + "spec": { + "description": "Enables a new panel time settings drawer", + "stage": "experimental", + "codeowner": "@grafana/dashboards-squad" + } + }, { "metadata": { "name": "panelTitleSearch", diff --git a/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataQueriesTab.test.tsx b/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataQueriesTab.test.tsx index aad14f4fa8b..40da4a158a8 100644 --- a/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataQueriesTab.test.tsx +++ b/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataQueriesTab.test.tsx @@ -24,7 +24,7 @@ import { SHARED_DASHBOARD_QUERY, DASHBOARD_DATASOURCE_PLUGIN_ID } from 'app/plug import { DashboardDataDTO } from 'app/types/dashboard'; import { PanelInspectDrawer } from '../../inspect/PanelInspectDrawer'; -import { PanelTimeRange, PanelTimeRangeState } from '../../scene/PanelTimeRange'; +import { PanelTimeRange, PanelTimeRangeState } from '../../scene/panel-timerange/PanelTimeRange'; import { transformSaveModelToScene } from '../../serialization/transformSaveModelToScene'; import { findVizPanelByKey } from '../../utils/utils'; import { buildPanelEditScene } from '../PanelEditor'; diff --git a/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataQueriesTab.tsx b/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataQueriesTab.tsx index 94e3d1ed2ad..490084b24ff 100644 --- a/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataQueriesTab.tsx +++ b/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataQueriesTab.tsx @@ -34,7 +34,7 @@ import { useQueryLibraryContext } from '../../../explore/QueryLibrary/QueryLibra import { ExpressionDatasourceUID } from '../../../expressions/types'; import { getDatasourceSrv } from '../../../plugins/datasource_srv'; import { PanelInspectDrawer } from '../../inspect/PanelInspectDrawer'; -import { PanelTimeRange } from '../../scene/PanelTimeRange'; +import { PanelTimeRange } from '../../scene/panel-timerange/PanelTimeRange'; import { getDashboardSceneFor, getQueryRunnerFor } from '../../utils/utils'; import { getUpdatedHoverHeader } from '../getPanelFrameOptions'; diff --git a/public/app/features/dashboard-scene/panel-edit/getPanelFrameOptions.tsx b/public/app/features/dashboard-scene/panel-edit/getPanelFrameOptions.tsx index c78b7a1a6b1..e881df09579 100644 --- a/public/app/features/dashboard-scene/panel-edit/getPanelFrameOptions.tsx +++ b/public/app/features/dashboard-scene/panel-edit/getPanelFrameOptions.tsx @@ -14,8 +14,8 @@ import { getPanelLinksVariableSuggestions } from 'app/features/panel/panellinks/ import { dashboardEditActions } from '../edit-pane/shared'; import { VizPanelLinks } from '../scene/PanelLinks'; -import { PanelTimeRange } from '../scene/PanelTimeRange'; import { useEditPaneInputAutoFocus } from '../scene/layouts-shared/utils'; +import { PanelTimeRange } from '../scene/panel-timerange/PanelTimeRange'; import { isDashboardLayoutItem } from '../scene/types/DashboardLayoutItem'; import { vizPanelToPanel, transformSceneToSaveModel } from '../serialization/transformSceneToSaveModel'; import { dashboardSceneGraph } from '../utils/dashboardSceneGraph'; diff --git a/public/app/features/dashboard-scene/saving/DashboardSceneChangeTracker.ts b/public/app/features/dashboard-scene/saving/DashboardSceneChangeTracker.ts index 658cf97fa1f..6bae04e7248 100644 --- a/public/app/features/dashboard-scene/saving/DashboardSceneChangeTracker.ts +++ b/public/app/features/dashboard-scene/saving/DashboardSceneChangeTracker.ts @@ -24,7 +24,6 @@ import { DashboardControls } from '../scene/DashboardControls'; import { DashboardScene, PERSISTED_PROPS } from '../scene/DashboardScene'; import { LibraryPanelBehavior } from '../scene/LibraryPanelBehavior'; import { VizPanelLinks } from '../scene/PanelLinks'; -import { PanelTimeRange } from '../scene/PanelTimeRange'; import { AutoGridItem } from '../scene/layout-auto-grid/AutoGridItem'; import { AutoGridLayoutManager } from '../scene/layout-auto-grid/AutoGridLayoutManager'; import { DashboardGridItem } from '../scene/layout-default/DashboardGridItem'; @@ -32,6 +31,7 @@ import { RowItem } from '../scene/layout-rows/RowItem'; import { RowsLayoutManager } from '../scene/layout-rows/RowsLayoutManager'; import { TabItem } from '../scene/layout-tabs/TabItem'; import { TabsLayoutManager } from '../scene/layout-tabs/TabsLayoutManager'; +import { PanelTimeRange } from '../scene/panel-timerange/PanelTimeRange'; import { isSceneVariableInstance } from '../settings/variables/utils'; import { DashboardChangeInfo } from './shared'; diff --git a/public/app/features/dashboard-scene/scene/CustomTimeRangeCompare.tsx b/public/app/features/dashboard-scene/scene/CustomTimeRangeCompare.tsx deleted file mode 100644 index e741ea98ba2..00000000000 --- a/public/app/features/dashboard-scene/scene/CustomTimeRangeCompare.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import { reportInteraction } from '@grafana/runtime'; -import { SceneTimeRangeCompare, SceneComponentProps, VizPanel, sceneGraph } from '@grafana/scenes'; -import { TimeCompareOptions } from '@grafana/schema'; - -function hasTimeCompare(options: unknown): options is TimeCompareOptions { - return options != null && typeof options === 'object' && 'timeCompare' in options; -} - -export class CustomTimeRangeCompare extends SceneTimeRangeCompare { - private readonly parentOnCompareWithChanged: (compareWith: string) => void; - - constructor(state: Partial = {}) { - super({ - ...state, - compareWith: undefined, - compareOptions: [], - hideCheckbox: true, - }); - - this.parentOnCompareWithChanged = this.onCompareWithChanged.bind(this); - - this.onCompareWithChanged = (compareWith: string) => { - const vizPanel = sceneGraph.getAncestor(this, VizPanel); - - reportInteraction('panel_time_comparison', { - viz_type: vizPanel?.getPlugin()?.meta.id || 'unknown', - select_type: 'option_selected', - option_type: compareWith, - }); - - this.parentOnCompareWithChanged(compareWith); - }; - - this.addActivationHandler(() => this._activationHandler()); - } - - private _activationHandler() { - // Subscribe to parent panel's options changes - const vizPanel = sceneGraph.getAncestor(this, VizPanel); - - this._subs.add( - vizPanel.subscribeToState((newState, prevState) => { - const newTimeCompareEnabled = hasTimeCompare(newState.options) && newState.options.timeCompare; - const prevTimeCompareEnabled = hasTimeCompare(prevState.options) && prevState.options.timeCompare; - - // Only act when transitioning from enabled to disabled - if (prevTimeCompareEnabled && !newTimeCompareEnabled) { - this._handleDisable(); - } - }) - ); - } - - private _handleDisable() { - // Only clear state if there's actually a comparison active - if (this.state.compareWith) { - this.setState({ - compareWith: undefined, - }); - } - } - - static Component = function CustomTimeRangeCompareRenderer({ model }: SceneComponentProps) { - const vizPanel = sceneGraph.getAncestor(model, VizPanel); - const { options } = vizPanel.useState(); - - const isTimeCompareEnabled = hasTimeCompare(options) && options.timeCompare; - - if (!isTimeCompareEnabled) { - return <>; - } - - return ( -
- -
- ); - }; -} diff --git a/public/app/features/dashboard-scene/scene/DashboardLevelTimeMacro.test.ts b/public/app/features/dashboard-scene/scene/DashboardLevelTimeMacro.test.ts index d55c4940f52..9a7bc67cd22 100644 --- a/public/app/features/dashboard-scene/scene/DashboardLevelTimeMacro.test.ts +++ b/public/app/features/dashboard-scene/scene/DashboardLevelTimeMacro.test.ts @@ -6,7 +6,6 @@ import { activateFullSceneTree } from '../utils/test-utils'; import { DashboardLevelTimeMacro } from './DashboardLevelTimeMacro'; import { DashboardScene } from './DashboardScene'; -import { PanelTimeRange } from './PanelTimeRange'; import { AutoGridItem } from './layout-auto-grid/AutoGridItem'; import { AutoGridLayout } from './layout-auto-grid/AutoGridLayout'; import { @@ -14,6 +13,7 @@ import { getAutoRowsTemplate, getTemplateColumnsTemplate, } from './layout-auto-grid/AutoGridLayoutManager'; +import { PanelTimeRange } from './panel-timerange/PanelTimeRange'; jest.mock('@grafana/runtime', () => ({ ...jest.requireActual('@grafana/runtime'), diff --git a/public/app/features/dashboard-scene/scene/DashboardScene.test.tsx b/public/app/features/dashboard-scene/scene/DashboardScene.test.tsx index 7454ad46eb3..29478ca80a1 100644 --- a/public/app/features/dashboard-scene/scene/DashboardScene.test.tsx +++ b/public/app/features/dashboard-scene/scene/DashboardScene.test.tsx @@ -43,10 +43,10 @@ import { findVizPanelByKey, getLibraryPanelBehavior, isLibraryPanel } from '../u import { DashboardControls } from './DashboardControls'; import { DashboardScene, DashboardSceneState } from './DashboardScene'; import { LibraryPanelBehavior } from './LibraryPanelBehavior'; -import { PanelTimeRange } from './PanelTimeRange'; import { DashboardGridItem } from './layout-default/DashboardGridItem'; import { DefaultGridLayoutManager } from './layout-default/DefaultGridLayoutManager'; import { RowActions } from './layout-default/row-actions/RowActions'; +import { PanelTimeRange } from './panel-timerange/PanelTimeRange'; jest.mock('../settings/version-history/HistorySrv'); jest.mock('../serialization/transformSaveModelToScene'); diff --git a/public/app/features/dashboard-scene/scene/LibraryPanelBehavior.test.tsx b/public/app/features/dashboard-scene/scene/LibraryPanelBehavior.test.tsx index e8a3c3d4d8b..238e6a87b06 100644 --- a/public/app/features/dashboard-scene/scene/LibraryPanelBehavior.test.tsx +++ b/public/app/features/dashboard-scene/scene/LibraryPanelBehavior.test.tsx @@ -22,9 +22,9 @@ import { getPanelIdForVizPanel } from '../utils/utils'; import { DashboardScene } from './DashboardScene'; import { LibraryPanelBehavior } from './LibraryPanelBehavior'; import { VizPanelLinks } from './PanelLinks'; -import { PanelTimeRange } from './PanelTimeRange'; import { DashboardGridItem } from './layout-default/DashboardGridItem'; import { DefaultGridLayoutManager } from './layout-default/DefaultGridLayoutManager'; +import { PanelTimeRange } from './panel-timerange/PanelTimeRange'; setPluginImportUtils({ importPanelPlugin: (id: string) => Promise.resolve(getPanelPlugin({})), diff --git a/public/app/features/dashboard-scene/scene/LibraryPanelBehavior.tsx b/public/app/features/dashboard-scene/scene/LibraryPanelBehavior.tsx index ecccc4b21d1..74b78bbc323 100644 --- a/public/app/features/dashboard-scene/scene/LibraryPanelBehavior.tsx +++ b/public/app/features/dashboard-scene/scene/LibraryPanelBehavior.tsx @@ -13,8 +13,8 @@ import { getPanelIdForVizPanel } from '../utils/utils'; import { VizPanelLinks, VizPanelLinksMenu } from './PanelLinks'; import { panelLinksBehavior } from './PanelMenuBehavior'; import { PanelNotices } from './PanelNotices'; -import { PanelTimeRange } from './PanelTimeRange'; import { DashboardGridItem } from './layout-default/DashboardGridItem'; +import { PanelTimeRange } from './panel-timerange/PanelTimeRange'; export interface LibraryPanelBehaviorState extends SceneObjectState { uid: string; diff --git a/public/app/features/dashboard-scene/scene/PanelMenuBehavior.tsx b/public/app/features/dashboard-scene/scene/PanelMenuBehavior.tsx index b357e734bb7..8a5502115a8 100644 --- a/public/app/features/dashboard-scene/scene/PanelMenuBehavior.tsx +++ b/public/app/features/dashboard-scene/scene/PanelMenuBehavior.tsx @@ -43,6 +43,7 @@ import { getDashboardSceneFor, getPanelIdForVizPanel, getQueryRunnerFor, isLibra import { DashboardScene } from './DashboardScene'; import { VizPanelLinks, VizPanelLinksMenu } from './PanelLinks'; import { UnlinkLibraryPanelModal } from './UnlinkLibraryPanelModal'; +import { PanelTimeRangeDrawer } from './panel-timerange/PanelTimeRangeDrawer'; let getPluginExtensions: GetPluginExtensions; @@ -280,6 +281,17 @@ export function panelMenuBehavior(menu: VizPanelMenu) { items.push(getInspectMenuItem(plugin, panel, dashboard)); + if (config.featureToggles.panelTimeSettings) { + items.push({ + text: t('panel.header-menu.time-settings', 'Time settings'), + iconClassName: 'clock-nine', + onClick: (e) => { + e.preventDefault(); + dashboard.showModal(new PanelTimeRangeDrawer({ panelRef: panel.getRef() })); + }, + }); + } + setupGetPluginExtensions(); const { extensions } = getPluginExtensions({ diff --git a/public/app/features/dashboard-scene/scene/PanelTimeRange.test.tsx b/public/app/features/dashboard-scene/scene/panel-timerange/PanelTimeRange.test.tsx similarity index 84% rename from public/app/features/dashboard-scene/scene/PanelTimeRange.test.tsx rename to public/app/features/dashboard-scene/scene/panel-timerange/PanelTimeRange.test.tsx index a4b7cdd944c..7e39e1e696e 100644 --- a/public/app/features/dashboard-scene/scene/PanelTimeRange.test.tsx +++ b/public/app/features/dashboard-scene/scene/panel-timerange/PanelTimeRange.test.tsx @@ -10,7 +10,7 @@ import { TestVariable, } from '@grafana/scenes'; -import { activateFullSceneTree } from '../utils/test-utils'; +import { activateFullSceneTree } from '../../utils/test-utils'; import { PanelTimeRange } from './PanelTimeRange'; @@ -43,7 +43,7 @@ describe('PanelTimeRange', () => { expect(panelTime.state.value.from.toISOString()).toBe('2019-02-11T11:00:00.000Z'); expect(panelTime.state.value.to.toISOString()).toBe('2019-02-11T17:00:00.000Z'); - expect(panelTime.state.timeInfo).toBe(' timeshift -2h'); + expect(panelTime.state.timeInfo).toBe('Timeshift -2h'); }); it('should apply both relative time and time shift', () => { @@ -52,7 +52,23 @@ describe('PanelTimeRange', () => { buildAndActivateSceneFor(panelTime); expect(panelTime.state.value.from.toISOString()).toBe('2019-02-11T15:00:00.000Z'); - expect(panelTime.state.timeInfo).toBe('Last 2 hours timeshift -2h'); + expect(panelTime.state.timeInfo).toBe('Last 2 hours + timeshift -2h'); + }); + + it('should add time comparison to timeInfo', () => { + const panelTime = new PanelTimeRange({ compareWith: '1d' }); + + buildAndActivateSceneFor(panelTime); + + expect(panelTime.state.timeInfo).toBe('Compared to day before'); + }); + + it('should add time override and time comparison to timeInfo', () => { + const panelTime = new PanelTimeRange({ timeFrom: '1h', compareWith: '1d' }); + + buildAndActivateSceneFor(panelTime); + + expect(panelTime.state.timeInfo).toBe('Last 1 hour + compared to day before'); }); it('should update timeInfo when timeShift and timeFrom are variable expressions', async () => { @@ -75,14 +91,14 @@ describe('PanelTimeRange', () => { }); activateFullSceneTree(scene); - expect(panelTime.state.timeInfo).toBe('Last 10 seconds timeshift -20s'); + expect(panelTime.state.timeInfo).toBe('Last 10 seconds + timeshift -20s'); customTimeFrom.setState({ value: '15s' }); customTimeShift.setState({ value: '25s' }); panelTime.forceRender(); - expect(panelTime.state.timeInfo).toBe('Last 15 seconds timeshift -25s'); + expect(panelTime.state.timeInfo).toBe('Last 15 seconds + timeshift -25s'); }); it('should update panelTimeRange from/to based on scene timeRange on activate', () => { diff --git a/public/app/features/dashboard-scene/scene/PanelTimeRange.tsx b/public/app/features/dashboard-scene/scene/panel-timerange/PanelTimeRange.tsx similarity index 60% rename from public/app/features/dashboard-scene/scene/PanelTimeRange.tsx rename to public/app/features/dashboard-scene/scene/panel-timerange/PanelTimeRange.tsx index 6c5fac6d443..4449cff2ea6 100644 --- a/public/app/features/dashboard-scene/scene/PanelTimeRange.tsx +++ b/public/app/features/dashboard-scene/scene/panel-timerange/PanelTimeRange.tsx @@ -1,22 +1,35 @@ import { css } from '@emotion/css'; +import { capitalize } from 'lodash'; -import { dateMath, getDefaultTimeRange, GrafanaTheme2, rangeUtil, TimeRange } from '@grafana/data'; +import { DataQueryRequest, dateMath, getDefaultTimeRange, GrafanaTheme2, rangeUtil, TimeRange } from '@grafana/data'; +import { config } from '@grafana/runtime'; import { + ExtraQueryDescriptor, SceneComponentProps, + SceneDataQuery, sceneGraph, SceneTimeRangeLike, SceneTimeRangeState, SceneTimeRangeTransformerBase, VariableDependencyConfig, + VizPanel, } from '@grafana/scenes'; -import { Icon, PanelChrome, TimePickerTooltip, Tooltip, useStyles2 } from '@grafana/ui'; +import { Icon, PanelChrome, Stack, TimePickerTooltip, Tooltip, useStyles2 } from '@grafana/ui'; import { TimeOverrideResult } from 'app/features/dashboard/utils/panel'; +import { getDashboardSceneFor } from '../../utils/utils'; + +import { DEFAULT_COMPARE_OPTIONS, PanelTimeRangeDrawer, PanelTimeRangeZoomBehavior } from './PanelTimeRangeDrawer'; +import { getCompareTimeRange, timeShiftAlignmentProcessor } from './utils'; + export interface PanelTimeRangeState extends SceneTimeRangeState { + enabled?: boolean; timeFrom?: string; + zoomBehavior?: PanelTimeRangeZoomBehavior; timeShift?: string; hideTimeOverride?: boolean; timeInfo?: string; + compareWith?: string; } export class PanelTimeRange extends SceneTimeRangeTransformerBase implements SceneTimeRangeLike { @@ -61,13 +74,48 @@ export class PanelTimeRange extends SceneTimeRangeTransformerBase query.timeRangeCompare !== false); + if (targets.length) { + extraQueries.push({ + req: { + ...request, + targets, + range: compareRange, + }, + processor: timeShiftAlignmentProcessor, + }); + } + return extraQueries; + } + + // The query runner should rerun the comparison query if the compareWith value has changed and there are queries that haven't opted out of TWC + public shouldRerun(prev: PanelTimeRangeState, next: PanelTimeRangeState, queries: SceneDataQuery[]): boolean { + return ( + prev.compareWith !== next.compareWith && queries.find((query) => query.timeRangeCompare !== false) !== undefined + ); + } + private getTimeOverride(parentTimeRange: TimeRange): TimeOverrideResult { - const { timeFrom, timeShift } = this.state; + const { timeFrom, timeShift, compareWith } = this.state; + const infoBlocks = []; const newTimeData = { timeInfo: '', timeRange: parentTimeRange }; if (timeFrom) { @@ -82,12 +130,12 @@ export class PanelTimeRange extends SceneTimeRangeTransformerBase x.value === compareWith); + const text = option ? `compared to ${option.label.toLowerCase()}` : ''; + infoBlocks.push(text); + } + + newTimeData.timeInfo = capitalize(infoBlocks.join(' + ')); return newTimeData; } + + public onOpenSettings = () => { + const panel = this.parent; + const dashboard = getDashboardSceneFor(this); + if (panel instanceof VizPanel) { + dashboard.showModal(new PanelTimeRangeDrawer({ panelRef: panel.getRef() })); + } + }; } function PanelTimeRangeRenderer({ model }: SceneComponentProps) { @@ -125,10 +189,15 @@ function PanelTimeRangeRenderer({ model }: SceneComponentProps) return null; } + const onClick = config.featureToggles.panelTimeSettings ? model.onOpenSettings : undefined; + return ( }> - - {timeInfo} + + + +
{timeInfo}
+
); diff --git a/public/app/features/dashboard-scene/scene/panel-timerange/PanelTimeRangeDrawer.tsx b/public/app/features/dashboard-scene/scene/panel-timerange/PanelTimeRangeDrawer.tsx new file mode 100644 index 00000000000..215a5fba5d4 --- /dev/null +++ b/public/app/features/dashboard-scene/scene/panel-timerange/PanelTimeRangeDrawer.tsx @@ -0,0 +1,201 @@ +import { FeatureState } from '@grafana/data'; +import { t, Trans } from '@grafana/i18n'; +import { config } from '@grafana/runtime'; +import { SceneComponentProps, SceneObjectBase, SceneObjectRef, SceneObjectState, VizPanel } from '@grafana/scenes'; +import { Box, Button, Combobox, Drawer, FeatureBadge, Field, Label, Stack, Switch } from '@grafana/ui'; + +import { getQuickOptions } from '../../../../../../packages/grafana-ui/src/components/DateTimePickers/options'; +import { getDashboardSceneFor, getQueryRunnerFor } from '../../utils/utils'; + +import { PanelTimeRange } from './PanelTimeRange'; + +export const DEFAULT_COMPARE_OPTIONS = [ + { label: 'Disabled', value: '' }, + { label: 'Day before', value: '1d' }, + { label: 'Week before', value: '1w' }, + { label: 'Month before', value: '1M' }, +]; + +export type PanelTimeRangeZoomBehavior = 'panel_and_dashboard' | 'dashboard' | 'panel'; + +export interface PanelTimeRangeDrawerState extends SceneObjectState { + panelRef: SceneObjectRef; + timeFrom?: string; + timeShift?: string; + zoomBehavior?: PanelTimeRangeZoomBehavior; + hideTimeOverride?: boolean; + compareWith?: string; + timeFromLocked?: boolean; +} + +export class PanelTimeRangeDrawer extends SceneObjectBase { + public constructor(state: PanelTimeRangeDrawerState) { + super({ + ...state, + }); + + const panel = this.state.panelRef.resolve(); + const timeRange = panel.state.$timeRange; + + if (timeRange instanceof PanelTimeRange) { + this.setState({ + timeFrom: timeRange.state.timeFrom, + timeShift: timeRange.state.timeShift, + hideTimeOverride: timeRange.state.hideTimeOverride, + compareWith: timeRange.state.compareWith, + }); + } + } + + public onClose = () => { + getDashboardSceneFor(this).closeModal(); + }; + + public onApply = () => { + const panel = this.state.panelRef.resolve(); + let timeRange = panel.state.$timeRange; + + if (!(timeRange instanceof PanelTimeRange)) { + timeRange = new PanelTimeRange(); + } + + timeRange.setState({ + timeFrom: this.state.timeFrom, + timeShift: this.state.timeShift, + hideTimeOverride: this.state.hideTimeOverride, + compareWith: this.state.compareWith, + zoomBehavior: this.state.zoomBehavior, + }); + + if (!panel.state.$timeRange) { + panel.setState({ $timeRange: timeRange }); + const queryRunner = getQueryRunnerFor(panel); + queryRunner?.runQueries(); + } + + this.onClose(); + }; + + static Component = ({ model }: SceneComponentProps) => { + const { timeFrom, timeShift, compareWith, hideTimeOverride } = model.useState(); + + const timeOptions = getQuickOptions() + .filter((o) => { + // Filter out time options that are not relative to now as we do not have persitance support for those yet + return o.to === 'now'; + }) + .map((option, index) => ({ label: option.display, value: option.from })); + + timeOptions.unshift({ label: t('common.disabled', 'Disabled'), value: '' }); + + const timeShiftOptions = [ + { label: t('common.disabled', 'Disabled'), value: '' }, + { label: t('time-period.1_hour', '1 hour'), value: '1h' }, + { label: t('time-period.6_hours', '6 hours'), value: '6h' }, + { label: t('time-period.12_hours', '12 hours'), value: '12h' }, + { label: t('time-period.1_day', '1 day'), value: '24h' }, + { label: t('time-period.7_days', '7 days'), value: '7d' }, + { label: t('time-period.30_days', '30 days'), value: '30d' }, + ]; + + return ( + + + + + { + model.setState({ timeFrom: x.value }); + }} + /> + + + + { + model.setState({ timeShift: x.value }); + }} + /> + + + {config.featureToggles.timeComparison && ( + + + + + } + > + model.setState({ compareWith: x.value })} + /> + + )} + + + model.setState({ hideTimeOverride: x.currentTarget.checked })} + /> + + + + + + + + + + + ); + }; +} diff --git a/public/app/features/dashboard-scene/scene/panel-timerange/utils.ts b/public/app/features/dashboard-scene/scene/panel-timerange/utils.ts new file mode 100644 index 00000000000..3bbd25c869b --- /dev/null +++ b/public/app/features/dashboard-scene/scene/panel-timerange/utils.ts @@ -0,0 +1,56 @@ +// Processor function for use with time shifted comparison series. +// This aligns the secondary series with the primary and adds custom +// metadata and config to the secondary series' fields so that it is + +import { of } from 'rxjs'; + +import { dateTime, DateTime, rangeUtil, TimeRange } from '@grafana/data'; +import { ExtraQueryDataProcessor } from '@grafana/scenes'; + +// rendered appropriately. +export const timeShiftAlignmentProcessor: ExtraQueryDataProcessor = (primary, secondary) => { + const diff = secondary.timeRange.from.diff(primary.timeRange.from); + secondary.series.forEach((series) => { + series.refId = getCompareSeriesRefId(series.refId || ''); + series.meta = { + ...series.meta, + // @ts-ignore Remove when https://github.com/grafana/grafana/pull/71129 is released + timeCompare: { + diffMs: diff, + isTimeShiftQuery: true, + }, + }; + }); + return of(secondary); +}; + +export const getCompareSeriesRefId = (refId: string) => `${refId}-compare`; + +const PREVIOUS_PERIOD_VALUE = '__previousPeriod'; + +export function getCompareTimeRange(timeRange: TimeRange, compareWith: string | undefined): TimeRange | undefined { + let compareFrom: DateTime; + let compareTo: DateTime; + + if (compareWith) { + if (compareWith === PREVIOUS_PERIOD_VALUE) { + const diffMs = timeRange.to.diff(timeRange.from); + compareFrom = dateTime(timeRange.from!).subtract(diffMs); + compareTo = dateTime(timeRange.to!).subtract(diffMs); + } else { + compareFrom = dateTime(timeRange.from!).subtract(rangeUtil.intervalToMs(compareWith)); + compareTo = dateTime(timeRange.to!).subtract(rangeUtil.intervalToMs(compareWith)); + } + + return { + from: compareFrom, + to: compareTo, + raw: { + from: compareFrom, + to: compareTo, + }, + }; + } + + return undefined; +} diff --git a/public/app/features/dashboard-scene/serialization/layoutSerializers/utils.test.ts b/public/app/features/dashboard-scene/serialization/layoutSerializers/utils.test.ts index d703b6e233d..be11b4e9dcd 100644 --- a/public/app/features/dashboard-scene/serialization/layoutSerializers/utils.test.ts +++ b/public/app/features/dashboard-scene/serialization/layoutSerializers/utils.test.ts @@ -1,8 +1,6 @@ -import { defaultDataQueryKind, PanelQueryKind, PanelKind } from '@grafana/schema/dist/esm/schema/dashboard/v2'; +import { defaultDataQueryKind, PanelQueryKind } from '@grafana/schema/dist/esm/schema/dashboard/v2'; -import { CustomTimeRangeCompare } from '../../scene/CustomTimeRangeCompare'; - -import { buildVizPanel, getRuntimePanelDataSource } from './utils'; +import { getRuntimePanelDataSource } from './utils'; // Mock the config needed for the function jest.mock('@grafana/runtime', () => ({ @@ -42,76 +40,6 @@ jest.mock('@grafana/runtime', () => ({ }, })); -// Mock only what's essential for header actions tests -jest.mock('../../scene/CustomTimeRangeCompare', () => ({ - CustomTimeRangeCompare: jest.fn(), -})); - -// Helper function to create a minimal panel for testing -const createTestPanel = (): PanelKind => ({ - kind: 'Panel', - spec: { - id: 1, - title: 'Test Panel', - description: '', - vizConfig: { - kind: 'VizConfig', - group: 'timeseries', - version: '1.0.0', - spec: { - options: {}, - fieldConfig: { defaults: {}, overrides: [] }, - }, - }, - data: { - kind: 'QueryGroup', - spec: { - queries: [], - queryOptions: {}, - transformations: [], - }, - }, - links: [], - }, -}); - -describe('buildVizPanel', () => { - describe('header actions', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('should include CustomTimeRangeCompare in headerActions when timeComparison feature toggle is enabled', () => { - // Mock config with timeComparison enabled - const mockConfig = require('@grafana/runtime').config; - mockConfig.featureToggles.timeComparison = true; - - const panel = createTestPanel(); - const vizPanel = buildVizPanel(panel); - - expect(vizPanel.state.headerActions).toBeDefined(); - expect(vizPanel.state.headerActions).toHaveLength(1); - expect(CustomTimeRangeCompare).toHaveBeenCalledWith({ - key: 'time-compare', - compareWith: undefined, - compareOptions: [], - }); - }); - - it('should not include headerActions when timeComparison feature toggle is disabled', () => { - // Mock config with timeComparison disabled - const mockConfig = require('@grafana/runtime').config; - mockConfig.featureToggles.timeComparison = false; - - const panel = createTestPanel(); - const vizPanel = buildVizPanel(panel); - - expect(vizPanel.state.headerActions).toBeUndefined(); - expect(CustomTimeRangeCompare).not.toHaveBeenCalled(); - }); - }); -}); - describe('getRuntimePanelDataSource', () => { it('should return the datasource when it is specified in the query', () => { const query: PanelQueryKind = { diff --git a/public/app/features/dashboard-scene/serialization/layoutSerializers/utils.ts b/public/app/features/dashboard-scene/serialization/layoutSerializers/utils.ts index 78ab49707cd..3f8c2edf01e 100644 --- a/public/app/features/dashboard-scene/serialization/layoutSerializers/utils.ts +++ b/public/app/features/dashboard-scene/serialization/layoutSerializers/utils.ts @@ -24,16 +24,15 @@ import { import { MIXED_DATASOURCE_NAME } from 'app/plugins/datasource/mixed/MixedDataSource'; import { ConditionalRenderingGroup } from '../../conditional-rendering/group/ConditionalRenderingGroup'; -import { CustomTimeRangeCompare } from '../../scene/CustomTimeRangeCompare'; import { DashboardDatasourceBehaviour } from '../../scene/DashboardDatasourceBehaviour'; import { DashboardScene } from '../../scene/DashboardScene'; import { LibraryPanelBehavior } from '../../scene/LibraryPanelBehavior'; import { VizPanelLinks, VizPanelLinksMenu } from '../../scene/PanelLinks'; import { panelLinksBehavior, panelMenuBehavior } from '../../scene/PanelMenuBehavior'; import { PanelNotices } from '../../scene/PanelNotices'; -import { PanelTimeRange } from '../../scene/PanelTimeRange'; import { AutoGridItem } from '../../scene/layout-auto-grid/AutoGridItem'; import { DashboardGridItem } from '../../scene/layout-default/DashboardGridItem'; +import { PanelTimeRange } from '../../scene/panel-timerange/PanelTimeRange'; import { setDashboardPanelContext } from '../../scene/setDashboardPanelContext'; import { DashboardLayoutManager } from '../../scene/types/DashboardLayoutManager'; import { getVizPanelKeyForPanelId } from '../../utils/utils'; @@ -72,10 +71,6 @@ export function buildVizPanel(panel: PanelKind, id?: number): VizPanel { titleItems, $behaviors: [], extendPanelContext: setDashboardPanelContext, - // _UNSAFE_customMigrationHandler: getAngularPanelMigrationHandler(panel), //FIXME: Angular Migration - headerActions: config.featureToggles.timeComparison - ? [new CustomTimeRangeCompare({ key: 'time-compare', compareWith: undefined, compareOptions: [] })] - : undefined, }; if (!config.publicDashboardAccessToken) { diff --git a/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.test.ts b/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.test.ts index 46b5324bbeb..a37dcd2fbae 100644 --- a/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.test.ts +++ b/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.test.ts @@ -29,11 +29,11 @@ import { DashboardDataDTO } from 'app/types/dashboard'; import { DashboardDataLayerSet } from '../scene/DashboardDataLayerSet'; import { LibraryPanelBehavior } from '../scene/LibraryPanelBehavior'; -import { PanelTimeRange } from '../scene/PanelTimeRange'; import { DashboardGridItem } from '../scene/layout-default/DashboardGridItem'; import { DefaultGridLayoutManager } from '../scene/layout-default/DefaultGridLayoutManager'; import { RowRepeaterBehavior } from '../scene/layout-default/RowRepeaterBehavior'; import { RowsLayoutManager } from '../scene/layout-rows/RowsLayoutManager'; +import { PanelTimeRange } from '../scene/panel-timerange/PanelTimeRange'; import { NEW_LINK } from '../settings/links/utils'; import { getQueryRunnerFor } from '../utils/utils'; @@ -810,41 +810,6 @@ describe('transformSaveModelToScene', () => { expect((libPanelBehavior as LibraryPanelBehavior).state.name).toEqual(panel.libraryPanel.name); expect(gridItem.state.body.state.title).toEqual(panel.title); }); - - describe('header actions', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('should include headerActions when timeComparison feature toggle is enabled', () => { - config.featureToggles.timeComparison = true; - - const panel = { - title: 'Test Panel', - type: 'timeseries', - gridPos: { x: 0, y: 0, w: 12, h: 8 }, - }; - - const { vizPanel } = buildGridItemForTest(panel); - - expect(vizPanel.state.headerActions).toBeDefined(); - expect(vizPanel.state.headerActions).toHaveLength(1); - }); - - it('should not include headerActions when timeComparison feature toggle is disabled', () => { - config.featureToggles.timeComparison = false; - - const panel = { - title: 'Test Panel', - type: 'timeseries', - gridPos: { x: 0, y: 0, w: 12, h: 8 }, - }; - - const { vizPanel } = buildGridItemForTest(panel); - - expect(vizPanel.state.headerActions).toBeUndefined(); - }); - }); }); describe('Convert to new rows', () => { diff --git a/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts b/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts index 8c3f3559030..6ec06c96c28 100644 --- a/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts +++ b/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts @@ -33,7 +33,6 @@ import { DashboardDTO, DashboardDataDTO } from 'app/types/dashboard'; import { addPanelsOnLoadBehavior } from '../addToDashboard/addPanelsOnLoadBehavior'; import { AlertStatesDataLayer } from '../scene/AlertStatesDataLayer'; -import { CustomTimeRangeCompare } from '../scene/CustomTimeRangeCompare'; import { DashboardAnnotationsDataLayer } from '../scene/DashboardAnnotationsDataLayer'; import { DashboardControls } from '../scene/DashboardControls'; import { DashboardDataLayerSet } from '../scene/DashboardDataLayerSet'; @@ -44,7 +43,6 @@ import { LibraryPanelBehavior } from '../scene/LibraryPanelBehavior'; import { VizPanelLinks, VizPanelLinksMenu } from '../scene/PanelLinks'; import { panelLinksBehavior, panelMenuBehavior } from '../scene/PanelMenuBehavior'; import { PanelNotices } from '../scene/PanelNotices'; -import { PanelTimeRange } from '../scene/PanelTimeRange'; import { DashboardGridItem, RepeatDirection } from '../scene/layout-default/DashboardGridItem'; import { DefaultGridLayoutManager } from '../scene/layout-default/DefaultGridLayoutManager'; import { RowRepeaterBehavior } from '../scene/layout-default/RowRepeaterBehavior'; @@ -52,6 +50,7 @@ import { RowActions } from '../scene/layout-default/row-actions/RowActions'; import { RowItem } from '../scene/layout-rows/RowItem'; import { RowsLayoutManager } from '../scene/layout-rows/RowsLayoutManager'; import { getIsLazy } from '../scene/layouts-shared/utils'; +import { PanelTimeRange } from '../scene/panel-timerange/PanelTimeRange'; import { setDashboardPanelContext } from '../scene/setDashboardPanelContext'; import { DashboardLayoutManager } from '../scene/types/DashboardLayoutManager'; import { createPanelDataProvider } from '../utils/createPanelDataProvider'; @@ -428,9 +427,6 @@ export function buildGridItemForPanel(panel: PanelModel): DashboardGridItem { $behaviors: [], extendPanelContext: setDashboardPanelContext, _UNSAFE_customMigrationHandler: getAngularPanelMigrationHandler(panel), - headerActions: config.featureToggles.timeComparison - ? [new CustomTimeRangeCompare({ key: 'time-compare', compareWith: undefined, compareOptions: [] })] - : undefined, }; if (panel.libraryPanel) { diff --git a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.ts b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.ts index a18a5918c7e..16b3957435f 100644 --- a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.ts +++ b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.ts @@ -32,10 +32,10 @@ import { GrafanaQueryType } from 'app/plugins/datasource/grafana/types'; import { DashboardDataLayerSet } from '../scene/DashboardDataLayerSet'; import { DashboardScene } from '../scene/DashboardScene'; -import { PanelTimeRange } from '../scene/PanelTimeRange'; import { DashboardGridItem } from '../scene/layout-default/DashboardGridItem'; import { DefaultGridLayoutManager } from '../scene/layout-default/DefaultGridLayoutManager'; import { RowRepeaterBehavior } from '../scene/layout-default/RowRepeaterBehavior'; +import { PanelTimeRange } from '../scene/panel-timerange/PanelTimeRange'; import { dashboardSceneGraph } from '../utils/dashboardSceneGraph'; import { djb2Hash } from '../utils/djb2Hash'; import { diff --git a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.ts b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.ts index a1d379d9865..da7e19d3a41 100644 --- a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.ts +++ b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.ts @@ -48,7 +48,7 @@ import { } from '../../../../../packages/grafana-schema/src/schema/dashboard/v2'; import { DashboardDataLayerSet } from '../scene/DashboardDataLayerSet'; import { DashboardScene, DashboardSceneState } from '../scene/DashboardScene'; -import { PanelTimeRange } from '../scene/PanelTimeRange'; +import { PanelTimeRange } from '../scene/panel-timerange/PanelTimeRange'; import { dashboardSceneGraph } from '../utils/dashboardSceneGraph'; import { getLibraryPanelBehavior, getPanelIdForVizPanel, getQueryRunnerFor, isLibraryPanel } from '../utils/utils'; diff --git a/public/app/features/dashboard-scene/sharing/SharePanelEmbedTab.tsx b/public/app/features/dashboard-scene/sharing/SharePanelEmbedTab.tsx index 7652a2c6f17..2e69a0abcb6 100644 --- a/public/app/features/dashboard-scene/sharing/SharePanelEmbedTab.tsx +++ b/public/app/features/dashboard-scene/sharing/SharePanelEmbedTab.tsx @@ -5,7 +5,7 @@ import { ShareEmbed } from 'app/features/dashboard/components/ShareModal/ShareEm import { buildParams, shareDashboardType } from 'app/features/dashboard/components/ShareModal/utils'; import { DashboardScene } from '../scene/DashboardScene'; -import { PanelTimeRange } from '../scene/PanelTimeRange'; +import { PanelTimeRange } from '../scene/panel-timerange/PanelTimeRange'; import { getDashboardUrl } from '../utils/getDashboardUrl'; import { getDashboardSceneFor } from '../utils/utils'; diff --git a/public/app/features/dashboard-scene/utils/utils.ts b/public/app/features/dashboard-scene/utils/utils.ts index 780275bc239..12dba08eda4 100644 --- a/public/app/features/dashboard-scene/utils/utils.ts +++ b/public/app/features/dashboard-scene/utils/utils.ts @@ -17,7 +17,6 @@ import { Dashboard, Panel, RowPanel } from '@grafana/schema'; import { createLogger } from '@grafana/ui'; import { initialIntervalVariableModelState } from 'app/features/variables/interval/reducer'; -import { CustomTimeRangeCompare } from '../scene/CustomTimeRangeCompare'; import { DashboardDatasourceBehaviour } from '../scene/DashboardDatasourceBehaviour'; import { DashboardLayoutOrchestrator } from '../scene/DashboardLayoutOrchestrator'; import { DashboardScene, DashboardSceneState } from '../scene/DashboardScene'; @@ -256,9 +255,6 @@ export function getDefaultVizPanel(): VizPanel { menu: new VizPanelMenu({ $behaviors: [panelMenuBehavior], }), - headerActions: config.featureToggles.timeComparison - ? [new CustomTimeRangeCompare({ key: 'time-compare', compareWith: undefined, compareOptions: [] })] - : undefined, $data: new SceneDataTransformer({ $data: new SceneQueryRunner({ queries: [{ refId: 'A' }], diff --git a/public/app/plugins/panel/timeseries/module.tsx b/public/app/plugins/panel/timeseries/module.tsx index 967cba918b5..beccdf480d6 100644 --- a/public/app/plugins/panel/timeseries/module.tsx +++ b/public/app/plugins/panel/timeseries/module.tsx @@ -1,6 +1,5 @@ import { PanelPlugin } from '@grafana/data'; import { t } from '@grafana/i18n'; -import { config } from '@grafana/runtime'; import { commonOptionsBuilder } from '@grafana/ui'; import { optsWithHideZeros } from '@grafana/ui/internal'; @@ -18,10 +17,6 @@ export const plugin = new PanelPlugin(TimeSeriesPanel) commonOptionsBuilder.addTooltipOptions(builder, false, true, optsWithHideZeros); commonOptionsBuilder.addLegendOptions(builder); - if (config.featureToggles.timeComparison && config.featureToggles.dashboardScene) { - commonOptionsBuilder.addTimeCompareOption(builder); - } - builder.addCustomEditor({ id: 'timezone', name: t('timeseries.name-time-zone', 'Time zone'), diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index b0d349ab26b..1beea7c487a 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -4157,6 +4157,7 @@ "cancel": "Cancel", "clear": "Clear", "collapse": "Collapse", + "disabled": "Disabled", "edit": "Edit", "help": "Help", "loading": "Loading...", @@ -5133,6 +5134,19 @@ "aria-label-remove-override": "Remove override", "tooltip-remove-override": "Remove override" }, + "panel": { + "time-range-settings": { + "hide-time-info": "Hidden time info", + "hide-time-info-description": "Do not show the custom time range in the panel header", + "time-from": "Custom panel time range", + "time-from-description": "Overrides the dashboard time range. To specify a value not found in the list just type in a custom value, for example 5m or 2h", + "time-shift": "Time shift", + "time-shift-description": "Adds a time shift relative to the dashboard or panel time range. To specify a value not found in the list just type in a custom value, for example 5m or 2h", + "time-window-compare": "Time window comparison", + "time-window-compare-description": "Query and overlay data from a different time period", + "title": "Panel time range settings" + } + }, "panel-edit": { "alerting-tab": { "dashboard-not-saved": "Dashboard must be saved before alerts can be added.", @@ -10872,6 +10886,7 @@ "replace-library-panel": "Replace library panel", "share": "Share", "show-legend": "Show legend", + "time-settings": "Time settings", "unlink-library-panel": "Unlink library panel", "view": "View" }, @@ -13135,6 +13150,14 @@ "title": "Panel" } }, + "time-period": { + "1_day": "1 day", + "1_hour": "1 hour", + "12_hours": "12 hours", + "30_days": "30 days", + "6_hours": "6 hours", + "7_days": "7 days" + }, "time-picker": { "absolute": { "recent-title": "Recently used absolute ranges",