From 4a3b8a567f38e11c3737b37ffd8e16a0f534e87d Mon Sep 17 00:00:00 2001 From: Haris Rozajac <58232930+harisrozajac@users.noreply.github.com> Date: Wed, 13 Aug 2025 15:01:50 -0600 Subject: [PATCH 1/3] Dashboard Migrations: v18 - gauge options (#109496) * migrate to v19 * migrate to v18 * Update v19.go --- .../pkg/migration/schemaversion/migrations.go | 3 +- .../pkg/migration/schemaversion/v18.go | 117 ++++++++++ .../pkg/migration/schemaversion/v18_test.go | 219 ++++++++++++++++++ .../testdata/input/v18.gauge_options.json | 66 ++++++ .../testdata/output/v18.gauge_options.json | 162 +++++++++++++ 5 files changed, 566 insertions(+), 1 deletion(-) create mode 100644 apps/dashboard/pkg/migration/schemaversion/v18.go create mode 100644 apps/dashboard/pkg/migration/schemaversion/v18_test.go create mode 100644 apps/dashboard/pkg/migration/testdata/input/v18.gauge_options.json create mode 100644 apps/dashboard/pkg/migration/testdata/output/v18.gauge_options.json diff --git a/apps/dashboard/pkg/migration/schemaversion/migrations.go b/apps/dashboard/pkg/migration/schemaversion/migrations.go index aa2039aebd2..6b9395b2965 100644 --- a/apps/dashboard/pkg/migration/schemaversion/migrations.go +++ b/apps/dashboard/pkg/migration/schemaversion/migrations.go @@ -5,7 +5,7 @@ import ( ) const ( - MIN_VERSION = 18 + MIN_VERSION = 17 LATEST_VERSION = 41 ) @@ -38,6 +38,7 @@ type PanelPluginInfoProvider interface { func GetMigrations(dsInfoProvider DataSourceInfoProvider, panelProvider PanelPluginInfoProvider) map[int]SchemaVersionMigrationFunc { return map[int]SchemaVersionMigrationFunc{ + 18: V18, 19: V19, 20: V20, 21: V21, diff --git a/apps/dashboard/pkg/migration/schemaversion/v18.go b/apps/dashboard/pkg/migration/schemaversion/v18.go new file mode 100644 index 00000000000..275c1123fb5 --- /dev/null +++ b/apps/dashboard/pkg/migration/schemaversion/v18.go @@ -0,0 +1,117 @@ +package schemaversion + +// V18 migrates gauge panel options from the legacy `options-gauge` format to the new `options` format. +// This migration restructures gauge panel configuration to use the modern options structure with valueOptions. +// +// Example before migration: +// +// "panels": [ +// { +// "type": "gauge", +// "options-gauge": { +// "unit": "ms", +// "stat": "last", +// "decimals": 2, +// "prefix": "Value: ", +// "suffix": " ms", +// "thresholds": [ +// { "color": "green", "value": 0 }, +// { "color": "red", "value": 100 } +// ] +// } +// } +// ] +// +// Example after migration: +// +// "panels": [ +// { +// "type": "gauge", +// "options": { +// "valueOptions": { +// "unit": "ms", +// "stat": "last", +// "decimals": 2, +// "prefix": "Value: ", +// "suffix": " ms" +// }, +// "thresholds": [ +// { "color": "red", "value": 100 }, +// { "color": "green", "value": 0 } +// ] +// } +// } +// ] +func V18(dashboard map[string]interface{}) error { + dashboard["schemaVersion"] = 18 + + panels, ok := dashboard["panels"].([]interface{}) + if !ok { + return nil + } + + for _, p := range panels { + panel, ok := p.(map[string]interface{}) + if !ok { + continue + } + + migrateGaugePanelOptions(panel) + } + + return nil +} + +func migrateGaugePanelOptions(panel map[string]interface{}) { + optionsGauge, hasOptionsGauge := panel["options-gauge"].(map[string]interface{}) + if !hasOptionsGauge { + return + } + + options := map[string]interface{}{} + + valueOptions := map[string]interface{}{} + if unit, ok := optionsGauge["unit"]; ok { + valueOptions["unit"] = unit + } + if stat, ok := optionsGauge["stat"]; ok { + valueOptions["stat"] = stat + } + if decimals, ok := optionsGauge["decimals"]; ok { + valueOptions["decimals"] = decimals + } + if prefix, ok := optionsGauge["prefix"]; ok { + valueOptions["prefix"] = prefix + } + if suffix, ok := optionsGauge["suffix"]; ok { + valueOptions["suffix"] = suffix + } + + options["valueOptions"] = valueOptions + + if thresholds, ok := optionsGauge["thresholds"].([]interface{}); ok && len(thresholds) > 0 { + reversedThresholds := make([]interface{}, len(thresholds)) + for i, threshold := range thresholds { + reversedThresholds[len(thresholds)-1-i] = threshold + } + options["thresholds"] = reversedThresholds + } + + // Copy any other properties from options-gauge to options + for key, value := range optionsGauge { + // Skip properties that were moved to valueOptions or are being deleted + if key == "options" || key == "unit" || key == "stat" || key == "decimals" || key == "prefix" || key == "suffix" || key == "thresholds" { + continue + } + options[key] = value + } + + panel["options"] = options + delete(panel, "options-gauge") + + // Clean up options.options property if it exists + // This options prop was due to a bug + if panelOptions, ok := panel["options"].(map[string]interface{}); ok { + delete(panelOptions, "options") + } +} diff --git a/apps/dashboard/pkg/migration/schemaversion/v18_test.go b/apps/dashboard/pkg/migration/schemaversion/v18_test.go new file mode 100644 index 00000000000..94fc93f30d5 --- /dev/null +++ b/apps/dashboard/pkg/migration/schemaversion/v18_test.go @@ -0,0 +1,219 @@ +package schemaversion_test + +import ( + "testing" + + "github.com/grafana/grafana/apps/dashboard/pkg/migration/schemaversion" +) + +func TestV18(t *testing.T) { + tests := []migrationTestCase{ + { + name: "gauge panel with legacy options-gauge gets migrated to new options format", + input: map[string]interface{}{ + "title": "V18 Gauge Options Migration Test Dashboard", + "schemaVersion": 17, + "panels": []interface{}{ + map[string]interface{}{ + "id": 1, + "type": "gauge", + "title": "Gauge Panel", + "options-gauge": map[string]interface{}{ + "unit": "ms", + "stat": "last", + "decimals": 2, + "prefix": "Value: ", + "suffix": " ms", + "thresholds": []interface{}{ + map[string]interface{}{"color": "green", "value": 0}, + map[string]interface{}{"color": "red", "value": 100}, + }, + }, + }, + }, + }, + expected: map[string]interface{}{ + "title": "V18 Gauge Options Migration Test Dashboard", + "schemaVersion": 18, + "panels": []interface{}{ + map[string]interface{}{ + "id": 1, + "type": "gauge", + "title": "Gauge Panel", + "options": map[string]interface{}{ + "valueOptions": map[string]interface{}{ + "unit": "ms", + "stat": "last", + "decimals": 2, + "prefix": "Value: ", + "suffix": " ms", + }, + "thresholds": []interface{}{ + map[string]interface{}{"color": "red", "value": 100}, + map[string]interface{}{"color": "green", "value": 0}, + }, + }, + }, + }, + }, + }, + { + name: "gauge panel with only some gauge options gets migrated correctly", + input: map[string]interface{}{ + "title": "V18 Partial Gauge Options Migration Test Dashboard", + "schemaVersion": 17, + "panels": []interface{}{ + map[string]interface{}{ + "id": 1, + "type": "gauge", + "title": "Partial Gauge Panel", + "options-gauge": map[string]interface{}{ + "unit": "percent", + "decimals": 1, + }, + }, + }, + }, + expected: map[string]interface{}{ + "title": "V18 Partial Gauge Options Migration Test Dashboard", + "schemaVersion": 18, + "panels": []interface{}{ + map[string]interface{}{ + "id": 1, + "type": "gauge", + "title": "Partial Gauge Panel", + "options": map[string]interface{}{ + "valueOptions": map[string]interface{}{ + "unit": "percent", + "decimals": 1, + }, + }, + }, + }, + }, + }, + { + name: "gauge panel with buggy options property gets cleaned up", + input: map[string]interface{}{ + "title": "V18 Buggy Options Cleanup Test Dashboard", + "schemaVersion": 17, + "panels": []interface{}{ + map[string]interface{}{ + "id": 1, + "type": "gauge", + "title": "Buggy Gauge Panel", + "options-gauge": map[string]interface{}{ + "unit": "bytes", + "options": "this should be deleted", + "stat": "avg", + "decimals": 0, + }, + }, + }, + }, + expected: map[string]interface{}{ + "title": "V18 Buggy Options Cleanup Test Dashboard", + "schemaVersion": 18, + "panels": []interface{}{ + map[string]interface{}{ + "id": 1, + "type": "gauge", + "title": "Buggy Gauge Panel", + "options": map[string]interface{}{ + "valueOptions": map[string]interface{}{ + "unit": "bytes", + "stat": "avg", + "decimals": 0, + }, + }, + }, + }, + }, + }, + { + name: "gauge panel with additional custom properties gets migrated correctly", + input: map[string]interface{}{ + "title": "V18 Custom Properties Migration Test Dashboard", + "schemaVersion": 17, + "panels": []interface{}{ + map[string]interface{}{ + "id": 1, + "type": "gauge", + "title": "Custom Gauge Panel", + "options-gauge": map[string]interface{}{ + "unit": "short", + "customProperty": "customValue", + "anotherProp": 42, + }, + }, + }, + }, + expected: map[string]interface{}{ + "title": "V18 Custom Properties Migration Test Dashboard", + "schemaVersion": 18, + "panels": []interface{}{ + map[string]interface{}{ + "id": 1, + "type": "gauge", + "title": "Custom Gauge Panel", + "options": map[string]interface{}{ + "valueOptions": map[string]interface{}{ + "unit": "short", + }, + "customProperty": "customValue", + "anotherProp": 42, + }, + }, + }, + }, + }, + { + name: "non-gauge panel remains unchanged", + input: map[string]interface{}{ + "title": "V18 Non-Gauge Panel Test Dashboard", + "schemaVersion": 17, + "panels": []interface{}{ + map[string]interface{}{ + "id": 1, + "type": "graph", + "title": "Graph Panel", + "options": map[string]interface{}{ + "legend": map[string]interface{}{ + "show": true, + }, + }, + }, + }, + }, + expected: map[string]interface{}{ + "title": "V18 Non-Gauge Panel Test Dashboard", + "schemaVersion": 18, + "panels": []interface{}{ + map[string]interface{}{ + "id": 1, + "type": "graph", + "title": "Graph Panel", + "options": map[string]interface{}{ + "legend": map[string]interface{}{ + "show": true, + }, + }, + }, + }, + }, + }, + { + name: "dashboard with no panels remains unchanged", + input: map[string]interface{}{ + "title": "V18 No Panels Test Dashboard", + "schemaVersion": 17, + }, + expected: map[string]interface{}{ + "title": "V18 No Panels Test Dashboard", + "schemaVersion": 18, + }, + }, + } + + runMigrationTests(t, tests, schemaversion.V18) +} diff --git a/apps/dashboard/pkg/migration/testdata/input/v18.gauge_options.json b/apps/dashboard/pkg/migration/testdata/input/v18.gauge_options.json new file mode 100644 index 00000000000..1e8566dd793 --- /dev/null +++ b/apps/dashboard/pkg/migration/testdata/input/v18.gauge_options.json @@ -0,0 +1,66 @@ +{ + "title": "V18 Gauge Options Migration Test Dashboard", + "schemaVersion": 17, + "panels": [ + { + "id": 1, + "type": "gauge", + "title": "Complete Gauge Panel", + "options-gauge": { + "unit": "ms", + "stat": "last", + "decimals": 2, + "prefix": "Value: ", + "suffix": " ms", + "thresholds": [ + {"color": "green", "value": 0}, + {"color": "yellow", "value": 50}, + {"color": "red", "value": 100} + ] + } + }, + { + "id": 2, + "type": "gauge", + "title": "Partial Gauge Panel", + "options-gauge": { + "unit": "percent", + "decimals": 1 + } + }, + { + "id": 3, + "type": "gauge", + "title": "Buggy Gauge Panel", + "options-gauge": { + "unit": "bytes", + "options": "this should be deleted", + "stat": "avg", + "decimals": 0 + } + }, + { + "id": 4, + "type": "gauge", + "title": "Custom Properties Gauge Panel", + "options-gauge": { + "unit": "short", + "customProperty": "customValue", + "anotherProp": 42, + "thresholds": [ + {"color": "blue", "value": 10} + ] + } + }, + { + "id": 5, + "type": "graph", + "title": "Non-Gauge Panel", + "options": { + "legend": { + "show": true + } + } + } + ] +} diff --git a/apps/dashboard/pkg/migration/testdata/output/v18.gauge_options.json b/apps/dashboard/pkg/migration/testdata/output/v18.gauge_options.json new file mode 100644 index 00000000000..f6dc7161c1b --- /dev/null +++ b/apps/dashboard/pkg/migration/testdata/output/v18.gauge_options.json @@ -0,0 +1,162 @@ +{ + "panels": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 1, + "options": { + "thresholds": [ + { + "color": "red", + "value": 100 + }, + { + "color": "yellow", + "value": 50 + }, + { + "color": "green", + "value": 0 + } + ], + "valueOptions": { + "decimals": 2, + "prefix": "Value: ", + "stat": "last", + "suffix": " ms", + "unit": "ms" + } + }, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Complete Gauge Panel", + "type": "gauge" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 2, + "options": { + "valueOptions": { + "decimals": 1, + "unit": "percent" + } + }, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Partial Gauge Panel", + "type": "gauge" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 3, + "options": { + "valueOptions": { + "decimals": 0, + "stat": "avg", + "unit": "bytes" + } + }, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Buggy Gauge Panel", + "type": "gauge" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 4, + "options": { + "anotherProp": 42, + "customProperty": "customValue", + "thresholds": [ + { + "color": "blue", + "value": 10 + } + ], + "valueOptions": { + "unit": "short" + } + }, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Custom Properties Gauge Panel", + "type": "gauge" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 5, + "options": { + "legend": { + "show": true, + "showLegend": true + } + }, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Non-Gauge Panel", + "type": "graph" + } + ], + "refresh": "", + "schemaVersion": 41, + "title": "V18 Gauge Options Migration Test Dashboard" +} \ No newline at end of file From f3394c3f339ba0c7fe381b4f13dc6abe146359fc Mon Sep 17 00:00:00 2001 From: Isabel Matwawana <76437239+imatwawana@users.noreply.github.com> Date: Wed, 13 Aug 2025 17:07:33 -0400 Subject: [PATCH 2/3] Docs: Add text wrap for data links and pill cells (#109621) --- .../visualizations/table/index.md | 20 +++++++++++++++++-- .../shared/visualizations/cell-options.md | 2 +- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/docs/sources/panels-visualizations/visualizations/table/index.md b/docs/sources/panels-visualizations/visualizations/table/index.md index 5c1035752b3..ce09350f660 100644 --- a/docs/sources/panels-visualizations/visualizations/table/index.md +++ b/docs/sources/panels-visualizations/visualizations/table/index.md @@ -238,7 +238,7 @@ If you want to apply a cell type to only some fields instead of all fields, you | [Auto](#auto) | A basic text and number cell. | | [Colored text](#colored-text) | If thresholds, value mappings, or color schemes are set, then the cell text is displayed in the appropriate color. | | [Colored background](#colored-background) | If thresholds, value mappings, or color schemes are set, then the cell background is displayed in the appropriate color. | -| Data links | If you've configured data links, when the cell type is **Auto**, the cell text becomes clickable. If you change the cell type to **Data links**, the cell text reflects the titles of the configured data links. To control the application of data link text more granularly, use a **Cell option > Cell type > Data links** field override. | +| [Data links](#data-links) | The cell text reflects the titles of the configured data links.| | [Gauge](#gauge) | Values are displayed as a horizontal bar gauge. You can set the [Gauge display mode](#gauge-display-mode) and the [Value display](#value-display) options. | | [Sparkline](#sparkline) | Shows values rendered as a sparkline. | | [JSON View](#json-view) | Shows values formatted as code. | @@ -283,12 +283,23 @@ The colored background cell type has the following options: | ------ | ----------- | | Background display mode | Choose between **Basic** and **Gradient**. | | Apply to entire row | Toggle the switch on to apply the background color that's configured for the cell to the whole row. | -| Wrap text |

Toggle the **Wrap text** switch to wrap text in the cell that contains the longest content in your table. To wrap the text _in a specific column only_, use a **Fields with name** [field override](ref:field-override), select the **Cell options > Cell type** override property, and toggle on the **Wrap text** switch.

Text wrapping is in [public preview](https://grafana.com/docs/release-life-cycle/#public-preview), however, it’s available to use by default.

| +| Wrap text | Toggle the **Wrap text** switch to wrap text in the cell that contains the longest content in your table. To wrap the text _in a specific column only_, use a **Fields with name** [field override](ref:field-override), select the **Cell options > Cell type** override property, and toggle on the **Wrap text** switch. | | Cell value inspect |

Enables value inspection from table cells. When the switch is toggled on, clicking the inspect icon in a cell opens the **Inspect value** drawer which contains two tabs: **Plain text** and **Code editor**.

Grafana attempts to automatically detect the type of data in the cell and opens the drawer with the associated tab showing. However, you can switch back and forth between tabs.

| +#### Data links + +If you've configured data links, when the cell type is **Auto**, the cell text becomes clickable. +If you change the cell type to **Data links**, the cell text reflects the titles of the configured data links. To control the application of data link text more granularly, use a **Cell option > Cell type > Data links** field override. + +Data links cells also support text wrapping. +Toggle the **Wrap text** switch to wrap text in the cell that contains the longest content in your table. +To wrap the text _in a specific column only_, use a **Fields with name** [field override](ref:field-override), select the **Cell options > Cell type** override property, and toggle on the **Wrap text** switch. + + + #### Gauge With this cell type, cells can be displayed as a graphical gauge, with several different presentation types controlled by the [gauge display mode](#gauge-display-mode) and the [value display](#value-display). @@ -370,7 +381,12 @@ The **Pill** cell type displays each item in a comma-separated string in a color The colors applied to each piece of text are maintained throughout the table. For example, if the word "test" is first displayed in a red pill, it will always be displayed in a red pill. + Pill cells also support text wrapping. +Toggle the **Wrap text** switch to wrap text in the cell that contains the longest content in your table. +To wrap the text _in a specific column only_, use a **Fields with name** [field override](ref:field-override), select the **Cell options > Cell type** override property, and toggle on the **Wrap text** switch. + + The following data formats are supported for the pill cell type: diff --git a/docs/sources/shared/visualizations/cell-options.md b/docs/sources/shared/visualizations/cell-options.md index 03c0e4a76dc..f2382fbee84 100644 --- a/docs/sources/shared/visualizations/cell-options.md +++ b/docs/sources/shared/visualizations/cell-options.md @@ -5,6 +5,6 @@ title: Cell options | Option | Description | | ------ | ----------- | -| Wrap text |

Toggle the **Wrap text** switch to wrap text in the cell that contains the longest content in your table. To wrap the text _in a specific column only_, use a **Fields with name** [field override](https://grafana.com/docs/grafana//panels-visualizations/configure-overrides/), select the **Cell options > Cell type** override property, and toggle on the **Wrap text** switch.

Text wrapping is in [public preview](https://grafana.com/docs/release-life-cycle/#public-preview), however, it’s available to use by default.

| +| Wrap text | Toggle the **Wrap text** switch to wrap text in the cell that contains the longest content in your table. To wrap the text _in a specific column only_, use a **Fields with name** [field override](https://grafana.com/docs/grafana//panels-visualizations/configure-overrides/), select the **Cell options > Cell type** override property, and toggle on the **Wrap text** switch. | | Cell value inspect |

Enables value inspection from table cells. When the switch is toggled on, clicking the inspect icon in a cell opens the **Inspect value** drawer which contains two tabs: **Plain text** and **Code editor**.

Grafana attempts to automatically detect the type of data in the cell and opens the drawer with the associated tab showing. However, you can switch back and forth between tabs.

| From c5d30f28922353bb22f138700920ddfd2f2f1a0f Mon Sep 17 00:00:00 2001 From: Isabel Matwawana <76437239+imatwawana@users.noreply.github.com> Date: Wed, 13 Aug 2025 17:45:12 -0400 Subject: [PATCH 3/3] Docs: Add wrap header text & freeze columns (#109623) --- .../visualizations/table/index.md | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/docs/sources/panels-visualizations/visualizations/table/index.md b/docs/sources/panels-visualizations/visualizations/table/index.md index ce09350f660..32191d113b2 100644 --- a/docs/sources/panels-visualizations/visualizations/table/index.md +++ b/docs/sources/panels-visualizations/visualizations/table/index.md @@ -197,15 +197,19 @@ This option is only available when you're editing the panel. ### Table options -| Option | Description | -| -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Show table header | Show or hide column names imported from your data source. | -| Cell height | Set the height of the cell. Choose from **Small**, **Medium**, or **Large**. | -| Enable pagination | Toggle the switch to control how many table rows are visible at once. When switched on, the page size automatically adjusts to the height of the table. This option doesn't affect queries. | + +| Option | Description | +| -------------------- | --------------------------------------------------------- | +| Show table header | Show or hide column names imported from your data source. | +| Frozen columns | Freeze columns starting from the left side of the table. Enter a value to set how many columns are frozen. | +| Cell height | Set the height of the cell. Choose from **Small**, **Medium**, or **Large**. | +| Enable pagination | Toggle the switch to control how many table rows are visible at once. When switched on, the page size automatically adjusts to the height of the table. This option doesn't affect queries. | | Minimum column width | Define the lower limit of the column width, in pixels. By default, the minimum width of the table column is 150 pixels. For small-screen devices, such as mobile phones or tablets, reduce the value to `50` to allow table-based panels to render correctly in dashboards. | -| Column width | Define a column width, in pixels, rather than allowing the width to be set automatically. By default, Grafana calculates the column width based on the table size and the minimum column width. | -| Column alignment | Set how Grafana should align cell contents. Choose from: **Auto** (default), **Left**, **Center**, or **Right**. | -| Column filter | Temporarily change how column data is displayed. For example, show or hide specific values. For more information, refer to [Column filtering](#column-filtering). | +| Column width | Define a column width, in pixels, rather than allowing the width to be set automatically. By default, Grafana calculates the column width based on the table size and the minimum column width. | +| Column alignment | Set how Grafana should align cell contents. Choose from: **Auto** (default), **Left**, **Center**, or **Right**. | +| Column filter | Temporarily change how column data is displayed. For example, show or hide specific values. For more information, refer to [Column filtering](#column-filtering). | +| Wrap header text | Enables text wrapping for column headers. | + ### Table footer options