From 6c907fd9559ebcb4a737860b457ab776797d9a28 Mon Sep 17 00:00:00 2001 From: Paul Marbach Date: Mon, 14 Jul 2025 17:41:32 -0400 Subject: [PATCH] Table: Ensure that SparklineCells honor configured mins and maxes (#107991) * Table: ensure that FieldType.frame fields have mins and maxes applied * update Sparklines gdev dashboard * update gdev dashboards for table * jsonnet spacing * fix gdev dashboard ids and versions --- .../panel-table/table_pagination.json | 13 +- .../panel-table/table_sparkline_cell.json | 547 +++++++++++++++++- .../panel-table/table_tests.json | 8 +- .../panel-table/table_tests_new.json | 3 +- .../src/field/fieldOverrides.test.ts | 49 +- .../grafana-data/src/field/fieldOverrides.ts | 9 + 6 files changed, 594 insertions(+), 35 deletions(-) diff --git a/devenv/dev-dashboards/panel-table/table_pagination.json b/devenv/dev-dashboards/panel-table/table_pagination.json index 0bb4758490b..48f5df03f1f 100644 --- a/devenv/dev-dashboards/panel-table/table_pagination.json +++ b/devenv/dev-dashboards/panel-table/table_pagination.json @@ -2662,9 +2662,10 @@ } ], "schemaVersion": 32, - "tags": [ - "gdev", - "panel-tests" + "tags": [ + "gdev", + "panel-tests", + "table" ], "templating": { "list": [] @@ -2675,8 +2676,8 @@ }, "timepicker": {}, "timezone": "", - "title": "Table panel - Pagination", + "title": "Panel Tests - Table - Pagination", "uid": "1YGJxPUnz", - "version": 2, + "version": 3, "weekStart": "" -} \ No newline at end of file +} diff --git a/devenv/dev-dashboards/panel-table/table_sparkline_cell.json b/devenv/dev-dashboards/panel-table/table_sparkline_cell.json index d18488ca89d..24ec5a57326 100644 --- a/devenv/dev-dashboards/panel-table/table_sparkline_cell.json +++ b/devenv/dev-dashboards/panel-table/table_sparkline_cell.json @@ -18,22 +18,27 @@ "editable": true, "fiscalYearStartMonth": 0, "graphTooltip": 0, + "id": 1734, "links": [], - "liveNow": false, "panels": [ { - "datasource": { "type": "testdata" }, + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "PD8C576611E62080A" + }, "fieldConfig": { "defaults": { "color": { - "mode": "thresholds" + "fixedColor": "blue", + "mode": "shades" }, "custom": { "align": "auto", "cellOptions": { - "type": "auto" + "type": "sparkline" }, - "inspect": false + "inspect": false, + "wrapHeaderText": false }, "mappings": [], "thresholds": { @@ -41,7 +46,7 @@ "steps": [ { "color": "green", - "value": null + "value": 0 }, { "color": "red", @@ -53,8 +58,8 @@ "overrides": [] }, "gridPos": { - "h": 8, - "w": 12, + "h": 16, + "w": 8, "x": 0, "y": 0 }, @@ -71,14 +76,507 @@ }, "showHeader": true }, - "pluginVersion": "10.1.0-pre", + "pluginVersion": "12.1.0-pre", + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "ca3da78e-6bcc-4a2a-a080-d338a14528ca" + }, + "refId": "A", + "scenarioId": "random_walk", + "seriesCount": 31 + } + ], + "title": "Sparkline (lines)", + "transformations": [ + { + "id": "timeSeriesTable", + "options": { + "A": { + "timeField": "time" + } + } + } + ], + "type": "table" + }, + { + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "PD8C576611E62080A" + }, + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "blue", + "mode": "shades" + }, + "custom": { + "align": "auto", + "cellOptions": { + "drawStyle": "bars", + "type": "sparkline" + }, + "inspect": false, + "wrapHeaderText": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 16, + "w": 8, + "x": 8, + "y": 0 + }, + "id": 2, + "maxDataPoints": 60, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true + }, + "pluginVersion": "12.1.0-pre", + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "ca3da78e-6bcc-4a2a-a080-d338a14528ca" + }, + "refId": "A", + "scenarioId": "random_walk", + "seriesCount": 31 + } + ], + "title": "Sparklines (bars)", + "transformations": [ + { + "id": "timeSeriesTable", + "options": { + "A": { + "timeField": "time" + } + } + } + ], + "type": "table" + }, + { + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "PD8C576611E62080A" + }, + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "blue", + "mode": "shades" + }, + "custom": { + "align": "auto", + "cellOptions": { + "drawStyle": "points", + "type": "sparkline" + }, + "inspect": false, + "wrapHeaderText": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 16, + "w": 8, + "x": 16, + "y": 0 + }, + "id": 3, + "maxDataPoints": 100, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true + }, + "pluginVersion": "12.1.0-pre", + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "ca3da78e-6bcc-4a2a-a080-d338a14528ca" + }, + "refId": "A", + "scenarioId": "random_walk", + "seriesCount": 31 + } + ], + "title": "Sparklines (points)", + "transformations": [ + { + "id": "timeSeriesTable", + "options": { + "A": { + "timeField": "time" + } + } + } + ], + "type": "table" + }, + { + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "PD8C576611E62080A" + }, + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "green", + "mode": "shades" + }, + "custom": { + "align": "auto", + "cellOptions": { + "drawStyle": "line", + "type": "sparkline" + }, + "inspect": false, + "wrapHeaderText": false + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "areaF2" + }, + "overrides": [] + }, + "gridPos": { + "h": 16, + "w": 8, + "x": 0, + "y": 16 + }, + "id": 4, + "maxDataPoints": 100, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true + }, + "pluginVersion": "12.1.0-pre", + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "ca3da78e-6bcc-4a2a-a080-d338a14528ca" + }, + "refId": "A", + "scenarioId": "random_walk", + "seriesCount": 31 + } + ], + "title": "Sparklines (min=0)", + "transformations": [ + { + "id": "timeSeriesTable", + "options": { + "A": { + "timeField": "time" + } + } + } + ], + "type": "table" + }, + { + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "PD8C576611E62080A" + }, + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "yellow", + "mode": "shades" + }, + "custom": { + "align": "auto", + "cellOptions": { + "drawStyle": "line", + "type": "sparkline" + }, + "inspect": false, + "wrapHeaderText": false + }, + "mappings": [], + "max": 100, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "areaF2" + }, + "overrides": [] + }, + "gridPos": { + "h": 16, + "w": 8, + "x": 8, + "y": 16 + }, + "id": 5, + "maxDataPoints": 100, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true + }, + "pluginVersion": "12.1.0-pre", + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "ca3da78e-6bcc-4a2a-a080-d338a14528ca" + }, + "refId": "A", + "scenarioId": "random_walk", + "seriesCount": 31 + } + ], + "title": "Sparklines (max=100)", + "transformations": [ + { + "id": "timeSeriesTable", + "options": { + "A": { + "timeField": "time" + } + } + } + ], + "type": "table" + }, + { + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "PD8C576611E62080A" + }, + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "purple", + "mode": "shades" + }, + "custom": { + "align": "auto", + "cellOptions": { + "drawStyle": "line", + "lineInterpolation": "linear", + "type": "sparkline" + }, + "inspect": false, + "wrapHeaderText": false + }, + "mappings": [], + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "areaF2" + }, + "overrides": [] + }, + "gridPos": { + "h": 16, + "w": 8, + "x": 16, + "y": 16 + }, + "id": 6, + "maxDataPoints": 100, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true + }, + "pluginVersion": "12.1.0-pre", + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "ca3da78e-6bcc-4a2a-a080-d338a14528ca" + }, + "refId": "A", + "scenarioId": "random_walk", + "seriesCount": 31 + } + ], + "title": "Sparklines (min=0, max=100)", + "transformations": [ + { + "id": "timeSeriesTable", + "options": { + "A": { + "timeField": "time" + } + } + } + ], + "type": "table" + }, + { + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "PD8C576611E62080A" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "cellOptions": { + "type": "auto" + }, + "inspect": false, + "wrapHeaderText": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 0, + "y": 32 + }, + "id": 7, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true + }, + "pluginVersion": "12.1.0-pre", "targets": [ { "csvContent": "time,x\n2023-06-12T05:49:00Z,10\n2023-06-12T07:01:00Z,20\n2023-06-12T08:13:00Z,NaN\n2023-06-12T09:25:00Z,NaN\n2023-06-12T10:37:00Z,NaN\n2023-06-12T11:49:00Z,30\n2023-06-12T13:01:00Z,20\n2023-06-12T14:13:00Z,10\n2023-06-12T15:25:00Z,Inf\n2023-06-12T16:37:00Z,null\n2023-06-12T17:49:00Z,", - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, "refId": "A", "scenarioId": "csv_content" } @@ -87,26 +585,31 @@ "transformations": [ { "id": "timeSeriesTable", - "options": {} + "options": { + "A": { + "stat": "last", + "timeField": "" + } + } } ], "type": "table" } ], + "preload": false, "refresh": "", - "schemaVersion": 38, + "schemaVersion": 41, "tags": [], "templating": { "list": [] }, "time": { - "from": "2023-06-12T00:00:00.000Z", - "to": "2023-06-12T18:00:00.000Z" + "from": "now-6h", + "to": "now" }, "timepicker": {}, - "timezone": "utc", - "title": "Table with SparklineCell", + "timezone": "", + "title": "Panel Tests - Table - Sparklines", "uid": "d6373b49-1957-4f00-9218-ee2120d3ecd9", - "version": 4, - "weekStart": "" -} \ No newline at end of file + "version": 5 +} diff --git a/devenv/dev-dashboards/panel-table/table_tests.json b/devenv/dev-dashboards/panel-table/table_tests.json index b8ca436e110..a8d0a0d5253 100644 --- a/devenv/dev-dashboards/panel-table/table_tests.json +++ b/devenv/dev-dashboards/panel-table/table_tests.json @@ -431,7 +431,11 @@ ], "refresh": false, "schemaVersion": 16, - "tags": ["gdev", "panel-tests"], + "tags": [ + "gdev", + "panel-tests", + "table" + ], "templating": { "list": [] }, @@ -445,5 +449,5 @@ "timezone": "browser", "title": "Panel Tests - Table", "uid": "pttable", - "version": 2 + "version": 3 } diff --git a/devenv/dev-dashboards/panel-table/table_tests_new.json b/devenv/dev-dashboards/panel-table/table_tests_new.json index 69b50b9b23e..e928e9fc578 100644 --- a/devenv/dev-dashboards/panel-table/table_tests_new.json +++ b/devenv/dev-dashboards/panel-table/table_tests_new.json @@ -995,7 +995,8 @@ "schemaVersion": 41, "tags": [ "gdev", - "panel-tests" + "panel-tests", + "table" ], "templating": { "list": [] diff --git a/packages/grafana-data/src/field/fieldOverrides.test.ts b/packages/grafana-data/src/field/fieldOverrides.test.ts index 0fd6e2d834e..4d62d1794df 100644 --- a/packages/grafana-data/src/field/fieldOverrides.test.ts +++ b/packages/grafana-data/src/field/fieldOverrides.test.ts @@ -1,4 +1,3 @@ -import { ArrayDataFrame } from '../dataframe/ArrayDataFrame'; import { createDataFrame, toDataFrame } from '../dataframe/processDataFrame'; import { relativeToTimeRange } from '../datetime/rangeutil'; import { createTheme } from '../themes/createTheme'; @@ -83,7 +82,7 @@ locationUtil.initialize({ describe('Global MinMax', () => { it('find global min max', () => { - const f0 = new ArrayDataFrame<{ title: string; value: number; value2: number | null }>([ + const f0 = toDataFrame([ { title: 'AAA', value: 100, value2: 1234 }, { title: 'BBB', value: -20, value2: null }, { title: 'CCC', value: 200, value2: 1000 }, @@ -95,7 +94,7 @@ describe('Global MinMax', () => { }); it('find global min max when all values are zero', () => { - const f0 = new ArrayDataFrame<{ title: string; value: number; value2: number | null }>([ + const f0 = toDataFrame([ { title: 'AAA', value: 0, value2: 0 }, { title: 'CCC', value: 0, value2: 0 }, ]); @@ -144,7 +143,7 @@ describe('Global MinMax', () => { }); describe('applyFieldOverrides', () => { - const f0 = new ArrayDataFrame<{ title: string; value: number; value2: number | null }>([ + const f0 = toDataFrame([ { title: 'AAA', value: 100, value2: 1234 }, { title: 'BBB', value: -20, value2: null }, { title: 'CCC', value: 200, value2: 1000 }, @@ -245,6 +244,48 @@ describe('applyFieldOverrides', () => { }); }); + describe('given a frame field', () => { + const f0Internal = createDataFrame({ + name: 'frame', + fields: [ + { name: 'time', type: FieldType.time, values: [1752170223000, 1752170224000] }, + { name: 'value', type: FieldType.number, values: [10, 20] }, + ], + }); + const f0 = createDataFrame({ + name: 'A', + fields: [ + { + name: 'message', + type: FieldType.string, + values: ['foo'], + }, + { + name: 'frame', + type: FieldType.frame, + values: [f0Internal], + }, + ], + }); + + it('will apply field overrides to the fields within the frame', () => { + const withOverrides = applyFieldOverrides({ + data: [f0], + fieldConfig: { + defaults: { + max: 30, + }, + overrides: [], + }, + replaceVariables: (value) => value, + theme: createTheme(), + fieldConfigRegistry: customFieldRegistry, + }); + + expect(withOverrides[0].fields[1].values[0].fields[1].state.range.max).toBe(30); + }); + }); + it('will merge FieldConfig with default values', () => { const field: FieldConfig = { min: 0, diff --git a/packages/grafana-data/src/field/fieldOverrides.ts b/packages/grafana-data/src/field/fieldOverrides.ts index 6b29551e1c0..cbdf9ba49e8 100644 --- a/packages/grafana-data/src/field/fieldOverrides.ts +++ b/packages/grafana-data/src/field/fieldOverrides.ts @@ -199,6 +199,8 @@ export function applyFieldOverrides(options: ApplyFieldOverrideOptions): DataFra if (field.type === FieldType.nestedFrames) { for (const nestedFrames of field.values) { for (let nfIndex = 0; nfIndex < nestedFrames.length; nfIndex++) { + // TODO: should we apply fieldOverrides to nested frames? + for (const valueField of nestedFrames[nfIndex].fields) { // Get display processor for nested fields valueField.display = getDisplayProcessor({ @@ -232,6 +234,13 @@ export function applyFieldOverrides(options: ApplyFieldOverrideOptions): DataFra } } } + + if (field.type === FieldType.frame) { + field.values = applyFieldOverrides({ + ...options, + data: field.values, + }); + } } return newFrame;