From 2b2b19478a45bfaaea748fa04ad99121216072ed Mon Sep 17 00:00:00 2001 From: Ivan Ortega Alba Date: Thu, 13 Feb 2025 08:19:51 +0100 Subject: [PATCH 1/4] Dashboard V0->V1 Migration: Schema migration v41 (#100554) --- .../migration/schemaversion/migrations.go | 3 +- .../dashboard/migration/schemaversion/v41.go | 11 ++ .../migration/schemaversion/v41_test.go | 38 +++++ .../input/36.legend_normalization.json | 4 +- .../37.timeseries_table_display_mode.json | 4 +- .../input/38.transform_timeseries_table.json | 4 +- .../testdata/input/39.refresh_true.json | 4 +- .../input/40.time_picker_time_options.json | 136 ++++++++++++++++++ .../output/36.legend_normalization.37.json | 14 +- .../output/36.legend_normalization.38.json | 14 +- .../output/36.legend_normalization.39.json | 14 +- .../output/36.legend_normalization.40.json | 14 +- .../output/36.legend_normalization.41.json | 132 +++++++++++++++++ .../37.timeseries_table_display_mode.38.json | 14 +- .../37.timeseries_table_display_mode.39.json | 14 +- .../37.timeseries_table_display_mode.40.json | 14 +- ... 37.timeseries_table_display_mode.41.json} | 33 +++-- .../38.transform_timeseries_table.39.json | 14 +- .../38.transform_timeseries_table.40.json | 14 +- ... => 38.transform_timeseries_table.41.json} | 12 +- .../testdata/output/39.refresh_true.40.json | 14 +- ...h_true.39.json => 39.refresh_true.41.json} | 4 +- .../40.time_picker_time_options.41.json | 134 +++++++++++++++++ 23 files changed, 629 insertions(+), 30 deletions(-) create mode 100644 pkg/apis/dashboard/migration/schemaversion/v41.go create mode 100644 pkg/apis/dashboard/migration/schemaversion/v41_test.go create mode 100644 pkg/apis/dashboard/migration/testdata/input/40.time_picker_time_options.json create mode 100644 pkg/apis/dashboard/migration/testdata/output/36.legend_normalization.41.json rename pkg/apis/dashboard/migration/testdata/output/{37.timeseries_table_display_mode.37.json => 37.timeseries_table_display_mode.41.json} (91%) rename pkg/apis/dashboard/migration/testdata/output/{38.transform_timeseries_table.38.json => 38.transform_timeseries_table.41.json} (95%) rename pkg/apis/dashboard/migration/testdata/output/{39.refresh_true.39.json => 39.refresh_true.41.json} (98%) create mode 100644 pkg/apis/dashboard/migration/testdata/output/40.time_picker_time_options.41.json diff --git a/pkg/apis/dashboard/migration/schemaversion/migrations.go b/pkg/apis/dashboard/migration/schemaversion/migrations.go index 6d3955c329f..ef46439a591 100644 --- a/pkg/apis/dashboard/migration/schemaversion/migrations.go +++ b/pkg/apis/dashboard/migration/schemaversion/migrations.go @@ -6,7 +6,7 @@ type SchemaVersionMigrationFunc func(map[string]interface{}) error const ( MINIUM_VERSION = 36 - LATEST_VERSION = 40 + LATEST_VERSION = 41 ) var Migrations = map[int]SchemaVersionMigrationFunc{ @@ -14,6 +14,7 @@ var Migrations = map[int]SchemaVersionMigrationFunc{ 38: V38, 39: V39, 40: V40, + 41: V41, } func GetSchemaVersion(dash map[string]interface{}) int { diff --git a/pkg/apis/dashboard/migration/schemaversion/v41.go b/pkg/apis/dashboard/migration/schemaversion/v41.go new file mode 100644 index 00000000000..1faafea8285 --- /dev/null +++ b/pkg/apis/dashboard/migration/schemaversion/v41.go @@ -0,0 +1,11 @@ +package schemaversion + +func V41(dash map[string]interface{}) error { + dash["schemaVersion"] = int(41) + if timepicker, ok := dash["timepicker"].(map[string]interface{}); ok { + // time_options is a legacy property that was not used since grafana version 5 + // therefore deprecating this property from the schema + delete(timepicker, "time_options") + } + return nil +} diff --git a/pkg/apis/dashboard/migration/schemaversion/v41_test.go b/pkg/apis/dashboard/migration/schemaversion/v41_test.go new file mode 100644 index 00000000000..d0f11d227f3 --- /dev/null +++ b/pkg/apis/dashboard/migration/schemaversion/v41_test.go @@ -0,0 +1,38 @@ +package schemaversion_test + +import ( + "testing" + + "github.com/grafana/grafana/pkg/apis/dashboard/migration/schemaversion" +) + +func TestV41(t *testing.T) { + tests := []migrationTestCase{ + { + name: "time_options is removed", + input: map[string]interface{}{ + "title": "Test Dashboard", + "timepicker": map[string]interface{}{ + "time_options": []string{"1m", "5m", "15m", "1h", "6h", "12h", "24h"}, + }, + }, + expected: map[string]interface{}{ + "title": "Test Dashboard", + "schemaVersion": 41, + "timepicker": map[string]interface{}{}, + }, + }, + { + name: "timepicker is not set", + input: map[string]interface{}{ + "title": "Test Dashboard", + }, + expected: map[string]interface{}{ + "title": "Test Dashboard", + "schemaVersion": 41, + }, + }, + } + + runMigrationTests(t, tests, schemaversion.V41) +} diff --git a/pkg/apis/dashboard/migration/testdata/input/36.legend_normalization.json b/pkg/apis/dashboard/migration/testdata/input/36.legend_normalization.json index 93ddfecb078..7776ccdf0cb 100644 --- a/pkg/apis/dashboard/migration/testdata/input/36.legend_normalization.json +++ b/pkg/apis/dashboard/migration/testdata/input/36.legend_normalization.json @@ -115,7 +115,9 @@ "from": "now-6h", "to": "now" }, - "timepicker": {}, + "timepicker": { + "time_options": ["5m", "15m", "1h", "6h", "12h", "24h", "2d", "7d", "30d"] + }, "timezone": "utc", "title": "New dashboard", "version": 0, diff --git a/pkg/apis/dashboard/migration/testdata/input/37.timeseries_table_display_mode.json b/pkg/apis/dashboard/migration/testdata/input/37.timeseries_table_display_mode.json index e800450fd51..20fa6fc0371 100644 --- a/pkg/apis/dashboard/migration/testdata/input/37.timeseries_table_display_mode.json +++ b/pkg/apis/dashboard/migration/testdata/input/37.timeseries_table_display_mode.json @@ -352,7 +352,9 @@ "from": "now-6h", "to": "now" }, - "timepicker": {}, + "timepicker": { + "time_options": ["5m", "15m", "1h", "6h", "12h", "24h", "2d", "7d", "30d"] + }, "timezone": "utc", "title": "New dashboard", "version": 0, diff --git a/pkg/apis/dashboard/migration/testdata/input/38.transform_timeseries_table.json b/pkg/apis/dashboard/migration/testdata/input/38.transform_timeseries_table.json index e5f08f17c69..9afbe33c607 100644 --- a/pkg/apis/dashboard/migration/testdata/input/38.transform_timeseries_table.json +++ b/pkg/apis/dashboard/migration/testdata/input/38.transform_timeseries_table.json @@ -145,7 +145,9 @@ "from": "now-6h", "to": "now" }, - "timepicker": {}, + "timepicker": { + "time_options": ["5m", "15m", "1h", "6h", "12h", "24h", "2d", "7d", "30d"] + }, "timezone": "utc", "title": "New dashboard", "version": 0, diff --git a/pkg/apis/dashboard/migration/testdata/input/39.refresh_true.json b/pkg/apis/dashboard/migration/testdata/input/39.refresh_true.json index 4ea2be531f9..844bd81eb23 100644 --- a/pkg/apis/dashboard/migration/testdata/input/39.refresh_true.json +++ b/pkg/apis/dashboard/migration/testdata/input/39.refresh_true.json @@ -124,7 +124,9 @@ "from": "now-6h", "to": "now" }, - "timepicker": {}, + "timepicker": { + "time_options": ["5m", "15m", "1h", "6h", "12h", "24h", "2d", "7d", "30d"] + }, "timezone": "utc", "title": "New dashboard", "version": 0, diff --git a/pkg/apis/dashboard/migration/testdata/input/40.time_picker_time_options.json b/pkg/apis/dashboard/migration/testdata/input/40.time_picker_time_options.json new file mode 100644 index 00000000000..5b6071de054 --- /dev/null +++ b/pkg/apis/dashboard/migration/testdata/input/40.time_picker_time_options.json @@ -0,0 +1,136 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "panels": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 1, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.5.0-81438", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "queryType": "randomWalk", + "refId": "A" + } + ], + "title": "Panel Title", + "type": "timeseries" + } + ], + "preload": false, + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "time_options": ["5m", "15m", "1h", "6h", "12h", "24h", "2d", "7d", "30d"] + }, + "timezone": "utc", + "title": "New dashboard", + "version": 0, + "weekStart": "", + "refresh": "", + "schemaVersion": 40 + } \ No newline at end of file diff --git a/pkg/apis/dashboard/migration/testdata/output/36.legend_normalization.37.json b/pkg/apis/dashboard/migration/testdata/output/36.legend_normalization.37.json index 1e6e0484aad..27e61667230 100644 --- a/pkg/apis/dashboard/migration/testdata/output/36.legend_normalization.37.json +++ b/pkg/apis/dashboard/migration/testdata/output/36.legend_normalization.37.json @@ -124,7 +124,19 @@ "from": "now-6h", "to": "now" }, - "timepicker": {}, + "timepicker": { + "time_options": [ + "5m", + "15m", + "1h", + "6h", + "12h", + "24h", + "2d", + "7d", + "30d" + ] + }, "timezone": "utc", "title": "New dashboard", "version": 0, diff --git a/pkg/apis/dashboard/migration/testdata/output/36.legend_normalization.38.json b/pkg/apis/dashboard/migration/testdata/output/36.legend_normalization.38.json index 14c5c32071f..de4220f3b61 100644 --- a/pkg/apis/dashboard/migration/testdata/output/36.legend_normalization.38.json +++ b/pkg/apis/dashboard/migration/testdata/output/36.legend_normalization.38.json @@ -124,7 +124,19 @@ "from": "now-6h", "to": "now" }, - "timepicker": {}, + "timepicker": { + "time_options": [ + "5m", + "15m", + "1h", + "6h", + "12h", + "24h", + "2d", + "7d", + "30d" + ] + }, "timezone": "utc", "title": "New dashboard", "version": 0, diff --git a/pkg/apis/dashboard/migration/testdata/output/36.legend_normalization.39.json b/pkg/apis/dashboard/migration/testdata/output/36.legend_normalization.39.json index 5e3239e89fa..472dbdd70a7 100644 --- a/pkg/apis/dashboard/migration/testdata/output/36.legend_normalization.39.json +++ b/pkg/apis/dashboard/migration/testdata/output/36.legend_normalization.39.json @@ -124,7 +124,19 @@ "from": "now-6h", "to": "now" }, - "timepicker": {}, + "timepicker": { + "time_options": [ + "5m", + "15m", + "1h", + "6h", + "12h", + "24h", + "2d", + "7d", + "30d" + ] + }, "timezone": "utc", "title": "New dashboard", "version": 0, diff --git a/pkg/apis/dashboard/migration/testdata/output/36.legend_normalization.40.json b/pkg/apis/dashboard/migration/testdata/output/36.legend_normalization.40.json index 91d625264b0..e097b1e8402 100644 --- a/pkg/apis/dashboard/migration/testdata/output/36.legend_normalization.40.json +++ b/pkg/apis/dashboard/migration/testdata/output/36.legend_normalization.40.json @@ -124,7 +124,19 @@ "from": "now-6h", "to": "now" }, - "timepicker": {}, + "timepicker": { + "time_options": [ + "5m", + "15m", + "1h", + "6h", + "12h", + "24h", + "2d", + "7d", + "30d" + ] + }, "timezone": "utc", "title": "New dashboard", "version": 0, diff --git a/pkg/apis/dashboard/migration/testdata/output/36.legend_normalization.41.json b/pkg/apis/dashboard/migration/testdata/output/36.legend_normalization.41.json new file mode 100644 index 00000000000..fda6883a462 --- /dev/null +++ b/pkg/apis/dashboard/migration/testdata/output/36.legend_normalization.41.json @@ -0,0 +1,132 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "panels": [ + { + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 1, + "options": {}, + "title": "No Legend Config", + "type": "graph" + }, + { + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 0 + }, + "id": 2, + "options": { + "legend": { + "displayMode": "list", + "showLegend": true + } + }, + "title": "Boolean Legend True" + }, + { + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 8 + }, + "id": 3, + "options": { + "legend": { + "displayMode": "list", + "showLegend": false + } + }, + "title": "Boolean Legend False" + }, + { + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 8 + }, + "id": 4, + "options": { + "legend": { + "displayMode": "list", + "showLegend": false + } + }, + "title": "Hidden DisplayMode" + }, + { + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 16 + }, + "id": 5, + "options": { + "legend": { + "displayMode": "list", + "showLegend": false + } + }, + "title": "ShowLegend False" + }, + { + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 16 + }, + "id": 6, + "options": { + "legend": { + "displayMode": "table", + "showLegend": true + } + }, + "title": "Visible Legend" + } + ], + "preload": false, + "refresh": "", + "schemaVersion": 41, + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "utc", + "title": "New dashboard", + "version": 0, + "weekStart": "" +} \ No newline at end of file diff --git a/pkg/apis/dashboard/migration/testdata/output/37.timeseries_table_display_mode.38.json b/pkg/apis/dashboard/migration/testdata/output/37.timeseries_table_display_mode.38.json index 30f64b0bc8e..5a17d602e82 100644 --- a/pkg/apis/dashboard/migration/testdata/output/37.timeseries_table_display_mode.38.json +++ b/pkg/apis/dashboard/migration/testdata/output/37.timeseries_table_display_mode.38.json @@ -367,7 +367,19 @@ "from": "now-6h", "to": "now" }, - "timepicker": {}, + "timepicker": { + "time_options": [ + "5m", + "15m", + "1h", + "6h", + "12h", + "24h", + "2d", + "7d", + "30d" + ] + }, "timezone": "utc", "title": "New dashboard", "version": 0, diff --git a/pkg/apis/dashboard/migration/testdata/output/37.timeseries_table_display_mode.39.json b/pkg/apis/dashboard/migration/testdata/output/37.timeseries_table_display_mode.39.json index 1173963dc58..dee3d62af57 100644 --- a/pkg/apis/dashboard/migration/testdata/output/37.timeseries_table_display_mode.39.json +++ b/pkg/apis/dashboard/migration/testdata/output/37.timeseries_table_display_mode.39.json @@ -367,7 +367,19 @@ "from": "now-6h", "to": "now" }, - "timepicker": {}, + "timepicker": { + "time_options": [ + "5m", + "15m", + "1h", + "6h", + "12h", + "24h", + "2d", + "7d", + "30d" + ] + }, "timezone": "utc", "title": "New dashboard", "version": 0, diff --git a/pkg/apis/dashboard/migration/testdata/output/37.timeseries_table_display_mode.40.json b/pkg/apis/dashboard/migration/testdata/output/37.timeseries_table_display_mode.40.json index 843cc6284f7..d5ddc2a55a8 100644 --- a/pkg/apis/dashboard/migration/testdata/output/37.timeseries_table_display_mode.40.json +++ b/pkg/apis/dashboard/migration/testdata/output/37.timeseries_table_display_mode.40.json @@ -367,7 +367,19 @@ "from": "now-6h", "to": "now" }, - "timepicker": {}, + "timepicker": { + "time_options": [ + "5m", + "15m", + "1h", + "6h", + "12h", + "24h", + "2d", + "7d", + "30d" + ] + }, "timezone": "utc", "title": "New dashboard", "version": 0, diff --git a/pkg/apis/dashboard/migration/testdata/output/37.timeseries_table_display_mode.37.json b/pkg/apis/dashboard/migration/testdata/output/37.timeseries_table_display_mode.41.json similarity index 91% rename from pkg/apis/dashboard/migration/testdata/output/37.timeseries_table_display_mode.37.json rename to pkg/apis/dashboard/migration/testdata/output/37.timeseries_table_display_mode.41.json index 4b7c2fa4572..2d1d083a874 100644 --- a/pkg/apis/dashboard/migration/testdata/output/37.timeseries_table_display_mode.37.json +++ b/pkg/apis/dashboard/migration/testdata/output/37.timeseries_table_display_mode.41.json @@ -31,7 +31,10 @@ "mode": "palette-classic" }, "custom": { - "displayMode": "basic" + "cellOptions": { + "mode": "basic", + "type": "gauge" + } }, "mappings": [], "thresholds": { @@ -84,7 +87,10 @@ "mode": "palette-classic" }, "custom": { - "displayMode": "gradient-gauge" + "cellOptions": { + "mode": "gradient", + "type": "gauge" + } }, "mappings": [], "thresholds": { @@ -137,7 +143,10 @@ "mode": "palette-classic" }, "custom": { - "displayMode": "lcd-gauge" + "cellOptions": { + "mode": "lcd", + "type": "gauge" + } }, "mappings": [], "thresholds": { @@ -190,7 +199,10 @@ "mode": "palette-classic" }, "custom": { - "displayMode": "color-background" + "cellOptions": { + "mode": "gradient", + "type": "color-background" + } }, "mappings": [], "thresholds": { @@ -243,7 +255,10 @@ "mode": "palette-classic" }, "custom": { - "displayMode": "color-background-solid" + "cellOptions": { + "mode": "basic", + "type": "color-background" + } }, "mappings": [], "thresholds": { @@ -296,7 +311,9 @@ "mode": "palette-classic" }, "custom": { - "displayMode": "some-other-mode" + "cellOptions": { + "type": "some-other-mode" + } }, "mappings": [], "thresholds": { @@ -340,8 +357,8 @@ } ], "preload": false, - "refresh": true, - "schemaVersion": 37, + "refresh": "", + "schemaVersion": 41, "tags": [], "templating": { "list": [] diff --git a/pkg/apis/dashboard/migration/testdata/output/38.transform_timeseries_table.39.json b/pkg/apis/dashboard/migration/testdata/output/38.transform_timeseries_table.39.json index 136a2fb9d40..19b3b5d79f8 100644 --- a/pkg/apis/dashboard/migration/testdata/output/38.transform_timeseries_table.39.json +++ b/pkg/apis/dashboard/migration/testdata/output/38.transform_timeseries_table.39.json @@ -147,7 +147,19 @@ "from": "now-6h", "to": "now" }, - "timepicker": {}, + "timepicker": { + "time_options": [ + "5m", + "15m", + "1h", + "6h", + "12h", + "24h", + "2d", + "7d", + "30d" + ] + }, "timezone": "utc", "title": "New dashboard", "version": 0, diff --git a/pkg/apis/dashboard/migration/testdata/output/38.transform_timeseries_table.40.json b/pkg/apis/dashboard/migration/testdata/output/38.transform_timeseries_table.40.json index 4217f847e8c..63b0959daa1 100644 --- a/pkg/apis/dashboard/migration/testdata/output/38.transform_timeseries_table.40.json +++ b/pkg/apis/dashboard/migration/testdata/output/38.transform_timeseries_table.40.json @@ -147,7 +147,19 @@ "from": "now-6h", "to": "now" }, - "timepicker": {}, + "timepicker": { + "time_options": [ + "5m", + "15m", + "1h", + "6h", + "12h", + "24h", + "2d", + "7d", + "30d" + ] + }, "timezone": "utc", "title": "New dashboard", "version": 0, diff --git a/pkg/apis/dashboard/migration/testdata/output/38.transform_timeseries_table.38.json b/pkg/apis/dashboard/migration/testdata/output/38.transform_timeseries_table.41.json similarity index 95% rename from pkg/apis/dashboard/migration/testdata/output/38.transform_timeseries_table.38.json rename to pkg/apis/dashboard/migration/testdata/output/38.transform_timeseries_table.41.json index 081cb14634f..be300a117c6 100644 --- a/pkg/apis/dashboard/migration/testdata/output/38.transform_timeseries_table.38.json +++ b/pkg/apis/dashboard/migration/testdata/output/38.transform_timeseries_table.41.json @@ -124,9 +124,11 @@ { "id": "timeSeriesTable", "options": { - "refIdToStat": { - "A": "mean", - "B": "max" + "A": { + "stat": "mean" + }, + "B": { + "stat": "max" } } } @@ -135,8 +137,8 @@ } ], "preload": false, - "refresh": true, - "schemaVersion": 38, + "refresh": "", + "schemaVersion": 41, "tags": [], "templating": { "list": [] diff --git a/pkg/apis/dashboard/migration/testdata/output/39.refresh_true.40.json b/pkg/apis/dashboard/migration/testdata/output/39.refresh_true.40.json index a8c67a1e80d..5c4ad99f35c 100644 --- a/pkg/apis/dashboard/migration/testdata/output/39.refresh_true.40.json +++ b/pkg/apis/dashboard/migration/testdata/output/39.refresh_true.40.json @@ -126,7 +126,19 @@ "from": "now-6h", "to": "now" }, - "timepicker": {}, + "timepicker": { + "time_options": [ + "5m", + "15m", + "1h", + "6h", + "12h", + "24h", + "2d", + "7d", + "30d" + ] + }, "timezone": "utc", "title": "New dashboard", "version": 0, diff --git a/pkg/apis/dashboard/migration/testdata/output/39.refresh_true.39.json b/pkg/apis/dashboard/migration/testdata/output/39.refresh_true.41.json similarity index 98% rename from pkg/apis/dashboard/migration/testdata/output/39.refresh_true.39.json rename to pkg/apis/dashboard/migration/testdata/output/39.refresh_true.41.json index 6c0cb4d1974..8c6dfe8ba06 100644 --- a/pkg/apis/dashboard/migration/testdata/output/39.refresh_true.39.json +++ b/pkg/apis/dashboard/migration/testdata/output/39.refresh_true.41.json @@ -116,8 +116,8 @@ } ], "preload": false, - "refresh": true, - "schemaVersion": 39, + "refresh": "", + "schemaVersion": 41, "tags": [], "templating": { "list": [] diff --git a/pkg/apis/dashboard/migration/testdata/output/40.time_picker_time_options.41.json b/pkg/apis/dashboard/migration/testdata/output/40.time_picker_time_options.41.json new file mode 100644 index 00000000000..8c6dfe8ba06 --- /dev/null +++ b/pkg/apis/dashboard/migration/testdata/output/40.time_picker_time_options.41.json @@ -0,0 +1,134 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "panels": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 1, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.5.0-81438", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "queryType": "randomWalk", + "refId": "A" + } + ], + "title": "Panel Title", + "type": "timeseries" + } + ], + "preload": false, + "refresh": "", + "schemaVersion": 41, + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "utc", + "title": "New dashboard", + "version": 0, + "weekStart": "" +} \ No newline at end of file From 2dee9ccbbcd0a01c2f0afcc89c5e0f5f03aa602a Mon Sep 17 00:00:00 2001 From: Mariell Hoversholm Date: Thu, 13 Feb 2025 08:54:58 +0100 Subject: [PATCH 2/4] APIServer: Cancel forked context after handler returns (#100504) We currently cancel the context when the adapter function is done. We should wait for the entire handler we're wrapping to finish before cancelling our context. --- pkg/apiserver/endpoints/responsewriter/responsewriter.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/apiserver/endpoints/responsewriter/responsewriter.go b/pkg/apiserver/endpoints/responsewriter/responsewriter.go index a076340d7bb..824cc05a17e 100644 --- a/pkg/apiserver/endpoints/responsewriter/responsewriter.go +++ b/pkg/apiserver/endpoints/responsewriter/responsewriter.go @@ -37,11 +37,12 @@ func WrapHandler(handler http.Handler) func(req *http.Request) (*http.Response, if err != nil { return nil, err } - defer cancel() + // The cancel happens in the goroutine we spawn, so as to not cancel it too early. req = req.WithContext(ctx) // returns a shallow copy, so we can't do it as part of the adapter. w := NewAdapter(req) go func() { + defer cancel() handler.ServeHTTP(w, req) if err := w.CloseWriter(); err != nil { klog.Errorf("error closing writer: %v", err) From df64dd076243e25808b4d6384b96d6ddb338e59a Mon Sep 17 00:00:00 2001 From: Karl Persson <23356117+kalleep@users.noreply.github.com> Date: Thu, 13 Feb 2025 09:17:16 +0100 Subject: [PATCH 3/4] LibraryElements: Propagate service identity in context when searching for dashboards (#100220) * Propagate service identity in context when searching for dashboards --- pkg/services/libraryelements/database.go | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/pkg/services/libraryelements/database.go b/pkg/services/libraryelements/database.go index 306352fce84..d133c36e650 100644 --- a/pkg/services/libraryelements/database.go +++ b/pkg/services/libraryelements/database.go @@ -250,14 +250,17 @@ func (l *LibraryElementService) deleteLibraryElement(c context.Context, signedIn return err } - // then find the dashboards that were supposed to be connected to this element - _, requester := identity.WithServiceIdentity(c, signedInUser.GetOrgID()) - dashs, err := l.dashboardsService.FindDashboards(c, &dashboards.FindPersistedDashboardsQuery{ + // then find the dashboards that were supposed to be connected to this element. + // A identity may be able to delete a library element but not read all dashboards so we fetch then as the + // service user so we can prevent deletion of those connections + serviceCtx, serviceIdent := identity.WithServiceIdentity(c, signedInUser.GetOrgID()) + dashs, err := l.dashboardsService.FindDashboards(serviceCtx, &dashboards.FindPersistedDashboardsQuery{ Type: searchstore.TypeDashboard, - OrgId: signedInUser.GetOrgID(), + OrgId: serviceIdent.GetOrgID(), DashboardIds: dashboardIDs, - SignedInUser: requester, // a user may be able to delete a library element but not read all dashboards. We still need to run this check, so we don't allow deleting elements if dashboards are connected + SignedInUser: serviceIdent, }) + if err != nil { return err } From fbf96916aa23a83346c3955c38914032300b9a29 Mon Sep 17 00:00:00 2001 From: Konrad Lalik Date: Thu, 13 Feb 2025 09:20:45 +0100 Subject: [PATCH 4/4] Alerting: Use alerting-specific error boundary for page components (#99980) Use alerting-specific error boundary for page components --- .../ErrorBoundary/ErrorBoundary.tsx | 15 ++++-- .../features/alerting/unified/AlertGroups.tsx | 15 +++--- .../alerting/unified/AlertingNotEnabled.tsx | 6 ++- .../alerting/unified/NewSilencePage.tsx | 5 +- .../unified/NotificationPoliciesPage.tsx | 17 +++--- .../alerting/unified/RedirectToRuleViewer.tsx | 5 +- .../features/alerting/unified/RuleList.tsx | 3 +- .../features/alerting/unified/RuleViewer.tsx | 5 +- .../features/alerting/unified/Settings.tsx | 5 +- .../features/alerting/unified/Templates.tsx | 41 +++++++------- .../contact-points/ContactPoints.tsx | 4 +- .../DuplicateMessageTemplate.tsx | 14 ++++- .../contact-points/EditContactPoint.tsx | 5 +- .../contact-points/EditMessageTemplate.tsx | 14 ++++- .../contact-points/NewMessageTemplate.tsx | 20 +++---- .../components/GlobalConfig.tsx | 11 ++-- .../export/ExportNewGrafanaRule.tsx | 21 ++------ .../components/export/GrafanaModifyExport.tsx | 54 ++++++++----------- .../mute-timings/EditMuteTiming.tsx | 27 +++++----- .../components/mute-timings/NewMuteTiming.tsx | 28 +++++----- .../components/receivers/NewReceiverView.tsx | 4 +- .../CentralAlertHistoryPage.tsx | 10 ++-- .../components/silences/SilencesEditor.tsx | 5 +- .../components/silences/SilencesTable.tsx | 5 +- .../features/alerting/unified/home/Home.tsx | 5 +- .../unified/rule-editor/RuleEditor.tsx | 6 ++- .../unified/rule-list/RuleList.v1.tsx | 4 +- .../unified/rule-list/RuleList.v2.tsx | 39 +++++++------- .../unified/withPageErrorBoundary.tsx | 25 +++++++++ 29 files changed, 238 insertions(+), 180 deletions(-) create mode 100644 public/app/features/alerting/unified/withPageErrorBoundary.tsx diff --git a/packages/grafana-ui/src/components/ErrorBoundary/ErrorBoundary.tsx b/packages/grafana-ui/src/components/ErrorBoundary/ErrorBoundary.tsx index 312d6e76029..8c50da47478 100644 --- a/packages/grafana-ui/src/components/ErrorBoundary/ErrorBoundary.tsx +++ b/packages/grafana-ui/src/components/ErrorBoundary/ErrorBoundary.tsx @@ -21,6 +21,8 @@ interface Props { onError?: (error: Error) => void; /** Callback error state is cleared due to recover props change */ onRecover?: () => void; + /** Default error logger - Faro by default */ + errorLogger?: (error: Error) => void; } interface State { @@ -35,7 +37,12 @@ export class ErrorBoundary extends PureComponent { }; componentDidCatch(error: Error, errorInfo: ErrorInfo) { - faro?.api?.pushError(error); + const logger = this.props.errorLogger ?? faro?.api?.pushError; + + if (logger) { + logger(error); + } + this.setState({ error, errorInfo }); if (this.props.onError) { @@ -89,6 +96,8 @@ export interface ErrorBoundaryAlertProps { /** Will re-render children after error if recover values changes */ dependencies?: unknown[]; + /** Default error logger - Faro by default */ + errorLogger?: (error: Error) => void; } export class ErrorBoundaryAlert extends PureComponent { @@ -98,10 +107,10 @@ export class ErrorBoundaryAlert extends PureComponent { }; render() { - const { title, children, style, dependencies } = this.props; + const { title, children, style, dependencies, errorLogger } = this.props; return ( - + {({ error, errorInfo }) => { if (!errorInfo) { return children; diff --git a/public/app/features/alerting/unified/AlertGroups.tsx b/public/app/features/alerting/unified/AlertGroups.tsx index 3422a093990..2d5863f7aea 100644 --- a/public/app/features/alerting/unified/AlertGroups.tsx +++ b/public/app/features/alerting/unified/AlertGroups.tsx @@ -19,6 +19,7 @@ import { NOTIFICATIONS_POLL_INTERVAL_MS } from './utils/constants'; import { GRAFANA_RULES_SOURCE_NAME } from './utils/datasource'; import { getFiltersFromUrlParams } from './utils/misc'; import { initialAsyncRequestState } from './utils/redux'; +import { withPageErrorBoundary } from './withPageErrorBoundary'; const AlertGroups = () => { const { selectedAlertmanager } = useAlertmanager(); @@ -89,10 +90,12 @@ const AlertGroups = () => { ); }; -const AlertGroupsPage = () => ( - - - -); +function AlertGroupsPage() { + return ( + + + + ); +} -export default AlertGroupsPage; +export default withPageErrorBoundary(AlertGroupsPage); diff --git a/public/app/features/alerting/unified/AlertingNotEnabled.tsx b/public/app/features/alerting/unified/AlertingNotEnabled.tsx index ca8a10aa487..9250fafed2c 100644 --- a/public/app/features/alerting/unified/AlertingNotEnabled.tsx +++ b/public/app/features/alerting/unified/AlertingNotEnabled.tsx @@ -1,7 +1,9 @@ import { NavModel } from '@grafana/data'; import { Page } from 'app/core/components/Page/Page'; -export default function FeatureTogglePage() { +import { withPageErrorBoundary } from './withPageErrorBoundary'; + +function FeatureTogglePage() { const navModel: NavModel = { node: { text: 'Alerting is not enabled', @@ -25,3 +27,5 @@ enabled = true ); } + +export default withPageErrorBoundary(FeatureTogglePage); diff --git a/public/app/features/alerting/unified/NewSilencePage.tsx b/public/app/features/alerting/unified/NewSilencePage.tsx index 859b9c3875e..2f2dc4e79cd 100644 --- a/public/app/features/alerting/unified/NewSilencePage.tsx +++ b/public/app/features/alerting/unified/NewSilencePage.tsx @@ -1,6 +1,5 @@ import { useLocation } from 'react-router-dom-v5-compat'; -import { withErrorBoundary } from '@grafana/ui'; import { defaultsFromQuery, getDefaultSilenceFormValues, @@ -12,6 +11,7 @@ import { AlertmanagerPageWrapper } from './components/AlertingPageWrapper'; import { GrafanaAlertmanagerDeliveryWarning } from './components/GrafanaAlertmanagerDeliveryWarning'; import { SilencesEditor } from './components/silences/SilencesEditor'; import { useAlertmanager } from './state/AlertmanagerContext'; +import { withPageErrorBoundary } from './withPageErrorBoundary'; const SilencesEditorComponent = () => { const location = useLocation(); @@ -48,4 +48,5 @@ function NewSilencePage() { ); } -export default withErrorBoundary(NewSilencePage, { style: 'page' }); + +export default withPageErrorBoundary(NewSilencePage); diff --git a/public/app/features/alerting/unified/NotificationPoliciesPage.tsx b/public/app/features/alerting/unified/NotificationPoliciesPage.tsx index 6b4d1b9b5e7..25d2ef8cbf8 100644 --- a/public/app/features/alerting/unified/NotificationPoliciesPage.tsx +++ b/public/app/features/alerting/unified/NotificationPoliciesPage.tsx @@ -2,7 +2,7 @@ import { css } from '@emotion/css'; import { useState } from 'react'; import { GrafanaTheme2, UrlQueryMap } from '@grafana/data'; -import { Tab, TabContent, TabsBar, useStyles2, withErrorBoundary } from '@grafana/ui'; +import { Tab, TabContent, TabsBar, useStyles2 } from '@grafana/ui'; import { useQueryParams } from 'app/core/hooks/useQueryParams'; import { useMuteTimings } from 'app/features/alerting/unified/components/mute-timings/useMuteTimings'; import { NotificationPoliciesList } from 'app/features/alerting/unified/components/notification-policies/NotificationPoliciesList'; @@ -12,6 +12,7 @@ import { AlertmanagerPageWrapper } from './components/AlertingPageWrapper'; import { GrafanaAlertmanagerDeliveryWarning } from './components/GrafanaAlertmanagerDeliveryWarning'; import { MuteTimingsTable } from './components/mute-timings/MuteTimingsTable'; import { useAlertmanager } from './state/AlertmanagerContext'; +import { withPageErrorBoundary } from './withPageErrorBoundary'; enum ActiveTab { NotificationPolicies = 'notification_policies', @@ -104,10 +105,12 @@ function getActiveTabFromUrl(queryParams: UrlQueryMap, defaultTab: ActiveTab): Q }; } -const NotificationPoliciesPage = () => ( - - - -); +function NotificationPoliciesPage() { + return ( + + + + ); +} -export default withErrorBoundary(NotificationPoliciesPage, { style: 'page' }); +export default withPageErrorBoundary(NotificationPoliciesPage); diff --git a/public/app/features/alerting/unified/RedirectToRuleViewer.tsx b/public/app/features/alerting/unified/RedirectToRuleViewer.tsx index e5375ad9133..bb032b5a9fd 100644 --- a/public/app/features/alerting/unified/RedirectToRuleViewer.tsx +++ b/public/app/features/alerting/unified/RedirectToRuleViewer.tsx @@ -5,7 +5,7 @@ import { useLocation } from 'react-use'; import { GrafanaTheme2 } from '@grafana/data'; import { config, isFetchError } from '@grafana/runtime'; -import { Alert, Card, Icon, LoadingPlaceholder, useStyles2, withErrorBoundary } from '@grafana/ui'; +import { Alert, Card, Icon, LoadingPlaceholder, useStyles2 } from '@grafana/ui'; import { AlertLabels } from './components/AlertLabels'; import { RuleViewerLayout } from './components/rule-viewer/RuleViewerLayout'; @@ -13,6 +13,7 @@ import { useCloudCombinedRulesMatching } from './hooks/useCombinedRule'; import { getRulesSourceByName } from './utils/datasource'; import { createViewLink } from './utils/misc'; import { unescapePathSeparators } from './utils/rule-id'; +import { withPageErrorBoundary } from './withPageErrorBoundary'; const pageTitle = 'Find rule'; const subUrl = config.appSubUrl; @@ -153,4 +154,4 @@ function getStyles(theme: GrafanaTheme2) { }; } -export default withErrorBoundary(RedirectToRuleViewer, { style: 'page' }); +export default withPageErrorBoundary(RedirectToRuleViewer); diff --git a/public/app/features/alerting/unified/RuleList.tsx b/public/app/features/alerting/unified/RuleList.tsx index 9c38d4bf049..f51f440bd14 100644 --- a/public/app/features/alerting/unified/RuleList.tsx +++ b/public/app/features/alerting/unified/RuleList.tsx @@ -3,6 +3,7 @@ import { Suspense, lazy } from 'react'; import { config } from '@grafana/runtime'; import RuleListV1 from './rule-list/RuleList.v1'; +import { withPageErrorBoundary } from './withPageErrorBoundary'; const RuleListV2 = lazy(() => import('./rule-list/RuleList.v2')); const RuleList = () => { @@ -11,4 +12,4 @@ const RuleList = () => { return {newView ? : }; }; -export default RuleList; +export default withPageErrorBoundary(RuleList); diff --git a/public/app/features/alerting/unified/RuleViewer.tsx b/public/app/features/alerting/unified/RuleViewer.tsx index 9584539e41b..9df84a41b18 100644 --- a/public/app/features/alerting/unified/RuleViewer.tsx +++ b/public/app/features/alerting/unified/RuleViewer.tsx @@ -3,7 +3,7 @@ import { useParams } from 'react-router-dom-v5-compat'; import { NavModelItem } from '@grafana/data'; import { isFetchError } from '@grafana/runtime'; -import { Alert, withErrorBoundary } from '@grafana/ui'; +import { Alert } from '@grafana/ui'; import { EntityNotFound } from 'app/core/components/PageNotFound/EntityNotFound'; import { AlertingPageWrapper } from './components/AlertingPageWrapper'; @@ -12,6 +12,7 @@ import DetailView, { ActiveTab, useActiveTab } from './components/rule-viewer/Ru import { useCombinedRule } from './hooks/useCombinedRule'; import { stringifyErrorLike } from './utils/misc'; import { getRuleIdFromPathname, parse as parseRuleId } from './utils/rule-id'; +import { withPageErrorBoundary } from './withPageErrorBoundary'; const RuleViewer = (): JSX.Element => { const params = useParams(); @@ -86,4 +87,4 @@ function ErrorMessage({ error }: ErrorMessageProps) { return {stringifyErrorLike(error)}; } -export default withErrorBoundary(RuleViewer, { style: 'page' }); +export default withPageErrorBoundary(RuleViewer); diff --git a/public/app/features/alerting/unified/Settings.tsx b/public/app/features/alerting/unified/Settings.tsx index 099bde572b0..7d3a91dafdf 100644 --- a/public/app/features/alerting/unified/Settings.tsx +++ b/public/app/features/alerting/unified/Settings.tsx @@ -6,8 +6,9 @@ import { useEditConfigurationDrawer } from './components/settings/ConfigurationD import { ExternalAlertmanagers } from './components/settings/ExternalAlertmanagers'; import InternalAlertmanager from './components/settings/InternalAlertmanager'; import { SettingsProvider, useSettings } from './components/settings/SettingsContext'; +import { withPageErrorBoundary } from './withPageErrorBoundary'; -export default function SettingsPage() { +function SettingsPage() { return ( @@ -47,3 +48,5 @@ function SettingsContent() { ); } + +export default withPageErrorBoundary(SettingsPage); diff --git a/public/app/features/alerting/unified/Templates.tsx b/public/app/features/alerting/unified/Templates.tsx index d32ea0541b4..f3b8b011f36 100644 --- a/public/app/features/alerting/unified/Templates.tsx +++ b/public/app/features/alerting/unified/Templates.tsx @@ -1,28 +1,29 @@ import { Route, Routes } from 'react-router-dom-v5-compat'; -import { withErrorBoundary } from '@grafana/ui'; - import { AlertmanagerPageWrapper } from './components/AlertingPageWrapper'; import DuplicateMessageTemplate from './components/contact-points/DuplicateMessageTemplate'; import EditMessageTemplate from './components/contact-points/EditMessageTemplate'; import NewMessageTemplate from './components/contact-points/NewMessageTemplate'; +import { withPageErrorBoundary } from './withPageErrorBoundary'; -const NotificationTemplates = (): JSX.Element => ( - - - } /> - } /> - } /> - - -); +function NotificationTemplates() { + return ( + + + } /> + } /> + } /> + + + ); +} -export default withErrorBoundary(NotificationTemplates, { style: 'page' }); +export default withPageErrorBoundary(NotificationTemplates); diff --git a/public/app/features/alerting/unified/components/contact-points/ContactPoints.tsx b/public/app/features/alerting/unified/components/contact-points/ContactPoints.tsx index 5ea42c97584..0d2f6cf447e 100644 --- a/public/app/features/alerting/unified/components/contact-points/ContactPoints.tsx +++ b/public/app/features/alerting/unified/components/contact-points/ContactPoints.tsx @@ -12,7 +12,6 @@ import { TabContent, TabsBar, Text, - withErrorBoundary, } from '@grafana/ui'; import { contextSrv } from 'app/core/core'; import { Trans, t } from 'app/core/internationalization'; @@ -25,6 +24,7 @@ import { usePagination } from '../../hooks/usePagination'; import { useURLSearchParams } from '../../hooks/useURLSearchParams'; import { useAlertmanager } from '../../state/AlertmanagerContext'; import { GRAFANA_RULES_SOURCE_NAME } from '../../utils/datasource'; +import { withPageErrorBoundary } from '../../withPageErrorBoundary'; import { AlertmanagerPageWrapper } from '../AlertingPageWrapper'; import { GrafanaAlertmanagerDeliveryWarning } from '../GrafanaAlertmanagerDeliveryWarning'; @@ -270,4 +270,4 @@ function ContactPointsPage() { ); } -export default withErrorBoundary(ContactPointsPage, { style: 'page' }); +export default withPageErrorBoundary(ContactPointsPage); diff --git a/public/app/features/alerting/unified/components/contact-points/DuplicateMessageTemplate.tsx b/public/app/features/alerting/unified/components/contact-points/DuplicateMessageTemplate.tsx index 3ded1052b5d..bc89effea98 100644 --- a/public/app/features/alerting/unified/components/contact-points/DuplicateMessageTemplate.tsx +++ b/public/app/features/alerting/unified/components/contact-points/DuplicateMessageTemplate.tsx @@ -8,13 +8,15 @@ import { useAlertmanager } from '../../state/AlertmanagerContext'; import { generateCopiedName } from '../../utils/duplicate'; import { stringifyErrorLike } from '../../utils/misc'; import { updateDefinesWithUniqueValue } from '../../utils/templates'; +import { withPageErrorBoundary } from '../../withPageErrorBoundary'; +import { AlertmanagerPageWrapper } from '../AlertingPageWrapper'; import { TemplateForm } from '../receivers/TemplateForm'; import { useGetNotificationTemplate, useNotificationTemplates } from './useNotificationTemplates'; const notFoundComponent = ; -const DuplicateMessageTemplate = () => { +const DuplicateMessageTemplateComponent = () => { const { selectedAlertmanager } = useAlertmanager(); const { name } = useParams<{ name: string }>(); const templateUid = name ? decodeURIComponent(name) : undefined; @@ -63,4 +65,12 @@ const DuplicateMessageTemplate = () => { ); }; -export default DuplicateMessageTemplate; +function DuplicateMessageTemplate() { + return ( + + + + ); +} + +export default withPageErrorBoundary(DuplicateMessageTemplate); diff --git a/public/app/features/alerting/unified/components/contact-points/EditContactPoint.tsx b/public/app/features/alerting/unified/components/contact-points/EditContactPoint.tsx index 96a00fc1d33..5e9deb4b3da 100644 --- a/public/app/features/alerting/unified/components/contact-points/EditContactPoint.tsx +++ b/public/app/features/alerting/unified/components/contact-points/EditContactPoint.tsx @@ -1,10 +1,11 @@ import { useParams } from 'react-router-dom-v5-compat'; -import { Alert, LoadingPlaceholder, withErrorBoundary } from '@grafana/ui'; +import { Alert, LoadingPlaceholder } from '@grafana/ui'; import { useGetContactPoint } from 'app/features/alerting/unified/components/contact-points/useContactPoints'; import { stringifyErrorLike } from 'app/features/alerting/unified/utils/misc'; import { useAlertmanager } from '../../state/AlertmanagerContext'; +import { withPageErrorBoundary } from '../../withPageErrorBoundary'; import { AlertmanagerPageWrapper } from '../AlertingPageWrapper'; import { EditReceiverView } from '../receivers/EditReceiverView'; @@ -50,4 +51,4 @@ function EditContactPointPage() { ); } -export default withErrorBoundary(EditContactPointPage, { style: 'page' }); +export default withPageErrorBoundary(EditContactPointPage); diff --git a/public/app/features/alerting/unified/components/contact-points/EditMessageTemplate.tsx b/public/app/features/alerting/unified/components/contact-points/EditMessageTemplate.tsx index 6c7df2a7fad..926e1c5404b 100644 --- a/public/app/features/alerting/unified/components/contact-points/EditMessageTemplate.tsx +++ b/public/app/features/alerting/unified/components/contact-points/EditMessageTemplate.tsx @@ -6,13 +6,15 @@ import { EntityNotFound } from 'app/core/components/PageNotFound/EntityNotFound' import { isNotFoundError } from '../../api/util'; import { useAlertmanager } from '../../state/AlertmanagerContext'; import { stringifyErrorLike } from '../../utils/misc'; +import { withPageErrorBoundary } from '../../withPageErrorBoundary'; +import { AlertmanagerPageWrapper } from '../AlertingPageWrapper'; import { TemplateForm } from '../receivers/TemplateForm'; import { useGetNotificationTemplate } from './useNotificationTemplates'; const notFoundComponent = ; -const EditMessageTemplate = () => { +const EditMessageTemplateComponent = () => { const { name } = useParams<{ name: string }>(); const templateUid = name ? decodeURIComponent(name) : undefined; @@ -47,4 +49,12 @@ const EditMessageTemplate = () => { return ; }; -export default EditMessageTemplate; +function EditMessageTemplate() { + return ( + + + + ); +} + +export default withPageErrorBoundary(EditMessageTemplate); diff --git a/public/app/features/alerting/unified/components/contact-points/NewMessageTemplate.tsx b/public/app/features/alerting/unified/components/contact-points/NewMessageTemplate.tsx index e11ac531390..7cdd2286be1 100644 --- a/public/app/features/alerting/unified/components/contact-points/NewMessageTemplate.tsx +++ b/public/app/features/alerting/unified/components/contact-points/NewMessageTemplate.tsx @@ -1,16 +1,16 @@ -import { EntityNotFound } from 'app/core/components/PageNotFound/EntityNotFound'; - import { useAlertmanager } from '../../state/AlertmanagerContext'; +import { withPageErrorBoundary } from '../../withPageErrorBoundary'; +import { AlertmanagerPageWrapper } from '../AlertingPageWrapper'; import { TemplateForm } from '../receivers/TemplateForm'; -const NewMessageTemplate = () => { +function NewMessageTemplate() { const { selectedAlertmanager } = useAlertmanager(); - if (!selectedAlertmanager) { - return ; - } + return ( + + + + ); +} - return ; -}; - -export default NewMessageTemplate; +export default withPageErrorBoundary(NewMessageTemplate); diff --git a/public/app/features/alerting/unified/components/contact-points/components/GlobalConfig.tsx b/public/app/features/alerting/unified/components/contact-points/components/GlobalConfig.tsx index e5514b01d66..42ec581c529 100644 --- a/public/app/features/alerting/unified/components/contact-points/components/GlobalConfig.tsx +++ b/public/app/features/alerting/unified/components/contact-points/components/GlobalConfig.tsx @@ -1,11 +1,12 @@ -import { Alert, withErrorBoundary } from '@grafana/ui'; +import { Alert } from '@grafana/ui'; import { useAlertmanagerConfig } from '../../../hooks/useAlertmanagerConfig'; import { useAlertmanager } from '../../../state/AlertmanagerContext'; +import { withPageErrorBoundary } from '../../../withPageErrorBoundary'; import { AlertmanagerPageWrapper } from '../../AlertingPageWrapper'; import { GlobalConfigForm } from '../../receivers/GlobalConfigForm'; -const NewMessageTemplate = () => { +const GlobalConfig = () => { const { selectedAlertmanager } = useAlertmanager(); const { data, isLoading, error } = useAlertmanagerConfig(selectedAlertmanager); @@ -28,12 +29,12 @@ const NewMessageTemplate = () => { return ; }; -function NewMessageTemplatePage() { +function GlobalConfigPage() { return ( - + ); } -export default withErrorBoundary(NewMessageTemplatePage, { style: 'page' }); +export default withPageErrorBoundary(GlobalConfigPage); diff --git a/public/app/features/alerting/unified/components/export/ExportNewGrafanaRule.tsx b/public/app/features/alerting/unified/components/export/ExportNewGrafanaRule.tsx index 2e99bc0dc41..614f74eaa2a 100644 --- a/public/app/features/alerting/unified/components/export/ExportNewGrafanaRule.tsx +++ b/public/app/features/alerting/unified/components/export/ExportNewGrafanaRule.tsx @@ -1,21 +1,8 @@ -import * as React from 'react'; - +import { withPageErrorBoundary } from '../../withPageErrorBoundary'; import { AlertingPageWrapper } from '../AlertingPageWrapper'; import { ModifyExportRuleForm } from '../rule-editor/alert-rule-form/ModifyExportRuleForm'; -export default function ExportNewGrafanaRule() { - return ( - - - - ); -} - -interface ExportNewGrafanaRuleWrapperProps { - children: React.ReactNode; -} - -function ExportNewGrafanaRuleWrapper({ children }: ExportNewGrafanaRuleWrapperProps) { +function ExportNewGrafanaRulePage() { return ( - {children} + ); } + +export default withPageErrorBoundary(ExportNewGrafanaRulePage); diff --git a/public/app/features/alerting/unified/components/export/GrafanaModifyExport.tsx b/public/app/features/alerting/unified/components/export/GrafanaModifyExport.tsx index 5aca1d77824..1c4241fe519 100644 --- a/public/app/features/alerting/unified/components/export/GrafanaModifyExport.tsx +++ b/public/app/features/alerting/unified/components/export/GrafanaModifyExport.tsx @@ -1,4 +1,3 @@ -import * as React from 'react'; import { useMemo } from 'react'; import { useParams } from 'react-router-dom-v5-compat'; @@ -12,10 +11,11 @@ import { stringifyErrorLike } from '../../utils/misc'; import * as ruleId from '../../utils/rule-id'; import { isGrafanaRulerRule } from '../../utils/rules'; import { createRelativeUrl } from '../../utils/url'; +import { withPageErrorBoundary } from '../../withPageErrorBoundary'; import { AlertingPageWrapper } from '../AlertingPageWrapper'; import { ModifyExportRuleForm } from '../rule-editor/alert-rule-form/ModifyExportRuleForm'; -export default function GrafanaModifyExport() { +function GrafanaModifyExport() { const { id } = useParams(); const ruleIdentifier = useMemo(() => { return ruleId.tryParse(id, true); @@ -23,38 +23,13 @@ export default function GrafanaModifyExport() { if (!ruleIdentifier) { return ( - - - The rule UID in the page URL is invalid. Please check the URL and try again. - - + + The rule UID in the page URL is invalid. Please check the URL and try again. + ); } - return ( - - - - ); -} - -interface ModifyExportWrapperProps { - children: React.ReactNode; -} - -function ModifyExportWrapper({ children }: ModifyExportWrapperProps) { - return ( - - {children} - - ); + return ; } function RuleModifyExport({ ruleIdentifier }: { ruleIdentifier: RuleIdentifier }) { @@ -105,3 +80,20 @@ function RuleModifyExport({ ruleIdentifier }: { ruleIdentifier: RuleIdentifier } return ; } + +function GrafanaModifyExportPage() { + return ( + + + + ); +} + +export default withPageErrorBoundary(GrafanaModifyExportPage); diff --git a/public/app/features/alerting/unified/components/mute-timings/EditMuteTiming.tsx b/public/app/features/alerting/unified/components/mute-timings/EditMuteTiming.tsx index f73b1a1d1af..f697b935408 100644 --- a/public/app/features/alerting/unified/components/mute-timings/EditMuteTiming.tsx +++ b/public/app/features/alerting/unified/components/mute-timings/EditMuteTiming.tsx @@ -1,10 +1,10 @@ import { Navigate } from 'react-router-dom-v5-compat'; -import { withErrorBoundary } from '@grafana/ui'; import { useGetMuteTiming } from 'app/features/alerting/unified/components/mute-timings/useMuteTimings'; import { useURLSearchParams } from 'app/features/alerting/unified/hooks/useURLSearchParams'; import { useAlertmanager } from '../../state/AlertmanagerContext'; +import { withPageErrorBoundary } from '../../withPageErrorBoundary'; import { AlertmanagerPageWrapper } from '../AlertingPageWrapper'; import MuteTimingForm from './MuteTimingForm'; @@ -38,17 +38,16 @@ const EditTimingRoute = () => { ); }; -const EditMuteTimingPage = () => ( - - - -); +function EditMuteTimingPage() { + return ( + + + + ); +} -export default withErrorBoundary(EditMuteTimingPage, { style: 'page' }); +export default withPageErrorBoundary(EditMuteTimingPage); diff --git a/public/app/features/alerting/unified/components/mute-timings/NewMuteTiming.tsx b/public/app/features/alerting/unified/components/mute-timings/NewMuteTiming.tsx index b73dda0e94f..c60e6d0f990 100644 --- a/public/app/features/alerting/unified/components/mute-timings/NewMuteTiming.tsx +++ b/public/app/features/alerting/unified/components/mute-timings/NewMuteTiming.tsx @@ -1,20 +1,18 @@ -import { withErrorBoundary } from '@grafana/ui'; - +import { withPageErrorBoundary } from '../../withPageErrorBoundary'; import { AlertmanagerPageWrapper } from '../AlertingPageWrapper'; import MuteTimingForm from './MuteTimingForm'; -const NewMuteTimingPage = () => ( - - - -); +function NewMuteTimingPage() { + return ( + + + + ); +} -export default withErrorBoundary(NewMuteTimingPage, { style: 'page' }); +export default withPageErrorBoundary(NewMuteTimingPage); diff --git a/public/app/features/alerting/unified/components/receivers/NewReceiverView.tsx b/public/app/features/alerting/unified/components/receivers/NewReceiverView.tsx index 4f2a3322da4..9258dddfc89 100644 --- a/public/app/features/alerting/unified/components/receivers/NewReceiverView.tsx +++ b/public/app/features/alerting/unified/components/receivers/NewReceiverView.tsx @@ -1,7 +1,7 @@ -import { withErrorBoundary } from '@grafana/ui'; import { useAlertmanager } from 'app/features/alerting/unified/state/AlertmanagerContext'; import { GRAFANA_RULES_SOURCE_NAME } from '../../utils/datasource'; +import { withPageErrorBoundary } from '../../withPageErrorBoundary'; import { AlertmanagerPageWrapper } from '../AlertingPageWrapper'; import { CloudReceiverForm } from './form/CloudReceiverForm'; @@ -24,4 +24,4 @@ function NewReceiverViewPage() { ); } -export default withErrorBoundary(NewReceiverViewPage, { style: 'page' }); +export default withPageErrorBoundary(NewReceiverViewPage); diff --git a/public/app/features/alerting/unified/components/rules/central-state-history/CentralAlertHistoryPage.tsx b/public/app/features/alerting/unified/components/rules/central-state-history/CentralAlertHistoryPage.tsx index eb767a1df2f..91463309afd 100644 --- a/public/app/features/alerting/unified/components/rules/central-state-history/CentralAlertHistoryPage.tsx +++ b/public/app/features/alerting/unified/components/rules/central-state-history/CentralAlertHistoryPage.tsx @@ -1,14 +1,14 @@ -import { withErrorBoundary } from '@grafana/ui'; - +import { withPageErrorBoundary } from '../../../withPageErrorBoundary'; import { AlertingPageWrapper } from '../../AlertingPageWrapper'; import { CentralAlertHistoryScene } from './CentralAlertHistoryScene'; -const HistoryPage = () => { +function HistoryPage() { return ( ); -}; -export default withErrorBoundary(HistoryPage, { style: 'page' }); +} + +export default withPageErrorBoundary(HistoryPage); diff --git a/public/app/features/alerting/unified/components/silences/SilencesEditor.tsx b/public/app/features/alerting/unified/components/silences/SilencesEditor.tsx index c72cb1e5521..2a1c1be335c 100644 --- a/public/app/features/alerting/unified/components/silences/SilencesEditor.tsx +++ b/public/app/features/alerting/unified/components/silences/SilencesEditor.tsx @@ -25,7 +25,6 @@ import { Stack, TextArea, useStyles2, - withErrorBoundary, } from '@grafana/ui'; import { Trans } from 'app/core/internationalization'; import { SilenceCreatedResponse, alertSilencesApi } from 'app/features/alerting/unified/api/alertSilencesApi'; @@ -38,6 +37,7 @@ import { useAlertmanager } from '../../state/AlertmanagerContext'; import { SilenceFormFields } from '../../types/silence-form'; import { matcherFieldToMatcher } from '../../utils/alertmanager'; import { makeAMLink } from '../../utils/misc'; +import { withPageErrorBoundary } from '../../withPageErrorBoundary'; import { AlertmanagerPageWrapper } from '../AlertingPageWrapper'; import { GrafanaAlertmanagerDeliveryWarning } from '../GrafanaAlertmanagerDeliveryWarning'; @@ -296,4 +296,5 @@ function ExistingSilenceEditorPage() { ); } -export default withErrorBoundary(ExistingSilenceEditorPage, { style: 'page' }); + +export default withPageErrorBoundary(ExistingSilenceEditorPage); diff --git a/public/app/features/alerting/unified/components/silences/SilencesTable.tsx b/public/app/features/alerting/unified/components/silences/SilencesTable.tsx index 6eaf819d11a..2faa5a2b1c8 100644 --- a/public/app/features/alerting/unified/components/silences/SilencesTable.tsx +++ b/public/app/features/alerting/unified/components/silences/SilencesTable.tsx @@ -12,7 +12,6 @@ import { LoadingPlaceholder, Stack, useStyles2, - withErrorBoundary, } from '@grafana/ui'; import { useQueryParams } from 'app/core/hooks/useQueryParams'; import { Trans } from 'app/core/internationalization'; @@ -27,6 +26,7 @@ import { AlertmanagerAction, useAlertmanagerAbility } from '../../hooks/useAbili import { useAlertmanager } from '../../state/AlertmanagerContext'; import { parsePromQLStyleMatcherLooseSafe } from '../../utils/matchers'; import { getSilenceFiltersFromUrlParams, makeAMLink, stringifyErrorLike } from '../../utils/misc'; +import { withPageErrorBoundary } from '../../withPageErrorBoundary'; import { AlertmanagerPageWrapper } from '../AlertingPageWrapper'; import { Authorize } from '../Authorize'; import { DynamicTable, DynamicTableColumnProps, DynamicTableItemProps } from '../DynamicTable'; @@ -393,4 +393,5 @@ function SilencesTablePage() { ); } -export default withErrorBoundary(SilencesTablePage, { style: 'page' }); + +export default withPageErrorBoundary(SilencesTablePage); diff --git a/public/app/features/alerting/unified/home/Home.tsx b/public/app/features/alerting/unified/home/Home.tsx index 898bfdebefb..023b6a237a3 100644 --- a/public/app/features/alerting/unified/home/Home.tsx +++ b/public/app/features/alerting/unified/home/Home.tsx @@ -5,12 +5,13 @@ import { Box, Stack, Tab, TabContent, TabsBar } from '@grafana/ui'; import { AlertingPageWrapper } from '../components/AlertingPageWrapper'; import { isLocalDevEnv } from '../utils/misc'; +import { withPageErrorBoundary } from '../withPageErrorBoundary'; import GettingStarted, { WelcomeHeader } from './GettingStarted'; import { getInsightsScenes, insightsIsAvailable } from './Insights'; import { PluginIntegrations } from './PluginIntegrations'; -export default function Home() { +function Home() { const insightsEnabled = (insightsIsAvailable() || isLocalDevEnv()) && Boolean(config.featureToggles.alertingInsights); const [activeTab, setActiveTab] = useState<'insights' | 'overview'>(insightsEnabled ? 'insights' : 'overview'); @@ -51,3 +52,5 @@ export default function Home() { ); } + +export default withPageErrorBoundary(Home); diff --git a/public/app/features/alerting/unified/rule-editor/RuleEditor.tsx b/public/app/features/alerting/unified/rule-editor/RuleEditor.tsx index b5adba57cf2..42585600052 100644 --- a/public/app/features/alerting/unified/rule-editor/RuleEditor.tsx +++ b/public/app/features/alerting/unified/rule-editor/RuleEditor.tsx @@ -2,7 +2,6 @@ import { useCallback } from 'react'; import { useParams } from 'react-router-dom-v5-compat'; import { NavModelItem } from '@grafana/data'; -import { withErrorBoundary } from '@grafana/ui'; import { RuleIdentifier } from 'app/types/unified-alerting'; import { AlertWarning } from '../AlertWarning'; @@ -11,6 +10,7 @@ import { AlertRuleForm } from '../components/rule-editor/alert-rule-form/AlertRu import { useURLSearchParams } from '../hooks/useURLSearchParams'; import { useRulesAccess } from '../utils/accessControlHooks'; import * as ruleId from '../utils/rule-id'; +import { withPageErrorBoundary } from '../withPageErrorBoundary'; import { CloneRuleEditor } from './CloneRuleEditor'; import { ExistingRuleEditor } from './ExistingRuleEditor'; @@ -78,7 +78,9 @@ const RuleEditor = () => { ); }; -export default withErrorBoundary(RuleEditor, { style: 'page' }); +// The pageNav property makes it difficult to only rely on AlertingPageWrapper +// to catch errors. +export default withPageErrorBoundary(RuleEditor); function useRuleEditorPathParams() { const params = useParams(); diff --git a/public/app/features/alerting/unified/rule-list/RuleList.v1.tsx b/public/app/features/alerting/unified/rule-list/RuleList.v1.tsx index d90c4b238c0..759e7b48474 100644 --- a/public/app/features/alerting/unified/rule-list/RuleList.v1.tsx +++ b/public/app/features/alerting/unified/rule-list/RuleList.v1.tsx @@ -4,7 +4,7 @@ import { useAsyncFn, useInterval } from 'react-use'; import { urlUtil } from '@grafana/data'; import { logInfo } from '@grafana/runtime'; -import { Button, LinkButton, Stack, withErrorBoundary } from '@grafana/ui'; +import { Button, LinkButton, Stack } from '@grafana/ui'; import { useQueryParams } from 'app/core/hooks/useQueryParams'; import { Trans } from 'app/core/internationalization'; import { useDispatch } from 'app/types'; @@ -155,7 +155,7 @@ const RuleListV1 = () => { ); }; -export default withErrorBoundary(RuleListV1, { style: 'page' }); +export default RuleListV1; export function CreateAlertButton() { const [createRuleSupported, createRuleAllowed] = useAlertingAbility(AlertingAction.CreateAlertRule); diff --git a/public/app/features/alerting/unified/rule-list/RuleList.v2.tsx b/public/app/features/alerting/unified/rule-list/RuleList.v2.tsx index b8ccbb2fa8f..86949386e3a 100644 --- a/public/app/features/alerting/unified/rule-list/RuleList.v2.tsx +++ b/public/app/features/alerting/unified/rule-list/RuleList.v2.tsx @@ -1,5 +1,3 @@ -import { withErrorBoundary } from '@grafana/ui'; - import { AlertingPageWrapper } from '../components/AlertingPageWrapper'; import RulesFilter from '../components/rules/Filter/RulesFilter'; import { SupportedView } from '../components/rules/Filter/RulesViewModeSelector'; @@ -9,24 +7,25 @@ import { useURLSearchParams } from '../hooks/useURLSearchParams'; import { FilterView } from './FilterView'; import { GroupedView } from './GroupedView'; -const RuleList = withErrorBoundary( - () => { - const [queryParams] = useURLSearchParams(); - const { filterState, hasActiveFilters } = useRulesFilter(); +function RuleList() { + const [queryParams] = useURLSearchParams(); + const { filterState, hasActiveFilters } = useRulesFilter(); - const view: SupportedView = queryParams.get('view') === 'list' ? 'list' : 'grouped'; - const showListView = hasActiveFilters || view === 'list'; + const view: SupportedView = queryParams.get('view') === 'list' ? 'list' : 'grouped'; + const showListView = hasActiveFilters || view === 'list'; - return ( - // We don't want to show the Loading... indicator for the whole page. - // We show separate indicators for Grafana-managed and Cloud rules - - {}} /> - {showListView ? : } - - ); - }, - { style: 'page' } -); + return ( + <> + {}} /> + {showListView ? : } + + ); +} -export default RuleList; +export default function RuleListPage() { + return ( + + + + ); +} diff --git a/public/app/features/alerting/unified/withPageErrorBoundary.tsx b/public/app/features/alerting/unified/withPageErrorBoundary.tsx new file mode 100644 index 00000000000..e53d5bb2e0d --- /dev/null +++ b/public/app/features/alerting/unified/withPageErrorBoundary.tsx @@ -0,0 +1,25 @@ +import { ComponentType } from 'react'; + +import { ErrorBoundaryAlertProps, withErrorBoundary } from '@grafana/ui'; + +import { logError } from './Analytics'; + +/** + * HOC for wrapping alerting page in an error boundary. + * It provides alerting-specific error handling. + * + * @param Component - the react component to wrap in error boundary + * @param errorBoundaryProps - error boundary options + * + * @public + */ +export function withPageErrorBoundary

( + Component: ComponentType

, + errorBoundaryProps: Omit = {} +): ComponentType

{ + return withErrorBoundary(Component, { + ...errorBoundaryProps, + style: 'page', + errorLogger: logError, + }); +}