Commit 67756c43 by Torkel Ödegaard Committed by GitHub

Merge pull request #15118 from grafana/1909/from-to-template-variables

Time range from & to as template variables
parents a21f6777 77bb3ddf
...@@ -245,6 +245,11 @@ summarize($myinterval, sum, false) ...@@ -245,6 +245,11 @@ summarize($myinterval, sum, false)
Grafana has global built-in variables that can be used in expressions in the query editor. Grafana has global built-in variables that can be used in expressions in the query editor.
### Time range variables
Grafana has two built in time range variables in `$__from` and `$__to`. They are currently always interpolated
as epoch milliseconds.
### The $__interval Variable ### The $__interval Variable
This $__interval variable is similar to the `auto` interval variable that is described above. It can be used as a parameter to group by time (for InfluxDB, MySQL, Postgres, MSSQL), Date histogram interval (for Elasticsearch) or as a *summarize* function parameter (for Graphite). This $__interval variable is similar to the `auto` interval variable that is described above. It can be used as a parameter to group by time (for InfluxDB, MySQL, Postgres, MSSQL), Date histogram interval (for Elasticsearch) or as a *summarize* function parameter (for Graphite).
......
...@@ -9,6 +9,7 @@ import sortByKeys from 'app/core/utils/sort_by_keys'; ...@@ -9,6 +9,7 @@ import sortByKeys from 'app/core/utils/sort_by_keys';
import { PanelModel } from './panel_model'; import { PanelModel } from './panel_model';
import { DashboardMigrator } from './dashboard_migration'; import { DashboardMigrator } from './dashboard_migration';
import { TimeRange } from '@grafana/ui/src';
export class DashboardModel { export class DashboardModel {
id: any; id: any;
...@@ -200,8 +201,8 @@ export class DashboardModel { ...@@ -200,8 +201,8 @@ export class DashboardModel {
this.events.emit('view-mode-changed', panel); this.events.emit('view-mode-changed', panel);
} }
timeRangeUpdated() { timeRangeUpdated(timeRange: TimeRange) {
this.events.emit('time-range-updated'); this.events.emit('time-range-updated', timeRange);
} }
startRefresh() { startRefresh() {
......
...@@ -147,7 +147,7 @@ export class TimeSrv { ...@@ -147,7 +147,7 @@ export class TimeSrv {
} }
refreshDashboard() { refreshDashboard() {
this.dashboard.timeRangeUpdated(); this.dashboard.timeRangeUpdated(this.timeRange());
} }
private startNextRefreshTimer(afterMs) { private startNextRefreshTimer(afterMs) {
......
...@@ -135,7 +135,7 @@ export class VariableEditorCtrl { ...@@ -135,7 +135,7 @@ export class VariableEditorCtrl {
$scope.runQuery().then(() => { $scope.runQuery().then(() => {
$scope.reset(); $scope.reset();
$scope.mode = 'list'; $scope.mode = 'list';
templateSrv.updateTemplateData(); templateSrv.updateIndex();
}); });
} }
}; };
......
...@@ -348,7 +348,7 @@ describe('templateSrv', () => { ...@@ -348,7 +348,7 @@ describe('templateSrv', () => {
}); });
}); });
describe('updateTemplateData with simple value', () => { describe('updateIndex with simple value', () => {
beforeEach(() => { beforeEach(() => {
initTemplateSrv([{ type: 'query', name: 'test', current: { value: 'muuuu' } }]); initTemplateSrv([{ type: 'query', name: 'test', current: { value: 'muuuu' } }]);
}); });
...@@ -476,7 +476,7 @@ describe('templateSrv', () => { ...@@ -476,7 +476,7 @@ describe('templateSrv', () => {
} }
]); ]);
_templateSrv.setGrafanaVariable('$__auto_interval_interval', '13m'); _templateSrv.setGrafanaVariable('$__auto_interval_interval', '13m');
_templateSrv.updateTemplateData(); _templateSrv.updateIndex();
}); });
it('should replace with text except for grafanaVariables', () => { it('should replace with text except for grafanaVariables', () => {
......
...@@ -8,7 +8,9 @@ describe('VariableSrv', function(this: any) { ...@@ -8,7 +8,9 @@ describe('VariableSrv', function(this: any) {
const ctx = { const ctx = {
datasourceSrv: {}, datasourceSrv: {},
timeSrv: { timeSrv: {
timeRange: () => {}, timeRange: () => {
return { from: '2018-01-29', to: '2019-01-29' };
},
}, },
$rootScope: { $rootScope: {
$on: () => {}, $on: () => {},
...@@ -21,7 +23,7 @@ describe('VariableSrv', function(this: any) { ...@@ -21,7 +23,7 @@ describe('VariableSrv', function(this: any) {
init: vars => { init: vars => {
this.variables = vars; this.variables = vars;
}, },
updateTemplateData: () => {}, updateIndex: () => {},
replace: str => replace: str =>
str.replace(this.regex, match => { str.replace(this.regex, match => {
return match; return match;
...@@ -45,7 +47,14 @@ describe('VariableSrv', function(this: any) { ...@@ -45,7 +47,14 @@ describe('VariableSrv', function(this: any) {
const ds: any = {}; const ds: any = {};
ds.metricFindQuery = () => Promise.resolve(scenario.queryResult); ds.metricFindQuery = () => Promise.resolve(scenario.queryResult);
ctx.variableSrv = new VariableSrv(ctx.$rootScope, $q, ctx.$location, ctx.$injector, ctx.templateSrv); ctx.variableSrv = new VariableSrv(
ctx.$rootScope,
$q,
ctx.$location,
ctx.$injector,
ctx.templateSrv,
ctx.timeSrv
);
ctx.variableSrv.timeSrv = ctx.timeSrv; ctx.variableSrv.timeSrv = ctx.timeSrv;
ctx.datasourceSrv = { ctx.datasourceSrv = {
......
...@@ -11,13 +11,19 @@ describe('VariableSrv init', function(this: any) { ...@@ -11,13 +11,19 @@ describe('VariableSrv init', function(this: any) {
this.variables = vars; this.variables = vars;
}, },
variableInitialized: () => {}, variableInitialized: () => {},
updateTemplateData: () => {}, updateIndex: () => {},
replace: str => replace: str =>
str.replace(this.regex, match => { str.replace(this.regex, match => {
return match; return match;
}), }),
}; };
const timeSrv = {
timeRange: () => {
return { from: '2018-01-29', to: '2019-01-29' };
},
};
const $injector = {} as any; const $injector = {} as any;
const $rootscope = { const $rootscope = {
$on: () => {}, $on: () => {},
...@@ -47,7 +53,8 @@ describe('VariableSrv init', function(this: any) { ...@@ -47,7 +53,8 @@ describe('VariableSrv init', function(this: any) {
templateSrv, templateSrv,
}; };
ctx.variableSrv = new VariableSrv($rootscope, $q, {}, $injector, templateSrv); // @ts-ignore
ctx.variableSrv = new VariableSrv($rootscope, $q, {}, $injector, templateSrv, timeSrv);
$injector.instantiate = (variable, model) => { $injector.instantiate = (variable, model) => {
return getVarMockConstructor(variable, model, ctx); return getVarMockConstructor(variable, model, ctx);
......
import kbn from 'app/core/utils/kbn'; import kbn from 'app/core/utils/kbn';
import _ from 'lodash'; import _ from 'lodash';
import { variableRegex } from 'app/features/templating/variable'; import { variableRegex } from 'app/features/templating/variable';
import { TimeRange } from '@grafana/ui/src';
function luceneEscape(value) { function luceneEscape(value) {
return value.replace(/([\!\*\+\-\=<>\s\&\|\(\)\[\]\{\}\^\~\?\:\\/"])/g, '\\$1'); return value.replace(/([\!\*\+\-\=<>\s\&\|\(\)\[\]\{\}\^\~\?\:\\/"])/g, '\\$1');
...@@ -13,6 +14,7 @@ export class TemplateSrv { ...@@ -13,6 +14,7 @@ export class TemplateSrv {
private index = {}; private index = {};
private grafanaVariables = {}; private grafanaVariables = {};
private builtIns = {}; private builtIns = {};
private timeRange: TimeRange = null;
constructor() { constructor() {
this.builtIns['__interval'] = { text: '1s', value: '1s' }; this.builtIns['__interval'] = { text: '1s', value: '1s' };
...@@ -20,12 +22,13 @@ export class TemplateSrv { ...@@ -20,12 +22,13 @@ export class TemplateSrv {
this.variables = []; this.variables = [];
} }
init(variables) { init(variables, timeRange?: TimeRange) {
this.variables = variables; this.variables = variables;
this.updateTemplateData(); this.timeRange = timeRange;
this.updateIndex();
} }
updateTemplateData() { updateIndex() {
const existsOrEmpty = value => value || value === ''; const existsOrEmpty = value => value || value === '';
this.index = this.variables.reduce((acc, currentValue) => { this.index = this.variables.reduce((acc, currentValue) => {
...@@ -34,6 +37,26 @@ export class TemplateSrv { ...@@ -34,6 +37,26 @@ export class TemplateSrv {
} }
return acc; return acc;
}, {}); }, {});
if (this.timeRange) {
const from = this.timeRange.from.valueOf().toString();
const to = this.timeRange.to.valueOf().toString();
this.index = {
...this.index,
['__from']: {
current: { value: from, text: from },
},
['__to']: {
current: { value: to, text: to },
},
};
}
}
updateTimeRange(timeRange: TimeRange) {
this.timeRange = timeRange;
this.updateIndex();
} }
variableInitialized(variable) { variableInitialized(variable) {
...@@ -81,8 +104,14 @@ export class TemplateSrv { ...@@ -81,8 +104,14 @@ export class TemplateSrv {
// also the sub-delims "!", "'", "(", ")" and "*" are encoded; // also the sub-delims "!", "'", "(", ")" and "*" are encoded;
// unicode handling uses UTF-8 as in ECMA-262. // unicode handling uses UTF-8 as in ECMA-262.
encodeURIComponentStrict(str) { encodeURIComponentStrict(str) {
return encodeURIComponent(str).replace(/[!'()*]/g, (c) => { return encodeURIComponent(str).replace(/[!'()*]/g, c => {
return '%' + c.charCodeAt(0).toString(16).toUpperCase(); return (
'%' +
c
.charCodeAt(0)
.toString(16)
.toUpperCase()
);
}); });
} }
...@@ -256,11 +285,11 @@ export class TemplateSrv { ...@@ -256,11 +285,11 @@ export class TemplateSrv {
const value = this.grafanaVariables[variable.current.value]; const value = this.grafanaVariables[variable.current.value];
return typeof(value) === 'string' ? value : variable.current.text; return typeof value === 'string' ? value : variable.current.text;
}); });
} }
fillVariableValuesForUrl(params, scopedVars) { fillVariableValuesForUrl(params, scopedVars?) {
_.each(this.variables, variable => { _.each(this.variables, variable => {
if (scopedVars && scopedVars[variable.name] !== void 0) { if (scopedVars && scopedVars[variable.name] !== void 0) {
if (scopedVars[variable.name].skipUrlSync) { if (scopedVars[variable.name].skipUrlSync) {
......
...@@ -6,23 +6,34 @@ import _ from 'lodash'; ...@@ -6,23 +6,34 @@ import _ from 'lodash';
import coreModule from 'app/core/core_module'; import coreModule from 'app/core/core_module';
import { variableTypes } from './variable'; import { variableTypes } from './variable';
import { Graph } from 'app/core/utils/dag'; import { Graph } from 'app/core/utils/dag';
import { TemplateSrv } from 'app/features/templating/template_srv';
import { TimeSrv } from 'app/features/dashboard/time_srv';
import { DashboardModel } from 'app/features/dashboard/dashboard_model';
// Types
import { TimeRange } from '@grafana/ui/src';
export class VariableSrv { export class VariableSrv {
dashboard: any; dashboard: DashboardModel;
variables: any; variables: any[];
/** @ngInject */ /** @ngInject */
constructor(private $rootScope, private $q, private $location, private $injector, private templateSrv) { constructor(private $rootScope,
private $q,
private $location,
private $injector,
private templateSrv: TemplateSrv,
private timeSrv: TimeSrv) {
$rootScope.$on('template-variable-value-updated', this.updateUrlParamsWithCurrentVariables.bind(this), $rootScope); $rootScope.$on('template-variable-value-updated', this.updateUrlParamsWithCurrentVariables.bind(this), $rootScope);
} }
init(dashboard) { init(dashboard: DashboardModel) {
this.dashboard = dashboard; this.dashboard = dashboard;
this.dashboard.events.on('time-range-updated', this.onTimeRangeUpdated.bind(this)); this.dashboard.events.on('time-range-updated', this.onTimeRangeUpdated.bind(this));
// create working class models representing variables // create working class models representing variables
this.variables = dashboard.templating.list = dashboard.templating.list.map(this.createVariableFromModel.bind(this)); this.variables = dashboard.templating.list = dashboard.templating.list.map(this.createVariableFromModel.bind(this));
this.templateSrv.init(this.variables); this.templateSrv.init(this.variables, this.timeSrv.timeRange());
// init variables // init variables
for (const variable of this.variables) { for (const variable of this.variables) {
...@@ -37,11 +48,12 @@ export class VariableSrv { ...@@ -37,11 +48,12 @@ export class VariableSrv {
}) })
) )
.then(() => { .then(() => {
this.templateSrv.updateTemplateData(); this.templateSrv.updateIndex();
}); });
} }
onTimeRangeUpdated() { onTimeRangeUpdated(timeRange: TimeRange) {
this.templateSrv.updateTimeRange(timeRange);
const promises = this.variables.filter(variable => variable.refresh === 2).map(variable => { const promises = this.variables.filter(variable => variable.refresh === 2).map(variable => {
const previousOptions = variable.options.slice(); const previousOptions = variable.options.slice();
...@@ -100,14 +112,14 @@ export class VariableSrv { ...@@ -100,14 +112,14 @@ export class VariableSrv {
addVariable(variable) { addVariable(variable) {
this.variables.push(variable); this.variables.push(variable);
this.templateSrv.updateTemplateData(); this.templateSrv.updateIndex();
this.dashboard.updateSubmenuVisibility(); this.dashboard.updateSubmenuVisibility();
} }
removeVariable(variable) { removeVariable(variable) {
const index = _.indexOf(this.variables, variable); const index = _.indexOf(this.variables, variable);
this.variables.splice(index, 1); this.variables.splice(index, 1);
this.templateSrv.updateTemplateData(); this.templateSrv.updateIndex();
this.dashboard.updateSubmenuVisibility(); this.dashboard.updateSubmenuVisibility();
} }
......
...@@ -182,7 +182,7 @@ export function TemplateSrvStub(this: any) { ...@@ -182,7 +182,7 @@ export function TemplateSrvStub(this: any) {
return []; return [];
}; };
this.fillVariableValuesForUrl = () => {}; this.fillVariableValuesForUrl = () => {};
this.updateTemplateData = () => {}; this.updateIndex = () => {};
this.variableExists = () => { this.variableExists = () => {
return false; return false;
}; };
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment