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 { css, cx } from 'emotion';
|
||||||
import { useStyles } from '../../themes';
|
import { useStyles } from '../../themes';
|
||||||
|
|
||||||
@ -18,6 +18,7 @@ export const InlineFieldRow: FC<Props> = ({ children, className, ...htmlProps })
|
|||||||
const getStyles = () => {
|
const getStyles = () => {
|
||||||
return {
|
return {
|
||||||
container: css`
|
container: css`
|
||||||
|
label: InlineFieldRow;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
@ -8,6 +8,10 @@ import { AdHocVariableEditorState } from './reducer';
|
|||||||
import { changeVariableDatasource, initAdHocVariableEditor } from './actions';
|
import { changeVariableDatasource, initAdHocVariableEditor } from './actions';
|
||||||
import { connectWithStore } from 'app/core/utils/connectWithReduxStore';
|
import { connectWithStore } from 'app/core/utils/connectWithReduxStore';
|
||||||
import { StoreState } from 'app/types';
|
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> {}
|
export interface OwnProps extends VariableEditorProps<AdHocVariableModel> {}
|
||||||
|
|
||||||
@ -27,45 +31,33 @@ export class AdHocVariableEditorUnConnected extends PureComponent<Props> {
|
|||||||
this.props.initAdHocVariableEditor();
|
this.props.initAdHocVariableEditor();
|
||||||
}
|
}
|
||||||
|
|
||||||
onDatasourceChanged = (event: React.ChangeEvent<HTMLSelectElement>) => {
|
onDatasourceChanged = (option: SelectableValue<string>) => {
|
||||||
this.props.changeVariableDatasource(event.target.value);
|
this.props.changeVariableDatasource(option.value ?? '');
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { variable, editor } = this.props;
|
const { variable, editor } = this.props;
|
||||||
const dataSources = editor.extended?.dataSources ?? [];
|
const dataSources = editor.extended?.dataSources ?? [];
|
||||||
const infoText = editor.extended?.infoText ?? null;
|
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 (
|
return (
|
||||||
<>
|
<VerticalGroup spacing="xs">
|
||||||
<div className="gf-form-group">
|
<VariableSectionHeader name="Options" />
|
||||||
<h5 className="section-heading">Options</h5>
|
<VerticalGroup spacing="sm">
|
||||||
<div className="gf-form max-width-21">
|
<InlineFieldRow>
|
||||||
<span className="gf-form-label width-8">Data source</span>
|
<VariableSelectField
|
||||||
<div className="gf-form-select-wrapper max-width-14">
|
name="Data source"
|
||||||
<select
|
value={value}
|
||||||
className="gf-form-input"
|
options={options}
|
||||||
required
|
onChange={this.onDatasourceChanged}
|
||||||
onChange={this.onDatasourceChanged}
|
labelWidth={10}
|
||||||
value={variable.datasource ?? ''}
|
/>
|
||||||
aria-label="Variable editor Form AdHoc DataSource select"
|
</InlineFieldRow>
|
||||||
>
|
{infoText ? <Alert title={infoText} severity="info" /> : null}
|
||||||
{dataSources.map(ds => (
|
</VerticalGroup>
|
||||||
<option key={ds.value ?? ''} value={ds.value ?? ''} label={ds.text}>
|
</VerticalGroup>
|
||||||
{ds.text}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{infoText && (
|
|
||||||
<div className="alert alert-info gf-form-group" aria-label="Variable editor Form Alert">
|
|
||||||
{infoText}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
import React, { ChangeEvent, FocusEvent, PureComponent } from 'react';
|
import React, { ChangeEvent, FocusEvent, PureComponent } from 'react';
|
||||||
import { selectors } from '@grafana/e2e-selectors';
|
import { selectors } from '@grafana/e2e-selectors';
|
||||||
|
import { VerticalGroup } from '@grafana/ui';
|
||||||
|
|
||||||
import { ConstantVariableModel } from '../types';
|
import { ConstantVariableModel } from '../types';
|
||||||
import { VariableEditorProps } from '../editor/types';
|
import { VariableEditorProps } from '../editor/types';
|
||||||
|
import { VariableSectionHeader } from '../editor/VariableSectionHeader';
|
||||||
|
import { VariableTextField } from '../editor/VariableTextField';
|
||||||
|
|
||||||
export interface Props extends VariableEditorProps<ConstantVariableModel> {}
|
export interface Props extends VariableEditorProps<ConstantVariableModel> {}
|
||||||
|
|
||||||
@ -24,23 +27,19 @@ export class ConstantVariableEditor extends PureComponent<Props> {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<>
|
<VerticalGroup spacing="xs">
|
||||||
<div className="gf-form-group">
|
<VariableSectionHeader name="Constant Options" />
|
||||||
<h5 className="section-heading">Constant options</h5>
|
<VariableTextField
|
||||||
<div className="gf-form">
|
value={this.props.variable.query}
|
||||||
<span className="gf-form-label">Value</span>
|
name="Value"
|
||||||
<input
|
placeholder="your metric prefix"
|
||||||
type="text"
|
onChange={this.onChange}
|
||||||
className="gf-form-input"
|
onBlur={this.onBlur}
|
||||||
value={this.props.variable.query}
|
labelWidth={20}
|
||||||
onChange={this.onChange}
|
ariaLabel={selectors.pages.Dashboard.Settings.Variables.Edit.ConstantVariable.constantOptionsQueryInput}
|
||||||
onBlur={this.onBlur}
|
grow
|
||||||
placeholder="your metric prefix"
|
/>
|
||||||
aria-label={selectors.pages.Dashboard.Settings.Variables.Edit.ConstantVariable.constantOptionsQueryInput}
|
</VerticalGroup>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,9 +4,11 @@ import { SelectionOptionsEditor } from '../editor/SelectionOptionsEditor';
|
|||||||
import { OnPropChangeArguments, VariableEditorProps } from '../editor/types';
|
import { OnPropChangeArguments, VariableEditorProps } from '../editor/types';
|
||||||
import { connectWithStore } from 'app/core/utils/connectWithReduxStore';
|
import { connectWithStore } from 'app/core/utils/connectWithReduxStore';
|
||||||
import { MapDispatchToProps, MapStateToProps } from 'react-redux';
|
import { MapDispatchToProps, MapStateToProps } from 'react-redux';
|
||||||
import { Field, TextArea } from '@grafana/ui';
|
import { VerticalGroup } from '@grafana/ui';
|
||||||
import { StoreState } from 'app/types';
|
import { StoreState } from 'app/types';
|
||||||
import { changeVariableMultiValue } from '../state/actions';
|
import { changeVariableMultiValue } from '../state/actions';
|
||||||
|
import { VariableSectionHeader } from '../editor/VariableSectionHeader';
|
||||||
|
import { VariableTextAreaField } from '../editor/VariableTextAreaField';
|
||||||
|
|
||||||
interface OwnProps extends VariableEditorProps<CustomVariableModel> {}
|
interface OwnProps extends VariableEditorProps<CustomVariableModel> {}
|
||||||
|
|
||||||
@ -40,31 +42,28 @@ class CustomVariableEditorUnconnected extends PureComponent<Props> {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<>
|
<VerticalGroup spacing="xs">
|
||||||
<div className="gf-form-group">
|
<VariableSectionHeader name="Custom Options" />
|
||||||
<h5 className="section-heading">Custom Options</h5>
|
<VerticalGroup spacing="md">
|
||||||
<div className="gf-form">
|
<VerticalGroup spacing="none">
|
||||||
<Field label="Values separated by comma">
|
<VariableTextAreaField
|
||||||
<TextArea
|
name="Values separated by comma"
|
||||||
className="gf-form-input"
|
value={this.props.variable.query}
|
||||||
value={this.props.variable.query}
|
placeholder="1, 10, mykey : myvalue, myvalue, escaped\,value"
|
||||||
onChange={this.onChange}
|
onChange={this.onChange}
|
||||||
onBlur={this.onBlur}
|
onBlur={this.onBlur}
|
||||||
rows={5}
|
required
|
||||||
cols={81}
|
width={50}
|
||||||
placeholder="1, 10, mykey : myvalue, myvalue, escaped\,value"
|
labelWidth={27}
|
||||||
required
|
/>
|
||||||
aria-label="Variable editor Form Custom Query field"
|
</VerticalGroup>
|
||||||
/>
|
<SelectionOptionsEditor
|
||||||
</Field>
|
variable={this.props.variable}
|
||||||
</div>
|
onPropChange={this.onSelectionOptionsChange}
|
||||||
</div>
|
onMultiChanged={this.props.changeVariableMultiValue}
|
||||||
<SelectionOptionsEditor
|
/>{' '}
|
||||||
variable={this.props.variable}
|
</VerticalGroup>
|
||||||
onPropChange={this.onSelectionOptionsChange}
|
</VerticalGroup>
|
||||||
onMultiChanged={this.props.changeVariableMultiValue}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,20 @@
|
|||||||
import React, { ChangeEvent, FocusEvent, PureComponent } from 'react';
|
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 { DataSourceVariableModel, VariableWithMultiSupport } from '../types';
|
||||||
import { OnPropChangeArguments, VariableEditorProps } from '../editor/types';
|
import { OnPropChangeArguments, VariableEditorProps } from '../editor/types';
|
||||||
import { SelectionOptionsEditor } from '../editor/SelectionOptionsEditor';
|
import { SelectionOptionsEditor } from '../editor/SelectionOptionsEditor';
|
||||||
import { InlineFormLabel } from '@grafana/ui';
|
|
||||||
import { VariableEditorState } from '../editor/reducer';
|
import { VariableEditorState } from '../editor/reducer';
|
||||||
import { DataSourceVariableEditorState } from './reducer';
|
import { DataSourceVariableEditorState } from './reducer';
|
||||||
import { initDataSourceVariableEditor } from './actions';
|
import { initDataSourceVariableEditor } from './actions';
|
||||||
import { MapDispatchToProps, MapStateToProps } from 'react-redux';
|
|
||||||
import { StoreState } from '../../../types';
|
import { StoreState } from '../../../types';
|
||||||
import { connectWithStore } from '../../../core/utils/connectWithReduxStore';
|
import { connectWithStore } from '../../../core/utils/connectWithReduxStore';
|
||||||
import { changeVariableMultiValue } from '../state/actions';
|
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> {}
|
export interface OwnProps extends VariableEditorProps<DataSourceVariableModel> {}
|
||||||
|
|
||||||
@ -58,66 +62,58 @@ export class DataSourceVariableEditorUnConnected extends PureComponent<Props> {
|
|||||||
return value ?? '';
|
return value ?? '';
|
||||||
};
|
};
|
||||||
|
|
||||||
onDataSourceTypeChanged = (event: ChangeEvent<HTMLSelectElement>) => {
|
onDataSourceTypeChanged = (option: SelectableValue<string>) => {
|
||||||
this.props.onPropChange({ propName: 'query', propValue: event.target.value, updateOptions: true });
|
this.props.onPropChange({ propName: 'query', propValue: option.value, updateOptions: true });
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
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 (
|
return (
|
||||||
<>
|
<VerticalGroup spacing="xs">
|
||||||
<div className="gf-form-group">
|
<VariableSectionHeader name="Data source options" />
|
||||||
<h5 className="section-heading">Data source options</h5>
|
<VerticalGroup spacing="md">
|
||||||
|
<VerticalGroup spacing="xs">
|
||||||
<div className="gf-form">
|
<InlineFieldRow>
|
||||||
<label className="gf-form-label width-12">Type</label>
|
<VariableSelectField
|
||||||
<div className="gf-form-select-wrapper max-width-18">
|
name="Type"
|
||||||
<select
|
value={typeValue}
|
||||||
className="gf-form-input"
|
options={typeOptions}
|
||||||
value={this.getSelectedDataSourceTypeValue()}
|
|
||||||
onChange={this.onDataSourceTypeChanged}
|
onChange={this.onDataSourceTypeChanged}
|
||||||
>
|
labelWidth={10}
|
||||||
{this.props.editor.extended?.dataSourceTypes?.length &&
|
/>
|
||||||
this.props.editor.extended?.dataSourceTypes?.map(ds => (
|
</InlineFieldRow>
|
||||||
<option key={ds.value ?? ''} value={ds.value ?? ''} label={ds.text}>
|
<InlineFieldRow>
|
||||||
{ds.text}
|
<VariableTextField
|
||||||
</option>
|
value={this.props.variable.regex}
|
||||||
))}
|
name="Instance name filter"
|
||||||
</select>
|
placeholder="/.*-(.*)-.*/"
|
||||||
</div>
|
onChange={this.onRegExChange}
|
||||||
</div>
|
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">
|
<SelectionOptionsEditor
|
||||||
<InlineFormLabel
|
variable={this.props.variable}
|
||||||
width={12}
|
onPropChange={this.onSelectionOptionsChange}
|
||||||
tooltip={
|
onMultiChanged={this.props.changeVariableMultiValue}
|
||||||
<div>
|
/>
|
||||||
Regex filter for which data source instances to choose from in the variable value dropdown. Leave
|
</VerticalGroup>
|
||||||
empty for all.
|
</VerticalGroup>
|
||||||
<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}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,8 @@ import React, { FC, useCallback, useState } from 'react';
|
|||||||
import { selectors } from '@grafana/e2e-selectors';
|
import { selectors } from '@grafana/e2e-selectors';
|
||||||
|
|
||||||
import { VariableQueryProps } from 'app/types/plugins';
|
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 { GrafanaTheme } from '@grafana/data';
|
||||||
import { css } from 'emotion';
|
import { css } from 'emotion';
|
||||||
|
|
||||||
@ -17,6 +18,7 @@ export const LegacyVariableQueryEditor: FC<VariableQueryProps> = ({ onChange, qu
|
|||||||
},
|
},
|
||||||
[onChange]
|
[onChange]
|
||||||
);
|
);
|
||||||
|
|
||||||
const onBlur = useCallback(
|
const onBlur = useCallback(
|
||||||
(event: React.FormEvent<HTMLTextAreaElement>) => {
|
(event: React.FormEvent<HTMLTextAreaElement>) => {
|
||||||
onChange(event.currentTarget.value, event.currentTarget.value);
|
onChange(event.currentTarget.value, event.currentTarget.value);
|
||||||
@ -25,19 +27,17 @@ export const LegacyVariableQueryEditor: FC<VariableQueryProps> = ({ onChange, qu
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="gf-form">
|
<div className={styles.container}>
|
||||||
<InlineField label="Query" labelWidth={20} grow={false} className={styles.inlineFieldOverride}>
|
<VariableTextAreaField
|
||||||
<span hidden />
|
name="Query"
|
||||||
</InlineField>
|
|
||||||
<TextArea
|
|
||||||
rows={getLineCount(value)}
|
|
||||||
className="gf-form-input"
|
|
||||||
value={value}
|
value={value}
|
||||||
|
placeholder="metric name or tags query"
|
||||||
|
width={100}
|
||||||
onChange={onValueChange}
|
onChange={onValueChange}
|
||||||
onBlur={onBlur}
|
onBlur={onBlur}
|
||||||
placeholder="metric name or tags query"
|
|
||||||
required
|
required
|
||||||
aria-label={selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsQueryInput}
|
labelWidth={20}
|
||||||
|
ariaLabel={selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsQueryInput}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -45,18 +45,10 @@ export const LegacyVariableQueryEditor: FC<VariableQueryProps> = ({ onChange, qu
|
|||||||
|
|
||||||
function getStyles(theme: GrafanaTheme) {
|
function getStyles(theme: GrafanaTheme) {
|
||||||
return {
|
return {
|
||||||
inlineFieldOverride: css`
|
container: css`
|
||||||
margin: 0;
|
margin-bottom: ${theme.spacing.xs};
|
||||||
`,
|
`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
LegacyVariableQueryEditor.displayName = LEGACY_VARIABLE_QUERY_EDITOR_NAME;
|
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 React, { FunctionComponent, useCallback } from 'react';
|
||||||
import { LegacyForms } from '@grafana/ui';
|
import { InlineFieldRow, VerticalGroup } from '@grafana/ui';
|
||||||
import { selectors } from '@grafana/e2e-selectors';
|
import { selectors } from '@grafana/e2e-selectors';
|
||||||
|
|
||||||
import { VariableWithMultiSupport } from '../types';
|
import { VariableWithMultiSupport } from '../types';
|
||||||
import { VariableEditorProps } from './types';
|
import { VariableEditorProps } from './types';
|
||||||
import { toVariableIdentifier, VariableIdentifier } from '../state/types';
|
import { toVariableIdentifier, VariableIdentifier } from '../state/types';
|
||||||
|
import { VariableSectionHeader } from './VariableSectionHeader';
|
||||||
const { Switch } = LegacyForms;
|
import { VariableSwitchField } from './VariableSwitchField';
|
||||||
|
import { VariableTextField } from './VariableTextField';
|
||||||
|
|
||||||
export interface SelectionOptionsEditorProps<Model extends VariableWithMultiSupport = VariableWithMultiSupport>
|
export interface SelectionOptionsEditorProps<Model extends VariableWithMultiSupport = VariableWithMultiSupport>
|
||||||
extends VariableEditorProps<Model> {
|
extends VariableEditorProps<Model> {
|
||||||
@ -35,42 +36,39 @@ export const SelectionOptionsEditor: FunctionComponent<SelectionOptionsEditorPro
|
|||||||
[props.onPropChange]
|
[props.onPropChange]
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<div className="section gf-form-group">
|
<VerticalGroup spacing="none">
|
||||||
<h5 className="section-heading">Selection Options</h5>
|
<VariableSectionHeader name="Selection Options" />
|
||||||
<div className="section">
|
<InlineFieldRow>
|
||||||
<div aria-label={selectors.pages.Dashboard.Settings.Variables.Edit.General.selectionOptionsMultiSwitch}>
|
<VariableSwitchField
|
||||||
<Switch
|
value={props.variable.multi}
|
||||||
label="Multi-value"
|
name="Multi-value"
|
||||||
labelClass="width-10"
|
tooltip="Enables multiple values to be selected at the same time"
|
||||||
checked={props.variable.multi}
|
onChange={onMultiChanged}
|
||||||
onChange={onMultiChanged}
|
ariaLabel={selectors.pages.Dashboard.Settings.Variables.Edit.General.selectionOptionsMultiSwitch}
|
||||||
tooltip={'Enables multiple values to be selected at the same time'}
|
/>
|
||||||
/>
|
</InlineFieldRow>
|
||||||
</div>
|
<InlineFieldRow>
|
||||||
<div aria-label={selectors.pages.Dashboard.Settings.Variables.Edit.General.selectionOptionsIncludeAllSwitch}>
|
<VariableSwitchField
|
||||||
<Switch
|
value={props.variable.includeAll}
|
||||||
label="Include All option"
|
name="Include All option"
|
||||||
labelClass="width-10"
|
tooltip="Enables an option to include all variables"
|
||||||
checked={props.variable.includeAll}
|
onChange={onIncludeAllChanged}
|
||||||
onChange={onIncludeAllChanged}
|
ariaLabel={selectors.pages.Dashboard.Settings.Variables.Edit.General.selectionOptionsIncludeAllSwitch}
|
||||||
tooltip={'Enables an option to include all variables'}
|
/>
|
||||||
/>
|
</InlineFieldRow>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{props.variable.includeAll && (
|
{props.variable.includeAll && (
|
||||||
<div className="gf-form">
|
<InlineFieldRow>
|
||||||
<span className="gf-form-label width-10">Custom all value</span>
|
<VariableTextField
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
className="gf-form-input max-width-15"
|
|
||||||
value={props.variable.allValue ?? ''}
|
value={props.variable.allValue ?? ''}
|
||||||
onChange={onAllValueChanged}
|
onChange={onAllValueChanged}
|
||||||
|
name="Custom all value"
|
||||||
placeholder="blank = auto"
|
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';
|
SelectionOptionsEditor.displayName = 'SelectionOptionsEditor';
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React, { ChangeEvent, FormEvent, PureComponent } from 'react';
|
import React, { ChangeEvent, FormEvent, PureComponent } from 'react';
|
||||||
import isEqual from 'lodash/isEqual';
|
import isEqual from 'lodash/isEqual';
|
||||||
import { AppEvents, LoadingState, VariableType } from '@grafana/data';
|
import { AppEvents, LoadingState, SelectableValue, VariableType } from '@grafana/data';
|
||||||
import { Icon, InlineFormLabel } from '@grafana/ui';
|
import { Button, Icon, InlineFieldRow, VerticalGroup } from '@grafana/ui';
|
||||||
import { selectors } from '@grafana/e2e-selectors';
|
import { selectors } from '@grafana/e2e-selectors';
|
||||||
|
|
||||||
import { variableAdapters } from '../adapters';
|
import { variableAdapters } from '../adapters';
|
||||||
@ -18,7 +18,11 @@ import { connectWithStore } from '../../../core/utils/connectWithReduxStore';
|
|||||||
import { OnPropChangeArguments } from './types';
|
import { OnPropChangeArguments } from './types';
|
||||||
import { changeVariableProp, changeVariableType } from '../state/sharedReducer';
|
import { changeVariableProp, changeVariableType } from '../state/sharedReducer';
|
||||||
import { updateOptions } from '../state/actions';
|
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 {
|
export interface OwnProps {
|
||||||
identifier: VariableIdentifier;
|
identifier: VariableIdentifier;
|
||||||
@ -63,11 +67,11 @@ export class VariableEditorEditorUnConnected extends PureComponent<Props> {
|
|||||||
this.props.changeVariableName(this.props.identifier, event.target.value);
|
this.props.changeVariableName(this.props.identifier, event.target.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
onTypeChange = (event: ChangeEvent<HTMLSelectElement>) => {
|
onTypeChange = (option: SelectableValue<VariableType>) => {
|
||||||
event.preventDefault();
|
if (!option.value) {
|
||||||
this.props.changeVariableType(
|
return;
|
||||||
toVariablePayload(this.props.identifier, { newType: event.target.value as VariableType })
|
}
|
||||||
);
|
this.props.changeVariableType(toVariablePayload(this.props.identifier, { newType: option.value }));
|
||||||
};
|
};
|
||||||
|
|
||||||
onLabelChange = (event: ChangeEvent<HTMLInputElement>) => {
|
onLabelChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||||
@ -77,12 +81,17 @@ export class VariableEditorEditorUnConnected extends PureComponent<Props> {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
onHideChange = (event: ChangeEvent<HTMLSelectElement>) => {
|
onDescriptionChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||||
event.preventDefault();
|
this.props.changeVariableProp(
|
||||||
|
toVariablePayload(this.props.identifier, { propName: 'description', propValue: event.target.value })
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
onHideChange = (option: SelectableValue<VariableHide>) => {
|
||||||
this.props.changeVariableProp(
|
this.props.changeVariableProp(
|
||||||
toVariablePayload(this.props.identifier, {
|
toVariablePayload(this.props.identifier, {
|
||||||
propName: 'hide',
|
propName: 'hide',
|
||||||
propValue: parseInt(event.target.value, 10) as VariableHide,
|
propValue: option.value,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -114,100 +123,64 @@ export class VariableEditorEditorUnConnected extends PureComponent<Props> {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<form aria-label="Variable editor Form" onSubmit={this.onHandleSubmit}>
|
<form aria-label="Variable editor Form" onSubmit={this.onHandleSubmit}>
|
||||||
<h5 className="section-heading">General</h5>
|
<VerticalGroup spacing="lg">
|
||||||
<div className="gf-form-group">
|
<VerticalGroup spacing="none">
|
||||||
<div className="gf-form-inline">
|
<VariableSectionHeader name="General" />
|
||||||
<div className="gf-form max-width-19">
|
<InlineFieldRow>
|
||||||
<span className="gf-form-label width-6">Name</span>
|
<VariableTextField
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
className="gf-form-input"
|
|
||||||
name="name"
|
|
||||||
placeholder="name"
|
|
||||||
required
|
|
||||||
value={this.props.editor.name}
|
value={this.props.editor.name}
|
||||||
onChange={this.onNameChange}
|
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>
|
<VariableTypeSelect onChange={this.onTypeChange} type={this.props.variable.type} />
|
||||||
<div className="gf-form max-width-19">
|
</InlineFieldRow>
|
||||||
<InlineFormLabel width={6} tooltip={variableAdapters.get(this.props.variable.type).description}>
|
|
||||||
Type
|
{this.props.editor.errors.name && (
|
||||||
</InlineFormLabel>
|
<div className="gf-form">
|
||||||
<div className="gf-form-select-wrapper max-width-17">
|
<span className="gf-form-label gf-form-label--error">{this.props.editor.errors.name}</span>
|
||||||
<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>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
</div>
|
|
||||||
|
|
||||||
{this.props.editor.errors.name && (
|
<InlineFieldRow>
|
||||||
<div className="gf-form">
|
<VariableTextField
|
||||||
<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"
|
|
||||||
value={this.props.variable.label ?? ''}
|
value={this.props.variable.label ?? ''}
|
||||||
onChange={this.onLabelChange}
|
onChange={this.onLabelChange}
|
||||||
|
name="Label"
|
||||||
placeholder="optional display name"
|
placeholder="optional display name"
|
||||||
aria-label={selectors.pages.Dashboard.Settings.Variables.Edit.General.generalLabelInput}
|
ariaLabel={selectors.pages.Dashboard.Settings.Variables.Edit.General.generalLabelInput}
|
||||||
/>
|
/>
|
||||||
</div>
|
<VariableHideSelect onChange={this.onHideChange} hide={this.props.variable.hide} />
|
||||||
<div className="gf-form max-width-19">
|
</InlineFieldRow>
|
||||||
<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>
|
|
||||||
|
|
||||||
{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">
|
{hasOptions(this.props.variable) ? <VariableValuesPreview variable={this.props.variable} /> : null}
|
||||||
<button
|
|
||||||
type="submit"
|
<VerticalGroup spacing="none">
|
||||||
className="btn btn-primary"
|
<Button
|
||||||
aria-label={selectors.pages.Dashboard.Settings.Variables.Edit.General.submitButton}
|
type="submit"
|
||||||
disabled={loading}
|
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}
|
Update
|
||||||
</button>
|
{loading ? (
|
||||||
</div>
|
<Icon className="spin-clockwise" name="sync" size="sm" style={{ marginLeft: '2px' }} />
|
||||||
|
) : null}
|
||||||
|
</Button>
|
||||||
|
</VerticalGroup>
|
||||||
|
</VerticalGroup>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</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 React, { MouseEvent, useCallback, useEffect, useState } from 'react';
|
||||||
import { VariableModel, VariableOption, VariableWithOptions } from '../types';
|
import { VariableOption, VariableWithOptions } from '../types';
|
||||||
import { selectors } from '@grafana/e2e-selectors';
|
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 {
|
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 [previewLimit, setPreviewLimit] = useState(20);
|
||||||
const [previewOptions, setPreviewOptions] = useState<VariableOption[]>([]);
|
const [previewOptions, setPreviewOptions] = useState<VariableOption[]>([]);
|
||||||
const showMoreOptions = useCallback(() => setPreviewLimit(previewLimit + 20), [previewLimit, setPreviewLimit]);
|
const showMoreOptions = useCallback(
|
||||||
useEffect(() => {
|
(event: MouseEvent) => {
|
||||||
if (!variable || !variable.hasOwnProperty('options')) {
|
event.preventDefault();
|
||||||
return;
|
setPreviewLimit(previewLimit + 20);
|
||||||
}
|
},
|
||||||
const variableWithOptions = variable as VariableWithOptions;
|
[previewLimit, setPreviewLimit]
|
||||||
setPreviewOptions(variableWithOptions.options.slice(0, previewLimit));
|
);
|
||||||
}, [previewLimit, variable]);
|
const styles = useStyles(getStyles);
|
||||||
|
useEffect(() => setPreviewOptions(options.slice(0, previewLimit)), [previewLimit, options]);
|
||||||
|
|
||||||
if (!previewOptions.length) {
|
if (!previewOptions.length) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="gf-form-group">
|
<VerticalGroup spacing="none">
|
||||||
<h5>Preview of values</h5>
|
<h5>Preview of values</h5>
|
||||||
<div className="gf-form-inline">
|
<InlineFieldRow>
|
||||||
{previewOptions.map((o, index) => (
|
{previewOptions.map((o, index) => (
|
||||||
<div className="gf-form" key={`${o.value}-${index}`}>
|
<InlineFieldRow key={`${o.value}-${index}`} className={styles.optionContainer}>
|
||||||
<span
|
<InlineLabel aria-label={selectors.pages.Dashboard.Settings.Variables.Edit.General.previewOfValuesOption}>
|
||||||
className="gf-form-label"
|
|
||||||
aria-label={selectors.pages.Dashboard.Settings.Variables.Edit.General.previewOfValuesOption}
|
|
||||||
>
|
|
||||||
{o.text}
|
{o.text}
|
||||||
</span>
|
</InlineLabel>
|
||||||
</div>
|
</InlineFieldRow>
|
||||||
))}
|
))}
|
||||||
{previewOptions.length > previewLimit && (
|
</InlineFieldRow>
|
||||||
<div className="gf-form" ng-if="current.options.length > optionsLimit">
|
{options.length > previewLimit && (
|
||||||
<a
|
<InlineFieldRow className={styles.optionContainer}>
|
||||||
className="gf-form-label btn-secondary"
|
<Button
|
||||||
onClick={showMoreOptions}
|
onClick={showMoreOptions}
|
||||||
aria-label="Variable editor Preview of Values Show More link"
|
variant="secondary"
|
||||||
>
|
size="sm"
|
||||||
Show more
|
aria-label="Variable editor Preview of Values Show More link"
|
||||||
</a>
|
>
|
||||||
</div>
|
Show more
|
||||||
)}
|
</Button>
|
||||||
</div>
|
</InlineFieldRow>
|
||||||
</div>
|
)}
|
||||||
|
</VerticalGroup>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
VariableValuesPreview.displayName = 'VariableValuesPreview';
|
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,
|
VariableModel,
|
||||||
VariableQueryEditorType,
|
VariableQueryEditorType,
|
||||||
VariableWithMultiSupport,
|
VariableWithMultiSupport,
|
||||||
|
VariableWithOptions,
|
||||||
} from './types';
|
} from './types';
|
||||||
import { VariableQueryProps } from '../../types';
|
import { VariableQueryProps } from '../../types';
|
||||||
import { LEGACY_VARIABLE_QUERY_EDITOR_NAME } from './editor/LegacyVariableQueryEditor';
|
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';
|
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<
|
interface DataSourceWithLegacyVariableSupport<
|
||||||
TQuery extends DataQuery = DataQuery,
|
TQuery extends DataQuery = DataQuery,
|
||||||
TOptions extends DataSourceJsonData = DataSourceJsonData
|
TOptions extends DataSourceJsonData = DataSourceJsonData
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
import React, { ChangeEvent, FocusEvent, PureComponent } from 'react';
|
import React, { ChangeEvent, FocusEvent, PureComponent } from 'react';
|
||||||
|
import { InlineFieldRow, VerticalGroup } from '@grafana/ui';
|
||||||
|
|
||||||
import { IntervalVariableModel } from '../types';
|
import { IntervalVariableModel } from '../types';
|
||||||
import { VariableEditorProps } from '../editor/types';
|
import { VariableEditorProps } from '../editor/types';
|
||||||
import { InlineFormLabel, LegacyForms } from '@grafana/ui';
|
import { VariableSectionHeader } from '../editor/VariableSectionHeader';
|
||||||
|
import { VariableTextField } from '../editor/VariableTextField';
|
||||||
const { Switch } = LegacyForms;
|
import { VariableSwitchField } from '../editor/VariableSwitchField';
|
||||||
|
import { VariableSelectField } from '../editor/VariableSelectField';
|
||||||
|
import { SelectableValue } from '@grafana/data';
|
||||||
|
|
||||||
export interface Props extends VariableEditorProps<IntervalVariableModel> {}
|
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({
|
this.props.onPropChange({
|
||||||
propName: 'auto_count',
|
propName: 'auto_count',
|
||||||
propValue: event.target.value,
|
propValue: option.value,
|
||||||
updateOptions: true,
|
updateOptions: true,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -49,73 +52,59 @@ export class IntervalVariableEditor extends PureComponent<Props> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
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 (
|
return (
|
||||||
<>
|
<VerticalGroup spacing="xs">
|
||||||
<div className="gf-form-group">
|
<VariableSectionHeader name="Interval Options" />
|
||||||
<h5 className="section-heading">Interval Options</h5>
|
<VerticalGroup spacing="none">
|
||||||
|
<VariableTextField
|
||||||
<div className="gf-form">
|
value={this.props.variable.query}
|
||||||
<span className="gf-form-label width-9">Values</span>
|
name="Values"
|
||||||
<input
|
placeholder="1m,10m,1h,6h,1d,7d"
|
||||||
type="text"
|
onChange={this.onQueryChanged}
|
||||||
className="gf-form-input"
|
onBlur={this.onQueryBlur}
|
||||||
value={this.props.variable.query}
|
labelWidth={20}
|
||||||
placeholder="1m,10m,1h,6h,1d,7d"
|
grow
|
||||||
onChange={this.onQueryChanged}
|
required
|
||||||
onBlur={this.onQueryBlur}
|
/>
|
||||||
required
|
<InlineFieldRow>
|
||||||
/>
|
<VariableSwitchField
|
||||||
</div>
|
value={this.props.variable.auto}
|
||||||
|
name="Auto Option"
|
||||||
<div className="gf-form-inline">
|
tooltip="Interval will be dynamically calculated by dividing time range by the count specified"
|
||||||
<Switch
|
|
||||||
label="Auto Option"
|
|
||||||
labelClass="width-9"
|
|
||||||
checked={this.props.variable.auto}
|
|
||||||
onChange={this.onAutoChange}
|
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">
|
<VariableSelectField
|
||||||
<InlineFormLabel
|
name="Step count"
|
||||||
width={9}
|
value={stepValue}
|
||||||
tooltip={'How many times should the current time range be divided to calculate the value'}
|
options={stepOptions}
|
||||||
>
|
onChange={this.onAutoCountChanged}
|
||||||
Step count
|
tooltip="How many times should the current time range be divided to calculate the value"
|
||||||
</InlineFormLabel>
|
labelWidth={7}
|
||||||
<div className="gf-form-select-wrapper max-width-10">
|
width={9}
|
||||||
<select
|
/>
|
||||||
className="gf-form-input"
|
<VariableTextField
|
||||||
value={this.props.variable.auto_count}
|
value={this.props.variable.auto_min}
|
||||||
onChange={this.onAutoCountChanged}
|
name="Min interval"
|
||||||
>
|
placeholder="10s"
|
||||||
{[1, 2, 3, 4, 5, 10, 20, 30, 40, 50, 100, 200, 300, 400, 500].map(count => (
|
onChange={this.onAutoMinChanged}
|
||||||
<option key={`auto_count_key-${count}`} label={`${count}`}>
|
tooltip="The calculated value will not go below this threshold"
|
||||||
{count}
|
labelWidth={13}
|
||||||
</option>
|
width={11}
|
||||||
))}
|
/>
|
||||||
</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>
|
|
||||||
</>
|
</>
|
||||||
)}
|
) : null}
|
||||||
</div>
|
</InlineFieldRow>
|
||||||
</div>
|
</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 { VariableHide, VariableModel } from '../types';
|
||||||
import { selectors } from '@grafana/e2e-selectors';
|
import { selectors } from '@grafana/e2e-selectors';
|
||||||
import { variableAdapters } from '../adapters';
|
import { variableAdapters } from '../adapters';
|
||||||
|
import { Tooltip } from '@grafana/ui';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
variable: VariableModel;
|
variable: VariableModel;
|
||||||
@ -9,7 +10,6 @@ interface Props {
|
|||||||
|
|
||||||
export const PickerRenderer: FunctionComponent<Props> = props => {
|
export const PickerRenderer: FunctionComponent<Props> = props => {
|
||||||
const PickerToRender = useMemo(() => variableAdapters.get(props.variable.type).picker, [props.variable]);
|
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) {
|
if (!props.variable) {
|
||||||
return <div>Couldn't load variable</div>;
|
return <div>Couldn't load variable</div>;
|
||||||
@ -17,17 +17,40 @@ export const PickerRenderer: FunctionComponent<Props> = props => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="gf-form">
|
<div className="gf-form">
|
||||||
{props.variable.hide === VariableHide.dontHide && (
|
<PickerLabel variable={props.variable} />
|
||||||
<label
|
|
||||||
className="gf-form-label gf-form-label--variable"
|
|
||||||
aria-label={selectors.pages.Dashboard.SubMenu.submenuItemLabels(labelOrName)}
|
|
||||||
>
|
|
||||||
{labelOrName}
|
|
||||||
</label>
|
|
||||||
)}
|
|
||||||
{props.variable.hide !== VariableHide.hideVariable && PickerToRender && (
|
{props.variable.hide !== VariableHide.hideVariable && PickerToRender && (
|
||||||
<PickerToRender variable={props.variable} />
|
<PickerToRender variable={props.variable} />
|
||||||
)}
|
)}
|
||||||
</div>
|
</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 React, { ChangeEvent, PureComponent } from 'react';
|
||||||
import { MapDispatchToProps, MapStateToProps } from 'react-redux';
|
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 { selectors } from '@grafana/e2e-selectors';
|
||||||
import { getTemplateSrv } from '@grafana/runtime';
|
import { getTemplateSrv } from '@grafana/runtime';
|
||||||
import { LoadingState } from '@grafana/data';
|
import { LoadingState, SelectableValue } from '@grafana/data';
|
||||||
|
|
||||||
import { SelectionOptionsEditor } from '../editor/SelectionOptionsEditor';
|
import { SelectionOptionsEditor } from '../editor/SelectionOptionsEditor';
|
||||||
import { QueryVariableModel, VariableRefresh, VariableSort, VariableWithMultiSupport } from '../types';
|
import { QueryVariableModel, VariableRefresh, VariableSort, VariableWithMultiSupport } from '../types';
|
||||||
@ -17,8 +17,12 @@ import { toVariableIdentifier } from '../state/types';
|
|||||||
import { changeVariableMultiValue } from '../state/actions';
|
import { changeVariableMultiValue } from '../state/actions';
|
||||||
import { getTimeSrv } from '../../dashboard/services/TimeSrv';
|
import { getTimeSrv } from '../../dashboard/services/TimeSrv';
|
||||||
import { isLegacyQueryEditor, isQueryEditor } from '../guard';
|
import { isLegacyQueryEditor, isQueryEditor } from '../guard';
|
||||||
|
import { VariableSectionHeader } from '../editor/VariableSectionHeader';
|
||||||
const { Switch } = LegacyForms;
|
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> {}
|
export interface OwnProps extends VariableEditorProps<QueryVariableModel> {}
|
||||||
|
|
||||||
@ -61,18 +65,9 @@ export class QueryVariableEditorUnConnected extends PureComponent<Props, State>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getSelectedDataSourceValue = (): string => {
|
onDataSourceChange = (option: SelectableValue<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>) => {
|
|
||||||
this.props.onPropChange({ propName: 'query', propValue: '' });
|
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) => {
|
onLegacyQueryChange = async (query: any, definition: string) => {
|
||||||
@ -126,12 +121,12 @@ export class QueryVariableEditorUnConnected extends PureComponent<Props, State>
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onRefreshChange = (event: ChangeEvent<HTMLSelectElement>) => {
|
onRefreshChange = (option: SelectableValue<VariableRefresh>) => {
|
||||||
this.props.onPropChange({ propName: 'refresh', propValue: parseInt(event.target.value, 10) });
|
this.props.onPropChange({ propName: 'refresh', propValue: option.value });
|
||||||
};
|
};
|
||||||
|
|
||||||
onSortChange = async (event: ChangeEvent<HTMLSelectElement>) => {
|
onSortChange = async (option: SelectableValue<VariableSort>) => {
|
||||||
this.props.onPropChange({ propName: 'sort', propValue: parseInt(event.target.value, 10), updateOptions: true });
|
this.props.onPropChange({ propName: 'sort', propValue: option.value, updateOptions: true });
|
||||||
};
|
};
|
||||||
|
|
||||||
onSelectionOptionsChange = async ({ propValue, propName }: OnPropChangeArguments<VariableWithMultiSupport>) => {
|
onSelectionOptionsChange = async ({ propValue, propName }: OnPropChangeArguments<VariableWithMultiSupport>) => {
|
||||||
@ -185,62 +180,28 @@ export class QueryVariableEditorUnConnected extends PureComponent<Props, State>
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<>
|
<VerticalGroup spacing="xs">
|
||||||
<div className="gf-form-group">
|
<VariableSectionHeader name="Query Options" />
|
||||||
<h5 className="section-heading">Query Options</h5>
|
<VerticalGroup spacing="md">
|
||||||
<div className="gf-form-inline">
|
<VerticalGroup spacing="none">
|
||||||
<div className="gf-form max-width-21">
|
<VerticalGroup spacing="xs">
|
||||||
<span className="gf-form-label width-10">Data source</span>
|
<InlineFieldRow>
|
||||||
<div className="gf-form-select-wrapper max-width-14">
|
<QueryVariableDatasourceSelect
|
||||||
<select
|
|
||||||
className="gf-form-input"
|
|
||||||
value={this.getSelectedDataSourceValue()}
|
|
||||||
onChange={this.onDataSourceChange}
|
onChange={this.onDataSourceChange}
|
||||||
required
|
datasource={this.props.variable.datasource}
|
||||||
aria-label={
|
dataSources={this.props.editor.extended?.dataSources}
|
||||||
selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsDataSourceSelect
|
/>
|
||||||
}
|
<QueryVariableRefreshSelect onChange={this.onRefreshChange} refresh={this.props.variable.refresh} />
|
||||||
>
|
</InlineFieldRow>
|
||||||
{this.props.editor.extended?.dataSources?.length &&
|
<div style={{ flexDirection: 'column' }}>{this.renderQueryEditor()}</div>
|
||||||
this.props.editor.extended?.dataSources.map(ds => (
|
</VerticalGroup>
|
||||||
<option key={ds.value ?? ''} value={ds.value ?? ''} label={ds.name}>
|
<VariableTextField
|
||||||
{ds.name}
|
value={this.state.regex ?? this.props.variable.regex}
|
||||||
</option>
|
name="Regex"
|
||||||
))}
|
placeholder="/.*-(?<text>.*)-(?<value>.*)-.*/"
|
||||||
</select>
|
onChange={this.onRegExChange}
|
||||||
</div>
|
onBlur={this.onRegExBlur}
|
||||||
</div>
|
labelWidth={20}
|
||||||
|
|
||||||
<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}
|
|
||||||
tooltip={
|
tooltip={
|
||||||
<div>
|
<div>
|
||||||
Optional, if you want to extract part of a series name or metric node segment. Named capture groups
|
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>
|
</div>
|
||||||
}
|
}
|
||||||
>
|
ariaLabel={selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsRegExInput}
|
||||||
Regex
|
grow
|
||||||
</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}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
<QueryVariableSortSelect onChange={this.onSortChange} sort={this.props.variable.sort} />
|
||||||
<div className="gf-form max-width-21">
|
</VerticalGroup>
|
||||||
<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>
|
|
||||||
|
|
||||||
<SelectionOptionsEditor
|
<SelectionOptionsEditor
|
||||||
variable={this.props.variable}
|
variable={this.props.variable}
|
||||||
onPropChange={this.onSelectionOptionsChange}
|
onPropChange={this.onSelectionOptionsChange}
|
||||||
onMultiChanged={this.props.changeVariableMultiValue}
|
onMultiChanged={this.props.changeVariableMultiValue}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="gf-form-group">
|
<VerticalGroup spacing="none">
|
||||||
<h5>Value groups/tags (Experimental feature)</h5>
|
<h5>Value groups/tags (Experimental feature)</h5>
|
||||||
<div
|
<VariableSwitchField
|
||||||
aria-label={selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.valueGroupsTagsEnabledSwitch}
|
value={this.props.variable.useTags}
|
||||||
>
|
name="Enabled"
|
||||||
<Switch
|
|
||||||
label="Enabled"
|
|
||||||
label-class="width-10"
|
|
||||||
checked={this.props.variable.useTags}
|
|
||||||
onChange={this.onUseTagsChange}
|
onChange={this.onUseTagsChange}
|
||||||
|
ariaLabel={selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.valueGroupsTagsEnabledSwitch}
|
||||||
/>
|
/>
|
||||||
</div>
|
{this.props.variable.useTags ? (
|
||||||
{this.props.variable.useTags && (
|
<VerticalGroup spacing="none">
|
||||||
<>
|
<VariableTextField
|
||||||
<div className="gf-form last">
|
|
||||||
<span className="gf-form-label width-10">Tags query</span>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
className="gf-form-input"
|
|
||||||
value={this.state.tagsQuery ?? this.props.variable.tagsQuery}
|
value={this.state.tagsQuery ?? this.props.variable.tagsQuery}
|
||||||
|
name="Tags query"
|
||||||
placeholder="metric name or tags query"
|
placeholder="metric name or tags query"
|
||||||
onChange={this.onTagsQueryChange}
|
onChange={this.onTagsQueryChange}
|
||||||
onBlur={this.onTagsQueryBlur}
|
onBlur={this.onTagsQueryBlur}
|
||||||
aria-label={
|
ariaLabel={
|
||||||
selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.valueGroupsTagsTagsQueryInput
|
selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.valueGroupsTagsTagsQueryInput
|
||||||
}
|
}
|
||||||
|
labelWidth={20}
|
||||||
|
grow
|
||||||
/>
|
/>
|
||||||
</div>
|
<VariableTextField
|
||||||
<div className="gf-form">
|
|
||||||
<li className="gf-form-label width-10">Tag values query</li>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
className="gf-form-input"
|
|
||||||
value={this.state.tagValuesQuery ?? this.props.variable.tagValuesQuery}
|
value={this.state.tagValuesQuery ?? this.props.variable.tagValuesQuery}
|
||||||
|
name="Tag values query"
|
||||||
placeholder="apps.$tag.*"
|
placeholder="apps.$tag.*"
|
||||||
onChange={this.onTagValuesQueryChange}
|
onChange={this.onTagValuesQueryChange}
|
||||||
onBlur={this.onTagValuesQueryBlur}
|
onBlur={this.onTagValuesQueryBlur}
|
||||||
aria-label={
|
ariaLabel={
|
||||||
selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.valueGroupsTagsTagsValuesQueryInput
|
selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.valueGroupsTagsTagsValuesQueryInput
|
||||||
}
|
}
|
||||||
|
labelWidth={20}
|
||||||
|
grow
|
||||||
/>
|
/>
|
||||||
</div>
|
</VerticalGroup>
|
||||||
</>
|
) : null}
|
||||||
)}
|
</VerticalGroup>
|
||||||
</div>
|
</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,
|
includeAll: true,
|
||||||
state: LoadingState.NotStarted,
|
state: LoadingState.NotStarted,
|
||||||
error: null,
|
error: null,
|
||||||
|
description: null,
|
||||||
...(extend ?? {}),
|
...(extend ?? {}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -28,6 +28,7 @@ export const getVariableState = (
|
|||||||
global: false,
|
global: false,
|
||||||
state: LoadingState.NotStarted,
|
state: LoadingState.NotStarted,
|
||||||
error: null,
|
error: null,
|
||||||
|
description: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,6 +44,7 @@ export const getVariableState = (
|
|||||||
global: false,
|
global: false,
|
||||||
state: LoadingState.NotStarted,
|
state: LoadingState.NotStarted,
|
||||||
error: null,
|
error: null,
|
||||||
|
description: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,6 +71,7 @@ describe('sharedReducer', () => {
|
|||||||
global: false,
|
global: false,
|
||||||
state: LoadingState.NotStarted,
|
state: LoadingState.NotStarted,
|
||||||
error: null,
|
error: null,
|
||||||
|
description: null,
|
||||||
},
|
},
|
||||||
'2': {
|
'2': {
|
||||||
id: '2',
|
id: '2',
|
||||||
@ -83,6 +84,7 @@ describe('sharedReducer', () => {
|
|||||||
global: false,
|
global: false,
|
||||||
state: LoadingState.NotStarted,
|
state: LoadingState.NotStarted,
|
||||||
error: null,
|
error: null,
|
||||||
|
description: null,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -107,6 +109,7 @@ describe('sharedReducer', () => {
|
|||||||
global: false,
|
global: false,
|
||||||
state: LoadingState.NotStarted,
|
state: LoadingState.NotStarted,
|
||||||
error: null,
|
error: null,
|
||||||
|
description: null,
|
||||||
},
|
},
|
||||||
'2': {
|
'2': {
|
||||||
id: '2',
|
id: '2',
|
||||||
@ -119,6 +122,7 @@ describe('sharedReducer', () => {
|
|||||||
global: false,
|
global: false,
|
||||||
state: LoadingState.NotStarted,
|
state: LoadingState.NotStarted,
|
||||||
error: null,
|
error: null,
|
||||||
|
description: null,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -143,6 +147,7 @@ describe('sharedReducer', () => {
|
|||||||
global: false,
|
global: false,
|
||||||
state: LoadingState.NotStarted,
|
state: LoadingState.NotStarted,
|
||||||
error: null,
|
error: null,
|
||||||
|
description: null,
|
||||||
},
|
},
|
||||||
'1': {
|
'1': {
|
||||||
id: '1',
|
id: '1',
|
||||||
@ -155,6 +160,7 @@ describe('sharedReducer', () => {
|
|||||||
global: false,
|
global: false,
|
||||||
state: LoadingState.NotStarted,
|
state: LoadingState.NotStarted,
|
||||||
error: null,
|
error: null,
|
||||||
|
description: null,
|
||||||
},
|
},
|
||||||
'2': {
|
'2': {
|
||||||
id: '2',
|
id: '2',
|
||||||
@ -167,6 +173,7 @@ describe('sharedReducer', () => {
|
|||||||
global: false,
|
global: false,
|
||||||
state: LoadingState.NotStarted,
|
state: LoadingState.NotStarted,
|
||||||
error: null,
|
error: null,
|
||||||
|
description: null,
|
||||||
},
|
},
|
||||||
'11': {
|
'11': {
|
||||||
...initialQueryVariableModelState,
|
...initialQueryVariableModelState,
|
||||||
@ -198,6 +205,7 @@ describe('sharedReducer', () => {
|
|||||||
global: false,
|
global: false,
|
||||||
state: LoadingState.NotStarted,
|
state: LoadingState.NotStarted,
|
||||||
error: null,
|
error: null,
|
||||||
|
description: null,
|
||||||
},
|
},
|
||||||
'1': {
|
'1': {
|
||||||
id: '1',
|
id: '1',
|
||||||
@ -210,6 +218,7 @@ describe('sharedReducer', () => {
|
|||||||
global: false,
|
global: false,
|
||||||
state: LoadingState.NotStarted,
|
state: LoadingState.NotStarted,
|
||||||
error: null,
|
error: null,
|
||||||
|
description: null,
|
||||||
},
|
},
|
||||||
'2': {
|
'2': {
|
||||||
id: '2',
|
id: '2',
|
||||||
@ -222,6 +231,7 @@ describe('sharedReducer', () => {
|
|||||||
global: false,
|
global: false,
|
||||||
state: LoadingState.NotStarted,
|
state: LoadingState.NotStarted,
|
||||||
error: null,
|
error: null,
|
||||||
|
description: null,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
import React, { ChangeEvent, PureComponent } from 'react';
|
import React, { ChangeEvent, PureComponent } from 'react';
|
||||||
|
import { VerticalGroup } from '@grafana/ui';
|
||||||
|
|
||||||
import { TextBoxVariableModel } from '../types';
|
import { TextBoxVariableModel } from '../types';
|
||||||
import { VariableEditorProps } from '../editor/types';
|
import { VariableEditorProps } from '../editor/types';
|
||||||
|
import { VariableSectionHeader } from '../editor/VariableSectionHeader';
|
||||||
|
import { VariableTextField } from '../editor/VariableTextField';
|
||||||
|
|
||||||
export interface Props extends VariableEditorProps<TextBoxVariableModel> {}
|
export interface Props extends VariableEditorProps<TextBoxVariableModel> {}
|
||||||
export class TextBoxVariableEditor extends PureComponent<Props> {
|
export class TextBoxVariableEditor extends PureComponent<Props> {
|
||||||
@ -15,20 +19,18 @@ export class TextBoxVariableEditor extends PureComponent<Props> {
|
|||||||
render() {
|
render() {
|
||||||
const { query } = this.props.variable;
|
const { query } = this.props.variable;
|
||||||
return (
|
return (
|
||||||
<div className="gf-form-group">
|
<VerticalGroup spacing="xs">
|
||||||
<h5 className="section-heading">Text options</h5>
|
<VariableSectionHeader name="Text Options" />
|
||||||
<div className="gf-form">
|
<VariableTextField
|
||||||
<span className="gf-form-label">Default value</span>
|
value={query}
|
||||||
<input
|
name="Default value"
|
||||||
type="text"
|
placeholder="default value, if any"
|
||||||
className="gf-form-input"
|
onChange={this.onQueryChange}
|
||||||
value={query}
|
onBlur={this.onQueryBlur}
|
||||||
onChange={this.onQueryChange}
|
labelWidth={20}
|
||||||
onBlur={this.onQueryBlur}
|
grow
|
||||||
placeholder="default value, if any"
|
/>
|
||||||
/>
|
</VerticalGroup>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -139,6 +139,7 @@ export interface VariableModel extends BaseVariableModel {
|
|||||||
index: number;
|
index: number;
|
||||||
state: LoadingState;
|
state: LoadingState;
|
||||||
error: any | null;
|
error: any | null;
|
||||||
|
description: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const initialVariableModelState: VariableModel = {
|
export const initialVariableModelState: VariableModel = {
|
||||||
@ -152,6 +153,7 @@ export const initialVariableModelState: VariableModel = {
|
|||||||
skipUrlSync: false,
|
skipUrlSync: false,
|
||||||
state: LoadingState.NotStarted,
|
state: LoadingState.NotStarted,
|
||||||
error: null,
|
error: null,
|
||||||
|
description: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
export type VariableQueryEditorType<
|
export type VariableQueryEditorType<
|
||||||
|
Loading…
Reference in New Issue
Block a user