mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Variables: Adds description field (#29332)
* Variables: Adds description field * Refactor: Adds new Form components * Refactor: Fixes aria labels * Refactor: removes skipped tests * Refactor: Breaks out smaller select components * Refactor: removes gf-form div * Refactor: Breaks up several more selects into smaller components * Chore: Fixes typings
This commit is contained in:
parent
b7dc6a1a22
commit
04d857dfe6
@ -1,710 +0,0 @@
|
||||
import { e2e } from '@grafana/e2e';
|
||||
|
||||
// skipped scenario helper because of some perf issue upgrading cypress to 4.5.0 and splitted the whole test into smaller
|
||||
// several it functions. Very important to keep the order of these it functions because they have dependency in the order
|
||||
// https://github.com/cypress-io/cypress/issues/5987
|
||||
// https://github.com/cypress-io/cypress/issues/6023#issuecomment-574031655
|
||||
describe.skip('Variables', () => {
|
||||
let lastUid = '';
|
||||
let lastData = '';
|
||||
let variables: VariablesData[] = [
|
||||
{ name: 'query1', query: '*', label: 'query1-label', options: ['All', 'A', 'B', 'C'], selectedOption: 'A' },
|
||||
{
|
||||
name: 'query2',
|
||||
query: '$query1.*',
|
||||
label: 'query2-label',
|
||||
options: ['All', 'AA', 'AB', 'AC'],
|
||||
selectedOption: 'AA',
|
||||
},
|
||||
{
|
||||
name: 'query3',
|
||||
query: '$query1.$query2.*',
|
||||
label: 'query3-label',
|
||||
options: ['All', 'AAA', 'AAB', 'AAC'],
|
||||
selectedOption: 'AAA',
|
||||
},
|
||||
];
|
||||
|
||||
beforeEach(() => {
|
||||
e2e.flows.login('admin', 'admin');
|
||||
if (!lastUid || !lastData) {
|
||||
e2e.flows.addDataSource();
|
||||
e2e.flows.addDashboard();
|
||||
lastUid = 'test';
|
||||
lastData = 'test';
|
||||
} else {
|
||||
e2e.flows.openDashboard();
|
||||
}
|
||||
});
|
||||
|
||||
it(`asserts defaults`, () => {
|
||||
e2e.pages.Dashboard.Toolbar.toolbarItems('Dashboard settings').click();
|
||||
e2e.pages.Dashboard.Settings.General.sectionItems('Variables').click();
|
||||
e2e.pages.Dashboard.Settings.Variables.List.addVariableCTA().click();
|
||||
|
||||
assertDefaultsForNewVariable();
|
||||
});
|
||||
|
||||
variables.forEach((variable, index) => {
|
||||
it(`creates variable ${variable.name}`, () => {
|
||||
e2e.pages.Dashboard.Toolbar.toolbarItems('Dashboard settings').click();
|
||||
e2e.pages.Dashboard.Settings.General.sectionItems('Variables').click();
|
||||
|
||||
if (index === 0) {
|
||||
e2e.pages.Dashboard.Settings.Variables.List.addVariableCTA().click();
|
||||
} else {
|
||||
e2e.pages.Dashboard.Settings.Variables.List.newButton().click();
|
||||
}
|
||||
|
||||
const { name, label, query, options, selectedOption } = variable;
|
||||
e2e.getScenarioContext().then(({ lastAddedDataSource }: any) => {
|
||||
createQueryVariable({
|
||||
dataSourceName: lastAddedDataSource,
|
||||
name,
|
||||
label,
|
||||
query,
|
||||
options,
|
||||
selectedOption,
|
||||
});
|
||||
});
|
||||
|
||||
e2e.pages.Dashboard.Settings.General.saveDashBoard()
|
||||
.should('be.visible')
|
||||
.click();
|
||||
e2e.pages.SaveDashboardModal.save()
|
||||
.should('be.visible')
|
||||
.click();
|
||||
e2e.flows.assertSuccessNotification();
|
||||
|
||||
e2e.components.BackButton.backArrow()
|
||||
.should('be.visible')
|
||||
.click();
|
||||
});
|
||||
});
|
||||
|
||||
it(`asserts submenus`, () => {
|
||||
assertVariableLabelsAndComponents(variables);
|
||||
});
|
||||
|
||||
it(`asserts variable table`, () => {
|
||||
e2e.pages.Dashboard.Toolbar.toolbarItems('Dashboard settings')
|
||||
.should('be.visible')
|
||||
.click();
|
||||
e2e.pages.Dashboard.Settings.General.sectionItems('Variables')
|
||||
.should('be.visible')
|
||||
.click();
|
||||
|
||||
assertVariableTable(variables);
|
||||
});
|
||||
|
||||
it(`asserts variable selects`, () => {
|
||||
assertSelects(variables);
|
||||
});
|
||||
|
||||
it(`asserts duplicate variable`, () => {
|
||||
// mutates variables
|
||||
variables = assertDuplicateItem(variables);
|
||||
e2e.flows.saveDashboard();
|
||||
});
|
||||
|
||||
it(`asserts delete variable`, () => {
|
||||
// mutates variables
|
||||
variables = assertDeleteItem(variables);
|
||||
e2e.flows.saveDashboard();
|
||||
});
|
||||
|
||||
it(`asserts update variable`, () => {
|
||||
// mutates variables
|
||||
variables = assertUpdateItem(variables);
|
||||
e2e.components.BackButton.backArrow()
|
||||
.should('be.visible')
|
||||
.should('be.visible')
|
||||
.click();
|
||||
e2e.flows.saveDashboard();
|
||||
});
|
||||
|
||||
it(`asserts move variable down`, () => {
|
||||
e2e.pages.Dashboard.Toolbar.toolbarItems('Dashboard settings')
|
||||
.should('be.visible')
|
||||
.click();
|
||||
e2e.pages.Dashboard.Settings.General.sectionItems('Variables')
|
||||
.should('be.visible')
|
||||
.click();
|
||||
|
||||
// mutates variables
|
||||
variables = assertMoveDownItem(variables);
|
||||
e2e.flows.saveDashboard();
|
||||
});
|
||||
|
||||
it(`asserts move variable up`, () => {
|
||||
e2e.pages.Dashboard.Toolbar.toolbarItems('Dashboard settings')
|
||||
.should('be.visible')
|
||||
.click();
|
||||
e2e.pages.Dashboard.Settings.General.sectionItems('Variables')
|
||||
.should('be.visible')
|
||||
.click();
|
||||
|
||||
// mutates variables
|
||||
assertMoveUpItem(variables);
|
||||
});
|
||||
});
|
||||
|
||||
interface VariablesData {
|
||||
name: string;
|
||||
query: string;
|
||||
label: string;
|
||||
options: string[];
|
||||
selectedOption: string;
|
||||
}
|
||||
|
||||
interface CreateQueryVariableArguments extends VariablesData {
|
||||
dataSourceName: string;
|
||||
}
|
||||
|
||||
const assertDefaultsForNewVariable = () => {
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalNameInput().within(input => {
|
||||
expect(input.attr('placeholder')).equals('name');
|
||||
expect(input.val()).equals('');
|
||||
});
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalTypeSelect().within(select => {
|
||||
e2e()
|
||||
.get('option:selected')
|
||||
.should('have.text', 'Query');
|
||||
});
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalLabelInput().within(input => {
|
||||
expect(input.attr('placeholder')).equals('optional display name');
|
||||
expect(input.val()).equals('');
|
||||
});
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalHideSelect().within(select => {
|
||||
e2e()
|
||||
.get('option:selected')
|
||||
.should('have.text', '');
|
||||
});
|
||||
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsDataSourceSelect().within(select => {
|
||||
e2e()
|
||||
.get('option:selected')
|
||||
.should('have.text', '');
|
||||
});
|
||||
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsQueryInput().should('not.exist');
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsRefreshSelect().within(select => {
|
||||
e2e()
|
||||
.get('option:selected')
|
||||
.should('have.text', 'Never');
|
||||
});
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsRegExInput().within(input => {
|
||||
expect(input.attr('placeholder')).equals('/.*-(.*)-.*/');
|
||||
expect(input.val()).equals('');
|
||||
});
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsSortSelect().within(select => {
|
||||
e2e()
|
||||
.get('option:selected')
|
||||
.should('have.text', 'Disabled');
|
||||
});
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.selectionOptionsMultiSwitch().within(select => {
|
||||
e2e()
|
||||
.get('input')
|
||||
.should('not.be.checked');
|
||||
});
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.selectionOptionsIncludeAllSwitch().within(select => {
|
||||
e2e()
|
||||
.get('input')
|
||||
.should('not.be.checked');
|
||||
});
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.QueryVariable.valueGroupsTagsEnabledSwitch().within(select => {
|
||||
e2e()
|
||||
.get('input')
|
||||
.should('not.be.checked');
|
||||
});
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.previewOfValuesOption().should('not.exist');
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.selectionOptionsCustomAllInput().should('not.exist');
|
||||
};
|
||||
|
||||
const createQueryVariable = ({ name, label, dataSourceName, query }: CreateQueryVariableArguments) => {
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalNameInput().should('be.visible');
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalNameInput().type(name);
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalLabelInput().type(label);
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsDataSourceSelect()
|
||||
.select(`${dataSourceName}`)
|
||||
.blur();
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsQueryInput()
|
||||
.type(query)
|
||||
.blur();
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.previewOfValuesOption().should('exist');
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.selectionOptionsMultiSwitch()
|
||||
.click()
|
||||
.within(() => {
|
||||
e2e()
|
||||
.get('input')
|
||||
.should('be.checked');
|
||||
});
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.selectionOptionsIncludeAllSwitch()
|
||||
.click()
|
||||
.within(() => {
|
||||
e2e()
|
||||
.get('input')
|
||||
.should('be.checked');
|
||||
});
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.selectionOptionsCustomAllInput().within(input => {
|
||||
expect(input.attr('placeholder')).equals('blank = auto');
|
||||
expect(input.val()).equals('');
|
||||
});
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.submitButton().click();
|
||||
};
|
||||
|
||||
const assertVariableLabelAndComponent = ({ label, options, selectedOption }: VariablesData) => {
|
||||
e2e.pages.Dashboard.SubMenu.submenuItemLabels(label).should('be.visible');
|
||||
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownValueLinkTexts(selectedOption)
|
||||
.should('be.visible')
|
||||
.click();
|
||||
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownDropDown().should('be.visible');
|
||||
for (let optionIndex = 0; optionIndex < options.length; optionIndex++) {
|
||||
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownOptionTexts(options[optionIndex]).should('be.visible');
|
||||
}
|
||||
};
|
||||
|
||||
const assertVariableLabelsAndComponents = (args: VariablesData[]) => {
|
||||
e2e.pages.Dashboard.SubMenu.submenuItem().should('have.length', args.length);
|
||||
for (let index = 0; index < args.length; index++) {
|
||||
e2e.pages.Dashboard.SubMenu.submenuItem()
|
||||
.eq(index)
|
||||
.within(() => {
|
||||
e2e()
|
||||
.get('label')
|
||||
.contains(args[index].name);
|
||||
});
|
||||
assertVariableLabelAndComponent(args[index]);
|
||||
}
|
||||
};
|
||||
|
||||
const assertVariableTableRow = ({ name, query }: VariablesData, index: number, length: number) => {
|
||||
e2e.pages.Dashboard.Settings.Variables.List.tableRowNameFields(name)
|
||||
.should('exist')
|
||||
.contains(name);
|
||||
e2e.pages.Dashboard.Settings.Variables.List.tableRowDefinitionFields(name)
|
||||
.should('exist')
|
||||
.contains(query);
|
||||
if (index !== length - 1) {
|
||||
e2e.pages.Dashboard.Settings.Variables.List.tableRowArrowDownButtons(name).should('exist');
|
||||
}
|
||||
if (index !== 0) {
|
||||
e2e.pages.Dashboard.Settings.Variables.List.tableRowArrowUpButtons(name).should('exist');
|
||||
}
|
||||
e2e.pages.Dashboard.Settings.Variables.List.tableRowDuplicateButtons(name).should('exist');
|
||||
e2e.pages.Dashboard.Settings.Variables.List.tableRowRemoveButtons(name).should('exist');
|
||||
};
|
||||
|
||||
const assertVariableTable = (args: VariablesData[]) => {
|
||||
e2e.pages.Dashboard.Settings.Variables.List.table()
|
||||
.should('be.visible')
|
||||
.within(() => {
|
||||
e2e()
|
||||
.get('tbody > tr')
|
||||
.should('have.length', args.length);
|
||||
});
|
||||
|
||||
for (let index = 0; index < args.length; index++) {
|
||||
assertVariableTableRow(args[index], index, args.length);
|
||||
}
|
||||
};
|
||||
|
||||
const assertSelects = (variables: VariablesData[]) => {
|
||||
// Values in submenus should be
|
||||
// query1: [A] query2: [AA] query3: [AAA]
|
||||
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownValueLinkTexts('A')
|
||||
.should('be.visible')
|
||||
.click();
|
||||
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownOptionTexts('A')
|
||||
.should('be.visible')
|
||||
.click();
|
||||
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownOptionTexts('B')
|
||||
.should('be.visible')
|
||||
.click();
|
||||
e2e.pages.Dashboard.Toolbar.navBar().click();
|
||||
// Values in submenus should be
|
||||
// query1: [B] query2: [All] query3: [All]
|
||||
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownValueLinkTexts('All')
|
||||
.should('be.visible')
|
||||
.should('have.length', 2);
|
||||
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownValueLinkTexts('All')
|
||||
.eq(0)
|
||||
.should('be.visible')
|
||||
.click();
|
||||
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownOptionTexts('BA').should('be.visible');
|
||||
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownOptionTexts('BB').should('be.visible');
|
||||
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownOptionTexts('BC').should('be.visible');
|
||||
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownOptionTexts('BB')
|
||||
.should('be.visible')
|
||||
.click();
|
||||
e2e.pages.Dashboard.Toolbar.navBar()
|
||||
.should('be.visible')
|
||||
.click();
|
||||
// Values in submenus should be
|
||||
// query1: [B] query2: [BB] query3: [All]
|
||||
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownValueLinkTexts('All')
|
||||
.should('be.visible')
|
||||
.should('have.length', 1);
|
||||
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownValueLinkTexts('All')
|
||||
.eq(0)
|
||||
.should('be.visible')
|
||||
.click();
|
||||
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownOptionTexts('BBA').should('be.visible');
|
||||
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownOptionTexts('BBB').should('be.visible');
|
||||
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownOptionTexts('BBC').should('be.visible');
|
||||
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownOptionTexts('BBB')
|
||||
.should('be.visible')
|
||||
.click();
|
||||
e2e.pages.Dashboard.Toolbar.navBar()
|
||||
.should('be.visible')
|
||||
.click();
|
||||
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownValueLinkTexts('All').should('have.length', 0);
|
||||
// Values in submenus should be
|
||||
// query1: [B] query2: [BB] query3: [BBB]
|
||||
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownValueLinkTexts('BB')
|
||||
.should('be.visible')
|
||||
.click();
|
||||
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownOptionTexts('BA').should('be.visible');
|
||||
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownOptionTexts('BB').should('be.visible');
|
||||
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownOptionTexts('BC').should('be.visible');
|
||||
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownOptionTexts('BC')
|
||||
.should('be.visible')
|
||||
.click();
|
||||
e2e.pages.Dashboard.Toolbar.navBar()
|
||||
.should('be.visible')
|
||||
.click();
|
||||
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownValueLinkTexts('BB + BC')
|
||||
.should('be.visible')
|
||||
.should('have.length', 1);
|
||||
// Values in submenus should be
|
||||
// query1: [B] query2: [BB + BC] query3: [BBB]
|
||||
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownValueLinkTexts('BBB')
|
||||
.should('be.visible')
|
||||
.click();
|
||||
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownOptionTexts('BBA').should('be.visible');
|
||||
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownOptionTexts('BBB').should('be.visible');
|
||||
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownOptionTexts('BBC').should('be.visible');
|
||||
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownOptionTexts('BCA').should('be.visible');
|
||||
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownOptionTexts('BCB').should('be.visible');
|
||||
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownOptionTexts('BCC').should('be.visible');
|
||||
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownOptionTexts('BCC')
|
||||
.should('be.visible')
|
||||
.click();
|
||||
e2e.pages.Dashboard.Toolbar.navBar()
|
||||
.should('be.visible')
|
||||
.click();
|
||||
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownValueLinkTexts('BBB + BCC')
|
||||
.should('be.visible')
|
||||
.should('have.length', 1);
|
||||
// Values in submenus should be
|
||||
// query1: [B] query2: [BB + BC] query3: [BBB + BCC]
|
||||
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownValueLinkTexts('BB + BC')
|
||||
.should('be.visible')
|
||||
.click();
|
||||
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownOptionTexts('BA').should('be.visible');
|
||||
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownOptionTexts('BB').should('be.visible');
|
||||
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownOptionTexts('BC').should('be.visible');
|
||||
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownOptionTexts('BA')
|
||||
.should('be.visible')
|
||||
.click();
|
||||
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownOptionTexts('BB')
|
||||
.should('be.visible')
|
||||
.click();
|
||||
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownOptionTexts('BC')
|
||||
.should('be.visible')
|
||||
.click();
|
||||
e2e.pages.Dashboard.Toolbar.navBar()
|
||||
.should('be.visible')
|
||||
.click();
|
||||
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownValueLinkTexts('BA')
|
||||
.should('be.visible')
|
||||
.should('have.length', 1);
|
||||
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownValueLinkTexts('All')
|
||||
.should('be.visible')
|
||||
.should('have.length', 1);
|
||||
// Values in submenus should be
|
||||
// query1: [B] query2: [BA] query3: [All]
|
||||
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownValueLinkTexts('B')
|
||||
.should('be.visible')
|
||||
.click();
|
||||
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownOptionTexts('A')
|
||||
.should('be.visible')
|
||||
.should('be.visible');
|
||||
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownOptionTexts('B')
|
||||
.should('be.visible')
|
||||
.should('be.visible');
|
||||
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownOptionTexts('C')
|
||||
.should('be.visible')
|
||||
.should('be.visible');
|
||||
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownOptionTexts('A')
|
||||
.should('be.visible')
|
||||
.click();
|
||||
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownOptionTexts('B')
|
||||
.should('be.visible')
|
||||
.click();
|
||||
e2e.pages.Dashboard.Toolbar.navBar()
|
||||
.should('be.visible')
|
||||
.click();
|
||||
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownValueLinkTexts('A')
|
||||
.should('be.visible')
|
||||
.should('have.length', 1);
|
||||
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownValueLinkTexts('All')
|
||||
.should('be.visible')
|
||||
.should('have.length', 2);
|
||||
// Values in submenus should be
|
||||
// query1: [A] query2: [All] query3: [All]
|
||||
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownValueLinkTexts('All')
|
||||
.eq(0)
|
||||
.should('be.visible')
|
||||
.click();
|
||||
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownOptionTexts('AA')
|
||||
.should('be.visible')
|
||||
.click();
|
||||
e2e.pages.Dashboard.Toolbar.navBar()
|
||||
.should('be.visible')
|
||||
.click();
|
||||
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownValueLinkTexts('A')
|
||||
.should('be.visible')
|
||||
.should('have.length', 1);
|
||||
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownValueLinkTexts('AA')
|
||||
.should('be.visible')
|
||||
.should('have.length', 1);
|
||||
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownValueLinkTexts('All')
|
||||
.should('be.visible')
|
||||
.should('have.length', 1);
|
||||
// Values in submenus should be
|
||||
// query1: [A] query2: [AA] query3: [All]
|
||||
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownValueLinkTexts('All')
|
||||
.eq(0)
|
||||
.should('be.visible')
|
||||
.click();
|
||||
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownOptionTexts('AAA')
|
||||
.should('be.visible')
|
||||
.click();
|
||||
e2e.pages.Dashboard.Toolbar.navBar()
|
||||
.should('be.visible')
|
||||
.click();
|
||||
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownValueLinkTexts('A')
|
||||
.should('be.visible')
|
||||
.should('have.length', 1);
|
||||
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownValueLinkTexts('AA')
|
||||
.should('be.visible')
|
||||
.should('have.length', 1);
|
||||
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownValueLinkTexts('AAA')
|
||||
.should('be.visible')
|
||||
.should('have.length', 1);
|
||||
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownValueLinkTexts('All').should('have.length', 0);
|
||||
};
|
||||
|
||||
const assertDuplicateItem = (variables: VariablesData[]) => {
|
||||
const itemToDuplicate = variables[1];
|
||||
e2e.pages.Dashboard.Toolbar.toolbarItems('Dashboard settings').click();
|
||||
e2e.pages.Dashboard.Settings.General.sectionItems('Variables').click();
|
||||
e2e.pages.Dashboard.Settings.Variables.List.tableRowDuplicateButtons(itemToDuplicate.name)
|
||||
.should('exist')
|
||||
.click();
|
||||
e2e.pages.Dashboard.Settings.Variables.List.table()
|
||||
.should('be.visible')
|
||||
.within(() => {
|
||||
e2e()
|
||||
.get('tbody > tr')
|
||||
.should('have.length', variables.length + 1);
|
||||
});
|
||||
const newItem = { ...itemToDuplicate, name: `copy_of_${itemToDuplicate.name}` };
|
||||
assertVariableTableRow(newItem, variables.length - 1, variables.length);
|
||||
e2e.pages.Dashboard.Settings.Variables.List.tableRowNameFields(newItem.name).click();
|
||||
|
||||
newItem.label = `copy_of_${itemToDuplicate.label}`;
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalLabelInput()
|
||||
.clear()
|
||||
.type(newItem.label);
|
||||
|
||||
e2e.pages.Dashboard.Settings.General.saveDashBoard().click();
|
||||
e2e.pages.SaveDashboardModal.save().click();
|
||||
e2e.flows.assertSuccessNotification();
|
||||
|
||||
e2e.components.BackButton.backArrow()
|
||||
.should('be.visible')
|
||||
.click();
|
||||
|
||||
e2e.pages.Dashboard.SubMenu.submenuItemLabels(newItem.label).should('be.visible');
|
||||
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownValueLinkTexts(newItem.selectedOption)
|
||||
.should('be.visible')
|
||||
.eq(1)
|
||||
.click();
|
||||
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownDropDown().should('be.visible');
|
||||
for (let optionIndex = 0; optionIndex < newItem.options.length; optionIndex++) {
|
||||
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownOptionTexts(newItem.options[optionIndex]).should('be.visible');
|
||||
}
|
||||
|
||||
return [...variables, newItem];
|
||||
};
|
||||
|
||||
const assertDeleteItem = (variables: VariablesData[]) => {
|
||||
const itemToDelete = variables[1];
|
||||
e2e.pages.Dashboard.Toolbar.toolbarItems('Dashboard settings').click();
|
||||
e2e.pages.Dashboard.Settings.General.sectionItems('Variables').click();
|
||||
|
||||
e2e.pages.Dashboard.Settings.Variables.List.tableRowRemoveButtons(itemToDelete.name).click();
|
||||
e2e.pages.Dashboard.Settings.Variables.List.table()
|
||||
.should('be.visible')
|
||||
.within(() => {
|
||||
e2e()
|
||||
.get('tbody > tr')
|
||||
.should('have.length', variables.length - 1);
|
||||
});
|
||||
|
||||
e2e.pages.Dashboard.Settings.General.saveDashBoard().click();
|
||||
e2e.pages.SaveDashboardModal.save().click();
|
||||
e2e.flows.assertSuccessNotification();
|
||||
|
||||
e2e.components.BackButton.backArrow()
|
||||
.should('be.visible')
|
||||
.click();
|
||||
|
||||
e2e.pages.Dashboard.SubMenu.submenuItemLabels(itemToDelete.label).should('not.exist');
|
||||
|
||||
return variables.filter(item => item.name !== itemToDelete.name);
|
||||
};
|
||||
|
||||
const assertUpdateItem = (data: VariablesData[]) => {
|
||||
const variables = [...data];
|
||||
// updates an item to a constant variable instead
|
||||
const itemToUpdate = variables[1];
|
||||
let updatedItem = {
|
||||
...itemToUpdate,
|
||||
name: `update_of_${itemToUpdate.name}`,
|
||||
label: `update_of_${itemToUpdate.label}`,
|
||||
query: 'A constant',
|
||||
options: ['A constant'],
|
||||
selectedOption: 'undefined',
|
||||
};
|
||||
|
||||
variables[1] = updatedItem;
|
||||
|
||||
e2e.pages.Dashboard.Toolbar.toolbarItems('Dashboard settings').click();
|
||||
e2e.pages.Dashboard.Settings.General.sectionItems('Variables').click();
|
||||
e2e.pages.Dashboard.Settings.Variables.List.tableRowNameFields(itemToUpdate.name).click();
|
||||
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalNameInput().should('be.visible');
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalNameInput()
|
||||
.should('have.value', itemToUpdate.name)
|
||||
.clear()
|
||||
.type(updatedItem.name);
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalLabelInput()
|
||||
.should('have.value', itemToUpdate.label)
|
||||
.clear()
|
||||
.type(updatedItem.label);
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalTypeSelect().select('Constant');
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalHideSelect().within(select => {
|
||||
e2e()
|
||||
.get('option:selected')
|
||||
.should('have.text', 'Variable');
|
||||
});
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalHideSelect().select('');
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.ConstantVariable.constantOptionsQueryInput().type(updatedItem.query);
|
||||
|
||||
e2e.components.BackButton.backArrow()
|
||||
.should('be.visible')
|
||||
.click();
|
||||
|
||||
variables[1].selectedOption = 'A constant';
|
||||
assertVariableLabelAndComponent(variables[1]);
|
||||
|
||||
e2e.pages.Dashboard.Toolbar.toolbarItems('Dashboard settings').click();
|
||||
e2e.pages.Dashboard.Settings.General.sectionItems('Variables').click();
|
||||
|
||||
assertVariableTableRow(variables[1], 1, variables.length);
|
||||
|
||||
variables[1].selectedOption = 'A constant';
|
||||
|
||||
return variables;
|
||||
};
|
||||
|
||||
const assertMoveDownItem = (data: VariablesData[]) => {
|
||||
const variables = [...data];
|
||||
e2e.pages.Dashboard.Settings.Variables.List.tableRowArrowDownButtons(variables[0].name).click();
|
||||
const temp = { ...variables[0] };
|
||||
variables[0] = { ...variables[1] };
|
||||
variables[1] = temp;
|
||||
e2e.pages.Dashboard.Settings.Variables.List.table().within(() => {
|
||||
e2e()
|
||||
.get('tbody > tr')
|
||||
.eq(0)
|
||||
.within(() => {
|
||||
e2e()
|
||||
.get('td')
|
||||
.eq(0)
|
||||
.contains(variables[0].name);
|
||||
e2e()
|
||||
.get('td')
|
||||
.eq(1)
|
||||
.contains(variables[0].query);
|
||||
});
|
||||
e2e()
|
||||
.get('tbody > tr')
|
||||
.eq(1)
|
||||
.within(() => {
|
||||
e2e()
|
||||
.get('td')
|
||||
.eq(0)
|
||||
.contains(variables[1].name);
|
||||
e2e()
|
||||
.get('td')
|
||||
.eq(1)
|
||||
.contains(variables[1].query);
|
||||
});
|
||||
});
|
||||
|
||||
e2e.components.BackButton.backArrow()
|
||||
.should('be.visible')
|
||||
.click();
|
||||
|
||||
assertVariableLabelsAndComponents(variables);
|
||||
|
||||
return variables;
|
||||
};
|
||||
|
||||
const assertMoveUpItem = (data: VariablesData[]) => {
|
||||
const variables = [...data];
|
||||
e2e.pages.Dashboard.Settings.Variables.List.tableRowArrowUpButtons(variables[1].name).click();
|
||||
const temp = { ...variables[0] };
|
||||
variables[0] = { ...variables[1] };
|
||||
variables[1] = temp;
|
||||
e2e.pages.Dashboard.Settings.Variables.List.table().within(() => {
|
||||
e2e()
|
||||
.get('tbody > tr')
|
||||
.eq(0)
|
||||
.within(() => {
|
||||
e2e()
|
||||
.get('td')
|
||||
.eq(0)
|
||||
.contains(variables[0].name);
|
||||
e2e()
|
||||
.get('td')
|
||||
.eq(1)
|
||||
.contains(variables[0].query);
|
||||
});
|
||||
e2e()
|
||||
.get('tbody > tr')
|
||||
.eq(1)
|
||||
.within(() => {
|
||||
e2e()
|
||||
.get('td')
|
||||
.eq(0)
|
||||
.contains(variables[1].name);
|
||||
e2e()
|
||||
.get('td')
|
||||
.eq(1)
|
||||
.contains(variables[1].query);
|
||||
});
|
||||
});
|
||||
|
||||
e2e.components.BackButton.backArrow()
|
||||
.should('be.visible')
|
||||
.click();
|
||||
|
||||
assertVariableLabelsAndComponents(variables);
|
||||
|
||||
return variables;
|
||||
};
|
@ -1,4 +1,4 @@
|
||||
import React, { FC, ReactNode, HTMLProps } from 'react';
|
||||
import React, { FC, HTMLProps, ReactNode } from 'react';
|
||||
import { css, cx } from 'emotion';
|
||||
import { useStyles } from '../../themes';
|
||||
|
||||
@ -18,6 +18,7 @@ export const InlineFieldRow: FC<Props> = ({ children, className, ...htmlProps })
|
||||
const getStyles = () => {
|
||||
return {
|
||||
container: css`
|
||||
label: InlineFieldRow;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
|
@ -8,6 +8,10 @@ import { AdHocVariableEditorState } from './reducer';
|
||||
import { changeVariableDatasource, initAdHocVariableEditor } from './actions';
|
||||
import { connectWithStore } from 'app/core/utils/connectWithReduxStore';
|
||||
import { StoreState } from 'app/types';
|
||||
import { Alert, InlineFieldRow, VerticalGroup } from '@grafana/ui';
|
||||
import { VariableSectionHeader } from '../editor/VariableSectionHeader';
|
||||
import { VariableSelectField } from '../editor/VariableSelectField';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
|
||||
export interface OwnProps extends VariableEditorProps<AdHocVariableModel> {}
|
||||
|
||||
@ -27,45 +31,33 @@ export class AdHocVariableEditorUnConnected extends PureComponent<Props> {
|
||||
this.props.initAdHocVariableEditor();
|
||||
}
|
||||
|
||||
onDatasourceChanged = (event: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
this.props.changeVariableDatasource(event.target.value);
|
||||
onDatasourceChanged = (option: SelectableValue<string>) => {
|
||||
this.props.changeVariableDatasource(option.value ?? '');
|
||||
};
|
||||
|
||||
render() {
|
||||
const { variable, editor } = this.props;
|
||||
const dataSources = editor.extended?.dataSources ?? [];
|
||||
const infoText = editor.extended?.infoText ?? null;
|
||||
const options = dataSources.map(ds => ({ label: ds.text, value: ds.value ?? '' }));
|
||||
const value = options.find(o => o.value === variable.datasource) ?? options[0];
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="gf-form-group">
|
||||
<h5 className="section-heading">Options</h5>
|
||||
<div className="gf-form max-width-21">
|
||||
<span className="gf-form-label width-8">Data source</span>
|
||||
<div className="gf-form-select-wrapper max-width-14">
|
||||
<select
|
||||
className="gf-form-input"
|
||||
required
|
||||
onChange={this.onDatasourceChanged}
|
||||
value={variable.datasource ?? ''}
|
||||
aria-label="Variable editor Form AdHoc DataSource select"
|
||||
>
|
||||
{dataSources.map(ds => (
|
||||
<option key={ds.value ?? ''} value={ds.value ?? ''} label={ds.text}>
|
||||
{ds.text}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{infoText && (
|
||||
<div className="alert alert-info gf-form-group" aria-label="Variable editor Form Alert">
|
||||
{infoText}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
<VerticalGroup spacing="xs">
|
||||
<VariableSectionHeader name="Options" />
|
||||
<VerticalGroup spacing="sm">
|
||||
<InlineFieldRow>
|
||||
<VariableSelectField
|
||||
name="Data source"
|
||||
value={value}
|
||||
options={options}
|
||||
onChange={this.onDatasourceChanged}
|
||||
labelWidth={10}
|
||||
/>
|
||||
</InlineFieldRow>
|
||||
{infoText ? <Alert title={infoText} severity="info" /> : null}
|
||||
</VerticalGroup>
|
||||
</VerticalGroup>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,11 @@
|
||||
import React, { ChangeEvent, FocusEvent, PureComponent } from 'react';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { VerticalGroup } from '@grafana/ui';
|
||||
|
||||
import { ConstantVariableModel } from '../types';
|
||||
import { VariableEditorProps } from '../editor/types';
|
||||
import { VariableSectionHeader } from '../editor/VariableSectionHeader';
|
||||
import { VariableTextField } from '../editor/VariableTextField';
|
||||
|
||||
export interface Props extends VariableEditorProps<ConstantVariableModel> {}
|
||||
|
||||
@ -24,23 +27,19 @@ export class ConstantVariableEditor extends PureComponent<Props> {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
<div className="gf-form-group">
|
||||
<h5 className="section-heading">Constant options</h5>
|
||||
<div className="gf-form">
|
||||
<span className="gf-form-label">Value</span>
|
||||
<input
|
||||
type="text"
|
||||
className="gf-form-input"
|
||||
value={this.props.variable.query}
|
||||
onChange={this.onChange}
|
||||
onBlur={this.onBlur}
|
||||
placeholder="your metric prefix"
|
||||
aria-label={selectors.pages.Dashboard.Settings.Variables.Edit.ConstantVariable.constantOptionsQueryInput}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
<VerticalGroup spacing="xs">
|
||||
<VariableSectionHeader name="Constant Options" />
|
||||
<VariableTextField
|
||||
value={this.props.variable.query}
|
||||
name="Value"
|
||||
placeholder="your metric prefix"
|
||||
onChange={this.onChange}
|
||||
onBlur={this.onBlur}
|
||||
labelWidth={20}
|
||||
ariaLabel={selectors.pages.Dashboard.Settings.Variables.Edit.ConstantVariable.constantOptionsQueryInput}
|
||||
grow
|
||||
/>
|
||||
</VerticalGroup>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -4,9 +4,11 @@ import { SelectionOptionsEditor } from '../editor/SelectionOptionsEditor';
|
||||
import { OnPropChangeArguments, VariableEditorProps } from '../editor/types';
|
||||
import { connectWithStore } from 'app/core/utils/connectWithReduxStore';
|
||||
import { MapDispatchToProps, MapStateToProps } from 'react-redux';
|
||||
import { Field, TextArea } from '@grafana/ui';
|
||||
import { VerticalGroup } from '@grafana/ui';
|
||||
import { StoreState } from 'app/types';
|
||||
import { changeVariableMultiValue } from '../state/actions';
|
||||
import { VariableSectionHeader } from '../editor/VariableSectionHeader';
|
||||
import { VariableTextAreaField } from '../editor/VariableTextAreaField';
|
||||
|
||||
interface OwnProps extends VariableEditorProps<CustomVariableModel> {}
|
||||
|
||||
@ -40,31 +42,28 @@ class CustomVariableEditorUnconnected extends PureComponent<Props> {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
<div className="gf-form-group">
|
||||
<h5 className="section-heading">Custom Options</h5>
|
||||
<div className="gf-form">
|
||||
<Field label="Values separated by comma">
|
||||
<TextArea
|
||||
className="gf-form-input"
|
||||
value={this.props.variable.query}
|
||||
onChange={this.onChange}
|
||||
onBlur={this.onBlur}
|
||||
rows={5}
|
||||
cols={81}
|
||||
placeholder="1, 10, mykey : myvalue, myvalue, escaped\,value"
|
||||
required
|
||||
aria-label="Variable editor Form Custom Query field"
|
||||
/>
|
||||
</Field>
|
||||
</div>
|
||||
</div>
|
||||
<SelectionOptionsEditor
|
||||
variable={this.props.variable}
|
||||
onPropChange={this.onSelectionOptionsChange}
|
||||
onMultiChanged={this.props.changeVariableMultiValue}
|
||||
/>
|
||||
</>
|
||||
<VerticalGroup spacing="xs">
|
||||
<VariableSectionHeader name="Custom Options" />
|
||||
<VerticalGroup spacing="md">
|
||||
<VerticalGroup spacing="none">
|
||||
<VariableTextAreaField
|
||||
name="Values separated by comma"
|
||||
value={this.props.variable.query}
|
||||
placeholder="1, 10, mykey : myvalue, myvalue, escaped\,value"
|
||||
onChange={this.onChange}
|
||||
onBlur={this.onBlur}
|
||||
required
|
||||
width={50}
|
||||
labelWidth={27}
|
||||
/>
|
||||
</VerticalGroup>
|
||||
<SelectionOptionsEditor
|
||||
variable={this.props.variable}
|
||||
onPropChange={this.onSelectionOptionsChange}
|
||||
onMultiChanged={this.props.changeVariableMultiValue}
|
||||
/>{' '}
|
||||
</VerticalGroup>
|
||||
</VerticalGroup>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +1,20 @@
|
||||
import React, { ChangeEvent, FocusEvent, PureComponent } from 'react';
|
||||
import { MapDispatchToProps, MapStateToProps } from 'react-redux';
|
||||
import { InlineFieldRow, VerticalGroup } from '@grafana/ui';
|
||||
|
||||
import { DataSourceVariableModel, VariableWithMultiSupport } from '../types';
|
||||
import { OnPropChangeArguments, VariableEditorProps } from '../editor/types';
|
||||
import { SelectionOptionsEditor } from '../editor/SelectionOptionsEditor';
|
||||
import { InlineFormLabel } from '@grafana/ui';
|
||||
import { VariableEditorState } from '../editor/reducer';
|
||||
import { DataSourceVariableEditorState } from './reducer';
|
||||
import { initDataSourceVariableEditor } from './actions';
|
||||
import { MapDispatchToProps, MapStateToProps } from 'react-redux';
|
||||
import { StoreState } from '../../../types';
|
||||
import { connectWithStore } from '../../../core/utils/connectWithReduxStore';
|
||||
import { changeVariableMultiValue } from '../state/actions';
|
||||
import { VariableSectionHeader } from '../editor/VariableSectionHeader';
|
||||
import { VariableSelectField } from '../editor/VariableSelectField';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { VariableTextField } from '../editor/VariableTextField';
|
||||
|
||||
export interface OwnProps extends VariableEditorProps<DataSourceVariableModel> {}
|
||||
|
||||
@ -58,66 +62,58 @@ export class DataSourceVariableEditorUnConnected extends PureComponent<Props> {
|
||||
return value ?? '';
|
||||
};
|
||||
|
||||
onDataSourceTypeChanged = (event: ChangeEvent<HTMLSelectElement>) => {
|
||||
this.props.onPropChange({ propName: 'query', propValue: event.target.value, updateOptions: true });
|
||||
onDataSourceTypeChanged = (option: SelectableValue<string>) => {
|
||||
this.props.onPropChange({ propName: 'query', propValue: option.value, updateOptions: true });
|
||||
};
|
||||
|
||||
render() {
|
||||
const typeOptions = this.props.editor.extended?.dataSourceTypes?.length
|
||||
? this.props.editor.extended?.dataSourceTypes?.map(ds => ({ value: ds.value ?? '', label: ds.text }))
|
||||
: [];
|
||||
const typeValue = typeOptions.find(o => o.value === this.props.variable.query) ?? typeOptions[0];
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="gf-form-group">
|
||||
<h5 className="section-heading">Data source options</h5>
|
||||
|
||||
<div className="gf-form">
|
||||
<label className="gf-form-label width-12">Type</label>
|
||||
<div className="gf-form-select-wrapper max-width-18">
|
||||
<select
|
||||
className="gf-form-input"
|
||||
value={this.getSelectedDataSourceTypeValue()}
|
||||
<VerticalGroup spacing="xs">
|
||||
<VariableSectionHeader name="Data source options" />
|
||||
<VerticalGroup spacing="md">
|
||||
<VerticalGroup spacing="xs">
|
||||
<InlineFieldRow>
|
||||
<VariableSelectField
|
||||
name="Type"
|
||||
value={typeValue}
|
||||
options={typeOptions}
|
||||
onChange={this.onDataSourceTypeChanged}
|
||||
>
|
||||
{this.props.editor.extended?.dataSourceTypes?.length &&
|
||||
this.props.editor.extended?.dataSourceTypes?.map(ds => (
|
||||
<option key={ds.value ?? ''} value={ds.value ?? ''} label={ds.text}>
|
||||
{ds.text}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
labelWidth={10}
|
||||
/>
|
||||
</InlineFieldRow>
|
||||
<InlineFieldRow>
|
||||
<VariableTextField
|
||||
value={this.props.variable.regex}
|
||||
name="Instance name filter"
|
||||
placeholder="/.*-(.*)-.*/"
|
||||
onChange={this.onRegExChange}
|
||||
onBlur={this.onRegExBlur}
|
||||
labelWidth={20}
|
||||
tooltip={
|
||||
<div>
|
||||
Regex filter for which data source instances to choose from in the variable value dropdown. Leave
|
||||
empty for all.
|
||||
<br />
|
||||
<br />
|
||||
Example: <code>/^prod/</code>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</InlineFieldRow>
|
||||
</VerticalGroup>
|
||||
|
||||
<div className="gf-form">
|
||||
<InlineFormLabel
|
||||
width={12}
|
||||
tooltip={
|
||||
<div>
|
||||
Regex filter for which data source instances to choose from in the variable value dropdown. Leave
|
||||
empty for all.
|
||||
<br />
|
||||
<br />
|
||||
Example: <code>/^prod/</code>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
Instance name filter
|
||||
</InlineFormLabel>
|
||||
<input
|
||||
type="text"
|
||||
className="gf-form-input max-width-18"
|
||||
placeholder="/.*-(.*)-.*/"
|
||||
value={this.props.variable.regex}
|
||||
onChange={this.onRegExChange}
|
||||
onBlur={this.onRegExBlur}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<SelectionOptionsEditor
|
||||
variable={this.props.variable}
|
||||
onPropChange={this.onSelectionOptionsChange}
|
||||
onMultiChanged={this.props.changeVariableMultiValue}
|
||||
/>
|
||||
</>
|
||||
<SelectionOptionsEditor
|
||||
variable={this.props.variable}
|
||||
onPropChange={this.onSelectionOptionsChange}
|
||||
onMultiChanged={this.props.changeVariableMultiValue}
|
||||
/>
|
||||
</VerticalGroup>
|
||||
</VerticalGroup>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,8 @@ import React, { FC, useCallback, useState } from 'react';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
|
||||
import { VariableQueryProps } from 'app/types/plugins';
|
||||
import { InlineField, TextArea, useStyles } from '@grafana/ui';
|
||||
import { VariableTextAreaField } from './VariableTextAreaField';
|
||||
import { useStyles } from '@grafana/ui';
|
||||
import { GrafanaTheme } from '@grafana/data';
|
||||
import { css } from 'emotion';
|
||||
|
||||
@ -17,6 +18,7 @@ export const LegacyVariableQueryEditor: FC<VariableQueryProps> = ({ onChange, qu
|
||||
},
|
||||
[onChange]
|
||||
);
|
||||
|
||||
const onBlur = useCallback(
|
||||
(event: React.FormEvent<HTMLTextAreaElement>) => {
|
||||
onChange(event.currentTarget.value, event.currentTarget.value);
|
||||
@ -25,19 +27,17 @@ export const LegacyVariableQueryEditor: FC<VariableQueryProps> = ({ onChange, qu
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="gf-form">
|
||||
<InlineField label="Query" labelWidth={20} grow={false} className={styles.inlineFieldOverride}>
|
||||
<span hidden />
|
||||
</InlineField>
|
||||
<TextArea
|
||||
rows={getLineCount(value)}
|
||||
className="gf-form-input"
|
||||
<div className={styles.container}>
|
||||
<VariableTextAreaField
|
||||
name="Query"
|
||||
value={value}
|
||||
placeholder="metric name or tags query"
|
||||
width={100}
|
||||
onChange={onValueChange}
|
||||
onBlur={onBlur}
|
||||
placeholder="metric name or tags query"
|
||||
required
|
||||
aria-label={selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsQueryInput}
|
||||
labelWidth={20}
|
||||
ariaLabel={selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsQueryInput}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
@ -45,18 +45,10 @@ export const LegacyVariableQueryEditor: FC<VariableQueryProps> = ({ onChange, qu
|
||||
|
||||
function getStyles(theme: GrafanaTheme) {
|
||||
return {
|
||||
inlineFieldOverride: css`
|
||||
margin: 0;
|
||||
container: css`
|
||||
margin-bottom: ${theme.spacing.xs};
|
||||
`,
|
||||
};
|
||||
}
|
||||
|
||||
LegacyVariableQueryEditor.displayName = LEGACY_VARIABLE_QUERY_EDITOR_NAME;
|
||||
|
||||
const getLineCount = (value: any) => {
|
||||
if (value && typeof value === 'string') {
|
||||
return value.split('\n').length;
|
||||
}
|
||||
|
||||
return 1;
|
||||
};
|
||||
|
@ -1,12 +1,13 @@
|
||||
import React, { FunctionComponent, useCallback } from 'react';
|
||||
import { LegacyForms } from '@grafana/ui';
|
||||
import { InlineFieldRow, VerticalGroup } from '@grafana/ui';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
|
||||
import { VariableWithMultiSupport } from '../types';
|
||||
import { VariableEditorProps } from './types';
|
||||
import { toVariableIdentifier, VariableIdentifier } from '../state/types';
|
||||
|
||||
const { Switch } = LegacyForms;
|
||||
import { VariableSectionHeader } from './VariableSectionHeader';
|
||||
import { VariableSwitchField } from './VariableSwitchField';
|
||||
import { VariableTextField } from './VariableTextField';
|
||||
|
||||
export interface SelectionOptionsEditorProps<Model extends VariableWithMultiSupport = VariableWithMultiSupport>
|
||||
extends VariableEditorProps<Model> {
|
||||
@ -35,42 +36,39 @@ export const SelectionOptionsEditor: FunctionComponent<SelectionOptionsEditorPro
|
||||
[props.onPropChange]
|
||||
);
|
||||
return (
|
||||
<div className="section gf-form-group">
|
||||
<h5 className="section-heading">Selection Options</h5>
|
||||
<div className="section">
|
||||
<div aria-label={selectors.pages.Dashboard.Settings.Variables.Edit.General.selectionOptionsMultiSwitch}>
|
||||
<Switch
|
||||
label="Multi-value"
|
||||
labelClass="width-10"
|
||||
checked={props.variable.multi}
|
||||
onChange={onMultiChanged}
|
||||
tooltip={'Enables multiple values to be selected at the same time'}
|
||||
/>
|
||||
</div>
|
||||
<div aria-label={selectors.pages.Dashboard.Settings.Variables.Edit.General.selectionOptionsIncludeAllSwitch}>
|
||||
<Switch
|
||||
label="Include All option"
|
||||
labelClass="width-10"
|
||||
checked={props.variable.includeAll}
|
||||
onChange={onIncludeAllChanged}
|
||||
tooltip={'Enables an option to include all variables'}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<VerticalGroup spacing="none">
|
||||
<VariableSectionHeader name="Selection Options" />
|
||||
<InlineFieldRow>
|
||||
<VariableSwitchField
|
||||
value={props.variable.multi}
|
||||
name="Multi-value"
|
||||
tooltip="Enables multiple values to be selected at the same time"
|
||||
onChange={onMultiChanged}
|
||||
ariaLabel={selectors.pages.Dashboard.Settings.Variables.Edit.General.selectionOptionsMultiSwitch}
|
||||
/>
|
||||
</InlineFieldRow>
|
||||
<InlineFieldRow>
|
||||
<VariableSwitchField
|
||||
value={props.variable.includeAll}
|
||||
name="Include All option"
|
||||
tooltip="Enables an option to include all variables"
|
||||
onChange={onIncludeAllChanged}
|
||||
ariaLabel={selectors.pages.Dashboard.Settings.Variables.Edit.General.selectionOptionsIncludeAllSwitch}
|
||||
/>
|
||||
</InlineFieldRow>
|
||||
{props.variable.includeAll && (
|
||||
<div className="gf-form">
|
||||
<span className="gf-form-label width-10">Custom all value</span>
|
||||
<input
|
||||
type="text"
|
||||
className="gf-form-input max-width-15"
|
||||
<InlineFieldRow>
|
||||
<VariableTextField
|
||||
value={props.variable.allValue ?? ''}
|
||||
onChange={onAllValueChanged}
|
||||
name="Custom all value"
|
||||
placeholder="blank = auto"
|
||||
aria-label={selectors.pages.Dashboard.Settings.Variables.Edit.General.selectionOptionsCustomAllInput}
|
||||
ariaLabel={selectors.pages.Dashboard.Settings.Variables.Edit.General.selectionOptionsCustomAllInput}
|
||||
labelWidth={20}
|
||||
/>
|
||||
</div>
|
||||
</InlineFieldRow>
|
||||
)}
|
||||
</div>
|
||||
</VerticalGroup>
|
||||
);
|
||||
};
|
||||
SelectionOptionsEditor.displayName = 'SelectionOptionsEditor';
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React, { ChangeEvent, FormEvent, PureComponent } from 'react';
|
||||
import isEqual from 'lodash/isEqual';
|
||||
import { AppEvents, LoadingState, VariableType } from '@grafana/data';
|
||||
import { Icon, InlineFormLabel } from '@grafana/ui';
|
||||
import { AppEvents, LoadingState, SelectableValue, VariableType } from '@grafana/data';
|
||||
import { Button, Icon, InlineFieldRow, VerticalGroup } from '@grafana/ui';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
|
||||
import { variableAdapters } from '../adapters';
|
||||
@ -18,7 +18,11 @@ import { connectWithStore } from '../../../core/utils/connectWithReduxStore';
|
||||
import { OnPropChangeArguments } from './types';
|
||||
import { changeVariableProp, changeVariableType } from '../state/sharedReducer';
|
||||
import { updateOptions } from '../state/actions';
|
||||
import { getVariableTypes } from '../utils';
|
||||
import { VariableTextField } from './VariableTextField';
|
||||
import { VariableSectionHeader } from './VariableSectionHeader';
|
||||
import { hasOptions } from '../guard';
|
||||
import { VariableTypeSelect } from './VariableTypeSelect';
|
||||
import { VariableHideSelect } from './VariableHideSelect';
|
||||
|
||||
export interface OwnProps {
|
||||
identifier: VariableIdentifier;
|
||||
@ -63,11 +67,11 @@ export class VariableEditorEditorUnConnected extends PureComponent<Props> {
|
||||
this.props.changeVariableName(this.props.identifier, event.target.value);
|
||||
};
|
||||
|
||||
onTypeChange = (event: ChangeEvent<HTMLSelectElement>) => {
|
||||
event.preventDefault();
|
||||
this.props.changeVariableType(
|
||||
toVariablePayload(this.props.identifier, { newType: event.target.value as VariableType })
|
||||
);
|
||||
onTypeChange = (option: SelectableValue<VariableType>) => {
|
||||
if (!option.value) {
|
||||
return;
|
||||
}
|
||||
this.props.changeVariableType(toVariablePayload(this.props.identifier, { newType: option.value }));
|
||||
};
|
||||
|
||||
onLabelChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
@ -77,12 +81,17 @@ export class VariableEditorEditorUnConnected extends PureComponent<Props> {
|
||||
);
|
||||
};
|
||||
|
||||
onHideChange = (event: ChangeEvent<HTMLSelectElement>) => {
|
||||
event.preventDefault();
|
||||
onDescriptionChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
this.props.changeVariableProp(
|
||||
toVariablePayload(this.props.identifier, { propName: 'description', propValue: event.target.value })
|
||||
);
|
||||
};
|
||||
|
||||
onHideChange = (option: SelectableValue<VariableHide>) => {
|
||||
this.props.changeVariableProp(
|
||||
toVariablePayload(this.props.identifier, {
|
||||
propName: 'hide',
|
||||
propValue: parseInt(event.target.value, 10) as VariableHide,
|
||||
propValue: option.value,
|
||||
})
|
||||
);
|
||||
};
|
||||
@ -114,100 +123,64 @@ export class VariableEditorEditorUnConnected extends PureComponent<Props> {
|
||||
return (
|
||||
<div>
|
||||
<form aria-label="Variable editor Form" onSubmit={this.onHandleSubmit}>
|
||||
<h5 className="section-heading">General</h5>
|
||||
<div className="gf-form-group">
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form max-width-19">
|
||||
<span className="gf-form-label width-6">Name</span>
|
||||
<input
|
||||
type="text"
|
||||
className="gf-form-input"
|
||||
name="name"
|
||||
placeholder="name"
|
||||
required
|
||||
<VerticalGroup spacing="lg">
|
||||
<VerticalGroup spacing="none">
|
||||
<VariableSectionHeader name="General" />
|
||||
<InlineFieldRow>
|
||||
<VariableTextField
|
||||
value={this.props.editor.name}
|
||||
onChange={this.onNameChange}
|
||||
aria-label={selectors.pages.Dashboard.Settings.Variables.Edit.General.generalNameInput}
|
||||
name="Name"
|
||||
placeholder="name"
|
||||
required
|
||||
ariaLabel={selectors.pages.Dashboard.Settings.Variables.Edit.General.generalNameInput}
|
||||
/>
|
||||
</div>
|
||||
<div className="gf-form max-width-19">
|
||||
<InlineFormLabel width={6} tooltip={variableAdapters.get(this.props.variable.type).description}>
|
||||
Type
|
||||
</InlineFormLabel>
|
||||
<div className="gf-form-select-wrapper max-width-17">
|
||||
<select
|
||||
className="gf-form-input"
|
||||
value={this.props.variable.type}
|
||||
onChange={this.onTypeChange}
|
||||
aria-label={selectors.pages.Dashboard.Settings.Variables.Edit.General.generalTypeSelect}
|
||||
>
|
||||
{getVariableTypes().map(({ label, value }) => (
|
||||
<option key={value} label={label} value={value}>
|
||||
{name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<VariableTypeSelect onChange={this.onTypeChange} type={this.props.variable.type} />
|
||||
</InlineFieldRow>
|
||||
|
||||
{this.props.editor.errors.name && (
|
||||
<div className="gf-form">
|
||||
<span className="gf-form-label gf-form-label--error">{this.props.editor.errors.name}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{this.props.editor.errors.name && (
|
||||
<div className="gf-form">
|
||||
<span className="gf-form-label gf-form-label--error">{this.props.editor.errors.name}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form max-width-19">
|
||||
<span className="gf-form-label width-6">Label</span>
|
||||
<input
|
||||
type="text"
|
||||
className="gf-form-input"
|
||||
<InlineFieldRow>
|
||||
<VariableTextField
|
||||
value={this.props.variable.label ?? ''}
|
||||
onChange={this.onLabelChange}
|
||||
name="Label"
|
||||
placeholder="optional display name"
|
||||
aria-label={selectors.pages.Dashboard.Settings.Variables.Edit.General.generalLabelInput}
|
||||
ariaLabel={selectors.pages.Dashboard.Settings.Variables.Edit.General.generalLabelInput}
|
||||
/>
|
||||
</div>
|
||||
<div className="gf-form max-width-19">
|
||||
<span className="gf-form-label width-6">Hide</span>
|
||||
<div className="gf-form-select-wrapper max-width-15">
|
||||
<select
|
||||
className="gf-form-input"
|
||||
value={this.props.variable.hide}
|
||||
onChange={this.onHideChange}
|
||||
aria-label={selectors.pages.Dashboard.Settings.Variables.Edit.General.generalHideSelect}
|
||||
>
|
||||
<option label="" value={VariableHide.dontHide}>
|
||||
{''}
|
||||
</option>
|
||||
<option label="Label" value={VariableHide.hideLabel}>
|
||||
Label
|
||||
</option>
|
||||
<option label="Variable" value={VariableHide.hideVariable}>
|
||||
Variable
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<VariableHideSelect onChange={this.onHideChange} hide={this.props.variable.hide} />
|
||||
</InlineFieldRow>
|
||||
|
||||
{EditorToRender && <EditorToRender variable={this.props.variable} onPropChange={this.onPropChanged} />}
|
||||
<VariableTextField
|
||||
name="Description"
|
||||
value={variable.description ?? ''}
|
||||
placeholder="descriptive text"
|
||||
onChange={this.onDescriptionChange}
|
||||
grow
|
||||
/>
|
||||
</VerticalGroup>
|
||||
|
||||
<VariableValuesPreview variable={this.props.variable} />
|
||||
{EditorToRender && <EditorToRender variable={this.props.variable} onPropChange={this.onPropChanged} />}
|
||||
|
||||
<div className="gf-form-button-row p-y-0">
|
||||
<button
|
||||
type="submit"
|
||||
className="btn btn-primary"
|
||||
aria-label={selectors.pages.Dashboard.Settings.Variables.Edit.General.submitButton}
|
||||
disabled={loading}
|
||||
>
|
||||
Update
|
||||
{loading ? <Icon className="spin-clockwise" name="sync" size="sm" style={{ marginLeft: '2px' }} /> : null}
|
||||
</button>
|
||||
</div>
|
||||
{hasOptions(this.props.variable) ? <VariableValuesPreview variable={this.props.variable} /> : null}
|
||||
|
||||
<VerticalGroup spacing="none">
|
||||
<Button
|
||||
type="submit"
|
||||
aria-label={selectors.pages.Dashboard.Settings.Variables.Edit.General.submitButton}
|
||||
disabled={loading}
|
||||
>
|
||||
Update
|
||||
{loading ? (
|
||||
<Icon className="spin-clockwise" name="sync" size="sm" style={{ marginLeft: '2px' }} />
|
||||
) : null}
|
||||
</Button>
|
||||
</VerticalGroup>
|
||||
</VerticalGroup>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
|
31
public/app/features/variables/editor/VariableHideSelect.tsx
Normal file
31
public/app/features/variables/editor/VariableHideSelect.tsx
Normal file
@ -0,0 +1,31 @@
|
||||
import React, { PropsWithChildren, useMemo } from 'react';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
|
||||
import { VariableSelectField } from '../editor/VariableSelectField';
|
||||
import { VariableHide } from '../types';
|
||||
|
||||
interface Props {
|
||||
onChange: (option: SelectableValue<VariableHide>) => void;
|
||||
hide: VariableHide;
|
||||
}
|
||||
|
||||
const HIDE_OPTIONS = [
|
||||
{ label: '', value: VariableHide.dontHide },
|
||||
{ label: 'Label', value: VariableHide.hideLabel },
|
||||
{ label: 'Variable', value: VariableHide.hideVariable },
|
||||
];
|
||||
|
||||
export function VariableHideSelect({ onChange, hide }: PropsWithChildren<Props>) {
|
||||
const value = useMemo(() => HIDE_OPTIONS.find(o => o.value === hide) ?? HIDE_OPTIONS[0], [hide]);
|
||||
|
||||
return (
|
||||
<VariableSelectField
|
||||
name="Hide"
|
||||
value={value}
|
||||
options={HIDE_OPTIONS}
|
||||
onChange={onChange}
|
||||
ariaLabel={selectors.pages.Dashboard.Settings.Variables.Edit.General.generalHideSelect}
|
||||
/>
|
||||
);
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
import React, { PropsWithChildren, ReactElement } from 'react';
|
||||
import { useStyles } from '@grafana/ui';
|
||||
import { GrafanaTheme } from '@grafana/data';
|
||||
import { css } from 'emotion';
|
||||
|
||||
interface VariableSectionHeaderProps {
|
||||
name: string;
|
||||
}
|
||||
|
||||
export function VariableSectionHeader({ name }: PropsWithChildren<VariableSectionHeaderProps>): ReactElement {
|
||||
const styles = useStyles(getStyles);
|
||||
|
||||
return <h5 className={styles.sectionHeading}>{name}</h5>;
|
||||
}
|
||||
|
||||
function getStyles(theme: GrafanaTheme) {
|
||||
return {
|
||||
sectionHeading: css`
|
||||
label: sectionHeading;
|
||||
font-size: ${theme.typography.size.md};
|
||||
margin-bottom: ${theme.spacing.sm};
|
||||
`,
|
||||
};
|
||||
}
|
53
public/app/features/variables/editor/VariableSelectField.tsx
Normal file
53
public/app/features/variables/editor/VariableSelectField.tsx
Normal file
@ -0,0 +1,53 @@
|
||||
import React, { PropsWithChildren, ReactElement } from 'react';
|
||||
import { InlineFormLabel, Select, useStyles } from '@grafana/ui';
|
||||
import { GrafanaTheme, SelectableValue } from '@grafana/data';
|
||||
import { css } from 'emotion';
|
||||
|
||||
interface VariableSelectFieldProps<T> {
|
||||
name: string;
|
||||
value: SelectableValue<T>;
|
||||
options: Array<SelectableValue<T>>;
|
||||
onChange: (option: SelectableValue<T>) => void;
|
||||
tooltip?: string;
|
||||
ariaLabel?: string;
|
||||
width?: number;
|
||||
labelWidth?: number;
|
||||
}
|
||||
|
||||
export function VariableSelectField({
|
||||
name,
|
||||
value,
|
||||
options,
|
||||
tooltip,
|
||||
onChange,
|
||||
ariaLabel,
|
||||
width,
|
||||
labelWidth,
|
||||
}: PropsWithChildren<VariableSelectFieldProps<any>>): ReactElement {
|
||||
const styles = useStyles(getStyles);
|
||||
|
||||
return (
|
||||
<>
|
||||
<InlineFormLabel width={labelWidth ?? 6} tooltip={tooltip}>
|
||||
{name}
|
||||
</InlineFormLabel>
|
||||
<div aria-label={ariaLabel}>
|
||||
<Select
|
||||
onChange={onChange}
|
||||
value={value}
|
||||
width={width ?? 25}
|
||||
options={options}
|
||||
className={styles.selectContainer}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function getStyles(theme: GrafanaTheme) {
|
||||
return {
|
||||
selectContainer: css`
|
||||
margin-right: ${theme.spacing.xs};
|
||||
`,
|
||||
};
|
||||
}
|
39
public/app/features/variables/editor/VariableSwitchField.tsx
Normal file
39
public/app/features/variables/editor/VariableSwitchField.tsx
Normal file
@ -0,0 +1,39 @@
|
||||
import React, { ChangeEvent, PropsWithChildren, ReactElement } from 'react';
|
||||
import { InlineField, Switch, useStyles } from '@grafana/ui';
|
||||
import { GrafanaTheme } from '@grafana/data';
|
||||
import { css } from 'emotion';
|
||||
|
||||
interface VariableSwitchFieldProps {
|
||||
value: boolean;
|
||||
name: string;
|
||||
onChange: (event: ChangeEvent<HTMLInputElement>) => void;
|
||||
tooltip?: string;
|
||||
ariaLabel?: string;
|
||||
}
|
||||
|
||||
export function VariableSwitchField({
|
||||
value,
|
||||
name,
|
||||
tooltip,
|
||||
onChange,
|
||||
ariaLabel,
|
||||
}: PropsWithChildren<VariableSwitchFieldProps>): ReactElement {
|
||||
const styles = useStyles(getStyles);
|
||||
|
||||
return (
|
||||
<InlineField label={name} labelWidth={20} tooltip={tooltip}>
|
||||
<div aria-label={ariaLabel} className={styles.switchContainer}>
|
||||
<Switch label={name} value={value} onChange={onChange} />
|
||||
</div>
|
||||
</InlineField>
|
||||
);
|
||||
}
|
||||
|
||||
function getStyles(theme: GrafanaTheme) {
|
||||
return {
|
||||
switchContainer: css`
|
||||
margin-left: ${theme.spacing.sm};
|
||||
margin-right: ${theme.spacing.sm};
|
||||
`,
|
||||
};
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
import React, { FormEvent, PropsWithChildren, ReactElement, useCallback } from 'react';
|
||||
import { HorizontalGroup, InlineField, TextArea, useStyles } from '@grafana/ui';
|
||||
import { GrafanaTheme } from '@grafana/data';
|
||||
import { css } from 'emotion';
|
||||
|
||||
interface VariableTextAreaFieldProps<T> {
|
||||
name: string;
|
||||
value: string;
|
||||
placeholder: string;
|
||||
onChange: (event: FormEvent<HTMLTextAreaElement>) => void;
|
||||
width: number;
|
||||
tooltip?: string;
|
||||
ariaLabel?: string;
|
||||
required?: boolean;
|
||||
labelWidth?: number;
|
||||
onBlur?: (event: FormEvent<HTMLTextAreaElement>) => void;
|
||||
}
|
||||
|
||||
export function VariableTextAreaField({
|
||||
name,
|
||||
value,
|
||||
placeholder,
|
||||
tooltip,
|
||||
onChange,
|
||||
onBlur,
|
||||
ariaLabel,
|
||||
required,
|
||||
width,
|
||||
labelWidth,
|
||||
}: PropsWithChildren<VariableTextAreaFieldProps<any>>): ReactElement {
|
||||
const styles = useStyles(getStyles);
|
||||
const getLineCount = useCallback((value: any) => {
|
||||
if (value && typeof value === 'string') {
|
||||
return value.split('\n').length;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<HorizontalGroup spacing="none">
|
||||
<InlineField
|
||||
label={name}
|
||||
labelWidth={labelWidth ?? 12}
|
||||
grow={false}
|
||||
tooltip={tooltip}
|
||||
className={styles.inlineFieldOverride}
|
||||
>
|
||||
<span hidden />
|
||||
</InlineField>
|
||||
<TextArea
|
||||
rows={getLineCount(value)}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
onBlur={onBlur}
|
||||
placeholder={placeholder}
|
||||
required={required}
|
||||
aria-label={ariaLabel}
|
||||
cols={width}
|
||||
className={styles.textarea}
|
||||
/>
|
||||
</HorizontalGroup>
|
||||
);
|
||||
}
|
||||
|
||||
function getStyles(theme: GrafanaTheme) {
|
||||
return {
|
||||
inlineFieldOverride: css`
|
||||
margin: 0;
|
||||
`,
|
||||
textarea: css`
|
||||
white-space: pre-wrap;
|
||||
min-height: 32px;
|
||||
height: auto;
|
||||
overflow: auto;
|
||||
padding: 6px 8px;
|
||||
`,
|
||||
};
|
||||
}
|
47
public/app/features/variables/editor/VariableTextField.tsx
Normal file
47
public/app/features/variables/editor/VariableTextField.tsx
Normal file
@ -0,0 +1,47 @@
|
||||
import React, { FormEvent, PropsWithChildren, ReactElement } from 'react';
|
||||
import { InlineField, Input, PopoverContent } from '@grafana/ui';
|
||||
|
||||
interface VariableTextFieldProps {
|
||||
value: string;
|
||||
name: string;
|
||||
placeholder: string;
|
||||
onChange: (event: FormEvent<HTMLInputElement>) => void;
|
||||
ariaLabel?: string;
|
||||
tooltip?: PopoverContent;
|
||||
required?: boolean;
|
||||
width?: number;
|
||||
labelWidth?: number;
|
||||
grow?: boolean;
|
||||
onBlur?: (event: FormEvent<HTMLInputElement>) => void;
|
||||
}
|
||||
|
||||
export function VariableTextField({
|
||||
value,
|
||||
name,
|
||||
placeholder,
|
||||
onChange,
|
||||
ariaLabel,
|
||||
width,
|
||||
labelWidth,
|
||||
required,
|
||||
onBlur,
|
||||
tooltip,
|
||||
grow,
|
||||
}: PropsWithChildren<VariableTextFieldProps>): ReactElement {
|
||||
return (
|
||||
<InlineField label={name} labelWidth={labelWidth ?? 12} tooltip={tooltip} grow={grow}>
|
||||
<Input
|
||||
type="text"
|
||||
id={name}
|
||||
name={name}
|
||||
placeholder={placeholder}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
onBlur={onBlur}
|
||||
width={grow ? undefined : width ?? 25}
|
||||
aria-label={ariaLabel}
|
||||
required={required}
|
||||
/>
|
||||
</InlineField>
|
||||
);
|
||||
}
|
28
public/app/features/variables/editor/VariableTypeSelect.tsx
Normal file
28
public/app/features/variables/editor/VariableTypeSelect.tsx
Normal file
@ -0,0 +1,28 @@
|
||||
import React, { PropsWithChildren, useMemo } from 'react';
|
||||
import { SelectableValue, VariableType } from '@grafana/data';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
|
||||
import { VariableSelectField } from '../editor/VariableSelectField';
|
||||
import { getVariableTypes } from '../utils';
|
||||
import { variableAdapters } from '../adapters';
|
||||
|
||||
interface Props {
|
||||
onChange: (option: SelectableValue<VariableType>) => void;
|
||||
type: VariableType;
|
||||
}
|
||||
|
||||
export function VariableTypeSelect({ onChange, type }: PropsWithChildren<Props>) {
|
||||
const options = useMemo(() => getVariableTypes(), [getVariableTypes]);
|
||||
const value = useMemo(() => options.find(o => o.value === type) ?? options[0], [options, type]);
|
||||
|
||||
return (
|
||||
<VariableSelectField
|
||||
name="Type"
|
||||
value={value}
|
||||
options={options}
|
||||
onChange={onChange}
|
||||
tooltip={variableAdapters.get(type).description}
|
||||
ariaLabel={selectors.pages.Dashboard.Settings.Variables.Edit.General.generalTypeSelect}
|
||||
/>
|
||||
);
|
||||
}
|
@ -1,54 +1,67 @@
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { VariableModel, VariableOption, VariableWithOptions } from '../types';
|
||||
import React, { MouseEvent, useCallback, useEffect, useState } from 'react';
|
||||
import { VariableOption, VariableWithOptions } from '../types';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { Button, InlineFieldRow, InlineLabel, useStyles, VerticalGroup } from '@grafana/ui';
|
||||
import { GrafanaTheme } from '@grafana/data';
|
||||
import { css } from 'emotion';
|
||||
|
||||
export interface VariableValuesPreviewProps {
|
||||
variable: VariableModel;
|
||||
variable: VariableWithOptions;
|
||||
}
|
||||
|
||||
export const VariableValuesPreview: React.FunctionComponent<VariableValuesPreviewProps> = ({ variable }) => {
|
||||
export const VariableValuesPreview: React.FunctionComponent<VariableValuesPreviewProps> = ({
|
||||
variable: { options },
|
||||
}) => {
|
||||
const [previewLimit, setPreviewLimit] = useState(20);
|
||||
const [previewOptions, setPreviewOptions] = useState<VariableOption[]>([]);
|
||||
const showMoreOptions = useCallback(() => setPreviewLimit(previewLimit + 20), [previewLimit, setPreviewLimit]);
|
||||
useEffect(() => {
|
||||
if (!variable || !variable.hasOwnProperty('options')) {
|
||||
return;
|
||||
}
|
||||
const variableWithOptions = variable as VariableWithOptions;
|
||||
setPreviewOptions(variableWithOptions.options.slice(0, previewLimit));
|
||||
}, [previewLimit, variable]);
|
||||
const showMoreOptions = useCallback(
|
||||
(event: MouseEvent) => {
|
||||
event.preventDefault();
|
||||
setPreviewLimit(previewLimit + 20);
|
||||
},
|
||||
[previewLimit, setPreviewLimit]
|
||||
);
|
||||
const styles = useStyles(getStyles);
|
||||
useEffect(() => setPreviewOptions(options.slice(0, previewLimit)), [previewLimit, options]);
|
||||
|
||||
if (!previewOptions.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="gf-form-group">
|
||||
<VerticalGroup spacing="none">
|
||||
<h5>Preview of values</h5>
|
||||
<div className="gf-form-inline">
|
||||
<InlineFieldRow>
|
||||
{previewOptions.map((o, index) => (
|
||||
<div className="gf-form" key={`${o.value}-${index}`}>
|
||||
<span
|
||||
className="gf-form-label"
|
||||
aria-label={selectors.pages.Dashboard.Settings.Variables.Edit.General.previewOfValuesOption}
|
||||
>
|
||||
<InlineFieldRow key={`${o.value}-${index}`} className={styles.optionContainer}>
|
||||
<InlineLabel aria-label={selectors.pages.Dashboard.Settings.Variables.Edit.General.previewOfValuesOption}>
|
||||
{o.text}
|
||||
</span>
|
||||
</div>
|
||||
</InlineLabel>
|
||||
</InlineFieldRow>
|
||||
))}
|
||||
{previewOptions.length > previewLimit && (
|
||||
<div className="gf-form" ng-if="current.options.length > optionsLimit">
|
||||
<a
|
||||
className="gf-form-label btn-secondary"
|
||||
onClick={showMoreOptions}
|
||||
aria-label="Variable editor Preview of Values Show More link"
|
||||
>
|
||||
Show more
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</InlineFieldRow>
|
||||
{options.length > previewLimit && (
|
||||
<InlineFieldRow className={styles.optionContainer}>
|
||||
<Button
|
||||
onClick={showMoreOptions}
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
aria-label="Variable editor Preview of Values Show More link"
|
||||
>
|
||||
Show more
|
||||
</Button>
|
||||
</InlineFieldRow>
|
||||
)}
|
||||
</VerticalGroup>
|
||||
);
|
||||
};
|
||||
VariableValuesPreview.displayName = 'VariableValuesPreview';
|
||||
|
||||
function getStyles(theme: GrafanaTheme) {
|
||||
return {
|
||||
optionContainer: css`
|
||||
margin-left: ${theme.spacing.xs};
|
||||
margin-bottom: ${theme.spacing.xs};
|
||||
`,
|
||||
};
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ import {
|
||||
VariableModel,
|
||||
VariableQueryEditorType,
|
||||
VariableWithMultiSupport,
|
||||
VariableWithOptions,
|
||||
} from './types';
|
||||
import { VariableQueryProps } from '../../types';
|
||||
import { LEGACY_VARIABLE_QUERY_EDITOR_NAME } from './editor/LegacyVariableQueryEditor';
|
||||
@ -42,6 +43,15 @@ export const isMulti = (model: VariableModel): model is VariableWithMultiSupport
|
||||
return withMulti.hasOwnProperty('multi') && typeof withMulti.multi === 'boolean';
|
||||
};
|
||||
|
||||
export const hasOptions = (model: VariableModel): model is VariableWithOptions => {
|
||||
if (!model) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const withOptions = model as VariableWithOptions;
|
||||
return withOptions.hasOwnProperty('options') && typeof withOptions.options === 'object';
|
||||
};
|
||||
|
||||
interface DataSourceWithLegacyVariableSupport<
|
||||
TQuery extends DataQuery = DataQuery,
|
||||
TOptions extends DataSourceJsonData = DataSourceJsonData
|
||||
|
@ -1,10 +1,13 @@
|
||||
import React, { ChangeEvent, FocusEvent, PureComponent } from 'react';
|
||||
import { InlineFieldRow, VerticalGroup } from '@grafana/ui';
|
||||
|
||||
import { IntervalVariableModel } from '../types';
|
||||
import { VariableEditorProps } from '../editor/types';
|
||||
import { InlineFormLabel, LegacyForms } from '@grafana/ui';
|
||||
|
||||
const { Switch } = LegacyForms;
|
||||
import { VariableSectionHeader } from '../editor/VariableSectionHeader';
|
||||
import { VariableTextField } from '../editor/VariableTextField';
|
||||
import { VariableSwitchField } from '../editor/VariableSwitchField';
|
||||
import { VariableSelectField } from '../editor/VariableSelectField';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
|
||||
export interface Props extends VariableEditorProps<IntervalVariableModel> {}
|
||||
|
||||
@ -32,10 +35,10 @@ export class IntervalVariableEditor extends PureComponent<Props> {
|
||||
});
|
||||
};
|
||||
|
||||
onAutoCountChanged = (event: ChangeEvent<HTMLSelectElement>) => {
|
||||
onAutoCountChanged = (option: SelectableValue<number>) => {
|
||||
this.props.onPropChange({
|
||||
propName: 'auto_count',
|
||||
propValue: event.target.value,
|
||||
propValue: option.value,
|
||||
updateOptions: true,
|
||||
});
|
||||
};
|
||||
@ -49,73 +52,59 @@ export class IntervalVariableEditor extends PureComponent<Props> {
|
||||
};
|
||||
|
||||
render() {
|
||||
const { variable } = this.props;
|
||||
const stepOptions = [1, 2, 3, 4, 5, 10, 20, 30, 40, 50, 100, 200, 300, 400, 500].map(count => ({
|
||||
label: `${count}`,
|
||||
value: count,
|
||||
}));
|
||||
const stepValue = stepOptions.find(o => o.value === variable.auto_count) ?? stepOptions[0];
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="gf-form-group">
|
||||
<h5 className="section-heading">Interval Options</h5>
|
||||
|
||||
<div className="gf-form">
|
||||
<span className="gf-form-label width-9">Values</span>
|
||||
<input
|
||||
type="text"
|
||||
className="gf-form-input"
|
||||
value={this.props.variable.query}
|
||||
placeholder="1m,10m,1h,6h,1d,7d"
|
||||
onChange={this.onQueryChanged}
|
||||
onBlur={this.onQueryBlur}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="gf-form-inline">
|
||||
<Switch
|
||||
label="Auto Option"
|
||||
labelClass="width-9"
|
||||
checked={this.props.variable.auto}
|
||||
<VerticalGroup spacing="xs">
|
||||
<VariableSectionHeader name="Interval Options" />
|
||||
<VerticalGroup spacing="none">
|
||||
<VariableTextField
|
||||
value={this.props.variable.query}
|
||||
name="Values"
|
||||
placeholder="1m,10m,1h,6h,1d,7d"
|
||||
onChange={this.onQueryChanged}
|
||||
onBlur={this.onQueryBlur}
|
||||
labelWidth={20}
|
||||
grow
|
||||
required
|
||||
/>
|
||||
<InlineFieldRow>
|
||||
<VariableSwitchField
|
||||
value={this.props.variable.auto}
|
||||
name="Auto Option"
|
||||
tooltip="Interval will be dynamically calculated by dividing time range by the count specified"
|
||||
onChange={this.onAutoChange}
|
||||
tooltip={'Interval will be dynamically calculated by dividing time range by the count specified'}
|
||||
/>
|
||||
|
||||
{this.props.variable.auto && (
|
||||
{this.props.variable.auto ? (
|
||||
<>
|
||||
<div className="gf-form">
|
||||
<InlineFormLabel
|
||||
width={9}
|
||||
tooltip={'How many times should the current time range be divided to calculate the value'}
|
||||
>
|
||||
Step count
|
||||
</InlineFormLabel>
|
||||
<div className="gf-form-select-wrapper max-width-10">
|
||||
<select
|
||||
className="gf-form-input"
|
||||
value={this.props.variable.auto_count}
|
||||
onChange={this.onAutoCountChanged}
|
||||
>
|
||||
{[1, 2, 3, 4, 5, 10, 20, 30, 40, 50, 100, 200, 300, 400, 500].map(count => (
|
||||
<option key={`auto_count_key-${count}`} label={`${count}`}>
|
||||
{count}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="gf-form">
|
||||
<InlineFormLabel width={9} tooltip={'The calculated value will not go below this threshold'}>
|
||||
Min interval
|
||||
</InlineFormLabel>
|
||||
<input
|
||||
type="text"
|
||||
className="gf-form-input max-width-10"
|
||||
value={this.props.variable.auto_min}
|
||||
onChange={this.onAutoMinChanged}
|
||||
placeholder="10s"
|
||||
/>
|
||||
</div>
|
||||
<VariableSelectField
|
||||
name="Step count"
|
||||
value={stepValue}
|
||||
options={stepOptions}
|
||||
onChange={this.onAutoCountChanged}
|
||||
tooltip="How many times should the current time range be divided to calculate the value"
|
||||
labelWidth={7}
|
||||
width={9}
|
||||
/>
|
||||
<VariableTextField
|
||||
value={this.props.variable.auto_min}
|
||||
name="Min interval"
|
||||
placeholder="10s"
|
||||
onChange={this.onAutoMinChanged}
|
||||
tooltip="The calculated value will not go below this threshold"
|
||||
labelWidth={13}
|
||||
width={11}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
) : null}
|
||||
</InlineFieldRow>
|
||||
</VerticalGroup>
|
||||
</VerticalGroup>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,8 @@
|
||||
import React, { FunctionComponent, useMemo } from 'react';
|
||||
import React, { FunctionComponent, PropsWithChildren, ReactElement, useMemo } from 'react';
|
||||
import { VariableHide, VariableModel } from '../types';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { variableAdapters } from '../adapters';
|
||||
import { Tooltip } from '@grafana/ui';
|
||||
|
||||
interface Props {
|
||||
variable: VariableModel;
|
||||
@ -9,7 +10,6 @@ interface Props {
|
||||
|
||||
export const PickerRenderer: FunctionComponent<Props> = props => {
|
||||
const PickerToRender = useMemo(() => variableAdapters.get(props.variable.type).picker, [props.variable]);
|
||||
const labelOrName = useMemo(() => props.variable.label || props.variable.name, [props.variable]);
|
||||
|
||||
if (!props.variable) {
|
||||
return <div>Couldn't load variable</div>;
|
||||
@ -17,17 +17,40 @@ export const PickerRenderer: FunctionComponent<Props> = props => {
|
||||
|
||||
return (
|
||||
<div className="gf-form">
|
||||
{props.variable.hide === VariableHide.dontHide && (
|
||||
<label
|
||||
className="gf-form-label gf-form-label--variable"
|
||||
aria-label={selectors.pages.Dashboard.SubMenu.submenuItemLabels(labelOrName)}
|
||||
>
|
||||
{labelOrName}
|
||||
</label>
|
||||
)}
|
||||
<PickerLabel variable={props.variable} />
|
||||
{props.variable.hide !== VariableHide.hideVariable && PickerToRender && (
|
||||
<PickerToRender variable={props.variable} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
function PickerLabel({ variable }: PropsWithChildren<Props>): ReactElement | null {
|
||||
const labelOrName = useMemo(() => variable.label || variable.name, [variable]);
|
||||
|
||||
if (variable.hide !== VariableHide.dontHide) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (variable.description) {
|
||||
return (
|
||||
<Tooltip content={variable.description} placement={'bottom'}>
|
||||
<label
|
||||
className="gf-form-label gf-form-label--variable"
|
||||
aria-label={selectors.pages.Dashboard.SubMenu.submenuItemLabels(labelOrName)}
|
||||
>
|
||||
{labelOrName}
|
||||
</label>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<label
|
||||
className="gf-form-label gf-form-label--variable"
|
||||
aria-label={selectors.pages.Dashboard.SubMenu.submenuItemLabels(labelOrName)}
|
||||
>
|
||||
{labelOrName}
|
||||
</label>
|
||||
);
|
||||
}
|
||||
|
@ -0,0 +1,28 @@
|
||||
import React, { PropsWithChildren, useMemo } from 'react';
|
||||
import { DataSourceSelectItem, SelectableValue } from '@grafana/data';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { VariableSelectField } from '../editor/VariableSelectField';
|
||||
|
||||
interface Props {
|
||||
onChange: (option: SelectableValue<string>) => void;
|
||||
datasource: string | null;
|
||||
dataSources?: DataSourceSelectItem[];
|
||||
}
|
||||
|
||||
export function QueryVariableDatasourceSelect({ onChange, datasource, dataSources }: PropsWithChildren<Props>) {
|
||||
const options = useMemo(() => {
|
||||
return dataSources ? dataSources.map(ds => ({ label: ds.name, value: ds.value ?? '' })) : [];
|
||||
}, [dataSources]);
|
||||
const value = useMemo(() => options.find(o => o.value === datasource) ?? options[0], [options, datasource]);
|
||||
|
||||
return (
|
||||
<VariableSelectField
|
||||
name="Data source"
|
||||
value={value}
|
||||
options={options}
|
||||
onChange={onChange}
|
||||
labelWidth={10}
|
||||
ariaLabel={selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsDataSourceSelect}
|
||||
/>
|
||||
);
|
||||
}
|
@ -1,9 +1,9 @@
|
||||
import React, { ChangeEvent, PureComponent } from 'react';
|
||||
import { MapDispatchToProps, MapStateToProps } from 'react-redux';
|
||||
import { InlineFormLabel, LegacyForms } from '@grafana/ui';
|
||||
import { InlineFieldRow, VerticalGroup } from '@grafana/ui';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { getTemplateSrv } from '@grafana/runtime';
|
||||
import { LoadingState } from '@grafana/data';
|
||||
import { LoadingState, SelectableValue } from '@grafana/data';
|
||||
|
||||
import { SelectionOptionsEditor } from '../editor/SelectionOptionsEditor';
|
||||
import { QueryVariableModel, VariableRefresh, VariableSort, VariableWithMultiSupport } from '../types';
|
||||
@ -17,8 +17,12 @@ import { toVariableIdentifier } from '../state/types';
|
||||
import { changeVariableMultiValue } from '../state/actions';
|
||||
import { getTimeSrv } from '../../dashboard/services/TimeSrv';
|
||||
import { isLegacyQueryEditor, isQueryEditor } from '../guard';
|
||||
|
||||
const { Switch } = LegacyForms;
|
||||
import { VariableSectionHeader } from '../editor/VariableSectionHeader';
|
||||
import { VariableTextField } from '../editor/VariableTextField';
|
||||
import { VariableSwitchField } from '../editor/VariableSwitchField';
|
||||
import { QueryVariableDatasourceSelect } from './QueryVariableDatasourceSelect';
|
||||
import { QueryVariableRefreshSelect } from './QueryVariableRefreshSelect';
|
||||
import { QueryVariableSortSelect } from './QueryVariableSortSelect';
|
||||
|
||||
export interface OwnProps extends VariableEditorProps<QueryVariableModel> {}
|
||||
|
||||
@ -61,18 +65,9 @@ export class QueryVariableEditorUnConnected extends PureComponent<Props, State>
|
||||
}
|
||||
}
|
||||
|
||||
getSelectedDataSourceValue = (): string => {
|
||||
if (!this.props.editor.extended?.dataSources?.length) {
|
||||
return '';
|
||||
}
|
||||
const foundItem = this.props.editor.extended?.dataSources.find(ds => ds.value === this.props.variable.datasource);
|
||||
const value = foundItem ? foundItem.value : this.props.editor.extended?.dataSources[0].value;
|
||||
return value ?? '';
|
||||
};
|
||||
|
||||
onDataSourceChange = (event: ChangeEvent<HTMLSelectElement>) => {
|
||||
onDataSourceChange = (option: SelectableValue<string>) => {
|
||||
this.props.onPropChange({ propName: 'query', propValue: '' });
|
||||
this.props.onPropChange({ propName: 'datasource', propValue: event.target.value });
|
||||
this.props.onPropChange({ propName: 'datasource', propValue: option.value });
|
||||
};
|
||||
|
||||
onLegacyQueryChange = async (query: any, definition: string) => {
|
||||
@ -126,12 +121,12 @@ export class QueryVariableEditorUnConnected extends PureComponent<Props, State>
|
||||
}
|
||||
};
|
||||
|
||||
onRefreshChange = (event: ChangeEvent<HTMLSelectElement>) => {
|
||||
this.props.onPropChange({ propName: 'refresh', propValue: parseInt(event.target.value, 10) });
|
||||
onRefreshChange = (option: SelectableValue<VariableRefresh>) => {
|
||||
this.props.onPropChange({ propName: 'refresh', propValue: option.value });
|
||||
};
|
||||
|
||||
onSortChange = async (event: ChangeEvent<HTMLSelectElement>) => {
|
||||
this.props.onPropChange({ propName: 'sort', propValue: parseInt(event.target.value, 10), updateOptions: true });
|
||||
onSortChange = async (option: SelectableValue<VariableSort>) => {
|
||||
this.props.onPropChange({ propName: 'sort', propValue: option.value, updateOptions: true });
|
||||
};
|
||||
|
||||
onSelectionOptionsChange = async ({ propValue, propName }: OnPropChangeArguments<VariableWithMultiSupport>) => {
|
||||
@ -185,62 +180,28 @@ export class QueryVariableEditorUnConnected extends PureComponent<Props, State>
|
||||
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
<div className="gf-form-group">
|
||||
<h5 className="section-heading">Query Options</h5>
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form max-width-21">
|
||||
<span className="gf-form-label width-10">Data source</span>
|
||||
<div className="gf-form-select-wrapper max-width-14">
|
||||
<select
|
||||
className="gf-form-input"
|
||||
value={this.getSelectedDataSourceValue()}
|
||||
<VerticalGroup spacing="xs">
|
||||
<VariableSectionHeader name="Query Options" />
|
||||
<VerticalGroup spacing="md">
|
||||
<VerticalGroup spacing="none">
|
||||
<VerticalGroup spacing="xs">
|
||||
<InlineFieldRow>
|
||||
<QueryVariableDatasourceSelect
|
||||
onChange={this.onDataSourceChange}
|
||||
required
|
||||
aria-label={
|
||||
selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsDataSourceSelect
|
||||
}
|
||||
>
|
||||
{this.props.editor.extended?.dataSources?.length &&
|
||||
this.props.editor.extended?.dataSources.map(ds => (
|
||||
<option key={ds.value ?? ''} value={ds.value ?? ''} label={ds.name}>
|
||||
{ds.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="gf-form max-width-22">
|
||||
<InlineFormLabel width={10} tooltip={'When to update the values of this variable.'}>
|
||||
Refresh
|
||||
</InlineFormLabel>
|
||||
<div className="gf-form-select-wrapper width-15">
|
||||
<select
|
||||
className="gf-form-input"
|
||||
value={this.props.variable.refresh}
|
||||
onChange={this.onRefreshChange}
|
||||
aria-label={selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsRefreshSelect}
|
||||
>
|
||||
<option label="Never" value={VariableRefresh.never}>
|
||||
Never
|
||||
</option>
|
||||
<option label="On Dashboard Load" value={VariableRefresh.onDashboardLoad}>
|
||||
On Dashboard Load
|
||||
</option>
|
||||
<option label="On Time Range Change" value={VariableRefresh.onTimeRangeChanged}>
|
||||
On Time Range Change
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{this.renderQueryEditor()}
|
||||
|
||||
<div className="gf-form">
|
||||
<InlineFormLabel
|
||||
width={10}
|
||||
datasource={this.props.variable.datasource}
|
||||
dataSources={this.props.editor.extended?.dataSources}
|
||||
/>
|
||||
<QueryVariableRefreshSelect onChange={this.onRefreshChange} refresh={this.props.variable.refresh} />
|
||||
</InlineFieldRow>
|
||||
<div style={{ flexDirection: 'column' }}>{this.renderQueryEditor()}</div>
|
||||
</VerticalGroup>
|
||||
<VariableTextField
|
||||
value={this.state.regex ?? this.props.variable.regex}
|
||||
name="Regex"
|
||||
placeholder="/.*-(?<text>.*)-(?<value>.*)-.*/"
|
||||
onChange={this.onRegExChange}
|
||||
onBlur={this.onRegExBlur}
|
||||
labelWidth={20}
|
||||
tooltip={
|
||||
<div>
|
||||
Optional, if you want to extract part of a series name or metric node segment. Named capture groups
|
||||
@ -254,114 +215,57 @@ export class QueryVariableEditorUnConnected extends PureComponent<Props, State>
|
||||
).
|
||||
</div>
|
||||
}
|
||||
>
|
||||
Regex
|
||||
</InlineFormLabel>
|
||||
<input
|
||||
type="text"
|
||||
className="gf-form-input"
|
||||
placeholder="/.*-(?<text>.*)-(?<value>.*)-.*/"
|
||||
value={this.state.regex ?? this.props.variable.regex}
|
||||
onChange={this.onRegExChange}
|
||||
onBlur={this.onRegExBlur}
|
||||
aria-label={selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsRegExInput}
|
||||
ariaLabel={selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsRegExInput}
|
||||
grow
|
||||
/>
|
||||
</div>
|
||||
<div className="gf-form max-width-21">
|
||||
<InlineFormLabel width={10} tooltip={'How to sort the values of this variable.'}>
|
||||
Sort
|
||||
</InlineFormLabel>
|
||||
<div className="gf-form-select-wrapper max-width-14">
|
||||
<select
|
||||
className="gf-form-input"
|
||||
value={this.props.variable.sort}
|
||||
onChange={this.onSortChange}
|
||||
aria-label={selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsSortSelect}
|
||||
>
|
||||
<option label="Disabled" value={VariableSort.disabled}>
|
||||
Disabled
|
||||
</option>
|
||||
<option label="Alphabetical (asc)" value={VariableSort.alphabeticalAsc}>
|
||||
Alphabetical (asc)
|
||||
</option>
|
||||
<option label="Alphabetical (desc)" value={VariableSort.alphabeticalDesc}>
|
||||
Alphabetical (desc)
|
||||
</option>
|
||||
<option label="Numerical (asc)" value={VariableSort.numericalAsc}>
|
||||
Numerical (asc)
|
||||
</option>
|
||||
<option label="Numerical (desc)" value={VariableSort.numericalDesc}>
|
||||
Numerical (desc)
|
||||
</option>
|
||||
<option
|
||||
label="Alphabetical (case-insensitive, asc)"
|
||||
value={VariableSort.alphabeticalCaseInsensitiveAsc}
|
||||
>
|
||||
Alphabetical (case-insensitive, asc)
|
||||
</option>
|
||||
<option
|
||||
label="Alphabetical (case-insensitive, desc)"
|
||||
value={VariableSort.alphabeticalCaseInsensitiveDesc}
|
||||
>
|
||||
Alphabetical (case-insensitive, desc)
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<QueryVariableSortSelect onChange={this.onSortChange} sort={this.props.variable.sort} />
|
||||
</VerticalGroup>
|
||||
|
||||
<SelectionOptionsEditor
|
||||
variable={this.props.variable}
|
||||
onPropChange={this.onSelectionOptionsChange}
|
||||
onMultiChanged={this.props.changeVariableMultiValue}
|
||||
/>
|
||||
<SelectionOptionsEditor
|
||||
variable={this.props.variable}
|
||||
onPropChange={this.onSelectionOptionsChange}
|
||||
onMultiChanged={this.props.changeVariableMultiValue}
|
||||
/>
|
||||
|
||||
<div className="gf-form-group">
|
||||
<h5>Value groups/tags (Experimental feature)</h5>
|
||||
<div
|
||||
aria-label={selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.valueGroupsTagsEnabledSwitch}
|
||||
>
|
||||
<Switch
|
||||
label="Enabled"
|
||||
label-class="width-10"
|
||||
checked={this.props.variable.useTags}
|
||||
<VerticalGroup spacing="none">
|
||||
<h5>Value groups/tags (Experimental feature)</h5>
|
||||
<VariableSwitchField
|
||||
value={this.props.variable.useTags}
|
||||
name="Enabled"
|
||||
onChange={this.onUseTagsChange}
|
||||
ariaLabel={selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.valueGroupsTagsEnabledSwitch}
|
||||
/>
|
||||
</div>
|
||||
{this.props.variable.useTags && (
|
||||
<>
|
||||
<div className="gf-form last">
|
||||
<span className="gf-form-label width-10">Tags query</span>
|
||||
<input
|
||||
type="text"
|
||||
className="gf-form-input"
|
||||
{this.props.variable.useTags ? (
|
||||
<VerticalGroup spacing="none">
|
||||
<VariableTextField
|
||||
value={this.state.tagsQuery ?? this.props.variable.tagsQuery}
|
||||
name="Tags query"
|
||||
placeholder="metric name or tags query"
|
||||
onChange={this.onTagsQueryChange}
|
||||
onBlur={this.onTagsQueryBlur}
|
||||
aria-label={
|
||||
ariaLabel={
|
||||
selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.valueGroupsTagsTagsQueryInput
|
||||
}
|
||||
labelWidth={20}
|
||||
grow
|
||||
/>
|
||||
</div>
|
||||
<div className="gf-form">
|
||||
<li className="gf-form-label width-10">Tag values query</li>
|
||||
<input
|
||||
type="text"
|
||||
className="gf-form-input"
|
||||
<VariableTextField
|
||||
value={this.state.tagValuesQuery ?? this.props.variable.tagValuesQuery}
|
||||
name="Tag values query"
|
||||
placeholder="apps.$tag.*"
|
||||
onChange={this.onTagValuesQueryChange}
|
||||
onBlur={this.onTagValuesQueryBlur}
|
||||
aria-label={
|
||||
ariaLabel={
|
||||
selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.valueGroupsTagsTagsValuesQueryInput
|
||||
}
|
||||
labelWidth={20}
|
||||
grow
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
</VerticalGroup>
|
||||
) : null}
|
||||
</VerticalGroup>
|
||||
</VerticalGroup>
|
||||
</VerticalGroup>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,32 @@
|
||||
import React, { PropsWithChildren, useMemo } from 'react';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { VariableSelectField } from '../editor/VariableSelectField';
|
||||
import { VariableRefresh } from '../types';
|
||||
|
||||
interface Props {
|
||||
onChange: (option: SelectableValue<VariableRefresh>) => void;
|
||||
refresh: VariableRefresh;
|
||||
}
|
||||
|
||||
const REFRESH_OPTIONS = [
|
||||
{ label: 'Never', value: VariableRefresh.never },
|
||||
{ label: 'On Dashboard Load', value: VariableRefresh.onDashboardLoad },
|
||||
{ label: 'On Time Range Change', value: VariableRefresh.onTimeRangeChanged },
|
||||
];
|
||||
|
||||
export function QueryVariableRefreshSelect({ onChange, refresh }: PropsWithChildren<Props>) {
|
||||
const value = useMemo(() => REFRESH_OPTIONS.find(o => o.value === refresh) ?? REFRESH_OPTIONS[0], [refresh]);
|
||||
|
||||
return (
|
||||
<VariableSelectField
|
||||
name="Refresh"
|
||||
value={value}
|
||||
options={REFRESH_OPTIONS}
|
||||
onChange={onChange}
|
||||
labelWidth={10}
|
||||
ariaLabel={selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsRefreshSelect}
|
||||
tooltip="When to update the values of this variable."
|
||||
/>
|
||||
);
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
import React, { PropsWithChildren, useMemo } from 'react';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { VariableSelectField } from '../editor/VariableSelectField';
|
||||
import { VariableSort } from '../types';
|
||||
|
||||
interface Props {
|
||||
onChange: (option: SelectableValue<VariableSort>) => void;
|
||||
sort: VariableSort;
|
||||
}
|
||||
|
||||
const SORT_OPTIONS = [
|
||||
{ label: 'Disabled', value: VariableSort.disabled },
|
||||
{ label: 'Alphabetical (asc)', value: VariableSort.alphabeticalAsc },
|
||||
{ label: 'Alphabetical (desc)', value: VariableSort.alphabeticalDesc },
|
||||
{ label: 'Numerical (asc)', value: VariableSort.numericalAsc },
|
||||
{ label: 'Numerical (desc)', value: VariableSort.numericalDesc },
|
||||
{ label: 'Alphabetical (case-insensitive, asc)', value: VariableSort.alphabeticalCaseInsensitiveAsc },
|
||||
{ label: 'Alphabetical (case-insensitive, desc)', value: VariableSort.alphabeticalCaseInsensitiveDesc },
|
||||
];
|
||||
|
||||
export function QueryVariableSortSelect({ onChange, sort }: PropsWithChildren<Props>) {
|
||||
const value = useMemo(() => SORT_OPTIONS.find(o => o.value === sort) ?? SORT_OPTIONS[0], [sort]);
|
||||
|
||||
return (
|
||||
<VariableSelectField
|
||||
name="Sort"
|
||||
value={value}
|
||||
options={SORT_OPTIONS}
|
||||
onChange={onChange}
|
||||
labelWidth={10}
|
||||
ariaLabel={selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsSortSelect}
|
||||
tooltip="How to sort the values of this variable."
|
||||
/>
|
||||
);
|
||||
}
|
@ -756,6 +756,7 @@ function createVariable(extend?: Partial<QueryVariableModel>): QueryVariableMode
|
||||
includeAll: true,
|
||||
state: LoadingState.NotStarted,
|
||||
error: null,
|
||||
description: null,
|
||||
...(extend ?? {}),
|
||||
};
|
||||
}
|
||||
|
@ -28,6 +28,7 @@ export const getVariableState = (
|
||||
global: false,
|
||||
state: LoadingState.NotStarted,
|
||||
error: null,
|
||||
description: null,
|
||||
};
|
||||
}
|
||||
|
||||
@ -43,6 +44,7 @@ export const getVariableState = (
|
||||
global: false,
|
||||
state: LoadingState.NotStarted,
|
||||
error: null,
|
||||
description: null,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -71,6 +71,7 @@ describe('sharedReducer', () => {
|
||||
global: false,
|
||||
state: LoadingState.NotStarted,
|
||||
error: null,
|
||||
description: null,
|
||||
},
|
||||
'2': {
|
||||
id: '2',
|
||||
@ -83,6 +84,7 @@ describe('sharedReducer', () => {
|
||||
global: false,
|
||||
state: LoadingState.NotStarted,
|
||||
error: null,
|
||||
description: null,
|
||||
},
|
||||
});
|
||||
});
|
||||
@ -107,6 +109,7 @@ describe('sharedReducer', () => {
|
||||
global: false,
|
||||
state: LoadingState.NotStarted,
|
||||
error: null,
|
||||
description: null,
|
||||
},
|
||||
'2': {
|
||||
id: '2',
|
||||
@ -119,6 +122,7 @@ describe('sharedReducer', () => {
|
||||
global: false,
|
||||
state: LoadingState.NotStarted,
|
||||
error: null,
|
||||
description: null,
|
||||
},
|
||||
});
|
||||
});
|
||||
@ -143,6 +147,7 @@ describe('sharedReducer', () => {
|
||||
global: false,
|
||||
state: LoadingState.NotStarted,
|
||||
error: null,
|
||||
description: null,
|
||||
},
|
||||
'1': {
|
||||
id: '1',
|
||||
@ -155,6 +160,7 @@ describe('sharedReducer', () => {
|
||||
global: false,
|
||||
state: LoadingState.NotStarted,
|
||||
error: null,
|
||||
description: null,
|
||||
},
|
||||
'2': {
|
||||
id: '2',
|
||||
@ -167,6 +173,7 @@ describe('sharedReducer', () => {
|
||||
global: false,
|
||||
state: LoadingState.NotStarted,
|
||||
error: null,
|
||||
description: null,
|
||||
},
|
||||
'11': {
|
||||
...initialQueryVariableModelState,
|
||||
@ -198,6 +205,7 @@ describe('sharedReducer', () => {
|
||||
global: false,
|
||||
state: LoadingState.NotStarted,
|
||||
error: null,
|
||||
description: null,
|
||||
},
|
||||
'1': {
|
||||
id: '1',
|
||||
@ -210,6 +218,7 @@ describe('sharedReducer', () => {
|
||||
global: false,
|
||||
state: LoadingState.NotStarted,
|
||||
error: null,
|
||||
description: null,
|
||||
},
|
||||
'2': {
|
||||
id: '2',
|
||||
@ -222,6 +231,7 @@ describe('sharedReducer', () => {
|
||||
global: false,
|
||||
state: LoadingState.NotStarted,
|
||||
error: null,
|
||||
description: null,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
@ -1,6 +1,10 @@
|
||||
import React, { ChangeEvent, PureComponent } from 'react';
|
||||
import { VerticalGroup } from '@grafana/ui';
|
||||
|
||||
import { TextBoxVariableModel } from '../types';
|
||||
import { VariableEditorProps } from '../editor/types';
|
||||
import { VariableSectionHeader } from '../editor/VariableSectionHeader';
|
||||
import { VariableTextField } from '../editor/VariableTextField';
|
||||
|
||||
export interface Props extends VariableEditorProps<TextBoxVariableModel> {}
|
||||
export class TextBoxVariableEditor extends PureComponent<Props> {
|
||||
@ -15,20 +19,18 @@ export class TextBoxVariableEditor extends PureComponent<Props> {
|
||||
render() {
|
||||
const { query } = this.props.variable;
|
||||
return (
|
||||
<div className="gf-form-group">
|
||||
<h5 className="section-heading">Text options</h5>
|
||||
<div className="gf-form">
|
||||
<span className="gf-form-label">Default value</span>
|
||||
<input
|
||||
type="text"
|
||||
className="gf-form-input"
|
||||
value={query}
|
||||
onChange={this.onQueryChange}
|
||||
onBlur={this.onQueryBlur}
|
||||
placeholder="default value, if any"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<VerticalGroup spacing="xs">
|
||||
<VariableSectionHeader name="Text Options" />
|
||||
<VariableTextField
|
||||
value={query}
|
||||
name="Default value"
|
||||
placeholder="default value, if any"
|
||||
onChange={this.onQueryChange}
|
||||
onBlur={this.onQueryBlur}
|
||||
labelWidth={20}
|
||||
grow
|
||||
/>
|
||||
</VerticalGroup>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -139,6 +139,7 @@ export interface VariableModel extends BaseVariableModel {
|
||||
index: number;
|
||||
state: LoadingState;
|
||||
error: any | null;
|
||||
description: string | null;
|
||||
}
|
||||
|
||||
export const initialVariableModelState: VariableModel = {
|
||||
@ -152,6 +153,7 @@ export const initialVariableModelState: VariableModel = {
|
||||
skipUrlSync: false,
|
||||
state: LoadingState.NotStarted,
|
||||
error: null,
|
||||
description: null,
|
||||
};
|
||||
|
||||
export type VariableQueryEditorType<
|
||||
|
Loading…
Reference in New Issue
Block a user