diff --git a/docs/sources/reference/templating.md b/docs/sources/reference/templating.md index 3ef32b1b10f..340384b62e5 100644 --- a/docs/sources/reference/templating.md +++ b/docs/sources/reference/templating.md @@ -245,6 +245,11 @@ summarize($myinterval, sum, false) 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 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). diff --git a/public/app/features/dashboard/dashboard_model.ts b/public/app/features/dashboard/dashboard_model.ts index 2ae2df0124b..0681e949c0c 100644 --- a/public/app/features/dashboard/dashboard_model.ts +++ b/public/app/features/dashboard/dashboard_model.ts @@ -9,6 +9,7 @@ import sortByKeys from 'app/core/utils/sort_by_keys'; import { PanelModel } from './panel_model'; import { DashboardMigrator } from './dashboard_migration'; +import { TimeRange } from '@grafana/ui/src'; export class DashboardModel { id: any; @@ -200,8 +201,8 @@ export class DashboardModel { this.events.emit('view-mode-changed', panel); } - timeRangeUpdated() { - this.events.emit('time-range-updated'); + timeRangeUpdated(timeRange: TimeRange) { + this.events.emit('time-range-updated', timeRange); } startRefresh() { diff --git a/public/app/features/dashboard/time_srv.ts b/public/app/features/dashboard/time_srv.ts index b4d18e0279a..1f7bdae6b04 100644 --- a/public/app/features/dashboard/time_srv.ts +++ b/public/app/features/dashboard/time_srv.ts @@ -147,7 +147,7 @@ export class TimeSrv { } refreshDashboard() { - this.dashboard.timeRangeUpdated(); + this.dashboard.timeRangeUpdated(this.timeRange()); } private startNextRefreshTimer(afterMs) { diff --git a/public/app/features/templating/editor_ctrl.ts b/public/app/features/templating/editor_ctrl.ts index fdab5587b42..c47c42a4aad 100644 --- a/public/app/features/templating/editor_ctrl.ts +++ b/public/app/features/templating/editor_ctrl.ts @@ -135,7 +135,7 @@ export class VariableEditorCtrl { $scope.runQuery().then(() => { $scope.reset(); $scope.mode = 'list'; - templateSrv.updateTemplateData(); + templateSrv.updateIndex(); }); } }; diff --git a/public/app/features/templating/specs/template_srv.test.ts b/public/app/features/templating/specs/template_srv.test.ts index 30faffea3be..9a041f42cd3 100644 --- a/public/app/features/templating/specs/template_srv.test.ts +++ b/public/app/features/templating/specs/template_srv.test.ts @@ -348,7 +348,7 @@ describe('templateSrv', () => { }); }); - describe('updateTemplateData with simple value', () => { + describe('updateIndex with simple value', () => { beforeEach(() => { initTemplateSrv([{ type: 'query', name: 'test', current: { value: 'muuuu' } }]); }); @@ -476,7 +476,7 @@ describe('templateSrv', () => { } ]); _templateSrv.setGrafanaVariable('$__auto_interval_interval', '13m'); - _templateSrv.updateTemplateData(); + _templateSrv.updateIndex(); }); it('should replace with text except for grafanaVariables', () => { diff --git a/public/app/features/templating/specs/variable_srv.test.ts b/public/app/features/templating/specs/variable_srv.test.ts index 47522073fb2..7e5d0ff98c7 100644 --- a/public/app/features/templating/specs/variable_srv.test.ts +++ b/public/app/features/templating/specs/variable_srv.test.ts @@ -8,7 +8,9 @@ describe('VariableSrv', function(this: any) { const ctx = { datasourceSrv: {}, timeSrv: { - timeRange: () => {}, + timeRange: () => { + return { from: '2018-01-29', to: '2019-01-29' }; + }, }, $rootScope: { $on: () => {}, @@ -21,7 +23,7 @@ describe('VariableSrv', function(this: any) { init: vars => { this.variables = vars; }, - updateTemplateData: () => {}, + updateIndex: () => {}, replace: str => str.replace(this.regex, match => { return match; @@ -45,7 +47,14 @@ describe('VariableSrv', function(this: any) { const ds: any = {}; 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.datasourceSrv = { diff --git a/public/app/features/templating/specs/variable_srv_init.test.ts b/public/app/features/templating/specs/variable_srv_init.test.ts index 0df6209bfdc..4e5e025522f 100644 --- a/public/app/features/templating/specs/variable_srv_init.test.ts +++ b/public/app/features/templating/specs/variable_srv_init.test.ts @@ -11,13 +11,19 @@ describe('VariableSrv init', function(this: any) { this.variables = vars; }, variableInitialized: () => {}, - updateTemplateData: () => {}, + updateIndex: () => {}, replace: str => str.replace(this.regex, match => { return match; }), }; + const timeSrv = { + timeRange: () => { + return { from: '2018-01-29', to: '2019-01-29' }; + }, + }; + const $injector = {} as any; const $rootscope = { $on: () => {}, @@ -47,7 +53,8 @@ describe('VariableSrv init', function(this: any) { templateSrv, }; - ctx.variableSrv = new VariableSrv($rootscope, $q, {}, $injector, templateSrv); + // @ts-ignore + ctx.variableSrv = new VariableSrv($rootscope, $q, {}, $injector, templateSrv, timeSrv); $injector.instantiate = (variable, model) => { return getVarMockConstructor(variable, model, ctx); diff --git a/public/app/features/templating/template_srv.ts b/public/app/features/templating/template_srv.ts index 2f8068137e5..de5efabcf23 100644 --- a/public/app/features/templating/template_srv.ts +++ b/public/app/features/templating/template_srv.ts @@ -1,6 +1,7 @@ import kbn from 'app/core/utils/kbn'; import _ from 'lodash'; import { variableRegex } from 'app/features/templating/variable'; +import { TimeRange } from '@grafana/ui/src'; function luceneEscape(value) { return value.replace(/([\!\*\+\-\=<>\s\&\|\(\)\[\]\{\}\^\~\?\:\\/"])/g, '\\$1'); @@ -13,6 +14,7 @@ export class TemplateSrv { private index = {}; private grafanaVariables = {}; private builtIns = {}; + private timeRange: TimeRange = null; constructor() { this.builtIns['__interval'] = { text: '1s', value: '1s' }; @@ -20,12 +22,13 @@ export class TemplateSrv { this.variables = []; } - init(variables) { + init(variables, timeRange?: TimeRange) { this.variables = variables; - this.updateTemplateData(); + this.timeRange = timeRange; + this.updateIndex(); } - updateTemplateData() { + updateIndex() { const existsOrEmpty = value => value || value === ''; this.index = this.variables.reduce((acc, currentValue) => { @@ -34,6 +37,26 @@ export class TemplateSrv { } 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) { @@ -81,8 +104,14 @@ export class TemplateSrv { // also the sub-delims "!", "'", "(", ")" and "*" are encoded; // unicode handling uses UTF-8 as in ECMA-262. encodeURIComponentStrict(str) { - return encodeURIComponent(str).replace(/[!'()*]/g, (c) => { - return '%' + c.charCodeAt(0).toString(16).toUpperCase(); + return encodeURIComponent(str).replace(/[!'()*]/g, c => { + return ( + '%' + + c + .charCodeAt(0) + .toString(16) + .toUpperCase() + ); }); } @@ -256,11 +285,11 @@ export class TemplateSrv { 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 => { if (scopedVars && scopedVars[variable.name] !== void 0) { if (scopedVars[variable.name].skipUrlSync) { diff --git a/public/app/features/templating/variable_srv.ts b/public/app/features/templating/variable_srv.ts index 896987de706..dff798ace29 100644 --- a/public/app/features/templating/variable_srv.ts +++ b/public/app/features/templating/variable_srv.ts @@ -6,23 +6,34 @@ import _ from 'lodash'; import coreModule from 'app/core/core_module'; import { variableTypes } from './variable'; 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 { - dashboard: any; - variables: any; + dashboard: DashboardModel; + variables: any[]; /** @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); } - init(dashboard) { + init(dashboard: DashboardModel) { this.dashboard = dashboard; this.dashboard.events.on('time-range-updated', this.onTimeRangeUpdated.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.templateSrv.init(this.variables, this.timeSrv.timeRange()); // init variables for (const variable of this.variables) { @@ -37,11 +48,12 @@ export class VariableSrv { }) ) .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 previousOptions = variable.options.slice(); @@ -100,14 +112,14 @@ export class VariableSrv { addVariable(variable) { this.variables.push(variable); - this.templateSrv.updateTemplateData(); + this.templateSrv.updateIndex(); this.dashboard.updateSubmenuVisibility(); } removeVariable(variable) { const index = _.indexOf(this.variables, variable); this.variables.splice(index, 1); - this.templateSrv.updateTemplateData(); + this.templateSrv.updateIndex(); this.dashboard.updateSubmenuVisibility(); } diff --git a/public/test/specs/helpers.ts b/public/test/specs/helpers.ts index 0345576d96c..536b277eec3 100644 --- a/public/test/specs/helpers.ts +++ b/public/test/specs/helpers.ts @@ -182,7 +182,7 @@ export function TemplateSrvStub(this: any) { return []; }; this.fillVariableValuesForUrl = () => {}; - this.updateTemplateData = () => {}; + this.updateIndex = () => {}; this.variableExists = () => { return false; };