NewGauge: Adds new feature toggle named newGauge (#112593)

* NewGauge: Feature toggle and fixes

* Gauge: New migration test dashboard

* Update

* Updates

* Tweaked default barWidth

* Fix multi data links

* remove sizing options

* merge fix

* Update

* Restore

* Update

* Tweaked name font size logic

* use file snapshots instead of inline

---------

Co-authored-by: Paul Marbach <paul.marbach@grafana.com>
This commit is contained in:
Torkel Ödegaard 2025-10-20 18:33:19 +02:00 committed by GitHub
parent 17771e0e1d
commit 7df537c9fc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
28 changed files with 3157 additions and 573 deletions

File diff suppressed because it is too large Load Diff

View File

@ -38,6 +38,7 @@
"gauge-multi-series": (import '../dev-dashboards/panel-gauge/gauge-multi-series.json'), "gauge-multi-series": (import '../dev-dashboards/panel-gauge/gauge-multi-series.json'),
"gauge_tests": (import '../dev-dashboards/panel-gauge/gauge_tests.json'), "gauge_tests": (import '../dev-dashboards/panel-gauge/gauge_tests.json'),
"gauge_tests_new": (import '../dev-dashboards/panel-gauge/gauge_tests_new.json'), "gauge_tests_new": (import '../dev-dashboards/panel-gauge/gauge_tests_new.json'),
"gauge_tests_old_to_new": (import '../dev-dashboards/panel-gauge/gauge_tests_old_to_new.json'),
"geomap-color-field": (import '../dev-dashboards/panel-geomap/geomap-color-field.json'), "geomap-color-field": (import '../dev-dashboards/panel-geomap/geomap-color-field.json'),
"geomap-photo-layer": (import '../dev-dashboards/panel-geomap/geomap-photo-layer.json'), "geomap-photo-layer": (import '../dev-dashboards/panel-geomap/geomap-photo-layer.json'),
"geomap-route-layer": (import '../dev-dashboards/panel-geomap/geomap-route-layer.json'), "geomap-route-layer": (import '../dev-dashboards/panel-geomap/geomap-route-layer.json'),

View File

@ -1218,6 +1218,11 @@ export interface FeatureToggles {
*/ */
cdnPluginsUrls?: boolean; cdnPluginsUrls?: boolean;
/** /**
* Enable new gauge visualization
* @default false
*/
newGauge?: boolean;
/**
* Restrict PanelChrome contents with overflow: hidden; * Restrict PanelChrome contents with overflow: hidden;
* @default true * @default true
*/ */

View File

@ -21,7 +21,7 @@ export interface GaugePanelEffects {
export const defaultGaugePanelEffects: Partial<GaugePanelEffects> = { export const defaultGaugePanelEffects: Partial<GaugePanelEffects> = {
barGlow: false, barGlow: false,
centerGlow: true, centerGlow: false,
rounded: false, rounded: false,
spotlight: false, spotlight: false,
}; };
@ -39,13 +39,13 @@ export interface Options extends common.SingleStatBaseOptions {
} }
export const defaultOptions: Partial<Options> = { export const defaultOptions: Partial<Options> = {
barWidthFactor: 0.4, barWidthFactor: 0.5,
effects: {}, effects: {},
gradient: 'none', gradient: 'auto',
segmentCount: 1, segmentCount: 1,
segmentSpacing: 0.3, segmentSpacing: 0.3,
shape: 'gauge', shape: 'gauge',
showThresholdLabels: false, showThresholdLabels: false,
showThresholdMarkers: true, showThresholdMarkers: true,
sparkline: false, sparkline: true,
}; };

View File

@ -73,11 +73,12 @@ export class RadialColorDefs {
this.defs.push( this.defs.push(
<radialGradient <radialGradient
key={id}
id={id}
cx={dimensions.centerX} cx={dimensions.centerX}
cy={dimensions.centerY} cy={dimensions.centerY}
r={dimensions.radius + dimensions.barWidth / 2} r={dimensions.radius + dimensions.barWidth / 2}
fr={dimensions.radius - dimensions.barWidth / 2} fr={dimensions.radius - dimensions.barWidth / 2}
id={id}
gradientUnits="userSpaceOnUse" gradientUnits="userSpaceOnUse"
> >
<stop offset="0%" stopColor={tinycolor(baseColor).spin(20).lighten(10).toString()} stopOpacity={1} /> <stop offset="0%" stopColor={tinycolor(baseColor).spin(20).lighten(10).toString()} stopOpacity={1} />
@ -85,6 +86,8 @@ export class RadialColorDefs {
<stop offset="100%" stopColor={color1.toString()} stopOpacity={1} /> <stop offset="100%" stopColor={color1.toString()} stopOpacity={1} />
</radialGradient> </radialGradient>
); );
return returnColor;
} }
// For fixed / palette based color scales we can create a more fun // For fixed / palette based color scales we can create a more fun
@ -103,11 +106,12 @@ export class RadialColorDefs {
this.defs.push( this.defs.push(
<linearGradient <linearGradient
key={id}
id={id}
x1="0" x1="0"
y1="0" y1="0"
x2={x2} x2={x2}
y2={y2} y2={y2}
id={id}
gradientUnits="userSpaceOnUse" gradientUnits="userSpaceOnUse"
gradientTransform={transform} gradientTransform={transform}
> >

View File

@ -1,4 +1,4 @@
import { css } from '@emotion/css'; import { css, cx } from '@emotion/css';
import { isNumber } from 'lodash'; import { isNumber } from 'lodash';
import { useId } from 'react'; import { useId } from 'react';
@ -64,6 +64,8 @@ export interface RadialGaugeProps {
/** Specify which text should be visible */ /** Specify which text should be visible */
textMode?: RadialTextMode; textMode?: RadialTextMode;
showScaleLabels?: boolean; showScaleLabels?: boolean;
/** For data links */
onClick?: React.MouseEventHandler<HTMLElement>;
} }
export type RadialGradientMode = 'none' | 'auto'; export type RadialGradientMode = 'none' | 'auto';
@ -87,6 +89,7 @@ export function RadialGauge(props: RadialGaugeProps) {
roundedBars = true, roundedBars = true,
thresholdsBar = false, thresholdsBar = false,
showScaleLabels = false, showScaleLabels = false,
onClick,
values, values,
} = props; } = props;
const theme = useTheme2(); const theme = useTheme2();
@ -247,13 +250,27 @@ export function RadialGauge(props: RadialGaugeProps) {
} }
} }
return ( const body = (
<div className={styles.vizWrapper} style={{ width, height }}> <>
<svg width={width} height={height} role="img" aria-label={t('gauge.category-gauge', 'Gauge')}> <svg width={width} height={height} role="img" aria-label={t('gauge.category-gauge', 'Gauge')}>
<defs>{defs}</defs> <defs>{defs}</defs>
{graphics} {graphics}
</svg> </svg>
{sparklineElement} {sparklineElement}
</>
);
if (onClick) {
return (
<button onClick={onClick} className={cx(styles.clearButton, styles.vizWrapper)} style={{ width, height }}>
{body}
</button>
);
}
return (
<div className={styles.vizWrapper} style={{ width, height }}>
{body}
</div> </div>
); );
} }
@ -281,5 +298,12 @@ function getStyles(theme: GrafanaTheme2) {
filter: theme.isLight ? `drop-shadow(0px 0px 1px #888);` : '', filter: theme.isLight ? `drop-shadow(0px 0px 1px #888);` : '',
}, },
}), }),
clearButton: css({
background: 'transparent',
color: theme.colors.text.primary,
border: 'none',
padding: 0,
cursor: 'context-menu',
}),
}; };
} }

