StateTimeline: Support NaN and null value mappings (#105638)

* fix(#92944): add StateTimeline Null+NaN handling

* chore: remove console.warns from debugging

* test: initialize a couple of simple tests

* test: more tests for hasMappedNaN and hasMappedNull

* chore: revert some of the let-const syntax cleanup for a later PR

* chore: rename should draw method

* chore: fix comment typo

* refactor(timeline-chart-utils): un-nest hasSpecialMappedValue() helper

* test(timeline-chart-utils): unit test hasSpecialMappedValue() helper

* chore: fix code comment typos in changed files

* refactor(timeline-chart): reduce helper DRY-ness for better performance

* fix(timeline-chart): check Y value for truthiness, not if it is finite

* test(state-timeline): additional gdev test panels with null + NN values

* fix(timeline-chart): allow Y value of zero in checks

---------

Co-authored-by: Jesse David Peterson <jesdavpet@users.noreply.github.com>
Co-authored-by: Adela Almasan <adela.almasan@grafana.com>
This commit is contained in:
Paul Marbach
2025-06-12 15:13:25 -04:00
committed by GitHub
co-authored by Jesse David Peterson Adela Almasan
parent 7b70271ce6
commit 1e41c07920
7 changed files with 692 additions and 51 deletions
@@ -24,19 +24,27 @@
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
"id": 1263,
"id": 15116,
"links": [],
"liveNow": false,
"panels": [
{
"datasource": { "type": "testdata" },
"datasource": {
"type": "testdata"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"custom": {
"axisPlacement": "auto",
"fillOpacity": 70,
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineWidth": 0,
"spanNulls": false
},
@@ -45,7 +53,8 @@
"mode": "absolute",
"steps": [
{
"color": "green"
"color": "green",
"value": 0
},
{
"color": "red",
@@ -74,10 +83,12 @@
"rowHeight": 0.9,
"showValue": "auto",
"tooltip": {
"hideZeros": false,
"mode": "single",
"sort": "none"
}
},
"pluginVersion": "12.1.0-pre",
"targets": [
{
"datasource": {
@@ -93,14 +104,23 @@
"type": "state-timeline"
},
{
"datasource": { "type": "testdata" },
"datasource": {
"type": "testdata"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"custom": {
"axisPlacement": "auto",
"fillOpacity": 70,
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineWidth": 0,
"spanNulls": false
},
@@ -109,7 +129,8 @@
"mode": "absolute",
"steps": [
{
"color": "green"
"color": "green",
"value": 0
},
{
"color": "red",
@@ -146,10 +167,12 @@
"rowHeight": 0.9,
"showValue": "auto",
"tooltip": {
"hideZeros": false,
"mode": "single",
"sort": "none"
}
},
"pluginVersion": "12.1.0-pre",
"targets": [
{
"datasource": {
@@ -165,14 +188,23 @@
"type": "state-timeline"
},
{
"datasource": { "type": "testdata" },
"datasource": {
"type": "testdata"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"custom": {
"axisPlacement": "auto",
"fillOpacity": 70,
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineWidth": 0,
"spanNulls": false
},
@@ -181,7 +213,8 @@
"mode": "percentage",
"steps": [
{
"color": "green"
"color": "green",
"value": 0
},
{
"color": "red",
@@ -218,10 +251,12 @@
"rowHeight": 0.9,
"showValue": "auto",
"tooltip": {
"hideZeros": false,
"mode": "single",
"sort": "none"
}
},
"pluginVersion": "12.1.0-pre",
"targets": [
{
"datasource": {
@@ -237,14 +272,23 @@
"type": "state-timeline"
},
{
"datasource": { "type": "testdata" },
"datasource": {
"type": "testdata"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"custom": {
"axisPlacement": "auto",
"fillOpacity": 70,
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineWidth": 0,
"spanNulls": false
},
@@ -253,7 +297,8 @@
"mode": "absolute",
"steps": [
{
"color": "green"
"color": "green",
"value": 0
},
{
"color": "red",
@@ -275,7 +320,8 @@
"mode": "absolute",
"steps": [
{
"color": "green"
"color": "green",
"value": 0
},
{
"color": "red",
@@ -314,10 +360,12 @@
"rowHeight": 0.9,
"showValue": "auto",
"tooltip": {
"hideZeros": false,
"mode": "single",
"sort": "none"
}
},
"pluginVersion": "12.1.0-pre",
"targets": [
{
"datasource": {
@@ -333,14 +381,23 @@
"type": "state-timeline"
},
{
"datasource": { "type": "testdata" },
"datasource": {
"type": "testdata"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisPlacement": "auto",
"fillOpacity": 70,
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineWidth": 0,
"spanNulls": false
},
@@ -394,7 +451,8 @@
"mode": "absolute",
"steps": [
{
"color": "green"
"color": "green",
"value": 0
}
]
}
@@ -416,13 +474,15 @@
"showLegend": true
},
"mergeValues": true,
"rowHeight": 0.9,
"rowHeight": 0.47,
"showValue": "auto",
"tooltip": {
"hideZeros": false,
"mode": "single",
"sort": "none"
}
},
"pluginVersion": "12.1.0-pre",
"targets": [
{
"datasource": {
@@ -438,14 +498,23 @@
"type": "state-timeline"
},
{
"datasource": { "type": "testdata" },
"datasource": {
"type": "testdata"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisPlacement": "auto",
"fillOpacity": 70,
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineWidth": 0,
"spanNulls": false
},
@@ -454,7 +523,8 @@
"mode": "absolute",
"steps": [
{
"color": "green"
"color": "green",
"value": 0
}
]
}
@@ -537,10 +607,12 @@
"rowHeight": 0.9,
"showValue": "auto",
"tooltip": {
"hideZeros": false,
"mode": "single",
"sort": "none"
}
},
"pluginVersion": "12.1.0-pre",
"targets": [
{
"datasource": {
@@ -556,14 +628,23 @@
"type": "state-timeline"
},
{
"datasource": { "type": "testdata" },
"datasource": {
"type": "testdata"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"custom": {
"axisPlacement": "auto",
"fillOpacity": 70,
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineWidth": 0,
"spanNulls": false
},
@@ -572,7 +653,8 @@
"mode": "absolute",
"steps": [
{
"color": "green"
"color": "green",
"value": 0
},
{
"color": "red",
@@ -601,10 +683,12 @@
"rowHeight": 0.9,
"showValue": "auto",
"tooltip": {
"hideZeros": false,
"mode": "single",
"sort": "none"
}
},
"pluginVersion": "12.1.0-pre",
"targets": [
{
"datasource": {
@@ -620,14 +704,23 @@
"type": "state-timeline"
},
{
"datasource": { "type": "testdata" },
"datasource": {
"type": "testdata"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"custom": {
"axisPlacement": "auto",
"fillOpacity": 70,
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineWidth": 0,
"spanNulls": false
},
@@ -636,7 +729,8 @@
"mode": "absolute",
"steps": [
{
"color": "green"
"color": "green",
"value": 0
},
{
"color": "red",
@@ -665,10 +759,12 @@
"rowHeight": 0.9,
"showValue": "auto",
"tooltip": {
"hideZeros": false,
"mode": "single",
"sort": "none"
}
},
"pluginVersion": "12.1.0-pre",
"targets": [
{
"datasource": {
@@ -709,10 +805,192 @@
}
],
"type": "state-timeline"
},
{
"datasource": {
"type": "testdata"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisPlacement": "auto",
"fillOpacity": 70,
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineWidth": 0,
"spanNulls": false
},
"fieldMinMax": false,
"mappings": [
{
"options": {
"match": "null",
"result": {
"color": "purple",
"index": 0,
"text": "null"
}
},
"type": "special"
},
{
"options": {
"match": "nan",
"result": {
"color": "red",
"index": 1,
"text": "NaN"
}
},
"type": "special"
}
],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": 0
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 9,
"w": 6,
"x": 0,
"y": 18
},
"id": 12,
"options": {
"alignValue": "center",
"legend": {
"displayMode": "list",
"placement": "bottom",
"showLegend": true
},
"mergeValues": true,
"rowHeight": 0.9,
"showValue": "auto",
"tooltip": {
"hideZeros": false,
"mode": "single",
"sort": "none"
}
},
"pluginVersion": "12.1.0-pre",
"targets": [
{
"datasource": {
"type": "testdata",
"uid": "PD8C576611E62080A"
},
"rawFrameContent": "[\n {\n \"schema\": {\n \"refId\": \"A\",\n \"fields\": [\n {\n \"name\": \"time\",\n \"type\": \"time\",\n \"typeInfo\": {\n \"frame\": \"time\",\n \"nullable\": true\n },\n \"config\": {}\n },\n {\n \"name\": \"value\",\n \"type\": \"number\",\n \"typeInfo\": {\n \"frame\": \"int64\",\n \"nullable\": true\n },\n \"config\": {\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n }\n ]\n }\n }\n }\n ]\n },\n \"data\": {\n \"values\": [\n [\n 1674732835000,\n 1674736435000,\n 1674740035000,\n 1674743635000,\n 1674747235000\n ],\n [\n 5,\n null,\n 20,\n null,\n 40\n ]\n ],\n \"entities\": [null, { \"NaN\": [3]}]\n }\n }\n]",
"refId": "A",
"scenarioId": "raw_frame"
}
],
"title": "special null | NaN value mapping from data",
"type": "state-timeline"
},
{
"datasource": {
"type": "testdata"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisPlacement": "auto",
"fillOpacity": 70,
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineWidth": 0,
"spanNulls": false
},
"fieldMinMax": false,
"mappings": [
{
"options": {
"match": "null+nan",
"result": {
"color": "super-light-red",
"index": 0,
"text": "null + NaN"
}
},
"type": "special"
}
],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": 0
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 9,
"w": 6,
"x": 6,
"y": 18
},
"id": 13,
"options": {
"alignValue": "center",
"legend": {
"displayMode": "list",
"placement": "bottom",
"showLegend": true
},
"mergeValues": true,
"rowHeight": 0.9,
"showValue": "auto",
"tooltip": {
"hideZeros": false,
"mode": "single",
"sort": "none"
}
},
"pluginVersion": "12.1.0-pre",
"targets": [
{
"datasource": {
"type": "testdata",
"uid": "PD8C576611E62080A"
},
"rawFrameContent": "[\n {\n \"schema\": {\n \"refId\": \"A\",\n \"fields\": [\n {\n \"name\": \"time\",\n \"type\": \"time\",\n \"typeInfo\": {\n \"frame\": \"time\",\n \"nullable\": true\n },\n \"config\": {}\n },\n {\n \"name\": \"value\",\n \"type\": \"number\",\n \"typeInfo\": {\n \"frame\": \"int64\",\n \"nullable\": true\n },\n \"config\": {\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n }\n ]\n }\n }\n }\n ]\n },\n \"data\": {\n \"values\": [\n [\n 1674732835000,\n 1674736435000,\n 1674740035000,\n 1674743635000,\n 1674747235000\n ],\n [\n 5,\n null,\n 20,\n null,\n 40\n ]\n ],\n \"entities\": [null, { \"NaN\": [1]}]\n }\n }\n]",
"refId": "A",
"scenarioId": "raw_frame"
}
],
"title": "special null + NaN value mapping from data",
"type": "state-timeline"
}
],
"refresh": false,
"schemaVersion": 38,
"preload": false,
"refresh": "",
"schemaVersion": 41,
"tags": [
"gdev",
"panel-tests",
@@ -723,13 +1001,12 @@
"list": []
},
"time": {
"from": "2023-01-26T11:33:55.000Z",
"to": "2023-01-26T14:33:55.000Z"
"from": "2023-01-26T11:29:47.180Z",
"to": "2023-01-26T16:29:39.205Z"
},
"timepicker": {},
"timezone": "",
"timezone": "utc",
"title": "StateTimeline - Thresholds & Mappings",
"uid": "Kce7z9TVz",
"version": 14,
"weekStart": ""
"version": 13
}