From 2c51f114405d8c7db54c79f8524987f6da29327a Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Fri, 21 Apr 2017 14:07:36 +0200 Subject: [PATCH 1/4] singlestat: fix variable spelling --- public/app/plugins/panel/singlestat/module.ts | 20 +++++++++---------- .../singlestat/specs/singlestat-specs.ts | 18 ++++++++--------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/public/app/plugins/panel/singlestat/module.ts b/public/app/plugins/panel/singlestat/module.ts index c4d7ed06aa4..c8ab03bd688 100644 --- a/public/app/plugins/panel/singlestat/module.ts +++ b/public/app/plugins/panel/singlestat/module.ts @@ -192,10 +192,10 @@ class SingleStatCtrl extends MetricsPanelCtrl { if (this.panel.valueName === 'name') { data.value = 0; data.valueRounded = 0; - data.valueFormated = this.series[0].alias; + data.valueFormatted = this.series[0].alias; } else if (_.isString(lastValue)) { data.value = 0; - data.valueFormated = _.escape(lastValue); + data.valueFormatted = _.escape(lastValue); data.valueRounded = 0; } else { data.value = this.series[0].stats[this.panel.valueName]; @@ -203,7 +203,7 @@ class SingleStatCtrl extends MetricsPanelCtrl { var decimalInfo = this.getDecimalsForValue(data.value); var formatFunc = kbn.valueFormats[this.panel.format]; - data.valueFormated = formatFunc(data.value, decimalInfo.decimals, decimalInfo.scaledDecimals); + data.valueFormatted = formatFunc(data.value, decimalInfo.decimals, decimalInfo.scaledDecimals); data.valueRounded = kbn.roundValue(data.value, decimalInfo.decimals); } @@ -219,7 +219,7 @@ class SingleStatCtrl extends MetricsPanelCtrl { // special null case if (map.value === 'null') { if (data.value === null || data.value === void 0) { - data.valueFormated = map.text; + data.valueFormatted = map.text; return; } continue; @@ -228,7 +228,7 @@ class SingleStatCtrl extends MetricsPanelCtrl { // value/number to text mapping var value = parseFloat(map.value); if (value === data.valueRounded) { - data.valueFormated = map.text; + data.valueFormatted = map.text; return; } } @@ -238,7 +238,7 @@ class SingleStatCtrl extends MetricsPanelCtrl { // special null case if (map.from === 'null' && map.to === 'null') { if (data.value === null || data.value === void 0) { - data.valueFormated = map.text; + data.valueFormatted = map.text; return; } continue; @@ -248,14 +248,14 @@ class SingleStatCtrl extends MetricsPanelCtrl { var from = parseFloat(map.from); var to = parseFloat(map.to); if (to >= data.valueRounded && from <= data.valueRounded) { - data.valueFormated = map.text; + data.valueFormatted = map.text; return; } } } if (data.value === null || data.value === void 0) { - data.valueFormated = "no value"; + data.valueFormatted = "no value"; } } @@ -317,7 +317,7 @@ class SingleStatCtrl extends MetricsPanelCtrl { if (panel.prefix) { body += getSpan('singlestat-panel-prefix', panel.prefixFontSize, panel.prefix); } - var value = applyColoringThresholds(data.value, data.valueFormated); + var value = applyColoringThresholds(data.value, data.valueFormatted); body += getSpan('singlestat-panel-value', panel.valueFontSize, value); if (panel.postfix) { body += getSpan('singlestat-panel-postfix', panel.postfixFontSize, panel.postfix); } @@ -329,7 +329,7 @@ class SingleStatCtrl extends MetricsPanelCtrl { function getValueText() { var result = panel.prefix ? panel.prefix : ''; - result += data.valueFormated; + result += data.valueFormatted; result += panel.postfix ? panel.postfix : ''; return result; diff --git a/public/app/plugins/panel/singlestat/specs/singlestat-specs.ts b/public/app/plugins/panel/singlestat/specs/singlestat-specs.ts index 3f67063bc0f..9c7a979af12 100644 --- a/public/app/plugins/panel/singlestat/specs/singlestat-specs.ts +++ b/public/app/plugins/panel/singlestat/specs/singlestat-specs.ts @@ -49,8 +49,8 @@ describe('SingleStatCtrl', function() { expect(ctx.data.valueRounded).to.be(15); }); - it('should set formated falue', function() { - expect(ctx.data.valueFormated).to.be('15'); + it('should set formatted falue', function() { + expect(ctx.data.valueFormatted).to.be('15'); }); }); @@ -65,8 +65,8 @@ describe('SingleStatCtrl', function() { expect(ctx.data.valueRounded).to.be(0); }); - it('should set formated falue', function() { - expect(ctx.data.valueFormated).to.be('test.cpu1'); + it('should set formatted falue', function() { + expect(ctx.data.valueFormatted).to.be('test.cpu1'); }); }); @@ -80,8 +80,8 @@ describe('SingleStatCtrl', function() { expect(ctx.data.valueRounded).to.be(100); }); - it('should set formated falue', function() { - expect(ctx.data.valueFormated).to.be('100'); + it('should set formatted falue', function() { + expect(ctx.data.valueFormatted).to.be('100'); }); }); @@ -100,7 +100,7 @@ describe('SingleStatCtrl', function() { }); it('Should replace value with text', function() { - expect(ctx.data.valueFormated).to.be('OK'); + expect(ctx.data.valueFormatted).to.be('OK'); }); }); @@ -112,7 +112,7 @@ describe('SingleStatCtrl', function() { }); it('Should replace value with text OK', function() { - expect(ctx.data.valueFormated).to.be('OK'); + expect(ctx.data.valueFormatted).to.be('OK'); }); }); @@ -124,7 +124,7 @@ describe('SingleStatCtrl', function() { }); it('Should replace value with text NOT OK', function() { - expect(ctx.data.valueFormated).to.be('NOT OK'); + expect(ctx.data.valueFormatted).to.be('NOT OK'); }); }); From 8874be4c663a089a2e01998c7c9753731c459665 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Fri, 21 Apr 2017 16:11:49 +0200 Subject: [PATCH 2/4] singlestat: add support for table data If data is of type Table, then will return the first row of data. The user can select which column should be shown in the SingleStat. --- .../app/plugins/panel/singlestat/editor.html | 10 ++- public/app/plugins/panel/singlestat/module.ts | 75 ++++++++++++++-- ...inglestat-specs.ts => singlestat_specs.ts} | 86 ++++++++++++++++--- 3 files changed, 153 insertions(+), 18 deletions(-) rename public/app/plugins/panel/singlestat/specs/{singlestat-specs.ts => singlestat_specs.ts} (67%) diff --git a/public/app/plugins/panel/singlestat/editor.html b/public/app/plugins/panel/singlestat/editor.html index 50f18ea6146..937f3c6cd9d 100644 --- a/public/app/plugins/panel/singlestat/editor.html +++ b/public/app/plugins/panel/singlestat/editor.html @@ -3,11 +3,19 @@
Value
-
+
+
+
+ +
+ +
+
+
diff --git a/public/app/plugins/panel/singlestat/module.ts b/public/app/plugins/panel/singlestat/module.ts index c8ab03bd688..7003130b64c 100644 --- a/public/app/plugins/panel/singlestat/module.ts +++ b/public/app/plugins/panel/singlestat/module.ts @@ -14,6 +14,7 @@ import {MetricsPanelCtrl} from 'app/plugins/sdk'; class SingleStatCtrl extends MetricsPanelCtrl { static templateUrl = 'module.html'; + dataType = 'timeseries'; series: any[]; data: any; fontSizes: any[]; @@ -22,6 +23,7 @@ class SingleStatCtrl extends MetricsPanelCtrl { panel: any; events: any; valueNameOptions: any[] = ['min','max','avg', 'current', 'total', 'name', 'first', 'delta', 'diff', 'range']; + tableColumnOptions: any; // Set and populate defaults panelDefaults = { @@ -67,7 +69,8 @@ class SingleStatCtrl extends MetricsPanelCtrl { maxValue: 100, thresholdMarkers: true, thresholdLabels: false - } + }, + tableColumn: '' }; /** @ngInject */ @@ -98,11 +101,16 @@ class SingleStatCtrl extends MetricsPanelCtrl { } onDataReceived(dataList) { - this.series = dataList.map(this.seriesHandler.bind(this)); - - var data: any = {}; - this.setValues(data); - + const data: any = {}; + if (dataList.length > 0 && dataList[0].type === 'table'){ + this.dataType = 'table'; + const tableData = dataList.map(this.tableHandler.bind(this)); + this.setTableValues(tableData, data); + } else { + this.dataType = 'timeseries'; + this.series = dataList.map(this.seriesHandler.bind(this)); + this.setValues(data); + } this.data = data; this.render(); } @@ -117,6 +125,61 @@ class SingleStatCtrl extends MetricsPanelCtrl { return series; } + tableHandler(tableData) { + const datapoints = []; + const columnNames = {}; + + tableData.columns.forEach((column, columnIndex) => { + columnNames[columnIndex] = column.text; + }); + + this.tableColumnOptions = columnNames; + if (!_.find(tableData.columns, ['text', this.panel.tableColumn])) { + this.setTableColumnToSensibleDefault(tableData); + } + + tableData.rows.forEach((row) => { + const datapoint = {}; + + row.forEach((value, columnIndex) => { + const key = columnNames[columnIndex]; + datapoint[key] = value; + }); + + datapoints.push(datapoint); + }); + + return datapoints; + } + + setTableColumnToSensibleDefault(tableData) { + if (this.tableColumnOptions.length === 1) { + this.panel.tableColumn = this.tableColumnOptions[0]; + } else { + this.panel.tableColumn = _.find(tableData.columns, (col) => { return col.type !== 'time'; }).text; + } + } + + setTableValues(tableData, data) { + if (!tableData || tableData.length === 0) { + return; + } + + if (tableData[0].length === 0 || !tableData[0][0][this.panel.tableColumn]) { + return; + } + + let highestValue = 0; + let lowestValue = Number.MAX_VALUE; + const datapoint = tableData[0][0]; + data.value = datapoint[this.panel.tableColumn]; + + var decimalInfo = this.getDecimalsForValue(data.value); + var formatFunc = kbn.valueFormats[this.panel.format]; + data.valueFormatted = formatFunc(datapoint[this.panel.tableColumn], decimalInfo.decimals, decimalInfo.scaledDecimals); + data.valueRounded = kbn.roundValue(data.value, this.panel.decimals || 0); + } + setColoring(options) { if (options.background) { this.panel.colorValue = false; diff --git a/public/app/plugins/panel/singlestat/specs/singlestat-specs.ts b/public/app/plugins/panel/singlestat/specs/singlestat_specs.ts similarity index 67% rename from public/app/plugins/panel/singlestat/specs/singlestat-specs.ts rename to public/app/plugins/panel/singlestat/specs/singlestat_specs.ts index 9c7a979af12..51ade2f9a98 100644 --- a/public/app/plugins/panel/singlestat/specs/singlestat-specs.ts +++ b/public/app/plugins/panel/singlestat/specs/singlestat_specs.ts @@ -26,11 +26,7 @@ describe('SingleStatCtrl', function() { beforeEach(function() { setupFunc(); - var data = [ - {target: 'test.cpu1', datapoints: ctx.datapoints} - ]; - - ctx.ctrl.onDataReceived(data); + ctx.ctrl.onDataReceived(ctx.data); ctx.data = ctx.ctrl.data; }); }; @@ -41,7 +37,9 @@ describe('SingleStatCtrl', function() { singleStatScenario('with defaults', function(ctx) { ctx.setup(function() { - ctx.datapoints = [[10,1], [20,2]]; + ctx.data = [ + {target: 'test.cpu1', datapoints: [[10,1], [20,2]]} + ]; }); it('Should use series avg as default main value', function() { @@ -56,7 +54,9 @@ describe('SingleStatCtrl', function() { singleStatScenario('showing serie name instead of value', function(ctx) { ctx.setup(function() { - ctx.datapoints = [[10,1], [20,2]]; + ctx.data = [ + {target: 'test.cpu1', datapoints: [[10,1], [20,2]]} + ]; ctx.ctrl.panel.valueName = 'name'; }); @@ -72,7 +72,9 @@ describe('SingleStatCtrl', function() { singleStatScenario('MainValue should use same number for decimals as displayed when checking thresholds', function(ctx) { ctx.setup(function() { - ctx.datapoints = [[99.999,1], [99.99999,2]]; + ctx.data = [ + {target: 'test.cpu1', datapoints: [[99.999,1], [99.99999,2]]} + ]; }); it('Should be rounded', function() { @@ -87,7 +89,9 @@ describe('SingleStatCtrl', function() { singleStatScenario('When value to text mapping is specified', function(ctx) { ctx.setup(function() { - ctx.datapoints = [[9.9,1]]; + ctx.data = [ + {target: 'test.cpu1', datapoints: [[9.9,1]]} + ]; ctx.ctrl.panel.valueMaps = [{value: '10', text: 'OK'}]; }); @@ -106,7 +110,9 @@ describe('SingleStatCtrl', function() { singleStatScenario('When range to text mapping is specifiedfor first range', function(ctx) { ctx.setup(function() { - ctx.datapoints = [[41,50]]; + ctx.data = [ + {target: 'test.cpu1', datapoints: [[41,50]]} + ]; ctx.ctrl.panel.mappingType = 2; ctx.ctrl.panel.rangeMaps = [{from: '10', to: '50', text: 'OK'},{from: '51', to: '100', text: 'NOT OK'}]; }); @@ -118,7 +124,9 @@ describe('SingleStatCtrl', function() { singleStatScenario('When range to text mapping is specified for other ranges', function(ctx) { ctx.setup(function() { - ctx.datapoints = [[65,75]]; + ctx.data = [ + {target: 'test.cpu1', datapoints: [[65,75]]} + ]; ctx.ctrl.panel.mappingType = 2; ctx.ctrl.panel.rangeMaps = [{from: '10', to: '50', text: 'OK'},{from: '51', to: '100', text: 'NOT OK'}]; }); @@ -128,4 +136,60 @@ describe('SingleStatCtrl', function() { }); }); + const tableData = [{ + "columns": [ + { + "text": "Time", + "type": "time" + }, + { + "text": "test1" + }, + { + "text": "mean" + }, + { + "text": "test2" + } + ], + "rows": [ + [ + 1492759673649, + 'ignore1', + 15, + 'ignore2' + ] + ], + "type": "table" + }]; + + singleStatScenario('When table data', function(ctx) { + ctx.setup(function() { + ctx.data = tableData; + ctx.ctrl.panel.tableColumn = 'mean'; + }); + + it('Should use series avg as default main value', function() { + expect(ctx.data.value).to.be(15); + expect(ctx.data.valueRounded).to.be(15); + }); + + it('should set formatted value', function() { + expect(ctx.data.valueFormatted).to.be('15'); + }); + }); + + singleStatScenario('When table data has multiple columns', function(ctx) { + ctx.setup(function() { + ctx.data = tableData; + ctx.ctrl.panel.tableColumn = ''; + }); + + it('Should set column to first column that is not time', function() { + expect(ctx.ctrl.panel.tableColumn).to.be('test1'); + }); + }); + }); + + From a49ef90a1df3592d717cf7dd75b7902fecd77b9d Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Fri, 21 Apr 2017 16:43:14 +0200 Subject: [PATCH 3/4] singlestat: value mapping for table data Adds support for value mapping for table data in the single stat panel. --- public/app/plugins/panel/singlestat/module.ts | 5 + .../singlestat/specs/singlestat_specs.ts | 147 ++++++++++++------ 2 files changed, 105 insertions(+), 47 deletions(-) diff --git a/public/app/plugins/panel/singlestat/module.ts b/public/app/plugins/panel/singlestat/module.ts index 7003130b64c..8000b844bf9 100644 --- a/public/app/plugins/panel/singlestat/module.ts +++ b/public/app/plugins/panel/singlestat/module.ts @@ -178,6 +178,8 @@ class SingleStatCtrl extends MetricsPanelCtrl { var formatFunc = kbn.valueFormats[this.panel.format]; data.valueFormatted = formatFunc(datapoint[this.panel.tableColumn], decimalInfo.decimals, decimalInfo.scaledDecimals); data.valueRounded = kbn.roundValue(data.value, this.panel.decimals || 0); + + this.setValueMapping(data); } setColoring(options) { @@ -274,7 +276,10 @@ class SingleStatCtrl extends MetricsPanelCtrl { data.scopedVars = _.extend({}, this.panel.scopedVars); data.scopedVars["__name"] = {value: this.series[0].label}; } + this.setValueMapping(data); + } + setValueMapping(data) { // check value to text mappings if its enabled if (this.panel.mappingType === 1) { for (let i = 0; i < this.panel.valueMaps.length; i++) { diff --git a/public/app/plugins/panel/singlestat/specs/singlestat_specs.ts b/public/app/plugins/panel/singlestat/specs/singlestat_specs.ts index 51ade2f9a98..2bc36d67c3a 100644 --- a/public/app/plugins/panel/singlestat/specs/singlestat_specs.ts +++ b/public/app/plugins/panel/singlestat/specs/singlestat_specs.ts @@ -136,60 +136,113 @@ describe('SingleStatCtrl', function() { }); }); - const tableData = [{ - "columns": [ - { - "text": "Time", - "type": "time" - }, - { - "text": "test1" - }, - { - "text": "mean" - }, - { - "text": "test2" - } - ], - "rows": [ - [ - 1492759673649, - 'ignore1', - 15, - 'ignore2' - ] - ], - "type": "table" - }]; + describe('When table data', function() { + const tableData = [{ + "columns": [ + { "text": "Time", "type": "time" }, + { "text": "test1" }, + { "text": "mean" }, + { "text": "test2" } + ], + "rows": [ + [1492759673649, 'ignore1', 15, 'ignore2'] + ], + "type": "table" + }]; - singleStatScenario('When table data', function(ctx) { - ctx.setup(function() { - ctx.data = tableData; - ctx.ctrl.panel.tableColumn = 'mean'; + singleStatScenario('with default values', function(ctx) { + ctx.setup(function() { + ctx.data = tableData; + ctx.ctrl.panel.tableColumn = 'mean'; + }); + + it('Should use first rows value as default main value', function() { + expect(ctx.data.value).to.be(15); + expect(ctx.data.valueRounded).to.be(15); + }); + + it('should set formatted value', function() { + expect(ctx.data.valueFormatted).to.be('15'); + }); }); - it('Should use series avg as default main value', function() { - expect(ctx.data.value).to.be(15); - expect(ctx.data.valueRounded).to.be(15); + singleStatScenario('When table data has multiple columns', function(ctx) { + ctx.setup(function() { + ctx.data = tableData; + ctx.ctrl.panel.tableColumn = ''; + }); + + it('Should set column to first column that is not time', function() { + expect(ctx.ctrl.panel.tableColumn).to.be('test1'); + }); }); - it('should set formatted value', function() { - expect(ctx.data.valueFormatted).to.be('15'); + singleStatScenario('MainValue should use same number for decimals as displayed when checking thresholds', function(ctx) { + ctx.setup(function() { + ctx.data = tableData; + ctx.data[0].rows[0] = [1492759673649,'ignore1', 99.99999, 'ignore2']; + ctx.ctrl.panel.tableColumn = 'mean'; + }); + + it('Should be rounded', function() { + expect(ctx.data.value).to.be(99.99999); + expect(ctx.data.valueRounded).to.be(100); + }); + + it('should set formatted falue', function() { + expect(ctx.data.valueFormatted).to.be('100'); + }); + }); + + singleStatScenario('When value to text mapping is specified', function(ctx) { + ctx.setup(function() { + ctx.data = tableData; + ctx.data[0].rows[0] = [1492759673649,'ignore1', 9.9, 'ignore2']; + ctx.ctrl.panel.tableColumn = 'mean'; + ctx.ctrl.panel.valueMaps = [{value: '10', text: 'OK'}]; + }); + + it('value should remain', function() { + expect(ctx.data.value).to.be(9.9); + }); + + it('round should be rounded up', function() { + expect(ctx.data.valueRounded).to.be(10); + }); + + it('Should replace value with text', function() { + expect(ctx.data.valueFormatted).to.be('OK'); + }); + }); + + singleStatScenario('When range to text mapping is specified for first range', function(ctx) { + ctx.setup(function() { + ctx.data = tableData; + ctx.data[0].rows[0] = [1492759673649,'ignore1', 41, 'ignore2']; + ctx.ctrl.panel.tableColumn = 'mean'; + ctx.ctrl.panel.mappingType = 2; + ctx.ctrl.panel.rangeMaps = [{from: '10', to: '50', text: 'OK'},{from: '51', to: '100', text: 'NOT OK'}]; + }); + + it('Should replace value with text OK', function() { + expect(ctx.data.valueFormatted).to.be('OK'); + }); + }); + + singleStatScenario('When range to text mapping is specified for other ranges', function(ctx) { + ctx.setup(function() { + ctx.data = tableData; + ctx.data[0].rows[0] = [1492759673649,'ignore1', 65, 'ignore2']; + ctx.ctrl.panel.tableColumn = 'mean'; + ctx.ctrl.panel.mappingType = 2; + ctx.ctrl.panel.rangeMaps = [{from: '10', to: '50', text: 'OK'},{from: '51', to: '100', text: 'NOT OK'}]; + }); + + it('Should replace value with text NOT OK', function() { + expect(ctx.data.valueFormatted).to.be('NOT OK'); + }); }); }); - - singleStatScenario('When table data has multiple columns', function(ctx) { - ctx.setup(function() { - ctx.data = tableData; - ctx.ctrl.panel.tableColumn = ''; - }); - - it('Should set column to first column that is not time', function() { - expect(ctx.ctrl.panel.tableColumn).to.be('test1'); - }); - }); - }); From 6160978019e9a43843de062d3af0e36a9b6503d4 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Fri, 21 Apr 2017 16:54:56 +0200 Subject: [PATCH 4/4] singlestat: with table data, support string values If the selected table column is string then show that in the singlestat --- public/app/plugins/panel/singlestat/module.ts | 14 ++++++++++---- .../panel/singlestat/specs/singlestat_specs.ts | 12 ++++++++++++ 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/public/app/plugins/panel/singlestat/module.ts b/public/app/plugins/panel/singlestat/module.ts index 8000b844bf9..406aedb3877 100644 --- a/public/app/plugins/panel/singlestat/module.ts +++ b/public/app/plugins/panel/singlestat/module.ts @@ -174,10 +174,16 @@ class SingleStatCtrl extends MetricsPanelCtrl { const datapoint = tableData[0][0]; data.value = datapoint[this.panel.tableColumn]; - var decimalInfo = this.getDecimalsForValue(data.value); - var formatFunc = kbn.valueFormats[this.panel.format]; - data.valueFormatted = formatFunc(datapoint[this.panel.tableColumn], decimalInfo.decimals, decimalInfo.scaledDecimals); - data.valueRounded = kbn.roundValue(data.value, this.panel.decimals || 0); + if (_.isString(data.value)) { + data.valueFormatted = _.escape(data.value); + data.value = 0; + data.valueRounded = 0; + } else { + const decimalInfo = this.getDecimalsForValue(data.value); + const formatFunc = kbn.valueFormats[this.panel.format]; + data.valueFormatted = formatFunc(datapoint[this.panel.tableColumn], decimalInfo.decimals, decimalInfo.scaledDecimals); + data.valueRounded = kbn.roundValue(data.value, this.panel.decimals || 0); + } this.setValueMapping(data); } diff --git a/public/app/plugins/panel/singlestat/specs/singlestat_specs.ts b/public/app/plugins/panel/singlestat/specs/singlestat_specs.ts index 2bc36d67c3a..6e05266b8fe 100644 --- a/public/app/plugins/panel/singlestat/specs/singlestat_specs.ts +++ b/public/app/plugins/panel/singlestat/specs/singlestat_specs.ts @@ -242,6 +242,18 @@ describe('SingleStatCtrl', function() { expect(ctx.data.valueFormatted).to.be('NOT OK'); }); }); + + singleStatScenario('When value is string', function(ctx) { + ctx.setup(function() { + ctx.data = tableData; + ctx.data[0].rows[0] = [1492759673649,'ignore1', 65, 'ignore2']; + ctx.ctrl.panel.tableColumn = 'test1'; + }); + + it('Should replace value with text NOT OK', function() { + expect(ctx.data.valueFormatted).to.be('ignore1'); + }); + }); }); });