diff --git a/devenv/dev-dashboards/feature-templating/templating-textbox-e2e-scenarios.json b/devenv/dev-dashboards/feature-templating/templating-textbox-e2e-scenarios.json
new file mode 100644
index 00000000000..37c853cafcc
--- /dev/null
+++ b/devenv/dev-dashboards/feature-templating/templating-textbox-e2e-scenarios.json
@@ -0,0 +1,116 @@
+{
+ "__inputs": [
+ {
+ "name": "DS_GDEV-TESTDATA",
+ "label": "gdev-testdata",
+ "description": "",
+ "type": "datasource",
+ "pluginId": "testdata",
+ "pluginName": "TestData DB"
+ }
+ ],
+ "__requires": [
+ {
+ "type": "grafana",
+ "id": "grafana",
+ "name": "Grafana",
+ "version": "7.4.0-pre"
+ },
+ {
+ "type": "datasource",
+ "id": "testdata",
+ "name": "TestData DB",
+ "version": "1.0.0"
+ },
+ {
+ "type": "panel",
+ "id": "text",
+ "name": "Text",
+ "version": ""
+ }
+ ],
+ "annotations": {
+ "list": [
+ {
+ "builtIn": 1,
+ "datasource": "-- Grafana --",
+ "enable": true,
+ "hide": true,
+ "iconColor": "rgba(0, 211, 255, 1)",
+ "name": "Annotations & Alerts",
+ "type": "dashboard"
+ }
+ ]
+ },
+ "editable": true,
+ "gnetId": null,
+ "graphTooltip": 0,
+ "id": null,
+ "iteration": 1606804991052,
+ "links": [],
+ "panels": [
+ {
+ "datasource": "${DS_GDEV-TESTDATA}",
+ "fieldConfig": {
+ "defaults": {
+ "custom": {}
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 9,
+ "w": 12,
+ "x": 0,
+ "y": 0
+ },
+ "id": 2,
+ "options": {
+ "content": "# variable: ${text}\n ",
+ "mode": "markdown"
+ },
+ "pluginVersion": "7.4.0-pre",
+ "timeFrom": null,
+ "timeShift": null,
+ "title": "Panel Title",
+ "type": "text"
+ }
+ ],
+ "schemaVersion": 27,
+ "style": "dark",
+ "tags": [],
+ "templating": {
+ "list": [
+ {
+ "current": {
+ "selected": false,
+ "text": "default value",
+ "value": "default value"
+ },
+ "description": null,
+ "error": null,
+ "hide": 0,
+ "label": null,
+ "name": "text",
+ "options": [
+ {
+ "selected": true,
+ "text": "default value",
+ "value": "default value"
+ }
+ ],
+ "query": "default value",
+ "skipUrlSync": false,
+ "type": "textbox"
+ }
+ ]
+ },
+ "time": {
+ "from": "now-6h",
+ "to": "now"
+ },
+ "timepicker": {},
+ "timezone": "",
+ "title": "Templating - Textbox e2e scenarios",
+ "uid": "AejrN1AMz",
+ "version": 1
+}
diff --git a/e2e/suite1/specs/variables/textbox-variables.ts b/e2e/suite1/specs/variables/textbox-variables.ts
new file mode 100644
index 00000000000..f85c5809d88
--- /dev/null
+++ b/e2e/suite1/specs/variables/textbox-variables.ts
@@ -0,0 +1,299 @@
+import { e2e } from '@grafana/e2e';
+
+const PAGE_UNDER_TEST = 'AejrN1AMz';
+
+describe('TextBox - load options scenarios', function() {
+ it('default options should be correct', function() {
+ e2e.flows.login('admin', 'admin');
+ e2e.flows.openDashboard({ uid: PAGE_UNDER_TEST });
+ e2e().server();
+ e2e()
+ .route({
+ method: 'GET',
+ url: `/api/dashboards/uid/${PAGE_UNDER_TEST}`,
+ })
+ .as('dash');
+
+ e2e().wait('@dash');
+
+ validateTextboxAndMarkup('default value');
+ });
+
+ it('loading variable from url should be correct', function() {
+ e2e.flows.login('admin', 'admin');
+ e2e.flows.openDashboard({ uid: `${PAGE_UNDER_TEST}?var-text=not default value` });
+ e2e().server();
+ e2e()
+ .route({
+ method: 'GET',
+ url: `/api/dashboards/uid/${PAGE_UNDER_TEST}`,
+ })
+ .as('dash');
+
+ e2e().wait('@dash');
+
+ validateTextboxAndMarkup('not default value');
+ });
+});
+
+describe('TextBox - change query scenarios', function() {
+ it('when changing the query value and not saving current as default should revert query value', function() {
+ copyExistingDashboard();
+
+ changeQueryInput();
+
+ e2e.components.BackButton.backArrow()
+ .should('be.visible')
+ .click({ force: true });
+
+ validateTextboxAndMarkup('changed value');
+
+ saveDashboard(false);
+
+ e2e()
+ .get('@dashuid')
+ .then((dashuid: any) => {
+ expect(dashuid).not.to.eq(PAGE_UNDER_TEST);
+
+ e2e.flows.openDashboard({ uid: dashuid });
+
+ e2e().wait('@load-dash');
+
+ validateTextboxAndMarkup('default value');
+
+ validateVariable('changed value');
+ });
+ });
+
+ it('when changing the query value and saving current as default should change query value', function() {
+ copyExistingDashboard();
+
+ changeQueryInput();
+
+ e2e.components.BackButton.backArrow()
+ .should('be.visible')
+ .click({ force: true });
+
+ validateTextboxAndMarkup('changed value');
+
+ saveDashboard(true);
+
+ e2e()
+ .get('@dashuid')
+ .then((dashuid: any) => {
+ expect(dashuid).not.to.eq(PAGE_UNDER_TEST);
+
+ e2e.flows.openDashboard({ uid: dashuid });
+
+ e2e().wait('@load-dash');
+
+ validateTextboxAndMarkup('changed value');
+
+ validateVariable('changed value');
+ });
+ });
+});
+
+describe('TextBox - change picker value scenarios', function() {
+ it('when changing the input value and not saving current as default should revert query value', function() {
+ copyExistingDashboard();
+
+ changeTextBoxInput();
+
+ validateTextboxAndMarkup('changed value');
+
+ saveDashboard(false);
+
+ e2e()
+ .get('@dashuid')
+ .then((dashuid: any) => {
+ expect(dashuid).not.to.eq(PAGE_UNDER_TEST);
+
+ e2e.flows.openDashboard({ uid: dashuid });
+
+ e2e().wait('@load-dash');
+
+ validateTextboxAndMarkup('default value');
+ validateVariable('default value');
+ });
+ });
+
+ it('when changing the input value and saving current as default should change query value', function() {
+ copyExistingDashboard();
+
+ changeTextBoxInput();
+
+ validateTextboxAndMarkup('changed value');
+
+ saveDashboard(true);
+
+ e2e()
+ .get('@dashuid')
+ .then((dashuid: any) => {
+ expect(dashuid).not.to.eq(PAGE_UNDER_TEST);
+
+ e2e.flows.openDashboard({ uid: dashuid });
+
+ e2e().wait('@load-dash');
+
+ validateTextboxAndMarkup('changed value');
+ validateVariable('changed value');
+ });
+ });
+});
+
+function copyExistingDashboard() {
+ e2e.flows.login('admin', 'admin');
+ e2e().server();
+ e2e()
+ .route({
+ method: 'GET',
+ url: '/api/search?query=&type=dash-folder&permission=Edit',
+ })
+ .as('dash-settings');
+ e2e()
+ .route({
+ method: 'POST',
+ url: '/api/dashboards/db/',
+ })
+ .as('save-dash');
+ e2e()
+ .route({
+ method: 'GET',
+ url: /\/api\/dashboards\/uid\/(?!AejrN1AMz)\w+/,
+ })
+ .as('load-dash');
+ e2e.flows.openDashboard({ uid: `${PAGE_UNDER_TEST}?editview=settings&orgId=1` });
+
+ e2e().wait('@dash-settings');
+
+ e2e.pages.Dashboard.Settings.General.saveAsDashBoard()
+ .should('be.visible')
+ .click();
+
+ e2e.pages.SaveDashboardAsModal.newName()
+ .should('be.visible')
+ .type(`${Date.now()}`);
+
+ e2e.pages.SaveDashboardAsModal.save()
+ .should('be.visible')
+ .click();
+
+ e2e().wait('@save-dash');
+ e2e().wait('@load-dash');
+
+ e2e.pages.Dashboard.SubMenu.submenuItem().should('be.visible');
+
+ e2e()
+ .location()
+ .then(loc => {
+ const dashuid = /\/d\/(\w+)\//.exec(loc.href)![1];
+ e2e()
+ .wrap(dashuid)
+ .as('dashuid');
+ });
+
+ e2e().wait(500);
+}
+
+function saveDashboard(saveVariables: boolean) {
+ e2e.pages.Dashboard.Toolbar.toolbarItems('Save dashboard')
+ .should('be.visible')
+ .click();
+
+ if (saveVariables) {
+ e2e.pages.SaveDashboardModal.saveVariables()
+ .should('exist')
+ .click({ force: true });
+ }
+
+ e2e.pages.SaveDashboardModal.save()
+ .should('be.visible')
+ .click();
+
+ e2e().wait('@save-dash');
+}
+
+function validateTextboxAndMarkup(value: string) {
+ e2e.pages.Dashboard.SubMenu.submenuItem()
+ .should('be.visible')
+ .within(() => {
+ e2e.pages.Dashboard.SubMenu.submenuItemLabels('text').should('be.visible');
+ e2e()
+ .get('input')
+ .should('be.visible')
+ .should('have.value', value);
+ });
+
+ e2e.components.Panels.Visualization.Text.container()
+ .should('be.visible')
+ .within(() => {
+ e2e()
+ .get('h1')
+ .should('be.visible')
+ .should('have.text', `variable: ${value}`);
+ });
+}
+
+function validateVariable(value: string) {
+ e2e.pages.Dashboard.Toolbar.toolbarItems('Dashboard settings')
+ .should('be.visible')
+ .click();
+
+ e2e.pages.Dashboard.Settings.General.sectionItems('Variables')
+ .should('be.visible')
+ .click();
+
+ e2e.pages.Dashboard.Settings.Variables.List.tableRowNameFields('text')
+ .should('be.visible')
+ .click();
+
+ e2e.pages.Dashboard.Settings.Variables.Edit.TextBoxVariable.textBoxOptionsQueryInput()
+ .should('be.visible')
+ .should('have.value', value);
+}
+
+function changeTextBoxInput() {
+ e2e.pages.Dashboard.SubMenu.submenuItemLabels('text').should('be.visible');
+ e2e.pages.Dashboard.SubMenu.submenuItem()
+ .should('be.visible')
+ .within(() => {
+ e2e()
+ .get('input')
+ .should('be.visible')
+ .should('have.value', 'default value')
+ .clear()
+ .type('changed value')
+ .type('{enter}');
+ });
+
+ e2e()
+ .location()
+ .should(loc => {
+ expect(loc.search).to.contain('var-text=changed%20value');
+ });
+}
+
+function changeQueryInput() {
+ e2e.pages.Dashboard.Toolbar.toolbarItems('Dashboard settings')
+ .should('be.visible')
+ .click();
+
+ e2e.pages.Dashboard.Settings.General.sectionItems('Variables')
+ .should('be.visible')
+ .click();
+
+ e2e.pages.Dashboard.Settings.Variables.List.tableRowNameFields('text')
+ .should('be.visible')
+ .click();
+
+ e2e.pages.Dashboard.Settings.Variables.Edit.TextBoxVariable.textBoxOptionsQueryInput()
+ .should('be.visible')
+ .clear()
+ .type('changed value')
+ .blur();
+
+ e2e.pages.Dashboard.Settings.Variables.Edit.General.previewOfValuesOption()
+ .should('have.length', 1)
+ .should('have.text', 'changed value');
+}
diff --git a/packages/grafana-e2e-selectors/src/selectors/components.ts b/packages/grafana-e2e-selectors/src/selectors/components.ts
index 73deb45ab09..ebd777e28d4 100644
--- a/packages/grafana-e2e-selectors/src/selectors/components.ts
+++ b/packages/grafana-e2e-selectors/src/selectors/components.ts
@@ -37,6 +37,9 @@ export const Components = {
BarGauge: {
value: 'Bar gauge value',
},
+ Text: {
+ container: () => '.markdown-html',
+ },
},
},
Drawer: {
diff --git a/packages/grafana-e2e-selectors/src/selectors/pages.ts b/packages/grafana-e2e-selectors/src/selectors/pages.ts
index ff00865f431..2c753972131 100644
--- a/packages/grafana-e2e-selectors/src/selectors/pages.ts
+++ b/packages/grafana-e2e-selectors/src/selectors/pages.ts
@@ -99,6 +99,9 @@ export const Pages = {
ConstantVariable: {
constantOptionsQueryInput: 'Variable editor Form Constant Query field',
},
+ TextBoxVariable: {
+ textBoxOptionsQueryInput: 'Variable editor Form TextBox Query field',
+ },
},
},
},
diff --git a/public/app/features/dashboard/components/DashboardSettings/template.html b/public/app/features/dashboard/components/DashboardSettings/template.html
index 6fb87f257f5..b951945faba 100644
--- a/public/app/features/dashboard/components/DashboardSettings/template.html
+++ b/public/app/features/dashboard/components/DashboardSettings/template.html
@@ -10,10 +10,10 @@
diff --git a/public/app/features/dashboard/components/SaveDashboard/SaveDashboardButton.tsx b/public/app/features/dashboard/components/SaveDashboard/SaveDashboardButton.tsx
index da7bf2bae57..7a07d4132fd 100644
--- a/public/app/features/dashboard/components/SaveDashboard/SaveDashboardButton.tsx
+++ b/public/app/features/dashboard/components/SaveDashboard/SaveDashboardButton.tsx
@@ -5,6 +5,7 @@ import { connectWithProvider } from 'app/core/utils/connectWithReduxStore';
import { provideModalsContext } from 'app/routes/ReactContainer';
import { SaveDashboardAsModal } from './SaveDashboardAsModal';
import { SaveDashboardModalProxy } from './SaveDashboardModalProxy';
+import { selectors } from '@grafana/e2e-selectors';
interface SaveDashboardButtonProps {
dashboard: DashboardModel;
@@ -30,6 +31,7 @@ export const SaveDashboardButton: React.FC = ({ dashbo
onDismiss: hideModal,
});
}}
+ aria-label={selectors.pages.Dashboard.Settings.General.saveDashBoard}
>
Save dashboard
@@ -63,6 +65,7 @@ export const SaveDashboardAsButton: React.FC
Save As...
diff --git a/public/app/features/dashboard/state/DashboardMigrator.test.ts b/public/app/features/dashboard/state/DashboardMigrator.test.ts
index ee477e60c59..bdea27b2765 100644
--- a/public/app/features/dashboard/state/DashboardMigrator.test.ts
+++ b/public/app/features/dashboard/state/DashboardMigrator.test.ts
@@ -857,7 +857,7 @@ describe('DashboardModel', () => {
});
});
- it('should have three variables after migration', () => {
+ it('should have six variables after migration', () => {
expect(model.templating.list.length).toBe(6);
});
diff --git a/public/app/features/dashboard/state/DashboardModel.ts b/public/app/features/dashboard/state/DashboardModel.ts
index 429c4a5ea8b..947933bb9bd 100644
--- a/public/app/features/dashboard/state/DashboardModel.ts
+++ b/public/app/features/dashboard/state/DashboardModel.ts
@@ -14,12 +14,12 @@ import {
dateTimeFormat,
dateTimeFormatTimeAgo,
DateTimeInput,
+ EventBusExtended,
+ EventBusSrv,
PanelEvents,
TimeRange,
TimeZone,
UrlQueryValue,
- EventBusSrv,
- EventBusExtended,
} from '@grafana/data';
import { CoreEvents, DashboardMeta, KIOSK_MODE_TV } from 'app/types';
import { GetVariables, getVariables } from 'app/features/variables/state/selectors';
@@ -236,7 +236,9 @@ export class DashboardModel {
const currentVariables = this.getVariablesFromState();
copy.templating = {
- list: currentVariables.map(variable => variableAdapters.get(variable.type).getSaveModel(variable)),
+ list: currentVariables.map(variable =>
+ variableAdapters.get(variable.type).getSaveModel(variable, defaults.saveVariables)
+ ),
};
if (!defaults.saveVariables) {
diff --git a/public/app/features/variables/adapters.ts b/public/app/features/variables/adapters.ts
index f83fb0e50e8..6bfba8bd37c 100644
--- a/public/app/features/variables/adapters.ts
+++ b/public/app/features/variables/adapters.ts
@@ -34,7 +34,7 @@ export interface VariableAdapter {
setValue: (variable: Model, option: VariableOption, emitChanges?: boolean) => Promise;
setValueFromUrl: (variable: Model, urlValue: UrlQueryValue) => Promise;
updateOptions: (variable: Model, searchFilter?: string) => Promise;
- getSaveModel: (variable: Model) => Partial;
+ getSaveModel: (variable: Model, saveCurrentAsDefault?: boolean) => Partial;
getValueForUrl: (variable: Model) => string | string[];
picker: ComponentType;
editor: ComponentType;
diff --git a/public/app/features/variables/shared/testing/builders.ts b/public/app/features/variables/shared/testing/builders.ts
index 3a5e813ef56..c2351663973 100644
--- a/public/app/features/variables/shared/testing/builders.ts
+++ b/public/app/features/variables/shared/testing/builders.ts
@@ -11,11 +11,12 @@ import { initialCustomVariableModelState } from '../../custom/reducer';
import { MultiVariableBuilder } from './multiVariableBuilder';
import { initialConstantVariableModelState } from '../../constant/reducer';
import { QueryVariableBuilder } from './queryVariableBuilder';
+import { TextBoxVariableBuilder } from './textboxVariableBuilder';
export const adHocBuilder = () => new AdHocVariableBuilder(initialAdHocVariableModelState);
export const intervalBuilder = () => new IntervalVariableBuilder(initialIntervalVariableModelState);
export const datasourceBuilder = () => new DatasourceVariableBuilder(initialDataSourceVariableModelState);
export const queryBuilder = () => new QueryVariableBuilder(initialQueryVariableModelState);
-export const textboxBuilder = () => new OptionsVariableBuilder(initialTextBoxVariableModelState);
+export const textboxBuilder = () => new TextBoxVariableBuilder(initialTextBoxVariableModelState);
export const customBuilder = () => new MultiVariableBuilder(initialCustomVariableModelState);
export const constantBuilder = () => new OptionsVariableBuilder(initialConstantVariableModelState);
diff --git a/public/app/features/variables/shared/testing/textboxVariableBuilder.ts b/public/app/features/variables/shared/testing/textboxVariableBuilder.ts
new file mode 100644
index 00000000000..6170c7ee8f7
--- /dev/null
+++ b/public/app/features/variables/shared/testing/textboxVariableBuilder.ts
@@ -0,0 +1,9 @@
+import { TextBoxVariableModel } from 'app/features/variables/types';
+import { OptionsVariableBuilder } from './optionsVariableBuilder';
+
+export class TextBoxVariableBuilder extends OptionsVariableBuilder {
+ withOriginalQuery(original: string) {
+ this.variable.originalQuery = original;
+ return this;
+ }
+}
diff --git a/public/app/features/variables/textbox/TextBoxVariableEditor.tsx b/public/app/features/variables/textbox/TextBoxVariableEditor.tsx
index 7ca8e3a204d..8f078c2a335 100644
--- a/public/app/features/variables/textbox/TextBoxVariableEditor.tsx
+++ b/public/app/features/variables/textbox/TextBoxVariableEditor.tsx
@@ -1,36 +1,40 @@
-import React, { ChangeEvent, PureComponent } from 'react';
+import React, { ChangeEvent, ReactElement, useCallback } from 'react';
import { VerticalGroup } from '@grafana/ui';
import { TextBoxVariableModel } from '../types';
import { VariableEditorProps } from '../editor/types';
import { VariableSectionHeader } from '../editor/VariableSectionHeader';
import { VariableTextField } from '../editor/VariableTextField';
+import { selectors } from '@grafana/e2e-selectors';
export interface Props extends VariableEditorProps {}
-export class TextBoxVariableEditor extends PureComponent {
- onQueryChange = (event: ChangeEvent) => {
- event.preventDefault();
- this.props.onPropChange({ propName: 'query', propValue: event.target.value, updateOptions: false });
- };
- onQueryBlur = (event: ChangeEvent) => {
- event.preventDefault();
- this.props.onPropChange({ propName: 'query', propValue: event.target.value, updateOptions: true });
- };
- render() {
- const { query } = this.props.variable;
- return (
-
-
-
-
- );
- }
+
+export function TextBoxVariableEditor({ onPropChange, variable: { query } }: Props): ReactElement {
+ const updateVariable = useCallback(
+ (event: ChangeEvent, updateOptions: boolean) => {
+ event.preventDefault();
+ onPropChange({ propName: 'originalQuery', propValue: event.target.value, updateOptions: false });
+ onPropChange({ propName: 'query', propValue: event.target.value, updateOptions });
+ },
+ [onPropChange]
+ );
+
+ const onChange = useCallback((e: ChangeEvent) => updateVariable(e, false), [updateVariable]);
+ const onBlur = useCallback((e: ChangeEvent) => updateVariable(e, true), [updateVariable]);
+
+ return (
+
+
+
+
+ );
}
diff --git a/public/app/features/variables/textbox/TextBoxVariablePicker.tsx b/public/app/features/variables/textbox/TextBoxVariablePicker.tsx
index 10632773665..b5f3e4a1570 100644
--- a/public/app/features/variables/textbox/TextBoxVariablePicker.tsx
+++ b/public/app/features/variables/textbox/TextBoxVariablePicker.tsx
@@ -1,43 +1,45 @@
-import React, { ChangeEvent, FocusEvent, KeyboardEvent, PureComponent } from 'react';
+import React, { ChangeEvent, FocusEvent, KeyboardEvent, ReactElement, useCallback, useEffect, useState } from 'react';
import { TextBoxVariableModel } from '../types';
-import { toVariableIdentifier, toVariablePayload } from '../state/types';
-import { dispatch } from '../../../store/store';
+import { toVariablePayload } from '../state/types';
import { changeVariableProp } from '../state/sharedReducer';
import { VariablePickerProps } from '../pickers/types';
-import { updateOptions } from '../state/actions';
+import { Input } from '@grafana/ui';
+import { variableAdapters } from '../adapters';
+import { useDispatch } from 'react-redux';
export interface Props extends VariablePickerProps {}
-export class TextBoxVariablePicker extends PureComponent {
- onQueryChange = (event: ChangeEvent) => {
+export function TextBoxVariablePicker({ variable }: Props): ReactElement {
+ const dispatch = useDispatch();
+ const [updatedValue, setUpdatedValue] = useState(variable.current.value);
+ useEffect(() => {
+ setUpdatedValue(variable.current.value);
+ }, [variable]);
+
+ const updateVariable = useCallback(() => {
+ if (variable.current.value === updatedValue) {
+ return;
+ }
+
dispatch(
- changeVariableProp(toVariablePayload(this.props.variable, { propName: 'query', propValue: event.target.value }))
+ changeVariableProp(
+ toVariablePayload({ id: variable.id, type: variable.type }, { propName: 'query', propValue: updatedValue })
+ )
);
- };
+ variableAdapters.get(variable.type).updateOptions(variable);
+ }, [dispatch, variable, updatedValue]);
- onQueryBlur = (event: FocusEvent) => {
- if (this.props.variable.current.value !== this.props.variable.query) {
- dispatch(updateOptions(toVariableIdentifier(this.props.variable)));
+ const onChange = useCallback((event: ChangeEvent) => setUpdatedValue(event.target.value), [
+ setUpdatedValue,
+ ]);
+
+ const onBlur = (e: FocusEvent) => updateVariable();
+ const onKeyDown = (event: KeyboardEvent) => {
+ if (event.keyCode === 13) {
+ updateVariable();
}
};
- onQueryKeyDown = (event: KeyboardEvent) => {
- if (event.keyCode === 13 && this.props.variable.current.value !== this.props.variable.query) {
- dispatch(updateOptions(toVariableIdentifier(this.props.variable)));
- }
- };
-
- render() {
- return (
-
- );
- }
+ return ;
}
diff --git a/public/app/features/variables/textbox/adapter.test.ts b/public/app/features/variables/textbox/adapter.test.ts
new file mode 100644
index 00000000000..e2e9d1db7ba
--- /dev/null
+++ b/public/app/features/variables/textbox/adapter.test.ts
@@ -0,0 +1,84 @@
+import { variableAdapters } from '../adapters';
+import { createTextBoxVariableAdapter } from './adapter';
+import { textboxBuilder } from '../shared/testing/builders';
+import { VariableHide } from '../types';
+
+variableAdapters.setInit(() => [createTextBoxVariableAdapter()]);
+
+describe('createTextBoxVariableAdapter', () => {
+ describe('getSaveModel', () => {
+ describe('when called and query differs from the original query and not saving current as default', () => {
+ it('then the model should be correct', () => {
+ const text = textboxBuilder()
+ .withId('text')
+ .withName('text')
+ .withQuery('query')
+ .withOriginalQuery('original')
+ .withCurrent('query')
+ .withOptions('query')
+ .build();
+
+ const adapter = variableAdapters.get('textbox');
+
+ const result = adapter.getSaveModel(text, false);
+
+ expect(result).toEqual({
+ name: 'text',
+ query: 'original',
+ current: { selected: false, text: 'original', value: 'original' },
+ options: [{ selected: false, text: 'original', value: 'original' }],
+ type: 'textbox',
+ label: null,
+ hide: VariableHide.dontHide,
+ skipUrlSync: false,
+ error: null,
+ description: null,
+ });
+ });
+ });
+
+ describe('when called and query differs from the original query and saving current as default', () => {
+ it('then the model should be correct', () => {
+ const text = textboxBuilder()
+ .withId('text')
+ .withName('text')
+ .withQuery('query')
+ .withOriginalQuery('original')
+ .withCurrent('query')
+ .withOptions('query')
+ .build();
+
+ const adapter = variableAdapters.get('textbox');
+
+ const result = adapter.getSaveModel(text, true);
+
+ expect(result).toEqual({
+ name: 'text',
+ query: 'query',
+ current: { selected: true, text: 'query', value: 'query' },
+ options: [{ selected: false, text: 'query', value: 'query' }],
+ type: 'textbox',
+ label: null,
+ hide: VariableHide.dontHide,
+ skipUrlSync: false,
+ error: null,
+ description: null,
+ });
+ });
+ });
+ });
+
+ describe('beforeAdding', () => {
+ describe('when called', () => {
+ it('then originalQuery should be same added', () => {
+ const model = { name: 'text', query: 'a query' };
+
+ const adapter = variableAdapters.get('textbox');
+
+ const result = adapter.beforeAdding!(model);
+
+ expect(result).toEqual({ name: 'text', query: 'a query', originalQuery: 'a query' });
+ });
+ });
+ });
+});
diff --git a/public/app/features/variables/textbox/adapter.ts b/public/app/features/variables/textbox/adapter.ts
index 801983d4c54..d57fe26b7c8 100644
--- a/public/app/features/variables/textbox/adapter.ts
+++ b/public/app/features/variables/textbox/adapter.ts
@@ -31,12 +31,22 @@ export const createTextBoxVariableAdapter = (): VariableAdapter {
await dispatch(updateTextBoxVariableOptions(toVariableIdentifier(variable)));
},
- getSaveModel: variable => {
- const { index, id, state, global, ...rest } = cloneDeep(variable);
+ getSaveModel: (variable, saveCurrentAsDefault) => {
+ const { index, id, state, global, originalQuery, ...rest } = cloneDeep(variable);
+
+ if (variable.query !== originalQuery && !saveCurrentAsDefault) {
+ const origQuery = originalQuery ?? '';
+ const current = { selected: false, text: origQuery, value: origQuery };
+ return { ...rest, query: origQuery, current, options: [current] };
+ }
+
return rest;
},
getValueForUrl: variable => {
return variable.current.value;
},
+ beforeAdding: model => {
+ return { ...cloneDeep(model), originalQuery: model.query };
+ },
};
};
diff --git a/public/app/features/variables/textbox/reducer.ts b/public/app/features/variables/textbox/reducer.ts
index ca962e87b08..bb4714ff55e 100644
--- a/public/app/features/variables/textbox/reducer.ts
+++ b/public/app/features/variables/textbox/reducer.ts
@@ -10,6 +10,7 @@ export const initialTextBoxVariableModelState: TextBoxVariableModel = {
query: '',
current: {} as VariableOption,
options: [],
+ originalQuery: null,
};
export const textBoxVariableSlice = createSlice({
diff --git a/public/app/features/variables/types.ts b/public/app/features/variables/types.ts
index a466aa8d10c..2e3c916f62a 100644
--- a/public/app/features/variables/types.ts
+++ b/public/app/features/variables/types.ts
@@ -86,7 +86,9 @@ export interface QueryVariableModel extends DataSourceVariableModel {
query: any;
}
-export interface TextBoxVariableModel extends VariableWithOptions {}
+export interface TextBoxVariableModel extends VariableWithOptions {
+ originalQuery: string | null;
+}
export interface ConstantVariableModel extends VariableWithOptions {}