mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
SingleStat: use DataFrame results rather than TimeSeries/TableData (#18580)
This commit is contained in:
parent
e6fbf358c8
commit
68f7413566
@ -501,6 +501,237 @@
|
||||
}
|
||||
],
|
||||
"valueName": "current"
|
||||
},
|
||||
{
|
||||
"cacheTimeout": null,
|
||||
"colorBackground": false,
|
||||
"colorPrefix": false,
|
||||
"colorValue": false,
|
||||
"colors": ["#299c46", "rgba(237, 129, 40, 0.89)", "#d44a3a"],
|
||||
"datasource": "gdev-testdata",
|
||||
"decimals": null,
|
||||
"description": "",
|
||||
"format": "none",
|
||||
"gauge": {
|
||||
"maxValue": 150,
|
||||
"minValue": 0,
|
||||
"show": false,
|
||||
"thresholdLabels": false,
|
||||
"thresholdMarkers": true
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 4,
|
||||
"w": 8,
|
||||
"x": 0,
|
||||
"y": 14
|
||||
},
|
||||
"id": 8,
|
||||
"interval": null,
|
||||
"links": [],
|
||||
"mappingType": 1,
|
||||
"mappingTypes": [
|
||||
{
|
||||
"name": "value to text",
|
||||
"value": 1
|
||||
},
|
||||
{
|
||||
"name": "range to text",
|
||||
"value": 2
|
||||
}
|
||||
],
|
||||
"maxDataPoints": 100,
|
||||
"nullPointMode": "connected",
|
||||
"nullText": null,
|
||||
"options": {},
|
||||
"postfix": "",
|
||||
"postfixFontSize": "50%",
|
||||
"prefix": "",
|
||||
"prefixFontSize": "50%",
|
||||
"rangeMaps": [
|
||||
{
|
||||
"from": "null",
|
||||
"text": "N/A",
|
||||
"to": "null"
|
||||
}
|
||||
],
|
||||
"sparkline": {
|
||||
"fillColor": "rgba(31, 118, 189, 0.18)",
|
||||
"full": true,
|
||||
"lineColor": "rgb(31, 120, 193)",
|
||||
"show": false
|
||||
},
|
||||
"tableColumn": "Info",
|
||||
"targets": [
|
||||
{
|
||||
"alias": "",
|
||||
"expr": "",
|
||||
"format": "time_series",
|
||||
"intervalFactor": 1,
|
||||
"refId": "A",
|
||||
"scenarioId": "random_walk_table",
|
||||
"stringInput": ""
|
||||
}
|
||||
],
|
||||
"thresholds": "81,90",
|
||||
"title": "TableData 'Info' string Column",
|
||||
"type": "singlestat",
|
||||
"valueFontSize": "80%",
|
||||
"valueMaps": [],
|
||||
"valueName": "current"
|
||||
},
|
||||
{
|
||||
"cacheTimeout": null,
|
||||
"colorBackground": false,
|
||||
"colorPrefix": false,
|
||||
"colorValue": false,
|
||||
"colors": ["#299c46", "rgba(237, 129, 40, 0.89)", "#d44a3a"],
|
||||
"datasource": "gdev-testdata",
|
||||
"decimals": 2,
|
||||
"description": "",
|
||||
"format": "celsius",
|
||||
"gauge": {
|
||||
"maxValue": 150,
|
||||
"minValue": 0,
|
||||
"show": false,
|
||||
"thresholdLabels": false,
|
||||
"thresholdMarkers": true
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 4,
|
||||
"w": 8,
|
||||
"x": 8,
|
||||
"y": 14
|
||||
},
|
||||
"id": 9,
|
||||
"interval": null,
|
||||
"links": [],
|
||||
"mappingType": 1,
|
||||
"mappingTypes": [
|
||||
{
|
||||
"name": "value to text",
|
||||
"value": 1
|
||||
},
|
||||
{
|
||||
"name": "range to text",
|
||||
"value": 2
|
||||
}
|
||||
],
|
||||
"maxDataPoints": 100,
|
||||
"nullPointMode": "connected",
|
||||
"nullText": null,
|
||||
"options": {},
|
||||
"postfix": "",
|
||||
"postfixFontSize": "50%",
|
||||
"prefix": "",
|
||||
"prefixFontSize": "50%",
|
||||
"rangeMaps": [
|
||||
{
|
||||
"from": "null",
|
||||
"text": "N/A",
|
||||
"to": "null"
|
||||
}
|
||||
],
|
||||
"sparkline": {
|
||||
"fillColor": "rgba(31, 118, 189, 0.18)",
|
||||
"full": true,
|
||||
"lineColor": "rgb(31, 120, 193)",
|
||||
"show": false
|
||||
},
|
||||
"tableColumn": "Min",
|
||||
"targets": [
|
||||
{
|
||||
"alias": "",
|
||||
"expr": "",
|
||||
"format": "time_series",
|
||||
"intervalFactor": 1,
|
||||
"refId": "A",
|
||||
"scenarioId": "random_walk_table",
|
||||
"stringInput": ""
|
||||
}
|
||||
],
|
||||
"thresholds": "81,90",
|
||||
"title": "TableData 'Value' as temp Column",
|
||||
"type": "singlestat",
|
||||
"valueFontSize": "80%",
|
||||
"valueMaps": [],
|
||||
"valueName": "current"
|
||||
},
|
||||
{
|
||||
"cacheTimeout": null,
|
||||
"colorBackground": false,
|
||||
"colorPrefix": false,
|
||||
"colorValue": false,
|
||||
"colors": ["#299c46", "rgba(237, 129, 40, 0.89)", "#d44a3a"],
|
||||
"datasource": "gdev-testdata",
|
||||
"decimals": null,
|
||||
"description": "",
|
||||
"format": "dateTimeFromNow",
|
||||
"gauge": {
|
||||
"maxValue": 150,
|
||||
"minValue": 0,
|
||||
"show": false,
|
||||
"thresholdLabels": false,
|
||||
"thresholdMarkers": true
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 4,
|
||||
"w": 8,
|
||||
"x": 16,
|
||||
"y": 14
|
||||
},
|
||||
"id": 10,
|
||||
"interval": null,
|
||||
"links": [],
|
||||
"mappingType": 1,
|
||||
"mappingTypes": [
|
||||
{
|
||||
"name": "value to text",
|
||||
"value": 1
|
||||
},
|
||||
{
|
||||
"name": "range to text",
|
||||
"value": 2
|
||||
}
|
||||
],
|
||||
"maxDataPoints": 100,
|
||||
"nullPointMode": "connected",
|
||||
"nullText": null,
|
||||
"options": {},
|
||||
"postfix": "",
|
||||
"postfixFontSize": "50%",
|
||||
"prefix": "",
|
||||
"prefixFontSize": "50%",
|
||||
"rangeMaps": [
|
||||
{
|
||||
"from": "null",
|
||||
"text": "N/A",
|
||||
"to": "null"
|
||||
}
|
||||
],
|
||||
"sparkline": {
|
||||
"fillColor": "rgba(31, 118, 189, 0.18)",
|
||||
"full": true,
|
||||
"lineColor": "rgb(31, 120, 193)",
|
||||
"show": false
|
||||
},
|
||||
"tableColumn": "time",
|
||||
"targets": [
|
||||
{
|
||||
"alias": "",
|
||||
"expr": "",
|
||||
"format": "time_series",
|
||||
"intervalFactor": 1,
|
||||
"refId": "A",
|
||||
"scenarioId": "random_walk",
|
||||
"stringInput": ""
|
||||
}
|
||||
],
|
||||
"thresholds": "81,90",
|
||||
"title": "last_time display (a few seconds ago)",
|
||||
"type": "singlestat",
|
||||
"valueFontSize": "80%",
|
||||
"valueMaps": [],
|
||||
"valueName": "last_time"
|
||||
}
|
||||
],
|
||||
"refresh": false,
|
||||
|
@ -192,7 +192,7 @@ export function migrateOldThresholds(thresholds?: any[]): Threshold[] | undefine
|
||||
/**
|
||||
* Convert the angular single stat mapping to new react style
|
||||
*/
|
||||
function convertOldAngulrValueMapping(panel: any): ValueMapping[] {
|
||||
export function convertOldAngulrValueMapping(panel: any): ValueMapping[] {
|
||||
const mappings: ValueMapping[] = [];
|
||||
|
||||
// Guess the right type based on options
|
||||
|
@ -5,4 +5,5 @@ export {
|
||||
SingleStatBaseOptions,
|
||||
sharedSingleStatPanelChangedHandler,
|
||||
sharedSingleStatMigrationHandler,
|
||||
convertOldAngulrValueMapping,
|
||||
} from './SingleStatBaseOptions';
|
||||
|
@ -58,6 +58,14 @@ export function getDisplayProcessor(options?: DisplayProcessorOptions): DisplayP
|
||||
if (shouldFormat && !_.isBoolean(value)) {
|
||||
const { decimals, scaledDecimals } = getDecimalsForValue(value, field.decimals);
|
||||
text = formatFunc(numeric, decimals, scaledDecimals, options.isUtc);
|
||||
|
||||
// Check if the formatted text mapped to a different value
|
||||
if (mappings && mappings.length > 0) {
|
||||
const mappedValue = getMappedValue(mappings, text);
|
||||
if (mappedValue) {
|
||||
text = mappedValue.text;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (thresholds && thresholds.length) {
|
||||
color = getColorFromThreshold(numeric, thresholds, theme);
|
||||
|
@ -40,16 +40,18 @@
|
||||
<h5 class="section-heading">Value</h5>
|
||||
|
||||
<div class="gf-form-inline">
|
||||
<div class="gf-form" ng-show="ctrl.dataType === 'timeseries'">
|
||||
<label class="gf-form-label width-6">Stat</label>
|
||||
<div class="gf-form" ng-if="ctrl.fieldNames.length > 1">
|
||||
<label class="gf-form-label width-6">Field</label>
|
||||
<div class="gf-form-select-wrapper width-12">
|
||||
<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>
|
||||
<select class="gf-form-input" ng-model="ctrl.panel.tableColumn" ng-options="f for f in ctrl.fieldNames" ng-change="ctrl.refresh()"></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gf-form" ng-show="ctrl.dataType === 'table'">
|
||||
<label class="gf-form-label width-6">Column</label>
|
||||
</div>
|
||||
<div class="gf-form-inline">
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-6">Show</label>
|
||||
<div class="gf-form-select-wrapper width-12">
|
||||
<select class="gf-form-input" ng-model="ctrl.panel.tableColumn" ng-options="f for f in ctrl.tableColumnOptions" ng-change="ctrl.refresh()"></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">
|
||||
@ -64,19 +66,24 @@
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-6">Prefix</label>
|
||||
<input type="text" class="gf-form-input width-12" ng-model="ctrl.panel.prefix" ng-change="ctrl.render()" ng-model-onblur>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-6">Font size</label>
|
||||
<div class="gf-form-select-wrapper">
|
||||
<select class="gf-form-input" ng-model="ctrl.panel.prefixFontSize" ng-options="f for f in ctrl.fontSizes" ng-change="ctrl.render()" ng-disabled="!ctrl.canModifyText()"></select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-6">Postfix</label>
|
||||
<input type="text" class="gf-form-input width-12" ng-model="ctrl.panel.postfix" ng-change="ctrl.render()" ng-model-onblur>
|
||||
<label class="gf-form-label width-6">Font size</label>
|
||||
<div class="gf-form-select-wrapper">
|
||||
<select class="input-small gf-form-input" ng-model="ctrl.panel.postfixFontSize" ng-options="f for f in ctrl.fontSizes" ng-change="ctrl.render()" ng-disabled="!ctrl.canModifyText()"></select>
|
||||
<div class="gf-form-inline">
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-6">Postfix</label>
|
||||
<input type="text" class="gf-form-input width-12" ng-model="ctrl.panel.postfix" ng-change="ctrl.render()" ng-model-onblur>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-6">Font size</label>
|
||||
<div class="gf-form-select-wrapper">
|
||||
<select class="input-small gf-form-input" ng-model="ctrl.panel.postfixFontSize" ng-options="f for f in ctrl.fontSizes" ng-change="ctrl.render()" ng-disabled="!ctrl.canModifyText()"></select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
@ -122,7 +129,7 @@
|
||||
|
||||
<div class="section gf-form-group">
|
||||
<h5 class="section-heading">Spark lines</h5>
|
||||
<gf-form-switch class="gf-form" label-class="width-9" label="Show" checked="ctrl.panel.sparkline.show" on-change="ctrl.render()"></gf-form-switch>
|
||||
<gf-form-switch class="gf-form" label-class="width-9" label="Show" checked="ctrl.panel.sparkline.show" on-change="ctrl.refresh()"></gf-form-switch>
|
||||
<div ng-if="ctrl.panel.sparkline.show">
|
||||
<gf-form-switch class="gf-form" label-class="width-9" label="Full height" checked="ctrl.panel.sparkline.full" on-change="ctrl.render()"></gf-form-switch>
|
||||
<div class="gf-form">
|
||||
|
@ -6,7 +6,7 @@
|
||||
</span>
|
||||
<div class="gf-form-select-wrapper">
|
||||
<select class="gf-form-input" ng-model="ctrl.panel.mappingType"
|
||||
ng-options="f.value as f.name for f in ctrl.panel.mappingTypes" ng-change="ctrl.render()"></select>
|
||||
ng-options="f.value as f.name for f in ctrl.panel.mappingTypes" ng-change="ctrl.refresh()"></select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -18,11 +18,11 @@
|
||||
<span class="gf-form-label">
|
||||
<i class="fa fa-remove pointer" ng-click="ctrl.removeValueMap(map)"></i>
|
||||
</span>
|
||||
<input type="text" ng-model="map.value" placeholder="value" class="gf-form-input max-width-6" ng-blur="ctrl.render()">
|
||||
<input type="text" ng-model="map.value" placeholder="value" class="gf-form-input max-width-6" ng-blur="ctrl.refresh()">
|
||||
<span class="gf-form-label">
|
||||
<i class="fa fa-arrow-right"></i>
|
||||
</span>
|
||||
<input type="text" placeholder="text" ng-model="map.text" class="gf-form-input max-width-8" ng-blur="ctrl.render()">
|
||||
<input type="text" placeholder="text" ng-model="map.text" class="gf-form-input max-width-8" ng-blur="ctrl.refresh()">
|
||||
</div>
|
||||
|
||||
<div class="gf-form-button-row">
|
||||
@ -41,11 +41,11 @@
|
||||
<i class="fa fa-remove pointer" ng-click="ctrl.removeRangeMap(rangeMap)"></i>
|
||||
</span>
|
||||
<span class="gf-form-label">From</span>
|
||||
<input type="text" ng-model="rangeMap.from" class="gf-form-input max-width-6" ng-blur="ctrl.render()">
|
||||
<input type="text" ng-model="rangeMap.from" class="gf-form-input max-width-6" ng-blur="ctrl.refresh()">
|
||||
<span class="gf-form-label">To</span>
|
||||
<input type="text" ng-model="rangeMap.to" class="gf-form-input max-width-6" ng-blur="ctrl.render()">
|
||||
<input type="text" ng-model="rangeMap.to" class="gf-form-input max-width-6" ng-blur="ctrl.refresh()">
|
||||
<span class="gf-form-label">Text</span>
|
||||
<input type="text" ng-model="rangeMap.text" class="gf-form-input max-width-8" ng-blur="ctrl.render()">
|
||||
<input type="text" ng-model="rangeMap.text" class="gf-form-input max-width-8" ng-blur="ctrl.refresh()">
|
||||
</div>
|
||||
|
||||
<div class="gf-form-button-row">
|
||||
|
@ -3,34 +3,56 @@ import $ from 'jquery';
|
||||
import 'vendor/flot/jquery.flot';
|
||||
import 'vendor/flot/jquery.flot.gauge';
|
||||
import 'app/features/panel/panellinks/link_srv';
|
||||
import { getDecimalsForValue } from '@grafana/ui';
|
||||
import {
|
||||
LegacyResponseData,
|
||||
getFlotPairs,
|
||||
getDisplayProcessor,
|
||||
convertOldAngulrValueMapping,
|
||||
getColorFromHexRgbOrName,
|
||||
} from '@grafana/ui';
|
||||
|
||||
import kbn from 'app/core/utils/kbn';
|
||||
import config from 'app/core/config';
|
||||
import TimeSeries from 'app/core/time_series2';
|
||||
import { MetricsPanelCtrl } from 'app/plugins/sdk';
|
||||
import { isTableData } from '@grafana/data';
|
||||
import { GrafanaThemeType, getValueFormat, getColorFromHexRgbOrName } from '@grafana/ui';
|
||||
import {
|
||||
DataFrame,
|
||||
FieldType,
|
||||
reduceField,
|
||||
ReducerID,
|
||||
Field,
|
||||
GraphSeriesValue,
|
||||
DisplayValue,
|
||||
fieldReducers,
|
||||
KeyValue,
|
||||
} from '@grafana/data';
|
||||
import { auto } from 'angular';
|
||||
import { LinkSrv, LinkModel } from 'app/features/panel/panellinks/link_srv';
|
||||
import TableModel from 'app/core/table_model';
|
||||
import { PanelQueryRunnerFormat } from 'app/features/dashboard/state/PanelQueryRunner';
|
||||
import { getProcessedDataFrames } from 'app/features/dashboard/state/PanelQueryState';
|
||||
|
||||
const BASE_FONT_SIZE = 38;
|
||||
|
||||
interface DataFormat {
|
||||
value: string | number;
|
||||
valueFormatted: string;
|
||||
valueRounded: number;
|
||||
export interface ShowData {
|
||||
field: Field;
|
||||
value: any;
|
||||
sparkline: GraphSeriesValue[][];
|
||||
display: DisplayValue;
|
||||
|
||||
scopedVars: any;
|
||||
|
||||
thresholds: any[];
|
||||
colorMap: any;
|
||||
}
|
||||
|
||||
class SingleStatCtrl extends MetricsPanelCtrl {
|
||||
static templateUrl = 'module.html';
|
||||
|
||||
dataType = 'timeseries';
|
||||
series: any[];
|
||||
data: any;
|
||||
data: Partial<ShowData> = {};
|
||||
|
||||
fontSizes: any[];
|
||||
unitFormats: any[];
|
||||
fieldNames: string[] = [];
|
||||
|
||||
invalidGaugeRange: boolean;
|
||||
panel: any;
|
||||
events: any;
|
||||
@ -47,7 +69,6 @@ class SingleStatCtrl extends MetricsPanelCtrl {
|
||||
{ value: 'range', text: 'Range' },
|
||||
{ value: 'last_time', text: 'Time of last point' },
|
||||
];
|
||||
tableColumnOptions: any;
|
||||
|
||||
// Set and populate defaults
|
||||
panelDefaults: any = {
|
||||
@ -102,6 +123,8 @@ class SingleStatCtrl extends MetricsPanelCtrl {
|
||||
this.events.on('data-snapshot-load', this.onDataReceived.bind(this));
|
||||
this.events.on('init-edit-mode', this.onInitEditMode.bind(this));
|
||||
|
||||
this.dataFormat = PanelQueryRunnerFormat.frames;
|
||||
|
||||
this.onSparklineColorChange = this.onSparklineColorChange.bind(this);
|
||||
this.onSparklineFillChange = this.onSparklineFillChange.bind(this);
|
||||
}
|
||||
@ -128,104 +151,115 @@ class SingleStatCtrl extends MetricsPanelCtrl {
|
||||
}
|
||||
|
||||
onDataError(err: any) {
|
||||
this.onDataReceived([]);
|
||||
this.handleDataFrames([]);
|
||||
}
|
||||
|
||||
onDataReceived(dataList: any[]) {
|
||||
const data: any = {
|
||||
scopedVars: _.extend({}, this.panel.scopedVars),
|
||||
};
|
||||
// This should only be called from the snapshot callback
|
||||
onDataReceived(dataList: LegacyResponseData[]) {
|
||||
this.handleDataFrames(getProcessedDataFrames(dataList));
|
||||
}
|
||||
|
||||
if (dataList.length > 0 && isTableData(dataList[0])) {
|
||||
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);
|
||||
// Directly support DataFrame skipping event callbacks
|
||||
handleDataFrames(frames: DataFrame[]) {
|
||||
const { panel } = this;
|
||||
super.handleDataFrames(frames);
|
||||
this.loading = false;
|
||||
|
||||
const distinct = getDistinctNames(frames);
|
||||
let fieldInfo = distinct.byName[panel.tableColumn]; //
|
||||
this.fieldNames = distinct.names;
|
||||
|
||||
if (!fieldInfo) {
|
||||
fieldInfo = distinct.first;
|
||||
}
|
||||
|
||||
if (!fieldInfo) {
|
||||
// When we don't have any field
|
||||
this.data = {
|
||||
value: 'No Data',
|
||||
display: {
|
||||
text: 'No Data',
|
||||
numeric: NaN,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
this.data = this.processField(fieldInfo);
|
||||
}
|
||||
|
||||
this.data = data;
|
||||
this.render();
|
||||
}
|
||||
|
||||
seriesHandler(dataFrame: any) {
|
||||
const series = new TimeSeries({
|
||||
datapoints: dataFrame.datapoints || [],
|
||||
alias: dataFrame.target,
|
||||
});
|
||||
processField(fieldInfo: FieldInfo) {
|
||||
const { panel, dashboard } = this;
|
||||
|
||||
series.flotpairs = series.getFlotPairs(this.panel.nullPointMode);
|
||||
return series;
|
||||
}
|
||||
const name = fieldInfo.field.config.title || fieldInfo.field.name;
|
||||
let calc = panel.valueName;
|
||||
let calcField = fieldInfo.field;
|
||||
let val: any = undefined;
|
||||
|
||||
tableHandler(tableData: TableModel) {
|
||||
const datapoints: any[] = [];
|
||||
const columnNames: string[] = [];
|
||||
if ('name' === calc) {
|
||||
val = name;
|
||||
} else {
|
||||
if ('last_time' === calc) {
|
||||
if (fieldInfo.frame.firstTimeField) {
|
||||
calcField = fieldInfo.frame.firstTimeField;
|
||||
calc = ReducerID.last;
|
||||
}
|
||||
}
|
||||
|
||||
tableData.columns.forEach((column, columnIndex) => {
|
||||
columnNames[columnIndex] = column.text;
|
||||
});
|
||||
// Normalize functions (avg -> mean, etc)
|
||||
const r = fieldReducers.getIfExists(calc);
|
||||
if (r) {
|
||||
calc = r.id;
|
||||
// With strings, don't accidentally use a math function
|
||||
if (calcField.type === FieldType.string) {
|
||||
const avoid = [ReducerID.mean, ReducerID.sum];
|
||||
if (avoid.includes(calc)) {
|
||||
calc = panel.valueName = ReducerID.first;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
calc = ReducerID.lastNotNull;
|
||||
}
|
||||
|
||||
this.tableColumnOptions = columnNames;
|
||||
if (!_.find(tableData.columns, ['text', this.panel.tableColumn])) {
|
||||
this.setTableColumnToSensibleDefault(tableData);
|
||||
// Calculate the value
|
||||
val = reduceField({
|
||||
field: calcField,
|
||||
reducers: [calc],
|
||||
})[calc];
|
||||
}
|
||||
|
||||
tableData.rows.forEach(row => {
|
||||
const datapoint: any = {};
|
||||
const processor = getDisplayProcessor({
|
||||
field: {
|
||||
...fieldInfo.field.config,
|
||||
unit: panel.format,
|
||||
decimals: panel.decimals,
|
||||
mappings: convertOldAngulrValueMapping(panel),
|
||||
},
|
||||
theme: config.theme,
|
||||
isUtc: dashboard.isTimezoneUtc && dashboard.isTimezoneUtc(),
|
||||
});
|
||||
|
||||
row.forEach((value: any, columnIndex: number) => {
|
||||
const key = columnNames[columnIndex];
|
||||
datapoint[key] = value;
|
||||
const data = {
|
||||
field: fieldInfo.field,
|
||||
value: val,
|
||||
display: processor(val),
|
||||
scopedVars: _.extend({}, panel.scopedVars),
|
||||
};
|
||||
|
||||
data.scopedVars['__name'] = name;
|
||||
panel.tableColumn = this.fieldNames.length > 1 ? name : '';
|
||||
|
||||
// Get the fields for a sparkline
|
||||
if (panel.sparkline && panel.sparkline.show && fieldInfo.frame.firstTimeField) {
|
||||
this.data.sparkline = getFlotPairs({
|
||||
xField: fieldInfo.frame.firstTimeField,
|
||||
yField: fieldInfo.field,
|
||||
nullValueMode: panel.nullPointMode,
|
||||
});
|
||||
|
||||
datapoints.push(datapoint);
|
||||
});
|
||||
|
||||
return datapoints;
|
||||
}
|
||||
|
||||
setTableColumnToSensibleDefault(tableData: TableModel) {
|
||||
if (tableData.columns.length === 1) {
|
||||
this.panel.tableColumn = tableData.columns[0].text;
|
||||
} else {
|
||||
this.panel.tableColumn = _.find(tableData.columns, col => {
|
||||
return col.type !== 'time';
|
||||
}).text;
|
||||
}
|
||||
}
|
||||
|
||||
setTableValues(tableData: any[], data: DataFormat) {
|
||||
if (!tableData || tableData.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (tableData[0].length === 0 || tableData[0][0][this.panel.tableColumn] === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
const datapoint = tableData[0][0];
|
||||
data.value = datapoint[this.panel.tableColumn];
|
||||
|
||||
if (_.isString(data.value)) {
|
||||
data.valueFormatted = _.escape(data.value);
|
||||
data.value = 0;
|
||||
data.valueRounded = 0;
|
||||
} else {
|
||||
const decimalInfo = getDecimalsForValue(data.value, this.panel.decimals);
|
||||
const formatFunc = getValueFormat(this.panel.format);
|
||||
|
||||
data.valueFormatted = formatFunc(
|
||||
datapoint[this.panel.tableColumn],
|
||||
decimalInfo.decimals,
|
||||
decimalInfo.scaledDecimals
|
||||
);
|
||||
data.valueRounded = kbn.roundValue(data.value, decimalInfo.decimals);
|
||||
}
|
||||
|
||||
this.setValueMapping(data);
|
||||
return data;
|
||||
}
|
||||
|
||||
canModifyText() {
|
||||
@ -267,106 +301,6 @@ class SingleStatCtrl extends MetricsPanelCtrl {
|
||||
this.render();
|
||||
}
|
||||
|
||||
setValues(data: any) {
|
||||
data.flotpairs = [];
|
||||
|
||||
if (this.series.length > 1) {
|
||||
const error: any = new Error();
|
||||
error.message = 'Multiple Series Error';
|
||||
error.data =
|
||||
'Metric query returns ' +
|
||||
this.series.length +
|
||||
' series. Single Stat Panel expects a single series.\n\nResponse:\n' +
|
||||
JSON.stringify(this.series);
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (this.series && this.series.length > 0) {
|
||||
const lastPoint: any = _.last(this.series[0].datapoints);
|
||||
const lastValue = _.isArray(lastPoint) ? lastPoint[0] : null;
|
||||
const formatFunc = getValueFormat(this.panel.format);
|
||||
|
||||
if (this.panel.valueName === 'name') {
|
||||
data.value = 0;
|
||||
data.valueRounded = 0;
|
||||
data.valueFormatted = this.series[0].alias;
|
||||
} else if (_.isString(lastValue)) {
|
||||
data.value = 0;
|
||||
data.valueFormatted = _.escape(lastValue);
|
||||
data.valueRounded = 0;
|
||||
} else if (this.panel.valueName === 'last_time') {
|
||||
data.value = lastPoint[1];
|
||||
data.valueRounded = data.value;
|
||||
data.valueFormatted = formatFunc(data.value, 0, 0, this.dashboard.isTimezoneUtc());
|
||||
} else {
|
||||
data.value = this.series[0].stats[this.panel.valueName];
|
||||
data.flotpairs = this.series[0].flotpairs;
|
||||
|
||||
const decimalInfo = getDecimalsForValue(data.value, this.panel.decimals);
|
||||
|
||||
data.valueFormatted = formatFunc(
|
||||
data.value,
|
||||
decimalInfo.decimals,
|
||||
decimalInfo.scaledDecimals,
|
||||
this.dashboard.isTimezoneUtc()
|
||||
);
|
||||
data.valueRounded = kbn.roundValue(data.value, decimalInfo.decimals);
|
||||
}
|
||||
|
||||
// Add $__name variable for using in prefix or postfix
|
||||
data.scopedVars['__name'] = { value: this.series[0].label };
|
||||
}
|
||||
this.setValueMapping(data);
|
||||
}
|
||||
|
||||
setValueMapping(data: DataFormat) {
|
||||
// check value to text mappings if its enabled
|
||||
if (this.panel.mappingType === 1) {
|
||||
for (let i = 0; i < this.panel.valueMaps.length; i++) {
|
||||
const map = this.panel.valueMaps[i];
|
||||
// special null case
|
||||
if (map.value === 'null') {
|
||||
if (data.value === null || data.value === void 0) {
|
||||
data.valueFormatted = map.text;
|
||||
return;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// value/number to text mapping
|
||||
const value = parseFloat(map.value);
|
||||
if (value === data.valueRounded) {
|
||||
data.valueFormatted = map.text;
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else if (this.panel.mappingType === 2) {
|
||||
for (let i = 0; i < this.panel.rangeMaps.length; i++) {
|
||||
const map = this.panel.rangeMaps[i];
|
||||
// special null case
|
||||
if (map.from === 'null' && map.to === 'null') {
|
||||
if (data.value === null || data.value === void 0) {
|
||||
data.valueFormatted = map.text;
|
||||
return;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// value/number to range mapping
|
||||
const from = parseFloat(map.from);
|
||||
const to = parseFloat(map.to);
|
||||
if (to >= data.valueRounded && from <= data.valueRounded) {
|
||||
data.valueFormatted = map.text;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (data.value === null || data.value === void 0) {
|
||||
data.valueFormatted = 'no value';
|
||||
}
|
||||
}
|
||||
|
||||
removeValueMap(map: any) {
|
||||
const index = _.indexOf(this.panel.valueMaps, map);
|
||||
this.panel.valueMaps.splice(index, 1);
|
||||
@ -394,12 +328,12 @@ class SingleStatCtrl extends MetricsPanelCtrl {
|
||||
const $sanitize = this.$sanitize;
|
||||
const panel = ctrl.panel;
|
||||
const templateSrv = this.templateSrv;
|
||||
let data: any;
|
||||
let linkInfo: LinkModel | null = null;
|
||||
const $panelContainer = elem.find('.panel-container');
|
||||
elem = elem.find('.singlestat-panel');
|
||||
|
||||
function applyColoringThresholds(valueString: string) {
|
||||
const data = ctrl.data;
|
||||
const color = getColorForValue(data, data.value);
|
||||
if (color) {
|
||||
return '<span style="color:' + color + '">' + valueString + '</span>';
|
||||
@ -409,20 +343,21 @@ class SingleStatCtrl extends MetricsPanelCtrl {
|
||||
}
|
||||
|
||||
function getSpan(className: string, fontSizePercent: string, applyColoring: any, value: string) {
|
||||
value = $sanitize(templateSrv.replace(value, data.scopedVars));
|
||||
value = $sanitize(templateSrv.replace(value, ctrl.data.scopedVars));
|
||||
value = applyColoring ? applyColoringThresholds(value) : value;
|
||||
const pixelSize = (parseInt(fontSizePercent, 10) / 100) * BASE_FONT_SIZE;
|
||||
return '<span class="' + className + '" style="font-size:' + pixelSize + 'px">' + value + '</span>';
|
||||
}
|
||||
|
||||
function getBigValueHtml() {
|
||||
const data: ShowData = ctrl.data;
|
||||
let body = '<div class="singlestat-panel-value-container">';
|
||||
|
||||
if (panel.prefix) {
|
||||
body += getSpan('singlestat-panel-prefix', panel.prefixFontSize, panel.colorPrefix, panel.prefix);
|
||||
}
|
||||
|
||||
body += getSpan('singlestat-panel-value', panel.valueFontSize, panel.colorValue, data.valueFormatted);
|
||||
body += getSpan('singlestat-panel-value', panel.valueFontSize, panel.colorValue, data.display.text);
|
||||
|
||||
if (panel.postfix) {
|
||||
body += getSpan('singlestat-panel-postfix', panel.postfixFontSize, panel.colorPostfix, panel.postfix);
|
||||
@ -434,14 +369,16 @@ class SingleStatCtrl extends MetricsPanelCtrl {
|
||||
}
|
||||
|
||||
function getValueText() {
|
||||
const data: ShowData = ctrl.data;
|
||||
let result = panel.prefix ? templateSrv.replace(panel.prefix, data.scopedVars) : '';
|
||||
result += data.valueFormatted;
|
||||
result += data.display.text;
|
||||
result += panel.postfix ? templateSrv.replace(panel.postfix, data.scopedVars) : '';
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function addGauge() {
|
||||
const data: ShowData = ctrl.data;
|
||||
const width = elem.width();
|
||||
const height = elem.height();
|
||||
// Allow to use a bit more space for wide gauges
|
||||
@ -513,7 +450,7 @@ class SingleStatCtrl extends MetricsPanelCtrl {
|
||||
width: thresholdMarkersWidth,
|
||||
},
|
||||
value: {
|
||||
color: panel.colorValue ? getColorForValue(data, data.valueRounded) : null,
|
||||
color: panel.colorValue ? getColorForValue(data, data.display.numeric) : null,
|
||||
formatter: () => {
|
||||
return getValueText();
|
||||
},
|
||||
@ -537,6 +474,7 @@ class SingleStatCtrl extends MetricsPanelCtrl {
|
||||
}
|
||||
|
||||
function addSparkline() {
|
||||
const data: ShowData = ctrl.data;
|
||||
const width = elem.width();
|
||||
if (width < 30) {
|
||||
// element has not gotten it's width yet
|
||||
@ -544,6 +482,10 @@ class SingleStatCtrl extends MetricsPanelCtrl {
|
||||
setTimeout(addSparkline, 30);
|
||||
return;
|
||||
}
|
||||
if (!data.sparkline || !data.sparkline.length) {
|
||||
// no sparkline data
|
||||
return;
|
||||
}
|
||||
|
||||
const height = ctrl.height;
|
||||
const plotCanvas = $('<div></div>');
|
||||
@ -592,7 +534,7 @@ class SingleStatCtrl extends MetricsPanelCtrl {
|
||||
elem.append(plotCanvas);
|
||||
|
||||
const plotSeries = {
|
||||
data: data.flotpairs,
|
||||
data: data.sparkline,
|
||||
color: getColorFromHexRgbOrName(panel.sparkline.lineColor, config.theme.type),
|
||||
};
|
||||
|
||||
@ -603,26 +545,24 @@ class SingleStatCtrl extends MetricsPanelCtrl {
|
||||
if (!ctrl.data) {
|
||||
return;
|
||||
}
|
||||
data = ctrl.data;
|
||||
const { data, panel } = ctrl;
|
||||
|
||||
// get thresholds
|
||||
data.thresholds = panel.thresholds.split(',').map((strVale: string) => {
|
||||
return Number(strVale.trim());
|
||||
});
|
||||
data.thresholds = panel.thresholds
|
||||
? panel.thresholds.split(',').map((strVale: string) => {
|
||||
return Number(strVale.trim());
|
||||
})
|
||||
: [];
|
||||
|
||||
// Map panel colors to hex or rgb/a values
|
||||
data.colorMap = panel.colors.map((color: string) =>
|
||||
getColorFromHexRgbOrName(
|
||||
color,
|
||||
config.bootData.user.lightTheme ? GrafanaThemeType.Light : GrafanaThemeType.Dark
|
||||
)
|
||||
);
|
||||
if (panel.colors) {
|
||||
data.colorMap = panel.colors.map((color: string) => getColorFromHexRgbOrName(color, config.theme.type));
|
||||
}
|
||||
|
||||
const body = panel.gauge.show ? '' : getBigValueHtml();
|
||||
|
||||
if (panel.colorBackground) {
|
||||
const color = getColorForValue(data, data.value);
|
||||
console.log(color);
|
||||
const color = getColorForValue(data, data.display.numeric);
|
||||
if (color) {
|
||||
$panelContainer.css('background-color', color);
|
||||
if (scope.fullscreen) {
|
||||
@ -729,4 +669,59 @@ function getColorForValue(data: any, value: number) {
|
||||
return _.first(data.colorMap);
|
||||
}
|
||||
|
||||
//------------------------------------------------
|
||||
// Private utility functions
|
||||
// Somethign like this should be avaliable in a
|
||||
// DataFrame[] abstraction helper
|
||||
//------------------------------------------------
|
||||
|
||||
interface FrameInfo {
|
||||
firstTimeField?: Field;
|
||||
frame: DataFrame;
|
||||
}
|
||||
|
||||
interface FieldInfo {
|
||||
field: Field;
|
||||
frame: FrameInfo;
|
||||
}
|
||||
|
||||
interface DistinctFieldsInfo {
|
||||
first?: FieldInfo;
|
||||
byName: KeyValue<FieldInfo>;
|
||||
names: string[];
|
||||
}
|
||||
|
||||
function getDistinctNames(data: DataFrame[]): DistinctFieldsInfo {
|
||||
const distinct: DistinctFieldsInfo = {
|
||||
byName: {},
|
||||
names: [],
|
||||
};
|
||||
for (const frame of data) {
|
||||
const info: FrameInfo = { frame };
|
||||
for (const field of frame.fields) {
|
||||
if (field.type === FieldType.time) {
|
||||
if (!info.firstTimeField) {
|
||||
info.firstTimeField = field;
|
||||
}
|
||||
} else {
|
||||
const f = { field, frame: info };
|
||||
if (!distinct.first) {
|
||||
distinct.first = f;
|
||||
}
|
||||
let t = field.config.title;
|
||||
if (t && !distinct.byName[t]) {
|
||||
distinct.byName[t] = f;
|
||||
distinct.names.push(t);
|
||||
}
|
||||
t = field.name;
|
||||
if (t && !distinct.byName[t]) {
|
||||
distinct.byName[t] = f;
|
||||
distinct.names.push(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return distinct;
|
||||
}
|
||||
|
||||
export { SingleStatCtrl, SingleStatCtrl as PanelCtrl, getColorForValue };
|
||||
|
@ -1,9 +1,17 @@
|
||||
import { SingleStatCtrl } from '../module';
|
||||
import { dateTime } from '@grafana/data';
|
||||
import { SingleStatCtrl, ShowData } from '../module';
|
||||
import { dateTime, ReducerID } from '@grafana/data';
|
||||
import { LinkSrv } from 'app/features/panel/panellinks/link_srv';
|
||||
import { LegacyResponseData } from '@grafana/ui';
|
||||
|
||||
interface TestContext {
|
||||
ctrl: SingleStatCtrl;
|
||||
input: LegacyResponseData[];
|
||||
data: Partial<ShowData>;
|
||||
setup: (setupFunc: any) => void;
|
||||
}
|
||||
|
||||
describe('SingleStatCtrl', () => {
|
||||
const ctx = {} as any;
|
||||
const ctx: TestContext = {} as TestContext;
|
||||
const epoch = 1505826363746;
|
||||
Date.now = () => epoch;
|
||||
|
||||
@ -37,7 +45,7 @@ describe('SingleStatCtrl', () => {
|
||||
// @ts-ignore
|
||||
ctx.ctrl = new SingleStatCtrl($scope, $injector, {} as LinkSrv, $sanitize);
|
||||
setupFunc();
|
||||
ctx.ctrl.onDataReceived(ctx.data);
|
||||
ctx.ctrl.onDataReceived(ctx.input);
|
||||
ctx.data = ctx.ctrl.data;
|
||||
});
|
||||
};
|
||||
@ -46,40 +54,38 @@ describe('SingleStatCtrl', () => {
|
||||
});
|
||||
}
|
||||
|
||||
singleStatScenario('with defaults', (ctx: any) => {
|
||||
singleStatScenario('with defaults', (ctx: TestContext) => {
|
||||
ctx.setup(() => {
|
||||
ctx.data = [{ target: 'test.cpu1', datapoints: [[10, 1], [20, 2]] }];
|
||||
ctx.input = [{ target: 'test.cpu1', datapoints: [[10, 1], [20, 2]] }];
|
||||
});
|
||||
|
||||
it('Should use series avg as default main value', () => {
|
||||
expect(ctx.data.value).toBe(15);
|
||||
expect(ctx.data.valueRounded).toBe(15);
|
||||
});
|
||||
|
||||
it('should set formatted falue', () => {
|
||||
expect(ctx.data.valueFormatted).toBe('15');
|
||||
expect(ctx.data.display.text).toBe('15');
|
||||
});
|
||||
});
|
||||
|
||||
singleStatScenario('showing serie name instead of value', (ctx: any) => {
|
||||
singleStatScenario('showing serie name instead of value', (ctx: TestContext) => {
|
||||
ctx.setup(() => {
|
||||
ctx.data = [{ target: 'test.cpu1', datapoints: [[10, 1], [20, 2]] }];
|
||||
ctx.input = [{ target: 'test.cpu1', datapoints: [[10, 1], [20, 2]] }];
|
||||
ctx.ctrl.panel.valueName = 'name';
|
||||
});
|
||||
|
||||
it('Should use series avg as default main value', () => {
|
||||
expect(ctx.data.value).toBe(0);
|
||||
expect(ctx.data.valueRounded).toBe(0);
|
||||
expect(ctx.data.value).toBe('test.cpu1');
|
||||
});
|
||||
|
||||
it('should set formatted value', () => {
|
||||
expect(ctx.data.valueFormatted).toBe('test.cpu1');
|
||||
expect(ctx.data.display.text).toBe('test.cpu1');
|
||||
});
|
||||
});
|
||||
|
||||
singleStatScenario('showing last iso time instead of value', (ctx: any) => {
|
||||
singleStatScenario('showing last iso time instead of value', (ctx: TestContext) => {
|
||||
ctx.setup(() => {
|
||||
ctx.data = [{ target: 'test.cpu1', datapoints: [[10, 12], [20, 1505634997920]] }];
|
||||
ctx.input = [{ target: 'test.cpu1', datapoints: [[10, 12], [20, 1505634997920]] }];
|
||||
ctx.ctrl.panel.valueName = 'last_time';
|
||||
ctx.ctrl.panel.format = 'dateTimeAsIso';
|
||||
ctx.ctrl.dashboard.isTimezoneUtc = () => false;
|
||||
@ -87,30 +93,29 @@ describe('SingleStatCtrl', () => {
|
||||
|
||||
it('Should use time instead of value', () => {
|
||||
expect(ctx.data.value).toBe(1505634997920);
|
||||
expect(ctx.data.valueRounded).toBe(1505634997920);
|
||||
});
|
||||
|
||||
it('should set formatted value', () => {
|
||||
expect(dateTime(ctx.data.valueFormatted).valueOf()).toBe(1505634997000);
|
||||
expect(dateTime(ctx.data.display.text).valueOf()).toBe(1505634997000);
|
||||
});
|
||||
});
|
||||
|
||||
singleStatScenario('showing last iso time instead of value (in UTC)', (ctx: any) => {
|
||||
singleStatScenario('showing last iso time instead of value (in UTC)', (ctx: TestContext) => {
|
||||
ctx.setup(() => {
|
||||
ctx.data = [{ target: 'test.cpu1', datapoints: [[10, 12], [20, 5000]] }];
|
||||
ctx.input = [{ target: 'test.cpu1', datapoints: [[10, 12], [20, 5000]] }];
|
||||
ctx.ctrl.panel.valueName = 'last_time';
|
||||
ctx.ctrl.panel.format = 'dateTimeAsIso';
|
||||
ctx.ctrl.dashboard.isTimezoneUtc = () => true;
|
||||
});
|
||||
|
||||
it('should set value', () => {
|
||||
expect(ctx.data.valueFormatted).toBe('1970-01-01 00:00:05');
|
||||
expect(ctx.data.display.text).toBe('1970-01-01 00:00:05');
|
||||
});
|
||||
});
|
||||
|
||||
singleStatScenario('showing last us time instead of value', (ctx: any) => {
|
||||
singleStatScenario('showing last us time instead of value', (ctx: TestContext) => {
|
||||
ctx.setup(() => {
|
||||
ctx.data = [{ target: 'test.cpu1', datapoints: [[10, 12], [20, 1505634997920]] }];
|
||||
ctx.input = [{ target: 'test.cpu1', datapoints: [[10, 12], [20, 1505634997920]] }];
|
||||
ctx.ctrl.panel.valueName = 'last_time';
|
||||
ctx.ctrl.panel.format = 'dateTimeAsUS';
|
||||
ctx.ctrl.dashboard.isTimezoneUtc = () => false;
|
||||
@ -118,79 +123,76 @@ describe('SingleStatCtrl', () => {
|
||||
|
||||
it('Should use time instead of value', () => {
|
||||
expect(ctx.data.value).toBe(1505634997920);
|
||||
expect(ctx.data.valueRounded).toBe(1505634997920);
|
||||
});
|
||||
|
||||
it('should set formatted value', () => {
|
||||
expect(ctx.data.valueFormatted).toBe(dateTime(1505634997920).format('MM/DD/YYYY h:mm:ss a'));
|
||||
expect(ctx.data.display.text).toBe(dateTime(1505634997920).format('MM/DD/YYYY h:mm:ss a'));
|
||||
});
|
||||
});
|
||||
|
||||
singleStatScenario('showing last us time instead of value (in UTC)', (ctx: any) => {
|
||||
singleStatScenario('showing last us time instead of value (in UTC)', (ctx: TestContext) => {
|
||||
ctx.setup(() => {
|
||||
ctx.data = [{ target: 'test.cpu1', datapoints: [[10, 12], [20, 5000]] }];
|
||||
ctx.input = [{ target: 'test.cpu1', datapoints: [[10, 12], [20, 5000]] }];
|
||||
ctx.ctrl.panel.valueName = 'last_time';
|
||||
ctx.ctrl.panel.format = 'dateTimeAsUS';
|
||||
ctx.ctrl.dashboard.isTimezoneUtc = () => true;
|
||||
});
|
||||
|
||||
it('should set formatted value', () => {
|
||||
expect(ctx.data.valueFormatted).toBe('01/01/1970 12:00:05 am');
|
||||
expect(ctx.data.display.text).toBe('01/01/1970 12:00:05 am');
|
||||
});
|
||||
});
|
||||
|
||||
singleStatScenario('showing last time from now instead of value', (ctx: any) => {
|
||||
singleStatScenario('showing last time from now instead of value', (ctx: TestContext) => {
|
||||
ctx.setup(() => {
|
||||
ctx.data = [{ target: 'test.cpu1', datapoints: [[10, 12], [20, 1505634997920]] }];
|
||||
ctx.input = [{ 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', () => {
|
||||
expect(ctx.data.value).toBe(1505634997920);
|
||||
expect(ctx.data.valueRounded).toBe(1505634997920);
|
||||
});
|
||||
|
||||
it('should set formatted value', () => {
|
||||
expect(ctx.data.valueFormatted).toBe('2 days ago');
|
||||
expect(ctx.data.display.text).toBe('2 days ago');
|
||||
});
|
||||
});
|
||||
|
||||
singleStatScenario('showing last time from now instead of value (in UTC)', (ctx: any) => {
|
||||
singleStatScenario('showing last time from now instead of value (in UTC)', (ctx: TestContext) => {
|
||||
ctx.setup(() => {
|
||||
ctx.data = [{ target: 'test.cpu1', datapoints: [[10, 12], [20, 1505634997920]] }];
|
||||
ctx.input = [{ target: 'test.cpu1', datapoints: [[10, 12], [20, 1505634997920]] }];
|
||||
ctx.ctrl.panel.valueName = 'last_time';
|
||||
ctx.ctrl.panel.format = 'dateTimeFromNow';
|
||||
});
|
||||
|
||||
it('should set formatted value', () => {
|
||||
expect(ctx.data.valueFormatted).toBe('2 days ago');
|
||||
expect(ctx.data.display.text).toBe('2 days ago');
|
||||
});
|
||||
});
|
||||
|
||||
singleStatScenario(
|
||||
'MainValue should use same number for decimals as displayed when checking thresholds',
|
||||
(ctx: any) => {
|
||||
(ctx: TestContext) => {
|
||||
ctx.setup(() => {
|
||||
ctx.data = [{ target: 'test.cpu1', datapoints: [[99.999, 1], [99.99999, 2]] }];
|
||||
ctx.input = [{ target: 'test.cpu1', datapoints: [[99.999, 1], [99.99999, 2]] }];
|
||||
ctx.ctrl.panel.valueName = 'avg';
|
||||
ctx.ctrl.panel.format = 'none';
|
||||
});
|
||||
|
||||
it('Should be rounded', () => {
|
||||
expect(ctx.data.value).toBe(99.999495);
|
||||
expect(ctx.data.valueRounded).toBe(100);
|
||||
});
|
||||
|
||||
it('should set formatted value', () => {
|
||||
expect(ctx.data.valueFormatted).toBe('100');
|
||||
expect(ctx.data.display.text).toBe('100');
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
singleStatScenario('When value to text mapping is specified', (ctx: any) => {
|
||||
singleStatScenario('When value to text mapping is specified', (ctx: TestContext) => {
|
||||
ctx.setup(() => {
|
||||
ctx.data = [{ target: 'test.cpu1', datapoints: [[9.9, 1]] }];
|
||||
ctx.input = [{ target: 'test.cpu1', datapoints: [[9.9, 1]] }];
|
||||
ctx.ctrl.panel.valueMaps = [{ value: '10', text: 'OK' }];
|
||||
});
|
||||
|
||||
@ -198,36 +200,32 @@ describe('SingleStatCtrl', () => {
|
||||
expect(ctx.data.value).toBe(9.9);
|
||||
});
|
||||
|
||||
it('round should be rounded up', () => {
|
||||
expect(ctx.data.valueRounded).toBe(10);
|
||||
});
|
||||
|
||||
it('Should replace value with text', () => {
|
||||
expect(ctx.data.valueFormatted).toBe('OK');
|
||||
expect(ctx.data.display.text).toBe('OK');
|
||||
});
|
||||
});
|
||||
|
||||
singleStatScenario('When range to text mapping is specified for first range', (ctx: any) => {
|
||||
singleStatScenario('When range to text mapping is specified for first range', (ctx: TestContext) => {
|
||||
ctx.setup(() => {
|
||||
ctx.data = [{ target: 'test.cpu1', datapoints: [[41, 50]] }];
|
||||
ctx.input = [{ 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' }];
|
||||
});
|
||||
|
||||
it('Should replace value with text OK', () => {
|
||||
expect(ctx.data.valueFormatted).toBe('OK');
|
||||
expect(ctx.data.display.text).toBe('OK');
|
||||
});
|
||||
});
|
||||
|
||||
singleStatScenario('When range to text mapping is specified for other ranges', (ctx: any) => {
|
||||
singleStatScenario('When range to text mapping is specified for other ranges', (ctx: TestContext) => {
|
||||
ctx.setup(() => {
|
||||
ctx.data = [{ target: 'test.cpu1', datapoints: [[65, 75]] }];
|
||||
ctx.input = [{ 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' }];
|
||||
});
|
||||
|
||||
it('Should replace value with text NOT OK', () => {
|
||||
expect(ctx.data.valueFormatted).toBe('NOT OK');
|
||||
expect(ctx.data.display.text).toBe('NOT OK');
|
||||
});
|
||||
});
|
||||
|
||||
@ -240,9 +238,9 @@ describe('SingleStatCtrl', () => {
|
||||
},
|
||||
];
|
||||
|
||||
singleStatScenario('with default values', (ctx: any) => {
|
||||
singleStatScenario('with default values', (ctx: TestContext) => {
|
||||
ctx.setup(() => {
|
||||
ctx.data = tableData;
|
||||
ctx.input = tableData;
|
||||
ctx.ctrl.panel = {
|
||||
emit: () => {},
|
||||
};
|
||||
@ -252,17 +250,16 @@ describe('SingleStatCtrl', () => {
|
||||
|
||||
it('Should use first rows value as default main value', () => {
|
||||
expect(ctx.data.value).toBe(15);
|
||||
expect(ctx.data.valueRounded).toBe(15);
|
||||
});
|
||||
|
||||
it('should set formatted value', () => {
|
||||
expect(ctx.data.valueFormatted).toBe('15');
|
||||
expect(ctx.data.display.text).toBe('15');
|
||||
});
|
||||
});
|
||||
|
||||
singleStatScenario('When table data has multiple columns', (ctx: any) => {
|
||||
singleStatScenario('When table data has multiple columns', (ctx: TestContext) => {
|
||||
ctx.setup(() => {
|
||||
ctx.data = tableData;
|
||||
ctx.input = tableData;
|
||||
ctx.ctrl.panel.tableColumn = '';
|
||||
});
|
||||
|
||||
@ -273,29 +270,28 @@ describe('SingleStatCtrl', () => {
|
||||
|
||||
singleStatScenario(
|
||||
'MainValue should use same number for decimals as displayed when checking thresholds',
|
||||
(ctx: any) => {
|
||||
(ctx: TestContext) => {
|
||||
ctx.setup(() => {
|
||||
ctx.data = tableData;
|
||||
ctx.data[0].rows[0] = [1492759673649, 'ignore1', 99.99999, 'ignore2'];
|
||||
ctx.input = tableData;
|
||||
ctx.input[0].rows[0] = [1492759673649, 'ignore1', 99.99999, 'ignore2'];
|
||||
ctx.ctrl.panel.mappingType = 0;
|
||||
ctx.ctrl.panel.tableColumn = 'mean';
|
||||
});
|
||||
|
||||
it('Should be rounded', () => {
|
||||
expect(ctx.data.value).toBe(99.99999);
|
||||
expect(ctx.data.valueRounded).toBe(100);
|
||||
});
|
||||
|
||||
it('should set formatted falue', () => {
|
||||
expect(ctx.data.valueFormatted).toBe('100');
|
||||
expect(ctx.data.display.text).toBe('100');
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
singleStatScenario('When value to text mapping is specified', (ctx: any) => {
|
||||
singleStatScenario('When value to text mapping is specified', (ctx: TestContext) => {
|
||||
ctx.setup(() => {
|
||||
ctx.data = tableData;
|
||||
ctx.data[0].rows[0] = [1492759673649, 'ignore1', 9.9, 'ignore2'];
|
||||
ctx.input = tableData;
|
||||
ctx.input[0].rows[0] = [1492759673649, 'ignore1', 9.9, 'ignore2'];
|
||||
ctx.ctrl.panel.mappingType = 2;
|
||||
ctx.ctrl.panel.tableColumn = 'mean';
|
||||
ctx.ctrl.panel.valueMaps = [{ value: '10', text: 'OK' }];
|
||||
@ -305,59 +301,60 @@ describe('SingleStatCtrl', () => {
|
||||
expect(ctx.data.value).toBe(9.9);
|
||||
});
|
||||
|
||||
it('round should be rounded up', () => {
|
||||
expect(ctx.data.valueRounded).toBe(10);
|
||||
});
|
||||
// it('round should be rounded up', () => {
|
||||
// expect(ctx.data.valueRounded).toBe(10);
|
||||
// });
|
||||
|
||||
it('Should replace value with text', () => {
|
||||
expect(ctx.data.valueFormatted).toBe('OK');
|
||||
expect(ctx.data.display.text).toBe('OK');
|
||||
});
|
||||
});
|
||||
|
||||
singleStatScenario('When range to text mapping is specified for first range', (ctx: any) => {
|
||||
singleStatScenario('When range to text mapping is specified for first range', (ctx: TestContext) => {
|
||||
ctx.setup(() => {
|
||||
ctx.data = tableData;
|
||||
ctx.data[0].rows[0] = [1492759673649, 'ignore1', 41, 'ignore2'];
|
||||
ctx.input = tableData;
|
||||
ctx.input[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', () => {
|
||||
expect(ctx.data.valueFormatted).toBe('OK');
|
||||
expect(ctx.data.display.text).toBe('OK');
|
||||
});
|
||||
});
|
||||
|
||||
singleStatScenario('When range to text mapping is specified for other ranges', (ctx: any) => {
|
||||
singleStatScenario('When range to text mapping is specified for other ranges', (ctx: TestContext) => {
|
||||
ctx.setup(() => {
|
||||
ctx.data = tableData;
|
||||
ctx.data[0].rows[0] = [1492759673649, 'ignore1', 65, 'ignore2'];
|
||||
ctx.input = tableData;
|
||||
ctx.input[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', () => {
|
||||
expect(ctx.data.valueFormatted).toBe('NOT OK');
|
||||
expect(ctx.data.display.text).toBe('NOT OK');
|
||||
});
|
||||
});
|
||||
|
||||
singleStatScenario('When value is string', (ctx: any) => {
|
||||
singleStatScenario('When value is string', (ctx: TestContext) => {
|
||||
ctx.setup(() => {
|
||||
ctx.data = tableData;
|
||||
ctx.data[0].rows[0] = [1492759673649, 'ignore1', 65, 'ignore2'];
|
||||
ctx.input = tableData;
|
||||
ctx.input[0].rows[0] = [1492759673649, 'ignore1', 65, 'ignore2'];
|
||||
ctx.ctrl.panel.tableColumn = 'test1';
|
||||
ctx.ctrl.panel.valueName = ReducerID.first;
|
||||
});
|
||||
|
||||
it('Should replace value with text NOT OK', () => {
|
||||
expect(ctx.data.valueFormatted).toBe('ignore1');
|
||||
expect(ctx.data.display.text).toBe('ignore1');
|
||||
});
|
||||
});
|
||||
|
||||
singleStatScenario('When value is zero', (ctx: any) => {
|
||||
singleStatScenario('When value is zero', (ctx: TestContext) => {
|
||||
ctx.setup(() => {
|
||||
ctx.data = tableData;
|
||||
ctx.data[0].rows[0] = [1492759673649, 'ignore1', 0, 'ignore2'];
|
||||
ctx.input = tableData;
|
||||
ctx.input[0].rows[0] = [1492759673649, 'ignore1', 0, 'ignore2'];
|
||||
ctx.ctrl.panel.tableColumn = 'mean';
|
||||
});
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user