feat(timepicker): more work on getting new time formats to work in all data sources

This commit is contained in:
Torkel Ödegaard 2015-09-17 12:40:04 +02:00
parent 1a9c52e17f
commit a30f73fe36
16 changed files with 110 additions and 145 deletions

View File

@ -1,9 +1,8 @@
define([
'jquery',
'lodash',
'moment'
],
function($, _, moment) {
function($, _) {
'use strict';
var kbn = {};

View File

@ -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() {

View File

@ -65,21 +65,21 @@ _.each(rangeOptions, function (frame) {
// now/d
// if no to <expr> 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";
}

View File

@ -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),

View File

@ -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) {

View File

@ -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',

View File

@ -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) {

View File

@ -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);

View File

@ -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) {

View File

@ -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;

View File

@ -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;
});
});

View File

@ -116,6 +116,7 @@ describe("DateMath", () => {
expect(date).to.equal(undefined);
});
});
});
export = {};

View File

@ -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')
});
});

View File

@ -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: [] }]
});

View File

@ -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,
};

View File

@ -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([
});
});
});