mirror of
https://github.com/grafana/grafana.git
synced 2024-11-21 16:38:03 -06:00
Dashboard Scene: Fix snapshots not displaying variables values (#88967)
* Use new snapshot variables from scenes * Add snapshotVariable implementation * Refactor: Extract variables logic from transforSaveModelToScene file --------- Co-authored-by: Dominik Prokop <dominik.prokop@grafana.com>
This commit is contained in:
parent
6f63def283
commit
cd4b7ef9db
@ -294,7 +294,8 @@ lineage: schemas: [{
|
|||||||
// `textbox`: Display a free text input field with an optional default value.
|
// `textbox`: Display a free text input field with an optional default value.
|
||||||
// `custom`: Define the variable options manually using a comma-separated list.
|
// `custom`: Define the variable options manually using a comma-separated list.
|
||||||
// `system`: Variables defined by Grafana. See: https://grafana.com/docs/grafana/latest/dashboards/variables/add-template-variables/#global-variables
|
// `system`: Variables defined by Grafana. See: https://grafana.com/docs/grafana/latest/dashboards/variables/add-template-variables/#global-variables
|
||||||
#VariableType: "query" | "adhoc" | "groupby" | "constant" | "datasource" | "interval" | "textbox" | "custom" | "system" @cuetsy(kind="type") @grafanamaturity(NeedsExpertReview)
|
#VariableType: "query" | "adhoc" | "groupby" | "constant" | "datasource" | "interval" | "textbox" | "custom" |
|
||||||
|
"system" | "snapshot" @cuetsy(kind="type") @grafanamaturity(NeedsExpertReview)
|
||||||
|
|
||||||
// Color mode for a field. You can specify a single color, or select a continuous (gradient) color schemes, based on a value.
|
// Color mode for a field. You can specify a single color, or select a continuous (gradient) color schemes, based on a value.
|
||||||
// Continuous color interpolates a color using the percentage of a value relative to min and max.
|
// Continuous color interpolates a color using the percentage of a value relative to min and max.
|
||||||
|
@ -515,6 +515,7 @@ export {
|
|||||||
type UserVariableModel,
|
type UserVariableModel,
|
||||||
type SystemVariable,
|
type SystemVariable,
|
||||||
type BaseVariableModel,
|
type BaseVariableModel,
|
||||||
|
type SnapshotVariableModel,
|
||||||
} from './types/templateVars';
|
} from './types/templateVars';
|
||||||
export { type Threshold, ThresholdsMode, type ThresholdsConfig } from './types/thresholds';
|
export { type Threshold, ThresholdsMode, type ThresholdsConfig } from './types/thresholds';
|
||||||
export {
|
export {
|
||||||
|
@ -22,7 +22,8 @@ export type TypedVariableModel =
|
|||||||
| CustomVariableModel
|
| CustomVariableModel
|
||||||
| UserVariableModel
|
| UserVariableModel
|
||||||
| OrgVariableModel
|
| OrgVariableModel
|
||||||
| DashboardVariableModel;
|
| DashboardVariableModel
|
||||||
|
| SnapshotVariableModel;
|
||||||
|
|
||||||
export enum VariableRefresh {
|
export enum VariableRefresh {
|
||||||
never, // removed from the UI
|
never, // removed from the UI
|
||||||
@ -178,3 +179,8 @@ export interface BaseVariableModel {
|
|||||||
description: string | null;
|
description: string | null;
|
||||||
usedInRepeat?: boolean;
|
usedInRepeat?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SnapshotVariableModel extends VariableWithOptions {
|
||||||
|
type: 'snapshot';
|
||||||
|
query: string;
|
||||||
|
}
|
||||||
|
@ -349,7 +349,7 @@ export type DashboardLinkType = ('link' | 'dashboards');
|
|||||||
* `custom`: Define the variable options manually using a comma-separated list.
|
* `custom`: Define the variable options manually using a comma-separated list.
|
||||||
* `system`: Variables defined by Grafana. See: https://grafana.com/docs/grafana/latest/dashboards/variables/add-template-variables/#global-variables
|
* `system`: Variables defined by Grafana. See: https://grafana.com/docs/grafana/latest/dashboards/variables/add-template-variables/#global-variables
|
||||||
*/
|
*/
|
||||||
export type VariableType = ('query' | 'adhoc' | 'groupby' | 'constant' | 'datasource' | 'interval' | 'textbox' | 'custom' | 'system');
|
export type VariableType = ('query' | 'adhoc' | 'groupby' | 'constant' | 'datasource' | 'interval' | 'textbox' | 'custom' | 'system' | 'snapshot');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Color mode for a field. You can specify a single color, or select a continuous (gradient) color schemes, based on a value.
|
* Color mode for a field. You can specify a single color, or select a continuous (gradient) color schemes, based on a value.
|
||||||
|
@ -168,6 +168,7 @@ const (
|
|||||||
VariableTypeGroupby VariableType = "groupby"
|
VariableTypeGroupby VariableType = "groupby"
|
||||||
VariableTypeInterval VariableType = "interval"
|
VariableTypeInterval VariableType = "interval"
|
||||||
VariableTypeQuery VariableType = "query"
|
VariableTypeQuery VariableType = "query"
|
||||||
|
VariableTypeSnapshot VariableType = "snapshot"
|
||||||
VariableTypeSystem VariableType = "system"
|
VariableTypeSystem VariableType = "system"
|
||||||
VariableTypeTextbox VariableType = "textbox"
|
VariableTypeTextbox VariableType = "textbox"
|
||||||
)
|
)
|
||||||
|
@ -0,0 +1,33 @@
|
|||||||
|
import { SnapshotVariable } from './SnapshotVariable';
|
||||||
|
|
||||||
|
describe('SnapshotVariable', () => {
|
||||||
|
describe('SnapshotVariable state', () => {
|
||||||
|
it('should create a new snapshotVariable when custom variable is passed', () => {
|
||||||
|
const { multiVariable } = setupScene();
|
||||||
|
const snapshot = new SnapshotVariable(multiVariable);
|
||||||
|
//expect snapshot to be defined
|
||||||
|
expect(snapshot).toBeDefined();
|
||||||
|
expect(snapshot.state).toBeDefined();
|
||||||
|
expect(snapshot.state.type).toBe('snapshot');
|
||||||
|
expect(snapshot.state.isReadOnly).toBe(true);
|
||||||
|
expect(snapshot.state.value).toBe(multiVariable.value);
|
||||||
|
expect(snapshot.state.text).toBe(multiVariable.text);
|
||||||
|
expect(snapshot.state.hide).toBe(multiVariable.hide);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function setupScene() {
|
||||||
|
// create custom variable type custom
|
||||||
|
|
||||||
|
const multiVariable = {
|
||||||
|
name: 'Multi',
|
||||||
|
description: 'Define variable values manually',
|
||||||
|
text: 'myMultiText',
|
||||||
|
value: 'myMultiValue',
|
||||||
|
multi: true,
|
||||||
|
hide: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
return { multiVariable };
|
||||||
|
}
|
@ -0,0 +1,81 @@
|
|||||||
|
import { Observable, map, of } from 'rxjs';
|
||||||
|
|
||||||
|
import {
|
||||||
|
MultiValueVariable,
|
||||||
|
MultiValueVariableState,
|
||||||
|
SceneComponentProps,
|
||||||
|
ValidateAndUpdateResult,
|
||||||
|
VariableDependencyConfig,
|
||||||
|
VariableValueOption,
|
||||||
|
renderSelectForVariable,
|
||||||
|
sceneGraph,
|
||||||
|
VariableGetOptionsArgs,
|
||||||
|
} from '@grafana/scenes';
|
||||||
|
|
||||||
|
export interface SnapshotVariableState extends MultiValueVariableState {
|
||||||
|
query?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SnapshotVariable extends MultiValueVariable<SnapshotVariableState> {
|
||||||
|
protected _variableDependency = new VariableDependencyConfig(this, {
|
||||||
|
statePaths: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
public constructor(initialState: Partial<SnapshotVariableState>) {
|
||||||
|
super({
|
||||||
|
name: '',
|
||||||
|
type: 'snapshot',
|
||||||
|
isReadOnly: true,
|
||||||
|
query: '',
|
||||||
|
value: '',
|
||||||
|
text: '',
|
||||||
|
options: [],
|
||||||
|
...initialState,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public getValueOptions(args: VariableGetOptionsArgs): Observable<VariableValueOption[]> {
|
||||||
|
const interpolated = sceneGraph.interpolate(this, this.state.query);
|
||||||
|
const match = interpolated.match(/(?:\\,|[^,])+/g) ?? [];
|
||||||
|
|
||||||
|
const options = match.map((text) => {
|
||||||
|
text = text.replace(/\\,/g, ',');
|
||||||
|
const textMatch = /^(.+)\s:\s(.+)$/g.exec(text) ?? [];
|
||||||
|
if (textMatch.length === 3) {
|
||||||
|
const [, key, value] = textMatch;
|
||||||
|
return { label: key.trim(), value: value.trim() };
|
||||||
|
} else {
|
||||||
|
return { label: text.trim(), value: text.trim() };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return of(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
public validateAndUpdate(): Observable<ValidateAndUpdateResult> {
|
||||||
|
return this.getValueOptions({}).pipe(
|
||||||
|
map((options) => {
|
||||||
|
if (this.state.options !== options) {
|
||||||
|
this._updateValueGivenNewOptions(options);
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Component = ({ model }: SceneComponentProps<MultiValueVariable<SnapshotVariableState>>) => {
|
||||||
|
return renderSelectForVariable(model);
|
||||||
|
};
|
||||||
|
// we will always preserve the current value and text for snapshots
|
||||||
|
private _updateValueGivenNewOptions(options: VariableValueOption[]) {
|
||||||
|
const { value: currentValue, text: currentText } = this.state;
|
||||||
|
const stateUpdate: Partial<MultiValueVariableState> = {
|
||||||
|
options,
|
||||||
|
loading: false,
|
||||||
|
value: currentValue ?? [],
|
||||||
|
text: currentText ?? [],
|
||||||
|
};
|
||||||
|
|
||||||
|
this.setState(stateUpdate);
|
||||||
|
}
|
||||||
|
}
|
@ -1,24 +1,10 @@
|
|||||||
import {
|
import { LoadingState } from '@grafana/data';
|
||||||
LoadingState,
|
|
||||||
ConstantVariableModel,
|
|
||||||
CustomVariableModel,
|
|
||||||
DataSourceVariableModel,
|
|
||||||
QueryVariableModel,
|
|
||||||
IntervalVariableModel,
|
|
||||||
TypedVariableModel,
|
|
||||||
TextBoxVariableModel,
|
|
||||||
GroupByVariableModel,
|
|
||||||
} from '@grafana/data';
|
|
||||||
import { getPanelPlugin } from '@grafana/data/test/__mocks__/pluginMocks';
|
import { getPanelPlugin } from '@grafana/data/test/__mocks__/pluginMocks';
|
||||||
import { config } from '@grafana/runtime';
|
import { config } from '@grafana/runtime';
|
||||||
import {
|
import {
|
||||||
AdHocFiltersVariable,
|
AdHocFiltersVariable,
|
||||||
behaviors,
|
behaviors,
|
||||||
ConstantVariable,
|
ConstantVariable,
|
||||||
CustomVariable,
|
|
||||||
DataSourceVariable,
|
|
||||||
GroupByVariable,
|
|
||||||
QueryVariable,
|
|
||||||
SceneDataLayerControls,
|
SceneDataLayerControls,
|
||||||
SceneDataTransformer,
|
SceneDataTransformer,
|
||||||
SceneGridLayout,
|
SceneGridLayout,
|
||||||
@ -50,12 +36,12 @@ import { getQueryRunnerFor } from '../utils/utils';
|
|||||||
|
|
||||||
import { buildNewDashboardSaveModel } from './buildNewDashboardSaveModel';
|
import { buildNewDashboardSaveModel } from './buildNewDashboardSaveModel';
|
||||||
import { GRAFANA_DATASOURCE_REF } from './const';
|
import { GRAFANA_DATASOURCE_REF } from './const';
|
||||||
|
import { SnapshotVariable } from './custom-variables/SnapshotVariable';
|
||||||
import dashboard_to_load1 from './testfiles/dashboard_to_load1.json';
|
import dashboard_to_load1 from './testfiles/dashboard_to_load1.json';
|
||||||
import repeatingRowsAndPanelsDashboardJson from './testfiles/repeating_rows_and_panels.json';
|
import repeatingRowsAndPanelsDashboardJson from './testfiles/repeating_rows_and_panels.json';
|
||||||
import {
|
import {
|
||||||
createDashboardSceneFromDashboardModel,
|
createDashboardSceneFromDashboardModel,
|
||||||
buildGridItemForPanel,
|
buildGridItemForPanel,
|
||||||
createSceneVariableFromVariableModel,
|
|
||||||
transformSaveModelToScene,
|
transformSaveModelToScene,
|
||||||
convertOldSnapshotToScenesSnapshot,
|
convertOldSnapshotToScenesSnapshot,
|
||||||
buildGridItemForLibPanel,
|
buildGridItemForLibPanel,
|
||||||
@ -193,6 +179,113 @@ describe('transformSaveModelToScene', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('When creating a snapshot dashboard scene', () => {
|
||||||
|
it('should initialize a dashboard scene with SnapshotVariables', () => {
|
||||||
|
const customVariable = {
|
||||||
|
current: {
|
||||||
|
selected: false,
|
||||||
|
text: 'a',
|
||||||
|
value: 'a',
|
||||||
|
},
|
||||||
|
hide: 0,
|
||||||
|
includeAll: false,
|
||||||
|
multi: false,
|
||||||
|
name: 'custom0',
|
||||||
|
options: [],
|
||||||
|
query: 'a,b,c,d',
|
||||||
|
skipUrlSync: false,
|
||||||
|
type: 'custom' as VariableType,
|
||||||
|
rootStateKey: 'N4XLmH5Vz',
|
||||||
|
};
|
||||||
|
|
||||||
|
const intervalVariable = {
|
||||||
|
current: {
|
||||||
|
selected: false,
|
||||||
|
text: '10s',
|
||||||
|
value: '10s',
|
||||||
|
},
|
||||||
|
hide: 0,
|
||||||
|
includeAll: false,
|
||||||
|
multi: false,
|
||||||
|
name: 'interval0',
|
||||||
|
options: [],
|
||||||
|
query: '10s,20s,30s',
|
||||||
|
skipUrlSync: false,
|
||||||
|
type: 'interval' as VariableType,
|
||||||
|
rootStateKey: 'N4XLmH5Vz',
|
||||||
|
};
|
||||||
|
|
||||||
|
const adHocVariable = {
|
||||||
|
global: false,
|
||||||
|
name: 'CoolFilters',
|
||||||
|
label: 'CoolFilters Label',
|
||||||
|
type: 'adhoc' as VariableType,
|
||||||
|
datasource: {
|
||||||
|
uid: 'gdev-prometheus',
|
||||||
|
type: 'prometheus',
|
||||||
|
},
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
key: 'filterTest',
|
||||||
|
operator: '=',
|
||||||
|
value: 'test',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
baseFilters: [
|
||||||
|
{
|
||||||
|
key: 'baseFilterTest',
|
||||||
|
operator: '=',
|
||||||
|
value: 'test',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
hide: 0,
|
||||||
|
index: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
const snapshot = {
|
||||||
|
...defaultDashboard,
|
||||||
|
title: 'snapshot dash',
|
||||||
|
uid: 'test-uid',
|
||||||
|
time: { from: 'now-10h', to: 'now' },
|
||||||
|
weekStart: 'saturday',
|
||||||
|
fiscalYearStartMonth: 2,
|
||||||
|
timezone: 'America/New_York',
|
||||||
|
timepicker: {
|
||||||
|
...defaultTimePickerConfig,
|
||||||
|
hidden: true,
|
||||||
|
},
|
||||||
|
links: [{ ...NEW_LINK, title: 'Link 1' }],
|
||||||
|
templating: {
|
||||||
|
list: [customVariable, adHocVariable, intervalVariable],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const oldModel = new DashboardModel(snapshot, { isSnapshot: true });
|
||||||
|
const scene = createDashboardSceneFromDashboardModel(oldModel, snapshot);
|
||||||
|
|
||||||
|
// check variables were converted to snapshot variables
|
||||||
|
expect(scene.state.$variables?.state.variables).toHaveLength(3);
|
||||||
|
expect(scene.state.$variables?.getByName('custom0')).toBeInstanceOf(SnapshotVariable);
|
||||||
|
expect(scene.state.$variables?.getByName('CoolFilters')).toBeInstanceOf(AdHocFiltersVariable);
|
||||||
|
expect(scene.state.$variables?.getByName('interval0')).toBeInstanceOf(SnapshotVariable);
|
||||||
|
// custom snapshot
|
||||||
|
const customSnapshot = scene.state.$variables?.getByName('custom0') as SnapshotVariable;
|
||||||
|
expect(customSnapshot.state.value).toBe('a');
|
||||||
|
expect(customSnapshot.state.text).toBe('a');
|
||||||
|
expect(customSnapshot.state.isReadOnly).toBe(true);
|
||||||
|
// adhoc snapshot
|
||||||
|
const adhocSnapshot = scene.state.$variables?.getByName('CoolFilters') as AdHocFiltersVariable;
|
||||||
|
expect(adhocSnapshot.state.filters).toEqual(adHocVariable.filters);
|
||||||
|
expect(adhocSnapshot.state.readOnly).toBe(true);
|
||||||
|
|
||||||
|
// interval snapshot
|
||||||
|
const intervalSnapshot = scene.state.$variables?.getByName('interval0') as SnapshotVariable;
|
||||||
|
expect(intervalSnapshot.state.value).toBe('10s');
|
||||||
|
expect(intervalSnapshot.state.text).toBe('10s');
|
||||||
|
expect(intervalSnapshot.state.isReadOnly).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('when organizing panels as scene children', () => {
|
describe('when organizing panels as scene children', () => {
|
||||||
it('should create panels within collapsed rows', () => {
|
it('should create panels within collapsed rows', () => {
|
||||||
const panel = createPanelSaveModel({
|
const panel = createPanelSaveModel({
|
||||||
@ -593,647 +686,6 @@ describe('transformSaveModelToScene', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('when creating variables objects', () => {
|
|
||||||
it('should migrate custom variable', () => {
|
|
||||||
const variable: CustomVariableModel = {
|
|
||||||
current: {
|
|
||||||
selected: false,
|
|
||||||
text: 'a',
|
|
||||||
value: 'a',
|
|
||||||
},
|
|
||||||
hide: 0,
|
|
||||||
includeAll: false,
|
|
||||||
multi: false,
|
|
||||||
name: 'query0',
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
selected: true,
|
|
||||||
text: 'a',
|
|
||||||
value: 'a',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
selected: false,
|
|
||||||
text: 'b',
|
|
||||||
value: 'b',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
selected: false,
|
|
||||||
text: 'c',
|
|
||||||
value: 'c',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
selected: false,
|
|
||||||
text: 'd',
|
|
||||||
value: 'd',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
query: 'a,b,c,d',
|
|
||||||
skipUrlSync: false,
|
|
||||||
type: 'custom',
|
|
||||||
rootStateKey: 'N4XLmH5Vz',
|
|
||||||
id: 'query0',
|
|
||||||
global: false,
|
|
||||||
index: 0,
|
|
||||||
state: LoadingState.Done,
|
|
||||||
error: null,
|
|
||||||
description: null,
|
|
||||||
allValue: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
const migrated = createSceneVariableFromVariableModel(variable);
|
|
||||||
const { key, ...rest } = migrated.state;
|
|
||||||
|
|
||||||
expect(migrated).toBeInstanceOf(CustomVariable);
|
|
||||||
expect(rest).toEqual({
|
|
||||||
allValue: undefined,
|
|
||||||
defaultToAll: false,
|
|
||||||
description: null,
|
|
||||||
includeAll: false,
|
|
||||||
isMulti: false,
|
|
||||||
label: undefined,
|
|
||||||
name: 'query0',
|
|
||||||
options: [],
|
|
||||||
query: 'a,b,c,d',
|
|
||||||
skipUrlSync: false,
|
|
||||||
text: 'a',
|
|
||||||
type: 'custom',
|
|
||||||
value: 'a',
|
|
||||||
hide: 0,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should migrate query variable with definition', () => {
|
|
||||||
const variable: QueryVariableModel = {
|
|
||||||
allValue: null,
|
|
||||||
current: {
|
|
||||||
text: 'America',
|
|
||||||
value: 'America',
|
|
||||||
selected: false,
|
|
||||||
},
|
|
||||||
datasource: {
|
|
||||||
uid: 'P15396BDD62B2BE29',
|
|
||||||
type: 'influxdb',
|
|
||||||
},
|
|
||||||
definition: 'SHOW TAG VALUES WITH KEY = "datacenter"',
|
|
||||||
hide: 0,
|
|
||||||
includeAll: false,
|
|
||||||
label: 'Datacenter',
|
|
||||||
multi: false,
|
|
||||||
name: 'datacenter',
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
text: 'America',
|
|
||||||
value: 'America',
|
|
||||||
selected: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Africa',
|
|
||||||
value: 'Africa',
|
|
||||||
selected: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Asia',
|
|
||||||
value: 'Asia',
|
|
||||||
selected: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Europe',
|
|
||||||
value: 'Europe',
|
|
||||||
selected: false,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
query: 'SHOW TAG VALUES WITH KEY = "datacenter" ',
|
|
||||||
refresh: 1,
|
|
||||||
regex: '',
|
|
||||||
skipUrlSync: false,
|
|
||||||
sort: 0,
|
|
||||||
type: 'query',
|
|
||||||
rootStateKey: '000000002',
|
|
||||||
id: 'datacenter',
|
|
||||||
global: false,
|
|
||||||
index: 0,
|
|
||||||
state: LoadingState.Done,
|
|
||||||
error: null,
|
|
||||||
description: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
const migrated = createSceneVariableFromVariableModel(variable);
|
|
||||||
const { key, ...rest } = migrated.state;
|
|
||||||
|
|
||||||
expect(migrated).toBeInstanceOf(QueryVariable);
|
|
||||||
expect(rest).toEqual({
|
|
||||||
allValue: undefined,
|
|
||||||
datasource: {
|
|
||||||
type: 'influxdb',
|
|
||||||
uid: 'P15396BDD62B2BE29',
|
|
||||||
},
|
|
||||||
defaultToAll: false,
|
|
||||||
description: null,
|
|
||||||
includeAll: false,
|
|
||||||
isMulti: false,
|
|
||||||
label: 'Datacenter',
|
|
||||||
name: 'datacenter',
|
|
||||||
options: [],
|
|
||||||
query: 'SHOW TAG VALUES WITH KEY = "datacenter" ',
|
|
||||||
refresh: 1,
|
|
||||||
regex: '',
|
|
||||||
skipUrlSync: false,
|
|
||||||
sort: 0,
|
|
||||||
text: 'America',
|
|
||||||
type: 'query',
|
|
||||||
value: 'America',
|
|
||||||
hide: 0,
|
|
||||||
definition: 'SHOW TAG VALUES WITH KEY = "datacenter"',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should migrate datasource variable', () => {
|
|
||||||
const variable: DataSourceVariableModel = {
|
|
||||||
id: 'query1',
|
|
||||||
rootStateKey: 'N4XLmH5Vz',
|
|
||||||
name: 'query1',
|
|
||||||
type: 'datasource',
|
|
||||||
global: false,
|
|
||||||
index: 1,
|
|
||||||
hide: 0,
|
|
||||||
skipUrlSync: false,
|
|
||||||
state: LoadingState.Done,
|
|
||||||
error: null,
|
|
||||||
description: null,
|
|
||||||
current: {
|
|
||||||
value: ['gdev-prometheus', 'gdev-slow-prometheus'],
|
|
||||||
text: ['gdev-prometheus', 'gdev-slow-prometheus'],
|
|
||||||
selected: true,
|
|
||||||
},
|
|
||||||
regex: '/^gdev/',
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
text: 'All',
|
|
||||||
value: '$__all',
|
|
||||||
selected: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'gdev-prometheus',
|
|
||||||
value: 'gdev-prometheus',
|
|
||||||
selected: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'gdev-slow-prometheus',
|
|
||||||
value: 'gdev-slow-prometheus',
|
|
||||||
selected: false,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
query: 'prometheus',
|
|
||||||
multi: true,
|
|
||||||
includeAll: true,
|
|
||||||
refresh: 1,
|
|
||||||
allValue: 'Custom all',
|
|
||||||
};
|
|
||||||
|
|
||||||
const migrated = createSceneVariableFromVariableModel(variable);
|
|
||||||
const { key, ...rest } = migrated.state;
|
|
||||||
|
|
||||||
expect(migrated).toBeInstanceOf(DataSourceVariable);
|
|
||||||
expect(rest).toEqual({
|
|
||||||
allValue: 'Custom all',
|
|
||||||
defaultToAll: true,
|
|
||||||
includeAll: true,
|
|
||||||
label: undefined,
|
|
||||||
name: 'query1',
|
|
||||||
options: [],
|
|
||||||
pluginId: 'prometheus',
|
|
||||||
regex: '/^gdev/',
|
|
||||||
skipUrlSync: false,
|
|
||||||
text: ['gdev-prometheus', 'gdev-slow-prometheus'],
|
|
||||||
type: 'datasource',
|
|
||||||
value: ['gdev-prometheus', 'gdev-slow-prometheus'],
|
|
||||||
isMulti: true,
|
|
||||||
description: null,
|
|
||||||
hide: 0,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should migrate constant variable', () => {
|
|
||||||
const variable: ConstantVariableModel = {
|
|
||||||
hide: 2,
|
|
||||||
label: 'constant',
|
|
||||||
name: 'constant',
|
|
||||||
skipUrlSync: false,
|
|
||||||
type: 'constant',
|
|
||||||
rootStateKey: 'N4XLmH5Vz',
|
|
||||||
current: {
|
|
||||||
selected: true,
|
|
||||||
text: 'test',
|
|
||||||
value: 'test',
|
|
||||||
},
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
selected: true,
|
|
||||||
text: 'test',
|
|
||||||
value: 'test',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
query: 'test',
|
|
||||||
id: 'constant',
|
|
||||||
global: false,
|
|
||||||
index: 3,
|
|
||||||
state: LoadingState.Done,
|
|
||||||
error: null,
|
|
||||||
description: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
const migrated = createSceneVariableFromVariableModel(variable);
|
|
||||||
const { key, ...rest } = migrated.state;
|
|
||||||
|
|
||||||
expect(rest).toEqual({
|
|
||||||
description: null,
|
|
||||||
hide: 2,
|
|
||||||
label: 'constant',
|
|
||||||
name: 'constant',
|
|
||||||
skipUrlSync: true,
|
|
||||||
type: 'constant',
|
|
||||||
value: 'test',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should migrate interval variable', () => {
|
|
||||||
const variable: IntervalVariableModel = {
|
|
||||||
name: 'intervalVar',
|
|
||||||
label: 'Interval Label',
|
|
||||||
type: 'interval',
|
|
||||||
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: LoadingState.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('should migrate textbox variable', () => {
|
|
||||||
const variable: TextBoxVariableModel = {
|
|
||||||
id: 'query0',
|
|
||||||
global: false,
|
|
||||||
index: 0,
|
|
||||||
state: LoadingState.Done,
|
|
||||||
error: null,
|
|
||||||
name: 'textboxVar',
|
|
||||||
label: 'Textbox Label',
|
|
||||||
description: 'Textbox Description',
|
|
||||||
type: 'textbox',
|
|
||||||
rootStateKey: 'N4XLmH5Vz',
|
|
||||||
current: {},
|
|
||||||
hide: 0,
|
|
||||||
options: [],
|
|
||||||
query: 'defaultValue',
|
|
||||||
originalQuery: 'defaultValue',
|
|
||||||
skipUrlSync: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
const migrated = createSceneVariableFromVariableModel(variable);
|
|
||||||
const { key, ...rest } = migrated.state;
|
|
||||||
expect(rest).toEqual({
|
|
||||||
description: 'Textbox Description',
|
|
||||||
hide: 0,
|
|
||||||
label: 'Textbox Label',
|
|
||||||
name: 'textboxVar',
|
|
||||||
skipUrlSync: false,
|
|
||||||
type: 'textbox',
|
|
||||||
value: 'defaultValue',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should migrate adhoc variable', () => {
|
|
||||||
const variable: TypedVariableModel = {
|
|
||||||
id: 'adhoc',
|
|
||||||
global: false,
|
|
||||||
index: 0,
|
|
||||||
state: LoadingState.Done,
|
|
||||||
error: null,
|
|
||||||
name: 'adhoc',
|
|
||||||
label: 'Adhoc Label',
|
|
||||||
description: 'Adhoc Description',
|
|
||||||
type: 'adhoc',
|
|
||||||
rootStateKey: 'N4XLmH5Vz',
|
|
||||||
datasource: {
|
|
||||||
uid: 'gdev-prometheus',
|
|
||||||
type: 'prometheus',
|
|
||||||
},
|
|
||||||
filters: [
|
|
||||||
{
|
|
||||||
key: 'filterTest',
|
|
||||||
operator: '=',
|
|
||||||
value: 'test',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
baseFilters: [
|
|
||||||
{
|
|
||||||
key: 'baseFilterTest',
|
|
||||||
operator: '=',
|
|
||||||
value: 'test',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
hide: 0,
|
|
||||||
skipUrlSync: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
const migrated = createSceneVariableFromVariableModel(variable) as AdHocFiltersVariable;
|
|
||||||
const filterVarState = migrated.state;
|
|
||||||
|
|
||||||
expect(migrated).toBeInstanceOf(AdHocFiltersVariable);
|
|
||||||
expect(filterVarState).toEqual({
|
|
||||||
key: expect.any(String),
|
|
||||||
description: 'Adhoc Description',
|
|
||||||
hide: 0,
|
|
||||||
label: 'Adhoc Label',
|
|
||||||
name: 'adhoc',
|
|
||||||
skipUrlSync: false,
|
|
||||||
type: 'adhoc',
|
|
||||||
filterExpression: 'filterTest="test"',
|
|
||||||
filters: [{ key: 'filterTest', operator: '=', value: 'test' }],
|
|
||||||
baseFilters: [{ key: 'baseFilterTest', operator: '=', value: 'test' }],
|
|
||||||
datasource: { uid: 'gdev-prometheus', type: 'prometheus' },
|
|
||||||
applyMode: 'auto',
|
|
||||||
useQueriesAsFilterForOptions: true,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should migrate adhoc variable with default keys', () => {
|
|
||||||
const variable: TypedVariableModel = {
|
|
||||||
id: 'adhoc',
|
|
||||||
global: false,
|
|
||||||
index: 0,
|
|
||||||
state: LoadingState.Done,
|
|
||||||
error: null,
|
|
||||||
name: 'adhoc',
|
|
||||||
label: 'Adhoc Label',
|
|
||||||
description: 'Adhoc Description',
|
|
||||||
type: 'adhoc',
|
|
||||||
rootStateKey: 'N4XLmH5Vz',
|
|
||||||
datasource: {
|
|
||||||
uid: 'gdev-prometheus',
|
|
||||||
type: 'prometheus',
|
|
||||||
},
|
|
||||||
filters: [
|
|
||||||
{
|
|
||||||
key: 'filterTest',
|
|
||||||
operator: '=',
|
|
||||||
value: 'test',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
baseFilters: [
|
|
||||||
{
|
|
||||||
key: 'baseFilterTest',
|
|
||||||
operator: '=',
|
|
||||||
value: 'test',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
defaultKeys: [
|
|
||||||
{
|
|
||||||
text: 'some',
|
|
||||||
value: '1',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'static',
|
|
||||||
value: '2',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'keys',
|
|
||||||
value: '3',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
hide: 0,
|
|
||||||
skipUrlSync: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
const migrated = createSceneVariableFromVariableModel(variable) as AdHocFiltersVariable;
|
|
||||||
const filterVarState = migrated.state;
|
|
||||||
|
|
||||||
expect(migrated).toBeInstanceOf(AdHocFiltersVariable);
|
|
||||||
expect(filterVarState).toEqual({
|
|
||||||
key: expect.any(String),
|
|
||||||
description: 'Adhoc Description',
|
|
||||||
hide: 0,
|
|
||||||
label: 'Adhoc Label',
|
|
||||||
name: 'adhoc',
|
|
||||||
skipUrlSync: false,
|
|
||||||
type: 'adhoc',
|
|
||||||
filterExpression: 'filterTest="test"',
|
|
||||||
filters: [{ key: 'filterTest', operator: '=', value: 'test' }],
|
|
||||||
baseFilters: [{ key: 'baseFilterTest', operator: '=', value: 'test' }],
|
|
||||||
datasource: { uid: 'gdev-prometheus', type: 'prometheus' },
|
|
||||||
applyMode: 'auto',
|
|
||||||
defaultKeys: [
|
|
||||||
{
|
|
||||||
text: 'some',
|
|
||||||
value: '1',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'static',
|
|
||||||
value: '2',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'keys',
|
|
||||||
value: '3',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
useQueriesAsFilterForOptions: true,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('when groupByVariable feature toggle is enabled', () => {
|
|
||||||
beforeAll(() => {
|
|
||||||
config.featureToggles.groupByVariable = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
afterAll(() => {
|
|
||||||
config.featureToggles.groupByVariable = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should migrate groupby variable', () => {
|
|
||||||
const variable: GroupByVariableModel = {
|
|
||||||
id: 'groupby',
|
|
||||||
global: false,
|
|
||||||
index: 0,
|
|
||||||
state: LoadingState.Done,
|
|
||||||
error: null,
|
|
||||||
name: 'groupby',
|
|
||||||
label: 'GroupBy Label',
|
|
||||||
description: 'GroupBy Description',
|
|
||||||
type: 'groupby',
|
|
||||||
rootStateKey: 'N4XLmH5Vz',
|
|
||||||
datasource: {
|
|
||||||
uid: 'gdev-prometheus',
|
|
||||||
type: 'prometheus',
|
|
||||||
},
|
|
||||||
multi: true,
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
selected: false,
|
|
||||||
text: 'Foo',
|
|
||||||
value: 'foo',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
selected: false,
|
|
||||||
text: 'Bar',
|
|
||||||
value: 'bar',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
current: {},
|
|
||||||
query: '',
|
|
||||||
hide: 0,
|
|
||||||
skipUrlSync: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
const migrated = createSceneVariableFromVariableModel(variable) as GroupByVariable;
|
|
||||||
const groupbyVarState = migrated.state;
|
|
||||||
|
|
||||||
expect(migrated).toBeInstanceOf(GroupByVariable);
|
|
||||||
expect(groupbyVarState).toEqual({
|
|
||||||
key: expect.any(String),
|
|
||||||
description: 'GroupBy Description',
|
|
||||||
hide: 0,
|
|
||||||
defaultOptions: [
|
|
||||||
{
|
|
||||||
selected: false,
|
|
||||||
text: 'Foo',
|
|
||||||
value: 'foo',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
selected: false,
|
|
||||||
text: 'Bar',
|
|
||||||
value: 'bar',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
isMulti: true,
|
|
||||||
layout: 'horizontal',
|
|
||||||
noValueOnClear: true,
|
|
||||||
label: 'GroupBy Label',
|
|
||||||
name: 'groupby',
|
|
||||||
skipUrlSync: false,
|
|
||||||
type: 'groupby',
|
|
||||||
baseFilters: [],
|
|
||||||
options: [],
|
|
||||||
text: [],
|
|
||||||
value: [],
|
|
||||||
datasource: { uid: 'gdev-prometheus', type: 'prometheus' },
|
|
||||||
applyMode: 'auto',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('when groupByVariable feature toggle is disabled', () => {
|
|
||||||
it('should not migrate groupby variable and throw an error instead', () => {
|
|
||||||
const variable: GroupByVariableModel = {
|
|
||||||
id: 'groupby',
|
|
||||||
global: false,
|
|
||||||
index: 0,
|
|
||||||
state: LoadingState.Done,
|
|
||||||
error: null,
|
|
||||||
name: 'groupby',
|
|
||||||
label: 'GroupBy Label',
|
|
||||||
description: 'GroupBy Description',
|
|
||||||
type: 'groupby',
|
|
||||||
rootStateKey: 'N4XLmH5Vz',
|
|
||||||
datasource: {
|
|
||||||
uid: 'gdev-prometheus',
|
|
||||||
type: 'prometheus',
|
|
||||||
},
|
|
||||||
multi: true,
|
|
||||||
options: [],
|
|
||||||
current: {},
|
|
||||||
query: '',
|
|
||||||
hide: 0,
|
|
||||||
skipUrlSync: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(() => createSceneVariableFromVariableModel(variable)).toThrow('Scenes: Unsupported variable type');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it.each(['system'])('should throw for unsupported (yet) variables', (type) => {
|
|
||||||
const variable = {
|
|
||||||
name: 'query0',
|
|
||||||
type: type as VariableType,
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(() => createSceneVariableFromVariableModel(variable as TypedVariableModel)).toThrow();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle variable without current', () => {
|
|
||||||
// @ts-expect-error
|
|
||||||
const variable: TypedVariableModel = {
|
|
||||||
id: 'query1',
|
|
||||||
name: 'query1',
|
|
||||||
type: 'datasource',
|
|
||||||
global: false,
|
|
||||||
regex: '/^gdev/',
|
|
||||||
options: [],
|
|
||||||
query: 'prometheus',
|
|
||||||
multi: true,
|
|
||||||
includeAll: true,
|
|
||||||
refresh: 1,
|
|
||||||
allValue: 'Custom all',
|
|
||||||
};
|
|
||||||
|
|
||||||
const migrated = createSceneVariableFromVariableModel(variable);
|
|
||||||
const { key, ...rest } = migrated.state;
|
|
||||||
|
|
||||||
expect(migrated).toBeInstanceOf(DataSourceVariable);
|
|
||||||
expect(rest).toEqual({
|
|
||||||
allValue: 'Custom all',
|
|
||||||
defaultToAll: true,
|
|
||||||
includeAll: true,
|
|
||||||
label: undefined,
|
|
||||||
name: 'query1',
|
|
||||||
options: [],
|
|
||||||
pluginId: 'prometheus',
|
|
||||||
regex: '/^gdev/',
|
|
||||||
text: '',
|
|
||||||
type: 'datasource',
|
|
||||||
value: '',
|
|
||||||
isMulti: true,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Repeating rows', () => {
|
describe('Repeating rows', () => {
|
||||||
it('Should build correct scene model', () => {
|
it('Should build correct scene model', () => {
|
||||||
const scene = transformSaveModelToScene({ dashboard: repeatingRowsAndPanelsDashboardJson as any, meta: {} });
|
const scene = transformSaveModelToScene({ dashboard: repeatingRowsAndPanelsDashboardJson as any, meta: {} });
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { uniqueId } from 'lodash';
|
import { uniqueId } from 'lodash';
|
||||||
|
|
||||||
import { DataFrameDTO, DataFrameJSON, TypedVariableModel } from '@grafana/data';
|
import { DataFrameDTO, DataFrameJSON } from '@grafana/data';
|
||||||
import { config } from '@grafana/runtime';
|
import { config } from '@grafana/runtime';
|
||||||
import {
|
import {
|
||||||
VizPanel,
|
VizPanel,
|
||||||
@ -10,12 +10,6 @@ import {
|
|||||||
SceneTimeRange,
|
SceneTimeRange,
|
||||||
SceneVariableSet,
|
SceneVariableSet,
|
||||||
VariableValueSelectors,
|
VariableValueSelectors,
|
||||||
SceneVariable,
|
|
||||||
CustomVariable,
|
|
||||||
DataSourceVariable,
|
|
||||||
QueryVariable,
|
|
||||||
ConstantVariable,
|
|
||||||
IntervalVariable,
|
|
||||||
SceneRefreshPicker,
|
SceneRefreshPicker,
|
||||||
SceneObject,
|
SceneObject,
|
||||||
VizPanelMenu,
|
VizPanelMenu,
|
||||||
@ -24,10 +18,7 @@ import {
|
|||||||
SceneGridItemLike,
|
SceneGridItemLike,
|
||||||
SceneDataLayerProvider,
|
SceneDataLayerProvider,
|
||||||
SceneDataLayerControls,
|
SceneDataLayerControls,
|
||||||
TextBoxVariable,
|
|
||||||
UserActionEvent,
|
UserActionEvent,
|
||||||
GroupByVariable,
|
|
||||||
AdHocFiltersVariable,
|
|
||||||
sceneGraph,
|
sceneGraph,
|
||||||
} from '@grafana/scenes';
|
} from '@grafana/scenes';
|
||||||
import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
|
import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
|
||||||
@ -52,12 +43,8 @@ import { setDashboardPanelContext } from '../scene/setDashboardPanelContext';
|
|||||||
import { createPanelDataProvider } from '../utils/createPanelDataProvider';
|
import { createPanelDataProvider } from '../utils/createPanelDataProvider';
|
||||||
import { preserveDashboardSceneStateInLocalStorage } from '../utils/dashboardSessionState';
|
import { preserveDashboardSceneStateInLocalStorage } from '../utils/dashboardSessionState';
|
||||||
import { DashboardInteractions } from '../utils/interactions';
|
import { DashboardInteractions } from '../utils/interactions';
|
||||||
import {
|
import { getDashboardSceneFor, getVizPanelKeyForPanelId } from '../utils/utils';
|
||||||
getCurrentValueForOldIntervalModel,
|
import { createVariablesForDashboard, createVariablesForSnapshot } from '../utils/variables';
|
||||||
getDashboardSceneFor,
|
|
||||||
getIntervalsFromQueryString,
|
|
||||||
getVizPanelKeyForPanelId,
|
|
||||||
} from '../utils/utils';
|
|
||||||
|
|
||||||
import { getAngularPanelMigrationHandler } from './angularMigration';
|
import { getAngularPanelMigrationHandler } from './angularMigration';
|
||||||
import { GRAFANA_DATASOURCE_REF } from './const';
|
import { GRAFANA_DATASOURCE_REF } from './const';
|
||||||
@ -198,22 +185,11 @@ export function createDashboardSceneFromDashboardModel(oldModel: DashboardModel,
|
|||||||
let alertStatesLayer: AlertStatesDataLayer | undefined;
|
let alertStatesLayer: AlertStatesDataLayer | undefined;
|
||||||
|
|
||||||
if (oldModel.templating?.list?.length) {
|
if (oldModel.templating?.list?.length) {
|
||||||
const variableObjects = oldModel.templating.list
|
if (oldModel.meta.isSnapshot) {
|
||||||
.map((v) => {
|
variables = createVariablesForSnapshot(oldModel);
|
||||||
try {
|
} else {
|
||||||
return createSceneVariableFromVariableModel(v);
|
variables = createVariablesForDashboard(oldModel);
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
})
|
|
||||||
// TODO: Remove filter
|
|
||||||
// Added temporarily to allow skipping non-compatible variables
|
|
||||||
.filter((v): v is SceneVariable => Boolean(v));
|
|
||||||
|
|
||||||
variables = new SceneVariableSet({
|
|
||||||
variables: variableObjects,
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
// Create empty variable set
|
// Create empty variable set
|
||||||
variables = new SceneVariableSet({
|
variables = new SceneVariableSet({
|
||||||
@ -303,128 +279,6 @@ export function createDashboardSceneFromDashboardModel(oldModel: DashboardModel,
|
|||||||
return dashboardScene;
|
return dashboardScene;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createSceneVariableFromVariableModel(variable: TypedVariableModel): SceneVariable {
|
|
||||||
const commonProperties = {
|
|
||||||
name: variable.name,
|
|
||||||
label: variable.label,
|
|
||||||
description: variable.description,
|
|
||||||
};
|
|
||||||
if (variable.type === 'adhoc') {
|
|
||||||
return new AdHocFiltersVariable({
|
|
||||||
...commonProperties,
|
|
||||||
description: variable.description,
|
|
||||||
skipUrlSync: variable.skipUrlSync,
|
|
||||||
hide: variable.hide,
|
|
||||||
datasource: variable.datasource,
|
|
||||||
applyMode: 'auto',
|
|
||||||
filters: variable.filters ?? [],
|
|
||||||
baseFilters: variable.baseFilters ?? [],
|
|
||||||
defaultKeys: variable.defaultKeys,
|
|
||||||
useQueriesAsFilterForOptions: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (variable.type === 'custom') {
|
|
||||||
return new CustomVariable({
|
|
||||||
...commonProperties,
|
|
||||||
value: variable.current?.value ?? '',
|
|
||||||
text: variable.current?.text ?? '',
|
|
||||||
|
|
||||||
query: variable.query,
|
|
||||||
isMulti: variable.multi,
|
|
||||||
allValue: variable.allValue || undefined,
|
|
||||||
includeAll: variable.includeAll,
|
|
||||||
defaultToAll: Boolean(variable.includeAll),
|
|
||||||
skipUrlSync: variable.skipUrlSync,
|
|
||||||
hide: variable.hide,
|
|
||||||
});
|
|
||||||
} else if (variable.type === 'query') {
|
|
||||||
return new QueryVariable({
|
|
||||||
...commonProperties,
|
|
||||||
value: variable.current?.value ?? '',
|
|
||||||
text: variable.current?.text ?? '',
|
|
||||||
|
|
||||||
query: variable.query,
|
|
||||||
datasource: variable.datasource,
|
|
||||||
sort: variable.sort,
|
|
||||||
refresh: variable.refresh,
|
|
||||||
regex: variable.regex,
|
|
||||||
allValue: variable.allValue || undefined,
|
|
||||||
includeAll: variable.includeAll,
|
|
||||||
defaultToAll: Boolean(variable.includeAll),
|
|
||||||
isMulti: variable.multi,
|
|
||||||
skipUrlSync: variable.skipUrlSync,
|
|
||||||
hide: variable.hide,
|
|
||||||
definition: variable.definition,
|
|
||||||
});
|
|
||||||
} else if (variable.type === 'datasource') {
|
|
||||||
return new DataSourceVariable({
|
|
||||||
...commonProperties,
|
|
||||||
value: variable.current?.value ?? '',
|
|
||||||
text: variable.current?.text ?? '',
|
|
||||||
regex: variable.regex,
|
|
||||||
pluginId: variable.query,
|
|
||||||
allValue: variable.allValue || undefined,
|
|
||||||
includeAll: variable.includeAll,
|
|
||||||
defaultToAll: Boolean(variable.includeAll),
|
|
||||||
skipUrlSync: variable.skipUrlSync,
|
|
||||||
isMulti: variable.multi,
|
|
||||||
hide: variable.hide,
|
|
||||||
});
|
|
||||||
} else if (variable.type === 'interval') {
|
|
||||||
const intervals = getIntervalsFromQueryString(variable.query);
|
|
||||||
const currentInterval = getCurrentValueForOldIntervalModel(variable, intervals);
|
|
||||||
return new IntervalVariable({
|
|
||||||
...commonProperties,
|
|
||||||
value: currentInterval,
|
|
||||||
intervals: intervals,
|
|
||||||
autoEnabled: variable.auto,
|
|
||||||
autoStepCount: variable.auto_count,
|
|
||||||
autoMinInterval: variable.auto_min,
|
|
||||||
refresh: variable.refresh,
|
|
||||||
skipUrlSync: variable.skipUrlSync,
|
|
||||||
hide: variable.hide,
|
|
||||||
});
|
|
||||||
} else if (variable.type === 'constant') {
|
|
||||||
return new ConstantVariable({
|
|
||||||
...commonProperties,
|
|
||||||
value: variable.query,
|
|
||||||
skipUrlSync: variable.skipUrlSync,
|
|
||||||
hide: variable.hide,
|
|
||||||
});
|
|
||||||
} else if (variable.type === 'textbox') {
|
|
||||||
let val;
|
|
||||||
if (!variable?.current?.value) {
|
|
||||||
val = variable.query;
|
|
||||||
} else {
|
|
||||||
if (typeof variable.current.value === 'string') {
|
|
||||||
val = variable.current.value;
|
|
||||||
} else {
|
|
||||||
val = variable.current.value[0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new TextBoxVariable({
|
|
||||||
...commonProperties,
|
|
||||||
value: val,
|
|
||||||
skipUrlSync: variable.skipUrlSync,
|
|
||||||
hide: variable.hide,
|
|
||||||
});
|
|
||||||
} else if (config.featureToggles.groupByVariable && variable.type === 'groupby') {
|
|
||||||
return new GroupByVariable({
|
|
||||||
...commonProperties,
|
|
||||||
datasource: variable.datasource,
|
|
||||||
value: variable.current?.value || [],
|
|
||||||
text: variable.current?.text || [],
|
|
||||||
skipUrlSync: variable.skipUrlSync,
|
|
||||||
hide: variable.hide,
|
|
||||||
// @ts-expect-error
|
|
||||||
defaultOptions: variable.options,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
throw new Error(`Scenes: Unsupported variable type ${variable.type}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function buildGridItemForLibPanel(panel: PanelModel) {
|
export function buildGridItemForLibPanel(panel: PanelModel) {
|
||||||
if (!panel.libraryPanel) {
|
if (!panel.libraryPanel) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -36,7 +36,8 @@ interface EditableVariableConfig {
|
|||||||
editor: React.ComponentType<any>;
|
editor: React.ComponentType<any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type EditableVariableType = Exclude<VariableType, 'system'>;
|
//exclude system variable type and snapshot variable type
|
||||||
|
export type EditableVariableType = Exclude<VariableType, 'system' | 'snapshot'>;
|
||||||
|
|
||||||
export function isEditableVariableType(type: VariableType): type is EditableVariableType {
|
export function isEditableVariableType(type: VariableType): type is EditableVariableType {
|
||||||
return type !== 'system';
|
return type !== 'system';
|
||||||
|
775
public/app/features/dashboard-scene/utils/variables.test.ts
Normal file
775
public/app/features/dashboard-scene/utils/variables.test.ts
Normal file
@ -0,0 +1,775 @@
|
|||||||
|
import {
|
||||||
|
ConstantVariableModel,
|
||||||
|
CustomVariableModel,
|
||||||
|
DataSourceVariableModel,
|
||||||
|
GroupByVariableModel,
|
||||||
|
IntervalVariableModel,
|
||||||
|
LoadingState,
|
||||||
|
QueryVariableModel,
|
||||||
|
TextBoxVariableModel,
|
||||||
|
TypedVariableModel,
|
||||||
|
} from '@grafana/data';
|
||||||
|
import { config } from '@grafana/runtime';
|
||||||
|
import {
|
||||||
|
AdHocFiltersVariable,
|
||||||
|
CustomVariable,
|
||||||
|
DataSourceVariable,
|
||||||
|
GroupByVariable,
|
||||||
|
QueryVariable,
|
||||||
|
SceneVariableSet,
|
||||||
|
} from '@grafana/scenes';
|
||||||
|
import { defaultDashboard, defaultTimePickerConfig, VariableType } from '@grafana/schema';
|
||||||
|
import { DashboardModel } from 'app/features/dashboard/state';
|
||||||
|
|
||||||
|
import { SnapshotVariable } from '../serialization/custom-variables/SnapshotVariable';
|
||||||
|
import { NEW_LINK } from '../settings/links/utils';
|
||||||
|
|
||||||
|
import { createSceneVariableFromVariableModel, createVariablesForSnapshot } from './variables';
|
||||||
|
|
||||||
|
describe('when creating variables objects', () => {
|
||||||
|
it('should migrate custom variable', () => {
|
||||||
|
const variable: CustomVariableModel = {
|
||||||
|
current: {
|
||||||
|
selected: false,
|
||||||
|
text: 'a',
|
||||||
|
value: 'a',
|
||||||
|
},
|
||||||
|
hide: 0,
|
||||||
|
includeAll: false,
|
||||||
|
multi: false,
|
||||||
|
name: 'query0',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
selected: true,
|
||||||
|
text: 'a',
|
||||||
|
value: 'a',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
selected: false,
|
||||||
|
text: 'b',
|
||||||
|
value: 'b',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
selected: false,
|
||||||
|
text: 'c',
|
||||||
|
value: 'c',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
selected: false,
|
||||||
|
text: 'd',
|
||||||
|
value: 'd',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
query: 'a,b,c,d',
|
||||||
|
skipUrlSync: false,
|
||||||
|
type: 'custom',
|
||||||
|
rootStateKey: 'N4XLmH5Vz',
|
||||||
|
id: 'query0',
|
||||||
|
global: false,
|
||||||
|
index: 0,
|
||||||
|
state: LoadingState.Done,
|
||||||
|
error: null,
|
||||||
|
description: null,
|
||||||
|
allValue: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
const migrated = createSceneVariableFromVariableModel(variable);
|
||||||
|
const { key, ...rest } = migrated.state;
|
||||||
|
|
||||||
|
expect(migrated).toBeInstanceOf(CustomVariable);
|
||||||
|
expect(rest).toEqual({
|
||||||
|
allValue: undefined,
|
||||||
|
defaultToAll: false,
|
||||||
|
description: null,
|
||||||
|
includeAll: false,
|
||||||
|
isMulti: false,
|
||||||
|
label: undefined,
|
||||||
|
name: 'query0',
|
||||||
|
options: [],
|
||||||
|
query: 'a,b,c,d',
|
||||||
|
skipUrlSync: false,
|
||||||
|
text: 'a',
|
||||||
|
type: 'custom',
|
||||||
|
value: 'a',
|
||||||
|
hide: 0,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should migrate query variable with definition', () => {
|
||||||
|
const variable: QueryVariableModel = {
|
||||||
|
allValue: null,
|
||||||
|
current: {
|
||||||
|
text: 'America',
|
||||||
|
value: 'America',
|
||||||
|
selected: false,
|
||||||
|
},
|
||||||
|
datasource: {
|
||||||
|
uid: 'P15396BDD62B2BE29',
|
||||||
|
type: 'influxdb',
|
||||||
|
},
|
||||||
|
definition: 'SHOW TAG VALUES WITH KEY = "datacenter"',
|
||||||
|
hide: 0,
|
||||||
|
includeAll: false,
|
||||||
|
label: 'Datacenter',
|
||||||
|
multi: false,
|
||||||
|
name: 'datacenter',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
text: 'America',
|
||||||
|
value: 'America',
|
||||||
|
selected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Africa',
|
||||||
|
value: 'Africa',
|
||||||
|
selected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Asia',
|
||||||
|
value: 'Asia',
|
||||||
|
selected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Europe',
|
||||||
|
value: 'Europe',
|
||||||
|
selected: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
query: 'SHOW TAG VALUES WITH KEY = "datacenter" ',
|
||||||
|
refresh: 1,
|
||||||
|
regex: '',
|
||||||
|
skipUrlSync: false,
|
||||||
|
sort: 0,
|
||||||
|
type: 'query',
|
||||||
|
rootStateKey: '000000002',
|
||||||
|
id: 'datacenter',
|
||||||
|
global: false,
|
||||||
|
index: 0,
|
||||||
|
state: LoadingState.Done,
|
||||||
|
error: null,
|
||||||
|
description: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
const migrated = createSceneVariableFromVariableModel(variable);
|
||||||
|
const { key, ...rest } = migrated.state;
|
||||||
|
|
||||||
|
expect(migrated).toBeInstanceOf(QueryVariable);
|
||||||
|
expect(rest).toEqual({
|
||||||
|
allValue: undefined,
|
||||||
|
datasource: {
|
||||||
|
type: 'influxdb',
|
||||||
|
uid: 'P15396BDD62B2BE29',
|
||||||
|
},
|
||||||
|
defaultToAll: false,
|
||||||
|
description: null,
|
||||||
|
includeAll: false,
|
||||||
|
isMulti: false,
|
||||||
|
label: 'Datacenter',
|
||||||
|
name: 'datacenter',
|
||||||
|
options: [],
|
||||||
|
query: 'SHOW TAG VALUES WITH KEY = "datacenter" ',
|
||||||
|
refresh: 1,
|
||||||
|
regex: '',
|
||||||
|
skipUrlSync: false,
|
||||||
|
sort: 0,
|
||||||
|
text: 'America',
|
||||||
|
type: 'query',
|
||||||
|
value: 'America',
|
||||||
|
hide: 0,
|
||||||
|
definition: 'SHOW TAG VALUES WITH KEY = "datacenter"',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should migrate datasource variable', () => {
|
||||||
|
const variable: DataSourceVariableModel = {
|
||||||
|
id: 'query1',
|
||||||
|
rootStateKey: 'N4XLmH5Vz',
|
||||||
|
name: 'query1',
|
||||||
|
type: 'datasource',
|
||||||
|
global: false,
|
||||||
|
index: 1,
|
||||||
|
hide: 0,
|
||||||
|
skipUrlSync: false,
|
||||||
|
state: LoadingState.Done,
|
||||||
|
error: null,
|
||||||
|
description: null,
|
||||||
|
current: {
|
||||||
|
value: ['gdev-prometheus', 'gdev-slow-prometheus'],
|
||||||
|
text: ['gdev-prometheus', 'gdev-slow-prometheus'],
|
||||||
|
selected: true,
|
||||||
|
},
|
||||||
|
regex: '/^gdev/',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
text: 'All',
|
||||||
|
value: '$__all',
|
||||||
|
selected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'gdev-prometheus',
|
||||||
|
value: 'gdev-prometheus',
|
||||||
|
selected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'gdev-slow-prometheus',
|
||||||
|
value: 'gdev-slow-prometheus',
|
||||||
|
selected: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
query: 'prometheus',
|
||||||
|
multi: true,
|
||||||
|
includeAll: true,
|
||||||
|
refresh: 1,
|
||||||
|
allValue: 'Custom all',
|
||||||
|
};
|
||||||
|
|
||||||
|
const migrated = createSceneVariableFromVariableModel(variable);
|
||||||
|
const { key, ...rest } = migrated.state;
|
||||||
|
|
||||||
|
expect(migrated).toBeInstanceOf(DataSourceVariable);
|
||||||
|
expect(rest).toEqual({
|
||||||
|
allValue: 'Custom all',
|
||||||
|
defaultToAll: true,
|
||||||
|
includeAll: true,
|
||||||
|
label: undefined,
|
||||||
|
name: 'query1',
|
||||||
|
options: [],
|
||||||
|
pluginId: 'prometheus',
|
||||||
|
regex: '/^gdev/',
|
||||||
|
skipUrlSync: false,
|
||||||
|
text: ['gdev-prometheus', 'gdev-slow-prometheus'],
|
||||||
|
type: 'datasource',
|
||||||
|
value: ['gdev-prometheus', 'gdev-slow-prometheus'],
|
||||||
|
isMulti: true,
|
||||||
|
description: null,
|
||||||
|
hide: 0,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should migrate constant variable', () => {
|
||||||
|
const variable: ConstantVariableModel = {
|
||||||
|
hide: 2,
|
||||||
|
label: 'constant',
|
||||||
|
name: 'constant',
|
||||||
|
skipUrlSync: false,
|
||||||
|
type: 'constant',
|
||||||
|
rootStateKey: 'N4XLmH5Vz',
|
||||||
|
current: {
|
||||||
|
selected: true,
|
||||||
|
text: 'test',
|
||||||
|
value: 'test',
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
selected: true,
|
||||||
|
text: 'test',
|
||||||
|
value: 'test',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
query: 'test',
|
||||||
|
id: 'constant',
|
||||||
|
global: false,
|
||||||
|
index: 3,
|
||||||
|
state: LoadingState.Done,
|
||||||
|
error: null,
|
||||||
|
description: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
const migrated = createSceneVariableFromVariableModel(variable);
|
||||||
|
const { key, ...rest } = migrated.state;
|
||||||
|
|
||||||
|
expect(rest).toEqual({
|
||||||
|
description: null,
|
||||||
|
hide: 2,
|
||||||
|
label: 'constant',
|
||||||
|
name: 'constant',
|
||||||
|
skipUrlSync: true,
|
||||||
|
type: 'constant',
|
||||||
|
value: 'test',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should migrate interval variable', () => {
|
||||||
|
const variable: IntervalVariableModel = {
|
||||||
|
name: 'intervalVar',
|
||||||
|
label: 'Interval Label',
|
||||||
|
type: 'interval',
|
||||||
|
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: LoadingState.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('should migrate textbox variable', () => {
|
||||||
|
const variable: TextBoxVariableModel = {
|
||||||
|
id: 'query0',
|
||||||
|
global: false,
|
||||||
|
index: 0,
|
||||||
|
state: LoadingState.Done,
|
||||||
|
error: null,
|
||||||
|
name: 'textboxVar',
|
||||||
|
label: 'Textbox Label',
|
||||||
|
description: 'Textbox Description',
|
||||||
|
type: 'textbox',
|
||||||
|
rootStateKey: 'N4XLmH5Vz',
|
||||||
|
current: {},
|
||||||
|
hide: 0,
|
||||||
|
options: [],
|
||||||
|
query: 'defaultValue',
|
||||||
|
originalQuery: 'defaultValue',
|
||||||
|
skipUrlSync: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const migrated = createSceneVariableFromVariableModel(variable);
|
||||||
|
const { key, ...rest } = migrated.state;
|
||||||
|
expect(rest).toEqual({
|
||||||
|
description: 'Textbox Description',
|
||||||
|
hide: 0,
|
||||||
|
label: 'Textbox Label',
|
||||||
|
name: 'textboxVar',
|
||||||
|
skipUrlSync: false,
|
||||||
|
type: 'textbox',
|
||||||
|
value: 'defaultValue',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should migrate adhoc variable', () => {
|
||||||
|
const variable: TypedVariableModel = {
|
||||||
|
id: 'adhoc',
|
||||||
|
global: false,
|
||||||
|
index: 0,
|
||||||
|
state: LoadingState.Done,
|
||||||
|
error: null,
|
||||||
|
name: 'adhoc',
|
||||||
|
label: 'Adhoc Label',
|
||||||
|
description: 'Adhoc Description',
|
||||||
|
type: 'adhoc',
|
||||||
|
rootStateKey: 'N4XLmH5Vz',
|
||||||
|
datasource: {
|
||||||
|
uid: 'gdev-prometheus',
|
||||||
|
type: 'prometheus',
|
||||||
|
},
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
key: 'filterTest',
|
||||||
|
operator: '=',
|
||||||
|
value: 'test',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
baseFilters: [
|
||||||
|
{
|
||||||
|
key: 'baseFilterTest',
|
||||||
|
operator: '=',
|
||||||
|
value: 'test',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
hide: 0,
|
||||||
|
skipUrlSync: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const migrated = createSceneVariableFromVariableModel(variable) as AdHocFiltersVariable;
|
||||||
|
const filterVarState = migrated.state;
|
||||||
|
|
||||||
|
expect(migrated).toBeInstanceOf(AdHocFiltersVariable);
|
||||||
|
expect(filterVarState).toEqual({
|
||||||
|
key: expect.any(String),
|
||||||
|
description: 'Adhoc Description',
|
||||||
|
hide: 0,
|
||||||
|
label: 'Adhoc Label',
|
||||||
|
name: 'adhoc',
|
||||||
|
skipUrlSync: false,
|
||||||
|
type: 'adhoc',
|
||||||
|
filterExpression: 'filterTest="test"',
|
||||||
|
filters: [{ key: 'filterTest', operator: '=', value: 'test' }],
|
||||||
|
baseFilters: [{ key: 'baseFilterTest', operator: '=', value: 'test' }],
|
||||||
|
datasource: { uid: 'gdev-prometheus', type: 'prometheus' },
|
||||||
|
applyMode: 'auto',
|
||||||
|
useQueriesAsFilterForOptions: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should migrate adhoc variable with default keys', () => {
|
||||||
|
const variable: TypedVariableModel = {
|
||||||
|
id: 'adhoc',
|
||||||
|
global: false,
|
||||||
|
index: 0,
|
||||||
|
state: LoadingState.Done,
|
||||||
|
error: null,
|
||||||
|
name: 'adhoc',
|
||||||
|
label: 'Adhoc Label',
|
||||||
|
description: 'Adhoc Description',
|
||||||
|
type: 'adhoc',
|
||||||
|
rootStateKey: 'N4XLmH5Vz',
|
||||||
|
datasource: {
|
||||||
|
uid: 'gdev-prometheus',
|
||||||
|
type: 'prometheus',
|
||||||
|
},
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
key: 'filterTest',
|
||||||
|
operator: '=',
|
||||||
|
value: 'test',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
baseFilters: [
|
||||||
|
{
|
||||||
|
key: 'baseFilterTest',
|
||||||
|
operator: '=',
|
||||||
|
value: 'test',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
defaultKeys: [
|
||||||
|
{
|
||||||
|
text: 'some',
|
||||||
|
value: '1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'static',
|
||||||
|
value: '2',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'keys',
|
||||||
|
value: '3',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
hide: 0,
|
||||||
|
skipUrlSync: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const migrated = createSceneVariableFromVariableModel(variable) as AdHocFiltersVariable;
|
||||||
|
const filterVarState = migrated.state;
|
||||||
|
|
||||||
|
expect(migrated).toBeInstanceOf(AdHocFiltersVariable);
|
||||||
|
expect(filterVarState).toEqual({
|
||||||
|
key: expect.any(String),
|
||||||
|
description: 'Adhoc Description',
|
||||||
|
hide: 0,
|
||||||
|
label: 'Adhoc Label',
|
||||||
|
name: 'adhoc',
|
||||||
|
skipUrlSync: false,
|
||||||
|
type: 'adhoc',
|
||||||
|
filterExpression: 'filterTest="test"',
|
||||||
|
filters: [{ key: 'filterTest', operator: '=', value: 'test' }],
|
||||||
|
baseFilters: [{ key: 'baseFilterTest', operator: '=', value: 'test' }],
|
||||||
|
datasource: { uid: 'gdev-prometheus', type: 'prometheus' },
|
||||||
|
applyMode: 'auto',
|
||||||
|
defaultKeys: [
|
||||||
|
{
|
||||||
|
text: 'some',
|
||||||
|
value: '1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'static',
|
||||||
|
value: '2',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'keys',
|
||||||
|
value: '3',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
useQueriesAsFilterForOptions: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when groupByVariable feature toggle is enabled', () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
config.featureToggles.groupByVariable = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
config.featureToggles.groupByVariable = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should migrate groupby variable', () => {
|
||||||
|
const variable: GroupByVariableModel = {
|
||||||
|
id: 'groupby',
|
||||||
|
global: false,
|
||||||
|
index: 0,
|
||||||
|
state: LoadingState.Done,
|
||||||
|
error: null,
|
||||||
|
name: 'groupby',
|
||||||
|
label: 'GroupBy Label',
|
||||||
|
description: 'GroupBy Description',
|
||||||
|
type: 'groupby',
|
||||||
|
rootStateKey: 'N4XLmH5Vz',
|
||||||
|
datasource: {
|
||||||
|
uid: 'gdev-prometheus',
|
||||||
|
type: 'prometheus',
|
||||||
|
},
|
||||||
|
multi: true,
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
selected: false,
|
||||||
|
text: 'Foo',
|
||||||
|
value: 'foo',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
selected: false,
|
||||||
|
text: 'Bar',
|
||||||
|
value: 'bar',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
current: {},
|
||||||
|
query: '',
|
||||||
|
hide: 0,
|
||||||
|
skipUrlSync: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const migrated = createSceneVariableFromVariableModel(variable) as GroupByVariable;
|
||||||
|
const groupbyVarState = migrated.state;
|
||||||
|
|
||||||
|
expect(migrated).toBeInstanceOf(GroupByVariable);
|
||||||
|
expect(groupbyVarState).toEqual({
|
||||||
|
key: expect.any(String),
|
||||||
|
description: 'GroupBy Description',
|
||||||
|
hide: 0,
|
||||||
|
defaultOptions: [
|
||||||
|
{
|
||||||
|
selected: false,
|
||||||
|
text: 'Foo',
|
||||||
|
value: 'foo',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
selected: false,
|
||||||
|
text: 'Bar',
|
||||||
|
value: 'bar',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
isMulti: true,
|
||||||
|
layout: 'horizontal',
|
||||||
|
noValueOnClear: true,
|
||||||
|
label: 'GroupBy Label',
|
||||||
|
name: 'groupby',
|
||||||
|
skipUrlSync: false,
|
||||||
|
type: 'groupby',
|
||||||
|
baseFilters: [],
|
||||||
|
options: [],
|
||||||
|
text: [],
|
||||||
|
value: [],
|
||||||
|
datasource: { uid: 'gdev-prometheus', type: 'prometheus' },
|
||||||
|
applyMode: 'auto',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when groupByVariable feature toggle is disabled', () => {
|
||||||
|
it('should not migrate groupby variable and throw an error instead', () => {
|
||||||
|
const variable: GroupByVariableModel = {
|
||||||
|
id: 'groupby',
|
||||||
|
global: false,
|
||||||
|
index: 0,
|
||||||
|
state: LoadingState.Done,
|
||||||
|
error: null,
|
||||||
|
name: 'groupby',
|
||||||
|
label: 'GroupBy Label',
|
||||||
|
description: 'GroupBy Description',
|
||||||
|
type: 'groupby',
|
||||||
|
rootStateKey: 'N4XLmH5Vz',
|
||||||
|
datasource: {
|
||||||
|
uid: 'gdev-prometheus',
|
||||||
|
type: 'prometheus',
|
||||||
|
},
|
||||||
|
multi: true,
|
||||||
|
options: [],
|
||||||
|
current: {},
|
||||||
|
query: '',
|
||||||
|
hide: 0,
|
||||||
|
skipUrlSync: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(() => createSceneVariableFromVariableModel(variable)).toThrow('Scenes: Unsupported variable type');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each(['system'])('should throw for unsupported (yet) variables', (type) => {
|
||||||
|
const variable = {
|
||||||
|
name: 'query0',
|
||||||
|
type: type as VariableType,
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(() => createSceneVariableFromVariableModel(variable as TypedVariableModel)).toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle variable without current', () => {
|
||||||
|
// @ts-expect-error
|
||||||
|
const variable: TypedVariableModel = {
|
||||||
|
id: 'query1',
|
||||||
|
name: 'query1',
|
||||||
|
type: 'datasource',
|
||||||
|
global: false,
|
||||||
|
regex: '/^gdev/',
|
||||||
|
options: [],
|
||||||
|
query: 'prometheus',
|
||||||
|
multi: true,
|
||||||
|
includeAll: true,
|
||||||
|
refresh: 1,
|
||||||
|
allValue: 'Custom all',
|
||||||
|
};
|
||||||
|
|
||||||
|
const migrated = createSceneVariableFromVariableModel(variable);
|
||||||
|
const { key, ...rest } = migrated.state;
|
||||||
|
|
||||||
|
expect(migrated).toBeInstanceOf(DataSourceVariable);
|
||||||
|
expect(rest).toEqual({
|
||||||
|
allValue: 'Custom all',
|
||||||
|
defaultToAll: true,
|
||||||
|
includeAll: true,
|
||||||
|
label: undefined,
|
||||||
|
name: 'query1',
|
||||||
|
options: [],
|
||||||
|
pluginId: 'prometheus',
|
||||||
|
regex: '/^gdev/',
|
||||||
|
text: '',
|
||||||
|
type: 'datasource',
|
||||||
|
value: '',
|
||||||
|
isMulti: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when creating snapshot variables from dashboard model', () => {
|
||||||
|
it('should create SnapshotVariables when required', () => {
|
||||||
|
const customVariable = {
|
||||||
|
current: {
|
||||||
|
selected: false,
|
||||||
|
text: 'a',
|
||||||
|
value: 'a',
|
||||||
|
},
|
||||||
|
hide: 0,
|
||||||
|
includeAll: false,
|
||||||
|
multi: false,
|
||||||
|
name: 'custom0',
|
||||||
|
options: [],
|
||||||
|
query: 'a,b,c,d',
|
||||||
|
skipUrlSync: false,
|
||||||
|
type: 'custom' as VariableType,
|
||||||
|
rootStateKey: 'N4XLmH5Vz',
|
||||||
|
};
|
||||||
|
|
||||||
|
const intervalVariable = {
|
||||||
|
current: {
|
||||||
|
selected: false,
|
||||||
|
text: '10s',
|
||||||
|
value: '10s',
|
||||||
|
},
|
||||||
|
hide: 0,
|
||||||
|
includeAll: false,
|
||||||
|
multi: false,
|
||||||
|
name: 'interval0',
|
||||||
|
options: [],
|
||||||
|
query: '10s,20s,30s',
|
||||||
|
skipUrlSync: false,
|
||||||
|
type: 'interval' as VariableType,
|
||||||
|
rootStateKey: 'N4XLmH5Vz',
|
||||||
|
};
|
||||||
|
|
||||||
|
const adHocVariable = {
|
||||||
|
global: false,
|
||||||
|
name: 'CoolFilters',
|
||||||
|
label: 'CoolFilters Label',
|
||||||
|
type: 'adhoc' as VariableType,
|
||||||
|
datasource: {
|
||||||
|
uid: 'gdev-prometheus',
|
||||||
|
type: 'prometheus',
|
||||||
|
},
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
key: 'filterTest',
|
||||||
|
operator: '=',
|
||||||
|
value: 'test',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
baseFilters: [
|
||||||
|
{
|
||||||
|
key: 'baseFilterTest',
|
||||||
|
operator: '=',
|
||||||
|
value: 'test',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
hide: 0,
|
||||||
|
index: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
const snapshot = {
|
||||||
|
...defaultDashboard,
|
||||||
|
title: 'snapshot dash',
|
||||||
|
uid: 'test-uid',
|
||||||
|
time: { from: 'now-10h', to: 'now' },
|
||||||
|
weekStart: 'saturday',
|
||||||
|
fiscalYearStartMonth: 2,
|
||||||
|
timezone: 'America/New_York',
|
||||||
|
timepicker: {
|
||||||
|
...defaultTimePickerConfig,
|
||||||
|
hidden: true,
|
||||||
|
},
|
||||||
|
links: [{ ...NEW_LINK, title: 'Link 1' }],
|
||||||
|
templating: {
|
||||||
|
list: [customVariable, adHocVariable, intervalVariable],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const oldModel = new DashboardModel(snapshot, { isSnapshot: true });
|
||||||
|
const variables = createVariablesForSnapshot(oldModel);
|
||||||
|
|
||||||
|
// check variables were converted to snapshot variables
|
||||||
|
expect(variables).toBeInstanceOf(SceneVariableSet);
|
||||||
|
expect(variables.getByName('custom0')).toBeInstanceOf(SnapshotVariable);
|
||||||
|
expect(variables?.getByName('CoolFilters')).toBeInstanceOf(AdHocFiltersVariable);
|
||||||
|
expect(variables?.getByName('interval0')).toBeInstanceOf(SnapshotVariable);
|
||||||
|
// // custom snapshot
|
||||||
|
const customSnapshot = variables?.getByName('custom0') as SnapshotVariable;
|
||||||
|
expect(customSnapshot.state.value).toBe('a');
|
||||||
|
expect(customSnapshot.state.text).toBe('a');
|
||||||
|
expect(customSnapshot.state.isReadOnly).toBe(true);
|
||||||
|
// // adhoc snapshot
|
||||||
|
const adhocSnapshot = variables?.getByName('CoolFilters') as AdHocFiltersVariable;
|
||||||
|
expect(adhocSnapshot.state.filters).toEqual(adHocVariable.filters);
|
||||||
|
expect(adhocSnapshot.state.readOnly).toBe(true);
|
||||||
|
//
|
||||||
|
// // interval snapshot
|
||||||
|
const intervalSnapshot = variables?.getByName('interval0') as SnapshotVariable;
|
||||||
|
expect(intervalSnapshot.state.value).toBe('10s');
|
||||||
|
expect(intervalSnapshot.state.text).toBe('10s');
|
||||||
|
expect(intervalSnapshot.state.isReadOnly).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
238
public/app/features/dashboard-scene/utils/variables.ts
Normal file
238
public/app/features/dashboard-scene/utils/variables.ts
Normal file
@ -0,0 +1,238 @@
|
|||||||
|
import { TypedVariableModel } from '@grafana/data';
|
||||||
|
import { config } from '@grafana/runtime';
|
||||||
|
import {
|
||||||
|
AdHocFiltersVariable,
|
||||||
|
ConstantVariable,
|
||||||
|
CustomVariable,
|
||||||
|
DataSourceVariable,
|
||||||
|
GroupByVariable,
|
||||||
|
IntervalVariable,
|
||||||
|
QueryVariable,
|
||||||
|
SceneVariable,
|
||||||
|
SceneVariableSet,
|
||||||
|
TextBoxVariable,
|
||||||
|
} from '@grafana/scenes';
|
||||||
|
import { DashboardModel } from 'app/features/dashboard/state';
|
||||||
|
|
||||||
|
import { SnapshotVariable } from '../serialization/custom-variables/SnapshotVariable';
|
||||||
|
|
||||||
|
import { getCurrentValueForOldIntervalModel, getIntervalsFromQueryString } from './utils';
|
||||||
|
|
||||||
|
export function createVariablesForDashboard(oldModel: DashboardModel) {
|
||||||
|
const variableObjects = oldModel.templating.list
|
||||||
|
.map((v) => {
|
||||||
|
try {
|
||||||
|
return createSceneVariableFromVariableModel(v);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// TODO: Remove filter
|
||||||
|
// Added temporarily to allow skipping non-compatible variables
|
||||||
|
.filter((v): v is SceneVariable => Boolean(v));
|
||||||
|
|
||||||
|
return new SceneVariableSet({
|
||||||
|
variables: variableObjects,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createVariablesForSnapshot(oldModel: DashboardModel) {
|
||||||
|
const variableObjects = oldModel.templating.list
|
||||||
|
.map((v) => {
|
||||||
|
try {
|
||||||
|
// for adhoc we are using the AdHocFiltersVariable from scenes becuase of its complexity
|
||||||
|
if (v.type === 'adhoc') {
|
||||||
|
return new AdHocFiltersVariable({
|
||||||
|
name: v.name,
|
||||||
|
label: v.label,
|
||||||
|
readOnly: true,
|
||||||
|
description: v.description,
|
||||||
|
skipUrlSync: v.skipUrlSync,
|
||||||
|
hide: v.hide,
|
||||||
|
datasource: v.datasource,
|
||||||
|
applyMode: 'auto',
|
||||||
|
filters: v.filters ?? [],
|
||||||
|
baseFilters: v.baseFilters ?? [],
|
||||||
|
defaultKeys: v.defaultKeys,
|
||||||
|
useQueriesAsFilterForOptions: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// for other variable types we are using the SnapshotVariable
|
||||||
|
return createSnapshotVariable(v);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// TODO: Remove filter
|
||||||
|
// Added temporarily to allow skipping non-compatible variables
|
||||||
|
.filter((v): v is SceneVariable => Boolean(v));
|
||||||
|
|
||||||
|
return new SceneVariableSet({
|
||||||
|
variables: variableObjects,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Snapshots variables are read-only and should not be updated */
|
||||||
|
export function createSnapshotVariable(variable: TypedVariableModel): SceneVariable {
|
||||||
|
let snapshotVariable: SnapshotVariable;
|
||||||
|
let current: { value: string | string[]; text: string | string[] };
|
||||||
|
if (variable.type === 'interval') {
|
||||||
|
const intervals = getIntervalsFromQueryString(variable.query);
|
||||||
|
const currentInterval = getCurrentValueForOldIntervalModel(variable, intervals);
|
||||||
|
snapshotVariable = new SnapshotVariable({
|
||||||
|
name: variable.name,
|
||||||
|
label: variable.label,
|
||||||
|
description: variable.description,
|
||||||
|
value: currentInterval,
|
||||||
|
text: currentInterval,
|
||||||
|
hide: variable.hide,
|
||||||
|
});
|
||||||
|
return snapshotVariable;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (variable.type === 'system' || variable.type === 'constant' || variable.type === 'adhoc') {
|
||||||
|
current = {
|
||||||
|
value: '',
|
||||||
|
text: '',
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
current = {
|
||||||
|
value: variable.current?.value ?? '',
|
||||||
|
text: variable.current?.text ?? '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
snapshotVariable = new SnapshotVariable({
|
||||||
|
name: variable.name,
|
||||||
|
label: variable.label,
|
||||||
|
description: variable.description,
|
||||||
|
value: current?.value ?? '',
|
||||||
|
text: current?.text ?? '',
|
||||||
|
hide: variable.hide,
|
||||||
|
});
|
||||||
|
return snapshotVariable;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createSceneVariableFromVariableModel(variable: TypedVariableModel): SceneVariable {
|
||||||
|
const commonProperties = {
|
||||||
|
name: variable.name,
|
||||||
|
label: variable.label,
|
||||||
|
description: variable.description,
|
||||||
|
};
|
||||||
|
if (variable.type === 'adhoc') {
|
||||||
|
return new AdHocFiltersVariable({
|
||||||
|
...commonProperties,
|
||||||
|
description: variable.description,
|
||||||
|
skipUrlSync: variable.skipUrlSync,
|
||||||
|
hide: variable.hide,
|
||||||
|
datasource: variable.datasource,
|
||||||
|
applyMode: 'auto',
|
||||||
|
filters: variable.filters ?? [],
|
||||||
|
baseFilters: variable.baseFilters ?? [],
|
||||||
|
defaultKeys: variable.defaultKeys,
|
||||||
|
useQueriesAsFilterForOptions: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (variable.type === 'custom') {
|
||||||
|
return new CustomVariable({
|
||||||
|
...commonProperties,
|
||||||
|
value: variable.current?.value ?? '',
|
||||||
|
text: variable.current?.text ?? '',
|
||||||
|
|
||||||
|
query: variable.query,
|
||||||
|
isMulti: variable.multi,
|
||||||
|
allValue: variable.allValue || undefined,
|
||||||
|
includeAll: variable.includeAll,
|
||||||
|
defaultToAll: Boolean(variable.includeAll),
|
||||||
|
skipUrlSync: variable.skipUrlSync,
|
||||||
|
hide: variable.hide,
|
||||||
|
});
|
||||||
|
} else if (variable.type === 'query') {
|
||||||
|
return new QueryVariable({
|
||||||
|
...commonProperties,
|
||||||
|
value: variable.current?.value ?? '',
|
||||||
|
text: variable.current?.text ?? '',
|
||||||
|
|
||||||
|
query: variable.query,
|
||||||
|
datasource: variable.datasource,
|
||||||
|
sort: variable.sort,
|
||||||
|
refresh: variable.refresh,
|
||||||
|
regex: variable.regex,
|
||||||
|
allValue: variable.allValue || undefined,
|
||||||
|
includeAll: variable.includeAll,
|
||||||
|
defaultToAll: Boolean(variable.includeAll),
|
||||||
|
isMulti: variable.multi,
|
||||||
|
skipUrlSync: variable.skipUrlSync,
|
||||||
|
hide: variable.hide,
|
||||||
|
definition: variable.definition,
|
||||||
|
});
|
||||||
|
} else if (variable.type === 'datasource') {
|
||||||
|
return new DataSourceVariable({
|
||||||
|
...commonProperties,
|
||||||
|
value: variable.current?.value ?? '',
|
||||||
|
text: variable.current?.text ?? '',
|
||||||
|
regex: variable.regex,
|
||||||
|
pluginId: variable.query,
|
||||||
|
allValue: variable.allValue || undefined,
|
||||||
|
includeAll: variable.includeAll,
|
||||||
|
defaultToAll: Boolean(variable.includeAll),
|
||||||
|
skipUrlSync: variable.skipUrlSync,
|
||||||
|
isMulti: variable.multi,
|
||||||
|
hide: variable.hide,
|
||||||
|
});
|
||||||
|
} else if (variable.type === 'interval') {
|
||||||
|
const intervals = getIntervalsFromQueryString(variable.query);
|
||||||
|
const currentInterval = getCurrentValueForOldIntervalModel(variable, intervals);
|
||||||
|
return new IntervalVariable({
|
||||||
|
...commonProperties,
|
||||||
|
value: currentInterval,
|
||||||
|
intervals: intervals,
|
||||||
|
autoEnabled: variable.auto,
|
||||||
|
autoStepCount: variable.auto_count,
|
||||||
|
autoMinInterval: variable.auto_min,
|
||||||
|
refresh: variable.refresh,
|
||||||
|
skipUrlSync: variable.skipUrlSync,
|
||||||
|
hide: variable.hide,
|
||||||
|
});
|
||||||
|
} else if (variable.type === 'constant') {
|
||||||
|
return new ConstantVariable({
|
||||||
|
...commonProperties,
|
||||||
|
value: variable.query,
|
||||||
|
skipUrlSync: variable.skipUrlSync,
|
||||||
|
hide: variable.hide,
|
||||||
|
});
|
||||||
|
} else if (variable.type === 'textbox') {
|
||||||
|
let val;
|
||||||
|
if (!variable?.current?.value) {
|
||||||
|
val = variable.query;
|
||||||
|
} else {
|
||||||
|
if (typeof variable.current.value === 'string') {
|
||||||
|
val = variable.current.value;
|
||||||
|
} else {
|
||||||
|
val = variable.current.value[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new TextBoxVariable({
|
||||||
|
...commonProperties,
|
||||||
|
value: val,
|
||||||
|
skipUrlSync: variable.skipUrlSync,
|
||||||
|
hide: variable.hide,
|
||||||
|
});
|
||||||
|
} else if (config.featureToggles.groupByVariable && variable.type === 'groupby') {
|
||||||
|
return new GroupByVariable({
|
||||||
|
...commonProperties,
|
||||||
|
datasource: variable.datasource,
|
||||||
|
value: variable.current?.value || [],
|
||||||
|
text: variable.current?.text || [],
|
||||||
|
skipUrlSync: variable.skipUrlSync,
|
||||||
|
hide: variable.hide,
|
||||||
|
// @ts-expect-error
|
||||||
|
defaultOptions: variable.options,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
throw new Error(`Scenes: Unsupported variable type ${variable.type}`);
|
||||||
|
}
|
||||||
|
}
|
@ -23,6 +23,7 @@ import {
|
|||||||
createIntervalVariable,
|
createIntervalVariable,
|
||||||
createOrgVariable,
|
createOrgVariable,
|
||||||
createQueryVariable,
|
createQueryVariable,
|
||||||
|
createSnapshotVariable,
|
||||||
createTextBoxVariable,
|
createTextBoxVariable,
|
||||||
createUserVariable,
|
createUserVariable,
|
||||||
} from './state/__tests__/fixtures';
|
} from './state/__tests__/fixtures';
|
||||||
@ -175,6 +176,7 @@ describe('type guards', () => {
|
|||||||
org: { variable: createOrgVariable(), isMulti: false, hasOptions: false, hasCurrent: true },
|
org: { variable: createOrgVariable(), isMulti: false, hasOptions: false, hasCurrent: true },
|
||||||
dashboard: { variable: createDashboardVariable(), isMulti: false, hasOptions: false, hasCurrent: true },
|
dashboard: { variable: createDashboardVariable(), isMulti: false, hasOptions: false, hasCurrent: true },
|
||||||
custom: { variable: createCustomVariable(), isMulti: true, hasOptions: true, hasCurrent: true },
|
custom: { variable: createCustomVariable(), isMulti: true, hasOptions: true, hasCurrent: true },
|
||||||
|
snapshot: { variable: createSnapshotVariable(), isMulti: false, hasOptions: true, hasCurrent: true },
|
||||||
};
|
};
|
||||||
|
|
||||||
const variableFacts = Object.values(variableFactsObj);
|
const variableFacts = Object.values(variableFactsObj);
|
||||||
|
@ -10,6 +10,7 @@ import {
|
|||||||
LoadingState,
|
LoadingState,
|
||||||
OrgVariableModel,
|
OrgVariableModel,
|
||||||
QueryVariableModel,
|
QueryVariableModel,
|
||||||
|
SnapshotVariableModel,
|
||||||
TextBoxVariableModel,
|
TextBoxVariableModel,
|
||||||
UserVariableModel,
|
UserVariableModel,
|
||||||
VariableHide,
|
VariableHide,
|
||||||
@ -198,3 +199,13 @@ export function createCustomVariable(input: Partial<CustomVariableModel> = {}):
|
|||||||
...input,
|
...input,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function createSnapshotVariable(input: Partial<SnapshotVariableModel> = {}): SnapshotVariableModel {
|
||||||
|
return {
|
||||||
|
...createBaseVariableModel('snapshot'),
|
||||||
|
query: '',
|
||||||
|
current: createVariableOption('prom-prod', { text: 'Prometheus (main)', selected: true }),
|
||||||
|
options: [],
|
||||||
|
...input,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user