From 37e2becdd71b350e4c1f82416844d900f23fd0ce Mon Sep 17 00:00:00 2001 From: Leon Sorokin Date: Thu, 25 May 2023 13:38:32 -0500 Subject: [PATCH] StatusHistory: Fix rendering of value-mapped null (#69033) --- packages/grafana-data/src/types/fieldColor.ts | 2 +- .../core/components/TimelineChart/timeline.ts | 16 ++++++++++++---- .../app/core/components/TimelineChart/utils.ts | 10 ++++++++++ 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/packages/grafana-data/src/types/fieldColor.ts b/packages/grafana-data/src/types/fieldColor.ts index 8dcbb526aa3..26d5cc2a662 100644 --- a/packages/grafana-data/src/types/fieldColor.ts +++ b/packages/grafana-data/src/types/fieldColor.ts @@ -37,4 +37,4 @@ export interface FieldColor { */ export type FieldColorSeriesByMode = 'min' | 'max' | 'last'; -export const FALLBACK_COLOR = 'gray'; +export const FALLBACK_COLOR = '#808080'; diff --git a/public/app/core/components/TimelineChart/timeline.ts b/public/app/core/components/TimelineChart/timeline.ts index 6a53ef3b363..892d0f4ea8f 100644 --- a/public/app/core/components/TimelineChart/timeline.ts +++ b/public/app/core/components/TimelineChart/timeline.ts @@ -47,6 +47,7 @@ export interface TimelineCoreOptions { showValue: VisibilityMode; mergeValues?: boolean; isDiscrete: (seriesIdx: number) => boolean; + hasMappedNull: (seriesIdx: number) => boolean; getValueColor: (seriesIdx: number, value: unknown) => string; label: (seriesIdx: number) => string; getTimeRange: () => TimeRange; @@ -64,6 +65,7 @@ export function getConfig(opts: TimelineCoreOptions) { mode, numSeries, isDiscrete, + hasMappedNull, rowHeight = 0, colWidth = 0, showValue, @@ -210,6 +212,7 @@ export function getConfig(opts: TimelineCoreOptions) { let strokeWidth = round((series.width || 0) * uPlot.pxRatio); let discrete = isDiscrete(sidx); + let mappedNull = discrete && hasMappedNull(sidx); u.ctx.save(); rect(u.ctx, u.bbox.left, u.bbox.top, u.bbox.width, u.bbox.height); @@ -220,7 +223,7 @@ export function getConfig(opts: TimelineCoreOptions) { for (let ix = 0; ix < dataY.length; ix++) { let yVal = dataY[ix]; - if (yVal != null) { + if (yVal != null || mappedNull) { let left = Math.round(valToPosX(dataX[ix], scaleX, xDim, xOff)); let nextIx = ix; @@ -262,7 +265,9 @@ export function getConfig(opts: TimelineCoreOptions) { //let xShift = align === 1 ? 0 : align === -1 ? barWid : barWid / 2; for (let ix = idx0; ix <= idx1; ix++) { - if (dataY[ix] != null) { + let yVal = dataY[ix]; + + if (yVal != null || mappedNull) { // TODO: all xPos can be pre-computed once for all series in aligned set let left = valToPosX(dataX[ix], scaleX, xDim, xOff); @@ -278,7 +283,7 @@ export function getConfig(opts: TimelineCoreOptions) { strokeWidth, iy, ix, - dataY[ix], + yVal, discrete ); } @@ -316,10 +321,13 @@ export function getConfig(opts: TimelineCoreOptions) { (series, dataX, dataY, scaleX, scaleY, valToPosX, valToPosY, xOff, yOff, xDim, yDim) => { let strokeWidth = round((series.width || 0) * uPlot.pxRatio); + let discrete = isDiscrete(sidx); + let mappedNull = discrete && hasMappedNull(sidx); + let y = round(yOff + yMids[sidx - 1]); for (let ix = 0; ix < dataY.length; ix++) { - if (dataY[ix] != null) { + if (dataY[ix] != null || mappedNull) { const boxRect = boxRectsBySeries[sidx - 1][ix]; if (!boxRect || boxRect.x >= xDim) { diff --git a/public/app/core/components/TimelineChart/utils.ts b/public/app/core/components/TimelineChart/utils.ts index d424525956f..0480cbaad90 100644 --- a/public/app/core/components/TimelineChart/utils.ts +++ b/public/app/core/components/TimelineChart/utils.ts @@ -31,6 +31,7 @@ import { VisibilityMode, TimelineValueAlignment, HideableFieldConfig, + MappingType, } from '@grafana/schema'; import { FIXED_UNIT, @@ -112,6 +113,14 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn = ( return !(mode && field.display && mode.startsWith('continuous-')); }; + const hasMappedNull = (field: Field) => { + return ( + field.config.mappings?.some( + (mapping) => mapping.type === MappingType.SpecialValue && mapping.options.match === 'null' + ) || false + ); + }; + const getValueColorFn = (seriesIdx: number, value: unknown) => { const field = frame.fields[seriesIdx]; @@ -130,6 +139,7 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn = ( mode: mode!, numSeries: frame.fields.length - 1, isDiscrete: (seriesIdx) => isDiscrete(frame.fields[seriesIdx]), + hasMappedNull: (seriesIdx) => hasMappedNull(frame.fields[seriesIdx]), mergeValues, rowHeight: rowHeight, colWidth: colWidth,