Table Panel: Add ability to use text color for value or hide value in gauge cell (#61477)

* BarGauge: New value options

* Fix typings for cell options, add new value mode option for bar gauge cells

* Add BarGauge panel option, tests, and update test dashboard

* Updated

* Added default

* Goodbye trusty console.log

* Update

* Merge changes from main

* Update docs

* Add valuemode doc changes

* Update gdev dashboard

* Update valueMode symbol name to valueDisplayMode

* Use Enums as Opposed to literals, don't calculate values when hidden

* Remove double import

* Fix tests

* One more test fix

* Remove erroneous targets field, fix type of maxDataPoints

* Strip nulls and add index field to Thresholds

* Gen cue

* remove bad targets again

* Fixes

---------

Co-authored-by: Kyle Cunningham <kyle@codeincarnate.com>
Co-authored-by: sam boyer <sdboyer@grafana.com>
This commit is contained in:
Torkel Ödegaard
2023-03-10 14:41:46 +01:00
committed by GitHub
co-authored by Kyle Cunningham sam boyer
parent 0a4d9f01e8
commit 73ce20ab48
25 changed files with 2259 additions and 1570 deletions
@@ -593,6 +593,15 @@ export enum BarGaugeDisplayMode {
Lcd = 'lcd',
}
/**
* Allows for the table cell gauge display type to set the gauge mode.
*/
export enum BarGaugeValueMode {
Color = 'color',
Hidden = 'hidden',
Text = 'text',
}
/**
* TODO docs
*/
@@ -697,6 +706,7 @@ export interface TableImageCellOptions {
export interface TableBarGaugeCellOptions {
mode?: BarGaugeDisplayMode;
type: TableCellDisplayMode.Gauge;
valueDisplayMode?: BarGaugeValueMode;
}
/**
@@ -242,6 +242,9 @@ VizLegendOptions: {
// for the bar gauge component of Grafana UI
BarGaugeDisplayMode: "basic" | "lcd" | "gradient" @cuetsy(kind="enum")
// Allows for the table cell gauge display type to set the gauge mode.
BarGaugeValueMode: "color" | "text" | "hidden" @cuetsy(kind="enum")
// TODO docs
VizTooltipOptions: {
mode: TooltipDisplayMode
@@ -52,6 +52,7 @@ TableImageCellOptions: {
TableBarGaugeCellOptions: {
type: TableCellDisplayMode & "gauge"
mode?: BarGaugeDisplayMode
valueDisplayMode?: BarGaugeValueMode
} @cuetsy(kind="interface")
// Sparkline cell options
@@ -238,6 +238,10 @@ export interface Threshold {
* TODO docs
*/
color: string;
/**
* Threshold index, an old property that is not needed an should only appear in older dashboards
*/
index?: number;
/**
* TODO docs
* TODO are the values here enumerable into a disjunction?
@@ -11,18 +11,18 @@ import {
getDisplayProcessor,
createTheme,
} from '@grafana/data';
import { BarGaugeDisplayMode } from '@grafana/schema';
import { BarGaugeDisplayMode, BarGaugeValueMode } from '@grafana/schema';
import {
BarGauge,
Props,
getCellColor,
getValueColor,
getTextValueColor,
getBasicAndGradientStyles,
getBarGradient,
getTitleStyles,
getValuePercent,
calculateBarAndValueDimensions,
getCellColor,
} from './BarGauge';
const green = '#73BF69';
@@ -63,7 +63,7 @@ function getProps(propOverrides?: Partial<Props>): Props {
}
function getValue(value: number, title?: string): DisplayValue {
return { numeric: value, text: value.toString(), title: title };
return { numeric: value, text: value.toString(), title: title, color: '#FF0000' };
}
describe('BarGauge', () => {
@@ -134,12 +134,12 @@ describe('BarGauge', () => {
it('should get the threshold color if value is same as a threshold', () => {
const props = getProps();
props.value = props.display!(70);
expect(getValueColor(props)).toEqual(orange);
expect(getTextValueColor(props)).toEqual(orange);
});
it('should get the base threshold', () => {
const props = getProps();
props.value = props.display!(-10);
expect(getValueColor(props)).toEqual(green);
expect(getTextValueColor(props)).toEqual(green);
});
});
@@ -325,5 +325,46 @@ describe('BarGauge', () => {
);
expect(result.valueWidth).toBe(21);
});
it('valueWidth be zero if valueMode is hideen', () => {
const result = calculateBarAndValueDimensions(
getProps({
height: 30,
width: 100,
value: getValue(1, 'AA'),
orientation: VizOrientation.Horizontal,
valueDisplayMode: BarGaugeValueMode.Hidden,
})
);
expect(result.valueWidth).toBe(0);
});
});
describe('With valueMode set to text color', () => {
it('should color value using text color', () => {
const props = getProps({
width: 150,
value: getValue(100),
orientation: VizOrientation.Vertical,
valueDisplayMode: BarGaugeValueMode.Text,
});
const styles = getBasicAndGradientStyles(props);
expect(styles.bar.background).toBe('rgba(255, 0, 0, 0.35)');
expect(styles.value.color).toBe('rgb(204, 204, 220)');
});
});
describe('With valueMode set to text value', () => {
it('should color value value color', () => {
const props = getProps({
width: 150,
value: getValue(100),
orientation: VizOrientation.Vertical,
valueDisplayMode: BarGaugeValueMode.Color,
});
const styles = getBasicAndGradientStyles(props);
expect(styles.bar.background).toBe('rgba(255, 0, 0, 0.35)');
expect(styles.value.color).toBe('#FF0000');
});
});
});
@@ -20,7 +20,7 @@ import {
VizOrientation,
} from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { BarGaugeDisplayMode, VizTextDisplayOptions } from '@grafana/schema';
import { BarGaugeDisplayMode, BarGaugeValueMode, VizTextDisplayOptions } from '@grafana/schema';
import { Themeable2 } from '../../types';
import { calculateFontSize, measureText } from '../../utils/measureText';
@@ -49,6 +49,7 @@ export interface Props extends Themeable2 {
className?: string;
showUnfilled?: boolean;
alignmentFactors?: DisplayValueAlignmentFactors;
valueDisplayMode?: BarGaugeValueMode;
}
export class BarGauge extends PureComponent<Props> {
@@ -111,17 +112,19 @@ export class BarGauge extends PureComponent<Props> {
}
renderBasicAndGradientBars(): ReactNode {
const { value, showUnfilled } = this.props;
const { value, showUnfilled, valueDisplayMode } = this.props;
const styles = getBasicAndGradientStyles(this.props);
return (
<div style={styles.wrapper}>
<FormattedValueDisplay
data-testid={selectors.components.Panels.Visualization.BarGauge.valueV2}
value={value}
style={styles.value}
/>
{valueDisplayMode !== BarGaugeValueMode.Hidden && (
<FormattedValueDisplay
data-testid={selectors.components.Panels.Visualization.BarGauge.valueV2}
value={value}
style={styles.value}
/>
)}
{showUnfilled && <div style={styles.emptyBar} />}
<div style={styles.bar} />
</div>
@@ -129,7 +132,8 @@ export class BarGauge extends PureComponent<Props> {
}
renderRetroBars(): ReactNode {
const { display, field, value, itemSpacing, alignmentFactors, orientation, lcdCellWidth, text } = this.props;
const { display, field, value, itemSpacing, alignmentFactors, orientation, lcdCellWidth, text, valueDisplayMode } =
this.props;
const { valueHeight, valueWidth, maxBarHeight, maxBarWidth, wrapperWidth, wrapperHeight } =
calculateBarAndValueDimensions(this.props);
const minValue = field.min ?? GAUGE_DEFAULT_MINIMUM;
@@ -141,7 +145,7 @@ export class BarGauge extends PureComponent<Props> {
const cellSpacing = itemSpacing!;
const cellCount = Math.floor(maxSize / lcdCellWidth!);
const cellSize = Math.floor((maxSize - cellSpacing * cellCount) / cellCount);
const valueColor = getValueColor(this.props);
const valueColor = getTextValueColor(this.props);
const valueToBaseSizeOn = alignmentFactors ? alignmentFactors : value;
const valueStyles = getValueStyles(valueToBaseSizeOn, valueColor, valueWidth, valueHeight, orientation, text);
@@ -192,11 +196,13 @@ export class BarGauge extends PureComponent<Props> {
return (
<div style={containerStyles}>
{cells}
<FormattedValueDisplay
data-testid={selectors.components.Panels.Visualization.BarGauge.valueV2}
value={value}
style={valueStyles}
/>
{valueDisplayMode !== BarGaugeValueMode.Hidden && (
<FormattedValueDisplay
data-testid={selectors.components.Panels.Visualization.BarGauge.valueV2}
value={value}
style={valueStyles}
/>
)}
</div>
);
}
@@ -338,7 +344,7 @@ interface BarAndValueDimensions {
* Only exported for unit tests
**/
export function calculateBarAndValueDimensions(props: Props): BarAndValueDimensions {
const { height, width, orientation, text, alignmentFactors } = props;
const { height, width, orientation, text, alignmentFactors, valueDisplayMode } = props;
const titleDim = calculateTitleDimensions(props);
const value = alignmentFactors ?? props.value;
const valueString = formattedValueToString(value);
@@ -363,13 +369,25 @@ export function calculateBarAndValueDimensions(props: Props): BarAndValueDimensi
}
valueWidth = width;
if (valueDisplayMode === BarGaugeValueMode.Hidden) {
valueHeight = 0;
valueWidth = 0;
}
maxBarHeight = height - (titleDim.height + valueHeight);
maxBarWidth = width;
wrapperWidth = width;
wrapperHeight = height - titleDim.height;
} else {
valueHeight = height - titleDim.height;
valueWidth = Math.max(Math.min(width * 0.2, MAX_VALUE_WIDTH), realValueWidth);
// Calculate the width and the height of the given values
if (valueDisplayMode === BarGaugeValueMode.Hidden) {
valueHeight = 0;
valueWidth = 0;
} else {
valueHeight = height - titleDim.height;
valueWidth = Math.max(Math.min(width * 0.2, MAX_VALUE_WIDTH), realValueWidth);
}
maxBarHeight = height - titleDim.height;
maxBarWidth = width - valueWidth - titleDim.width;
@@ -447,10 +465,11 @@ export function getBasicAndGradientStyles(props: Props): BasicAndGradientStyles
const minValue = field.min ?? GAUGE_DEFAULT_MINIMUM;
const maxValue = field.max ?? GAUGE_DEFAULT_MAXIMUM;
const valuePercent = getValuePercent(value.numeric, minValue, maxValue);
const valueColor = getValueColor(props);
const textColor = getTextValueColor(props);
const barColor = value.color ?? FALLBACK_COLOR;
const valueToBaseSizeOn = alignmentFactors ? alignmentFactors : value;
const valueStyles = getValueStyles(valueToBaseSizeOn, valueColor, valueWidth, valueHeight, orientation, text);
const valueStyles = getValueStyles(valueToBaseSizeOn, textColor, valueWidth, valueHeight, orientation, text);
const isBasic = displayMode === 'basic';
const wrapperStyles: CSSProperties = {
@@ -491,8 +510,8 @@ export function getBasicAndGradientStyles(props: Props): BasicAndGradientStyles
if (isBasic) {
// Basic styles
barStyles.background = `${tinycolor(valueColor).setAlpha(0.35).toRgbString()}`;
barStyles.borderTop = `2px solid ${valueColor}`;
barStyles.background = `${tinycolor(barColor).setAlpha(0.35).toRgbString()}`;
barStyles.borderTop = `2px solid ${barColor}`;
} else {
// Gradient styles
barStyles.background = getBarGradient(props, maxBarHeight);
@@ -517,8 +536,8 @@ export function getBasicAndGradientStyles(props: Props): BasicAndGradientStyles
if (isBasic) {
// Basic styles
barStyles.background = `${tinycolor(valueColor).setAlpha(0.35).toRgbString()}`;
barStyles.borderRight = `2px solid ${valueColor}`;
barStyles.background = `${tinycolor(barColor).setAlpha(0.35).toRgbString()}`;
barStyles.borderRight = `2px solid ${barColor}`;
} else {
// Gradient styles
barStyles.background = getBarGradient(props, maxBarWidth);
@@ -598,7 +617,11 @@ export function getBarGradient(props: Props, maxSize: number): string {
/**
* Only exported to for unit test
*/
export function getValueColor(props: Props): string {
export function getTextValueColor(props: Props): string {
if (props.valueDisplayMode === 'text') {
return props.theme.colors.text.primary;
}
const { value } = props;
if (value.color) {
return value.color;
@@ -2,7 +2,7 @@ import { isFunction } from 'lodash';
import React, { FC } from 'react';
import { ThresholdsConfig, ThresholdsMode, VizOrientation, getFieldConfigWithMinMax } from '@grafana/data';
import { BarGaugeDisplayMode } from '@grafana/schema';
import { BarGaugeDisplayMode, BarGaugeValueMode } from '@grafana/schema';
import { BarGauge } from '../BarGauge/BarGauge';
import { DataLinksContextMenu, DataLinksContextMenuApi } from '../DataLinks/DataLinksContextMenu';
@@ -26,6 +26,8 @@ const defaultScale: ThresholdsConfig = {
export const BarGaugeCell: FC<TableCellProps> = (props) => {
const { field, innerWidth, tableStyles, cell, cellProps, row } = props;
const displayValue = field.display!(cell.value);
const cellOptions = getCellOptions(field);
let config = getFieldConfigWithMinMax(field, false);
if (!config.thresholds) {
@@ -35,14 +37,15 @@ export const BarGaugeCell: FC<TableCellProps> = (props) => {
};
}
const displayValue = field.display!(cell.value);
// Set default display mode
// Set default display mode and update if defined
// and update the valueMode if defined
let barGaugeMode: BarGaugeDisplayMode = BarGaugeDisplayMode.Gradient;
let valueDisplayMode: BarGaugeValueMode | undefined = undefined;
const cellOptions = getCellOptions(field);
if (cellOptions.type === TableCellDisplayMode.Gauge) {
barGaugeMode = cellOptions.mode ?? BarGaugeDisplayMode.Gradient;
valueDisplayMode =
cellOptions.valueDisplayMode !== undefined ? cellOptions.valueDisplayMode : BarGaugeValueMode.Text;
}
const getLinks = () => {
@@ -73,6 +76,7 @@ export const BarGaugeCell: FC<TableCellProps> = (props) => {
itemSpacing={1}
lcdCellWidth={8}
displayMode={barGaugeMode}
valueDisplayMode={valueDisplayMode}
/>
);
};
@@ -84,21 +88,7 @@ export const BarGaugeCell: FC<TableCellProps> = (props) => {
{(api) => renderComponent(api)}
</DataLinksContextMenu>
)}
{!hasLinks && (
<BarGauge
width={innerWidth}
height={tableStyles.cellHeightInner}
field={config}
display={field.display}
text={{ valueSize: 14 }}
value={displayValue}
orientation={VizOrientation.Horizontal}
theme={tableStyles.theme}
itemSpacing={1}
lcdCellWidth={8}
displayMode={barGaugeMode}
/>
)}
{!hasLinks && renderComponent({})}
</div>
);
};