mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Gauge/BarGauge: Added support for value mapping of "no data"-state to text/value (#20842)
* FieldDisplay: added support for value mapping of no data state. Committer: Marcus Andersson <marcus.andersson@grafana.com> * FieldDisplay: fixed issue when switching between modes and display numeric was null. * ValueMapping: introduced a private function for checking null values. * FieldDisplay: refactoring of test setup to reduce duplication. * Docs: added info about new 'no data' value to text mapping. * Docs: improved according to feedback. Reverted prettier formatting changes. * FieldDisplay: removed unused import.
This commit is contained in:
committed by
Torkel Ödegaard
parent
e0229045f2
commit
7a710737ae
@@ -1,8 +1,10 @@
|
||||
import merge from 'lodash/merge';
|
||||
import { getFieldProperties, getFieldDisplayValues, GetFieldDisplayValuesOptions } from './fieldDisplay';
|
||||
import { toDataFrame } from '../dataframe/processDataFrame';
|
||||
import { ReducerID } from '../transformations/fieldReducer';
|
||||
import { Threshold } from '../types/threshold';
|
||||
import { GrafanaTheme } from '../types/theme';
|
||||
import { MappingType } from '../types';
|
||||
|
||||
describe('FieldDisplay', () => {
|
||||
it('Construct simple field properties', () => {
|
||||
@@ -32,33 +34,8 @@ describe('FieldDisplay', () => {
|
||||
expect(field.unit).toEqual('ms');
|
||||
});
|
||||
|
||||
// Simple test dataset
|
||||
|
||||
const options: GetFieldDisplayValuesOptions = {
|
||||
data: [
|
||||
toDataFrame({
|
||||
name: 'Series Name',
|
||||
fields: [
|
||||
{ name: 'Field 1', values: ['a', 'b', 'c'] },
|
||||
{ name: 'Field 2', values: [1, 3, 5] },
|
||||
{ name: 'Field 3', values: [2, 4, 6] },
|
||||
],
|
||||
}),
|
||||
],
|
||||
replaceVariables: (value: string) => {
|
||||
return value; // Return it unchanged
|
||||
},
|
||||
fieldOptions: {
|
||||
calcs: [],
|
||||
override: {},
|
||||
defaults: {},
|
||||
},
|
||||
theme: {} as GrafanaTheme,
|
||||
};
|
||||
|
||||
it('show first numeric values', () => {
|
||||
const display = getFieldDisplayValues({
|
||||
...options,
|
||||
const options = createDisplayOptions({
|
||||
fieldOptions: {
|
||||
calcs: [ReducerID.first],
|
||||
override: {},
|
||||
@@ -67,28 +44,24 @@ describe('FieldDisplay', () => {
|
||||
},
|
||||
},
|
||||
});
|
||||
const display = getFieldDisplayValues(options);
|
||||
expect(display.map(v => v.display.text)).toEqual(['1', '2']);
|
||||
// expect(display.map(v => v.display.title)).toEqual([
|
||||
// 'a * Field 1 * Series Name', // 0
|
||||
// 'b * Field 2 * Series Name', // 1
|
||||
// ]);
|
||||
});
|
||||
|
||||
it('show last numeric values', () => {
|
||||
const display = getFieldDisplayValues({
|
||||
...options,
|
||||
const options = createDisplayOptions({
|
||||
fieldOptions: {
|
||||
calcs: [ReducerID.last],
|
||||
override: {},
|
||||
defaults: {},
|
||||
},
|
||||
});
|
||||
const display = getFieldDisplayValues(options);
|
||||
expect(display.map(v => v.display.numeric)).toEqual([5, 6]);
|
||||
});
|
||||
|
||||
it('show all numeric values', () => {
|
||||
const display = getFieldDisplayValues({
|
||||
...options,
|
||||
const options = createDisplayOptions({
|
||||
fieldOptions: {
|
||||
values: true, //
|
||||
limit: 1000,
|
||||
@@ -97,12 +70,12 @@ describe('FieldDisplay', () => {
|
||||
defaults: {},
|
||||
},
|
||||
});
|
||||
const display = getFieldDisplayValues(options);
|
||||
expect(display.map(v => v.display.numeric)).toEqual([1, 3, 5, 2, 4, 6]);
|
||||
});
|
||||
|
||||
it('show 2 numeric values (limit)', () => {
|
||||
const display = getFieldDisplayValues({
|
||||
...options,
|
||||
const options = createDisplayOptions({
|
||||
fieldOptions: {
|
||||
values: true, //
|
||||
limit: 2,
|
||||
@@ -111,6 +84,7 @@ describe('FieldDisplay', () => {
|
||||
defaults: {},
|
||||
},
|
||||
});
|
||||
const display = getFieldDisplayValues(options);
|
||||
expect(display.map(v => v.display.numeric)).toEqual([1, 3]); // First 2 are from the first field
|
||||
});
|
||||
|
||||
@@ -132,28 +106,108 @@ describe('FieldDisplay', () => {
|
||||
});
|
||||
|
||||
it('Should return field thresholds when there is no data', () => {
|
||||
const options: GetFieldDisplayValuesOptions = {
|
||||
data: [
|
||||
{
|
||||
name: 'No data',
|
||||
fields: [],
|
||||
length: 0,
|
||||
},
|
||||
],
|
||||
replaceVariables: (value: string) => {
|
||||
return value;
|
||||
},
|
||||
const options = createEmptyDisplayOptions({
|
||||
fieldOptions: {
|
||||
calcs: [],
|
||||
override: {},
|
||||
defaults: {
|
||||
thresholds: [{ color: '#F2495C', value: 50 }],
|
||||
},
|
||||
},
|
||||
theme: {} as GrafanaTheme,
|
||||
};
|
||||
});
|
||||
|
||||
const display = getFieldDisplayValues(options);
|
||||
expect(display[0].field.thresholds!.length).toEqual(1);
|
||||
expect(display[0].display.numeric).toEqual(0);
|
||||
});
|
||||
|
||||
it('Should return field with default text when no mapping or data available', () => {
|
||||
const options = createEmptyDisplayOptions();
|
||||
const display = getFieldDisplayValues(options);
|
||||
expect(display[0].display.text).toEqual('No data');
|
||||
expect(display[0].display.numeric).toEqual(0);
|
||||
});
|
||||
|
||||
it('Should return field mapped value when there is no data', () => {
|
||||
const mapEmptyToText = '0';
|
||||
const options = createEmptyDisplayOptions({
|
||||
fieldOptions: {
|
||||
override: {
|
||||
mappings: [
|
||||
{
|
||||
id: 1,
|
||||
operator: '',
|
||||
text: mapEmptyToText,
|
||||
type: MappingType.ValueToText,
|
||||
value: 'null',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const display = getFieldDisplayValues(options);
|
||||
expect(display[0].display.text).toEqual(mapEmptyToText);
|
||||
expect(display[0].display.numeric).toEqual(0);
|
||||
});
|
||||
|
||||
it('Should always return display numeric 0 when there is no data', () => {
|
||||
const mapEmptyToText = '0';
|
||||
const options = createEmptyDisplayOptions({
|
||||
fieldOptions: {
|
||||
override: {
|
||||
mappings: [
|
||||
{
|
||||
id: 1,
|
||||
operator: '',
|
||||
text: mapEmptyToText,
|
||||
type: MappingType.ValueToText,
|
||||
value: 'null',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const display = getFieldDisplayValues(options);
|
||||
expect(display[0].display.numeric).toEqual(0);
|
||||
});
|
||||
});
|
||||
|
||||
function createEmptyDisplayOptions(extend = {}): GetFieldDisplayValuesOptions {
|
||||
const options = createDisplayOptions(extend);
|
||||
|
||||
return Object.assign(options, {
|
||||
data: [
|
||||
{
|
||||
name: 'No data',
|
||||
fields: [],
|
||||
length: 0,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
function createDisplayOptions(extend = {}): GetFieldDisplayValuesOptions {
|
||||
const options: GetFieldDisplayValuesOptions = {
|
||||
data: [
|
||||
toDataFrame({
|
||||
name: 'Series Name',
|
||||
fields: [
|
||||
{ name: 'Field 1', values: ['a', 'b', 'c'] },
|
||||
{ name: 'Field 2', values: [1, 3, 5] },
|
||||
{ name: 'Field 3', values: [2, 4, 6] },
|
||||
],
|
||||
}),
|
||||
],
|
||||
replaceVariables: (value: string) => {
|
||||
return value;
|
||||
},
|
||||
fieldOptions: {
|
||||
calcs: [],
|
||||
override: {},
|
||||
defaults: {},
|
||||
},
|
||||
theme: {} as GrafanaTheme,
|
||||
};
|
||||
|
||||
return merge<GetFieldDisplayValuesOptions, any>(options, extend);
|
||||
}
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import toNumber from 'lodash/toNumber';
|
||||
import toString from 'lodash/toString';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
|
||||
import { getDisplayProcessor } from './displayProcessor';
|
||||
import { getFlotPairs } from '../utils/flotPairs';
|
||||
@@ -196,16 +197,7 @@ export const getFieldDisplayValues = (options: GetFieldDisplayValuesOptions): Fi
|
||||
}
|
||||
|
||||
if (values.length === 0) {
|
||||
values.push({
|
||||
name: 'No data',
|
||||
field: {
|
||||
...defaults,
|
||||
},
|
||||
display: {
|
||||
numeric: 0,
|
||||
text: 'No data',
|
||||
},
|
||||
});
|
||||
values.push(createNoValuesFieldDisplay(options));
|
||||
} else if (values.length === 1 && !fieldOptions.defaults.title) {
|
||||
// Don't show title for single item
|
||||
values[0].display.title = undefined;
|
||||
@@ -312,3 +304,37 @@ export function getDisplayValueAlignmentFactors(values: FieldDisplay[]): Display
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
function createNoValuesFieldDisplay(options: GetFieldDisplayValuesOptions): FieldDisplay {
|
||||
const displayName = 'No data';
|
||||
const { fieldOptions } = options;
|
||||
const { defaults, override } = fieldOptions;
|
||||
|
||||
const config = getFieldProperties(defaults, {}, override);
|
||||
const displayProcessor = getDisplayProcessor({
|
||||
config,
|
||||
theme: options.theme,
|
||||
type: FieldType.other,
|
||||
});
|
||||
|
||||
const display = displayProcessor(null);
|
||||
const text = getDisplayText(display, displayName);
|
||||
|
||||
return {
|
||||
name: displayName,
|
||||
field: {
|
||||
...defaults,
|
||||
},
|
||||
display: {
|
||||
text,
|
||||
numeric: 0,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function getDisplayText(display: DisplayValue, fallback: string): string {
|
||||
if (!display || isEmpty(display.text)) {
|
||||
return fallback;
|
||||
}
|
||||
return display.text;
|
||||
}
|
||||
|
@@ -11,7 +11,7 @@ const addValueToTextMappingText = (
|
||||
return allValueMappings;
|
||||
}
|
||||
|
||||
if (value === null && valueToTextMapping.value && valueToTextMapping.value.toLowerCase() === 'null') {
|
||||
if (value === null && isNullValueMap(valueToTextMapping)) {
|
||||
return allValueMappings.concat(valueToTextMapping);
|
||||
}
|
||||
|
||||
@@ -84,3 +84,10 @@ const getAllFormattedValueMappings = (valueMappings: ValueMapping[], value: Time
|
||||
export const getMappedValue = (valueMappings: ValueMapping[], value: TimeSeriesValue): ValueMapping => {
|
||||
return getAllFormattedValueMappings(valueMappings, value)[0];
|
||||
};
|
||||
|
||||
const isNullValueMap = (mapping: ValueMap): boolean => {
|
||||
if (!mapping || !mapping.value) {
|
||||
return false;
|
||||
}
|
||||
return mapping.value.toLowerCase() === 'null';
|
||||
};
|
||||
|
Reference in New Issue
Block a user