diff --git a/public/app/core/utils/kbn.js b/public/app/core/utils/kbn.js
index 41a2d2c1bb3..fdebe5dd20b 100644
--- a/public/app/core/utils/kbn.js
+++ b/public/app/core/utils/kbn.js
@@ -1,8 +1,9 @@
define([
'jquery',
- 'lodash'
+ 'lodash',
+ 'moment'
],
-function($, _) {
+function($, _, moment) {
'use strict';
var kbn = {};
@@ -702,6 +703,28 @@ function($, _) {
return kbn.toDuration(size, decimals, 'second');
};
+ kbn.valueFormats.dateTimeAsIso = function(epoch) {
+ var time = moment(epoch);
+
+ if (moment().isSame(epoch, 'day')) {
+ return time.format('HH:mm:ss');
+ }
+ return time.format('YYYY-MM-DD HH:mm:ss');
+ };
+
+ kbn.valueFormats.dateTimeAsUS = function(epoch) {
+ var time = moment(epoch);
+
+ if (moment().isSame(epoch, 'day')) {
+ return time.format('h:mm:ss a');
+ }
+ return time.format('MM/DD/YYYY h:mm:ss a');
+ };
+
+ kbn.valueFormats.dateTimeFromNow = function(epoch) {
+ return moment(epoch).fromNow();
+ };
+
///// FORMAT MENU /////
kbn.getUnitFormats = function() {
@@ -745,7 +768,15 @@ function($, _) {
{text: 'hours (h)', value: 'h' },
{text: 'days (d)', value: 'd' },
{text: 'duration (ms)', value: 'dtdurationms' },
- {text: 'duration (s)', value: 'dtdurations' }
+ {text: 'duration (s)', value: 'dtdurations' },
+ ]
+ },
+ {
+ text: 'date & time',
+ submenu: [
+ {text: 'YYYY-MM-DD HH:mm:ss', value: 'dateTimeAsIso' },
+ {text: 'DD/MM/YYYY h:mm:ss a', value: 'dateTimeAsUS' },
+ {text: 'from now', value: 'dateTimeFromNow' },
]
},
{
diff --git a/public/app/plugins/panel/singlestat/editor.html b/public/app/plugins/panel/singlestat/editor.html
index 937f3c6cd9d..09d041b8b8e 100644
--- a/public/app/plugins/panel/singlestat/editor.html
+++ b/public/app/plugins/panel/singlestat/editor.html
@@ -6,7 +6,7 @@
diff --git a/public/app/plugins/panel/singlestat/module.ts b/public/app/plugins/panel/singlestat/module.ts
index 4b7f6e2e90d..0ea3e02e5aa 100644
--- a/public/app/plugins/panel/singlestat/module.ts
+++ b/public/app/plugins/panel/singlestat/module.ts
@@ -5,6 +5,7 @@ import _ from 'lodash';
import $ from 'jquery';
import 'jquery.flot';
import 'jquery.flot.gauge';
+import moment from 'moment';
import kbn from 'app/core/utils/kbn';
import config from 'app/core/config';
@@ -22,7 +23,19 @@ class SingleStatCtrl extends MetricsPanelCtrl {
invalidGaugeRange: boolean;
panel: any;
events: any;
- valueNameOptions: any[] = ['min','max','avg', 'current', 'total', 'name', 'first', 'delta', 'diff', 'range'];
+ valueNameOptions: any[] = [
+ {value : 'min', text: 'Min'},
+ {value : 'max', text: 'Max'},
+ {value : 'avg', text: 'Average'},
+ {value : 'current', text: 'Current'},
+ {value : 'total', text: 'Total'},
+ {value : 'name', text: 'Name'},
+ {value : 'first', text: 'First'},
+ {value : 'delta', text: 'Delta'},
+ {value : 'diff', text: 'Difference'},
+ {value : 'range', text: 'Range'},
+ {value : 'last_time', text: 'Time of last point'}
+ ];
tableColumnOptions: any;
// Set and populate defaults
@@ -93,7 +106,7 @@ class SingleStatCtrl extends MetricsPanelCtrl {
setUnitFormat(subItem) {
this.panel.format = subItem.value;
- this.render();
+ this.refresh();
}
onDataError(err) {
@@ -257,8 +270,8 @@ class SingleStatCtrl extends MetricsPanelCtrl {
}
if (this.series && this.series.length > 0) {
- var lastPoint = _.last(this.series[0].datapoints);
- var lastValue = _.isArray(lastPoint) ? lastPoint[0] : null;
+ let lastPoint = _.last(this.series[0].datapoints);
+ let lastValue = _.isArray(lastPoint) ? lastPoint[0] : null;
if (this.panel.valueName === 'name') {
data.value = 0;
@@ -268,12 +281,17 @@ class SingleStatCtrl extends MetricsPanelCtrl {
data.value = 0;
data.valueFormatted = _.escape(lastValue);
data.valueRounded = 0;
+ } else if (this.panel.valueName === 'last_time') {
+ let formatFunc = kbn.valueFormats[this.panel.format];
+ data.value = lastPoint[1];
+ data.valueRounded = data.value;
+ data.valueFormatted = formatFunc(data.value, 0, 0);
} else {
data.value = this.series[0].stats[this.panel.valueName];
data.flotpairs = this.series[0].flotpairs;
- var decimalInfo = this.getDecimalsForValue(data.value);
- var formatFunc = kbn.valueFormats[this.panel.format];
+ let decimalInfo = this.getDecimalsForValue(data.value);
+ let formatFunc = kbn.valueFormats[this.panel.format];
data.valueFormatted = formatFunc(data.value, decimalInfo.decimals, decimalInfo.scaledDecimals);
data.valueRounded = kbn.roundValue(data.value, decimalInfo.decimals);
}
diff --git a/public/app/plugins/panel/singlestat/specs/singlestat_specs.ts b/public/app/plugins/panel/singlestat/specs/singlestat_specs.ts
index 48b13d66c84..6098769e940 100644
--- a/public/app/plugins/panel/singlestat/specs/singlestat_specs.ts
+++ b/public/app/plugins/panel/singlestat/specs/singlestat_specs.ts
@@ -1,13 +1,16 @@
///
-import {describe, beforeEach, it, sinon, expect, angularMocks} from '../../../../../test/lib/common';
+import {describe, beforeEach, afterEach, it, sinon, expect, angularMocks} from '../../../../../test/lib/common';
import angular from 'angular';
import helpers from '../../../../../test/specs/helpers';
import {SingleStatCtrl} from '../module';
+import moment from 'moment';
describe('SingleStatCtrl', function() {
var ctx = new helpers.ControllerTestContext();
+ var epoch = 1505826363746;
+ var clock;
function singleStatScenario(desc, func) {
@@ -70,6 +73,72 @@ describe('SingleStatCtrl', function() {
});
});
+ singleStatScenario('showing last iso time instead of value', function(ctx) {
+ ctx.setup(function() {
+ ctx.data = [
+ {target: 'test.cpu1', datapoints: [[10, 12], [20,1505634997920]]}
+ ];
+ ctx.ctrl.panel.valueName = 'last_time';
+ ctx.ctrl.panel.format = 'dateTimeAsIso';
+ });
+
+ it('Should use time instead of value', function() {
+ expect(ctx.data.value).to.be(1505634997920);
+ expect(ctx.data.valueRounded).to.be(1505634997920);
+ });
+
+ it('should set formatted value', function() {
+ expect(ctx.data.valueFormatted).to.be(moment(1505634997920).format('YYYY-MM-DD HH:mm:ss'));
+ });
+ });
+
+ singleStatScenario('showing last us time instead of value', function(ctx) {
+ ctx.setup(function() {
+ ctx.data = [
+ {target: 'test.cpu1', datapoints: [[10, 12], [20,1505634997920]]}
+ ];
+ ctx.ctrl.panel.valueName = 'last_time';
+ ctx.ctrl.panel.format = 'dateTimeAsUS';
+ });
+
+ it('Should use time instead of value', function() {
+ expect(ctx.data.value).to.be(1505634997920);
+ expect(ctx.data.valueRounded).to.be(1505634997920);
+ });
+
+ it('should set formatted value', function() {
+ expect(ctx.data.valueFormatted).to.be(moment(1505634997920).format('MM/DD/YYYY H:mm:ss a'));
+ });
+ });
+
+ singleStatScenario('showing last time from now instead of value', function(ctx) {
+
+ beforeEach(() => {
+ clock = sinon.useFakeTimers(epoch);
+ });
+
+ ctx.setup(function() {
+ ctx.data = [
+ {target: 'test.cpu1', datapoints: [[10, 12], [20,1505634997920]]}
+ ];
+ ctx.ctrl.panel.valueName = 'last_time';
+ ctx.ctrl.panel.format = 'dateTimeFromNow';
+ });
+
+ it('Should use time instead of value', function() {
+ expect(ctx.data.value).to.be(1505634997920);
+ expect(ctx.data.valueRounded).to.be(1505634997920);
+ });
+
+ it('should set formatted value', function() {
+ expect(ctx.data.valueFormatted).to.be('2 days ago');
+ });
+
+ afterEach(() => {
+ clock.restore();
+ });
+ });
+
singleStatScenario('MainValue should use same number for decimals as displayed when checking thresholds', function(ctx) {
ctx.setup(function() {
ctx.data = [
diff --git a/public/test/core/utils/datemath_specs.ts b/public/test/core/utils/datemath_specs.ts
index c6096485f65..e027a59c2ee 100644
--- a/public/test/core/utils/datemath_specs.ts
+++ b/public/test/core/utils/datemath_specs.ts
@@ -1,4 +1,4 @@
-import {describe, beforeEach, it, sinon, expect} from 'test/lib/common';
+import {describe, beforeEach, afterEach, it, sinon, expect} from 'test/lib/common';
import * as dateMath from 'app/core/utils/datemath';
import moment from 'moment';
@@ -68,6 +68,10 @@ describe("DateMath", () => {
expect(dateMath.parse(thenEx).format(format)).to.eql(anchored.subtract(5, span).format(format));
});
});
+
+ afterEach(() => {
+ clock.restore();
+ });
});
describe('rounding', () => {
@@ -89,6 +93,10 @@ describe("DateMath", () => {
expect(dateMath.parse('now/' + span, true).format(format)).to.eql(now.endOf(span).format(format));
});
});
+
+ afterEach(() => {
+ clock.restore();
+ });
});
describe('isValid', () => {
diff --git a/public/test/core/utils/kbn_specs.js b/public/test/core/utils/kbn_specs.js
index 0e92255792b..12e9862f88a 100644
--- a/public/test/core/utils/kbn_specs.js
+++ b/public/test/core/utils/kbn_specs.js
@@ -1,7 +1,8 @@
define([
'app/core/utils/kbn',
- 'app/core/utils/datemath'
-], function(kbn, dateMath) {
+ 'app/core/utils/datemath',
+ 'moment'
+], function(kbn, dateMath, moment) {
'use strict';
describe('unit format menu', function() {
@@ -94,6 +95,42 @@ define([
describeValueFormat('d', 245, 100, 0, '35 week');
describeValueFormat('d', 2456, 10, 0, '6.73 year');
+ describe('date time formats', function() {
+ it('should format as iso date', function() {
+ var str = kbn.valueFormats.dateTimeAsIso(1505634997920, 1);
+ expect(str).to.be(moment(1505634997920).format('YYYY-MM-DD HH:mm:ss'));
+ });
+
+ it('should format as iso date and skip date when today', function() {
+ var now = moment();
+ var str = kbn.valueFormats.dateTimeAsIso(now.valueOf(), 1);
+ expect(str).to.be(now.format("HH:mm:ss"));
+ });
+
+ it('should format as US date', function() {
+ var str = kbn.valueFormats.dateTimeAsUS(1505634997920, 1);
+ expect(str).to.be(moment(1505634997920).format('MM/DD/YYYY H:mm:ss a'));
+ });
+
+ it('should format as US date and skip date when today', function() {
+ var now = moment();
+ var str = kbn.valueFormats.dateTimeAsUS(now.valueOf(), 1);
+ expect(str).to.be(now.format("h:mm:ss a"));
+ });
+
+ it('should format as from now with days', function() {
+ var daysAgo = moment().add(-7, 'd');
+ var str = kbn.valueFormats.dateTimeFromNow(daysAgo.valueOf(), 1);
+ expect(str).to.be('7 days ago');
+ });
+
+ it('should format as from now with minutes', function() {
+ var daysAgo = moment().add(-2, 'm');
+ var str = kbn.valueFormats.dateTimeFromNow(daysAgo.valueOf(), 1);
+ expect(str).to.be('2 minutes ago');
+ });
+ });
+
describe('kbn.toFixed and negative decimals', function() {
it('should treat as zero decimals', function() {
var str = kbn.toFixed(186.123, -2);
diff --git a/public/test/lib/common.ts b/public/test/lib/common.ts
index ca07fe01f32..75f97bb7576 100644
--- a/public/test/lib/common.ts
+++ b/public/test/lib/common.ts
@@ -2,6 +2,7 @@
var _global =
(window);
var beforeEach = _global.beforeEach;
+var afterEach = _global.afterEach;
var before = _global.before;
var describe = _global.describe;
var it = _global.it;
@@ -15,6 +16,7 @@ var angularMocks = {
export {
beforeEach,
+ afterEach,
before,
describe,
it,
diff --git a/tasks/options/watch.js b/tasks/options/watch.js
index 54ecbd6672e..c421ec3e01e 100644
--- a/tasks/options/watch.js
+++ b/tasks/options/watch.js
@@ -23,6 +23,7 @@ module.exports = function(config, grunt) {
gaze([
config.srcDir + '/app/**/*',
+ config.srcDir + '/test/**/*',
config.srcDir + '/sass/**/*',
], function(err, watcher) {