diff --git a/public/app/components/kbn.js b/public/app/components/kbn.js index a8bce04292a..5b9516bc82f 100644 --- a/public/app/components/kbn.js +++ b/public/app/components/kbn.js @@ -1,9 +1,8 @@ define([ 'jquery', 'lodash', - 'moment' ], -function($, _, moment) { +function($, _) { 'use strict'; var kbn = {}; diff --git a/public/app/core/directives/ng_model_on_blur.js b/public/app/core/directives/ng_model_on_blur.js index 1a066d1e4bd..5e32a28eb94 100644 --- a/public/app/core/directives/ng_model_on_blur.js +++ b/public/app/core/directives/ng_model_on_blur.js @@ -2,9 +2,8 @@ define([ 'kbn', 'app/core/core_module', 'app/core/utils/rangeutil', - 'moment', ], -function (kbn, coreModule, rangeUtil, moment) { +function (kbn, coreModule, rangeUtil) { 'use strict'; coreModule.directive('ngModelOnblur', function() { diff --git a/public/app/core/utils/rangeutil.ts b/public/app/core/utils/rangeutil.ts index a33c4ff5467..d5574f4b7e9 100644 --- a/public/app/core/utils/rangeutil.ts +++ b/public/app/core/utils/rangeutil.ts @@ -65,21 +65,21 @@ _.each(rangeOptions, function (frame) { // now/d // if no to then to now is assumed function describeTextRange(expr: string) { - let rangeExpr = 'now-' + expr + ' to now'; - if (expr.indexOf('now') === 0) { - rangeExpr = expr + ' to now'; + if (expr.indexOf('now') === -1) { + expr = 'now-' + expr; } - let opt = rangeIndex[rangeExpr]; + let opt = rangeIndex[expr + ' to now']; if (opt) { return opt; } - opt = {from: 'now-' + expr, to: 'now'}; + opt = {from: expr, to: 'now'}; - if (/^\d+\w$/.test(expr)) { - let unit = expr[expr.length - 1]; - let amount = parseInt(expr.substring(0, expr.length - 1)); + let parts = /^now-(\d+)(\w)/.exec(expr); + if (parts) { + let unit = parts[2]; + let amount = parseInt(parts[1]); let span = spans[unit]; if (span) { opt.display = 'Last ' + amount + ' ' + span.display; @@ -100,6 +100,10 @@ _.each(rangeOptions, function (frame) { if (option) { return option.display; } + if (range.to === 'now') { + return describeTextRange(range.from).display; + } + return "NA"; } diff --git a/public/app/features/dashboard/timeSrv.js b/public/app/features/dashboard/timeSrv.js index 3edc9106db0..d88b8fd41a4 100644 --- a/public/app/features/dashboard/timeSrv.js +++ b/public/app/features/dashboard/timeSrv.js @@ -123,13 +123,10 @@ define([ var _t = this.time; if(parse === false) { - return { - from: _t.from, - to: _t.to - }; + return { from: _t.from, to: _t.to }; } else { var _from = _t.from; - var _to = _t.to || new Date(); + var _to = _t.to || moment(); return { from: dateMath.parse(_from, false), diff --git a/public/app/features/dashboard/timepicker/timepicker.ts b/public/app/features/dashboard/timepicker/timepicker.ts index 5427cb2a308..f169f5b4167 100644 --- a/public/app/features/dashboard/timepicker/timepicker.ts +++ b/public/app/features/dashboard/timepicker/timepicker.ts @@ -35,14 +35,14 @@ export class TimePickerCtrl { init() { this.$scope.panel = this.$scope.dashboard.timepicker; + this.$scope.panel.now = false; _.defaults(this.$scope.panel, TimePickerCtrl.defaults); - var time = this.timeSrv.timeRange(true); - this.$scope.panel.now = false; + var time = this.timeSrv.timeRange(); + var timeRaw = this.timeSrv.timeRange(false); - var unparsed = this.timeSrv.timeRange(false); - if (_.isString(unparsed.to) && unparsed.to.indexOf('now') === 0) { + if (_.isString(timeRaw.to) && timeRaw.to.indexOf('now') === 0) { this.$scope.panel.now = true; } @@ -97,23 +97,14 @@ export class TimePickerCtrl { this.$scope.refreshMenuLeftSide = this.$scope.time.rangeString.length < 10; } - cloneTime(time) { - var _n = { from: _.clone(time.from), to: _.clone(time.to) }; - - // Create new dates as _.clone is shallow. - _n.from.date = new Date(_n.from.date); - _n.to.date = new Date(_n.to.date); - return _n; - } - customTime() { // Assume the form is valid since we're setting it to something valid this.$scope.input.$setValidity("dummy", true); - this.$scope.temptime = this.cloneTime(this.$scope.time); + this.$scope.temptime = angular.copy(this.$scope.time); this.$scope.temptime.now = this.$scope.panel.now; - this.$scope.temptime.from.date.setHours(0, 0, 0, 0); - this.$scope.temptime.to.date.setHours(0, 0, 0, 0); + // this.$scope.temptime.from.date.setHours(0, 0, 0, 0); + // this.$scope.temptime.to.date.setHours(0, 0, 0, 0); // Date picker needs the date to be at the start of the day if (new Date().getTimezoneOffset() < 0) { diff --git a/public/app/features/panel/panelHelper.js b/public/app/features/panel/panelHelper.js index c38c946a2a2..199434e6d1f 100644 --- a/public/app/features/panel/panelHelper.js +++ b/public/app/features/panel/panelHelper.js @@ -48,7 +48,8 @@ function (angular, dateMath, rangeUtil, _, kbn, $) { this.updateTimeRange = function(scope) { scope.range = timeSrv.timeRange(); - scope.rangeUnparsed = timeSrv.timeRange(false); + scope.rangeRaw = timeSrv.timeRange(false); + this.applyPanelTimeOverrides(scope); if (scope.panel.maxDataPoints) { @@ -57,6 +58,7 @@ function (angular, dateMath, rangeUtil, _, kbn, $) { else { scope.resolution = Math.ceil($(window).width() * (scope.panel.span / 12)); } + scope.interval = kbn.calculateInterval(scope.range, scope.resolution, scope.panel.interval); }; @@ -71,11 +73,11 @@ function (angular, dateMath, rangeUtil, _, kbn, $) { return; } - if (_.isString(scope.rangeUnparsed.from)) { + if (_.isString(scope.rangeRaw.from)) { var timeFromDate = dateMath.parse(timeFromInfo.from); scope.panelMeta.timeInfo = timeFromInfo.display; - scope.rangeUnparsed.from = timeFromInfo.from; - scope.rangeUnparsed.to = timeFromInfo.to; + scope.rangeRaw.from = timeFromInfo.from; + scope.rangeRaw.to = timeFromInfo.to; scope.range.from = timeFromDate; } } @@ -92,7 +94,7 @@ function (angular, dateMath, rangeUtil, _, kbn, $) { scope.range.from = dateMath.parseDateMath(timeShift, scope.range.from, false); scope.range.to = dateMath.parseDateMath(timeShift, scope.range.to, true); - scope.rangeUnparsed = scope.range; + scope.rangeRaw = scope.range; } if (scope.panel.hideTimeOverride) { @@ -102,9 +104,8 @@ function (angular, dateMath, rangeUtil, _, kbn, $) { this.issueMetricQuery = function(scope, datasource) { var metricsQuery = { - range: scope.rangeUnparsed, - timeFrom: scope.range.valueOf(), - timeTo: scope.range.valueOf(), + range: scope.range, + rangeRaw: scope.rangeRaw, interval: scope.interval, targets: scope.panel.targets, format: scope.panel.renderer === 'png' ? 'png' : 'json', diff --git a/public/app/panels/graph/module.js b/public/app/panels/graph/module.js index 9e8d1e66f8e..5cdeab799de 100644 --- a/public/app/panels/graph/module.js +++ b/public/app/panels/graph/module.js @@ -129,7 +129,7 @@ function (angular, $, _, kbn, moment, TimeSeries, PanelMeta) { $scope.refreshData = function(datasource) { panelHelper.updateTimeRange($scope); - $scope.annotationsPromise = annotationsSrv.getAnnotations($scope.rangeUnparsed, $scope.dashboard); + $scope.annotationsPromise = annotationsSrv.getAnnotations($scope.rangeRaw, $scope.dashboard); return panelHelper.issueMetricQuery($scope, datasource) .then($scope.dataHandler, function(err) { diff --git a/public/app/plugins/datasource/elasticsearch/datasource.js b/public/app/plugins/datasource/elasticsearch/datasource.js index 33b2b80c871..6b1616c8cb9 100644 --- a/public/app/plugins/datasource/elasticsearch/datasource.js +++ b/public/app/plugins/datasource/elasticsearch/datasource.js @@ -152,13 +152,13 @@ function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticRes var target; var sentTargets = []; - var header = this.getQueryHeader(options.timeFrom, options.timeTo); + var header = this.getQueryHeader(options.range.from, options.range.to); for (var i = 0; i < options.targets.length; i++) { target = options.targets[i]; if (target.hide) {return;} - var esQuery = this.queryBuilder.build(target, options.timeFrom, options.timeTo); + var esQuery = this.queryBuilder.build(target); payload += header + '\n'; payload += angular.toJson(esQuery) + '\n'; @@ -166,8 +166,8 @@ function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticRes } payload = payload.replace(/\$interval/g, options.interval); - payload = payload.replace(/\$timeFrom/g, options.timeFrom); - payload = payload.replace(/\$timeTo/g, options.timeTo); + payload = payload.replace(/\$timeFrom/g, options.range.from.valueOf()); + payload = payload.replace(/\$timeTo/g, options.range.to.valueOf()); payload = payload.replace(/\$maxDataPoints/g, options.maxDataPoints); payload = templateSrv.replace(payload, options.scopedVars); diff --git a/public/app/plugins/datasource/graphite/datasource.js b/public/app/plugins/datasource/graphite/datasource.js index 6cb769ae4f4..ad7adf2162c 100644 --- a/public/app/plugins/datasource/graphite/datasource.js +++ b/public/app/plugins/datasource/graphite/datasource.js @@ -29,8 +29,8 @@ function (angular, _, $, config, dateMath, moment) { GraphiteDatasource.prototype.query = function(options) { try { var graphOptions = { - from: this.translateTime(options.range.from, false), - until: this.translateTime(options.range.to, true), + from: this.translateTime(options.rangeRaw.from, false), + until: this.translateTime(options.rangeRaw.to, true), targets: options.targets, format: options.format, cacheTimeout: options.cacheTimeout || this.cacheTimeout, @@ -135,7 +135,8 @@ function (angular, _, $, config, dateMath, moment) { return this.doGraphiteRequest({ method: 'GET', - url: '/events/get_data?from=' + this.translateTime(options.range.from, false) + '&until=' + this.translateTime(options.range.to, true) + tags, + url: '/events/get_data?from=' + this.translateTime(options.range.from, false) + + '&until=' + this.translateTime(options.range.to, true) + tags, }); } catch(err) { @@ -159,16 +160,16 @@ function (angular, _, $, config, dateMath, moment) { date = moment.utc(date); + // graphite' s from filter is exclusive + // here we step back one minute in order + // to guarantee that we get all the data that + // exists for the specified range if (roundUp) { if (date.get('s')) { date.add(1, 'm'); } } else if (roundUp === false) { - // graphite' s from filter is exclusive - // here we step back one minute in order - // to guarantee that we get all the data that - // exists for the specified range if (date.get('s')) { date.subtract(1, 'm'); } @@ -187,14 +188,14 @@ function (angular, _, $, config, dateMath, moment) { } return this.doGraphiteRequest({method: 'GET', url: '/metrics/find/?query=' + interpolated }) - .then(function(results) { - return _.map(results.data, function(metric) { - return { - text: metric.text, - expandable: metric.expandable ? true : false - }; - }); + .then(function(results) { + return _.map(results.data, function(metric) { + return { + text: metric.text, + expandable: metric.expandable ? true : false + }; }); + }); }; GraphiteDatasource.prototype.testDatasource = function() { @@ -205,9 +206,9 @@ function (angular, _, $, config, dateMath, moment) { GraphiteDatasource.prototype.listDashboards = function(query) { return this.doGraphiteRequest({ method: 'GET', url: '/dashboard/find/', params: {query: query || ''} }) - .then(function(results) { - return results.data.dashboards; - }); + .then(function(results) { + return results.data.dashboards; + }); }; GraphiteDatasource.prototype.loadDashboard = function(dashName) { diff --git a/public/app/plugins/datasource/influxdb/datasource.js b/public/app/plugins/datasource/influxdb/datasource.js index 4e801534d0c..36cc80a15a0 100644 --- a/public/app/plugins/datasource/influxdb/datasource.js +++ b/public/app/plugins/datasource/influxdb/datasource.js @@ -1,13 +1,13 @@ define([ 'angular', 'lodash', - 'kbn', + 'app/core/utils/datemath', './influxSeries', './queryBuilder', './directives', './queryCtrl', ], -function (angular, _, kbn, InfluxSeries, InfluxQueryBuilder) { +function (angular, _, dateMath, InfluxSeries, InfluxQueryBuilder) { 'use strict'; var module = angular.module('grafana.services'); @@ -176,8 +176,8 @@ function (angular, _, kbn, InfluxSeries, InfluxQueryBuilder) { }; function getTimeFilter(options) { - var from = getInfluxTime(options.range.from); - var until = getInfluxTime(options.range.to); + var from = getInfluxTime(options.rangeRaw.from); + var until = getInfluxTime(options.rangeRaw.to); var fromIsAbsolute = from[from.length-1] === 's'; if (until === 'now()' && !fromIsAbsolute) { @@ -189,17 +189,15 @@ function (angular, _, kbn, InfluxSeries, InfluxQueryBuilder) { function getInfluxTime(date) { if (_.isString(date)) { - if (date.indexOf('now') >= 0) { + if (date === 'now') { + return 'now()'; + } + if (date.indexOf('now-') >= 0) { return date.replace('now', 'now()').replace('-', ' - '); } - date = kbn.parseDate(date); + date = dateMath.parse(date); } - - return to_utc_epoch_seconds(date); - } - - function to_utc_epoch_seconds(date) { - return (date.getTime() / 1000).toFixed(0) + 's'; + return (date.valueOf() / 1000).toFixed(0) + 's'; } return InfluxDatasource; diff --git a/public/app/plugins/datasource/influxdb_08/datasource.js b/public/app/plugins/datasource/influxdb_08/datasource.js index 5c1590168f1..1c3c5688b67 100644 --- a/public/app/plugins/datasource/influxdb_08/datasource.js +++ b/public/app/plugins/datasource/influxdb_08/datasource.js @@ -1,14 +1,14 @@ define([ 'angular', 'lodash', - 'kbn', + 'app/core/utils/datemath', './influxSeries', './queryBuilder', './directives', './queryCtrl', './funcEditor', ], -function (angular, _, kbn, InfluxSeries, InfluxQueryBuilder) { +function (angular, _, dateMath, InfluxSeries, InfluxQueryBuilder) { 'use strict'; var module = angular.module('grafana.services'); @@ -58,7 +58,7 @@ function (angular, _, kbn, InfluxSeries, InfluxQueryBuilder) { }; InfluxDatasource.prototype.annotationQuery = function(annotation, rangeUnparsed) { - var timeFilter = getTimeFilter({ range: rangeUnparsed }); + var timeFilter = getTimeFilter({ rangeRaw: rangeUnparsed }); var query = annotation.query.replace('$timeFilter', timeFilter); query = templateSrv.replace(query); @@ -187,13 +187,9 @@ function (angular, _, kbn, InfluxSeries, InfluxQueryBuilder) { return deferred.promise; }; - InfluxDatasource.prototype._getDashboardInternal = function(id, isTemp) { + InfluxDatasource.prototype._getDashboardInternal = function(id) { var queryString = 'select dashboard from "grafana.dashboard_' + btoa(id) + '"'; - if (isTemp) { - queryString = 'select dashboard from "grafana.temp_dashboard_' + btoa(id) + '"'; - } - return this._seriesQuery(queryString).then(function(results) { if (!results || !results.length) { return null; @@ -208,55 +204,19 @@ function (angular, _, kbn, InfluxSeries, InfluxQueryBuilder) { }); }; - InfluxDatasource.prototype.getDashboard = function(id, isTemp) { - var self = this; - return this._getDashboardInternal(id, isTemp).then(function(dashboard) { + InfluxDatasource.prototype.getDashboard = function(id) { + return this._getDashboardInternal(id).then(function(dashboard) { if (dashboard !== null) { return dashboard; } - - // backward compatible load for unslugified ids - var slug = kbn.slugifyForUrl(id); - if (slug !== id) { - return self.getDashboard(slug, isTemp); - } - throw "Dashboard not found"; }, function(err) { throw "Could not load dashboard, " + err.data; }); }; - InfluxDatasource.prototype.deleteDashboard = function(id) { - return this._seriesQuery('drop series "grafana.dashboard_' + btoa(id) + '"').then(function(results) { - if (!results) { - throw "Could not delete dashboard"; - } - return id; - }, function(err) { - throw "Could not delete dashboard, " + err.data; - }); - }; - - InfluxDatasource.prototype.searchDashboards = function(queryString) { - var influxQuery = 'select * from /grafana.dashboard_.*/ where '; - - var tagsOnly = queryString.indexOf('tags!:') === 0; - if (tagsOnly) { - var tagsQuery = queryString.substring(6, queryString.length); - influxQuery = influxQuery + 'tags =~ /.*' + tagsQuery + '.*/i'; - } - else { - var titleOnly = queryString.indexOf('title:') === 0; - if (titleOnly) { - var titleQuery = queryString.substring(6, queryString.length); - influxQuery = influxQuery + ' title =~ /.*' + titleQuery + '.*/i'; - } - else { - influxQuery = influxQuery + '(tags =~ /.*' + queryString + '.*/i or title =~ /.*' + queryString + '.*/i)'; - } - } - + InfluxDatasource.prototype.searchDashboards = function() { + var influxQuery = 'select * from /grafana.dashboard_.*/ '; return this._seriesQuery(influxQuery).then(function(results) { var hits = { dashboards: [], tags: [], tagsOnly: false }; @@ -266,20 +226,17 @@ function (angular, _, kbn, InfluxSeries, InfluxQueryBuilder) { for (var i = 0; i < results.length; i++) { var dashCol = _.indexOf(results[i].columns, 'title'); - var tagsCol = _.indexOf(results[i].columns, 'tags'); var idCol = _.indexOf(results[i].columns, 'id'); var hit = { id: results[i].points[0][dashCol], title: results[i].points[0][dashCol], - tags: results[i].points[0][tagsCol].split(",") }; if (idCol !== -1) { hit.id = results[i].points[0][idCol]; } - hit.tags = hit.tags[0] ? hit.tags : []; hits.dashboards.push(hit); } return hits; @@ -297,8 +254,8 @@ function (angular, _, kbn, InfluxSeries, InfluxQueryBuilder) { } function getTimeFilter(options) { - var from = getInfluxTime(options.range.from); - var until = getInfluxTime(options.range.to); + var from = getInfluxTime(options.rangeRaw.from); + var until = getInfluxTime(options.rangeRaw.to); var fromIsAbsolute = from[from.length-1] === 's'; if (until === 'now()' && !fromIsAbsolute) { @@ -310,18 +267,17 @@ function (angular, _, kbn, InfluxSeries, InfluxQueryBuilder) { function getInfluxTime(date) { if (_.isString(date)) { - return date.replace('now', 'now()'); + if (date === 'now') { + return 'now()'; + } + if (date.indexOf('now-') >= 0) { + return date.replace('now', 'now()'); + } + date = dateMath.parse(date); } - - return to_utc_epoch_seconds(date); - } - - function to_utc_epoch_seconds(date) { - return (date.getTime() / 1000).toFixed(0) + 's'; + return (date.valueOf() / 1000).toFixed(0) + 's'; } return InfluxDatasource; - }); - }); diff --git a/public/test/specs/core/utils/datemath_specs.ts b/public/test/specs/core/utils/datemath_specs.ts index 888da340c07..2bf67b7ec6d 100644 --- a/public/test/specs/core/utils/datemath_specs.ts +++ b/public/test/specs/core/utils/datemath_specs.ts @@ -116,6 +116,7 @@ describe("DateMath", () => { expect(date).to.equal(undefined); }); }); + }); export = {}; diff --git a/public/test/specs/core/utils/rangeutil_specs.ts b/public/test/specs/core/utils/rangeutil_specs.ts index 0c72be4f7dc..a1135a11b7e 100644 --- a/public/test/specs/core/utils/rangeutil_specs.ts +++ b/public/test/specs/core/utils/rangeutil_specs.ts @@ -6,8 +6,7 @@ import moment = require('moment') describe("rangeUtil", () => { - describe("Can get range explained", () => { - + describe("Can get range text described", () => { it('should handle simple old expression with only amount and unit', () => { var info = rangeUtil.describeTextRange('5m'); expect(info.display).to.be('Last 5 minutes') @@ -18,6 +17,12 @@ describe("rangeUtil", () => { expect(info.display).to.be('Last 1 hour') }); + it('should handle non default amount', () => { + var info = rangeUtil.describeTextRange('13h'); + expect(info.display).to.be('Last 13 hours') + expect(info.from).to.be('now-13h') + }); + it('should handle now/d', () => { var info = rangeUtil.describeTextRange('now/d'); expect(info.display).to.be('The day so far'); @@ -27,7 +32,19 @@ describe("rangeUtil", () => { var info = rangeUtil.describeTextRange('now/w'); expect(info.display).to.be('Week to date'); }); + }); + describe("Can get date range described", () => { + + it('Date range with simple ranges', () => { + var text = rangeUtil.describeTimeRange({from: 'now-1h', to: 'now'}); + expect(text).to.be('Last 1 hour') + }); + + it('Date range with non matching default ranges', () => { + var text = rangeUtil.describeTimeRange({from: 'now-13h', to: 'now'}); + expect(text).to.be('Last 13 hours') + }); }); diff --git a/public/test/specs/elasticsearch-specs.js b/public/test/specs/elasticsearch-specs.js index 086413c4861..784108a2fd0 100644 --- a/public/test/specs/elasticsearch-specs.js +++ b/public/test/specs/elasticsearch-specs.js @@ -57,8 +57,10 @@ define([ }; ctx.ds.query({ - timeFrom: moment(new Date(2015, 4, 30, 10)), - timeTo: moment(new Date(2015, 5, 1, 10)), + range: { + from: moment([2015, 4, 30, 10]), + to: moment([2015, 5, 1, 10]) + }, targets: [{ bucketAggs: [], metrics: [] }] }); diff --git a/public/test/specs/graphiteDatasource-specs.js b/public/test/specs/graphiteDatasource-specs.js index 5defd404c82..3aacff95efd 100644 --- a/public/test/specs/graphiteDatasource-specs.js +++ b/public/test/specs/graphiteDatasource-specs.js @@ -17,7 +17,7 @@ define([ describe('When querying influxdb with one target using query editor target spec', function() { var query = { - range: { from: 'now-1h', to: 'now' }, + rangeRaw: { from: 'now-1h', to: 'now' }, targets: [{ target: 'prod1.count' }, {target: 'prod2.count'}], maxDataPoints: 500, }; diff --git a/public/test/specs/influxdb-datasource-specs.js b/public/test/specs/influxdb-datasource-specs.js index 2f86675bcc5..175215663e0 100644 --- a/public/test/specs/influxdb-datasource-specs.js +++ b/public/test/specs/influxdb-datasource-specs.js @@ -21,7 +21,7 @@ define([ var urlExpected = "/series?p=mupp&q=select+mean(value)+from+%22test%22"+ "+where+time+%3E+now()-1h+group+by+time(1s)+order+asc"; var query = { - range: { from: 'now-1h', to: 'now' }, + rangeRaw: { from: 'now-1h', to: 'now' }, targets: [{ series: 'test', column: 'value', function: 'mean' }], interval: '1s' }; @@ -54,7 +54,7 @@ define([ var urlExpected = "/series?p=mupp&q=select+value+from+series"+ "+where+time+%3E+now()-1h"; var query = { - range: { from: 'now-1h', to: 'now' }, + rangeRaw: { from: 'now-1h', to: 'now' }, targets: [{ query: "select value from series where $timeFilter", rawQuery: true }] }; @@ -97,6 +97,5 @@ define([ }); }); - });