Просмотр исходного кода

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
Patrick O'Carroll 8 лет назад
Родитель
Сommit
de17dcf3b0

+ 34 - 3
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' },
         ]
       },
       {

+ 1 - 1
public/app/plugins/panel/singlestat/editor.html

@@ -6,7 +6,7 @@
       <div class="gf-form" ng-show="ctrl.dataType === 'timeseries'">
         <label class="gf-form-label width-6">Stat</label>
         <div class="gf-form-select-wrapper width-7">
-          <select class="gf-form-input" ng-model="ctrl.panel.valueName" ng-options="f for f in ctrl.valueNameOptions" ng-change="ctrl.render()"></select>
+          <select class="gf-form-input" ng-model="ctrl.panel.valueName" ng-options="f.value as f.text for f in ctrl.valueNameOptions" ng-change="ctrl.refresh()"></select>
         </div>
       </div>
       <div class="gf-form" ng-show="ctrl.dataType === 'table'">

+ 24 - 6
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);
       }

+ 70 - 1
public/app/plugins/panel/singlestat/specs/singlestat_specs.ts

@@ -1,13 +1,16 @@
 ///<reference path="../../../../headers/common.d.ts" />
 
-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 = [

+ 9 - 1
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', () => {

+ 39 - 2
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);

+ 2 - 0
public/test/lib/common.ts

@@ -2,6 +2,7 @@
 
 var _global = <any>(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,

+ 1 - 0
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) {