363 lines
12 KiB
TypeScript
363 lines
12 KiB
TypeScript
import { of } from 'rxjs';
|
|
|
|
import {
|
|
FieldType,
|
|
LoadingState,
|
|
PanelData,
|
|
VariableSupportType,
|
|
getDefaultTimeRange,
|
|
toDataFrame,
|
|
} from '@grafana/data';
|
|
import { getPanelPlugin } from '@grafana/data/test/__mocks__/pluginMocks';
|
|
import { setPluginImportUtils, setRunRequest } from '@grafana/runtime';
|
|
import {
|
|
SceneVariableSet,
|
|
CustomVariable,
|
|
VizPanel,
|
|
AdHocFiltersVariable,
|
|
SceneVariableState,
|
|
SceneTimeRange,
|
|
} from '@grafana/scenes';
|
|
import { mockDataSource } from 'app/features/alerting/unified/mocks';
|
|
import { LegacyVariableQueryEditor } from 'app/features/variables/editor/LegacyVariableQueryEditor';
|
|
|
|
import { DashboardScene } from '../scene/DashboardScene';
|
|
import { DefaultGridLayoutManager } from '../scene/layout-default/DefaultGridLayoutManager';
|
|
import { activateFullSceneTree } from '../utils/test-utils';
|
|
|
|
import { VariablesEditView } from './VariablesEditView';
|
|
|
|
setPluginImportUtils({
|
|
importPanelPlugin: (id: string) => Promise.resolve(getPanelPlugin({})),
|
|
getPanelPluginFromCache: (id: string) => undefined,
|
|
});
|
|
|
|
const defaultDatasource = mockDataSource({
|
|
name: 'Default Test Data Source',
|
|
type: 'test',
|
|
});
|
|
|
|
const promDatasource = mockDataSource({
|
|
name: 'Prometheus',
|
|
type: 'prometheus',
|
|
});
|
|
|
|
jest.mock('@grafana/runtime/src/services/dataSourceSrv', () => ({
|
|
...jest.requireActual('@grafana/runtime/src/services/dataSourceSrv'),
|
|
getDataSourceSrv: () => ({
|
|
get: async () => ({
|
|
...defaultDatasource,
|
|
variables: {
|
|
getType: () => VariableSupportType.Custom,
|
|
query: jest.fn(),
|
|
editor: jest.fn().mockImplementation(LegacyVariableQueryEditor),
|
|
},
|
|
}),
|
|
getList: () => [defaultDatasource, promDatasource],
|
|
getInstanceSettings: () => ({ ...defaultDatasource }),
|
|
}),
|
|
}));
|
|
|
|
const runRequestMock = jest.fn().mockReturnValue(
|
|
of<PanelData>({
|
|
state: LoadingState.Done,
|
|
series: [
|
|
toDataFrame({
|
|
fields: [{ name: 'text', type: FieldType.string, values: ['val1', 'val2', 'val11'] }],
|
|
}),
|
|
],
|
|
timeRange: getDefaultTimeRange(),
|
|
})
|
|
);
|
|
|
|
setRunRequest(runRequestMock);
|
|
|
|
describe('VariablesEditView', () => {
|
|
describe('Dashboard Variables state', () => {
|
|
let dashboard: DashboardScene;
|
|
let variableView: VariablesEditView;
|
|
|
|
beforeEach(async () => {
|
|
const result = await buildTestScene();
|
|
dashboard = result.dashboard;
|
|
variableView = result.variableView;
|
|
});
|
|
|
|
it('should return the correct urlKey', () => {
|
|
expect(variableView.getUrlKey()).toBe('variables');
|
|
});
|
|
|
|
it('should return the dashboard', () => {
|
|
expect(variableView.getDashboard()).toBe(dashboard);
|
|
});
|
|
|
|
it('should return the list of variables', () => {
|
|
const expectedVariables = [
|
|
{
|
|
type: 'custom',
|
|
name: 'customVar',
|
|
query: 'test, test2',
|
|
value: 'test',
|
|
},
|
|
{
|
|
type: 'custom',
|
|
name: 'customVar2',
|
|
query: 'test3, test4, $customVar',
|
|
value: 'test3',
|
|
},
|
|
{
|
|
type: 'adhoc',
|
|
name: 'adhoc',
|
|
},
|
|
];
|
|
const variables = variableView.getVariables();
|
|
expect(variables).toHaveLength(3);
|
|
expect(variables[0].state).toMatchObject(expectedVariables[0]);
|
|
expect(variables[1].state).toMatchObject(expectedVariables[1]);
|
|
expect(variables[2].state).toMatchObject(expectedVariables[2]);
|
|
});
|
|
});
|
|
|
|
describe('Dashboard Variables actions', () => {
|
|
let variableView: VariablesEditView;
|
|
|
|
beforeEach(async () => {
|
|
const result = await buildTestScene();
|
|
variableView = result.variableView;
|
|
});
|
|
|
|
it('should duplicate a variable', () => {
|
|
const variables = variableView.getVariables();
|
|
const variable = variables[0];
|
|
variableView.onDuplicated(variable.state.name);
|
|
expect(variableView.getVariables()).toHaveLength(4);
|
|
expect(variableView.getVariables()[1].state.name).toBe('copy_of_customVar');
|
|
});
|
|
|
|
it('should handle name when duplicating a variable twice', () => {
|
|
const variableIdentifier = 'customVar';
|
|
variableView.onDuplicated(variableIdentifier);
|
|
variableView.onDuplicated(variableIdentifier);
|
|
expect(variableView.getVariables()).toHaveLength(5);
|
|
expect(variableView.getVariables()[1].state.name).toBe('copy_of_customVar_1');
|
|
expect(variableView.getVariables()[2].state.name).toBe('copy_of_customVar');
|
|
});
|
|
|
|
it('should delete a variable', () => {
|
|
const variableIdentifier = 'customVar';
|
|
|
|
variableView.onEdit(variableIdentifier);
|
|
expect(variableView.state.editIndex).toBe(0);
|
|
|
|
variableView.onDelete(variableIdentifier);
|
|
expect(variableView.getVariables()).toHaveLength(2);
|
|
expect(variableView.getVariables()[0].state.name).toBe('customVar2');
|
|
expect(variableView.state.editIndex).toBeUndefined();
|
|
});
|
|
|
|
it('should change order of variables', () => {
|
|
const fromIndex = 0; // customVar is first
|
|
const toIndex = 1;
|
|
variableView.onOrderChanged(fromIndex, toIndex);
|
|
expect(variableView.getVariables()[0].state.name).toBe('customVar2');
|
|
expect(variableView.getVariables()[1].state.name).toBe('customVar');
|
|
});
|
|
|
|
it('should keep the same order of variables with invalid indexes', () => {
|
|
const fromIndex = 0;
|
|
const toIndex = 3;
|
|
|
|
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
|
|
|
variableView.onOrderChanged(fromIndex, toIndex);
|
|
expect(errorSpy).toHaveBeenCalledTimes(1);
|
|
expect(variableView.getVariables()[0].state.name).toBe('customVar');
|
|
expect(variableView.getVariables()[1].state.name).toBe('customVar2');
|
|
|
|
errorSpy.mockRestore();
|
|
});
|
|
|
|
it('should change the variable type creating a new variable object', () => {
|
|
const previousVariable = variableView.getVariables()[1] as CustomVariable;
|
|
variableView.onEdit('customVar2');
|
|
|
|
variableView.onTypeChange('adhoc');
|
|
expect(variableView.getVariables()).toHaveLength(3);
|
|
const variable = variableView.getVariables()[1];
|
|
expect(variable).not.toBe(previousVariable);
|
|
expect(variable.state.type).toBe('adhoc');
|
|
|
|
// Values to be kept between the old and new variable
|
|
expect(variable.state.name).toEqual(previousVariable.state.name);
|
|
expect(variable.state.label).toEqual(previousVariable.state.label);
|
|
});
|
|
|
|
it('should reset editing variable when going back', () => {
|
|
variableView.onEdit('customVar2');
|
|
expect(variableView.state.editIndex).toBe(1);
|
|
|
|
variableView.onGoBack();
|
|
expect(variableView.state.editIndex).toBeUndefined();
|
|
});
|
|
|
|
it('should add default new query variable when onAdd is called', () => {
|
|
variableView.onAdd();
|
|
expect(variableView.getVariables()).toHaveLength(4);
|
|
expect(variableView.getVariables()[3].state.name).toBe('query0');
|
|
expect(variableView.getVariables()[3].state.type).toBe('query');
|
|
});
|
|
|
|
afterEach(() => {
|
|
jest.clearAllMocks();
|
|
});
|
|
});
|
|
|
|
describe('Variables name validation', () => {
|
|
let variableView: VariablesEditView;
|
|
let variable1: SceneVariableState;
|
|
let variable2: SceneVariableState;
|
|
|
|
beforeAll(async () => {
|
|
const result = await buildTestScene();
|
|
variableView = result.variableView;
|
|
|
|
const variables = variableView.getVariables();
|
|
variable1 = variables[0].state;
|
|
variable2 = variables[1].state;
|
|
});
|
|
|
|
it('should not return error on same name and key', () => {
|
|
expect(variableView.onValidateVariableName(variable1.name, variable1.key)[0]).toBe(false);
|
|
});
|
|
|
|
it('should not return error if name is unique', () => {
|
|
expect(variableView.onValidateVariableName('unique_variable_name', variable1.key)[0]).toBe(false);
|
|
});
|
|
|
|
it('should return error if global variable name is used', () => {
|
|
expect(variableView.onValidateVariableName('__', variable1.key)[0]).toBe(true);
|
|
});
|
|
|
|
it('should not return error if global variable name is used not at the beginning ', () => {
|
|
expect(variableView.onValidateVariableName('test__', variable1.key)[0]).toBe(false);
|
|
});
|
|
|
|
it('should return error if name is empty', () => {
|
|
expect(variableView.onValidateVariableName('', variable1.key)[0]).toBe(true);
|
|
});
|
|
|
|
it('should return error if non word characters are used', () => {
|
|
expect(variableView.onValidateVariableName('-', variable1.key)[0]).toBe(true);
|
|
});
|
|
|
|
it('should return error if variable name is taken', () => {
|
|
expect(variableView.onValidateVariableName(variable2.name, variable1.key)[0]).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('Dashboard Variables dependencies', () => {
|
|
let variableView: VariablesEditView;
|
|
let dashboard: DashboardScene;
|
|
|
|
beforeEach(async () => {
|
|
const result = await buildTestScene();
|
|
variableView = result.variableView;
|
|
dashboard = result.dashboard;
|
|
});
|
|
|
|
// FIXME: This is not working because the variable is replaced or it is not resolved yet
|
|
it.skip('should keep dependencies between variables the type is changed so the variable is replaced', () => {
|
|
// Uses function to avoid store reference to previous existing variables
|
|
const getSourceVariable = () => variableView.getVariables()[0] as CustomVariable;
|
|
const getDependantVariable = () => variableView.getVariables()[1] as CustomVariable;
|
|
|
|
expect(getSourceVariable().getValue()).toBe('test');
|
|
// Using getOptionsForSelect to get the interpolated values
|
|
expect(getDependantVariable().getOptionsForSelect()[2].label).toBe('test');
|
|
|
|
variableView.onEdit(getSourceVariable().state.name);
|
|
// Simulating changing the type and update the value
|
|
variableView.onTypeChange('constant');
|
|
getSourceVariable().setState({ value: 'newValue' });
|
|
|
|
expect(getSourceVariable().getValue()).toBe('newValue');
|
|
expect(getDependantVariable().getOptionsForSelect()[2].label).toBe('newValue');
|
|
});
|
|
|
|
it('should keep dependencies with panels when the type is changed so the variable is replaced', async () => {
|
|
// Uses function to avoid store reference to previous existing variables
|
|
const getSourceVariable = () => variableView.getVariables()[0] as CustomVariable;
|
|
const getDependantPanel = () => dashboard.state.body.getVizPanels()[0];
|
|
|
|
expect(getSourceVariable().getValue()).toBe('test');
|
|
// Using description to get the interpolated value
|
|
expect(getDependantPanel().getDescription()).toContain('Panel A depends on customVar with current value test');
|
|
|
|
variableView.onEdit(getSourceVariable().state.name);
|
|
// Simulating changing the type and update the value
|
|
variableView.onTypeChange('constant');
|
|
getSourceVariable().setState({ value: 'newValue' });
|
|
|
|
expect(getSourceVariable().getValue()).toBe('newValue');
|
|
expect(getDependantPanel().getDescription()).toContain('newValue');
|
|
});
|
|
});
|
|
});
|
|
|
|
async function buildTestScene() {
|
|
const variableView = new VariablesEditView({});
|
|
const dashboard = new DashboardScene({
|
|
title: 'Dashboard with variables',
|
|
uid: 'dash-variables',
|
|
meta: {
|
|
canEdit: true,
|
|
},
|
|
$timeRange: new SceneTimeRange({}),
|
|
$variables: new SceneVariableSet({
|
|
variables: [
|
|
new CustomVariable({
|
|
name: 'customVar',
|
|
query: 'test, test2',
|
|
value: 'test',
|
|
text: 'test',
|
|
}),
|
|
new CustomVariable({
|
|
name: 'customVar2',
|
|
query: 'test3, test4, $customVar',
|
|
value: '$customVar',
|
|
text: '$customVar',
|
|
}),
|
|
new AdHocFiltersVariable({
|
|
type: 'adhoc',
|
|
name: 'adhoc',
|
|
filters: [
|
|
{
|
|
key: 'test',
|
|
operator: '=',
|
|
value: 'testValue',
|
|
},
|
|
],
|
|
}),
|
|
],
|
|
}),
|
|
body: DefaultGridLayoutManager.fromVizPanels([
|
|
new VizPanel({
|
|
title: 'Panel A',
|
|
description: 'Panel A depends on customVar with current value $customVar',
|
|
key: 'panel-1',
|
|
pluginId: 'table',
|
|
}),
|
|
]),
|
|
editview: variableView,
|
|
});
|
|
|
|
activateFullSceneTree(dashboard);
|
|
|
|
await new Promise((r) => setTimeout(r, 1));
|
|
|
|
dashboard.onEnterEditMode();
|
|
variableView.activate();
|
|
|
|
return { dashboard, variableView };
|
|
}
|