PanelTimeSettings: Support panel time range settings changes from dashboard in view mode (#113027)
* Something is working * Progress * Update * Update * Update * Some new unit tests * Fix * time shift fix * Update * Always show hidden toggle * Update
This commit is contained in:
@@ -1233,6 +1233,10 @@ export interface FeatureToggles {
|
||||
*/
|
||||
onlyStoreActionSets?: boolean;
|
||||
/**
|
||||
* Enables a new panel time settings drawer
|
||||
*/
|
||||
panelTimeSettings?: boolean;
|
||||
/**
|
||||
* Enable template dashboards
|
||||
*/
|
||||
dashboardTemplates?: boolean;
|
||||
|
||||
@@ -39,7 +39,13 @@ export const TitleItem = forwardRef<TitleItemElement, TitleItemProps>(
|
||||
);
|
||||
} else if (onClick) {
|
||||
return (
|
||||
<Button ref={ref} className={cx(styles.item, className)} variant="secondary" fill="text" onClick={onClick}>
|
||||
<Button
|
||||
ref={ref}
|
||||
className={cx(styles.buttonItem, className)}
|
||||
variant="secondary"
|
||||
fill="text"
|
||||
onClick={onClick}
|
||||
>
|
||||
{children}
|
||||
</Button>
|
||||
);
|
||||
@@ -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' })),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -4,4 +4,3 @@ export * from './legend';
|
||||
export { addTooltipOptions } from './tooltip';
|
||||
export * from './text';
|
||||
export * from './stacking';
|
||||
export { addTimeCompareOption } from './timeCompare';
|
||||
|
||||
@@ -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<T extends { timeCompare?: TimeCompareOptions }>(
|
||||
builder: PanelOptionsEditorBuilder<T>,
|
||||
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,
|
||||
});
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
|
@@ -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"
|
||||
|
||||
@@ -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",
|
||||
|
||||
+1
-1
@@ -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';
|
||||
|
||||
+1
-1
@@ -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';
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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<SceneTimeRangeCompare['state']> = {}) {
|
||||
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<SceneTimeRangeCompare>) {
|
||||
const vizPanel = sceneGraph.getAncestor(model, VizPanel);
|
||||
const { options } = vizPanel.useState();
|
||||
|
||||
const isTimeCompareEnabled = hasTimeCompare(options) && options.timeCompare;
|
||||
|
||||
if (!isTimeCompareEnabled) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="show-on-hover">
|
||||
<SceneTimeRangeCompare.Component model={model} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -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'),
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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({})),
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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({
|
||||
|
||||
+21
-5
@@ -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', () => {
|
||||
+76
-7
@@ -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<PanelTimeRangeState> implements SceneTimeRangeLike {
|
||||
@@ -61,13 +74,48 @@ export class PanelTimeRange extends SceneTimeRangeTransformerBase<PanelTimeRange
|
||||
to: timeRange.raw.to.toString(),
|
||||
});
|
||||
}
|
||||
|
||||
protected ancestorTimeRangeChanged(timeRange: SceneTimeRangeState): void {
|
||||
if (this.state.timeFrom && this.state.zoomBehavior === 'dashboard') {
|
||||
return;
|
||||
}
|
||||
|
||||
const overrideResult = this.getTimeOverride(timeRange.value);
|
||||
this.setState({ value: overrideResult.timeRange, timeInfo: overrideResult.timeInfo });
|
||||
}
|
||||
|
||||
// Get a time shifted request to compare with the primary request.
|
||||
public getExtraQueries(request: DataQueryRequest): ExtraQueryDescriptor[] {
|
||||
const extraQueries: ExtraQueryDescriptor[] = [];
|
||||
const compareRange = getCompareTimeRange(request.range, this.state.compareWith);
|
||||
if (!compareRange) {
|
||||
return extraQueries;
|
||||
}
|
||||
|
||||
const targets = request.targets.filter((query: SceneDataQuery) => 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<PanelTimeRange
|
||||
// Only evaluate if the timeFrom if parent time is relative
|
||||
if (rangeUtil.isRelativeTimeRange(parentTimeRange.raw)) {
|
||||
const timeZone = this.getTimeZone();
|
||||
newTimeData.timeInfo = timeFromInfo.display;
|
||||
newTimeData.timeRange = {
|
||||
from: dateMath.parse(timeFromInfo.from, undefined, timeZone)!,
|
||||
to: dateMath.parse(timeFromInfo.to, undefined, timeZone)!,
|
||||
raw: { from: timeFromInfo.from, to: timeFromInfo.to },
|
||||
};
|
||||
infoBlocks.push(timeFromInfo.display);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,7 +149,8 @@ export class PanelTimeRange extends SceneTimeRangeTransformerBase<PanelTimeRange
|
||||
}
|
||||
|
||||
const timeShift = '-' + timeShiftInterpolated;
|
||||
newTimeData.timeInfo += ' timeshift ' + timeShift;
|
||||
infoBlocks.push('timeshift ' + timeShift);
|
||||
|
||||
const from = dateMath.parseDateMath(timeShift, newTimeData.timeRange.from, false)!;
|
||||
const to = dateMath.parseDateMath(timeShift, newTimeData.timeRange.to, true)!;
|
||||
|
||||
@@ -113,8 +162,23 @@ export class PanelTimeRange extends SceneTimeRangeTransformerBase<PanelTimeRange
|
||||
newTimeData.timeRange = { from, to, raw: { from, to } };
|
||||
}
|
||||
|
||||
if (compareWith) {
|
||||
const option = DEFAULT_COMPARE_OPTIONS.find((x) => 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<PanelTimeRange>) {
|
||||
@@ -125,10 +189,15 @@ function PanelTimeRangeRenderer({ model }: SceneComponentProps<PanelTimeRange>)
|
||||
return null;
|
||||
}
|
||||
|
||||
const onClick = config.featureToggles.panelTimeSettings ? model.onOpenSettings : undefined;
|
||||
|
||||
return (
|
||||
<Tooltip content={<TimePickerTooltip timeRange={model.state.value} timeZone={model.getTimeZone()} />}>
|
||||
<PanelChrome.TitleItem className={styles.timeshift}>
|
||||
<Icon name="clock-nine" size="sm" /> {timeInfo}
|
||||
<PanelChrome.TitleItem className={styles.timeshift} onClick={onClick}>
|
||||
<Stack gap={1} alignItems={'center'}>
|
||||
<Icon name="clock-nine" size="sm" />
|
||||
<div>{timeInfo}</div>
|
||||
</Stack>
|
||||
</PanelChrome.TitleItem>
|
||||
</Tooltip>
|
||||
);
|
||||
@@ -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<VizPanel>;
|
||||
timeFrom?: string;
|
||||
timeShift?: string;
|
||||
zoomBehavior?: PanelTimeRangeZoomBehavior;
|
||||
hideTimeOverride?: boolean;
|
||||
compareWith?: string;
|
||||
timeFromLocked?: boolean;
|
||||
}
|
||||
|
||||
export class PanelTimeRangeDrawer extends SceneObjectBase<PanelTimeRangeDrawerState> {
|
||||
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<PanelTimeRangeDrawer>) => {
|
||||
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 (
|
||||
<Drawer
|
||||
title={t('dashboard.panel.time-range-settings.title', 'Panel time range settings')}
|
||||
onClose={model.onClose}
|
||||
size="sm"
|
||||
>
|
||||
<Stack direction="column" gap={2}>
|
||||
<Field
|
||||
label={t('dashboard.panel.time-range-settings.time-from', 'Custom panel time range')}
|
||||
noMargin
|
||||
description={t(
|
||||
'dashboard.panel.time-range-settings.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'
|
||||
)}
|
||||
>
|
||||
<Stack>
|
||||
<Combobox
|
||||
options={timeOptions}
|
||||
value={timeFrom ?? ''}
|
||||
createCustomValue={true}
|
||||
onChange={(x) => {
|
||||
model.setState({ timeFrom: x.value });
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
</Field>
|
||||
<Field
|
||||
label={t('dashboard.panel.time-range-settings.time-shift', 'Time shift')}
|
||||
noMargin
|
||||
description={t(
|
||||
'dashboard.panel.time-range-settings.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'
|
||||
)}
|
||||
>
|
||||
<Combobox
|
||||
options={timeShiftOptions}
|
||||
value={timeShift ?? ''}
|
||||
createCustomValue={true}
|
||||
onChange={(x) => {
|
||||
model.setState({ timeShift: x.value });
|
||||
}}
|
||||
/>
|
||||
</Field>
|
||||
|
||||
{config.featureToggles.timeComparison && (
|
||||
<Field
|
||||
noMargin
|
||||
label={
|
||||
<Stack alignItems={'center'} justifyContent={'space-between'}>
|
||||
<Label
|
||||
description={t(
|
||||
'dashboard.panel.time-range-settings.time-window-compare-description',
|
||||
'Query and overlay data from a different time period'
|
||||
)}
|
||||
>
|
||||
<Trans i18nKey="dashboard.panel.time-range-settings.time-window-compare">
|
||||
Time window comparison
|
||||
</Trans>
|
||||
</Label>
|
||||
<FeatureBadge featureState={FeatureState.new} />
|
||||
</Stack>
|
||||
}
|
||||
>
|
||||
<Combobox
|
||||
options={DEFAULT_COMPARE_OPTIONS}
|
||||
createCustomValue={true}
|
||||
value={compareWith ?? ''}
|
||||
onChange={(x) => model.setState({ compareWith: x.value })}
|
||||
/>
|
||||
</Field>
|
||||
)}
|
||||
|
||||
<Field
|
||||
noMargin
|
||||
label={t('dashboard.panel.time-range-settings.hide-time-info', 'Hidden time info')}
|
||||
description={t(
|
||||
'dashboard.panel.time-range-settings.hide-time-info-description',
|
||||
'Do not show the custom time range in the panel header'
|
||||
)}
|
||||
>
|
||||
<Switch
|
||||
value={Boolean(hideTimeOverride)}
|
||||
onChange={(x) => model.setState({ hideTimeOverride: x.currentTarget.checked })}
|
||||
/>
|
||||
</Field>
|
||||
|
||||
<Box paddingTop={3}>
|
||||
<Stack>
|
||||
<Button variant="secondary" onClick={model.onClose}>
|
||||
<Trans i18nKey="common.cancel">Cancel</Trans>
|
||||
</Button>
|
||||
<Button variant="primary" onClick={model.onApply}>
|
||||
<Trans i18nKey="common.apply">Apply</Trans>
|
||||
</Button>
|
||||
</Stack>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Drawer>
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
+1
-36
@@ -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', () => {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
+1
-1
@@ -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';
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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' }],
|
||||
|
||||
@@ -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<Options, FieldConfig>(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'),
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user