import { DataSourceApi } from '@grafana/data'; import { config, setTemplateSrv, TemplateSrv } from '@grafana/runtime'; import { CustomVariable, ConstantVariable, IntervalVariable, QueryVariable, DataSourceVariable, AdHocFiltersVariable, GroupByVariable, TextBoxVariable, SceneVariableSet, } from '@grafana/scenes'; import { DataQuery, DataSourceJsonData, VariableHide, VariableType } from '@grafana/schema'; import { SHARED_DASHBOARD_QUERY, DASHBOARD_DATASOURCE_PLUGIN_ID } from 'app/plugins/datasource/dashboard/constants'; import { AdHocFiltersVariableEditor } from './editors/AdHocFiltersVariableEditor'; import { ConstantVariableEditor } from './editors/ConstantVariableEditor'; import { CustomVariableEditor } from './editors/CustomVariableEditor'; import { DataSourceVariableEditor } from './editors/DataSourceVariableEditor'; import { GroupByVariableEditor } from './editors/GroupByVariableEditor'; import { IntervalVariableEditor } from './editors/IntervalVariableEditor'; import { QueryVariableEditor } from './editors/QueryVariableEditor'; import { TextBoxVariableEditor } from './editors/TextBoxVariableEditor'; import { isEditableVariableType, EDITABLE_VARIABLES, EDITABLE_VARIABLES_SELECT_ORDER, getVariableTypeSelectOptions, getVariableEditor, getVariableScene, hasVariableOptions, EditableVariableType, getDefinition, getOptionDataSourceTypes, getNextAvailableId, getVariableDefault, isSceneVariableInstance, } from './utils'; const templateSrv = { getAdhocFilters: jest.fn().mockReturnValue([{ key: 'origKey', operator: '=', value: '' }]), } as unknown as TemplateSrv; const dsMock: DataSourceApi = { meta: { id: DASHBOARD_DATASOURCE_PLUGIN_ID, }, name: SHARED_DASHBOARD_QUERY, type: SHARED_DASHBOARD_QUERY, uid: SHARED_DASHBOARD_QUERY, getRef: () => { return { type: SHARED_DASHBOARD_QUERY, uid: SHARED_DASHBOARD_QUERY }; }, } as DataSourceApi; jest.mock('@grafana/runtime', () => ({ ...jest.requireActual('@grafana/runtime'), getDataSourceSrv: () => ({ get: async () => dsMock, getList: () => { return [ { name: 'DataSourceInstance1', uid: 'ds1', meta: { name: 'ds1', id: 'dsTestDataSource', }, }, ]; }, }), })); describe('isEditableVariableType', () => { it('should return true for editable variable types', () => { const editableTypes: VariableType[] = [ 'custom', 'query', 'constant', 'interval', 'datasource', 'adhoc', 'groupby', 'textbox', ]; editableTypes.forEach((type) => { expect(isEditableVariableType(type)).toBe(true); }); }); it('should return false for non-editable variable types', () => { const nonEditableTypes: VariableType[] = ['system']; nonEditableTypes.forEach((type) => { expect(isEditableVariableType(type)).toBe(false); }); }); }); describe('isSceneVariableInstance', () => { it.each([ CustomVariable, QueryVariable, ConstantVariable, IntervalVariable, DataSourceVariable, AdHocFiltersVariable, GroupByVariable, TextBoxVariable, ])('should return true for scene variable instances %s', (instanceType) => { const variable = new instanceType({ name: 'MyVariable' }); expect(isSceneVariableInstance(variable)).toBe(true); }); it('should return false for non-scene variable instances', () => { const variable = { name: 'MyVariable', type: 'query', }; expect(variable).not.toBeInstanceOf(QueryVariable); }); }); describe('getVariableTypeSelectOptions', () => { describe('when groupByVariable is enabled', () => { beforeAll(() => { config.featureToggles.groupByVariable = true; }); afterAll(() => { config.featureToggles.groupByVariable = false; }); it('should contain all editable variable types', () => { const options = getVariableTypeSelectOptions(); expect(options).toHaveLength(Object.keys(EDITABLE_VARIABLES).length); EDITABLE_VARIABLES_SELECT_ORDER.forEach((type) => { expect(EDITABLE_VARIABLES).toHaveProperty(type); }); }); it('should return an array of selectable values for editable variable types', () => { const options = getVariableTypeSelectOptions(); expect(options).toHaveLength(8); options.forEach((option, index) => { const editableType = EDITABLE_VARIABLES_SELECT_ORDER[index]; const variableTypeConfig = EDITABLE_VARIABLES[editableType]; expect(option.value).toBe(editableType); expect(option.label).toBe(variableTypeConfig.name); expect(option.description).toBe(variableTypeConfig.description); }); }); }); describe('when groupByVariable is disabled', () => { it('should contain all editable variable types except groupby', () => { const options = getVariableTypeSelectOptions(); expect(options).toHaveLength(Object.keys(EDITABLE_VARIABLES).length - 1); EDITABLE_VARIABLES_SELECT_ORDER.forEach((type) => { expect(EDITABLE_VARIABLES).toHaveProperty(type); }); }); it('should return an array of selectable values for editable variable types', () => { const options = getVariableTypeSelectOptions(); expect(options).toHaveLength(7); options.forEach((option, index) => { const editableType = EDITABLE_VARIABLES_SELECT_ORDER[index]; const variableTypeConfig = EDITABLE_VARIABLES[editableType]; expect(option.value).toBe(editableType); expect(option.label).toBe(variableTypeConfig.name); expect(option.description).toBe(variableTypeConfig.description); }); }); }); }); describe('getVariableEditor', () => { beforeEach(() => { jest.clearAllMocks(); }); it.each(Object.keys(EDITABLE_VARIABLES) as EditableVariableType[])( 'should define an editor for variable type "%s"', (type) => { const editor = getVariableEditor(type); expect(editor).toBeDefined(); } ); it.each([ ['custom', CustomVariableEditor], ['query', QueryVariableEditor], ['constant', ConstantVariableEditor], ['interval', IntervalVariableEditor], ['datasource', DataSourceVariableEditor], ['adhoc', AdHocFiltersVariableEditor], ['groupby', GroupByVariableEditor], ['textbox', TextBoxVariableEditor], ])('should return the correct editor for variable type "%s"', (type, ExpectedVariableEditor) => { expect(getVariableEditor(type as EditableVariableType)).toBe(ExpectedVariableEditor); }); }); describe('getVariableScene', () => { beforeAll(() => { setTemplateSrv(templateSrv); }); it.each(Object.keys(EDITABLE_VARIABLES) as EditableVariableType[])( 'should define a scene object for every variable type', (type) => { const variable = getVariableScene(type, { name: 'foo' }); expect(variable).toBeDefined(); } ); it.each([ ['custom', CustomVariable], ['query', QueryVariable], ['interval', IntervalVariable], ['datasource', DataSourceVariable], ['adhoc', AdHocFiltersVariable], ['groupby', GroupByVariable], ['textbox', TextBoxVariable], ])('should return the scene variable instance for the given editable variable type', (type, instanceType) => { const initialState = { name: 'MyVariable' }; const sceneVariable = getVariableScene(type as EditableVariableType, initialState); expect(sceneVariable).toBeInstanceOf(instanceType); expect(sceneVariable.state.name).toBe(initialState.name); expect(sceneVariable.state.hide).toBe(undefined); }); it('should return the scene variable instance for the constant editable variable type', () => { const initialState = { name: 'MyVariable' }; const sceneVariable = getVariableScene('constant' as EditableVariableType, initialState); expect(sceneVariable).toBeInstanceOf(ConstantVariable); expect(sceneVariable.state.name).toBe(initialState.name); expect(sceneVariable.state.hide).toBe(VariableHide.hideVariable); }); }); describe('hasVariableOptions', () => { it('should return true for scene variables with options property', () => { const variableWithOptions = new CustomVariable({ name: 'MyVariable', options: [{ value: 'option1', label: 'Option 1' }], }); expect(hasVariableOptions(variableWithOptions)).toBe(true); }); it('should return false for scene variables without options property', () => { const variableWithoutOptions = new ConstantVariable({ name: 'MyVariable' }); expect(hasVariableOptions(variableWithoutOptions)).toBe(false); }); }); describe('getDefinition', () => { it('returns the correct definition for QueryVariable when definition is defined', () => { const model = new QueryVariable({ name: 'custom0', query: '', definition: 'legacy ABC query definition', }); expect(getDefinition(model)).toBe('legacy ABC query definition'); }); it('returns the correct definition for QueryVariable when definition is not defined', () => { const model = new QueryVariable({ name: 'custom0', query: 'ABC query', definition: '', }); expect(getDefinition(model)).toBe('ABC query'); }); it('returns the correct definition for DataSourceVariable', () => { const model = new DataSourceVariable({ name: 'ds0', pluginId: 'datasource-plugin', value: 'datasource-value', }); expect(getDefinition(model)).toBe('datasource-plugin'); }); it('returns the correct definition for CustomVariable', () => { const model = new CustomVariable({ name: 'custom0', query: 'Custom, A, B, C', }); expect(getDefinition(model)).toBe('Custom, A, B, C'); }); it('returns the correct definition for IntervalVariable', () => { const model = new IntervalVariable({ name: 'interval0', intervals: ['1m', '5m', '15m', '30m', '1h', '6h', '12h', '1d'], }); expect(getDefinition(model)).toBe('1m,5m,15m,30m,1h,6h,12h,1d'); }); it('returns the correct definition for TextBoxVariable', () => { const model = new TextBoxVariable({ name: 'textbox0', value: 'TextBox Value', }); expect(getDefinition(model)).toBe('TextBox Value'); }); it('returns the correct definition for ConstantVariable', () => { const model = new ConstantVariable({ name: 'constant0', value: 'Constant Value', }); expect(getDefinition(model)).toBe('Constant Value'); }); }); describe('getOptionDataSourceTypes', () => { it('should return all data source types when no data source types are specified', () => { const optionTypes = getOptionDataSourceTypes(); expect(optionTypes).toHaveLength(2); // in the old code we always had an empty option expect(optionTypes[0].value).toBe(''); expect(optionTypes[1].label).toBe('ds1'); }); }); describe('getNextAvailableId', () => { it('should return the initial ID for an empty array', () => { const sceneVariables = new SceneVariableSet({ variables: [], }); expect(getNextAvailableId('query', sceneVariables.state.variables)).toBe('query0'); }); it('should return a non-conflicting ID for a non-empty array', () => { const variable = new QueryVariable({ name: 'query0', label: 'test-label', description: 'test-desc', value: ['selected-value'], text: ['selected-value-text'], datasource: { uid: 'fake-std', type: 'fake-std' }, query: 'query', includeAll: true, allValue: 'test-all', isMulti: true, }); const sceneVariables = new SceneVariableSet({ variables: [variable], }); expect(getNextAvailableId('query', sceneVariables.state.variables)).toBe('query1'); }); }); describe('getVariableDefault', () => { it('should return a QueryVariable instance with the correct name', () => { const sceneVariables = new SceneVariableSet({ variables: [], }); const defaultVariable = getVariableDefault(sceneVariables.state.variables); expect(defaultVariable).toBeInstanceOf(QueryVariable); expect(defaultVariable.state.name).toBe('query0'); }); });