Dashboard: Use Interval variable in DashboardScene (#75836)

Co-authored-by: Dominik Prokop <dominik.prokop@grafana.com>
This commit is contained in:
Alexa V 2023-10-16 17:34:09 +02:00 committed by GitHub
parent 157ea31b03
commit 8dfd918200
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 328 additions and 9 deletions

View File

@ -62,7 +62,7 @@ exports[`transformSceneToSaveModel Annotations should transform annotations to s
]
`;
exports[`transformSceneToSaveModel Given a scene with rows Should transform back to peristed model 1`] = `
exports[`transformSceneToSaveModel Given a scene with rows Should transform back to persisted model 1`] = `
{
"annotations": {
"list": [
@ -242,7 +242,7 @@ exports[`transformSceneToSaveModel Given a scene with rows Should transform back
}
`;
exports[`transformSceneToSaveModel Given a simple scene Should transform back to peristed model 1`] = `
exports[`transformSceneToSaveModel Given a simple scene with variables Should transform back to persisted model 1`] = `
{
"annotations": {
"list": [
@ -448,6 +448,76 @@ exports[`transformSceneToSaveModel Given a simple scene Should transform back to
"tags": [],
"templating": {
"list": [
{
"auto": true,
"auto_count": 30,
"auto_min": "10s",
"current": {
"text": "1m",
"value": "1m",
},
"hide": 2,
"name": "intervalVar",
"query": "1m,10m,30m,1h,6h,12h,1d,7d,14d,30d",
"refresh": 2,
"type": "interval",
},
{
"current": {
"text": [
"a",
],
"value": [
"a",
],
},
"includeAll": true,
"multi": true,
"name": "customVar",
"options": [],
"query": "a, b, c",
"type": "custom",
},
{
"current": {
"text": "gdev-testdata",
"value": "PD8C576611E62080A",
},
"includeAll": false,
"name": "dsVar",
"options": [],
"query": "grafana-testdata-datasource",
"refresh": 1,
"regex": "",
"type": "datasource",
},
{
"current": {
"text": "A",
"value": "A",
},
"includeAll": false,
"name": "query0",
"options": [],
"query": {
"query": "*",
"refId": "StandardVariableQuery",
},
"refresh": 1,
"regex": "",
"type": "query",
},
{
"current": {
"text": "test",
"value": "test",
},
"hide": 2,
"name": "constant",
"query": "test",
"skipUrlSync": true,
"type": "constant",
},
{
"datasource": {
"type": "prometheus",

View File

@ -1,6 +1,15 @@
import { SceneVariableSet, QueryVariable, CustomVariable, DataSourceVariable, ConstantVariable } from '@grafana/scenes';
import {
SceneVariableSet,
QueryVariable,
CustomVariable,
DataSourceVariable,
ConstantVariable,
IntervalVariable,
} from '@grafana/scenes';
import { VariableModel, VariableHide, VariableRefresh, VariableSort } from '@grafana/schema';
import { getIntervalsQueryFromNewIntervalModel } from '../utils/utils';
export function sceneVariablesSetToVariables(set: SceneVariableSet) {
const variables: VariableModel[] = [];
for (const variable of set.state.variables) {
@ -78,6 +87,22 @@ export function sceneVariablesSetToVariables(set: SceneVariableSet) {
query: variable.state.value,
hide: VariableHide.hideVariable,
});
} else if (variable instanceof IntervalVariable) {
const intervals = getIntervalsQueryFromNewIntervalModel(variable.state.intervals);
variables.push({
...commonProperties,
current: {
text: variable.state.value,
value: variable.state.value,
},
query: intervals,
hide: VariableHide.hideVariable,
refresh: variable.state.refresh,
// @ts-expect-error ?? how to fix this without adding the ts-expect-error
auto: variable.state.autoEnabled,
auto_min: variable.state.autoMinInterval,
auto_count: variable.state.autoStepCount,
});
} else {
throw new Error('Unsupported variable type');
}

View File

@ -202,6 +202,105 @@
"name": "Filters",
"skipUrlSync": false,
"type": "adhoc"
},
{
"auto": true,
"auto_count": 30,
"auto_min": "10s",
"current": {
"selected": false,
"text": "1m",
"value": "1m"
},
"hide": 0,
"name": "intervalVar",
"query": "1m,10m,30m,1h,6h,12h,1d,7d,14d,30d",
"queryValue": "",
"refresh": 2,
"skipUrlSync": false,
"type": "interval"
},
{
"current": {
"selected": true,
"text": ["a"],
"value": ["a"]
},
"hide": 0,
"includeAll": true,
"multi": true,
"name": "customVar",
"options": [
{
"selected": false,
"text": "All",
"value": "$__all"
},
{
"selected": true,
"text": "a",
"value": "a"
},
{
"selected": false,
"text": "b",
"value": "b"
},
{
"selected": false,
"text": "c",
"value": "c"
}
],
"query": "a, b, c",
"skipUrlSync": false,
"type": "custom"
},
{
"current": {
"selected": false,
"text": "gdev-testdata",
"value": "PD8C576611E62080A"
},
"hide": 0,
"includeAll": false,
"multi": false,
"name": "dsVar",
"options": [],
"query": "grafana-testdata-datasource",
"refresh": 1,
"regex": "",
"skipUrlSync": false,
"type": "datasource"
},
{
"current": {
"selected": false,
"text": "A",
"value": "A"
},
"definition": "*",
"hide": 0,
"includeAll": false,
"multi": false,
"name": "query0",
"options": [],
"query": {
"query": "*",
"refId": "StandardVariableQuery"
},
"refresh": 1,
"regex": "",
"skipUrlSync": false,
"sort": 0,
"type": "query"
},
{
"hide": 2,
"name": "constant",
"query": "test",
"skipUrlSync": false,
"type": "constant"
}
]
},

View File

@ -637,7 +637,56 @@ describe('transformSaveModelToScene', () => {
});
});
it.each(['interval', 'textbox', 'system'])('should throw for unsupported (yet) variables', (type) => {
it('should migrate interval variable', () => {
const variable = {
name: 'intervalVar',
label: 'Interval Label',
type: 'interval' as VariableType,
rootStateKey: 'N4XLmH5Vz',
auto: false,
refresh: 2,
auto_count: 30,
auto_min: '10s',
current: {
selected: true,
text: '1m',
value: '1m',
},
options: [
{
selected: true,
text: '1m',
value: '1m',
},
],
query: '1m, 5m, 15m, 30m, 1h, 6h, 12h, 1d, 7d, 14d, 30d',
id: 'intervalVar',
global: false,
index: 4,
hide: 0,
skipUrlSync: false,
state: 'Done',
error: null,
description: null,
};
const migrated = createSceneVariableFromVariableModel(variable);
const { key, ...rest } = migrated.state;
expect(rest).toEqual({
label: 'Interval Label',
autoEnabled: false,
autoMinInterval: '10s',
autoStepCount: 30,
description: null,
refresh: 2,
intervals: ['1m', '5m', '15m', '30m', '1h', '6h', '12h', '1d', '7d', '14d', '30d'],
hide: 0,
name: 'intervalVar',
skipUrlSync: false,
type: 'interval',
value: '1m',
});
});
it.each(['textbox', 'system'])('should throw for unsupported (yet) variables', (type) => {
const variable = {
name: 'query0',
type: type as VariableType,

View File

@ -3,6 +3,7 @@ import {
ConstantVariableModel,
CustomVariableModel,
DataSourceVariableModel,
IntervalVariableModel,
QueryVariableModel,
VariableModel,
} from '@grafana/data';
@ -19,6 +20,7 @@ import {
DataSourceVariable,
QueryVariable,
ConstantVariable,
IntervalVariable,
SceneRefreshPicker,
SceneGridItem,
SceneObject,
@ -44,7 +46,11 @@ import { PanelTimeRange } from '../scene/PanelTimeRange';
import { RowRepeaterBehavior } from '../scene/RowRepeaterBehavior';
import { setDashboardPanelContext } from '../scene/setDashboardPanelContext';
import { createPanelDataProvider } from '../utils/createPanelDataProvider';
import { getVizPanelKeyForPanelId } from '../utils/utils';
import {
getCurrentValueForOldIntervalModel,
getIntervalsFromOldIntervalModel,
getVizPanelKeyForPanelId,
} from '../utils/utils';
import { getAngularPanelMigrationHandler } from './angularMigration';
@ -292,6 +298,21 @@ export function createSceneVariableFromVariableModel(variable: VariableModel): S
isMulti: variable.multi,
hide: variable.hide,
});
} else if (isIntervalVariable(variable)) {
const intervals = getIntervalsFromOldIntervalModel(variable);
const currentInterval = getCurrentValueForOldIntervalModel(variable, intervals);
return new IntervalVariable({
...commonProperties,
value: currentInterval,
description: variable.description,
intervals: intervals,
autoEnabled: variable.auto,
autoStepCount: variable.auto_count,
autoMinInterval: variable.auto_min,
refresh: variable.refresh,
skipUrlSync: variable.skipUrlSync,
hide: variable.hide,
});
} else if (isConstantVariable(variable)) {
return new ConstantVariable({
...commonProperties,
@ -323,6 +344,7 @@ export function buildGridItemForLibPanel(panel: PanelModel) {
height: panel.gridPos.h,
});
}
export function buildGridItemForPanel(panel: PanelModel): SceneGridItemLike {
const vizPanelState: VizPanelState = {
key: getVizPanelKeyForPanelId(panel.id),
@ -382,4 +404,5 @@ const isCustomVariable = (v: VariableModel): v is CustomVariableModel => v.type
const isQueryVariable = (v: VariableModel): v is QueryVariableModel => v.type === 'query';
const isDataSourceVariable = (v: VariableModel): v is DataSourceVariableModel => v.type === 'datasource';
const isConstantVariable = (v: VariableModel): v is ConstantVariableModel => v.type === 'constant';
const isIntervalVariable = (v: VariableModel): v is IntervalVariableModel => v.type === 'interval';
const isAdhocVariable = (v: VariableModel): v is AdHocVariableModel => v.type === 'adhoc';

View File

@ -138,8 +138,8 @@ jest.mock('@grafana/runtime', () => ({
},
}));
describe('transformSceneToSaveModel', () => {
describe('Given a simple scene', () => {
it('Should transform back to peristed model', () => {
describe('Given a simple scene with variables', () => {
it('Should transform back to persisted model', () => {
const scene = transformSaveModelToScene({ dashboard: dashboard_to_load1 as any, meta: {} });
const saveModel = transformSceneToSaveModel(scene);
@ -148,7 +148,7 @@ describe('transformSceneToSaveModel', () => {
});
describe('Given a scene with rows', () => {
it('Should transform back to peristed model', () => {
it('Should transform back to persisted model', () => {
const scene = transformSaveModelToScene({ dashboard: repeatingRowsAndPanelsDashboardJson as any, meta: {} });
const saveModel = transformSceneToSaveModel(scene);
const row2: RowPanel = saveModel.panels![2] as RowPanel;

View File

@ -1,4 +1,4 @@
import { UrlQueryMap, urlUtil } from '@grafana/data';
import { IntervalVariableModel, UrlQueryMap, urlUtil } from '@grafana/data';
import { config, locationSearchToObject } from '@grafana/runtime';
import {
MultiValueVariable,
@ -8,6 +8,7 @@ import {
SceneQueryRunner,
VizPanel,
} from '@grafana/scenes';
import { initialIntervalVariableModelState } from 'app/features/variables/interval/reducer';
import { DashboardScene } from '../scene/DashboardScene';
@ -150,6 +151,58 @@ export function getMultiVariableValues(variable: MultiValueVariable) {
};
}
// Transform old interval model to new interval model from scenes
export function getIntervalsFromOldIntervalModel(variable: IntervalVariableModel): string[] {
// separate intervals by quotes either single or double
const matchIntervals = variable.query.match(/(["'])(.*?)\1|\w+/g);
// If no intervals are found in query, return the initial state of the interval reducer.
if (!matchIntervals) {
return initialIntervalVariableModelState.query?.split(',') ?? [];
}
const uniqueIntervals = new Set<string>();
// when options are defined in variable.query
const intervals = matchIntervals.reduce((uniqueIntervals: Set<string>, text: string) => {
// Remove surrounding quotes from the interval value.
const intervalValue = text.replace(/["']+/g, '');
// Skip intervals that start with "$__auto_interval_",scenes will handle them.
if (intervalValue.startsWith('$__auto_interval_')) {
return uniqueIntervals;
}
// Add the interval if it's not already in the Set.
uniqueIntervals.add(intervalValue);
return uniqueIntervals;
}, uniqueIntervals);
return Array.from(intervals);
}
// Transform new interval scene model to old interval core model
export function getIntervalsQueryFromNewIntervalModel(intervals: string[]): string {
const variableQuery = Array.isArray(intervals) ? intervals.join(',') : '';
return variableQuery;
}
export function getCurrentValueForOldIntervalModel(variable: IntervalVariableModel, intervals: string[]): string {
const selectedInterval = Array.isArray(variable.current.value) ? variable.current.value[0] : variable.current.value;
// If the interval is the old auto format, return the new auto interval from scenes.
if (selectedInterval.startsWith('$__auto_interval_')) {
return '$__auto';
}
// Check if the selected interval is valid.
if (intervals.includes(selectedInterval)) {
return selectedInterval;
}
// If the selected interval is not valid, return the first valid interval.
return intervals[0];
}
export function getQueryRunnerFor(sceneObject: SceneObject | undefined): SceneQueryRunner | undefined {
if (!sceneObject) {
return undefined;