View File

@ -25,7 +25,7 @@ export function RadialSparkline({ sparkline, dimensions, theme, color, shape }:
const height = radius / 4; const height = radius / 4;
const widthFactor = shape === 'gauge' ? 1.6 : 1.4; const widthFactor = shape === 'gauge' ? 1.6 : 1.4;
const width = radius * widthFactor - barWidth; const width = radius * widthFactor - barWidth;
const topPos = shape === 'gauge' ? `calc(50% + ${radius / 1.75}px)` : `calc(50% + ${radius / 2.8}px)`; const topPos = shape === 'gauge' ? `${dimensions.gaugeBottomY - height}px` : `calc(50% + ${radius / 2.8}px)`;
const styles = css({ const styles = css({
position: 'absolute', position: 'absolute',

View File

@ -59,9 +59,9 @@ export function RadialText({
// Not sure where this comes from but svg text is not using body line-height // Not sure where this comes from but svg text is not using body line-height
const lineHeight = 1.21; const lineHeight = 1.21;
const valueWidthToRadiusFactor = 0.6; const valueWidthToRadiusFactor = 0.85;
const nameToHeightFactor = 0.3; const nameToHeightFactor = 0.45;
const largeRadiusScalingDecay = 0.92; const largeRadiusScalingDecay = 0.86;
// This pow 0.92 factor is to create a decay so the font size does not become rediculously large for very large panels // This pow 0.92 factor is to create a decay so the font size does not become rediculously large for very large panels
let maxValueHeight = valueWidthToRadiusFactor * Math.pow(radius, largeRadiusScalingDecay); let maxValueHeight = valueWidthToRadiusFactor * Math.pow(radius, largeRadiusScalingDecay);
@ -99,7 +99,8 @@ export function RadialText({
const nameHeight = nameFontSize * lineHeight; const nameHeight = nameFontSize * lineHeight;
const valueY = showName ? centerY - nameHeight / 2 : centerY; const valueY = showName ? centerY - nameHeight / 2 : centerY;
const nameY = showValue ? valueY + valueHeight * 0.7 : centerY; const valueNameSpacing = valueHeight / 3.5;
const nameY = showValue ? valueY + valueHeight / 2 + valueNameSpacing : centerY;
const nameColor = showValue ? theme.colors.text.secondary : theme.colors.text.primary; const nameColor = showValue ? theme.colors.text.secondary : theme.colors.text.primary;
const suffixShift = (valueFontSize - unitFontSize * 1.2) / 2; const suffixShift = (valueFontSize - unitFontSize * 1.2) / 2;

View File

@ -40,8 +40,15 @@ export function ThresholdsBar({
for (let i = 1; i < thresholds.length; i++) { for (let i = 1; i < thresholds.length; i++) {
const threshold = thresholds[i]; const threshold = thresholds[i];
const valueDeg = ((threshold.value - min) / (max - min)) * angleRange; let valueDeg = ((threshold.value - min) / (max - min)) * angleRange;
const lengthDeg = valueDeg - currentStart + startAngle;
if (valueDeg > angleRange) {
valueDeg = angleRange;
} else if (valueDeg < 0) {
valueDeg = 0;
}
let lengthDeg = valueDeg - currentStart + startAngle;
paths.push( paths.push(
<RadialArcPath <RadialArcPath

View File

@ -178,5 +178,20 @@ describe('RadialGauge utils', () => {
expect(result.angle).toBe(360); expect(result.angle).toBe(360);
}); });
it('should handle values lower than min', () => {
const fieldDisplay = createFieldDisplay(-50, 0, 100);
const result = getValueAngleForValue(fieldDisplay, 240, 120);
expect(result.angle).toBe(0);
});
it('should handle values higher than max', () => {
const fieldDisplay = createFieldDisplay(200, 0, 100);
const result = getValueAngleForValue(fieldDisplay, 240, 120);
// Expect the angle to be clamped to the maximum range
expect(result.angle).toBe(240);
});
}); });
}); });

View File

@ -9,6 +9,8 @@ export function getValueAngleForValue(fieldDisplay: FieldDisplay, startAngle: nu
if (angle > angleRange) { if (angle > angleRange) {
angle = angleRange; angle = angleRange;
} else if (angle < 0) {
angle = 0;
} }
return { angleRange, angle }; return { angleRange, angle };
@ -39,6 +41,7 @@ export interface GaugeDimensions {
scaleLabelsFontSize: number; scaleLabelsFontSize: number;
scaleLabelsSpacing: number; scaleLabelsSpacing: number;
scaleLabelsRadius: number; scaleLabelsRadius: number;
gaugeBottomY: number;
} }
export function calculateDimensions( export function calculateDimensions(
@ -119,8 +122,8 @@ export function calculateDimensions(
let innerRadius = outerRadius - barWidth / 2; let innerRadius = outerRadius - barWidth / 2;
const maxY = maxRadius * Math.sin(toRad(yMaxAngle)) + maxRadius; const belowCenterY = maxRadius * Math.sin(toRad(yMaxAngle));
const rest = height - maxY - margin * 2; const rest = height - belowCenterY - margin * 2 - maxRadius;
const centerX = width / 2; const centerX = width / 2;
const centerY = maxRadius + margin + rest / 2; const centerY = maxRadius + margin + rest / 2;
@ -130,6 +133,7 @@ export function calculateDimensions(
return { return {
margin, margin,
gaugeBottomY: centerY + belowCenterY,
radius: innerRadius, radius: innerRadius,
centerX, centerX,
centerY, centerY,

View File

@ -2110,6 +2110,14 @@ var (
Owner: grafanaPluginsPlatformSquad, Owner: grafanaPluginsPlatformSquad,
Expression: "false", Expression: "false",
}, },
{
Name: "newGauge",
Description: "Enable new gauge visualization",
Stage: FeatureStageExperimental,
FrontendOnly: true,
Owner: grafanaDatavizSquad,
Expression: "false",
},
{ {
Name: "preventPanelChromeOverflow", Name: "preventPanelChromeOverflow",
Description: "Restrict PanelChrome contents with overflow: hidden;", Description: "Restrict PanelChrome contents with overflow: hidden;",

View File

@ -271,6 +271,7 @@ pluginContainers,privatePreview,@grafana/plugins-platform-backend,false,true,fal
tempoSearchBackendMigration,GA,@grafana/oss-big-tent,false,true,false tempoSearchBackendMigration,GA,@grafana/oss-big-tent,false,true,false
cdnPluginsLoadFirst,experimental,@grafana/plugins-platform-backend,false,false,false cdnPluginsLoadFirst,experimental,@grafana/plugins-platform-backend,false,false,false
cdnPluginsUrls,experimental,@grafana/plugins-platform-backend,false,false,false cdnPluginsUrls,experimental,@grafana/plugins-platform-backend,false,false,false
newGauge,experimental,@grafana/dataviz-squad,false,false,true
preventPanelChromeOverflow,preview,@grafana/grafana-frontend-platform,false,false,true preventPanelChromeOverflow,preview,@grafana/grafana-frontend-platform,false,false,true
pluginStoreServiceLoading,experimental,@grafana/plugins-platform-backend,false,false,false pluginStoreServiceLoading,experimental,@grafana/plugins-platform-backend,false,false,false
onlyStoreActionSets,GA,@grafana/identity-access-team,false,false,false onlyStoreActionSets,GA,@grafana/identity-access-team,false,false,false

1 Name Stage Owner requiresDevMode RequiresRestart FrontendOnly
271 tempoSearchBackendMigration GA @grafana/oss-big-tent false true false
272 cdnPluginsLoadFirst experimental @grafana/plugins-platform-backend false false false
273 cdnPluginsUrls experimental @grafana/plugins-platform-backend false false false
274 newGauge experimental @grafana/dataviz-squad false false true
275 preventPanelChromeOverflow preview @grafana/grafana-frontend-platform false false true
276 pluginStoreServiceLoading experimental @grafana/plugins-platform-backend false false false
277 onlyStoreActionSets GA @grafana/identity-access-team false false false

View File

@ -1094,6 +1094,10 @@ const (
// Enable loading plugins via declarative URLs // Enable loading plugins via declarative URLs
FlagCdnPluginsUrls = "cdnPluginsUrls" FlagCdnPluginsUrls = "cdnPluginsUrls"
// FlagNewGauge
// Enable new gauge visualization
FlagNewGauge = "newGauge"
// FlagPreventPanelChromeOverflow // FlagPreventPanelChromeOverflow
// Restrict PanelChrome contents with overflow: hidden; // Restrict PanelChrome contents with overflow: hidden;
FlagPreventPanelChromeOverflow = "preventPanelChromeOverflow" FlagPreventPanelChromeOverflow = "preventPanelChromeOverflow"

View File

@ -2694,6 +2694,20 @@
"expression": "true" "expression": "true"
} }
}, },
{
"metadata": {
"name": "newGauge",
"resourceVersion": "1760700645318",
"creationTimestamp": "2025-10-17T11:30:45Z"
},
"spec": {
"description": "Enable new gauge visualization",
"stage": "experimental",
"codeowner": "@grafana/dataviz-squad",
"frontend": true,
"expression": "false"
}
},
{ {
"metadata": { "metadata": {
"name": "newInfluxDSConfigPageDesign", "name": "newInfluxDSConfigPageDesign",

View File

@ -1,3 +1,5 @@
import { config } from '@grafana/runtime';
const cloudwatchPlugin = async () => const cloudwatchPlugin = async () =>
await import(/* webpackChunkName: "cloudwatchPlugin" */ 'app/plugins/datasource/cloudwatch/module'); await import(/* webpackChunkName: "cloudwatchPlugin" */ 'app/plugins/datasource/cloudwatch/module');
const dashboardDSPlugin = async () => const dashboardDSPlugin = async () =>
@ -104,7 +106,7 @@ const builtInPlugins: Record<string, System.Module | (() => Promise<System.Modul
'core:plugin/debug': debugPanel, 'core:plugin/debug': debugPanel,
'core:plugin/flamegraph': flamegraphPanel, 'core:plugin/flamegraph': flamegraphPanel,
'core:plugin/gettingstarted': gettingStartedPanel, 'core:plugin/gettingstarted': gettingStartedPanel,
'core:plugin/gauge': gaugePanel, 'core:plugin/gauge': config.featureToggles.newGauge ? radialBar : gaugePanel,
'core:plugin/piechart': pieChartPanel, 'core:plugin/piechart': pieChartPanel,
'core:plugin/bargauge': barGaugePanel, 'core:plugin/bargauge': barGaugePanel,
'core:plugin/barchart': barChartPanel, 'core:plugin/barchart': barChartPanel,

View File

@ -28,7 +28,10 @@
"$ref": "#/definitions/DeepPartial<ThemeRichColor>" "$ref": "#/definitions/DeepPartial<ThemeRichColor>"
}, },
"mode": { "mode": {
"enum": ["dark", "light"], "enum": [
"dark",
"light"
],
"type": "string" "type": "string"
}, },
"primary": { "primary": {
@ -350,27 +353,63 @@
"type": "object" "type": "object"
}, },
"ThemeVizColorShadeName": { "ThemeVizColorShadeName": {
"enum": ["dark-red", "light-red", "red", "semi-dark-red", "super-light-red"], "enum": [
"dark-red",
"light-red",
"red",
"semi-dark-red",
"super-light-red"
],
"type": "string" "type": "string"
}, },
"ThemeVizColorShadeName_1": { "ThemeVizColorShadeName_1": {
"enum": ["dark-orange", "light-orange", "orange", "semi-dark-orange", "super-light-orange"], "enum": [
"dark-orange",
"light-orange",
"orange",
"semi-dark-orange",
"super-light-orange"
],
"type": "string" "type": "string"
}, },
"ThemeVizColorShadeName_2": { "ThemeVizColorShadeName_2": {
"enum": ["dark-yellow", "light-yellow", "semi-dark-yellow", "super-light-yellow", "yellow"], "enum": [
"dark-yellow",
"light-yellow",
"semi-dark-yellow",
"super-light-yellow",
"yellow"
],
"type": "string" "type": "string"
}, },
"ThemeVizColorShadeName_3": { "ThemeVizColorShadeName_3": {
"enum": ["dark-green", "green", "light-green", "semi-dark-green", "super-light-green"], "enum": [
"dark-green",
"green",
"light-green",
"semi-dark-green",
"super-light-green"
],
"type": "string" "type": "string"
}, },
"ThemeVizColorShadeName_4": { "ThemeVizColorShadeName_4": {
"enum": ["blue", "dark-blue", "light-blue", "semi-dark-blue", "super-light-blue"], "enum": [
"blue",
"dark-blue",
"light-blue",
"semi-dark-blue",
"super-light-blue"
],
"type": "string" "type": "string"
}, },
"ThemeVizColorShadeName_5": { "ThemeVizColorShadeName_5": {
"enum": ["dark-purple", "light-purple", "purple", "semi-dark-purple", "super-light-purple"], "enum": [
"dark-purple",
"light-purple",
"purple",
"semi-dark-purple",
"super-light-purple"
],
"type": "string" "type": "string"
}, },
"ThemeVizHue": { "ThemeVizHue": {
@ -509,3 +548,4 @@
}, },
"type": "object" "type": "object"
} }

View File

@ -88,56 +88,7 @@ describe('Gauge Panel Migrations', () => {
//@ts-ignore //@ts-ignore
expect(result.reduceOptions.overrides).toBeUndefined(); expect(result.reduceOptions.overrides).toBeUndefined();
expect((panel as PanelModel).fieldConfig).toMatchInlineSnapshot(` expect((panel as PanelModel).fieldConfig).toMatchSnapshot();
{
"defaults": {
"color": {
"mode": "thresholds",
},
"decimals": 3,
"mappings": [
{
"from": "50",
"id": 1,
"operator": "",
"text": "BIG",
"to": "1000",
"type": 2,
"value": "",
},
],
"max": "50",
"min": "-50",
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"index": 0,
"value": -Infinity,
},
{
"color": "#EAB839",
"index": 1,
"value": -25,
},
{
"color": "#6ED0E0",
"index": 2,
"value": 0,
},
{
"color": "red",
"index": 3,
"value": 25,
},
],
},
"unit": "accMS2",
},
"overrides": [],
}
`);
}); });
it('change from angular singlestat to gauge', () => { it('change from angular singlestat to gauge', () => {

View File

@ -14,3 +14,54 @@ exports[`Gauge Panel Migrations from 6.1.1 1`] = `
"showThresholdMarkers": true, "showThresholdMarkers": true,
} }
`; `;
exports[`Gauge Panel Migrations from 6.1.1 2`] = `
{
"defaults": {
"color": {
"mode": "thresholds",
},
"decimals": 3,
"mappings": [
{
"from": "50",
"id": 1,
"operator": "",
"text": "BIG",
"to": "1000",
"type": 2,
"value": "",
},
],
"max": "50",
"min": "-50",
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"index": 0,
"value": -Infinity,
},
{
"color": "#EAB839",
"index": 1,
"value": -25,
},
{
"color": "#6ED0E0",
"index": 2,
"value": 0,
},
{
"color": "red",
"index": 3,
"value": 25,
},
],
},
"unit": "accMS2",
},
"overrides": [],
}
`;

View File

@ -0,0 +1,146 @@
import { PanelModel } from '@grafana/data';
import { FieldColorModeId } from '@grafana/schema/dist/esm/index.gen';
import { gaugePanelMigrationHandler, gaugePanelChangedHandler } from './GaugeMigrations';
describe('Gauge Panel Migrations', () => {
it('from old gauge', () => {
const panel = {
id: 2,
options: {
reduceOptions: {
calcs: ['lastNotNull'],
},
showThresholdLabels: false,
showThresholdMarkers: true,
},
fieldConfig: {
defaults: {
color: {
mode: FieldColorModeId.Fixed,
fixedColor: 'blue',
},
},
overrides: [],
},
pluginVersion: '12.3.0',
type: 'gauge',
} as Omit<PanelModel, 'fieldConfig'>;
const result = gaugePanelMigrationHandler(panel as PanelModel);
expect(result.showThresholdMarkers).toBe(false);
expect(result.sparkline).toBe(false);
});
it('from 6.1.1', () => {
const panel = {
datasource: '-- Grafana --',
gridPos: {
h: 9,
w: 12,
x: 0,
y: 0,
},
id: 2,
options: {
maxValue: '50',
minValue: '-50',
orientation: 'auto',
showThresholdLabels: true,
showThresholdMarkers: true,
thresholds: [
{
color: 'green',
index: 0,
value: -Infinity,
},
{
color: '#EAB839',
index: 1,
value: -25,
},
{
color: '#6ED0E0',
index: 2,
value: 0,
},
{
color: 'red',
index: 3,
value: 25,
},
],
valueMappings: [
{
id: 1,
operator: '',
value: '',
text: 'BIG',
type: 2,
from: '50',
to: '1000',
},
],
valueOptions: {
decimals: 3,
prefix: 'XX',
stat: 'last',
suffix: 'YY',
unit: 'accMS2',
},
},
pluginVersion: '6.1.6',
targets: [
{
refId: 'A',
},
{
refId: 'B',
},
{
refId: 'C',
},
],
timeFrom: null,
timeShift: null,
title: 'Panel Title',
type: 'gauge',
} as Omit<PanelModel, 'fieldConfig'>;
const result = gaugePanelMigrationHandler(panel as PanelModel);
// Ignored due to the API change
//@ts-ignore
expect(result.reduceOptions.defaults).toBeUndefined();
// Ignored due to the API change
//@ts-ignore
expect(result.reduceOptions.overrides).toBeUndefined();
expect((panel as PanelModel).fieldConfig).toMatchSnapshot();
});
it('change from angular singlestat to gauge', () => {
const old = {
angular: {
format: 'ms',
decimals: 7,
gauge: {
maxValue: 150,
minValue: -10,
show: true,
thresholdLabels: true,
thresholdMarkers: true,
},
},
};
const panel = {} as PanelModel;
const newOptions = gaugePanelChangedHandler(panel, 'singlestat', old, { defaults: {}, overrides: [] });
expect(panel.fieldConfig.defaults.unit).toBe('ms');
expect(panel.fieldConfig.defaults.min).toBe(-10);
expect(panel.fieldConfig.defaults.max).toBe(150);
expect(panel.fieldConfig.defaults.decimals).toBe(7);
expect(newOptions.showThresholdMarkers).toBe(true);
expect(newOptions.showThresholdLabels).toBe(true);
});
});

View File

@ -0,0 +1,64 @@
import { PanelModel, PanelTypeChangedHandler } from '@grafana/data';
import { FieldColorModeId } from '@grafana/schema/dist/esm/index.gen';
import { sharedSingleStatPanelChangedHandler, sharedSingleStatMigrationHandler } from '@grafana/ui';
import { Options } from './panelcfg.gen';
// This is called when the panel first loads
export function gaugePanelMigrationHandler(panel: PanelModel<Options>): Partial<Options> {
const sharedOptions = sharedSingleStatMigrationHandler(panel);
const newOptions: Partial<Options> = { ...sharedOptions };
const previousVersion = parseFloat(panel.pluginVersion || '8');
const fieldConfig = panel.fieldConfig;
if (previousVersion <= 12.3) {
// This option had no effect in old gauge unless color mode was 'From thresholds'
if (newOptions.showThresholdMarkers && fieldConfig?.defaults?.color?.mode !== FieldColorModeId.Thresholds) {
newOptions.showThresholdMarkers = false;
}
// This option is enabled by default in new gauge but does not exist in old gauge
newOptions.sparkline = false;
// Remove deprecated sizing options
if ('sizing' in newOptions) {
delete newOptions.sizing;
}
if ('minVizHeight' in newOptions) {
delete newOptions.minVizHeight;
}
if ('minVizWidth' in newOptions) {
delete newOptions.minVizWidth;
}
}
return newOptions;
}
export function shouldMigrateGauge(panel: PanelModel): boolean {
const previousVersion = parseFloat(panel.pluginVersion ?? '8');
return previousVersion <= 12.3;
}
// This is called when the panel changes from another panel
export const gaugePanelChangedHandler: PanelTypeChangedHandler<Options> = (
panel,
prevPluginId: string,
prevOptions
) => {
// This handles most config changes
const opts: Options = sharedSingleStatPanelChangedHandler(panel, prevPluginId, prevOptions);
// Changing from angular singlestat
if (prevPluginId === 'singlestat' && prevOptions.angular) {
const gauge = prevOptions.angular.gauge;
if (gauge) {
opts.showThresholdMarkers = gauge.thresholdMarkers;
opts.showThresholdLabels = gauge.thresholdLabels;
}
}
return opts;
};

View File

@ -47,6 +47,7 @@ export function RadialBarPanel({
alignmentFactors={valueProps.alignmentFactors} alignmentFactors={valueProps.alignmentFactors}
valueManualFontSize={options.text?.valueSize} valueManualFontSize={options.text?.valueSize}
nameManualFontSize={options.text?.titleSize} nameManualFontSize={options.text?.titleSize}
onClick={menuProps.openMenu}
/> />
); );
} }

View File

@ -0,0 +1,52 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Gauge Panel Migrations from 6.1.1 1`] = `
{
"defaults": {
"color": {
"mode": "thresholds",
},
"decimals": 3,
"mappings": [
{
"from": "50",
"id": 1,
"operator": "",
"text": "BIG",
"to": "1000",
"type": 2,
"value": "",
},
],
"max": "50",
"min": "-50",
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"index": 0,
"value": -Infinity,
},
{
"color": "#EAB839",
"index": 1,
"value": -25,
},
{
"color": "#6ED0E0",
"index": 2,
"value": 0,
},
{
"color": "red",
"index": 3,
"value": 25,
},
],
},
"unit": "accMS2",
},
"overrides": [],
}
`;

View File

@ -5,6 +5,7 @@ import { commonOptionsBuilder } from '@grafana/ui';
import { addOrientationOption, addStandardDataReduceOptions } from '../stat/common'; import { addOrientationOption, addStandardDataReduceOptions } from '../stat/common';
import { EffectsEditor } from './EffectsEditor'; import { EffectsEditor } from './EffectsEditor';
import { gaugePanelChangedHandler, gaugePanelMigrationHandler, shouldMigrateGauge } from './GaugeMigrations';
import { RadialBarPanel } from './RadialBarPanel'; import { RadialBarPanel } from './RadialBarPanel';
import { defaultGaugePanelEffects, defaultOptions, Options } from './panelcfg.gen'; import { defaultGaugePanelEffects, defaultOptions, Options } from './panelcfg.gen';
import { GaugeSuggestionsSupplier } from './suggestions'; import { GaugeSuggestionsSupplier } from './suggestions';
@ -111,4 +112,6 @@ export const plugin = new PanelPlugin<Options>(RadialBarPanel)
defaultValue: defaultGaugePanelEffects, defaultValue: defaultGaugePanelEffects,
}); });
}) })
.setSuggestionsSupplier(new GaugeSuggestionsSupplier()); .setSuggestionsSupplier(new GaugeSuggestionsSupplier())
.setMigrationHandler(gaugePanelMigrationHandler, shouldMigrateGauge)
.setPanelChangeHandler(gaugePanelChangedHandler);

