Commit 1a9c52e1 by Torkel Ödegaard

feat(timepicker): lots of big changes, moving to datemath from…

feat(timepicker): lots of big changes, moving to datemath from kbn.parseDateMath, moving to moment dates instead of native javascript dates
parent 5ad38ee9
......@@ -163,172 +163,6 @@ function($, _, moment) {
return info.sec * info.count;
};
/* This is a simplified version of elasticsearch's date parser */
kbn.parseDate = function(text) {
if(_.isDate(text)) {
return text;
}
var time;
var mathString = "";
var index;
var parseString;
if (text.substring(0,3) === "now") {
time = new Date();
mathString = text.substring(3);
}
else if (text.substring(0,5) === 'today') {
time = new Date();
time.setHours(0,0,0,0);
mathString = text.substring(5);
}
else {
index = text.indexOf("||");
parseString;
if (index === -1) {
parseString = text;
mathString = ""; // nothing else
} else {
parseString = text.substring(0, index);
mathString = text.substring(index + 2);
}
// We're going to just require ISO8601 timestamps, k?
time = new Date(parseString);
}
if (!mathString.length) {
return time;
}
//return [time,parseString,mathString];
return kbn.parseDateMath(mathString, time);
};
kbn.getRelativeTimeInfo = function(str) {
var info = {value: str};
if (str === 'today') {
info.text = 'Today';
info.from = 'today';
info.to = 'now';
} else {
info.text = 'Last ' + str;
info.from = 'now-'+str;
info.to = 'now';
}
return info;
};
kbn._timespanRegex = /^(\d+[h,m,M,w,s,H,d])|today$/;
kbn.isValidTimeSpan = function(str) {
return kbn._timespanRegex.test(str);
};
kbn.parseDateMath = function(mathString, time, roundUp) {
var dateTime = moment(time);
for (var i = 0; i < mathString.length;) {
var c = mathString.charAt(i++),
type,
num,
unit;
if (c === '/') {
type = 0;
} else if (c === '+') {
type = 1;
} else if (c === '-') {
type = 2;
} else {
return false;
}
if (isNaN(mathString.charAt(i))) {
num = 1;
} else {
var numFrom = i;
while (!isNaN(mathString.charAt(i))) {
i++;
}
num = parseInt(mathString.substring(numFrom, i),10);
}
if (type === 0) {
// rounding is only allowed on whole numbers
if (num !== 1) {
return false;
}
}
unit = mathString.charAt(i++);
switch (unit) {
case 'y':
if (type === 0) {
roundUp ? dateTime.endOf('year') : dateTime.startOf('year');
} else if (type === 1) {
dateTime.add(num, 'years');
} else if (type === 2) {
dateTime.subtract(num, 'years');
}
break;
case 'M':
if (type === 0) {
roundUp ? dateTime.endOf('month') : dateTime.startOf('month');
} else if (type === 1) {
dateTime.add(num, 'months');
} else if (type === 2) {
dateTime.subtract(num, 'months');
}
break;
case 'w':
if (type === 0) {
roundUp ? dateTime.endOf('week') : dateTime.startOf('week');
} else if (type === 1) {
dateTime.add(num, 'weeks');
} else if (type === 2) {
dateTime.subtract(num, 'weeks');
}
break;
case 'd':
if (type === 0) {
roundUp ? dateTime.endOf('day') : dateTime.startOf('day');
} else if (type === 1) {
dateTime.add(num, 'days');
} else if (type === 2) {
dateTime.subtract(num, 'days');
}
break;
case 'h':
case 'H':
if (type === 0) {
roundUp ? dateTime.endOf('hour') : dateTime.startOf('hour');
} else if (type === 1) {
dateTime.add(num, 'hours');
} else if (type === 2) {
dateTime.subtract(num,'hours');
}
break;
case 'm':
if (type === 0) {
roundUp ? dateTime.endOf('minute') : dateTime.startOf('minute');
} else if (type === 1) {
dateTime.add(num, 'minutes');
} else if (type === 2) {
dateTime.subtract(num, 'minutes');
}
break;
case 's':
if (type === 0) {
roundUp ? dateTime.endOf('second') : dateTime.startOf('second');
} else if (type === 1) {
dateTime.add(num, 'seconds');
} else if (type === 2) {
dateTime.subtract(num, 'seconds');
}
break;
default:
return false;
}
}
return dateTime.toDate();
};
kbn.query_color_dot = function (color, diameter) {
return '<div class="icon-circle" style="' + [
'display:inline-block',
......
define([
'kbn',
'../core_module',
'app/core/core_module',
'app/core/utils/rangeutil',
'moment',
],
function (kbn, coreModule) {
function (kbn, coreModule, rangeUtil, moment) {
'use strict';
coreModule.directive('ngModelOnblur', function() {
......@@ -46,7 +48,8 @@ function (kbn, coreModule) {
if (ctrl.$isEmpty(modelValue)) {
return true;
}
return kbn.isValidTimeSpan(viewValue);
var info = rangeUtil.describeTextRange(viewValue);
return info.invalid !== true;
};
}
};
......
......@@ -43,6 +43,19 @@ function parse(text, roundUp?) {
return parseDateMath(mathString, time, roundUp);
}
function isValid(text) {
var date = parse(text);
if (!date) {
return false;
}
if (moment.isMoment(date)) {
return date.isValid();
}
return false;
}
function parseDateMath(mathString, time, roundUp?) {
var dateTime = time;
var i = 0;
......@@ -107,5 +120,6 @@ function parseDateMath(mathString, time, roundUp?) {
export = {
parse: parse,
parseDateMath: parseDateMath
parseDateMath: parseDateMath,
isValid: isValid
};
......@@ -75,7 +75,7 @@ _.each(rangeOptions, function (frame) {
return opt;
}
opt = {from: 'now-' + expr, to: 'now', display: 'Parse error'};
opt = {from: 'now-' + expr, to: 'now'};
if (/^\d+\w$/.test(expr)) {
let unit = expr[expr.length - 1];
......@@ -87,6 +87,9 @@ _.each(rangeOptions, function (frame) {
opt.display += 's';
}
}
} else {
opt.display = 'parse error';
opt.invalid = true;
}
return opt;
......
......@@ -42,14 +42,14 @@ define([
return value;
}
if (value.length === 8) {
return moment.utc(value, 'YYYYMMDD').toDate();
return moment.utc(value, 'YYYYMMDD');
}
if (value.length === 15) {
return moment.utc(value, 'YYYYMMDDTHHmmss').toDate();
return moment.utc(value, 'YYYYMMDDTHHmmss');
}
var epoch = parseInt(value);
if (!_.isNaN(epoch)) {
return new Date(epoch);
return moment(epoch);
}
return null;
......
define([
'angular',
'app/core/utils/datemath',
'app/core/utils/rangeutil',
'lodash',
'kbn',
'jquery',
],
function (angular, _, kbn, $) {
function (angular, dateMath, rangeUtil, _, kbn, $) {
'use strict';
var module = angular.module('grafana.services');
......@@ -63,30 +65,32 @@ function (angular, _, kbn, $) {
// check panel time overrrides
if (scope.panel.timeFrom) {
if (!kbn.isValidTimeSpan(scope.panel.timeFrom)) {
scope.panelMeta.timeInfo = 'invalid time override';
var timeFromInfo = rangeUtil.describeTextRange(scope.panel.timeFrom);
if (timeFromInfo.invalid) {
scope.panelMeta.timeFromInfo = 'invalid time override';
return;
}
if (_.isString(scope.rangeUnparsed.from)) {
var timeInfo = kbn.getRelativeTimeInfo(scope.panel.timeFrom);
scope.panelMeta.timeInfo = timeInfo.text;
scope.rangeUnparsed.from = timeInfo.from;
scope.rangeUnparsed.to = timeInfo.to;
scope.range.from = kbn.parseDate(scope.rangeUnparsed.from);
var timeFromDate = dateMath.parse(timeFromInfo.from);
scope.panelMeta.timeInfo = timeFromInfo.display;
scope.rangeUnparsed.from = timeFromInfo.from;
scope.rangeUnparsed.to = timeFromInfo.to;
scope.range.from = timeFromDate;
}
}
if (scope.panel.timeShift) {
if (!kbn.isValidTimeSpan(scope.panel.timeShift)) {
var timeShiftInfo = rangeUtil.describeTextRange(scope.panel.timeFrom);
if (timeShiftInfo.invalid) {
scope.panelMeta.timeInfo = 'invalid timeshift';
return;
}
var timeShift = '-' + scope.panel.timeShift;
scope.panelMeta.timeInfo += ' timeshift ' + timeShift;
scope.range.from = kbn.parseDateMath(timeShift, scope.range.from);
scope.range.to = kbn.parseDateMath(timeShift, scope.range.to);
scope.range.from = dateMath.parseDateMath(timeShift, scope.range.from, false);
scope.range.to = dateMath.parseDateMath(timeShift, scope.range.to, true);
scope.rangeUnparsed = scope.range;
}
......@@ -99,6 +103,8 @@ function (angular, _, kbn, $) {
this.issueMetricQuery = function(scope, datasource) {
var metricsQuery = {
range: scope.rangeUnparsed,
timeFrom: scope.range.valueOf(),
timeTo: scope.range.valueOf(),
interval: scope.interval,
targets: scope.panel.targets,
format: scope.panel.renderer === 'png' ? 'png' : 'json',
......@@ -118,6 +124,5 @@ function (angular, _, kbn, $) {
return results;
});
};
});
});
......@@ -141,11 +141,9 @@ function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticRes
});
};
ElasticDatasource.prototype.getQueryHeader = function(timeRange) {
ElasticDatasource.prototype.getQueryHeader = function(timeFrom, timeTo) {
var header = {search_type: "count", "ignore_unavailable": true};
var from = kbn.parseDate(timeRange.from);
var to = kbn.parseDate(timeRange.to);
header.index = this.indexPattern.getIndexList(from, to);
header.index = this.indexPattern.getIndexList(timeFrom, timeTo);
return angular.toJson(header);
};
......@@ -154,15 +152,13 @@ function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticRes
var target;
var sentTargets = [];
var header = this.getQueryHeader(options.range);
var timeFrom = this.translateTime(options.range.from);
var timeTo = this.translateTime(options.range.to);
var header = this.getQueryHeader(options.timeFrom, options.timeTo);
for (var i = 0; i < options.targets.length; i++) {
target = options.targets[i];
if (target.hide) {return;}
var esQuery = this.queryBuilder.build(target, timeFrom, timeTo);
var esQuery = this.queryBuilder.build(target, options.timeFrom, options.timeTo);
payload += header + '\n';
payload += angular.toJson(esQuery) + '\n';
......@@ -170,8 +166,8 @@ function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticRes
}
payload = payload.replace(/\$interval/g, options.interval);
payload = payload.replace(/\$timeFrom/g, this.translateTime(options.range.from));
payload = payload.replace(/\$timeTo/g, this.translateTime(options.range.to));
payload = payload.replace(/\$timeFrom/g, options.timeFrom);
payload = payload.replace(/\$timeTo/g, options.timeTo);
payload = payload.replace(/\$maxDataPoints/g, options.maxDataPoints);
payload = templateSrv.replace(payload, options.scopedVars);
......@@ -180,14 +176,6 @@ function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticRes
});
};
ElasticDatasource.prototype.translateTime = function(date) {
if (_.isString(date)) {
return date;
}
return date.getTime();
};
ElasticDatasource.prototype.metricFindQuery = function() {
return this._get('/_mapping').then(function(res) {
var fields = {};
......
define([
'./helpers',
'app/plugins/datasource/cloudwatch/datasource',
'aws-sdk',
], function(helpers) {
'use strict';
describe('CloudWatchDatasource', function() {
var ctx = new helpers.ServiceTestContext();
beforeEach(module('grafana.services'));
beforeEach(module('grafana.controllers'));
beforeEach(ctx.providePhase(['templateSrv']));
beforeEach(ctx.createService('CloudWatchDatasource'));
beforeEach(function() {
ctx.ds = new ctx.service({
jsonData: {
defaultRegion: 'us-east-1',
access: 'proxy'
}
});
});
describe('When performing CloudWatch query', function() {
var requestParams;
var query = {
range: { from: 'now-1h', to: 'now' },
targets: [
{
region: 'us-east-1',
namespace: 'AWS/EC2',
metricName: 'CPUUtilization',
dimensions: {
InstanceId: 'i-12345678'
},
statistics: {
Average: true
},
period: 300
}
]
};
var response = {
Datapoints: [
{
Average: 1,
Timestamp: 'Wed Dec 31 1969 16:00:00 GMT-0800 (PST)'
}
],
Label: 'CPUUtilization'
};
beforeEach(function() {
ctx.ds.getCloudWatchClient = function() {
return {
getMetricStatistics: function(params, callback) {
setTimeout(function() {
requestParams = params;
callback(null, response);
}, 0);
}
};
};
});
it('should generate the correct query', function() {
ctx.ds.query(query).then(function() {
expect(requestParams.Namespace).to.be(query.targets[0].namespace);
expect(requestParams.MetricName).to.be(query.targets[0].metricName);
expect(requestParams.Dimensions[0].Name).to.be(Object.keys(query.targets[0].dimensions)[0]);
expect(requestParams.Dimensions[0].Value).to.be(query.targets[0].dimensions[Object.keys(query.targets[0].dimensions)[0]]);
expect(requestParams.Statistics).to.eql(Object.keys(query.targets[0].statistics));
expect(requestParams.Period).to.be(query.targets[0].period);
});
});
it('should return series list', function() {
ctx.ds.query(query).then(function(result) {
var s = Object.keys(query.targets[0].statistics)[0];
expect(result.data[0].target).to.be(response.Label + s);
expect(result.data[0].datapoints[0][0]).to.be(response.Datapoints[0][s]);
});
});
});
describe('When performing CloudWatch metricFindQuery', function() {
var requestParams;
var response = {
Metrics: [
{
Namespace: 'AWS/EC2',
MetricName: 'CPUUtilization',
Dimensions: [
{
Name: 'InstanceId',
Value: 'i-12345678'
}
]
}
]
};
beforeEach(function() {
ctx.ds.getCloudWatchClient = function() {
return {
listMetrics: function(params, callback) {
setTimeout(function() {
requestParams = params;
callback(null, response);
}, 0);
}
};
};
});
it('should return suggest list for region()', function() {
var query = 'region()';
ctx.ds.metricFindQuery(query).then(function(result) {
expect(result).to.contain('us-east-1');
});
});
it('should return suggest list for namespace()', function() {
var query = 'namespace()';
ctx.ds.metricFindQuery(query).then(function(result) {
expect(result).to.contain('AWS/EC2');
});
});
it('should return suggest list for metrics()', function() {
var query = 'metrics(AWS/EC2)';
ctx.ds.metricFindQuery(query).then(function(result) {
expect(result).to.contain('CPUUtilization');
});
});
it('should return suggest list for dimension_keys()', function() {
var query = 'dimension_keys(AWS/EC2)';
ctx.ds.metricFindQuery(query).then(function(result) {
expect(result).to.contain('InstanceId');
});
});
it('should return suggest list for dimension_values()', function() {
var query = 'dimension_values(us-east-1,AWS/EC2,CPUUtilization)';
ctx.ds.metricFindQuery(query).then(function(result) {
expect(result).to.contain('InstanceId');
});
});
});
});
});
// define([
// './helpers',
// 'app/plugins/datasource/cloudwatch/datasource',
// 'aws-sdk',
// ], function(helpers) {
// 'use strict';
//
// describe('CloudWatchDatasource', function() {
// var ctx = new helpers.ServiceTestContext();
//
// beforeEach(module('grafana.services'));
// beforeEach(module('grafana.controllers'));
// beforeEach(ctx.providePhase(['templateSrv']));
// beforeEach(ctx.createService('CloudWatchDatasource'));
// beforeEach(function() {
// ctx.ds = new ctx.service({
// jsonData: {
// defaultRegion: 'us-east-1',
// access: 'proxy'
// }
// });
// });
//
// describe('When performing CloudWatch query', function() {
// var requestParams;
//
// var query = {
// range: { from: 'now-1h', to: 'now' },
// targets: [
// {
// region: 'us-east-1',
// namespace: 'AWS/EC2',
// metricName: 'CPUUtilization',
// dimensions: {
// InstanceId: 'i-12345678'
// },
// statistics: {
// Average: true
// },
// period: 300
// }
// ]
// };
//
// var response = {
// Datapoints: [
// {
// Average: 1,
// Timestamp: 'Wed Dec 31 1969 16:00:00 GMT-0800 (PST)'
// }
// ],
// Label: 'CPUUtilization'
// };
//
// beforeEach(function() {
// ctx.ds.getCloudWatchClient = function() {
// return {
// getMetricStatistics: function(params, callback) {
// setTimeout(function() {
// requestParams = params;
// callback(null, response);
// }, 0);
// }
// };
// };
// });
//
// it('should generate the correct query', function() {
// ctx.ds.query(query).then(function() {
// expect(requestParams.Namespace).to.be(query.targets[0].namespace);
// expect(requestParams.MetricName).to.be(query.targets[0].metricName);
// expect(requestParams.Dimensions[0].Name).to.be(Object.keys(query.targets[0].dimensions)[0]);
// expect(requestParams.Dimensions[0].Value).to.be(query.targets[0].dimensions[Object.keys(query.targets[0].dimensions)[0]]);
// expect(requestParams.Statistics).to.eql(Object.keys(query.targets[0].statistics));
// expect(requestParams.Period).to.be(query.targets[0].period);
// });
// });
//
// it('should return series list', function() {
// ctx.ds.query(query).then(function(result) {
// var s = Object.keys(query.targets[0].statistics)[0];
// expect(result.data[0].target).to.be(response.Label + s);
// expect(result.data[0].datapoints[0][0]).to.be(response.Datapoints[0][s]);
// });
// });
// });
//
// describe('When performing CloudWatch metricFindQuery', function() {
// var requestParams;
//
// var response = {
// Metrics: [
// {
// Namespace: 'AWS/EC2',
// MetricName: 'CPUUtilization',
// Dimensions: [
// {
// Name: 'InstanceId',
// Value: 'i-12345678'
// }
// ]
// }
// ]
// };
//
// beforeEach(function() {
// ctx.ds.getCloudWatchClient = function() {
// return {
// listMetrics: function(params, callback) {
// setTimeout(function() {
// requestParams = params;
// callback(null, response);
// }, 0);
// }
// };
// };
// });
//
// it('should return suggest list for region()', function() {
// var query = 'region()';
// ctx.ds.metricFindQuery(query).then(function(result) {
// expect(result).to.contain('us-east-1');
// });
// });
//
// it('should return suggest list for namespace()', function() {
// var query = 'namespace()';
// ctx.ds.metricFindQuery(query).then(function(result) {
// expect(result).to.contain('AWS/EC2');
// });
// });
//
// it('should return suggest list for metrics()', function() {
// var query = 'metrics(AWS/EC2)';
// ctx.ds.metricFindQuery(query).then(function(result) {
// expect(result).to.contain('CPUUtilization');
// });
// });
//
// it('should return suggest list for dimension_keys()', function() {
// var query = 'dimension_keys(AWS/EC2)';
// ctx.ds.metricFindQuery(query).then(function(result) {
// expect(result).to.contain('InstanceId');
// });
// });
//
// it('should return suggest list for dimension_values()', function() {
// var query = 'dimension_values(us-east-1,AWS/EC2,CPUUtilization)';
// ctx.ds.metricFindQuery(query).then(function(result) {
// expect(result).to.contain('InstanceId');
// });
// });
// });
// });
// });
......@@ -89,7 +89,32 @@ describe("DateMath", () => {
expect(dateMath.parse('now/' + span, true).format(format)).to.eql(now.endOf(span).format(format));
});
});
});
describe('isValid', () => {
it('should return false when invalid date text', () => {
expect(dateMath.isValid('asd')).to.be(false);
});
it('should return true when valid date text', () => {
expect(dateMath.isValid('now-1h')).to.be(true);
});
});
describe('relative time to date parsing', function() {
it('should handle negative time', function() {
var date = dateMath.parseDateMath('-2d', moment([2014, 1, 5]));
expect(date.valueOf()).to.equal(moment([2014, 1, 3]).valueOf());
});
it('should handle multiple math expressions', function() {
var date = dateMath.parseDateMath('-2d-6h', moment([2014, 1, 5]));
expect(date.valueOf()).to.equal(moment([2014, 1, 2, 18]).valueOf());
});
it('should return false when invalid expression', function() {
var date = dateMath.parseDateMath('2', moment([2014, 1, 5]));
expect(date).to.equal(undefined);
});
});
});
......
......@@ -57,10 +57,8 @@ define([
};
ctx.ds.query({
range: {
from: new Date(2015, 4, 30, 10),
to: new Date(2015, 5, 1, 10)
},
timeFrom: moment(new Date(2015, 4, 30, 10)),
timeTo: moment(new Date(2015, 5, 1, 10)),
targets: [{ bucketAggs: [], metrics: [] }]
});
......
define([
'kbn',
'lodash'
], function(kbn, _) {
'lodash',
'app/core/utils/datemath',
], function(kbn, _, dateMath) {
'use strict';
function ControllerTestContext() {
......@@ -107,8 +108,8 @@ define([
return this.time;
}
return {
from : kbn.parseDate(this.time.from),
to : kbn.parseDate(this.time.to)
from : dateMath.parse(this.time.from, false),
to : dateMath.parse(this.time.to, true)
};
};
......
define([
'kbn'
], function(kbn) {
'kbn',
'app/core/utils/datemath'
], function(kbn, dateMath) {
'use strict';
function describeValueFormat(desc, value, tickSize, tickDecimals, result) {
......@@ -60,60 +61,33 @@ define([
describe('calculateInterval', function() {
it('1h 100 resultion', function() {
var range = { from: kbn.parseDate('now-1h'), to: kbn.parseDate('now') };
var range = { from: dateMath.parse('now-1h'), to: dateMath.parse('now') };
var str = kbn.calculateInterval(range, 100, null);
expect(str).to.be('30s');
});
it('10m 1600 resolution', function() {
var range = { from: kbn.parseDate('now-10m'), to: kbn.parseDate('now') };
var range = { from: dateMath.parse('now-10m'), to: dateMath.parse('now') };
var str = kbn.calculateInterval(range, 1600, null);
expect(str).to.be('100ms');
});
it('fixed user interval', function() {
var range = { from: kbn.parseDate('now-10m'), to: kbn.parseDate('now') };
var range = { from: dateMath.parse('now-10m'), to: dateMath.parse('now') };
var str = kbn.calculateInterval(range, 1600, '10s');
expect(str).to.be('10s');
});
it('short time range and user low limit', function() {
var range = { from: kbn.parseDate('now-10m'), to: kbn.parseDate('now') };
var range = { from: dateMath.parse('now-10m'), to: dateMath.parse('now') };
var str = kbn.calculateInterval(range, 1600, '>10s');
expect(str).to.be('10s');
});
it('large time range and user low limit', function() {
var range = { from: kbn.parseDate('now-14d'), to: kbn.parseDate('now') };
var range = { from: dateMath.parse('now-14d'), to: dateMath.parse('now') };
var str = kbn.calculateInterval(range, 1000, '>10s');
expect(str).to.be('30m');
});
});
describe('relative time to date parsing', function() {
it('should handle negative time', function() {
var date = kbn.parseDateMath('-2d', new Date(2014,1,5));
expect(date.getTime()).to.equal(new Date(2014, 1, 3).getTime());
});
it('should handle today', function() {
var date = kbn.parseDate('today');
var today = new Date();
today.setHours(0,0,0,0);
expect(date.getTime()).to.equal(today.getTime());
});
it('should handle multiple math expressions', function() {
var date = kbn.parseDateMath('-2d-6h', new Date(2014, 1, 5));
expect(date.toString()).to.equal(new Date(2014, 1, 2, 18).toString());
});
it('should return false when invalid expression', function() {
var date = kbn.parseDateMath('2', new Date(2014, 1, 5));
expect(date).to.equal(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