From de17dcf3b0087d2826e7b4cdd5c94cb85a841a27 Mon Sep 17 00:00:00 2001 From: Patrick O'Carroll Date: Thu, 21 Sep 2017 13:34:51 +0200 Subject: [PATCH] Singlestat time (#9298) * Added a timestamp option to single stat * can now choose last time as value * Finished last_time so it formats correctly, updated value stat * fixed som issues, but still issue with testing * Clean up after fake clock in test * timezone-issue fix, fake time for from now test * fix for timedifference --- public/app/core/utils/kbn.js | 37 +++++++++- .../app/plugins/panel/singlestat/editor.html | 2 +- public/app/plugins/panel/singlestat/module.ts | 30 ++++++-- .../singlestat/specs/singlestat_specs.ts | 71 ++++++++++++++++++- public/test/core/utils/datemath_specs.ts | 10 ++- public/test/core/utils/kbn_specs.js | 41 ++++++++++- public/test/lib/common.ts | 2 + tasks/options/watch.js | 1 + 8 files changed, 180 insertions(+), 14 deletions(-) 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) {