From 00a9af00fc8a4c6d75dedd164da545e80c79a791 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20H=C3=A4ggmark?= Date: Thu, 4 Jun 2020 13:44:48 +0200 Subject: [PATCH] Templating: removes old Angular variable system and featureToggle (#24779) * Chore: initial commit * Tests: fixes MetricsQueryEditor.test.tsx * Tests: fixes cloudwatch/specs/datasource.test.ts * Tests: fixes stackdriver/specs/datasource.test.ts * Tests: remove refrences to CustomVariable * Refactor: moves DefaultVariableQueryEditor * Refactor: moves utils * Refactor: moves types * Refactor: removes variableSrv * Refactor: removes feature toggle newVariables * Refactor: removes valueSelectDropDown * Chore: removes GeneralTabCtrl * Chore: migrates RowOptions * Refactor: adds RowOptionsButton * Refactor: makes the interface more explicit * Refactor: small changes * Refactor: changed type as it can be any variable type * Tests: fixes broken test * Refactor: changes after PR comments * Refactor: adds loading state and call to onChange in componentDidMount --- .../panel_tests_graph.json | 30 +- packages/grafana-data/src/types/config.ts | 1 - .../grafana-e2e/cypress/fixtures/example.json | 5 + packages/grafana-runtime/src/config.ts | 9 +- public/app/core/core.ts | 4 +- .../core/directives/value_select_dropdown.ts | 356 ---------- public/app/core/services/bridge_srv.ts | 9 +- .../core/specs/value_select_dropdown.test.ts | 270 ------- .../AdHocFilters/AdHocFiltersCtrl.ts | 182 ----- .../components/AdHocFilters/index.ts | 1 - .../DashExportModal/DashboardExporter.test.ts | 10 +- .../DashExportModal/DashboardExporter.ts | 12 +- .../DashboardRow/DashboardRow.test.tsx | 8 +- .../components/DashboardRow/DashboardRow.tsx | 25 +- .../DashboardSettings/SettingsCtrl.ts | 2 - .../DashboardSettings/template.html | 6 +- .../components/PanelEditor/PanelEditor.tsx | 2 +- .../PanelEditor/PanelOptionsTab.tsx | 36 +- .../RepeatRowSelect/RepeatRowSelect.tsx | 40 ++ .../RowOptions/RowOptionsButton.tsx | 37 + .../components/RowOptions/RowOptionsCtrl.ts | 39 -- .../components/RowOptions/RowOptionsForm.tsx | 46 ++ .../components/RowOptions/RowOptionsModal.tsx | 30 + .../dashboard/components/RowOptions/index.ts | 1 - .../components/RowOptions/template.html | 30 - .../components/ShareModal/ShareSnapshot.tsx | 12 +- .../dashboard/components/SubMenu/SubMenu.tsx | 2 +- .../components/SubMenu/SubMenuItems.tsx | 2 +- .../components/SubMenu/template.html | 60 -- .../dashboard/containers/DashboardPage.tsx | 5 +- public/app/features/dashboard/index.ts | 4 - .../dashboard/services/ChangeTracker.ts | 2 +- .../dashboard/state/DashboardMigrator.ts | 4 +- .../dashboard/state/DashboardModel.ts | 67 +- .../dashboard/state/initDashboard.test.ts | 28 +- .../features/dashboard/state/initDashboard.ts | 4 +- public/app/features/panel/GeneralTabCtrl.ts | 46 -- public/app/features/panel/all.ts | 1 - .../features/panel/partials/general_tab.html | 49 -- public/app/features/panel/repeat_option.ts | 59 -- public/app/features/plugins/datasource_srv.ts | 2 +- .../plugins/variableQueryEditorLoader.tsx | 2 +- .../features/templating/TextBoxVariable.ts | 71 -- .../app/features/templating/adhoc_variable.ts | 101 --- public/app/features/templating/all.ts | 21 - .../features/templating/constant_variable.ts | 70 -- .../features/templating/custom_variable.ts | 93 --- .../templating/datasource_variable.ts | 133 ---- public/app/features/templating/editor_ctrl.ts | 247 ------- .../features/templating/interval_variable.ts | 117 ---- .../features/templating/partials/editor.html | 526 -------------- .../app/features/templating/query_variable.ts | 254 ------- .../templating/specs/adhoc_variable.test.ts | 36 - .../templating/specs/editor_ctrl.test.ts | 42 -- .../templating/specs/query_variable.test.ts | 133 ---- .../templating/specs/template_srv.test.ts | 609 ---------------- .../features/templating/specs/utils.test.ts | 261 ------- .../templating/specs/variable_srv.test.ts | 663 ------------------ .../specs/variable_srv_init.test.ts | 275 -------- .../app/features/templating/template_srv.ts | 21 +- .../app/features/templating/variable_srv.ts | 427 ----------- public/app/features/variables/adapters.ts | 2 +- .../variables/adhoc/AdHocVariableEditor.tsx | 2 +- .../features/variables/adhoc/actions.test.ts | 6 +- .../app/features/variables/adhoc/actions.ts | 6 +- .../app/features/variables/adhoc/adapter.ts | 4 +- .../adhoc/picker/AdHocFilterBuilder.tsx | 4 +- .../variables/adhoc/picker/AdHocPicker.tsx | 8 +- .../features/variables/adhoc/reducer.test.ts | 2 +- .../app/features/variables/adhoc/reducer.ts | 2 +- .../variables/adhoc/urlParser.test.ts | 2 +- .../app/features/variables/adhoc/urlParser.ts | 2 +- .../constant/ConstantVariableEditor.tsx | 2 +- .../variables/constant/actions.test.ts | 4 +- .../features/variables/constant/adapter.ts | 2 +- .../variables/constant/reducer.test.ts | 2 +- .../features/variables/constant/reducer.ts | 2 +- .../variables/custom/CustomVariableEditor.tsx | 2 +- .../features/variables/custom/actions.test.ts | 4 +- .../app/features/variables/custom/adapter.ts | 2 +- .../features/variables/custom/reducer.test.ts | 2 +- .../app/features/variables/custom/reducer.ts | 2 +- .../datasource/DataSourceVariableEditor.tsx | 2 +- .../features/variables/datasource/actions.ts | 4 +- .../features/variables/datasource/adapter.ts | 4 +- .../variables/datasource/reducer.test.ts | 2 +- .../features/variables/datasource/reducer.ts | 2 +- .../editor}/DefaultVariableQueryEditor.tsx | 0 .../editor/SelectionOptionsEditor.tsx | 2 +- .../editor/VariableEditorContainer.tsx | 2 +- .../variables/editor/VariableEditorEditor.tsx | 2 +- .../variables/editor/VariableEditorList.tsx | 4 +- .../editor/VariableValuesPreview.tsx | 2 +- .../app/features/variables/editor/actions.ts | 8 +- public/app/features/variables/editor/types.ts | 2 +- public/app/features/variables/guard.ts | 12 +- .../interval/IntervalVariableEditor.tsx | 3 +- .../features/variables/interval/actions.ts | 2 +- .../features/variables/interval/adapter.ts | 2 +- .../variables/interval/reducer.test.ts | 2 +- .../features/variables/interval/reducer.ts | 4 +- .../pickers/OptionsPicker/OptionsPicker.tsx | 2 +- .../pickers/OptionsPicker/actions.test.ts | 4 +- .../pickers/OptionsPicker/actions.ts | 9 +- .../pickers/OptionsPicker/reducer.test.ts | 6 +- .../pickers/OptionsPicker/reducer.ts | 6 +- .../variables/pickers/PickerRenderer.tsx | 2 +- .../variables/pickers/shared/VariableLink.tsx | 2 +- .../pickers/shared/VariableOptions.tsx | 2 +- .../app/features/variables/pickers/types.ts | 2 +- .../variables/query/QueryVariableEditor.tsx | 2 +- .../features/variables/query/actions.test.ts | 10 +- .../app/features/variables/query/actions.ts | 10 +- .../app/features/variables/query/adapter.ts | 4 +- .../features/variables/query/reducer.test.ts | 2 +- .../app/features/variables/query/reducer.ts | 11 +- .../variables/shared/multiOptions.test.ts | 2 +- .../features/variables/shared/multiOptions.ts | 2 +- .../shared/testing/adHocVariableBuilder.ts | 2 +- .../testing/datasourceVariableBuilder.ts | 2 +- .../shared/testing/intervalVariableBuilder.ts | 2 +- .../shared/testing/multiVariableBuilder.ts | 2 +- .../shared/testing/optionsVariableBuilder.ts | 2 +- .../shared/testing/variableBuilder.ts | 2 +- .../features/variables/state/actions.test.ts | 5 +- .../app/features/variables/state/actions.ts | 67 +- .../app/features/variables/state/helpers.ts | 4 +- .../state/onTimeRangeUpdated.test.ts | 2 +- .../variables/state/processVariable.test.ts | 2 +- .../features/variables/state/reducers.test.ts | 7 +- .../app/features/variables/state/reducers.ts | 2 +- .../app/features/variables/state/selectors.ts | 6 +- .../variables/state/sharedReducer.test.ts | 16 +- .../features/variables/state/sharedReducer.ts | 26 +- public/app/features/variables/state/types.ts | 6 +- .../variables/state/variablesReducer.ts | 4 +- .../textbox/TextBoxVariableEditor.tsx | 2 +- .../textbox/TextBoxVariablePicker.tsx | 2 +- .../variables/textbox/actions.test.ts | 2 +- .../app/features/variables/textbox/actions.ts | 6 +- .../app/features/variables/textbox/adapter.ts | 2 +- .../variables/textbox/reducer.test.ts | 2 +- .../app/features/variables/textbox/reducer.ts | 4 +- .../{templating => variables}/types.ts | 30 +- .../{templating => variables}/utils.ts | 0 public/app/partials/valueSelectDropdown.html | 72 -- .../components/MetricsQueryEditor.test.tsx | 41 +- .../datasource/cloudwatch/datasource.ts | 24 +- .../cloudwatch/specs/datasource.test.ts | 155 ++-- .../plugins/datasource/graphite/datasource.ts | 2 +- .../datasource/loki/datasource.test.ts | 9 +- .../datasource/mssql/specs/datasource.test.ts | 6 +- .../plugins/datasource/mysql/datasource.ts | 2 +- .../plugins/datasource/mysql/query_ctrl.ts | 2 +- .../datasource/mysql/specs/datasource.test.ts | 4 +- .../plugins/datasource/postgres/datasource.ts | 2 +- .../plugins/datasource/postgres/query_ctrl.ts | 2 +- .../postgres/specs/datasource.test.ts | 4 +- .../datasource/prometheus/datasource.test.ts | 24 +- .../components/VariableQueryEditor.test.tsx | 4 +- .../components/VariableQueryEditor.tsx | 35 +- .../VariableQueryEditor.test.tsx.snap | 99 +-- .../stackdriver/specs/datasource.test.ts | 24 +- .../plugins/datasource/stackdriver/types.ts | 1 + .../plugins/datasource/testdata/datasource.ts | 12 +- public/app/plugins/panel/table-old/module.ts | 19 +- 166 files changed, 678 insertions(+), 5917 deletions(-) create mode 100644 packages/grafana-e2e/cypress/fixtures/example.json delete mode 100644 public/app/core/directives/value_select_dropdown.ts delete mode 100644 public/app/core/specs/value_select_dropdown.test.ts delete mode 100644 public/app/features/dashboard/components/AdHocFilters/AdHocFiltersCtrl.ts delete mode 100644 public/app/features/dashboard/components/AdHocFilters/index.ts create mode 100644 public/app/features/dashboard/components/RepeatRowSelect/RepeatRowSelect.tsx create mode 100644 public/app/features/dashboard/components/RowOptions/RowOptionsButton.tsx delete mode 100644 public/app/features/dashboard/components/RowOptions/RowOptionsCtrl.ts create mode 100644 public/app/features/dashboard/components/RowOptions/RowOptionsForm.tsx create mode 100644 public/app/features/dashboard/components/RowOptions/RowOptionsModal.tsx delete mode 100644 public/app/features/dashboard/components/RowOptions/index.ts delete mode 100644 public/app/features/dashboard/components/RowOptions/template.html delete mode 100644 public/app/features/dashboard/components/SubMenu/template.html delete mode 100644 public/app/features/panel/GeneralTabCtrl.ts delete mode 100644 public/app/features/panel/partials/general_tab.html delete mode 100644 public/app/features/panel/repeat_option.ts delete mode 100644 public/app/features/templating/TextBoxVariable.ts delete mode 100644 public/app/features/templating/adhoc_variable.ts delete mode 100644 public/app/features/templating/constant_variable.ts delete mode 100644 public/app/features/templating/custom_variable.ts delete mode 100644 public/app/features/templating/datasource_variable.ts delete mode 100644 public/app/features/templating/editor_ctrl.ts delete mode 100644 public/app/features/templating/interval_variable.ts delete mode 100644 public/app/features/templating/partials/editor.html delete mode 100644 public/app/features/templating/query_variable.ts delete mode 100644 public/app/features/templating/specs/adhoc_variable.test.ts delete mode 100644 public/app/features/templating/specs/editor_ctrl.test.ts delete mode 100644 public/app/features/templating/specs/query_variable.test.ts delete mode 100644 public/app/features/templating/specs/template_srv.test.ts delete mode 100644 public/app/features/templating/specs/utils.test.ts delete mode 100644 public/app/features/templating/specs/variable_srv.test.ts delete mode 100644 public/app/features/templating/specs/variable_srv_init.test.ts rename public/app/features/{templating => variables/editor}/DefaultVariableQueryEditor.tsx (100%) rename public/app/features/{templating => variables}/types.ts (74%) rename public/app/features/{templating => variables}/utils.ts (100%) delete mode 100644 public/app/partials/valueSelectDropdown.html diff --git a/devenv/dev-dashboards-without-uid/panel_tests_graph.json b/devenv/dev-dashboards-without-uid/panel_tests_graph.json index 03fba9fdabd..6612f9b80f3 100644 --- a/devenv/dev-dashboards-without-uid/panel_tests_graph.json +++ b/devenv/dev-dashboards-without-uid/panel_tests_graph.json @@ -1632,10 +1632,7 @@ "revision": 8, "schemaVersion": 16, "style": "dark", - "tags": [ - "gdev", - "panel-tests" - ], + "tags": ["gdev", "panel-tests"], "templating": { "list": [] }, @@ -1644,29 +1641,8 @@ "to": "now" }, "timepicker": { - "refresh_intervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "time_options": [ - "5m", - "15m", - "1h", - "6h", - "12h", - "24h", - "2d", - "7d", - "30d" - ] + "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"], + "time_options": ["5m", "15m", "1h", "6h", "12h", "24h", "2d", "7d", "30d"] }, "timezone": "browser", "title": "Panel Tests - Graph", diff --git a/packages/grafana-data/src/types/config.ts b/packages/grafana-data/src/types/config.ts index c1e8219c900..d1c2dfb6bfc 100644 --- a/packages/grafana-data/src/types/config.ts +++ b/packages/grafana-data/src/types/config.ts @@ -38,7 +38,6 @@ export interface FeatureToggles { * Available only in Grafana Enterprise */ meta: boolean; - newVariables: boolean; } /** diff --git a/packages/grafana-e2e/cypress/fixtures/example.json b/packages/grafana-e2e/cypress/fixtures/example.json new file mode 100644 index 00000000000..02e4254378e --- /dev/null +++ b/packages/grafana-e2e/cypress/fixtures/example.json @@ -0,0 +1,5 @@ +{ + "name": "Using fixtures to represent data", + "email": "hello@cypress.io", + "body": "Fixtures are a great way to mock data for responses to routes" +} diff --git a/packages/grafana-runtime/src/config.ts b/packages/grafana-runtime/src/config.ts index 1e4bb6582f3..b4d970edf58 100644 --- a/packages/grafana-runtime/src/config.ts +++ b/packages/grafana-runtime/src/config.ts @@ -1,14 +1,14 @@ import merge from 'lodash/merge'; import { getTheme } from '@grafana/ui'; import { + BuildInfo, DataSourceInstanceSettings, + FeatureToggles, + GrafanaConfig, GrafanaTheme, GrafanaThemeType, - PanelPluginMeta, - GrafanaConfig, LicenseInfo, - BuildInfo, - FeatureToggles, + PanelPluginMeta, } from '@grafana/data'; export class GrafanaBootConfig implements GrafanaConfig { @@ -52,7 +52,6 @@ export class GrafanaBootConfig implements GrafanaConfig { expressions: false, newEdit: false, meta: false, - newVariables: true, }; licenseInfo: LicenseInfo = {} as LicenseInfo; rendererAvailable = false; diff --git a/public/app/core/core.ts b/public/app/core/core.ts index a9c6ce57421..4539393dc21 100644 --- a/public/app/core/core.ts +++ b/public/app/core/core.ts @@ -4,7 +4,6 @@ import './directives/metric_segment'; import './directives/misc'; import './directives/ng_model_on_blur'; import './directives/tags'; -import './directives/value_select_dropdown'; import './directives/rebuild_on_change'; import './directives/give_focus'; import './directives/diff-view'; @@ -39,8 +38,7 @@ import { NavModelSrv } from './nav_model_srv'; import { geminiScrollbar } from './components/scroll/scroll'; import { profiler } from './profiler'; import { registerAngularDirectives } from './angular_wrappers'; -import { updateLegendValues } from './time_series2'; -import TimeSeries from './time_series2'; +import TimeSeries, { updateLegendValues } from './time_series2'; import { NavModel } from '@grafana/data'; export { diff --git a/public/app/core/directives/value_select_dropdown.ts b/public/app/core/directives/value_select_dropdown.ts deleted file mode 100644 index b7eb870d5b2..00000000000 --- a/public/app/core/directives/value_select_dropdown.ts +++ /dev/null @@ -1,356 +0,0 @@ -import angular, { IScope } from 'angular'; -import debounce from 'lodash/debounce'; -import each from 'lodash/each'; -import filter from 'lodash/filter'; -import find from 'lodash/find'; -import indexOf from 'lodash/indexOf'; -import map from 'lodash/map'; -import { selectors } from '@grafana/e2e-selectors'; - -import coreModule from '../core_module'; -import { GrafanaRootScope } from 'app/routes/GrafanaCtrl'; -import { containsSearchFilter } from '../../features/templating/utils'; - -export class ValueSelectDropdownCtrl { - dropdownVisible: any; - highlightIndex: any; - linkText: any; - oldVariableText: any; - options: any; - search: any; - selectedTags: any; - selectedValues: any; - tags: any; - variable: any; - - hide: any; - onUpdated: any; - queryHasSearchFilter: boolean; - debouncedQueryChanged: Function; - selectors: typeof selectors.pages.Dashboard.SubMenu; - - /** @ngInject */ - constructor(private $scope: IScope) { - this.queryHasSearchFilter = this.variable ? containsSearchFilter(this.variable.query) : false; - this.debouncedQueryChanged = debounce(this.queryChanged.bind(this), 200); - this.selectors = selectors.pages.Dashboard.SubMenu; - } - - show() { - this.oldVariableText = this.variable.current.text; - this.highlightIndex = -1; - - this.options = this.variable.options; - this.selectedValues = filter(this.options, { selected: true }); - - this.tags = map(this.variable.tags, value => { - let tag = { text: value, selected: false }; - each(this.variable.current.tags, tagObj => { - if (tagObj.text === value) { - tag = tagObj; - } - }); - return tag; - }); - - // new behaviour, if this is a query that uses searchfilter it might be a nicer - // user experience to show the last typed search query in the input field - const query = this.queryHasSearchFilter && this.search && this.search.query ? this.search.query : ''; - - this.search = { - query, - options: this.options.slice(0, Math.min(this.options.length, 1000)), - }; - - this.dropdownVisible = true; - } - - updateLinkText() { - const current = this.variable.current; - - if (current.tags && current.tags.length) { - // filer out values that are in selected tags - const selectedAndNotInTag = filter(this.variable.options, option => { - if (!option.selected) { - return false; - } - for (let i = 0; i < current.tags.length; i++) { - const tag = current.tags[i]; - if (indexOf(tag.values, option.value) !== -1) { - return false; - } - } - return true; - }); - - // convert values to text - const currentTexts = map(selectedAndNotInTag, 'text'); - - // join texts - this.linkText = currentTexts.join(' + '); - if (this.linkText.length > 0) { - this.linkText += ' + '; - } - } else { - this.linkText = this.variable.current.text; - } - } - - clearSelections() { - this.selectedValues = filter(this.options, { selected: true }); - - if (this.selectedValues.length) { - each(this.options, option => { - option.selected = false; - }); - } else { - each(this.search.options, option => { - option.selected = true; - }); - } - this.selectionsChanged(false); - } - - selectTag(tag: any) { - tag.selected = !tag.selected; - let tagValuesPromise; - if (!tag.values) { - tagValuesPromise = this.variable.getValuesForTag(tag.text); - } else { - tagValuesPromise = Promise.resolve(tag.values); - } - - return tagValuesPromise.then((values: any) => { - tag.values = values; - tag.valuesText = values.join(' + '); - each(this.options, option => { - if (indexOf(tag.values, option.value) !== -1) { - option.selected = tag.selected; - } - }); - - this.selectionsChanged(false); - }); - } - - keyDown(evt: any) { - if (evt.keyCode === 27) { - this.hide(); - } - if (evt.keyCode === 40) { - this.moveHighlight(1); - } - if (evt.keyCode === 38) { - this.moveHighlight(-1); - } - if (evt.keyCode === 13) { - if (this.search.options.length === 0) { - this.commitChanges(); - } else { - this.selectValue(this.search.options[this.highlightIndex], {}, true); - } - } - if (evt.keyCode === 32) { - this.selectValue(this.search.options[this.highlightIndex], {}, false); - } - } - - moveHighlight(direction: number) { - this.highlightIndex = (this.highlightIndex + direction) % this.search.options.length; - } - - selectValue(option: any, event: any, commitChange?: boolean) { - if (!option) { - return; - } - - option.selected = this.variable.multi ? !option.selected : true; - - commitChange = commitChange || false; - - const setAllExceptCurrentTo = (newValue: any) => { - each(this.options, other => { - if (option !== other) { - other.selected = newValue; - } - }); - }; - - // commit action (enter key), should not deselect it - if (commitChange) { - option.selected = true; - } - - if (option.text === 'All') { - // always clear search query if all is marked - this.search.query = ''; - setAllExceptCurrentTo(false); - commitChange = true; - } else if (!this.variable.multi) { - setAllExceptCurrentTo(false); - commitChange = true; - } else if (event.ctrlKey || event.metaKey || event.shiftKey) { - commitChange = true; - setAllExceptCurrentTo(false); - } - - this.selectionsChanged(commitChange); - } - - selectionsChanged(commitChange: boolean) { - this.selectedValues = filter(this.options, { selected: true }); - - if (this.selectedValues.length > 1) { - if (this.selectedValues[0].text === 'All') { - this.selectedValues[0].selected = false; - this.selectedValues = this.selectedValues.slice(1, this.selectedValues.length); - } - } - - // validate selected tags - each(this.tags, tag => { - if (tag.selected) { - each(tag.values, value => { - if (!find(this.selectedValues, { value: value })) { - tag.selected = false; - } - }); - } - }); - - this.selectedTags = filter(this.tags, { selected: true }); - this.variable.current.value = map(this.selectedValues, 'value'); - this.variable.current.text = map(this.selectedValues, 'text').join(' + '); - this.variable.current.tags = this.selectedTags; - - if (!this.variable.multi) { - this.variable.current.value = this.selectedValues[0].value; - } - - if (commitChange) { - this.commitChanges(); - } - } - - commitChanges() { - // if we have a search query and no options use that - if (this.search.options.length === 0 && this.search.query.length > 0) { - this.variable.current = { text: this.search.query, value: this.search.query }; - } else if (this.selectedValues.length === 0) { - // make sure one option is selected - this.options[0].selected = true; - this.selectionsChanged(false); - } - - this.dropdownVisible = false; - this.updateLinkText(); - if (this.queryHasSearchFilter) { - this.updateLazyLoadedOptions(); - } - - if (this.variable.current.text !== this.oldVariableText) { - this.onUpdated(); - } - } - - async queryChanged() { - if (this.queryHasSearchFilter) { - await this.updateLazyLoadedOptions(); - return; - } - - const options = filter(this.options, option => { - return option.text.toLowerCase().indexOf(this.search.query.toLowerCase()) !== -1; - }); - - this.updateUIBoundOptions(this.$scope, options); - } - - init() { - this.selectedTags = this.variable.current.tags || []; - this.updateLinkText(); - } - - async updateLazyLoadedOptions() { - this.options = await this.lazyLoadOptions(this.search.query); - this.updateUIBoundOptions(this.$scope, this.options); - } - - async lazyLoadOptions(query: string): Promise { - await this.variable.updateOptions(query); - return this.variable.options; - } - - updateUIBoundOptions($scope: IScope, options: any[]) { - this.highlightIndex = 0; - this.search.options = options.slice(0, Math.min(options.length, 1000)); - $scope.$apply(); - } -} - -/** @ngInject */ -export function valueSelectDropdown($compile: any, $window: any, $timeout: any, $rootScope: GrafanaRootScope) { - return { - scope: { dashboard: '=', variable: '=', onUpdated: '&' }, - templateUrl: 'public/app/partials/valueSelectDropdown.html', - controller: 'ValueSelectDropdownCtrl', - controllerAs: 'vm', - bindToController: true, - link: (scope: any, elem: any) => { - const bodyEl = angular.element($window.document.body); - const linkEl = elem.find('.variable-value-link'); - const inputEl = elem.find('input'); - - function openDropdown() { - inputEl.css('width', Math.max(linkEl.width(), 80) + 'px'); - - inputEl.show(); - linkEl.hide(); - - inputEl.focus(); - $timeout( - () => { - bodyEl.on('click', bodyOnClick); - }, - 0, - false - ); - } - - function switchToLink() { - inputEl.hide(); - linkEl.show(); - bodyEl.off('click', bodyOnClick); - } - - function bodyOnClick(e: any) { - if (elem.has(e.target).length === 0) { - scope.$apply(() => { - scope.vm.commitChanges(); - }); - } - } - - scope.$watch('vm.dropdownVisible', (newValue: any) => { - if (newValue) { - openDropdown(); - } else { - switchToLink(); - } - }); - - scope.vm.dashboard.on( - 'template-variable-value-updated', - () => { - scope.vm.updateLinkText(); - }, - scope - ); - - scope.vm.init(); - }, - }; -} - -coreModule.controller('ValueSelectDropdownCtrl', ValueSelectDropdownCtrl); -coreModule.directive('valueSelectDropdown', valueSelectDropdown); diff --git a/public/app/core/services/bridge_srv.ts b/public/app/core/services/bridge_srv.ts index 38448cea7da..ca7a3442aab 100644 --- a/public/app/core/services/bridge_srv.ts +++ b/public/app/core/services/bridge_srv.ts @@ -1,13 +1,13 @@ import coreModule from 'app/core/core_module'; import appEvents from 'app/core/app_events'; -import { store } from 'app/store/store'; +import { dispatch, store } from 'app/store/store'; import { updateLocation } from 'app/core/actions'; import { ILocationService, ITimeoutService, IWindowService } from 'angular'; import { CoreEvents } from 'app/types'; import { GrafanaRootScope } from 'app/routes/GrafanaCtrl'; import { locationUtil, UrlQueryMap } from '@grafana/data'; import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv'; -import { VariableSrv } from 'app/features/templating/all'; +import { templateVarsChangedInUrl } from 'app/features/variables/state/actions'; // Services that handles angular -> redux store sync & other react <-> angular sync export class BridgeSrv { @@ -22,8 +22,7 @@ export class BridgeSrv { private $timeout: ITimeoutService, private $window: IWindowService, private $rootScope: GrafanaRootScope, - private $route: any, - private variableSrv: VariableSrv + private $route: any ) { this.fullPageReloadRoutes = ['/logout']; this.angularUrl = $location.url(); @@ -84,7 +83,7 @@ export class BridgeSrv { if (changes) { const dash = getDashboardSrv().getCurrent(); if (dash) { - this.variableSrv.templateVarsChangedInUrl(changes); + dispatch(templateVarsChangedInUrl(changes)); } } this.lastQuery = state.location.query; diff --git a/public/app/core/specs/value_select_dropdown.test.ts b/public/app/core/specs/value_select_dropdown.test.ts deleted file mode 100644 index ab232c43f1e..00000000000 --- a/public/app/core/specs/value_select_dropdown.test.ts +++ /dev/null @@ -1,270 +0,0 @@ -import 'app/core/directives/value_select_dropdown'; -import { ValueSelectDropdownCtrl } from '../directives/value_select_dropdown'; -import { IScope } from 'angular'; - -describe('SelectDropdownCtrl', () => { - const tagValuesMap: any = {}; - const $scope: IScope = {} as IScope; - - ValueSelectDropdownCtrl.prototype.onUpdated = jest.fn(); - let ctrl: ValueSelectDropdownCtrl; - - describe('Given simple variable', () => { - beforeEach(() => { - ctrl = new ValueSelectDropdownCtrl($scope); - ctrl.variable = { - current: { text: 'hej', value: 'hej' }, - getValuesForTag: (key: string) => { - return Promise.resolve(tagValuesMap[key]); - }, - }; - ctrl.init(); - }); - - it('Should init labelText and linkText', () => { - expect(ctrl.linkText).toBe('hej'); - }); - }); - - describe('Given variable with tags and dropdown is opened', () => { - beforeEach(() => { - ctrl = new ValueSelectDropdownCtrl($scope); - ctrl.variable = { - current: { text: 'server-1', value: 'server-1' }, - options: [ - { text: 'server-1', value: 'server-1', selected: true }, - { text: 'server-2', value: 'server-2' }, - { text: 'server-3', value: 'server-3' }, - ], - tags: ['key1', 'key2', 'key3'], - getValuesForTag: (key: string) => { - return Promise.resolve(tagValuesMap[key]); - }, - multi: true, - }; - tagValuesMap.key1 = ['server-1', 'server-3']; - tagValuesMap.key2 = ['server-2', 'server-3']; - tagValuesMap.key3 = ['server-1', 'server-2', 'server-3']; - ctrl.init(); - ctrl.show(); - }); - - it('should init tags model', () => { - expect(ctrl.tags.length).toBe(3); - expect(ctrl.tags[0].text).toBe('key1'); - }); - - it('should init options model', () => { - expect(ctrl.options.length).toBe(3); - }); - - it('should init selected values array', () => { - expect(ctrl.selectedValues.length).toBe(1); - }); - - it('should set linkText', () => { - expect(ctrl.linkText).toBe('server-1'); - }); - - describe('after adititional value is selected', () => { - beforeEach(() => { - ctrl.selectValue(ctrl.options[2], {}); - ctrl.commitChanges(); - }); - - it('should update link text', () => { - expect(ctrl.linkText).toBe('server-1 + server-3'); - }); - }); - - describe('When tag is selected', () => { - beforeEach(async () => { - await ctrl.selectTag(ctrl.tags[0]); - ctrl.commitChanges(); - }); - - it('should select tag', () => { - expect(ctrl.selectedTags.length).toBe(1); - }); - - it('should select values', () => { - expect(ctrl.options[0].selected).toBe(true); - expect(ctrl.options[2].selected).toBe(true); - }); - - it('link text should not include tag values', () => { - expect(ctrl.linkText).toBe(''); - }); - - describe('and then dropdown is opened and closed without changes', () => { - beforeEach(() => { - ctrl.show(); - ctrl.commitChanges(); - }); - - it('should still have selected tag', () => { - expect(ctrl.selectedTags.length).toBe(1); - }); - }); - - describe('and then unselected', () => { - beforeEach(async () => { - await ctrl.selectTag(ctrl.tags[0]); - }); - - it('should deselect tag', () => { - expect(ctrl.selectedTags.length).toBe(0); - }); - }); - - describe('and then value is unselected', () => { - beforeEach(() => { - ctrl.selectValue(ctrl.options[0], {}); - }); - - it('should deselect tag', () => { - expect(ctrl.selectedTags.length).toBe(0); - }); - }); - }); - }); - - describe('Given variable with selected tags', () => { - beforeEach(() => { - ctrl = new ValueSelectDropdownCtrl($scope); - ctrl.variable = { - current: { - text: 'server-1', - value: 'server-1', - tags: [{ text: 'key1', selected: true }], - }, - options: [ - { text: 'server-1', value: 'server-1' }, - { text: 'server-2', value: 'server-2' }, - { text: 'server-3', value: 'server-3' }, - ], - tags: ['key1', 'key2', 'key3'], - getValuesForTag: (key: any) => { - return Promise.resolve(tagValuesMap[key]); - }, - multi: true, - }; - ctrl.init(); - ctrl.show(); - }); - - it('should set tag as selected', () => { - expect(ctrl.tags[0].selected).toBe(true); - }); - }); -}); - -describe('queryChanged', () => { - describe('when called and variable query contains search filter', () => { - it('then it should use lazy loading', async () => { - const $scope = {} as IScope; - const ctrl = new ValueSelectDropdownCtrl($scope); - const options = [ - { text: 'server-1', value: 'server-1' }, - { text: 'server-2', value: 'server-2' }, - { text: 'server-3', value: 'server-3' }, - ]; - ctrl.lazyLoadOptions = jest.fn().mockResolvedValue(options); - ctrl.updateUIBoundOptions = jest.fn(); - ctrl.search = { - query: 'alpha', - }; - ctrl.queryHasSearchFilter = true; - - await ctrl.queryChanged(); - - expect(ctrl.lazyLoadOptions).toBeCalledTimes(1); - expect(ctrl.lazyLoadOptions).toBeCalledWith('alpha'); - expect(ctrl.updateUIBoundOptions).toBeCalledTimes(1); - expect(ctrl.updateUIBoundOptions).toBeCalledWith($scope, options); - }); - }); - - describe('when called and variable query does not contain search filter', () => { - it('then it should not use lazy loading', async () => { - const $scope = {} as IScope; - const ctrl = new ValueSelectDropdownCtrl($scope); - ctrl.lazyLoadOptions = jest.fn().mockResolvedValue([]); - ctrl.updateUIBoundOptions = jest.fn(); - ctrl.search = { - query: 'alpha', - }; - ctrl.queryHasSearchFilter = false; - - await ctrl.queryChanged(); - - expect(ctrl.lazyLoadOptions).toBeCalledTimes(0); - expect(ctrl.updateUIBoundOptions).toBeCalledTimes(1); - }); - }); -}); - -describe('lazyLoadOptions', () => { - describe('when called with a query', () => { - it('then the variables updateOptions should be called with the query', async () => { - const $scope = {} as IScope; - const ctrl = new ValueSelectDropdownCtrl($scope); - ctrl.variable = { - updateOptions: jest.fn(), - options: [ - { text: 'server-1', value: 'server-1' }, - { text: 'server-2', value: 'server-2' }, - { text: 'server-3', value: 'server-3' }, - ], - }; - const query = 'server-1'; - - const result = await ctrl.lazyLoadOptions(query); - - expect(ctrl.variable.updateOptions).toBeCalledTimes(1); - expect(ctrl.variable.updateOptions).toBeCalledWith(query); - expect(result).toEqual(ctrl.variable.options); - }); - }); -}); - -describe('updateUIBoundOptions', () => { - describe('when called with options', () => { - let options: any[]; - let ctrl: ValueSelectDropdownCtrl; - let $scope: IScope; - - beforeEach(() => { - $scope = ({ - $apply: jest.fn(), - } as any) as IScope; - options = []; - for (let index = 0; index < 1001; index++) { - options.push({ text: `server-${index}`, value: `server-${index}` }); - } - ctrl = new ValueSelectDropdownCtrl($scope); - ctrl.highlightIndex = 0; - ctrl.options = []; - ctrl.search = { - options: [], - }; - ctrl.updateUIBoundOptions($scope, options); - }); - - it('then highlightIndex should be reset to first item', () => { - expect(ctrl.highlightIndex).toEqual(0); - }); - - it('then search.options should be same as options but capped to 1000', () => { - expect(ctrl.search.options.length).toEqual(1000); - - for (let index = 0; index < 1000; index++) { - expect(ctrl.search.options[index]).toEqual(options[index]); - } - }); - - it('then scope apply should be called', () => { - expect($scope.$apply).toBeCalledTimes(1); - }); - }); -}); diff --git a/public/app/features/dashboard/components/AdHocFilters/AdHocFiltersCtrl.ts b/public/app/features/dashboard/components/AdHocFilters/AdHocFiltersCtrl.ts deleted file mode 100644 index d9afdd66a2c..00000000000 --- a/public/app/features/dashboard/components/AdHocFilters/AdHocFiltersCtrl.ts +++ /dev/null @@ -1,182 +0,0 @@ -import _ from 'lodash'; -import angular from 'angular'; -import coreModule from 'app/core/core_module'; -import { DashboardModel } from 'app/features/dashboard/state'; -import DatasourceSrv from 'app/features/plugins/datasource_srv'; -import { VariableSrv } from 'app/features/templating/all'; -import { CoreEvents } from 'app/types'; - -export class AdHocFiltersCtrl { - segments: any; - variable: any; - dashboard: DashboardModel; - removeTagFilterSegment: any; - - /** @ngInject */ - constructor( - private uiSegmentSrv: any, - private datasourceSrv: DatasourceSrv, - private variableSrv: VariableSrv, - $scope: any - ) { - this.removeTagFilterSegment = uiSegmentSrv.newSegment({ - fake: true, - value: '-- remove filter --', - }); - this.buildSegmentModel(); - this.dashboard.events.on(CoreEvents.templateVariableValueUpdated, this.buildSegmentModel.bind(this), $scope); - } - - buildSegmentModel() { - this.segments = []; - - if (this.variable.value && !_.isArray(this.variable.value)) { - } - - for (const tag of this.variable.filters) { - if (this.segments.length > 0) { - this.segments.push(this.uiSegmentSrv.newCondition('AND')); - } - - if (tag.key !== undefined && tag.value !== undefined) { - this.segments.push(this.uiSegmentSrv.newKey(tag.key)); - this.segments.push(this.uiSegmentSrv.newOperator(tag.operator)); - this.segments.push(this.uiSegmentSrv.newKeyValue(tag.value)); - } - } - - this.segments.push(this.uiSegmentSrv.newPlusButton()); - } - - getOptions(segment: { type: string }, index: number) { - if (segment.type === 'operator') { - return Promise.resolve(this.uiSegmentSrv.newOperators(['=', '!=', '<', '>', '=~', '!~'])); - } - - if (segment.type === 'condition') { - return Promise.resolve([this.uiSegmentSrv.newSegment('AND')]); - } - - return this.datasourceSrv.get(this.variable.datasource).then(ds => { - const options: any = {}; - let promise = null; - - if (segment.type !== 'value') { - promise = ds.getTagKeys ? ds.getTagKeys() : Promise.resolve([]); - } else { - options.key = this.segments[index - 2].value; - promise = ds.getTagValues ? ds.getTagValues(options) : Promise.resolve([]); - } - - return promise.then((results: any) => { - results = _.map(results, segment => { - return this.uiSegmentSrv.newSegment({ value: segment.text }); - }); - - // add remove option for keys - if (segment.type === 'key') { - results.splice(0, 0, angular.copy(this.removeTagFilterSegment)); - } - return results; - }); - }); - } - - segmentChanged(segment: { value: any; type: string; cssClass: string }, index: number) { - this.segments[index] = segment; - - // handle remove tag condition - if (segment.value === this.removeTagFilterSegment.value) { - this.segments.splice(index, 3); - if (this.segments.length === 0) { - this.segments.push(this.uiSegmentSrv.newPlusButton()); - } else if (this.segments.length > 2) { - this.segments.splice(Math.max(index - 1, 0), 1); - if (this.segments[this.segments.length - 1].type !== 'plus-button') { - this.segments.push(this.uiSegmentSrv.newPlusButton()); - } - } - } else { - if (segment.type === 'plus-button') { - if (index > 2) { - this.segments.splice(index, 0, this.uiSegmentSrv.newCondition('AND')); - } - this.segments.push(this.uiSegmentSrv.newOperator('=')); - this.segments.push(this.uiSegmentSrv.newFake('select value', 'value', 'query-segment-value')); - segment.type = 'key'; - segment.cssClass = 'query-segment-key'; - } - - if (index + 1 === this.segments.length) { - this.segments.push(this.uiSegmentSrv.newPlusButton()); - } - } - - this.updateVariableModel(); - } - - updateVariableModel() { - const filters: any[] = []; - let filterIndex = -1; - let hasFakes = false; - - this.segments.forEach((segment: any) => { - if (segment.type === 'value' && segment.fake) { - hasFakes = true; - return; - } - - switch (segment.type) { - case 'key': { - filters.push({ key: segment.value }); - filterIndex += 1; - break; - } - case 'value': { - filters[filterIndex].value = segment.value; - break; - } - case 'operator': { - filters[filterIndex].operator = segment.value; - break; - } - case 'condition': { - filters[filterIndex].condition = segment.value; - break; - } - } - }); - - if (hasFakes) { - return; - } - - this.variable.setFilters(filters); - this.variableSrv.variableUpdated(this.variable, true); - } -} - -const template = ` -
-
- -
-
-`; - -export function adHocFiltersComponent() { - return { - restrict: 'E', - template: template, - controller: AdHocFiltersCtrl, - bindToController: true, - controllerAs: 'ctrl', - scope: { - variable: '=', - dashboard: '=', - }, - }; -} - -coreModule.directive('adHocFilters', adHocFiltersComponent); diff --git a/public/app/features/dashboard/components/AdHocFilters/index.ts b/public/app/features/dashboard/components/AdHocFilters/index.ts deleted file mode 100644 index 522b564d004..00000000000 --- a/public/app/features/dashboard/components/AdHocFilters/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { AdHocFiltersCtrl } from './AdHocFiltersCtrl'; diff --git a/public/app/features/dashboard/components/DashExportModal/DashboardExporter.test.ts b/public/app/features/dashboard/components/DashExportModal/DashboardExporter.test.ts index 5e79995f639..7c9d9791347 100644 --- a/public/app/features/dashboard/components/DashExportModal/DashboardExporter.test.ts +++ b/public/app/features/dashboard/components/DashExportModal/DashboardExporter.test.ts @@ -3,6 +3,10 @@ import config from 'app/core/config'; import { DashboardExporter } from './DashboardExporter'; import { DashboardModel } from '../../state/DashboardModel'; import { PanelPluginMeta } from '@grafana/data'; +import { variableAdapters } from '../../../variables/adapters'; +import { createConstantVariableAdapter } from '../../../variables/constant/adapter'; +import { createQueryVariableAdapter } from '../../../variables/query/adapter'; +import { createDataSourceVariableAdapter } from '../../../variables/datasource/adapter'; jest.mock('app/core/store', () => { return { @@ -25,6 +29,10 @@ jest.mock('@grafana/runtime', () => ({ }, })); +variableAdapters.register(createQueryVariableAdapter()); +variableAdapters.register(createConstantVariableAdapter()); +variableAdapters.register(createDataSourceVariableAdapter()); + describe('given dashboard with repeated panels', () => { let dash: any, exported: any; @@ -122,7 +130,7 @@ describe('given dashboard with repeated panels', () => { info: { version: '1.1.2' }, } as PanelPluginMeta; - dash = new DashboardModel(dash, {}); + dash = new DashboardModel(dash, {}, () => dash.templating.list); const exporter = new DashboardExporter(); exporter.makeExportable(dash).then(clean => { exported = clean; diff --git a/public/app/features/dashboard/components/DashExportModal/DashboardExporter.ts b/public/app/features/dashboard/components/DashExportModal/DashboardExporter.ts index 6f46ed10ee9..7f0f0e4fa9c 100644 --- a/public/app/features/dashboard/components/DashExportModal/DashboardExporter.ts +++ b/public/app/features/dashboard/components/DashExportModal/DashboardExporter.ts @@ -5,6 +5,8 @@ import { DashboardModel } from '../../state/DashboardModel'; import { PanelModel } from 'app/features/dashboard/state'; import { PanelPluginMeta } from '@grafana/data'; import { getDataSourceSrv } from '@grafana/runtime'; +import { VariableOption, VariableRefresh } from '../../../variables/types'; +import { isConstant, isQuery } from '../../../variables/guard'; interface Input { name: string; @@ -144,11 +146,12 @@ export class DashboardExporter { // templatize template vars for (const variable of saveModel.getVariables()) { - if (variable.type === 'query') { + if (isQuery(variable)) { templateizeDatasourceUsage(variable); variable.options = []; - variable.current = {}; - variable.refresh = variable.refresh > 0 ? variable.refresh : 1; + variable.current = ({} as unknown) as VariableOption; + variable.refresh = + variable.refresh !== VariableRefresh.never ? variable.refresh : VariableRefresh.onDashboardLoad; } } @@ -173,7 +176,7 @@ export class DashboardExporter { // templatize constants for (const variable of saveModel.getVariables()) { - if (variable.type === 'constant') { + if (isConstant(variable)) { const refName = 'VAR_' + variable.name.replace(' ', '_').toUpperCase(); inputs.push({ name: refName, @@ -187,6 +190,7 @@ export class DashboardExporter { variable.options[0] = variable.current = { value: variable.query, text: variable.query, + selected: false, }; } } diff --git a/public/app/features/dashboard/components/DashboardRow/DashboardRow.test.tsx b/public/app/features/dashboard/components/DashboardRow/DashboardRow.test.tsx index 87554653cce..df944b049c5 100644 --- a/public/app/features/dashboard/components/DashboardRow/DashboardRow.test.tsx +++ b/public/app/features/dashboard/components/DashboardRow/DashboardRow.test.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { shallow } from 'enzyme'; +import { mount } from 'enzyme'; import { DashboardRow } from './DashboardRow'; import { PanelModel } from '../../state/PanelModel'; @@ -16,7 +16,7 @@ describe('DashboardRow', () => { }; panel = new PanelModel({ collapsed: false }); - wrapper = shallow(); + wrapper = mount(); }); it('Should not have collapsed class when collaped is false', () => { @@ -37,14 +37,14 @@ describe('DashboardRow', () => { it('should not show row drag handle when cannot edit', () => { dashboardMock.meta.canEdit = false; - wrapper = shallow(); + wrapper = mount(); expect(wrapper.find('.dashboard-row__drag')).toHaveLength(0); }); it('should have zero actions when cannot edit', () => { dashboardMock.meta.canEdit = false; panel = new PanelModel({ collapsed: false }); - wrapper = shallow(); + wrapper = mount(); expect(wrapper.find('.dashboard-row__actions .pointer')).toHaveLength(0); }); }); diff --git a/public/app/features/dashboard/components/DashboardRow/DashboardRow.tsx b/public/app/features/dashboard/components/DashboardRow/DashboardRow.tsx index 9c715d7b74e..04511859bbb 100644 --- a/public/app/features/dashboard/components/DashboardRow/DashboardRow.tsx +++ b/public/app/features/dashboard/components/DashboardRow/DashboardRow.tsx @@ -6,6 +6,7 @@ import { DashboardModel } from '../../state/DashboardModel'; import templateSrv from 'app/features/templating/template_srv'; import appEvents from 'app/core/app_events'; import { CoreEvents } from 'app/types'; +import { RowOptionsButton } from '../RowOptions/RowOptionsButton'; export interface DashboardRowProps { panel: PanelModel; @@ -39,22 +40,14 @@ export class DashboardRow extends React.Component { }); }; - onUpdate = () => { + onUpdate = (title: string | null, repeat: string | null) => { + this.props.panel['title'] = title; + this.props.panel['repeat'] = repeat; + this.props.panel.render(); this.props.dashboard.processRepeats(); this.forceUpdate(); }; - onOpenSettings = () => { - appEvents.emit(CoreEvents.showModal, { - templateHtml: ``, - modalClass: 'modal--narrow', - model: { - row: this.props.panel, - onUpdated: this.onUpdate, - }, - }); - }; - onDelete = () => { appEvents.emit(CoreEvents.showConfirmModal, { title: 'Delete Row', @@ -92,9 +85,11 @@ export class DashboardRow extends React.Component { {canEdit && (
- - - + diff --git a/public/app/features/dashboard/components/DashboardSettings/SettingsCtrl.ts b/public/app/features/dashboard/components/DashboardSettings/SettingsCtrl.ts index 98ac0209fbf..dce4789f33c 100644 --- a/public/app/features/dashboard/components/DashboardSettings/SettingsCtrl.ts +++ b/public/app/features/dashboard/components/DashboardSettings/SettingsCtrl.ts @@ -25,7 +25,6 @@ export class SettingsCtrl { sections: any[]; hasUnsavedFolderChange: boolean; selectors: typeof selectors.pages.Dashboard.Settings.General; - useAngularTemplating: boolean; /** @ngInject */ constructor( @@ -60,7 +59,6 @@ export class SettingsCtrl { appEvents.on(CoreEvents.dashboardSaved, this.onPostSave.bind(this), $scope); this.selectors = selectors.pages.Dashboard.Settings.General; - this.useAngularTemplating = !getConfig().featureToggles.newVariables; } buildSectionList() { diff --git a/public/app/features/dashboard/components/DashboardSettings/template.html b/public/app/features/dashboard/components/DashboardSettings/template.html index 32211442276..429af171dd5 100644 --- a/public/app/features/dashboard/components/DashboardSettings/template.html +++ b/public/app/features/dashboard/components/DashboardSettings/template.html @@ -77,11 +77,7 @@ ng-include="'public/app/features/annotations/partials/editor.html'">
-
-
- -
+
diff --git a/public/app/features/dashboard/components/PanelEditor/PanelEditor.tsx b/public/app/features/dashboard/components/PanelEditor/PanelEditor.tsx index 23a62c164a4..66a38ec34e9 100644 --- a/public/app/features/dashboard/components/PanelEditor/PanelEditor.tsx +++ b/public/app/features/dashboard/components/PanelEditor/PanelEditor.tsx @@ -25,7 +25,7 @@ import { getPanelEditorTabs } from './state/selectors'; import { getPanelStateById } from '../../state/selectors'; import { OptionsPaneContent } from './OptionsPaneContent'; import { DashNavButton } from 'app/features/dashboard/components/DashNav/DashNavButton'; -import { VariableModel } from 'app/features/templating/types'; +import { VariableModel } from 'app/features/variables/types'; import { getVariables } from 'app/features/variables/state/selectors'; import { SubMenuItems } from 'app/features/dashboard/components/SubMenu/SubMenuItems'; import { BackButton } from 'app/core/components/BackButton/BackButton'; diff --git a/public/app/features/dashboard/components/PanelEditor/PanelOptionsTab.tsx b/public/app/features/dashboard/components/PanelEditor/PanelOptionsTab.tsx index 3fa929c79ff..1979c4cdde6 100644 --- a/public/app/features/dashboard/components/PanelEditor/PanelOptionsTab.tsx +++ b/public/app/features/dashboard/components/PanelEditor/PanelOptionsTab.tsx @@ -1,13 +1,13 @@ -import React, { FC, useMemo, useRef } from 'react'; +import React, { FC, useCallback, useMemo, useRef } from 'react'; import { DashboardModel, PanelModel } from '../../state'; -import { PanelData, PanelPlugin, SelectableValue } from '@grafana/data'; +import { PanelData, PanelPlugin } from '@grafana/data'; import { Counter, DataLinksInlineEditor, Field, Input, RadioButtonGroup, Select, Switch, TextArea } from '@grafana/ui'; import { getPanelLinksVariableSuggestions } from '../../../panel/panellinks/link_srv'; -import { getVariables } from '../../../variables/state/selectors'; import { PanelOptionsEditor } from './PanelOptionsEditor'; import { AngularPanelOptions } from './AngularPanelOptions'; import { VisualizationTab } from './VisualizationTab'; import { OptionsGroup } from './OptionsGroup'; +import { RepeatRowSelect } from '../RepeatRowSelect/RepeatRowSelect'; interface Props { panel: PanelModel; @@ -28,10 +28,12 @@ export const PanelOptionsTab: FC = ({ }) => { const visTabInputRef = useRef(); const linkVariablesSuggestions = useMemo(() => getPanelLinksVariableSuggestions(), []); + const onRepeatRowSelectChange = useCallback((value: string | null) => onPanelConfigChange('repeat', value), [ + onPanelConfigChange, + ]); const elements: JSX.Element[] = []; const panelLinksCount = panel && panel.links ? panel.links.length : 0; - const variableOptions = getVariableOptions(); const directionOptions = [ { label: 'Horizontal', value: 'h' }, { label: 'Vertical', value: 'v' }, @@ -120,11 +122,7 @@ export const PanelOptionsTab: FC = ({ This is not visible while in edit mode. You need to go back to dashboard and then update the variable or reload the dashboard." > - ; +}; diff --git a/public/app/features/dashboard/components/RowOptions/RowOptionsButton.tsx b/public/app/features/dashboard/components/RowOptions/RowOptionsButton.tsx new file mode 100644 index 00000000000..97cdd1686d1 --- /dev/null +++ b/public/app/features/dashboard/components/RowOptions/RowOptionsButton.tsx @@ -0,0 +1,37 @@ +import React, { FC } from 'react'; +import { Icon, ModalsController } from '@grafana/ui'; + +import { RowOptionsModal } from './RowOptionsModal'; +import { OnRowOptionsUpdate } from './RowOptionsForm'; + +export interface RowOptionsButtonProps { + title: string | null; + repeat: string | null; + onUpdate: OnRowOptionsUpdate; +} + +export const RowOptionsButton: FC = ({ repeat, title, onUpdate }) => { + const onUpdateChange = (hideModal: () => void) => (title: string | null, repeat: string | null) => { + onUpdate(title, repeat); + hideModal(); + }; + + return ( + + {({ showModal, hideModal }) => { + return ( + { + showModal(RowOptionsModal, { title, repeat, onDismiss: hideModal, onUpdate: onUpdateChange(hideModal) }); + }} + > + + + ); + }} + + ); +}; + +RowOptionsButton.displayName = 'RowOptionsButton'; diff --git a/public/app/features/dashboard/components/RowOptions/RowOptionsCtrl.ts b/public/app/features/dashboard/components/RowOptions/RowOptionsCtrl.ts deleted file mode 100644 index d2526c92cd0..00000000000 --- a/public/app/features/dashboard/components/RowOptions/RowOptionsCtrl.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { coreModule } from 'app/core/core'; - -export class RowOptionsCtrl { - row: any; - source: any; - dismiss: any; - onUpdated: any; - showDelete: boolean; - - /** @ngInject */ - constructor() { - this.source = this.row; - this.row = this.row.getSaveModel(); - } - - update() { - this.source.title = this.row.title; - this.source.repeat = this.row.repeat; - this.onUpdated(); - this.dismiss(); - } -} - -export function rowOptionsDirective() { - return { - restrict: 'E', - templateUrl: 'public/app/features/dashboard/components/RowOptions/template.html', - controller: RowOptionsCtrl, - bindToController: true, - controllerAs: 'ctrl', - scope: { - row: '=', - dismiss: '&', - onUpdated: '&', - }, - }; -} - -coreModule.directive('rowOptions', rowOptionsDirective); diff --git a/public/app/features/dashboard/components/RowOptions/RowOptionsForm.tsx b/public/app/features/dashboard/components/RowOptions/RowOptionsForm.tsx new file mode 100644 index 00000000000..e40e4d5bc8a --- /dev/null +++ b/public/app/features/dashboard/components/RowOptions/RowOptionsForm.tsx @@ -0,0 +1,46 @@ +import React, { FC, useCallback, useState } from 'react'; +import { Button, Field, Form, HorizontalGroup, Input } from '@grafana/ui'; + +import { RepeatRowSelect } from '../RepeatRowSelect/RepeatRowSelect'; + +export type OnRowOptionsUpdate = (title: string | null, repeat: string | null) => void; + +export interface Props { + title: string | null; + repeat: string | null; + onUpdate: OnRowOptionsUpdate; + onCancel: () => void; +} + +export const RowOptionsForm: FC = ({ repeat, title, onUpdate, onCancel }) => { + const [newRepeat, setNewRepeat] = useState(repeat); + const onChangeRepeat = useCallback((name: string) => setNewRepeat(name), [setNewRepeat]); + + return ( +
{ + onUpdate(formData.title, newRepeat); + }} + > + {({ register }) => ( + <> + + + + + + + + + + + + + + )} +
+ ); +}; diff --git a/public/app/features/dashboard/components/RowOptions/RowOptionsModal.tsx b/public/app/features/dashboard/components/RowOptions/RowOptionsModal.tsx new file mode 100644 index 00000000000..a7478598cd5 --- /dev/null +++ b/public/app/features/dashboard/components/RowOptions/RowOptionsModal.tsx @@ -0,0 +1,30 @@ +import React, { FC } from 'react'; +import { Modal, stylesFactory } from '@grafana/ui'; +import { css } from 'emotion'; + +import { OnRowOptionsUpdate, RowOptionsForm } from './RowOptionsForm'; + +export interface RowOptionsModalProps { + title: string | null; + repeat: string | null; + onDismiss: () => void; + onUpdate: OnRowOptionsUpdate; +} + +export const RowOptionsModal: FC = ({ repeat, title, onDismiss, onUpdate }) => { + const styles = getStyles(); + return ( + + + + ); +}; + +const getStyles = stylesFactory(() => { + return { + modal: css` + label: RowOptionsModal; + width: 500px; + `, + }; +}); diff --git a/public/app/features/dashboard/components/RowOptions/index.ts b/public/app/features/dashboard/components/RowOptions/index.ts deleted file mode 100644 index 626e4cd65b3..00000000000 --- a/public/app/features/dashboard/components/RowOptions/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { RowOptionsCtrl } from './RowOptionsCtrl'; diff --git a/public/app/features/dashboard/components/RowOptions/template.html b/public/app/features/dashboard/components/RowOptions/template.html deleted file mode 100644 index 97ecdac07a2..00000000000 --- a/public/app/features/dashboard/components/RowOptions/template.html +++ /dev/null @@ -1,30 +0,0 @@ - diff --git a/public/app/features/dashboard/components/ShareModal/ShareSnapshot.tsx b/public/app/features/dashboard/components/ShareModal/ShareSnapshot.tsx index 87d5bb862a4..282d0d3635e 100644 --- a/public/app/features/dashboard/components/ShareModal/ShareSnapshot.tsx +++ b/public/app/features/dashboard/components/ShareModal/ShareSnapshot.tsx @@ -1,11 +1,13 @@ import React, { PureComponent } from 'react'; -import { Button, ClipboardButton, LinkButton, LegacyForms, Icon } from '@grafana/ui'; -const { Select, Input } = LegacyForms; +import { Button, ClipboardButton, Icon, LegacyForms, LinkButton } from '@grafana/ui'; import { AppEvents, SelectableValue } from '@grafana/data'; import { getBackendSrv } from '@grafana/runtime'; import { DashboardModel, PanelModel } from 'app/features/dashboard/state'; import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv'; import { appEvents } from 'app/core/core'; +import { VariableRefresh } from '../../../variables/types'; + +const { Select, Input } = LegacyForms; const snapshotApiUrl = '/api/snapshots'; @@ -140,10 +142,10 @@ export class ShareSnapshot extends PureComponent { }); // remove template queries - dash.getVariables().forEach(variable => { + dash.getVariables().forEach((variable: any) => { variable.query = ''; - variable.options = variable.current; - variable.refresh = false; + variable.options = variable.current ? [variable.current] : []; + variable.refresh = VariableRefresh.never; }); // snapshot single panel diff --git a/public/app/features/dashboard/components/SubMenu/SubMenu.tsx b/public/app/features/dashboard/components/SubMenu/SubMenu.tsx index 45ce7ea9c31..f6ead203cd6 100644 --- a/public/app/features/dashboard/components/SubMenu/SubMenu.tsx +++ b/public/app/features/dashboard/components/SubMenu/SubMenu.tsx @@ -2,7 +2,7 @@ import React, { PureComponent } from 'react'; import { connect, MapStateToProps } from 'react-redux'; import { StoreState } from '../../../../types'; import { getVariables } from '../../../variables/state/selectors'; -import { VariableHide, VariableModel } from '../../../templating/types'; +import { VariableHide, VariableModel } from '../../../variables/types'; import { DashboardModel } from '../../state'; import { DashboardLinks } from './DashboardLinks'; import { Annotations } from './Annotations'; diff --git a/public/app/features/dashboard/components/SubMenu/SubMenuItems.tsx b/public/app/features/dashboard/components/SubMenu/SubMenuItems.tsx index 90064567141..9b4d12f5f1c 100644 --- a/public/app/features/dashboard/components/SubMenu/SubMenuItems.tsx +++ b/public/app/features/dashboard/components/SubMenu/SubMenuItems.tsx @@ -1,5 +1,5 @@ import React, { FunctionComponent, useEffect, useState } from 'react'; -import { VariableHide, VariableModel } from '../../../templating/types'; +import { VariableHide, VariableModel } from '../../../variables/types'; import { selectors } from '@grafana/e2e-selectors'; import { PickerRenderer } from '../../../variables/pickers/PickerRenderer'; diff --git a/public/app/features/dashboard/components/SubMenu/template.html b/public/app/features/dashboard/components/SubMenu/template.html deleted file mode 100644 index 23b4ba837be..00000000000 --- a/public/app/features/dashboard/components/SubMenu/template.html +++ /dev/null @@ -1,60 +0,0 @@ - diff --git a/public/app/features/dashboard/containers/DashboardPage.tsx b/public/app/features/dashboard/containers/DashboardPage.tsx index 7e99a7cc5b0..4da79352e64 100644 --- a/public/app/features/dashboard/containers/DashboardPage.tsx +++ b/public/app/features/dashboard/containers/DashboardPage.tsx @@ -28,7 +28,6 @@ import { import { DashboardModel, PanelModel } from 'app/features/dashboard/state'; import { InspectTab, PanelInspector } from '../components/Inspector/PanelInspector'; -import { getConfig } from '../../../core/config'; import { SubMenu } from '../components/SubMenu/SubMenu'; import { cleanUpDashboardAndVariables } from '../state/actions'; import { cancelVariables } from '../../variables/state/actions'; @@ -275,7 +274,6 @@ export class DashboardPage extends PureComponent { } = this.props; const { editPanel, viewPanel, scrollTop, updateScrollTop } = this.state; - const { featureToggles } = getConfig(); if (!dashboard) { if (isInitSlow) { @@ -307,8 +305,7 @@ export class DashboardPage extends PureComponent { {initError && this.renderInitFailedState()}
- {!featureToggles.newVariables && } - {!editPanel && featureToggles.newVariables && } + {!editPanel && } { + _.each(dash.getVariables(), (variable: any) => { variable.current = null; variable.options = null; variable.filters = null; diff --git a/public/app/features/dashboard/state/DashboardMigrator.ts b/public/app/features/dashboard/state/DashboardMigrator.ts index 8031eae0597..1416f7f0621 100644 --- a/public/app/features/dashboard/state/DashboardMigrator.ts +++ b/public/app/features/dashboard/state/DashboardMigrator.ts @@ -18,7 +18,7 @@ import { } from 'app/core/constants'; import { isMulti, isQuery } from 'app/features/variables/guard'; import { alignCurrentWithMulti } from 'app/features/variables/shared/multiOptions'; -import { VariableTag } from '../../templating/types'; +import { VariableTag } from '../../variables/types'; export class DashboardMigrator { dashboard: DashboardModel; @@ -240,7 +240,7 @@ export class DashboardMigrator { if (oldVersion < 12) { // update template variables - _.each(this.dashboard.getVariables(), templateVariable => { + _.each(this.dashboard.getVariables(), (templateVariable: any) => { if (templateVariable.refresh) { templateVariable.refresh = 1; } diff --git a/public/app/features/dashboard/state/DashboardModel.ts b/public/app/features/dashboard/state/DashboardModel.ts index 94ca887b989..e98eba0e04d 100644 --- a/public/app/features/dashboard/state/DashboardModel.ts +++ b/public/app/features/dashboard/state/DashboardModel.ts @@ -21,7 +21,6 @@ import { UrlQueryValue, } from '@grafana/data'; import { CoreEvents, DashboardMeta, KIOSK_MODE_TV } from 'app/types'; -import { getConfig } from '../../../core/config'; import { GetVariables, getVariables } from 'app/features/variables/state/selectors'; import { variableAdapters } from 'app/features/variables/adapters'; import { onTimeRangeUpdated } from 'app/features/variables/state/actions'; @@ -229,46 +228,6 @@ export class DashboardModel { private updateTemplatingSaveModelClone( copy: any, defaults: { saveTimerange: boolean; saveVariables: boolean } & CloneOptions - ) { - if (getConfig().featureToggles.newVariables) { - this.updateTemplatingSaveModel(copy, defaults); - return; - } - this.updateAngularTemplatingSaveModel(copy, defaults); - } - - private updateAngularTemplatingSaveModel( - copy: any, - defaults: { saveTimerange: boolean; saveVariables: boolean } & CloneOptions - ) { - // get variable save models - copy.templating = { - list: _.map(this.templating.list, (variable: any) => - variable.getSaveModel ? variable.getSaveModel() : variable - ), - }; - - if (!defaults.saveVariables) { - for (let i = 0; i < copy.templating.list.length; i++) { - const current = copy.templating.list[i]; - const original: any = _.find(this.originalTemplating, { name: current.name, type: current.type }); - - if (!original) { - continue; - } - - if (current.type === 'adhoc') { - copy.templating.list[i].filters = original.filters; - } else { - copy.templating.list[i].current = original.current; - } - } - } - } - - private updateTemplatingSaveModel( - copy: any, - defaults: { saveTimerange: boolean; saveVariables: boolean } & CloneOptions ) { const originalVariables = this.originalTemplating; const currentVariables = this.getVariablesFromState(); @@ -297,9 +256,7 @@ export class DashboardModel { timeRangeUpdated(timeRange: TimeRange) { this.events.emit(CoreEvents.timeRangeUpdated, timeRange); - if (getConfig().featureToggles.newVariables) { - dispatch(onTimeRangeUpdated(timeRange)); - } + dispatch(onTimeRangeUpdated(timeRange)); } startRefresh() { @@ -965,7 +922,7 @@ export class DashboardModel { } resetOriginalVariables(initial = false) { - if (!getConfig().featureToggles.newVariables || initial) { + if (initial) { this.originalTemplating = this.cloneVariablesFrom(this.templating.list); return; } @@ -974,11 +931,7 @@ export class DashboardModel { } hasVariableValuesChanged() { - if (getConfig().featureToggles.newVariables) { - return this.hasVariablesChanged(this.originalTemplating, this.getVariablesFromState()); - } - - return this.hasVariablesChanged(this.originalTemplating, this.templating.list); + return this.hasVariablesChanged(this.originalTemplating, this.getVariablesFromState()); } autoFitPanels(viewHeight: number, kioskMode?: UrlQueryValue) { @@ -1048,17 +1001,10 @@ export class DashboardModel { } getVariables = () => { - if (getConfig().featureToggles.newVariables) { - return this.getVariablesFromState(); - } - return this.templating.list; + return this.getVariablesFromState(); }; private getPanelRepeatVariable(panel: PanelModel) { - if (!getConfig().featureToggles.newVariables) { - return _.find(this.templating.list, { name: panel.repeat } as any); - } - return this.getVariablesFromState().find(variable => variable.name === panel.repeat); } @@ -1067,10 +1013,7 @@ export class DashboardModel { } private hasVariables() { - if (getConfig().featureToggles.newVariables) { - return this.getVariablesFromState().length > 0; - } - return this.templating.list.length > 0; + return this.getVariablesFromState().length > 0; } private hasVariablesChanged(originalVariables: any[], currentVariables: any[]): boolean { diff --git a/public/app/features/dashboard/state/initDashboard.test.ts b/public/app/features/dashboard/state/initDashboard.test.ts index 9258f41ca9d..854278252d5 100644 --- a/public/app/features/dashboard/state/initDashboard.test.ts +++ b/public/app/features/dashboard/state/initDashboard.test.ts @@ -7,7 +7,6 @@ import { dashboardInitCompleted, dashboardInitFetching, dashboardInitServices } import { updateLocation } from '../../../core/actions'; import { setEchoSrv } from '@grafana/runtime'; import { Echo } from '../../../core/services/echo/Echo'; -import { getConfig } from 'app/core/config'; import { variableAdapters } from 'app/features/variables/adapters'; import { createConstantVariableAdapter } from 'app/features/variables/constant/adapter'; import { constantBuilder } from 'app/features/variables/shared/testing/builders'; @@ -38,7 +37,6 @@ interface ScenarioContext { timeSrv: any; annotationsSrv: any; unsavedChangesSrv: any; - variableSrv: any; dashboardSrv: any; loaderSrv: any; keybindingSrv: any; @@ -55,7 +53,6 @@ function describeInitScenario(description: string, scenarioFn: ScenarioFn) { const timeSrv = { init: jest.fn() }; const annotationsSrv = { init: jest.fn() }; const unsavedChangesSrv = { init: jest.fn() }; - const variableSrv = { init: jest.fn() }; const dashboardSrv = { setCurrent: jest.fn() }; const keybindingSrv = { setupDashboardBindings: jest.fn() }; const loaderSrv = { @@ -102,8 +99,6 @@ function describeInitScenario(description: string, scenarioFn: ScenarioFn) { return unsavedChangesSrv; case 'dashboardSrv': return dashboardSrv; - case 'variableSrv': - return variableSrv; case 'keybindingSrv': return keybindingSrv; default: @@ -126,7 +121,6 @@ function describeInitScenario(description: string, scenarioFn: ScenarioFn) { timeSrv, annotationsSrv, unsavedChangesSrv, - variableSrv, dashboardSrv, keybindingSrv, loaderSrv, @@ -201,13 +195,6 @@ describeInitScenario('Initializing new dashboard', ctx => { expect(ctx.keybindingSrv.setupDashboardBindings).toBeCalled(); expect(ctx.dashboardSrv.setCurrent).toBeCalled(); }); - - it('Should initialize variableSrv if newVariables is disabled', () => { - if (getConfig().featureToggles.newVariables) { - return expect.assertions(0); - } - expect(ctx.variableSrv.init).toBeCalled(); - }); }); describeInitScenario('Initializing home dashboard', ctx => { @@ -260,9 +247,8 @@ describeInitScenario('Initializing existing dashboard', ctx => { }); it('Should send action dashboardInitCompleted', () => { - const index = getConfig().featureToggles.newVariables ? 6 : 5; - expect(ctx.actions[index].type).toBe(dashboardInitCompleted.type); - expect(ctx.actions[index].payload.title).toBe('My cool dashboard'); + expect(ctx.actions[6].type).toBe(dashboardInitCompleted.type); + expect(ctx.actions[6].payload.title).toBe('My cool dashboard'); }); it('Should initialize services', () => { @@ -273,17 +259,7 @@ describeInitScenario('Initializing existing dashboard', ctx => { expect(ctx.dashboardSrv.setCurrent).toBeCalled(); }); - it('Should initialize variableSrv if newVariables is disabled', () => { - if (getConfig().featureToggles.newVariables) { - return expect.assertions(0); - } - expect(ctx.variableSrv.init).toBeCalled(); - }); - it('Should initialize redux variables if newVariables is enabled', () => { - if (!getConfig().featureToggles.newVariables) { - return expect.assertions(0); - } expect(ctx.actions[3].type).toBe(variablesInitTransaction.type); }); }); diff --git a/public/app/features/dashboard/state/initDashboard.ts b/public/app/features/dashboard/state/initDashboard.ts index 93b499bc277..381fa77b373 100644 --- a/public/app/features/dashboard/state/initDashboard.ts +++ b/public/app/features/dashboard/state/initDashboard.ts @@ -5,7 +5,6 @@ import { DashboardSrv } from 'app/features/dashboard/services/DashboardSrv'; import { DashboardLoaderSrv } from 'app/features/dashboard/services/DashboardLoaderSrv'; import { TimeSrv } from 'app/features/dashboard/services/TimeSrv'; import { AnnotationsSrv } from 'app/features/annotations/annotations_srv'; -import { VariableSrv } from 'app/features/templating/variable_srv'; import { KeybindingSrv } from 'app/core/services/keybindingSrv'; // Actions import { notifyApp, updateLocation } from 'app/core/actions'; @@ -175,7 +174,6 @@ export function initDashboard(args: InitDashboardArgs): ThunkResult { // init services const timeSrv: TimeSrv = args.$injector.get('timeSrv'); const annotationsSrv: AnnotationsSrv = args.$injector.get('annotationsSrv'); - const variableSrv: VariableSrv = args.$injector.get('variableSrv'); const keybindingSrv: KeybindingSrv = args.$injector.get('keybindingSrv'); const unsavedChangesSrv = args.$injector.get('unsavedChangesSrv'); const dashboardSrv: DashboardSrv = args.$injector.get('dashboardSrv'); @@ -189,7 +187,7 @@ export function initDashboard(args: InitDashboardArgs): ThunkResult { } // template values service needs to initialize completely before the rest of the dashboard can load - await dispatch(initVariablesTransaction(args.urlUid, dashboard, variableSrv)); + await dispatch(initVariablesTransaction(args.urlUid, dashboard)); if (getState().templating.transaction.uid !== args.urlUid) { // if a previous dashboard has slow running variable queries the batch uid will be the new one diff --git a/public/app/features/panel/GeneralTabCtrl.ts b/public/app/features/panel/GeneralTabCtrl.ts deleted file mode 100644 index a97e2536b3a..00000000000 --- a/public/app/features/panel/GeneralTabCtrl.ts +++ /dev/null @@ -1,46 +0,0 @@ -import coreModule from 'app/core/core_module'; - -const obj2string = (obj: any) => { - return Object.keys(obj) - .reduce((acc, curr) => acc.concat(curr + '=' + obj[curr]), []) - .join(); -}; - -export class GeneralTabCtrl { - panelCtrl: any; - - /** @ngInject */ - constructor($scope: any) { - this.panelCtrl = $scope.ctrl; - - const updatePanel = () => { - console.log('panel.render()'); - this.panelCtrl.panel.render(); - }; - - const generateValueFromPanel = (scope: any) => { - const { panel } = scope.ctrl; - const panelPropsToTrack = ['title', 'description', 'transparent', 'repeat', 'repeatDirection', 'minSpan']; - const panelPropsString = panelPropsToTrack - .map(prop => prop + '=' + (panel[prop] && panel[prop].toString ? panel[prop].toString() : panel[prop])) - .join(); - const panelLinks = panel.links || []; - const panelLinksString = panelLinks.map(obj2string).join(); - return panelPropsString + panelLinksString; - }; - - $scope.$watch(generateValueFromPanel, updatePanel, true); - } -} - -/** @ngInject */ -export function generalTab() { - 'use strict'; - return { - restrict: 'E', - templateUrl: 'public/app/features/panel/partials/general_tab.html', - controller: GeneralTabCtrl, - }; -} - -coreModule.directive('panelGeneralTab', generalTab); diff --git a/public/app/features/panel/all.ts b/public/app/features/panel/all.ts index be6bf9027be..d691575ccfc 100644 --- a/public/app/features/panel/all.ts +++ b/public/app/features/panel/all.ts @@ -2,5 +2,4 @@ import './panel_directive'; import './query_ctrl'; import './panel_editor_tab'; import './query_editor_row'; -import './repeat_option'; import './panellinks/module'; diff --git a/public/app/features/panel/partials/general_tab.html b/public/app/features/panel/partials/general_tab.html deleted file mode 100644 index 45318052099..00000000000 --- a/public/app/features/panel/partials/general_tab.html +++ /dev/null @@ -1,49 +0,0 @@ -
- -
-
-
- Title - -
- -
-
-
- Description - -
-
-
-
- -
-
-
Repeating
-
-
-
-
- Repeat - -
-
- Direction - -
-
- Max per row - -
-
-
- Note: You may need to change the variable selection to see this in action. -
-
-
-
-
diff --git a/public/app/features/panel/repeat_option.ts b/public/app/features/panel/repeat_option.ts deleted file mode 100644 index 9d847807a93..00000000000 --- a/public/app/features/panel/repeat_option.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { coreModule } from 'app/core/core'; -import { VariableSrv } from 'app/features/templating/variable_srv'; -import { getConfig } from '../../core/config'; -import { getVariables } from '../variables/state/selectors'; - -const template = ` -
- -
-
- - Type - - {{ variableTypes[current.type].description }} - - -
- -
-
-
- -
- Template names cannot begin with '__', that's reserved for Grafana's global variables -
- -
-
- Label - -
-
- Hide -
- -
-
-
-
- -
-
Interval Options
- -
- Values - -
- -
- - - -
- - Step count How many times should the current time range be divided to calculate the value - -
- -
-
-
- - Min interval The calculated value will not go below this threshold - - -
-
-
- -
-
Custom Options
-
- Values separated by comma - -
-
- -
-
Constant options
-
- Value - -
-
- -
-
Text options
-
- Default value - -
-
- -
-
Query Options
- -
-
- Data source -
- -
-
- -
- - Refresh - - When to update the values of this variable. - - -
- -
-
-
- - - - - -
- - Regex - - Optional, if you want to extract part of a series name or metric node segment. - - - -
-
- - Sort - - How to sort the values of this variable. - - -
- -
-
-
- -
-
Data source options
- -
- -
- -
-
- -
- - -
-
- -
-
Options
-
- Data source -
- -
-
-
- -
-
Selection Options
-
- - - - -
-
- Custom all value - -
-
- -
-
Value groups/tags (Experimental feature)
- - -
- Tags query - -
-
-
  • Tag values query
  • - -
    -
    - -
    -
    Preview of values
    -
    -
    - {{ option.text }} -
    - -
    -
    - -
    - {{ infoText }} -
    - -
    - - -
    - - diff --git a/public/app/features/templating/query_variable.ts b/public/app/features/templating/query_variable.ts deleted file mode 100644 index 0b1b85314a6..00000000000 --- a/public/app/features/templating/query_variable.ts +++ /dev/null @@ -1,254 +0,0 @@ -import _ from 'lodash'; -import { - assignModelProperties, - QueryVariableModel, - VariableActions, - VariableHide, - VariableOption, - VariableRefresh, - VariableSort, - VariableTag, - variableTypes, -} from './types'; -import { VariableType, DataSourceApi, stringToJsRegex } from '@grafana/data'; -import DatasourceSrv from '../plugins/datasource_srv'; -import { TemplateSrv } from './template_srv'; -import { VariableSrv } from './variable_srv'; -import { TimeSrv } from '../dashboard/services/TimeSrv'; -import { containsVariable } from './utils'; - -function getNoneOption(): VariableOption { - return { text: 'None', value: '', isNone: true, selected: false }; -} - -export class QueryVariable implements QueryVariableModel, VariableActions { - type: VariableType; - name: string; - label: string | null; - hide: VariableHide; - skipUrlSync: boolean; - datasource: string | null; - query: string; - regex: string; - sort: VariableSort; - options: VariableOption[]; - current: VariableOption; - refresh: VariableRefresh; - multi: boolean; - includeAll: boolean; - useTags: boolean; - tagsQuery: string; - tagValuesQuery: string; - tags: VariableTag[]; - definition: string; - allValue: string; - index: number; - - defaults: QueryVariableModel = { - type: 'query', - name: '', - label: null, - hide: VariableHide.dontHide, - skipUrlSync: false, - datasource: null, - query: '', - regex: '', - sort: VariableSort.disabled, - refresh: VariableRefresh.never, - multi: false, - includeAll: false, - allValue: null, - options: [], - current: {} as VariableOption, - tags: [], - useTags: false, - tagsQuery: '', - tagValuesQuery: '', - definition: '', - index: -1, - }; - - /** @ngInject */ - constructor( - private model: any, - private datasourceSrv: DatasourceSrv, - private templateSrv: TemplateSrv, - private variableSrv: VariableSrv, - private timeSrv: TimeSrv - ) { - // copy model properties to this instance - assignModelProperties(this, model, this.defaults); - this.updateOptionsFromMetricFindQuery.bind(this); - } - - getSaveModel() { - // copy back model properties to model - assignModelProperties(this.model, this, this.defaults); - - // remove options - if (this.refresh !== 0) { - this.model.options = []; - } - - return this.model; - } - - setValue(option: any) { - return this.variableSrv.setOptionAsCurrent(this, option); - } - - setValueFromUrl(urlValue: any) { - return this.variableSrv.setOptionFromUrl(this, urlValue); - } - - getValueForUrl() { - if (this.current.text === 'All') { - return 'All'; - } - return this.current.value; - } - - updateOptions(searchFilter?: string) { - return this.datasourceSrv - .get(this.datasource ?? '') - .then((ds: DataSourceApi) => this.updateOptionsFromMetricFindQuery(ds, searchFilter)) - .then(this.updateTags.bind(this)) - .then(this.variableSrv.validateVariableSelectionState.bind(this.variableSrv, this)); - } - - updateTags(datasource: any) { - if (this.useTags) { - return this.metricFindQuery(datasource, this.tagsQuery).then((results: any[]) => { - this.tags = []; - for (let i = 0; i < results.length; i++) { - this.tags.push(results[i].text); - } - return datasource; - }); - } else { - delete this.tags; - } - - return datasource; - } - - getValuesForTag(tagKey: string) { - return this.datasourceSrv.get(this.datasource ?? '').then((datasource: DataSourceApi) => { - const query = this.tagValuesQuery.replace('$tag', tagKey); - return this.metricFindQuery(datasource, query).then((results: any) => { - return _.map(results, value => { - return value.text; - }); - }); - }); - } - - updateOptionsFromMetricFindQuery(datasource: any, searchFilter?: string) { - return this.metricFindQuery(datasource, this.query, searchFilter).then((results: any) => { - this.options = this.metricNamesToVariableValues(results); - if (this.includeAll) { - this.addAllOption(); - } - if (!this.options.length) { - this.options.push(getNoneOption()); - } - return datasource; - }); - } - - metricFindQuery(datasource: any, query: string, searchFilter?: string) { - const options: any = { range: undefined, variable: this, searchFilter }; - - if (this.refresh === 2) { - options.range = this.timeSrv.timeRange(); - } - - return datasource.metricFindQuery(query, options); - } - - addAllOption() { - this.options.unshift({ text: 'All', value: '$__all', selected: false }); - } - - metricNamesToVariableValues(metricNames: any[]) { - let regex, options, i, matches; - options = []; - - if (this.regex) { - regex = stringToJsRegex(this.templateSrv.replace(this.regex, {}, 'regex')); - } - for (i = 0; i < metricNames.length; i++) { - const item = metricNames[i]; - let text = item.text === undefined || item.text === null ? item.value : item.text; - - let value = item.value === undefined || item.value === null ? item.text : item.value; - - if (_.isNumber(value)) { - value = value.toString(); - } - - if (_.isNumber(text)) { - text = text.toString(); - } - - if (regex) { - matches = regex.exec(value); - if (!matches) { - continue; - } - if (matches.length > 1) { - value = matches[1]; - text = matches[1]; - } - } - - options.push({ text: text, value: value }); - } - - options = _.uniqBy(options, 'value'); - return this.sortVariableValues(options, this.sort); - } - - sortVariableValues(options: any[], sortOrder: number) { - if (sortOrder === 0) { - return options; - } - - const sortType = Math.ceil(sortOrder / 2); - const reverseSort = sortOrder % 2 === 0; - - if (sortType === 1) { - options = _.sortBy(options, 'text'); - } else if (sortType === 2) { - options = _.sortBy(options, opt => { - const matches = opt.text.match(/.*?(\d+).*/); - if (!matches || matches.length < 2) { - return -1; - } else { - return parseInt(matches[1], 10); - } - }); - } else if (sortType === 3) { - options = _.sortBy(options, opt => { - return _.toLower(opt.text); - }); - } - - if (reverseSort) { - options = options.reverse(); - } - - return options; - } - - dependsOn(variable: any) { - return containsVariable(this.query, this.datasource, this.regex, variable.name); - } -} -// @ts-ignore -variableTypes['query'] = { - name: 'Query', - ctor: QueryVariable, - description: 'Variable values are fetched from a datasource query', - supportsMulti: true, -}; diff --git a/public/app/features/templating/specs/adhoc_variable.test.ts b/public/app/features/templating/specs/adhoc_variable.test.ts deleted file mode 100644 index 6d15ce8362c..00000000000 --- a/public/app/features/templating/specs/adhoc_variable.test.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { AdhocVariable } from '../adhoc_variable'; - -describe('AdhocVariable', () => { - describe('when serializing to url', () => { - it('should set return key value and op separated by pipe', () => { - const variable = new AdhocVariable({ - filters: [ - { key: 'key1', operator: '=', value: 'value1' }, - { key: 'key2', operator: '!=', value: 'value2' }, - { key: 'key3', operator: '=', value: 'value3a|value3b|value3c' }, - ], - }); - const urlValue = variable.getValueForUrl(); - expect(urlValue).toMatchObject(['key1|=|value1', 'key2|!=|value2', 'key3|=|value3a__gfp__value3b__gfp__value3c']); - }); - }); - - describe('when deserializing from url', () => { - it('should restore filters', () => { - const variable = new AdhocVariable({}); - variable.setValueFromUrl(['key1|=|value1', 'key2|!=|value2', 'key3|=|value3a__gfp__value3b__gfp__value3c']); - - expect(variable.filters[0].key).toBe('key1'); - expect(variable.filters[0].operator).toBe('='); - expect(variable.filters[0].value).toBe('value1'); - - expect(variable.filters[1].key).toBe('key2'); - expect(variable.filters[1].operator).toBe('!='); - expect(variable.filters[1].value).toBe('value2'); - - expect(variable.filters[2].key).toBe('key3'); - expect(variable.filters[2].operator).toBe('='); - expect(variable.filters[2].value).toBe('value3a|value3b|value3c'); - }); - }); -}); diff --git a/public/app/features/templating/specs/editor_ctrl.test.ts b/public/app/features/templating/specs/editor_ctrl.test.ts deleted file mode 100644 index 9823d1d9cea..00000000000 --- a/public/app/features/templating/specs/editor_ctrl.test.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { VariableEditorCtrl } from '../editor_ctrl'; -import { TemplateSrv } from '../template_srv'; -import { AppEvents } from '@grafana/data'; - -let mockEmit: any; -jest.mock('app/core/app_events', () => { - mockEmit = jest.fn(); - return { - emit: mockEmit, - }; -}); - -describe('VariableEditorCtrl', () => { - const scope = { - runQuery: () => { - return Promise.resolve({}); - }, - }; - - describe('When running a variable query and the data source returns an error', () => { - beforeEach(() => { - const variableSrv: any = { - updateOptions: () => { - return Promise.reject({ - data: { message: 'error' }, - }); - }, - }; - - return new VariableEditorCtrl(scope, {} as any, variableSrv, {} as TemplateSrv); - }); - - it('should emit an error', () => { - return scope.runQuery().then(res => { - expect(mockEmit).toBeCalled(); - expect(mockEmit.mock.calls[0][0]).toBe(AppEvents.alertError); - expect(mockEmit.mock.calls[0][1][0]).toBe('Templating'); - expect(mockEmit.mock.calls[0][1][1]).toBe('Template variables could not be initialized: error'); - }); - }); - }); -}); diff --git a/public/app/features/templating/specs/query_variable.test.ts b/public/app/features/templating/specs/query_variable.test.ts deleted file mode 100644 index 6c42bfc1acb..00000000000 --- a/public/app/features/templating/specs/query_variable.test.ts +++ /dev/null @@ -1,133 +0,0 @@ -import { QueryVariable } from '../query_variable'; -import DatasourceSrv from '../../plugins/datasource_srv'; -import { TemplateSrv } from '../template_srv'; -import { VariableSrv } from '../variable_srv'; -import { TimeSrv } from '../../dashboard/services/TimeSrv'; - -describe('QueryVariable', () => { - describe('when creating from model', () => { - it('should set defaults', () => { - const variable = new QueryVariable( - {}, - (null as unknown) as DatasourceSrv, - (null as unknown) as TemplateSrv, - (null as unknown) as VariableSrv, - (null as unknown) as TimeSrv - ); - expect(variable.datasource).toBe(null); - expect(variable.refresh).toBe(0); - expect(variable.sort).toBe(0); - expect(variable.name).toBe(''); - expect(variable.hide).toBe(0); - expect(variable.options.length).toBe(0); - expect(variable.multi).toBe(false); - expect(variable.includeAll).toBe(false); - }); - - it('get model should copy changes back to model', () => { - const variable = new QueryVariable( - {}, - (null as unknown) as DatasourceSrv, - (null as unknown) as TemplateSrv, - (null as unknown) as VariableSrv, - (null as unknown) as TimeSrv - ); - variable.options = [{ text: 'test', value: '', selected: false }]; - variable.datasource = 'google'; - variable.regex = 'asd'; - variable.sort = 50; - - const model = variable.getSaveModel(); - expect(model.options.length).toBe(1); - expect(model.options[0].text).toBe('test'); - expect(model.datasource).toBe('google'); - expect(model.regex).toBe('asd'); - expect(model.sort).toBe(50); - }); - - it('if refresh != 0 then remove options in presisted mode', () => { - const variable = new QueryVariable( - {}, - (null as unknown) as DatasourceSrv, - (null as unknown) as TemplateSrv, - (null as unknown) as VariableSrv, - (null as unknown) as TimeSrv - ); - variable.options = [{ text: 'test', value: '', selected: false }]; - variable.refresh = 1; - - const model = variable.getSaveModel(); - expect(model.options.length).toBe(0); - }); - }); - - describe('can convert and sort metric names', () => { - const variable = new QueryVariable( - {}, - (null as unknown) as DatasourceSrv, - (null as unknown) as TemplateSrv, - (null as unknown) as VariableSrv, - (null as unknown) as TimeSrv - ); - let input: any; - - beforeEach(() => { - input = [ - { text: '0', value: '0' }, - { text: '1', value: '1' }, - { text: null, value: 3 }, - { text: undefined, value: 4 }, - { text: '5', value: null }, - { text: '6', value: undefined }, - { text: null, value: '7' }, - { text: undefined, value: '8' }, - { text: 9, value: null }, - { text: 10, value: undefined }, - { text: '', value: undefined }, - { text: undefined, value: '' }, - ]; - }); - - describe('can sort a mixed array of metric variables in numeric order', () => { - let result: any; - - beforeEach(() => { - variable.sort = 3; // Numerical (asc) - result = variable.metricNamesToVariableValues(input); - }); - - it('should return in same order', () => { - let i = 0; - expect(result.length).toBe(11); - expect(result[i++].text).toBe(''); - expect(result[i++].text).toBe('0'); - expect(result[i++].text).toBe('1'); - expect(result[i++].text).toBe('3'); - expect(result[i++].text).toBe('4'); - expect(result[i++].text).toBe('5'); - expect(result[i++].text).toBe('6'); - }); - }); - - describe('can sort a mixed array of metric variables in alphabetical order', () => { - let result: any; - - beforeEach(() => { - variable.sort = 5; // Alphabetical CI (asc) - result = variable.metricNamesToVariableValues(input); - }); - - it('should return in same order', () => { - let i = 0; - expect(result.length).toBe(11); - expect(result[i++].text).toBe(''); - expect(result[i++].text).toBe('0'); - expect(result[i++].text).toBe('1'); - expect(result[i++].text).toBe('10'); - expect(result[i++].text).toBe('3'); - expect(result[i++].text).toBe('4'); - expect(result[i++].text).toBe('5'); - }); - }); - }); -}); diff --git a/public/app/features/templating/specs/template_srv.test.ts b/public/app/features/templating/specs/template_srv.test.ts deleted file mode 100644 index 209e1f7c82e..00000000000 --- a/public/app/features/templating/specs/template_srv.test.ts +++ /dev/null @@ -1,609 +0,0 @@ -import { TemplateSrv } from '../template_srv'; -import { convertToStoreState } from 'test/helpers/convertToStoreState'; -import { getTemplateSrvDependencies } from '../../../../test/helpers/getTemplateSrvDependencies'; -import { variableAdapters } from '../../variables/adapters'; -import { createQueryVariableAdapter } from '../../variables/query/adapter'; - -describe('templateSrv', () => { - let _templateSrv: any; - - function initTemplateSrv(variables: any[]) { - const state = convertToStoreState(variables); - - _templateSrv = new TemplateSrv(getTemplateSrvDependencies(state)); - _templateSrv.init(variables); - } - - describe('init', () => { - beforeEach(() => { - initTemplateSrv([{ type: 'query', name: 'test', current: { value: 'oogle' } }]); - }); - - it('should initialize template data', () => { - const target = _templateSrv.replace('this.[[test]].filters'); - expect(target).toBe('this.oogle.filters'); - }); - }); - - describe('replace can pass scoped vars', () => { - beforeEach(() => { - initTemplateSrv([{ type: 'query', name: 'test', current: { value: 'oogle' } }]); - }); - - it('scoped vars should support objects', () => { - const target = _templateSrv.replace('${series.name} ${series.nested.field}', { - series: { value: { name: 'Server1', nested: { field: 'nested' } } }, - }); - expect(target).toBe('Server1 nested'); - }); - - it('built in vars should support objects', () => { - _templateSrv.setGlobalVariable('__dashboard', { - value: { name: 'hello' }, - }); - const target = _templateSrv.replace('${__dashboard.name}'); - expect(target).toBe('hello'); - }); - - it('scoped vars should support objects with propert names with dot', () => { - const target = _templateSrv.replace('${series.name} ${series.nested["field.with.dot"]}', { - series: { value: { name: 'Server1', nested: { 'field.with.dot': 'nested' } } }, - }); - expect(target).toBe('Server1 nested'); - }); - - it('scoped vars should support arrays of objects', () => { - const target = _templateSrv.replace('${series.rows[0].name} ${series.rows[1].name}', { - series: { value: { rows: [{ name: 'first' }, { name: 'second' }] } }, - }); - expect(target).toBe('first second'); - }); - - it('should replace $test with scoped value', () => { - const target = _templateSrv.replace('this.$test.filters', { - test: { value: 'mupp', text: 'asd' }, - }); - expect(target).toBe('this.mupp.filters'); - }); - - it('should replace ${test} with scoped value', () => { - const target = _templateSrv.replace('this.${test}.filters', { - test: { value: 'mupp', text: 'asd' }, - }); - expect(target).toBe('this.mupp.filters'); - }); - - it('should replace ${test:glob} with scoped value', () => { - const target = _templateSrv.replace('this.${test:glob}.filters', { - test: { value: 'mupp', text: 'asd' }, - }); - expect(target).toBe('this.mupp.filters'); - }); - - it('should replace $test with scoped text', () => { - const target = _templateSrv.replaceWithText('this.$test.filters', { - test: { value: 'mupp', text: 'asd' }, - }); - expect(target).toBe('this.asd.filters'); - }); - - it('should replace ${test} with scoped text', () => { - const target = _templateSrv.replaceWithText('this.${test}.filters', { - test: { value: 'mupp', text: 'asd' }, - }); - expect(target).toBe('this.asd.filters'); - }); - - it('should replace ${test:glob} with scoped text', () => { - const target = _templateSrv.replaceWithText('this.${test:glob}.filters', { - test: { value: 'mupp', text: 'asd' }, - }); - expect(target).toBe('this.asd.filters'); - }); - }); - - describe('getAdhocFilters', () => { - beforeEach(() => { - initTemplateSrv([ - { - type: 'datasource', - name: 'ds', - current: { value: 'logstash', text: 'logstash' }, - }, - { type: 'adhoc', name: 'test', datasource: 'oogle', filters: [1] }, - { type: 'adhoc', name: 'test2', datasource: '$ds', filters: [2] }, - ]); - }); - - it('should return filters if datasourceName match', () => { - const filters = _templateSrv.getAdhocFilters('oogle'); - expect(filters).toMatchObject([1]); - }); - - it('should return empty array if datasourceName does not match', () => { - const filters = _templateSrv.getAdhocFilters('oogleasdasd'); - expect(filters).toMatchObject([]); - }); - - it('should return filters when datasourceName match via data source variable', () => { - const filters = _templateSrv.getAdhocFilters('logstash'); - expect(filters).toMatchObject([2]); - }); - }); - - describe('replace can pass multi / all format', () => { - beforeEach(() => { - initTemplateSrv([ - { - type: 'query', - name: 'test', - current: { value: ['value1', 'value2'] }, - }, - ]); - }); - - it('should replace $test with globbed value', () => { - const target = _templateSrv.replace('this.$test.filters', {}, 'glob'); - expect(target).toBe('this.{value1,value2}.filters'); - }); - - describe('when the globbed variable only has one value', () => { - beforeEach(() => { - initTemplateSrv([ - { - type: 'query', - name: 'test', - current: { value: ['value1'] }, - }, - ]); - }); - - it('should not glob the value', () => { - const target = _templateSrv.replace('this.$test.filters', {}, 'glob'); - expect(target).toBe('this.value1.filters'); - }); - }); - - it('should replace ${test} with globbed value', () => { - const target = _templateSrv.replace('this.${test}.filters', {}, 'glob'); - expect(target).toBe('this.{value1,value2}.filters'); - }); - - it('should replace ${test:glob} with globbed value', () => { - const target = _templateSrv.replace('this.${test:glob}.filters', {}); - expect(target).toBe('this.{value1,value2}.filters'); - }); - - it('should replace $test with piped value', () => { - const target = _templateSrv.replace('this=$test', {}, 'pipe'); - expect(target).toBe('this=value1|value2'); - }); - - it('should replace ${test} with piped value', () => { - const target = _templateSrv.replace('this=${test}', {}, 'pipe'); - expect(target).toBe('this=value1|value2'); - }); - - it('should replace ${test:pipe} with piped value', () => { - const target = _templateSrv.replace('this=${test:pipe}', {}); - expect(target).toBe('this=value1|value2'); - }); - - it('should replace ${test:pipe} with piped value and $test with globbed value', () => { - const target = _templateSrv.replace('${test:pipe},$test', {}, 'glob'); - expect(target).toBe('value1|value2,{value1,value2}'); - }); - }); - - describe('variable with all option', () => { - beforeEach(() => { - initTemplateSrv([ - { - type: 'query', - name: 'test', - current: { value: '$__all' }, - options: [{ value: '$__all' }, { value: 'value1' }, { value: 'value2' }], - }, - ]); - }); - - it('should replace $test with formatted all value', () => { - const target = _templateSrv.replace('this.$test.filters', {}, 'glob'); - expect(target).toBe('this.{value1,value2}.filters'); - }); - - it('should replace ${test} with formatted all value', () => { - const target = _templateSrv.replace('this.${test}.filters', {}, 'glob'); - expect(target).toBe('this.{value1,value2}.filters'); - }); - - it('should replace ${test:glob} with formatted all value', () => { - const target = _templateSrv.replace('this.${test:glob}.filters', {}); - expect(target).toBe('this.{value1,value2}.filters'); - }); - - it('should replace ${test:pipe} with piped value and $test with globbed value', () => { - const target = _templateSrv.replace('${test:pipe},$test', {}, 'glob'); - expect(target).toBe('value1|value2,{value1,value2}'); - }); - }); - - describe('variable with all option and custom value', () => { - beforeEach(() => { - initTemplateSrv([ - { - type: 'query', - name: 'test', - current: { value: '$__all' }, - allValue: '*', - options: [{ value: 'value1' }, { value: 'value2' }], - }, - ]); - }); - - it('should replace $test with formatted all value', () => { - const target = _templateSrv.replace('this.$test.filters', {}, 'glob'); - expect(target).toBe('this.*.filters'); - }); - - it('should replace ${test} with formatted all value', () => { - const target = _templateSrv.replace('this.${test}.filters', {}, 'glob'); - expect(target).toBe('this.*.filters'); - }); - - it('should replace ${test:glob} with formatted all value', () => { - const target = _templateSrv.replace('this.${test:glob}.filters', {}); - expect(target).toBe('this.*.filters'); - }); - - it('should not escape custom all value', () => { - const target = _templateSrv.replace('this.$test', {}, 'regex'); - expect(target).toBe('this.*'); - }); - }); - - describe('lucene format', () => { - it('should properly escape $test with lucene escape sequences', () => { - initTemplateSrv([{ type: 'query', name: 'test', current: { value: 'value/4' } }]); - const target = _templateSrv.replace('this:$test', {}, 'lucene'); - expect(target).toBe('this:value\\/4'); - }); - - it('should properly escape ${test} with lucene escape sequences', () => { - initTemplateSrv([{ type: 'query', name: 'test', current: { value: 'value/4' } }]); - const target = _templateSrv.replace('this:${test}', {}, 'lucene'); - expect(target).toBe('this:value\\/4'); - }); - - it('should properly escape ${test:lucene} with lucene escape sequences', () => { - initTemplateSrv([{ type: 'query', name: 'test', current: { value: 'value/4' } }]); - const target = _templateSrv.replace('this:${test:lucene}', {}); - expect(target).toBe('this:value\\/4'); - }); - }); - - describe('html format', () => { - it('should encode values html escape sequences', () => { - initTemplateSrv([{ type: 'query', name: 'test', current: { value: '' } }]); - const target = _templateSrv.replace('$test', {}, 'html'); - expect(target).toBe('<script>alert(asd)</script>'); - }); - }); - - describe('format variable to string values', () => { - it('single value should return value', () => { - const result = _templateSrv.formatValue('test'); - expect(result).toBe('test'); - }); - - it('multi value and glob format should render glob string', () => { - const result = _templateSrv.formatValue(['test', 'test2'], 'glob'); - expect(result).toBe('{test,test2}'); - }); - - it('multi value and lucene should render as lucene expr', () => { - const result = _templateSrv.formatValue(['test', 'test2'], 'lucene'); - expect(result).toBe('("test" OR "test2")'); - }); - - it('multi value and regex format should render regex string', () => { - const result = _templateSrv.formatValue(['test.', 'test2'], 'regex'); - expect(result).toBe('(test\\.|test2)'); - }); - - it('multi value and pipe should render pipe string', () => { - const result = _templateSrv.formatValue(['test', 'test2'], 'pipe'); - expect(result).toBe('test|test2'); - }); - - it('multi value and distributed should render distributed string', () => { - const result = _templateSrv.formatValue(['test', 'test2'], 'distributed', { - name: 'build', - }); - expect(result).toBe('test,build=test2'); - }); - - it('multi value and distributed should render when not string', () => { - const result = _templateSrv.formatValue(['test'], 'distributed', { - name: 'build', - }); - expect(result).toBe('test'); - }); - - it('multi value and csv format should render csv string', () => { - const result = _templateSrv.formatValue(['test', 'test2'], 'csv'); - expect(result).toBe('test,test2'); - }); - - it('multi value and percentencode format should render percent-encoded string', () => { - const result = _templateSrv.formatValue(['foo()bar BAZ', 'test2'], 'percentencode'); - expect(result).toBe('%7Bfoo%28%29bar%20BAZ%2Ctest2%7D'); - }); - - it('slash should be properly escaped in regex format', () => { - const result = _templateSrv.formatValue('Gi3/14', 'regex'); - expect(result).toBe('Gi3\\/14'); - }); - - it('single value and singlequote format should render string with value enclosed in single quotes', () => { - const result = _templateSrv.formatValue('test', 'singlequote'); - expect(result).toBe("'test'"); - }); - - it('multi value and singlequote format should render string with values enclosed in single quotes', () => { - const result = _templateSrv.formatValue(['test', "test'2"], 'singlequote'); - expect(result).toBe("'test','test\\'2'"); - }); - - it('single value and doublequote format should render string with value enclosed in double quotes', () => { - const result = _templateSrv.formatValue('test', 'doublequote'); - expect(result).toBe('"test"'); - }); - - it('multi value and doublequote format should render string with values enclosed in double quotes', () => { - const result = _templateSrv.formatValue(['test', 'test"2'], 'doublequote'); - expect(result).toBe('"test","test\\"2"'); - }); - - it('single value and sqlstring format should render string with value enclosed in single quotes', () => { - const result = _templateSrv.formatValue("test'value", 'sqlstring'); - expect(result).toBe(`'test''value'`); - }); - - it('multi value and sqlstring format should render string with values enclosed in single quotes', () => { - const result = _templateSrv.formatValue(['test', "test'value2"], 'sqlstring'); - expect(result).toBe(`'test','test''value2'`); - }); - }); - - describe('can check if variable exists', () => { - beforeEach(() => { - initTemplateSrv([{ type: 'query', name: 'test', current: { value: 'oogle' } }]); - }); - - it('should return true if $test exists', () => { - const result = _templateSrv.variableExists('$test'); - expect(result).toBe(true); - }); - - it('should return true if $test exists in string', () => { - const result = _templateSrv.variableExists('something $test something'); - expect(result).toBe(true); - }); - - it('should return true if [[test]] exists in string', () => { - const result = _templateSrv.variableExists('something [[test]] something'); - expect(result).toBe(true); - }); - - it('should return true if [[test:csv]] exists in string', () => { - const result = _templateSrv.variableExists('something [[test:csv]] something'); - expect(result).toBe(true); - }); - - it('should return true if ${test} exists in string', () => { - const result = _templateSrv.variableExists('something ${test} something'); - expect(result).toBe(true); - }); - - it('should return true if ${test:raw} exists in string', () => { - const result = _templateSrv.variableExists('something ${test:raw} something'); - expect(result).toBe(true); - }); - - it('should return null if there are no variables in string', () => { - const result = _templateSrv.variableExists('string without variables'); - expect(result).toBe(null); - }); - }); - - describe('can highlight variables in string', () => { - beforeEach(() => { - initTemplateSrv([{ type: 'query', name: 'test', current: { value: 'oogle' } }]); - }); - - it('should insert html', () => { - const result = _templateSrv.highlightVariablesAsHtml('$test'); - expect(result).toBe('$test'); - }); - - it('should insert html anywhere in string', () => { - const result = _templateSrv.highlightVariablesAsHtml('this $test ok'); - expect(result).toBe('this $test ok'); - }); - - it('should ignore if variables does not exist', () => { - const result = _templateSrv.highlightVariablesAsHtml('this $google ok'); - expect(result).toBe('this $google ok'); - }); - }); - - describe('updateIndex with simple value', () => { - beforeEach(() => { - initTemplateSrv([{ type: 'query', name: 'test', current: { value: 'muuuu' } }]); - }); - - it('should set current value and update template data', () => { - const target = _templateSrv.replace('this.[[test]].filters'); - expect(target).toBe('this.muuuu.filters'); - }); - }); - - describe('fillVariableValuesForUrl with multi value', () => { - beforeAll(() => { - variableAdapters.register(createQueryVariableAdapter()); - }); - beforeEach(() => { - initTemplateSrv([ - { - type: 'query', - name: 'test', - current: { value: ['val1', 'val2'] }, - getValueForUrl: function() { - return this.current.value; - }, - }, - ]); - }); - - it('should set multiple url params', () => { - const params: any = {}; - _templateSrv.fillVariableValuesForUrl(params); - expect(params['var-test']).toMatchObject(['val1', 'val2']); - }); - }); - - describe('fillVariableValuesForUrl skip url sync', () => { - beforeEach(() => { - initTemplateSrv([ - { - name: 'test', - skipUrlSync: true, - current: { value: 'value' }, - getValueForUrl: function() { - return this.current.value; - }, - }, - ]); - }); - - it('should not include template variable value in url', () => { - const params: any = {}; - _templateSrv.fillVariableValuesForUrl(params); - expect(params['var-test']).toBe(undefined); - }); - }); - - describe('fillVariableValuesForUrl with multi value with skip url sync', () => { - beforeEach(() => { - initTemplateSrv([ - { - type: 'query', - name: 'test', - skipUrlSync: true, - current: { value: ['val1', 'val2'] }, - getValueForUrl: function() { - return this.current.value; - }, - }, - ]); - }); - - it('should not include template variable value in url', () => { - const params: any = {}; - _templateSrv.fillVariableValuesForUrl(params); - expect(params['var-test']).toBe(undefined); - }); - }); - - describe('fillVariableValuesForUrl with multi value and scopedVars', () => { - beforeEach(() => { - initTemplateSrv([{ type: 'query', name: 'test', current: { value: ['val1', 'val2'] } }]); - }); - - it('should set scoped value as url params', () => { - const params: any = {}; - _templateSrv.fillVariableValuesForUrl(params, { - test: { value: 'val1' }, - }); - expect(params['var-test']).toBe('val1'); - }); - }); - - describe('fillVariableValuesForUrl with multi value, scopedVars and skip url sync', () => { - beforeEach(() => { - initTemplateSrv([{ type: 'query', name: 'test', current: { value: ['val1', 'val2'] } }]); - }); - - it('should not set scoped value as url params', () => { - const params: any = {}; - _templateSrv.fillVariableValuesForUrl(params, { - test: { name: 'test', value: 'val1', skipUrlSync: true }, - }); - expect(params['var-test']).toBe(undefined); - }); - }); - - describe('replaceWithText', () => { - beforeEach(() => { - initTemplateSrv([ - { - type: 'query', - name: 'server', - current: { value: '{asd,asd2}', text: 'All' }, - }, - { - type: 'interval', - name: 'period', - current: { value: '$__auto_interval_interval', text: 'auto' }, - }, - { - type: 'textbox', - name: 'empty_on_init', - current: { value: '', text: '' }, - }, - { - type: 'custom', - name: 'foo', - current: { value: 'constructor', text: 'constructor' }, - }, - ]); - _templateSrv.setGrafanaVariable('$__auto_interval_interval', '13m'); - _templateSrv.updateIndex(); - }); - - it('should replace with text except for grafanaVariables', () => { - const target = _templateSrv.replaceWithText('Server: $server, period: $period'); - expect(target).toBe('Server: All, period: 13m'); - }); - - it('should replace empty string-values with an empty string', () => { - const target = _templateSrv.replaceWithText('Hello $empty_on_init'); - expect(target).toBe('Hello '); - }); - - it('should not return a string representation of a constructor property', () => { - const target = _templateSrv.replaceWithText('$foo'); - expect(target).not.toBe('function Object() { [native code] }'); - expect(target).toBe('constructor'); - }); - }); - - describe('built in interval variables', () => { - beforeEach(() => { - initTemplateSrv([]); - }); - - it('should be possible to fetch value with getBuilInIntervalValue', () => { - const val = _templateSrv.getBuiltInIntervalValue(); - expect(val).toBe('1s'); - }); - - it('should replace $__interval_ms with interval milliseconds', () => { - const target = _templateSrv.replace('10 * $__interval_ms', { - __interval_ms: { text: '100', value: '100' }, - }); - expect(target).toBe('10 * 100'); - }); - }); -}); diff --git a/public/app/features/templating/specs/utils.test.ts b/public/app/features/templating/specs/utils.test.ts deleted file mode 100644 index cf129672ea7..00000000000 --- a/public/app/features/templating/specs/utils.test.ts +++ /dev/null @@ -1,261 +0,0 @@ -import { assignModelProperties } from '../types'; -import { ScopedVars } from '@grafana/data'; -import { containsSearchFilter, containsVariable, getSearchFilterScopedVar, SEARCH_FILTER_VARIABLE } from '../utils'; - -describe('containsVariable', () => { - describe('when checking if a string contains a variable', () => { - it('should find it with $const syntax', () => { - const contains = containsVariable('this.$test.filters', 'test'); - expect(contains).toBe(true); - }); - - it('should not find it if only part matches with $const syntax', () => { - const contains = containsVariable('this.$serverDomain.filters', 'server'); - expect(contains).toBe(false); - }); - - it('should find it if it ends with variable and passing multiple test strings', () => { - const contains = containsVariable('show field keys from $pgmetric', 'test string2', 'pgmetric'); - expect(contains).toBe(true); - }); - - it('should find it with [[var]] syntax', () => { - const contains = containsVariable('this.[[test]].filters', 'test'); - expect(contains).toBe(true); - }); - - it('should find it with [[var:option]] syntax', () => { - const contains = containsVariable('this.[[test:csv]].filters', 'test'); - expect(contains).toBe(true); - }); - - it('should find it when part of segment', () => { - const contains = containsVariable('metrics.$env.$group-*', 'group'); - expect(contains).toBe(true); - }); - - it('should find it its the only thing', () => { - const contains = containsVariable('$env', 'env'); - expect(contains).toBe(true); - }); - - it('should be able to pass in multiple test strings', () => { - const contains = containsVariable('asd', 'asd2.$env', 'env'); - expect(contains).toBe(true); - }); - - it('should find it with ${var} syntax', () => { - const contains = containsVariable('this.${test}.filters', 'test'); - expect(contains).toBe(true); - }); - - it('should find it with ${var:option} syntax', () => { - const contains = containsVariable('this.${test:csv}.filters', 'test'); - expect(contains).toBe(true); - }); - }); -}); - -describe('assignModelProperties', () => { - it('only set properties defined in defaults', () => { - const target: any = { test: 'asd' }; - assignModelProperties(target, { propA: 1, propB: 2 }, { propB: 0 }); - expect(target.propB).toBe(2); - expect(target.test).toBe('asd'); - }); - - it('use default value if not found on source', () => { - const target: any = { test: 'asd' }; - assignModelProperties(target, { propA: 1, propB: 2 }, { propC: 10 }); - expect(target.propC).toBe(10); - }); -}); - -describe('containsSearchFilter', () => { - describe('when called without query', () => { - it('then it should return false', () => { - const result = containsSearchFilter(null); - - expect(result).toBe(false); - }); - }); - - describe('when called with an object', () => { - it('then it should return false', () => { - const result = containsSearchFilter({}); - - expect(result).toBe(false); - }); - }); - - describe(`when called with a query without ${SEARCH_FILTER_VARIABLE}`, () => { - it('then it should return false', () => { - const result = containsSearchFilter('$app.*'); - - expect(result).toBe(false); - }); - }); - - describe(`when called with a query with $${SEARCH_FILTER_VARIABLE}`, () => { - it('then it should return true', () => { - const result = containsSearchFilter(`$app.$${SEARCH_FILTER_VARIABLE}`); - - expect(result).toBe(true); - }); - }); - - describe(`when called with a query with [[${SEARCH_FILTER_VARIABLE}]]`, () => { - it('then it should return true', () => { - const result = containsSearchFilter(`$app.[[${SEARCH_FILTER_VARIABLE}]]`); - - expect(result).toBe(true); - }); - }); - - describe(`when called with a query with \$\{${SEARCH_FILTER_VARIABLE}:regex\}`, () => { - it('then it should return true', () => { - const result = containsSearchFilter(`$app.\$\{${SEARCH_FILTER_VARIABLE}:regex\}`); - - expect(result).toBe(true); - }); - }); -}); - -interface GetSearchFilterScopedVarScenario { - query: string; - wildcardChar: string; - options: { searchFilter?: string }; - expected: ScopedVars; -} - -const scenarios: GetSearchFilterScopedVarScenario[] = [ - // testing the $__searchFilter notation - { - query: 'abc.$__searchFilter', - wildcardChar: '', - options: { searchFilter: '' }, - expected: { __searchFilter: { value: '', text: '' } }, - }, - { - query: 'abc.$__searchFilter', - wildcardChar: '*', - options: { searchFilter: '' }, - expected: { __searchFilter: { value: '*', text: '' } }, - }, - { - query: 'abc.$__searchFilter', - wildcardChar: '', - options: { searchFilter: 'a' }, - expected: { __searchFilter: { value: 'a', text: '' } }, - }, - { - query: 'abc.$__searchFilter', - wildcardChar: '*', - options: { searchFilter: 'a' }, - expected: { __searchFilter: { value: 'a*', text: '' } }, - }, - // testing the [[__searchFilter]] notation - { - query: 'abc.[[__searchFilter]]', - wildcardChar: '', - options: { searchFilter: '' }, - expected: { __searchFilter: { value: '', text: '' } }, - }, - { - query: 'abc.[[__searchFilter]]', - wildcardChar: '*', - options: { searchFilter: '' }, - expected: { __searchFilter: { value: '*', text: '' } }, - }, - { - query: 'abc.[[__searchFilter]]', - wildcardChar: '', - options: { searchFilter: 'a' }, - expected: { __searchFilter: { value: 'a', text: '' } }, - }, - { - query: 'abc.[[__searchFilter]]', - wildcardChar: '*', - options: { searchFilter: 'a' }, - expected: { __searchFilter: { value: 'a*', text: '' } }, - }, - // testing the ${__searchFilter:fmt} notation - { - query: 'abc.${__searchFilter:regex}', - wildcardChar: '', - options: { searchFilter: '' }, - expected: { __searchFilter: { value: '', text: '' } }, - }, - { - query: 'abc.${__searchFilter:regex}', - wildcardChar: '*', - options: { searchFilter: '' }, - expected: { __searchFilter: { value: '*', text: '' } }, - }, - { - query: 'abc.${__searchFilter:regex}', - wildcardChar: '', - options: { searchFilter: 'a' }, - expected: { __searchFilter: { value: 'a', text: '' } }, - }, - { - query: 'abc.${__searchFilter:regex}', - wildcardChar: '*', - options: { searchFilter: 'a' }, - expected: { __searchFilter: { value: 'a*', text: '' } }, - }, - // testing the no options - { - query: 'abc.$__searchFilter', - wildcardChar: '', - options: null as any, - expected: { __searchFilter: { value: '', text: '' } }, - }, - { - query: 'abc.$__searchFilter', - wildcardChar: '*', - options: null as any, - expected: { __searchFilter: { value: '*', text: '' } }, - }, - // testing the no search filter at all - { - query: 'abc.$def', - wildcardChar: '', - options: { searchFilter: '' }, - expected: {}, - }, - { - query: 'abc.$def', - wildcardChar: '*', - options: { searchFilter: '' }, - expected: {}, - }, - { - query: 'abc.$def', - wildcardChar: '', - options: { searchFilter: 'a' }, - expected: {}, - }, - { - query: 'abc.$def', - wildcardChar: '*', - options: { searchFilter: 'a' }, - expected: {}, - }, -]; - -scenarios.map(scenario => { - describe('getSearchFilterScopedVar', () => { - describe(`when called with query:'${scenario.query}'`, () => { - describe(`and wildcardChar:'${scenario.wildcardChar}'`, () => { - describe(`and options:'${JSON.stringify(scenario.options, null, 0)}'`, () => { - it(`then the result should be ${JSON.stringify(scenario.expected, null, 0)}`, () => { - const { expected, ...args } = scenario; - - expect(getSearchFilterScopedVar(args)).toEqual(expected); - }); - }); - }); - }); - }); -}); diff --git a/public/app/features/templating/specs/variable_srv.test.ts b/public/app/features/templating/specs/variable_srv.test.ts deleted file mode 100644 index c8fdbc54151..00000000000 --- a/public/app/features/templating/specs/variable_srv.test.ts +++ /dev/null @@ -1,663 +0,0 @@ -import '../all'; -import { VariableSrv } from '../variable_srv'; -import { DashboardModel } from '../../dashboard/state/DashboardModel'; -// @ts-ignore -import $q from 'q'; -import { dateTime } from '@grafana/data'; -import { CustomVariable } from '../custom_variable'; - -jest.mock('app/core/core', () => ({ - contextSrv: { - user: { orgId: 1, orgName: 'TestOrg' }, - }, -})); - -describe('VariableSrv', function(this: any) { - const ctx = { - datasourceSrv: {}, - timeSrv: { - timeRange: () => { - return { from: '2018-01-29', to: '2019-01-29' }; - }, - }, - $rootScope: { - $on: () => {}, - }, - $injector: { - instantiate: (ctr: any, obj: { model: any }) => new ctr(obj.model), - }, - templateSrv: { - setGrafanaVariable: jest.fn(), - init: (vars: any) => { - this.variables = vars; - }, - updateIndex: () => {}, - setGlobalVariable: (name: string, variable: any) => {}, - replace: (str: any) => - str.replace(this.regex, (match: string) => { - return match; - }), - }, - $location: { - search: () => {}, - }, - } as any; - - function describeUpdateVariable(desc: string, fn: Function) { - describe(desc, () => { - const scenario: any = {}; - scenario.setup = (setupFn: Function) => { - scenario.setupFn = setupFn; - }; - - beforeEach(async () => { - scenario.setupFn(); - - const ds: any = {}; - ds.metricFindQuery = () => Promise.resolve(scenario.queryResult); - - ctx.variableSrv = new VariableSrv($q, ctx.$location, ctx.$injector, ctx.templateSrv, ctx.timeSrv); - - ctx.variableSrv.timeSrv = ctx.timeSrv; - ctx.datasourceSrv = { - get: () => Promise.resolve(ds), - getMetricSources: () => scenario.metricSources, - }; - - ctx.$injector.instantiate = (ctr: any, model: any) => { - return getVarMockConstructor(ctr, model, ctx); - }; - - ctx.variableSrv.init( - new DashboardModel({ - templating: { list: [] }, - updateSubmenuVisibility: () => {}, - }) - ); - - scenario.variable = ctx.variableSrv.createVariableFromModel(scenario.variableModel); - ctx.variableSrv.addVariable(scenario.variable); - - await ctx.variableSrv.updateOptions(scenario.variable); - }); - - fn(scenario); - }); - } - - describeUpdateVariable('interval variable without auto', (scenario: any) => { - scenario.setup(() => { - scenario.variableModel = { - type: 'interval', - query: '1s,2h,5h,1d', - name: 'test', - }; - }); - - it('should update options array', () => { - expect(scenario.variable.options.length).toBe(4); - expect(scenario.variable.options[0].text).toBe('1s'); - expect(scenario.variable.options[0].value).toBe('1s'); - }); - }); - - // - // Interval variable update - // - describeUpdateVariable('interval variable with auto', (scenario: any) => { - scenario.setup(() => { - scenario.variableModel = { - type: 'interval', - query: '1s,2h,5h,1d', - name: 'test', - auto: true, - auto_count: 10, - }; - - const range = { - from: dateTime(new Date()) - .subtract(7, 'days') - .toDate(), - to: new Date(), - }; - - ctx.timeSrv.timeRange = () => range; - // ctx.templateSrv.setGrafanaVariable = jest.fn(); - }); - - it('should update options array', () => { - expect(scenario.variable.options.length).toBe(5); - expect(scenario.variable.options[0].text).toBe('auto'); - expect(scenario.variable.options[0].value).toBe('$__auto_interval_test'); - }); - - it('should set $__auto_interval_test', () => { - const call = ctx.templateSrv.setGrafanaVariable.mock.calls[0]; - expect(call[0]).toBe('$__auto_interval_test'); - expect(call[1]).toBe('12h'); - }); - - // updateAutoValue() gets called twice: once directly once via VariableSrv.validateVariableSelectionState() - // So use lastCall instead of a specific call number - it('should set $__auto_interval', () => { - const call = ctx.templateSrv.setGrafanaVariable.mock.calls.pop(); - expect(call[0]).toBe('$__auto_interval'); - expect(call[1]).toBe('12h'); - }); - }); - - // - // Query variable update - // - describeUpdateVariable('query variable with empty current object and refresh', (scenario: any) => { - scenario.setup(() => { - scenario.variableModel = { - type: 'query', - query: '', - name: 'test', - current: {}, - }; - scenario.queryResult = [{ text: 'backend1' }, { text: 'backend2' }]; - }); - - it('should set current value to first option', () => { - expect(scenario.variable.options.length).toBe(2); - expect(scenario.variable.current.value).toBe('backend1'); - }); - }); - - describeUpdateVariable( - 'query variable with multi select and new options does not contain some selected values', - (scenario: any) => { - scenario.setup(() => { - scenario.variableModel = { - type: 'query', - query: '', - name: 'test', - current: { - value: ['val1', 'val2', 'val3'], - text: 'val1 + val2 + val3', - }, - }; - scenario.queryResult = [{ text: 'val2' }, { text: 'val3' }]; - }); - - it('should update current value', () => { - expect(scenario.variable.current.value).toEqual(['val2', 'val3']); - expect(scenario.variable.current.text).toEqual('val2 + val3'); - }); - } - ); - - describeUpdateVariable( - 'query variable with multi select and new options does not contain any selected values', - (scenario: any) => { - scenario.setup(() => { - scenario.variableModel = { - type: 'query', - query: '', - name: 'test', - current: { - value: ['val1', 'val2', 'val3'], - text: 'val1 + val2 + val3', - }, - }; - scenario.queryResult = [{ text: 'val5' }, { text: 'val6' }]; - }); - - it('should update current value with first one', () => { - expect(scenario.variable.current.value).toEqual('val5'); - expect(scenario.variable.current.text).toEqual('val5'); - }); - } - ); - - describeUpdateVariable('query variable with multi select and $__all selected', (scenario: any) => { - scenario.setup(() => { - scenario.variableModel = { - type: 'query', - query: '', - name: 'test', - includeAll: true, - current: { - value: ['$__all'], - text: 'All', - }, - }; - scenario.queryResult = [{ text: 'val5' }, { text: 'val6' }]; - }); - - it('should keep current All value', () => { - expect(scenario.variable.current.value).toEqual(['$__all']); - expect(scenario.variable.current.text).toEqual('All'); - }); - }); - - describeUpdateVariable('query variable with numeric results', (scenario: any) => { - scenario.setup(() => { - scenario.variableModel = { - type: 'query', - query: '', - name: 'test', - current: {}, - }; - scenario.queryResult = [{ text: 12, value: 12 }]; - }); - - it('should set current value to first option', () => { - expect(scenario.variable.current.value).toBe('12'); - expect(scenario.variable.options[0].value).toBe('12'); - expect(scenario.variable.options[0].text).toBe('12'); - }); - }); - - describeUpdateVariable('basic query variable', (scenario: any) => { - scenario.setup(() => { - scenario.variableModel = { type: 'query', query: 'apps.*', name: 'test' }; - scenario.queryResult = [{ text: 'backend1' }, { text: 'backend2' }]; - }); - - it('should update options array', () => { - expect(scenario.variable.options.length).toBe(2); - expect(scenario.variable.options[0].text).toBe('backend1'); - expect(scenario.variable.options[0].value).toBe('backend1'); - expect(scenario.variable.options[1].value).toBe('backend2'); - }); - - it('should select first option as value', () => { - expect(scenario.variable.current.value).toBe('backend1'); - }); - }); - - describeUpdateVariable('and existing value still exists in options', (scenario: any) => { - scenario.setup(() => { - scenario.variableModel = { type: 'query', query: 'apps.*', name: 'test' }; - scenario.variableModel.current = { value: 'backend2', text: 'backend2' }; - scenario.queryResult = [{ text: 'backend1' }, { text: 'backend2' }]; - }); - - it('should keep variable value', () => { - expect(scenario.variable.current.text).toBe('backend2'); - }); - }); - - describeUpdateVariable('and regex pattern exists', (scenario: any) => { - scenario.setup(() => { - scenario.variableModel = { type: 'query', query: 'apps.*', name: 'test' }; - scenario.variableModel.regex = '/apps.*(backend_[0-9]+)/'; - scenario.queryResult = [ - { text: 'apps.backend.backend_01.counters.req' }, - { text: 'apps.backend.backend_02.counters.req' }, - ]; - }); - - it('should extract and use match group', () => { - expect(scenario.variable.options[0].value).toBe('backend_01'); - }); - }); - - describeUpdateVariable('and regex pattern exists and no match', (scenario: any) => { - scenario.setup(() => { - scenario.variableModel = { type: 'query', query: 'apps.*', name: 'test' }; - scenario.variableModel.regex = '/apps.*(backendasd[0-9]+)/'; - scenario.queryResult = [ - { text: 'apps.backend.backend_01.counters.req' }, - { text: 'apps.backend.backend_02.counters.req' }, - ]; - }); - - it('should not add non matching items, None option should be added instead', () => { - expect(scenario.variable.options.length).toBe(1); - expect(scenario.variable.options[0].isNone).toBe(true); - }); - }); - - describeUpdateVariable('regex pattern without slashes', (scenario: any) => { - scenario.setup(() => { - scenario.variableModel = { type: 'query', query: 'apps.*', name: 'test' }; - scenario.variableModel.regex = 'backend_01'; - scenario.queryResult = [ - { text: 'apps.backend.backend_01.counters.req' }, - { text: 'apps.backend.backend_02.counters.req' }, - ]; - }); - - it('should return matches options', () => { - expect(scenario.variable.options.length).toBe(1); - }); - }); - - describeUpdateVariable('regex pattern remove duplicates', (scenario: any) => { - scenario.setup(() => { - scenario.variableModel = { type: 'query', query: 'apps.*', name: 'test' }; - scenario.variableModel.regex = '/backend_01/'; - scenario.queryResult = [ - { text: 'apps.backend.backend_01.counters.req' }, - { text: 'apps.backend.backend_01.counters.req' }, - ]; - }); - - it('should return matches options', () => { - expect(scenario.variable.options.length).toBe(1); - }); - }); - - describeUpdateVariable('with include All', (scenario: any) => { - scenario.setup(() => { - scenario.variableModel = { - type: 'query', - query: 'apps.*', - name: 'test', - includeAll: true, - }; - scenario.queryResult = [{ text: 'backend1' }, { text: 'backend2' }, { text: 'backend3' }]; - }); - - it('should add All option', () => { - expect(scenario.variable.options[0].text).toBe('All'); - expect(scenario.variable.options[0].value).toBe('$__all'); - }); - }); - - describeUpdateVariable('with include all and custom value', (scenario: any) => { - scenario.setup(() => { - scenario.variableModel = { - type: 'query', - query: 'apps.*', - name: 'test', - includeAll: true, - allValue: '*', - }; - scenario.queryResult = [{ text: 'backend1' }, { text: 'backend2' }, { text: 'backend3' }]; - }); - - it('should add All option with custom value', () => { - expect(scenario.variable.options[0].value).toBe('$__all'); - }); - }); - - describeUpdateVariable('without sort', (scenario: any) => { - scenario.setup(() => { - scenario.variableModel = { - type: 'query', - query: 'apps.*', - name: 'test', - sort: 0, - }; - scenario.queryResult = [{ text: 'bbb2' }, { text: 'aaa10' }, { text: 'ccc3' }]; - }); - - it('should return options without sort', () => { - expect(scenario.variable.options[0].text).toBe('bbb2'); - expect(scenario.variable.options[1].text).toBe('aaa10'); - expect(scenario.variable.options[2].text).toBe('ccc3'); - }); - }); - - describeUpdateVariable('with alphabetical sort (asc)', (scenario: any) => { - scenario.setup(() => { - scenario.variableModel = { - type: 'query', - query: 'apps.*', - name: 'test', - sort: 1, - }; - scenario.queryResult = [{ text: 'bbb2' }, { text: 'aaa10' }, { text: 'ccc3' }]; - }); - - it('should return options with alphabetical sort', () => { - expect(scenario.variable.options[0].text).toBe('aaa10'); - expect(scenario.variable.options[1].text).toBe('bbb2'); - expect(scenario.variable.options[2].text).toBe('ccc3'); - }); - }); - - describeUpdateVariable('with alphabetical sort (desc)', (scenario: any) => { - scenario.setup(() => { - scenario.variableModel = { - type: 'query', - query: 'apps.*', - name: 'test', - sort: 2, - }; - scenario.queryResult = [{ text: 'bbb2' }, { text: 'aaa10' }, { text: 'ccc3' }]; - }); - - it('should return options with alphabetical sort', () => { - expect(scenario.variable.options[0].text).toBe('ccc3'); - expect(scenario.variable.options[1].text).toBe('bbb2'); - expect(scenario.variable.options[2].text).toBe('aaa10'); - }); - }); - - describeUpdateVariable('with numerical sort (asc)', (scenario: any) => { - scenario.setup(() => { - scenario.variableModel = { - type: 'query', - query: 'apps.*', - name: 'test', - sort: 3, - }; - scenario.queryResult = [{ text: 'bbb2' }, { text: 'aaa10' }, { text: 'ccc3' }]; - }); - - it('should return options with numerical sort', () => { - expect(scenario.variable.options[0].text).toBe('bbb2'); - expect(scenario.variable.options[1].text).toBe('ccc3'); - expect(scenario.variable.options[2].text).toBe('aaa10'); - }); - }); - - describeUpdateVariable('with numerical sort (desc)', (scenario: any) => { - scenario.setup(() => { - scenario.variableModel = { - type: 'query', - query: 'apps.*', - name: 'test', - sort: 4, - }; - scenario.queryResult = [{ text: 'bbb2' }, { text: 'aaa10' }, { text: 'ccc3' }]; - }); - - it('should return options with numerical sort', () => { - expect(scenario.variable.options[0].text).toBe('aaa10'); - expect(scenario.variable.options[1].text).toBe('ccc3'); - expect(scenario.variable.options[2].text).toBe('bbb2'); - }); - }); - - // - // datasource variable update - // - describeUpdateVariable('datasource variable with regex filter', (scenario: any) => { - scenario.setup(() => { - scenario.variableModel = { - type: 'datasource', - query: 'graphite', - name: 'test', - current: { value: 'backend4_pee', text: 'backend4_pee' }, - regex: '/pee$/', - }; - scenario.metricSources = [ - { name: 'backend1', meta: { id: 'influx' } }, - { name: 'backend2_pee', meta: { id: 'graphite' } }, - { name: 'backend3', meta: { id: 'graphite' } }, - { name: 'backend4_pee', meta: { id: 'graphite' } }, - ]; - }); - - it('should set only contain graphite ds and filtered using regex', () => { - expect(scenario.variable.options.length).toBe(2); - expect(scenario.variable.options[0].value).toBe('backend2_pee'); - expect(scenario.variable.options[1].value).toBe('backend4_pee'); - }); - - it('should keep current value if available', () => { - expect(scenario.variable.current.value).toBe('backend4_pee'); - }); - }); - - // - // Custom variable update - // - describeUpdateVariable('update custom variable', (scenario: any) => { - scenario.setup(() => { - scenario.variableModel = { - type: 'custom', - query: 'hej, hop, asd, escaped\\,var', - name: 'test', - }; - }); - - it('should update options array', () => { - expect(scenario.variable.options.length).toBe(4); - expect(scenario.variable.options[0].text).toBe('hej'); - expect(scenario.variable.options[1].value).toBe('hop'); - expect(scenario.variable.options[2].value).toBe('asd'); - expect(scenario.variable.options[3].value).toBe('escaped,var'); - }); - }); - - describe('multiple interval variables with auto', () => { - let variable1: any, variable2: any; - - beforeEach(() => { - const range = { - from: dateTime(new Date()) - .subtract(7, 'days') - .toDate(), - to: new Date(), - }; - ctx.timeSrv.timeRange = () => range; - ctx.templateSrv.setGrafanaVariable = jest.fn(); - - const variableModel1 = { - type: 'interval', - query: '1s,2h,5h,1d', - name: 'variable1', - auto: true, - auto_count: 10, - }; - variable1 = ctx.variableSrv.createVariableFromModel(variableModel1); - ctx.variableSrv.addVariable(variable1); - - const variableModel2 = { - type: 'interval', - query: '1s,2h,5h', - name: 'variable2', - auto: true, - auto_count: 1000, - }; - variable2 = ctx.variableSrv.createVariableFromModel(variableModel2); - ctx.variableSrv.addVariable(variable2); - - ctx.variableSrv.updateOptions(variable1); - ctx.variableSrv.updateOptions(variable2); - // ctx.$rootScope.$digest(); - }); - - it('should update options array', () => { - expect(variable1.options.length).toBe(5); - expect(variable1.options[0].text).toBe('auto'); - expect(variable1.options[0].value).toBe('$__auto_interval_variable1'); - expect(variable2.options.length).toBe(4); - expect(variable2.options[0].text).toBe('auto'); - expect(variable2.options[0].value).toBe('$__auto_interval_variable2'); - }); - - it('should correctly set $__auto_interval_variableX', () => { - let variable1Set, - variable2Set, - legacySet, - unknownSet = false; - // updateAutoValue() gets called repeatedly: once directly once via VariableSrv.validateVariableSelectionState() - // So check that all calls are valid rather than expect a specific number and/or ordering of calls - for (let i = 0; i < ctx.templateSrv.setGrafanaVariable.mock.calls.length; i++) { - const call = ctx.templateSrv.setGrafanaVariable.mock.calls[i]; - switch (call[0]) { - case '$__auto_interval_variable1': - expect(call[1]).toBe('12h'); - variable1Set = true; - break; - case '$__auto_interval_variable2': - expect(call[1]).toBe('10m'); - variable2Set = true; - break; - case '$__auto_interval': - expect(call[1]).toEqual(expect.stringMatching(/^(12h|10m)$/)); - legacySet = true; - break; - default: - unknownSet = true; - break; - } - } - expect(variable1Set).toEqual(true); - expect(variable2Set).toEqual(true); - expect(legacySet).toEqual(true); - expect(unknownSet).toEqual(false); - }); - }); - - describe('setOptionFromUrl', () => { - it('sets single value as string if not multi choice', async () => { - const [setValueMock, setFromUrl] = setupSetFromUrlTest(ctx); - await setFromUrl('one'); - expect(setValueMock).toHaveBeenCalledWith({ text: 'one', value: 'one' }); - }); - - it('sets single value as array if multi choice', async () => { - const [setValueMock, setFromUrl] = setupSetFromUrlTest(ctx, { multi: true }); - await setFromUrl('one'); - expect(setValueMock).toHaveBeenCalledWith({ text: ['one'], value: ['one'] }); - }); - - it('sets both text and value as array if multiple values in url', async () => { - const [setValueMock, setFromUrl] = setupSetFromUrlTest(ctx, { multi: true }); - await setFromUrl(['one', 'two']); - expect(setValueMock).toHaveBeenCalledWith({ text: ['one', 'two'], value: ['one', 'two'] }); - }); - - it('sets text and value even if it does not match any option', async () => { - const [setValueMock, setFromUrl] = setupSetFromUrlTest(ctx); - await setFromUrl('none'); - expect(setValueMock).toHaveBeenCalledWith({ text: 'none', value: 'none' }); - }); - - it('sets text and value even if it does not match any option and it is array', async () => { - const [setValueMock, setFromUrl] = setupSetFromUrlTest(ctx); - await setFromUrl(['none', 'none2']); - expect(setValueMock).toHaveBeenCalledWith({ text: ['none', 'none2'], value: ['none', 'none2'] }); - }); - }); -}); - -function setupSetFromUrlTest(ctx: any, model = {}) { - const variableSrv = new VariableSrv($q, ctx.$location, ctx.$injector, ctx.templateSrv, ctx.timeSrv); - const finalModel = { - type: 'custom', - options: ['one', 'two', 'three'].map(v => ({ text: v, value: v })), - name: 'test', - ...model, - }; - const variable = new CustomVariable(finalModel, variableSrv); - // We are mocking the setValue here instead of just checking the final variable.current value because there is lots - // of stuff going when the setValue is called that is hard to mock out. - variable.setValue = jest.fn(); - return [variable.setValue, (val: any) => variableSrv.setOptionFromUrl(variable, val)]; -} - -function getVarMockConstructor(variable: any, model: any, ctx: any) { - switch (model.model.type) { - case 'datasource': - return new variable(model.model, ctx.datasourceSrv, ctx.variableSrv, ctx.templateSrv); - case 'query': - return new variable(model.model, ctx.datasourceSrv, ctx.templateSrv, ctx.variableSrv); - case 'interval': - return new variable(model.model, ctx.timeSrv, ctx.templateSrv, ctx.variableSrv); - case 'custom': - return new variable(model.model, ctx.variableSrv); - default: - return new variable(model.model); - } -} diff --git a/public/app/features/templating/specs/variable_srv_init.test.ts b/public/app/features/templating/specs/variable_srv_init.test.ts deleted file mode 100644 index 027d9696c75..00000000000 --- a/public/app/features/templating/specs/variable_srv_init.test.ts +++ /dev/null @@ -1,275 +0,0 @@ -import '../all'; - -import _ from 'lodash'; -import { VariableSrv } from '../variable_srv'; -import { DashboardModel } from '../../dashboard/state/DashboardModel'; -// @ts-ignore -import $q from 'q'; - -jest.mock('app/core/core', () => ({ - contextSrv: { - user: { orgId: 1, orgName: 'TestOrg' }, - }, -})); - -describe('VariableSrv init', function(this: any) { - const templateSrv = { - init: (vars: any) => { - this.variables = vars; - }, - variableInitialized: () => {}, - updateIndex: () => {}, - setGlobalVariable: (name: string, variable: any) => {}, - replace: (str: string) => - str.replace(this.regex, match => { - return match; - }), - }; - - const timeSrv = { - timeRange: () => { - return { from: '2018-01-29', to: '2019-01-29' }; - }, - }; - - const $injector = {} as any; - let ctx = {} as any; - - function describeInitScenario(desc: string, fn: Function) { - describe(desc, () => { - const scenario: any = { - urlParams: {}, - setup: (setupFn: Function) => { - scenario.setupFn = setupFn; - }, - }; - - beforeEach(async () => { - scenario.setupFn(); - ctx = { - datasource: { - metricFindQuery: jest.fn(() => Promise.resolve(scenario.queryResult)), - }, - datasourceSrv: { - get: () => Promise.resolve(ctx.datasource), - getMetricSources: () => scenario.metricSources, - }, - templateSrv, - }; - - // @ts-ignore - ctx.variableSrv = new VariableSrv($q, {}, $injector, templateSrv, timeSrv); - - $injector.instantiate = (variable: any, model: any) => { - return getVarMockConstructor(variable, model, ctx); - }; - - ctx.variableSrv.datasource = ctx.datasource; - ctx.variableSrv.datasourceSrv = ctx.datasourceSrv; - - ctx.variableSrv.$location.search = () => scenario.urlParams; - ctx.variableSrv.dashboard = new DashboardModel({ - templating: { list: scenario.variables }, - }); - - await ctx.variableSrv.init(ctx.variableSrv.dashboard); - - scenario.variables = ctx.variableSrv.variables; - }); - - fn(scenario); - }); - } - - ['interval', 'custom', 'datasource'].forEach(type => { - describeInitScenario('when setting ' + type + ' variable via url', (scenario: any) => { - scenario.setup(() => { - scenario.variables = [ - { - name: 'apps', - type: type, - current: { text: 'Test', value: 'test' }, - options: [{ text: 'Test', value: 'test' }], - }, - ]; - scenario.urlParams['var-apps'] = 'new'; - scenario.metricSources = []; - }); - - it('should update current value', () => { - expect(scenario.variables[0].current.value).toBe('new'); - expect(scenario.variables[0].current.text).toBe('new'); - }); - }); - }); - - // this test will moved to redux tests instead - describe('given dependent variables', () => { - const variableList = [ - { - name: 'app', - type: 'query', - query: '', - current: { text: 'app1', value: 'app1' }, - options: [{ text: 'app1', value: 'app1' }], - }, - { - name: 'server', - type: 'query', - refresh: 1, - query: '$app.*', - current: { text: 'server1', value: 'server1' }, - options: [{ text: 'server1', value: 'server1' }], - }, - ]; - - describeInitScenario('when setting parent const from url', (scenario: any) => { - scenario.setup(() => { - scenario.variables = _.cloneDeep(variableList); - scenario.urlParams['var-app'] = 'google'; - scenario.queryResult = [{ text: 'google-server1' }, { text: 'google-server2' }]; - }); - - it('should update child variable', () => { - expect(scenario.variables[1].options.length).toBe(2); - expect(scenario.variables[1].current.text).toBe('google-server1'); - }); - - it('should only update it once', () => { - expect(ctx.variableSrv.datasource.metricFindQuery).toHaveBeenCalledTimes(1); - }); - }); - }); - - describeInitScenario('when datasource variable is initialized', (scenario: any) => { - scenario.setup(() => { - scenario.variables = [ - { - type: 'datasource', - query: 'graphite', - name: 'test', - current: { value: 'backend4_pee', text: 'backend4_pee' }, - regex: '/pee$/', - }, - ]; - scenario.metricSources = [ - { name: 'backend1', meta: { id: 'influx' } }, - { name: 'backend2_pee', meta: { id: 'graphite' } }, - { name: 'backend3', meta: { id: 'graphite' } }, - { name: 'backend4_pee', meta: { id: 'graphite' } }, - ]; - }); - - it('should update current value', () => { - const variable = ctx.variableSrv.variables[0]; - expect(variable.options.length).toBe(2); - }); - }); - - describeInitScenario('when template variable is present in url multiple times', (scenario: any) => { - scenario.setup(() => { - scenario.variables = [ - { - name: 'apps', - type: 'custom', - multi: true, - current: { text: 'Val1', value: 'val1' }, - options: [ - { text: 'Val1', value: 'val1' }, - { text: 'Val2', value: 'val2' }, - { text: 'Val3', value: 'val3', selected: true }, - ], - }, - ]; - scenario.urlParams['var-apps'] = ['val2', 'val1']; - }); - - it('should update current value', () => { - const variable = ctx.variableSrv.variables[0]; - expect(variable.current.value.length).toBe(2); - expect(variable.current.value[0]).toBe('val2'); - expect(variable.current.value[1]).toBe('val1'); - expect(variable.current.text).toBe('Val2 + Val1'); - expect(variable.options[0].selected).toBe(true); - expect(variable.options[1].selected).toBe(true); - }); - - it('should set options that are not in value to selected false', () => { - const variable = ctx.variableSrv.variables[0]; - expect(variable.options[2].selected).toBe(false); - }); - }); - - describeInitScenario( - 'when template variable is present in url multiple times and variables have no text', - (scenario: any) => { - scenario.setup(() => { - scenario.variables = [ - { - name: 'apps', - type: 'custom', - multi: true, - }, - ]; - scenario.urlParams['var-apps'] = ['val1', 'val2']; - }); - - it('should display concatenated values in text', () => { - const variable = ctx.variableSrv.variables[0]; - expect(variable.current.value.length).toBe(2); - expect(variable.current.value[0]).toBe('val1'); - expect(variable.current.value[1]).toBe('val2'); - expect(variable.current.text).toBe('val1 + val2'); - }); - } - ); - - describeInitScenario('when template variable is present in url multiple times using key/values', (scenario: any) => { - scenario.setup(() => { - scenario.variables = [ - { - name: 'apps', - type: 'custom', - multi: true, - current: { text: 'Val1', value: 'val1' }, - options: [ - { text: 'Val1', value: 'val1' }, - { text: 'Val2', value: 'val2' }, - { text: 'Val3', value: 'val3', selected: true }, - ], - }, - ]; - scenario.urlParams['var-apps'] = ['val2', 'val1']; - }); - - it('should update current value', () => { - const variable = ctx.variableSrv.variables[0]; - expect(variable.current.value.length).toBe(2); - expect(variable.current.value[0]).toBe('val2'); - expect(variable.current.value[1]).toBe('val1'); - expect(variable.current.text).toBe('Val2 + Val1'); - expect(variable.options[0].selected).toBe(true); - expect(variable.options[1].selected).toBe(true); - }); - - it('should set options that are not in value to selected false', () => { - const variable = ctx.variableSrv.variables[0]; - expect(variable.options[2].selected).toBe(false); - }); - }); -}); - -function getVarMockConstructor(variable: any, model: any, ctx: any) { - switch (model.model.type) { - case 'datasource': - return new variable(model.model, ctx.datasourceSrv, ctx.variableSrv, ctx.templateSrv); - case 'query': - return new variable(model.model, ctx.datasourceSrv, ctx.templateSrv, ctx.variableSrv); - case 'interval': - return new variable(model.model, {}, ctx.templateSrv, ctx.variableSrv); - case 'custom': - return new variable(model.model, ctx.variableSrv); - default: - return new variable(model.model); - } -} diff --git a/public/app/features/templating/template_srv.ts b/public/app/features/templating/template_srv.ts index df55841d912..2852dac0dc7 100644 --- a/public/app/features/templating/template_srv.ts +++ b/public/app/features/templating/template_srv.ts @@ -2,10 +2,9 @@ import kbn from 'app/core/utils/kbn'; import _ from 'lodash'; import { deprecationWarning, ScopedVars, textUtil, TimeRange } from '@grafana/data'; import { getFilteredVariables, getVariables, getVariableWithName } from '../variables/state/selectors'; -import { getConfig } from 'app/core/config'; -import { variableRegex } from './utils'; +import { variableRegex } from '../variables/utils'; import { isAdHoc } from '../variables/guard'; -import { VariableModel } from './types'; +import { VariableModel } from '../variables/types'; import { setTemplateSrv, TemplateSrv as BaseTemplateSrv } from '@grafana/runtime'; import { variableAdapters } from '../variables/adapters'; @@ -65,11 +64,7 @@ export class TemplateSrv implements BaseTemplateSrv { } getVariables(): VariableModel[] { - if (getConfig().featureToggles.newVariables) { - return this.dependencies.getVariables(); - } - - return this._variables; + return this.dependencies.getVariables(); } updateIndex() { @@ -431,7 +426,7 @@ export class TemplateSrv implements BaseTemplateSrv { return; } - if (getConfig().featureToggles.newVariables && !this.index[name]) { + if (!this.index[name]) { return this.dependencies.getVariableWithName(name); } @@ -439,13 +434,7 @@ export class TemplateSrv implements BaseTemplateSrv { }; private getAdHocVariables = (): any[] => { - if (getConfig().featureToggles.newVariables) { - return this.dependencies.getFilteredVariables(isAdHoc); - } - if (Array.isArray(this._variables)) { - return this._variables.filter(isAdHoc); - } - return []; + return this.dependencies.getFilteredVariables(isAdHoc); }; } diff --git a/public/app/features/templating/variable_srv.ts b/public/app/features/templating/variable_srv.ts index 6cbeccda3df..e69de29bb2d 100644 --- a/public/app/features/templating/variable_srv.ts +++ b/public/app/features/templating/variable_srv.ts @@ -1,427 +0,0 @@ -// Libaries -import angular, { auto, ILocationService, IPromise, IQService } from 'angular'; -import _ from 'lodash'; -// Utils & Services -import coreModule from 'app/core/core_module'; -import { VariableActions, variableTypes } from './types'; -import { Graph } from 'app/core/utils/dag'; -import { TemplateSrv } from 'app/features/templating/template_srv'; -import { TimeSrv } from 'app/features/dashboard/services/TimeSrv'; -import { DashboardModel } from 'app/features/dashboard/state/DashboardModel'; - -// Types -import { AppEvents, TimeRange, UrlQueryMap } from '@grafana/data'; -import { CoreEvents } from 'app/types'; -import { appEvents, contextSrv } from 'app/core/core'; - -export class VariableSrv { - dashboard: DashboardModel; - variables: any[] = []; - - /** @ngInject */ - constructor( - private $q: IQService, - private $location: ILocationService, - private $injector: auto.IInjectorService, - private templateSrv: TemplateSrv, - private timeSrv: TimeSrv - ) {} - - init(dashboard: DashboardModel) { - this.dashboard = dashboard; - this.dashboard.events.on(CoreEvents.timeRangeUpdated, this.onTimeRangeUpdated.bind(this)); - this.dashboard.events.on( - CoreEvents.templateVariableValueUpdated, - this.updateUrlParamsWithCurrentVariables.bind(this) - ); - - // create working class models representing variables - this.variables = dashboard.templating.list = dashboard.templating.list.map(this.createVariableFromModel.bind(this)); - this.templateSrv.init(this.variables, this.timeSrv.timeRange()); - - // init variables - for (const variable of this.variables) { - variable.initLock = this.$q.defer(); - } - - const queryParams = this.$location.search(); - return this.$q - .all( - this.variables.map(variable => { - return this.processVariable(variable, queryParams); - }) - ) - .then(() => { - this.templateSrv.updateIndex(); - this.templateSrv.setGlobalVariable('__dashboard', { - value: { - name: dashboard.title, - uid: dashboard.uid, - toString: function() { - return this.uid; - }, - }, - }); - this.templateSrv.setGlobalVariable('__org', { - value: { - name: contextSrv.user.orgName, - id: contextSrv.user.orgId, - toString: function() { - return this.id; - }, - }, - }); - this.templateSrv.setGlobalVariable('__user', { - value: { - login: contextSrv.user.login, - id: contextSrv.user.id, - toString: function() { - return this.id; - }, - }, - }); - }); - } - - onTimeRangeUpdated(timeRange: TimeRange) { - this.templateSrv.updateTimeRange(timeRange); - const promises = this.variables - .filter(variable => variable.refresh === 2) - .map(variable => { - const previousOptions = variable.options.slice(); - - return variable.updateOptions().then(() => { - if (angular.toJson(previousOptions) !== angular.toJson(variable.options)) { - this.dashboard.templateVariableValueUpdated(); - } - }); - }); - - return this.$q - .all(promises) - .then(() => { - this.dashboard.startRefresh(); - }) - .catch(e => { - appEvents.emit(AppEvents.alertError, ['Template variable service failed', e.message]); - }); - } - - processVariable(variable: any, queryParams: any) { - const dependencies = []; - - for (const otherVariable of this.variables) { - if (variable.dependsOn(otherVariable)) { - dependencies.push(otherVariable.initLock.promise); - } - } - - return this.$q - .all(dependencies) - .then(() => { - const urlValue = queryParams['var-' + variable.name]; - if (urlValue !== void 0) { - return variable.setValueFromUrl(urlValue).then(variable.initLock.resolve); - } - - if (variable.refresh === 1 || variable.refresh === 2) { - return variable.updateOptions().then(variable.initLock.resolve); - } - - variable.initLock.resolve(); - }) - .finally(() => { - this.templateSrv.variableInitialized(variable); - delete variable.initLock; - }); - } - - createVariableFromModel(model: any) { - // @ts-ignore - const ctor = variableTypes[model.type].ctor; - if (!ctor) { - throw { - message: 'Unable to find variable constructor for ' + model.type, - }; - } - - const variable = this.$injector.instantiate(ctor, { model: model }); - return variable; - } - - addVariable(variable: any) { - this.variables.push(variable); - this.templateSrv.updateIndex(); - this.dashboard.updateSubmenuVisibility(); - } - - removeVariable(variable: any) { - const index = _.indexOf(this.variables, variable); - this.variables.splice(index, 1); - this.templateSrv.updateIndex(); - this.dashboard.updateSubmenuVisibility(); - } - - updateOptions(variable: any) { - return variable.updateOptions(); - } - - variableUpdated(variable: any, emitChangeEvents?: any) { - // if there is a variable lock ignore cascading update because we are in a boot up scenario - if (variable.initLock) { - return this.$q.when(); - } - - const g = this.createGraph(); - const node = g.getNode(variable.name); - let promises = []; - if (node) { - promises = node.getOptimizedInputEdges().map(e => { - return this.updateOptions(this.variables.find(v => v.name === e.inputNode.name)); - }); - } - - return this.$q.all(promises).then(() => { - if (emitChangeEvents) { - this.dashboard.templateVariableValueUpdated(); - this.dashboard.startRefresh(); - } - }); - } - - selectOptionsForCurrentValue(variable: any) { - let i, y, value, option; - const selected: any = []; - - for (i = 0; i < variable.options.length; i++) { - option = variable.options[i]; - option.selected = false; - if (_.isArray(variable.current.value)) { - for (y = 0; y < variable.current.value.length; y++) { - value = variable.current.value[y]; - if (option.value === value) { - option.selected = true; - selected.push(option); - } - } - } else if (option.value === variable.current.value) { - option.selected = true; - selected.push(option); - } - } - - return selected; - } - - validateVariableSelectionState(variable: any, defaultValue?: string) { - if (!variable.current) { - variable.current = {}; - } - - if (_.isArray(variable.current.value)) { - let selected = this.selectOptionsForCurrentValue(variable); - - // if none pick first - if (selected.length === 0) { - selected = variable.options[0]; - } else { - selected = { - value: _.map(selected, val => { - return val.value; - }), - text: _.map(selected, val => { - return val.text; - }), - }; - } - - return variable.setValue(selected); - } else { - let option: any = undefined; - - // 1. find the current value - option = _.find(variable.options, { - text: variable.current.text, - }); - if (option) { - return variable.setValue(option); - } - - // 2. find the default value - if (defaultValue) { - option = _.find(variable.options, { - text: defaultValue, - }); - if (option) { - return variable.setValue(option); - } - } - - // 3. use the first value - if (variable.options) { - return variable.setValue(variable.options[0]); - } - - // 4... give up - return Promise.resolve(); - } - } - - /** - * Sets the current selected option (or options) based on the query params in the url. It is possible for values - * in the url to not match current options of the variable. In that case the variables current value will be still set - * to that value. - * @param variable Instance of Variable - * @param urlValue Value of the query parameter - */ - setOptionFromUrl(variable: any, urlValue: string | string[]): IPromise { - let promise = this.$q.when(); - - if (variable.refresh) { - promise = variable.updateOptions(); - } - - return promise.then(() => { - // Simple case. Value in url matches existing options text or value. - let option: any = _.find(variable.options, op => { - return op.text === urlValue || op.value === urlValue; - }); - - // No luck either it is array or value does not exist in the variables options. - if (!option) { - let defaultText = urlValue; - const defaultValue = urlValue; - - if (_.isArray(urlValue)) { - // Multiple values in the url. We construct text as a list of texts from all matched options. - defaultText = urlValue.reduce((acc, item) => { - const t: any = _.find(variable.options, { value: item }); - if (t) { - acc.push(t.text); - } else { - acc.push(item); - } - - return acc; - }, []); - } - - // It is possible that we did not match the value to any existing option. In that case the url value will be - // used anyway for both text and value. - option = { text: defaultText, value: defaultValue }; - } - - if (variable.multi) { - // In case variable is multiple choice, we cast to array to preserve the same behaviour as when selecting - // the option directly, which will return even single value in an array. - option = { text: _.castArray(option.text), value: _.castArray(option.value) }; - } - - return variable.setValue(option); - }); - } - - setOptionAsCurrent(variable: any, option: any) { - variable.current = _.cloneDeep(option); - - if (_.isArray(variable.current.text) && variable.current.text.length > 0) { - variable.current.text = variable.current.text.join(' + '); - } else if (_.isArray(variable.current.value) && variable.current.value[0] !== '$__all') { - variable.current.text = variable.current.value.join(' + '); - } - - this.selectOptionsForCurrentValue(variable); - return this.variableUpdated(variable); - } - - templateVarsChangedInUrl(vars: UrlQueryMap) { - const update: Array> = []; - for (const v of this.variables) { - const key = `var-${v.name}`; - if (vars.hasOwnProperty(key)) { - if (this.isVariableUrlValueDifferentFromCurrent(v, vars[key])) { - update.push(v.setValueFromUrl(vars[key])); - } - } - } - - if (update.length) { - Promise.all(update).then(() => { - this.dashboard.templateVariableValueUpdated(); - this.dashboard.startRefresh(); - }); - } - } - - isVariableUrlValueDifferentFromCurrent(variable: VariableActions, urlValue: any) { - // lodash _.isEqual handles array of value equality checks as well - return !_.isEqual(variable.getValueForUrl(), urlValue); - } - - updateUrlParamsWithCurrentVariables() { - // update url - const params = this.$location.search(); - - // remove variable params - _.each(params, (value, key) => { - if (key.indexOf('var-') === 0) { - delete params[key]; - } - }); - - // add new values - this.templateSrv.fillVariableValuesForUrl(params); - // update url - this.$location.search(params); - } - - setAdhocFilter(options: any) { - let variable: any = _.find(this.variables, { - type: 'adhoc', - datasource: options.datasource, - } as any); - if (!variable) { - variable = this.createVariableFromModel({ - name: 'Filters', - type: 'adhoc', - datasource: options.datasource, - }); - this.addVariable(variable); - } - - const filters = variable.filters; - let filter: any = _.find(filters, { key: options.key, value: options.value }); - - if (!filter) { - filter = { key: options.key, value: options.value }; - filters.push(filter); - } - - filter.operator = options.operator; - this.variableUpdated(variable, true); - } - - createGraph() { - const g = new Graph(); - - this.variables.forEach(v => { - g.createNode(v.name); - }); - - this.variables.forEach(v1 => { - this.variables.forEach(v2 => { - if (v1 === v2) { - return; - } - - if (v1.dependsOn(v2)) { - g.link(v1.name, v2.name); - } - }); - }); - - return g; - } -} - -coreModule.service('variableSrv', VariableSrv); diff --git a/public/app/features/variables/adapters.ts b/public/app/features/variables/adapters.ts index bb3736882d3..9a423eb39a0 100644 --- a/public/app/features/variables/adapters.ts +++ b/public/app/features/variables/adapters.ts @@ -12,7 +12,7 @@ import { TextBoxVariableModel, VariableModel, VariableOption, -} from '../templating/types'; +} from './types'; import { VariableEditorProps } from './editor/types'; import { VariablesState } from './state/variablesReducer'; import { VariablePickerProps } from './pickers/types'; diff --git a/public/app/features/variables/adhoc/AdHocVariableEditor.tsx b/public/app/features/variables/adhoc/AdHocVariableEditor.tsx index 4eb6258bd99..d4e22c09d80 100644 --- a/public/app/features/variables/adhoc/AdHocVariableEditor.tsx +++ b/public/app/features/variables/adhoc/AdHocVariableEditor.tsx @@ -1,7 +1,7 @@ import React, { PureComponent } from 'react'; import { MapDispatchToProps, MapStateToProps } from 'react-redux'; -import { AdHocVariableModel } from '../../templating/types'; +import { AdHocVariableModel } from '../types'; import { VariableEditorProps } from '../editor/types'; import { VariableEditorState } from '../editor/reducer'; import { AdHocVariableEditorState } from './reducer'; diff --git a/public/app/features/variables/adhoc/actions.test.ts b/public/app/features/variables/adhoc/actions.test.ts index 93cf3e8c68a..a612e3fafb6 100644 --- a/public/app/features/variables/adhoc/actions.test.ts +++ b/public/app/features/variables/adhoc/actions.test.ts @@ -20,7 +20,7 @@ import { filterAdded, filterRemoved, filtersRestored, filterUpdated } from './re import { addVariable, changeVariableProp } from '../state/sharedReducer'; import { updateLocation } from 'app/core/actions'; import { DashboardState, LocationState } from 'app/types'; -import { VariableModel } from 'app/features/templating/types'; +import { VariableModel } from 'app/features/variables/types'; import { changeVariableEditorExtended, setIdInEditor } from '../editor/reducer'; import { adHocBuilder } from '../shared/testing/builders'; @@ -421,7 +421,7 @@ describe('adhoc actions', () => { const tester = await reduxTester() .givenRootReducer(getRootReducer()) .whenActionIsDispatched(createAddVariableAction(variable)) - .whenActionIsDispatched(setIdInEditor({ id: variable.id! })) + .whenActionIsDispatched(setIdInEditor({ id: variable.id })) .whenAsyncActionIsDispatched(changeVariableDatasource(datasource), true); tester.thenDispatchedActionsShouldEqual( @@ -453,7 +453,7 @@ describe('adhoc actions', () => { const tester = await reduxTester() .givenRootReducer(getRootReducer()) .whenActionIsDispatched(createAddVariableAction(variable)) - .whenActionIsDispatched(setIdInEditor({ id: variable.id! })) + .whenActionIsDispatched(setIdInEditor({ id: variable.id })) .whenAsyncActionIsDispatched(changeVariableDatasource(datasource), true); tester.thenDispatchedActionsShouldEqual( diff --git a/public/app/features/variables/adhoc/actions.ts b/public/app/features/variables/adhoc/actions.ts index 0c522c549a4..e3605f99be5 100644 --- a/public/app/features/variables/adhoc/actions.ts +++ b/public/app/features/variables/adhoc/actions.ts @@ -13,7 +13,7 @@ import { filterUpdated, initialAdHocVariableModelState, } from './reducer'; -import { AdHocVariableFilter, AdHocVariableModel } from 'app/features/templating/types'; +import { AdHocVariableFilter, AdHocVariableModel } from 'app/features/variables/types'; import { variableUpdated } from '../state/actions'; import { isAdHoc } from '../guard'; @@ -40,11 +40,11 @@ export const applyFilterFromTable = (options: AdHocTableOptions): ThunkResult { const filters = urlParser.toFilters(urlValue); - await dispatch(setFiltersFromUrl(variable.id!, filters)); + await dispatch(setFiltersFromUrl(variable.id, filters)); }, updateOptions: noop, getSaveModel: variable => { diff --git a/public/app/features/variables/adhoc/picker/AdHocFilterBuilder.tsx b/public/app/features/variables/adhoc/picker/AdHocFilterBuilder.tsx index 80236252ba6..ae1e3a51b50 100644 --- a/public/app/features/variables/adhoc/picker/AdHocFilterBuilder.tsx +++ b/public/app/features/variables/adhoc/picker/AdHocFilterBuilder.tsx @@ -1,7 +1,7 @@ import React, { FC, ReactElement, useState } from 'react'; -import { SegmentAsync, Icon } from '@grafana/ui'; +import { Icon, SegmentAsync } from '@grafana/ui'; import { OperatorSegment } from './OperatorSegment'; -import { AdHocVariableFilter } from 'app/features/templating/types'; +import { AdHocVariableFilter } from 'app/features/variables/types'; import { SelectableValue } from '@grafana/data'; interface Props { diff --git a/public/app/features/variables/adhoc/picker/AdHocPicker.tsx b/public/app/features/variables/adhoc/picker/AdHocPicker.tsx index 2a4378bb28d..a8beba48dab 100644 --- a/public/app/features/variables/adhoc/picker/AdHocPicker.tsx +++ b/public/app/features/variables/adhoc/picker/AdHocPicker.tsx @@ -1,7 +1,7 @@ import React, { PureComponent, ReactNode } from 'react'; import { connect, MapDispatchToProps, MapStateToProps } from 'react-redux'; import { StoreState } from 'app/types'; -import { AdHocVariableFilter, AdHocVariableModel } from 'app/features/templating/types'; +import { AdHocVariableFilter, AdHocVariableModel } from 'app/features/variables/types'; import { SegmentAsync } from '@grafana/ui'; import { VariablePickerProps } from '../../pickers/types'; import { OperatorSegment } from './OperatorSegment'; @@ -31,10 +31,10 @@ export class AdHocPickerUnconnected extends PureComponent { const { value } = key; if (key.value === REMOVE_FILTER_KEY) { - return this.props.removeFilter(id!, index); + return this.props.removeFilter(id, index); } - return this.props.changeFilter(id!, { + return this.props.changeFilter(id, { index, filter: { ...filters[index], @@ -45,7 +45,7 @@ export class AdHocPickerUnconnected extends PureComponent { appendFilterToVariable = (filter: AdHocVariableFilter) => { const { id } = this.props.variable; - this.props.addFilter(id!, filter); + this.props.addFilter(id, filter); }; fetchFilterKeys = async () => { diff --git a/public/app/features/variables/adhoc/reducer.test.ts b/public/app/features/variables/adhoc/reducer.test.ts index c4fbae380e1..ea43e0b92fd 100644 --- a/public/app/features/variables/adhoc/reducer.test.ts +++ b/public/app/features/variables/adhoc/reducer.test.ts @@ -4,7 +4,7 @@ import { getVariableTestContext } from '../state/helpers'; import { toVariablePayload } from '../state/types'; import { adHocVariableReducer, filterAdded, filterRemoved, filtersRestored, filterUpdated } from './reducer'; import { VariablesState } from '../state/variablesReducer'; -import { AdHocVariableFilter, AdHocVariableModel } from '../../templating/types'; +import { AdHocVariableFilter, AdHocVariableModel } from '../types'; import { createAdHocVariableAdapter } from './adapter'; describe('adHocVariableReducer', () => { diff --git a/public/app/features/variables/adhoc/reducer.ts b/public/app/features/variables/adhoc/reducer.ts index 0e1c7bd2687..5a02aebadec 100644 --- a/public/app/features/variables/adhoc/reducer.ts +++ b/public/app/features/variables/adhoc/reducer.ts @@ -1,4 +1,4 @@ -import { AdHocVariableFilter, AdHocVariableModel, VariableHide } from 'app/features/templating/types'; +import { AdHocVariableFilter, AdHocVariableModel, VariableHide } from 'app/features/variables/types'; import { getInstanceState, NEW_VARIABLE_ID, VariablePayload } from '../state/types'; import { createSlice, PayloadAction } from '@reduxjs/toolkit'; import { initialVariablesState, VariablesState } from '../state/variablesReducer'; diff --git a/public/app/features/variables/adhoc/urlParser.test.ts b/public/app/features/variables/adhoc/urlParser.test.ts index 1e1fd21ffae..228b07dc506 100644 --- a/public/app/features/variables/adhoc/urlParser.test.ts +++ b/public/app/features/variables/adhoc/urlParser.test.ts @@ -1,5 +1,5 @@ import { toFilters, toUrl } from './urlParser'; -import { AdHocVariableFilter } from 'app/features/templating/types'; +import { AdHocVariableFilter } from 'app/features/variables/types'; import { UrlQueryValue } from '@grafana/data'; describe('urlParser', () => { diff --git a/public/app/features/variables/adhoc/urlParser.ts b/public/app/features/variables/adhoc/urlParser.ts index 76cd372b4eb..adbe40bf0dd 100644 --- a/public/app/features/variables/adhoc/urlParser.ts +++ b/public/app/features/variables/adhoc/urlParser.ts @@ -1,4 +1,4 @@ -import { AdHocVariableFilter } from 'app/features/templating/types'; +import { AdHocVariableFilter } from 'app/features/variables/types'; import { UrlQueryValue } from '@grafana/data'; import { isArray, isString } from 'lodash'; diff --git a/public/app/features/variables/constant/ConstantVariableEditor.tsx b/public/app/features/variables/constant/ConstantVariableEditor.tsx index 45e0d20a4f4..6d361938b32 100644 --- a/public/app/features/variables/constant/ConstantVariableEditor.tsx +++ b/public/app/features/variables/constant/ConstantVariableEditor.tsx @@ -1,7 +1,7 @@ import React, { ChangeEvent, FocusEvent, PureComponent } from 'react'; import { selectors } from '@grafana/e2e-selectors'; -import { ConstantVariableModel } from '../../templating/types'; +import { ConstantVariableModel } from '../types'; import { VariableEditorProps } from '../editor/types'; export interface Props extends VariableEditorProps {} diff --git a/public/app/features/variables/constant/actions.test.ts b/public/app/features/variables/constant/actions.test.ts index 75af63c31d3..277b2271baf 100644 --- a/public/app/features/variables/constant/actions.test.ts +++ b/public/app/features/variables/constant/actions.test.ts @@ -4,10 +4,10 @@ import { reduxTester } from '../../../../test/core/redux/reduxTester'; import { TemplatingState } from 'app/features/variables/state/reducers'; import { updateConstantVariableOptions } from './actions'; import { getRootReducer } from '../state/helpers'; -import { ConstantVariableModel, VariableHide, VariableOption } from '../../templating/types'; +import { ConstantVariableModel, VariableHide, VariableOption } from '../types'; import { toVariablePayload } from '../state/types'; import { createConstantOptionsFromQuery } from './reducer'; -import { setCurrentVariableValue, addVariable } from '../state/sharedReducer'; +import { addVariable, setCurrentVariableValue } from '../state/sharedReducer'; describe('constant actions', () => { variableAdapters.setInit(() => [createConstantVariableAdapter()]); diff --git a/public/app/features/variables/constant/adapter.ts b/public/app/features/variables/constant/adapter.ts index 608499bdbc0..4c2d90c6eba 100644 --- a/public/app/features/variables/constant/adapter.ts +++ b/public/app/features/variables/constant/adapter.ts @@ -1,5 +1,5 @@ import cloneDeep from 'lodash/cloneDeep'; -import { ConstantVariableModel } from '../../templating/types'; +import { ConstantVariableModel } from '../types'; import { dispatch } from '../../../store/store'; import { setOptionAsCurrent, setOptionFromUrl } from '../state/actions'; import { VariableAdapter } from '../adapters'; diff --git a/public/app/features/variables/constant/reducer.test.ts b/public/app/features/variables/constant/reducer.test.ts index 0b2482259fb..670bdfb4b8f 100644 --- a/public/app/features/variables/constant/reducer.test.ts +++ b/public/app/features/variables/constant/reducer.test.ts @@ -4,7 +4,7 @@ import { getVariableTestContext } from '../state/helpers'; import { toVariablePayload } from '../state/types'; import { constantVariableReducer, createConstantOptionsFromQuery } from './reducer'; import { VariablesState } from '../state/variablesReducer'; -import { ConstantVariableModel } from '../../templating/types'; +import { ConstantVariableModel } from '../types'; import { createConstantVariableAdapter } from './adapter'; describe('constantVariableReducer', () => { diff --git a/public/app/features/variables/constant/reducer.ts b/public/app/features/variables/constant/reducer.ts index bf18b2d3951..6b228c79876 100644 --- a/public/app/features/variables/constant/reducer.ts +++ b/public/app/features/variables/constant/reducer.ts @@ -1,5 +1,5 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; -import { ConstantVariableModel, VariableHide, VariableOption } from '../../templating/types'; +import { ConstantVariableModel, VariableHide, VariableOption } from '../types'; import { getInstanceState, NEW_VARIABLE_ID, VariablePayload } from '../state/types'; import { initialVariablesState, VariablesState } from '../state/variablesReducer'; diff --git a/public/app/features/variables/custom/CustomVariableEditor.tsx b/public/app/features/variables/custom/CustomVariableEditor.tsx index d9eb27fd295..4dff3a65840 100644 --- a/public/app/features/variables/custom/CustomVariableEditor.tsx +++ b/public/app/features/variables/custom/CustomVariableEditor.tsx @@ -1,5 +1,5 @@ import React, { ChangeEvent, FocusEvent, PureComponent } from 'react'; -import { CustomVariableModel, VariableWithMultiSupport } from '../../templating/types'; +import { CustomVariableModel, VariableWithMultiSupport } from '../types'; import { SelectionOptionsEditor } from '../editor/SelectionOptionsEditor'; import { OnPropChangeArguments, VariableEditorProps } from '../editor/types'; import { connectWithStore } from 'app/core/utils/connectWithReduxStore'; diff --git a/public/app/features/variables/custom/actions.test.ts b/public/app/features/variables/custom/actions.test.ts index 47363b4b9df..be73b78f627 100644 --- a/public/app/features/variables/custom/actions.test.ts +++ b/public/app/features/variables/custom/actions.test.ts @@ -3,9 +3,9 @@ import { updateCustomVariableOptions } from './actions'; import { createCustomVariableAdapter } from './adapter'; import { reduxTester } from '../../../../test/core/redux/reduxTester'; import { getRootReducer } from '../state/helpers'; -import { CustomVariableModel, VariableHide, VariableOption } from '../../templating/types'; +import { CustomVariableModel, VariableHide, VariableOption } from '../types'; import { toVariablePayload } from '../state/types'; -import { setCurrentVariableValue, addVariable } from '../state/sharedReducer'; +import { addVariable, setCurrentVariableValue } from '../state/sharedReducer'; import { TemplatingState } from '../state/reducers'; import { createCustomOptionsFromQuery } from './reducer'; diff --git a/public/app/features/variables/custom/adapter.ts b/public/app/features/variables/custom/adapter.ts index 71335442b01..3165484e9e0 100644 --- a/public/app/features/variables/custom/adapter.ts +++ b/public/app/features/variables/custom/adapter.ts @@ -1,5 +1,5 @@ import cloneDeep from 'lodash/cloneDeep'; -import { CustomVariableModel } from '../../templating/types'; +import { CustomVariableModel } from '../types'; import { dispatch } from '../../../store/store'; import { setOptionAsCurrent, setOptionFromUrl } from '../state/actions'; import { VariableAdapter } from '../adapters'; diff --git a/public/app/features/variables/custom/reducer.test.ts b/public/app/features/variables/custom/reducer.test.ts index 87f8f8aafb3..6fd7e8fea0a 100644 --- a/public/app/features/variables/custom/reducer.test.ts +++ b/public/app/features/variables/custom/reducer.test.ts @@ -5,7 +5,7 @@ import { ALL_VARIABLE_TEXT, ALL_VARIABLE_VALUE, toVariablePayload } from '../sta import { createCustomOptionsFromQuery, customVariableReducer } from './reducer'; import { createCustomVariableAdapter } from './adapter'; import { VariablesState } from '../state/variablesReducer'; -import { CustomVariableModel } from '../../templating/types'; +import { CustomVariableModel } from '../types'; describe('customVariableReducer', () => { const adapter = createCustomVariableAdapter(); diff --git a/public/app/features/variables/custom/reducer.ts b/public/app/features/variables/custom/reducer.ts index 7a8d5ce00a0..193362bd94c 100644 --- a/public/app/features/variables/custom/reducer.ts +++ b/public/app/features/variables/custom/reducer.ts @@ -1,6 +1,6 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; -import { CustomVariableModel, VariableHide, VariableOption } from '../../templating/types'; +import { CustomVariableModel, VariableHide, VariableOption } from '../types'; import { ALL_VARIABLE_TEXT, ALL_VARIABLE_VALUE, diff --git a/public/app/features/variables/datasource/DataSourceVariableEditor.tsx b/public/app/features/variables/datasource/DataSourceVariableEditor.tsx index 190715e9331..af08e8bfe1a 100644 --- a/public/app/features/variables/datasource/DataSourceVariableEditor.tsx +++ b/public/app/features/variables/datasource/DataSourceVariableEditor.tsx @@ -1,6 +1,6 @@ import React, { ChangeEvent, FocusEvent, PureComponent } from 'react'; -import { DataSourceVariableModel, VariableWithMultiSupport } from '../../templating/types'; +import { DataSourceVariableModel, VariableWithMultiSupport } from '../types'; import { OnPropChangeArguments, VariableEditorProps } from '../editor/types'; import { SelectionOptionsEditor } from '../editor/SelectionOptionsEditor'; import { InlineFormLabel } from '@grafana/ui'; diff --git a/public/app/features/variables/datasource/actions.ts b/public/app/features/variables/datasource/actions.ts index 032647a7ff4..02a6b9d1e0d 100644 --- a/public/app/features/variables/datasource/actions.ts +++ b/public/app/features/variables/datasource/actions.ts @@ -5,7 +5,7 @@ import { validateVariableSelectionState } from '../state/actions'; import { DataSourceSelectItem, stringToJsRegex } from '@grafana/data'; import { getDatasourceSrv } from '../../plugins/datasource_srv'; import { getVariable } from '../state/selectors'; -import { DataSourceVariableModel } from '../../templating/types'; +import { DataSourceVariableModel } from '../types'; import templateSrv from '../../templating/template_srv'; import _ from 'lodash'; import { changeVariableEditorExtended } from '../editor/reducer'; @@ -19,7 +19,7 @@ export const updateDataSourceVariableOptions = ( dependencies: DataSourceVariableActionDependencies = { getDatasourceSrv: getDatasourceSrv } ): ThunkResult => async (dispatch, getState) => { const sources = await dependencies.getDatasourceSrv().getMetricSources({ skipVariables: true }); - const variableInState = getVariable(identifier.id!, getState()); + const variableInState = getVariable(identifier.id, getState()); let regex; if (variableInState.regex) { diff --git a/public/app/features/variables/datasource/adapter.ts b/public/app/features/variables/datasource/adapter.ts index de09172771f..6428266dded 100644 --- a/public/app/features/variables/datasource/adapter.ts +++ b/public/app/features/variables/datasource/adapter.ts @@ -1,5 +1,5 @@ import cloneDeep from 'lodash/cloneDeep'; -import { DataSourceVariableModel } from '../../templating/types'; +import { DataSourceVariableModel } from '../types'; import { dispatch } from '../../../store/store'; import { setOptionAsCurrent, setOptionFromUrl } from '../state/actions'; import { VariableAdapter } from '../adapters'; @@ -8,7 +8,7 @@ import { OptionsPicker } from '../pickers'; import { ALL_VARIABLE_TEXT, toVariableIdentifier } from '../state/types'; import { DataSourceVariableEditor } from './DataSourceVariableEditor'; import { updateDataSourceVariableOptions } from './actions'; -import { containsVariable } from '../../templating/utils'; +import { containsVariable } from '../utils'; export const createDataSourceVariableAdapter = (): VariableAdapter => { return { diff --git a/public/app/features/variables/datasource/reducer.test.ts b/public/app/features/variables/datasource/reducer.test.ts index eadf9da84fa..c29a93e838c 100644 --- a/public/app/features/variables/datasource/reducer.test.ts +++ b/public/app/features/variables/datasource/reducer.test.ts @@ -1,7 +1,7 @@ import { reducerTester } from '../../../../test/core/redux/reducerTester'; import { VariablesState } from '../state/variablesReducer'; import { createDataSourceOptions, dataSourceVariableReducer } from './reducer'; -import { DataSourceVariableModel } from '../../templating/types'; +import { DataSourceVariableModel } from '../types'; import { getVariableTestContext } from '../state/helpers'; import cloneDeep from 'lodash/cloneDeep'; import { createDataSourceVariableAdapter } from './adapter'; diff --git a/public/app/features/variables/datasource/reducer.ts b/public/app/features/variables/datasource/reducer.ts index 7d8045249bc..e4e50f581a2 100644 --- a/public/app/features/variables/datasource/reducer.ts +++ b/public/app/features/variables/datasource/reducer.ts @@ -1,5 +1,5 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; -import { DataSourceVariableModel, VariableHide, VariableOption, VariableRefresh } from '../../templating/types'; +import { DataSourceVariableModel, VariableHide, VariableOption, VariableRefresh } from '../types'; import { ALL_VARIABLE_TEXT, ALL_VARIABLE_VALUE, diff --git a/public/app/features/templating/DefaultVariableQueryEditor.tsx b/public/app/features/variables/editor/DefaultVariableQueryEditor.tsx similarity index 100% rename from public/app/features/templating/DefaultVariableQueryEditor.tsx rename to public/app/features/variables/editor/DefaultVariableQueryEditor.tsx diff --git a/public/app/features/variables/editor/SelectionOptionsEditor.tsx b/public/app/features/variables/editor/SelectionOptionsEditor.tsx index fca45b5ed58..75fb6dbc0f7 100644 --- a/public/app/features/variables/editor/SelectionOptionsEditor.tsx +++ b/public/app/features/variables/editor/SelectionOptionsEditor.tsx @@ -2,7 +2,7 @@ import React, { FunctionComponent, useCallback } from 'react'; import { LegacyForms } from '@grafana/ui'; import { selectors } from '@grafana/e2e-selectors'; -import { VariableWithMultiSupport } from '../../templating/types'; +import { VariableWithMultiSupport } from '../types'; import { VariableEditorProps } from './types'; import { toVariableIdentifier, VariableIdentifier } from '../state/types'; diff --git a/public/app/features/variables/editor/VariableEditorContainer.tsx b/public/app/features/variables/editor/VariableEditorContainer.tsx index a5b798cdaf9..0fff1dc7e2f 100644 --- a/public/app/features/variables/editor/VariableEditorContainer.tsx +++ b/public/app/features/variables/editor/VariableEditorContainer.tsx @@ -9,7 +9,7 @@ import { VariableEditorEditor } from './VariableEditorEditor'; import { MapDispatchToProps, MapStateToProps } from 'react-redux'; import { connectWithStore } from '../../../core/utils/connectWithReduxStore'; import { getVariables } from '../state/selectors'; -import { VariableModel } from '../../templating/types'; +import { VariableModel } from '../types'; import { switchToEditMode, switchToListMode, switchToNewMode } from './actions'; import { changeVariableOrder, duplicateVariable, removeVariable } from '../state/sharedReducer'; diff --git a/public/app/features/variables/editor/VariableEditorEditor.tsx b/public/app/features/variables/editor/VariableEditorEditor.tsx index 273746292e2..e4ba55d00d2 100644 --- a/public/app/features/variables/editor/VariableEditorEditor.tsx +++ b/public/app/features/variables/editor/VariableEditorEditor.tsx @@ -6,7 +6,7 @@ import { selectors } from '@grafana/e2e-selectors'; import { variableAdapters } from '../adapters'; import { NEW_VARIABLE_ID, toVariablePayload, VariableIdentifier } from '../state/types'; -import { VariableHide, VariableModel } from '../../templating/types'; +import { VariableHide, VariableModel } from '../types'; import { appEvents } from '../../../core/core'; import { VariableValuesPreview } from './VariableValuesPreview'; import { changeVariableName, onEditorAdd, onEditorUpdate, variableEditorMount, variableEditorUnMount } from './actions'; diff --git a/public/app/features/variables/editor/VariableEditorList.tsx b/public/app/features/variables/editor/VariableEditorList.tsx index fe18f55fbd2..9999bd08867 100644 --- a/public/app/features/variables/editor/VariableEditorList.tsx +++ b/public/app/features/variables/editor/VariableEditorList.tsx @@ -3,7 +3,7 @@ import { IconButton } from '@grafana/ui'; import { selectors } from '@grafana/e2e-selectors'; import EmptyListCTA from '../../../core/components/EmptyListCTA/EmptyListCTA'; -import { QueryVariableModel, VariableModel } from '../../templating/types'; +import { QueryVariableModel, VariableModel } from '../types'; import { toVariableIdentifier, VariableIdentifier } from '../state/types'; export interface Props { @@ -28,7 +28,7 @@ export class VariableEditorList extends PureComponent { onChangeVariableOrder = (event: MouseEvent, variable: VariableModel, moveType: MoveType) => { event.preventDefault(); - this.props.onChangeVariableOrder(toVariableIdentifier(variable), variable.index!, variable.index! + moveType); + this.props.onChangeVariableOrder(toVariableIdentifier(variable), variable.index, variable.index + moveType); }; onDuplicateVariable = (event: MouseEvent, identifier: VariableIdentifier) => { diff --git a/public/app/features/variables/editor/VariableValuesPreview.tsx b/public/app/features/variables/editor/VariableValuesPreview.tsx index ab61cd0fcc8..a73629a9afb 100644 --- a/public/app/features/variables/editor/VariableValuesPreview.tsx +++ b/public/app/features/variables/editor/VariableValuesPreview.tsx @@ -1,5 +1,5 @@ import React, { useCallback, useEffect, useState } from 'react'; -import { VariableModel, VariableOption, VariableWithOptions } from '../../templating/types'; +import { VariableModel, VariableOption, VariableWithOptions } from '../types'; import { selectors } from '@grafana/e2e-selectors'; export interface VariableValuesPreviewProps { diff --git a/public/app/features/variables/editor/actions.ts b/public/app/features/variables/editor/actions.ts index 242bda71e09..1a7164a1881 100644 --- a/public/app/features/variables/editor/actions.ts +++ b/public/app/features/variables/editor/actions.ts @@ -22,7 +22,7 @@ import { addVariable, removeVariable, storeNewVariable } from '../state/sharedRe export const variableEditorMount = (identifier: VariableIdentifier): ThunkResult => { return async dispatch => { - dispatch(variableEditorMounted({ name: getVariable(identifier.id!).name })); + dispatch(variableEditorMounted({ name: getVariable(identifier.id).name })); }; }; @@ -37,7 +37,7 @@ export const variableEditorUnMount = (identifier: VariableIdentifier): ThunkResu export const onEditorUpdate = (identifier: VariableIdentifier): ThunkResult => { return async (dispatch, getState) => { - const variableInState = getVariable(identifier.id!, getState()); + const variableInState = getVariable(identifier.id, getState()); await variableAdapters.get(variableInState.type).updateOptions(variableInState); dispatch(switchToListMode()); }; @@ -101,8 +101,8 @@ export const completeChangeVariableName = (identifier: VariableIdentifier, newNa ) => { const originalVariable = getVariable(identifier.id, getState()); const model = { ...cloneDeep(originalVariable), name: newName, id: newName }; - const global = originalVariable.global!; // global is undefined because of old variable system - const index = originalVariable.index!; // index is undefined because of old variable system + const global = originalVariable.global; + const index = originalVariable.index; const renamedIdentifier = toVariableIdentifier(model); dispatch(addVariable(toVariablePayload(renamedIdentifier, { global, index, model }))); diff --git a/public/app/features/variables/editor/types.ts b/public/app/features/variables/editor/types.ts index c43df1e9510..419ab1be7db 100644 --- a/public/app/features/variables/editor/types.ts +++ b/public/app/features/variables/editor/types.ts @@ -1,4 +1,4 @@ -import { VariableModel } from '../../templating/types'; +import { VariableModel } from '../types'; export interface OnPropChangeArguments { propName: keyof Model; diff --git a/public/app/features/variables/guard.ts b/public/app/features/variables/guard.ts index abf3e45e681..99fb6f15ce8 100644 --- a/public/app/features/variables/guard.ts +++ b/public/app/features/variables/guard.ts @@ -1,4 +1,10 @@ -import { QueryVariableModel, VariableModel, AdHocVariableModel, VariableWithMultiSupport } from '../templating/types'; +import { + AdHocVariableModel, + ConstantVariableModel, + QueryVariableModel, + VariableModel, + VariableWithMultiSupport, +} from './types'; export const isQuery = (model: VariableModel): model is QueryVariableModel => { return model.type === 'query'; @@ -8,6 +14,10 @@ export const isAdHoc = (model: VariableModel): model is AdHocVariableModel => { return model.type === 'adhoc'; }; +export const isConstant = (model: VariableModel): model is ConstantVariableModel => { + return model.type === 'constant'; +}; + export const isMulti = (model: VariableModel): model is VariableWithMultiSupport => { const withMulti = model as VariableWithMultiSupport; return withMulti.hasOwnProperty('multi') && typeof withMulti.multi === 'boolean'; diff --git a/public/app/features/variables/interval/IntervalVariableEditor.tsx b/public/app/features/variables/interval/IntervalVariableEditor.tsx index 1a3ac8cd65e..f0e33f347cf 100644 --- a/public/app/features/variables/interval/IntervalVariableEditor.tsx +++ b/public/app/features/variables/interval/IntervalVariableEditor.tsx @@ -1,8 +1,9 @@ import React, { ChangeEvent, FocusEvent, PureComponent } from 'react'; -import { IntervalVariableModel } from '../../templating/types'; +import { IntervalVariableModel } from '../types'; import { VariableEditorProps } from '../editor/types'; import { InlineFormLabel, LegacyForms } from '@grafana/ui'; + const { Switch } = LegacyForms; export interface Props extends VariableEditorProps {} diff --git a/public/app/features/variables/interval/actions.ts b/public/app/features/variables/interval/actions.ts index b42d6c18023..3c804290b08 100644 --- a/public/app/features/variables/interval/actions.ts +++ b/public/app/features/variables/interval/actions.ts @@ -5,7 +5,7 @@ import { ThunkResult } from '../../../types'; import { createIntervalOptions } from './reducer'; import { validateVariableSelectionState } from '../state/actions'; import { getVariable } from '../state/selectors'; -import { IntervalVariableModel } from '../../templating/types'; +import { IntervalVariableModel } from '../types'; import kbn from '../../../core/utils/kbn'; import { getTimeSrv } from '../../dashboard/services/TimeSrv'; import templateSrv from '../../templating/template_srv'; diff --git a/public/app/features/variables/interval/adapter.ts b/public/app/features/variables/interval/adapter.ts index 34c4b344156..b40da6768e5 100644 --- a/public/app/features/variables/interval/adapter.ts +++ b/public/app/features/variables/interval/adapter.ts @@ -1,5 +1,5 @@ import cloneDeep from 'lodash/cloneDeep'; -import { IntervalVariableModel } from '../../templating/types'; +import { IntervalVariableModel } from '../types'; import { dispatch } from '../../../store/store'; import { setOptionAsCurrent, setOptionFromUrl } from '../state/actions'; import { VariableAdapter } from '../adapters'; diff --git a/public/app/features/variables/interval/reducer.test.ts b/public/app/features/variables/interval/reducer.test.ts index 849994f583e..c356c647370 100644 --- a/public/app/features/variables/interval/reducer.test.ts +++ b/public/app/features/variables/interval/reducer.test.ts @@ -3,7 +3,7 @@ import cloneDeep from 'lodash/cloneDeep'; import { getVariableTestContext } from '../state/helpers'; import { toVariablePayload } from '../state/types'; import { createIntervalVariableAdapter } from './adapter'; -import { IntervalVariableModel } from '../../templating/types'; +import { IntervalVariableModel } from '../types'; import { reducerTester } from '../../../../test/core/redux/reducerTester'; import { VariablesState } from '../state/variablesReducer'; import { createIntervalOptions, intervalVariableReducer } from './reducer'; diff --git a/public/app/features/variables/interval/reducer.ts b/public/app/features/variables/interval/reducer.ts index 2d79d6f35d9..e643ba14f62 100644 --- a/public/app/features/variables/interval/reducer.ts +++ b/public/app/features/variables/interval/reducer.ts @@ -1,5 +1,5 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; -import { IntervalVariableModel, VariableHide, VariableOption, VariableRefresh } from '../../templating/types'; +import { IntervalVariableModel, VariableHide, VariableOption, VariableRefresh } from '../types'; import { getInstanceState, NEW_VARIABLE_ID, VariablePayload } from '../state/types'; import { initialVariablesState, VariablesState } from '../state/variablesReducer'; import _ from 'lodash'; @@ -28,7 +28,7 @@ export const intervalVariableSlice = createSlice({ initialState: initialVariablesState, reducers: { createIntervalOptions: (state: VariablesState, action: PayloadAction) => { - const instanceState = getInstanceState(state, action.payload.id!); + const instanceState = getInstanceState(state, action.payload.id); const options: VariableOption[] = _.map(instanceState.query.match(/(["'])(.*?)\1|\w+/g), text => { text = text.replace(/["']+/g, ''); return { text: text.trim(), value: text.trim(), selected: false }; diff --git a/public/app/features/variables/pickers/OptionsPicker/OptionsPicker.tsx b/public/app/features/variables/pickers/OptionsPicker/OptionsPicker.tsx index 34d6618dd2e..7709b303166 100644 --- a/public/app/features/variables/pickers/OptionsPicker/OptionsPicker.tsx +++ b/public/app/features/variables/pickers/OptionsPicker/OptionsPicker.tsx @@ -6,7 +6,7 @@ import { VariableLink } from '../shared/VariableLink'; import { VariableInput } from '../shared/VariableInput'; import { commitChangesToVariable, filterOrSearchOptions, navigateOptions, toggleAndFetchTag } from './actions'; import { OptionsPickerState, showOptions, toggleAllOptions, toggleOption } from './reducer'; -import { VariableOption, VariableTag, VariableWithMultiSupport, VariableWithOptions } from '../../../templating/types'; +import { VariableOption, VariableTag, VariableWithMultiSupport, VariableWithOptions } from '../../types'; import { VariableOptions } from '../shared/VariableOptions'; import { isQuery } from '../../guard'; import { VariablePickerProps } from '../types'; diff --git a/public/app/features/variables/pickers/OptionsPicker/actions.test.ts b/public/app/features/variables/pickers/OptionsPicker/actions.test.ts index 4ac2031af27..02551d6aaa9 100644 --- a/public/app/features/variables/pickers/OptionsPicker/actions.test.ts +++ b/public/app/features/variables/pickers/OptionsPicker/actions.test.ts @@ -1,7 +1,7 @@ import { reduxTester } from '../../../../../test/core/redux/reduxTester'; import { getRootReducer } from '../../state/helpers'; import { TemplatingState } from '../../state/reducers'; -import { QueryVariableModel, VariableHide, VariableRefresh, VariableSort } from '../../../templating/types'; +import { QueryVariableModel, VariableHide, VariableRefresh, VariableSort } from '../../types'; import { hideOptions, showOptions, @@ -20,7 +20,7 @@ import { } from './actions'; import { NavigationKey } from '../types'; import { toVariablePayload } from '../../state/types'; -import { changeVariableProp, setCurrentVariableValue, addVariable } from '../../state/sharedReducer'; +import { addVariable, changeVariableProp, setCurrentVariableValue } from '../../state/sharedReducer'; import { variableAdapters } from '../../adapters'; import { createQueryVariableAdapter } from '../../query/adapter'; import { updateLocation } from 'app/core/actions'; diff --git a/public/app/features/variables/pickers/OptionsPicker/actions.ts b/public/app/features/variables/pickers/OptionsPicker/actions.ts index 93239868c7a..19b2ac499a9 100644 --- a/public/app/features/variables/pickers/OptionsPicker/actions.ts +++ b/public/app/features/variables/pickers/OptionsPicker/actions.ts @@ -1,4 +1,5 @@ -import { debounce, trim } from 'lodash'; +import debounce from 'lodash/debounce'; +import trim from 'lodash/trim'; import { StoreState, ThunkDispatch, ThunkResult } from 'app/types'; import { QueryVariableModel, @@ -7,7 +8,7 @@ import { VariableTag, VariableWithMultiSupport, VariableWithOptions, -} from '../../../templating/types'; +} from '../../types'; import { variableAdapters } from '../../adapters'; import { getVariable } from '../../state/selectors'; import { NavigationKey } from '../types'; @@ -25,7 +26,7 @@ import { getDataSourceSrv } from '@grafana/runtime'; import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv'; import { changeVariableProp, setCurrentVariableValue } from '../../state/sharedReducer'; import { toVariablePayload } from '../../state/types'; -import { containsSearchFilter } from '../../../templating/utils'; +import { containsSearchFilter } from '../../utils'; export const navigateOptions = (key: NavigationKey, clearOthers: boolean): ThunkResult => { return async (dispatch, getState) => { @@ -57,7 +58,7 @@ export const navigateOptions = (key: NavigationKey, clearOthers: boolean): Thunk export const filterOrSearchOptions = (searchQuery = ''): ThunkResult => { return async (dispatch, getState) => { const { id, queryValue } = getState().templating.optionsPicker; - const { query, options } = getVariable(id!, getState()); + const { query, options } = getVariable(id, getState()); dispatch(updateSearchQuery(searchQuery)); if (trim(queryValue) === trim(searchQuery)) { diff --git a/public/app/features/variables/pickers/OptionsPicker/reducer.test.ts b/public/app/features/variables/pickers/OptionsPicker/reducer.test.ts index 4fbc045d2cf..97011a2a9b9 100644 --- a/public/app/features/variables/pickers/OptionsPicker/reducer.test.ts +++ b/public/app/features/variables/pickers/OptionsPicker/reducer.test.ts @@ -15,7 +15,7 @@ import { updateSearchQuery, } from './reducer'; import { reducerTester } from '../../../../../test/core/redux/reducerTester'; -import { QueryVariableModel, VariableTag, VariableOption } from '../../../templating/types'; +import { QueryVariableModel, VariableTag, VariableOption } from '../../types'; import { ALL_VARIABLE_TEXT, ALL_VARIABLE_VALUE } from '../../state/types'; const getVariableTestContext = (extend: Partial) => { @@ -244,7 +244,7 @@ describe('optionsPickerReducer', () => { .thenStateShouldEqual({ ...initialState, options: payload.options, - id: payload.id!, + id: payload.id, multi: payload.multi, selectedValues: [{ text: 'B', value: 'B', selected: true }], queryValue: '', @@ -274,7 +274,7 @@ describe('optionsPickerReducer', () => { ...initialState, options: payload.options, queryValue, - id: payload.id!, + id: payload.id, multi: payload.multi, selectedValues: [selected], }); diff --git a/public/app/features/variables/pickers/OptionsPicker/reducer.ts b/public/app/features/variables/pickers/OptionsPicker/reducer.ts index 196a9e47f4b..71c93a5c78d 100644 --- a/public/app/features/variables/pickers/OptionsPicker/reducer.ts +++ b/public/app/features/variables/pickers/OptionsPicker/reducer.ts @@ -1,10 +1,10 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; import { cloneDeep, isString, trim } from 'lodash'; -import { VariableOption, VariableTag, VariableWithMultiSupport } from '../../../templating/types'; +import { VariableOption, VariableTag, VariableWithMultiSupport } from '../../types'; import { ALL_VARIABLE_VALUE } from '../../state/types'; import { isQuery } from '../../guard'; import { applyStateChanges } from '../../../../core/utils/applyStateChanges'; -import { containsSearchFilter } from '../../../templating/utils'; +import { containsSearchFilter } from '../../utils'; export interface ToggleOption { option: VariableOption; @@ -125,7 +125,7 @@ const optionsPickerSlice = createSlice({ state.options = cloneDeep(options); state.tags = getTags(action.payload); state.multi = multi ?? false; - state.id = action.payload.id!; + state.id = action.payload.id; state.queryValue = ''; if (isQuery(action.payload)) { diff --git a/public/app/features/variables/pickers/PickerRenderer.tsx b/public/app/features/variables/pickers/PickerRenderer.tsx index ad19d1c5d58..177616d54ad 100644 --- a/public/app/features/variables/pickers/PickerRenderer.tsx +++ b/public/app/features/variables/pickers/PickerRenderer.tsx @@ -1,5 +1,5 @@ import React, { FunctionComponent, useMemo } from 'react'; -import { VariableHide, VariableModel } from '../../templating/types'; +import { VariableHide, VariableModel } from '../types'; import { selectors } from '@grafana/e2e-selectors'; import { variableAdapters } from '../adapters'; diff --git a/public/app/features/variables/pickers/shared/VariableLink.tsx b/public/app/features/variables/pickers/shared/VariableLink.tsx index 8890cb07eb6..f901bfb06b8 100644 --- a/public/app/features/variables/pickers/shared/VariableLink.tsx +++ b/public/app/features/variables/pickers/shared/VariableLink.tsx @@ -2,7 +2,7 @@ import React, { PureComponent } from 'react'; import { getTagColorsFromName, Icon } from '@grafana/ui'; import { selectors } from '@grafana/e2e-selectors'; -import { VariableTag } from '../../../templating/types'; +import { VariableTag } from '../../types'; interface Props { onClick: () => void; diff --git a/public/app/features/variables/pickers/shared/VariableOptions.tsx b/public/app/features/variables/pickers/shared/VariableOptions.tsx index 3f0883c9f2f..4c601148b13 100644 --- a/public/app/features/variables/pickers/shared/VariableOptions.tsx +++ b/public/app/features/variables/pickers/shared/VariableOptions.tsx @@ -2,7 +2,7 @@ import React, { PureComponent } from 'react'; import { getTagColorsFromName, Icon, Tooltip } from '@grafana/ui'; import { selectors } from '@grafana/e2e-selectors'; -import { VariableOption, VariableTag } from '../../../templating/types'; +import { VariableOption, VariableTag } from '../../types'; export interface Props { multi: boolean; diff --git a/public/app/features/variables/pickers/types.ts b/public/app/features/variables/pickers/types.ts index 4d86cd92d37..6a8ef8e9695 100644 --- a/public/app/features/variables/pickers/types.ts +++ b/public/app/features/variables/pickers/types.ts @@ -1,4 +1,4 @@ -import { VariableModel } from '../../templating/types'; +import { VariableModel } from '../types'; export interface VariablePickerProps { variable: Model; diff --git a/public/app/features/variables/query/QueryVariableEditor.tsx b/public/app/features/variables/query/QueryVariableEditor.tsx index 157b317196d..127e24b7c4a 100644 --- a/public/app/features/variables/query/QueryVariableEditor.tsx +++ b/public/app/features/variables/query/QueryVariableEditor.tsx @@ -4,7 +4,7 @@ import { selectors } from '@grafana/e2e-selectors'; import templateSrv from '../../templating/template_srv'; import { SelectionOptionsEditor } from '../editor/SelectionOptionsEditor'; -import { QueryVariableModel, VariableRefresh, VariableSort, VariableWithMultiSupport } from '../../templating/types'; +import { QueryVariableModel, VariableRefresh, VariableSort, VariableWithMultiSupport } from '../types'; import { QueryVariableEditorState } from './reducer'; import { changeQueryVariableDataSource, changeQueryVariableQuery, initQueryVariableEditor } from './actions'; import { VariableEditorState } from '../editor/reducer'; diff --git a/public/app/features/variables/query/actions.test.ts b/public/app/features/variables/query/actions.test.ts index e585e23b385..a83d4d45ccf 100644 --- a/public/app/features/variables/query/actions.test.ts +++ b/public/app/features/variables/query/actions.test.ts @@ -2,9 +2,9 @@ import { variableAdapters } from '../adapters'; import { createQueryVariableAdapter } from './adapter'; import { reduxTester } from '../../../../test/core/redux/reduxTester'; import { getRootReducer } from '../state/helpers'; -import { QueryVariableModel, VariableHide, VariableRefresh, VariableSort } from '../../templating/types'; +import { QueryVariableModel, VariableHide, VariableRefresh, VariableSort } from '../types'; import { ALL_VARIABLE_TEXT, ALL_VARIABLE_VALUE, toVariablePayload } from '../state/types'; -import { changeVariableProp, setCurrentVariableValue, addVariable } from '../state/sharedReducer'; +import { addVariable, changeVariableProp, setCurrentVariableValue } from '../state/sharedReducer'; import { TemplatingState } from '../state/reducers'; import { changeQueryVariableDataSource, @@ -19,7 +19,7 @@ import { removeVariableEditorError, setIdInEditor, } from '../editor/reducer'; -import DefaultVariableQueryEditor from '../../templating/DefaultVariableQueryEditor'; +import DefaultVariableQueryEditor from '../editor/DefaultVariableQueryEditor'; import { expect } from 'test/lib/common'; const mocks: Record = { @@ -170,7 +170,7 @@ describe('query actions', () => { const tester = await reduxTester<{ templating: TemplatingState }>() .givenRootReducer(getRootReducer()) .whenActionIsDispatched(addVariable(toVariablePayload(variable, { global: false, index: 0, model: variable }))) - .whenActionIsDispatched(setIdInEditor({ id: variable.id! })) + .whenActionIsDispatched(setIdInEditor({ id: variable.id })) .whenAsyncActionIsDispatched(updateQueryVariableOptions(toVariablePayload(variable)), true); const option = createOption(ALL_VARIABLE_TEXT, ALL_VARIABLE_VALUE); @@ -198,7 +198,7 @@ describe('query actions', () => { const tester = await reduxTester<{ templating: TemplatingState }>() .givenRootReducer(getRootReducer()) .whenActionIsDispatched(addVariable(toVariablePayload(variable, { global: false, index: 0, model: variable }))) - .whenActionIsDispatched(setIdInEditor({ id: variable.id! })) + .whenActionIsDispatched(setIdInEditor({ id: variable.id })) .whenAsyncActionIsDispatched(updateQueryVariableOptions(toVariablePayload(variable)), true); tester.thenDispatchedActionsPredicateShouldEqual(actions => { diff --git a/public/app/features/variables/query/actions.ts b/public/app/features/variables/query/actions.ts index 82459fbd878..48cdf35bb7e 100644 --- a/public/app/features/variables/query/actions.ts +++ b/public/app/features/variables/query/actions.ts @@ -1,13 +1,13 @@ import { AppEvents, DataSourcePluginMeta, DataSourceSelectItem } from '@grafana/data'; import { validateVariableSelectionState } from '../state/actions'; -import { QueryVariableModel, VariableRefresh } from '../../templating/types'; +import { QueryVariableModel, VariableRefresh } from '../types'; import { ThunkResult } from '../../../types'; import { getDatasourceSrv } from '../../plugins/datasource_srv'; import templateSrv from '../../templating/template_srv'; import { getTimeSrv } from '../../dashboard/services/TimeSrv'; import appEvents from '../../../core/app_events'; import { importDataSourcePlugin } from '../../plugins/plugin_loader'; -import DefaultVariableQueryEditor from '../../templating/DefaultVariableQueryEditor'; +import DefaultVariableQueryEditor from '../editor/DefaultVariableQueryEditor'; import { getVariable } from '../state/selectors'; import { addVariableEditorError, changeVariableEditorExtended, removeVariableEditorError } from '../editor/reducer'; import { variableAdapters } from '../adapters'; @@ -20,7 +20,7 @@ export const updateQueryVariableOptions = ( searchFilter?: string ): ThunkResult => { return async (dispatch, getState) => { - const variableInState = getVariable(identifier.id!, getState()); + const variableInState = getVariable(identifier.id, getState()); try { const beforeUid = getState().templating.transaction.uid; if (getState().templating.editor.id === variableInState.id) { @@ -81,7 +81,7 @@ export const initQueryVariableEditor = (identifier: VariableIdentifier): ThunkRe const allDataSources = [defaultDatasource].concat(dataSources); dispatch(changeVariableEditorExtended({ propName: 'dataSources', propValue: allDataSources })); - const variable = getVariable(identifier.id!, getState()); + const variable = getVariable(identifier.id, getState()); if (!variable.datasource) { return; } @@ -110,7 +110,7 @@ export const changeQueryVariableQuery = ( query: any, definition: string ): ThunkResult => async (dispatch, getState) => { - const variableInState = getVariable(identifier.id!, getState()); + const variableInState = getVariable(identifier.id, getState()); if (typeof query === 'string' && query.match(new RegExp('\\$' + variableInState.name + '(/| |$)'))) { const errorText = 'Query cannot contain a reference to itself. Variable: $' + variableInState.name; dispatch(addVariableEditorError({ errorProp: 'query', errorText })); diff --git a/public/app/features/variables/query/adapter.ts b/public/app/features/variables/query/adapter.ts index 58159c8c3d9..959a9173afc 100644 --- a/public/app/features/variables/query/adapter.ts +++ b/public/app/features/variables/query/adapter.ts @@ -1,6 +1,6 @@ import cloneDeep from 'lodash/cloneDeep'; -import { QueryVariableModel, VariableRefresh } from '../../templating/types'; +import { QueryVariableModel, VariableRefresh } from '../types'; import { initialQueryVariableModelState, queryVariableReducer } from './reducer'; import { dispatch } from '../../../store/store'; import { setOptionAsCurrent, setOptionFromUrl } from '../state/actions'; @@ -9,7 +9,7 @@ import { OptionsPicker } from '../pickers'; import { QueryVariableEditor } from './QueryVariableEditor'; import { updateQueryVariableOptions } from './actions'; import { ALL_VARIABLE_TEXT, toVariableIdentifier } from '../state/types'; -import { containsVariable } from '../../templating/utils'; +import { containsVariable } from '../utils'; export const createQueryVariableAdapter = (): VariableAdapter => { return { diff --git a/public/app/features/variables/query/reducer.test.ts b/public/app/features/variables/query/reducer.test.ts index bcb6c4c4df0..bc8172a188c 100644 --- a/public/app/features/variables/query/reducer.test.ts +++ b/public/app/features/variables/query/reducer.test.ts @@ -1,6 +1,6 @@ import { reducerTester } from '../../../../test/core/redux/reducerTester'; import { queryVariableReducer, updateVariableOptions, updateVariableTags } from './reducer'; -import { QueryVariableModel } from '../../templating/types'; +import { QueryVariableModel } from '../types'; import cloneDeep from 'lodash/cloneDeep'; import { VariablesState } from '../state/variablesReducer'; import { getVariableTestContext } from '../state/helpers'; diff --git a/public/app/features/variables/query/reducer.ts b/public/app/features/variables/query/reducer.ts index 8a86248d6fd..1d607b0e5b6 100644 --- a/public/app/features/variables/query/reducer.ts +++ b/public/app/features/variables/query/reducer.ts @@ -1,15 +1,8 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; import _ from 'lodash'; -import { DataSourceApi, DataSourceSelectItem, stringToJsRegex, MetricFindValue } from '@grafana/data'; +import { DataSourceApi, DataSourceSelectItem, MetricFindValue, stringToJsRegex } from '@grafana/data'; -import { - QueryVariableModel, - VariableHide, - VariableOption, - VariableRefresh, - VariableSort, - VariableTag, -} from '../../templating/types'; +import { QueryVariableModel, VariableHide, VariableOption, VariableRefresh, VariableSort, VariableTag } from '../types'; import { ALL_VARIABLE_TEXT, diff --git a/public/app/features/variables/shared/multiOptions.test.ts b/public/app/features/variables/shared/multiOptions.test.ts index 9a9bb4bc60b..51d423067fe 100644 --- a/public/app/features/variables/shared/multiOptions.test.ts +++ b/public/app/features/variables/shared/multiOptions.test.ts @@ -1,4 +1,4 @@ -import { VariableOption } from 'app/features/templating/types'; +import { VariableOption } from 'app/features/variables/types'; import { alignCurrentWithMulti } from './multiOptions'; describe('alignCurrentWithMulti', () => { diff --git a/public/app/features/variables/shared/multiOptions.ts b/public/app/features/variables/shared/multiOptions.ts index cd34e92ac69..6cc80c4a031 100644 --- a/public/app/features/variables/shared/multiOptions.ts +++ b/public/app/features/variables/shared/multiOptions.ts @@ -1,4 +1,4 @@ -import { VariableOption } from 'app/features/templating/types'; +import { VariableOption } from 'app/features/variables/types'; export const alignCurrentWithMulti = (current: VariableOption, value: boolean): VariableOption => { if (!current) { diff --git a/public/app/features/variables/shared/testing/adHocVariableBuilder.ts b/public/app/features/variables/shared/testing/adHocVariableBuilder.ts index 6e36ed25439..99085c7abd9 100644 --- a/public/app/features/variables/shared/testing/adHocVariableBuilder.ts +++ b/public/app/features/variables/shared/testing/adHocVariableBuilder.ts @@ -1,4 +1,4 @@ -import { AdHocVariableFilter, AdHocVariableModel } from 'app/features/templating/types'; +import { AdHocVariableFilter, AdHocVariableModel } from 'app/features/variables/types'; import { VariableBuilder } from './variableBuilder'; export class AdHocVariableBuilder extends VariableBuilder { diff --git a/public/app/features/variables/shared/testing/datasourceVariableBuilder.ts b/public/app/features/variables/shared/testing/datasourceVariableBuilder.ts index 73bea4871ae..39e58ea7e63 100644 --- a/public/app/features/variables/shared/testing/datasourceVariableBuilder.ts +++ b/public/app/features/variables/shared/testing/datasourceVariableBuilder.ts @@ -1,5 +1,5 @@ import { MultiVariableBuilder } from './multiVariableBuilder'; -import { DataSourceVariableModel, VariableRefresh } from 'app/features/templating/types'; +import { DataSourceVariableModel, VariableRefresh } from 'app/features/variables/types'; export class DatasourceVariableBuilder extends MultiVariableBuilder { withRefresh(refresh: VariableRefresh) { diff --git a/public/app/features/variables/shared/testing/intervalVariableBuilder.ts b/public/app/features/variables/shared/testing/intervalVariableBuilder.ts index 6d4b13ca327..fa6358acaa4 100644 --- a/public/app/features/variables/shared/testing/intervalVariableBuilder.ts +++ b/public/app/features/variables/shared/testing/intervalVariableBuilder.ts @@ -1,5 +1,5 @@ import { OptionsVariableBuilder } from './optionsVariableBuilder'; -import { IntervalVariableModel, VariableRefresh } from 'app/features/templating/types'; +import { IntervalVariableModel, VariableRefresh } from 'app/features/variables/types'; export class IntervalVariableBuilder extends OptionsVariableBuilder { withRefresh(refresh: VariableRefresh) { diff --git a/public/app/features/variables/shared/testing/multiVariableBuilder.ts b/public/app/features/variables/shared/testing/multiVariableBuilder.ts index 2599bfa90f9..2bc67bef70d 100644 --- a/public/app/features/variables/shared/testing/multiVariableBuilder.ts +++ b/public/app/features/variables/shared/testing/multiVariableBuilder.ts @@ -1,4 +1,4 @@ -import { VariableWithMultiSupport } from 'app/features/templating/types'; +import { VariableWithMultiSupport } from 'app/features/variables/types'; import { OptionsVariableBuilder } from './optionsVariableBuilder'; export class MultiVariableBuilder extends OptionsVariableBuilder { diff --git a/public/app/features/variables/shared/testing/optionsVariableBuilder.ts b/public/app/features/variables/shared/testing/optionsVariableBuilder.ts index 859906004bd..b3ef1e14300 100644 --- a/public/app/features/variables/shared/testing/optionsVariableBuilder.ts +++ b/public/app/features/variables/shared/testing/optionsVariableBuilder.ts @@ -1,4 +1,4 @@ -import { VariableOption, VariableWithOptions } from 'app/features/templating/types'; +import { VariableOption, VariableWithOptions } from 'app/features/variables/types'; import { VariableBuilder } from './variableBuilder'; export class OptionsVariableBuilder extends VariableBuilder { diff --git a/public/app/features/variables/shared/testing/variableBuilder.ts b/public/app/features/variables/shared/testing/variableBuilder.ts index 21f03c0b493..89bc1a70b3b 100644 --- a/public/app/features/variables/shared/testing/variableBuilder.ts +++ b/public/app/features/variables/shared/testing/variableBuilder.ts @@ -1,5 +1,5 @@ import cloneDeep from 'lodash/cloneDeep'; -import { VariableModel } from 'app/features/templating/types'; +import { VariableModel } from 'app/features/variables/types'; export class VariableBuilder { protected variable: T; diff --git a/public/app/features/variables/state/actions.test.ts b/public/app/features/variables/state/actions.test.ts index 3390ed2f063..9eeff9226f3 100644 --- a/public/app/features/variables/state/actions.test.ts +++ b/public/app/features/variables/state/actions.test.ts @@ -559,13 +559,12 @@ describe('shared actions', () => { const templating: any = { list: [constant] }; const uid = 'uid'; const dashboard: any = { title: 'Some dash', uid, templating }; - const variableSrv: any = {}; describe('when called and the previous dashboard has completed', () => { it('then correct actions are dispatched', async () => { const tester = await reduxTester() .givenRootReducer(getRootReducer()) - .whenAsyncActionIsDispatched(initVariablesTransaction(uid, dashboard, variableSrv)); + .whenAsyncActionIsDispatched(initVariablesTransaction(uid, dashboard)); tester.thenDispatchedActionsShouldEqual( variablesInitTransaction({ uid }), @@ -593,7 +592,7 @@ describe('shared actions', () => { } as unknown) as ReducersUsedInContext, }) .givenRootReducer(getRootReducer()) - .whenAsyncActionIsDispatched(initVariablesTransaction(uid, dashboard, variableSrv)); + .whenAsyncActionIsDispatched(initVariablesTransaction(uid, dashboard)); tester.thenDispatchedActionsShouldEqual( cleanVariables(), diff --git a/public/app/features/variables/state/actions.ts b/public/app/features/variables/state/actions.ts index 04f3f87e38a..2ac74fb748e 100644 --- a/public/app/features/variables/state/actions.ts +++ b/public/app/features/variables/state/actions.ts @@ -9,7 +9,7 @@ import { VariableRefresh, VariableWithMultiSupport, VariableWithOptions, -} from '../../templating/types'; +} from '../types'; import { StoreState, ThunkResult } from '../../../types'; import { getVariable, getVariables } from './selectors'; import { variableAdapters } from '../adapters'; @@ -31,9 +31,7 @@ import { alignCurrentWithMulti } from '../shared/multiOptions'; import { isMulti } from '../guard'; import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv'; import { DashboardModel } from 'app/features/dashboard/state'; -import { getConfig } from '../../../core/config'; import { createErrorNotification } from '../../../core/copy/appNotification'; -import { VariableSrv } from '../../templating/variable_srv'; import { TransactionStatus, variablesClearTransaction, @@ -42,6 +40,7 @@ import { } from './transactionReducer'; import { getBackendSrv } from '../../../core/services/backend_srv'; import { cleanVariables } from './variablesReducer'; +import isEqual from 'lodash/isEqual'; // process flow queryVariable // thunk => processVariables @@ -127,7 +126,7 @@ export const completeDashboardTemplating = (dashboard: DashboardModel): ThunkRes export const changeVariableMultiValue = (identifier: VariableIdentifier, multi: boolean): ThunkResult => { return (dispatch, getState) => { - const variable = getVariable(identifier.id!, getState()); + const variable = getVariable(identifier.id, getState()); const current = alignCurrentWithMulti(variable.current, multi); dispatch(changeVariableProp(toVariablePayload(identifier, { propName: 'multi', propValue: multi }))); @@ -158,7 +157,7 @@ export const processVariable = ( queryParams: UrlQueryMap ): ThunkResult> => { return async (dispatch, getState) => { - const variable = getVariable(identifier.id!, getState()); + const variable = getVariable(identifier.id, getState()); await processVariableDependencies(variable, getState()); const urlValue = queryParams['var-' + variable.name]; @@ -204,14 +203,14 @@ export const setOptionFromUrl = ( urlValue: UrlQueryValue ): ThunkResult> => { return async (dispatch, getState) => { - const variable = getVariable(identifier.id!, getState()); + const variable = getVariable(identifier.id, getState()); if (variable.hasOwnProperty('refresh') && (variable as QueryVariableModel).refresh !== VariableRefresh.never) { // updates options await variableAdapters.get(variable.type).updateOptions(variable); } // get variable from state - const variableFromState = getVariable(variable.id!, getState()); + const variableFromState = getVariable(variable.id, getState()); if (!variableFromState) { throw new Error(`Couldn't find variable with name: ${variable.name}`); } @@ -288,7 +287,7 @@ export const validateVariableSelectionState = ( defaultValue?: string ): ThunkResult> => { return (dispatch, getState) => { - const variableInState = getVariable(identifier.id!, getState()); + const variableInState = getVariable(identifier.id, getState()); const current = variableInState.current || (({} as unknown) as VariableOption); const setValue = variableAdapters.get(variableInState.type).setValue; @@ -377,7 +376,7 @@ export const variableUpdated = ( ): ThunkResult> => { return (dispatch, getState) => { // if there is a variable lock ignore cascading update because we are in a boot up scenario - const variable = getVariable(identifier.id!, getState()); + const variable = getVariable(identifier.id, getState()); if (variable.initLock) { return Promise.resolve(); } @@ -430,7 +429,7 @@ export const onTimeRangeUpdated = ( const promises = variablesThatNeedRefresh.map(async (variable: VariableWithOptions) => { const previousOptions = variable.options.slice(); await variableAdapters.get(variable.type).updateOptions(variable); - const updatedVariable = getVariable(variable.id!, getState()); + const updatedVariable = getVariable(variable.id, getState()); if (angular.toJson(previousOptions) !== angular.toJson(updatedVariable.options)) { const dashboard = getState().dashboard.getModel(); dashboard?.templateVariableValueUpdated(); @@ -447,6 +446,31 @@ export const onTimeRangeUpdated = ( } }; +export const templateVarsChangedInUrl = (vars: UrlQueryMap): ThunkResult => async (dispatch, getState) => { + const update: Array> = []; + for (const variable of getVariables(getState())) { + const key = `var-${variable.name}`; + if (vars.hasOwnProperty(key)) { + if (isVariableUrlValueDifferentFromCurrent(variable, vars[key])) { + const promise = variableAdapters.get(variable.type).setValueFromUrl(variable, vars[key]); + update.push(promise); + } + } + } + + if (update.length) { + await Promise.all(update); + const dashboard = getState().dashboard.getModel(); + dashboard?.templateVariableValueUpdated(); + dashboard?.startRefresh(); + } +}; + +const isVariableUrlValueDifferentFromCurrent = (variable: VariableModel, urlValue: any): boolean => { + // lodash isEqual handles array of value equality checks as well + return !isEqual(variableAdapters.get(variable.type).getValueForUrl(variable), urlValue); +}; + const getQueryWithVariables = (getState: () => StoreState): UrlQueryMap => { const queryParams = getState().location.query; @@ -469,11 +493,10 @@ const getQueryWithVariables = (getState: () => StoreState): UrlQueryMap => { return queryParamsNew; }; -export const initVariablesTransaction = ( - dashboardUid: string, - dashboard: DashboardModel, - variableSrv: VariableSrv -): ThunkResult => async (dispatch, getState) => { +export const initVariablesTransaction = (dashboardUid: string, dashboard: DashboardModel): ThunkResult => async ( + dispatch, + getState +) => { try { const transactionState = getState().templating.transaction; if (transactionState.status === TransactionStatus.Fetching) { @@ -483,17 +506,9 @@ export const initVariablesTransaction = ( dispatch(variablesInitTransaction({ uid: dashboardUid })); - const newVariables = getConfig().featureToggles.newVariables; - - if (!newVariables) { - await variableSrv.init(dashboard); - } - - if (newVariables) { - dispatch(initDashboardTemplating(dashboard.templating.list)); - await dispatch(processVariables()); - dispatch(completeDashboardTemplating(dashboard)); - } + dispatch(initDashboardTemplating(dashboard.templating.list)); + await dispatch(processVariables()); + dispatch(completeDashboardTemplating(dashboard)); dispatch(variablesCompleteTransaction({ uid: dashboardUid })); } catch (err) { diff --git a/public/app/features/variables/state/helpers.ts b/public/app/features/variables/state/helpers.ts index c193f5ae3e4..0bb31ee4551 100644 --- a/public/app/features/variables/state/helpers.ts +++ b/public/app/features/variables/state/helpers.ts @@ -1,7 +1,7 @@ import { combineReducers } from '@reduxjs/toolkit'; import { NEW_VARIABLE_ID } from './types'; -import { VariableHide, VariableModel } from '../../templating/types'; +import { VariableHide, VariableModel } from '../types'; import { VariablesState } from './variablesReducer'; import { locationReducer } from '../../../core/reducers/location'; import { VariableAdapter } from '../adapters'; @@ -24,6 +24,7 @@ export const getVariableState = ( index, label: `Label-${index}`, skipUrlSync: false, + global: false, }; } @@ -36,6 +37,7 @@ export const getVariableState = ( index: noOfVariables, label: `Label-${NEW_VARIABLE_ID}`, skipUrlSync: false, + global: false, }; } diff --git a/public/app/features/variables/state/onTimeRangeUpdated.test.ts b/public/app/features/variables/state/onTimeRangeUpdated.test.ts index 8622e1bd7c1..7f4c055333a 100644 --- a/public/app/features/variables/state/onTimeRangeUpdated.test.ts +++ b/public/app/features/variables/state/onTimeRangeUpdated.test.ts @@ -8,7 +8,7 @@ import { DashboardState } from '../../../types'; import { createIntervalVariableAdapter } from '../interval/adapter'; import { variableAdapters } from '../adapters'; import { createConstantVariableAdapter } from '../constant/adapter'; -import { VariableRefresh } from '../../templating/types'; +import { VariableRefresh } from '../types'; import { constantBuilder, intervalBuilder } from '../shared/testing/builders'; variableAdapters.setInit(() => [createIntervalVariableAdapter(), createConstantVariableAdapter()]); diff --git a/public/app/features/variables/state/processVariable.test.ts b/public/app/features/variables/state/processVariable.test.ts index 2614fc58884..32e3aaa30bb 100644 --- a/public/app/features/variables/state/processVariable.test.ts +++ b/public/app/features/variables/state/processVariable.test.ts @@ -9,7 +9,7 @@ import { TemplatingState } from 'app/features/variables/state/reducers'; import { initDashboardTemplating, processVariable } from './actions'; import { resolveInitLock, setCurrentVariableValue } from './sharedReducer'; import { toVariableIdentifier, toVariablePayload } from './types'; -import { VariableRefresh } from '../../templating/types'; +import { VariableRefresh } from '../types'; import { updateVariableOptions } from '../query/reducer'; import { customBuilder, queryBuilder } from '../shared/testing/builders'; diff --git a/public/app/features/variables/state/reducers.test.ts b/public/app/features/variables/state/reducers.test.ts index 03ac4876b4d..2b10e687c23 100644 --- a/public/app/features/variables/state/reducers.test.ts +++ b/public/app/features/variables/state/reducers.test.ts @@ -1,5 +1,5 @@ import { reducerTester } from '../../../../test/core/redux/reducerTester'; -import { QueryVariableModel, VariableHide } from '../../templating/types'; +import { QueryVariableModel, VariableHide } from '../types'; import { VariableAdapter, variableAdapters } from '../adapters'; import { createAction } from '@reduxjs/toolkit'; import { cleanVariables, variablesReducer, VariablesState } from './variablesReducer'; @@ -36,6 +36,7 @@ describe('variablesReducer', () => { index: 0, label: 'Label-0', skipUrlSync: false, + global: false, }, '1': { id: '1', @@ -55,6 +56,7 @@ describe('variablesReducer', () => { index: 2, label: 'Label-2', skipUrlSync: false, + global: false, }, '3': { id: '3', @@ -107,6 +109,7 @@ describe('variablesReducer', () => { index: 0, label: 'Label-0', skipUrlSync: false, + global: false, }, }; variableAdapters.get('mock').reducer = jest.fn().mockReturnValue(initialState); @@ -134,6 +137,7 @@ describe('variablesReducer', () => { index: 0, label: 'Label-0', skipUrlSync: false, + global: false, }, }; variableAdapters.get('mock').reducer = jest.fn().mockReturnValue(initialState); @@ -157,6 +161,7 @@ describe('variablesReducer', () => { index: 0, label: 'Label-0', skipUrlSync: false, + global: false, }, }; variableAdapters.get('mock').reducer = jest.fn().mockReturnValue(initialState); diff --git a/public/app/features/variables/state/reducers.ts b/public/app/features/variables/state/reducers.ts index 8f3784c5e1d..e4fb24cde11 100644 --- a/public/app/features/variables/state/reducers.ts +++ b/public/app/features/variables/state/reducers.ts @@ -2,7 +2,7 @@ import { combineReducers } from '@reduxjs/toolkit'; import { optionsPickerReducer, OptionsPickerState } from '../pickers/OptionsPicker/reducer'; import { variableEditorReducer, VariableEditorState } from '../editor/reducer'; import { variablesReducer } from './variablesReducer'; -import { VariableModel } from '../../templating/types'; +import { VariableModel } from '../types'; import { transactionReducer, TransactionState } from './transactionReducer'; export interface TemplatingState { diff --git a/public/app/features/variables/state/selectors.ts b/public/app/features/variables/state/selectors.ts index 9136e268e56..4df3a53ccbe 100644 --- a/public/app/features/variables/state/selectors.ts +++ b/public/app/features/variables/state/selectors.ts @@ -1,5 +1,5 @@ import { StoreState } from '../../../types'; -import { VariableModel } from '../../templating/types'; +import { VariableModel } from '../types'; import { getState } from '../../../store/store'; import { NEW_VARIABLE_ID } from './types'; @@ -21,7 +21,7 @@ export const getVariable = ( export const getFilteredVariables = (filter: (model: VariableModel) => boolean, state: StoreState = getState()) => { return Object.values(state.templating.variables) .filter(filter) - .sort((s1, s2) => s1.index! - s2.index!); + .sort((s1, s2) => s1.index - s2.index); }; export const getVariableWithName = (name: string, state: StoreState = getState()) => { @@ -29,7 +29,7 @@ export const getVariableWithName = (name: string, state: StoreState = getState() }; export const getVariables = (state: StoreState = getState(), includeNewVariable = false): VariableModel[] => { - return getFilteredVariables(variable => (includeNewVariable ? true : variable.id! !== NEW_VARIABLE_ID), state); + return getFilteredVariables(variable => (includeNewVariable ? true : variable.id !== NEW_VARIABLE_ID), state); }; export type GetVariables = typeof getVariables; diff --git a/public/app/features/variables/state/sharedReducer.test.ts b/public/app/features/variables/state/sharedReducer.test.ts index 4ac2d2ef277..dd50ceafde5 100644 --- a/public/app/features/variables/state/sharedReducer.test.ts +++ b/public/app/features/variables/state/sharedReducer.test.ts @@ -15,7 +15,7 @@ import { sharedReducer, storeNewVariable, } from './sharedReducer'; -import { QueryVariableModel, VariableHide } from '../../templating/types'; +import { QueryVariableModel, VariableHide } from '../types'; import { ALL_VARIABLE_TEXT, ALL_VARIABLE_VALUE, NEW_VARIABLE_ID, toVariablePayload } from './types'; import { variableAdapters } from '../adapters'; import { createQueryVariableAdapter } from '../query/adapter'; @@ -68,6 +68,7 @@ describe('sharedReducer', () => { index: 0, label: 'Label-0', skipUrlSync: false, + global: false, }, '2': { id: '2', @@ -77,6 +78,7 @@ describe('sharedReducer', () => { index: 1, label: 'Label-2', skipUrlSync: false, + global: false, }, }); }); @@ -98,6 +100,7 @@ describe('sharedReducer', () => { index: 0, label: 'Label-0', skipUrlSync: false, + global: false, }, '2': { id: '2', @@ -107,6 +110,7 @@ describe('sharedReducer', () => { index: 2, label: 'Label-2', skipUrlSync: false, + global: false, }, }); }); @@ -128,6 +132,7 @@ describe('sharedReducer', () => { index: 0, label: 'Label-0', skipUrlSync: false, + global: false, }, '1': { id: '1', @@ -137,6 +142,7 @@ describe('sharedReducer', () => { index: 1, label: 'Label-1', skipUrlSync: false, + global: false, }, '2': { id: '2', @@ -146,6 +152,7 @@ describe('sharedReducer', () => { index: 2, label: 'Label-2', skipUrlSync: false, + global: false, }, '11': { ...initialQueryVariableModelState, @@ -174,6 +181,7 @@ describe('sharedReducer', () => { index: 1, label: 'Label-0', skipUrlSync: false, + global: false, }, '1': { id: '1', @@ -183,6 +191,7 @@ describe('sharedReducer', () => { index: 0, label: 'Label-1', skipUrlSync: false, + global: false, }, '2': { id: '2', @@ -192,6 +201,7 @@ describe('sharedReducer', () => { index: 2, label: 'Label-2', skipUrlSync: false, + global: false, }, }); }); @@ -213,6 +223,7 @@ describe('sharedReducer', () => { index: 0, label: 'Label-0', skipUrlSync: false, + global: false, }, '1': { id: '1', @@ -222,6 +233,7 @@ describe('sharedReducer', () => { index: 1, label: 'Label-1', skipUrlSync: false, + global: false, }, '2': { id: '2', @@ -231,6 +243,7 @@ describe('sharedReducer', () => { index: 2, label: 'Label-2', skipUrlSync: false, + global: false, }, [NEW_VARIABLE_ID]: { id: NEW_VARIABLE_ID, @@ -240,6 +253,7 @@ describe('sharedReducer', () => { index: 3, label: `Label-${NEW_VARIABLE_ID}`, skipUrlSync: false, + global: false, }, [11]: { ...initialQueryVariableModelState, diff --git a/public/app/features/variables/state/sharedReducer.ts b/public/app/features/variables/state/sharedReducer.ts index d03866c9e6f..9c240e3bac5 100644 --- a/public/app/features/variables/state/sharedReducer.ts +++ b/public/app/features/variables/state/sharedReducer.ts @@ -3,7 +3,7 @@ import cloneDeep from 'lodash/cloneDeep'; import { default as lodashDefaults } from 'lodash/defaults'; import { VariableType } from '@grafana/data'; -import { VariableModel, VariableOption, VariableWithOptions } from '../../templating/types'; +import { VariableModel, VariableOption, VariableWithOptions } from '../types'; import { AddVariable, ALL_VARIABLE_VALUE, getInstanceState, NEW_VARIABLE_ID, VariablePayload } from './types'; import { variableAdapters } from '../adapters'; import { changeVariableNameSucceeded } from '../editor/reducer'; @@ -30,31 +30,29 @@ const sharedReducerSlice = createSlice({ state[id] = variable; }, addInitLock: (state: VariablesState, action: PayloadAction) => { - const instanceState = getInstanceState(state, action.payload.id!); + const instanceState = getInstanceState(state, action.payload.id); instanceState.initLock = new Deferred(); }, resolveInitLock: (state: VariablesState, action: PayloadAction) => { - const instanceState = getInstanceState(state, action.payload.id!); + const instanceState = getInstanceState(state, action.payload.id); if (!instanceState) { // we might have cancelled a batch so then this state has been removed return; } - instanceState.initLock?.resolve(); }, removeInitLock: (state: VariablesState, action: PayloadAction) => { - const instanceState = getInstanceState(state, action.payload.id!); + const instanceState = getInstanceState(state, action.payload.id); if (!instanceState) { // we might have cancelled a batch so then this state has been removed return; } - instanceState.initLock = null; }, removeVariable: (state: VariablesState, action: PayloadAction>) => { - delete state[action.payload.id!]; + delete state[action.payload.id]; if (!action.payload.data.reIndex) { return; } @@ -86,17 +84,17 @@ const sharedReducerSlice = createSlice({ const toVariable = variables.find(v => v.index === action.payload.data.toIndex); if (fromVariable) { - state[fromVariable.id!].index = action.payload.data.toIndex; + state[fromVariable.id].index = action.payload.data.toIndex; } if (toVariable) { - state[toVariable.id!].index = action.payload.data.fromIndex; + state[toVariable.id].index = action.payload.data.fromIndex; } }, storeNewVariable: (state: VariablesState, action: PayloadAction) => { - const id = action.payload.id!; + const id = action.payload.id; const emptyVariable = cloneDeep(state[NEW_VARIABLE_ID]); - state[id!] = { + state[id] = { ...cloneDeep(variableAdapters.get(action.payload.type).initialState), ...emptyVariable, id, @@ -105,9 +103,9 @@ const sharedReducerSlice = createSlice({ }, changeVariableType: (state: VariablesState, action: PayloadAction>) => { const { id } = action.payload; - const { label, name, index } = state[id!]; + const { label, name, index } = state[id]; - state[id!] = { + state[id] = { ...cloneDeep(variableAdapters.get(action.payload.data.newType).initialState), id: id, label, @@ -167,7 +165,7 @@ const sharedReducerSlice = createSlice({ state: VariablesState, action: PayloadAction> ) => { - const instanceState = getInstanceState(state, action.payload.id!); + const instanceState = getInstanceState(state, action.payload.id); (instanceState as Record)[action.payload.data.propName] = action.payload.data.propValue; }, }, diff --git a/public/app/features/variables/state/types.ts b/public/app/features/variables/state/types.ts index 6171a722747..532d8643101 100644 --- a/public/app/features/variables/state/types.ts +++ b/public/app/features/variables/state/types.ts @@ -1,4 +1,4 @@ -import { VariableModel } from '../../templating/types'; +import { VariableModel } from '../types'; import { VariablesState } from './variablesReducer'; import { VariableType } from '@grafana/data'; @@ -28,7 +28,7 @@ export interface AddVariable { } export const toVariableIdentifier = (variable: VariableModel): VariableIdentifier => { - return { type: variable.type, id: variable.id! }; + return { type: variable.type, id: variable.id }; }; export function toVariablePayload( @@ -40,5 +40,5 @@ export function toVariablePayload( obj: VariableIdentifier | VariableModel, data?: T ): VariablePayload { - return { type: obj.type, id: obj.id!, data: data as T }; + return { type: obj.type, id: obj.id, data: data as T }; } diff --git a/public/app/features/variables/state/variablesReducer.ts b/public/app/features/variables/state/variablesReducer.ts index 001e92ca5ae..d9616b6b86f 100644 --- a/public/app/features/variables/state/variablesReducer.ts +++ b/public/app/features/variables/state/variablesReducer.ts @@ -1,7 +1,7 @@ import { createAction, PayloadAction } from '@reduxjs/toolkit'; import { variableAdapters } from '../adapters'; import { sharedReducer } from './sharedReducer'; -import { VariableModel } from '../../templating/types'; +import { VariableModel } from '../types'; import { VariablePayload } from './types'; export interface VariablesState extends Record {} @@ -21,7 +21,7 @@ export const variablesReducer = ( } const variables = globalVariables.reduce((allVariables, state) => { - allVariables[state.id!] = state; + allVariables[state.id] = state; return allVariables; }, {} as Record); diff --git a/public/app/features/variables/textbox/TextBoxVariableEditor.tsx b/public/app/features/variables/textbox/TextBoxVariableEditor.tsx index d4de06a3951..a51ae295035 100644 --- a/public/app/features/variables/textbox/TextBoxVariableEditor.tsx +++ b/public/app/features/variables/textbox/TextBoxVariableEditor.tsx @@ -1,5 +1,5 @@ import React, { ChangeEvent, PureComponent } from 'react'; -import { TextBoxVariableModel } from '../../templating/types'; +import { TextBoxVariableModel } from '../types'; import { VariableEditorProps } from '../editor/types'; export interface Props extends VariableEditorProps {} diff --git a/public/app/features/variables/textbox/TextBoxVariablePicker.tsx b/public/app/features/variables/textbox/TextBoxVariablePicker.tsx index 2797782d2aa..515cccb7892 100644 --- a/public/app/features/variables/textbox/TextBoxVariablePicker.tsx +++ b/public/app/features/variables/textbox/TextBoxVariablePicker.tsx @@ -1,6 +1,6 @@ import React, { ChangeEvent, FocusEvent, KeyboardEvent, PureComponent } from 'react'; -import { TextBoxVariableModel } from '../../templating/types'; +import { TextBoxVariableModel } from '../types'; import { toVariablePayload } from '../state/types'; import { dispatch } from '../../../store/store'; import { variableAdapters } from '../adapters'; diff --git a/public/app/features/variables/textbox/actions.test.ts b/public/app/features/variables/textbox/actions.test.ts index 7caecd3f24d..cc9f7b494dd 100644 --- a/public/app/features/variables/textbox/actions.test.ts +++ b/public/app/features/variables/textbox/actions.test.ts @@ -4,7 +4,7 @@ import { reduxTester } from '../../../../test/core/redux/reduxTester'; import { TemplatingState } from 'app/features/variables/state/reducers'; import { setTextBoxVariableOptionsFromUrl, updateTextBoxVariableOptions } from './actions'; import { getRootReducer } from '../state/helpers'; -import { VariableOption } from '../../templating/types'; +import { VariableOption } from '../types'; import { toVariablePayload } from '../state/types'; import { createTextBoxOptions } from './reducer'; import { addVariable, changeVariableProp, setCurrentVariableValue } from '../state/sharedReducer'; diff --git a/public/app/features/variables/textbox/actions.ts b/public/app/features/variables/textbox/actions.ts index 94b5106d3e1..2874305acab 100644 --- a/public/app/features/variables/textbox/actions.ts +++ b/public/app/features/variables/textbox/actions.ts @@ -1,4 +1,4 @@ -import { TextBoxVariableModel } from '../../templating/types'; +import { TextBoxVariableModel } from '../types'; import { ThunkResult } from '../../../types'; import { getVariable } from '../state/selectors'; import { variableAdapters } from '../adapters'; @@ -12,7 +12,7 @@ export const updateTextBoxVariableOptions = (identifier: VariableIdentifier): Th return async (dispatch, getState) => { await dispatch(createTextBoxOptions(toVariablePayload(identifier))); - const variableInState = getVariable(identifier.id!, getState()); + const variableInState = getVariable(identifier.id, getState()); await variableAdapters.get(identifier.type).setValue(variableInState, variableInState.options[0], true); }; }; @@ -21,7 +21,7 @@ export const setTextBoxVariableOptionsFromUrl = ( identifier: VariableIdentifier, urlValue: UrlQueryValue ): ThunkResult => async (dispatch, getState) => { - const variableInState = getVariable(identifier.id!, getState()); + const variableInState = getVariable(identifier.id, getState()); dispatch(changeVariableProp(toVariablePayload(variableInState, { propName: 'query', propValue: urlValue }))); diff --git a/public/app/features/variables/textbox/adapter.ts b/public/app/features/variables/textbox/adapter.ts index f354e0f4ca5..a6e4b5bc3d6 100644 --- a/public/app/features/variables/textbox/adapter.ts +++ b/public/app/features/variables/textbox/adapter.ts @@ -1,6 +1,6 @@ import cloneDeep from 'lodash/cloneDeep'; -import { TextBoxVariableModel } from '../../templating/types'; +import { TextBoxVariableModel } from '../types'; import { initialTextBoxVariableModelState, textBoxVariableReducer } from './reducer'; import { dispatch } from '../../../store/store'; import { setOptionAsCurrent } from '../state/actions'; diff --git a/public/app/features/variables/textbox/reducer.test.ts b/public/app/features/variables/textbox/reducer.test.ts index f5c28f88c67..30a2b9ac678 100644 --- a/public/app/features/variables/textbox/reducer.test.ts +++ b/public/app/features/variables/textbox/reducer.test.ts @@ -4,7 +4,7 @@ import { getVariableTestContext } from '../state/helpers'; import { toVariablePayload } from '../state/types'; import { createTextBoxOptions, textBoxVariableReducer } from './reducer'; import { VariablesState } from '../state/variablesReducer'; -import { TextBoxVariableModel } from '../../templating/types'; +import { TextBoxVariableModel } from '../types'; import { createTextBoxVariableAdapter } from './adapter'; describe('textBoxVariableReducer', () => { diff --git a/public/app/features/variables/textbox/reducer.ts b/public/app/features/variables/textbox/reducer.ts index 7ced3babfe8..b371010e05c 100644 --- a/public/app/features/variables/textbox/reducer.ts +++ b/public/app/features/variables/textbox/reducer.ts @@ -1,6 +1,6 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; -import { TextBoxVariableModel, VariableHide, VariableOption } from '../../templating/types'; +import { TextBoxVariableModel, VariableHide, VariableOption } from '../types'; import { getInstanceState, NEW_VARIABLE_ID, VariablePayload } from '../state/types'; import { initialVariablesState, VariablesState } from '../state/variablesReducer'; @@ -24,7 +24,7 @@ export const textBoxVariableSlice = createSlice({ initialState: initialVariablesState, reducers: { createTextBoxOptions: (state: VariablesState, action: PayloadAction) => { - const instanceState = getInstanceState(state, action.payload.id!); + const instanceState = getInstanceState(state, action.payload.id); const option = { text: instanceState.query.trim(), value: instanceState.query.trim(), selected: false }; instanceState.options = [option]; instanceState.current = option; diff --git a/public/app/features/templating/types.ts b/public/app/features/variables/types.ts similarity index 74% rename from public/app/features/templating/types.ts rename to public/app/features/variables/types.ts index 0f5519e6e80..daf580dc66b 100644 --- a/public/app/features/templating/types.ts +++ b/public/app/features/variables/types.ts @@ -1,4 +1,3 @@ -import { assignModelProperties } from 'app/core/utils/model_utils'; import { Deferred } from '../../core/utils/deferred'; import { VariableModel as BaseVariableModel } from '@grafana/data'; @@ -93,33 +92,10 @@ export interface VariableWithOptions extends VariableModel { } export interface VariableModel extends BaseVariableModel { - id?: string; // only exists for variables in redux state - global?: boolean; // only exists for variables in redux state + id: string; + global: boolean; hide: VariableHide; skipUrlSync: boolean; - index?: number; + index: number; initLock?: Deferred | null; } - -export interface VariableActions { - setValue(option: any): any; - updateOptions(searchFilter?: string): any; - dependsOn(variable: any): any; - setValueFromUrl(urlValue: any): any; - getValueForUrl(): any; - getSaveModel(): any; -} - -export type CtorType = new (...args: any[]) => {}; - -export interface VariableTypes { - [key: string]: { - name: string; - ctor: CtorType; - description: string; - supportsMulti?: boolean; - }; -} - -export let variableTypes: VariableTypes = {}; -export { assignModelProperties }; diff --git a/public/app/features/templating/utils.ts b/public/app/features/variables/utils.ts similarity index 100% rename from public/app/features/templating/utils.ts rename to public/app/features/variables/utils.ts diff --git a/public/app/partials/valueSelectDropdown.html b/public/app/partials/valueSelectDropdown.html deleted file mode 100644 index fbc9586e306..00000000000 --- a/public/app/partials/valueSelectDropdown.html +++ /dev/null @@ -1,72 +0,0 @@ - diff --git a/public/app/plugins/datasource/cloudwatch/components/MetricsQueryEditor.test.tsx b/public/app/plugins/datasource/cloudwatch/components/MetricsQueryEditor.test.tsx index afaa9281219..ea82bf060b8 100644 --- a/public/app/plugins/datasource/cloudwatch/components/MetricsQueryEditor.test.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/MetricsQueryEditor.test.tsx @@ -4,9 +4,9 @@ import { mount } from 'enzyme'; import { act } from 'react-dom/test-utils'; import { DataSourceInstanceSettings } from '@grafana/data'; import { TemplateSrv } from 'app/features/templating/template_srv'; -import { CustomVariable } from 'app/features/templating/all'; -import { MetricsQueryEditor, Props, normalizeQuery } from './MetricsQueryEditor'; +import { MetricsQueryEditor, normalizeQuery, Props } from './MetricsQueryEditor'; import { CloudWatchDatasource } from '../datasource'; +import { CustomVariableModel, VariableHide } from '../../../../features/variables/types'; const setup = () => { const instanceSettings = { @@ -14,23 +14,26 @@ const setup = () => { } as DataSourceInstanceSettings; const templateSrv = new TemplateSrv(); - templateSrv.init([ - new CustomVariable( - { - name: 'var3', - options: [ - { selected: true, value: 'var3-foo' }, - { selected: false, value: 'var3-bar' }, - { selected: true, value: 'var3-baz' }, - ], - current: { - value: ['var3-foo', 'var3-baz'], - }, - multi: true, - }, - {} as any - ), - ]); + const variable: CustomVariableModel = { + id: 'var3', + index: 0, + name: 'var3', + options: [ + { selected: true, value: 'var3-foo', text: 'var3-foo' }, + { selected: false, value: 'var3-bar', text: 'var3-bar' }, + { selected: true, value: 'var3-baz', text: 'var3-baz' }, + ], + current: { selected: true, value: ['var3-foo', 'var3-baz'], text: 'var3-foo + var3-baz' }, + multi: true, + includeAll: false, + query: '', + hide: VariableHide.dontHide, + type: 'custom', + label: null, + skipUrlSync: false, + global: false, + }; + templateSrv.init([variable]); const datasource = new CloudWatchDatasource(instanceSettings, templateSrv as any, {} as any); datasource.metricFindQuery = async () => [{ value: 'test', label: 'test' }]; diff --git a/public/app/plugins/datasource/cloudwatch/datasource.ts b/public/app/plugins/datasource/cloudwatch/datasource.ts index 0385af5cf96..1a77eb999d9 100644 --- a/public/app/plugins/datasource/cloudwatch/datasource.ts +++ b/public/app/plugins/datasource/cloudwatch/datasource.ts @@ -7,17 +7,17 @@ import { AppNotificationTimeout } from 'app/types'; import { store } from 'app/store/store'; import kbn from 'app/core/utils/kbn'; import { + DataFrame, DataQueryRequest, + DataQueryResponse, DataSourceApi, DataSourceInstanceSettings, dateMath, + LoadingState, + LogRowModel, ScopedVars, TimeRange, - DataFrame, - DataQueryResponse, - LoadingState, toDataFrame, - LogRowModel, } from '@grafana/data'; import { getBackendSrv, toDataQueryResponse } from '@grafana/runtime'; import { TemplateSrv } from 'app/features/templating/template_srv'; @@ -25,25 +25,25 @@ import { TimeSrv } from 'app/features/dashboard/services/TimeSrv'; import { ThrottlingErrorMessage } from './components/ThrottlingErrorMessage'; import memoizedDebounce from './memoizedDebounce'; import { - CloudWatchQuery, CloudWatchJsonData, - CloudWatchMetricsQuery, CloudWatchLogsQuery, CloudWatchLogsQueryStatus, + CloudWatchMetricsQuery, + CloudWatchQuery, DescribeLogGroupsRequest, - TSDBResponse, - MetricRequest, + GetLogEventsRequest, GetLogGroupFieldsRequest, GetLogGroupFieldsResponse, LogAction, - GetLogEventsRequest, MetricQuery, + MetricRequest, + TSDBResponse, } from './types'; -import { from, empty, Observable } from 'rxjs'; -import { delay, expand, map, mergeMap, tap, finalize, catchError } from 'rxjs/operators'; +import { empty, from, Observable } from 'rxjs'; +import { catchError, delay, expand, finalize, map, mergeMap, tap } from 'rxjs/operators'; import { CloudWatchLanguageProvider } from './language_provider'; -import { VariableWithMultiSupport } from 'app/features/templating/types'; +import { VariableWithMultiSupport } from 'app/features/variables/types'; import { RowContextOptions } from '@grafana/ui/src/components/Logs/LogRowContextProvider'; import { AwsUrl, encodeUrl } from './aws_url'; diff --git a/public/app/plugins/datasource/cloudwatch/specs/datasource.test.ts b/public/app/plugins/datasource/cloudwatch/specs/datasource.test.ts index dc2b6eec968..ca10ecd099e 100644 --- a/public/app/plugins/datasource/cloudwatch/specs/datasource.test.ts +++ b/public/app/plugins/datasource/cloudwatch/specs/datasource.test.ts @@ -1,15 +1,15 @@ import '../datasource'; import { CloudWatchDatasource, MAX_ATTEMPTS } from '../datasource'; import * as redux from 'app/store/store'; -import { DataSourceInstanceSettings, dateMath, getFrameDisplayName, DataFrame, DataQueryResponse } from '@grafana/data'; +import { DataFrame, DataQueryResponse, DataSourceInstanceSettings, dateMath, getFrameDisplayName } from '@grafana/data'; import { TemplateSrv } from 'app/features/templating/template_srv'; -import { CustomVariable } from 'app/features/templating/all'; -import { CloudWatchQuery, CloudWatchMetricsQuery, CloudWatchLogsQueryStatus, LogAction } from '../types'; +import { CloudWatchLogsQueryStatus, CloudWatchMetricsQuery, CloudWatchQuery, LogAction } from '../types'; import { backendSrv } from 'app/core/services/backend_srv'; // will use the version in __mocks__ import { TimeSrv } from 'app/features/dashboard/services/TimeSrv'; import { convertToStoreState } from '../../../../../test/helpers/convertToStoreState'; import { getTemplateSrvDependencies } from 'test/helpers/getTemplateSrvDependencies'; import { of } from 'rxjs'; +import { CustomVariableModel, VariableHide } from '../../../../features/variables/types'; jest.mock('rxjs/operators', () => { const operators = jest.requireActual('rxjs/operators'); @@ -313,18 +313,22 @@ describe('CloudWatchDatasource', () => { }); it('should generate the correct query with interval variable', async () => { - templateSrv.init([ - new CustomVariable( - { - name: 'period', - current: { - value: '10m', - }, - multi: false, - }, - {} as any - ), - ]); + const period: CustomVariableModel = { + id: 'period', + name: 'period', + index: 0, + current: { value: '10m', text: '10m', selected: true }, + options: [{ value: '10m', text: '10m', selected: true }], + multi: false, + includeAll: false, + query: '', + hide: VariableHide.dontHide, + type: 'custom', + label: null, + skipUrlSync: false, + global: false, + }; + templateSrv.init([period]); const query = { range: defaultTimeRange, @@ -667,58 +671,75 @@ describe('CloudWatchDatasource', () => { describe('When performing CloudWatch query with template variables', () => { let requestParams: { queries: CloudWatchMetricsQuery[] }; beforeEach(() => { - const variables = [ - new CustomVariable( - { - name: 'var1', - current: { - value: 'var1-foo', - }, - multi: false, - }, - {} as any - ), - new CustomVariable( - { - name: 'var2', - current: { - value: 'var2-foo', - }, - multi: false, - }, - {} as any - ), - new CustomVariable( - { - name: 'var3', - options: [ - { selected: true, value: 'var3-foo' }, - { selected: false, value: 'var3-bar' }, - { selected: true, value: 'var3-baz' }, - ], - current: { - value: ['var3-foo', 'var3-baz'], - }, - multi: true, - }, - {} as any - ), - new CustomVariable( - { - name: 'var4', - options: [ - { selected: true, value: 'var4-foo' }, - { selected: false, value: 'var4-bar' }, - { selected: true, value: 'var4-baz' }, - ], - current: { - value: ['var4-foo', 'var4-baz'], - }, - multi: true, - }, - {} as any - ), - ]; + const var1: CustomVariableModel = { + id: 'var1', + name: 'var1', + index: 0, + current: { value: 'var1-foo', text: 'var1-foo', selected: true }, + options: [{ value: 'var1-foo', text: 'var1-foo', selected: true }], + multi: false, + includeAll: false, + query: '', + hide: VariableHide.dontHide, + type: 'custom', + label: null, + skipUrlSync: false, + global: false, + }; + const var2: CustomVariableModel = { + id: 'var2', + name: 'var2', + index: 1, + current: { value: 'var2-foo', text: 'var2-foo', selected: true }, + options: [{ value: 'var2-foo', text: 'var2-foo', selected: true }], + multi: false, + includeAll: false, + query: '', + hide: VariableHide.dontHide, + type: 'custom', + label: null, + skipUrlSync: false, + global: false, + }; + const var3: CustomVariableModel = { + id: 'var3', + name: 'var3', + index: 2, + current: { value: ['var3-foo', 'var3-baz'], text: 'var3-foo + var3-baz', selected: true }, + options: [ + { selected: true, value: 'var3-foo', text: 'var3-foo' }, + { selected: false, value: 'var3-bar', text: 'var3-bar' }, + { selected: true, value: 'var3-baz', text: 'var3-baz' }, + ], + multi: true, + includeAll: false, + query: '', + hide: VariableHide.dontHide, + type: 'custom', + label: null, + skipUrlSync: false, + global: false, + }; + const var4: CustomVariableModel = { + id: 'var4', + name: 'var4', + index: 3, + options: [ + { selected: true, value: 'var4-foo', text: 'var4-foo' }, + { selected: false, value: 'var4-bar', text: 'var4-bar' }, + { selected: true, value: 'var4-baz', text: 'var4-baz' }, + ], + current: { value: ['var4-foo', 'var4-baz'], text: 'var4-foo + var4-baz', selected: true }, + multi: true, + includeAll: false, + query: '', + hide: VariableHide.dontHide, + type: 'custom', + label: null, + skipUrlSync: false, + global: false, + }; + const variables = [var1, var2, var3, var4]; const state = convertToStoreState(variables); const _templateSrv = new TemplateSrv(getTemplateSrvDependencies(state)); _templateSrv.init(variables); diff --git a/public/app/plugins/datasource/graphite/datasource.ts b/public/app/plugins/datasource/graphite/datasource.ts index 188727861ab..7a6af8474c3 100644 --- a/public/app/plugins/datasource/graphite/datasource.ts +++ b/public/app/plugins/datasource/graphite/datasource.ts @@ -16,7 +16,7 @@ import { TemplateSrv } from 'app/features/templating/template_srv'; // Types import { GraphiteOptions, GraphiteQuery, GraphiteType, MetricTankRequestMeta } from './types'; import { getRollupNotice, getRuntimeConsolidationNotice } from 'app/plugins/datasource/graphite/meta'; -import { getSearchFilterScopedVar } from '../../../features/templating/utils'; +import { getSearchFilterScopedVar } from '../../../features/variables/utils'; export class GraphiteDatasource extends DataSourceApi { basicAuth: string; diff --git a/public/app/plugins/datasource/loki/datasource.test.ts b/public/app/plugins/datasource/loki/datasource.test.ts index a806a6296fc..8a346c07f35 100644 --- a/public/app/plugins/datasource/loki/datasource.test.ts +++ b/public/app/plugins/datasource/loki/datasource.test.ts @@ -11,11 +11,12 @@ import { TimeRange, } from '@grafana/data'; import { TemplateSrv } from 'app/features/templating/template_srv'; -import { CustomVariable } from 'app/features/templating/custom_variable'; import { makeMockLokiDatasource } from './mocks'; import { of } from 'rxjs'; import omit from 'lodash/omit'; -import { backendSrv } from 'app/core/services/backend_srv'; // will use the version in __mocks__ +import { backendSrv } from 'app/core/services/backend_srv'; +import { CustomVariableModel } from '../../../features/variables/types'; +import { initialCustomVariableModelState } from '../../../features/variables/custom/reducer'; // will use the version in __mocks__ jest.mock('@grafana/runtime', () => ({ ...jest.requireActual('@grafana/runtime'), @@ -192,13 +193,13 @@ describe('LokiDatasource', () => { describe('When interpolating variables', () => { let ds: LokiDatasource; - let variable: CustomVariable; + let variable: CustomVariableModel; beforeEach(() => { const customData = { ...(instanceSettings.jsonData || {}), maxLines: 20 }; const customSettings = { ...instanceSettings, jsonData: customData }; ds = new LokiDatasource(customSettings, templateSrvMock); - variable = new CustomVariable({}, {} as any); + variable = { ...initialCustomVariableModelState }; }); it('should only escape single quotes', () => { diff --git a/public/app/plugins/datasource/mssql/specs/datasource.test.ts b/public/app/plugins/datasource/mssql/specs/datasource.test.ts index 3413f80edf9..41360bff6da 100644 --- a/public/app/plugins/datasource/mssql/specs/datasource.test.ts +++ b/public/app/plugins/datasource/mssql/specs/datasource.test.ts @@ -1,10 +1,10 @@ import { MssqlDatasource } from '../datasource'; import { TimeSrvStub } from 'test/specs/helpers'; -import { CustomVariable } from 'app/features/templating/custom_variable'; import { dateTime } from '@grafana/data'; import { TemplateSrv } from 'app/features/templating/template_srv'; -import { backendSrv } from 'app/core/services/backend_srv'; // will use the version in __mocks__ +import { backendSrv } from 'app/core/services/backend_srv'; +import { initialCustomVariableModelState } from '../../../../features/variables/custom/reducer'; // will use the version in __mocks__ jest.mock('@grafana/runtime', () => ({ ...jest.requireActual('@grafana/runtime'), @@ -245,7 +245,7 @@ describe('MSSQLDatasource', () => { describe('When interpolating variables', () => { beforeEach(() => { - ctx.variable = new CustomVariable({}, {} as any); + ctx.variable = { ...initialCustomVariableModelState }; }); describe('and value is a string', () => { diff --git a/public/app/plugins/datasource/mysql/datasource.ts b/public/app/plugins/datasource/mysql/datasource.ts index 063c1642bcf..416ca60b40c 100644 --- a/public/app/plugins/datasource/mysql/datasource.ts +++ b/public/app/plugins/datasource/mysql/datasource.ts @@ -7,7 +7,7 @@ import { TemplateSrv } from 'app/features/templating/template_srv'; import { TimeSrv } from 'app/features/dashboard/services/TimeSrv'; //Types import { MysqlQueryForInterpolation } from './types'; -import { getSearchFilterScopedVar } from '../../../features/templating/utils'; +import { getSearchFilterScopedVar } from '../../../features/variables/utils'; export class MysqlDatasource { id: any; diff --git a/public/app/plugins/datasource/mysql/query_ctrl.ts b/public/app/plugins/datasource/mysql/query_ctrl.ts index 3e666f8b75f..e47786d884a 100644 --- a/public/app/plugins/datasource/mysql/query_ctrl.ts +++ b/public/app/plugins/datasource/mysql/query_ctrl.ts @@ -9,7 +9,7 @@ import { auto } from 'angular'; import { TemplateSrv } from 'app/features/templating/template_srv'; import { CoreEvents } from 'app/types'; import { PanelEvents } from '@grafana/data'; -import { VariableWithMultiSupport } from 'app/features/templating/types'; +import { VariableWithMultiSupport } from 'app/features/variables/types'; import { getLocationSrv } from '@grafana/runtime'; export interface QueryMeta { diff --git a/public/app/plugins/datasource/mysql/specs/datasource.test.ts b/public/app/plugins/datasource/mysql/specs/datasource.test.ts index 06c5cd82af3..edd188df33a 100644 --- a/public/app/plugins/datasource/mysql/specs/datasource.test.ts +++ b/public/app/plugins/datasource/mysql/specs/datasource.test.ts @@ -1,8 +1,8 @@ import { MysqlDatasource } from '../datasource'; -import { CustomVariable } from 'app/features/templating/custom_variable'; import { dateTime, toUtc } from '@grafana/data'; import { backendSrv } from 'app/core/services/backend_srv'; // will use the version in __mocks__ import { TemplateSrv } from 'app/features/templating/template_srv'; +import { initialCustomVariableModelState } from '../../../../features/variables/custom/reducer'; jest.mock('@grafana/runtime', () => ({ ...jest.requireActual('@grafana/runtime'), @@ -295,7 +295,7 @@ describe('MySQLDatasource', () => { describe('When interpolating variables', () => { beforeEach(() => { - ctx.variable = new CustomVariable({}, {} as any); + ctx.variable = { ...initialCustomVariableModelState }; }); describe('and value is a string', () => { diff --git a/public/app/plugins/datasource/postgres/datasource.ts b/public/app/plugins/datasource/postgres/datasource.ts index 0536301361f..05b2fb81349 100644 --- a/public/app/plugins/datasource/postgres/datasource.ts +++ b/public/app/plugins/datasource/postgres/datasource.ts @@ -7,7 +7,7 @@ import { TemplateSrv } from 'app/features/templating/template_srv'; import { TimeSrv } from 'app/features/dashboard/services/TimeSrv'; //Types import { PostgresQueryForInterpolation } from './types'; -import { getSearchFilterScopedVar } from '../../../features/templating/utils'; +import { getSearchFilterScopedVar } from '../../../features/variables/utils'; export class PostgresDatasource { id: any; diff --git a/public/app/plugins/datasource/postgres/query_ctrl.ts b/public/app/plugins/datasource/postgres/query_ctrl.ts index d09413e600f..db83042454c 100644 --- a/public/app/plugins/datasource/postgres/query_ctrl.ts +++ b/public/app/plugins/datasource/postgres/query_ctrl.ts @@ -9,7 +9,7 @@ import { auto } from 'angular'; import { TemplateSrv } from 'app/features/templating/template_srv'; import { CoreEvents } from 'app/types'; import { PanelEvents } from '@grafana/data'; -import { VariableWithMultiSupport } from 'app/features/templating/types'; +import { VariableWithMultiSupport } from 'app/features/variables/types'; import { getLocationSrv } from '@grafana/runtime'; export interface QueryMeta { diff --git a/public/app/plugins/datasource/postgres/specs/datasource.test.ts b/public/app/plugins/datasource/postgres/specs/datasource.test.ts index 4c6f415408f..079ecc999ea 100644 --- a/public/app/plugins/datasource/postgres/specs/datasource.test.ts +++ b/public/app/plugins/datasource/postgres/specs/datasource.test.ts @@ -1,8 +1,8 @@ import { PostgresDatasource } from '../datasource'; -import { CustomVariable } from 'app/features/templating/custom_variable'; import { dateTime, toUtc } from '@grafana/data'; import { backendSrv } from 'app/core/services/backend_srv'; // will use the version in __mocks__ import { TemplateSrv } from 'app/features/templating/template_srv'; +import { initialCustomVariableModelState } from '../../../../features/variables/custom/reducer'; jest.mock('@grafana/runtime', () => ({ ...jest.requireActual('@grafana/runtime'), @@ -301,7 +301,7 @@ describe('PostgreSQLDatasource', () => { describe('When interpolating variables', () => { beforeEach(() => { - ctx.variable = new CustomVariable({}, {} as any); + ctx.variable = { ...initialCustomVariableModelState }; }); describe('and value is a string', () => { diff --git a/public/app/plugins/datasource/prometheus/datasource.test.ts b/public/app/plugins/datasource/prometheus/datasource.test.ts index f911b1648dd..c20c19cd14a 100644 --- a/public/app/plugins/datasource/prometheus/datasource.test.ts +++ b/public/app/plugins/datasource/prometheus/datasource.test.ts @@ -12,14 +12,14 @@ import { DataQueryResponseData, DataSourceInstanceSettings, dateTime, + getFieldDisplayName, LoadingState, toDataFrame, - getFieldDisplayName, } from '@grafana/data'; import { PromOptions, PromQuery } from './types'; import templateSrv from 'app/features/templating/template_srv'; import { getTimeSrv, TimeSrv } from 'app/features/dashboard/services/TimeSrv'; -import { CustomVariable } from 'app/features/templating/custom_variable'; +import { VariableHide } from '../../../features/variables/types'; const datasourceRequestMock = jest.fn().mockResolvedValue(createDefaultPromResponse()); @@ -447,9 +447,25 @@ describe('PrometheusDatasource', () => { }); describe('When interpolating variables', () => { - let customVariable: CustomVariable; + let customVariable: any; beforeEach(() => { - customVariable = new CustomVariable({}, {} as any); + customVariable = { + id: '', + global: false, + multi: false, + includeAll: false, + allValue: null, + query: '', + options: [], + current: {}, + name: '', + type: 'custom', + label: null, + hide: VariableHide.dontHide, + skipUrlSync: false, + index: -1, + initLock: null, + }; }); describe('and value is a string', () => { diff --git a/public/app/plugins/datasource/stackdriver/components/VariableQueryEditor.test.tsx b/public/app/plugins/datasource/stackdriver/components/VariableQueryEditor.test.tsx index 1f4c2949903..72120878c50 100644 --- a/public/app/plugins/datasource/stackdriver/components/VariableQueryEditor.test.tsx +++ b/public/app/plugins/datasource/stackdriver/components/VariableQueryEditor.test.tsx @@ -4,7 +4,7 @@ import renderer from 'react-test-renderer'; import { StackdriverVariableQueryEditor } from './VariableQueryEditor'; import { VariableQueryProps } from 'app/types/plugins'; import { MetricFindQueryTypes } from '../types'; -import { VariableModel } from 'app/features/templating/types'; +import { VariableModel } from 'app/features/variables/types'; jest.mock('../functions', () => ({ getMetricTypes: (): any => ({ metricTypes: [], selectedMetricType: '' }), @@ -46,6 +46,7 @@ describe('VariableQueryEditor', () => { }); describe('and a new variable is created', () => { + // these test need to be updated to reflect the changes from old variables system to new it('should trigger a query using the first query type in the array', done => { props.onChange = (query, definition) => { expect(definition).toBe('Stackdriver - Projects'); @@ -56,6 +57,7 @@ describe('VariableQueryEditor', () => { }); describe('and an existing variable is edited', () => { + // these test need to be updated to reflect the changes from old variables system to new it('should trigger new query using the saved query type', done => { props.query = { selectedQueryType: MetricFindQueryTypes.LabelKeys }; props.onChange = (query, definition) => { diff --git a/public/app/plugins/datasource/stackdriver/components/VariableQueryEditor.tsx b/public/app/plugins/datasource/stackdriver/components/VariableQueryEditor.tsx index 949fa96e0a7..2ea8141add6 100644 --- a/public/app/plugins/datasource/stackdriver/components/VariableQueryEditor.tsx +++ b/public/app/plugins/datasource/stackdriver/components/VariableQueryEditor.tsx @@ -3,7 +3,6 @@ import { VariableQueryProps } from 'app/types/plugins'; import { SimpleSelect } from './'; import { extractServicesFromMetricDescriptors, getLabelKeys, getMetricTypes } from '../functions'; import { MetricFindQueryTypes, VariableQueryData } from '../types'; -import { getConfig } from 'app/core/config'; export class StackdriverVariableQueryEditor extends PureComponent { queryTypes: Array<{ value: string; name: string }> = [ @@ -34,6 +33,7 @@ export class StackdriverVariableQueryEditor extends PureComponent ({ value, name: label })), ...(await this.getLabels(selectedMetricType, this.state.projectName)), sloServices: sloServices.map(({ value, label }: any) => ({ value, name: label })), + loading: false, }; - this.setState(state); + this.setState(state, () => this.onPropsChange()); } + onPropsChange = () => { + const { metricDescriptors, labels, metricTypes, services, ...queryModel } = this.state; + const query = this.queryTypes.find(q => q.value === this.state.selectedQueryType); + this.props.onChange(queryModel, `Stackdriver - ${query.name}`); + }; + async onQueryTypeChange(queryType: string) { const state: any = { selectedQueryType: queryType, @@ -144,15 +151,10 @@ export class StackdriverVariableQueryEditor extends PureComponent, prevState: Readonly) { + const selecQueryTypeChanged = prevState.selectedQueryType !== this.state.selectedQueryType; const selectSLOServiceChanged = this.state.selectedSLOService !== prevState.selectedSLOService; - if ( - !getConfig().featureToggles.newVariables || - prevState.selectedQueryType !== this.state.selectedQueryType || - selectSLOServiceChanged - ) { - const { metricDescriptors, labels, metricTypes, services, ...queryModel } = this.state; - const query = this.queryTypes.find(q => q.value === this.state.selectedQueryType); - this.props.onChange(queryModel, `Stackdriver - ${query.name}`); + if (selecQueryTypeChanged || selectSLOServiceChanged) { + this.onPropsChange(); } } @@ -286,6 +288,19 @@ export class StackdriverVariableQueryEditor extends PureComponent + Query Type +
    + +
    + + ); + } + return ( <> + - +
    + - - - - - - - - - - - - - -
    - , - "", -] + + + + `; diff --git a/public/app/plugins/datasource/stackdriver/specs/datasource.test.ts b/public/app/plugins/datasource/stackdriver/specs/datasource.test.ts index c1bf1124dcf..0f22fd4d1ae 100644 --- a/public/app/plugins/datasource/stackdriver/specs/datasource.test.ts +++ b/public/app/plugins/datasource/stackdriver/specs/datasource.test.ts @@ -1,11 +1,12 @@ import StackdriverDataSource from '../datasource'; import { metricDescriptors } from './testData'; import { TemplateSrv } from 'app/features/templating/template_srv'; -import { CustomVariable } from 'app/features/templating/all'; import { DataSourceInstanceSettings, toUtc } from '@grafana/data'; import { StackdriverOptions } from '../types'; import { backendSrv } from 'app/core/services/backend_srv'; // will use the version in __mocks__ import { TimeSrv } from 'app/features/dashboard/services/TimeSrv'; +import { CustomVariableModel } from '../../../../features/variables/types'; +import { initialCustomVariableModelState } from '../../../../features/variables/custom/reducer'; jest.mock('@grafana/runtime', () => ({ ...jest.requireActual('@grafana/runtime'), @@ -296,17 +297,14 @@ describe('StackdriverDataSource', () => { }); function initTemplateSrv(values: any, multi = false) { const templateSrv = new TemplateSrv(); - templateSrv.init([ - new CustomVariable( - { - name: 'test', - current: { - value: values, - }, - multi: multi, - }, - {} as any - ), - ]); + const test: CustomVariableModel = { + ...initialCustomVariableModelState, + id: 'test', + name: 'test', + current: { value: values, text: Array.isArray(values) ? values.toString() : values, selected: true }, + options: [{ value: values, text: Array.isArray(values) ? values.toString() : values, selected: false }], + multi, + }; + templateSrv.init([test]); return templateSrv; } diff --git a/public/app/plugins/datasource/stackdriver/types.ts b/public/app/plugins/datasource/stackdriver/types.ts index 09cdd2da9f4..7671d1767ed 100644 --- a/public/app/plugins/datasource/stackdriver/types.ts +++ b/public/app/plugins/datasource/stackdriver/types.ts @@ -39,6 +39,7 @@ export interface VariableQueryData { projects: Array<{ value: string; name: string }>; sloServices: Array<{ value: string; name: string }>; projectName: string; + loading: boolean; } export enum QueryType { diff --git a/public/app/plugins/datasource/testdata/datasource.ts b/public/app/plugins/datasource/testdata/datasource.ts index a86fd271190..7d335a833be 100644 --- a/public/app/plugins/datasource/testdata/datasource.ts +++ b/public/app/plugins/datasource/testdata/datasource.ts @@ -1,17 +1,17 @@ import { + ArrayDataFrame, + arrowTableToDataFrame, + base64StringToArrowTable, + DataFrame, DataQueryError, DataQueryRequest, DataQueryResponse, DataSourceApi, DataSourceInstanceSettings, + LoadingState, MetricFindValue, TableData, TimeSeries, - LoadingState, - ArrayDataFrame, - base64StringToArrowTable, - arrowTableToDataFrame, - DataFrame, } from '@grafana/data'; import { Scenario, TestDataQuery } from './types'; import { getBackendSrv, toDataQueryError } from '@grafana/runtime'; @@ -19,7 +19,7 @@ import { queryMetricTree } from './metricTree'; import { from, merge, Observable, of } from 'rxjs'; import { runStream } from './runStreams'; import templateSrv from 'app/features/templating/template_srv'; -import { getSearchFilterScopedVar } from 'app/features/templating/utils'; +import { getSearchFilterScopedVar } from 'app/features/variables/utils'; type TestData = TimeSeries | TableData; diff --git a/public/app/plugins/panel/table-old/module.ts b/public/app/plugins/panel/table-old/module.ts index f9f45a4dba9..b6b7a24d84a 100644 --- a/public/app/plugins/panel/table-old/module.ts +++ b/public/app/plugins/panel/table-old/module.ts @@ -1,15 +1,14 @@ import _ from 'lodash'; import $ from 'jquery'; import { MetricsPanelCtrl } from 'app/plugins/sdk'; -import config, { getConfig } from 'app/core/config'; +import config from 'app/core/config'; import { transformDataToTable } from './transformers'; import { tablePanelEditor } from './editor'; import { columnOptionsTab } from './column_options'; import { TableRenderer } from './renderer'; -import { isTableData, PanelEvents, PanelPlugin } from '@grafana/data'; +import { isTableData, PanelEvents, PanelPlugin, PanelProps } from '@grafana/data'; import { dispatch } from 'app/store/store'; import { ComponentType } from 'react'; -import { PanelProps } from '@grafana/data'; import { applyFilterFromTable } from 'app/features/variables/adhoc/actions'; export class TablePanelCtrl extends MetricsPanelCtrl { @@ -52,13 +51,7 @@ export class TablePanelCtrl extends MetricsPanelCtrl { }; /** @ngInject */ - constructor( - $scope: any, - $injector: any, - private annotationsSrv: any, - private $sanitize: any, - private variableSrv: any - ) { + constructor($scope: any, $injector: any, private annotationsSrv: any, private $sanitize: any) { super($scope, $injector); this.pageIndex = 0; @@ -242,11 +235,7 @@ export class TablePanelCtrl extends MetricsPanelCtrl { operator: filterData.operator, }; - if (getConfig().featureToggles.newVariables) { - dispatch(applyFilterFromTable(options)); - } else { - ctrl.variableSrv.setAdhocFilter(options); - } + dispatch(applyFilterFromTable(options)); } elem.on('click', '.table-panel-page-link', switchPage);