View File

@ -10,3 +10,9 @@
Gauge => new gauge migration notes Gauge => new gauge migration notes
Old gauge "Show threshold markers" does nothing when color scheme != From thresholds Old gauge "Show threshold markers" does nothing when color scheme != From thresholds
Decide what to do with
sizing: manual & minVizHeight & minVizWidth (do not think this panel should scroll, and minWidth is broken/does not scroll)
neutral value

View File

@ -29,7 +29,7 @@ composableKinds: PanelCfg: {
barGlow?: bool | *false barGlow?: bool | *false
spotlight?: bool | *false spotlight?: bool | *false
rounded?: bool | *false rounded?: bool | *false
centerGlow?: bool | *true centerGlow?: bool | *false
} @cuetsy(kind="interface") } @cuetsy(kind="interface")
Options: { Options: {
@ -38,10 +38,10 @@ composableKinds: PanelCfg: {
showThresholdLabels: bool | *false showThresholdLabels: bool | *false
segmentCount: number | *1 segmentCount: number | *1
segmentSpacing: number | *0.3 segmentSpacing: number | *0.3
sparkline?: bool | *false sparkline?: bool | *true
shape: "circle" | *"gauge" shape: "circle" | *"gauge"
barWidthFactor: number | *0.4 barWidthFactor: number | *0.5
gradient: *"none" | "auto" gradient: "none" | *"auto"
effects: GaugePanelEffects | *{} effects: GaugePanelEffects | *{}
} @cuetsy(kind="interface") } @cuetsy(kind="interface")
} }

View File

@ -19,7 +19,7 @@ export interface GaugePanelEffects {
export const defaultGaugePanelEffects: Partial<GaugePanelEffects> = { export const defaultGaugePanelEffects: Partial<GaugePanelEffects> = {
barGlow: false, barGlow: false,
centerGlow: true, centerGlow: false,
rounded: false, rounded: false,
spotlight: false, spotlight: false,
}; };
@ -37,13 +37,13 @@ export interface Options extends common.SingleStatBaseOptions {
} }
export const defaultOptions: Partial<Options> = { export const defaultOptions: Partial<Options> = {
barWidthFactor: 0.4, barWidthFactor: 0.5,
effects: {}, effects: {},
gradient: 'none', gradient: 'auto',
segmentCount: 1, segmentCount: 1,
segmentSpacing: 0.3, segmentSpacing: 0.3,
shape: 'gauge', shape: 'gauge',
showThresholdLabels: false, showThresholdLabels: false,
showThresholdMarkers: true, showThresholdMarkers: true,
sparkline: false, sparkline: true,
}; };