From d9d39ae17817bf53f966cc4690f6a4f1d2d06d4c Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Thu, 18 Dec 2025 09:56:46 +0100 Subject: [PATCH 01/36] E2E Selectors: Add missing comma (#115531) add missing comma --- packages/grafana-e2e-selectors/src/selectors/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grafana-e2e-selectors/src/selectors/index.ts b/packages/grafana-e2e-selectors/src/selectors/index.ts index bfe90848026..3abef8119dd 100644 --- a/packages/grafana-e2e-selectors/src/selectors/index.ts +++ b/packages/grafana-e2e-selectors/src/selectors/index.ts @@ -9,7 +9,7 @@ const Components = resolveSelectors(versionedComponents); const selectors = { pages: Pages, components: Components }; /** - * Exposes Pages, Component selectors and E2ESelectors type in package for easy use in e2e tests and in production code + * Exposes Pages, Component selectors, and E2ESelectors type in package for easy use in e2e tests and in production code. */ export { Pages, From 5e3a1091b351a63d2b4339f621c2f69e4a0f3336 Mon Sep 17 00:00:00 2001 From: Alexander Akhmetov Date: Thu, 18 Dec 2025 10:06:39 +0100 Subject: [PATCH 02/36] Alerting: Add a feature toggle to fetch rules with compact=true (#115533) --- .../grafana-data/src/types/featureToggles.gen.ts | 4 ++++ pkg/services/featuremgmt/registry.go | 7 +++++++ pkg/services/featuremgmt/toggles_gen.csv | 1 + pkg/services/featuremgmt/toggles_gen.go | 4 ++++ pkg/services/featuremgmt/toggles_gen.json | 13 +++++++++++++ 5 files changed, 29 insertions(+) diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index 51eada8c120..6ce08b92452 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -828,6 +828,10 @@ export interface FeatureToggles { */ fetchRulesUsingPost?: boolean; /** + * Add compact=true when fetching rules + */ + fetchRulesInCompactMode?: boolean; + /** * Enables the new logs panel * @default true */ diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index b0beaa05482..af82c05e61d 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -1360,6 +1360,13 @@ var ( Owner: grafanaAlertingSquad, HideFromDocs: true, }, + { + Name: "fetchRulesInCompactMode", + Description: "Add compact=true when fetching rules", + Stage: FeatureStageExperimental, + Owner: grafanaAlertingSquad, + HideFromDocs: true, + }, { Name: "newLogsPanel", Description: "Enables the new logs panel", diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index 46581205ae4..fedcd197a1f 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -188,6 +188,7 @@ grafanaAdvisor,privatePreview,@grafana/plugins-platform-backend,false,false,fals elasticsearchImprovedParsing,experimental,@grafana/partner-datasources,false,false,false datasourceConnectionsTab,privatePreview,@grafana/plugins-platform-backend,false,false,true fetchRulesUsingPost,experimental,@grafana/alerting-squad,false,false,false +fetchRulesInCompactMode,experimental,@grafana/alerting-squad,false,false,false newLogsPanel,GA,@grafana/observability-logs,false,false,true grafanaconThemes,GA,@grafana/grafana-frontend-platform,false,true,false alertingJiraIntegration,experimental,@grafana/alerting-squad,false,false,true diff --git a/pkg/services/featuremgmt/toggles_gen.go b/pkg/services/featuremgmt/toggles_gen.go index fc7b8043dec..020e0bce893 100644 --- a/pkg/services/featuremgmt/toggles_gen.go +++ b/pkg/services/featuremgmt/toggles_gen.go @@ -571,6 +571,10 @@ const ( // Use a POST request to list rules by passing down the namespaces user has access to FlagFetchRulesUsingPost = "fetchRulesUsingPost" + // FlagFetchRulesInCompactMode + // Add compact=true when fetching rules + FlagFetchRulesInCompactMode = "fetchRulesInCompactMode" + // FlagGrafanaconThemes // Enables the temporary themes for GrafanaCon FlagGrafanaconThemes = "grafanaconThemes" diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json index 4cc98de091b..3c33f362b29 100644 --- a/pkg/services/featuremgmt/toggles_gen.json +++ b/pkg/services/featuremgmt/toggles_gen.json @@ -1457,6 +1457,19 @@ "expression": "false" } }, + { + "metadata": { + "name": "fetchRulesInCompactMode", + "resourceVersion": "1766045288124", + "creationTimestamp": "2025-12-18T08:08:08Z" + }, + "spec": { + "description": "Add compact=true when fetching rules", + "stage": "experimental", + "codeowner": "@grafana/alerting-squad", + "hideFromDocs": true + } + }, { "metadata": { "name": "fetchRulesUsingPost", From a2a278a52eb79b54fae615899cc748db67e8a589 Mon Sep 17 00:00:00 2001 From: Oscar Kilhed Date: Thu, 18 Dec 2025 10:12:31 +0100 Subject: [PATCH 03/36] Schema V2: Always set unique refid for queries in conversion V1 -> V2 (#115534) Always set unique refid in conversion --- .../v1beta1.panel-target-missing-refid.json | 156 ++++++++++++ ...1.panel-target-missing-refid.v0alpha1.json | 161 ++++++++++++ ...1.panel-target-missing-refid.v2alpha1.json | 227 +++++++++++++++++ ...a1.panel-target-missing-refid.v2beta1.json | 231 ++++++++++++++++++ .../conversion/v1beta1_to_v2alpha1.go | 43 ++++ .../conversion/v1beta1_to_v2alpha1_test.go | 42 ++++ 6 files changed, 860 insertions(+) create mode 100644 apps/dashboard/pkg/migration/conversion/testdata/input/v1beta1.panel-target-missing-refid.json create mode 100644 apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.panel-target-missing-refid.v0alpha1.json create mode 100644 apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.panel-target-missing-refid.v2alpha1.json create mode 100644 apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.panel-target-missing-refid.v2beta1.json diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/v1beta1.panel-target-missing-refid.json b/apps/dashboard/pkg/migration/conversion/testdata/input/v1beta1.panel-target-missing-refid.json new file mode 100644 index 00000000000..65bf0b136a6 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/input/v1beta1.panel-target-missing-refid.json @@ -0,0 +1,156 @@ +{ + "apiVersion": "dashboard.grafana.app/v1beta1", + "kind": "Dashboard", + "metadata": { + "name": "ad5vfcn", + "namespace": "default", + "uid": "dlMZZl6GndU8gJLUQSmgZxXBPCNXyXhNBeQJhHXl0r4X", + "resourceVersion": "2", + "generation": 2, + "creationTimestamp": "2025-11-28T10:14:21Z", + "labels": { + "grafana.app/deprecatedInternalID": "288" + }, + "annotations": { + "grafana.app/createdBy": "user:eex2ofwuj0agwd", + "grafana.app/updatedBy": "user:eex2ofwuj0agwd", + "grafana.app/updatedTimestamp": "2025-11-28T10:15:06Z" + } + }, + "spec": { + "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, + "id": 288, + "links": [], + "panels": [ + { + "datasource": { + "type": "testdata", + "uid": "gdev-testdata" + }, + "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", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "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": "12.4.0-pre", + "targets": [ + { + "datasource": {}, + "queryType": "randomWalk" + }, + { + "datasource": {}, + "queryType": "randomWalk" + } + ], + "title": "New panel", + "type": "timeseries" + } + ], + "preload": false, + "schemaVersion": 42, + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "browser", + "title": "Panel ds inheritance ", + "uid": "ad5vfcn", + "version": 2 + }, + "status": {} +} diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.panel-target-missing-refid.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.panel-target-missing-refid.v0alpha1.json new file mode 100644 index 00000000000..ad063633f86 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.panel-target-missing-refid.v0alpha1.json @@ -0,0 +1,161 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v0alpha1", + "metadata": { + "name": "ad5vfcn", + "namespace": "default", + "uid": "dlMZZl6GndU8gJLUQSmgZxXBPCNXyXhNBeQJhHXl0r4X", + "resourceVersion": "2", + "generation": 2, + "creationTimestamp": "2025-11-28T10:14:21Z", + "labels": { + "grafana.app/deprecatedInternalID": "288" + }, + "annotations": { + "grafana.app/createdBy": "user:eex2ofwuj0agwd", + "grafana.app/updatedBy": "user:eex2ofwuj0agwd", + "grafana.app/updatedTimestamp": "2025-11-28T10:15:06Z" + } + }, + "spec": { + "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, + "id": 288, + "links": [], + "panels": [ + { + "datasource": { + "type": "testdata", + "uid": "gdev-testdata" + }, + "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", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "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": "12.4.0-pre", + "targets": [ + { + "datasource": {}, + "queryType": "randomWalk" + }, + { + "datasource": {}, + "queryType": "randomWalk" + } + ], + "title": "New panel", + "type": "timeseries" + } + ], + "preload": false, + "schemaVersion": 42, + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "browser", + "title": "Panel ds inheritance ", + "uid": "ad5vfcn", + "version": 2 + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.panel-target-missing-refid.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.panel-target-missing-refid.v2alpha1.json new file mode 100644 index 00000000000..a8ce2b6a42b --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.panel-target-missing-refid.v2alpha1.json @@ -0,0 +1,227 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2alpha1", + "metadata": { + "name": "ad5vfcn", + "namespace": "default", + "uid": "dlMZZl6GndU8gJLUQSmgZxXBPCNXyXhNBeQJhHXl0r4X", + "resourceVersion": "2", + "generation": 2, + "creationTimestamp": "2025-11-28T10:14:21Z", + "labels": { + "grafana.app/deprecatedInternalID": "288" + }, + "annotations": { + "grafana.app/createdBy": "user:eex2ofwuj0agwd", + "grafana.app/updatedBy": "user:eex2ofwuj0agwd", + "grafana.app/updatedTimestamp": "2025-11-28T10:15:06Z" + } + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "query": { + "kind": "grafana", + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "New panel", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "testdata", + "spec": { + "queryType": "randomWalk" + } + }, + "datasource": { + "type": "testdata", + "uid": "gdev-testdata" + }, + "refId": "A", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "testdata", + "spec": { + "queryType": "randomWalk" + } + }, + "datasource": { + "type": "testdata", + "uid": "gdev-testdata" + }, + "refId": "B", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "12.4.0-pre", + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "fieldConfig": { + "defaults": { + "thresholds": { + "mode": "absolute", + "steps": [ + { + "value": 0, + "color": "green" + }, + { + "value": 80, + "color": "red" + } + ] + }, + "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", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + } + }, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 12, + "height": 8, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "browser", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "Panel ds inheritance ", + "variables": [] + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.panel-target-missing-refid.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.panel-target-missing-refid.v2beta1.json new file mode 100644 index 00000000000..c407d612154 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.panel-target-missing-refid.v2beta1.json @@ -0,0 +1,231 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2beta1", + "metadata": { + "name": "ad5vfcn", + "namespace": "default", + "uid": "dlMZZl6GndU8gJLUQSmgZxXBPCNXyXhNBeQJhHXl0r4X", + "resourceVersion": "2", + "generation": 2, + "creationTimestamp": "2025-11-28T10:14:21Z", + "labels": { + "grafana.app/deprecatedInternalID": "288" + }, + "annotations": { + "grafana.app/createdBy": "user:eex2ofwuj0agwd", + "grafana.app/updatedBy": "user:eex2ofwuj0agwd", + "grafana.app/updatedTimestamp": "2025-11-28T10:15:06Z" + } + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "grafana", + "version": "v0", + "datasource": { + "name": "-- Grafana --" + }, + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "New panel", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "testdata", + "version": "v0", + "datasource": { + "name": "gdev-testdata" + }, + "spec": { + "queryType": "randomWalk" + } + }, + "refId": "A", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "testdata", + "version": "v0", + "datasource": { + "name": "gdev-testdata" + }, + "spec": { + "queryType": "randomWalk" + } + }, + "refId": "B", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "12.4.0-pre", + "spec": { + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "fieldConfig": { + "defaults": { + "thresholds": { + "mode": "absolute", + "steps": [ + { + "value": 0, + "color": "green" + }, + { + "value": 80, + "color": "red" + } + ] + }, + "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", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + } + }, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 12, + "height": 8, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "browser", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "Panel ds inheritance ", + "variables": [] + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/v1beta1_to_v2alpha1.go b/apps/dashboard/pkg/migration/conversion/v1beta1_to_v2alpha1.go index 81c52bcc43e..db86f8c87a9 100644 --- a/apps/dashboard/pkg/migration/conversion/v1beta1_to_v2alpha1.go +++ b/apps/dashboard/pkg/migration/conversion/v1beta1_to_v2alpha1.go @@ -2006,6 +2006,28 @@ func transformPanelQueries(ctx context.Context, panelMap map[string]interface{}, } } + // Ensure each target has a non-empty refId. We only fill missing refIds; + existingRefIds := make(map[string]bool) + for _, target := range targets { + if targetMap, ok := target.(map[string]interface{}); ok { + if refId := schemaversion.GetStringValue(targetMap, "refId"); refId != "" { + existingRefIds[refId] = true + } + } + } + for _, target := range targets { + targetMap, ok := target.(map[string]interface{}) + if !ok { + continue + } + refId := schemaversion.GetStringValue(targetMap, "refId") + if refId == "" { + refId = nextAvailableRefId(existingRefIds) + targetMap["refId"] = refId + existingRefIds[refId] = true + } + } + queries := make([]dashv2alpha1.DashboardPanelQueryKind, 0, len(targets)) for _, target := range targets { @@ -2018,6 +2040,27 @@ func transformPanelQueries(ctx context.Context, panelMap map[string]interface{}, return queries } +// nextAvailableRefId returns the next unused refId using the same sequence as the +// frontend helper (A, B, ..., Z, AA, AB, ...). +func nextAvailableRefId(existing map[string]bool) string { + const letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + + var refIdFromIndex func(num int) string + refIdFromIndex = func(num int) string { + if num < len(letters) { + return string(letters[num]) + } + return refIdFromIndex(num/len(letters)-1) + string(letters[num%len(letters)]) + } + + for i := 0; ; i++ { + refId := refIdFromIndex(i) + if !existing[refId] { + return refId + } + } +} + func transformSingleQuery(ctx context.Context, targetMap map[string]interface{}, panelDatasource *dashv2alpha1.DashboardDataSourceRef, dsIndexProvider schemaversion.DataSourceIndexProvider) dashv2alpha1.DashboardPanelQueryKind { refId := schemaversion.GetStringValue(targetMap, "refId", "A") if refId == "" { diff --git a/apps/dashboard/pkg/migration/conversion/v1beta1_to_v2alpha1_test.go b/apps/dashboard/pkg/migration/conversion/v1beta1_to_v2alpha1_test.go index a80bf9c8072..6c0e8f4f919 100644 --- a/apps/dashboard/pkg/migration/conversion/v1beta1_to_v2alpha1_test.go +++ b/apps/dashboard/pkg/migration/conversion/v1beta1_to_v2alpha1_test.go @@ -210,6 +210,48 @@ func TestV1beta1ToV2alpha1(t *testing.T) { assert.Equal(t, "", query.Spec.Query.Kind, "Query kind should be empty when datasource is empty object {}") }, }, + { + name: "missing refIds are assigned while existing refIds are preserved", + createV1beta1: func() *dashv1.Dashboard { + return &dashv1.Dashboard{ + Spec: dashv1.DashboardSpec{ + Object: map[string]interface{}{ + "title": "Test Dashboard", + "panels": []interface{}{ + map[string]interface{}{ + "id": 1, + "type": "bargauge", + "targets": []interface{}{ + map[string]interface{}{ + "refId": "", + "scenarioId": "random_walk", + }, + map[string]interface{}{ + "refId": "A", + "scenarioId": "random_walk", + }, + map[string]interface{}{ + "refId": "", + "scenarioId": "random_walk", + }, + }, + }, + }, + }, + }, + } + }, + validateV2alpha1: func(t *testing.T, v2alpha1 *dashv2alpha1.Dashboard) { + require.NotNil(t, v2alpha1.Spec.Elements["panel-1"]) + panel := v2alpha1.Spec.Elements["panel-1"].PanelKind + require.NotNil(t, panel) + + require.Len(t, panel.Spec.Data.Spec.Queries, 3) + assert.Equal(t, "B", panel.Spec.Data.Spec.Queries[0].Spec.RefId) + assert.Equal(t, "A", panel.Spec.Data.Spec.Queries[1].Spec.RefId) + assert.Equal(t, "C", panel.Spec.Data.Spec.Queries[2].Spec.RefId) + }, + }, } for _, tt := range testCases { From 3b254467e1cce8551eb218e324489ff87b341615 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20=C5=A0tibran=C3=BD?= Date: Thu, 18 Dec 2025 10:18:44 +0100 Subject: [PATCH 04/36] DB snapshot for MySQL. (#115402) * DB snapshot for MySQL. * Fix double import. * Update schema from version 12.4.0-20306503000 --- .../docker/blocks/mysql/docker-compose.yaml | 10 +- pkg/services/sqlstore/migrator/dialect.go | 5 +- pkg/services/sqlstore/migrator/migrator.go | 2 +- .../sqlstore/migrator/mysql_dialect.go | 88 + .../sqlstore/migrator/snapshot/00-schema.sql | 2325 +++++++++++++++++ .../snapshot/96-secret_migration_log.sql | 57 + .../snapshot/97-resource_migration_log.sql | 60 + .../sqlstore/migrator/snapshot/98-data.sql | 469 ++++ .../migrator/snapshot/99-migration_log.sql | 850 ++++++ 9 files changed, 3862 insertions(+), 4 deletions(-) create mode 100644 pkg/services/sqlstore/migrator/snapshot/00-schema.sql create mode 100644 pkg/services/sqlstore/migrator/snapshot/96-secret_migration_log.sql create mode 100644 pkg/services/sqlstore/migrator/snapshot/97-resource_migration_log.sql create mode 100644 pkg/services/sqlstore/migrator/snapshot/98-data.sql create mode 100644 pkg/services/sqlstore/migrator/snapshot/99-migration_log.sql diff --git a/devenv/docker/blocks/mysql/docker-compose.yaml b/devenv/docker/blocks/mysql/docker-compose.yaml index cd5de0e6e67..c0c5403ef25 100644 --- a/devenv/docker/blocks/mysql/docker-compose.yaml +++ b/devenv/docker/blocks/mysql/docker-compose.yaml @@ -7,7 +7,15 @@ MYSQL_PASSWORD: password ports: - "3306:3306" - command: [mysqld, --character-set-server=utf8mb4, --collation-server=utf8mb4_unicode_ci, --innodb_monitor_enable=all, --default-authentication-plugin=mysql_native_password] + command: + - mysqld + - --character-set-server=utf8mb4 + - --collation-server=utf8mb4_unicode_ci + - --innodb_monitor_enable=all + - --default-authentication-plugin=mysql_native_password + # Please keep sql-require-primary-key option enabled, to make sure we don't accidentally introduce migration + # adding new table without PK. + - --sql-require-primary-key=ON fake-mysql-data: image: grafana/fake-data-gen diff --git a/pkg/services/sqlstore/migrator/dialect.go b/pkg/services/sqlstore/migrator/dialect.go index 623bada2253..d75f87989f7 100644 --- a/pkg/services/sqlstore/migrator/dialect.go +++ b/pkg/services/sqlstore/migrator/dialect.go @@ -7,6 +7,7 @@ import ( "strconv" "strings" + "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/services/sqlstore/session" "github.com/grafana/grafana/pkg/util/xorm" ) @@ -77,7 +78,7 @@ type Dialect interface { // CreateDatabaseFromSnapshot is called when migration log table is not found. // Dialect can recreate all tables from existing snapshot. After successful (nil error) return, // migrator will list migrations from the log, and apply all missing migrations. - CreateDatabaseFromSnapshot(ctx context.Context, engine *xorm.Engine, migrationLogTableName string) error + CreateDatabaseFromSnapshot(ctx context.Context, engine *xorm.Engine, migrationLogTableName string, logger log.Logger) error IsUniqueConstraintViolation(err error) bool ErrorMessage(err error) string @@ -355,7 +356,7 @@ func (b *BaseDialect) CleanDB(engine *xorm.Engine) error { return nil } -func (b *BaseDialect) CreateDatabaseFromSnapshot(ctx context.Context, engine *xorm.Engine, tableName string) error { +func (b *BaseDialect) CreateDatabaseFromSnapshot(ctx context.Context, engine *xorm.Engine, migrationLogTableName string, logger log.Logger) error { return nil } diff --git a/pkg/services/sqlstore/migrator/migrator.go b/pkg/services/sqlstore/migrator/migrator.go index 88a2c65d057..ca921affdac 100644 --- a/pkg/services/sqlstore/migrator/migrator.go +++ b/pkg/services/sqlstore/migrator/migrator.go @@ -250,7 +250,7 @@ func (mg *Migrator) run(ctx context.Context) (err error) { if !migrationLogExists { // Check if dialect can initialize database from a snapshot. - err := mg.Dialect.CreateDatabaseFromSnapshot(ctx, mg.DBEngine, mg.tableName) + err := mg.Dialect.CreateDatabaseFromSnapshot(ctx, mg.DBEngine, mg.tableName, logger) if err != nil { return fmt.Errorf("failed to create database from snapshot: %w", err) } diff --git a/pkg/services/sqlstore/migrator/mysql_dialect.go b/pkg/services/sqlstore/migrator/mysql_dialect.go index 5a76b4555ca..c356caa8f1c 100644 --- a/pkg/services/sqlstore/migrator/mysql_dialect.go +++ b/pkg/services/sqlstore/migrator/mysql_dialect.go @@ -1,15 +1,19 @@ package migrator import ( + "context" "database/sql" + "embed" "errors" "fmt" + "path" "strconv" "strings" "github.com/VividCortex/mysqlerr" "github.com/go-sql-driver/mysql" + "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/util/xorm" ) @@ -317,3 +321,87 @@ func (db *MySQLDialect) GetDBName(dsn string) (string, error) { return cfg.DBName, nil } + +//go:embed snapshot/*.sql +var sqlFiles embed.FS + +func (db *MySQLDialect) CreateDatabaseFromSnapshot(ctx context.Context, engine *xorm.Engine, migrationLogTableName string, logger log.Logger) error { + // Entire schema is generated only when called with "migration_log" table. + // Normally if schema creates other "*_migration_log" tables, we don't get here. However if new migration table is introduced by later migration, + // CreateDatabaseFromSnapshot may be called for given table, but if it wasn't part of original schema, we can't do anything here. + if migrationLogTableName != "migration_log" { + return nil + } + + entries, err := sqlFiles.ReadDir("snapshot") + if err != nil { + return err + } + + logger.Info("creating database schema from snapshot") + + for _, entry := range entries { + logger.Debug("using snapshot file", "file", entry.Name()) + data, err := sqlFiles.ReadFile(path.Join("snapshot", entry.Name())) + if err != nil { + return err + } + + statements := extractStatements(string(data)) + if err := db.executeStatements(engine, statements); err != nil { + return err + } + } + + logger.Info("successfully created database schema from snapshot") + return nil +} + +func extractStatements(schema string) []string { + lines := strings.Split(schema, "\n") + + statements := make([]string, 0, len(lines)) + + sb := strings.Builder{} + for _, line := range lines { + line = strings.TrimSpace(line) + if strings.HasPrefix(line, "--") { + continue + } + + if line == "" { + continue + } + + endOfStatement := false + if strings.HasSuffix(line, ";") { + endOfStatement = true + line = strings.TrimSuffix(line, ";") + } + + if sb.Len() > 0 { + sb.WriteRune('\n') + } + sb.WriteString(line) + if endOfStatement && sb.Len() > 0 { + statements = append(statements, sb.String()) + sb.Reset() + } + } + + if sb.Len() > 0 { + statements = append(statements, sb.String()) + } + return statements +} + +func (s *MySQLDialect) executeStatements(engine *xorm.Engine, statements []string) error { + sess := engine.NewSession() + for _, s := range statements { + _, err := sess.Exec(s) + if err != nil { + return fmt.Errorf("statement %s failed with error: %v", s, err) + } + } + return nil +} diff --git a/pkg/services/sqlstore/migrator/snapshot/00-schema.sql b/pkg/services/sqlstore/migrator/snapshot/00-schema.sql new file mode 100644 index 00000000000..9907f7083cf --- /dev/null +++ b/pkg/services/sqlstore/migrator/snapshot/00-schema.sql @@ -0,0 +1,2325 @@ +-- MySQL dump 10.13 Distrib 8.4.5, for Linux (x86_64) +-- +-- Host: localhost Database: hg_dump +-- ------------------------------------------------------ +-- Server version 8.4.5 + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!50503 SET NAMES utf8mb4 */; +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + +-- +-- Table structure for table `alert` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `alert` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `version` bigint NOT NULL, + `dashboard_id` bigint NOT NULL, + `panel_id` bigint NOT NULL, + `org_id` bigint NOT NULL, + `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `message` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `state` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `settings` mediumtext COLLATE utf8mb4_unicode_ci, + `frequency` bigint NOT NULL, + `handler` bigint NOT NULL, + `severity` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `silenced` tinyint(1) NOT NULL, + `execution_error` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `eval_data` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `eval_date` datetime DEFAULT NULL, + `new_state_date` datetime NOT NULL, + `state_changes` int NOT NULL, + `created` datetime NOT NULL, + `updated` datetime NOT NULL, + `for` bigint DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `IDX_alert_org_id_id` (`org_id`,`id`), + KEY `IDX_alert_state` (`state`), + KEY `IDX_alert_dashboard_id` (`dashboard_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `alert_configuration` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `alert_configuration` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `alertmanager_configuration` mediumtext COLLATE utf8mb4_unicode_ci, + `configuration_version` varchar(3) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `created_at` int NOT NULL, + `default` tinyint(1) NOT NULL DEFAULT '0', + `org_id` bigint NOT NULL DEFAULT '0', + `configuration_hash` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'not-yet-calculated', + PRIMARY KEY (`id`), + UNIQUE KEY `UQE_alert_configuration_org_id` (`org_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `alert_configuration_history` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `alert_configuration_history` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `org_id` bigint NOT NULL DEFAULT '0', + `alertmanager_configuration` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `configuration_hash` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'not-yet-calculated', + `configuration_version` varchar(3) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `created_at` int NOT NULL, + `default` tinyint(1) NOT NULL DEFAULT '0', + `last_applied` int NOT NULL DEFAULT '0', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `alert_image` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `alert_image` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `token` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `path` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `url` varchar(2048) COLLATE utf8mb4_unicode_ci NOT NULL, + `created_at` datetime NOT NULL, + `expires_at` datetime NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `UQE_alert_image_token` (`token`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `alert_instance` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `alert_instance` ( + `rule_org_id` bigint NOT NULL, + `rule_uid` varchar(40) COLLATE utf8mb4_unicode_ci NOT NULL, + `labels` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `labels_hash` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `current_state` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `current_state_since` bigint NOT NULL, + `last_eval_time` bigint NOT NULL, + `current_state_end` bigint NOT NULL DEFAULT '0', + `current_reason` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `result_fingerprint` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `resolved_at` bigint DEFAULT NULL, + `last_sent_at` bigint DEFAULT NULL, + `fired_at` bigint DEFAULT NULL, + `annotations` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + PRIMARY KEY (`rule_org_id`,`rule_uid`,`labels_hash`), + KEY `IDX_alert_instance_rule_org_id_rule_uid_current_state` (`rule_org_id`,`rule_uid`,`current_state`), + KEY `IDX_alert_instance_rule_org_id_current_state` (`rule_org_id`,`current_state`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `alert_notification` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `alert_notification` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `org_id` bigint NOT NULL, + `name` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `settings` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `created` datetime NOT NULL, + `updated` datetime NOT NULL, + `is_default` tinyint(1) NOT NULL DEFAULT '0', + `frequency` bigint DEFAULT NULL, + `send_reminder` tinyint(1) DEFAULT '0', + `disable_resolve_message` tinyint(1) NOT NULL DEFAULT '0', + `uid` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `secure_settings` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + PRIMARY KEY (`id`), + UNIQUE KEY `UQE_alert_notification_org_id_uid` (`org_id`,`uid`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `alert_notification_state` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `alert_notification_state` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `org_id` bigint NOT NULL, + `alert_id` bigint NOT NULL, + `notifier_id` bigint NOT NULL, + `state` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `version` bigint NOT NULL, + `updated_at` bigint NOT NULL, + `alert_rule_state_updated_version` bigint NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `UQE_alert_notification_state_org_id_alert_id_notifier_id` (`org_id`,`alert_id`,`notifier_id`), + KEY `IDX_alert_notification_state_alert_id` (`alert_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `alert_rule` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `alert_rule` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `org_id` bigint NOT NULL, + `title` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `condition` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `data` mediumtext COLLATE utf8mb4_unicode_ci, + `updated` datetime NOT NULL, + `interval_seconds` bigint NOT NULL DEFAULT '60', + `version` int NOT NULL DEFAULT '0', + `uid` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '0', + `namespace_uid` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, + `rule_group` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, + `no_data_state` varchar(15) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'NoData', + `exec_err_state` varchar(15) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'Alerting', + `for` bigint NOT NULL DEFAULT '0', + `annotations` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `labels` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `dashboard_uid` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `panel_id` bigint DEFAULT NULL, + `rule_group_idx` bigint DEFAULT NULL, + `is_paused` tinyint(1) NOT NULL DEFAULT '0', + `notification_settings` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `record` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `metadata` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `updated_by` varchar(190) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `guid` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '', + `keep_firing_for` bigint NOT NULL DEFAULT '0', + `missing_series_evals_to_resolve` smallint DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `UQE_alert_rule_org_id_uid` (`org_id`,`uid`), + UNIQUE KEY `UQE_alert_rule_guid` (`guid`), + KEY `IDX_alert_rule_org_id_namespace_uid_rule_group` (`org_id`,`namespace_uid`,`rule_group`), + KEY `IDX_alert_rule_org_id_dashboard_uid_panel_id` (`org_id`,`dashboard_uid`,`panel_id`), + KEY `IDX_alert_rule_org_id_namespace_uid_rule_group_rule_group_idx` (`org_id`,`namespace_uid`,`rule_group`,`rule_group_idx`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `alert_rule_state` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `alert_rule_state` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `org_id` bigint NOT NULL, + `rule_uid` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `data` longblob NOT NULL, + `updated_at` datetime NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `UQE_alert_rule_state_org_id_rule_uid` (`org_id`,`rule_uid`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `alert_rule_tag` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `alert_rule_tag` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `alert_id` bigint NOT NULL, + `tag_id` bigint NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `UQE_alert_rule_tag_alert_id_tag_id` (`alert_id`,`tag_id`), + KEY `IDX_alert_rule_tag_alert_id` (`alert_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `alert_rule_version` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `alert_rule_version` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `rule_org_id` bigint NOT NULL, + `rule_uid` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '0', + `rule_namespace_uid` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `rule_group` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `parent_version` int NOT NULL, + `restored_from` int NOT NULL, + `version` int NOT NULL, + `created` datetime NOT NULL, + `title` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `condition` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `data` mediumtext COLLATE utf8mb4_unicode_ci, + `interval_seconds` bigint NOT NULL, + `no_data_state` varchar(15) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'NoData', + `exec_err_state` varchar(15) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'Alerting', + `for` bigint NOT NULL DEFAULT '0', + `annotations` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `labels` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `rule_group_idx` bigint DEFAULT NULL, + `is_paused` tinyint(1) NOT NULL DEFAULT '0', + `message` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `notification_settings` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `record` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `metadata` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `created_by` varchar(190) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `rule_guid` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '', + `keep_firing_for` bigint NOT NULL DEFAULT '0', + `missing_series_evals_to_resolve` smallint DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `UQE_alert_rule_version_rule_org_id_rule_uid_rule_guid_version` (`rule_org_id`,`rule_uid`,`rule_guid`,`version`), + UNIQUE KEY `UQE_alert_rule_version_rule_guid_version` (`rule_guid`,`version`), + KEY `IDX_alert_rule_version_rule_org_id_rule_namespace_uid_rule_group` (`rule_org_id`,`rule_namespace_uid`,`rule_group`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `annotation` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `annotation` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `org_id` bigint NOT NULL, + `alert_id` bigint DEFAULT NULL, + `user_id` bigint DEFAULT NULL, + `dashboard_id` bigint DEFAULT NULL, + `panel_id` bigint DEFAULT NULL, + `category_id` bigint DEFAULT NULL, + `type` varchar(25) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `title` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `text` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `metric` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `prev_state` varchar(40) COLLATE utf8mb4_unicode_ci NOT NULL, + `new_state` varchar(40) COLLATE utf8mb4_unicode_ci NOT NULL, + `data` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `epoch` bigint NOT NULL, + `region_id` bigint DEFAULT '0', + `tags` varchar(4096) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `created` bigint DEFAULT '0', + `updated` bigint DEFAULT '0', + `epoch_end` bigint NOT NULL DEFAULT '0', + `dashboard_uid` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `IDX_annotation_org_id_alert_id` (`org_id`,`alert_id`), + KEY `IDX_annotation_org_id_type` (`org_id`,`type`), + KEY `IDX_annotation_org_id_created` (`org_id`,`created`), + KEY `IDX_annotation_org_id_updated` (`org_id`,`updated`), + KEY `IDX_annotation_org_id_dashboard_id_epoch_end_epoch` (`org_id`,`dashboard_id`,`epoch_end`,`epoch`), + KEY `IDX_annotation_org_id_epoch_end_epoch` (`org_id`,`epoch_end`,`epoch`), + KEY `IDX_annotation_alert_id` (`alert_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `annotation_tag` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `annotation_tag` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `annotation_id` bigint NOT NULL, + `tag_id` bigint NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `UQE_annotation_tag_annotation_id_tag_id` (`annotation_id`,`tag_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `anon_device` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `anon_device` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `client_ip` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `created_at` datetime NOT NULL, + `device_id` varchar(127) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `updated_at` datetime NOT NULL, + `user_agent` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `UQE_anon_device_device_id` (`device_id`), + KEY `IDX_anon_device_updated_at` (`updated_at`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `api_key` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `api_key` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `org_id` bigint NOT NULL, + `name` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `key` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `role` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `created` datetime NOT NULL, + `updated` datetime NOT NULL, + `expires` bigint DEFAULT NULL, + `service_account_id` bigint DEFAULT NULL, + `last_used_at` datetime DEFAULT NULL, + `is_revoked` tinyint(1) DEFAULT '0', + PRIMARY KEY (`id`), + UNIQUE KEY `UQE_api_key_key` (`key`), + UNIQUE KEY `UQE_api_key_org_id_name` (`org_id`,`name`), + KEY `IDX_api_key_org_id` (`org_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `builtin_role` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `builtin_role` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `role` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `role_id` bigint NOT NULL, + `created` datetime NOT NULL, + `updated` datetime NOT NULL, + `org_id` bigint NOT NULL DEFAULT '0', + PRIMARY KEY (`id`), + UNIQUE KEY `UQE_builtin_role_org_id_role_id_role` (`org_id`,`role_id`,`role`), + KEY `IDX_builtin_role_role_id` (`role_id`), + KEY `IDX_builtin_role_role` (`role`), + KEY `IDX_builtin_role_org_id` (`org_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `cache_data` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `cache_data` ( + `cache_key` varchar(168) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `data` blob NOT NULL, + `expires` int NOT NULL, + `created_at` int NOT NULL, + PRIMARY KEY (`cache_key`), + UNIQUE KEY `UQE_cache_data_cache_key` (`cache_key`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `cloud_migration_resource` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `cloud_migration_resource` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `uid` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `resource_type` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `resource_uid` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL, + `status` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `error_string` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `snapshot_uid` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `name` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `parent_name` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `error_code` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `cloud_migration_session` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `cloud_migration_session` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `uid` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `auth_token` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `slug` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `stack_id` bigint NOT NULL, + `region_slug` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `cluster_slug` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `created` datetime NOT NULL, + `updated` datetime NOT NULL, + `org_id` bigint NOT NULL DEFAULT '1', + PRIMARY KEY (`id`), + UNIQUE KEY `UQE_cloud_migration_session_uid` (`uid`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `cloud_migration_snapshot` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `cloud_migration_snapshot` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `uid` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `session_uid` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `created` datetime NOT NULL, + `updated` datetime NOT NULL, + `finished` datetime DEFAULT NULL, + `upload_url` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `status` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `local_directory` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `gms_snapshot_uid` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `encryption_key` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `error_string` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `resource_storage_type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `encryption_algo` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `metadata` blob, + `public_key` blob, + PRIMARY KEY (`id`), + UNIQUE KEY `UQE_cloud_migration_snapshot_uid` (`uid`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `cloud_migration_snapshot_partition` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `cloud_migration_snapshot_partition` ( + `snapshot_uid` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `partition_number` int NOT NULL, + `resource_type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `data` longblob NOT NULL, + PRIMARY KEY (`snapshot_uid`,`resource_type`,`partition_number`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `correlation` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `correlation` ( + `uid` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `org_id` bigint NOT NULL DEFAULT '0', + `source_uid` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `target_uid` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `label` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `config` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `provisioned` tinyint(1) NOT NULL DEFAULT '0', + `type` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'query', + PRIMARY KEY (`uid`,`org_id`,`source_uid`), + KEY `IDX_correlation_uid` (`uid`), + KEY `IDX_correlation_source_uid` (`source_uid`), + KEY `IDX_correlation_org_id` (`org_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `dashboard` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `dashboard` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `version` int NOT NULL, + `slug` varchar(189) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `title` varchar(189) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `data` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `org_id` bigint NOT NULL, + `created` datetime NOT NULL, + `updated` datetime NOT NULL, + `updated_by` int DEFAULT NULL, + `created_by` int DEFAULT NULL, + `gnet_id` bigint DEFAULT NULL, + `plugin_id` varchar(189) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `folder_id` bigint NOT NULL DEFAULT '0', + `is_folder` tinyint(1) NOT NULL DEFAULT '0', + `has_acl` tinyint(1) NOT NULL DEFAULT '0', + `uid` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `is_public` tinyint(1) NOT NULL DEFAULT '0', + `deleted` datetime DEFAULT NULL, + `api_version` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `folder_uid` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `UQE_dashboard_org_id_uid` (`org_id`,`uid`), + KEY `IDX_dashboard_org_id` (`org_id`), + KEY `IDX_dashboard_gnet_id` (`gnet_id`), + KEY `IDX_dashboard_org_id_plugin_id` (`org_id`,`plugin_id`), + KEY `IDX_dashboard_title` (`title`), + KEY `IDX_dashboard_is_folder` (`is_folder`), + KEY `IDX_dashboard_deleted` (`deleted`), + KEY `IDX_dashboard_org_id_folder_id_title` (`org_id`,`folder_id`,`title`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `dashboard_acl` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `dashboard_acl` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `org_id` bigint NOT NULL, + `dashboard_id` bigint NOT NULL, + `user_id` bigint DEFAULT NULL, + `team_id` bigint DEFAULT NULL, + `permission` smallint NOT NULL DEFAULT '4', + `role` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `created` datetime NOT NULL, + `updated` datetime NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `UQE_dashboard_acl_dashboard_id_user_id` (`dashboard_id`,`user_id`), + UNIQUE KEY `UQE_dashboard_acl_dashboard_id_team_id` (`dashboard_id`,`team_id`), + KEY `IDX_dashboard_acl_dashboard_id` (`dashboard_id`), + KEY `IDX_dashboard_acl_user_id` (`user_id`), + KEY `IDX_dashboard_acl_team_id` (`team_id`), + KEY `IDX_dashboard_acl_org_id_role` (`org_id`,`role`), + KEY `IDX_dashboard_acl_permission` (`permission`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `dashboard_provisioning` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `dashboard_provisioning` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `dashboard_id` bigint DEFAULT NULL, + `name` varchar(150) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `external_id` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `updated` int NOT NULL DEFAULT '0', + `check_sum` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `IDX_dashboard_provisioning_dashboard_id` (`dashboard_id`), + KEY `IDX_dashboard_provisioning_dashboard_id_name` (`dashboard_id`,`name`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `dashboard_public` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `dashboard_public` ( + `uid` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `dashboard_uid` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `org_id` bigint NOT NULL, + `time_settings` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `template_variables` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `access_token` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `created_by` int NOT NULL, + `updated_by` int DEFAULT NULL, + `created_at` datetime NOT NULL, + `updated_at` datetime DEFAULT NULL, + `is_enabled` tinyint(1) NOT NULL DEFAULT '0', + `annotations_enabled` tinyint(1) NOT NULL DEFAULT '0', + `time_selection_enabled` tinyint(1) NOT NULL DEFAULT '0', + `share` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'public', + PRIMARY KEY (`uid`), + UNIQUE KEY `UQE_dashboard_public_config_uid` (`uid`), + UNIQUE KEY `UQE_dashboard_public_config_access_token` (`access_token`), + KEY `IDX_dashboard_public_config_org_id_dashboard_uid` (`org_id`,`dashboard_uid`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `dashboard_public_email_share` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `dashboard_public_email_share` ( + `uid` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `public_dashboard_uid` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `recipient` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `type` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'email', + `created_at` datetime NOT NULL, + `updated_at` datetime NOT NULL, + PRIMARY KEY (`uid`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `dashboard_public_magic_link` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `dashboard_public_magic_link` ( + `uid` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `token_uuid` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `public_dashboard_uid` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `email` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `created_at` datetime NOT NULL, + `updated_at` datetime NOT NULL, + PRIMARY KEY (`uid`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `dashboard_public_session` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `dashboard_public_session` ( + `uid` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `cookie_uuid` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `public_dashboard_uid` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `email` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `created_at` datetime NOT NULL, + `updated_at` datetime NOT NULL, + `last_seen_at` datetime DEFAULT NULL, + PRIMARY KEY (`uid`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `dashboard_public_usage_by_day` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `dashboard_public_usage_by_day` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `public_dashboard_uid` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `day` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `views` bigint NOT NULL, + `queries` bigint NOT NULL, + `errors` bigint NOT NULL, + `load_duration` float NOT NULL, + `cached_queries` bigint NOT NULL DEFAULT '0', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `dashboard_snapshot` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `dashboard_snapshot` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `key` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `delete_key` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `org_id` bigint NOT NULL, + `user_id` bigint NOT NULL, + `external` tinyint(1) NOT NULL, + `external_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `dashboard` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `expires` datetime NOT NULL, + `created` datetime NOT NULL, + `updated` datetime NOT NULL, + `external_delete_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `dashboard_encrypted` mediumblob, + PRIMARY KEY (`id`), + UNIQUE KEY `UQE_dashboard_snapshot_key` (`key`), + UNIQUE KEY `UQE_dashboard_snapshot_delete_key` (`delete_key`), + KEY `IDX_dashboard_snapshot_user_id` (`user_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `dashboard_tag` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `dashboard_tag` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `dashboard_id` bigint NOT NULL, + `term` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `dashboard_uid` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `org_id` bigint DEFAULT '1', + PRIMARY KEY (`id`), + KEY `IDX_dashboard_tag_dashboard_id` (`dashboard_id`), + KEY `IDX_dashboard_tag_dashboard_uid` (`dashboard_uid`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `dashboard_usage_by_day` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `dashboard_usage_by_day` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `dashboard_id` bigint NOT NULL, + `day` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `views` bigint NOT NULL, + `queries` bigint NOT NULL, + `errors` bigint NOT NULL, + `load_duration` float NOT NULL, + `cached_queries` bigint NOT NULL DEFAULT '0', + `dashboard_uid` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `org_id` bigint DEFAULT '1', + PRIMARY KEY (`id`), + UNIQUE KEY `UQE_dashboard_usage_by_day_dashboard_id_day` (`dashboard_id`,`day`), + UNIQUE KEY `UQE_dashboard_usage_by_day_dashboard_uid_org_id_day` (`dashboard_uid`,`org_id`,`day`), + KEY `IDX_dashboard_usage_by_day_dashboard_id` (`dashboard_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `dashboard_usage_sums` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `dashboard_usage_sums` ( + `dashboard_id` bigint NOT NULL, + `updated` datetime NOT NULL, + `views_last_1_days` bigint NOT NULL, + `views_last_7_days` bigint NOT NULL, + `views_last_30_days` bigint NOT NULL, + `views_total` bigint NOT NULL, + `queries_last_1_days` bigint NOT NULL, + `queries_last_7_days` bigint NOT NULL, + `queries_last_30_days` bigint NOT NULL, + `queries_total` bigint NOT NULL, + `errors_last_1_days` bigint NOT NULL DEFAULT '0', + `errors_last_7_days` bigint NOT NULL DEFAULT '0', + `errors_last_30_days` bigint NOT NULL DEFAULT '0', + `errors_total` bigint NOT NULL DEFAULT '0', + `dashboard_uid` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `org_id` bigint DEFAULT '1', + PRIMARY KEY (`dashboard_id`), + UNIQUE KEY `UQE_dashboard_usage_sums_org_id_dashboard_uid` (`org_id`,`dashboard_uid`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `dashboard_version` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `dashboard_version` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `dashboard_id` bigint NOT NULL, + `parent_version` int NOT NULL, + `restored_from` int NOT NULL, + `version` int NOT NULL, + `created` datetime NOT NULL, + `created_by` bigint NOT NULL, + `message` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `data` mediumtext COLLATE utf8mb4_unicode_ci, + `api_version` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `UQE_dashboard_version_dashboard_id_version` (`dashboard_id`,`version`), + KEY `IDX_dashboard_version_dashboard_id` (`dashboard_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `data_keys` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `data_keys` ( + `name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `active` tinyint(1) NOT NULL, + `scope` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `provider` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `encrypted_data` blob NOT NULL, + `created` datetime NOT NULL, + `updated` datetime NOT NULL, + `label` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + PRIMARY KEY (`name`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `data_source` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `data_source` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `org_id` bigint NOT NULL, + `version` int NOT NULL, + `type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `name` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `access` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `user` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `database` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `basic_auth` tinyint(1) NOT NULL, + `basic_auth_user` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `basic_auth_password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `is_default` tinyint(1) NOT NULL, + `json_data` mediumtext COLLATE utf8mb4_unicode_ci, + `created` datetime NOT NULL, + `updated` datetime NOT NULL, + `with_credentials` tinyint(1) NOT NULL DEFAULT '0', + `secure_json_data` mediumtext COLLATE utf8mb4_unicode_ci, + `read_only` tinyint(1) DEFAULT NULL, + `uid` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '0', + `is_prunable` tinyint(1) DEFAULT '0', + `api_version` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `UQE_data_source_org_id_name` (`org_id`,`name`), + UNIQUE KEY `UQE_data_source_org_id_uid` (`org_id`,`uid`), + KEY `IDX_data_source_org_id` (`org_id`), + KEY `IDX_data_source_org_id_is_default` (`org_id`,`is_default`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `data_source_acl` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `data_source_acl` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `org_id` bigint NOT NULL, + `data_source_id` bigint NOT NULL, + `team_id` bigint NOT NULL, + `user_id` bigint NOT NULL, + `permission` smallint NOT NULL, + `created` datetime NOT NULL, + `updated` datetime NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `UQE_data_source_acl_data_source_id_team_id_user_id` (`data_source_id`,`team_id`,`user_id`), + KEY `IDX_data_source_acl_data_source_id` (`data_source_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `data_source_cache` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `data_source_cache` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `data_source_id` bigint NOT NULL, + `enabled` tinyint(1) NOT NULL, + `ttl_ms` bigint NOT NULL, + `created` datetime NOT NULL, + `updated` datetime NOT NULL, + `use_default_ttl` tinyint(1) NOT NULL DEFAULT '1', + `data_source_uid` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '0', + `ttl_resources_ms` bigint NOT NULL DEFAULT '300000', + PRIMARY KEY (`id`), + KEY `IDX_data_source_cache_data_source_id` (`data_source_id`), + KEY `IDX_data_source_cache_data_source_uid` (`data_source_uid`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `data_source_usage_by_day` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `data_source_usage_by_day` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `data_source_id` bigint NOT NULL, + `day` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `queries` bigint NOT NULL, + `errors` bigint NOT NULL, + `load_duration_ms` bigint NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `UQE_data_source_usage_by_day_data_source_id_day` (`data_source_id`,`day`), + KEY `IDX_data_source_usage_by_day_data_source_id` (`data_source_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `entity_event` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `entity_event` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `entity_id` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `event_type` varchar(8) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `created` bigint NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `file` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `file` ( + `path` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `path_hash` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `parent_folder_path_hash` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `contents` mediumblob, + `etag` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `cache_control` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `content_disposition` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `updated` datetime NOT NULL, + `created` datetime NOT NULL, + `size` bigint NOT NULL, + `mime_type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + PRIMARY KEY (`path_hash`), + KEY `IDX_file_parent_folder_path_hash` (`parent_folder_path_hash`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `file_meta` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `file_meta` ( + `path_hash` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `key` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `value` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + PRIMARY KEY (`path_hash`,`key`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `folder` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `folder` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `uid` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `org_id` bigint NOT NULL, + `title` varchar(189) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `parent_uid` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `created` datetime NOT NULL, + `updated` datetime NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `UQE_folder_org_id_uid` (`org_id`,`uid`), + KEY `IDX_folder_org_id_parent_uid` (`org_id`,`parent_uid`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `kv_store` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `kv_store` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `org_id` bigint NOT NULL, + `namespace` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `key` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `value` longtext COLLATE utf8mb4_unicode_ci NOT NULL, + `created` datetime NOT NULL, + `updated` datetime NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `UQE_kv_store_org_id_namespace_key` (`org_id`,`namespace`,`key`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `library_element` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `library_element` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `org_id` bigint NOT NULL, + `folder_id` bigint NOT NULL, + `uid` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `name` varchar(150) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `kind` bigint NOT NULL, + `type` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `description` varchar(2048) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `model` mediumtext COLLATE utf8mb4_unicode_ci NOT NULL, + `created` datetime NOT NULL, + `created_by` bigint NOT NULL, + `updated` datetime NOT NULL, + `updated_by` bigint NOT NULL, + `version` bigint NOT NULL, + `folder_uid` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `UQE_library_element_org_id_uid` (`org_id`,`uid`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `library_element_connection` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `library_element_connection` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `element_id` bigint NOT NULL, + `kind` bigint NOT NULL, + `connection_id` bigint NOT NULL, + `created` datetime NOT NULL, + `created_by` bigint NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `UQE_library_element_connection_element_id_kind_connection_id` (`element_id`,`kind`,`connection_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `license_token` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `license_token` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `token` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `created` datetime NOT NULL, + `updated` datetime NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `login_attempt` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `login_attempt` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `username` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `ip_address` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `created` bigint DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `IDX_login_attempt_username` (`username`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `migration_log` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `migration_log` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `migration_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `sql` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `success` tinyint(1) NOT NULL, + `error` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `timestamp` datetime NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `ngalert_configuration` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `ngalert_configuration` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `org_id` bigint NOT NULL, + `alertmanagers` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `created_at` int NOT NULL, + `updated_at` int NOT NULL, + `send_alerts_to` smallint NOT NULL DEFAULT '0', + PRIMARY KEY (`id`), + UNIQUE KEY `UQE_ngalert_configuration_org_id` (`org_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `org` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `org` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `version` int NOT NULL, + `name` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `address1` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `address2` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `city` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `state` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `zip_code` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `country` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `billing_email` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `created` datetime NOT NULL, + `updated` datetime NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `UQE_org_name` (`name`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `org_user` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `org_user` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `org_id` bigint NOT NULL, + `user_id` bigint NOT NULL, + `role` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `created` datetime NOT NULL, + `updated` datetime NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `UQE_org_user_org_id_user_id` (`org_id`,`user_id`), + KEY `IDX_org_user_org_id` (`org_id`), + KEY `IDX_org_user_user_id` (`user_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `permission` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `permission` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `role_id` bigint NOT NULL, + `action` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `scope` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `created` datetime NOT NULL, + `updated` datetime NOT NULL, + `kind` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '', + `attribute` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '', + `identifier` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '', + PRIMARY KEY (`id`), + UNIQUE KEY `UQE_permission_action_scope_role_id` (`action`,`scope`,`role_id`), + KEY `IDX_permission_identifier` (`identifier`), + KEY `IDX_permission_role_id_action` (`role_id`,`action`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `playlist` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `playlist` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `interval` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `org_id` bigint NOT NULL, + `created_at` bigint NOT NULL DEFAULT '0', + `updated_at` bigint NOT NULL DEFAULT '0', + `uid` varchar(80) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '0', + PRIMARY KEY (`id`), + UNIQUE KEY `UQE_playlist_org_id_uid` (`org_id`,`uid`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `playlist_item` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `playlist_item` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `playlist_id` bigint NOT NULL, + `type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `value` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `title` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `order` int NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `plugin_setting` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `plugin_setting` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `org_id` bigint NOT NULL DEFAULT '1', + `plugin_id` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `enabled` tinyint(1) NOT NULL, + `pinned` tinyint(1) NOT NULL, + `json_data` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `secure_json_data` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `created` datetime NOT NULL, + `updated` datetime NOT NULL, + `plugin_version` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `UQE_plugin_setting_org_id_plugin_id` (`org_id`,`plugin_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `preferences` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `preferences` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `org_id` bigint NOT NULL, + `user_id` bigint NOT NULL, + `version` int NOT NULL, + `home_dashboard_id` bigint NOT NULL, + `timezone` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `theme` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `created` datetime NOT NULL, + `updated` datetime NOT NULL, + `team_id` bigint DEFAULT NULL, + `week_start` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `json_data` mediumtext COLLATE utf8mb4_unicode_ci, + `home_dashboard_uid` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `IDX_preferences_org_id` (`org_id`), + KEY `IDX_preferences_user_id` (`user_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `provenance_type` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `provenance_type` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `org_id` bigint NOT NULL, + `record_key` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `record_type` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `provenance` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `UQE_provenance_type_record_type_record_key_org_id` (`record_type`,`record_key`,`org_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `query_history` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `query_history` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `uid` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `org_id` bigint NOT NULL, + `datasource_uid` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `created_by` bigint DEFAULT NULL, + `created_at` int NOT NULL, + `comment` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `queries` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + PRIMARY KEY (`id`), + KEY `IDX_query_history_org_id_created_by_datasource_uid` (`org_id`,`created_by`,`datasource_uid`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `query_history_details` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `query_history_details` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `query_history_item_uid` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `datasource_uid` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `query_history_star` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `query_history_star` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `query_uid` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `user_id` bigint DEFAULT NULL, + `org_id` bigint NOT NULL DEFAULT '1', + PRIMARY KEY (`id`), + UNIQUE KEY `UQE_query_history_star_user_id_query_uid` (`user_id`,`query_uid`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `quota` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `quota` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `org_id` bigint DEFAULT NULL, + `user_id` bigint DEFAULT NULL, + `target` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `limit` bigint NOT NULL, + `created` datetime NOT NULL, + `updated` datetime NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `UQE_quota_org_id_user_id_target` (`org_id`,`user_id`,`target`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `recording_rules` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `recording_rules` ( + `id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `target_ref_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `org_id` bigint NOT NULL, + `interval` bigint NOT NULL, + `range` bigint NOT NULL, + `active` tinyint(1) NOT NULL DEFAULT '0', + `count` tinyint(1) NOT NULL DEFAULT '0', + `queries` blob NOT NULL, + `created_at` datetime NOT NULL, + `prom_name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + PRIMARY KEY (`id`,`target_ref_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `remote_write_targets` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `remote_write_targets` ( + `id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `data_source_uid` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `write_path` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `org_id` int NOT NULL, + PRIMARY KEY (`id`,`data_source_uid`,`write_path`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `report` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `report` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `user_id` bigint NOT NULL, + `org_id` bigint NOT NULL, + `dashboard_id` bigint NOT NULL, + `name` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `recipients` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `reply_to` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `message` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `schedule_frequency` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `schedule_day` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `schedule_hour` bigint NOT NULL, + `schedule_minute` bigint NOT NULL, + `created` datetime NOT NULL, + `updated` datetime NOT NULL, + `schedule_timezone` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'Europe/Stockholm', + `time_from` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `time_to` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `pdf_landscape` tinyint(1) DEFAULT NULL, + `schedule_day_of_month` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `pdf_layout` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `pdf_orientation` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `dashboard_uid` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `template_vars` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `enable_dashboard_url` tinyint(1) DEFAULT NULL, + `state` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `enable_csv` tinyint(1) DEFAULT NULL, + `schedule_start` int DEFAULT NULL, + `schedule_end` int DEFAULT NULL, + `schedule_interval_frequency` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `schedule_interval_amount` bigint DEFAULT NULL, + `schedule_workdays_only` tinyint(1) DEFAULT NULL, + `formats` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '["pdf"]', + `scale_factor` smallint NOT NULL DEFAULT '2', + `uid` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `pdf_show_template_variables` tinyint(1) NOT NULL DEFAULT '0', + `pdf_combine_one_file` tinyint(1) NOT NULL DEFAULT '1', + `subject` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + PRIMARY KEY (`id`), + UNIQUE KEY `UQE_report_org_id_uid` (`org_id`,`uid`), + KEY `IDX_report_user_id` (`user_id`), + KEY `IDX_report_dashboard_id` (`dashboard_id`), + KEY `IDX_report_org_id` (`org_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `report_dashboards` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `report_dashboards` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `report_id` bigint NOT NULL, + `dashboard_uid` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '', + `report_variables` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `time_to` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `time_from` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `created` datetime DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `IDX_report_dashboards_report_id` (`report_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `report_settings` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `report_settings` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `user_id` bigint NOT NULL, + `org_id` bigint NOT NULL, + `branding_report_logo_url` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `branding_email_logo_url` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `branding_email_footer_link` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `branding_email_footer_text` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `branding_email_footer_mode` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `pdf_theme` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'light', + `embedded_image_theme` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'dark', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `resource` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `resource` ( + `guid` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `resource_version` bigint DEFAULT NULL, + `group` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `resource` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `namespace` varchar(63) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `name` varchar(253) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `value` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `action` int NOT NULL, + `label_set` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `previous_resource_version` bigint DEFAULT NULL, + `folder` varchar(253) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '', + PRIMARY KEY (`guid`), + UNIQUE KEY `UQE_resource_namespace_group_resource_name` (`namespace`,`group`,`resource`,`name`), + KEY `IDX_resource_group_resource` (`group`,`resource`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `resource_blob` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `resource_blob` ( + `uuid` char(36) COLLATE utf8mb4_unicode_ci NOT NULL, + `created` datetime NOT NULL, + `group` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `resource` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `namespace` varchar(63) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `name` varchar(253) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `value` longblob NOT NULL, + `hash` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `content_type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + PRIMARY KEY (`uuid`), + KEY `IDX_resource_history_namespace_group_name` (`namespace`,`group`,`resource`,`name`), + KEY `IDX_resource_blob_created` (`created`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `resource_events` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `resource_events` ( + `key_path` varchar(2048) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL, + `value` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + PRIMARY KEY (`key_path`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `resource_history` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `resource_history` ( + `guid` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `resource_version` bigint DEFAULT NULL, + `group` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `resource` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `namespace` varchar(63) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `name` varchar(253) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `value` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `action` int NOT NULL, + `label_set` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `previous_resource_version` bigint DEFAULT NULL, + `folder` varchar(253) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '', + `generation` bigint NOT NULL DEFAULT '0', + `key_path` varchar(2048) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL DEFAULT '', + PRIMARY KEY (`guid`), + UNIQUE KEY `UQE_resource_history_namespace_group_name_version` (`namespace`,`group`,`resource`,`name`,`resource_version`), + KEY `IDX_resource_history_resource_version` (`resource_version`), + KEY `IDX_resource_history_group_resource_resource_version` (`group`,`resource`,`resource_version`), + KEY `IDX_resource_history_namespace_group_resource_action_version` (`namespace`,`group`,`resource`,`action`,`resource_version`), + KEY `IDX_resource_history_namespace_group_resource_name_generation` (`namespace`,`group`,`resource`,`name`,`generation`), + KEY `IDX_resource_history_key_path` (`key_path`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `resource_last_import_time` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `resource_last_import_time` ( + `group` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `resource` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `namespace` varchar(63) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `last_import_time` datetime NOT NULL, + PRIMARY KEY (`group`,`resource`,`namespace`), + KEY `UQE_resource_last_import_time_last_import_time` (`last_import_time`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `resource_migration_log` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `resource_migration_log` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `migration_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `sql` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `success` tinyint(1) NOT NULL, + `error` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `timestamp` datetime NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `resource_version` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `resource_version` ( + `group` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `resource` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `resource_version` bigint NOT NULL, + PRIMARY KEY (`group`,`resource`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `role` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `role` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `name` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `version` bigint NOT NULL, + `org_id` bigint NOT NULL, + `uid` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `created` datetime NOT NULL, + `updated` datetime NOT NULL, + `display_name` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `group_name` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `hidden` tinyint(1) NOT NULL DEFAULT '0', + PRIMARY KEY (`id`), + UNIQUE KEY `UQE_role_org_id_name` (`org_id`,`name`), + UNIQUE KEY `UQE_role_uid` (`uid`), + KEY `IDX_role_org_id` (`org_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `secret_data_key` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `secret_data_key` ( + `uid` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `namespace` varchar(253) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `label` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `active` tinyint(1) NOT NULL, + `provider` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `encrypted_data` blob NOT NULL, + `created` datetime NOT NULL, + `updated` datetime NOT NULL, + PRIMARY KEY (`uid`), + KEY `IDX_secret_data_key_namespace_label_active` (`namespace`,`label`,`active`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `secret_encrypted_value` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `secret_encrypted_value` ( + `namespace` varchar(253) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `name` varchar(253) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `version` bigint NOT NULL, + `encrypted_data` blob NOT NULL, + `created` bigint NOT NULL, + `updated` bigint NOT NULL, + `data_key_id` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '', + PRIMARY KEY (`namespace`,`name`,`version`), + KEY `IDX_secret_encrypted_value_data_key_id` (`data_key_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `secret_keeper` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `secret_keeper` ( + `guid` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `name` varchar(253) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `namespace` varchar(253) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `annotations` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `labels` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `created` bigint NOT NULL, + `created_by` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `updated` bigint NOT NULL, + `updated_by` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `description` varchar(253) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `type` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `payload` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `active` tinyint(1) NOT NULL DEFAULT '0', + PRIMARY KEY (`guid`), + UNIQUE KEY `UQE_secret_keeper_namespace_name` (`namespace`,`name`), + KEY `IDX_secret_keeper_namespace_name_active` (`namespace`,`name`,`active`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `secret_migration_log` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `secret_migration_log` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `migration_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `sql` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `success` tinyint(1) NOT NULL, + `error` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `timestamp` datetime NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `secret_secure_value` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `secret_secure_value` ( + `guid` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `name` varchar(253) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `namespace` varchar(253) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `annotations` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `labels` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `created` bigint NOT NULL, + `created_by` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `updated` bigint NOT NULL, + `updated_by` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `external_id` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `active` tinyint(1) NOT NULL, + `version` bigint NOT NULL, + `description` varchar(253) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `keeper` varchar(253) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `decrypters` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `ref` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `owner_reference_api_group` varchar(253) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `owner_reference_api_version` varchar(253) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `owner_reference_kind` varchar(253) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `owner_reference_name` varchar(253) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `lease_token` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `lease_created` bigint NOT NULL DEFAULT '0', + PRIMARY KEY (`guid`), + UNIQUE KEY `UQE_secret_secure_value_namespace_name_version_active` (`namespace`,`name`,`version`,`active`), + UNIQUE KEY `UQE_secret_secure_value_namespace_name_version` (`namespace`,`name`,`version`), + KEY `IDX_secret_secure_value_namespace_active_updated` (`namespace`,`active`,`updated`), + KEY `IDX_secret_secure_value_lease_token` (`lease_token`), + KEY `IDX_secret_secure_value_lease_created` (`lease_created`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `secrets` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `secrets` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `org_id` bigint NOT NULL, + `namespace` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `value` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `created` datetime NOT NULL, + `updated` datetime NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `seed_assignment` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `seed_assignment` ( + `id` int NOT NULL AUTO_INCREMENT, + `builtin_role` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `role_name` varchar(190) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `action` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `scope` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `origin` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `UQE_seed_assignment_builtin_role_role_name` (`builtin_role`,`role_name`), + UNIQUE KEY `UQE_seed_assignment_builtin_role_action_scope` (`builtin_role`,`action`,`scope`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `server_lock` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `server_lock` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `operation_uid` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `version` bigint NOT NULL, + `last_execution` bigint NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `UQE_server_lock_operation_uid` (`operation_uid`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `session` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `session` ( + `key` char(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `data` blob NOT NULL, + `expiry` int NOT NULL, + PRIMARY KEY (`key`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `setting` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `setting` ( + `section` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `key` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `value` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `encrypted_value` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + PRIMARY KEY (`section`,`key`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `short_url` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `short_url` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `org_id` bigint NOT NULL, + `uid` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `path` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `created_by` bigint DEFAULT NULL, + `created_at` int NOT NULL, + `last_seen_at` int DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `UQE_short_url_org_id_uid` (`org_id`,`uid`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `signing_key` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `signing_key` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `key_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `private_key` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `added_at` datetime NOT NULL, + `expires_at` datetime DEFAULT NULL, + `alg` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `UQE_signing_key_key_id` (`key_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `sso_setting` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `sso_setting` ( + `id` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `provider` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `settings` mediumtext COLLATE utf8mb4_unicode_ci NOT NULL, + `created` datetime NOT NULL, + `updated` datetime NOT NULL, + `is_deleted` tinyint(1) NOT NULL DEFAULT '0', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `star` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `star` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `user_id` bigint NOT NULL, + `dashboard_id` bigint NOT NULL, + `dashboard_uid` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `org_id` bigint DEFAULT '1', + `updated` datetime DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `UQE_star_user_id_dashboard_id` (`user_id`,`dashboard_id`), + UNIQUE KEY `UQE_star_user_id_dashboard_uid_org_id` (`user_id`,`dashboard_uid`,`org_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `tag` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `tag` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `key` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `value` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `UQE_tag_key_value` (`key`,`value`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `team` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `team` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `name` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `org_id` bigint NOT NULL, + `created` datetime NOT NULL, + `updated` datetime NOT NULL, + `uid` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `external_uid` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `is_provisioned` tinyint(1) DEFAULT NULL, + `email` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `UQE_team_org_id_name` (`org_id`,`name`), + UNIQUE KEY `UQE_team_org_id_uid` (`org_id`,`uid`), + KEY `IDX_team_org_id` (`org_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `team_group` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `team_group` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `org_id` bigint NOT NULL, + `team_id` bigint NOT NULL, + `group_id` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `created` datetime NOT NULL, + `updated` datetime NOT NULL, + `uid` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `UQE_team_group_org_id_team_id_group_id` (`org_id`,`team_id`,`group_id`), + UNIQUE KEY `UQE_team_group_uid` (`uid`), + KEY `IDX_team_group_org_id` (`org_id`), + KEY `IDX_team_group_group_id` (`group_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `team_member` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `team_member` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `org_id` bigint NOT NULL, + `team_id` bigint NOT NULL, + `user_id` bigint NOT NULL, + `created` datetime NOT NULL, + `updated` datetime NOT NULL, + `external` tinyint(1) DEFAULT NULL, + `permission` smallint DEFAULT NULL, + `uid` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `UQE_team_member_org_id_team_id_user_id` (`org_id`,`team_id`,`user_id`), + UNIQUE KEY `UQE_team_member_uid` (`uid`), + KEY `IDX_team_member_org_id` (`org_id`), + KEY `IDX_team_member_team_id` (`team_id`), + KEY `IDX_team_member_user_id_org_id` (`user_id`,`org_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `team_role` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `team_role` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `org_id` bigint NOT NULL, + `team_id` bigint NOT NULL, + `role_id` bigint NOT NULL, + `created` datetime NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `UQE_team_role_org_id_team_id_role_id` (`org_id`,`team_id`,`role_id`), + KEY `IDX_team_role_org_id` (`org_id`), + KEY `IDX_team_role_team_id` (`team_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `temp_user` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `temp_user` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `org_id` bigint NOT NULL, + `version` int NOT NULL, + `email` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `role` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `code` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `status` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `invited_by_user_id` bigint DEFAULT NULL, + `email_sent` tinyint(1) NOT NULL, + `email_sent_on` datetime DEFAULT NULL, + `remote_addr` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `created` int NOT NULL DEFAULT '0', + `updated` int NOT NULL DEFAULT '0', + PRIMARY KEY (`id`), + KEY `IDX_temp_user_email` (`email`), + KEY `IDX_temp_user_org_id` (`org_id`), + KEY `IDX_temp_user_code` (`code`), + KEY `IDX_temp_user_status` (`status`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `test_data` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `test_data` ( + `id` int NOT NULL AUTO_INCREMENT, + `metric1` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `metric2` varchar(150) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `value_big_int` bigint DEFAULT NULL, + `value_double` double DEFAULT NULL, + `value_float` float DEFAULT NULL, + `value_int` int DEFAULT NULL, + `time_epoch` bigint NOT NULL, + `time_date_time` datetime NOT NULL, + `time_time_stamp` timestamp NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `unifiedstorage_migration_log` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `unifiedstorage_migration_log` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `migration_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `sql` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `success` tinyint(1) NOT NULL, + `error` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `timestamp` datetime NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `user` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `user` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `version` int NOT NULL, + `login` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `email` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `salt` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `rands` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `company` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `org_id` bigint NOT NULL, + `is_admin` tinyint(1) NOT NULL, + `email_verified` tinyint(1) DEFAULT NULL, + `theme` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `created` datetime NOT NULL, + `updated` datetime NOT NULL, + `help_flags1` bigint NOT NULL DEFAULT '0', + `last_seen_at` datetime DEFAULT NULL, + `is_disabled` tinyint(1) NOT NULL DEFAULT '0', + `is_service_account` tinyint(1) DEFAULT '0', + `uid` varchar(190) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `is_provisioned` tinyint(1) NOT NULL DEFAULT '0', + PRIMARY KEY (`id`), + UNIQUE KEY `UQE_user_login` (`login`), + UNIQUE KEY `UQE_user_email` (`email`), + UNIQUE KEY `UQE_user_uid` (`uid`), + KEY `IDX_user_login_email` (`login`,`email`), + KEY `IDX_user_is_service_account_last_seen_at` (`is_service_account`,`last_seen_at`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `user_auth` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `user_auth` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `user_id` bigint NOT NULL, + `auth_module` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `auth_id` varchar(190) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `created` datetime NOT NULL, + `o_auth_access_token` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `o_auth_refresh_token` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `o_auth_token_type` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `o_auth_expiry` datetime DEFAULT NULL, + `o_auth_id_token` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `external_uid` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + PRIMARY KEY (`id`), + KEY `IDX_user_auth_auth_module_auth_id` (`auth_module`,`auth_id`), + KEY `IDX_user_auth_user_id` (`user_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `user_auth_token` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `user_auth_token` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `user_id` bigint NOT NULL, + `auth_token` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `prev_auth_token` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `user_agent` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `client_ip` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `auth_token_seen` tinyint(1) NOT NULL, + `seen_at` int DEFAULT NULL, + `rotated_at` int NOT NULL, + `created_at` int NOT NULL, + `updated_at` int NOT NULL, + `revoked_at` int DEFAULT NULL, + `external_session_id` bigint DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `UQE_user_auth_token_auth_token` (`auth_token`), + UNIQUE KEY `UQE_user_auth_token_prev_auth_token` (`prev_auth_token`), + KEY `IDX_user_auth_token_user_id` (`user_id`), + KEY `IDX_user_auth_token_revoked_at` (`revoked_at`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `user_dashboard_views` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `user_dashboard_views` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `user_id` bigint NOT NULL, + `dashboard_id` bigint NOT NULL, + `viewed` datetime NOT NULL, + `org_id` bigint DEFAULT NULL, + `dashboard_uid` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `UQE_user_dashboard_views_user_id_dashboard_id` (`user_id`,`dashboard_id`), + UNIQUE KEY `UQE_user_dashboard_views_user_id_org_id_dashboard_uid` (`user_id`,`org_id`,`dashboard_uid`), + KEY `IDX_user_dashboard_views_user_id` (`user_id`), + KEY `IDX_user_dashboard_views_dashboard_id` (`dashboard_id`), + KEY `IDX_user_dashboard_views_org_id_dashboard_uid` (`org_id`,`dashboard_uid`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `user_external_session` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `user_external_session` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `user_auth_id` bigint NOT NULL, + `user_id` bigint NOT NULL, + `auth_module` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `access_token` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `id_token` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `refresh_token` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `session_id` varchar(1024) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL, + `session_id_hash` char(44) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `name_id` varchar(1024) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL, + `name_id_hash` char(44) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `expires_at` datetime DEFAULT NULL, + `created_at` datetime NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `user_role` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `user_role` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `org_id` bigint NOT NULL, + `user_id` bigint NOT NULL, + `role_id` bigint NOT NULL, + `created` datetime NOT NULL, + `group_mapping_uid` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '', + PRIMARY KEY (`id`), + UNIQUE KEY `UQE_user_role_org_id_user_id_role_id_group_mapping_uid` (`org_id`,`user_id`,`role_id`,`group_mapping_uid`), + KEY `IDX_user_role_org_id` (`org_id`), + KEY `IDX_user_role_user_id` (`user_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `user_stats` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `user_stats` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `user_id` bigint NOT NULL, + `billing_role` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `created` datetime NOT NULL, + `updated` datetime NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `UQE_user_stats_user_id` (`user_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; + +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; +/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; + diff --git a/pkg/services/sqlstore/migrator/snapshot/96-secret_migration_log.sql b/pkg/services/sqlstore/migrator/snapshot/96-secret_migration_log.sql new file mode 100644 index 00000000000..f5ae4ff6ab8 --- /dev/null +++ b/pkg/services/sqlstore/migrator/snapshot/96-secret_migration_log.sql @@ -0,0 +1,57 @@ +-- MySQL dump 10.13 Distrib 8.4.5, for Linux (x86_64) +-- +-- Host: localhost Database: hg_dump +-- ------------------------------------------------------ +-- Server version 8.4.5 +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + +-- +-- Dumping data for table `secret_migration_log` +-- + +INSERT INTO `secret_migration_log` (`migration_id`, `sql`, `success`, `error`, `timestamp`) VALUES + ('create secret_migration_log table','CREATE TABLE IF NOT EXISTS `secret_migration_log` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `migration_id` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `sql` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `success` TINYINT(1) NOT NULL\n, `error` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `timestamp` DATETIME NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('Initialize secrets tables','',1,'','2022-01-01 00:00:00'), + ('drop table secret_secure_value','DROP TABLE IF EXISTS `secret_secure_value`',1,'','2022-01-01 00:00:00'), + ('create table secret_secure_value','CREATE TABLE IF NOT EXISTS `secret_secure_value` (\n`guid` VARCHAR(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci PRIMARY KEY NOT NULL\n, `name` VARCHAR(253) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `namespace` VARCHAR(253) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `annotations` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n, `labels` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n, `created` BIGINT(20) NOT NULL\n, `created_by` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `updated` BIGINT(20) NOT NULL\n, `updated_by` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `external_id` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `active` TINYINT(1) NOT NULL\n, `version` BIGINT(20) NOT NULL\n, `description` VARCHAR(253) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `keeper` VARCHAR(253) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n, `decrypters` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n, `ref` VARCHAR(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('create table secret_secure_value, index: 0','CREATE UNIQUE INDEX `UQE_secret_secure_value_namespace_name_version_active` ON `secret_secure_value` (`namespace`,`name`,`version`,`active`);',1,'','2022-01-01 00:00:00'), + ('create table secret_secure_value, index: 1','CREATE UNIQUE INDEX `UQE_secret_secure_value_namespace_name_version` ON `secret_secure_value` (`namespace`,`name`,`version`);',1,'','2022-01-01 00:00:00'), + ('drop table secret_keeper','DROP TABLE IF EXISTS `secret_keeper`',1,'','2022-01-01 00:00:00'), + ('create table secret_keeper','CREATE TABLE IF NOT EXISTS `secret_keeper` (\n`guid` VARCHAR(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci PRIMARY KEY NOT NULL\n, `name` VARCHAR(253) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `namespace` VARCHAR(253) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `annotations` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n, `labels` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n, `created` BIGINT(20) NOT NULL\n, `created_by` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `updated` BIGINT(20) NOT NULL\n, `updated_by` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `description` VARCHAR(253) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `type` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `payload` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('create table secret_keeper, index: 0','CREATE UNIQUE INDEX `UQE_secret_keeper_namespace_name` ON `secret_keeper` (`namespace`,`name`);',1,'','2022-01-01 00:00:00'), + ('drop table secret_data_key','DROP TABLE IF EXISTS `secret_data_key`',1,'','2022-01-01 00:00:00'), + ('create table secret_data_key','CREATE TABLE IF NOT EXISTS `secret_data_key` (\n`uid` VARCHAR(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci PRIMARY KEY NOT NULL\n, `namespace` VARCHAR(253) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `label` VARCHAR(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `active` TINYINT(1) NOT NULL\n, `provider` VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `encrypted_data` BLOB NOT NULL\n, `created` DATETIME NOT NULL\n, `updated` DATETIME NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('drop table secret_encrypted_value','DROP TABLE IF EXISTS `secret_encrypted_value`',1,'','2022-01-01 00:00:00'), + ('create table secret_encrypted_value','CREATE TABLE IF NOT EXISTS `secret_encrypted_value` (\n`namespace` VARCHAR(253) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `name` VARCHAR(253) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `version` BIGINT(20) NOT NULL\n, `encrypted_data` BLOB NOT NULL\n, `created` BIGINT(20) NOT NULL\n, `updated` BIGINT(20) NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('create table secret_encrypted_value, index: 0','CREATE UNIQUE INDEX `UQE_secret_encrypted_value_namespace_name_version` ON `secret_encrypted_value` (`namespace`,`name`,`version`);',1,'','2022-01-01 00:00:00'), + ('create index for list on secret_secure_value','CREATE INDEX `IDX_secret_secure_value_namespace_active_updated` ON `secret_secure_value` (`namespace`,`active`,`updated`);',1,'','2022-01-01 00:00:00'), + ('create index for list and read current on secret_data_key','CREATE INDEX `IDX_secret_data_key_namespace_label_active` ON `secret_data_key` (`namespace`,`label`,`active`);',1,'','2022-01-01 00:00:00'), + ('add owner_reference_api_group column to secret_secure_value','alter table `secret_secure_value` ADD COLUMN `owner_reference_api_group` VARCHAR(253) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('add owner_reference_api_version column to secret_secure_value','alter table `secret_secure_value` ADD COLUMN `owner_reference_api_version` VARCHAR(253) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('add owner_reference_kind column to secret_secure_value','alter table `secret_secure_value` ADD COLUMN `owner_reference_kind` VARCHAR(253) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('add owner_reference_name column to secret_secure_value','alter table `secret_secure_value` ADD COLUMN `owner_reference_name` VARCHAR(253) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('add lease_token column to secret_secure_value','alter table `secret_secure_value` ADD COLUMN `lease_token` VARCHAR(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('add lease_token index to secret_secure_value','CREATE INDEX `IDX_secret_secure_value_lease_token` ON `secret_secure_value` (`lease_token`);',1,'','2022-01-01 00:00:00'), + ('add lease_created column to secret_secure_value','alter table `secret_secure_value` ADD COLUMN `lease_created` BIGINT(20) NOT NULL DEFAULT 0 ',1,'','2022-01-01 00:00:00'), + ('add lease_created index to secret_secure_value','CREATE INDEX `IDX_secret_secure_value_lease_created` ON `secret_secure_value` (`lease_created`);',1,'','2022-01-01 00:00:00'), + ('add data_key_id column to secret_encrypted_value','alter table `secret_encrypted_value` ADD COLUMN `data_key_id` VARCHAR(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT \'\' ',1,'','2022-01-01 00:00:00'), + ('add data_key_id index to secret_encrypted_value','CREATE INDEX `IDX_secret_encrypted_value_data_key_id` ON `secret_encrypted_value` (`data_key_id`);',1,'','2022-01-01 00:00:00'), + ('add active column to secret_keeper','alter table `secret_keeper` ADD COLUMN `active` TINYINT(1) NOT NULL DEFAULT 0 ',1,'','2022-01-01 00:00:00'), + ('add active column index to secret_keeper','CREATE INDEX `IDX_secret_keeper_namespace_name_active` ON `secret_keeper` (`namespace`,`name`,`active`);',1,'','2022-01-01 00:00:00'), + ('set secret_secure_value.keeper to \'system\' where keeper is null in secret_secure_value','UPDATE secret_secure_value SET keeper = \'system\' WHERE keeper IS NULL',1,'','2022-01-01 00:00:00'), + ('drop my_row_id and add primary key with columns namespace,name,version to table secret_encrypted_value if my_row_id exists (auto-generated mysql column)','\n ALTER TABLE secret_encrypted_value\n DROP PRIMARY KEY,\n DROP COLUMN my_row_id,\n DROP INDEX UQE_secret_encrypted_value_namespace_name_version,\n ADD PRIMARY KEY (`namespace`,`name`,`version`);\n ',1,'','2022-01-01 00:00:00'), + ('drop unique index UQE_secret_encrypted_value_namespace_name_version from secret_encrypted_value table if it exists (mysql)','ALTER TABLE secret_encrypted_value DROP INDEX UQE_secret_encrypted_value_namespace_name_version',1,'','2022-01-01 00:00:00'), + ('add primary key with columns namespace,name,version to table secret_encrypted_value if it doesn\'t exist (mysql)','ALTER TABLE secret_encrypted_value ADD PRIMARY KEY (`namespace`,`name`,`version`)',1,'','2022-01-01 00:00:00'), + ('add primary key with columns namespace,name,version to table secret_encrypted_value (postgres and sqlite)','',1,'','2022-01-01 00:00:00'); +/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; + +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; +/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; + diff --git a/pkg/services/sqlstore/migrator/snapshot/97-resource_migration_log.sql b/pkg/services/sqlstore/migrator/snapshot/97-resource_migration_log.sql new file mode 100644 index 00000000000..35327ae4531 --- /dev/null +++ b/pkg/services/sqlstore/migrator/snapshot/97-resource_migration_log.sql @@ -0,0 +1,60 @@ +-- MySQL dump 10.13 Distrib 8.4.5, for Linux (x86_64) +-- +-- Host: localhost Database: hg_dump +-- ------------------------------------------------------ +-- Server version 8.4.5 +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + +-- +-- Dumping data for table `resource_migration_log` +-- + +INSERT INTO `resource_migration_log` (`migration_id`, `sql`, `success`, `error`, `timestamp`) VALUES + ('create resource_migration_log table','CREATE TABLE IF NOT EXISTS `resource_migration_log` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `migration_id` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `sql` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `success` TINYINT(1) NOT NULL\n, `error` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `timestamp` DATETIME NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('Initialize resource tables','',1,'','2022-01-01 00:00:00'), + ('drop table resource','DROP TABLE IF EXISTS `resource`',1,'','2022-01-01 00:00:00'), + ('create table resource','CREATE TABLE IF NOT EXISTS `resource` (\n`guid` VARCHAR(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci PRIMARY KEY NOT NULL\n, `resource_version` BIGINT(20) NULL\n, `group` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `resource` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `namespace` VARCHAR(63) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `name` VARCHAR(253) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `value` LONGTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n, `action` INT NOT NULL\n, `label_set` VARCHAR(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('create table resource, index: 0','CREATE UNIQUE INDEX `UQE_resource_namespace_group_resource_name` ON `resource` (`namespace`,`group`,`resource`,`name`);',1,'','2022-01-01 00:00:00'), + ('drop table resource_history','DROP TABLE IF EXISTS `resource_history`',1,'','2022-01-01 00:00:00'), + ('create table resource_history','CREATE TABLE IF NOT EXISTS `resource_history` (\n`guid` VARCHAR(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci PRIMARY KEY NOT NULL\n, `resource_version` BIGINT(20) NULL\n, `group` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `resource` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `namespace` VARCHAR(63) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `name` VARCHAR(253) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `value` LONGTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n, `action` INT NOT NULL\n, `label_set` VARCHAR(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('create table resource_history, index: 0','CREATE UNIQUE INDEX `UQE_resource_history_namespace_group_name_version` ON `resource_history` (`namespace`,`group`,`resource`,`name`,`resource_version`);',1,'','2022-01-01 00:00:00'), + ('create table resource_history, index: 1','CREATE INDEX `IDX_resource_history_resource_version` ON `resource_history` (`resource_version`);',1,'','2022-01-01 00:00:00'), + ('drop table resource_version','DROP TABLE IF EXISTS `resource_version`',1,'','2022-01-01 00:00:00'), + ('create table resource_version','CREATE TABLE IF NOT EXISTS `resource_version` (\n`group` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `resource` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `resource_version` BIGINT(20) NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('create table resource_version, index: 0','CREATE UNIQUE INDEX `UQE_resource_version_group_resource` ON `resource_version` (`group`,`resource`);',1,'','2022-01-01 00:00:00'), + ('drop table resource_blob','DROP TABLE IF EXISTS `resource_blob`',1,'','2022-01-01 00:00:00'), + ('create table resource_blob','CREATE TABLE IF NOT EXISTS `resource_blob` (\n`uuid` CHAR(36) PRIMARY KEY NOT NULL\n, `created` DATETIME NOT NULL\n, `group` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `resource` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `namespace` VARCHAR(63) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `name` VARCHAR(253) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `value` LONGBLOB NOT NULL\n, `hash` VARCHAR(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `content_type` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('create table resource_blob, index: 0','CREATE INDEX `IDX_resource_history_namespace_group_name` ON `resource_blob` (`namespace`,`group`,`resource`,`name`);',1,'','2022-01-01 00:00:00'), + ('create table resource_blob, index: 1','CREATE INDEX `IDX_resource_blob_created` ON `resource_blob` (`created`);',1,'','2022-01-01 00:00:00'), + ('drop table resource_last_import_time','DROP TABLE IF EXISTS `resource_last_import_time`',1,'','2022-01-01 00:00:00'), + ('create table resource_last_import_time','CREATE TABLE IF NOT EXISTS `resource_last_import_time` (\n`group` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `resource` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `namespace` VARCHAR(63) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `last_import_time` DATETIME NOT NULL\n, PRIMARY KEY ( `group`,`resource`,`namespace` )) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('Add column previous_resource_version in resource_history','alter table `resource_history` ADD COLUMN `previous_resource_version` BIGINT(20) NULL ',1,'','2022-01-01 00:00:00'), + ('Add column previous_resource_version in resource','alter table `resource` ADD COLUMN `previous_resource_version` BIGINT(20) NULL ',1,'','2022-01-01 00:00:00'), + ('Add index to resource_history for polling','CREATE INDEX `IDX_resource_history_group_resource_resource_version` ON `resource_history` (`group`,`resource`,`resource_version`);',1,'','2022-01-01 00:00:00'), + ('Add index to resource for loading','CREATE INDEX `IDX_resource_group_resource` ON `resource` (`group`,`resource`);',1,'','2022-01-01 00:00:00'), + ('Add column folder in resource_history','alter table `resource_history` ADD COLUMN `folder` VARCHAR(253) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT \'\' ',1,'','2022-01-01 00:00:00'), + ('Add column folder in resource','alter table `resource` ADD COLUMN `folder` VARCHAR(253) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT \'\' ',1,'','2022-01-01 00:00:00'), + ('Migrate DeletionMarkers to real Resource objects','Find rows in resource_history with value LIKE {\"kind\":\"DeletedMarker\"%',1,'','2022-01-01 00:00:00'), + ('Add index to resource_history for get trash','CREATE INDEX `IDX_resource_history_namespace_group_resource_action_version` ON `resource_history` (`namespace`,`group`,`resource`,`action`,`resource_version`);',1,'','2022-01-01 00:00:00'), + ('Add generation to resource history','alter table `resource_history` ADD COLUMN `generation` BIGINT(20) NOT NULL DEFAULT 0 ',1,'','2022-01-01 00:00:00'), + ('Add generation index to resource history','CREATE INDEX `IDX_resource_history_namespace_group_resource_name_generation` ON `resource_history` (`namespace`,`group`,`resource`,`name`,`generation`);',1,'','2022-01-01 00:00:00'), + ('Add UQE_resource_last_import_time_last_import_time index','CREATE INDEX `UQE_resource_last_import_time_last_import_time` ON `resource_last_import_time` (`last_import_time`);',1,'','2022-01-01 00:00:00'), + ('Add key_path column to resource_history','alter table `resource_history` ADD COLUMN `key_path` VARCHAR(2048) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL DEFAULT \'\' ',1,'','2022-01-01 00:00:00'), + ('create table resource_events','CREATE TABLE IF NOT EXISTS `resource_events` (\n`key_path` VARCHAR(2048) CHARACTER SET latin1 COLLATE latin1_bin PRIMARY KEY NOT NULL\n, `value` MEDIUMTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('Add IDX_resource_history_key_path index','CREATE INDEX `IDX_resource_history_key_path` ON `resource_history` (`key_path`);',1,'','2022-01-01 00:00:00'), + ('drop my_row_id and add primary key with columns group,resource to table resource_version if my_row_id exists (auto-generated mysql column)','\n ALTER TABLE resource_version\n DROP PRIMARY KEY,\n DROP COLUMN my_row_id,\n DROP INDEX UQE_resource_version_group_resource,\n ADD PRIMARY KEY (`group`,`resource`);\n ',1,'','2022-01-01 00:00:00'), + ('drop unique index UQE_resource_version_group_resource from resource_version table if it exists (mysql)','ALTER TABLE resource_version DROP INDEX UQE_resource_version_group_resource',1,'','2022-01-01 00:00:00'), + ('add primary key with columns group,resource to table resource_version if it doesn\'t exist (mysql)','ALTER TABLE resource_version ADD PRIMARY KEY (`group`,`resource`)',1,'','2022-01-01 00:00:00'), + ('add primary key with columns group,resource to table resource_version (postgres and sqlite)','',1,'','2022-01-01 00:00:00'); +/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; + +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; +/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; + diff --git a/pkg/services/sqlstore/migrator/snapshot/98-data.sql b/pkg/services/sqlstore/migrator/snapshot/98-data.sql new file mode 100644 index 00000000000..de4d2e04323 --- /dev/null +++ b/pkg/services/sqlstore/migrator/snapshot/98-data.sql @@ -0,0 +1,469 @@ +-- MySQL dump 10.13 Distrib 8.4.5, for Linux (x86_64) +-- +-- Host: localhost Database: hg_dump +-- ------------------------------------------------------ +-- Server version 8.4.5 +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + +-- +-- Dumping data for table `alert` +-- + + +-- +-- Dumping data for table `alert_image` +-- + + +-- +-- Dumping data for table `alert_instance` +-- + + +-- +-- Dumping data for table `alert_notification` +-- + + +-- +-- Dumping data for table `alert_notification_state` +-- + + +-- +-- Dumping data for table `alert_rule` +-- + + +-- +-- Dumping data for table `alert_rule_state` +-- + + +-- +-- Dumping data for table `alert_rule_tag` +-- + + +-- +-- Dumping data for table `alert_rule_version` +-- + + +-- +-- Dumping data for table `annotation` +-- + + +-- +-- Dumping data for table `annotation_tag` +-- + + +-- +-- Dumping data for table `anon_device` +-- + + +-- +-- Dumping data for table `api_key` +-- + + +-- +-- Dumping data for table `cache_data` +-- + + +-- +-- Dumping data for table `cloud_migration_resource` +-- + + +-- +-- Dumping data for table `cloud_migration_session` +-- + + +-- +-- Dumping data for table `cloud_migration_snapshot` +-- + + +-- +-- Dumping data for table `cloud_migration_snapshot_partition` +-- + + +-- +-- Dumping data for table `correlation` +-- + + +-- +-- Dumping data for table `dashboard` +-- + + +-- +-- Dumping data for table `dashboard_acl` +-- + +INSERT INTO `dashboard_acl` (`id`, `org_id`, `dashboard_id`, `user_id`, `team_id`, `permission`, `role`, `created`, `updated`) VALUES + (1,-1,-1,NULL,NULL,1,'Viewer','2022-01-01 00:00:00','2022-01-01 00:00:00'), + (2,-1,-1,NULL,NULL,2,'Editor','2022-01-01 00:00:00','2022-01-01 00:00:00'); + +-- +-- Dumping data for table `dashboard_provisioning` +-- + + +-- +-- Dumping data for table `dashboard_public` +-- + + +-- +-- Dumping data for table `dashboard_public_email_share` +-- + + +-- +-- Dumping data for table `dashboard_public_magic_link` +-- + + +-- +-- Dumping data for table `dashboard_public_session` +-- + + +-- +-- Dumping data for table `dashboard_public_usage_by_day` +-- + + +-- +-- Dumping data for table `dashboard_snapshot` +-- + + +-- +-- Dumping data for table `dashboard_tag` +-- + + +-- +-- Dumping data for table `dashboard_usage_by_day` +-- + + +-- +-- Dumping data for table `dashboard_usage_sums` +-- + + +-- +-- Dumping data for table `dashboard_version` +-- + + +-- +-- Dumping data for table `data_keys` +-- + + +-- +-- Dumping data for table `data_source` +-- + + +-- +-- Dumping data for table `data_source_acl` +-- + + +-- +-- Dumping data for table `data_source_cache` +-- + + +-- +-- Dumping data for table `data_source_usage_by_day` +-- + + +-- +-- Dumping data for table `entity_event` +-- + + +-- +-- Dumping data for table `file` +-- + + +-- +-- Dumping data for table `file_meta` +-- + + +-- +-- Dumping data for table `folder` +-- + + +-- +-- Dumping data for table `library_element` +-- + + +-- +-- Dumping data for table `library_element_connection` +-- + + +-- +-- Dumping data for table `license_token` +-- + + +-- +-- Dumping data for table `login_attempt` +-- + + +-- +-- Dumping data for table `ngalert_configuration` +-- + + +-- +-- Dumping data for table `playlist` +-- + + +-- +-- Dumping data for table `playlist_item` +-- + + +-- +-- Dumping data for table `plugin_setting` +-- + + +-- +-- Dumping data for table `preferences` +-- + + +-- +-- Dumping data for table `provenance_type` +-- + + +-- +-- Dumping data for table `query_history` +-- + + +-- +-- Dumping data for table `query_history_details` +-- + + +-- +-- Dumping data for table `query_history_star` +-- + + +-- +-- Dumping data for table `quota` +-- + + +-- +-- Dumping data for table `recording_rules` +-- + + +-- +-- Dumping data for table `remote_write_targets` +-- + + +-- +-- Dumping data for table `report` +-- + + +-- +-- Dumping data for table `report_dashboards` +-- + + +-- +-- Dumping data for table `report_settings` +-- + + +-- +-- Dumping data for table `resource` +-- + + +-- +-- Dumping data for table `resource_blob` +-- + + +-- +-- Dumping data for table `resource_events` +-- + + +-- +-- Dumping data for table `resource_history` +-- + + +-- +-- Dumping data for table `secret_data_key` +-- + + +-- +-- Dumping data for table `secret_encrypted_value` +-- + + +-- +-- Dumping data for table `secret_keeper` +-- + + +-- +-- Dumping data for table `secret_secure_value` +-- + + +-- +-- Dumping data for table `secrets` +-- + + +-- +-- Dumping data for table `session` +-- + + +-- +-- Dumping data for table `setting` +-- + + +-- +-- Dumping data for table `short_url` +-- + + +-- +-- Dumping data for table `signing_key` +-- + + +-- +-- Dumping data for table `sso_setting` +-- + + +-- +-- Dumping data for table `star` +-- + + +-- +-- Dumping data for table `tag` +-- + + +-- +-- Dumping data for table `team` +-- + + +-- +-- Dumping data for table `team_group` +-- + + +-- +-- Dumping data for table `team_member` +-- + + +-- +-- Dumping data for table `team_role` +-- + + +-- +-- Dumping data for table `temp_user` +-- + + +-- +-- Dumping data for table `test_data` +-- + + +-- +-- Dumping data for table `unifiedstorage_migration_log` +-- + +INSERT INTO `unifiedstorage_migration_log` (`id`, `migration_id`, `sql`, `success`, `error`, `timestamp`) VALUES + (1,'create unifiedstorage_migration_log table','CREATE TABLE IF NOT EXISTS `unifiedstorage_migration_log` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `migration_id` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `sql` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `success` TINYINT(1) NOT NULL\n, `error` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `timestamp` DATETIME NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + (2,'playlists migration','unified storage data migration: playlists',1,'','2022-01-01 00:00:00'); + +-- +-- Dumping data for table `user_auth` +-- + + +-- +-- Dumping data for table `user_auth_token` +-- + + +-- +-- Dumping data for table `user_dashboard_views` +-- + + +-- +-- Dumping data for table `user_external_session` +-- + + +-- +-- Dumping data for table `user_role` +-- + + +-- +-- Dumping data for table `user_stats` +-- + +/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; + +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; +/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; + diff --git a/pkg/services/sqlstore/migrator/snapshot/99-migration_log.sql b/pkg/services/sqlstore/migrator/snapshot/99-migration_log.sql new file mode 100644 index 00000000000..b4900f95f13 --- /dev/null +++ b/pkg/services/sqlstore/migrator/snapshot/99-migration_log.sql @@ -0,0 +1,850 @@ +-- MySQL dump 10.13 Distrib 8.4.5, for Linux (x86_64) +-- +-- Host: localhost Database: hg_dump +-- ------------------------------------------------------ +-- Server version 8.4.5 +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + +-- +-- Dumping data for table `migration_log` +-- + +INSERT INTO `migration_log` (`migration_id`, `sql`, `success`, `error`, `timestamp`) VALUES + ('create migration_log table','CREATE TABLE IF NOT EXISTS `migration_log` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `migration_id` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `sql` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `success` TINYINT(1) NOT NULL\n, `error` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `timestamp` DATETIME NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('create user table','CREATE TABLE IF NOT EXISTS `user` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `version` INT NOT NULL\n, `login` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `email` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `name` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n, `password` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n, `salt` VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n, `rands` VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n, `company` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n, `account_id` BIGINT(20) NOT NULL\n, `is_admin` TINYINT(1) NOT NULL\n, `created` DATETIME NOT NULL\n, `updated` DATETIME NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('add unique index user.login','CREATE UNIQUE INDEX `UQE_user_login` ON `user` (`login`);',1,'','2022-01-01 00:00:00'), + ('add unique index user.email','CREATE UNIQUE INDEX `UQE_user_email` ON `user` (`email`);',1,'','2022-01-01 00:00:00'), + ('drop index UQE_user_login - v1','DROP INDEX `UQE_user_login` ON `user`',1,'','2022-01-01 00:00:00'), + ('drop index UQE_user_email - v1','DROP INDEX `UQE_user_email` ON `user`',1,'','2022-01-01 00:00:00'), + ('Rename table user to user_v1 - v1','ALTER TABLE `user` RENAME TO `user_v1`',1,'','2022-01-01 00:00:00'), + ('create user table v2','CREATE TABLE IF NOT EXISTS `user` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `version` INT NOT NULL\n, `login` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `email` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `name` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n, `password` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n, `salt` VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n, `rands` VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n, `company` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n, `org_id` BIGINT(20) NOT NULL\n, `is_admin` TINYINT(1) NOT NULL\n, `email_verified` TINYINT(1) NULL\n, `theme` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n, `created` DATETIME NOT NULL\n, `updated` DATETIME NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('create index UQE_user_login - v2','CREATE UNIQUE INDEX `UQE_user_login` ON `user` (`login`);',1,'','2022-01-01 00:00:00'), + ('create index UQE_user_email - v2','CREATE UNIQUE INDEX `UQE_user_email` ON `user` (`email`);',1,'','2022-01-01 00:00:00'), + ('copy data_source v1 to v2','INSERT INTO `user` (`version`\n, `email`\n, `name`\n, `password`\n, `is_admin`\n, `id`\n, `login`\n, `salt`\n, `rands`\n, `company`\n, `org_id`\n, `created`\n, `updated`)\nSELECT `version`\n, `email`\n, `name`\n, `password`\n, `is_admin`\n, `id`\n, `login`\n, `salt`\n, `rands`\n, `company`\n, `account_id`\n, `created`\n, `updated`\nFROM `user_v1`',1,'','2022-01-01 00:00:00'), + ('Drop old table user_v1','DROP TABLE IF EXISTS `user_v1`',1,'','2022-01-01 00:00:00'), + ('Add column help_flags1 to user table','alter table `user` ADD COLUMN `help_flags1` BIGINT(20) NOT NULL DEFAULT 0 ',1,'','2022-01-01 00:00:00'), + ('Update user table charset','ALTER TABLE `user` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, MODIFY `login` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL , MODIFY `email` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL , MODIFY `name` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL , MODIFY `password` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL , MODIFY `salt` VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL , MODIFY `rands` VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL , MODIFY `company` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL , MODIFY `theme` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ;',1,'','2022-01-01 00:00:00'), + ('Add last_seen_at column to user','alter table `user` ADD COLUMN `last_seen_at` DATETIME NULL ',1,'','2022-01-01 00:00:00'), + ('Add missing user data','code migration',1,'','2022-01-01 00:00:00'), + ('Add is_disabled column to user','alter table `user` ADD COLUMN `is_disabled` TINYINT(1) NOT NULL DEFAULT 0 ',1,'','2022-01-01 00:00:00'), + ('Add index user.login/user.email','CREATE INDEX `IDX_user_login_email` ON `user` (`login`,`email`);',1,'','2022-01-01 00:00:00'), + ('Add is_service_account column to user','alter table `user` ADD COLUMN `is_service_account` TINYINT(1) NOT NULL DEFAULT 0 ',1,'','2022-01-01 00:00:00'), + ('Update is_service_account column to nullable','ALTER TABLE user MODIFY is_service_account BOOLEAN DEFAULT 0;',1,'','2022-01-01 00:00:00'), + ('Add uid column to user','alter table `user` ADD COLUMN `uid` VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('Update uid column values for users','UPDATE user SET uid=concat(\'u\',lpad(id,9,\'0\')) WHERE uid IS NULL;',1,'','2022-01-01 00:00:00'), + ('Make sure users uid are set','UPDATE user SET uid=concat(\'u\',lpad(id,9,\'0\')) WHERE uid is NULL OR uid = \'\';',1,'','2022-01-01 00:00:00'), + ('Add unique index user_uid','CREATE UNIQUE INDEX `UQE_user_uid` ON `user` (`uid`);',1,'','2022-01-01 00:00:00'), + ('Add is_provisioned column to user','alter table `user` ADD COLUMN `is_provisioned` TINYINT(1) NOT NULL DEFAULT 0 ',1,'','2022-01-01 00:00:00'), + ('update login field with orgid to allow for multiple service accounts with same name across orgs','code migration',1,'','2022-01-01 00:00:00'), + ('update service accounts login field orgid to appear only once','code migration',1,'','2022-01-01 00:00:00'), + ('update login and email fields to lowercase','code migration',1,'','2022-01-01 00:00:00'), + ('update login and email fields to lowercase2','code migration',1,'','2022-01-01 00:00:00'), + ('Add index on user.is_service_account and user.last_seen_at','CREATE INDEX `IDX_user_is_service_account_last_seen_at` ON `user` (`is_service_account`,`last_seen_at`);',1,'','2022-01-01 00:00:00'), + ('Expand user.uid length to 190','ALTER TABLE user MODIFY uid VARCHAR(190);',1,'','2022-01-01 00:00:00'), + ('Prefix SCIM uid for provisioned users','UPDATE user SET uid = CONCAT(\'scim-\', uid) WHERE is_provisioned = 1 AND uid NOT LIKE \'scim-%\';',1,'','2022-01-01 00:00:00'), + ('create temp user table v1-7','CREATE TABLE IF NOT EXISTS `temp_user` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `org_id` BIGINT(20) NOT NULL\n, `version` INT NOT NULL\n, `email` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `name` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n, `role` VARCHAR(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n, `code` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `status` VARCHAR(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `invited_by_user_id` BIGINT(20) NULL\n, `email_sent` TINYINT(1) NOT NULL\n, `email_sent_on` DATETIME NULL\n, `remote_addr` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n, `created` DATETIME NOT NULL\n, `updated` DATETIME NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('create index IDX_temp_user_email - v1-7','CREATE INDEX `IDX_temp_user_email` ON `temp_user` (`email`);',1,'','2022-01-01 00:00:00'), + ('create index IDX_temp_user_org_id - v1-7','CREATE INDEX `IDX_temp_user_org_id` ON `temp_user` (`org_id`);',1,'','2022-01-01 00:00:00'), + ('create index IDX_temp_user_code - v1-7','CREATE INDEX `IDX_temp_user_code` ON `temp_user` (`code`);',1,'','2022-01-01 00:00:00'), + ('create index IDX_temp_user_status - v1-7','CREATE INDEX `IDX_temp_user_status` ON `temp_user` (`status`);',1,'','2022-01-01 00:00:00'), + ('Update temp_user table charset','ALTER TABLE `temp_user` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, MODIFY `email` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL , MODIFY `name` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL , MODIFY `role` VARCHAR(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL , MODIFY `code` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL , MODIFY `status` VARCHAR(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL , MODIFY `remote_addr` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ;',1,'','2022-01-01 00:00:00'), + ('drop index IDX_temp_user_email - v1','DROP INDEX `IDX_temp_user_email` ON `temp_user`',1,'','2022-01-01 00:00:00'), + ('drop index IDX_temp_user_org_id - v1','DROP INDEX `IDX_temp_user_org_id` ON `temp_user`',1,'','2022-01-01 00:00:00'), + ('drop index IDX_temp_user_code - v1','DROP INDEX `IDX_temp_user_code` ON `temp_user`',1,'','2022-01-01 00:00:00'), + ('drop index IDX_temp_user_status - v1','DROP INDEX `IDX_temp_user_status` ON `temp_user`',1,'','2022-01-01 00:00:00'), + ('Rename table temp_user to temp_user_tmp_qwerty - v1','ALTER TABLE `temp_user` RENAME TO `temp_user_tmp_qwerty`',1,'','2022-01-01 00:00:00'), + ('create temp_user v2','CREATE TABLE IF NOT EXISTS `temp_user` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `org_id` BIGINT(20) NOT NULL\n, `version` INT NOT NULL\n, `email` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `name` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n, `role` VARCHAR(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n, `code` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `status` VARCHAR(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `invited_by_user_id` BIGINT(20) NULL\n, `email_sent` TINYINT(1) NOT NULL\n, `email_sent_on` DATETIME NULL\n, `remote_addr` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n, `created` INT NOT NULL DEFAULT 0\n, `updated` INT NOT NULL DEFAULT 0\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('create index IDX_temp_user_email - v2','CREATE INDEX `IDX_temp_user_email` ON `temp_user` (`email`);',1,'','2022-01-01 00:00:00'), + ('create index IDX_temp_user_org_id - v2','CREATE INDEX `IDX_temp_user_org_id` ON `temp_user` (`org_id`);',1,'','2022-01-01 00:00:00'), + ('create index IDX_temp_user_code - v2','CREATE INDEX `IDX_temp_user_code` ON `temp_user` (`code`);',1,'','2022-01-01 00:00:00'), + ('create index IDX_temp_user_status - v2','CREATE INDEX `IDX_temp_user_status` ON `temp_user` (`status`);',1,'','2022-01-01 00:00:00'), + ('copy temp_user v1 to v2','INSERT INTO `temp_user` (`invited_by_user_id`\n, `email_sent`\n, `email_sent_on`\n, `org_id`\n, `version`\n, `email`\n, `name`\n, `code`\n, `status`\n, `remote_addr`\n, `id`\n, `role`)\nSELECT `invited_by_user_id`\n, `email_sent`\n, `email_sent_on`\n, `org_id`\n, `version`\n, `email`\n, `name`\n, `code`\n, `status`\n, `remote_addr`\n, `id`\n, `role`\nFROM `temp_user_tmp_qwerty`',1,'','2022-01-01 00:00:00'), + ('drop temp_user_tmp_qwerty','DROP TABLE IF EXISTS `temp_user_tmp_qwerty`',1,'','2022-01-01 00:00:00'), + ('Set created for temp users that will otherwise prematurely expire','code migration',1,'','2022-01-01 00:00:00'), + ('create star table','CREATE TABLE IF NOT EXISTS `star` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `user_id` BIGINT(20) NOT NULL\n, `dashboard_id` BIGINT(20) NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('add unique index star.user_id_dashboard_id','CREATE UNIQUE INDEX `UQE_star_user_id_dashboard_id` ON `star` (`user_id`,`dashboard_id`);',1,'','2022-01-01 00:00:00'), + ('Add column dashboard_uid in star','alter table `star` ADD COLUMN `dashboard_uid` VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('Add column org_id in star','alter table `star` ADD COLUMN `org_id` BIGINT(20) NULL DEFAULT 1 ',1,'','2022-01-01 00:00:00'), + ('Add column updated in star','alter table `star` ADD COLUMN `updated` DATETIME NULL ',1,'','2022-01-01 00:00:00'), + ('add index in star table on dashboard_uid, org_id and user_id columns','CREATE UNIQUE INDEX `UQE_star_user_id_dashboard_uid_org_id` ON `star` (`user_id`,`dashboard_uid`,`org_id`);',1,'','2022-01-01 00:00:00'), + ('create org table v1','CREATE TABLE IF NOT EXISTS `org` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `version` INT NOT NULL\n, `name` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `address1` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n, `address2` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n, `city` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n, `state` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n, `zip_code` VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n, `country` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n, `billing_email` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n, `created` DATETIME NOT NULL\n, `updated` DATETIME NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('create index UQE_org_name - v1','CREATE UNIQUE INDEX `UQE_org_name` ON `org` (`name`);',1,'','2022-01-01 00:00:00'), + ('create org_user table v1','CREATE TABLE IF NOT EXISTS `org_user` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `org_id` BIGINT(20) NOT NULL\n, `user_id` BIGINT(20) NOT NULL\n, `role` VARCHAR(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `created` DATETIME NOT NULL\n, `updated` DATETIME NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('create index IDX_org_user_org_id - v1','CREATE INDEX `IDX_org_user_org_id` ON `org_user` (`org_id`);',1,'','2022-01-01 00:00:00'), + ('create index UQE_org_user_org_id_user_id - v1','CREATE UNIQUE INDEX `UQE_org_user_org_id_user_id` ON `org_user` (`org_id`,`user_id`);',1,'','2022-01-01 00:00:00'), + ('create index IDX_org_user_user_id - v1','CREATE INDEX `IDX_org_user_user_id` ON `org_user` (`user_id`);',1,'','2022-01-01 00:00:00'), + ('Update org table charset','ALTER TABLE `org` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, MODIFY `name` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL , MODIFY `address1` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL , MODIFY `address2` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL , MODIFY `city` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL , MODIFY `state` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL , MODIFY `zip_code` VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL , MODIFY `country` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL , MODIFY `billing_email` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ;',1,'','2022-01-01 00:00:00'), + ('Update org_user table charset','ALTER TABLE `org_user` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, MODIFY `role` VARCHAR(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL ;',1,'','2022-01-01 00:00:00'), + ('Migrate all Read Only Viewers to Viewers','UPDATE org_user SET role = \'Viewer\' WHERE role = \'Read Only Editor\'',1,'','2022-01-01 00:00:00'), + ('create dashboard table','CREATE TABLE IF NOT EXISTS `dashboard` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `version` INT NOT NULL\n, `slug` VARCHAR(189) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `title` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `data` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `account_id` BIGINT(20) NOT NULL\n, `created` DATETIME NOT NULL\n, `updated` DATETIME NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('add index dashboard.account_id','CREATE INDEX `IDX_dashboard_account_id` ON `dashboard` (`account_id`);',1,'','2022-01-01 00:00:00'), + ('add unique index dashboard_account_id_slug','CREATE UNIQUE INDEX `UQE_dashboard_account_id_slug` ON `dashboard` (`account_id`,`slug`);',1,'','2022-01-01 00:00:00'), + ('create dashboard_tag table','CREATE TABLE IF NOT EXISTS `dashboard_tag` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `dashboard_id` BIGINT(20) NOT NULL\n, `term` VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('add unique index dashboard_tag.dasboard_id_term','CREATE UNIQUE INDEX `UQE_dashboard_tag_dashboard_id_term` ON `dashboard_tag` (`dashboard_id`,`term`);',1,'','2022-01-01 00:00:00'), + ('drop index UQE_dashboard_tag_dashboard_id_term - v1','DROP INDEX `UQE_dashboard_tag_dashboard_id_term` ON `dashboard_tag`',1,'','2022-01-01 00:00:00'), + ('Rename table dashboard to dashboard_v1 - v1','ALTER TABLE `dashboard` RENAME TO `dashboard_v1`',1,'','2022-01-01 00:00:00'), + ('create dashboard v2','CREATE TABLE IF NOT EXISTS `dashboard` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `version` INT NOT NULL\n, `slug` VARCHAR(189) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `title` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `data` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `org_id` BIGINT(20) NOT NULL\n, `created` DATETIME NOT NULL\n, `updated` DATETIME NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('create index IDX_dashboard_org_id - v2','CREATE INDEX `IDX_dashboard_org_id` ON `dashboard` (`org_id`);',1,'','2022-01-01 00:00:00'), + ('create index UQE_dashboard_org_id_slug - v2','CREATE UNIQUE INDEX `UQE_dashboard_org_id_slug` ON `dashboard` (`org_id`,`slug`);',1,'','2022-01-01 00:00:00'), + ('copy dashboard v1 to v2','INSERT INTO `dashboard` (`id`\n, `version`\n, `slug`\n, `title`\n, `data`\n, `org_id`\n, `created`\n, `updated`)\nSELECT `id`\n, `version`\n, `slug`\n, `title`\n, `data`\n, `account_id`\n, `created`\n, `updated`\nFROM `dashboard_v1`',1,'','2022-01-01 00:00:00'), + ('drop table dashboard_v1','DROP TABLE IF EXISTS `dashboard_v1`',1,'','2022-01-01 00:00:00'), + ('alter dashboard.data to mediumtext v1','ALTER TABLE dashboard MODIFY data MEDIUMTEXT;',1,'','2022-01-01 00:00:00'), + ('Add column updated_by in dashboard - v2','alter table `dashboard` ADD COLUMN `updated_by` INT NULL ',1,'','2022-01-01 00:00:00'), + ('Add column created_by in dashboard - v2','alter table `dashboard` ADD COLUMN `created_by` INT NULL ',1,'','2022-01-01 00:00:00'), + ('Add column gnetId in dashboard','alter table `dashboard` ADD COLUMN `gnet_id` BIGINT(20) NULL ',1,'','2022-01-01 00:00:00'), + ('Add index for gnetId in dashboard','CREATE INDEX `IDX_dashboard_gnet_id` ON `dashboard` (`gnet_id`);',1,'','2022-01-01 00:00:00'), + ('Add column plugin_id in dashboard','alter table `dashboard` ADD COLUMN `plugin_id` VARCHAR(189) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('Add index for plugin_id in dashboard','CREATE INDEX `IDX_dashboard_org_id_plugin_id` ON `dashboard` (`org_id`,`plugin_id`);',1,'','2022-01-01 00:00:00'), + ('Add index for dashboard_id in dashboard_tag','CREATE INDEX `IDX_dashboard_tag_dashboard_id` ON `dashboard_tag` (`dashboard_id`);',1,'','2022-01-01 00:00:00'), + ('Update dashboard table charset','ALTER TABLE `dashboard` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, MODIFY `slug` VARCHAR(189) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL , MODIFY `title` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL , MODIFY `plugin_id` VARCHAR(189) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL , MODIFY `data` MEDIUMTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL ;',1,'','2022-01-01 00:00:00'), + ('Update dashboard_tag table charset','ALTER TABLE `dashboard_tag` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, MODIFY `term` VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL ;',1,'','2022-01-01 00:00:00'), + ('Add column folder_id in dashboard','alter table `dashboard` ADD COLUMN `folder_id` BIGINT(20) NOT NULL DEFAULT 0 ',1,'','2022-01-01 00:00:00'), + ('Add column isFolder in dashboard','alter table `dashboard` ADD COLUMN `is_folder` TINYINT(1) NOT NULL DEFAULT 0 ',1,'','2022-01-01 00:00:00'), + ('Add column has_acl in dashboard','alter table `dashboard` ADD COLUMN `has_acl` TINYINT(1) NOT NULL DEFAULT 0 ',1,'','2022-01-01 00:00:00'), + ('Add column uid in dashboard','alter table `dashboard` ADD COLUMN `uid` VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('Update uid column values in dashboard','UPDATE dashboard SET uid=lpad(id,9,\'0\') WHERE uid IS NULL;',1,'','2022-01-01 00:00:00'), + ('Add unique index dashboard_org_id_uid','CREATE UNIQUE INDEX `UQE_dashboard_org_id_uid` ON `dashboard` (`org_id`,`uid`);',1,'','2022-01-01 00:00:00'), + ('Remove unique index org_id_slug','DROP INDEX `UQE_dashboard_org_id_slug` ON `dashboard`',1,'','2022-01-01 00:00:00'), + ('Update dashboard title length','ALTER TABLE `dashboard` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, MODIFY `title` VARCHAR(189) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL ;',1,'','2022-01-01 00:00:00'), + ('Add unique index for dashboard_org_id_title_folder_id','CREATE UNIQUE INDEX `UQE_dashboard_org_id_folder_id_title` ON `dashboard` (`org_id`,`folder_id`,`title`);',1,'','2022-01-01 00:00:00'), + ('create dashboard_provisioning','CREATE TABLE IF NOT EXISTS `dashboard_provisioning` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `dashboard_id` BIGINT(20) NULL\n, `name` VARCHAR(150) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `external_id` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `updated` DATETIME NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('Rename table dashboard_provisioning to dashboard_provisioning_tmp_qwerty - v1','ALTER TABLE `dashboard_provisioning` RENAME TO `dashboard_provisioning_tmp_qwerty`',1,'','2022-01-01 00:00:00'), + ('create dashboard_provisioning v2','CREATE TABLE IF NOT EXISTS `dashboard_provisioning` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `dashboard_id` BIGINT(20) NULL\n, `name` VARCHAR(150) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `external_id` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `updated` INT NOT NULL DEFAULT 0\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('create index IDX_dashboard_provisioning_dashboard_id - v2','CREATE INDEX `IDX_dashboard_provisioning_dashboard_id` ON `dashboard_provisioning` (`dashboard_id`);',1,'','2022-01-01 00:00:00'), + ('create index IDX_dashboard_provisioning_dashboard_id_name - v2','CREATE INDEX `IDX_dashboard_provisioning_dashboard_id_name` ON `dashboard_provisioning` (`dashboard_id`,`name`);',1,'','2022-01-01 00:00:00'), + ('copy dashboard_provisioning v1 to v2','INSERT INTO `dashboard_provisioning` (`name`\n, `external_id`\n, `id`\n, `dashboard_id`)\nSELECT `name`\n, `external_id`\n, `id`\n, `dashboard_id`\nFROM `dashboard_provisioning_tmp_qwerty`',1,'','2022-01-01 00:00:00'), + ('drop dashboard_provisioning_tmp_qwerty','DROP TABLE IF EXISTS `dashboard_provisioning_tmp_qwerty`',1,'','2022-01-01 00:00:00'), + ('Add check_sum column','alter table `dashboard_provisioning` ADD COLUMN `check_sum` VARCHAR(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('Add index for dashboard_title','CREATE INDEX `IDX_dashboard_title` ON `dashboard` (`title`);',1,'','2022-01-01 00:00:00'), + ('delete tags for deleted dashboards','DELETE FROM dashboard_tag WHERE dashboard_id NOT IN (SELECT id FROM dashboard)',1,'','2022-01-01 00:00:00'), + ('delete stars for deleted dashboards','DELETE FROM star WHERE dashboard_id NOT IN (SELECT id FROM dashboard)',1,'','2022-01-01 00:00:00'), + ('Add index for dashboard_is_folder','CREATE INDEX `IDX_dashboard_is_folder` ON `dashboard` (`is_folder`);',1,'','2022-01-01 00:00:00'), + ('Add isPublic for dashboard','alter table `dashboard` ADD COLUMN `is_public` TINYINT(1) NOT NULL DEFAULT 0 ',1,'','2022-01-01 00:00:00'), + ('Add deleted for dashboard','alter table `dashboard` ADD COLUMN `deleted` DATETIME NULL ',1,'','2022-01-01 00:00:00'), + ('Add index for deleted','CREATE INDEX `IDX_dashboard_deleted` ON `dashboard` (`deleted`);',1,'','2022-01-01 00:00:00'), + ('Add column dashboard_uid in dashboard_tag','alter table `dashboard_tag` ADD COLUMN `dashboard_uid` VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('Add column org_id in dashboard_tag','alter table `dashboard_tag` ADD COLUMN `org_id` BIGINT(20) NULL DEFAULT 1 ',1,'','2022-01-01 00:00:00'), + ('Add missing dashboard_uid and org_id to dashboard_tag','code migration',1,'','2022-01-01 00:00:00'), + ('Add apiVersion for dashboard','alter table `dashboard` ADD COLUMN `api_version` VARCHAR(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('Add index for dashboard_uid on dashboard_tag table','CREATE INDEX `IDX_dashboard_tag_dashboard_uid` ON `dashboard_tag` (`dashboard_uid`);',1,'','2022-01-01 00:00:00'), + ('Add missing dashboard_uid and org_id to star','code migration',1,'','2022-01-01 00:00:00'), + ('create data_source table','CREATE TABLE IF NOT EXISTS `data_source` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `account_id` BIGINT(20) NOT NULL\n, `version` INT NOT NULL\n, `type` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `name` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `access` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `url` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `password` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n, `user` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n, `database` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n, `basic_auth` TINYINT(1) NOT NULL\n, `basic_auth_user` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n, `basic_auth_password` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n, `is_default` TINYINT(1) NOT NULL\n, `created` DATETIME NOT NULL\n, `updated` DATETIME NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('add index data_source.account_id','CREATE INDEX `IDX_data_source_account_id` ON `data_source` (`account_id`);',1,'','2022-01-01 00:00:00'), + ('add unique index data_source.account_id_name','CREATE UNIQUE INDEX `UQE_data_source_account_id_name` ON `data_source` (`account_id`,`name`);',1,'','2022-01-01 00:00:00'), + ('drop index IDX_data_source_account_id - v1','DROP INDEX `IDX_data_source_account_id` ON `data_source`',1,'','2022-01-01 00:00:00'), + ('drop index UQE_data_source_account_id_name - v1','DROP INDEX `UQE_data_source_account_id_name` ON `data_source`',1,'','2022-01-01 00:00:00'), + ('Rename table data_source to data_source_v1 - v1','ALTER TABLE `data_source` RENAME TO `data_source_v1`',1,'','2022-01-01 00:00:00'), + ('create data_source table v2','CREATE TABLE IF NOT EXISTS `data_source` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `org_id` BIGINT(20) NOT NULL\n, `version` INT NOT NULL\n, `type` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `name` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `access` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `url` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `password` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n, `user` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n, `database` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n, `basic_auth` TINYINT(1) NOT NULL\n, `basic_auth_user` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n, `basic_auth_password` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n, `is_default` TINYINT(1) NOT NULL\n, `json_data` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n, `created` DATETIME NOT NULL\n, `updated` DATETIME NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('create index IDX_data_source_org_id - v2','CREATE INDEX `IDX_data_source_org_id` ON `data_source` (`org_id`);',1,'','2022-01-01 00:00:00'), + ('create index UQE_data_source_org_id_name - v2','CREATE UNIQUE INDEX `UQE_data_source_org_id_name` ON `data_source` (`org_id`,`name`);',1,'','2022-01-01 00:00:00'), + ('Drop old table data_source_v1 #2','DROP TABLE IF EXISTS `data_source_v1`',1,'','2022-01-01 00:00:00'), + ('Add column with_credentials','alter table `data_source` ADD COLUMN `with_credentials` TINYINT(1) NOT NULL DEFAULT 0 ',1,'','2022-01-01 00:00:00'), + ('Add secure json data column','alter table `data_source` ADD COLUMN `secure_json_data` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('Update data_source table charset','ALTER TABLE `data_source` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, MODIFY `type` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL , MODIFY `name` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL , MODIFY `access` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL , MODIFY `url` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL , MODIFY `password` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL , MODIFY `user` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL , MODIFY `database` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL , MODIFY `basic_auth_user` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL , MODIFY `basic_auth_password` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL , MODIFY `json_data` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL , MODIFY `secure_json_data` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ;',1,'','2022-01-01 00:00:00'), + ('Update initial version to 1','UPDATE data_source SET version = 1 WHERE version = 0',1,'','2022-01-01 00:00:00'), + ('Add read_only data column','alter table `data_source` ADD COLUMN `read_only` TINYINT(1) NULL ',1,'','2022-01-01 00:00:00'), + ('Migrate logging ds to loki ds','UPDATE data_source SET type = \'loki\' WHERE type = \'logging\'',1,'','2022-01-01 00:00:00'), + ('Update json_data with nulls','UPDATE data_source SET json_data = \'{}\' WHERE json_data is null',1,'','2022-01-01 00:00:00'), + ('Add uid column','alter table `data_source` ADD COLUMN `uid` VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 0 ',1,'','2022-01-01 00:00:00'), + ('Update uid value','UPDATE data_source SET uid=lpad(id,9,\'0\');',1,'','2022-01-01 00:00:00'), + ('Add unique index datasource_org_id_uid','CREATE UNIQUE INDEX `UQE_data_source_org_id_uid` ON `data_source` (`org_id`,`uid`);',1,'','2022-01-01 00:00:00'), + ('add unique index datasource_org_id_is_default','CREATE INDEX `IDX_data_source_org_id_is_default` ON `data_source` (`org_id`,`is_default`);',1,'','2022-01-01 00:00:00'), + ('Add is_prunable column','alter table `data_source` ADD COLUMN `is_prunable` TINYINT(1) NULL DEFAULT 0 ',1,'','2022-01-01 00:00:00'), + ('Add api_version column','alter table `data_source` ADD COLUMN `api_version` VARCHAR(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('Update secure_json_data column to MediumText','ALTER TABLE data_source MODIFY COLUMN secure_json_data MEDIUMTEXT;',1,'','2022-01-01 00:00:00'), + ('Update json_data column to MediumText','ALTER TABLE data_source MODIFY COLUMN json_data MEDIUMTEXT;',1,'','2022-01-01 00:00:00'), + ('create api_key table','CREATE TABLE IF NOT EXISTS `api_key` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `account_id` BIGINT(20) NOT NULL\n, `name` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `key` VARCHAR(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `role` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `created` DATETIME NOT NULL\n, `updated` DATETIME NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('add index api_key.account_id','CREATE INDEX `IDX_api_key_account_id` ON `api_key` (`account_id`);',1,'','2022-01-01 00:00:00'), + ('add index api_key.key','CREATE UNIQUE INDEX `UQE_api_key_key` ON `api_key` (`key`);',1,'','2022-01-01 00:00:00'), + ('add index api_key.account_id_name','CREATE UNIQUE INDEX `UQE_api_key_account_id_name` ON `api_key` (`account_id`,`name`);',1,'','2022-01-01 00:00:00'), + ('drop index IDX_api_key_account_id - v1','DROP INDEX `IDX_api_key_account_id` ON `api_key`',1,'','2022-01-01 00:00:00'), + ('drop index UQE_api_key_key - v1','DROP INDEX `UQE_api_key_key` ON `api_key`',1,'','2022-01-01 00:00:00'), + ('drop index UQE_api_key_account_id_name - v1','DROP INDEX `UQE_api_key_account_id_name` ON `api_key`',1,'','2022-01-01 00:00:00'), + ('Rename table api_key to api_key_v1 - v1','ALTER TABLE `api_key` RENAME TO `api_key_v1`',1,'','2022-01-01 00:00:00'), + ('create api_key table v2','CREATE TABLE IF NOT EXISTS `api_key` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `org_id` BIGINT(20) NOT NULL\n, `name` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `key` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `role` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `created` DATETIME NOT NULL\n, `updated` DATETIME NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('create index IDX_api_key_org_id - v2','CREATE INDEX `IDX_api_key_org_id` ON `api_key` (`org_id`);',1,'','2022-01-01 00:00:00'), + ('create index UQE_api_key_key - v2','CREATE UNIQUE INDEX `UQE_api_key_key` ON `api_key` (`key`);',1,'','2022-01-01 00:00:00'), + ('create index UQE_api_key_org_id_name - v2','CREATE UNIQUE INDEX `UQE_api_key_org_id_name` ON `api_key` (`org_id`,`name`);',1,'','2022-01-01 00:00:00'), + ('copy api_key v1 to v2','INSERT INTO `api_key` (`name`\n, `key`\n, `role`\n, `created`\n, `updated`\n, `id`\n, `org_id`)\nSELECT `name`\n, `key`\n, `role`\n, `created`\n, `updated`\n, `id`\n, `account_id`\nFROM `api_key_v1`',1,'','2022-01-01 00:00:00'), + ('Drop old table api_key_v1','DROP TABLE IF EXISTS `api_key_v1`',1,'','2022-01-01 00:00:00'), + ('Update api_key table charset','ALTER TABLE `api_key` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, MODIFY `name` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL , MODIFY `key` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL , MODIFY `role` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL ;',1,'','2022-01-01 00:00:00'), + ('Add expires to api_key table','alter table `api_key` ADD COLUMN `expires` BIGINT(20) NULL ',1,'','2022-01-01 00:00:00'), + ('Add service account foreign key','alter table `api_key` ADD COLUMN `service_account_id` BIGINT(20) NULL ',1,'','2022-01-01 00:00:00'), + ('set service account foreign key to nil if 0','UPDATE api_key SET service_account_id = NULL WHERE service_account_id = 0;',1,'','2022-01-01 00:00:00'), + ('Add last_used_at to api_key table','alter table `api_key` ADD COLUMN `last_used_at` DATETIME NULL ',1,'','2022-01-01 00:00:00'), + ('Add is_revoked column to api_key table','alter table `api_key` ADD COLUMN `is_revoked` TINYINT(1) NULL DEFAULT 0 ',1,'','2022-01-01 00:00:00'), + ('create dashboard_snapshot table v4','CREATE TABLE IF NOT EXISTS `dashboard_snapshot` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `name` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `key` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `dashboard` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `expires` DATETIME NOT NULL\n, `created` DATETIME NOT NULL\n, `updated` DATETIME NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('drop table dashboard_snapshot_v4 #1','DROP TABLE IF EXISTS `dashboard_snapshot`',1,'','2022-01-01 00:00:00'), + ('create dashboard_snapshot table v5 #2','CREATE TABLE IF NOT EXISTS `dashboard_snapshot` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `name` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `key` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `delete_key` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `org_id` BIGINT(20) NOT NULL\n, `user_id` BIGINT(20) NOT NULL\n, `external` TINYINT(1) NOT NULL\n, `external_url` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `dashboard` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `expires` DATETIME NOT NULL\n, `created` DATETIME NOT NULL\n, `updated` DATETIME NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('create index UQE_dashboard_snapshot_key - v5','CREATE UNIQUE INDEX `UQE_dashboard_snapshot_key` ON `dashboard_snapshot` (`key`);',1,'','2022-01-01 00:00:00'), + ('create index UQE_dashboard_snapshot_delete_key - v5','CREATE UNIQUE INDEX `UQE_dashboard_snapshot_delete_key` ON `dashboard_snapshot` (`delete_key`);',1,'','2022-01-01 00:00:00'), + ('create index IDX_dashboard_snapshot_user_id - v5','CREATE INDEX `IDX_dashboard_snapshot_user_id` ON `dashboard_snapshot` (`user_id`);',1,'','2022-01-01 00:00:00'), + ('alter dashboard_snapshot to mediumtext v2','ALTER TABLE dashboard_snapshot MODIFY dashboard MEDIUMTEXT;',1,'','2022-01-01 00:00:00'), + ('Update dashboard_snapshot table charset','ALTER TABLE `dashboard_snapshot` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, MODIFY `name` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL , MODIFY `key` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL , MODIFY `delete_key` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL , MODIFY `external_url` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL , MODIFY `dashboard` MEDIUMTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL ;',1,'','2022-01-01 00:00:00'), + ('Add column external_delete_url to dashboard_snapshots table','alter table `dashboard_snapshot` ADD COLUMN `external_delete_url` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('Add encrypted dashboard json column','alter table `dashboard_snapshot` ADD COLUMN `dashboard_encrypted` BLOB NULL ',1,'','2022-01-01 00:00:00'), + ('Change dashboard_encrypted column to MEDIUMBLOB','ALTER TABLE dashboard_snapshot MODIFY dashboard_encrypted MEDIUMBLOB;',1,'','2022-01-01 00:00:00'), + ('create quota table v1','CREATE TABLE IF NOT EXISTS `quota` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `org_id` BIGINT(20) NULL\n, `user_id` BIGINT(20) NULL\n, `target` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `limit` BIGINT(20) NOT NULL\n, `created` DATETIME NOT NULL\n, `updated` DATETIME NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('create index UQE_quota_org_id_user_id_target - v1','CREATE UNIQUE INDEX `UQE_quota_org_id_user_id_target` ON `quota` (`org_id`,`user_id`,`target`);',1,'','2022-01-01 00:00:00'), + ('Update quota table charset','ALTER TABLE `quota` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, MODIFY `target` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL ;',1,'','2022-01-01 00:00:00'), + ('create plugin_setting table','CREATE TABLE IF NOT EXISTS `plugin_setting` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `org_id` BIGINT(20) NULL\n, `plugin_id` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `enabled` TINYINT(1) NOT NULL\n, `pinned` TINYINT(1) NOT NULL\n, `json_data` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n, `secure_json_data` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n, `created` DATETIME NOT NULL\n, `updated` DATETIME NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('create index UQE_plugin_setting_org_id_plugin_id - v1','CREATE UNIQUE INDEX `UQE_plugin_setting_org_id_plugin_id` ON `plugin_setting` (`org_id`,`plugin_id`);',1,'','2022-01-01 00:00:00'), + ('Add column plugin_version to plugin_settings','alter table `plugin_setting` ADD COLUMN `plugin_version` VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('Update plugin_setting table charset','ALTER TABLE `plugin_setting` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, MODIFY `plugin_id` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL , MODIFY `json_data` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL , MODIFY `secure_json_data` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL , MODIFY `plugin_version` VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ;',1,'','2022-01-01 00:00:00'), + ('update NULL org_id to 1','UPDATE plugin_setting SET org_id=1 where org_id IS NULL;',1,'','2022-01-01 00:00:00'), + ('make org_id NOT NULL and DEFAULT VALUE 1','ALTER TABLE plugin_setting MODIFY COLUMN org_id BIGINT NOT NULL DEFAULT 1;',1,'','2022-01-01 00:00:00'), + ('create session table','CREATE TABLE IF NOT EXISTS `session` (\n`key` CHAR(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci PRIMARY KEY NOT NULL\n, `data` BLOB NOT NULL\n, `expiry` INTEGER(255) NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('Drop old table playlist table','DROP TABLE IF EXISTS `playlist`',1,'','2022-01-01 00:00:00'), + ('Drop old table playlist_item table','DROP TABLE IF EXISTS `playlist_item`',1,'','2022-01-01 00:00:00'), + ('create playlist table v2','CREATE TABLE IF NOT EXISTS `playlist` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `name` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `interval` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `org_id` BIGINT(20) NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('create playlist item table v2','CREATE TABLE IF NOT EXISTS `playlist_item` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `playlist_id` BIGINT(20) NOT NULL\n, `type` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `value` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `title` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `order` INT NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('Update playlist table charset','ALTER TABLE `playlist` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, MODIFY `name` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL , MODIFY `interval` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL ;',1,'','2022-01-01 00:00:00'), + ('Update playlist_item table charset','ALTER TABLE `playlist_item` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, MODIFY `type` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL , MODIFY `value` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL , MODIFY `title` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL ;',1,'','2022-01-01 00:00:00'), + ('Add playlist column created_at','alter table `playlist` ADD COLUMN `created_at` BIGINT(20) NOT NULL DEFAULT 0 ',1,'','2022-01-01 00:00:00'), + ('Add playlist column updated_at','alter table `playlist` ADD COLUMN `updated_at` BIGINT(20) NOT NULL DEFAULT 0 ',1,'','2022-01-01 00:00:00'), + ('drop preferences table v2','DROP TABLE IF EXISTS `preferences`',1,'','2022-01-01 00:00:00'), + ('drop preferences table v3','DROP TABLE IF EXISTS `preferences`',1,'','2022-01-01 00:00:00'), + ('create preferences table v3','CREATE TABLE IF NOT EXISTS `preferences` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `org_id` BIGINT(20) NOT NULL\n, `user_id` BIGINT(20) NOT NULL\n, `version` INT NOT NULL\n, `home_dashboard_id` BIGINT(20) NOT NULL\n, `timezone` VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `theme` VARCHAR(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `created` DATETIME NOT NULL\n, `updated` DATETIME NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('Update preferences table charset','ALTER TABLE `preferences` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, MODIFY `timezone` VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL , MODIFY `theme` VARCHAR(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL ;',1,'','2022-01-01 00:00:00'), + ('Add column team_id in preferences','alter table `preferences` ADD COLUMN `team_id` BIGINT(20) NULL ',1,'','2022-01-01 00:00:00'), + ('Update team_id column values in preferences','UPDATE preferences SET team_id=0 WHERE team_id IS NULL;',1,'','2022-01-01 00:00:00'), + ('Add column week_start in preferences','alter table `preferences` ADD COLUMN `week_start` VARCHAR(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('Add column preferences.json_data','alter table `preferences` ADD COLUMN `json_data` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('alter preferences.json_data to mediumtext v1','ALTER TABLE preferences MODIFY json_data MEDIUMTEXT;',1,'','2022-01-01 00:00:00'), + ('Add preferences index org_id','CREATE INDEX `IDX_preferences_org_id` ON `preferences` (`org_id`);',1,'','2022-01-01 00:00:00'), + ('Add preferences index user_id','CREATE INDEX `IDX_preferences_user_id` ON `preferences` (`user_id`);',1,'','2022-01-01 00:00:00'), + ('Add home_dashboard_uid column to preferences table','alter table `preferences` ADD COLUMN `home_dashboard_uid` VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('Add missing dashboard_uid to preferences table','code migration',1,'','2022-01-01 00:00:00'), + ('create alert table v1','CREATE TABLE IF NOT EXISTS `alert` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `version` BIGINT(20) NOT NULL\n, `dashboard_id` BIGINT(20) NOT NULL\n, `panel_id` BIGINT(20) NOT NULL\n, `org_id` BIGINT(20) NOT NULL\n, `name` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `message` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `state` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `settings` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `frequency` BIGINT(20) NOT NULL\n, `handler` BIGINT(20) NOT NULL\n, `severity` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `silenced` TINYINT(1) NOT NULL\n, `execution_error` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `eval_data` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n, `eval_date` DATETIME NULL\n, `new_state_date` DATETIME NOT NULL\n, `state_changes` INT NOT NULL\n, `created` DATETIME NOT NULL\n, `updated` DATETIME NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('add index alert org_id & id ','CREATE INDEX `IDX_alert_org_id_id` ON `alert` (`org_id`,`id`);',1,'','2022-01-01 00:00:00'), + ('add index alert state','CREATE INDEX `IDX_alert_state` ON `alert` (`state`);',1,'','2022-01-01 00:00:00'), + ('add index alert dashboard_id','CREATE INDEX `IDX_alert_dashboard_id` ON `alert` (`dashboard_id`);',1,'','2022-01-01 00:00:00'), + ('Create alert_rule_tag table v1','CREATE TABLE IF NOT EXISTS `alert_rule_tag` (\n`alert_id` BIGINT(20) NOT NULL\n, `tag_id` BIGINT(20) NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('Add unique index alert_rule_tag.alert_id_tag_id','CREATE UNIQUE INDEX `UQE_alert_rule_tag_alert_id_tag_id` ON `alert_rule_tag` (`alert_id`,`tag_id`);',1,'','2022-01-01 00:00:00'), + ('drop index UQE_alert_rule_tag_alert_id_tag_id - v1','DROP INDEX `UQE_alert_rule_tag_alert_id_tag_id` ON `alert_rule_tag`',1,'','2022-01-01 00:00:00'), + ('Rename table alert_rule_tag to alert_rule_tag_v1 - v1','ALTER TABLE `alert_rule_tag` RENAME TO `alert_rule_tag_v1`',1,'','2022-01-01 00:00:00'), + ('Create alert_rule_tag table v2','CREATE TABLE IF NOT EXISTS `alert_rule_tag` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `alert_id` BIGINT(20) NOT NULL\n, `tag_id` BIGINT(20) NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('create index UQE_alert_rule_tag_alert_id_tag_id - Add unique index alert_rule_tag.alert_id_tag_id V2','CREATE UNIQUE INDEX `UQE_alert_rule_tag_alert_id_tag_id` ON `alert_rule_tag` (`alert_id`,`tag_id`);',1,'','2022-01-01 00:00:00'), + ('copy alert_rule_tag v1 to v2','INSERT INTO `alert_rule_tag` (`alert_id`\n, `tag_id`)\nSELECT `alert_id`\n, `tag_id`\nFROM `alert_rule_tag_v1`',1,'','2022-01-01 00:00:00'), + ('drop table alert_rule_tag_v1','DROP TABLE IF EXISTS `alert_rule_tag_v1`',1,'','2022-01-01 00:00:00'), + ('create alert_notification table v1','CREATE TABLE IF NOT EXISTS `alert_notification` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `org_id` BIGINT(20) NOT NULL\n, `name` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `type` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `settings` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `created` DATETIME NOT NULL\n, `updated` DATETIME NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('Add column is_default','alter table `alert_notification` ADD COLUMN `is_default` TINYINT(1) NOT NULL DEFAULT 0 ',1,'','2022-01-01 00:00:00'), + ('Add column frequency','alter table `alert_notification` ADD COLUMN `frequency` BIGINT(20) NULL ',1,'','2022-01-01 00:00:00'), + ('Add column send_reminder','alter table `alert_notification` ADD COLUMN `send_reminder` TINYINT(1) NULL DEFAULT 0 ',1,'','2022-01-01 00:00:00'), + ('Add column disable_resolve_message','alter table `alert_notification` ADD COLUMN `disable_resolve_message` TINYINT(1) NOT NULL DEFAULT 0 ',1,'','2022-01-01 00:00:00'), + ('add index alert_notification org_id & name','CREATE UNIQUE INDEX `UQE_alert_notification_org_id_name` ON `alert_notification` (`org_id`,`name`);',1,'','2022-01-01 00:00:00'), + ('Update alert table charset','ALTER TABLE `alert` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, MODIFY `name` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL , MODIFY `message` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL , MODIFY `state` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL , MODIFY `settings` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL , MODIFY `severity` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL , MODIFY `execution_error` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL , MODIFY `eval_data` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ;',1,'','2022-01-01 00:00:00'), + ('Update alert_notification table charset','ALTER TABLE `alert_notification` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, MODIFY `name` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL , MODIFY `type` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL , MODIFY `settings` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL ;',1,'','2022-01-01 00:00:00'), + ('create notification_journal table v1','CREATE TABLE IF NOT EXISTS `alert_notification_journal` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `org_id` BIGINT(20) NOT NULL\n, `alert_id` BIGINT(20) NOT NULL\n, `notifier_id` BIGINT(20) NOT NULL\n, `sent_at` BIGINT(20) NOT NULL\n, `success` TINYINT(1) NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('add index notification_journal org_id & alert_id & notifier_id','CREATE INDEX `IDX_alert_notification_journal_org_id_alert_id_notifier_id` ON `alert_notification_journal` (`org_id`,`alert_id`,`notifier_id`);',1,'','2022-01-01 00:00:00'), + ('drop alert_notification_journal','DROP TABLE IF EXISTS `alert_notification_journal`',1,'','2022-01-01 00:00:00'), + ('create alert_notification_state table v1','CREATE TABLE IF NOT EXISTS `alert_notification_state` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `org_id` BIGINT(20) NOT NULL\n, `alert_id` BIGINT(20) NOT NULL\n, `notifier_id` BIGINT(20) NOT NULL\n, `state` VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `version` BIGINT(20) NOT NULL\n, `updated_at` BIGINT(20) NOT NULL\n, `alert_rule_state_updated_version` BIGINT(20) NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('add index alert_notification_state org_id & alert_id & notifier_id','CREATE UNIQUE INDEX `UQE_alert_notification_state_org_id_alert_id_notifier_id` ON `alert_notification_state` (`org_id`,`alert_id`,`notifier_id`);',1,'','2022-01-01 00:00:00'), + ('Add for to alert table','alter table `alert` ADD COLUMN `for` BIGINT(20) NULL ',1,'','2022-01-01 00:00:00'), + ('Add column uid in alert_notification','alter table `alert_notification` ADD COLUMN `uid` VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('Update uid column values in alert_notification','UPDATE alert_notification SET uid=lpad(id,9,\'0\') WHERE uid IS NULL;',1,'','2022-01-01 00:00:00'), + ('Add unique index alert_notification_org_id_uid','CREATE UNIQUE INDEX `UQE_alert_notification_org_id_uid` ON `alert_notification` (`org_id`,`uid`);',1,'','2022-01-01 00:00:00'), + ('Remove unique index org_id_name','DROP INDEX `UQE_alert_notification_org_id_name` ON `alert_notification`',1,'','2022-01-01 00:00:00'), + ('Add column secure_settings in alert_notification','alter table `alert_notification` ADD COLUMN `secure_settings` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('alter alert.settings to mediumtext','ALTER TABLE alert MODIFY settings MEDIUMTEXT;',1,'','2022-01-01 00:00:00'), + ('Add non-unique index alert_notification_state_alert_id','CREATE INDEX `IDX_alert_notification_state_alert_id` ON `alert_notification_state` (`alert_id`);',1,'','2022-01-01 00:00:00'), + ('Add non-unique index alert_rule_tag_alert_id','CREATE INDEX `IDX_alert_rule_tag_alert_id` ON `alert_rule_tag` (`alert_id`);',1,'','2022-01-01 00:00:00'), + ('Drop old annotation table v4','DROP TABLE IF EXISTS `annotation`',1,'','2022-01-01 00:00:00'), + ('create annotation table v5','CREATE TABLE IF NOT EXISTS `annotation` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `org_id` BIGINT(20) NOT NULL\n, `alert_id` BIGINT(20) NULL\n, `user_id` BIGINT(20) NULL\n, `dashboard_id` BIGINT(20) NULL\n, `panel_id` BIGINT(20) NULL\n, `category_id` BIGINT(20) NULL\n, `type` VARCHAR(25) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `title` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `text` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `metric` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n, `prev_state` VARCHAR(25) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `new_state` VARCHAR(25) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `data` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `epoch` BIGINT(20) NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('add index annotation 0 v3','CREATE INDEX `IDX_annotation_org_id_alert_id` ON `annotation` (`org_id`,`alert_id`);',1,'','2022-01-01 00:00:00'), + ('add index annotation 1 v3','CREATE INDEX `IDX_annotation_org_id_type` ON `annotation` (`org_id`,`type`);',1,'','2022-01-01 00:00:00'), + ('add index annotation 2 v3','CREATE INDEX `IDX_annotation_org_id_category_id` ON `annotation` (`org_id`,`category_id`);',1,'','2022-01-01 00:00:00'), + ('add index annotation 3 v3','CREATE INDEX `IDX_annotation_org_id_dashboard_id_panel_id_epoch` ON `annotation` (`org_id`,`dashboard_id`,`panel_id`,`epoch`);',1,'','2022-01-01 00:00:00'), + ('add index annotation 4 v3','CREATE INDEX `IDX_annotation_org_id_epoch` ON `annotation` (`org_id`,`epoch`);',1,'','2022-01-01 00:00:00'), + ('Update annotation table charset','ALTER TABLE `annotation` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, MODIFY `type` VARCHAR(25) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL , MODIFY `title` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL , MODIFY `text` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL , MODIFY `metric` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL , MODIFY `prev_state` VARCHAR(25) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL , MODIFY `new_state` VARCHAR(25) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL , MODIFY `data` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL ;',1,'','2022-01-01 00:00:00'), + ('Add column region_id to annotation table','alter table `annotation` ADD COLUMN `region_id` BIGINT(20) NULL DEFAULT 0 ',1,'','2022-01-01 00:00:00'), + ('Drop category_id index','DROP INDEX `IDX_annotation_org_id_category_id` ON `annotation`',1,'','2022-01-01 00:00:00'), + ('Add column tags to annotation table','alter table `annotation` ADD COLUMN `tags` VARCHAR(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('Create annotation_tag table v2','CREATE TABLE IF NOT EXISTS `annotation_tag` (\n`annotation_id` BIGINT(20) NOT NULL\n, `tag_id` BIGINT(20) NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('Add unique index annotation_tag.annotation_id_tag_id','CREATE UNIQUE INDEX `UQE_annotation_tag_annotation_id_tag_id` ON `annotation_tag` (`annotation_id`,`tag_id`);',1,'','2022-01-01 00:00:00'), + ('drop index UQE_annotation_tag_annotation_id_tag_id - v2','DROP INDEX `UQE_annotation_tag_annotation_id_tag_id` ON `annotation_tag`',1,'','2022-01-01 00:00:00'), + ('Rename table annotation_tag to annotation_tag_v2 - v2','ALTER TABLE `annotation_tag` RENAME TO `annotation_tag_v2`',1,'','2022-01-01 00:00:00'), + ('Create annotation_tag table v3','CREATE TABLE IF NOT EXISTS `annotation_tag` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `annotation_id` BIGINT(20) NOT NULL\n, `tag_id` BIGINT(20) NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('create index UQE_annotation_tag_annotation_id_tag_id - Add unique index annotation_tag.annotation_id_tag_id V3','CREATE UNIQUE INDEX `UQE_annotation_tag_annotation_id_tag_id` ON `annotation_tag` (`annotation_id`,`tag_id`);',1,'','2022-01-01 00:00:00'), + ('copy annotation_tag v2 to v3','INSERT INTO `annotation_tag` (`annotation_id`\n, `tag_id`)\nSELECT `annotation_id`\n, `tag_id`\nFROM `annotation_tag_v2`',1,'','2022-01-01 00:00:00'), + ('drop table annotation_tag_v2','DROP TABLE IF EXISTS `annotation_tag_v2`',1,'','2022-01-01 00:00:00'), + ('Update alert annotations and set TEXT to empty','UPDATE annotation SET TEXT = \'\' WHERE alert_id > 0',1,'','2022-01-01 00:00:00'), + ('Add created time to annotation table','alter table `annotation` ADD COLUMN `created` BIGINT(20) NULL DEFAULT 0 ',1,'','2022-01-01 00:00:00'), + ('Add updated time to annotation table','alter table `annotation` ADD COLUMN `updated` BIGINT(20) NULL DEFAULT 0 ',1,'','2022-01-01 00:00:00'), + ('Add index for created in annotation table','CREATE INDEX `IDX_annotation_org_id_created` ON `annotation` (`org_id`,`created`);',1,'','2022-01-01 00:00:00'), + ('Add index for updated in annotation table','CREATE INDEX `IDX_annotation_org_id_updated` ON `annotation` (`org_id`,`updated`);',1,'','2022-01-01 00:00:00'), + ('Convert existing annotations from seconds to milliseconds','UPDATE annotation SET epoch = (epoch*1000) where epoch < 9999999999',1,'','2022-01-01 00:00:00'), + ('Add epoch_end column','alter table `annotation` ADD COLUMN `epoch_end` BIGINT(20) NOT NULL DEFAULT 0 ',1,'','2022-01-01 00:00:00'), + ('Add index for epoch_end','CREATE INDEX `IDX_annotation_org_id_epoch_epoch_end` ON `annotation` (`org_id`,`epoch`,`epoch_end`);',1,'','2022-01-01 00:00:00'), + ('Make epoch_end the same as epoch','UPDATE annotation SET epoch_end = epoch',1,'','2022-01-01 00:00:00'), + ('Move region to single row','code migration',1,'','2022-01-01 00:00:00'), + ('Remove index org_id_epoch from annotation table','DROP INDEX `IDX_annotation_org_id_epoch` ON `annotation`',1,'','2022-01-01 00:00:00'), + ('Remove index org_id_dashboard_id_panel_id_epoch from annotation table','DROP INDEX `IDX_annotation_org_id_dashboard_id_panel_id_epoch` ON `annotation`',1,'','2022-01-01 00:00:00'), + ('Add index for org_id_dashboard_id_epoch_end_epoch on annotation table','CREATE INDEX `IDX_annotation_org_id_dashboard_id_epoch_end_epoch` ON `annotation` (`org_id`,`dashboard_id`,`epoch_end`,`epoch`);',1,'','2022-01-01 00:00:00'), + ('Add index for org_id_epoch_end_epoch on annotation table','CREATE INDEX `IDX_annotation_org_id_epoch_end_epoch` ON `annotation` (`org_id`,`epoch_end`,`epoch`);',1,'','2022-01-01 00:00:00'), + ('Remove index org_id_epoch_epoch_end from annotation table','DROP INDEX `IDX_annotation_org_id_epoch_epoch_end` ON `annotation`',1,'','2022-01-01 00:00:00'), + ('Add index for alert_id on annotation table','CREATE INDEX `IDX_annotation_alert_id` ON `annotation` (`alert_id`);',1,'','2022-01-01 00:00:00'), + ('Increase tags column to length 4096','ALTER TABLE annotation MODIFY tags VARCHAR(4096);',1,'','2022-01-01 00:00:00'), + ('Increase prev_state column to length 40 not null','ALTER TABLE annotation MODIFY prev_state VARCHAR(40) NOT NULL;',1,'','2022-01-01 00:00:00'), + ('Increase new_state column to length 40 not null','ALTER TABLE annotation MODIFY new_state VARCHAR(40) NOT NULL;',1,'','2022-01-01 00:00:00'), + ('Add dashboard_uid column to annotation table','alter table `annotation` ADD COLUMN `dashboard_uid` VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('Add missing dashboard_uid to annotation table','code migration',1,'','2022-01-01 00:00:00'), + ('create test_data table','CREATE TABLE IF NOT EXISTS `test_data` (\n`id` INT PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `metric1` VARCHAR(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n, `metric2` VARCHAR(150) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n, `value_big_int` BIGINT(20) NULL\n, `value_double` DOUBLE NULL\n, `value_float` FLOAT NULL\n, `value_int` INT NULL\n, `time_epoch` BIGINT(20) NOT NULL\n, `time_date_time` DATETIME NOT NULL\n, `time_time_stamp` TIMESTAMP NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('create dashboard_version table v1','CREATE TABLE IF NOT EXISTS `dashboard_version` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `dashboard_id` BIGINT(20) NOT NULL\n, `parent_version` INT NOT NULL\n, `restored_from` INT NOT NULL\n, `version` INT NOT NULL\n, `created` DATETIME NOT NULL\n, `created_by` BIGINT(20) NOT NULL\n, `message` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `data` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('add index dashboard_version.dashboard_id','CREATE INDEX `IDX_dashboard_version_dashboard_id` ON `dashboard_version` (`dashboard_id`);',1,'','2022-01-01 00:00:00'), + ('add unique index dashboard_version.dashboard_id and dashboard_version.version','CREATE UNIQUE INDEX `UQE_dashboard_version_dashboard_id_version` ON `dashboard_version` (`dashboard_id`,`version`);',1,'','2022-01-01 00:00:00'), + ('Set dashboard version to 1 where 0','UPDATE dashboard SET version = 1 WHERE version = 0',1,'','2022-01-01 00:00:00'), + ('save existing dashboard data in dashboard_version table v1','INSERT INTO dashboard_version\n(\n dashboard_id,\n version,\n parent_version,\n restored_from,\n created,\n created_by,\n message,\n data\n)\nSELECT\n dashboard.id,\n dashboard.version,\n dashboard.version,\n dashboard.version,\n dashboard.updated,\n COALESCE(dashboard.updated_by, -1),\n \'\',\n dashboard.data\nFROM dashboard;',1,'','2022-01-01 00:00:00'), + ('alter dashboard_version.data to mediumtext v1','ALTER TABLE dashboard_version MODIFY data MEDIUMTEXT;',1,'','2022-01-01 00:00:00'), + ('Add apiVersion for dashboard_version','alter table `dashboard_version` ADD COLUMN `api_version` VARCHAR(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('create team table','CREATE TABLE IF NOT EXISTS `team` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `name` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `org_id` BIGINT(20) NOT NULL\n, `created` DATETIME NOT NULL\n, `updated` DATETIME NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('add index team.org_id','CREATE INDEX `IDX_team_org_id` ON `team` (`org_id`);',1,'','2022-01-01 00:00:00'), + ('add unique index team_org_id_name','CREATE UNIQUE INDEX `UQE_team_org_id_name` ON `team` (`org_id`,`name`);',1,'','2022-01-01 00:00:00'), + ('Add column uid in team','alter table `team` ADD COLUMN `uid` VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('Update uid column values in team','UPDATE team SET uid=concat(\'t\',lpad(id,9,\'0\')) WHERE uid IS NULL;',1,'','2022-01-01 00:00:00'), + ('Add unique index team_org_id_uid','CREATE UNIQUE INDEX `UQE_team_org_id_uid` ON `team` (`org_id`,`uid`);',1,'','2022-01-01 00:00:00'), + ('Add column external_uid in team','alter table `team` ADD COLUMN `external_uid` VARCHAR(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('Add column is_provisioned in team','alter table `team` ADD COLUMN `is_provisioned` TINYINT(1) NULL ',1,'','2022-01-01 00:00:00'), + ('create team member table','CREATE TABLE IF NOT EXISTS `team_member` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `org_id` BIGINT(20) NOT NULL\n, `team_id` BIGINT(20) NOT NULL\n, `user_id` BIGINT(20) NOT NULL\n, `created` DATETIME NOT NULL\n, `updated` DATETIME NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('add index team_member.org_id','CREATE INDEX `IDX_team_member_org_id` ON `team_member` (`org_id`);',1,'','2022-01-01 00:00:00'), + ('add unique index team_member_org_id_team_id_user_id','CREATE UNIQUE INDEX `UQE_team_member_org_id_team_id_user_id` ON `team_member` (`org_id`,`team_id`,`user_id`);',1,'','2022-01-01 00:00:00'), + ('add index team_member.team_id','CREATE INDEX `IDX_team_member_team_id` ON `team_member` (`team_id`);',1,'','2022-01-01 00:00:00'), + ('Add column email to team table','alter table `team` ADD COLUMN `email` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('Add column external to team_member table','alter table `team_member` ADD COLUMN `external` TINYINT(1) NULL ',1,'','2022-01-01 00:00:00'), + ('Add column permission to team_member table','alter table `team_member` ADD COLUMN `permission` SMALLINT NULL ',1,'','2022-01-01 00:00:00'), + ('add unique index team_member_user_id_org_id','CREATE INDEX `IDX_team_member_user_id_org_id` ON `team_member` (`user_id`,`org_id`);',1,'','2022-01-01 00:00:00'), + ('Add column uid in team_member','alter table `team_member` ADD COLUMN `uid` VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('Update uid column values in team_member','UPDATE team_member SET uid=concat(\'tm\',lpad(id,9,\'0\')) WHERE uid IS NULL OR uid = \'\';',1,'','2022-01-01 00:00:00'), + ('Add unique index team_member_uid','CREATE UNIQUE INDEX `UQE_team_member_uid` ON `team_member` (`uid`);',1,'','2022-01-01 00:00:00'), + ('create dashboard acl table','CREATE TABLE IF NOT EXISTS `dashboard_acl` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `org_id` BIGINT(20) NOT NULL\n, `dashboard_id` BIGINT(20) NOT NULL\n, `user_id` BIGINT(20) NULL\n, `team_id` BIGINT(20) NULL\n, `permission` SMALLINT NOT NULL DEFAULT 4\n, `role` VARCHAR(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n, `created` DATETIME NOT NULL\n, `updated` DATETIME NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('add index dashboard_acl_dashboard_id','CREATE INDEX `IDX_dashboard_acl_dashboard_id` ON `dashboard_acl` (`dashboard_id`);',1,'','2022-01-01 00:00:00'), + ('add unique index dashboard_acl_dashboard_id_user_id','CREATE UNIQUE INDEX `UQE_dashboard_acl_dashboard_id_user_id` ON `dashboard_acl` (`dashboard_id`,`user_id`);',1,'','2022-01-01 00:00:00'), + ('add unique index dashboard_acl_dashboard_id_team_id','CREATE UNIQUE INDEX `UQE_dashboard_acl_dashboard_id_team_id` ON `dashboard_acl` (`dashboard_id`,`team_id`);',1,'','2022-01-01 00:00:00'), + ('add index dashboard_acl_user_id','CREATE INDEX `IDX_dashboard_acl_user_id` ON `dashboard_acl` (`user_id`);',1,'','2022-01-01 00:00:00'), + ('add index dashboard_acl_team_id','CREATE INDEX `IDX_dashboard_acl_team_id` ON `dashboard_acl` (`team_id`);',1,'','2022-01-01 00:00:00'), + ('add index dashboard_acl_org_id_role','CREATE INDEX `IDX_dashboard_acl_org_id_role` ON `dashboard_acl` (`org_id`,`role`);',1,'','2022-01-01 00:00:00'), + ('add index dashboard_permission','CREATE INDEX `IDX_dashboard_acl_permission` ON `dashboard_acl` (`permission`);',1,'','2022-01-01 00:00:00'), + ('save default acl rules in dashboard_acl table','\nINSERT INTO dashboard_acl\n (\n org_id,\n dashboard_id,\n permission,\n role,\n created,\n updated\n )\n VALUES\n (-1,-1, 1,\'Viewer\',\'2017-06-20\',\'2017-06-20\'),\n (-1,-1, 2,\'Editor\',\'2017-06-20\',\'2017-06-20\')\n ',1,'','2022-01-01 00:00:00'), + ('delete acl rules for deleted dashboards and folders','DELETE FROM dashboard_acl WHERE dashboard_id NOT IN (SELECT id FROM dashboard) AND dashboard_id != -1',1,'','2022-01-01 00:00:00'), + ('create tag table','CREATE TABLE IF NOT EXISTS `tag` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `key` VARCHAR(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `value` VARCHAR(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('add index tag.key_value','CREATE UNIQUE INDEX `UQE_tag_key_value` ON `tag` (`key`,`value`);',1,'','2022-01-01 00:00:00'), + ('create login attempt table','CREATE TABLE IF NOT EXISTS `login_attempt` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `username` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `ip_address` VARCHAR(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `created` DATETIME NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('add index login_attempt.username','CREATE INDEX `IDX_login_attempt_username` ON `login_attempt` (`username`);',1,'','2022-01-01 00:00:00'), + ('drop index IDX_login_attempt_username - v1','DROP INDEX `IDX_login_attempt_username` ON `login_attempt`',1,'','2022-01-01 00:00:00'), + ('Rename table login_attempt to login_attempt_tmp_qwerty - v1','ALTER TABLE `login_attempt` RENAME TO `login_attempt_tmp_qwerty`',1,'','2022-01-01 00:00:00'), + ('create login_attempt v2','CREATE TABLE IF NOT EXISTS `login_attempt` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `username` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `ip_address` VARCHAR(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `created` INT NOT NULL DEFAULT 0\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('create index IDX_login_attempt_username - v2','CREATE INDEX `IDX_login_attempt_username` ON `login_attempt` (`username`);',1,'','2022-01-01 00:00:00'), + ('copy login_attempt v1 to v2','INSERT INTO `login_attempt` (`id`\n, `username`\n, `ip_address`)\nSELECT `id`\n, `username`\n, `ip_address`\nFROM `login_attempt_tmp_qwerty`',1,'','2022-01-01 00:00:00'), + ('drop login_attempt_tmp_qwerty','DROP TABLE IF EXISTS `login_attempt_tmp_qwerty`',1,'','2022-01-01 00:00:00'), + ('increase login_attempt.ip_address column length for IPv6 support','ALTER TABLE login_attempt MODIFY ip_address VARCHAR(50);',1,'','2022-01-01 00:00:00'), + ('alter table login_attempt alter column created type to bigint','ALTER TABLE login_attempt MODIFY created BIGINT;',1,'','2022-01-01 00:00:00'), + ('create user auth table','CREATE TABLE IF NOT EXISTS `user_auth` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `user_id` BIGINT(20) NOT NULL\n, `auth_module` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `auth_id` VARCHAR(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `created` DATETIME NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('create index IDX_user_auth_auth_module_auth_id - v1','CREATE INDEX `IDX_user_auth_auth_module_auth_id` ON `user_auth` (`auth_module`,`auth_id`);',1,'','2022-01-01 00:00:00'), + ('alter user_auth.auth_id to length 190','ALTER TABLE user_auth MODIFY auth_id VARCHAR(190);',1,'','2022-01-01 00:00:00'), + ('Add OAuth access token to user_auth','alter table `user_auth` ADD COLUMN `o_auth_access_token` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('Add OAuth refresh token to user_auth','alter table `user_auth` ADD COLUMN `o_auth_refresh_token` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('Add OAuth token type to user_auth','alter table `user_auth` ADD COLUMN `o_auth_token_type` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('Add OAuth expiry to user_auth','alter table `user_auth` ADD COLUMN `o_auth_expiry` DATETIME NULL ',1,'','2022-01-01 00:00:00'), + ('Add index to user_id column in user_auth','CREATE INDEX `IDX_user_auth_user_id` ON `user_auth` (`user_id`);',1,'','2022-01-01 00:00:00'), + ('Add OAuth ID token to user_auth','alter table `user_auth` ADD COLUMN `o_auth_id_token` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('Add user_unique_id to user_auth','alter table `user_auth` ADD COLUMN `external_uid` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('create server_lock table','CREATE TABLE IF NOT EXISTS `server_lock` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `operation_uid` VARCHAR(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `version` BIGINT(20) NOT NULL\n, `last_execution` BIGINT(20) NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('add index server_lock.operation_uid','CREATE UNIQUE INDEX `UQE_server_lock_operation_uid` ON `server_lock` (`operation_uid`);',1,'','2022-01-01 00:00:00'), + ('create user auth token table','CREATE TABLE IF NOT EXISTS `user_auth_token` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `user_id` BIGINT(20) NOT NULL\n, `auth_token` VARCHAR(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `prev_auth_token` VARCHAR(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `user_agent` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `client_ip` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `auth_token_seen` TINYINT(1) NOT NULL\n, `seen_at` INT NULL\n, `rotated_at` INT NOT NULL\n, `created_at` INT NOT NULL\n, `updated_at` INT NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('add unique index user_auth_token.auth_token','CREATE UNIQUE INDEX `UQE_user_auth_token_auth_token` ON `user_auth_token` (`auth_token`);',1,'','2022-01-01 00:00:00'), + ('add unique index user_auth_token.prev_auth_token','CREATE UNIQUE INDEX `UQE_user_auth_token_prev_auth_token` ON `user_auth_token` (`prev_auth_token`);',1,'','2022-01-01 00:00:00'), + ('add index user_auth_token.user_id','CREATE INDEX `IDX_user_auth_token_user_id` ON `user_auth_token` (`user_id`);',1,'','2022-01-01 00:00:00'), + ('Add revoked_at to the user auth token','alter table `user_auth_token` ADD COLUMN `revoked_at` INT NULL ',1,'','2022-01-01 00:00:00'), + ('add index user_auth_token.revoked_at','CREATE INDEX `IDX_user_auth_token_revoked_at` ON `user_auth_token` (`revoked_at`);',1,'','2022-01-01 00:00:00'), + ('add external_session_id to user_auth_token','alter table `user_auth_token` ADD COLUMN `external_session_id` BIGINT(20) NULL ',1,'','2022-01-01 00:00:00'), + ('create cache_data table','CREATE TABLE IF NOT EXISTS `cache_data` (\n`cache_key` VARCHAR(168) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci PRIMARY KEY NOT NULL\n, `data` BLOB NOT NULL\n, `expires` INTEGER(255) NOT NULL\n, `created_at` INTEGER(255) NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('add unique index cache_data.cache_key','CREATE UNIQUE INDEX `UQE_cache_data_cache_key` ON `cache_data` (`cache_key`);',1,'','2022-01-01 00:00:00'), + ('create short_url table v1','CREATE TABLE IF NOT EXISTS `short_url` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `org_id` BIGINT(20) NOT NULL\n, `uid` VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `path` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `created_by` INT NOT NULL\n, `created_at` INT NOT NULL\n, `last_seen_at` INT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('add index short_url.org_id-uid','CREATE UNIQUE INDEX `UQE_short_url_org_id_uid` ON `short_url` (`org_id`,`uid`);',1,'','2022-01-01 00:00:00'), + ('alter table short_url alter column created_by type to bigint','ALTER TABLE short_url MODIFY created_by BIGINT;',1,'','2022-01-01 00:00:00'), + ('delete alert_definition table','DROP TABLE IF EXISTS `alert_definition`',1,'','2022-01-01 00:00:00'), + ('recreate alert_definition table','CREATE TABLE IF NOT EXISTS `alert_definition` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `org_id` BIGINT(20) NOT NULL\n, `title` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `condition` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `data` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `updated` DATETIME NOT NULL\n, `interval_seconds` BIGINT(20) NOT NULL DEFAULT 60\n, `version` INT NOT NULL DEFAULT 0\n, `uid` VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 0\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('add index in alert_definition on org_id and title columns','CREATE INDEX `IDX_alert_definition_org_id_title` ON `alert_definition` (`org_id`,`title`);',1,'','2022-01-01 00:00:00'), + ('add index in alert_definition on org_id and uid columns','CREATE INDEX `IDX_alert_definition_org_id_uid` ON `alert_definition` (`org_id`,`uid`);',1,'','2022-01-01 00:00:00'), + ('alter alert_definition table data column to mediumtext in mysql','ALTER TABLE alert_definition MODIFY data MEDIUMTEXT;',1,'','2022-01-01 00:00:00'), + ('drop index in alert_definition on org_id and title columns','DROP INDEX `IDX_alert_definition_org_id_title` ON `alert_definition`',1,'','2022-01-01 00:00:00'), + ('drop index in alert_definition on org_id and uid columns','DROP INDEX `IDX_alert_definition_org_id_uid` ON `alert_definition`',1,'','2022-01-01 00:00:00'), + ('add unique index in alert_definition on org_id and title columns','CREATE UNIQUE INDEX `UQE_alert_definition_org_id_title` ON `alert_definition` (`org_id`,`title`);',1,'','2022-01-01 00:00:00'), + ('add unique index in alert_definition on org_id and uid columns','CREATE UNIQUE INDEX `UQE_alert_definition_org_id_uid` ON `alert_definition` (`org_id`,`uid`);',1,'','2022-01-01 00:00:00'), + ('Add column paused in alert_definition','alter table `alert_definition` ADD COLUMN `paused` TINYINT(1) NOT NULL DEFAULT 0 ',1,'','2022-01-01 00:00:00'), + ('drop alert_definition table','DROP TABLE IF EXISTS `alert_definition`',1,'','2022-01-01 00:00:00'), + ('delete alert_definition_version table','DROP TABLE IF EXISTS `alert_definition_version`',1,'','2022-01-01 00:00:00'), + ('recreate alert_definition_version table','CREATE TABLE IF NOT EXISTS `alert_definition_version` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `alert_definition_id` BIGINT(20) NOT NULL\n, `alert_definition_uid` VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 0\n, `parent_version` INT NOT NULL\n, `restored_from` INT NOT NULL\n, `version` INT NOT NULL\n, `created` DATETIME NOT NULL\n, `title` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `condition` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `data` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `interval_seconds` BIGINT(20) NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('add index in alert_definition_version table on alert_definition_id and version columns','CREATE UNIQUE INDEX `UQE_alert_definition_version_alert_definition_id_version` ON `alert_definition_version` (`alert_definition_id`,`version`);',1,'','2022-01-01 00:00:00'), + ('add index in alert_definition_version table on alert_definition_uid and version columns','CREATE UNIQUE INDEX `UQE_alert_definition_version_alert_definition_uid_version` ON `alert_definition_version` (`alert_definition_uid`,`version`);',1,'','2022-01-01 00:00:00'), + ('alter alert_definition_version table data column to mediumtext in mysql','ALTER TABLE alert_definition_version MODIFY data MEDIUMTEXT;',1,'','2022-01-01 00:00:00'), + ('drop alert_definition_version table','DROP TABLE IF EXISTS `alert_definition_version`',1,'','2022-01-01 00:00:00'), + ('create alert_instance table','CREATE TABLE IF NOT EXISTS `alert_instance` (\n`def_org_id` BIGINT(20) NOT NULL\n, `def_uid` VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 0\n, `labels` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `labels_hash` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `current_state` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `current_state_since` BIGINT(20) NOT NULL\n, `last_eval_time` BIGINT(20) NOT NULL\n, PRIMARY KEY ( `def_org_id`,`def_uid`,`labels_hash` )) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('add index in alert_instance table on def_org_id, def_uid and current_state columns','CREATE INDEX `IDX_alert_instance_def_org_id_def_uid_current_state` ON `alert_instance` (`def_org_id`,`def_uid`,`current_state`);',1,'','2022-01-01 00:00:00'), + ('add index in alert_instance table on def_org_id, current_state columns','CREATE INDEX `IDX_alert_instance_def_org_id_current_state` ON `alert_instance` (`def_org_id`,`current_state`);',1,'','2022-01-01 00:00:00'), + ('add column current_state_end to alert_instance','alter table `alert_instance` ADD COLUMN `current_state_end` BIGINT(20) NOT NULL DEFAULT 0 ',1,'','2022-01-01 00:00:00'), + ('remove index def_org_id, def_uid, current_state on alert_instance','DROP INDEX `IDX_alert_instance_def_org_id_def_uid_current_state` ON `alert_instance`',1,'','2022-01-01 00:00:00'), + ('remove index def_org_id, current_state on alert_instance','DROP INDEX `IDX_alert_instance_def_org_id_current_state` ON `alert_instance`',1,'','2022-01-01 00:00:00'), + ('rename def_org_id to rule_org_id in alert_instance','ALTER TABLE alert_instance CHANGE def_org_id rule_org_id BIGINT;',1,'','2022-01-01 00:00:00'), + ('rename def_uid to rule_uid in alert_instance','ALTER TABLE alert_instance CHANGE def_uid rule_uid VARCHAR(40);',1,'','2022-01-01 00:00:00'), + ('add index rule_org_id, rule_uid, current_state on alert_instance','CREATE INDEX `IDX_alert_instance_rule_org_id_rule_uid_current_state` ON `alert_instance` (`rule_org_id`,`rule_uid`,`current_state`);',1,'','2022-01-01 00:00:00'), + ('add index rule_org_id, current_state on alert_instance','CREATE INDEX `IDX_alert_instance_rule_org_id_current_state` ON `alert_instance` (`rule_org_id`,`current_state`);',1,'','2022-01-01 00:00:00'), + ('add current_reason column related to current_state','alter table `alert_instance` ADD COLUMN `current_reason` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('add result_fingerprint column to alert_instance','alter table `alert_instance` ADD COLUMN `result_fingerprint` VARCHAR(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('create alert_rule table','CREATE TABLE IF NOT EXISTS `alert_rule` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `org_id` BIGINT(20) NOT NULL\n, `title` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `condition` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `data` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `updated` DATETIME NOT NULL\n, `interval_seconds` BIGINT(20) NOT NULL DEFAULT 60\n, `version` INT NOT NULL DEFAULT 0\n, `uid` VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 0\n, `namespace_uid` VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `rule_group` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `no_data_state` VARCHAR(15) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT \'NoData\'\n, `exec_err_state` VARCHAR(15) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT \'Alerting\'\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('add index in alert_rule on org_id and title columns','CREATE UNIQUE INDEX `UQE_alert_rule_org_id_title` ON `alert_rule` (`org_id`,`title`);',1,'','2022-01-01 00:00:00'), + ('add index in alert_rule on org_id and uid columns','CREATE UNIQUE INDEX `UQE_alert_rule_org_id_uid` ON `alert_rule` (`org_id`,`uid`);',1,'','2022-01-01 00:00:00'), + ('add index in alert_rule on org_id, namespace_uid, group_uid columns','CREATE INDEX `IDX_alert_rule_org_id_namespace_uid_rule_group` ON `alert_rule` (`org_id`,`namespace_uid`,`rule_group`);',1,'','2022-01-01 00:00:00'), + ('alter alert_rule table data column to mediumtext in mysql','ALTER TABLE alert_rule MODIFY data MEDIUMTEXT;',1,'','2022-01-01 00:00:00'), + ('add column for to alert_rule','alter table `alert_rule` ADD COLUMN `for` BIGINT(20) NOT NULL DEFAULT 0 ',1,'','2022-01-01 00:00:00'), + ('add column annotations to alert_rule','alter table `alert_rule` ADD COLUMN `annotations` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('add column labels to alert_rule','alter table `alert_rule` ADD COLUMN `labels` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('remove unique index from alert_rule on org_id, title columns','DROP INDEX `UQE_alert_rule_org_id_title` ON `alert_rule`',1,'','2022-01-01 00:00:00'), + ('add index in alert_rule on org_id, namespase_uid and title columns','CREATE UNIQUE INDEX `UQE_alert_rule_org_id_namespace_uid_title` ON `alert_rule` (`org_id`,`namespace_uid`,`title`);',1,'','2022-01-01 00:00:00'), + ('add dashboard_uid column to alert_rule','alter table `alert_rule` ADD COLUMN `dashboard_uid` VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('add panel_id column to alert_rule','alter table `alert_rule` ADD COLUMN `panel_id` BIGINT(20) NULL ',1,'','2022-01-01 00:00:00'), + ('add index in alert_rule on org_id, dashboard_uid and panel_id columns','CREATE INDEX `IDX_alert_rule_org_id_dashboard_uid_panel_id` ON `alert_rule` (`org_id`,`dashboard_uid`,`panel_id`);',1,'','2022-01-01 00:00:00'), + ('add rule_group_idx column to alert_rule','alter table `alert_rule` ADD COLUMN `rule_group_idx` INT NOT NULL DEFAULT 1 ',1,'','2022-01-01 00:00:00'), + ('add is_paused column to alert_rule table','alter table `alert_rule` ADD COLUMN `is_paused` TINYINT(1) NOT NULL DEFAULT 0 ',1,'','2022-01-01 00:00:00'), + ('fix is_paused column for alert_rule table','',1,'','2022-01-01 00:00:00'), + ('alter table alert_rule alter column rule_group_idx type to bigint','ALTER TABLE alert_rule MODIFY rule_group_idx BIGINT;',1,'','2022-01-01 00:00:00'), + ('create alert_rule_version table','CREATE TABLE IF NOT EXISTS `alert_rule_version` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `rule_org_id` BIGINT(20) NOT NULL\n, `rule_uid` VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 0\n, `rule_namespace_uid` VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `rule_group` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `parent_version` INT NOT NULL\n, `restored_from` INT NOT NULL\n, `version` INT NOT NULL\n, `created` DATETIME NOT NULL\n, `title` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `condition` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `data` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `interval_seconds` BIGINT(20) NOT NULL\n, `no_data_state` VARCHAR(15) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT \'NoData\'\n, `exec_err_state` VARCHAR(15) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT \'Alerting\'\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('add index in alert_rule_version table on rule_org_id, rule_uid and version columns','CREATE UNIQUE INDEX `UQE_alert_rule_version_rule_org_id_rule_uid_version` ON `alert_rule_version` (`rule_org_id`,`rule_uid`,`version`);',1,'','2022-01-01 00:00:00'), + ('add index in alert_rule_version table on rule_org_id, rule_namespace_uid and rule_group columns','CREATE INDEX `IDX_alert_rule_version_rule_org_id_rule_namespace_uid_rule_group` ON `alert_rule_version` (`rule_org_id`,`rule_namespace_uid`,`rule_group`);',1,'','2022-01-01 00:00:00'), + ('alter alert_rule_version table data column to mediumtext in mysql','ALTER TABLE alert_rule_version MODIFY data MEDIUMTEXT;',1,'','2022-01-01 00:00:00'), + ('add column for to alert_rule_version','alter table `alert_rule_version` ADD COLUMN `for` BIGINT(20) NOT NULL DEFAULT 0 ',1,'','2022-01-01 00:00:00'), + ('add column annotations to alert_rule_version','alter table `alert_rule_version` ADD COLUMN `annotations` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('add column labels to alert_rule_version','alter table `alert_rule_version` ADD COLUMN `labels` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('add rule_group_idx column to alert_rule_version','alter table `alert_rule_version` ADD COLUMN `rule_group_idx` INT NOT NULL DEFAULT 1 ',1,'','2022-01-01 00:00:00'), + ('add is_paused column to alert_rule_versions table','alter table `alert_rule_version` ADD COLUMN `is_paused` TINYINT(1) NOT NULL DEFAULT 0 ',1,'','2022-01-01 00:00:00'), + ('fix is_paused column for alert_rule_version table','',1,'','2022-01-01 00:00:00'), + ('alter table alert_rule_version alter column rule_group_idx type to bigint','ALTER TABLE alert_rule_version MODIFY rule_group_idx BIGINT;',1,'','2022-01-01 00:00:00'), + ('create_alert_configuration_table','CREATE TABLE IF NOT EXISTS `alert_configuration` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `alertmanager_configuration` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `configuration_version` VARCHAR(3) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `created_at` INT NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('Add column default in alert_configuration','alter table `alert_configuration` ADD COLUMN `default` TINYINT(1) NOT NULL DEFAULT 0 ',1,'','2022-01-01 00:00:00'), + ('alert alert_configuration alertmanager_configuration column from TEXT to MEDIUMTEXT if mysql','ALTER TABLE alert_configuration MODIFY alertmanager_configuration MEDIUMTEXT;',1,'','2022-01-01 00:00:00'), + ('add column org_id in alert_configuration','alter table `alert_configuration` ADD COLUMN `org_id` BIGINT(20) NOT NULL DEFAULT 0 ',1,'','2022-01-01 00:00:00'), + ('add index in alert_configuration table on org_id column','CREATE INDEX `IDX_alert_configuration_org_id` ON `alert_configuration` (`org_id`);',1,'','2022-01-01 00:00:00'), + ('add configuration_hash column to alert_configuration','alter table `alert_configuration` ADD COLUMN `configuration_hash` VARCHAR(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT \'not-yet-calculated\' ',1,'','2022-01-01 00:00:00'), + ('create_ngalert_configuration_table','CREATE TABLE IF NOT EXISTS `ngalert_configuration` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `org_id` BIGINT(20) NOT NULL\n, `alertmanagers` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n, `created_at` INT NOT NULL\n, `updated_at` INT NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('add index in ngalert_configuration on org_id column','CREATE UNIQUE INDEX `UQE_ngalert_configuration_org_id` ON `ngalert_configuration` (`org_id`);',1,'','2022-01-01 00:00:00'), + ('add column send_alerts_to in ngalert_configuration','alter table `ngalert_configuration` ADD COLUMN `send_alerts_to` SMALLINT NOT NULL DEFAULT 0 ',1,'','2022-01-01 00:00:00'), + ('create provenance_type table','CREATE TABLE IF NOT EXISTS `provenance_type` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `org_id` BIGINT(20) NOT NULL\n, `record_key` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `record_type` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `provenance` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('add index to uniquify (record_key, record_type, org_id) columns','CREATE UNIQUE INDEX `UQE_provenance_type_record_type_record_key_org_id` ON `provenance_type` (`record_type`,`record_key`,`org_id`);',1,'','2022-01-01 00:00:00'), + ('create alert_image table','CREATE TABLE IF NOT EXISTS `alert_image` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `token` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `path` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `url` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `created_at` DATETIME NOT NULL\n, `expires_at` DATETIME NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('add unique index on token to alert_image table','CREATE UNIQUE INDEX `UQE_alert_image_token` ON `alert_image` (`token`);',1,'','2022-01-01 00:00:00'), + ('support longer URLs in alert_image table','ALTER TABLE alert_image MODIFY url VARCHAR(2048) NOT NULL;',1,'','2022-01-01 00:00:00'), + ('create_alert_configuration_history_table','CREATE TABLE IF NOT EXISTS `alert_configuration_history` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `org_id` BIGINT(20) NOT NULL DEFAULT 0\n, `alertmanager_configuration` MEDIUMTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `configuration_hash` VARCHAR(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT \'not-yet-calculated\'\n, `configuration_version` VARCHAR(3) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `created_at` INT NOT NULL\n, `default` TINYINT(1) NOT NULL DEFAULT 0\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('drop non-unique orgID index on alert_configuration','DROP INDEX `IDX_alert_configuration_org_id` ON `alert_configuration`',1,'','2022-01-01 00:00:00'), + ('drop unique orgID index on alert_configuration if exists','DROP INDEX `UQE_alert_configuration_org_id` ON `alert_configuration`',1,'','2022-01-01 00:00:00'), + ('extract alertmanager configuration history to separate table','code migration',1,'','2022-01-01 00:00:00'), + ('add unique index on orgID to alert_configuration','CREATE UNIQUE INDEX `UQE_alert_configuration_org_id` ON `alert_configuration` (`org_id`);',1,'','2022-01-01 00:00:00'), + ('add last_applied column to alert_configuration_history','alter table `alert_configuration_history` ADD COLUMN `last_applied` INT NOT NULL DEFAULT 0 ',1,'','2022-01-01 00:00:00'), + ('add message column to alert_rule_version','alter table `alert_rule_version` ADD COLUMN `message` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('create library_element table v1','CREATE TABLE IF NOT EXISTS `library_element` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `org_id` BIGINT(20) NOT NULL\n, `folder_id` BIGINT(20) NOT NULL\n, `uid` VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `name` VARCHAR(150) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `kind` BIGINT(20) NOT NULL\n, `type` VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `description` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `model` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `created` DATETIME NOT NULL\n, `created_by` BIGINT(20) NOT NULL\n, `updated` DATETIME NOT NULL\n, `updated_by` BIGINT(20) NOT NULL\n, `version` BIGINT(20) NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('add index library_element org_id-folder_id-name-kind','CREATE UNIQUE INDEX `UQE_library_element_org_id_folder_id_name_kind` ON `library_element` (`org_id`,`folder_id`,`name`,`kind`);',1,'','2022-01-01 00:00:00'), + ('create library_element_connection table v1','CREATE TABLE IF NOT EXISTS `library_element_connection` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `element_id` BIGINT(20) NOT NULL\n, `kind` BIGINT(20) NOT NULL\n, `connection_id` BIGINT(20) NOT NULL\n, `created` DATETIME NOT NULL\n, `created_by` BIGINT(20) NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('add index library_element_connection element_id-kind-connection_id','CREATE UNIQUE INDEX `UQE_library_element_connection_element_id_kind_connection_id` ON `library_element_connection` (`element_id`,`kind`,`connection_id`);',1,'','2022-01-01 00:00:00'), + ('add unique index library_element org_id_uid','CREATE UNIQUE INDEX `UQE_library_element_org_id_uid` ON `library_element` (`org_id`,`uid`);',1,'','2022-01-01 00:00:00'), + ('increase max description length to 2048','ALTER TABLE `library_element` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, MODIFY `description` VARCHAR(2048) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL ;',1,'','2022-01-01 00:00:00'), + ('alter library_element model to mediumtext','ALTER TABLE library_element MODIFY model MEDIUMTEXT NOT NULL;',1,'','2022-01-01 00:00:00'), + ('add library_element folder uid','alter table `library_element` ADD COLUMN `folder_uid` VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('populate library_element folder_uid','UPDATE library_element\n SET folder_uid = (\n SELECT dashboard.uid\n FROM dashboard\n WHERE library_element.folder_id = dashboard.id AND library_element.org_id = dashboard.org_id\n )',1,'','2022-01-01 00:00:00'), + ('add index library_element org_id-folder_uid-name-kind','CREATE UNIQUE INDEX `UQE_library_element_org_id_folder_uid_name_kind` ON `library_element` (`org_id`,`folder_uid`,`name`,`kind`);',1,'','2022-01-01 00:00:00'), + ('drop unique name in folder index (id)','DROP INDEX `UQE_library_element_org_id_folder_id_name_kind` ON `library_element`',1,'','2022-01-01 00:00:00'), + ('drop unique name in folder index','DROP INDEX `UQE_library_element_org_id_folder_uid_name_kind` ON `library_element`',1,'','2022-01-01 00:00:00'), + ('clone move dashboard alerts to unified alerting','code migration',1,'','2022-01-01 00:00:00'), + ('create data_keys table','CREATE TABLE IF NOT EXISTS `data_keys` (\n`name` VARCHAR(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci PRIMARY KEY NOT NULL\n, `active` TINYINT(1) NOT NULL\n, `scope` VARCHAR(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `provider` VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `encrypted_data` BLOB NOT NULL\n, `created` DATETIME NOT NULL\n, `updated` DATETIME NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('create secrets table','CREATE TABLE IF NOT EXISTS `secrets` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `org_id` BIGINT(20) NOT NULL\n, `namespace` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `type` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `value` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n, `created` DATETIME NOT NULL\n, `updated` DATETIME NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('rename data_keys name column to id','ALTER TABLE `data_keys` CHANGE `name` `id` VARCHAR(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci',1,'','2022-01-01 00:00:00'), + ('add name column into data_keys','alter table `data_keys` ADD COLUMN `name` VARCHAR(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT \'\' ',1,'','2022-01-01 00:00:00'), + ('copy data_keys id column values into name','UPDATE data_keys SET name = id',1,'','2022-01-01 00:00:00'), + ('rename data_keys name column to label','ALTER TABLE `data_keys` CHANGE `name` `label` VARCHAR(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci',1,'','2022-01-01 00:00:00'), + ('rename data_keys id column back to name','ALTER TABLE `data_keys` CHANGE `id` `name` VARCHAR(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci',1,'','2022-01-01 00:00:00'), + ('create kv_store table v1','CREATE TABLE IF NOT EXISTS `kv_store` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `org_id` BIGINT(20) NOT NULL\n, `namespace` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `key` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `value` MEDIUMTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `created` DATETIME NOT NULL\n, `updated` DATETIME NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('add index kv_store.org_id-namespace-key','CREATE UNIQUE INDEX `UQE_kv_store_org_id_namespace_key` ON `kv_store` (`org_id`,`namespace`,`key`);',1,'','2022-01-01 00:00:00'), + ('update dashboard_uid and panel_id from existing annotations','set dashboard_uid and panel_id migration',1,'','2022-01-01 00:00:00'), + ('create permission table','CREATE TABLE IF NOT EXISTS `permission` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `role_id` BIGINT(20) NOT NULL\n, `action` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `scope` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `created` DATETIME NOT NULL\n, `updated` DATETIME NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('add unique index permission.role_id','CREATE INDEX `IDX_permission_role_id` ON `permission` (`role_id`);',1,'','2022-01-01 00:00:00'), + ('add unique index role_id_action_scope','CREATE UNIQUE INDEX `UQE_permission_role_id_action_scope` ON `permission` (`role_id`,`action`,`scope`);',1,'','2022-01-01 00:00:00'), + ('create role table','CREATE TABLE IF NOT EXISTS `role` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `name` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `description` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n, `version` BIGINT(20) NOT NULL\n, `org_id` BIGINT(20) NOT NULL\n, `uid` VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `created` DATETIME NOT NULL\n, `updated` DATETIME NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('add column display_name','alter table `role` ADD COLUMN `display_name` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('add column group_name','alter table `role` ADD COLUMN `group_name` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('add index role.org_id','CREATE INDEX `IDX_role_org_id` ON `role` (`org_id`);',1,'','2022-01-01 00:00:00'), + ('add unique index role_org_id_name','CREATE UNIQUE INDEX `UQE_role_org_id_name` ON `role` (`org_id`,`name`);',1,'','2022-01-01 00:00:00'), + ('add index role_org_id_uid','CREATE UNIQUE INDEX `UQE_role_org_id_uid` ON `role` (`org_id`,`uid`);',1,'','2022-01-01 00:00:00'), + ('create team role table','CREATE TABLE IF NOT EXISTS `team_role` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `org_id` BIGINT(20) NOT NULL\n, `team_id` BIGINT(20) NOT NULL\n, `role_id` BIGINT(20) NOT NULL\n, `created` DATETIME NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('add index team_role.org_id','CREATE INDEX `IDX_team_role_org_id` ON `team_role` (`org_id`);',1,'','2022-01-01 00:00:00'), + ('add unique index team_role_org_id_team_id_role_id','CREATE UNIQUE INDEX `UQE_team_role_org_id_team_id_role_id` ON `team_role` (`org_id`,`team_id`,`role_id`);',1,'','2022-01-01 00:00:00'), + ('add index team_role.team_id','CREATE INDEX `IDX_team_role_team_id` ON `team_role` (`team_id`);',1,'','2022-01-01 00:00:00'), + ('create user role table','CREATE TABLE IF NOT EXISTS `user_role` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `org_id` BIGINT(20) NOT NULL\n, `user_id` BIGINT(20) NOT NULL\n, `role_id` BIGINT(20) NOT NULL\n, `created` DATETIME NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('add index user_role.org_id','CREATE INDEX `IDX_user_role_org_id` ON `user_role` (`org_id`);',1,'','2022-01-01 00:00:00'), + ('add unique index user_role_org_id_user_id_role_id','CREATE UNIQUE INDEX `UQE_user_role_org_id_user_id_role_id` ON `user_role` (`org_id`,`user_id`,`role_id`);',1,'','2022-01-01 00:00:00'), + ('add index user_role.user_id','CREATE INDEX `IDX_user_role_user_id` ON `user_role` (`user_id`);',1,'','2022-01-01 00:00:00'), + ('create builtin role table','CREATE TABLE IF NOT EXISTS `builtin_role` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `role` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `role_id` BIGINT(20) NOT NULL\n, `created` DATETIME NOT NULL\n, `updated` DATETIME NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('add index builtin_role.role_id','CREATE INDEX `IDX_builtin_role_role_id` ON `builtin_role` (`role_id`);',1,'','2022-01-01 00:00:00'), + ('add index builtin_role.name','CREATE INDEX `IDX_builtin_role_role` ON `builtin_role` (`role`);',1,'','2022-01-01 00:00:00'), + ('Add column org_id to builtin_role table','alter table `builtin_role` ADD COLUMN `org_id` BIGINT(20) NOT NULL DEFAULT 0 ',1,'','2022-01-01 00:00:00'), + ('add index builtin_role.org_id','CREATE INDEX `IDX_builtin_role_org_id` ON `builtin_role` (`org_id`);',1,'','2022-01-01 00:00:00'), + ('add unique index builtin_role_org_id_role_id_role','CREATE UNIQUE INDEX `UQE_builtin_role_org_id_role_id_role` ON `builtin_role` (`org_id`,`role_id`,`role`);',1,'','2022-01-01 00:00:00'), + ('Remove unique index role_org_id_uid','DROP INDEX `UQE_role_org_id_uid` ON `role`',1,'','2022-01-01 00:00:00'), + ('add unique index role.uid','CREATE UNIQUE INDEX `UQE_role_uid` ON `role` (`uid`);',1,'','2022-01-01 00:00:00'), + ('create seed assignment table','CREATE TABLE IF NOT EXISTS `seed_assignment` (\n`builtin_role` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `role_name` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('add unique index builtin_role_role_name','CREATE UNIQUE INDEX `UQE_seed_assignment_builtin_role_role_name` ON `seed_assignment` (`builtin_role`,`role_name`);',1,'','2022-01-01 00:00:00'), + ('add column hidden to role table','alter table `role` ADD COLUMN `hidden` TINYINT(1) NOT NULL DEFAULT 0 ',1,'','2022-01-01 00:00:00'), + ('permission kind migration','alter table `permission` ADD COLUMN `kind` VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT \'\' ',1,'','2022-01-01 00:00:00'), + ('permission attribute migration','alter table `permission` ADD COLUMN `attribute` VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT \'\' ',1,'','2022-01-01 00:00:00'), + ('permission identifier migration','alter table `permission` ADD COLUMN `identifier` VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT \'\' ',1,'','2022-01-01 00:00:00'), + ('add permission identifier index','CREATE INDEX `IDX_permission_identifier` ON `permission` (`identifier`);',1,'','2022-01-01 00:00:00'), + ('add permission action scope role_id index','CREATE UNIQUE INDEX `UQE_permission_action_scope_role_id` ON `permission` (`action`,`scope`,`role_id`);',1,'','2022-01-01 00:00:00'), + ('remove permission role_id action scope index','DROP INDEX `UQE_permission_role_id_action_scope` ON `permission`',1,'','2022-01-01 00:00:00'), + ('add group mapping UID column to user_role table','alter table `user_role` ADD COLUMN `group_mapping_uid` VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT \'\' ',1,'','2022-01-01 00:00:00'), + ('add user_role org ID, user ID, role ID, group mapping UID index','CREATE UNIQUE INDEX `UQE_user_role_org_id_user_id_role_id_group_mapping_uid` ON `user_role` (`org_id`,`user_id`,`role_id`,`group_mapping_uid`);',1,'','2022-01-01 00:00:00'), + ('remove user_role org ID, user ID, role ID index','DROP INDEX `UQE_user_role_org_id_user_id_role_id` ON `user_role`',1,'','2022-01-01 00:00:00'), + ('add permission role_id action index','CREATE INDEX `IDX_permission_role_id_action` ON `permission` (`role_id`,`action`);',1,'','2022-01-01 00:00:00'), + ('Remove permission role_id index','DROP INDEX `IDX_permission_role_id` ON `permission`',1,'','2022-01-01 00:00:00'), + ('create query_history table v1','CREATE TABLE IF NOT EXISTS `query_history` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `uid` VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `org_id` BIGINT(20) NOT NULL\n, `datasource_uid` VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `created_by` INT NOT NULL\n, `created_at` INT NOT NULL\n, `comment` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `queries` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('add index query_history.org_id-created_by-datasource_uid','CREATE INDEX `IDX_query_history_org_id_created_by_datasource_uid` ON `query_history` (`org_id`,`created_by`,`datasource_uid`);',1,'','2022-01-01 00:00:00'), + ('alter table query_history alter column created_by type to bigint','ALTER TABLE query_history MODIFY created_by BIGINT;',1,'','2022-01-01 00:00:00'), + ('create query_history_details table v1','CREATE TABLE IF NOT EXISTS `query_history_details` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `query_history_item_uid` VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `datasource_uid` VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('rbac disabled migrator','code migration',1,'','2022-01-01 00:00:00'), + ('teams permissions migration','code migration',1,'','2022-01-01 00:00:00'), + ('dashboard permissions','code migration',1,'','2022-01-01 00:00:00'), + ('dashboard permissions uid scopes','code migration',1,'','2022-01-01 00:00:00'), + ('drop managed folder create actions','code migration',1,'','2022-01-01 00:00:00'), + ('alerting notification permissions','code migration',1,'','2022-01-01 00:00:00'), + ('create query_history_star table v1','CREATE TABLE IF NOT EXISTS `query_history_star` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `query_uid` VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `user_id` INT NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('add index query_history.user_id-query_uid','CREATE UNIQUE INDEX `UQE_query_history_star_user_id_query_uid` ON `query_history_star` (`user_id`,`query_uid`);',1,'','2022-01-01 00:00:00'), + ('add column org_id in query_history_star','alter table `query_history_star` ADD COLUMN `org_id` BIGINT(20) NOT NULL DEFAULT 1 ',1,'','2022-01-01 00:00:00'), + ('alter table query_history_star_mig column user_id type to bigint','ALTER TABLE query_history_star MODIFY user_id BIGINT;',1,'','2022-01-01 00:00:00'), + ('create correlation table v1','CREATE TABLE IF NOT EXISTS `correlation` (\n`uid` VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `source_uid` VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `target_uid` VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n, `label` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `description` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, PRIMARY KEY ( `uid`,`source_uid` )) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('add index correlations.uid','CREATE INDEX `IDX_correlation_uid` ON `correlation` (`uid`);',1,'','2022-01-01 00:00:00'), + ('add index correlations.source_uid','CREATE INDEX `IDX_correlation_source_uid` ON `correlation` (`source_uid`);',1,'','2022-01-01 00:00:00'), + ('add correlation config column','alter table `correlation` ADD COLUMN `config` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('drop index IDX_correlation_uid - v1','DROP INDEX `IDX_correlation_uid` ON `correlation`',1,'','2022-01-01 00:00:00'), + ('drop index IDX_correlation_source_uid - v1','DROP INDEX `IDX_correlation_source_uid` ON `correlation`',1,'','2022-01-01 00:00:00'), + ('Rename table correlation to correlation_tmp_qwerty - v1','ALTER TABLE `correlation` RENAME TO `correlation_tmp_qwerty`',1,'','2022-01-01 00:00:00'), + ('create correlation v2','CREATE TABLE IF NOT EXISTS `correlation` (\n`uid` VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `org_id` BIGINT(20) NOT NULL DEFAULT 0\n, `source_uid` VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `target_uid` VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n, `label` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `description` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `config` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n, PRIMARY KEY ( `uid`,`org_id`,`source_uid` )) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('create index IDX_correlation_uid - v2','CREATE INDEX `IDX_correlation_uid` ON `correlation` (`uid`);',1,'','2022-01-01 00:00:00'), + ('create index IDX_correlation_source_uid - v2','CREATE INDEX `IDX_correlation_source_uid` ON `correlation` (`source_uid`);',1,'','2022-01-01 00:00:00'), + ('create index IDX_correlation_org_id - v2','CREATE INDEX `IDX_correlation_org_id` ON `correlation` (`org_id`);',1,'','2022-01-01 00:00:00'), + ('copy correlation v1 to v2','INSERT INTO `correlation` (`target_uid`\n, `label`\n, `description`\n, `config`\n, `uid`\n, `source_uid`)\nSELECT `target_uid`\n, `label`\n, `description`\n, `config`\n, `uid`\n, `source_uid`\nFROM `correlation_tmp_qwerty`',1,'','2022-01-01 00:00:00'), + ('drop correlation_tmp_qwerty','DROP TABLE IF EXISTS `correlation_tmp_qwerty`',1,'','2022-01-01 00:00:00'), + ('add provisioning column','alter table `correlation` ADD COLUMN `provisioned` TINYINT(1) NOT NULL DEFAULT 0 ',1,'','2022-01-01 00:00:00'), + ('add type column','alter table `correlation` ADD COLUMN `type` VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT \'query\' ',1,'','2022-01-01 00:00:00'), + ('create entity_events table','CREATE TABLE IF NOT EXISTS `entity_event` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `entity_id` VARCHAR(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `event_type` VARCHAR(8) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `created` BIGINT(20) NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('create dashboard public config v1','CREATE TABLE IF NOT EXISTS `dashboard_public_config` (\n`uid` VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci PRIMARY KEY NOT NULL\n, `dashboard_uid` VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `org_id` BIGINT(20) NOT NULL\n, `time_settings` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `refresh_rate` INT NOT NULL DEFAULT 30\n, `template_variables` MEDIUMTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('drop index UQE_dashboard_public_config_uid - v1','DROP INDEX `UQE_dashboard_public_config_uid` ON `dashboard_public_config`',1,'','2022-01-01 00:00:00'), + ('drop index IDX_dashboard_public_config_org_id_dashboard_uid - v1','DROP INDEX `IDX_dashboard_public_config_org_id_dashboard_uid` ON `dashboard_public_config`',1,'','2022-01-01 00:00:00'), + ('Drop old dashboard public config table','DROP TABLE IF EXISTS `dashboard_public_config`',1,'','2022-01-01 00:00:00'), + ('recreate dashboard public config v1','CREATE TABLE IF NOT EXISTS `dashboard_public_config` (\n`uid` VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci PRIMARY KEY NOT NULL\n, `dashboard_uid` VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `org_id` BIGINT(20) NOT NULL\n, `time_settings` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `refresh_rate` INT NOT NULL DEFAULT 30\n, `template_variables` MEDIUMTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('create index UQE_dashboard_public_config_uid - v1','CREATE UNIQUE INDEX `UQE_dashboard_public_config_uid` ON `dashboard_public_config` (`uid`);',1,'','2022-01-01 00:00:00'), + ('create index IDX_dashboard_public_config_org_id_dashboard_uid - v1','CREATE INDEX `IDX_dashboard_public_config_org_id_dashboard_uid` ON `dashboard_public_config` (`org_id`,`dashboard_uid`);',1,'','2022-01-01 00:00:00'), + ('drop index UQE_dashboard_public_config_uid - v2','DROP INDEX `UQE_dashboard_public_config_uid` ON `dashboard_public_config`',1,'','2022-01-01 00:00:00'), + ('drop index IDX_dashboard_public_config_org_id_dashboard_uid - v2','DROP INDEX `IDX_dashboard_public_config_org_id_dashboard_uid` ON `dashboard_public_config`',1,'','2022-01-01 00:00:00'), + ('Drop public config table','DROP TABLE IF EXISTS `dashboard_public_config`',1,'','2022-01-01 00:00:00'), + ('Recreate dashboard public config v2','CREATE TABLE IF NOT EXISTS `dashboard_public_config` (\n`uid` VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci PRIMARY KEY NOT NULL\n, `dashboard_uid` VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `org_id` BIGINT(20) NOT NULL\n, `time_settings` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n, `template_variables` MEDIUMTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n, `access_token` VARCHAR(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `created_by` INT NOT NULL\n, `updated_by` INT NULL\n, `created_at` DATETIME NOT NULL\n, `updated_at` DATETIME NULL\n, `is_enabled` TINYINT(1) NOT NULL DEFAULT 0\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('create index UQE_dashboard_public_config_uid - v2','CREATE UNIQUE INDEX `UQE_dashboard_public_config_uid` ON `dashboard_public_config` (`uid`);',1,'','2022-01-01 00:00:00'), + ('create index IDX_dashboard_public_config_org_id_dashboard_uid - v2','CREATE INDEX `IDX_dashboard_public_config_org_id_dashboard_uid` ON `dashboard_public_config` (`org_id`,`dashboard_uid`);',1,'','2022-01-01 00:00:00'), + ('create index UQE_dashboard_public_config_access_token - v2','CREATE UNIQUE INDEX `UQE_dashboard_public_config_access_token` ON `dashboard_public_config` (`access_token`);',1,'','2022-01-01 00:00:00'), + ('Rename table dashboard_public_config to dashboard_public - v2','ALTER TABLE `dashboard_public_config` RENAME TO `dashboard_public`',1,'','2022-01-01 00:00:00'), + ('add annotations_enabled column','alter table `dashboard_public` ADD COLUMN `annotations_enabled` TINYINT(1) NOT NULL DEFAULT 0 ',1,'','2022-01-01 00:00:00'), + ('add time_selection_enabled column','alter table `dashboard_public` ADD COLUMN `time_selection_enabled` TINYINT(1) NOT NULL DEFAULT 0 ',1,'','2022-01-01 00:00:00'), + ('delete orphaned public dashboards','DELETE FROM dashboard_public WHERE dashboard_uid NOT IN (SELECT uid FROM dashboard)',1,'','2022-01-01 00:00:00'), + ('add share column','alter table `dashboard_public` ADD COLUMN `share` VARCHAR(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT \'public\' ',1,'','2022-01-01 00:00:00'), + ('backfill empty share column fields with default of public','UPDATE dashboard_public SET share=\'public\' WHERE share=\'\'',1,'','2022-01-01 00:00:00'), + ('create file table','CREATE TABLE IF NOT EXISTS `file` (\n`path` VARCHAR(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `path_hash` VARCHAR(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `parent_folder_path_hash` VARCHAR(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `contents` BLOB NOT NULL\n, `etag` VARCHAR(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `cache_control` VARCHAR(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `content_disposition` VARCHAR(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `updated` DATETIME NOT NULL\n, `created` DATETIME NOT NULL\n, `size` BIGINT(20) NOT NULL\n, `mime_type` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('file table idx: path natural pk','CREATE UNIQUE INDEX `UQE_file_path_hash` ON `file` (`path_hash`);',1,'','2022-01-01 00:00:00'), + ('file table idx: parent_folder_path_hash fast folder retrieval','CREATE INDEX `IDX_file_parent_folder_path_hash` ON `file` (`parent_folder_path_hash`);',1,'','2022-01-01 00:00:00'), + ('create file_meta table','CREATE TABLE IF NOT EXISTS `file_meta` (\n`path_hash` VARCHAR(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `key` VARCHAR(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `value` VARCHAR(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('file table idx: path key','CREATE UNIQUE INDEX `UQE_file_meta_path_hash_key` ON `file_meta` (`path_hash`,`key`);',1,'','2022-01-01 00:00:00'), + ('set path collation in file table','',1,'','2022-01-01 00:00:00'), + ('migrate contents column to mediumblob for MySQL','ALTER TABLE file MODIFY contents MEDIUMBLOB;',1,'','2022-01-01 00:00:00'), + ('drop my_row_id and add primary key to file table if my_row_id exists (auto-generated mysql column)','\n ALTER TABLE file\n DROP PRIMARY KEY,\n DROP COLUMN my_row_id,\n DROP INDEX UQE_file_path_hash,\n ADD PRIMARY KEY (path_hash);\n ',1,'','2022-01-01 00:00:00'), + ('drop file_path unique index from file table if it exists (mysql)','ALTER TABLE file DROP INDEX UQE_file_path_hash',1,'','2022-01-01 00:00:00'), + ('add primary key to file table if it doesn\'t exist (mysql)','ALTER TABLE file ADD PRIMARY KEY (path_hash);',1,'','2022-01-01 00:00:00'), + ('add primary key to file table (postgres and sqlite)','',1,'','2022-01-01 00:00:00'), + ('drop my_row_id and add primary key to file_meta table if my_row_id exists (auto-generated mysql column)','\n ALTER TABLE file_meta\n DROP PRIMARY KEY,\n DROP COLUMN my_row_id,\n DROP INDEX UQE_file_meta_path_hash_key,\n ADD PRIMARY KEY (path_hash, `key`);\n ',1,'','2022-01-01 00:00:00'), + ('drop file_path unique index from file_meta table if it exists (mysql)','ALTER TABLE file_meta DROP INDEX UQE_file_meta_path_hash_key',1,'','2022-01-01 00:00:00'), + ('add primary key to file_meta table if it doesn\'t exist (mysql)','ALTER TABLE file_meta ADD PRIMARY KEY (path_hash, `key`);',1,'','2022-01-01 00:00:00'), + ('add primary key to file_meta table (postgres and sqlite)','',1,'','2022-01-01 00:00:00'), + ('managed permissions migration','code migration',1,'','2022-01-01 00:00:00'), + ('managed folder permissions alert actions migration','code migration',1,'','2022-01-01 00:00:00'), + ('RBAC action name migrator','code migration',1,'','2022-01-01 00:00:00'), + ('Add UID column to playlist','alter table `playlist` ADD COLUMN `uid` VARCHAR(80) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 0 ',1,'','2022-01-01 00:00:00'), + ('Update uid column values in playlist','UPDATE playlist SET uid=id;',1,'','2022-01-01 00:00:00'), + ('Add index for uid in playlist','CREATE UNIQUE INDEX `UQE_playlist_org_id_uid` ON `playlist` (`org_id`,`uid`);',1,'','2022-01-01 00:00:00'), + ('update group index for alert rules','code migration',1,'','2022-01-01 00:00:00'), + ('managed folder permissions alert actions repeated migration','code migration',1,'','2022-01-01 00:00:00'), + ('admin only folder/dashboard permission','code migration',1,'','2022-01-01 00:00:00'), + ('add action column to seed_assignment','alter table `seed_assignment` ADD COLUMN `action` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('add scope column to seed_assignment','alter table `seed_assignment` ADD COLUMN `scope` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('remove unique index builtin_role_role_name before nullable update','DROP INDEX `UQE_seed_assignment_builtin_role_role_name` ON `seed_assignment`',1,'','2022-01-01 00:00:00'), + ('update seed_assignment role_name column to nullable','ALTER TABLE seed_assignment MODIFY role_name VARCHAR(190) DEFAULT NULL;',1,'','2022-01-01 00:00:00'), + ('add unique index builtin_role_name back','CREATE UNIQUE INDEX `UQE_seed_assignment_builtin_role_role_name` ON `seed_assignment` (`builtin_role`,`role_name`);',1,'','2022-01-01 00:00:00'), + ('add unique index builtin_role_action_scope','CREATE UNIQUE INDEX `UQE_seed_assignment_builtin_role_action_scope` ON `seed_assignment` (`builtin_role`,`action`,`scope`);',1,'','2022-01-01 00:00:00'), + ('add primary key to seed_assigment','code migration',1,'','2022-01-01 00:00:00'), + ('add origin column to seed_assignment','alter table `seed_assignment` ADD COLUMN `origin` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('add origin to plugin seed_assignment','code migration',1,'','2022-01-01 00:00:00'), + ('prevent seeding OnCall access','code migration',1,'','2022-01-01 00:00:00'), + ('managed folder permissions alert actions repeated fixed migration','code migration',1,'','2022-01-01 00:00:00'), + ('managed folder permissions library panel actions migration','code migration',1,'','2022-01-01 00:00:00'), + ('migrate external alertmanagers to datsourcse','migrate external alertmanagers to datasource',1,'','2022-01-01 00:00:00'), + ('create folder table','CREATE TABLE IF NOT EXISTS `folder` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `uid` VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `org_id` BIGINT(20) NOT NULL\n, `title` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `description` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n, `parent_uid` VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n, `created` DATETIME NOT NULL\n, `updated` DATETIME NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('Add index for parent_uid','CREATE INDEX `IDX_folder_parent_uid_org_id` ON `folder` (`parent_uid`,`org_id`);',1,'','2022-01-01 00:00:00'), + ('Add unique index for folder.uid and folder.org_id','CREATE UNIQUE INDEX `UQE_folder_uid_org_id` ON `folder` (`uid`,`org_id`);',1,'','2022-01-01 00:00:00'), + ('Update folder title length','ALTER TABLE `folder` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, MODIFY `title` VARCHAR(189) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL ;',1,'','2022-01-01 00:00:00'), + ('Add unique index for folder.title and folder.parent_uid','CREATE UNIQUE INDEX `UQE_folder_title_parent_uid` ON `folder` (`title`,`parent_uid`);',1,'','2022-01-01 00:00:00'), + ('Remove unique index for folder.title and folder.parent_uid','DROP INDEX `UQE_folder_title_parent_uid` ON `folder`',1,'','2022-01-01 00:00:00'), + ('Add unique index for title, parent_uid, and org_id','CREATE UNIQUE INDEX `UQE_folder_title_parent_uid_org_id` ON `folder` (`title`,`parent_uid`,`org_id`);',1,'','2022-01-01 00:00:00'), + ('Sync dashboard and folder table','\n INSERT INTO folder (uid, org_id, title, created, updated)\n SELECT * FROM (SELECT uid, org_id, title, created, updated FROM dashboard WHERE is_folder = 1) AS derived\n ON DUPLICATE KEY UPDATE title=derived.title, updated=derived.updated\n ',1,'','2022-01-01 00:00:00'), + ('Remove ghost folders from the folder table','\n DELETE FROM folder WHERE NOT EXISTS\n (SELECT 1 FROM dashboard WHERE dashboard.uid = folder.uid AND dashboard.org_id = folder.org_id AND dashboard.is_folder = true)\n ',1,'','2022-01-01 00:00:00'), + ('Remove unique index UQE_folder_uid_org_id','DROP INDEX `UQE_folder_uid_org_id` ON `folder`',1,'','2022-01-01 00:00:00'), + ('Add unique index UQE_folder_org_id_uid','CREATE UNIQUE INDEX `UQE_folder_org_id_uid` ON `folder` (`org_id`,`uid`);',1,'','2022-01-01 00:00:00'), + ('Remove unique index UQE_folder_title_parent_uid_org_id','DROP INDEX `UQE_folder_title_parent_uid_org_id` ON `folder`',1,'','2022-01-01 00:00:00'), + ('Add unique index UQE_folder_org_id_parent_uid_title','CREATE UNIQUE INDEX `UQE_folder_org_id_parent_uid_title` ON `folder` (`org_id`,`parent_uid`,`title`);',1,'','2022-01-01 00:00:00'), + ('Remove index IDX_folder_parent_uid_org_id','DROP INDEX `IDX_folder_parent_uid_org_id` ON `folder`',1,'','2022-01-01 00:00:00'), + ('Remove unique index UQE_folder_org_id_parent_uid_title','DROP INDEX `UQE_folder_org_id_parent_uid_title` ON `folder`',1,'','2022-01-01 00:00:00'), + ('Add index IDX_folder_org_id_parent_uid','CREATE INDEX `IDX_folder_org_id_parent_uid` ON `folder` (`org_id`,`parent_uid`);',1,'','2022-01-01 00:00:00'), + ('create anon_device table','CREATE TABLE IF NOT EXISTS `anon_device` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `client_ip` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `created_at` DATETIME NOT NULL\n, `device_id` VARCHAR(127) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `updated_at` DATETIME NOT NULL\n, `user_agent` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('add unique index anon_device.device_id','CREATE UNIQUE INDEX `UQE_anon_device_device_id` ON `anon_device` (`device_id`);',1,'','2022-01-01 00:00:00'), + ('add index anon_device.updated_at','CREATE INDEX `IDX_anon_device_updated_at` ON `anon_device` (`updated_at`);',1,'','2022-01-01 00:00:00'), + ('create signing_key table','CREATE TABLE IF NOT EXISTS `signing_key` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `key_id` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `private_key` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `added_at` DATETIME NOT NULL\n, `expires_at` DATETIME NULL\n, `alg` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('add unique index signing_key.key_id','CREATE UNIQUE INDEX `UQE_signing_key_key_id` ON `signing_key` (`key_id`);',1,'','2022-01-01 00:00:00'), + ('set legacy alert migration status in kvstore','code migration',1,'','2022-01-01 00:00:00'), + ('migrate record of created folders during legacy migration to kvstore','code migration',1,'','2022-01-01 00:00:00'), + ('Add folder_uid for dashboard','alter table `dashboard` ADD COLUMN `folder_uid` VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('Populate dashboard folder_uid column','code migration',1,'','2022-01-01 00:00:00'), + ('Add unique index for dashboard_org_id_folder_uid_title','code migration',1,'','2022-01-01 00:00:00'), + ('Delete unique index for dashboard_org_id_folder_id_title','DROP INDEX `UQE_dashboard_org_id_folder_id_title` ON `dashboard`',1,'','2022-01-01 00:00:00'), + ('Delete unique index for dashboard_org_id_folder_uid_title','code migration',1,'','2022-01-01 00:00:00'), + ('Add unique index for dashboard_org_id_folder_uid_title_is_folder','CREATE UNIQUE INDEX `UQE_dashboard_org_id_folder_uid_title_is_folder` ON `dashboard` (`org_id`,`folder_uid`,`title`,`is_folder`);',1,'','2022-01-01 00:00:00'), + ('Restore index for dashboard_org_id_folder_id_title','CREATE INDEX `IDX_dashboard_org_id_folder_id_title` ON `dashboard` (`org_id`,`folder_id`,`title`);',1,'','2022-01-01 00:00:00'), + ('Remove unique index for dashboard_org_id_folder_uid_title_is_folder','DROP INDEX `UQE_dashboard_org_id_folder_uid_title_is_folder` ON `dashboard`',1,'','2022-01-01 00:00:00'), + ('create sso_setting table','CREATE TABLE IF NOT EXISTS `sso_setting` (\n`id` VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci PRIMARY KEY NOT NULL\n, `provider` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `settings` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `created` DATETIME NOT NULL\n, `updated` DATETIME NOT NULL\n, `is_deleted` TINYINT(1) NOT NULL DEFAULT 0\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('update settings column to MEDIUMTEXT','ALTER TABLE sso_setting MODIFY COLUMN settings MEDIUMTEXT NOT NULL;',1,'','2022-01-01 00:00:00'), + ('copy kvstore migration status to each org','code migration',1,'','2022-01-01 00:00:00'), + ('add back entry for orgid=0 migrated status','code migration',1,'','2022-01-01 00:00:00'), + ('managed dashboard permissions annotation actions migration','code migration',1,'','2022-01-01 00:00:00'), + ('create cloud_migration table v1','CREATE TABLE IF NOT EXISTS `cloud_migration` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `auth_token` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n, `stack` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `created` DATETIME NOT NULL\n, `updated` DATETIME NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('create cloud_migration_run table v1','CREATE TABLE IF NOT EXISTS `cloud_migration_run` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `cloud_migration_uid` VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n, `result` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `created` DATETIME NOT NULL\n, `updated` DATETIME NOT NULL\n, `finished` DATETIME NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('add stack_id column','alter table `cloud_migration` ADD COLUMN `stack_id` BIGINT(20) NOT NULL ',1,'','2022-01-01 00:00:00'), + ('add region_slug column','alter table `cloud_migration` ADD COLUMN `region_slug` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL ',1,'','2022-01-01 00:00:00'), + ('add cluster_slug column','alter table `cloud_migration` ADD COLUMN `cluster_slug` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL ',1,'','2022-01-01 00:00:00'), + ('add migration uid column','alter table `cloud_migration` ADD COLUMN `uid` VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('Update uid column values for migration','UPDATE cloud_migration SET uid=concat(\'u\',lpad(id,9,\'0\')) WHERE uid IS NULL;',1,'','2022-01-01 00:00:00'), + ('Add unique index migration_uid','CREATE UNIQUE INDEX `UQE_cloud_migration_uid` ON `cloud_migration` (`uid`);',1,'','2022-01-01 00:00:00'), + ('add migration run uid column','alter table `cloud_migration_run` ADD COLUMN `uid` VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('Update uid column values for migration run','UPDATE cloud_migration_run SET uid=concat(\'u\',lpad(id,9,\'0\')) WHERE uid IS NULL;',1,'','2022-01-01 00:00:00'), + ('Add unique index migration_run_uid','CREATE UNIQUE INDEX `UQE_cloud_migration_run_uid` ON `cloud_migration_run` (`uid`);',1,'','2022-01-01 00:00:00'), + ('Rename table cloud_migration to cloud_migration_session_tmp_qwerty - v1','ALTER TABLE `cloud_migration` RENAME TO `cloud_migration_session_tmp_qwerty`',1,'','2022-01-01 00:00:00'), + ('create cloud_migration_session v2','CREATE TABLE IF NOT EXISTS `cloud_migration_session` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `uid` VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n, `auth_token` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n, `slug` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `stack_id` BIGINT(20) NOT NULL\n, `region_slug` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `cluster_slug` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `created` DATETIME NOT NULL\n, `updated` DATETIME NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('create index UQE_cloud_migration_session_uid - v2','CREATE UNIQUE INDEX `UQE_cloud_migration_session_uid` ON `cloud_migration_session` (`uid`);',1,'','2022-01-01 00:00:00'), + ('copy cloud_migration_session v1 to v2','INSERT INTO `cloud_migration_session` (`id`\n, `auth_token`\n, `stack_id`\n, `cluster_slug`\n, `updated`\n, `uid`\n, `slug`\n, `region_slug`\n, `created`)\nSELECT `id`\n, `auth_token`\n, `stack_id`\n, `cluster_slug`\n, `updated`\n, `uid`\n, `stack`\n, `region_slug`\n, `created`\nFROM `cloud_migration_session_tmp_qwerty`',1,'','2022-01-01 00:00:00'), + ('drop cloud_migration_session_tmp_qwerty','DROP TABLE IF EXISTS `cloud_migration_session_tmp_qwerty`',1,'','2022-01-01 00:00:00'), + ('Rename table cloud_migration_run to cloud_migration_snapshot_tmp_qwerty - v1','ALTER TABLE `cloud_migration_run` RENAME TO `cloud_migration_snapshot_tmp_qwerty`',1,'','2022-01-01 00:00:00'), + ('create cloud_migration_snapshot v2','CREATE TABLE IF NOT EXISTS `cloud_migration_snapshot` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `uid` VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n, `session_uid` VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n, `result` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `created` DATETIME NOT NULL\n, `updated` DATETIME NOT NULL\n, `finished` DATETIME NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('create index UQE_cloud_migration_snapshot_uid - v2','CREATE UNIQUE INDEX `UQE_cloud_migration_snapshot_uid` ON `cloud_migration_snapshot` (`uid`);',1,'','2022-01-01 00:00:00'), + ('copy cloud_migration_snapshot v1 to v2','INSERT INTO `cloud_migration_snapshot` (`created`\n, `updated`\n, `finished`\n, `id`\n, `uid`\n, `session_uid`\n, `result`)\nSELECT `created`\n, `updated`\n, `finished`\n, `id`\n, `uid`\n, `cloud_migration_uid`\n, `result`\nFROM `cloud_migration_snapshot_tmp_qwerty`',1,'','2022-01-01 00:00:00'), + ('drop cloud_migration_snapshot_tmp_qwerty','DROP TABLE IF EXISTS `cloud_migration_snapshot_tmp_qwerty`',1,'','2022-01-01 00:00:00'), + ('add snapshot upload_url column','alter table `cloud_migration_snapshot` ADD COLUMN `upload_url` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('add snapshot status column','alter table `cloud_migration_snapshot` ADD COLUMN `status` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL ',1,'','2022-01-01 00:00:00'), + ('add snapshot local_directory column','alter table `cloud_migration_snapshot` ADD COLUMN `local_directory` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('add snapshot gms_snapshot_uid column','alter table `cloud_migration_snapshot` ADD COLUMN `gms_snapshot_uid` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('add snapshot encryption_key column','alter table `cloud_migration_snapshot` ADD COLUMN `encryption_key` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('add snapshot error_string column','alter table `cloud_migration_snapshot` ADD COLUMN `error_string` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('create cloud_migration_resource table v1','CREATE TABLE IF NOT EXISTS `cloud_migration_resource` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `uid` VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `resource_type` VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `resource_uid` VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `status` VARCHAR(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `error_string` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n, `snapshot_uid` VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('delete cloud_migration_snapshot.result column','ALTER TABLE cloud_migration_snapshot DROP COLUMN result',1,'','2022-01-01 00:00:00'), + ('add cloud_migration_resource.name column','alter table `cloud_migration_resource` ADD COLUMN `name` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('add cloud_migration_resource.parent_name column','alter table `cloud_migration_resource` ADD COLUMN `parent_name` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('add cloud_migration_session.org_id column','alter table `cloud_migration_session` ADD COLUMN `org_id` BIGINT(20) NOT NULL DEFAULT 1 ',1,'','2022-01-01 00:00:00'), + ('add cloud_migration_resource.error_code column','alter table `cloud_migration_resource` ADD COLUMN `error_code` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('increase resource_uid column length','ALTER TABLE cloud_migration_resource MODIFY resource_uid NVARCHAR(255);',1,'','2022-01-01 00:00:00'), + ('create cloud_migration_snapshot_partition table v1','CREATE TABLE IF NOT EXISTS `cloud_migration_snapshot_partition` (\n`snapshot_uid` VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `partition_number` INT NOT NULL\n, `resource_type` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `data` LONGBLOB NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('add cloud_migration_snapshot_partition srp_unique index','CREATE UNIQUE INDEX `UQE_cloud_migration_snapshot_partition_srp_unique` ON `cloud_migration_snapshot_partition` (`snapshot_uid`,`resource_type`,`partition_number`);',1,'','2022-01-01 00:00:00'), + ('add resource_storage_type column to cloud_migration_snapshot table','alter table `cloud_migration_snapshot` ADD COLUMN `resource_storage_type` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('add encryption_algo column to cloud_migration_snapshot table','alter table `cloud_migration_snapshot` ADD COLUMN `encryption_algo` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('add metadata column to cloud_migration_snapshot table','alter table `cloud_migration_snapshot` ADD COLUMN `metadata` BLOB NULL ',1,'','2022-01-01 00:00:00'), + ('add public_key column to cloud_migration_snapshot table','alter table `cloud_migration_snapshot` ADD COLUMN `public_key` BLOB NULL ',1,'','2022-01-01 00:00:00'), + ('drop my_row_id and add primary key with columns snapshot_uid,resource_type,partition_number to table cloud_migration_snapshot_partition if my_row_id exists (auto-generated mysql column)','\n ALTER TABLE cloud_migration_snapshot_partition\n DROP PRIMARY KEY,\n DROP COLUMN my_row_id,\n DROP INDEX UQE_cloud_migration_snapshot_partition_srp_unique,\n ADD PRIMARY KEY (`snapshot_uid`,`resource_type`,`partition_number`);\n ',1,'','2022-01-01 00:00:00'), + ('drop unique index UQE_cloud_migration_snapshot_partition_srp_unique from cloud_migration_snapshot_partition table if it exists (mysql)','ALTER TABLE cloud_migration_snapshot_partition DROP INDEX UQE_cloud_migration_snapshot_partition_srp_unique',1,'','2022-01-01 00:00:00'), + ('add primary key with columns snapshot_uid,resource_type,partition_number to table cloud_migration_snapshot_partition if it doesn\'t exist (mysql)','ALTER TABLE cloud_migration_snapshot_partition ADD PRIMARY KEY (`snapshot_uid`,`resource_type`,`partition_number`)',1,'','2022-01-01 00:00:00'), + ('add primary key with columns snapshot_uid,resource_type,partition_number to table cloud_migration_snapshot_partition (postgres and sqlite)','',1,'','2022-01-01 00:00:00'), + ('alter kv_store.value to longtext','ALTER TABLE kv_store MODIFY value LONGTEXT NOT NULL;',1,'','2022-01-01 00:00:00'), + ('add notification_settings column to alert_rule table','alter table `alert_rule` ADD COLUMN `notification_settings` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('add notification_settings column to alert_rule_version table','alter table `alert_rule_version` ADD COLUMN `notification_settings` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('removing scope from alert.instances:read action migration','code migration',1,'','2022-01-01 00:00:00'), + ('managed folder permissions alerting silences actions migration','code migration',1,'','2022-01-01 00:00:00'), + ('add record column to alert_rule table','alter table `alert_rule` ADD COLUMN `record` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('add record column to alert_rule_version table','alter table `alert_rule_version` ADD COLUMN `record` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('add resolved_at column to alert_instance table','alter table `alert_instance` ADD COLUMN `resolved_at` BIGINT(20) NULL ',1,'','2022-01-01 00:00:00'), + ('add last_sent_at column to alert_instance table','alter table `alert_instance` ADD COLUMN `last_sent_at` BIGINT(20) NULL ',1,'','2022-01-01 00:00:00'), + ('Add scope to alert.notifications.receivers:read and alert.notifications.receivers.secrets:read','code migration',1,'','2022-01-01 00:00:00'), + ('add metadata column to alert_rule table','alter table `alert_rule` ADD COLUMN `metadata` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('add metadata column to alert_rule_version table','alter table `alert_rule_version` ADD COLUMN `metadata` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('delete orphaned service account permissions','code migration',1,'','2022-01-01 00:00:00'), + ('adding action set permissions','code migration',1,'','2022-01-01 00:00:00'), + ('create user_external_session table','CREATE TABLE IF NOT EXISTS `user_external_session` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `user_auth_id` BIGINT(20) NOT NULL\n, `user_id` BIGINT(20) NOT NULL\n, `auth_module` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `access_token` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n, `id_token` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n, `refresh_token` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n, `session_id` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n, `session_id_hash` CHAR(44) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n, `name_id` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n, `name_id_hash` CHAR(44) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n, `expires_at` DATETIME NULL\n, `created_at` DATETIME NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('increase name_id column length to 1024','ALTER TABLE user_external_session MODIFY name_id NVARCHAR(1024);',1,'','2022-01-01 00:00:00'), + ('increase session_id column length to 1024','ALTER TABLE user_external_session MODIFY session_id NVARCHAR(1024);',1,'','2022-01-01 00:00:00'), + ('remove scope from alert.notifications.receivers:create','code migration',1,'','2022-01-01 00:00:00'), + ('add created_by column to alert_rule_version table','alter table `alert_rule_version` ADD COLUMN `created_by` VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('add updated_by column to alert_rule table','alter table `alert_rule` ADD COLUMN `updated_by` VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('add alert_rule_state table','CREATE TABLE IF NOT EXISTS `alert_rule_state` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `org_id` BIGINT(20) NOT NULL\n, `rule_uid` VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `data` LONGBLOB NOT NULL\n, `updated_at` DATETIME NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('add index to alert_rule_state on org_id and rule_uid columns','CREATE UNIQUE INDEX `UQE_alert_rule_state_org_id_rule_uid` ON `alert_rule_state` (`org_id`,`rule_uid`);',1,'','2022-01-01 00:00:00'), + ('add guid column to alert_rule table','alter table `alert_rule` ADD COLUMN `guid` VARCHAR(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT \'\' ',1,'','2022-01-01 00:00:00'), + ('add rule_guid column to alert_rule_version table','alter table `alert_rule_version` ADD COLUMN `rule_guid` VARCHAR(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT \'\' ',1,'','2022-01-01 00:00:00'), + ('cleanup alert_rule_version table','code migration',1,'','2022-01-01 00:00:00'), + ('populate rule guid in alert rule table','code migration',1,'','2022-01-01 00:00:00'), + ('drop index in alert_rule_version table on rule_org_id, rule_uid and version columns','DROP INDEX `UQE_alert_rule_version_rule_org_id_rule_uid_version` ON `alert_rule_version`',1,'','2022-01-01 00:00:00'), + ('add index in alert_rule_version table on rule_org_id, rule_uid, rule_guid and version columns','CREATE UNIQUE INDEX `UQE_alert_rule_version_rule_org_id_rule_uid_rule_guid_version` ON `alert_rule_version` (`rule_org_id`,`rule_uid`,`rule_guid`,`version`);',1,'','2022-01-01 00:00:00'), + ('add index in alert_rule_version table on rule_guid and version columns','CREATE UNIQUE INDEX `UQE_alert_rule_version_rule_guid_version` ON `alert_rule_version` (`rule_guid`,`version`);',1,'','2022-01-01 00:00:00'), + ('add index in alert_rule table on guid columns','CREATE UNIQUE INDEX `UQE_alert_rule_guid` ON `alert_rule` (`guid`);',1,'','2022-01-01 00:00:00'), + ('add keep_firing_for column to alert_rule','alter table `alert_rule` ADD COLUMN `keep_firing_for` BIGINT(20) NOT NULL DEFAULT 0 ',1,'','2022-01-01 00:00:00'), + ('add keep_firing_for column to alert_rule_version','alter table `alert_rule_version` ADD COLUMN `keep_firing_for` BIGINT(20) NOT NULL DEFAULT 0 ',1,'','2022-01-01 00:00:00'), + ('add missing_series_evals_to_resolve column to alert_rule','alter table `alert_rule` ADD COLUMN `missing_series_evals_to_resolve` SMALLINT NULL ',1,'','2022-01-01 00:00:00'), + ('add missing_series_evals_to_resolve column to alert_rule_version','alter table `alert_rule_version` ADD COLUMN `missing_series_evals_to_resolve` SMALLINT NULL ',1,'','2022-01-01 00:00:00'), + ('remove the datasources:drilldown action','code migration',1,'','2022-01-01 00:00:00'), + ('remove title in folder unique index','DROP INDEX `UQE_alert_rule_org_id_namespace_uid_title` ON `alert_rule`',1,'','2022-01-01 00:00:00'), + ('add fired_at column to alert_instance table','alter table `alert_instance` ADD COLUMN `fired_at` BIGINT(20) NULL ',1,'','2022-01-01 00:00:00'), + ('ensure rule_group column is case sensitive in returned results','ALTER TABLE alert_rule MODIFY rule_group VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_as_cs NOT NULL;',1,'','2022-01-01 00:00:00'), + ('expand created_by in alert_rule_version table','ALTER TABLE `alert_rule_version` MODIFY created_by VARCHAR(190);',1,'','2022-01-01 00:00:00'), + ('expand updated_by in alert_rule table','ALTER TABLE `alert_rule` MODIFY updated_by VARCHAR(190);',1,'','2022-01-01 00:00:00'), + ('add index in alert_rule on org_id, namespace_uid, rule_group and rule_group_idx columns','CREATE INDEX `IDX_alert_rule_org_id_namespace_uid_rule_group_rule_group_idx` ON `alert_rule` (`org_id`,`namespace_uid`,`rule_group`,`rule_group_idx`);',1,'','2022-01-01 00:00:00'), + ('add annotations column to alert_instance table','alter table `alert_instance` ADD COLUMN `annotations` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('ensure namespace_uid column sorts the same way as golang','ALTER TABLE alert_rule MODIFY namespace_uid VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL;',1,'','2022-01-01 00:00:00'), + ('ensure rule_group column sorts the same way as golang','ALTER TABLE alert_rule MODIFY rule_group VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL;',1,'','2022-01-01 00:00:00'), + ('add \'alert.notifications.receivers.protected:write\' to receiver admins','code migration',1,'','2022-01-01 00:00:00'), + ('create data_source_usage_by_day table','CREATE TABLE IF NOT EXISTS `data_source_usage_by_day` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `data_source_id` BIGINT(20) NOT NULL\n, `day` VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `queries` BIGINT(20) NOT NULL\n, `errors` BIGINT(20) NOT NULL\n, `load_duration_ms` BIGINT(20) NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('create data_source_usage_by_day(data_source_id) index','CREATE INDEX `IDX_data_source_usage_by_day_data_source_id` ON `data_source_usage_by_day` (`data_source_id`);',1,'','2022-01-01 00:00:00'), + ('create data_source_usage_by_day(data_source_id, day) unique index','CREATE UNIQUE INDEX `UQE_data_source_usage_by_day_data_source_id_day` ON `data_source_usage_by_day` (`data_source_id`,`day`);',1,'','2022-01-01 00:00:00'), + ('create dashboard_usage_by_day table','CREATE TABLE IF NOT EXISTS `dashboard_usage_by_day` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `dashboard_id` BIGINT(20) NOT NULL\n, `day` VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `views` BIGINT(20) NOT NULL\n, `queries` BIGINT(20) NOT NULL\n, `errors` BIGINT(20) NOT NULL\n, `load_duration` FLOAT NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('create dashboard_usage_sums table','CREATE TABLE IF NOT EXISTS `dashboard_usage_sums` (\n`dashboard_id` BIGINT(20) PRIMARY KEY NOT NULL\n, `updated` DATETIME NOT NULL\n, `views_last_1_days` BIGINT(20) NOT NULL\n, `views_last_7_days` BIGINT(20) NOT NULL\n, `views_last_30_days` BIGINT(20) NOT NULL\n, `views_total` BIGINT(20) NOT NULL\n, `queries_last_1_days` BIGINT(20) NOT NULL\n, `queries_last_7_days` BIGINT(20) NOT NULL\n, `queries_last_30_days` BIGINT(20) NOT NULL\n, `queries_total` BIGINT(20) NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('create dashboard_usage_by_day(dashboard_id) index','CREATE INDEX `IDX_dashboard_usage_by_day_dashboard_id` ON `dashboard_usage_by_day` (`dashboard_id`);',1,'','2022-01-01 00:00:00'), + ('create dashboard_usage_by_day(dashboard_id, day) index','CREATE UNIQUE INDEX `UQE_dashboard_usage_by_day_dashboard_id_day` ON `dashboard_usage_by_day` (`dashboard_id`,`day`);',1,'','2022-01-01 00:00:00'), + ('add column errors_last_1_days to dashboard_usage_sums','alter table `dashboard_usage_sums` ADD COLUMN `errors_last_1_days` BIGINT(20) NOT NULL DEFAULT 0 ',1,'','2022-01-01 00:00:00'), + ('add column errors_last_7_days to dashboard_usage_sums','alter table `dashboard_usage_sums` ADD COLUMN `errors_last_7_days` BIGINT(20) NOT NULL DEFAULT 0 ',1,'','2022-01-01 00:00:00'), + ('add column errors_last_30_days to dashboard_usage_sums','alter table `dashboard_usage_sums` ADD COLUMN `errors_last_30_days` BIGINT(20) NOT NULL DEFAULT 0 ',1,'','2022-01-01 00:00:00'), + ('add column errors_total to dashboard_usage_sums','alter table `dashboard_usage_sums` ADD COLUMN `errors_total` BIGINT(20) NOT NULL DEFAULT 0 ',1,'','2022-01-01 00:00:00'), + ('create dashboard_public_usage_by_day table','CREATE TABLE IF NOT EXISTS `dashboard_public_usage_by_day` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `public_dashboard_uid` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `day` VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `views` BIGINT(20) NOT NULL\n, `queries` BIGINT(20) NOT NULL\n, `errors` BIGINT(20) NOT NULL\n, `load_duration` FLOAT NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('add column cached_queries to dashboard_usage_by_day table','alter table `dashboard_usage_by_day` ADD COLUMN `cached_queries` BIGINT(20) NOT NULL DEFAULT 0 ',1,'','2022-01-01 00:00:00'), + ('add column cached_queries to dashboard_public_usage_by_day table','alter table `dashboard_public_usage_by_day` ADD COLUMN `cached_queries` BIGINT(20) NOT NULL DEFAULT 0 ',1,'','2022-01-01 00:00:00'), + ('add column dashboard_uid to dashboard_usage_sums','alter table `dashboard_usage_sums` ADD COLUMN `dashboard_uid` VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('add column org_id to dashboard_usage_sums','alter table `dashboard_usage_sums` ADD COLUMN `org_id` BIGINT(20) NULL DEFAULT 1 ',1,'','2022-01-01 00:00:00'), + ('add column dashboard_uid to dashboard_usage_by_day','alter table `dashboard_usage_by_day` ADD COLUMN `dashboard_uid` VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('add column org_id to dashboard_usage_by_day','alter table `dashboard_usage_by_day` ADD COLUMN `org_id` BIGINT(20) NULL DEFAULT 1 ',1,'','2022-01-01 00:00:00'), + ('create dashboard_usage_by_day(dashboard_uid, org_id, day) unique index','CREATE UNIQUE INDEX `UQE_dashboard_usage_by_day_dashboard_uid_org_id_day` ON `dashboard_usage_by_day` (`dashboard_uid`,`org_id`,`day`);',1,'','2022-01-01 00:00:00'), + ('Add missing dashboard_uid and org_id to dashboard_usage_by_day and dashboard_usage_sums','code migration',1,'','2022-01-01 00:00:00'), + ('Add dashboard_usage_sums(org_id, dashboard_uid) index','CREATE UNIQUE INDEX `UQE_dashboard_usage_sums_org_id_dashboard_uid` ON `dashboard_usage_sums` (`org_id`,`dashboard_uid`);',1,'','2022-01-01 00:00:00'), + ('create user_dashboard_views table','CREATE TABLE IF NOT EXISTS `user_dashboard_views` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `user_id` BIGINT(20) NOT NULL\n, `dashboard_id` BIGINT(20) NOT NULL\n, `viewed` DATETIME NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('add index user_dashboard_views.user_id','CREATE INDEX `IDX_user_dashboard_views_user_id` ON `user_dashboard_views` (`user_id`);',1,'','2022-01-01 00:00:00'), + ('add index user_dashboard_views.dashboard_id','CREATE INDEX `IDX_user_dashboard_views_dashboard_id` ON `user_dashboard_views` (`dashboard_id`);',1,'','2022-01-01 00:00:00'), + ('add unique index user_dashboard_views_user_id_dashboard_id','CREATE UNIQUE INDEX `UQE_user_dashboard_views_user_id_dashboard_id` ON `user_dashboard_views` (`user_id`,`dashboard_id`);',1,'','2022-01-01 00:00:00'), + ('add org_id column to user_dashboard_views','alter table `user_dashboard_views` ADD COLUMN `org_id` BIGINT(20) NULL ',1,'','2022-01-01 00:00:00'), + ('add dashboard_uid column to user_dashboard_views','alter table `user_dashboard_views` ADD COLUMN `dashboard_uid` VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('add unique index user_dashboard_views_org_id_dashboard_uid','CREATE INDEX `IDX_user_dashboard_views_org_id_dashboard_uid` ON `user_dashboard_views` (`org_id`,`dashboard_uid`);',1,'','2022-01-01 00:00:00'), + ('add unique index user_dashboard_views_org_id_user_id_dashboard_uid','CREATE UNIQUE INDEX `UQE_user_dashboard_views_user_id_org_id_dashboard_uid` ON `user_dashboard_views` (`user_id`,`org_id`,`dashboard_uid`);',1,'','2022-01-01 00:00:00'), + ('populate user_dashboard_views.dashboard_uid and user_dashboard_views.org_id from dashboard table','views migration',1,'','2022-01-01 00:00:00'), + ('delete views where dashboard does not exist','DELETE FROM user_dashboard_views WHERE (dashboard_uid IS NULL OR org_id IS NULL) AND NOT EXISTS (SELECT 1 FROM dashboard WHERE dashboard.id = user_dashboard_views.dashboard_id)',1,'','2022-01-01 00:00:00'), + ('create user_stats table','CREATE TABLE IF NOT EXISTS `user_stats` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `user_id` BIGINT(20) NOT NULL\n, `billing_role` VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `created` DATETIME NOT NULL\n, `updated` DATETIME NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('add unique index user_stats(user_id)','CREATE UNIQUE INDEX `UQE_user_stats_user_id` ON `user_stats` (`user_id`);',1,'','2022-01-01 00:00:00'), + ('create data_source_cache table','CREATE TABLE IF NOT EXISTS `data_source_cache` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `data_source_id` BIGINT(20) NOT NULL\n, `enabled` TINYINT(1) NOT NULL\n, `ttl_ms` BIGINT(20) NOT NULL\n, `created` DATETIME NOT NULL\n, `updated` DATETIME NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('add index data_source_cache.data_source_id','CREATE INDEX `IDX_data_source_cache_data_source_id` ON `data_source_cache` (`data_source_id`);',1,'','2022-01-01 00:00:00'), + ('add use_default_ttl column','alter table `data_source_cache` ADD COLUMN `use_default_ttl` TINYINT(1) NOT NULL DEFAULT 1 ',1,'','2022-01-01 00:00:00'), + ('add data_source_cache.data_source_uid column','alter table `data_source_cache` ADD COLUMN `data_source_uid` VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 0 ',1,'','2022-01-01 00:00:00'), + ('remove abandoned data_source_cache records','DELETE FROM data_source_cache WHERE NOT EXISTS (SELECT 1 FROM data_source WHERE id = data_source_cache.data_source_id);',1,'','2022-01-01 00:00:00'), + ('update data_source_cache.data_source_uid value','UPDATE data_source_cache JOIN data_source ON data_source_cache.data_source_id = data_source.id SET data_source_cache.data_source_uid = data_source.uid;',1,'','2022-01-01 00:00:00'), + ('add index data_source_cache.data_source_uid','CREATE INDEX `IDX_data_source_cache_data_source_uid` ON `data_source_cache` (`data_source_uid`);',1,'','2022-01-01 00:00:00'), + ('add data_source_cache.ttl_resources_ms column','alter table `data_source_cache` ADD COLUMN `ttl_resources_ms` BIGINT(20) NOT NULL DEFAULT 300000 ',1,'','2022-01-01 00:00:00'), + ('update data_source_cache.ttl_resources_ms to have the same value as ttl_ms','UPDATE data_source_cache SET ttl_resources_ms = ttl_ms;',1,'','2022-01-01 00:00:00'), + ('create data_source_acl table','CREATE TABLE IF NOT EXISTS `data_source_acl` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `org_id` BIGINT(20) NOT NULL\n, `data_source_id` BIGINT(20) NOT NULL\n, `team_id` BIGINT(20) NOT NULL\n, `user_id` BIGINT(20) NOT NULL\n, `permission` SMALLINT NOT NULL\n, `created` DATETIME NOT NULL\n, `updated` DATETIME NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('add index data_source_acl.data_source_id','CREATE INDEX `IDX_data_source_acl_data_source_id` ON `data_source_acl` (`data_source_id`);',1,'','2022-01-01 00:00:00'), + ('add unique index datasource_acl.unique','CREATE UNIQUE INDEX `UQE_data_source_acl_data_source_id_team_id_user_id` ON `data_source_acl` (`data_source_id`,`team_id`,`user_id`);',1,'','2022-01-01 00:00:00'), + ('create license_token table','CREATE TABLE IF NOT EXISTS `license_token` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `token` LONGTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `created` DATETIME NOT NULL\n, `updated` DATETIME NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('drop recorded_queries table v14','DROP TABLE IF EXISTS `recorded_queries`',1,'','2022-01-01 00:00:00'), + ('drop recording_rules table v14','DROP TABLE IF EXISTS `recording_rules`',1,'','2022-01-01 00:00:00'), + ('create recording_rules table v14','CREATE TABLE IF NOT EXISTS `recording_rules` (\n`id` VARCHAR(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `target_ref_id` VARCHAR(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `name` VARCHAR(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `description` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `org_id` BIGINT(20) NOT NULL\n, `interval` BIGINT(20) NOT NULL\n, `range` BIGINT(20) NOT NULL\n, `active` TINYINT(1) NOT NULL DEFAULT 0\n, `count` TINYINT(1) NOT NULL DEFAULT 0\n, `queries` BLOB NOT NULL\n, `created_at` DATETIME NOT NULL\n, PRIMARY KEY ( `id`,`target_ref_id` )) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('create remote_write_targets table v1','CREATE TABLE IF NOT EXISTS `remote_write_targets` (\n`id` VARCHAR(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `data_source_uid` VARCHAR(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `write_path` VARCHAR(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `org_id` INT NOT NULL\n, PRIMARY KEY ( `id`,`data_source_uid`,`write_path` )) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('Add prom_name to recording_rules table','alter table `recording_rules` ADD COLUMN `prom_name` VARCHAR(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('ensure remote_write_targets table','CREATE TABLE IF NOT EXISTS `remote_write_targets` (\n`id` VARCHAR(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `data_source_uid` VARCHAR(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `write_path` VARCHAR(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `org_id` INT NOT NULL\n, PRIMARY KEY ( `id`,`data_source_uid`,`write_path` )) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('create report config table v1','CREATE TABLE IF NOT EXISTS `report` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `user_id` BIGINT(20) NOT NULL\n, `org_id` BIGINT(20) NOT NULL\n, `dashboard_id` BIGINT(20) NOT NULL\n, `name` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `recipients` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `reply_to` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n, `message` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n, `schedule_frequency` VARCHAR(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `schedule_day` VARCHAR(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `schedule_hour` BIGINT(20) NOT NULL\n, `schedule_minute` BIGINT(20) NOT NULL\n, `created` DATETIME NOT NULL\n, `updated` DATETIME NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('Add index report.user_id','CREATE INDEX `IDX_report_user_id` ON `report` (`user_id`);',1,'','2022-01-01 00:00:00'), + ('add index to dashboard_id','CREATE INDEX `IDX_report_dashboard_id` ON `report` (`dashboard_id`);',1,'','2022-01-01 00:00:00'), + ('add index to org_id','CREATE INDEX `IDX_report_org_id` ON `report` (`org_id`);',1,'','2022-01-01 00:00:00'), + ('Add timezone to the report','alter table `report` ADD COLUMN `schedule_timezone` VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT \'Europe/Stockholm\' ',1,'','2022-01-01 00:00:00'), + ('Add time_from to the report','alter table `report` ADD COLUMN `time_from` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('Add time_to to the report','alter table `report` ADD COLUMN `time_to` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('Add PDF landscape option to the report','alter table `report` ADD COLUMN `pdf_landscape` TINYINT(1) NULL ',1,'','2022-01-01 00:00:00'), + ('Add monthly day scheduling option to the report','alter table `report` ADD COLUMN `schedule_day_of_month` VARCHAR(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('Add PDF layout option to the report','alter table `report` ADD COLUMN `pdf_layout` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('Add PDF orientation option to the report','alter table `report` ADD COLUMN `pdf_orientation` VARCHAR(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('Update report pdf_orientation from pdf_landscape','UPDATE report SET pdf_orientation = \'landscape\' WHERE pdf_landscape = 1',1,'','2022-01-01 00:00:00'), + ('create report settings table','CREATE TABLE IF NOT EXISTS `report_settings` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `user_id` BIGINT(20) NOT NULL\n, `org_id` BIGINT(20) NOT NULL\n, `branding_report_logo_url` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n, `branding_email_logo_url` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n, `branding_email_footer_link` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n, `branding_email_footer_text` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n, `branding_email_footer_mode` VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('Add dashboard_uid field to the report','alter table `report` ADD COLUMN `dashboard_uid` VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('Add template_vars field to the report','alter table `report` ADD COLUMN `template_vars` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('Add option to include dashboard url in the report','alter table `report` ADD COLUMN `enable_dashboard_url` TINYINT(1) NULL ',1,'','2022-01-01 00:00:00'), + ('Add state field to the report','alter table `report` ADD COLUMN `state` VARCHAR(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('Add option to add CSV files to the report','alter table `report` ADD COLUMN `enable_csv` TINYINT(1) NULL ',1,'','2022-01-01 00:00:00'), + ('Add scheduling start date','alter table `report` ADD COLUMN `schedule_start` INT NULL ',1,'','2022-01-01 00:00:00'), + ('Add missing schedule_start date for old reports','code migration',1,'','2022-01-01 00:00:00'), + ('Add scheduling end date','alter table `report` ADD COLUMN `schedule_end` INT NULL ',1,'','2022-01-01 00:00:00'), + ('Add schedulinng custom interval frequency','alter table `report` ADD COLUMN `schedule_interval_frequency` VARCHAR(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('Add scheduling custom interval amount','alter table `report` ADD COLUMN `schedule_interval_amount` BIGINT(20) NULL ',1,'','2022-01-01 00:00:00'), + ('Add workdays only flag to report','alter table `report` ADD COLUMN `schedule_workdays_only` TINYINT(1) NULL ',1,'','2022-01-01 00:00:00'), + ('create report dashboards table','CREATE TABLE IF NOT EXISTS `report_dashboards` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `report_id` BIGINT(20) NOT NULL\n, `dashboard_uid` VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT \'\'\n, `report_variables` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n, `time_to` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n, `time_from` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('Add index report_dashboards.report_id','CREATE INDEX `IDX_report_dashboards_report_id` ON `report_dashboards` (`report_id`);',1,'','2022-01-01 00:00:00'), + ('Migrate report fields into report_dashboards','INSERT INTO report_dashboards (report_id, dashboard_uid, report_variables, time_to, time_from) SELECT id, CASE WHEN dashboard_uid IS NULL THEN \'\' ELSE dashboard_uid END, template_vars, time_to, time_from FROM report',1,'','2022-01-01 00:00:00'), + ('Add formats option to the report','alter table `report` ADD COLUMN `formats` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT \'[\"pdf\"]\' ',1,'','2022-01-01 00:00:00'), + ('Migrate reports with csv enabled','UPDATE report SET formats=\'[\"pdf\",\"csv\"]\' WHERE enable_csv IS TRUE;',1,'','2022-01-01 00:00:00'), + ('Migrate ancient reports','code migration',1,'','2022-01-01 00:00:00'), + ('Add created column in report_dashboards','alter table `report_dashboards` ADD COLUMN `created` DATETIME NULL ',1,'','2022-01-01 00:00:00'), + ('Add scale_factor to the report','alter table `report` ADD COLUMN `scale_factor` TINYINT NOT NULL DEFAULT 2 ',1,'','2022-01-01 00:00:00'), + ('Alter scale_factor from TINYINT to SMALLINT','ALTER TABLE report MODIFY scale_factor SMALLINT NOT NULL DEFAULT 2',1,'','2022-01-01 00:00:00'), + ('Add uid column to report','alter table `report` ADD COLUMN `uid` VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('Add unique index reports_org_id_uid','CREATE UNIQUE INDEX `UQE_report_org_id_uid` ON `report` (`org_id`,`uid`);',1,'','2022-01-01 00:00:00'), + ('Add pdf show template variable values to the report','alter table `report` ADD COLUMN `pdf_show_template_variables` TINYINT(1) NOT NULL DEFAULT 0 ',1,'','2022-01-01 00:00:00'), + ('Add pdf combine in one file','alter table `report` ADD COLUMN `pdf_combine_one_file` TINYINT(1) NOT NULL DEFAULT 1 ',1,'','2022-01-01 00:00:00'), + ('Add pdf theme to report settings table','alter table `report_settings` ADD COLUMN `pdf_theme` VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT \'light\' ',1,'','2022-01-01 00:00:00'), + ('Add email subject to the report','alter table `report` ADD COLUMN `subject` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('Populate email subject with report name','UPDATE report SET subject = name WHERE subject IS NULL',1,'','2022-01-01 00:00:00'), + ('Add embedded image theme to report settings table','alter table `report_settings` ADD COLUMN `embedded_image_theme` VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT \'dark\' ',1,'','2022-01-01 00:00:00'), + ('create team group table','CREATE TABLE IF NOT EXISTS `team_group` (\n`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL\n, `org_id` BIGINT(20) NOT NULL\n, `team_id` BIGINT(20) NOT NULL\n, `group_id` VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `created` DATETIME NOT NULL\n, `updated` DATETIME NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('add index team_group.org_id','CREATE INDEX `IDX_team_group_org_id` ON `team_group` (`org_id`);',1,'','2022-01-01 00:00:00'), + ('add unique index team_group.org_id_team_id_group_id','CREATE UNIQUE INDEX `UQE_team_group_org_id_team_id_group_id` ON `team_group` (`org_id`,`team_id`,`group_id`);',1,'','2022-01-01 00:00:00'), + ('add index team_group.group_id','CREATE INDEX `IDX_team_group_group_id` ON `team_group` (`group_id`);',1,'','2022-01-01 00:00:00'), + ('Add column uid in team_group','alter table `team_group` ADD COLUMN `uid` VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('Update uid column values in team_group','UPDATE team_group SET uid=concat(\'tg\',lpad(id,9,\'0\')) WHERE uid IS NULL OR uid = \'\';',1,'','2022-01-01 00:00:00'), + ('Add unique index team_group_uid','CREATE UNIQUE INDEX `UQE_team_group_uid` ON `team_group` (`uid`);',1,'','2022-01-01 00:00:00'), + ('create settings table','CREATE TABLE IF NOT EXISTS `setting` (\n`section` VARCHAR(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `key` VARCHAR(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `value` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('add unique index settings.section_key','CREATE UNIQUE INDEX `UQE_setting_section_key` ON `setting` (`section`,`key`);',1,'','2022-01-01 00:00:00'), + ('add setting.encrypted_value','alter table `setting` ADD COLUMN `encrypted_value` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL ',1,'','2022-01-01 00:00:00'), + ('drop my_row_id and add primary key to setting table if my_row_id exists (auto-generated mysql column)','\n ALTER TABLE setting\n DROP PRIMARY KEY,\n DROP COLUMN my_row_id,\n DROP INDEX UQE_setting_section_key,\n ADD PRIMARY KEY (section, `key`);\n ',1,'','2022-01-01 00:00:00'), + ('drop setting_section unique index from setting table if it exists (mysql)','ALTER TABLE setting DROP INDEX UQE_setting_section_key',1,'','2022-01-01 00:00:00'), + ('add primary key to setting table if it doesn\'t exist (mysql)','ALTER TABLE setting ADD PRIMARY KEY (section, `key`);',1,'','2022-01-01 00:00:00'), + ('add primary key to setting table (postgres and sqlite)','',1,'','2022-01-01 00:00:00'), + ('migrate role names','code migration',1,'','2022-01-01 00:00:00'), + ('rename orgs roles','code migration',1,'','2022-01-01 00:00:00'), + ('remove duplicated org role','code migration',1,'','2022-01-01 00:00:00'), + ('migrate alerting role names','code migration',1,'','2022-01-01 00:00:00'), + ('data source permissions','code migration',1,'','2022-01-01 00:00:00'), + ('data source uid permissions','code migration',1,'','2022-01-01 00:00:00'), + ('rename permissions:delegate scope','UPDATE permission SET scope = \'permissions:type:delegate\' WHERE scope = \'permissions:delegate\'',1,'','2022-01-01 00:00:00'), + ('remove invalid managed permissions','code migration',1,'','2022-01-01 00:00:00'), + ('builtin role migration','code migration',1,'','2022-01-01 00:00:00'), + ('seed permissions migration','code migration',1,'','2022-01-01 00:00:00'), + ('managed permissions migration enterprise','code migration',1,'','2022-01-01 00:00:00'), + ('create table dashboard_public_email_share','CREATE TABLE IF NOT EXISTS `dashboard_public_email_share` (\n`uid` VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci PRIMARY KEY NOT NULL\n, `public_dashboard_uid` VARCHAR(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `recipient` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `type` VARCHAR(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT \'email\'\n, `created_at` DATETIME NOT NULL\n, `updated_at` DATETIME NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('create table dashboard_public_magic_link','CREATE TABLE IF NOT EXISTS `dashboard_public_magic_link` (\n`uid` VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci PRIMARY KEY NOT NULL\n, `token_uuid` VARCHAR(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `public_dashboard_uid` VARCHAR(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `email` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `created_at` DATETIME NOT NULL\n, `updated_at` DATETIME NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('create table dashboard_public_session','CREATE TABLE IF NOT EXISTS `dashboard_public_session` (\n`uid` VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci PRIMARY KEY NOT NULL\n, `cookie_uuid` VARCHAR(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `public_dashboard_uid` VARCHAR(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `email` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL\n, `created_at` DATETIME NOT NULL\n, `updated_at` DATETIME NOT NULL\n) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;',1,'','2022-01-01 00:00:00'), + ('add last_seen_at column','alter table `dashboard_public_session` ADD COLUMN `last_seen_at` DATETIME NULL ',1,'','2022-01-01 00:00:00'); +/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; + +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; +/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; + From f970cbb42b3d488985868ae214219f84727f1062 Mon Sep 17 00:00:00 2001 From: Ihor Yeromin Date: Thu, 18 Dec 2025 10:25:53 +0100 Subject: [PATCH 05/36] Transformations: Gray out inapplicable transformation cards (#115512) * fix(transformation): gray out transformation card on transformation tab * fix(transformations): make data prop required in EmptyTransformationsMessage This ensures TypeScript enforces that all call sites pass the data prop, which is required for graying out inapplicable transformation cards. - Changed data prop from optional to required in EmptyTransformationsProps - Fixed TransformationsEditor.tsx to pass data (was missing in legacy code) - Updated tests to pass the required data prop --------- Co-authored-by: Sam Jewell --- .../PanelDataPane/EmptyTransformationsMessage.test.tsx | 9 +++++++-- .../PanelDataPane/EmptyTransformationsMessage.tsx | 4 +++- .../PanelDataPane/PanelDataTransformationsTab.tsx | 1 + .../TransformationsEditor/TransformationsEditor.tsx | 3 ++- 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/public/app/features/dashboard-scene/panel-edit/PanelDataPane/EmptyTransformationsMessage.test.tsx b/public/app/features/dashboard-scene/panel-edit/PanelDataPane/EmptyTransformationsMessage.test.tsx index 7f7af75146b..c974710b566 100644 --- a/public/app/features/dashboard-scene/panel-edit/PanelDataPane/EmptyTransformationsMessage.test.tsx +++ b/public/app/features/dashboard-scene/panel-edit/PanelDataPane/EmptyTransformationsMessage.test.tsx @@ -56,6 +56,7 @@ describe('EmptyTransformationsMessage', () => { onShowPicker={onShowPicker} onGoToQueries={onGoToQueries} onAddTransformation={onAddTransformation} + data={[]} /> ); @@ -75,6 +76,7 @@ describe('EmptyTransformationsMessage', () => { onShowPicker={onShowPicker} onGoToQueries={onGoToQueries} onAddTransformation={onAddTransformation} + data={[]} /> ); @@ -92,6 +94,7 @@ describe('EmptyTransformationsMessage', () => { onShowPicker={onShowPicker} onGoToQueries={onGoToQueries} onAddTransformation={onAddTransformation} + data={[]} /> ); @@ -103,13 +106,15 @@ describe('EmptyTransformationsMessage', () => { }); it('should not show SQL transformation card when onGoToQueries is not provided', () => { - render(); + render( + + ); expect(screen.queryByText('Transform with SQL')).not.toBeInTheDocument(); }); it('should not show transformation cards grid when neither onGoToQueries nor onAddTransformation are provided', () => { - render(); + render(); expect(screen.queryByText('Transform with SQL')).not.toBeInTheDocument(); diff --git a/public/app/features/dashboard-scene/panel-edit/PanelDataPane/EmptyTransformationsMessage.tsx b/public/app/features/dashboard-scene/panel-edit/PanelDataPane/EmptyTransformationsMessage.tsx index 80680aa4e1c..eab5f3e9c58 100644 --- a/public/app/features/dashboard-scene/panel-edit/PanelDataPane/EmptyTransformationsMessage.tsx +++ b/public/app/features/dashboard-scene/panel-edit/PanelDataPane/EmptyTransformationsMessage.tsx @@ -1,6 +1,6 @@ import { useMemo } from 'react'; -import { DataTransformerID, standardTransformersRegistry, TransformerRegistryItem } from '@grafana/data'; +import { DataFrame, DataTransformerID, standardTransformersRegistry, TransformerRegistryItem } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; import { t, Trans } from '@grafana/i18n'; import { reportInteraction } from '@grafana/runtime'; @@ -16,6 +16,7 @@ interface EmptyTransformationsProps { onShowPicker: () => void; onGoToQueries?: () => void; onAddTransformation?: (transformationId: string) => void; + data: DataFrame[]; } const TRANSFORMATION_IDS = [ @@ -121,6 +122,7 @@ export function NewEmptyTransformationsMessage(props: EmptyTransformationsProps) showIllustrations={true} showPluginState={false} showTags={false} + data={props.data} /> ))} diff --git a/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataTransformationsTab.tsx b/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataTransformationsTab.tsx index af937d150c1..425eb792109 100644 --- a/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataTransformationsTab.tsx +++ b/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataTransformationsTab.tsx @@ -152,6 +152,7 @@ export function PanelDataTransformationsTabRendered({ model }: SceneComponentPro onShowPicker={openDrawer} onGoToQueries={onGoToQueries} onAddTransformation={onAddTransformation} + data={sourceData.data.series} /> {transformationsDrawer} diff --git a/public/app/features/dashboard/components/TransformationsEditor/TransformationsEditor.tsx b/public/app/features/dashboard/components/TransformationsEditor/TransformationsEditor.tsx index 1593c6162b7..9bdcefdcab2 100644 --- a/public/app/features/dashboard/components/TransformationsEditor/TransformationsEditor.tsx +++ b/public/app/features/dashboard/components/TransformationsEditor/TransformationsEditor.tsx @@ -256,7 +256,8 @@ class UnThemedTransformationsEditor extends React.PureComponent { this.setState({ showPicker: true }); }} - > + data={this.state.data.series} + /> ); }; From e5b0353c417ef3837156a94d1e8a2258a6185895 Mon Sep 17 00:00:00 2001 From: Gabriel MABILLE Date: Thu, 18 Dec 2025 10:29:50 +0100 Subject: [PATCH 06/36] `grafana-iam`: Use an API Installer interface (#115310) * `grafana-iam`: Add basic roles to the apis * Fix validation * chore: trigger CI * Leave the hooks intact for now, moving them later * Remove Role mention from the interface * Refactor to use a NoopRest backend and Deny access --- pkg/registry/apis/iam/api_installer.go | 79 +++++++++++++++++++ pkg/registry/apis/iam/authorizer.go | 8 +- pkg/registry/apis/iam/models.go | 2 +- pkg/registry/apis/iam/noopstorage/rest.go | 74 ++++++++++++++++++ pkg/registry/apis/iam/register.go | 40 +++++----- pkg/registry/apis/iam/role_hooks.go | 71 +++++++++-------- pkg/registry/apis/iam/role_hooks_test.go | 92 +++++++++++------------ pkg/registry/apis/wireset.go | 2 +- pkg/server/wire_gen.go | 6 +- 9 files changed, 275 insertions(+), 99 deletions(-) create mode 100644 pkg/registry/apis/iam/api_installer.go create mode 100644 pkg/registry/apis/iam/noopstorage/rest.go diff --git a/pkg/registry/apis/iam/api_installer.go b/pkg/registry/apis/iam/api_installer.go new file mode 100644 index 00000000000..1d456dd3777 --- /dev/null +++ b/pkg/registry/apis/iam/api_installer.go @@ -0,0 +1,79 @@ +package iam + +import ( + "context" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apiserver/pkg/authorization/authorizer" + "k8s.io/apiserver/pkg/registry/rest" + "k8s.io/apiserver/pkg/server" + + iamv0 "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1" + "github.com/grafana/grafana/pkg/apimachinery/utils" + "github.com/grafana/grafana/pkg/registry/apis/iam/noopstorage" + "github.com/grafana/grafana/pkg/services/apiserver/builder" +) + +// ApiInstaller is a generic interface for installing API resources. +// This allows different implementations (OSS noop, Enterprise with features) +// to provide their own resource registration and validation logic. +type ApiInstaller[T runtime.Object] interface { + // GetAuthorizer returns the authorizer for the API group. + GetAuthorizer() authorizer.Authorizer + + // RegisterStorage registers storage and hooks with the API group. + RegisterStorage(apiGroupInfo *server.APIGroupInfo, opts *builder.APIGroupOptions, storage map[string]rest.Storage) error + + // ValidateOnCreate validates object creation. + ValidateOnCreate(ctx context.Context, obj T) error + + // ValidateOnUpdate validates object updates. + ValidateOnUpdate(ctx context.Context, oldObj, newObj T) error + + // ValidateOnDelete validates object deletion. + ValidateOnDelete(ctx context.Context, obj T) error +} + +// RoleApiInstaller provides Role-specific API registration and validation. +// This interface allows enterprise implementations to provide custom role handling +// while keeping the core IAM registration logic in OSS. +type RoleApiInstaller ApiInstaller[*iamv0.Role] + +// NoopApiInstaller is a no-op implementation for when roles are not available (OSS). +type NoopApiInstaller[T runtime.Object] struct { + ResourceInfo utils.ResourceInfo +} + +func (n *NoopApiInstaller[T]) GetAuthorizer() authorizer.Authorizer { + return authorizer.AuthorizerFunc(func(ctx context.Context, attr authorizer.Attributes) (authorizer.Decision, string, error) { + return authorizer.DecisionDeny, "Unavailable functionality", nil + }) +} + +func (n *NoopApiInstaller[T]) RegisterStorage(apiGroupInfo *server.APIGroupInfo, opts *builder.APIGroupOptions, storage map[string]rest.Storage) error { + storage[n.ResourceInfo.StoragePath()] = &noopstorage.NoopREST{ResourceInfo: n.ResourceInfo} + return nil +} + +func (n *NoopApiInstaller[T]) ValidateOnCreate(ctx context.Context, obj T) error { + // No validation needed in OSS + return nil +} + +func (n *NoopApiInstaller[T]) ValidateOnUpdate(ctx context.Context, oldObj, newObj T) error { + // No validation needed in OSS + return nil +} + +func (n *NoopApiInstaller[T]) ValidateOnDelete(ctx context.Context, obj T) error { + // No validation needed in OSS + return nil +} + +// ProvideNoopRoleApiInstaller provides a no-op role installer specifically for Role types. +// This is needed for Wire dependency injection which doesn't handle generic functions well. +func ProvideNoopRoleApiInstaller() RoleApiInstaller { + return &NoopApiInstaller[*iamv0.Role]{ + ResourceInfo: iamv0.RoleInfo, + } +} diff --git a/pkg/registry/apis/iam/authorizer.go b/pkg/registry/apis/iam/authorizer.go index 026254ebab6..8abc67bf885 100644 --- a/pkg/registry/apis/iam/authorizer.go +++ b/pkg/registry/apis/iam/authorizer.go @@ -19,7 +19,11 @@ type iamAuthorizer struct { resourceAuthorizer map[string]authorizer.Authorizer // Map resource to its authorizer } -func newIAMAuthorizer(accessClient authlib.AccessClient, legacyAccessClient authlib.AccessClient) authorizer.Authorizer { +func newIAMAuthorizer( + accessClient authlib.AccessClient, + legacyAccessClient authlib.AccessClient, + roleApiInstaller RoleApiInstaller, +) authorizer.Authorizer { resourceAuthorizer := make(map[string]authorizer.Authorizer) serviceAuthorizer := gfauthorizer.NewServiceAuthorizer() @@ -44,7 +48,7 @@ func newIAMAuthorizer(accessClient authlib.AccessClient, legacyAccessClient auth // Access specific resources authorizer := gfauthorizer.NewResourceAuthorizer(accessClient) resourceAuthorizer[iamv0.CoreRoleInfo.GetName()] = iamauthorizer.NewCoreRoleAuthorizer(accessClient) - resourceAuthorizer[iamv0.RoleInfo.GetName()] = authorizer + resourceAuthorizer[iamv0.RoleInfo.GetName()] = roleApiInstaller.GetAuthorizer() resourceAuthorizer[iamv0.ResourcePermissionInfo.GetName()] = allowAuthorizer // Handled by the backend wrapper resourceAuthorizer[iamv0.RoleBindingInfo.GetName()] = authorizer resourceAuthorizer[iamv0.ServiceAccountResourceInfo.GetName()] = authorizer diff --git a/pkg/registry/apis/iam/models.go b/pkg/registry/apis/iam/models.go index f8ae4219b65..9e4d8e484e4 100644 --- a/pkg/registry/apis/iam/models.go +++ b/pkg/registry/apis/iam/models.go @@ -56,7 +56,7 @@ type IdentityAccessManagementAPIBuilder struct { teamBindingLegacyStore *teambinding.LegacyBindingStore ssoLegacyStore *sso.LegacyStore coreRolesStorage CoreRoleStorageBackend - rolesStorage RoleStorageBackend + roleApiInstaller RoleApiInstaller resourcePermissionsStorage resource.StorageBackend roleBindingsStorage RoleBindingStorageBackend externalGroupMappingStorage ExternalGroupMappingStorageBackend diff --git a/pkg/registry/apis/iam/noopstorage/rest.go b/pkg/registry/apis/iam/noopstorage/rest.go new file mode 100644 index 00000000000..aa0214788ab --- /dev/null +++ b/pkg/registry/apis/iam/noopstorage/rest.go @@ -0,0 +1,74 @@ +package noopstorage + +import ( + "context" + + "k8s.io/apimachinery/pkg/apis/meta/internalversion" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/watch" + "k8s.io/apiserver/pkg/registry/rest" + + "github.com/grafana/grafana/pkg/apimachinery/utils" +) + +type NoopREST struct { + ResourceInfo utils.ResourceInfo +} + +var ( + _ rest.Storage = (*NoopREST)(nil) + _ rest.Scoper = (*NoopREST)(nil) + _ rest.SingularNameProvider = (*NoopREST)(nil) + _ rest.Getter = (*NoopREST)(nil) +) + +func (n *NoopREST) New() runtime.Object { + return n.ResourceInfo.NewFunc() +} + +func (n *NoopREST) NewList() runtime.Object { + return n.ResourceInfo.NewListFunc() +} + +func (n *NoopREST) NamespaceScoped() bool { + return true +} + +func (n *NoopREST) GetSingularName() string { + return n.ResourceInfo.GetSingularName() +} + +func (n *NoopREST) Destroy() {} + +func (n *NoopREST) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { + return nil, errNoopStorage +} + +func (n *NoopREST) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) { + return nil, errNoopStorage +} + +func (n *NoopREST) Create(ctx context.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) { + return nil, errNoopStorage +} + +func (n *NoopREST) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (runtime.Object, bool, error) { + return nil, false, errNoopStorage +} + +func (n *NoopREST) Delete(ctx context.Context, name string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error) { + return nil, false, errNoopStorage +} + +func (n *NoopREST) DeleteCollection(ctx context.Context, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions, listOptions *internalversion.ListOptions) (runtime.Object, error) { + return nil, errNoopStorage +} + +func (n *NoopREST) Watch(ctx context.Context, options *internalversion.ListOptions) (watch.Interface, error) { + return nil, errNoopStorage +} + +func (n *NoopREST) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) { + return nil, errNoopStorage +} diff --git a/pkg/registry/apis/iam/register.go b/pkg/registry/apis/iam/register.go index a9a68e90d2c..8a3dcb9586b 100644 --- a/pkg/registry/apis/iam/register.go +++ b/pkg/registry/apis/iam/register.go @@ -72,7 +72,7 @@ func RegisterAPIService( zClient zanzana.Client, reg prometheus.Registerer, coreRolesStorage CoreRoleStorageBackend, - rolesStorage RoleStorageBackend, + roleApiInstaller RoleApiInstaller, tracing *tracing.TracingService, roleBindingsStorage RoleBindingStorageBackend, externalGroupMappingStorageBackend ExternalGroupMappingStorageBackend, @@ -87,7 +87,7 @@ func RegisterAPIService( dbProvider := legacysql.NewDatabaseProvider(sql) store := legacy.NewLegacySQLStores(dbProvider) legacyAccessClient := newLegacyAccessClient(ac, store) - authorizer := newIAMAuthorizer(accessClient, legacyAccessClient) + authorizer := newIAMAuthorizer(accessClient, legacyAccessClient, roleApiInstaller) registerMetrics(reg) //nolint:staticcheck // not yet migrated to OpenFeature @@ -106,7 +106,7 @@ func RegisterAPIService( teamBindingLegacyStore: teambinding.NewLegacyBindingStore(store, enableAuthnMutation, tracing), ssoLegacyStore: sso.NewLegacyStore(ssoService, tracing), coreRolesStorage: coreRolesStorage, - rolesStorage: rolesStorage, + roleApiInstaller: roleApiInstaller, resourcePermissionsStorage: resourcepermission.ProvideStorageBackend(dbProvider), roleBindingsStorage: roleBindingsStorage, externalGroupMappingStorage: externalGroupMappingStorageBackend, @@ -139,7 +139,7 @@ func NewAPIService( accessClient types.AccessClient, dbProvider legacysql.LegacyDatabaseProvider, coreRoleStorage CoreRoleStorageBackend, - roleStorage RoleStorageBackend, + roleApiInstaller RoleApiInstaller, features featuremgmt.FeatureToggles, zClient zanzana.Client, reg prometheus.Registerer, @@ -162,7 +162,6 @@ func NewAPIService( store: store, display: user.NewLegacyDisplayREST(store), resourcePermissionsStorage: resourcePermissionsStorage, - rolesStorage: roleStorage, coreRolesStorage: coreRoleStorage, roleBindingsStorage: noopstorage.ProvideStorageBackend(), // TODO: add a proper storage backend logger: log.New("iam.apis"), @@ -172,6 +171,7 @@ func NewAPIService( zClient: zClient, zTickets: make(chan bool, MaxConcurrentZanzanaWrites), reg: reg, + roleApiInstaller: roleApiInstaller, authorizer: authorizer.AuthorizerFunc( func(ctx context.Context, a authorizer.Attributes) (authorizer.Decision, string, error) { user, ok := types.AuthInfoFrom(ctx) @@ -377,23 +377,17 @@ func (b *IdentityAccessManagementAPIBuilder) UpdateAPIGroupInfo(apiGroupInfo *ge } if enableZanzanaSync { b.logger.Info("Enabling hooks for CoreRole to sync to Zanzana") - coreRoleStore.AfterCreate = b.AfterRoleCreate - coreRoleStore.AfterDelete = b.AfterRoleDelete - coreRoleStore.BeginUpdate = b.BeginRoleUpdate + h := NewRoleHooks(b.zClient, b.zTickets, b.logger) + coreRoleStore.AfterCreate = h.AfterRoleCreate + coreRoleStore.AfterDelete = h.AfterRoleDelete + coreRoleStore.BeginUpdate = h.BeginRoleUpdate } storage[iamv0.CoreRoleInfo.StoragePath()] = coreRoleStore - roleStore, err := NewLocalStore(iamv0.RoleInfo, apiGroupInfo.Scheme, opts.OptsGetter, b.reg, b.accessClient, b.rolesStorage) - if err != nil { + // Role registration is delegated to the RoleApiInstaller + if err := b.roleApiInstaller.RegisterStorage(apiGroupInfo, &opts, storage); err != nil { return err } - if enableZanzanaSync { - b.logger.Info("Enabling hooks for Role to sync to Zanzana") - roleStore.AfterCreate = b.AfterRoleCreate - roleStore.AfterDelete = b.AfterRoleDelete - roleStore.BeginUpdate = b.BeginRoleUpdate - } - storage[iamv0.RoleInfo.StoragePath()] = roleStore roleBindingStore, err := NewLocalStore(iamv0.RoleBindingInfo, apiGroupInfo.Scheme, opts.OptsGetter, b.reg, b.accessClient, b.roleBindingsStorage) if err != nil { @@ -568,6 +562,8 @@ func (b *IdentityAccessManagementAPIBuilder) Validate(ctx context.Context, a adm return resourcepermission.ValidateCreateAndUpdateInput(ctx, typedObj) case *iamv0.ExternalGroupMapping: return externalgroupmapping.ValidateOnCreate(typedObj) + case *iamv0.Role: + return b.roleApiInstaller.ValidateOnCreate(ctx, typedObj) } return nil case admission.Update: @@ -592,9 +588,19 @@ func (b *IdentityAccessManagementAPIBuilder) Validate(ctx context.Context, a adm return fmt.Errorf("expected old object to be a TeamBinding, got %T", oldTeamBindingObj) } return teambinding.ValidateOnUpdate(ctx, typedObj, oldTeamBindingObj) + case *iamv0.Role: + oldRoleObj, ok := a.GetOldObject().(*iamv0.Role) + if !ok { + return fmt.Errorf("expected old object to be a Role, got %T", oldRoleObj) + } + return b.roleApiInstaller.ValidateOnUpdate(ctx, oldRoleObj, typedObj) } return nil case admission.Delete: + switch oldRoleObj := a.GetOldObject().(type) { + case *iamv0.Role: + return b.roleApiInstaller.ValidateOnDelete(ctx, oldRoleObj) + } return nil case admission.Connect: return nil diff --git a/pkg/registry/apis/iam/role_hooks.go b/pkg/registry/apis/iam/role_hooks.go index 831a2993fe5..8d86a35bd27 100644 --- a/pkg/registry/apis/iam/role_hooks.go +++ b/pkg/registry/apis/iam/role_hooks.go @@ -9,12 +9,23 @@ import ( "k8s.io/apiserver/pkg/registry/generic/registry" iamv0 "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1" + "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/services/accesscontrol" v1 "github.com/grafana/grafana/pkg/services/authz/proto/v1" "github.com/grafana/grafana/pkg/services/authz/zanzana" "github.com/grafana/grafana/pkg/services/authz/zanzana/common" ) +type RoleHooks struct { + zClient zanzana.Client + zTickets chan bool + logger log.Logger +} + +func NewRoleHooks(zClient zanzana.Client, zTickets chan bool, logger log.Logger) *RoleHooks { + return &RoleHooks{zClient: zClient, zTickets: zTickets, logger: logger} +} + // convertRolePermissionsToTuples converts role permissions (action/scope) to v1 TupleKey format // using the shared zanzana.ConvertRolePermissionsToTuples utility and common.ToAuthzExtTupleKeys func convertRolePermissionsToTuples(roleUID string, permissions []iamv0.CoreRolespecPermission) ([]*v1.TupleKey, error) { @@ -44,8 +55,8 @@ func convertRolePermissionsToTuples(roleUID string, permissions []iamv0.CoreRole // AfterRoleCreate is a post-create hook that writes the role permissions to Zanzana (openFGA) // It handles both Role and CoreRole types -func (b *IdentityAccessManagementAPIBuilder) AfterRoleCreate(obj runtime.Object, _ *metav1.CreateOptions) { - if b.zClient == nil { +func (h *RoleHooks) AfterRoleCreate(obj runtime.Object, _ *metav1.CreateOptions) { + if h.zClient == nil { return } @@ -76,21 +87,21 @@ func (b *IdentityAccessManagementAPIBuilder) AfterRoleCreate(obj runtime.Object, } wait := time.Now() - b.zTickets <- true + h.zTickets <- true hooksWaitHistogram.WithLabelValues(rType, "create").Observe(time.Since(wait).Seconds()) go func(role *iamv0.CoreRole, roleType string) { start := time.Now() status := "success" defer func() { - <-b.zTickets + <-h.zTickets hooksDurationHistogram.WithLabelValues(rType, "create", status).Observe(time.Since(start).Seconds()) hooksOperationCounter.WithLabelValues(rType, "create", status).Inc() }() tuples, err := convertRolePermissionsToTuples(role.Name, role.Spec.Permissions) if err != nil { - b.logger.Error("failed to convert role permissions to tuples", + h.logger.Error("failed to convert role permissions to tuples", "namespace", role.Namespace, "roleUID", role.Name, "roleType", roleType, @@ -103,7 +114,7 @@ func (b *IdentityAccessManagementAPIBuilder) AfterRoleCreate(obj runtime.Object, // Avoid writing if there are no valid tuples if len(tuples) == 0 { - b.logger.Debug("no valid tuples to write for role", + h.logger.Debug("no valid tuples to write for role", "namespace", role.Namespace, "roleUID", role.Name, "roleType", roleType, @@ -113,7 +124,7 @@ func (b *IdentityAccessManagementAPIBuilder) AfterRoleCreate(obj runtime.Object, return } - b.logger.Debug("writing role permissions to zanzana", + h.logger.Debug("writing role permissions to zanzana", "namespace", role.Namespace, "roleUID", role.Name, "roleType", roleType, @@ -124,14 +135,14 @@ func (b *IdentityAccessManagementAPIBuilder) AfterRoleCreate(obj runtime.Object, ctx, cancel := context.WithTimeout(context.Background(), defaultWriteTimeout) defer cancel() - err = b.zClient.Write(ctx, &v1.WriteRequest{ + err = h.zClient.Write(ctx, &v1.WriteRequest{ Namespace: role.Namespace, Writes: &v1.WriteRequestWrites{ TupleKeys: tuples, }, }) if err != nil { - b.logger.Error("failed to write role permissions to zanzana", + h.logger.Error("failed to write role permissions to zanzana", "err", err, "namespace", role.Namespace, "roleUID", role.Name, @@ -149,8 +160,8 @@ func (b *IdentityAccessManagementAPIBuilder) AfterRoleCreate(obj runtime.Object, // AfterRoleDelete is a post-delete hook that removes the role permissions from Zanzana (openFGA) // It handles both Role and CoreRole types -func (b *IdentityAccessManagementAPIBuilder) AfterRoleDelete(obj runtime.Object, _ *metav1.DeleteOptions) { - if b.zClient == nil { +func (h *RoleHooks) AfterRoleDelete(obj runtime.Object, _ *metav1.DeleteOptions) { + if h.zClient == nil { return } @@ -182,15 +193,15 @@ func (b *IdentityAccessManagementAPIBuilder) AfterRoleDelete(obj runtime.Object, } wait := time.Now() - b.zTickets <- true + h.zTickets <- true hooksWaitHistogram.WithLabelValues("role", "delete").Observe(time.Since(wait).Seconds()) // Record wait time go func(role *iamv0.CoreRole, roleType string) { defer func() { - <-b.zTickets + <-h.zTickets }() - b.logger.Debug("deleting role permissions from zanzana", + h.logger.Debug("deleting role permissions from zanzana", "namespace", role.Namespace, "roleUID", role.Name, "roleType", roleType, @@ -199,7 +210,7 @@ func (b *IdentityAccessManagementAPIBuilder) AfterRoleDelete(obj runtime.Object, tuples, err := convertRolePermissionsToTuples(role.Name, role.Spec.Permissions) if err != nil { - b.logger.Error("failed to convert role permissions to tuples for deletion", + h.logger.Error("failed to convert role permissions to tuples for deletion", "namespace", role.Namespace, "roleUID", role.Name, "roleType", roleType, @@ -211,7 +222,7 @@ func (b *IdentityAccessManagementAPIBuilder) AfterRoleDelete(obj runtime.Object, // Avoid deleting if there are no valid tuples if len(tuples) == 0 { - b.logger.Debug("no valid tuples to delete for role", + h.logger.Debug("no valid tuples to delete for role", "namespace", role.Namespace, "roleUID", role.Name, "roleType", roleType, @@ -223,7 +234,7 @@ func (b *IdentityAccessManagementAPIBuilder) AfterRoleDelete(obj runtime.Object, // Convert tuples to TupleKeyWithoutCondition for deletion deleteTuples := toTupleKeysWithoutCondition(tuples) - b.logger.Debug("deleting role permissions from zanzana", + h.logger.Debug("deleting role permissions from zanzana", "namespace", role.Namespace, "roleUID", role.Name, "roleType", roleType, @@ -234,14 +245,14 @@ func (b *IdentityAccessManagementAPIBuilder) AfterRoleDelete(obj runtime.Object, ctx, cancel := context.WithTimeout(context.Background(), defaultWriteTimeout) defer cancel() - err = b.zClient.Write(ctx, &v1.WriteRequest{ + err = h.zClient.Write(ctx, &v1.WriteRequest{ Namespace: role.Namespace, Deletes: &v1.WriteRequestDeletes{ TupleKeys: deleteTuples, }, }) if err != nil { - b.logger.Error("failed to delete role permissions from zanzana", + h.logger.Error("failed to delete role permissions from zanzana", "err", err, "namespace", role.Namespace, "roleUID", role.Name, @@ -255,8 +266,8 @@ func (b *IdentityAccessManagementAPIBuilder) AfterRoleDelete(obj runtime.Object, // beginRoleUpdate is a pre-update hook that prepares zanzana updates // It converts old and new permissions to tuples and performs the zanzana write after K8s update succeeds // It handles both Role and CoreRole types -func (b *IdentityAccessManagementAPIBuilder) BeginRoleUpdate(ctx context.Context, obj, oldObj runtime.Object, options *metav1.UpdateOptions) (registry.FinishFunc, error) { - if b.zClient == nil { +func (h *RoleHooks) BeginRoleUpdate(ctx context.Context, obj, oldObj runtime.Object, options *metav1.UpdateOptions) (registry.FinishFunc, error) { + if h.zClient == nil { return nil, nil } var oldRole, newRole *iamv0.CoreRole @@ -316,12 +327,12 @@ func (b *IdentityAccessManagementAPIBuilder) BeginRoleUpdate(ctx context.Context // Grab a ticket to write to Zanzana wait := time.Now() - b.zTickets <- true + h.zTickets <- true hooksWaitHistogram.WithLabelValues(roleType, "update").Observe(time.Since(wait).Seconds()) // Record wait time go func(old *iamv0.CoreRole, new *iamv0.CoreRole) { defer func() { - <-b.zTickets + <-h.zTickets }() roleUID, namespace := old.Name, old.Namespace oldPermissions, newPermissions := old.Spec.Permissions, new.Spec.Permissions @@ -332,7 +343,7 @@ func (b *IdentityAccessManagementAPIBuilder) BeginRoleUpdate(ctx context.Context var err error oldTuples, err = convertRolePermissionsToTuples(roleUID, oldPermissions) if err != nil { - b.logger.Error("failed to convert old role permissions to tuples", + h.logger.Error("failed to convert old role permissions to tuples", "namespace", namespace, "roleUID", roleUID, "roleType", roleType, @@ -344,7 +355,7 @@ func (b *IdentityAccessManagementAPIBuilder) BeginRoleUpdate(ctx context.Context // Convert new permissions to tuples for writing newTuples, err := convertRolePermissionsToTuples(roleUID, newPermissions) if err != nil { - b.logger.Error("failed to convert new role permissions to tuples", + h.logger.Error("failed to convert new role permissions to tuples", "namespace", namespace, "roleUID", roleUID, "roleType", roleType, @@ -353,7 +364,7 @@ func (b *IdentityAccessManagementAPIBuilder) BeginRoleUpdate(ctx context.Context return } - b.logger.Debug("updating role permissions in zanzana", + h.logger.Debug("updating role permissions in zanzana", "namespace", namespace, "roleUID", roleUID, "roleType", roleType, @@ -375,7 +386,7 @@ func (b *IdentityAccessManagementAPIBuilder) BeginRoleUpdate(ctx context.Context req.Deletes = &v1.WriteRequestDeletes{ TupleKeys: deleteTuples, } - b.logger.Debug("deleting existing role permissions from zanzana", + h.logger.Debug("deleting existing role permissions from zanzana", "namespace", namespace, "roleUID", roleUID, "roleType", roleType, @@ -388,7 +399,7 @@ func (b *IdentityAccessManagementAPIBuilder) BeginRoleUpdate(ctx context.Context req.Writes = &v1.WriteRequestWrites{ TupleKeys: newTuples, } - b.logger.Debug("writing new role permissions to zanzana", + h.logger.Debug("writing new role permissions to zanzana", "namespace", namespace, "roleUID", roleUID, "roleType", roleType, @@ -398,9 +409,9 @@ func (b *IdentityAccessManagementAPIBuilder) BeginRoleUpdate(ctx context.Context // Only make the request if there are deletes or writes if req.Deletes != nil || req.Writes != nil { - err = b.zClient.Write(ctx, req) + err = h.zClient.Write(ctx, req) if err != nil { - b.logger.Error("failed to update role permissions in zanzana", + h.logger.Error("failed to update role permissions in zanzana", "err", err, "namespace", namespace, "roleUID", roleUID, diff --git a/pkg/registry/apis/iam/role_hooks_test.go b/pkg/registry/apis/iam/role_hooks_test.go index 497150f1c61..847d0c7727a 100644 --- a/pkg/registry/apis/iam/role_hooks_test.go +++ b/pkg/registry/apis/iam/role_hooks_test.go @@ -53,7 +53,7 @@ func requireDeleteTuplesMatch(t *testing.T, actual []*v1.TupleKeyWithoutConditio func TestAfterCoreRoleCreate(t *testing.T) { var wg sync.WaitGroup - b := &IdentityAccessManagementAPIBuilder{ + h := &RoleHooks{ logger: log.NewNopLogger(), zTickets: make(chan bool, 1), } @@ -90,8 +90,8 @@ func TestAfterCoreRoleCreate(t *testing.T) { return nil } - b.zClient = &FakeZanzanaClient{writeCallback: testCoreRoleEntries} - b.AfterRoleCreate(&coreRole, nil) + h.zClient = &FakeZanzanaClient{writeCallback: testCoreRoleEntries} + h.AfterRoleCreate(&coreRole, nil) wg.Wait() }) @@ -129,8 +129,8 @@ func TestAfterCoreRoleCreate(t *testing.T) { return nil } - b.zClient = &FakeZanzanaClient{writeCallback: testDashboardRoleEntries} - b.AfterRoleCreate(&coreRole, nil) + h.zClient = &FakeZanzanaClient{writeCallback: testDashboardRoleEntries} + h.AfterRoleCreate(&coreRole, nil) wg.Wait() }) @@ -163,8 +163,8 @@ func TestAfterCoreRoleCreate(t *testing.T) { return nil } - b.zClient = &FakeZanzanaClient{writeCallback: testWildcardEntries} - b.AfterRoleCreate(&coreRole, nil) + h.zClient = &FakeZanzanaClient{writeCallback: testWildcardEntries} + h.AfterRoleCreate(&coreRole, nil) wg.Wait() }) @@ -198,15 +198,15 @@ func TestAfterCoreRoleCreate(t *testing.T) { return nil } - b.zClient = &FakeZanzanaClient{writeCallback: testMixedEntries} - b.AfterRoleCreate(&coreRole, nil) + h.zClient = &FakeZanzanaClient{writeCallback: testMixedEntries} + h.AfterRoleCreate(&coreRole, nil) wg.Wait() }) } func TestAfterRoleCreate(t *testing.T) { var wg sync.WaitGroup - b := &IdentityAccessManagementAPIBuilder{ + h := &RoleHooks{ logger: log.NewNopLogger(), zTickets: make(chan bool, 1), } @@ -243,8 +243,8 @@ func TestAfterRoleCreate(t *testing.T) { return nil } - b.zClient = &FakeZanzanaClient{writeCallback: testRoleEntries} - b.AfterRoleCreate(&role, nil) + h.zClient = &FakeZanzanaClient{writeCallback: testRoleEntries} + h.AfterRoleCreate(&role, nil) wg.Wait() }) @@ -281,8 +281,8 @@ func TestAfterRoleCreate(t *testing.T) { return nil } - b.zClient = &FakeZanzanaClient{writeCallback: testDashRoleEntries} - b.AfterRoleCreate(&role, nil) + h.zClient = &FakeZanzanaClient{writeCallback: testDashRoleEntries} + h.AfterRoleCreate(&role, nil) wg.Wait() }) @@ -317,15 +317,15 @@ func TestAfterRoleCreate(t *testing.T) { return nil } - b.zClient = &FakeZanzanaClient{writeCallback: testMergedEntries} - b.AfterRoleCreate(&role, nil) + h.zClient = &FakeZanzanaClient{writeCallback: testMergedEntries} + h.AfterRoleCreate(&role, nil) wg.Wait() }) } func TestBeginCoreRoleUpdate(t *testing.T) { var wg sync.WaitGroup - b := &IdentityAccessManagementAPIBuilder{ + h := &RoleHooks{ logger: log.NewNopLogger(), zTickets: make(chan bool, 1), } @@ -387,10 +387,10 @@ func TestBeginCoreRoleUpdate(t *testing.T) { return nil } - b.zClient = &FakeZanzanaClient{writeCallback: testUpdate} + h.zClient = &FakeZanzanaClient{writeCallback: testUpdate} // Call BeginUpdate which does all the work - finishFunc, err := b.BeginRoleUpdate(context.Background(), &newRole, &oldRole, nil) + finishFunc, err := h.BeginRoleUpdate(context.Background(), &newRole, &oldRole, nil) require.NoError(t, err) require.NotNil(t, finishFunc) @@ -445,10 +445,10 @@ func TestBeginCoreRoleUpdate(t *testing.T) { return nil } - b.zClient = &FakeZanzanaClient{writeCallback: testExpand} + h.zClient = &FakeZanzanaClient{writeCallback: testExpand} // Call BeginUpdate which does all the work - finishFunc, err := b.BeginRoleUpdate(context.Background(), &newRole, &oldRole, nil) + finishFunc, err := h.BeginRoleUpdate(context.Background(), &newRole, &oldRole, nil) require.NoError(t, err) require.NotNil(t, finishFunc) @@ -499,10 +499,10 @@ func TestBeginCoreRoleUpdate(t *testing.T) { return nil } - b.zClient = &FakeZanzanaClient{writeCallback: testClear} + h.zClient = &FakeZanzanaClient{writeCallback: testClear} // Call BeginUpdate which does all the work - finishFunc, err := b.BeginRoleUpdate(context.Background(), &newRole, &oldRole, nil) + finishFunc, err := h.BeginRoleUpdate(context.Background(), &newRole, &oldRole, nil) require.NoError(t, err) require.NotNil(t, finishFunc) @@ -514,7 +514,7 @@ func TestBeginCoreRoleUpdate(t *testing.T) { func TestBeginRoleUpdate(t *testing.T) { var wg sync.WaitGroup - b := &IdentityAccessManagementAPIBuilder{ + h := &RoleHooks{ logger: log.NewNopLogger(), zTickets: make(chan bool, 1), } @@ -577,10 +577,10 @@ func TestBeginRoleUpdate(t *testing.T) { return nil } - b.zClient = &FakeZanzanaClient{writeCallback: testUpdate} + h.zClient = &FakeZanzanaClient{writeCallback: testUpdate} // Call BeginUpdate which does all the work - finishFunc, err := b.BeginRoleUpdate(context.Background(), &newRole, &oldRole, nil) + finishFunc, err := h.BeginRoleUpdate(context.Background(), &newRole, &oldRole, nil) require.NoError(t, err) require.NotNil(t, finishFunc) @@ -640,10 +640,10 @@ func TestBeginRoleUpdate(t *testing.T) { return nil } - b.zClient = &FakeZanzanaClient{writeCallback: testSwap} + h.zClient = &FakeZanzanaClient{writeCallback: testSwap} // Call BeginUpdate which does all the work - finishFunc, err := b.BeginRoleUpdate(context.Background(), &newRole, &oldRole, nil) + finishFunc, err := h.BeginRoleUpdate(context.Background(), &newRole, &oldRole, nil) require.NoError(t, err) require.NotNil(t, finishFunc) @@ -695,10 +695,10 @@ func TestBeginRoleUpdate(t *testing.T) { return nil } - b.zClient = &FakeZanzanaClient{writeCallback: testAddToEmpty} + h.zClient = &FakeZanzanaClient{writeCallback: testAddToEmpty} // Call BeginUpdate which does all the work - finishFunc, err := b.BeginRoleUpdate(context.Background(), &newRole, &oldRole, nil) + finishFunc, err := h.BeginRoleUpdate(context.Background(), &newRole, &oldRole, nil) require.NoError(t, err) require.NotNil(t, finishFunc) @@ -710,7 +710,7 @@ func TestBeginRoleUpdate(t *testing.T) { func TestAfterCoreRoleDelete(t *testing.T) { var wg sync.WaitGroup - b := &IdentityAccessManagementAPIBuilder{ + h := &RoleHooks{ logger: log.NewNopLogger(), zTickets: make(chan bool, 1), } @@ -747,8 +747,8 @@ func TestAfterCoreRoleDelete(t *testing.T) { return nil } - b.zClient = &FakeZanzanaClient{writeCallback: testCoreRoleDeletes} - b.AfterRoleDelete(&coreRole, nil) + h.zClient = &FakeZanzanaClient{writeCallback: testCoreRoleDeletes} + h.AfterRoleDelete(&coreRole, nil) wg.Wait() }) @@ -785,8 +785,8 @@ func TestAfterCoreRoleDelete(t *testing.T) { return nil } - b.zClient = &FakeZanzanaClient{writeCallback: testDashboardRoleDeletes} - b.AfterRoleDelete(&coreRole, nil) + h.zClient = &FakeZanzanaClient{writeCallback: testDashboardRoleDeletes} + h.AfterRoleDelete(&coreRole, nil) wg.Wait() }) @@ -818,8 +818,8 @@ func TestAfterCoreRoleDelete(t *testing.T) { return nil } - b.zClient = &FakeZanzanaClient{writeCallback: testWildcardDeletes} - b.AfterRoleDelete(&coreRole, nil) + h.zClient = &FakeZanzanaClient{writeCallback: testWildcardDeletes} + h.AfterRoleDelete(&coreRole, nil) wg.Wait() }) @@ -853,15 +853,15 @@ func TestAfterCoreRoleDelete(t *testing.T) { return nil } - b.zClient = &FakeZanzanaClient{writeCallback: testMixedDeletes} - b.AfterRoleDelete(&coreRole, nil) + h.zClient = &FakeZanzanaClient{writeCallback: testMixedDeletes} + h.AfterRoleDelete(&coreRole, nil) wg.Wait() }) } func TestAfterRoleDelete(t *testing.T) { var wg sync.WaitGroup - b := &IdentityAccessManagementAPIBuilder{ + h := &RoleHooks{ logger: log.NewNopLogger(), zTickets: make(chan bool, 1), } @@ -898,8 +898,8 @@ func TestAfterRoleDelete(t *testing.T) { return nil } - b.zClient = &FakeZanzanaClient{writeCallback: testRoleDeletes} - b.AfterRoleDelete(&role, nil) + h.zClient = &FakeZanzanaClient{writeCallback: testRoleDeletes} + h.AfterRoleDelete(&role, nil) wg.Wait() }) @@ -936,8 +936,8 @@ func TestAfterRoleDelete(t *testing.T) { return nil } - b.zClient = &FakeZanzanaClient{writeCallback: testDashRoleDeletes} - b.AfterRoleDelete(&role, nil) + h.zClient = &FakeZanzanaClient{writeCallback: testDashRoleDeletes} + h.AfterRoleDelete(&role, nil) wg.Wait() }) @@ -982,8 +982,8 @@ func TestAfterRoleDelete(t *testing.T) { return nil } - b.zClient = &FakeZanzanaClient{writeCallback: testMultiDeletes} - b.AfterRoleDelete(&role, nil) + h.zClient = &FakeZanzanaClient{writeCallback: testMultiDeletes} + h.AfterRoleDelete(&role, nil) wg.Wait() }) } diff --git a/pkg/registry/apis/wireset.go b/pkg/registry/apis/wireset.go index df38965759b..d95f01bef36 100644 --- a/pkg/registry/apis/wireset.go +++ b/pkg/registry/apis/wireset.go @@ -28,7 +28,7 @@ import ( var WireSetExts = wire.NewSet( noopstorage.ProvideStorageBackend, wire.Bind(new(iam.CoreRoleStorageBackend), new(*noopstorage.StorageBackendImpl)), - wire.Bind(new(iam.RoleStorageBackend), new(*noopstorage.StorageBackendImpl)), + iam.ProvideNoopRoleApiInstaller, wire.Bind(new(iam.RoleBindingStorageBackend), new(*noopstorage.StorageBackendImpl)), wire.Bind(new(iam.ExternalGroupMappingStorageBackend), new(*noopstorage.StorageBackendImpl)), diff --git a/pkg/server/wire_gen.go b/pkg/server/wire_gen.go index 478ef749f11..676b0605e83 100644 --- a/pkg/server/wire_gen.go +++ b/pkg/server/wire_gen.go @@ -882,8 +882,9 @@ func Initialize(ctx context.Context, cfg *setting.Cfg, opts Options, apiOpts api } folderAPIBuilder := folders.RegisterAPIService(cfg, featureToggles, apiserverService, folderimplService, folderPermissionsService, accessControl, acimplService, accessClient, registerer, resourceClient, zanzanaClient) storageBackendImpl := noopstorage.ProvideStorageBackend() + roleApiInstaller := iam.ProvideNoopRoleApiInstaller() noopTeamGroupsREST := externalgroupmapping.ProvideNoopTeamGroupsREST() - identityAccessManagementAPIBuilder, err := iam.RegisterAPIService(cfg, featureToggles, apiserverService, ssosettingsimplService, sqlStore, accessControl, accessClient, zanzanaClient, registerer, storageBackendImpl, storageBackendImpl, tracingService, storageBackendImpl, storageBackendImpl, noopTeamGroupsREST, dualwriteService, resourceClient, orgService, userService, teamService, eventualRestConfigProvider) + identityAccessManagementAPIBuilder, err := iam.RegisterAPIService(cfg, featureToggles, apiserverService, ssosettingsimplService, sqlStore, accessControl, accessClient, zanzanaClient, registerer, storageBackendImpl, roleApiInstaller, tracingService, storageBackendImpl, storageBackendImpl, noopTeamGroupsREST, dualwriteService, resourceClient, orgService, userService, teamService, eventualRestConfigProvider) if err != nil { return nil, err } @@ -1543,8 +1544,9 @@ func InitializeForTest(ctx context.Context, t sqlutil.ITestDB, testingT interfac } folderAPIBuilder := folders.RegisterAPIService(cfg, featureToggles, apiserverService, folderimplService, folderPermissionsService, accessControl, acimplService, accessClient, registerer, resourceClient, zanzanaClient) storageBackendImpl := noopstorage.ProvideStorageBackend() + roleApiInstaller := iam.ProvideNoopRoleApiInstaller() noopTeamGroupsREST := externalgroupmapping.ProvideNoopTeamGroupsREST() - identityAccessManagementAPIBuilder, err := iam.RegisterAPIService(cfg, featureToggles, apiserverService, ssosettingsimplService, sqlStore, accessControl, accessClient, zanzanaClient, registerer, storageBackendImpl, storageBackendImpl, tracingService, storageBackendImpl, storageBackendImpl, noopTeamGroupsREST, dualwriteService, resourceClient, orgService, userService, teamService, eventualRestConfigProvider) + identityAccessManagementAPIBuilder, err := iam.RegisterAPIService(cfg, featureToggles, apiserverService, ssosettingsimplService, sqlStore, accessControl, accessClient, zanzanaClient, registerer, storageBackendImpl, roleApiInstaller, tracingService, storageBackendImpl, storageBackendImpl, noopTeamGroupsREST, dualwriteService, resourceClient, orgService, userService, teamService, eventualRestConfigProvider) if err != nil { return nil, err } From f75b5654c9a23524a829cca61532a350247bb6cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 18 Dec 2025 11:20:48 +0100 Subject: [PATCH 07/36] Modal: Fix modal button row (#115483) * Modal: Fix modal button row * update * update --- packages/grafana-ui/src/components/Modal/getModalStyles.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grafana-ui/src/components/Modal/getModalStyles.ts b/packages/grafana-ui/src/components/Modal/getModalStyles.ts index 3c124d24e68..f1e92cfa3f6 100644 --- a/packages/grafana-ui/src/components/Modal/getModalStyles.ts +++ b/packages/grafana-ui/src/components/Modal/getModalStyles.ts @@ -79,7 +79,7 @@ export const getModalStyles = (theme: GrafanaTheme2) => { modalContent: css({ overflow: 'auto', padding: theme.spacing(3, 3, 0, 3), - marginBottom: theme.spacing(3), + marginBottom: theme.spacing(2.5), scrollbarWidth: 'thin', width: '100%', From 2123099e882c7cff6f914ce81ab089fa7e1e56ac Mon Sep 17 00:00:00 2001 From: Gonzalo Trigueros Manzanas <242162051+gttrigger@users.noreply.github.com> Date: Thu, 18 Dec 2025 11:28:10 +0100 Subject: [PATCH 08/36] Provisioning: escape URLs in PR comments to avoid malformed markdown. (#115486) provisioning: escape URLs in webhook changes to allow for proper markdown. --- .../webhooks/pullrequest/changes.go | 15 ++-- .../webhooks/pullrequest/changes_test.go | 74 ++++++++++++++++++- 2 files changed, 82 insertions(+), 7 deletions(-) diff --git a/pkg/registry/apis/provisioning/webhooks/pullrequest/changes.go b/pkg/registry/apis/provisioning/webhooks/pullrequest/changes.go index f8e485e8fe5..d6fec09bc96 100644 --- a/pkg/registry/apis/provisioning/webhooks/pullrequest/changes.go +++ b/pkg/registry/apis/provisioning/webhooks/pullrequest/changes.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "net/url" - "path" "strings" "time" @@ -141,14 +140,20 @@ func (e *evaluator) evaluateFile(ctx context.Context, repo repository.Reader, ba if info.Parsed.GVK.Kind == dashboardKind { // FIXME: extract the logic out of a dashboard URL builder/injector or similar // for testability and decoupling + urlBuilder, err := url.Parse(baseURL) + if err != nil { + info.Error = err.Error() + return info + } + if info.Parsed.Existing != nil { - info.GrafanaURL = fmt.Sprintf("%sd/%s/%s", baseURL, obj.GetName(), - slugify.Slugify(info.Title)) + grafanaURL := urlBuilder.JoinPath("d", obj.GetName(), slugify.Slugify(info.Title)) + info.GrafanaURL = grafanaURL.String() } // Load this file directly - info.PreviewURL = baseURL + path.Join("admin/provisioning", - info.Parsed.Repo.Name, "dashboard/preview", info.Parsed.Info.Path) + previewURL := urlBuilder.JoinPath("admin/provisioning", info.Parsed.Repo.Name, "dashboard/preview", info.Parsed.Info.Path) + info.PreviewURL = previewURL.String() query := url.Values{} query.Set("ref", info.Parsed.Info.Ref) diff --git a/pkg/registry/apis/provisioning/webhooks/pullrequest/changes_test.go b/pkg/registry/apis/provisioning/webhooks/pullrequest/changes_test.go index c8f0c33e92a..6c513830d29 100644 --- a/pkg/registry/apis/provisioning/webhooks/pullrequest/changes_test.go +++ b/pkg/registry/apis/provisioning/webhooks/pullrequest/changes_test.go @@ -737,8 +737,78 @@ func TestCalculateChanges(t *testing.T) { Path: "path/to/file.json", Ref: "ref", }, - GrafanaURL: "ht tp://bad url/d/the-uid/hello-world", // Malformed URL - PreviewURL: "ht tp://bad url/admin/provisioning/y/dashboard/preview/path/to/file.json?pull_request_url=http%253A%252F%252Fgithub.com%252Fpr%252F&ref=ref", + Error: "parse \"ht tp://bad url/\": first path segment in URL cannot contain colon", + }}, + }, + }, + { + name: "path with spaces", + setupMocks: func(parser *resources.MockParser, reader *repository.MockReader, progress *jobs.MockJobProgressRecorder, renderer *MockScreenshotRenderer, parserFactory *resources.MockParserFactory) { + finfo := &repository.FileInfo{ + Path: "path/to/file with spaces.json", + Ref: "ref", + Data: []byte("xxxx"), + } + obj := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": resources.DashboardResource.GroupVersion().String(), + "kind": dashboardKind, + "metadata": map[string]interface{}{ + "name": "the-uid", + }, + "spec": map[string]interface{}{ + "title": "hello world", + }, + }, + } + meta, _ := utils.MetaAccessor(obj) + + progress.On("SetMessage", mock.Anything, "process path/to/file with spaces.json").Return() + reader.On("Read", mock.Anything, "path/to/file with spaces.json", "ref").Return(finfo, nil) + reader.On("Config").Return(&provisioning.Repository{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-repo", + Namespace: "x", + }, + Spec: provisioning.RepositorySpec{ + GitHub: &provisioning.GitHubRepositoryConfig{ + GenerateDashboardPreviews: true, + }, + }, + }) + parser.On("Parse", mock.Anything, finfo).Return(&resources.ParsedResource{ + Info: finfo, + Repo: provisioning.ResourceRepositoryInfo{ + Namespace: "x", + Name: "y", + }, + GVK: schema.GroupVersionKind{ + Kind: dashboardKind, + }, + Obj: obj, + Existing: obj, + Meta: meta, + DryRunResponse: obj, + }, nil) + renderer.On("IsAvailable", mock.Anything, mock.Anything).Return(false) + parserFactory.On("GetParser", mock.Anything, mock.Anything).Return(parser, nil) + }, + changes: []repository.VersionedFileChange{{ + Action: repository.FileActionCreated, + Path: "path/to/file with spaces.json", + Ref: "ref", + }}, + expectedInfo: changeInfo{ + Changes: []fileChangeInfo{{ + Change: repository.VersionedFileChange{ + Action: repository.FileActionCreated, + Path: "path/to/file with spaces.json", + Ref: "ref", + }, + GrafanaURL: "http://host/d/the-uid/hello-world", + PreviewURL: "http://host/admin/provisioning/y/dashboard/preview/path/to/file%20with%20spaces.json?pull_request_url=http%253A%252F%252Fgithub.com%252Fpr%252F&ref=ref", + GrafanaScreenshotURL: "", + PreviewScreenshotURL: "", }}, }, }, From 5bedcc7bd7cf0d8636c1d0839466404f0790e48f Mon Sep 17 00:00:00 2001 From: Jack Westbrook Date: Thu, 18 Dec 2025 11:47:38 +0100 Subject: [PATCH 09/36] Frontend: use custom conditions for development and build (#111685) * build(frontend): enable custom condition for resolving source files during dev and build * feat(packages): apply conditional name to export properties * chore(packages): add standard exports to flamegraph and prometheus * chore(packages): resolve main, module, types to built files * build(packages): clean up prepare-npm-package for custom condition changes * refactor(packages): reduce repetition in conditional exports * build(storybook): add @grafana-app/source to conditionNames * test(frontend): add grafana-app/source customCondition for jest tests * refactor(frontend): remove nested package import paths * chore(jest): use customExportConditions for source files and browser * chore(i18n): use src for ./eslint-plugin export * chore(packages): set packages tsconfigs to moduleResolution bundler * chore(packages): fix rollup builds * build(packages): build cjs as multiple files * chore(sql): reference MonitoringLogger for moduleresolution bundler to pass typecheck * chore(ui): add type refs for moduleresolution bundler to pass typecheck * feat(schema): add exports for cleaner import paths * refactor(frontend): clean up schema paths to point to exports instead of nested file paths * build(storybook): hack the builder-manager for custom conditions to resolve * build(decoupled-plugins): fix broken builds due to missing conditionNames * chore(e2e): pass condition to playwright to resolve local packages * build(frontend): fix failing build * chore(select): fix typings * style(frontend): clean up eslint suppressions * chore(packages): fix type errors due to incorrect tsconfig settings * build(generate-apis): use swc with ts-node and moduleResolution bundler * chore(cypress): add conditionNames to resolve monorepo packages * build(npm): update prepare to work with latest exports changes * build(packages): fix prepare-npm-package script * fix(e2e-selectors): update debugoverlay for data-testid change * build(packages): stop editing package.json at pack n publish time * rerun ci * chore(api-clients): use moduleResolution: bundler for customConditions support * chore(api-clients): fix generation * build(packages): remove aliasing exports, remove exports with only customConditions * Revert "refactor(frontend): clean up schema paths to point to exports instead of nested file paths" This reverts commit 7949b6ea0e60e51989d2a8149b7a24647cd68916. * revert(schema): remove exports from package so builds work * build(api-clients): fix up api-clients exports and rollup config * build(api-clients): Update generated package exports for api clients * build(schema): add overrides to cjsOutput and esmOutput so built directory structure is correct * fix(packages): use rootDirs to prevent types/src directories in built d.ts file paths * build(packages): prevent empty exports added to package.json during pack * docs(packages): update readme with custom conditions information --------- Co-authored-by: Tom Ratcliffe --- ...@storybook-core-npm-8.6.2-8c752112c0.patch | 13 ++ e2e/cypress/plugins/typescriptPreprocessor.js | 1 + eslint-suppressions.json | 3 - jest.config.js | 3 + package.json | 13 +- packages/README.md | 31 +++- packages/grafana-alerting/package.json | 38 +++-- packages/grafana-alerting/rollup.config.ts | 6 +- packages/grafana-api-clients/package.json | 157 +++++++++++------- packages/grafana-api-clients/rollup.config.ts | 22 +-- .../src/generator/helpers.ts | 6 +- packages/grafana-api-clients/tsconfig.json | 12 +- packages/grafana-data/package.json | 35 ++-- packages/grafana-data/rollup.config.ts | 4 +- packages/grafana-data/tsconfig.json | 4 +- packages/grafana-e2e-selectors/package.json | 17 +- .../grafana-e2e-selectors/rollup.config.ts | 2 +- .../src/selectors/components.ts | 1 + packages/grafana-e2e-selectors/tsconfig.json | 3 +- packages/grafana-flamegraph/package.json | 17 +- packages/grafana-flamegraph/rollup.config.ts | 2 +- packages/grafana-flamegraph/tsconfig.json | 3 +- packages/grafana-i18n/package.json | 23 ++- packages/grafana-i18n/rollup.config.ts | 10 +- packages/grafana-i18n/tsconfig.json | 3 +- .../grafana-o11y-ds-frontend/tsconfig.json | 3 +- .../jest/jest.config.js | 3 + packages/grafana-plugin-configs/tsconfig.json | 7 +- .../grafana-plugin-configs/webpack.config.ts | 1 + packages/grafana-prometheus/package.json | 17 +- packages/grafana-prometheus/rollup.config.ts | 2 +- packages/grafana-runtime/package.json | 32 ++-- packages/grafana-runtime/rollup.config.ts | 4 +- packages/grafana-runtime/src/index.ts | 10 +- packages/grafana-runtime/tsconfig.json | 3 +- packages/grafana-schema/package.json | 9 +- packages/grafana-schema/rollup.config.ts | 7 +- packages/grafana-schema/tsconfig.json | 4 +- packages/grafana-sql/src/utils/logging.ts | 4 +- packages/grafana-sql/tsconfig.json | 3 +- packages/grafana-ui/.storybook/main.ts | 10 ++ packages/grafana-ui/.storybook/tsconfig.json | 3 +- packages/grafana-ui/package.json | 32 ++-- packages/grafana-ui/rollup.config.ts | 4 +- .../Forms/Legacy/Select/SelectOptionGroup.tsx | 3 +- .../src/components/Select/ValueContainer.tsx | 3 +- packages/grafana-ui/tsconfig.json | 3 +- packages/rollup.config.parts.ts | 12 +- project.json | 3 +- .../app/features/admin/UserListAdminPage.tsx | 2 +- .../features/admin/UserListAnonymousPage.tsx | 2 +- .../app/features/admin/UserListPage.test.tsx | 2 +- .../DashboardsListModalButton.tsx | 2 +- .../UserListPublicDashboardPage.tsx | 2 +- .../inspect/InspectJsonTab.tsx | 2 +- .../layout-auto-grid/AutoGridItemRenderer.tsx | 2 +- .../components/DashNav/ShareButton.tsx | 2 +- .../PublicDashboardNotAvailable.tsx | 2 +- .../ConfigPublicDashboard.tsx | 2 +- .../ConfigPublicDashboard/Configuration.tsx | 2 +- .../AcknowledgeCheckboxes.tsx | 2 +- .../ModalAlerts/NoUpsertPermissionsAlert.tsx | 2 +- .../UnsupportedDataSourcesAlert.tsx | 2 +- .../UnsupportedTemplateVariablesAlert.tsx | 2 +- .../SharePublicDashboard.test.tsx | 2 +- .../containers/PublicDashboardPage.test.tsx | 2 +- .../containers/PublicDashboardPage.tsx | 2 +- .../PrometheusListView/RawListItem.tsx | 2 +- .../logs/components/panel/LogListControls.tsx | 2 +- .../PublicDashboardListTable.test.tsx | 2 +- .../PublicDashboardListTable.tsx | 2 +- .../panel/geomap/components/DebugOverlay.tsx | 4 +- scripts/cli/tsconfig.json | 4 +- scripts/prepare-npm-package.js | 111 +++---------- scripts/tsconfig.base.json | 1 + scripts/validate-npm-packages.sh | 2 +- scripts/webpack/webpack.common.js | 1 + tsconfig.json | 3 +- yarn.lock | 24 +++ 79 files changed, 440 insertions(+), 365 deletions(-) create mode 100644 .yarn/patches/@storybook-core-npm-8.6.2-8c752112c0.patch diff --git a/.yarn/patches/@storybook-core-npm-8.6.2-8c752112c0.patch b/.yarn/patches/@storybook-core-npm-8.6.2-8c752112c0.patch new file mode 100644 index 00000000000..730ecce8fb2 --- /dev/null +++ b/.yarn/patches/@storybook-core-npm-8.6.2-8c752112c0.patch @@ -0,0 +1,13 @@ +diff --git a/dist/builder-manager/index.js b/dist/builder-manager/index.js +index 3d7f9b213dae1801bda62b31db31b9113e382ccd..212501c63d20146c29db63fb0f6300c6779eecb5 100644 +--- a/dist/builder-manager/index.js ++++ b/dist/builder-manager/index.js +@@ -1970,7 +1970,7 @@ var pa = /^\/($|\?)/, G, C, xt = /* @__PURE__ */ o(async (e) => { + bundle: !0, + minify: !0, + sourcemap: !1, +- conditions: ["browser", "module", "default"], ++ conditions: ["@grafana-app/source", "browser", "module", "default"], + jsxFactory: "React.createElement", + jsxFragment: "React.Fragment", + jsx: "transform", diff --git a/e2e/cypress/plugins/typescriptPreprocessor.js b/e2e/cypress/plugins/typescriptPreprocessor.js index 27765380994..ed054f6f943 100644 --- a/e2e/cypress/plugins/typescriptPreprocessor.js +++ b/e2e/cypress/plugins/typescriptPreprocessor.js @@ -18,6 +18,7 @@ const webpackOptions = { }, resolve: { extensions: ['.ts', '.js'], + conditionNames: ['@grafana-app/source', '...'], }, }; diff --git a/eslint-suppressions.json b/eslint-suppressions.json index dfa69ae64f4..08f37b316d6 100644 --- a/eslint-suppressions.json +++ b/eslint-suppressions.json @@ -4246,9 +4246,6 @@ } }, "public/app/plugins/panel/geomap/components/DebugOverlay.tsx": { - "@grafana/no-aria-label-selectors": { - "count": 1 - }, "react-prefer-function-component/react-prefer-function-component": { "count": 1 } diff --git a/jest.config.js b/jest.config.js index ef107c8cc24..17a2ce9ca32 100644 --- a/jest.config.js +++ b/jest.config.js @@ -40,6 +40,9 @@ const esModules = [ module.exports = { verbose: false, testEnvironment: 'jsdom', + testEnvironmentOptions: { + customExportConditions: ['@grafana-app/source', 'browser'], + }, transform: { '^.+\\.(ts|tsx|js|jsx)$': [require.resolve('ts-jest')], }, diff --git a/package.json b/package.json index e148217cac3..f2300869740 100644 --- a/package.json +++ b/package.json @@ -26,10 +26,10 @@ "e2e:enterprise": "./e2e/start-and-run-suite enterprise", "e2e:enterprise:dev": "./e2e/start-and-run-suite enterprise dev", "e2e:enterprise:debug": "./e2e/start-and-run-suite enterprise debug", - "e2e:playwright": "yarn playwright test --grep-invert @cloud-plugins", - "e2e:playwright:cloud-plugins": "yarn playwright test --grep @cloud-plugins", - "e2e:playwright:storybook": "yarn playwright test -c playwright.storybook.config.ts", - "e2e:acceptance": "yarn playwright test --grep @acceptance", + "e2e:playwright": "NODE_OPTIONS='-C @grafana-app/source' yarn playwright test --grep-invert @cloud-plugins", + "e2e:playwright:cloud-plugins": "NODE_OPTIONS='-C @grafana-app/source' yarn playwright test --grep @cloud-plugins", + "e2e:playwright:storybook": "NODE_OPTIONS='-C @grafana-app/source' yarn playwright test -c playwright.storybook.config.ts", + "e2e:acceptance": "NODE_OPTIONS='-C @grafana-app/source' yarn playwright test --grep @acceptance", "e2e:storybook": "PORT=9001 ./e2e/run-suite storybook true", "e2e:plugin:build": "nx run-many -t build --projects='@test-plugins/*'", "e2e:plugin:build:dev": "nx run-many -t dev --projects='@test-plugins/*' --maxParallel=100", @@ -63,7 +63,7 @@ "storybook": "yarn workspace @grafana/ui storybook --ci", "storybook:build": "yarn workspace @grafana/ui storybook:build", "themes-schema": "typescript-json-schema ./tsconfig.json NewThemeOptions --include 'packages/grafana-data/src/themes/createTheme.ts' --out public/app/features/theme-playground/schema.generated.json", - "themes-generate": "yarn themes-schema && esbuild --target=es6 ./scripts/cli/generateSassVariableFiles.ts --bundle --platform=node --tsconfig=./scripts/cli/tsconfig.json | node", + "themes-generate": "yarn themes-schema && esbuild --target=es6 ./scripts/cli/generateSassVariableFiles.ts --bundle --conditions=@grafana-app/source --platform=node --tsconfig=./scripts/cli/tsconfig.json | node", "themes:usage": "eslint . --ignore-pattern '*.test.ts*' --ignore-pattern '*.spec.ts*' --cache --plugin '@grafana' --rule '{ @grafana/theme-token-usage: \"error\" }'", "typecheck": "tsc --noEmit && yarn run packages:typecheck", "plugins:build-bundled": "echo 'bundled plugins are no longer supported'", @@ -460,7 +460,8 @@ "tmp@npm:^0.0.33": "~0.2.1", "js-yaml@npm:4.1.0": "^4.1.0", "js-yaml@npm:=4.1.0": "^4.1.0", - "nodemailer": "7.0.7" + "nodemailer": "7.0.7", + "@storybook/core@npm:8.6.2": "patch:@storybook/core@npm%3A8.6.2#~/.yarn/patches/@storybook-core-npm-8.6.2-8c752112c0.patch" }, "workspaces": { "packages": [ diff --git a/packages/README.md b/packages/README.md index b42f7082c5c..3fa0dceebc7 100644 --- a/packages/README.md +++ b/packages/README.md @@ -2,13 +2,32 @@ ## Exporting code conventions -`@grafana/ui`, `@grafana/data` and `@grafana/runtime` makes use of `exports` in package.json to define three entrypoints that Grafana core and Grafana plugins can access. Before exposing anything in these packages please consider the table below to better understand the use case of each export. +All the `@grafana` packages in this repo (except `@grafana/schema`) make use of `exports` in package.json to define entrypoints that Grafana core and Grafana plugins can access. Exports can also be used to restrict access to internal files in packages. -| Export Name | Import Path | Description | Available to Grafana | Available to plugins | -| ------------ | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------- | -------------------- | -| `./` | `@grafana/ui` | The public API entrypoint. If the code is stable and you want to share it everywhere, this is the place to export it. | ✅ | ✅ | -| `./unstable` | `@grafana/ui/unstable` | The public API entrypoint for all experimental code. If you want to iterate and test code from Grafana and plugins, this is the place to export it. | ✅ | ✅ | -| `./internal` | `@grafana/ui/internal` | The private API entrypoint for internal code shared with Grafana. If you need to import code in Grafana but don't want to expose it to plugins, this is the place to export it. | ✅ | ❌ | +Package authors are free to create as many exports as they like but should consider the following points: + +1. Resolution of source code within this repo is handled by the [customCondition](https://www.typescriptlang.org/tsconfig/#customConditions) `@grafana-app/source`. This allows the frontend tooling in this repo to resolve to the source code preventing the need to build all the packages up front. When adding exports it is important to add an entry for the custom condition as the first item. All other entries should point to the built, bundled files. For example: + + ```json + "exports": { + ".": { + "@grafana-app/source": "./src/index.ts", + "types": "./dist/types/index.d.ts", + "import": "./dist/esm/index.mjs", + "require": "./dist/cjs/index.cjs" + } + } + ``` + +2. If you add exports to your package you must export the `package.json` file. + +3. Before exposing anything in these packages please consider the table below to better understand the conventions we have put in place for most of the packages in this repository. + +| Export Name | Import Path | Description | Available to Grafana | Available to plugins | +| ------------ | ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------- | -------------------- | +| `./` | `@grafana/ui` | The public API entrypoint. If the code is stable and you want to share it everywhere, this is the place to export it. | ✅ | ✅ | +| `./unstable` | `@grafana/ui/unstable` | The public API entrypoint for all experimental code. If you want to iterate and test code from Grafana and plugins, this is the place to export it. | ✅ | ✅ | +| `./internal` | `@grafana/ui/internal` | The private API entrypoint for internal code shared with Grafana. If you want to co-locate code in a package with it's public API but only want the Grafana application to access it, this is the place to export it. | ✅ | ❌ | ## Versioning diff --git a/packages/grafana-alerting/package.json b/packages/grafana-alerting/package.json index 1c3ab2bf9b4..d64f1c01893 100644 --- a/packages/grafana-alerting/package.json +++ b/packages/grafana-alerting/package.json @@ -17,32 +17,34 @@ "url": "http://github.com/grafana/grafana.git", "directory": "packages/grafana-alerting" }, - "main": "src/index.ts", - "types": "src/index.ts", - "module": "src/index.ts", + "main": "./dist/cjs/index.cjs", + "module": "./dist/esm/index.mjs", + "types": "./dist/types/index.d.ts", "exports": { "./package.json": "./package.json", ".": { - "import": "./src/index.ts", - "require": "./src/index.ts" - }, - "./internal": { - "import": "./src/internal.ts", - "require": "./src/internal.ts" + "@grafana-app/source": "./src/index.ts", + "types": "./dist/types/index.d.ts", + "import": "./dist/esm/index.mjs", + "require": "./dist/cjs/index.cjs" }, "./unstable": { - "import": "./src/unstable.ts", - "require": "./src/unstable.ts" + "@grafana-app/source": "./src/unstable.ts", + "types": "./dist/types/unstable.d.ts", + "import": "./dist/esm/unstable.mjs", + "require": "./dist/cjs/unstable.cjs" + }, + "./internal": { + "@grafana-app/source": "./src/internal.ts" }, "./testing": { - "import": "./src/testing.ts", - "require": "./src/testing.ts" + "@grafana-app/source": "./src/testing.ts", + "types": "./dist/types/testing.d.ts", + "import": "./dist/esm/testing.mjs", + "require": "./dist/cjs/testing.cjs" } }, "publishConfig": { - "main": "./dist/cjs/index.cjs", - "module": "./dist/esm/index.mjs", - "types": "./dist/types/index.d.ts", "access": "public" }, "files": [ @@ -57,8 +59,8 @@ "clean": "rimraf ./dist ./compiled ./unstable ./testing ./package.tgz", "typecheck": "tsc --emitDeclarationOnly false --noEmit", "codegen": "rtk-query-codegen-openapi ./scripts/codegen.ts", - "prepack": "cp package.json package.json.bak && ALIAS_PACKAGE_NAME=testing,unstable node ../../scripts/prepare-npm-package.js", - "postpack": "mv package.json.bak package.json && rimraf ./unstable ./testing", + "prepack": "cp package.json package.json.bak && node ../../scripts/prepare-npm-package.js", + "postpack": "mv package.json.bak package.json", "i18n-extract": "i18next-cli extract --sync-primary" }, "devDependencies": { diff --git a/packages/grafana-alerting/rollup.config.ts b/packages/grafana-alerting/rollup.config.ts index f8f41e3ad2c..43914b47b70 100644 --- a/packages/grafana-alerting/rollup.config.ts +++ b/packages/grafana-alerting/rollup.config.ts @@ -9,19 +9,19 @@ export default [ { input: entryPoint, plugins, - output: [cjsOutput(pkg), esmOutput(pkg, 'grafana-alerting')], + output: [cjsOutput(pkg, 'grafana-alerting'), esmOutput(pkg, 'grafana-alerting')], treeshake: false, }, { input: 'src/unstable.ts', plugins, - output: [cjsOutput(pkg), esmOutput(pkg, 'grafana-alerting')], + output: [cjsOutput(pkg, 'grafana-alerting'), esmOutput(pkg, 'grafana-alerting')], treeshake: false, }, { input: 'src/testing.ts', plugins, - output: [cjsOutput(pkg), esmOutput(pkg, 'grafana-alerting')], + output: [cjsOutput(pkg, 'grafana-alerting'), esmOutput(pkg, 'grafana-alerting')], treeshake: false, }, ]; diff --git a/packages/grafana-api-clients/package.json b/packages/grafana-api-clients/package.json index 2d976b3a890..39accb04535 100644 --- a/packages/grafana-api-clients/package.json +++ b/packages/grafana-api-clients/package.json @@ -15,88 +15,121 @@ "url": "https://github.com/grafana/grafana.git", "directory": "packages/grafana-api-clients" }, - "main": "src/index.ts", - "module": "src/index.ts", - "types": "src/index.ts", + "main": "./dist/cjs/index.cjs", + "module": "./dist/esm/index.mjs", + "types": "./dist/types/index.d.ts", "exports": { "./package.json": "./package.json", ".": { - "import": "./src/index.ts", - "require": "./src/index.ts" + "@grafana-app/source": "./src/index.ts", + "types": "./dist/types/index.d.ts", + "import": "./dist/esm/index.mjs", + "require": "./dist/cjs/index.cjs" }, "./rtkq": { - "import": "./src/clients/rtkq/index.ts", - "require": "./src/clients/rtkq/index.ts" + "@grafana-app/source": "./src/clients/rtkq/index.ts", + "types": "./dist/types/clients/rtkq/index.d.ts", + "import": "./dist/esm/clients/rtkq/index.mjs", + "require": "./dist/cjs/clients/rtkq/index.cjs" }, "./rtkq/advisor/v0alpha1": { - "import": "./src/clients/rtkq/advisor/v0alpha1/index.ts", - "require": "./src/clients/rtkq/advisor/v0alpha1/index.ts" - }, - "./rtkq/correlations/v0alpha1": { - "import": "./src/clients/rtkq/correlations/v0alpha1/index.ts", - "require": "./src/clients/rtkq/correlations/v0alpha1/index.ts" - }, - "./rtkq/dashboard/v0alpha1": { - "import": "./src/clients/rtkq/dashboard/v0alpha1/index.ts", - "require": "./src/clients/rtkq/dashboard/v0alpha1/index.ts" - }, - "./rtkq/folder/v1beta1": { - "import": "./src/clients/rtkq/folder/v1beta1/index.ts", - "require": "./src/clients/rtkq/folder/v1beta1/index.ts" - }, - "./rtkq/iam/v0alpha1": { - "import": "./src/clients/rtkq/iam/v0alpha1/index.ts", - "require": "./src/clients/rtkq/iam/v0alpha1/index.ts" - }, - "./rtkq/legacy": { - "import": "./src/clients/rtkq/legacy/index.ts", - "require": "./src/clients/rtkq/legacy/index.ts" - }, - "./rtkq/legacy/migrate-to-cloud": { - "import": "./src/clients/rtkq/migrate-to-cloud/index.ts", - "require": "./src/clients/rtkq/migrate-to-cloud/index.ts" - }, - "./rtkq/legacy/preferences": { - "import": "./src/clients/rtkq/preferences/user/index.ts", - "require": "./src/clients/rtkq/preferences/user/index.ts" - }, - "./rtkq/legacy/user": { - "import": "./src/clients/rtkq/user/index.ts", - "require": "./src/clients/rtkq/user/index.ts" - }, - "./rtkq/playlist/v0alpha1": { - "import": "./src/clients/rtkq/playlist/v0alpha1/index.ts", - "require": "./src/clients/rtkq/playlist/v0alpha1/index.ts" - }, - "./rtkq/preferences/v1alpha1": { - "import": "./src/clients/rtkq/preferences/v1alpha1/index.ts", - "require": "./src/clients/rtkq/preferences/v1alpha1/index.ts" + "@grafana-app/source": "./src/clients/rtkq/advisor/v0alpha1/index.ts", + "types": "./dist/types/clients/rtkq/advisor/v0alpha1/index.d.ts", + "import": "./dist/esm/clients/rtkq/advisor/v0alpha1/index.mjs", + "require": "./dist/cjs/clients/rtkq/advisor/v0alpha1/index.cjs" }, "./rtkq/collections/v1alpha1": { - "import": "./src/clients/rtkq/collections/v1alpha1/index.ts", - "require": "./src/clients/rtkq/collections/v1alpha1/index.ts" + "@grafana-app/source": "./src/clients/rtkq/collections/v1alpha1/index.ts", + "types": "./dist/types/clients/rtkq/collections/v1alpha1/index.d.ts", + "import": "./dist/esm/clients/rtkq/collections/v1alpha1/index.mjs", + "require": "./dist/cjs/clients/rtkq/collections/v1alpha1/index.cjs" + }, + "./rtkq/correlations/v0alpha1": { + "@grafana-app/source": "./src/clients/rtkq/correlations/v0alpha1/index.ts", + "types": "./dist/types/clients/rtkq/correlations/v0alpha1/index.d.ts", + "import": "./dist/esm/clients/rtkq/correlations/v0alpha1/index.mjs", + "require": "./dist/cjs/clients/rtkq/correlations/v0alpha1/index.cjs" + }, + "./rtkq/dashboard/v0alpha1": { + "@grafana-app/source": "./src/clients/rtkq/dashboard/v0alpha1/index.ts", + "types": "./dist/types/clients/rtkq/dashboard/v0alpha1/index.d.ts", + "import": "./dist/esm/clients/rtkq/dashboard/v0alpha1/index.mjs", + "require": "./dist/cjs/clients/rtkq/dashboard/v0alpha1/index.cjs" + }, + "./rtkq/folder/v1beta1": { + "@grafana-app/source": "./src/clients/rtkq/folder/v1beta1/index.ts", + "types": "./dist/types/clients/rtkq/folder/v1beta1/index.d.ts", + "import": "./dist/esm/clients/rtkq/folder/v1beta1/index.mjs", + "require": "./dist/cjs/clients/rtkq/folder/v1beta1/index.cjs" + }, + "./rtkq/iam/v0alpha1": { + "@grafana-app/source": "./src/clients/rtkq/iam/v0alpha1/index.ts", + "types": "./dist/types/clients/rtkq/iam/v0alpha1/index.d.ts", + "import": "./dist/esm/clients/rtkq/iam/v0alpha1/index.mjs", + "require": "./dist/cjs/clients/rtkq/iam/v0alpha1/index.cjs" + }, + "./rtkq/legacy": { + "@grafana-app/source": "./src/clients/rtkq/legacy/index.ts", + "types": "./dist/types/clients/rtkq/legacy/index.d.ts", + "import": "./dist/esm/clients/rtkq/legacy/index.mjs", + "require": "./dist/cjs/clients/rtkq/legacy/index.cjs" + }, + "./rtkq/legacy/migrate-to-cloud": { + "@grafana-app/source": "./src/clients/rtkq/migrate-to-cloud/index.ts", + "types": "./dist/types/clients/rtkq/migrate-to-cloud/index.d.ts", + "import": "./dist/esm/clients/rtkq/migrate-to-cloud/index.mjs", + "require": "./dist/cjs/clients/rtkq/migrate-to-cloud/index.cjs" + }, + "./rtkq/legacy/preferences": { + "@grafana-app/source": "./src/clients/rtkq/preferences/user/index.ts", + "types": "./dist/types/clients/rtkq/preferences/user/index.d.ts", + "import": "./dist/esm/clients/rtkq/preferences/user/index.mjs", + "require": "./dist/cjs/clients/rtkq/preferences/user/index.cjs" + }, + "./rtkq/legacy/user": { + "@grafana-app/source": "./src/clients/rtkq/user/index.ts", + "types": "./dist/types/clients/rtkq/user/index.d.ts", + "import": "./dist/esm/clients/rtkq/user/index.mjs", + "require": "./dist/cjs/clients/rtkq/user/index.cjs" + }, + "./rtkq/playlist/v0alpha1": { + "@grafana-app/source": "./src/clients/rtkq/playlist/v0alpha1/index.ts", + "types": "./dist/types/clients/rtkq/playlist/v0alpha1/index.d.ts", + "import": "./dist/esm/clients/rtkq/playlist/v0alpha1/index.mjs", + "require": "./dist/cjs/clients/rtkq/playlist/v0alpha1/index.cjs" + }, + "./rtkq/preferences/v1alpha1": { + "@grafana-app/source": "./src/clients/rtkq/preferences/v1alpha1/index.ts", + "types": "./dist/types/clients/rtkq/preferences/v1alpha1/index.d.ts", + "import": "./dist/esm/clients/rtkq/preferences/v1alpha1/index.mjs", + "require": "./dist/cjs/clients/rtkq/preferences/v1alpha1/index.cjs" }, "./rtkq/provisioning/v0alpha1": { - "import": "./src/clients/rtkq/provisioning/v0alpha1/index.ts", - "require": "./src/clients/rtkq/provisioning/v0alpha1/index.ts" + "@grafana-app/source": "./src/clients/rtkq/provisioning/v0alpha1/index.ts", + "types": "./dist/types/clients/rtkq/provisioning/v0alpha1/index.d.ts", + "import": "./dist/esm/clients/rtkq/provisioning/v0alpha1/index.mjs", + "require": "./dist/cjs/clients/rtkq/provisioning/v0alpha1/index.cjs" }, "./rtkq/shorturl/v1beta1": { - "import": "./src/clients/rtkq/shorturl/v1beta1/index.ts", - "require": "./src/clients/rtkq/shorturl/v1beta1/index.ts" + "@grafana-app/source": "./src/clients/rtkq/shorturl/v1beta1/index.ts", + "types": "./dist/types/clients/rtkq/shorturl/v1beta1/index.d.ts", + "import": "./dist/esm/clients/rtkq/shorturl/v1beta1/index.mjs", + "require": "./dist/cjs/clients/rtkq/shorturl/v1beta1/index.cjs" }, "./rtkq/historian.alerting/v0alpha1": { - "import": "./src/clients/rtkq/historian.alerting/v0alpha1/index.ts", - "require": "./src/clients/rtkq/historian.alerting/v0alpha1/index.ts" + "@grafana-app/source": "./src/clients/rtkq/historian.alerting/v0alpha1/index.ts", + "types": "./dist/types/clients/rtkq/historian.alerting/v0alpha1/index.d.ts", + "import": "./dist/esm/clients/rtkq/historian.alerting/v0alpha1/index.mjs", + "require": "./dist/cjs/clients/rtkq/historian.alerting/v0alpha1/index.cjs" }, "./rtkq/logsdrilldown/v1alpha1": { - "import": "./src/clients/rtkq/logsdrilldown/v1alpha1/index.ts", - "require": "./src/clients/rtkq/logsdrilldown/v1alpha1/index.ts" + "@grafana-app/source": "./src/clients/rtkq/logsdrilldown/v1alpha1/index.ts", + "types": "./dist/types/clients/rtkq/logsdrilldown/v1alpha1/index.d.ts", + "import": "./dist/esm/clients/rtkq/logsdrilldown/v1alpha1/index.mjs", + "require": "./dist/cjs/clients/rtkq/logsdrilldown/v1alpha1/index.cjs" } }, "publishConfig": { - "main": "./dist/cjs/index.cjs", - "module": "./dist/esm/index.mjs", - "types": "./dist/types/index.d.ts", "access": "public" }, "files": [ diff --git a/packages/grafana-api-clients/rollup.config.ts b/packages/grafana-api-clients/rollup.config.ts index b36d59278fc..976a0c000fa 100644 --- a/packages/grafana-api-clients/rollup.config.ts +++ b/packages/grafana-api-clients/rollup.config.ts @@ -5,35 +5,17 @@ import { cjsOutput, entryPoint, esmOutput, plugins } from '../rollup.config.part const rq = createRequire(import.meta.url); const pkg = rq('./package.json'); -const apiClients = Object.entries<{ import: string; require: string }>(pkg.exports).filter(([key]) => - key.startsWith('./rtkq/') -); - -const apiClientConfigs = apiClients.map(([name, { import: importPath }]) => { - const baseCjsOutput = cjsOutput(pkg); - const entryFileNames = name.replace('./', '') + '.cjs'; - const cjsOutputConfig = { ...baseCjsOutput, entryFileNames }; - return { - input: importPath.replace('./', ''), - - plugins, - output: [cjsOutputConfig, esmOutput(pkg, 'grafana-api-clients')], - treeshake: false, - }; -}); - export default [ { input: entryPoint, plugins, - output: [cjsOutput(pkg), esmOutput(pkg, 'grafana-api-clients')], + output: [cjsOutput(pkg, 'grafana-api-clients'), esmOutput(pkg, 'grafana-api-clients')], treeshake: false, }, { input: 'src/clients/rtkq/index.ts', plugins, - output: [cjsOutput(pkg), esmOutput(pkg, 'grafana-api-clients')], + output: [cjsOutput(pkg, 'grafana-api-clients'), esmOutput(pkg, 'grafana-api-clients')], treeshake: false, }, - ...apiClientConfigs, ]; diff --git a/packages/grafana-api-clients/src/generator/helpers.ts b/packages/grafana-api-clients/src/generator/helpers.ts index 2ac2ce01389..daba285787c 100644 --- a/packages/grafana-api-clients/src/generator/helpers.ts +++ b/packages/grafana-api-clients/src/generator/helpers.ts @@ -143,8 +143,10 @@ export const updatePackageJsonExports = // Create the new export entry const newExportKey = `./rtkq/${groupName}/${version}`; const newExportValue = { - import: `./src/clients/rtkq/${groupName}/${version}/index.ts`, - require: `./src/clients/rtkq/${groupName}/${version}/index.ts`, + '@grafana-app/source': `./src/clients/rtkq/${groupName}/${version}/index.ts`, + types: `./dist/types/clients/rtkq/${groupName}/${version}/index.d.ts`, + import: `./dist/esm/clients/rtkq/${groupName}/${version}/index.mjs`, + require: `./dist/cjs/clients/rtkq/${groupName}/${version}/index.cjs`, }; // Check if export already exists diff --git a/packages/grafana-api-clients/tsconfig.json b/packages/grafana-api-clients/tsconfig.json index ed1c294703e..e75b58104b0 100644 --- a/packages/grafana-api-clients/tsconfig.json +++ b/packages/grafana-api-clients/tsconfig.json @@ -8,7 +8,8 @@ "emitDeclarationOnly": true, "isolatedModules": true, "rootDirs": ["."], - "allowImportingTsExtensions": true + "allowImportingTsExtensions": true, + "moduleResolution": "bundler" }, "exclude": ["dist/**/*"], "include": [ @@ -17,5 +18,12 @@ "../grafana-ui/src/types/*.d.ts", "../grafana-i18n/src/types/*.d.ts", "src/**/*.ts*" - ] + ], + "ts-node": { + "swc": true, + "compilerOptions": { + "module": "es2020", + "moduleResolution": "Bundler" + } + } } diff --git a/packages/grafana-data/package.json b/packages/grafana-data/package.json index ff90b2c295d..df595973cca 100644 --- a/packages/grafana-data/package.json +++ b/packages/grafana-data/package.json @@ -13,32 +13,31 @@ "url": "http://github.com/grafana/grafana.git", "directory": "packages/grafana-data" }, - "main": "src/index.ts", - "types": "src/index.ts", - "module": "src/index.ts", + "main": "./dist/cjs/index.cjs", + "module": "./dist/esm/index.mjs", + "types": "./dist/types/index.d.ts", "exports": { "./package.json": "./package.json", ".": { - "import": "./src/index.ts", - "require": "./src/index.ts" - }, - "./internal": { - "import": "./src/internal/index.ts", - "require": "./src/internal/index.ts" + "@grafana-app/source": "./src/index.ts", + "types": "./dist/types/index.d.ts", + "import": "./dist/esm/index.mjs", + "require": "./dist/cjs/index.cjs" }, "./unstable": { - "import": "./src/unstable.ts", - "require": "./src/unstable.ts" + "@grafana-app/source": "./src/unstable.ts", + "types": "./dist/types/unstable.d.ts", + "import": "./dist/esm/unstable.mjs", + "require": "./dist/cjs/unstable.cjs" + }, + "./internal": { + "@grafana-app/source": "./src/internal/index.ts" }, "./test": { - "import": "./test/index.ts", - "require": "./test/index.ts" + "@grafana-app/source": "./test/index.ts" } }, "publishConfig": { - "main": "./dist/cjs/index.cjs", - "module": "./dist/esm/index.mjs", - "types": "./dist/types/index.d.ts", "access": "public" }, "files": [ @@ -51,8 +50,8 @@ "build": "tsc -p ./tsconfig.build.json && rollup -c rollup.config.ts --configPlugin esbuild", "clean": "rimraf ./dist ./compiled ./unstable ./package.tgz", "typecheck": "tsc --emitDeclarationOnly false --noEmit", - "prepack": "cp package.json package.json.bak && ALIAS_PACKAGE_NAME=unstable node ../../scripts/prepare-npm-package.js", - "postpack": "mv package.json.bak package.json && rimraf ./unstable" + "prepack": "cp package.json package.json.bak && node ../../scripts/prepare-npm-package.js", + "postpack": "mv package.json.bak package.json" }, "dependencies": { "@braintree/sanitize-url": "7.0.1", diff --git a/packages/grafana-data/rollup.config.ts b/packages/grafana-data/rollup.config.ts index 378ab225769..87008ddc45f 100644 --- a/packages/grafana-data/rollup.config.ts +++ b/packages/grafana-data/rollup.config.ts @@ -9,13 +9,13 @@ export default [ { input: entryPoint, plugins, - output: [cjsOutput(pkg), esmOutput(pkg, 'grafana-data')], + output: [cjsOutput(pkg, 'grafana-data'), esmOutput(pkg, 'grafana-data')], treeshake: false, }, { input: 'src/unstable.ts', plugins, - output: [cjsOutput(pkg), esmOutput(pkg, 'grafana-data')], + output: [cjsOutput(pkg, 'grafana-data'), esmOutput(pkg, 'grafana-data')], treeshake: false, }, ]; diff --git a/packages/grafana-data/tsconfig.json b/packages/grafana-data/tsconfig.json index d13025d3115..8e6013e32d9 100644 --- a/packages/grafana-data/tsconfig.json +++ b/packages/grafana-data/tsconfig.json @@ -3,10 +3,12 @@ "compilerOptions": { "declaration": true, "jsx": "react-jsx", + "baseUrl": "./", "declarationDir": "./dist/types", "emitDeclarationOnly": true, "isolatedModules": true, - "rootDirs": ["."] + "rootDirs": ["."], + "moduleResolution": "bundler" }, "exclude": ["dist/**/*"], "include": [ diff --git a/packages/grafana-e2e-selectors/package.json b/packages/grafana-e2e-selectors/package.json index 0dd5c5a9925..5527f4c0ca6 100644 --- a/packages/grafana-e2e-selectors/package.json +++ b/packages/grafana-e2e-selectors/package.json @@ -16,12 +16,19 @@ "url": "http://github.com/grafana/grafana.git", "directory": "packages/grafana-e2e-selectors" }, - "main": "src/index.ts", - "types": "src/index.ts", + "main": "./dist/cjs/index.cjs", + "module": "./dist/esm/index.mjs", + "types": "./dist/types/index.d.ts", + "exports": { + "./package.json": "./package.json", + ".": { + "@grafana-app/source": "./src/index.ts", + "types": "./dist/types/index.d.ts", + "import": "./dist/esm/index.mjs", + "require": "./dist/cjs/index.cjs" + } + }, "publishConfig": { - "main": "./dist/cjs/index.cjs", - "module": "./dist/esm/index.mjs", - "types": "./dist/types/index.d.ts", "access": "public" }, "files": [ diff --git a/packages/grafana-e2e-selectors/rollup.config.ts b/packages/grafana-e2e-selectors/rollup.config.ts index 1d47540a42a..a17799cc9a7 100644 --- a/packages/grafana-e2e-selectors/rollup.config.ts +++ b/packages/grafana-e2e-selectors/rollup.config.ts @@ -9,7 +9,7 @@ export default [ { input: entryPoint, plugins, - output: [cjsOutput(pkg), esmOutput(pkg, 'grafana-e2e-selectors')], + output: [cjsOutput(pkg, 'grafana-e2e-selectors'), esmOutput(pkg, 'grafana-e2e-selectors')], treeshake: false, }, ]; diff --git a/packages/grafana-e2e-selectors/src/selectors/components.ts b/packages/grafana-e2e-selectors/src/selectors/components.ts index 0755477f93b..de04398a3e1 100644 --- a/packages/grafana-e2e-selectors/src/selectors/components.ts +++ b/packages/grafana-e2e-selectors/src/selectors/components.ts @@ -1332,6 +1332,7 @@ export const versionedComponents = { }, DebugOverlay: { wrapper: { + '12.3.0': 'data-testid debug-overlay-wrapper', '9.2.0': 'debug-overlay', }, }, diff --git a/packages/grafana-e2e-selectors/tsconfig.json b/packages/grafana-e2e-selectors/tsconfig.json index 41f1fb8efb0..b3fd8756461 100644 --- a/packages/grafana-e2e-selectors/tsconfig.json +++ b/packages/grafana-e2e-selectors/tsconfig.json @@ -5,7 +5,8 @@ "declarationDir": "./dist/types", "emitDeclarationOnly": true, "isolatedModules": true, - "rootDirs": ["."] + "rootDirs": ["."], + "moduleResolution": "bundler" }, "exclude": ["dist/**/*"], "include": ["src/**/*.ts"] diff --git a/packages/grafana-flamegraph/package.json b/packages/grafana-flamegraph/package.json index f4ec11c7cfa..b1c16ed68d1 100644 --- a/packages/grafana-flamegraph/package.json +++ b/packages/grafana-flamegraph/package.json @@ -16,12 +16,19 @@ "url": "http://github.com/grafana/grafana.git", "directory": "packages/grafana-flamegraph" }, - "main": "src/index.ts", - "types": "src/index.ts", + "main": "./dist/cjs/index.cjs", + "module": "./dist/esm/index.mjs", + "types": "./dist/types/index.d.ts", + "exports": { + "./package.json": "./package.json", + ".": { + "@grafana-app/source": "./src/index.ts", + "types": "./dist/types/index.d.ts", + "import": "./dist/esm/index.mjs", + "require": "./dist/cjs/index.cjs" + } + }, "publishConfig": { - "main": "./dist/cjs/index.cjs", - "module": "./dist/esm/index.mjs", - "types": "./dist/types/index.d.ts", "access": "public" }, "files": [ diff --git a/packages/grafana-flamegraph/rollup.config.ts b/packages/grafana-flamegraph/rollup.config.ts index 967916710d6..190cd7a6922 100644 --- a/packages/grafana-flamegraph/rollup.config.ts +++ b/packages/grafana-flamegraph/rollup.config.ts @@ -9,7 +9,7 @@ export default [ { input: entryPoint, plugins, - output: [cjsOutput(pkg), esmOutput(pkg, 'grafana-flamegraph')], + output: [cjsOutput(pkg, 'grafana-flamegraph'), esmOutput(pkg, 'grafana-flamegraph')], treeshake: false, }, ]; diff --git a/packages/grafana-flamegraph/tsconfig.json b/packages/grafana-flamegraph/tsconfig.json index 0a1330e2114..7d4e07d6534 100644 --- a/packages/grafana-flamegraph/tsconfig.json +++ b/packages/grafana-flamegraph/tsconfig.json @@ -7,7 +7,8 @@ "declarationDir": "./dist/types", "emitDeclarationOnly": true, "isolatedModules": true, - "rootDirs": ["."] + "rootDirs": ["."], + "moduleResolution": "bundler" }, "exclude": ["dist/**/*"], "include": ["src/**/*.ts*", "../../public/app/types/*.d.ts", "../grafana-ui/src/types/*.d.ts"] diff --git a/packages/grafana-i18n/package.json b/packages/grafana-i18n/package.json index 824f594f8e6..36a9faa541b 100644 --- a/packages/grafana-i18n/package.json +++ b/packages/grafana-i18n/package.json @@ -14,33 +14,32 @@ "url": "http://github.com/grafana/grafana.git", "directory": "packages/grafana-i18n" }, - "main": "src/index.ts", - "types": "src/index.ts", - "module": "src/index.ts", + "main": "./dist/cjs/index.cjs", + "module": "./dist/esm/index.mjs", + "types": "./dist/types/index.d.ts", "exports": { "./package.json": "./package.json", ".": { - "import": "./src/index.ts", - "require": "./src/index.ts" + "@grafana-app/source": "./src/index.ts", + "types": "./dist/types/index.d.ts", + "import": "./dist/esm/index.mjs", + "require": "./dist/cjs/index.cjs" }, "./internal": { - "import": "./src/internal/index.ts", - "require": "./src/internal/index.ts" + "@grafana-app/source": "./src/internal/index.ts" }, "./eslint-plugin": { + "@grafana-app/source": "./src/eslint/index.cjs", "types": "./src/eslint/index.d.ts", - "import": "./src/eslint/index.cjs", - "require": "./src/eslint/index.cjs" + "default": "./src/eslint/index.cjs" } }, "publishConfig": { - "main": "./dist/cjs/index.cjs", - "module": "./dist/esm/index.mjs", - "types": "./dist/types/index.d.ts", "access": "public" }, "files": [ "dist", + "src/eslint/**/*", "./README.md", "./CHANGELOG.md", "LICENSE_APACHE2" diff --git a/packages/grafana-i18n/rollup.config.ts b/packages/grafana-i18n/rollup.config.ts index 1b1115f65c9..913791e7d69 100644 --- a/packages/grafana-i18n/rollup.config.ts +++ b/packages/grafana-i18n/rollup.config.ts @@ -1,5 +1,4 @@ import { createRequire } from 'node:module'; -import copy from 'rollup-plugin-copy'; import { entryPoint, plugins, esmOutput, cjsOutput } from '../rollup.config.parts'; @@ -9,13 +8,8 @@ const pkg = rq('./package.json'); export default [ { input: entryPoint, - plugins: [ - ...plugins, - copy({ - targets: [{ src: 'src/eslint', dest: 'dist' }], - }), - ], - output: [cjsOutput(pkg), esmOutput(pkg, 'grafana-i18n')], + plugins, + output: [cjsOutput(pkg, 'grafana-i18n'), esmOutput(pkg, 'grafana-i18n')], treeshake: false, }, ]; diff --git a/packages/grafana-i18n/tsconfig.json b/packages/grafana-i18n/tsconfig.json index 2c83128f0f1..b1340cd69d0 100644 --- a/packages/grafana-i18n/tsconfig.json +++ b/packages/grafana-i18n/tsconfig.json @@ -6,7 +6,8 @@ "declarationDir": "./dist/types", "emitDeclarationOnly": true, "isolatedModules": true, - "rootDirs": ["."] + "rootDirs": ["."], + "moduleResolution": "bundler" }, "exclude": ["dist/**/*"], "include": ["src/**/*.ts*"] diff --git a/packages/grafana-o11y-ds-frontend/tsconfig.json b/packages/grafana-o11y-ds-frontend/tsconfig.json index c77d4f035c9..1817cc219ce 100644 --- a/packages/grafana-o11y-ds-frontend/tsconfig.json +++ b/packages/grafana-o11y-ds-frontend/tsconfig.json @@ -8,7 +8,8 @@ "emitDeclarationOnly": true, "isolatedModules": true, "allowJs": true, - "rootDirs": ["."] + "rootDirs": ["."], + "moduleResolution": "bundler" }, "exclude": ["dist/**/*"], "include": [ diff --git a/packages/grafana-plugin-configs/jest/jest.config.js b/packages/grafana-plugin-configs/jest/jest.config.js index 1f87fd10556..05e5a701226 100644 --- a/packages/grafana-plugin-configs/jest/jest.config.js +++ b/packages/grafana-plugin-configs/jest/jest.config.js @@ -17,6 +17,9 @@ export default { setupFiles: ['jest-canvas-mock'], setupFilesAfterEnv: ['/jest-setup.js'], testEnvironment: 'jsdom', + testEnvironmentOptions: { + customExportConditions: ['@grafana-app/source', 'browser'], + }, testMatch: ['/**/__tests__/**/*.{js,jsx,ts,tsx}', '/**/*.{spec,test,jest}.{js,jsx,ts,tsx}'], transform: { '^.+\\.(t|j)sx?$': [ diff --git a/packages/grafana-plugin-configs/tsconfig.json b/packages/grafana-plugin-configs/tsconfig.json index b7a0dd239e8..db619d2b1c2 100644 --- a/packages/grafana-plugin-configs/tsconfig.json +++ b/packages/grafana-plugin-configs/tsconfig.json @@ -1,12 +1,13 @@ { "compilerOptions": { - "jsx": "react-jsx", + "allowImportingTsExtensions": true, "alwaysStrict": true, + "customConditions": ["@grafana-app/source"], "declaration": false, - "resolveJsonModule": true, + "jsx": "react-jsx", "moduleResolution": "bundler", "noEmit": true, - "allowImportingTsExtensions": true + "resolveJsonModule": true }, "extends": "@grafana/tsconfig", "exclude": ["**/*.test.ts", "**/*.test.tsx", "**/*.spec.ts", "**/*.spec.tsx"], diff --git a/packages/grafana-plugin-configs/webpack.config.ts b/packages/grafana-plugin-configs/webpack.config.ts index 6f2846b5a97..8bfcdcec69a 100644 --- a/packages/grafana-plugin-configs/webpack.config.ts +++ b/packages/grafana-plugin-configs/webpack.config.ts @@ -312,6 +312,7 @@ const config = async (env: Env): Promise => { resolve: { extensions: ['.ts', '.tsx', '.js', '.jsx'], + conditionNames: ['@grafana-app/source', '...'], unsafeCache: true, }, diff --git a/packages/grafana-prometheus/package.json b/packages/grafana-prometheus/package.json index 8ed4bd95d5a..6d6fa062635 100644 --- a/packages/grafana-prometheus/package.json +++ b/packages/grafana-prometheus/package.json @@ -15,8 +15,18 @@ "url": "http://github.com/grafana/grafana.git", "directory": "packages/grafana-prometheus" }, - "main": "src/index.ts", - "types": "src/index.ts", + "main": "./dist/cjs/index.cjs", + "module": "./dist/esm/index.mjs", + "types": "./dist/types/index.d.ts", + "exports": { + "./package.json": "./package.json", + ".": { + "@grafana-app/source": "./src/index.ts", + "types": "./dist/types/index.d.ts", + "import": "./dist/esm/index.mjs", + "require": "./dist/cjs/index.cjs" + } + }, "files": [ "./dist", "./README.md", @@ -24,9 +34,6 @@ "./LICENSE_AGPL" ], "publishConfig": { - "main": "./dist/cjs/index.cjs", - "module": "./dist/esm/index.mjs", - "types": "./dist/types/index.d.ts", "access": "public" }, "scripts": { diff --git a/packages/grafana-prometheus/rollup.config.ts b/packages/grafana-prometheus/rollup.config.ts index 8dbc60c8935..28083b2d26f 100644 --- a/packages/grafana-prometheus/rollup.config.ts +++ b/packages/grafana-prometheus/rollup.config.ts @@ -12,7 +12,7 @@ export default [ { input: entryPoint, plugins: [...plugins, image(), json(), dynamicImportVars()], - output: [cjsOutput(pkg), esmOutput(pkg, 'grafana-prometheus')], + output: [cjsOutput(pkg, 'grafana-prometheus'), esmOutput(pkg, 'grafana-prometheus')], treeshake: false, }, ]; diff --git a/packages/grafana-runtime/package.json b/packages/grafana-runtime/package.json index a46d08d4623..acf3f60bbc3 100644 --- a/packages/grafana-runtime/package.json +++ b/packages/grafana-runtime/package.json @@ -14,28 +14,28 @@ "url": "http://github.com/grafana/grafana.git", "directory": "packages/grafana-runtime" }, - "main": "src/index.ts", - "types": "src/index.ts", - "module": "src/index.ts", + "main": "./dist/cjs/index.cjs", + "module": "./dist/esm/index.mjs", + "types": "./dist/types/index.d.ts", "exports": { "./package.json": "./package.json", ".": { - "import": "./src/index.ts", - "require": "./src/index.ts" - }, - "./internal": { - "import": "./src/internal/index.ts", - "require": "./src/internal/index.ts" + "@grafana-app/source": "./src/index.ts", + "types": "./dist/types/index.d.ts", + "import": "./dist/esm/index.mjs", + "require": "./dist/cjs/index.cjs" }, "./unstable": { - "import": "./src/unstable.ts", - "require": "./src/unstable.ts" + "@grafana-app/source": "./src/unstable.ts", + "types": "./dist/types/unstable.d.ts", + "import": "./dist/esm/unstable.mjs", + "require": "./dist/cjs/unstable.cjs" + }, + "./internal": { + "@grafana-app/source": "./src/internal/index.ts" } }, "publishConfig": { - "main": "./dist/cjs/index.cjs", - "module": "./dist/esm/index.mjs", - "types": "./dist/types/index.d.ts", "access": "public" }, "files": [ @@ -49,8 +49,8 @@ "bundle": "rollup -c rollup.config.ts --configPlugin esbuild", "clean": "rimraf ./dist ./compiled ./unstable ./package.tgz", "typecheck": "tsc --emitDeclarationOnly false --noEmit", - "prepack": "cp package.json package.json.bak && ALIAS_PACKAGE_NAME=unstable node ../../scripts/prepare-npm-package.js", - "postpack": "mv package.json.bak package.json && rimraf ./unstable" + "prepack": "cp package.json package.json.bak && node ../../scripts/prepare-npm-package.js", + "postpack": "mv package.json.bak package.json" }, "dependencies": { "@grafana/data": "12.4.0-pre", diff --git a/packages/grafana-runtime/rollup.config.ts b/packages/grafana-runtime/rollup.config.ts index 5807c919136..120ad98645d 100644 --- a/packages/grafana-runtime/rollup.config.ts +++ b/packages/grafana-runtime/rollup.config.ts @@ -9,13 +9,13 @@ export default [ { input: entryPoint, plugins, - output: [cjsOutput(pkg), esmOutput(pkg, 'grafana-runtime')], + output: [cjsOutput(pkg, 'grafana-runtime'), esmOutput(pkg, 'grafana-runtime')], treeshake: false, }, { input: 'src/unstable.ts', plugins, - output: [cjsOutput(pkg), esmOutput(pkg, 'grafana-runtime')], + output: [cjsOutput(pkg, 'grafana-runtime'), esmOutput(pkg, 'grafana-runtime')], treeshake: false, }, ]; diff --git a/packages/grafana-runtime/src/index.ts b/packages/grafana-runtime/src/index.ts index 489f9157700..58b30be8542 100644 --- a/packages/grafana-runtime/src/index.ts +++ b/packages/grafana-runtime/src/index.ts @@ -9,7 +9,15 @@ export * from './analytics/types'; export { loadPluginCss, type PluginCssOptions, setPluginImportUtils, getPluginImportUtils } from './utils/plugin'; export { reportMetaAnalytics, reportInteraction, reportPageview, reportExperimentView } from './analytics/utils'; export { featureEnabled } from './utils/licensing'; -export { logInfo, logDebug, logWarning, logError, createMonitoringLogger, logMeasurement } from './utils/logging'; +export { + logInfo, + logDebug, + logWarning, + logError, + createMonitoringLogger, + logMeasurement, + type MonitoringLogger, +} from './utils/logging'; export { DataSourceWithBackend, HealthCheckError, diff --git a/packages/grafana-runtime/tsconfig.json b/packages/grafana-runtime/tsconfig.json index 9dec8dd53ed..bbfc33b77ed 100644 --- a/packages/grafana-runtime/tsconfig.json +++ b/packages/grafana-runtime/tsconfig.json @@ -8,7 +8,8 @@ "emitDeclarationOnly": true, "isolatedModules": true, "allowJs": true, - "rootDirs": ["."] + "rootDirs": ["."], + "moduleResolution": "bundler" }, "exclude": ["dist/**/*"], "include": [ diff --git a/packages/grafana-schema/package.json b/packages/grafana-schema/package.json index 067475cd209..4f48e1511b2 100644 --- a/packages/grafana-schema/package.json +++ b/packages/grafana-schema/package.json @@ -13,13 +13,14 @@ "url": "http://github.com/grafana/grafana.git", "directory": "packages/grafana-schema" }, - "main": "src/index.ts", - "types": "src/index.ts", + "main": "./src/index.ts", + "module": "./src/index.ts", + "types": "./src/index.ts", "publishConfig": { + "access": "public", "main": "./dist/cjs/index.cjs", "module": "./dist/esm/index.mjs", - "types": "./dist/types/index.d.ts", - "access": "public" + "types": "./dist/types/index.d.ts" }, "files": [ "dist", diff --git a/packages/grafana-schema/rollup.config.ts b/packages/grafana-schema/rollup.config.ts index 04928f75c47..4e5f77b3deb 100644 --- a/packages/grafana-schema/rollup.config.ts +++ b/packages/grafana-schema/rollup.config.ts @@ -15,7 +15,12 @@ export default [ { input: entryPoint, plugins, - output: [cjsOutput(pkg), esmOutput(pkg, 'grafana-schema')], + output: [ + // Schema still uses publishConfig to define output directory. + // TODO: Migrate this package to use exports. + cjsOutput(pkg, 'grafana-schema', { dir: path.dirname(pkg.publishConfig.main) }), + esmOutput(pkg, 'grafana-schema', { dir: path.dirname(pkg.publishConfig.module) }), + ], treeshake: false, }, { diff --git a/packages/grafana-schema/tsconfig.json b/packages/grafana-schema/tsconfig.json index 2c83128f0f1..8012f3267e6 100644 --- a/packages/grafana-schema/tsconfig.json +++ b/packages/grafana-schema/tsconfig.json @@ -3,10 +3,12 @@ "compilerOptions": { "declaration": true, "jsx": "react-jsx", + "baseUrl": "./", "declarationDir": "./dist/types", "emitDeclarationOnly": true, "isolatedModules": true, - "rootDirs": ["."] + "rootDirs": ["."], + "moduleResolution": "bundler" }, "exclude": ["dist/**/*"], "include": ["src/**/*.ts*"] diff --git a/packages/grafana-sql/src/utils/logging.ts b/packages/grafana-sql/src/utils/logging.ts index 95900184359..2d481dc4b8a 100644 --- a/packages/grafana-sql/src/utils/logging.ts +++ b/packages/grafana-sql/src/utils/logging.ts @@ -1,3 +1,3 @@ -import { createMonitoringLogger } from '@grafana/runtime'; +import { createMonitoringLogger, MonitoringLogger } from '@grafana/runtime'; -export const sqlPluginLogger = createMonitoringLogger('features.plugins.sql'); +export const sqlPluginLogger: MonitoringLogger = createMonitoringLogger('features.plugins.sql'); diff --git a/packages/grafana-sql/tsconfig.json b/packages/grafana-sql/tsconfig.json index 4bd2161f0e0..87f6db544ad 100644 --- a/packages/grafana-sql/tsconfig.json +++ b/packages/grafana-sql/tsconfig.json @@ -8,7 +8,8 @@ "emitDeclarationOnly": true, "isolatedModules": true, "strict": true, - "rootDirs": ["."] + "rootDirs": ["."], + "moduleResolution": "bundler" }, "exclude": ["dist/**/*"], "include": ["src/**/*.ts*", "../../public/app/types/*.d.ts", "../grafana-ui/src/types/*.d.ts"] diff --git a/packages/grafana-ui/.storybook/main.ts b/packages/grafana-ui/.storybook/main.ts index 29258c4a228..9bbf711b5c2 100644 --- a/packages/grafana-ui/.storybook/main.ts +++ b/packages/grafana-ui/.storybook/main.ts @@ -95,6 +95,16 @@ const mainConfig: StorybookConfig = { }, }); + // Tell storybook to resolve imports with the @grafana-app/source condition for + // the packages in this repo. + if (config && config.resolve) { + if (Array.isArray(config.resolve.conditionNames)) { + config.resolve.conditionNames.unshift('@grafana-app/source'); + } else { + config.resolve.conditionNames = ['@grafana-app/source', '...']; + } + } + return config; }, }; diff --git a/packages/grafana-ui/.storybook/tsconfig.json b/packages/grafana-ui/.storybook/tsconfig.json index cbcee5966e1..9d5c974f558 100644 --- a/packages/grafana-ui/.storybook/tsconfig.json +++ b/packages/grafana-ui/.storybook/tsconfig.json @@ -1,8 +1,7 @@ { "compilerOptions": { "declarationDir": "dist", - "noUnusedLocals": false, - "outDir": "compiled" + "noUnusedLocals": false }, "extends": "../tsconfig.json", "include": ["../src/**/*.ts*", "../../../public/app/types/svg.d.ts"] diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json index f4a358d1654..478c44704ed 100644 --- a/packages/grafana-ui/package.json +++ b/packages/grafana-ui/package.json @@ -16,28 +16,28 @@ "url": "http://github.com/grafana/grafana.git", "directory": "packages/grafana-ui" }, - "main": "src/index.ts", - "types": "src/index.ts", - "module": "src/index.ts", + "main": "./dist/cjs/index.cjs", + "module": "./dist/esm/index.mjs", + "types": "./dist/types/index.d.ts", "exports": { "./package.json": "./package.json", ".": { - "import": "./src/index.ts", - "require": "./src/index.ts" - }, - "./internal": { - "import": "./src/internal/index.ts", - "require": "./src/internal/index.ts" + "@grafana-app/source": "./src/index.ts", + "types": "./dist/types/index.d.ts", + "import": "./dist/esm/index.mjs", + "require": "./dist/cjs/index.cjs" }, "./unstable": { - "import": "./src/unstable.ts", - "require": "./src/unstable.ts" + "@grafana-app/source": "./src/unstable.ts", + "types": "./dist/types/unstable.d.ts", + "import": "./dist/esm/unstable.mjs", + "require": "./dist/cjs/unstable.cjs" + }, + "./internal": { + "@grafana-app/source": "./src/internal/index.ts" } }, "publishConfig": { - "main": "./dist/cjs/index.cjs", - "module": "./dist/esm/index.mjs", - "types": "./dist/types/index.d.ts", "access": "public" }, "files": [ @@ -55,8 +55,8 @@ "storybook:build": "storybook build -o ./dist/storybook -c .storybook", "storybook:test": "test-storybook --url http://localhost:9001", "typecheck": "tsc --emitDeclarationOnly false --noEmit", - "prepack": "cp package.json package.json.bak && ALIAS_PACKAGE_NAME=unstable node ../../scripts/prepare-npm-package.js", - "postpack": "mv package.json.bak package.json && rimraf ./unstable" + "prepack": "cp package.json package.json.bak && node ../../scripts/prepare-npm-package.js", + "postpack": "mv package.json.bak package.json" }, "browserslist": [ "defaults", diff --git a/packages/grafana-ui/rollup.config.ts b/packages/grafana-ui/rollup.config.ts index 16c81ca4af8..14ff11d279b 100644 --- a/packages/grafana-ui/rollup.config.ts +++ b/packages/grafana-ui/rollup.config.ts @@ -24,7 +24,7 @@ export default [ flatten: false, }), ], - output: [cjsOutput(pkg), esmOutput(pkg, 'grafana-ui')], + output: [cjsOutput(pkg, 'grafana-ui'), esmOutput(pkg, 'grafana-ui')], treeshake: false, }, { @@ -37,7 +37,7 @@ export default [ flatten: false, }), ], - output: [cjsOutput(pkg), esmOutput(pkg, 'grafana-ui')], + output: [cjsOutput(pkg, 'grafana-ui'), esmOutput(pkg, 'grafana-ui')], treeshake: false, }, ]; diff --git a/packages/grafana-ui/src/components/Forms/Legacy/Select/SelectOptionGroup.tsx b/packages/grafana-ui/src/components/Forms/Legacy/Select/SelectOptionGroup.tsx index aecc7bf4514..a249db3eb2e 100644 --- a/packages/grafana-ui/src/components/Forms/Legacy/Select/SelectOptionGroup.tsx +++ b/packages/grafana-ui/src/components/Forms/Legacy/Select/SelectOptionGroup.tsx @@ -94,4 +94,5 @@ class UnthemedSelectOptionGroup extends PureComponent } } -export const SelectOptionGroup = withTheme2(UnthemedSelectOptionGroup); +// TODO: type this properly +export const SelectOptionGroup: React.FC = withTheme2(UnthemedSelectOptionGroup); diff --git a/packages/grafana-ui/src/components/Select/ValueContainer.tsx b/packages/grafana-ui/src/components/Select/ValueContainer.tsx index 6eb8326ae5d..f39666c8c5f 100644 --- a/packages/grafana-ui/src/components/Select/ValueContainer.tsx +++ b/packages/grafana-ui/src/components/Select/ValueContainer.tsx @@ -76,4 +76,5 @@ class UnthemedValueContainer>> = + withTheme2(UnthemedValueContainer); diff --git a/packages/grafana-ui/tsconfig.json b/packages/grafana-ui/tsconfig.json index 502d1958513..95871844f67 100644 --- a/packages/grafana-ui/tsconfig.json +++ b/packages/grafana-ui/tsconfig.json @@ -8,7 +8,8 @@ "emitDeclarationOnly": true, "isolatedModules": true, "allowJs": true, - "rootDirs": ["."] + "rootDirs": ["."], + "moduleResolution": "bundler" }, "exclude": ["dist/**/*"], "include": ["../../public/test/setupTests.ts", "../../public/app/types/*.d.ts", "src/**/*.ts*"], diff --git a/packages/rollup.config.parts.ts b/packages/rollup.config.parts.ts index 31116f56bdb..534b8cd7d25 100644 --- a/packages/rollup.config.parts.ts +++ b/packages/rollup.config.parts.ts @@ -23,25 +23,29 @@ export const plugins = [ ]; // Generates a rollup configuration for commonjs output. -export function cjsOutput(pkg) { +export function cjsOutput(pkg, pkgName, overrides = {}) { return { format: 'cjs', sourcemap: true, - dir: dirname(pkg.publishConfig.main), + dir: dirname(pkg.main), entryFileNames: '[name].cjs', + preserveModules: true, + preserveModulesRoot: resolve(projectCwd, `packages/${pkgName}/src`), esModule: true, interop: 'compat', + ...overrides, }; } // Generate a rollup configuration for es module output. -export function esmOutput(pkg, pkgName) { +export function esmOutput(pkg, pkgName, overrides = {}) { return { format: 'esm', sourcemap: true, - dir: dirname(pkg.publishConfig.module), + dir: dirname(pkg.module), entryFileNames: '[name].mjs', preserveModules: true, preserveModulesRoot: resolve(projectCwd, `packages/${pkgName}/src`), + ...overrides, }; } diff --git a/project.json b/project.json index a4a3fa81c09..702efba58af 100644 --- a/project.json +++ b/project.json @@ -39,7 +39,8 @@ "inputs": [ "{workspaceRoot}/scripts/cli/generateSassVariableFiles.ts", "{workspaceRoot}/packages/grafana-data/src/themes/**", - "{workspaceRoot}/packages/grafana-ui/src/themes/**" + "{workspaceRoot}/packages/grafana-ui/src/themes/**", + "{workspaceRoot}/package.json" ], "outputs": [ "{workspaceRoot}/public/sass/_variables.generated.scss", diff --git a/public/app/features/admin/UserListAdminPage.tsx b/public/app/features/admin/UserListAdminPage.tsx index cd7695b1db8..feebb6b98e1 100644 --- a/public/app/features/admin/UserListAdminPage.tsx +++ b/public/app/features/admin/UserListAdminPage.tsx @@ -3,7 +3,7 @@ import { ComponentType, useEffect } from 'react'; import { connect, ConnectedProps } from 'react-redux'; import { GrafanaTheme2 } from '@grafana/data'; -import { selectors as e2eSelectors } from '@grafana/e2e-selectors/src'; +import { selectors as e2eSelectors } from '@grafana/e2e-selectors'; import { Trans, t } from '@grafana/i18n'; import { LinkButton, RadioButtonGroup, useStyles2, FilterInput, EmptyState } from '@grafana/ui'; import { Page } from 'app/core/components/Page/Page'; diff --git a/public/app/features/admin/UserListAnonymousPage.tsx b/public/app/features/admin/UserListAnonymousPage.tsx index eec26483830..bfb1f6f9125 100644 --- a/public/app/features/admin/UserListAnonymousPage.tsx +++ b/public/app/features/admin/UserListAnonymousPage.tsx @@ -3,7 +3,7 @@ import { useEffect } from 'react'; import { connect, ConnectedProps } from 'react-redux'; import { GrafanaTheme2 } from '@grafana/data'; -import { selectors as e2eSelectors } from '@grafana/e2e-selectors/src'; +import { selectors as e2eSelectors } from '@grafana/e2e-selectors'; import { t } from '@grafana/i18n'; import { RadioButtonGroup, useStyles2, FilterInput } from '@grafana/ui'; import { Page } from 'app/core/components/Page/Page'; diff --git a/public/app/features/admin/UserListPage.test.tsx b/public/app/features/admin/UserListPage.test.tsx index 6601c06827d..4df8161b70c 100644 --- a/public/app/features/admin/UserListPage.test.tsx +++ b/public/app/features/admin/UserListPage.test.tsx @@ -1,7 +1,7 @@ import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { selectors as e2eSelectors } from '@grafana/e2e-selectors/src'; +import { selectors as e2eSelectors } from '@grafana/e2e-selectors'; import { GrafanaBootConfig } from '@grafana/runtime'; import config from 'app/core/config'; diff --git a/public/app/features/admin/UserListPublicDashboardPage/DashboardsListModalButton.tsx b/public/app/features/admin/UserListPublicDashboardPage/DashboardsListModalButton.tsx index af1eda1f2fd..bf384a83bbc 100644 --- a/public/app/features/admin/UserListPublicDashboardPage/DashboardsListModalButton.tsx +++ b/public/app/features/admin/UserListPublicDashboardPage/DashboardsListModalButton.tsx @@ -1,7 +1,7 @@ import { css, cx } from '@emotion/css'; import { GrafanaTheme2 } from '@grafana/data'; -import { selectors as e2eSelectors } from '@grafana/e2e-selectors/src'; +import { selectors as e2eSelectors } from '@grafana/e2e-selectors'; import { Trans, t } from '@grafana/i18n'; import { Button, LoadingPlaceholder, Modal, ModalsController, useStyles2 } from '@grafana/ui'; import { diff --git a/public/app/features/admin/UserListPublicDashboardPage/UserListPublicDashboardPage.tsx b/public/app/features/admin/UserListPublicDashboardPage/UserListPublicDashboardPage.tsx index c3618ba9b67..c18cb9c5c15 100644 --- a/public/app/features/admin/UserListPublicDashboardPage/UserListPublicDashboardPage.tsx +++ b/public/app/features/admin/UserListPublicDashboardPage/UserListPublicDashboardPage.tsx @@ -1,4 +1,4 @@ -import { selectors as e2eSelectors } from '@grafana/e2e-selectors/src'; +import { selectors as e2eSelectors } from '@grafana/e2e-selectors'; import { Trans, t } from '@grafana/i18n'; import { Icon, Stack, Tag, Tooltip } from '@grafana/ui'; import { Page } from 'app/core/components/Page/Page'; diff --git a/public/app/features/dashboard-scene/inspect/InspectJsonTab.tsx b/public/app/features/dashboard-scene/inspect/InspectJsonTab.tsx index 648f6de57ef..4fd9bc8b9a6 100644 --- a/public/app/features/dashboard-scene/inspect/InspectJsonTab.tsx +++ b/public/app/features/dashboard-scene/inspect/InspectJsonTab.tsx @@ -16,7 +16,7 @@ import { sceneUtils, VizPanel, } from '@grafana/scenes'; -import { LibraryPanel } from '@grafana/schema/'; +import { LibraryPanel } from '@grafana/schema'; import { Alert, Button, CodeEditor, Field, Select, useStyles2 } from '@grafana/ui'; import { isDashboardV2Spec } from 'app/features/dashboard/api/utils'; import { getPanelDataFrames } from 'app/features/dashboard/components/HelpWizard/utils'; diff --git a/public/app/features/dashboard-scene/scene/layout-auto-grid/AutoGridItemRenderer.tsx b/public/app/features/dashboard-scene/scene/layout-auto-grid/AutoGridItemRenderer.tsx index ee7fd012add..15b7e82ae36 100644 --- a/public/app/features/dashboard-scene/scene/layout-auto-grid/AutoGridItemRenderer.tsx +++ b/public/app/features/dashboard-scene/scene/layout-auto-grid/AutoGridItemRenderer.tsx @@ -1,7 +1,7 @@ import { css, cx } from '@emotion/css'; import { memo, useMemo } from 'react'; -import { GrafanaTheme2 } from '@grafana/data/'; +import { GrafanaTheme2 } from '@grafana/data'; import { LazyLoader, SceneComponentProps, VizPanel } from '@grafana/scenes'; import { useStyles2 } from '@grafana/ui'; diff --git a/public/app/features/dashboard/components/DashNav/ShareButton.tsx b/public/app/features/dashboard/components/DashNav/ShareButton.tsx index 1908ea1612a..a0e5200f47a 100644 --- a/public/app/features/dashboard/components/DashNav/ShareButton.tsx +++ b/public/app/features/dashboard/components/DashNav/ShareButton.tsx @@ -1,4 +1,4 @@ -import { selectors as e2eSelectors } from '@grafana/e2e-selectors/src'; +import { selectors as e2eSelectors } from '@grafana/e2e-selectors'; import { Trans } from '@grafana/i18n'; import { locationService } from '@grafana/runtime'; import { Button } from '@grafana/ui'; diff --git a/public/app/features/dashboard/components/PublicDashboardNotAvailable/PublicDashboardNotAvailable.tsx b/public/app/features/dashboard/components/PublicDashboardNotAvailable/PublicDashboardNotAvailable.tsx index 1ab0b57a937..ffa019d20d3 100644 --- a/public/app/features/dashboard/components/PublicDashboardNotAvailable/PublicDashboardNotAvailable.tsx +++ b/public/app/features/dashboard/components/PublicDashboardNotAvailable/PublicDashboardNotAvailable.tsx @@ -1,7 +1,7 @@ import { css, cx } from '@emotion/css'; import { GrafanaTheme2 } from '@grafana/data'; -import { selectors as e2eSelectors } from '@grafana/e2e-selectors/src'; +import { selectors as e2eSelectors } from '@grafana/e2e-selectors'; import { Trans, t } from '@grafana/i18n'; import { useStyles2 } from '@grafana/ui'; diff --git a/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/ConfigPublicDashboard/ConfigPublicDashboard.tsx b/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/ConfigPublicDashboard/ConfigPublicDashboard.tsx index be257dc5042..9d3dcb4a78b 100644 --- a/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/ConfigPublicDashboard/ConfigPublicDashboard.tsx +++ b/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/ConfigPublicDashboard/ConfigPublicDashboard.tsx @@ -2,7 +2,7 @@ import { css } from '@emotion/css'; import { useForm } from 'react-hook-form'; import { GrafanaTheme2, TimeRange } from '@grafana/data'; -import { selectors as e2eSelectors } from '@grafana/e2e-selectors/src'; +import { selectors as e2eSelectors } from '@grafana/e2e-selectors'; import { Trans, t } from '@grafana/i18n'; import { Button, ClipboardButton, Field, Input, Stack, Label, ModalsController, Switch, useStyles2 } from '@grafana/ui'; import { diff --git a/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/ConfigPublicDashboard/Configuration.tsx b/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/ConfigPublicDashboard/Configuration.tsx index 6cb97b9ac47..c0018735f4b 100644 --- a/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/ConfigPublicDashboard/Configuration.tsx +++ b/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/ConfigPublicDashboard/Configuration.tsx @@ -1,7 +1,7 @@ import { UseFormRegister } from 'react-hook-form'; import { TimeRange } from '@grafana/data'; -import { selectors as e2eSelectors } from '@grafana/e2e-selectors/src'; +import { selectors as e2eSelectors } from '@grafana/e2e-selectors'; import { Trans, t } from '@grafana/i18n'; import { FieldSet, Label, Switch, TimeRangeInput, Stack } from '@grafana/ui'; import { DashboardInteractions } from 'app/features/dashboard-scene/utils/interactions'; diff --git a/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/CreatePublicDashboard/AcknowledgeCheckboxes.tsx b/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/CreatePublicDashboard/AcknowledgeCheckboxes.tsx index 76bed8c8d94..c875e3a7d90 100644 --- a/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/CreatePublicDashboard/AcknowledgeCheckboxes.tsx +++ b/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/CreatePublicDashboard/AcknowledgeCheckboxes.tsx @@ -2,7 +2,7 @@ import { css } from '@emotion/css'; import { UseFormRegister } from 'react-hook-form'; import { GrafanaTheme2 } from '@grafana/data'; -import { selectors as e2eSelectors } from '@grafana/e2e-selectors/src'; +import { selectors as e2eSelectors } from '@grafana/e2e-selectors'; import { Trans, t } from '@grafana/i18n'; import { Checkbox, FieldSet, LinkButton, useStyles2, Stack } from '@grafana/ui'; diff --git a/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/ModalAlerts/NoUpsertPermissionsAlert.tsx b/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/ModalAlerts/NoUpsertPermissionsAlert.tsx index a480fdd918f..4af3ade3397 100644 --- a/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/ModalAlerts/NoUpsertPermissionsAlert.tsx +++ b/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/ModalAlerts/NoUpsertPermissionsAlert.tsx @@ -1,4 +1,4 @@ -import { selectors as e2eSelectors } from '@grafana/e2e-selectors/src'; +import { selectors as e2eSelectors } from '@grafana/e2e-selectors'; import { Trans, t } from '@grafana/i18n'; import { Alert } from '@grafana/ui'; diff --git a/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/ModalAlerts/UnsupportedDataSourcesAlert.tsx b/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/ModalAlerts/UnsupportedDataSourcesAlert.tsx index cc000e869ab..d0ac5015bfe 100644 --- a/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/ModalAlerts/UnsupportedDataSourcesAlert.tsx +++ b/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/ModalAlerts/UnsupportedDataSourcesAlert.tsx @@ -2,7 +2,7 @@ import { css } from '@emotion/css'; import cx from 'classnames'; import { GrafanaTheme2 } from '@grafana/data'; -import { selectors as e2eSelectors } from '@grafana/e2e-selectors/src'; +import { selectors as e2eSelectors } from '@grafana/e2e-selectors'; import { Trans, t } from '@grafana/i18n'; import { Alert, useStyles2 } from '@grafana/ui'; diff --git a/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/ModalAlerts/UnsupportedTemplateVariablesAlert.tsx b/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/ModalAlerts/UnsupportedTemplateVariablesAlert.tsx index 3eb2c3d0904..6ce2607fb13 100644 --- a/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/ModalAlerts/UnsupportedTemplateVariablesAlert.tsx +++ b/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/ModalAlerts/UnsupportedTemplateVariablesAlert.tsx @@ -1,4 +1,4 @@ -import { selectors as e2eSelectors } from '@grafana/e2e-selectors/src'; +import { selectors as e2eSelectors } from '@grafana/e2e-selectors'; import { Trans, t } from '@grafana/i18n'; import { Alert } from '@grafana/ui'; diff --git a/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/SharePublicDashboard.test.tsx b/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/SharePublicDashboard.test.tsx index 9a0357f0626..99da08f24f8 100644 --- a/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/SharePublicDashboard.test.tsx +++ b/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/SharePublicDashboard.test.tsx @@ -4,7 +4,7 @@ import { http, HttpResponse } from 'msw'; import { setupServer } from 'msw/node'; import { BootData, DataQuery } from '@grafana/data'; -import { selectors as e2eSelectors } from '@grafana/e2e-selectors/src'; +import { selectors as e2eSelectors } from '@grafana/e2e-selectors'; import { reportInteraction, setEchoSrv } from '@grafana/runtime'; import { Panel } from '@grafana/schema'; import config from 'app/core/config'; diff --git a/public/app/features/dashboard/containers/PublicDashboardPage.test.tsx b/public/app/features/dashboard/containers/PublicDashboardPage.test.tsx index 4500526802a..222f334ee4d 100644 --- a/public/app/features/dashboard/containers/PublicDashboardPage.test.tsx +++ b/public/app/features/dashboard/containers/PublicDashboardPage.test.tsx @@ -5,7 +5,7 @@ import { useEffectOnce } from 'react-use'; import { Props as AutoSizerProps } from 'react-virtualized-auto-sizer'; import { render } from 'test/test-utils'; -import { selectors as e2eSelectors } from '@grafana/e2e-selectors/src'; +import { selectors as e2eSelectors } from '@grafana/e2e-selectors'; import { Dashboard, DashboardCursorSync, FieldConfigSource, Panel, ThresholdsMode } from '@grafana/schema/src'; import { getRouteComponentProps } from 'app/core/navigation/mocks/routeProps'; import { DashboardInitPhase, DashboardMeta, DashboardRoutes } from 'app/types/dashboard'; diff --git a/public/app/features/dashboard/containers/PublicDashboardPage.tsx b/public/app/features/dashboard/containers/PublicDashboardPage.tsx index b6f74800a76..98553e46bc1 100644 --- a/public/app/features/dashboard/containers/PublicDashboardPage.tsx +++ b/public/app/features/dashboard/containers/PublicDashboardPage.tsx @@ -4,7 +4,7 @@ import { useLocation, useParams } from 'react-router-dom-v5-compat'; import { usePrevious } from 'react-use'; import { GrafanaTheme2, PageLayoutType, TimeZone } from '@grafana/data'; -import { selectors as e2eSelectors } from '@grafana/e2e-selectors/src'; +import { selectors as e2eSelectors } from '@grafana/e2e-selectors'; import { PageToolbar, useStyles2 } from '@grafana/ui'; import { Page } from 'app/core/components/Page/Page'; import { useGrafana } from 'app/core/context/GrafanaContext'; diff --git a/public/app/features/explore/PrometheusListView/RawListItem.tsx b/public/app/features/explore/PrometheusListView/RawListItem.tsx index 4da433e89ce..d192b5da3e9 100644 --- a/public/app/features/explore/PrometheusListView/RawListItem.tsx +++ b/public/app/features/explore/PrometheusListView/RawListItem.tsx @@ -3,7 +3,7 @@ import { useCopyToClipboard } from 'react-use'; import { Field, GrafanaTheme2 } from '@grafana/data'; import { t } from '@grafana/i18n'; -import { isValidLegacyName, utf8Support } from '@grafana/prometheus/src/utf8_support'; +import { isValidLegacyName, utf8Support } from '@grafana/prometheus'; import { reportInteraction } from '@grafana/runtime'; import { IconButton, useStyles2 } from '@grafana/ui'; diff --git a/public/app/features/logs/components/panel/LogListControls.tsx b/public/app/features/logs/components/panel/LogListControls.tsx index 5b482af2aad..a5deb524d66 100644 --- a/public/app/features/logs/components/panel/LogListControls.tsx +++ b/public/app/features/logs/components/panel/LogListControls.tsx @@ -5,13 +5,13 @@ import { MouseEvent, useCallback, useMemo } from 'react'; import { CoreApp, EventBus, + GrafanaTheme2, LogLevel, LogsDedupDescription, LogsDedupStrategy, LogsSortOrder, store, } from '@grafana/data'; -import { GrafanaTheme2 } from '@grafana/data/'; import { t } from '@grafana/i18n'; import { config, reportInteraction } from '@grafana/runtime'; import { Dropdown, Menu, useStyles2 } from '@grafana/ui'; diff --git a/public/app/features/manage-dashboards/components/PublicDashboardListTable/PublicDashboardListTable.test.tsx b/public/app/features/manage-dashboards/components/PublicDashboardListTable/PublicDashboardListTable.test.tsx index f5d3ad028b0..0014f107004 100644 --- a/public/app/features/manage-dashboards/components/PublicDashboardListTable/PublicDashboardListTable.test.tsx +++ b/public/app/features/manage-dashboards/components/PublicDashboardListTable/PublicDashboardListTable.test.tsx @@ -3,7 +3,7 @@ import { http, HttpResponse } from 'msw'; import { setupServer } from 'msw/node'; import { render } from 'test/test-utils'; -import { selectors as e2eSelectors } from '@grafana/e2e-selectors/src'; +import { selectors as e2eSelectors } from '@grafana/e2e-selectors'; import { backendSrv } from 'app/core/services/backend_srv'; import { contextSrv } from 'app/core/services/context_srv'; diff --git a/public/app/features/manage-dashboards/components/PublicDashboardListTable/PublicDashboardListTable.tsx b/public/app/features/manage-dashboards/components/PublicDashboardListTable/PublicDashboardListTable.tsx index d14fd970d1d..a1bcedb5e70 100644 --- a/public/app/features/manage-dashboards/components/PublicDashboardListTable/PublicDashboardListTable.tsx +++ b/public/app/features/manage-dashboards/components/PublicDashboardListTable/PublicDashboardListTable.tsx @@ -3,7 +3,7 @@ import { useMemo, useState } from 'react'; import { useMedia } from 'react-use'; import { GrafanaTheme2 } from '@grafana/data'; -import { selectors as e2eSelectors } from '@grafana/e2e-selectors/src'; +import { selectors as e2eSelectors } from '@grafana/e2e-selectors'; import { Trans, t } from '@grafana/i18n'; import { reportInteraction } from '@grafana/runtime'; import { diff --git a/public/app/plugins/panel/geomap/components/DebugOverlay.tsx b/public/app/plugins/panel/geomap/components/DebugOverlay.tsx index de267de908f..d08d2042f05 100644 --- a/public/app/plugins/panel/geomap/components/DebugOverlay.tsx +++ b/public/app/plugins/panel/geomap/components/DebugOverlay.tsx @@ -6,7 +6,7 @@ import { PureComponent } from 'react'; import tinycolor from 'tinycolor2'; import { GrafanaTheme2 } from '@grafana/data'; -import { selectors } from '@grafana/e2e-selectors/src'; +import { selectors } from '@grafana/e2e-selectors'; import { Trans } from '@grafana/i18n'; import { config } from 'app/core/config'; @@ -44,7 +44,7 @@ export class DebugOverlay extends PureComponent { const { zoom, center } = this.state; return ( -
+
diff --git a/scripts/cli/tsconfig.json b/scripts/cli/tsconfig.json index 468d9938751..057344a363c 100644 --- a/scripts/cli/tsconfig.json +++ b/scripts/cli/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { - "moduleResolution": "node", - "module": "commonjs" + "moduleResolution": "nodenext", + "module": "NodeNext" }, "extends": "../../tsconfig.json", "ts-node": { diff --git a/scripts/prepare-npm-package.js b/scripts/prepare-npm-package.js index 054b3d12609..7a11a2eca4a 100644 --- a/scripts/prepare-npm-package.js +++ b/scripts/prepare-npm-package.js @@ -1,108 +1,35 @@ +//@ts-check import PackageJson from '@npmcli/package-json'; -import { mkdir } from 'node:fs/promises'; const cwd = process.cwd(); try { const pkgJson = await PackageJson.load(cwd); - const cjsIndex = pkgJson.content.publishConfig?.main ?? pkgJson.content.main; - const esmIndex = pkgJson.content.publishConfig?.module ?? pkgJson.content.module; - const typesIndex = pkgJson.content.publishConfig?.types ?? pkgJson.content.types; + const pkgJsonExports = pkgJson.content.exports; - const exports = { - './package.json': './package.json', - '.': { - import: { - types: typesIndex, - default: esmIndex, - }, - require: { - types: typesIndex, - default: cjsIndex, - }, - }, - }; - // Fix so scenes can access `@grafana/schema` nested dist import paths e.g. - // import {} from '@grafana/schema/dist/esm/raw/composable/bargauge/panelcfg/x/BarGaugePanelCfg_types.gen' - if (pkgJson.content.name === '@grafana/schema') { - exports['./dist/*'] = { - types: './dist/*', - default: './dist/*', - }; - } - - // Fix for @grafana/i18n so eslint-plugin can be imported by consumers - if (pkgJson.content.name === '@grafana/i18n') { - exports['./eslint-plugin'] = { - types: './dist/eslint/index.d.ts', - import: './dist/eslint/index.cjs', - require: './dist/eslint/index.cjs', - }; - } - - pkgJson.update({ - main: cjsIndex, - types: typesIndex, - module: esmIndex, - exports, - }); - - await pkgJson.save(); - - // If an alias package name is provided we add an exports entry for the alias - // then generate an additional "nested" package.json for typescript resolution that - // doesn't use the exports property in package.json. - if (process.env.ALIAS_PACKAGE_NAME) { - const aliasNames = process.env.ALIAS_PACKAGE_NAME.split(','); - - const additionalExports = aliasNames.reduce((acc, alias) => { - acc[`./${alias}`] = { - import: { - types: typesIndex.replace('index', alias), - default: esmIndex.replace('index', alias), - }, - require: { - types: typesIndex.replace('index', alias), - default: cjsIndex.replace('index', alias), - }, - }; - return acc; - }, {}); + // skip packages without exports otherwise consumers cannot import anything from the package + if (pkgJsonExports && typeof pkgJsonExports === 'object') { + // Remove all exports that only contain a single key '@grafana-app/source' + // as these will not resolve when validating packages with attw because the + // source code is not available in the tarball. + for (const [key, val] of Object.entries(pkgJsonExports)) { + if ( + val !== null && + typeof val === 'object' && + Object.keys(val).length === 1 && + Object.keys(val)[0] === '@grafana-app/source' + ) { + delete pkgJsonExports[key]; + } + } pkgJson.update({ - exports: { - ...pkgJson.content.exports, - ...additionalExports, - }, - files: [...pkgJson.content.files, ...aliasNames], + exports: pkgJsonExports, }); - await pkgJson.save(); - for await (const aliasName of aliasNames) { - await createAliasPackageJsonFiles(pkgJson.content, aliasName); - } + await pkgJson.save(); } } catch (e) { console.error(e); process.exit(1); } - -async function createAliasPackageJsonFiles(packageJsonContent, aliasName) { - const pkgName = `${packageJsonContent.name}/${aliasName}`; - try { - console.log(`📦 Writing alias package.json for ${pkgName}.`); - const pkgJsonPath = `${cwd}/${aliasName}`; - await mkdir(pkgJsonPath, { recursive: true }); - const pkgJson = await PackageJson.create(pkgJsonPath, { - data: { - name: pkgName, - types: `../dist/types/${aliasName}.d.ts`, - main: `../dist/cjs/${aliasName}.cjs`, - module: `../dist/esm/${aliasName}.mjs`, - }, - }); - await pkgJson.save(); - } catch (error) { - throw new Error(`Error generating package.json for ${pkgName}`, error); - } -} diff --git a/scripts/tsconfig.base.json b/scripts/tsconfig.base.json index 55eacdf4b61..152674b91e7 100644 --- a/scripts/tsconfig.base.json +++ b/scripts/tsconfig.base.json @@ -4,6 +4,7 @@ "alwaysStrict": true, "strict": true, "allowSyntheticDefaultImports": true, + "customConditions": ["@grafana-app/source"], "downlevelIteration": true, "esModuleInterop": true, "experimentalDecorators": true, diff --git a/scripts/validate-npm-packages.sh b/scripts/validate-npm-packages.sh index 92e871b6899..99474041107 100755 --- a/scripts/validate-npm-packages.sh +++ b/scripts/validate-npm-packages.sh @@ -17,7 +17,7 @@ for file in "$ARTIFACTS_DIR"/*.tgz; do fi # shellcheck disable=SC2086 - if ! yarn attw "$file" --ignore-rules "false-cjs" $ATTW_FLAGS; then + if ! NODE_OPTIONS="-C @grafana-app/source" yarn attw "$file" --ignore-rules "false-cjs" $ATTW_FLAGS; then echo "attw check failed for $file" echo "" failed_checks+=("$file - yarn attw") diff --git a/scripts/webpack/webpack.common.js b/scripts/webpack/webpack.common.js index e694b812100..f81c279d6a3 100644 --- a/scripts/webpack/webpack.common.js +++ b/scripts/webpack/webpack.common.js @@ -22,6 +22,7 @@ module.exports = { publicPath: 'public/build/', }, resolve: { + conditionNames: ['@grafana-app/source', '...'], extensions: ['.ts', '.tsx', '.es6', '.js', '.json', '.svg'], alias: { // some of data source plugins use global Prism object to add the language definition diff --git a/tsconfig.json b/tsconfig.json index 963aa84555c..46f7b630cb2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -18,9 +18,10 @@ } }, "ts-node": { + "swc": true, "compilerOptions": { "module": "ESNext", - "moduleResolution": "Node" + "moduleResolution": "Bundler" } }, "include": [ diff --git a/yarn.lock b/yarn.lock index 1c4d07dbb6e..e20676b04f0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8290,6 +8290,30 @@ __metadata: languageName: node linkType: hard +"@storybook/core@patch:@storybook/core@npm%3A8.6.2#~/.yarn/patches/@storybook-core-npm-8.6.2-8c752112c0.patch": + version: 8.6.2 + resolution: "@storybook/core@patch:@storybook/core@npm%3A8.6.2#~/.yarn/patches/@storybook-core-npm-8.6.2-8c752112c0.patch::version=8.6.2&hash=f4cc1f" + dependencies: + "@storybook/theming": "npm:8.6.2" + better-opn: "npm:^3.0.2" + browser-assert: "npm:^1.2.1" + esbuild: "npm:^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0" + esbuild-register: "npm:^3.5.0" + jsdoc-type-pratt-parser: "npm:^4.0.0" + process: "npm:^0.11.10" + recast: "npm:^0.23.5" + semver: "npm:^7.6.2" + util: "npm:^0.12.5" + ws: "npm:^8.2.3" + peerDependencies: + prettier: ^2 || ^3 + peerDependenciesMeta: + prettier: + optional: true + checksum: 10/cd95a51437135dd3c4333b14acefd528d8064b2cea7789f859ba80783c115c92ed4be51d4a7bd6236888fdd5f46f488a379e0c71bc1a712ffe6dc1353fb4e648 + languageName: node + linkType: hard + "@storybook/csf-plugin@npm:8.6.2": version: 8.6.2 resolution: "@storybook/csf-plugin@npm:8.6.2" From e29bb47e95006886b512792901a21c2e60536d2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20Jim=C3=A9nez=20S=C3=A1nchez?= Date: Thu, 18 Dec 2025 13:16:57 +0100 Subject: [PATCH 10/36] Provisioning: Add Git Sync limitations warning and migrate resources checkbox (#115532) * Provisioning: Add Git Sync limitations warning and migrate resources checkbox - Update SynchronizeStep alert to use warning severity with comprehensive Git Sync limitations - Add conditional warnings for instance sync (permissions loss, alerts/library panels loss) - Add conditional warnings for folder sync (folder structure changes, manual cleanup needed) - Add "Migrate existing resources" checkbox for folder sync mode - Update useCreateSyncJob hook to handle migrateResources option for folder sync - Extract i18n translations for new strings * Simplify createSyncJob: calculate requiresMigration in caller - Remove syncTarget and migrateResources parameters from useCreateSyncJob hook - Calculate requiresMigration in SynchronizeStep based on sync target and checkbox value - Pass requiresMigration as parameter to createSyncJob function * Revert: Pass requiresMigration as hook parameter - Calculate requiresMigration in SynchronizeStep using useMemo - Pass requiresMigration to useCreateSyncJob hook - Remove parameter from createSyncJob function call * Revert "Revert: Pass requiresMigration as hook parameter" This reverts commit 97e3b7107d282d10a1be487d9186daaaf62f41aa. * Fix TypeScript errors in ProvisioningWizard - Remove requiresMigration from useCreateSyncJob call - Pass requiresMigration parameter to createSyncJob call - Remove unused Target import from SynchronizeStep * Show migrate resources checkbox for instance sync (checked and disabled) - Display checkbox for both instance and folder sync - For instance sync: checkbox is checked and disabled with explanation - For instance sync: automatically set migrateResources to true via useEffect - Update description to explain instance sync requires all resources to be managed * Extract i18n translations for instance-migrate-resources-description * Rename 'Synchronization options' to 'Options' * Update i18n translations: rename synchronization-options to options * Remove unnecessary conditional check for sync target * Add bodySmall variant to announcement banner TextLink * Move requiresMigration calculation logic into useResourceStats hook - Add migrateResources parameter to useResourceStats hook - Calculate final requiresMigration in hook based on sync target and checkbox value - Use watch instead of getValues to reactively get migrateResources value - Simplify startSynchronization to use requiresMigration from hook --- .../Wizard/ProvisioningWizard.tsx | 11 +- .../provisioning/Wizard/SynchronizeStep.tsx | 131 ++++++++++++++---- .../Wizard/hooks/useCreateSyncJob.ts | 5 +- .../Wizard/hooks/useResourceStats.ts | 19 ++- .../app/features/provisioning/Wizard/types.ts | 1 + public/locales/en-US/grafana.json | 13 +- 6 files changed, 135 insertions(+), 45 deletions(-) diff --git a/public/app/features/provisioning/Wizard/ProvisioningWizard.tsx b/public/app/features/provisioning/Wizard/ProvisioningWizard.tsx index 32829dba4c0..0950500a378 100644 --- a/public/app/features/provisioning/Wizard/ProvisioningWizard.tsx +++ b/public/app/features/provisioning/Wizard/ProvisioningWizard.tsx @@ -117,14 +117,9 @@ export const ProvisioningWizard = memo(function ProvisioningWizard({ const [repoName = '', repoType, syncTarget] = watch(['repositoryName', 'repository.type', 'repository.sync.target']); const [submitData] = useCreateOrUpdateRepository(repoName); const [deleteRepository] = useDeleteRepositoryMutation(); - const { - shouldSkipSync, - requiresMigration, - isLoading: isResourceStatsLoading, - } = useResourceStats(repoName, syncTarget); + const { shouldSkipSync, isLoading: isResourceStatsLoading } = useResourceStats(repoName, syncTarget); const { createSyncJob, isLoading: isCreatingSkipJob } = useCreateSyncJob({ repoName: repoName, - requiresMigration, setStepStatusInfo, }); @@ -274,8 +269,8 @@ export const ProvisioningWizard = memo(function ProvisioningWizard({ if (activeStep === 'bootstrap' && canSkipSync) { nextStepIndex = currentStepIndex + 2; // Skip to finish step - // Create a pull job to initialize the repository - const job = await createSyncJob(); + // No migration needed when skipping sync + const job = await createSyncJob(false); if (!job) { return; // Don't proceed if job creation fails } diff --git a/public/app/features/provisioning/Wizard/SynchronizeStep.tsx b/public/app/features/provisioning/Wizard/SynchronizeStep.tsx index 5842b313060..299fe775a0e 100644 --- a/public/app/features/provisioning/Wizard/SynchronizeStep.tsx +++ b/public/app/features/provisioning/Wizard/SynchronizeStep.tsx @@ -3,7 +3,7 @@ import { memo, useEffect, useState } from 'react'; import { useFormContext } from 'react-hook-form'; import { Trans, t } from '@grafana/i18n'; -import { Alert, Button, Field, Spinner, Stack, Text, TextLink } from '@grafana/ui'; +import { Alert, Button, Checkbox, Field, Spinner, Stack, Text, TextLink } from '@grafana/ui'; import { Job, useGetRepositoryStatusQuery } from 'app/api/clients/provisioning/v0alpha1'; import { JobStatus } from '../Job/JobStatus'; @@ -20,13 +20,17 @@ export interface SynchronizeStepProps { } export const SynchronizeStep = memo(function SynchronizeStep({ onCancel, isCancelling }: SynchronizeStepProps) { - const { watch } = useFormContext(); + const { watch, register } = useFormContext(); const { setStepStatusInfo } = useStepStatus(); - const [repoName = '', syncTarget] = watch(['repositoryName', 'repository.sync.target']); - const { requiresMigration } = useResourceStats(repoName, syncTarget); + const [repoName = '', syncTarget, migrateResources] = watch([ + 'repositoryName', + 'repository.sync.target', + 'migrate.migrateResources', + ]); + const { requiresMigration } = useResourceStats(repoName, syncTarget, migrateResources); + const { createSyncJob } = useCreateSyncJob({ repoName, - requiresMigration, setStepStatusInfo, }); const [job, setJob] = useState(); @@ -63,7 +67,7 @@ export const SynchronizeStep = memo(function SynchronizeStep({ onCancel, isCance const isButtonDisabled = hasError || (checked !== undefined && isRepositoryHealthy === false) || healthStatusNotReady; const startSynchronization = async () => { - const response = await createSyncJob(); + const response = await createSyncJob(requiresMigration); if (response) { setJob(response); } @@ -108,41 +112,108 @@ export const SynchronizeStep = memo(function SynchronizeStep({ onCancel, isCance )} {isRepositoryHealthy && ( -
    -
  • - - Resources can still be created, edited, or deleted during this process, but changes may not be exported. + + + + Please be aware of the following limitations. For more details, see the{' '} + + Git Sync documentation + + . -
  • -
  • - - Once provisioning is complete, resources will be marked as managed through external storage. - -
  • -
  • - - The duration of this process depends on the number of resources involved. - -
  • -
  • + +
      +
    • + + Resources can still be created, edited, or deleted during this process, but changes may not be + exported. + +
    • +
    • + + Alerts and library panels are not supported in provisioned folders. + +
    • +
    • + + Fine-grained permissions are not supported. Default permissions apply: Admin, Editor, and Viewer roles + are preserved with their standard access levels. + +
    • +
    • + + The duration of this process depends on the number of resources involved. + +
    • + {syncTarget === 'instance' && ( +
    • + + Existing alerts and library panels will be lost and will not be usable after migration. + +
    • + )} + {syncTarget === 'folder' && ( + <> +
    • + + When migrating existing dashboards, the folder structure will be replicated in the repository. + Original folders will be emptied of dashboards but may still contain alerts or library panels. + +
    • +
    • + + You may need to manually remove or manage original folders after migration. + +
    • + + )} +
    + Enterprise instance administrators can display an announcement banner to notify users that migration is in progress. See{' '} - + this guide {' '} for step-by-step instructions. -
  • -
+ +
)} + + Options + + + + Instance sync requires all resources to be managed. Existing resources will be migrated automatically. + + ) : ( + + Import existing dashboards from all folders into the new provisioned folder + + ) + } + /> + {healthStatusNotReady ? ( <> diff --git a/public/app/features/provisioning/Wizard/hooks/useCreateSyncJob.ts b/public/app/features/provisioning/Wizard/hooks/useCreateSyncJob.ts index 7e3c0c30874..1aaa7a250f0 100644 --- a/public/app/features/provisioning/Wizard/hooks/useCreateSyncJob.ts +++ b/public/app/features/provisioning/Wizard/hooks/useCreateSyncJob.ts @@ -5,14 +5,13 @@ import { StepStatusInfo } from '../types'; export interface UseCreateSyncJobParams { repoName: string; - requiresMigration: boolean; setStepStatusInfo?: (info: StepStatusInfo) => void; } -export function useCreateSyncJob({ repoName, requiresMigration, setStepStatusInfo }: UseCreateSyncJobParams) { +export function useCreateSyncJob({ repoName, setStepStatusInfo }: UseCreateSyncJobParams) { const [createJob, { isLoading }] = useCreateRepositoryJobsMutation(); - const createSyncJob = async () => { + const createSyncJob = async (requiresMigration: boolean) => { if (!repoName) { setStepStatusInfo?.({ status: 'error', diff --git a/public/app/features/provisioning/Wizard/hooks/useResourceStats.ts b/public/app/features/provisioning/Wizard/hooks/useResourceStats.ts index 7e2ffef178b..dd4b222cb19 100644 --- a/public/app/features/provisioning/Wizard/hooks/useResourceStats.ts +++ b/public/app/features/provisioning/Wizard/hooks/useResourceStats.ts @@ -100,7 +100,7 @@ function getResourceStats(files?: GetRepositoryFilesApiResponse, stats?: GetReso /** * Hook that provides resource statistics and sync logic */ -export function useResourceStats(repoName?: string, syncTarget?: RepositoryView['target']) { +export function useResourceStats(repoName?: string, syncTarget?: RepositoryView['target'], migrateResources?: boolean) { const resourceStatsQuery = useGetResourceStatsQuery(repoName ? undefined : skipToken); const filesQuery = useGetRepositoryFilesQuery(repoName ? { name: repoName } : skipToken); @@ -121,7 +121,22 @@ export function useResourceStats(repoName?: string, syncTarget?: RepositoryView[ }; }, [resourceStatsQuery.data]); - const requiresMigration = resourceCount > 0 && syncTarget === 'instance'; + // Calculate base requiresMigration: true if there are resources to migrate + const baseRequiresMigration = resourceCount > 0; + + // Calculate final requiresMigration based on sync target and user selection + // For instance sync: always use baseRequiresMigration (checkbox is disabled and always true) + // For folder sync: only migrate if user explicitly opts in via checkbox + const requiresMigration = useMemo(() => { + if (syncTarget === 'instance') { + return baseRequiresMigration; + } + if (syncTarget === 'folder') { + return migrateResources ?? false; + } + return baseRequiresMigration; + }, [syncTarget, baseRequiresMigration, migrateResources]); + const shouldSkipSync = (resourceCount === 0 || syncTarget === 'folder') && fileCount === 0; // Format display strings diff --git a/public/app/features/provisioning/Wizard/types.ts b/public/app/features/provisioning/Wizard/types.ts index 0dc2f001a43..34ee246da04 100644 --- a/public/app/features/provisioning/Wizard/types.ts +++ b/public/app/features/provisioning/Wizard/types.ts @@ -9,6 +9,7 @@ export type RepoType = RepositorySpec['type']; export interface MigrateFormData { history: boolean; identifier: boolean; + migrateResources?: boolean; } export interface WizardFormData { diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index 79b1f20b8e0..8c7bc95d901 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -12182,6 +12182,9 @@ "tooltip-unhealthy-repository": "Unable to pull an unhealthy repository" }, "synchronize-step": { + "instance-migrate-resources-description": "Instance sync requires all resources to be managed. Existing resources will be migrated automatically.", + "migrate-resources-description": "Import existing dashboards from all folders into the new provisioned folder", + "options": "Options", "repository-error": "Repository error", "repository-error-message": "Unable to check repository status. Please verify the repository configuration and try again.", "repository-unhealthy": "The repository cannot be synchronized. Cancel provisioning and try again once the issue has been resolved. See details below." @@ -12201,11 +12204,16 @@ }, "warning-title-default": "Warning", "wizard": { + "alert-intro": "Please be aware of the following limitations. For more details, see the <2>Git Sync documentation.", "alert-point-1": "Resources can still be created, edited, or deleted during this process, but changes may not be exported.", - "alert-point-2": "Once provisioning is complete, resources will be marked as managed through external storage.", "alert-point-3": "The duration of this process depends on the number of resources involved.", "alert-point-4": "Enterprise instance administrators can display an announcement banner to notify users that migration is in progress. See <2>this guide for step-by-step instructions.", - "alert-title": "Important: No data or configuration will be lost. Dashboards remain accessible during migration, but changes made during this process may not be exported.", + "alert-point-folder-cleanup": "You may need to manually remove or manage original folders after migration.", + "alert-point-folder-structure": "When migrating existing dashboards, the folder structure will be replicated in the repository. Original folders will be emptied of dashboards but may still contain alerts or library panels.", + "alert-point-instance-alerts": "Existing alerts and library panels will be lost and will not be usable after migration.", + "alert-point-permissions": "Fine-grained permissions are not supported. Default permissions apply: Admin, Editor, and Viewer roles are preserved with their standard access levels.", + "alert-point-unsupported": "Alerts and library panels are not supported in provisioned folders.", + "alert-title": "Important: Review Git Sync limitations before proceeding", "button-cancel": "Cancel", "button-cancelling": "Cancelling...", "button-next": "Finish", @@ -12223,6 +12231,7 @@ "step-finish": "Choose additional settings", "step-synchronize": "Synchronize with external storage", "sync-description": "Sync resources with external storage. After this one-time step, all future updates will be automatically saved to the repository and provisioned back into the instance.", + "sync-option-migrate-resources": "Migrate existing resources", "title-bootstrap": "Choose what to synchronize", "title-connect": "Connect to external storage", "title-finish": "Choose additional settings", From 241fd69e028f922bb38de6b46dd1388304b9e62e Mon Sep 17 00:00:00 2001 From: Andre Pereira Date: Thu, 18 Dec 2025 12:38:50 +0000 Subject: [PATCH 11/36] Trace View: Correctly handle span and service name in span filters (#115215) * Correctly handle span name and service name in trace view span filters * Consistency and fix test * i18n extract --- .../SpanFilters/SpanFilters.test.tsx | 262 -------------- .../SpanFilters/SpanFilters.tsx | 319 ------------------ .../SpanFilters/SpanFiltersTags.tsx | 202 ----------- .../useTraceAdHocFiltersController.ts | 15 - .../TraceView/components/constants/span.ts | 2 + .../components/utils/filter-spans.tsx | 20 +- .../explore/TraceView/useSearch.test.ts | 14 +- .../features/explore/TraceView/useSearch.ts | 5 +- .../features/explore/TraceView/utils/tags.ts | 15 + public/locales/en-US/grafana.json | 33 -- 10 files changed, 42 insertions(+), 845 deletions(-) delete mode 100644 public/app/features/explore/TraceView/components/TracePageHeader/SpanFilters/SpanFilters.test.tsx delete mode 100644 public/app/features/explore/TraceView/components/TracePageHeader/SpanFilters/SpanFilters.tsx delete mode 100644 public/app/features/explore/TraceView/components/TracePageHeader/SpanFilters/SpanFiltersTags.tsx diff --git a/public/app/features/explore/TraceView/components/TracePageHeader/SpanFilters/SpanFilters.test.tsx b/public/app/features/explore/TraceView/components/TracePageHeader/SpanFilters/SpanFilters.test.tsx deleted file mode 100644 index eba49425afb..00000000000 --- a/public/app/features/explore/TraceView/components/TracePageHeader/SpanFilters/SpanFilters.test.tsx +++ /dev/null @@ -1,262 +0,0 @@ -import { render, screen, waitFor } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import { useState } from 'react'; - -import { DEFAULT_SPAN_FILTERS } from 'app/features/explore/state/constants'; - -import { Trace } from '../../types/trace'; - -import { SpanFilters } from './SpanFilters'; - -const trace: Trace = { - traceID: '1ed38015486087ca', - spans: [ - { - traceID: '1ed38015486087ca', - spanID: '1ed38015486087ca', - operationName: 'Span0', - tags: [{ key: 'TagKey0', type: 'string', value: 'TagValue0' }], - kind: 'server', - statusCode: 2, - statusMessage: 'message', - instrumentationLibraryName: 'name', - instrumentationLibraryVersion: 'version', - traceState: 'state', - process: { - serviceName: 'Service0', - tags: [{ key: 'ProcessKey0', type: 'string', value: 'ProcessValue0' }], - }, - logs: [{ fields: [{ key: 'LogKey0', type: 'string', value: 'LogValue0' }] }], - }, - { - traceID: '1ed38015486087ca', - spanID: '2ed38015486087ca', - operationName: 'Span1', - tags: [{ key: 'TagKey1', type: 'string', value: 'TagValue1' }], - process: { - serviceName: 'Service1', - tags: [{ key: 'ProcessKey1', type: 'string', value: 'ProcessValue1' }], - }, - logs: [{ fields: [{ key: 'LogKey1', type: 'string', value: 'LogValue1' }] }], - }, - ], - processes: { - '1ed38015486087ca': { - serviceName: 'Service0', - tags: [], - }, - }, -} as unknown as Trace; - -describe('SpanFilters', () => { - let user: ReturnType; - const SpanFiltersWithProps = ({ showFilters = true, matches }: { showFilters?: boolean; matches?: Set }) => { - const [search, setSearch] = useState(DEFAULT_SPAN_FILTERS); - const props = { - trace: trace, - showSpanFilters: showFilters, - setShowSpanFilters: jest.fn(), - search, - setSearch, - spanFilterMatches: matches, - setFocusedSpanIdForSearch: jest.fn(), - datasourceType: 'tempo', - }; - - return ; - }; - - beforeEach(() => { - jest.useFakeTimers(); - // Need to use delay: null here to work with fakeTimers - // see https://github.com/testing-library/user-event/issues/833 - user = userEvent.setup({ delay: null }); - }); - - afterEach(() => { - jest.useRealTimers(); - }); - - it('should render', () => { - expect(() => render()).not.toThrow(); - }); - - it('should render filters', async () => { - render(); - - const serviceOperator = screen.getByLabelText('Select service name operator'); - const serviceValue = screen.getByLabelText('Select service name'); - const spanOperator = screen.getByLabelText('Select span name operator'); - const spanValue = screen.getByLabelText('Select span name'); - const fromOperator = screen.getByLabelText('Select min span operator'); - const fromValue = screen.getByLabelText('Select min span duration'); - const toOperator = screen.getByLabelText('Select max span operator'); - const toValue = screen.getByLabelText('Select max span duration'); - const tagKey = screen.getByLabelText('Select tag key'); - const tagOperator = screen.getByLabelText('Select tag operator'); - const tagSelectValue = screen.getByLabelText('Select tag value'); - - expect(serviceOperator).toBeInTheDocument(); - expect(getElemText(serviceOperator)).toBe('='); - expect(serviceValue).toBeInTheDocument(); - expect(spanOperator).toBeInTheDocument(); - expect(getElemText(spanOperator)).toBe('='); - expect(spanValue).toBeInTheDocument(); - expect(fromOperator).toBeInTheDocument(); - expect(getElemText(fromOperator)).toBe('>'); - expect(fromValue).toBeInTheDocument(); - expect(toOperator).toBeInTheDocument(); - expect(getElemText(toOperator)).toBe('<'); - expect(toValue).toBeInTheDocument(); - expect(tagKey).toBeInTheDocument(); - expect(tagOperator).toBeInTheDocument(); - expect(getElemText(tagOperator)).toBe('='); - expect(tagSelectValue).toBeInTheDocument(); - - await user.click(serviceValue); - jest.advanceTimersByTime(1000); - await waitFor(() => { - expect(screen.getByText('Service0')).toBeInTheDocument(); - expect(screen.getByText('Service1')).toBeInTheDocument(); - }); - await user.click(spanValue); - jest.advanceTimersByTime(1000); - await waitFor(() => { - expect(screen.getByText('Span0')).toBeInTheDocument(); - expect(screen.getByText('Span1')).toBeInTheDocument(); - }); - await user.click(tagOperator); - jest.advanceTimersByTime(1000); - await waitFor(() => { - expect(screen.getByText('!~')).toBeInTheDocument(); - expect(screen.getByText('=~')).toBeInTheDocument(); - expect(screen.getByText('!~')).toBeInTheDocument(); - }); - await user.click(tagKey); - jest.advanceTimersByTime(1000); - await waitFor(() => { - expect(screen.getByText('TagKey0')).toBeInTheDocument(); - expect(screen.getByText('TagKey1')).toBeInTheDocument(); - expect(screen.getByText('kind')).toBeInTheDocument(); - expect(screen.getByText('ProcessKey0')).toBeInTheDocument(); - expect(screen.getByText('ProcessKey1')).toBeInTheDocument(); - expect(screen.getByText('LogKey0')).toBeInTheDocument(); - expect(screen.getByText('LogKey1')).toBeInTheDocument(); - expect(screen.getByPlaceholderText('Find...')).toBeInTheDocument(); - }); - }); - - it('should update filters', async () => { - render(); - const serviceValue = screen.getByLabelText('Select service name'); - const spanValue = screen.getByLabelText('Select span name'); - const tagKey = screen.getByLabelText('Select tag key'); - const tagOperator = screen.getByLabelText('Select tag operator'); - const tagValue = screen.getByLabelText('Select tag value'); - - expect(getElemText(serviceValue)).toBe('All service names'); - await selectAndCheckValue(user, serviceValue, 'Service0'); - expect(getElemText(spanValue)).toBe('All span names'); - await selectAndCheckValue(user, spanValue, 'Span0'); - - await user.click(tagValue); - jest.advanceTimersByTime(1000); - await waitFor(() => expect(screen.getByText('No options found')).toBeInTheDocument()); - - expect(getElemText(tagKey)).toBe('Select tag'); - await selectAndCheckValue(user, tagKey, 'TagKey0'); - expect(getElemText(tagValue)).toBe('Select value'); - await selectAndCheckValue(user, tagValue, 'TagValue0'); - expect(screen.queryByLabelText('Input tag value')).toBeNull(); - await selectAndCheckValue(user, tagOperator, '=~'); - expect(screen.getByLabelText('Input tag value')).toBeInTheDocument(); - }); - - it('should order tag filters', async () => { - render(); - const tagKey = screen.getByLabelText('Select tag key'); - - await user.click(tagKey); - jest.advanceTimersByTime(1000); - await waitFor(() => { - const container = screen.getByText('TagKey0').parentElement?.parentElement?.parentElement; - expect(container?.childNodes[1].textContent).toBe('ProcessKey0'); - expect(container?.childNodes[2].textContent).toBe('ProcessKey1'); - expect(container?.childNodes[3].textContent).toBe('TagKey0'); - expect(container?.childNodes[4].textContent).toBe('TagKey1'); - expect(container?.childNodes[5].textContent).toBe('id'); - expect(container?.childNodes[6].textContent).toBe('kind'); - expect(container?.childNodes[7].textContent).toBe('library.name'); - expect(container?.childNodes[8].textContent).toBe('library.version'); - expect(container?.childNodes[9].textContent).toBe('status'); - expect(container?.childNodes[10].textContent).toBe('status.message'); - expect(container?.childNodes[11].textContent).toBe('trace.state'); - expect(container?.childNodes[12].textContent).toBe('LogKey0'); - expect(container?.childNodes[13].textContent).toBe('LogKey1'); - }); - }); - - it('should only show add/remove tag when necessary', async () => { - render(); - expect(screen.queryAllByLabelText('Add tag').length).toBe(0); // not filled in the default tag, so no need to add another one - expect(screen.queryAllByLabelText('Remove tag').length).toBe(0); // mot filled in the default tag, so no values to remove - expect(screen.getAllByLabelText('Select tag key').length).toBe(1); - - await selectAndCheckValue(user, screen.getByLabelText('Select tag key'), 'TagKey0'); - expect(screen.getAllByLabelText('Add tag').length).toBe(1); - expect(screen.getAllByLabelText('Remove tag').length).toBe(1); - - await user.click(screen.getByLabelText('Add tag')); - jest.advanceTimersByTime(1000); - expect(screen.queryAllByLabelText('Add tag').length).toBe(0); // not filled in the new tag, so no need to add another one - expect(screen.getAllByLabelText('Remove tag').length).toBe(2); // one for each tag - expect(screen.getAllByLabelText('Select tag key').length).toBe(2); - - await user.click(screen.getAllByLabelText('Remove tag')[1]); - jest.advanceTimersByTime(1000); - expect(screen.queryAllByLabelText('Add tag').length).toBe(1); // filled in the default tag, so can add another one - expect(screen.queryAllByLabelText('Remove tag').length).toBe(1); // filled in the default tag, so can remove values - expect(screen.getAllByLabelText('Select tag key').length).toBe(1); - - await user.click(screen.getAllByLabelText('Remove tag')[0]); - jest.advanceTimersByTime(1000); - expect(screen.queryAllByLabelText('Add tag').length).toBe(0); // not filled in the default tag, so no need to add another one - expect(screen.queryAllByLabelText('Remove tag').length).toBe(0); // mot filled in the default tag, so no values to remove - expect(screen.getAllByLabelText('Select tag key').length).toBe(1); - }); - - it('should allow adding/removing tags', async () => { - render(); - expect(screen.getAllByLabelText('Select tag key').length).toBe(1); - const tagKey = screen.getByLabelText('Select tag key'); - await selectAndCheckValue(user, tagKey, 'TagKey0'); - - await user.click(screen.getByLabelText('Add tag')); - jest.advanceTimersByTime(1000); - expect(screen.getAllByLabelText('Select tag key').length).toBe(2); - - await user.click(screen.getAllByLabelText('Remove tag')[0]); - jest.advanceTimersByTime(1000); - expect(screen.getAllByLabelText('Select tag key').length).toBe(1); - }); - - it('renders buttons when span filters is collapsed', async () => { - render(); - expect(screen.queryByRole('button', { name: 'Next result button' })).toBeInTheDocument(); - expect(screen.queryByRole('button', { name: 'Prev result button' })).toBeInTheDocument(); - }); -}); - -const selectAndCheckValue = async (user: ReturnType, elem: HTMLElement, text: string) => { - await user.click(elem); - jest.advanceTimersByTime(1000); - await waitFor(() => expect(screen.getByText(text)).toBeInTheDocument()); - - await user.click(screen.getByText(text)); - jest.advanceTimersByTime(1000); - expect(screen.getByText(text)).toBeInTheDocument(); -}; - -const getElemText = (elem: HTMLElement) => { - return elem.parentElement?.previousSibling?.textContent; -}; diff --git a/public/app/features/explore/TraceView/components/TracePageHeader/SpanFilters/SpanFilters.tsx b/public/app/features/explore/TraceView/components/TracePageHeader/SpanFilters/SpanFilters.tsx deleted file mode 100644 index eda085bc412..00000000000 --- a/public/app/features/explore/TraceView/components/TracePageHeader/SpanFilters/SpanFilters.tsx +++ /dev/null @@ -1,319 +0,0 @@ -// Copyright (c) 2017 Uber Technologies, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { css } from '@emotion/css'; -import React, { useState, useEffect, memo, useCallback, useRef } from 'react'; - -import { GrafanaTheme2, TraceSearchProps, SelectableValue, toOption } from '@grafana/data'; -import { Trans, t } from '@grafana/i18n'; -import { IntervalInput } from '@grafana/o11y-ds-frontend'; -import { Collapse, Icon, InlineField, InlineFieldRow, Select, Stack, Tooltip, useStyles2 } from '@grafana/ui'; - -import { DEFAULT_SPAN_FILTERS } from '../../../../state/constants'; -import { getTraceServiceNames, getTraceSpanNames } from '../../../utils/tags'; -import SearchBarInput from '../../common/SearchBarInput'; -import { Trace } from '../../types/trace'; -import NextPrevResult from '../SearchBar/NextPrevResult'; -import TracePageSearchBar from '../SearchBar/TracePageSearchBar'; - -import { SpanFiltersTags } from './SpanFiltersTags'; - -export type SpanFilterProps = { - trace: Trace; - search: TraceSearchProps; - setSearch: (newSearch: TraceSearchProps) => void; - showSpanFilters: boolean; - setShowSpanFilters: (isOpen: boolean) => void; - setFocusedSpanIdForSearch: React.Dispatch>; - spanFilterMatches: Set | undefined; - datasourceType: string; -}; - -export const SpanFilters = memo((props: SpanFilterProps) => { - const { - trace, - search, - setSearch, - showSpanFilters, - setShowSpanFilters, - setFocusedSpanIdForSearch, - spanFilterMatches, - datasourceType, - } = props; - const styles = { ...useStyles2(getStyles) }; - const [serviceNames, setServiceNames] = useState>>(); - const [spanNames, setSpanNames] = useState>>(); - const [focusedSpanIndexForSearch, setFocusedSpanIndexForSearch] = useState(-1); - const [tagKeys, setTagKeys] = useState>>(); - const [tagValues, setTagValues] = useState<{ [key: string]: Array> }>({}); - const prevTraceIdRef = useRef(); - - const durationRegex = /^\d+(?:\.\d)?\d*(?:ns|us|µs|ms|s|m|h)$/; - - const clear = useCallback(() => { - setServiceNames(undefined); - setSpanNames(undefined); - setTagKeys(undefined); - setTagValues({}); - setSearch(DEFAULT_SPAN_FILTERS); - }, [setSearch]); - - useEffect(() => { - // Only clear filters when trace ID actually changes (not on initial mount) - const currentTraceId = trace?.traceID; - - const traceHasChanged = prevTraceIdRef.current && prevTraceIdRef.current !== currentTraceId; - - if (traceHasChanged) { - clear(); - } - - prevTraceIdRef.current = currentTraceId; - }, [clear, trace]); - - const setShowSpanFilterMatchesOnly = useCallback( - (showMatchesOnly: boolean) => { - setSearch({ ...search, matchesOnly: showMatchesOnly }); - }, - [search, setSearch] - ); - - if (!trace) { - return null; - } - - const setSpanFiltersSearch = (spanSearch: TraceSearchProps) => { - setFocusedSpanIndexForSearch(-1); - setFocusedSpanIdForSearch(''); - setSearch(spanSearch); - }; - - const getServiceNames = () => { - if (!serviceNames) { - setServiceNames(getTraceServiceNames(trace).map(toOption)); - } - }; - - const getSpanNames = () => { - if (!spanNames) { - setSpanNames(getTraceSpanNames(trace).map(toOption)); - } - }; - - const collapseLabel = ( - <> - - - Span Filters - - - - - {!showSpanFilters && ( -
- -
- )} - - ); - - return ( -
- - - - - setSpanFiltersSearch({ ...search, serviceName: v?.value || '' })} - onOpenMenu={getServiceNames} - options={serviceNames || (search.serviceName ? [search.serviceName].map(toOption) : [])} - placeholder={t('explore.span-filters.placeholder-all-service-names', 'All service names')} - value={search.serviceName || null} - defaultValue={search.serviceName || null} - /> - - - { - setSpanFiltersSearch({ ...search, query: v, matchesOnly: v !== '' }); - }} - value={search.query || ''} - /> - - - - - setSpanFiltersSearch({ ...search, spanName: v?.value || '' })} - onOpenMenu={getSpanNames} - options={spanNames || (search.spanName ? [search.spanName].map(toOption) : [])} - placeholder={t('explore.span-filters.placeholder-all-span-names', 'All span names')} - value={search.spanName || null} - /> - - - - - - - setSpanFiltersSearch({ ...search, toOperator: v.value! })} - options={[toOption('<'), toOption('<=')]} - value={search.toOperator} - /> - setSpanFiltersSearch({ ...search, to: val })} - isInvalidError="Invalid duration" - // eslint-disable-next-line @grafana/i18n/no-untranslated-strings - placeholder="e.g. 100ms, 1.2s" - width={18} - value={search.to || ''} - validationRegex={durationRegex} - /> - - - - - - - - - - - -
- ); -}); - -SpanFilters.displayName = 'SpanFilters'; - -const getStyles = (theme: GrafanaTheme2) => ({ - container: css({ - label: 'SpanFilters', - margin: `0.5em 0 -${theme.spacing(1)} 0`, - zIndex: 5, - - '& > div': { - borderLeft: 'none', - borderRight: 'none', - }, - }), - collapseLabel: css({ - svg: { - color: '#aaa', - margin: '-2px 0 0 10px', - }, - }), - flexContainer: css({ - display: 'flex', - justifyContent: 'space-between', - }), - intervalInput: css({ - margin: '0 -4px 0 0', - }), - tagsRow: css({ - margin: '-4px 0 0 0', - }), - nextPrevResult: css({ - flex: 1, - alignItems: 'center', - display: 'flex', - justifyContent: 'flex-end', - marginRight: theme.spacing(1), - }), -}); diff --git a/public/app/features/explore/TraceView/components/TracePageHeader/SpanFilters/SpanFiltersTags.tsx b/public/app/features/explore/TraceView/components/TracePageHeader/SpanFilters/SpanFiltersTags.tsx deleted file mode 100644 index 3cf2d5f8488..00000000000 --- a/public/app/features/explore/TraceView/components/TracePageHeader/SpanFilters/SpanFiltersTags.tsx +++ /dev/null @@ -1,202 +0,0 @@ -import { css } from '@emotion/css'; -import React from 'react'; -import { useMount } from 'react-use'; - -import { GrafanaTheme2, SelectableValue, toOption, TraceSearchProps, TraceSearchTag } from '@grafana/data'; -import { t } from '@grafana/i18n'; -import { AccessoryButton } from '@grafana/plugin-ui'; -import { Input, Select, Stack, useStyles2 } from '@grafana/ui'; - -import { randomId } from '../../../../state/constants'; -import { getTraceTagKeys, getTraceTagValues } from '../../../utils/tags'; -import { Trace } from '../../types/trace'; - -interface Props { - search: TraceSearchProps; - setSearch: (search: TraceSearchProps) => void; - trace: Trace; - tagKeys?: Array>; - setTagKeys: React.Dispatch> | undefined>>; - tagValues: Record>>; - setTagValues: React.Dispatch> }>>; -} - -export const SpanFiltersTags = ({ search, trace, setSearch, tagKeys, setTagKeys, tagValues, setTagValues }: Props) => { - const styles = { ...useStyles2(getStyles) }; - - const getTagKeys = () => { - if (!tagKeys) { - setTagKeys(getTraceTagKeys(trace).map(toOption)); - } - }; - - const getTagValues = (key: string) => { - return getTraceTagValues(trace, key).map(toOption); - }; - - useMount(() => { - if (search.tags) { - search.tags.forEach((tag) => { - if (tag.key) { - setTagValues({ - ...tagValues, - [tag.id]: getTagValues(tag.key), - }); - } - }); - } - }); - - const onTagChange = (tag: TraceSearchTag, v: SelectableValue) => { - setSearch({ - ...search, - tags: search.tags?.map((x) => { - return x.id === tag.id ? { ...x, key: v?.value || '', value: undefined } : x; - }), - }); - - const loadTagValues = async () => { - if (v?.value) { - setTagValues({ - ...tagValues, - [tag.id]: getTagValues(v.value), - }); - } else { - // removed value - const updatedValues = { ...tagValues }; - if (updatedValues[tag.id]) { - delete updatedValues[tag.id]; - } - setTagValues(updatedValues); - } - }; - loadTagValues(); - }; - - const addTag = () => { - const tag = { - id: randomId(), - operator: '=', - }; - setSearch({ ...search, tags: [...search.tags, tag] }); - }; - - const removeTag = (id: string) => { - let tags = search.tags.filter((tag) => { - return tag.id !== id; - }); - if (tags.length === 0) { - tags = [ - { - id: randomId(), - operator: '=', - }, - ]; - } - setSearch({ ...search, tags: tags }); - }; - - return ( -
- {search.tags?.map((tag, i) => ( -
- -
- { - setSearch({ - ...search, - tags: search.tags?.map((x) => { - return x.id === tag.id ? { ...x, operator: v.value! } : x; - }), - }); - }} - options={[toOption('='), toOption('!='), toOption('=~'), toOption('!~')]} - value={tag.operator} - /> -
- - - {(tag.operator === '=' || tag.operator === '!=') && ( - { - setSearch({ - ...search, - tags: search.tags?.map((x) => { - return x.id === tag.id ? { ...x, value: v?.currentTarget?.value || '' } : x; - }), - }); - }} - placeholder={t('explore.span-filters-tags.placeholder-tag-value', 'Tag value')} - width={18} - value={tag.value || ''} - /> - )} - - {(tag.key || tag.value || search.tags.length > 1) && ( - removeTag(tag.id)} - tooltip={t('explore.span-filters-tags.tooltip-remove-tag', 'Remove tag')} - /> - )} - {(tag.key || tag.value) && i === search.tags.length - 1 && ( - - - - )} -
-
- ))} -
- ); -}; - -const getStyles = (theme: GrafanaTheme2) => ({ - addTag: css({ - marginLeft: theme.spacing(1), - }), - tagValues: css({ - maxWidth: '200px', - }), -}); diff --git a/public/app/features/explore/TraceView/components/TracePageHeader/useTraceAdHocFiltersController.ts b/public/app/features/explore/TraceView/components/TracePageHeader/useTraceAdHocFiltersController.ts index 8615c446294..c5286297087 100644 --- a/public/app/features/explore/TraceView/components/TracePageHeader/useTraceAdHocFiltersController.ts +++ b/public/app/features/explore/TraceView/components/TracePageHeader/useTraceAdHocFiltersController.ts @@ -1,18 +1,3 @@ -// Copyright (c) 2025 Grafana Labs -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - import { useMemo, useState } from 'react'; import { TraceSearchProps } from '@grafana/data'; diff --git a/public/app/features/explore/TraceView/components/constants/span.ts b/public/app/features/explore/TraceView/components/constants/span.ts index 8d2fb1e5052..2e4d04be71b 100644 --- a/public/app/features/explore/TraceView/components/constants/span.ts +++ b/public/app/features/explore/TraceView/components/constants/span.ts @@ -5,3 +5,5 @@ export const LIBRARY_NAME = 'library.name'; export const LIBRARY_VERSION = 'library.version'; export const TRACE_STATE = 'trace.state'; export const ID = 'id'; +export const SPAN_NAME = 'span.name'; +export const SERVICE_NAME = 'service.name'; diff --git a/public/app/features/explore/TraceView/components/utils/filter-spans.tsx b/public/app/features/explore/TraceView/components/utils/filter-spans.tsx index d03016dab78..69c42ee2e8f 100644 --- a/public/app/features/explore/TraceView/components/utils/filter-spans.tsx +++ b/public/app/features/explore/TraceView/components/utils/filter-spans.tsx @@ -16,7 +16,17 @@ import { SpanStatusCode } from '@opentelemetry/api'; import { SelectableValue, TraceKeyValuePair, TraceSearchProps, TraceSearchTag } from '@grafana/data'; -import { KIND, LIBRARY_NAME, LIBRARY_VERSION, STATUS, STATUS_MESSAGE, TRACE_STATE, ID } from '../constants/span'; +import { + KIND, + LIBRARY_NAME, + LIBRARY_VERSION, + STATUS, + STATUS_MESSAGE, + TRACE_STATE, + ID, + SPAN_NAME, + SERVICE_NAME, +} from '../constants/span'; import TNil from '../types/TNil'; import { TraceSpan, CriticalPathSection } from '../types/trace'; @@ -46,13 +56,13 @@ const getAdhocFilterMatches = (spans: TraceSpan[], adhocFilters: Array { // Check that adhoc filter was created expect(result.current.search.adhocFilters).toHaveLength(1); expect(result.current.search.adhocFilters?.[0]).toMatchObject({ - key: 'serviceName', + key: 'service.name', operator: '=', value: 'my-service', }); @@ -120,7 +120,7 @@ describe('useSearch', () => { // Check that adhoc filter was created expect(result.current.search.adhocFilters).toHaveLength(1); expect(result.current.search.adhocFilters?.[0]).toMatchObject({ - key: 'spanName', + key: 'span.name', operator: '!=', value: 'my-operation', }); @@ -195,13 +195,13 @@ describe('useSearch', () => { // Verify each filter const filters = result.current.search.adhocFilters || []; - expect(filters.find((f) => f.key === 'serviceName')).toMatchObject({ - key: 'serviceName', + expect(filters.find((f) => f.key === 'service.name')).toMatchObject({ + key: 'service.name', operator: '=', value: 'my-service', }); - expect(filters.find((f) => f.key === 'spanName')).toMatchObject({ - key: 'spanName', + expect(filters.find((f) => f.key === 'span.name')).toMatchObject({ + key: 'span.name', operator: '!=', value: 'my-operation', }); @@ -306,7 +306,7 @@ describe('useSearch', () => { expect(result.current.search.adhocFilters).toHaveLength(5); const filters = result.current.search.adhocFilters || []; - expect(filters.find((f) => f.key === 'serviceName')?.operator).toBe('!='); + expect(filters.find((f) => f.key === 'service.name')?.operator).toBe('!='); expect(filters.find((f) => f.key === 'tag1')?.operator).toBe('='); expect(filters.find((f) => f.key === 'tag2')?.operator).toBe('!='); expect(filters.find((f) => f.key === 'tag3')?.operator).toBe('=~'); diff --git a/public/app/features/explore/TraceView/useSearch.ts b/public/app/features/explore/TraceView/useSearch.ts index 9deb191a8d3..086866b87fb 100644 --- a/public/app/features/explore/TraceView/useSearch.ts +++ b/public/app/features/explore/TraceView/useSearch.ts @@ -7,6 +7,7 @@ import { useDispatch, useSelector } from 'app/types/store'; import { DEFAULT_SPAN_FILTERS, randomId } from '../state/constants'; import { changePanelState } from '../state/explorePane'; +import { SPAN_NAME, SERVICE_NAME } from './components/constants/span'; import { TraceSpan, CriticalPathSection } from './components/types/trace'; import { filterSpans } from './components/utils/filter-spans'; @@ -25,7 +26,7 @@ export function migrateToAdhocFilters(search: TraceSearchProps): TraceSearchProp // Migrate serviceName if (search.serviceName && search.serviceName.trim() !== '') { adhocFilters.push({ - key: 'serviceName', + key: SERVICE_NAME, operator: search.serviceNameOperator || '=', value: search.serviceName, }); @@ -34,7 +35,7 @@ export function migrateToAdhocFilters(search: TraceSearchProps): TraceSearchProp // Migrate spanName if (search.spanName && search.spanName.trim() !== '') { adhocFilters.push({ - key: 'spanName', + key: SPAN_NAME, operator: search.spanNameOperator || '=', value: search.spanName, }); diff --git a/public/app/features/explore/TraceView/utils/tags.ts b/public/app/features/explore/TraceView/utils/tags.ts index 293326e359e..38ae3c92148 100644 --- a/public/app/features/explore/TraceView/utils/tags.ts +++ b/public/app/features/explore/TraceView/utils/tags.ts @@ -9,6 +9,8 @@ import { STATUS, STATUS_MESSAGE, TRACE_STATE, + SPAN_NAME, + SERVICE_NAME, } from '../components/constants/span'; import { Trace } from '../components/types/trace'; @@ -37,6 +39,11 @@ export const getTraceTagKeys = (trace: Trace) => { span.process.tags.forEach((tag) => { keys.push(tag.key); }); + + if (span.process.serviceName) { + keys.push(SERVICE_NAME); + } + if (span.logs !== null) { span.logs.forEach((log) => { log.fields.forEach((field) => { @@ -63,6 +70,9 @@ export const getTraceTagKeys = (trace: Trace) => { if (span.traceState) { keys.push(TRACE_STATE); } + if (span.operationName) { + keys.push(SPAN_NAME); + } keys.push(ID); }); keys = uniq(keys).sort(); @@ -93,6 +103,11 @@ export const getTraceTagValues = (trace: Trace, key: string) => { } switch (key) { + case SPAN_NAME: + if (span.operationName) { + values.push(span.operationName); + } + break; case KIND: if (span.kind) { values.push(span.kind); diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index 8c7bc95d901..25ad73abe1a 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -7641,39 +7641,6 @@ }, "share-span": "Share" }, - "span-filters": { - "aria-label-select-max-span-operator": "Select max span operator", - "aria-label-select-min-span-operator": "Select min span operator", - "aria-label-select-service-name": "Select service name", - "aria-label-select-service-name-operator": "Select service name operator", - "aria-label-select-span-name": "Select span name", - "aria-label-select-span-name-operator": "Select span name operator", - "ariaLabel-select-max-span-duration": "Select max span duration", - "ariaLabel-select-min-span-duration": "Select min span duration", - "label-collapse": "Span Filters", - "label-duration": "Duration", - "label-service-name": "Service name", - "label-span-name": "Span name", - "label-tags": "Tags", - "placeholder-all-service-names": "All service names", - "placeholder-all-span-names": "All span names", - "tooltip-collapse": "Filter your spans below. You can continue to apply filters until you have narrowed down your resulting spans to the select few you are most interested in.", - "tooltip-duration": "Filter by duration. Accepted units are {{units}}", - "tooltip-tags": "Filter by tags, process tags or log fields in your spans." - }, - "span-filters-tags": { - "aria-label-add-tag": "Add tag", - "aria-label-input-tag-value": "Input tag value", - "aria-label-remove-tag": "Remove tag", - "aria-label-select-tag-key": "Select tag key", - "aria-label-select-tag-operator": "Select tag operator", - "aria-label-select-tag-value": "Select tag value", - "placeholder-select-tag": "Select tag", - "placeholder-select-value": "Select value", - "placeholder-tag-value": "Tag value", - "tooltip-add-tag": "Add tag", - "tooltip-remove-tag": "Remove tag" - }, "span-flame-graph": { "flame-graph": "Flame graph" }, From 90af2c3c3b6c28e66506e0e67f7acd77959efbe9 Mon Sep 17 00:00:00 2001 From: Rafael Bortolon Paulovic Date: Thu, 18 Dec 2025 14:11:47 +0100 Subject: [PATCH 12/36] fix(dashboard): panic on nil logger on dashboard accessor (#115545) fix(dashboard): fix panic on log --- pkg/registry/apis/dashboard/legacy/sql_dashboards.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/registry/apis/dashboard/legacy/sql_dashboards.go b/pkg/registry/apis/dashboard/legacy/sql_dashboards.go index 0a496e4667b..0b7ffa04ea4 100644 --- a/pkg/registry/apis/dashboard/legacy/sql_dashboards.go +++ b/pkg/registry/apis/dashboard/legacy/sql_dashboards.go @@ -113,6 +113,7 @@ func ProvideMigratorDashboardAccessor( dashboardPermissionSvc: nil, // not needed for migration libraryPanelSvc: nil, // not needed for migration accessControl: accessControl, + log: log.New("legacy.dashboard.migrator.accessor"), } } @@ -136,6 +137,7 @@ func NewDashboardSQLAccess(sql legacysql.LegacyDatabaseProvider, dashboardPermissionSvc: dashboardPermissionSvc, libraryPanelSvc: libraryPanelSvc, accessControl: accessControl, + log: log.New("legacy.dashboard.accessor"), } } From 14ef6ca4eb265bf19fddf669ab5d7c982116b00d Mon Sep 17 00:00:00 2001 From: Mariell Hoversholm Date: Thu, 18 Dec 2025 14:23:07 +0100 Subject: [PATCH 13/36] docs: remove SECURITY.md (#115549) --- .github/CODEOWNERS | 1 - SECURITY.md | 29 ----------------------------- 2 files changed, 30 deletions(-) delete mode 100644 SECURITY.md diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 7ef6be6644c..4cac8f6dd81 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -24,7 +24,6 @@ /NOTICE.md @torkelo /README.md @grafana/docs-grafana /ROADMAP.md @torkelo -/SECURITY.md @grafana/security-team /SUPPORT.md @torkelo /WORKFLOW.md @torkelo /contribute/ @grafana/grafana-community-support diff --git a/SECURITY.md b/SECURITY.md deleted file mode 100644 index 42c58f9cd67..00000000000 --- a/SECURITY.md +++ /dev/null @@ -1,29 +0,0 @@ -# Reporting security issues - -If you think you have found a security vulnerability, we have two routes for reporting security issues. - -Important: Whichever route you choose, we ask you to not disclose the vulnerability before it has been fixed and announced, unless you received a response from the Grafana Labs security team that you can do so. - -[Full guidance on reporting a security issue can be found here](https://grafana.com/legal/report-a-security-issue/). - -This product is in scope for our Bug Bounty Program. To submit a vulnerability report, please visit [Grafana Labs Bug Bounty page](https://app.intigriti.com/programs/grafanalabs/grafanaossbbp/detail) and follow the instructions provided. Our security team will review your submission and get back to you as soon as possible. - ---- - -For products and services outside the scope of our bug bounty program, or if you do not wish to receive a bounty, you can report issues directly to us via email at security@grafana.com. This address can be used for all of Grafana Labs’ open source and commercial products (including but not limited to Grafana, Grafana Cloud, Grafana Enterprise, and grafana.com). - -Please encrypt your message to us; please use our PGP key. The key fingerprint is: - -225E 6A9B BB15 A37E 95EB 6312 C66A 51CC B44C 27E0 - -The key is available from [keyserver.ubuntu.com](https://keyserver.ubuntu.com/pks/lookup?search=0x225E6A9BBB15A37E95EB6312C66A51CCB44C27E0&fingerprint=on&op=index). - -Grafana Labs will send you a response indicating the next steps in handling your report. After the initial reply to your report, the security team will keep you informed of the progress towards a fix and full announcement, and may ask for additional information or guidance. - -**Important:** We ask you to not disclose the vulnerability before it have been fixed and announced, unless you received a response from the Grafana Labs security team that you can do so. - -## Security announcements - -We will post a summary, remediation, and mitigation details for any patch containing security fixes on the Grafana blog. The security announcement blog posts will be tagged with the [security tag](https://grafana.com/tags/security/). - -You can also track security announcements via the [RSS feed](https://grafana.com/tags/security/index.xml). From 39fa6559ee01a5250b9fbc15c63ba63215af9bf3 Mon Sep 17 00:00:00 2001 From: Kevin Minehart <5140827+kminehart@users.noreply.github.com> Date: Thu, 18 Dec 2025 14:46:24 +0100 Subject: [PATCH 14/36] CI: Remove the default alpine & ubuntu versions so that the ones in Dockerfile (#115544) * Remove the default alpine & ubuntu versions so that the ones in Dockerfile are used * set default to just 'alpine' or 'ubuntu' * use defaults instead --- .github/actions/build-package/action.yml | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/.github/actions/build-package/action.yml b/.github/actions/build-package/action.yml index dd5aaa13650..978c6645240 100644 --- a/.github/actions/build-package/action.yml +++ b/.github/actions/build-package/action.yml @@ -82,14 +82,6 @@ inputs: description: Docker registry of produced images default: docker.io required: false - ubuntu-base: - type: string - default: 'ubuntu:22.04' - required: false - alpine-base: - type: string - default: 'alpine:3.22' - required: false outputs: dist-dir: description: Directory where artifacts are placed @@ -134,13 +126,11 @@ runs: UBUNTU_TAG_FORMAT: ${{ inputs.docker-tag-format-ubuntu }} CHECKSUM: ${{ inputs.checksum }} VERIFY: ${{ inputs.verify }} - ALPINE_BASE: ${{ inputs.alpine-base }} - UBUNTU_BASE: ${{ inputs.ubuntu-base }} with: verb: run dagger-flags: --verbose=0 version: 0.18.8 - args: go run -C ${GRAFANA_PATH} ./pkg/build/cmd artifacts --artifacts ${ARTIFACTS} --grafana-dir=${GRAFANA_PATH} --alpine-base=${ALPINE_BASE} --ubuntu-base=${UBUNTU_BASE} --enterprise-dir=${ENTERPRISE_PATH} --version=${VERSION} --patches-repo=${PATCHES_REPO} --patches-ref=${PATCHES_REF} --patches-path=${PATCHES_PATH} --build-id=${BUILD_ID} --tag-format="${TAG_FORMAT}" --ubuntu-tag-format="${UBUNTU_TAG_FORMAT}" --org=${DOCKER_ORG} --registry=${DOCKER_REGISTRY} --checksum=${CHECKSUM} --verify=${VERIFY} > $OUTFILE + args: go run -C ${GRAFANA_PATH} ./pkg/build/cmd artifacts --artifacts ${ARTIFACTS} --grafana-dir=${GRAFANA_PATH} --enterprise-dir=${ENTERPRISE_PATH} --version=${VERSION} --patches-repo=${PATCHES_REPO} --patches-ref=${PATCHES_REF} --patches-path=${PATCHES_PATH} --build-id=${BUILD_ID} --tag-format="${TAG_FORMAT}" --ubuntu-tag-format="${UBUNTU_TAG_FORMAT}" --org=${DOCKER_ORG} --registry=${DOCKER_REGISTRY} --checksum=${CHECKSUM} --verify=${VERIFY} > $OUTFILE - id: output shell: bash env: From 5c7cdabaa39c26b4024d114f565d05077f72fa39 Mon Sep 17 00:00:00 2001 From: Alexander Akhmetov Date: Thu, 18 Dec 2025 14:58:39 +0100 Subject: [PATCH 15/36] Alerting: Improve performance of rule list view with limit_alerts=0 (#115548) Alerting: Improve performance of rule list view --- .../ngalert/api/prometheus/api_prometheus.go | 38 ++++++++++--------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/pkg/services/ngalert/api/prometheus/api_prometheus.go b/pkg/services/ngalert/api/prometheus/api_prometheus.go index 934805d74f4..b4e14a66cfe 100644 --- a/pkg/services/ngalert/api/prometheus/api_prometheus.go +++ b/pkg/services/ngalert/api/prometheus/api_prometheus.go @@ -357,7 +357,7 @@ func (srv PrometheusSrv) RouteGetRuleStatuses(c *contextmodel.ReqContext) respon type RuleStatusMutator func(source *ngmodels.AlertRule, toMutate *apimodels.AlertingRule) // mutator function used to attach alert states to the rule and returns the totals and filtered totals -type RuleAlertStateMutator func(source *ngmodels.AlertRule, toMutate *apimodels.AlertingRule, stateFilterSet map[eval.State]struct{}, matchers labels.Matchers, labelOptions []ngmodels.LabelOption) (total map[string]int64, filteredTotal map[string]int64) +type RuleAlertStateMutator func(source *ngmodels.AlertRule, toMutate *apimodels.AlertingRule, stateFilterSet map[eval.State]struct{}, matchers labels.Matchers, labelOptions []ngmodels.LabelOption, limitAlerts int64) (total map[string]int64, filteredTotal map[string]int64) func RuleStatusMutatorGenerator(statusReader StatusReader) RuleStatusMutator { return func(source *ngmodels.AlertRule, toMutate *apimodels.AlertingRule) { @@ -377,32 +377,18 @@ func RuleStatusMutatorGenerator(statusReader StatusReader) RuleStatusMutator { } func RuleAlertStateMutatorGenerator(manager state.AlertInstanceManager) RuleAlertStateMutator { - return func(source *ngmodels.AlertRule, toMutate *apimodels.AlertingRule, stateFilterSet map[eval.State]struct{}, matchers labels.Matchers, labelOptions []ngmodels.LabelOption) (map[string]int64, map[string]int64) { + return func(source *ngmodels.AlertRule, toMutate *apimodels.AlertingRule, stateFilterSet map[eval.State]struct{}, matchers labels.Matchers, labelOptions []ngmodels.LabelOption, limitAlerts int64) (map[string]int64, map[string]int64) { states := manager.GetStatesForRuleUID(source.OrgID, source.UID) totals := make(map[string]int64) totalsFiltered := make(map[string]int64) for _, alertState := range states { activeAt := alertState.StartsAt - valString := "" - if alertState.State == eval.Alerting || alertState.State == eval.Pending || alertState.State == eval.Recovering { - valString = FormatValues(alertState) - } stateKey := strings.ToLower(alertState.State.String()) totals[stateKey] += 1 // Do not add error twice when execution error state is Error if alertState.Error != nil && source.ExecErrState != ngmodels.ErrorErrState { totals["error"] += 1 } - alert := apimodels.Alert{ - Labels: apimodels.LabelsFromMap(alertState.GetLabels(labelOptions...)), - Annotations: apimodels.LabelsFromMap(alertState.Annotations), - - // TODO: or should we make this two fields? Using one field lets the - // frontend use the same logic for parsing text on annotations and this. - State: state.FormatStateAndReason(alertState.State, alertState.StateReason), - ActiveAt: &activeAt, - Value: valString, - } // Set the state of the rule based on the state of its alerts. // Only update the rule state with 'pending' or 'recovering' if the current state is 'inactive'. @@ -442,7 +428,23 @@ func RuleAlertStateMutatorGenerator(manager state.AlertInstanceManager) RuleAler totalsFiltered["error"] += 1 } - toMutate.Alerts = append(toMutate.Alerts, alert) + if limitAlerts != 0 { + valString := "" + if alertState.State == eval.Alerting || alertState.State == eval.Pending || alertState.State == eval.Recovering { + valString = FormatValues(alertState) + } + + toMutate.Alerts = append(toMutate.Alerts, apimodels.Alert{ + Labels: apimodels.LabelsFromMap(alertState.GetLabels(labelOptions...)), + Annotations: apimodels.LabelsFromMap(alertState.Annotations), + + // TODO: or should we make this two fields? Using one field lets the + // frontend use the same logic for parsing text on annotations and this. + State: state.FormatStateAndReason(alertState.State, alertState.StateReason), + ActiveAt: &activeAt, + Value: valString, + }) + } } return totals, totalsFiltered } @@ -1227,7 +1229,7 @@ func toRuleGroup(log log.Logger, groupKey ngmodels.AlertRuleGroupKey, folderFull } // mutate rule for alert states - totals, totalsFiltered := ruleAlertStateMutator(rule, &alertingRule, stateFilterSet, matchers, labelOptions) + totals, totalsFiltered := ruleAlertStateMutator(rule, &alertingRule, stateFilterSet, matchers, labelOptions, limitAlerts) if alertingRule.State != "" { rulesTotals[alertingRule.State] += 1 From 4fbcebac2c78c48b4338881b42b5c39db7c8b9c5 Mon Sep 17 00:00:00 2001 From: "Marc M." <146180665+grafakus@users.noreply.github.com> Date: Thu, 18 Dec 2025 15:01:04 +0100 Subject: [PATCH 16/36] Deps: Upgrade Scenes to v6.51.0 (#115547) Scenes: Upgrade to v6.51.0 --- package.json | 4 ++-- yarn.lock | 22 +++++++++++----------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index f2300869740..37dc6ff745c 100644 --- a/package.json +++ b/package.json @@ -295,8 +295,8 @@ "@grafana/plugin-ui": "^0.11.1", "@grafana/prometheus": "workspace:*", "@grafana/runtime": "workspace:*", - "@grafana/scenes": "6.50.0", - "@grafana/scenes-react": "6.50.0", + "@grafana/scenes": "^6.51.0", + "@grafana/scenes-react": "^6.51.0", "@grafana/schema": "workspace:*", "@grafana/sql": "workspace:*", "@grafana/ui": "workspace:*", diff --git a/yarn.lock b/yarn.lock index e20676b04f0..a60a9c08912 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3604,11 +3604,11 @@ __metadata: languageName: unknown linkType: soft -"@grafana/scenes-react@npm:6.50.0": - version: 6.50.0 - resolution: "@grafana/scenes-react@npm:6.50.0" +"@grafana/scenes-react@npm:^6.51.0": + version: 6.51.0 + resolution: "@grafana/scenes-react@npm:6.51.0" dependencies: - "@grafana/scenes": "npm:6.50.0" + "@grafana/scenes": "npm:6.51.0" lru-cache: "npm:^10.2.2" react-use: "npm:^17.4.0" peerDependencies: @@ -3620,7 +3620,7 @@ __metadata: react: ^18.0.0 react-dom: ^18.0.0 react-router-dom: ^6.28.0 - checksum: 10/9ac9f8a32699f447c7b67dd2aef4e3ca5bc9fc98e94e0dc139e7824274ffa005b7fb3fc42ca5e55bdf89b91e3af0d3807b03e1a261db91c65717ee1763e5e807 + checksum: 10/14acdfe5220e67e7450780320b779e2e4a255995d55f0c82eb0d25933e72598e54826df0a8beee05591efe01a91ddab840483fea3bb828bd5925c3f0b44b8d17 languageName: node linkType: hard @@ -3650,9 +3650,9 @@ __metadata: languageName: node linkType: hard -"@grafana/scenes@npm:6.50.0": - version: 6.50.0 - resolution: "@grafana/scenes@npm:6.50.0" +"@grafana/scenes@npm:6.51.0, @grafana/scenes@npm:^6.51.0": + version: 6.51.0 + resolution: "@grafana/scenes@npm:6.51.0" dependencies: "@floating-ui/react": "npm:^0.26.16" "@leeoniya/ufuzzy": "npm:^1.0.16" @@ -3672,7 +3672,7 @@ __metadata: react: ^18.0.0 react-dom: ^18.0.0 react-router-dom: ^6.28.0 - checksum: 10/7bc6280ff065bbba37f010e2a1f0a7dc998fe43721ddc0121e27a754c41e824b82a44222100282a69143a52061cf0dce39e6bc8b95292ca444a59c114d4b5a41 + checksum: 10/4e4f43babe786ff729d58b7636182df57c58ce40c13b56036f725c070e0cf597cbe52aaa0f811184b8d42d8d1f9a32679695471d410f883051b09da44f8bf36a languageName: node linkType: hard @@ -19508,8 +19508,8 @@ __metadata: "@grafana/plugin-ui": "npm:^0.11.1" "@grafana/prometheus": "workspace:*" "@grafana/runtime": "workspace:*" - "@grafana/scenes": "npm:6.50.0" - "@grafana/scenes-react": "npm:6.50.0" + "@grafana/scenes": "npm:^6.51.0" + "@grafana/scenes-react": "npm:^6.51.0" "@grafana/schema": "workspace:*" "@grafana/sql": "workspace:*" "@grafana/test-utils": "workspace:*" From f1b19dd9faccc5e868c18c5ae7def8cea7438dd8 Mon Sep 17 00:00:00 2001 From: Andreas Christou Date: Thu, 18 Dec 2025 15:38:15 +0100 Subject: [PATCH 17/36] ElasticSearch: Update annotation time-range properties (#115500) Update time-range properties --- .../plugins/datasource/elasticsearch/datasource.test.ts | 6 +++--- public/app/plugins/datasource/elasticsearch/datasource.ts | 8 ++++---- public/app/plugins/datasource/elasticsearch/types.ts | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/public/app/plugins/datasource/elasticsearch/datasource.test.ts b/public/app/plugins/datasource/elasticsearch/datasource.test.ts index 33c3486bd31..6e3c7b2c2ce 100644 --- a/public/app/plugins/datasource/elasticsearch/datasource.test.ts +++ b/public/app/plugins/datasource/elasticsearch/datasource.test.ts @@ -1000,7 +1000,7 @@ describe('ElasticDatasource', () => { }); expect(postResourceRequestMock).toHaveBeenCalledWith( '_msearch', - '{"search_type":"query_then_fetch","ignore_unavailable":true,"index":"[test-]YYYY.MM.DD"}\n{"query":{"bool":{"filter":[{"bool":{"should":[{"range":{"@test_time":{"from":1683291160012,"to":1683291460012,"format":"epoch_millis"}}},{"range":{"@time_end_field":{"from":1683291160012,"to":1683291460012,"format":"epoch_millis"}}}],"minimum_should_match":1}},{"query_string":{"query":"abc"}}]}},"size":10000}\n' + '{"search_type":"query_then_fetch","ignore_unavailable":true,"index":"[test-]YYYY.MM.DD"}\n{"query":{"bool":{"filter":[{"bool":{"should":[{"range":{"@test_time":{"gte":1683291160012,"lte":1683291460012,"format":"epoch_millis"}}},{"range":{"@time_end_field":{"gte":1683291160012,"lte":1683291460012,"format":"epoch_millis"}}}],"minimum_should_match":1}},{"query_string":{"query":"abc"}}]}},"size":10000}\n' ); }); @@ -1030,7 +1030,7 @@ describe('ElasticDatasource', () => { }); expect(postResourceRequestMock).toHaveBeenCalledWith( '_msearch', - '{"search_type":"query_then_fetch","ignore_unavailable":true,"index":"[test-]YYYY.MM.DD"}\n{"query":{"bool":{"filter":[{"bool":{"should":[{"range":{"@timestamp":{"from":1683291160012,"to":1683291460012,"format":"epoch_millis"}}}],"minimum_should_match":1}}]}},"size":10000}\n' + '{"search_type":"query_then_fetch","ignore_unavailable":true,"index":"[test-]YYYY.MM.DD"}\n{"query":{"bool":{"filter":[{"bool":{"should":[{"range":{"@timestamp":{"gte":1683291160012,"lte":1683291460012,"format":"epoch_millis"}}}],"minimum_should_match":1}}]}},"size":10000}\n' ); }); @@ -1087,7 +1087,7 @@ describe('ElasticDatasource', () => { }); expect(postResourceRequestMock).toHaveBeenCalledWith( '_msearch', - '{"search_type":"query_then_fetch","ignore_unavailable":true,"index":"[test-]YYYY.MM.DD"}\n{"query":{"bool":{"filter":[{"bool":{"should":[{"range":{"@test_time":{"from":1683291160012,"to":1683291460012,"format":"epoch_millis"}}},{"range":{"@time_end_field":{"from":1683291160012,"to":1683291460012,"format":"epoch_millis"}}}],"minimum_should_match":1}},{"query_string":{"query":"abc AND abc_key:\\"abc_value\\""}}]}},"size":10000}\n' + '{"search_type":"query_then_fetch","ignore_unavailable":true,"index":"[test-]YYYY.MM.DD"}\n{"query":{"bool":{"filter":[{"bool":{"should":[{"range":{"@test_time":{"gte":1683291160012,"lte":1683291460012,"format":"epoch_millis"}}},{"range":{"@time_end_field":{"gte":1683291160012,"lte":1683291460012,"format":"epoch_millis"}}}],"minimum_should_match":1}},{"query_string":{"query":"abc AND abc_key:\\"abc_value\\""}}]}},"size":10000}\n' ); }); }); diff --git a/public/app/plugins/datasource/elasticsearch/datasource.ts b/public/app/plugins/datasource/elasticsearch/datasource.ts index d9294e5e62e..95e329e03ea 100644 --- a/public/app/plugins/datasource/elasticsearch/datasource.ts +++ b/public/app/plugins/datasource/elasticsearch/datasource.ts @@ -294,8 +294,8 @@ export class ElasticDatasource const dateRanges = []; const rangeStart: RangeMap = {}; rangeStart[timeField] = { - from: options.range.from.valueOf(), - to: options.range.to.valueOf(), + gte: options.range.from.valueOf(), + lte: options.range.to.valueOf(), format: 'epoch_millis', }; dateRanges.push({ range: rangeStart }); @@ -303,8 +303,8 @@ export class ElasticDatasource if (timeEndField) { const rangeEnd: RangeMap = {}; rangeEnd[timeEndField] = { - from: options.range.from.valueOf(), - to: options.range.to.valueOf(), + gte: options.range.from.valueOf(), + lte: options.range.to.valueOf(), format: 'epoch_millis', }; dateRanges.push({ range: rangeEnd }); diff --git a/public/app/plugins/datasource/elasticsearch/types.ts b/public/app/plugins/datasource/elasticsearch/types.ts index c3a7c5f9da2..4645a2a824f 100644 --- a/public/app/plugins/datasource/elasticsearch/types.ts +++ b/public/app/plugins/datasource/elasticsearch/types.ts @@ -137,7 +137,7 @@ export interface ElasticsearchAnnotationQuery { index?: string; } -export type RangeMap = Record; +export type RangeMap = Record; export type ElasticsearchResponse = ElasticsearchResponseWithHits | ElasticsearchResponseWithAggregations; From 4bcd31b17acb8f6d8af55452078a914863077282 Mon Sep 17 00:00:00 2001 From: Kristina Demeshchik Date: Thu, 18 Dec 2025 09:59:26 -0500 Subject: [PATCH 18/36] Dashboard: change export dropdown placement in sidebar (#115515) Update export menu placement --- .../dashboard-scene/edit-pane/DashboardExportButton.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/features/dashboard-scene/edit-pane/DashboardExportButton.tsx b/public/app/features/dashboard-scene/edit-pane/DashboardExportButton.tsx index 35d8afbdb4d..e048a016868 100644 --- a/public/app/features/dashboard-scene/edit-pane/DashboardExportButton.tsx +++ b/public/app/features/dashboard-scene/edit-pane/DashboardExportButton.tsx @@ -18,7 +18,7 @@ const newExportButtonSelector = selectors.pages.Dashboard.DashNav.NewExportButto export function ShareExportDashboardButton({ dashboard }: Props) { return ( - } placement="left-end"> + } placement="left-start"> Date: Thu, 18 Dec 2025 09:59:58 -0500 Subject: [PATCH 19/36] Dashboards: Fix text panel content loss during v1 to v2 migration (#115496) * move content and mode properties to options level * move to angular section * Update comments * handle missing angular text panel * re-generate test files * angualr panels tests * fixing test * Update output files * Update output for dev dashboard * Spread options at the top panel level for migration * linting issue --------- Co-authored-by: Ivan Ortega --- .../migrations/v0alpha1.migrations.v42.json | 12 +- ...-v16.grid_layout_upgrade.v42.v2alpha1.json | 9 +- ...g-v16.grid_layout_upgrade.v42.v2beta1.json | 9 +- ...g-v2.panels-and-services.v42.v2alpha1.json | 15 +- ...ig-v2.panels-and-services.v42.v2beta1.json | 15 +- ...a1-mig-v24.table-angular.v42.v2alpha1.json | 24 +- ...ta1-mig-v24.table-angular.v42.v2beta1.json | 24 +- ...a1-mig-v26.text2_to_text.v42.v2alpha1.json | 10 +- ...ta1-mig-v26.text2_to_text.v42.v2beta1.json | 10 +- ...lpha1.testdata-datalinks.v42.v2alpha1.json | 10 +- ...alpha1.testdata-datalinks.v42.v2beta1.json | 10 +- ...sted-variables-drilldown.v42.v2alpha1.json | 10 +- ...ested-variables-drilldown.v42.v2beta1.json | 10 +- ...estdata-nested-variables.v42.v2alpha1.json | 10 +- ...testdata-nested-variables.v42.v2beta1.json | 10 +- .../v0alpha1.migrations.v42.v1beta1.json | 11 +- .../v0alpha1.migrations.v42.v2alpha1.json | 14 +- .../v0alpha1.migrations.v42.v2beta1.json | 14 +- .../v0alpha1.gauge_tests.v42.v2alpha1.json | 72 + .../v0alpha1.gauge_tests.v42.v2beta1.json | 72 + .../v0alpha1.graph_tests.v42.v2alpha1.json | 72 +- .../v0alpha1.graph_tests.v42.v2beta1.json | 72 +- .../v0alpha1.heatmap-legacy.v42.v2alpha1.json | 238 +- .../v0alpha1.heatmap-legacy.v42.v2beta1.json | 238 +- .../v0alpha1.polystat_test.v42.v2alpha1.json | 3277 ++++++++++++++++- .../v0alpha1.polystat_test.v42.v2beta1.json | 3277 ++++++++++++++++- ...beta1.v1beta1.all-panels.v42.v2alpha1.json | 37 +- ...1beta1.v1beta1.all-panels.v42.v2beta1.json | 37 +- ...ha1.v1beta1.v1beta1.home.v42.v2alpha1.json | 50 + ...pha1.v1beta1.v1beta1.home.v42.v2beta1.json | 50 + .../conversion/v1beta1_to_v2alpha1.go | 6 +- .../migrations/migrations.v42.json | 11 +- .../dev-dashboards/migrations/migrations.json | 12 +- eslint-suppressions.json | 5 - .../serialization/angularMigration.test.ts | 151 +- .../serialization/angularMigration.ts | 38 +- .../dashboard/api/ResponseTransformers.ts | 16 +- 37 files changed, 7835 insertions(+), 123 deletions(-) diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/migrations/v0alpha1.migrations.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/migrations/v0alpha1.migrations.v42.json index 89ce80876d2..e4e6f8831da 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/migrations/v0alpha1.migrations.v42.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/migrations/v0alpha1.migrations.v42.json @@ -300,15 +300,9 @@ "y": 0 }, "id": 6, - "options": { - "code": { - "language": "plaintext", - "showLineNumbers": false, - "showMiniMap": false - }, - "content": "# Graph panel \u003e\u003e Timeseries panel\n\nKnown issues:\n* hiding null/empty series\n* time regions", - "mode": "markdown" - }, + "options": {}, + "content": "# Graph panel \u003e\u003e Timeseries panel\n\nKnown issues:\n* hiding null/empty series\n* time regions", + "mode": "markdown", "pluginVersion": "11.0.0-pre", "targets": [ { diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v16.grid_layout_upgrade.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v16.grid_layout_upgrade.v42.v2alpha1.json index 7229c6df268..c4198fa24b9 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v16.grid_layout_upgrade.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v16.grid_layout_upgrade.v42.v2alpha1.json @@ -115,7 +115,14 @@ "kind": "logs", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "logs", + "originalOptions": { + "height": 100 + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v16.grid_layout_upgrade.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v16.grid_layout_upgrade.v42.v2beta1.json index efdbb91f745..578e9d707ad 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v16.grid_layout_upgrade.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v16.grid_layout_upgrade.v42.v2beta1.json @@ -120,7 +120,14 @@ "group": "logs", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "logs", + "originalOptions": { + "height": 100 + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v2.panels-and-services.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v2.panels-and-services.v42.v2alpha1.json index 57b7b24ed56..d30c3ce7923 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v2.panels-and-services.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v2.panels-and-services.v42.v2alpha1.json @@ -182,7 +182,20 @@ "kind": "table", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table", + "originalOptions": { + "grid": { + "max": 100, + "min": 0 + }, + "legend": true, + "y2_format": "bytes", + "y_format": "short" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v2.panels-and-services.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v2.panels-and-services.v42.v2beta1.json index 0143563e75b..6a02d64f5b2 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v2.panels-and-services.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v2.panels-and-services.v42.v2beta1.json @@ -189,7 +189,20 @@ "group": "table", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table", + "originalOptions": { + "grid": { + "max": 100, + "min": 0 + }, + "legend": true, + "y2_format": "bytes", + "y_format": "short" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v24.table-angular.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v24.table-angular.v42.v2alpha1.json index 4734298869d..6fa76798215 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v24.table-angular.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v24.table-angular.v42.v2alpha1.json @@ -435,7 +435,29 @@ "kind": "table", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table", + "originalOptions": { + "styles": [ + { + "colors": [ + "red", + "yellow", + "green" + ], + "pattern": "/.*/", + "thresholds": [ + "10", + "20" + ], + "unit": "short" + } + ], + "table": "table2" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v24.table-angular.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v24.table-angular.v42.v2beta1.json index 57979e9f551..9ae87fe0f32 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v24.table-angular.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v24.table-angular.v42.v2beta1.json @@ -449,7 +449,29 @@ "group": "table", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table", + "originalOptions": { + "styles": [ + { + "colors": [ + "red", + "yellow", + "green" + ], + "pattern": "/.*/", + "thresholds": [ + "10", + "20" + ], + "unit": "short" + } + ], + "table": "table2" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v26.text2_to_text.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v26.text2_to_text.v42.v2alpha1.json index d15f179ac31..c7298296064 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v26.text2_to_text.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v26.text2_to_text.v42.v2alpha1.json @@ -110,7 +110,15 @@ "kind": "text", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "text", + "originalOptions": { + "content": "# Angular Text Panel\n# $constant\n\nFor markdown syntax help: [commonmark.org/help](https://commonmark.org/help/)\n\n## $text\n\n", + "mode": "markdown" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v26.text2_to_text.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v26.text2_to_text.v42.v2beta1.json index 59aad2fdb0b..db2cfcb916e 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v26.text2_to_text.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v26.text2_to_text.v42.v2beta1.json @@ -115,7 +115,15 @@ "group": "text", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "text", + "originalOptions": { + "content": "# Angular Text Panel\n# $constant\n\nFor markdown syntax help: [commonmark.org/help](https://commonmark.org/help/)\n\n## $text\n\n", + "mode": "markdown" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-datalinks.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-datalinks.v42.v2alpha1.json index 6eb2f2493aa..4a9e24d85f6 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-datalinks.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-datalinks.v42.v2alpha1.json @@ -361,7 +361,15 @@ "kind": "text", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "text", + "originalOptions": { + "content": "## Data link variables overview\n\nThis dashboard presents variables that one can use when creating *data links*. All links redirect to this dashboard and this panel represents the values that were interpolated in the link that was clicked.\n\n\n#### Series variables\n1. **Name:** \u003cspan style=\"color: orange;\"\u003e$seriesName\u003c/span\u003e\n2. **label.datacenter:** \u003cspan style=\"color: orange;\"\u003e$labelDatacenter\u003c/span\u003e\n3. **label.datacenter.region:** \u003cspan style=\"color: orange;\"\u003e$labelDatacenterRegion\u003c/span\u003e\n\n#### Field variables\n1. **Name:** \u003cspan style=\"color: orange;\"\u003e$fieldName\u003c/span\u003e\n\n#### Value variables\n1. **Time:** \u003cspan style=\"color: orange;\"\u003e$valueTime\u003c/span\u003e\n2. **Numeric:** \u003cspan style=\"color: orange;\"\u003e$valueNumeric\u003c/span\u003e\n3. **Text:** \u003cspan style=\"color: orange;\"\u003e$valueText\u003c/span\u003e\n4. **Calc:** \u003cspan style=\"color: orange;\"\u003e$valueCalc\u003c/span\u003e\n\n", + "mode": "markdown" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-datalinks.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-datalinks.v42.v2beta1.json index 22814d61a1e..62b30aa6480 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-datalinks.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-datalinks.v42.v2beta1.json @@ -372,7 +372,15 @@ "group": "text", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "text", + "originalOptions": { + "content": "## Data link variables overview\n\nThis dashboard presents variables that one can use when creating *data links*. All links redirect to this dashboard and this panel represents the values that were interpolated in the link that was clicked.\n\n\n#### Series variables\n1. **Name:** \u003cspan style=\"color: orange;\"\u003e$seriesName\u003c/span\u003e\n2. **label.datacenter:** \u003cspan style=\"color: orange;\"\u003e$labelDatacenter\u003c/span\u003e\n3. **label.datacenter.region:** \u003cspan style=\"color: orange;\"\u003e$labelDatacenterRegion\u003c/span\u003e\n\n#### Field variables\n1. **Name:** \u003cspan style=\"color: orange;\"\u003e$fieldName\u003c/span\u003e\n\n#### Value variables\n1. **Time:** \u003cspan style=\"color: orange;\"\u003e$valueTime\u003c/span\u003e\n2. **Numeric:** \u003cspan style=\"color: orange;\"\u003e$valueNumeric\u003c/span\u003e\n3. **Text:** \u003cspan style=\"color: orange;\"\u003e$valueText\u003c/span\u003e\n4. **Calc:** \u003cspan style=\"color: orange;\"\u003e$valueCalc\u003c/span\u003e\n\n", + "mode": "markdown" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-nested-variables-drilldown.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-nested-variables-drilldown.v42.v2alpha1.json index a469d4020e5..f754cbd766a 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-nested-variables-drilldown.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-nested-variables-drilldown.v42.v2alpha1.json @@ -167,7 +167,15 @@ "kind": "text", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "text", + "originalOptions": { + "content": "## Data center = $datacenter\n\n### server = $server\n\n#### pod = $pod", + "mode": "markdown" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-nested-variables-drilldown.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-nested-variables-drilldown.v42.v2beta1.json index 0a46a76d48e..8a8cffdf465 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-nested-variables-drilldown.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-nested-variables-drilldown.v42.v2beta1.json @@ -174,7 +174,15 @@ "group": "text", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "text", + "originalOptions": { + "content": "## Data center = $datacenter\n\n### server = $server\n\n#### pod = $pod", + "mode": "markdown" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-nested-variables.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-nested-variables.v42.v2alpha1.json index ecc156114f4..89857905689 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-nested-variables.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-nested-variables.v42.v2alpha1.json @@ -273,7 +273,15 @@ "kind": "text", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "text", + "originalOptions": { + "content": "## Data center = $datacenter\n\n### server = $server\n\n#### pod = $pod", + "mode": "markdown" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-nested-variables.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-nested-variables.v42.v2beta1.json index 54d6e9efa1c..13320b47904 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-nested-variables.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-nested-variables.v42.v2beta1.json @@ -282,7 +282,15 @@ "group": "text", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "text", + "originalOptions": { + "content": "## Data center = $datacenter\n\n### server = $server\n\n#### pod = $pod", + "mode": "markdown" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/migrations/v0alpha1.migrations.v42.v1beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/migrations/v0alpha1.migrations.v42.v1beta1.json index 5ae52076acc..7542cf17652 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/migrations/v0alpha1.migrations.v42.v1beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/migrations/v0alpha1.migrations.v42.v1beta1.json @@ -296,6 +296,7 @@ } }, { + "content": "# Graph panel \u003e\u003e Timeseries panel\n\nKnown issues:\n* hiding null/empty series\n* time regions", "datasource": { "type": "grafana-testdata-datasource" }, @@ -306,15 +307,7 @@ "y": 0 }, "id": 6, - "options": { - "code": { - "language": "plaintext", - "showLineNumbers": false, - "showMiniMap": false - }, - "content": "# Graph panel \u003e\u003e Timeseries panel\n\nKnown issues:\n* hiding null/empty series\n* time regions", - "mode": "markdown" - }, + "mode": "markdown", "pluginVersion": "11.0.0-pre", "targets": [ { diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/migrations/v0alpha1.migrations.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/migrations/v0alpha1.migrations.v42.v2alpha1.json index 3c2a1a4f9d7..21d45e91f10 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/migrations/v0alpha1.migrations.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/migrations/v0alpha1.migrations.v42.v2alpha1.json @@ -1256,13 +1256,13 @@ "spec": { "pluginVersion": "11.0.0-pre", "options": { - "code": { - "language": "plaintext", - "showLineNumbers": false, - "showMiniMap": false - }, - "content": "# Graph panel \u003e\u003e Timeseries panel\n\nKnown issues:\n* hiding null/empty series\n* time regions", - "mode": "markdown" + "__angularMigration": { + "autoMigrateFrom": "text", + "originalOptions": { + "content": "# Graph panel \u003e\u003e Timeseries panel\n\nKnown issues:\n* hiding null/empty series\n* time regions", + "mode": "markdown" + } + } }, "fieldConfig": { "defaults": {}, diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/migrations/v0alpha1.migrations.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/migrations/v0alpha1.migrations.v42.v2beta1.json index c6a9186b9e0..ed7123f5e38 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/migrations/v0alpha1.migrations.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/migrations/v0alpha1.migrations.v42.v2beta1.json @@ -1301,13 +1301,13 @@ "version": "11.0.0-pre", "spec": { "options": { - "code": { - "language": "plaintext", - "showLineNumbers": false, - "showMiniMap": false - }, - "content": "# Graph panel \u003e\u003e Timeseries panel\n\nKnown issues:\n* hiding null/empty series\n* time regions", - "mode": "markdown" + "__angularMigration": { + "autoMigrateFrom": "text", + "originalOptions": { + "content": "# Graph panel \u003e\u003e Timeseries panel\n\nKnown issues:\n* hiding null/empty series\n* time regions", + "mode": "markdown" + } + } }, "fieldConfig": { "defaults": {}, diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests.v42.v2alpha1.json index 77874ee6291..fe4271d08e7 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests.v42.v2alpha1.json @@ -62,6 +62,12 @@ "spec": { "pluginVersion": "7.4.0-pre", "options": { + "__angularMigration": { + "autoMigrateFrom": "gauge", + "originalOptions": { + "nullPointMode": "null" + } + }, "baseColor": "#299c46", "reduceOptions": { "calcs": [ @@ -151,6 +157,12 @@ "spec": { "pluginVersion": "7.4.0-pre", "options": { + "__angularMigration": { + "autoMigrateFrom": "gauge", + "originalOptions": { + "nullPointMode": "null" + } + }, "baseColor": "#299c46", "reduceOptions": { "calcs": [ @@ -241,6 +253,12 @@ "spec": { "pluginVersion": "7.4.0-pre", "options": { + "__angularMigration": { + "autoMigrateFrom": "gauge", + "originalOptions": { + "nullPointMode": "null" + } + }, "baseColor": "#299c46", "reduceOptions": { "calcs": [ @@ -320,6 +338,12 @@ "spec": { "pluginVersion": "7.4.0-pre", "options": { + "__angularMigration": { + "autoMigrateFrom": "gauge", + "originalOptions": { + "nullPointMode": "null" + } + }, "baseColor": "#299c46", "reduceOptions": { "calcs": [ @@ -401,6 +425,12 @@ "spec": { "pluginVersion": "7.4.0-pre", "options": { + "__angularMigration": { + "autoMigrateFrom": "gauge", + "originalOptions": { + "nullPointMode": "null" + } + }, "baseColor": "#299c46", "reduceOptions": { "calcs": [ @@ -480,6 +510,12 @@ "spec": { "pluginVersion": "7.4.0-pre", "options": { + "__angularMigration": { + "autoMigrateFrom": "gauge", + "originalOptions": { + "nullPointMode": "null" + } + }, "baseColor": "#299c46", "reduceOptions": { "calcs": [ @@ -559,6 +595,12 @@ "spec": { "pluginVersion": "7.4.0-pre", "options": { + "__angularMigration": { + "autoMigrateFrom": "gauge", + "originalOptions": { + "nullPointMode": "null" + } + }, "baseColor": "#299c46", "reduceOptions": { "calcs": [ @@ -639,6 +681,12 @@ "spec": { "pluginVersion": "7.4.0-pre", "options": { + "__angularMigration": { + "autoMigrateFrom": "gauge", + "originalOptions": { + "nullPointMode": "null" + } + }, "baseColor": "#299c46", "reduceOptions": { "calcs": [ @@ -730,6 +778,12 @@ "spec": { "pluginVersion": "7.4.0-pre", "options": { + "__angularMigration": { + "autoMigrateFrom": "gauge", + "originalOptions": { + "nullPointMode": "null" + } + }, "baseColor": "#299c46", "reduceOptions": { "calcs": [ @@ -927,6 +981,12 @@ "spec": { "pluginVersion": "7.4.0-pre", "options": { + "__angularMigration": { + "autoMigrateFrom": "gauge", + "originalOptions": { + "nullPointMode": "null" + } + }, "baseColor": "#299c46", "reduceOptions": { "calcs": [ @@ -1006,6 +1066,12 @@ "spec": { "pluginVersion": "7.4.0-pre", "options": { + "__angularMigration": { + "autoMigrateFrom": "gauge", + "originalOptions": { + "nullPointMode": "null" + } + }, "baseColor": "#299c46", "reduceOptions": { "calcs": [ @@ -1085,6 +1151,12 @@ "spec": { "pluginVersion": "7.4.0-pre", "options": { + "__angularMigration": { + "autoMigrateFrom": "gauge", + "originalOptions": { + "nullPointMode": "null" + } + }, "baseColor": "#299c46", "reduceOptions": { "calcs": [ diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests.v42.v2beta1.json index 663abfbf74c..261cd5228db 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests.v42.v2beta1.json @@ -67,6 +67,12 @@ "version": "7.4.0-pre", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "gauge", + "originalOptions": { + "nullPointMode": "null" + } + }, "baseColor": "#299c46", "reduceOptions": { "calcs": [ @@ -159,6 +165,12 @@ "version": "7.4.0-pre", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "gauge", + "originalOptions": { + "nullPointMode": "null" + } + }, "baseColor": "#299c46", "reduceOptions": { "calcs": [ @@ -252,6 +264,12 @@ "version": "7.4.0-pre", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "gauge", + "originalOptions": { + "nullPointMode": "null" + } + }, "baseColor": "#299c46", "reduceOptions": { "calcs": [ @@ -334,6 +352,12 @@ "version": "7.4.0-pre", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "gauge", + "originalOptions": { + "nullPointMode": "null" + } + }, "baseColor": "#299c46", "reduceOptions": { "calcs": [ @@ -418,6 +442,12 @@ "version": "7.4.0-pre", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "gauge", + "originalOptions": { + "nullPointMode": "null" + } + }, "baseColor": "#299c46", "reduceOptions": { "calcs": [ @@ -500,6 +530,12 @@ "version": "7.4.0-pre", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "gauge", + "originalOptions": { + "nullPointMode": "null" + } + }, "baseColor": "#299c46", "reduceOptions": { "calcs": [ @@ -582,6 +618,12 @@ "version": "7.4.0-pre", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "gauge", + "originalOptions": { + "nullPointMode": "null" + } + }, "baseColor": "#299c46", "reduceOptions": { "calcs": [ @@ -665,6 +707,12 @@ "version": "7.4.0-pre", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "gauge", + "originalOptions": { + "nullPointMode": "null" + } + }, "baseColor": "#299c46", "reduceOptions": { "calcs": [ @@ -759,6 +807,12 @@ "version": "7.4.0-pre", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "gauge", + "originalOptions": { + "nullPointMode": "null" + } + }, "baseColor": "#299c46", "reduceOptions": { "calcs": [ @@ -961,6 +1015,12 @@ "version": "7.4.0-pre", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "gauge", + "originalOptions": { + "nullPointMode": "null" + } + }, "baseColor": "#299c46", "reduceOptions": { "calcs": [ @@ -1043,6 +1103,12 @@ "version": "7.4.0-pre", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "gauge", + "originalOptions": { + "nullPointMode": "null" + } + }, "baseColor": "#299c46", "reduceOptions": { "calcs": [ @@ -1125,6 +1191,12 @@ "version": "7.4.0-pre", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "gauge", + "originalOptions": { + "nullPointMode": "null" + } + }, "baseColor": "#299c46", "reduceOptions": { "calcs": [ diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph_tests.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph_tests.v42.v2alpha1.json index f93a54ad34d..fa1ae3a63d8 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph_tests.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph_tests.v42.v2alpha1.json @@ -412,7 +412,17 @@ "kind": "text", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "text", + "originalOptions": { + "content": "Should be a long line connecting the null region in the `connected` mode, and in zero it should just be a line with zero value at the null points. ", + "editable": true, + "error": false, + "mode": "markdown" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -456,7 +466,17 @@ "kind": "text", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "text", + "originalOptions": { + "content": "Stacking values on top of nulls, should treat the null values as zero. ", + "editable": true, + "error": false, + "mode": "markdown" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -500,7 +520,17 @@ "kind": "text", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "text", + "originalOptions": { + "content": "Stacking when all values are null should leave a gap in the graph", + "editable": true, + "error": false, + "mode": "markdown" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1681,7 +1711,17 @@ "kind": "text", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "text", + "originalOptions": { + "content": "Left is showing null between values for a normal line graph and staircase graph. Orphaned data points should be rendered as points", + "editable": true, + "error": false, + "mode": "markdown" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -2061,7 +2101,17 @@ "kind": "text", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "text", + "originalOptions": { + "content": "Just verify that the tooltip time has millisecond resolution ", + "editable": true, + "error": false, + "mode": "markdown" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -2105,7 +2155,17 @@ "kind": "text", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "text", + "originalOptions": { + "content": "Verify that axis labels look ok", + "editable": true, + "error": false, + "mode": "markdown" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph_tests.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph_tests.v42.v2beta1.json index d611243ea1d..5a580bbaa59 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph_tests.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph_tests.v42.v2beta1.json @@ -429,7 +429,17 @@ "group": "text", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "text", + "originalOptions": { + "content": "Should be a long line connecting the null region in the `connected` mode, and in zero it should just be a line with zero value at the null points. ", + "editable": true, + "error": false, + "mode": "markdown" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -475,7 +485,17 @@ "group": "text", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "text", + "originalOptions": { + "content": "Stacking values on top of nulls, should treat the null values as zero. ", + "editable": true, + "error": false, + "mode": "markdown" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -521,7 +541,17 @@ "group": "text", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "text", + "originalOptions": { + "content": "Stacking when all values are null should leave a gap in the graph", + "editable": true, + "error": false, + "mode": "markdown" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1779,7 +1809,17 @@ "group": "text", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "text", + "originalOptions": { + "content": "Left is showing null between values for a normal line graph and staircase graph. Orphaned data points should be rendered as points", + "editable": true, + "error": false, + "mode": "markdown" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -2172,7 +2212,17 @@ "group": "text", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "text", + "originalOptions": { + "content": "Just verify that the tooltip time has millisecond resolution ", + "editable": true, + "error": false, + "mode": "markdown" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -2218,7 +2268,17 @@ "group": "text", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "text", + "originalOptions": { + "content": "Verify that axis labels look ok", + "editable": true, + "error": false, + "mode": "markdown" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-heatmap/v0alpha1.heatmap-legacy.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-heatmap/v0alpha1.heatmap-legacy.v42.v2alpha1.json index 4873fe5fc74..8225aec6834 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-heatmap/v0alpha1.heatmap-legacy.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-heatmap/v0alpha1.heatmap-legacy.v42.v2alpha1.json @@ -74,7 +74,44 @@ "kind": "heatmap", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "heatmap", + "originalOptions": { + "cards": {}, + "color": { + "cardColor": "#b4ff00", + "colorScale": "sqrt", + "colorScheme": "interpolateViridis", + "exponent": 0.5, + "mode": "spectrum" + }, + "dataFormat": "timeseries", + "heatmap": {}, + "hideZeroBuckets": false, + "highlightCards": true, + "legend": { + "show": true + }, + "reverseYBuckets": false, + "tooltip": { + "show": true, + "showHistogram": true + }, + "tooltipDecimals": 4, + "xAxis": { + "show": true + }, + "yAxis": { + "decimals": 2, + "format": "areaM2", + "logBase": 1, + "show": true + }, + "yBucketBound": "auto" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -116,7 +153,46 @@ "kind": "heatmap", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "heatmap", + "originalOptions": { + "cards": { + "cardRound": 50 + }, + "color": { + "cardColor": "#1F60C4", + "colorScale": "sqrt", + "colorScheme": "interpolateOranges", + "exponent": 0.5, + "mode": "opacity" + }, + "dataFormat": "tsbuckets", + "heatmap": {}, + "hideZeroBuckets": false, + "highlightCards": true, + "legend": { + "show": true + }, + "reverseYBuckets": false, + "tooltip": { + "show": true, + "showHistogram": false + }, + "xAxis": { + "show": true + }, + "yAxis": { + "decimals": 1, + "format": "kwatt", + "logBase": 1, + "show": true, + "width": "100" + }, + "yBucketBound": "auto" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -158,7 +234,44 @@ "kind": "heatmap", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "heatmap", + "originalOptions": { + "cards": {}, + "color": { + "cardColor": "#1F60C4", + "colorScale": "sqrt", + "colorScheme": "interpolateOranges", + "exponent": 0.5, + "mode": "opacity" + }, + "dataFormat": "tsbuckets", + "heatmap": {}, + "hideZeroBuckets": false, + "highlightCards": true, + "legend": { + "show": true + }, + "reverseYBuckets": true, + "tooltip": { + "show": true, + "showHistogram": false + }, + "xAxis": { + "show": true + }, + "yAxis": { + "decimals": 1, + "format": "kwatt", + "logBase": 1, + "show": true, + "width": "100" + }, + "yBucketBound": "auto" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -204,7 +317,46 @@ "kind": "heatmap", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "heatmap", + "originalOptions": { + "cards": {}, + "color": { + "cardColor": "#b4ff00", + "colorScale": "sqrt", + "colorScheme": "interpolateViridis", + "exponent": 0.5, + "mode": "spectrum" + }, + "dataFormat": "timeseries", + "heatmap": {}, + "hideZeroBuckets": false, + "highlightCards": true, + "legend": { + "show": true + }, + "reverseYBuckets": false, + "tooltip": { + "show": true, + "showHistogram": true + }, + "tooltipDecimals": 4, + "xAxis": { + "show": true + }, + "yAxis": { + "decimals": 2, + "format": "areaM2", + "logBase": 1, + "max": "50", + "min": "20", + "show": true + }, + "yBucketBound": "auto" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -246,7 +398,44 @@ "kind": "heatmap", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "heatmap", + "originalOptions": { + "cards": {}, + "color": { + "cardColor": "#b4ff00", + "colorScale": "sqrt", + "colorScheme": "interpolateBuGn", + "exponent": 0.5, + "mode": "spectrum" + }, + "dataFormat": "timeseries", + "heatmap": {}, + "hideZeroBuckets": true, + "highlightCards": true, + "legend": { + "show": true + }, + "reverseYBuckets": false, + "tooltip": { + "show": true, + "showHistogram": true + }, + "xAxis": { + "show": true + }, + "xBucketNumber": 10, + "yAxis": { + "format": "short", + "logBase": 2, + "show": true, + "splitFactor": 2 + }, + "yBucketBound": "auto" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -288,7 +477,44 @@ "kind": "heatmap", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "heatmap", + "originalOptions": { + "cards": {}, + "color": { + "cardColor": "#b4ff00", + "colorScale": "sqrt", + "colorScheme": "interpolateBuGn", + "exponent": 0.5, + "mode": "spectrum" + }, + "dataFormat": "timeseries", + "heatmap": {}, + "hideZeroBuckets": true, + "highlightCards": true, + "legend": { + "show": true + }, + "reverseYBuckets": false, + "tooltip": { + "show": true, + "showHistogram": true + }, + "xAxis": { + "show": true + }, + "xBucketNumber": 10, + "yAxis": { + "format": "short", + "logBase": 10, + "show": true, + "splitFactor": 5 + }, + "yBucketBound": "auto" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-heatmap/v0alpha1.heatmap-legacy.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-heatmap/v0alpha1.heatmap-legacy.v42.v2beta1.json index 2e11457ec4d..d7ec2abb5e8 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-heatmap/v0alpha1.heatmap-legacy.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-heatmap/v0alpha1.heatmap-legacy.v42.v2beta1.json @@ -78,7 +78,44 @@ "group": "heatmap", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "heatmap", + "originalOptions": { + "cards": {}, + "color": { + "cardColor": "#b4ff00", + "colorScale": "sqrt", + "colorScheme": "interpolateViridis", + "exponent": 0.5, + "mode": "spectrum" + }, + "dataFormat": "timeseries", + "heatmap": {}, + "hideZeroBuckets": false, + "highlightCards": true, + "legend": { + "show": true + }, + "reverseYBuckets": false, + "tooltip": { + "show": true, + "showHistogram": true + }, + "tooltipDecimals": 4, + "xAxis": { + "show": true + }, + "yAxis": { + "decimals": 2, + "format": "areaM2", + "logBase": 1, + "show": true + }, + "yBucketBound": "auto" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -123,7 +160,46 @@ "group": "heatmap", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "heatmap", + "originalOptions": { + "cards": { + "cardRound": 50 + }, + "color": { + "cardColor": "#1F60C4", + "colorScale": "sqrt", + "colorScheme": "interpolateOranges", + "exponent": 0.5, + "mode": "opacity" + }, + "dataFormat": "tsbuckets", + "heatmap": {}, + "hideZeroBuckets": false, + "highlightCards": true, + "legend": { + "show": true + }, + "reverseYBuckets": false, + "tooltip": { + "show": true, + "showHistogram": false + }, + "xAxis": { + "show": true + }, + "yAxis": { + "decimals": 1, + "format": "kwatt", + "logBase": 1, + "show": true, + "width": "100" + }, + "yBucketBound": "auto" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -168,7 +244,44 @@ "group": "heatmap", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "heatmap", + "originalOptions": { + "cards": {}, + "color": { + "cardColor": "#1F60C4", + "colorScale": "sqrt", + "colorScheme": "interpolateOranges", + "exponent": 0.5, + "mode": "opacity" + }, + "dataFormat": "tsbuckets", + "heatmap": {}, + "hideZeroBuckets": false, + "highlightCards": true, + "legend": { + "show": true + }, + "reverseYBuckets": true, + "tooltip": { + "show": true, + "showHistogram": false + }, + "xAxis": { + "show": true + }, + "yAxis": { + "decimals": 1, + "format": "kwatt", + "logBase": 1, + "show": true, + "width": "100" + }, + "yBucketBound": "auto" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -216,7 +329,46 @@ "group": "heatmap", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "heatmap", + "originalOptions": { + "cards": {}, + "color": { + "cardColor": "#b4ff00", + "colorScale": "sqrt", + "colorScheme": "interpolateViridis", + "exponent": 0.5, + "mode": "spectrum" + }, + "dataFormat": "timeseries", + "heatmap": {}, + "hideZeroBuckets": false, + "highlightCards": true, + "legend": { + "show": true + }, + "reverseYBuckets": false, + "tooltip": { + "show": true, + "showHistogram": true + }, + "tooltipDecimals": 4, + "xAxis": { + "show": true + }, + "yAxis": { + "decimals": 2, + "format": "areaM2", + "logBase": 1, + "max": "50", + "min": "20", + "show": true + }, + "yBucketBound": "auto" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -261,7 +413,44 @@ "group": "heatmap", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "heatmap", + "originalOptions": { + "cards": {}, + "color": { + "cardColor": "#b4ff00", + "colorScale": "sqrt", + "colorScheme": "interpolateBuGn", + "exponent": 0.5, + "mode": "spectrum" + }, + "dataFormat": "timeseries", + "heatmap": {}, + "hideZeroBuckets": true, + "highlightCards": true, + "legend": { + "show": true + }, + "reverseYBuckets": false, + "tooltip": { + "show": true, + "showHistogram": true + }, + "xAxis": { + "show": true + }, + "xBucketNumber": 10, + "yAxis": { + "format": "short", + "logBase": 2, + "show": true, + "splitFactor": 2 + }, + "yBucketBound": "auto" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -306,7 +495,44 @@ "group": "heatmap", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "heatmap", + "originalOptions": { + "cards": {}, + "color": { + "cardColor": "#b4ff00", + "colorScale": "sqrt", + "colorScheme": "interpolateBuGn", + "exponent": 0.5, + "mode": "spectrum" + }, + "dataFormat": "timeseries", + "heatmap": {}, + "hideZeroBuckets": true, + "highlightCards": true, + "legend": { + "show": true + }, + "reverseYBuckets": false, + "tooltip": { + "show": true, + "showHistogram": true + }, + "xAxis": { + "show": true + }, + "xBucketNumber": 10, + "yAxis": { + "format": "short", + "logBase": 10, + "show": true, + "splitFactor": 5 + }, + "yBucketBound": "auto" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-polystat/v0alpha1.polystat_test.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-polystat/v0alpha1.polystat_test.v42.v2alpha1.json index 68c7c2b6529..636612934c4 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-polystat/v0alpha1.polystat_test.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-polystat/v0alpha1.polystat_test.v42.v2alpha1.json @@ -119,7 +119,1073 @@ "kind": "grafana-polystat-panel", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "grafana-polystat-panel", + "originalOptions": { + "animationModes": [ + { + "text": "Show All", + "value": "all" + }, + { + "text": "Show Triggered", + "value": "triggered" + } + ], + "colors": [ + "#299c46", + "rgba(237, 129, 40, 0.89)", + "#d44a3a" + ], + "d3DivId": "d3_svg_2", + "decimals": 2, + "displayModes": [ + { + "text": "Show All", + "value": "all" + }, + { + "text": "Show Triggered", + "value": "triggered" + } + ], + "fontSizes": [ + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 22, + 24, + 26, + 28, + 30, + 32, + 34, + 36, + 38, + 40, + 42, + 44, + 46, + 48, + 50, + 52, + 54, + 56, + 58, + 60, + 62, + 64, + 66, + 68, + 70 + ], + "fontTypes": [ + "Open Sans", + "Arial", + "Avant Garde", + "Bookman", + "Consolas", + "Courier", + "Courier New", + "Futura", + "Garamond", + "Helvetica", + "Palatino", + "Times", + "Times New Roman", + "Verdana" + ], + "format": "none", + "notcolors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "operatorName": "avg", + "operatorOptions": [ + { + "text": "Average", + "value": "avg" + }, + { + "text": "Count", + "value": "count" + }, + { + "text": "Current", + "value": "current" + }, + { + "text": "Delta", + "value": "delta" + }, + { + "text": "Difference", + "value": "diff" + }, + { + "text": "First", + "value": "first" + }, + { + "text": "Log Min", + "value": "logmin" + }, + { + "text": "Max", + "value": "max" + }, + { + "text": "Min", + "value": "min" + }, + { + "text": "Name", + "value": "name" + }, + { + "text": "Time of Last Point", + "value": "last_time" + }, + { + "text": "Time Step", + "value": "time_step" + }, + { + "text": "Total", + "value": "total" + } + ], + "polystat": { + "animationSpeed": 2500, + "columnAutoSize": true, + "columns": 1, + "defaultClickThrough": "", + "defaultClickThroughSanitize": true, + "displayLimit": 100, + "fontAutoScale": true, + "fontSize": 12, + "globalDisplayMode": "all", + "globalOperatorName": "avg", + "gradientEnabled": true, + "hexagonSortByDirection": "asc", + "hexagonSortByField": "name", + "maxMetrics": 0, + "polygonBorderColor": "black", + "polygonBorderSize": 2, + "radius": "", + "radiusAutoSize": true, + "rowAutoSize": true, + "rows": 1, + "shape": "hexagon_pointed_top", + "tooltipDisplayMode": "all", + "tooltipDisplayTextTriggeredEmpty": "OK", + "tooltipFontSize": 12, + "tooltipFontType": "Open Sans", + "tooltipPrimarySortDirection": "desc", + "tooltipPrimarySortField": "thresholdLevel", + "tooltipSecondarySortDirection": "desc", + "tooltipSecondarySortField": "value", + "tooltipTimestampEnabled": true + }, + "savedComposites": [], + "savedOverrides": [], + "shapes": [ + { + "text": "Hexagon Pointed Top", + "value": "hexagon_pointed_top" + }, + { + "text": "Hexagon Flat Top", + "value": "hexagon_flat_top" + }, + { + "text": "Circle", + "value": "circle" + }, + { + "text": "Cross", + "value": "cross" + }, + { + "text": "Diamond", + "value": "diamond" + }, + { + "text": "Square", + "value": "square" + }, + { + "text": "Star", + "value": "star" + }, + { + "text": "Triangle", + "value": "triangle" + }, + { + "text": "Wye", + "value": "wye" + } + ], + "sortDirections": [ + { + "text": "Ascending", + "value": "asc" + }, + { + "text": "Descending", + "value": "desc" + } + ], + "sortFields": [ + { + "text": "Name", + "value": "name" + }, + { + "text": "Threshold Level", + "value": "thresholdLevel" + }, + { + "text": "Value", + "value": "value" + } + ], + "svgContainer": {}, + "thresholdStates": [ + { + "text": "ok", + "value": 0 + }, + { + "text": "warning", + "value": 1 + }, + { + "text": "critical", + "value": 2 + }, + { + "text": "custom", + "value": 3 + } + ], + "unitFormats": [ + { + "submenu": [ + { + "text": "none", + "value": "none" + }, + { + "text": "short", + "value": "short" + }, + { + "text": "percent (0-100)", + "value": "percent" + }, + { + "text": "percent (0.0-1.0)", + "value": "percentunit" + }, + { + "text": "Humidity (%H)", + "value": "humidity" + }, + { + "text": "decibel", + "value": "dB" + }, + { + "text": "hexadecimal (0x)", + "value": "hex0x" + }, + { + "text": "hexadecimal", + "value": "hex" + }, + { + "text": "scientific notation", + "value": "sci" + }, + { + "text": "locale format", + "value": "locale" + } + ], + "text": "none" + }, + { + "submenu": [ + { + "text": "Dollars ($)", + "value": "currencyUSD" + }, + { + "text": "Pounds (£)", + "value": "currencyGBP" + }, + { + "text": "Euro (€)", + "value": "currencyEUR" + }, + { + "text": "Yen (¥)", + "value": "currencyJPY" + }, + { + "text": "Rubles (₽)", + "value": "currencyRUB" + }, + { + "text": "Hryvnias (₴)", + "value": "currencyUAH" + }, + { + "text": "Real (R$)", + "value": "currencyBRL" + }, + { + "text": "Danish Krone (kr)", + "value": "currencyDKK" + }, + { + "text": "Icelandic Króna (kr)", + "value": "currencyISK" + }, + { + "text": "Norwegian Krone (kr)", + "value": "currencyNOK" + }, + { + "text": "Swedish Krona (kr)", + "value": "currencySEK" + }, + { + "text": "Czech koruna (czk)", + "value": "currencyCZK" + }, + { + "text": "Swiss franc (CHF)", + "value": "currencyCHF" + }, + { + "text": "Polish Złoty (PLN)", + "value": "currencyPLN" + }, + { + "text": "Bitcoin (฿)", + "value": "currencyBTC" + }, + { + "text": "Vietnamese Dong (VND)", + "value": "currencyVND" + }, + { + "text": "Malaysian Ringgit (RM)", + "value": "currencyMYR" + }, + { + "text": "Bulgarian Lev (BGN)", + "value": "currencyBGN" + } + ], + "text": "currency" + }, + { + "submenu": [ + { + "text": "Hertz (1/s)", + "value": "hertz" + }, + { + "text": "nanoseconds (ns)", + "value": "ns" + }, + { + "text": "microseconds (µs)", + "value": "µs" + }, + { + "text": "milliseconds (ms)", + "value": "ms" + }, + { + "text": "seconds (s)", + "value": "s" + }, + { + "text": "minutes (m)", + "value": "m" + }, + { + "text": "hours (h)", + "value": "h" + }, + { + "text": "days (d)", + "value": "d" + }, + { + "text": "duration (ms)", + "value": "dtdurationms" + }, + { + "text": "duration (s)", + "value": "dtdurations" + }, + { + "text": "duration (hh:mm:ss)", + "value": "dthms" + }, + { + "text": "Timeticks (s/100)", + "value": "timeticks" + } + ], + "text": "time" + }, + { + "submenu": [ + { + "text": "YYYY-MM-DD HH:mm:ss", + "value": "dateTimeAsIso" + }, + { + "text": "DD/MM/YYYY h:mm:ss a", + "value": "dateTimeAsUS" + }, + { + "text": "From Now", + "value": "dateTimeFromNow" + } + ], + "text": "date \u0026 time" + }, + { + "submenu": [ + { + "text": "bits", + "value": "bits" + }, + { + "text": "bytes", + "value": "bytes" + }, + { + "text": "kibibytes", + "value": "kbytes" + }, + { + "text": "mebibytes", + "value": "mbytes" + }, + { + "text": "gibibytes", + "value": "gbytes" + } + ], + "text": "data (IEC)" + }, + { + "submenu": [ + { + "text": "bits", + "value": "decbits" + }, + { + "text": "bytes", + "value": "decbytes" + }, + { + "text": "kilobytes", + "value": "deckbytes" + }, + { + "text": "megabytes", + "value": "decmbytes" + }, + { + "text": "gigabytes", + "value": "decgbytes" + } + ], + "text": "data (Metric)" + }, + { + "submenu": [ + { + "text": "packets/sec", + "value": "pps" + }, + { + "text": "bits/sec", + "value": "bps" + }, + { + "text": "bytes/sec", + "value": "Bps" + }, + { + "text": "kilobits/sec", + "value": "Kbits" + }, + { + "text": "kilobytes/sec", + "value": "KBs" + }, + { + "text": "megabits/sec", + "value": "Mbits" + }, + { + "text": "megabytes/sec", + "value": "MBs" + }, + { + "text": "gigabytes/sec", + "value": "GBs" + }, + { + "text": "gigabits/sec", + "value": "Gbits" + } + ], + "text": "data rate" + }, + { + "submenu": [ + { + "text": "hashes/sec", + "value": "Hs" + }, + { + "text": "kilohashes/sec", + "value": "KHs" + }, + { + "text": "megahashes/sec", + "value": "MHs" + }, + { + "text": "gigahashes/sec", + "value": "GHs" + }, + { + "text": "terahashes/sec", + "value": "THs" + }, + { + "text": "petahashes/sec", + "value": "PHs" + }, + { + "text": "exahashes/sec", + "value": "EHs" + } + ], + "text": "hash rate" + }, + { + "submenu": [ + { + "text": "ops/sec (ops)", + "value": "ops" + }, + { + "text": "requests/sec (rps)", + "value": "reqps" + }, + { + "text": "reads/sec (rps)", + "value": "rps" + }, + { + "text": "writes/sec (wps)", + "value": "wps" + }, + { + "text": "I/O ops/sec (iops)", + "value": "iops" + }, + { + "text": "ops/min (opm)", + "value": "opm" + }, + { + "text": "requests/min (rpm)", + "value": "reqpm" + }, + { + "text": "reads/min (rpm)", + "value": "rpm" + }, + { + "text": "writes/min (wpm)", + "value": "wpm" + } + ], + "text": "throughput" + }, + { + "submenu": [ + { + "text": "millimetre (mm)", + "value": "lengthmm" + }, + { + "text": "meter (m)", + "value": "lengthm" + }, + { + "text": "inch (in)", + "value": "lengthin" + }, + { + "text": "feet (ft)", + "value": "lengthft" + }, + { + "text": "kilometer (km)", + "value": "lengthkm" + }, + { + "text": "mile (mi)", + "value": "lengthmi" + } + ], + "text": "length" + }, + { + "submenu": [ + { + "text": "Square Meters (m²)", + "value": "areaM2" + }, + { + "text": "Square Feet (ft²)", + "value": "areaF2" + }, + { + "text": "Square Miles (mi²)", + "value": "areaMI2" + } + ], + "text": "area" + }, + { + "submenu": [ + { + "text": "milligram (mg)", + "value": "massmg" + }, + { + "text": "gram (g)", + "value": "massg" + }, + { + "text": "pound (lb)", + "value": "masslb" + }, + { + "text": "kilogram (kg)", + "value": "masskg" + }, + { + "text": "metric ton (t)", + "value": "masst" + } + ], + "text": "mass" + }, + { + "submenu": [ + { + "text": "metres/second (m/s)", + "value": "velocityms" + }, + { + "text": "kilometers/hour (km/h)", + "value": "velocitykmh" + }, + { + "text": "miles/hour (mph)", + "value": "velocitymph" + }, + { + "text": "knot (kn)", + "value": "velocityknot" + } + ], + "text": "velocity" + }, + { + "submenu": [ + { + "text": "millilitre (mL)", + "value": "mlitre" + }, + { + "text": "litre (L)", + "value": "litre" + }, + { + "text": "cubic metre", + "value": "m3" + }, + { + "text": "Normal cubic metre", + "value": "Nm3" + }, + { + "text": "cubic decimetre", + "value": "dm3" + }, + { + "text": "gallons", + "value": "gallons" + } + ], + "text": "volume" + }, + { + "submenu": [ + { + "text": "Watt (W)", + "value": "watt" + }, + { + "text": "Kilowatt (kW)", + "value": "kwatt" + }, + { + "text": "Milliwatt (mW)", + "value": "mwatt" + }, + { + "text": "Watt per square metre (W/m²)", + "value": "Wm2" + }, + { + "text": "Volt-ampere (VA)", + "value": "voltamp" + }, + { + "text": "Kilovolt-ampere (kVA)", + "value": "kvoltamp" + }, + { + "text": "Volt-ampere reactive (var)", + "value": "voltampreact" + }, + { + "text": "Kilovolt-ampere reactive (kvar)", + "value": "kvoltampreact" + }, + { + "text": "Watt-hour (Wh)", + "value": "watth" + }, + { + "text": "Kilowatt-hour (kWh)", + "value": "kwatth" + }, + { + "text": "Kilowatt-min (kWm)", + "value": "kwattm" + }, + { + "text": "Joule (J)", + "value": "joule" + }, + { + "text": "Electron volt (eV)", + "value": "ev" + }, + { + "text": "Ampere (A)", + "value": "amp" + }, + { + "text": "Kiloampere (kA)", + "value": "kamp" + }, + { + "text": "Milliampere (mA)", + "value": "mamp" + }, + { + "text": "Volt (V)", + "value": "volt" + }, + { + "text": "Kilovolt (kV)", + "value": "kvolt" + }, + { + "text": "Millivolt (mV)", + "value": "mvolt" + }, + { + "text": "Decibel-milliwatt (dBm)", + "value": "dBm" + }, + { + "text": "Ohm (Ω)", + "value": "ohm" + }, + { + "text": "Lumens (Lm)", + "value": "lumens" + } + ], + "text": "energy" + }, + { + "submenu": [ + { + "text": "Celsius (°C)", + "value": "celsius" + }, + { + "text": "Fahrenheit (°F)", + "value": "fahrenheit" + }, + { + "text": "Kelvin (K)", + "value": "kelvin" + } + ], + "text": "temperature" + }, + { + "submenu": [ + { + "text": "Millibars", + "value": "pressurembar" + }, + { + "text": "Bars", + "value": "pressurebar" + }, + { + "text": "Kilobars", + "value": "pressurekbar" + }, + { + "text": "Hectopascals", + "value": "pressurehpa" + }, + { + "text": "Kilopascals", + "value": "pressurekpa" + }, + { + "text": "Inches of mercury", + "value": "pressurehg" + }, + { + "text": "PSI", + "value": "pressurepsi" + } + ], + "text": "pressure" + }, + { + "submenu": [ + { + "text": "Newton-meters (Nm)", + "value": "forceNm" + }, + { + "text": "Kilonewton-meters (kNm)", + "value": "forcekNm" + }, + { + "text": "Newtons (N)", + "value": "forceN" + }, + { + "text": "Kilonewtons (kN)", + "value": "forcekN" + } + ], + "text": "force" + }, + { + "submenu": [ + { + "text": "Gallons/min (gpm)", + "value": "flowgpm" + }, + { + "text": "Cubic meters/sec (cms)", + "value": "flowcms" + }, + { + "text": "Cubic feet/sec (cfs)", + "value": "flowcfs" + }, + { + "text": "Cubic feet/min (cfm)", + "value": "flowcfm" + }, + { + "text": "Litre/hour", + "value": "litreh" + }, + { + "text": "Litre/min (l/min)", + "value": "flowlpm" + }, + { + "text": "milliLitre/min (mL/min)", + "value": "flowmlpm" + } + ], + "text": "flow" + }, + { + "submenu": [ + { + "text": "Degrees (°)", + "value": "degree" + }, + { + "text": "Radians", + "value": "radian" + }, + { + "text": "Gradian", + "value": "grad" + } + ], + "text": "angle" + }, + { + "submenu": [ + { + "text": "Meters/sec²", + "value": "accMS2" + }, + { + "text": "Feet/sec²", + "value": "accFS2" + }, + { + "text": "G unit", + "value": "accG" + } + ], + "text": "acceleration" + }, + { + "submenu": [ + { + "text": "Becquerel (Bq)", + "value": "radbq" + }, + { + "text": "curie (Ci)", + "value": "radci" + }, + { + "text": "Gray (Gy)", + "value": "radgy" + }, + { + "text": "rad", + "value": "radrad" + }, + { + "text": "Sievert (Sv)", + "value": "radsv" + }, + { + "text": "rem", + "value": "radrem" + }, + { + "text": "Exposure (C/kg)", + "value": "radexpckg" + }, + { + "text": "roentgen (R)", + "value": "radr" + }, + { + "text": "Sievert/hour (Sv/h)", + "value": "radsvh" + } + ], + "text": "radiation" + }, + { + "submenu": [ + { + "text": "parts-per-million (ppm)", + "value": "ppm" + }, + { + "text": "parts-per-billion (ppb)", + "value": "conppb" + }, + { + "text": "nanogram per cubic metre (ng/m³)", + "value": "conngm3" + }, + { + "text": "nanogram per normal cubic metre (ng/Nm³)", + "value": "conngNm3" + }, + { + "text": "microgram per cubic metre (μg/m³)", + "value": "conμgm3" + }, + { + "text": "microgram per normal cubic metre (μg/Nm³)", + "value": "conμgNm3" + }, + { + "text": "milligram per cubic metre (mg/m³)", + "value": "conmgm3" + }, + { + "text": "milligram per normal cubic metre (mg/Nm³)", + "value": "conmgNm3" + }, + { + "text": "gram per cubic metre (g/m³)", + "value": "congm3" + }, + { + "text": "gram per normal cubic metre (g/Nm³)", + "value": "congNm3" + } + ], + "text": "concentration" + } + ] + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -228,7 +1294,1129 @@ "kind": "grafana-polystat-panel", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "grafana-polystat-panel", + "originalOptions": { + "animationModes": [ + { + "text": "Show All", + "value": "all" + }, + { + "text": "Show Triggered", + "value": "triggered" + } + ], + "colors": [ + "#299c46", + "rgba(237, 129, 40, 0.89)", + "#d44a3a" + ], + "d3DivId": "d3_svg_4", + "decimals": 2, + "displayModes": [ + { + "text": "Show All", + "value": "all" + }, + { + "text": "Show Triggered", + "value": "triggered" + } + ], + "fontSizes": [ + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 22, + 24, + 26, + 28, + 30, + 32, + 34, + 36, + 38, + 40, + 42, + 44, + 46, + 48, + 50, + 52, + 54, + 56, + 58, + 60, + 62, + 64, + 66, + 68, + 70 + ], + "fontTypes": [ + "Open Sans", + "Arial", + "Avant Garde", + "Bookman", + "Consolas", + "Courier", + "Courier New", + "Futura", + "Garamond", + "Helvetica", + "Palatino", + "Times", + "Times New Roman", + "Verdana" + ], + "format": "none", + "notcolors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "operatorName": "avg", + "operatorOptions": [ + { + "text": "Average", + "value": "avg" + }, + { + "text": "Count", + "value": "count" + }, + { + "text": "Current", + "value": "current" + }, + { + "text": "Delta", + "value": "delta" + }, + { + "text": "Difference", + "value": "diff" + }, + { + "text": "First", + "value": "first" + }, + { + "text": "Log Min", + "value": "logmin" + }, + { + "text": "Max", + "value": "max" + }, + { + "text": "Min", + "value": "min" + }, + { + "text": "Name", + "value": "name" + }, + { + "text": "Time of Last Point", + "value": "last_time" + }, + { + "text": "Time Step", + "value": "time_step" + }, + { + "text": "Total", + "value": "total" + } + ], + "polystat": { + "animationSpeed": 2500, + "columnAutoSize": true, + "columns": "", + "defaultClickThrough": "", + "defaultClickThroughSanitize": true, + "displayLimit": 100, + "fontAutoScale": true, + "fontSize": 12, + "globalDisplayMode": "all", + "globalOperatorName": "avg", + "gradientEnabled": true, + "hexagonSortByDirection": "asc", + "hexagonSortByField": "name", + "maxMetrics": 0, + "polygonBorderColor": "black", + "polygonBorderSize": 2, + "radius": "", + "radiusAutoSize": true, + "rowAutoSize": true, + "rows": "", + "shape": "hexagon_pointed_top", + "tooltipDisplayMode": "all", + "tooltipDisplayTextTriggeredEmpty": "OK", + "tooltipFontSize": 12, + "tooltipFontType": "Open Sans", + "tooltipPrimarySortDirection": "desc", + "tooltipPrimarySortField": "thresholdLevel", + "tooltipSecondarySortDirection": "desc", + "tooltipSecondarySortField": "value", + "tooltipTimestampEnabled": true + }, + "savedComposites": [], + "savedOverrides": [], + "shapes": [ + { + "text": "Hexagon Pointed Top", + "value": "hexagon_pointed_top" + }, + { + "text": "Hexagon Flat Top", + "value": "hexagon_flat_top" + }, + { + "text": "Circle", + "value": "circle" + }, + { + "text": "Cross", + "value": "cross" + }, + { + "text": "Diamond", + "value": "diamond" + }, + { + "text": "Square", + "value": "square" + }, + { + "text": "Star", + "value": "star" + }, + { + "text": "Triangle", + "value": "triangle" + }, + { + "text": "Wye", + "value": "wye" + } + ], + "sortDirections": [ + { + "text": "Ascending", + "value": "asc" + }, + { + "text": "Descending", + "value": "desc" + } + ], + "sortFields": [ + { + "text": "Name", + "value": "name" + }, + { + "text": "Threshold Level", + "value": "thresholdLevel" + }, + { + "text": "Value", + "value": "value" + } + ], + "svgContainer": {}, + "thresholdStates": [ + { + "text": "ok", + "value": 0 + }, + { + "text": "warning", + "value": 1 + }, + { + "text": "critical", + "value": 2 + }, + { + "text": "custom", + "value": 3 + } + ], + "unitFormats": [ + { + "submenu": [ + { + "text": "none", + "value": "none" + }, + { + "text": "short", + "value": "short" + }, + { + "text": "percent (0-100)", + "value": "percent" + }, + { + "text": "percent (0.0-1.0)", + "value": "percentunit" + }, + { + "text": "Humidity (%H)", + "value": "humidity" + }, + { + "text": "decibel", + "value": "dB" + }, + { + "text": "hexadecimal (0x)", + "value": "hex0x" + }, + { + "text": "hexadecimal", + "value": "hex" + }, + { + "text": "scientific notation", + "value": "sci" + }, + { + "text": "locale format", + "value": "locale" + } + ], + "text": "none" + }, + { + "submenu": [ + { + "text": "Dollars ($)", + "value": "currencyUSD" + }, + { + "text": "Pounds (£)", + "value": "currencyGBP" + }, + { + "text": "Euro (€)", + "value": "currencyEUR" + }, + { + "text": "Yen (¥)", + "value": "currencyJPY" + }, + { + "text": "Rubles (₽)", + "value": "currencyRUB" + }, + { + "text": "Hryvnias (₴)", + "value": "currencyUAH" + }, + { + "text": "Real (R$)", + "value": "currencyBRL" + }, + { + "text": "Danish Krone (kr)", + "value": "currencyDKK" + }, + { + "text": "Icelandic Króna (kr)", + "value": "currencyISK" + }, + { + "text": "Norwegian Krone (kr)", + "value": "currencyNOK" + }, + { + "text": "Swedish Krona (kr)", + "value": "currencySEK" + }, + { + "text": "Czech koruna (czk)", + "value": "currencyCZK" + }, + { + "text": "Swiss franc (CHF)", + "value": "currencyCHF" + }, + { + "text": "Polish Złoty (PLN)", + "value": "currencyPLN" + }, + { + "text": "Bitcoin (฿)", + "value": "currencyBTC" + }, + { + "text": "Milli Bitcoin (mBTC)", + "value": "currencymBTC" + }, + { + "text": "Micro Bitcoin (μBTC)", + "value": "currencyμBTC" + }, + { + "text": "Vietnamese Dong (VND)", + "value": "currencyVND" + }, + { + "text": "Turkish Lira (₺)", + "value": "currencyTRY" + }, + { + "text": "Malaysian Ringgit (RM)", + "value": "currencyMYR" + }, + { + "text": "CFP franc (XPF)", + "value": "currencyXPF" + }, + { + "text": "Bulgarian Lev (BGN)", + "value": "currencyBGN" + } + ], + "text": "currency" + }, + { + "submenu": [ + { + "text": "Hertz (1/s)", + "value": "hertz" + }, + { + "text": "nanoseconds (ns)", + "value": "ns" + }, + { + "text": "microseconds (µs)", + "value": "µs" + }, + { + "text": "milliseconds (ms)", + "value": "ms" + }, + { + "text": "seconds (s)", + "value": "s" + }, + { + "text": "minutes (m)", + "value": "m" + }, + { + "text": "hours (h)", + "value": "h" + }, + { + "text": "days (d)", + "value": "d" + }, + { + "text": "duration (ms)", + "value": "dtdurationms" + }, + { + "text": "duration (s)", + "value": "dtdurations" + }, + { + "text": "duration (hh:mm:ss)", + "value": "dthms" + }, + { + "text": "Timeticks (s/100)", + "value": "timeticks" + } + ], + "text": "time" + }, + { + "submenu": [ + { + "text": "YYYY-MM-DD HH:mm:ss", + "value": "dateTimeAsIso" + }, + { + "text": "DD/MM/YYYY h:mm:ss a", + "value": "dateTimeAsUS" + }, + { + "text": "From Now", + "value": "dateTimeFromNow" + } + ], + "text": "date \u0026 time" + }, + { + "submenu": [ + { + "text": "bits", + "value": "bits" + }, + { + "text": "bytes", + "value": "bytes" + }, + { + "text": "kibibytes", + "value": "kbytes" + }, + { + "text": "mebibytes", + "value": "mbytes" + }, + { + "text": "gibibytes", + "value": "gbytes" + } + ], + "text": "data (IEC)" + }, + { + "submenu": [ + { + "text": "bits", + "value": "decbits" + }, + { + "text": "bytes", + "value": "decbytes" + }, + { + "text": "kilobytes", + "value": "deckbytes" + }, + { + "text": "megabytes", + "value": "decmbytes" + }, + { + "text": "gigabytes", + "value": "decgbytes" + } + ], + "text": "data (Metric)" + }, + { + "submenu": [ + { + "text": "packets/sec", + "value": "pps" + }, + { + "text": "bits/sec", + "value": "bps" + }, + { + "text": "bytes/sec", + "value": "Bps" + }, + { + "text": "kilobits/sec", + "value": "Kbits" + }, + { + "text": "kilobytes/sec", + "value": "KBs" + }, + { + "text": "megabits/sec", + "value": "Mbits" + }, + { + "text": "megabytes/sec", + "value": "MBs" + }, + { + "text": "gigabytes/sec", + "value": "GBs" + }, + { + "text": "gigabits/sec", + "value": "Gbits" + } + ], + "text": "data rate" + }, + { + "submenu": [ + { + "text": "hashes/sec", + "value": "Hs" + }, + { + "text": "kilohashes/sec", + "value": "KHs" + }, + { + "text": "megahashes/sec", + "value": "MHs" + }, + { + "text": "gigahashes/sec", + "value": "GHs" + }, + { + "text": "terahashes/sec", + "value": "THs" + }, + { + "text": "petahashes/sec", + "value": "PHs" + }, + { + "text": "exahashes/sec", + "value": "EHs" + } + ], + "text": "hash rate" + }, + { + "submenu": [ + { + "text": "counts/sec (cps)", + "value": "cps" + }, + { + "text": "ops/sec (ops)", + "value": "ops" + }, + { + "text": "requests/sec (rps)", + "value": "reqps" + }, + { + "text": "reads/sec (rps)", + "value": "rps" + }, + { + "text": "writes/sec (wps)", + "value": "wps" + }, + { + "text": "I/O ops/sec (iops)", + "value": "iops" + }, + { + "text": "events/sec (eps)", + "value": "eps" + }, + { + "text": "messages/sec (mps)", + "value": "mps" + }, + { + "text": "records/sec (rps)", + "value": "recps" + }, + { + "text": "rows/sec (rps)", + "value": "rowsps" + }, + { + "text": "counts/min (cpm)", + "value": "cpm" + }, + { + "text": "ops/min (opm)", + "value": "opm" + }, + { + "text": "requests/min (rpm)", + "value": "reqpm" + }, + { + "text": "reads/min (rpm)", + "value": "rpm" + }, + { + "text": "writes/min (wpm)", + "value": "wpm" + }, + { + "text": "events/min (epm)", + "value": "epm" + }, + { + "text": "messages/min (mpm)", + "value": "mpm" + }, + { + "text": "records/min (rpm)", + "value": "recpm" + }, + { + "text": "rows/min (rpm)", + "value": "rowspm" + } + ], + "text": "throughput" + }, + { + "submenu": [ + { + "text": "millimetre (mm)", + "value": "lengthmm" + }, + { + "text": "meter (m)", + "value": "lengthm" + }, + { + "text": "inch (in)", + "value": "lengthin" + }, + { + "text": "feet (ft)", + "value": "lengthft" + }, + { + "text": "kilometer (km)", + "value": "lengthkm" + }, + { + "text": "mile (mi)", + "value": "lengthmi" + } + ], + "text": "length" + }, + { + "submenu": [ + { + "text": "Square Meters (m²)", + "value": "areaM2" + }, + { + "text": "Square Feet (ft²)", + "value": "areaF2" + }, + { + "text": "Square Miles (mi²)", + "value": "areaMI2" + } + ], + "text": "area" + }, + { + "submenu": [ + { + "text": "milligram (mg)", + "value": "massmg" + }, + { + "text": "gram (g)", + "value": "massg" + }, + { + "text": "pound (lb)", + "value": "masslb" + }, + { + "text": "kilogram (kg)", + "value": "masskg" + }, + { + "text": "metric ton (t)", + "value": "masst" + } + ], + "text": "mass" + }, + { + "submenu": [ + { + "text": "metres/second (m/s)", + "value": "velocityms" + }, + { + "text": "kilometers/hour (km/h)", + "value": "velocitykmh" + }, + { + "text": "miles/hour (mph)", + "value": "velocitymph" + }, + { + "text": "knot (kn)", + "value": "velocityknot" + } + ], + "text": "velocity" + }, + { + "submenu": [ + { + "text": "millilitre (mL)", + "value": "mlitre" + }, + { + "text": "litre (L)", + "value": "litre" + }, + { + "text": "cubic metre", + "value": "m3" + }, + { + "text": "Normal cubic metre", + "value": "Nm3" + }, + { + "text": "cubic decimetre", + "value": "dm3" + }, + { + "text": "gallons", + "value": "gallons" + } + ], + "text": "volume" + }, + { + "submenu": [ + { + "text": "Watt (W)", + "value": "watt" + }, + { + "text": "Kilowatt (kW)", + "value": "kwatt" + }, + { + "text": "Milliwatt (mW)", + "value": "mwatt" + }, + { + "text": "Watt per square metre (W/m²)", + "value": "Wm2" + }, + { + "text": "Volt-Ampere (VA)", + "value": "voltamp" + }, + { + "text": "Kilovolt-Ampere (kVA)", + "value": "kvoltamp" + }, + { + "text": "Volt-Ampere reactive (VAr)", + "value": "voltampreact" + }, + { + "text": "Kilovolt-Ampere reactive (kVAr)", + "value": "kvoltampreact" + }, + { + "text": "Watt-hour (Wh)", + "value": "watth" + }, + { + "text": "Kilowatt-hour (kWh)", + "value": "kwatth" + }, + { + "text": "Kilowatt-min (kWm)", + "value": "kwattm" + }, + { + "text": "Joule (J)", + "value": "joule" + }, + { + "text": "Electron volt (eV)", + "value": "ev" + }, + { + "text": "Ampere (A)", + "value": "amp" + }, + { + "text": "Kiloampere (kA)", + "value": "kamp" + }, + { + "text": "Milliampere (mA)", + "value": "mamp" + }, + { + "text": "Volt (V)", + "value": "volt" + }, + { + "text": "Kilovolt (kV)", + "value": "kvolt" + }, + { + "text": "Millivolt (mV)", + "value": "mvolt" + }, + { + "text": "Decibel-milliwatt (dBm)", + "value": "dBm" + }, + { + "text": "Ohm (Ω)", + "value": "ohm" + }, + { + "text": "Lumens (Lm)", + "value": "lumens" + } + ], + "text": "energy" + }, + { + "submenu": [ + { + "text": "Celsius (°C)", + "value": "celsius" + }, + { + "text": "Fahrenheit (°F)", + "value": "fahrenheit" + }, + { + "text": "Kelvin (K)", + "value": "kelvin" + } + ], + "text": "temperature" + }, + { + "submenu": [ + { + "text": "Millibars", + "value": "pressurembar" + }, + { + "text": "Bars", + "value": "pressurebar" + }, + { + "text": "Kilobars", + "value": "pressurekbar" + }, + { + "text": "Hectopascals", + "value": "pressurehpa" + }, + { + "text": "Kilopascals", + "value": "pressurekpa" + }, + { + "text": "Inches of mercury", + "value": "pressurehg" + }, + { + "text": "PSI", + "value": "pressurepsi" + } + ], + "text": "pressure" + }, + { + "submenu": [ + { + "text": "Newton-meters (Nm)", + "value": "forceNm" + }, + { + "text": "Kilonewton-meters (kNm)", + "value": "forcekNm" + }, + { + "text": "Newtons (N)", + "value": "forceN" + }, + { + "text": "Kilonewtons (kN)", + "value": "forcekN" + } + ], + "text": "force" + }, + { + "submenu": [ + { + "text": "Gallons/min (gpm)", + "value": "flowgpm" + }, + { + "text": "Cubic meters/sec (cms)", + "value": "flowcms" + }, + { + "text": "Cubic feet/sec (cfs)", + "value": "flowcfs" + }, + { + "text": "Cubic feet/min (cfm)", + "value": "flowcfm" + }, + { + "text": "Litre/hour", + "value": "litreh" + }, + { + "text": "Litre/min (l/min)", + "value": "flowlpm" + }, + { + "text": "milliLitre/min (mL/min)", + "value": "flowmlpm" + } + ], + "text": "flow" + }, + { + "submenu": [ + { + "text": "Degrees (°)", + "value": "degree" + }, + { + "text": "Radians", + "value": "radian" + }, + { + "text": "Gradian", + "value": "grad" + } + ], + "text": "angle" + }, + { + "submenu": [ + { + "text": "Meters/sec²", + "value": "accMS2" + }, + { + "text": "Feet/sec²", + "value": "accFS2" + }, + { + "text": "G unit", + "value": "accG" + } + ], + "text": "acceleration" + }, + { + "submenu": [ + { + "text": "Becquerel (Bq)", + "value": "radbq" + }, + { + "text": "curie (Ci)", + "value": "radci" + }, + { + "text": "Gray (Gy)", + "value": "radgy" + }, + { + "text": "rad", + "value": "radrad" + }, + { + "text": "Sievert (Sv)", + "value": "radsv" + }, + { + "text": "rem", + "value": "radrem" + }, + { + "text": "Exposure (C/kg)", + "value": "radexpckg" + }, + { + "text": "roentgen (R)", + "value": "radr" + }, + { + "text": "Sievert/hour (Sv/h)", + "value": "radsvh" + } + ], + "text": "radiation" + }, + { + "submenu": [ + { + "text": "parts-per-million (ppm)", + "value": "ppm" + }, + { + "text": "parts-per-billion (ppb)", + "value": "conppb" + }, + { + "text": "nanogram per cubic metre (ng/m³)", + "value": "conngm3" + }, + { + "text": "nanogram per normal cubic metre (ng/Nm³)", + "value": "conngNm3" + }, + { + "text": "microgram per cubic metre (μg/m³)", + "value": "conμgm3" + }, + { + "text": "microgram per normal cubic metre (μg/Nm³)", + "value": "conμgNm3" + }, + { + "text": "milligram per cubic metre (mg/m³)", + "value": "conmgm3" + }, + { + "text": "milligram per normal cubic metre (mg/Nm³)", + "value": "conmgNm3" + }, + { + "text": "gram per cubic metre (g/m³)", + "value": "congm3" + }, + { + "text": "gram per normal cubic metre (g/Nm³)", + "value": "congNm3" + } + ], + "text": "concentration" + } + ] + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -337,7 +2525,1090 @@ "kind": "grafana-polystat-panel", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "grafana-polystat-panel", + "originalOptions": { + "animationModes": [ + { + "text": "Show All", + "value": "all" + }, + { + "text": "Show Triggered", + "value": "triggered" + } + ], + "colors": [ + "#299c46", + "rgba(237, 129, 40, 0.89)", + "#d44a3a" + ], + "d3DivId": "d3_svg_5", + "decimals": 2, + "displayModes": [ + { + "text": "Show All", + "value": "all" + }, + { + "text": "Show Triggered", + "value": "triggered" + } + ], + "fontSizes": [ + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 22, + 24, + 26, + 28, + 30, + 32, + 34, + 36, + 38, + 40, + 42, + 44, + 46, + 48, + 50, + 52, + 54, + 56, + 58, + 60, + 62, + 64, + 66, + 68, + 70 + ], + "fontTypes": [ + "Open Sans", + "Arial", + "Avant Garde", + "Bookman", + "Consolas", + "Courier", + "Courier New", + "Futura", + "Garamond", + "Helvetica", + "Palatino", + "Times", + "Times New Roman", + "Verdana" + ], + "format": "none", + "notcolors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "operatorName": "avg", + "operatorOptions": [ + { + "text": "Average", + "value": "avg" + }, + { + "text": "Count", + "value": "count" + }, + { + "text": "Current", + "value": "current" + }, + { + "text": "Delta", + "value": "delta" + }, + { + "text": "Difference", + "value": "diff" + }, + { + "text": "First", + "value": "first" + }, + { + "text": "Log Min", + "value": "logmin" + }, + { + "text": "Max", + "value": "max" + }, + { + "text": "Min", + "value": "min" + }, + { + "text": "Name", + "value": "name" + }, + { + "text": "Time of Last Point", + "value": "last_time" + }, + { + "text": "Time Step", + "value": "time_step" + }, + { + "text": "Total", + "value": "total" + } + ], + "polystat": { + "animationSpeed": 2500, + "columnAutoSize": true, + "columns": "", + "defaultClickThrough": "", + "defaultClickThroughSanitize": true, + "displayLimit": 100, + "fontAutoScale": true, + "fontSize": 12, + "globalDisplayMode": "all", + "globalOperatorName": "avg", + "gradientEnabled": true, + "hexagonSortByDirection": "asc", + "hexagonSortByField": "name", + "maxMetrics": 0, + "polygonBorderColor": "black", + "polygonBorderSize": 2, + "radius": "", + "radiusAutoSize": true, + "rowAutoSize": true, + "rows": "", + "shape": "hexagon_pointed_top", + "tooltipDisplayMode": "all", + "tooltipDisplayTextTriggeredEmpty": "OK", + "tooltipFontSize": 12, + "tooltipFontType": "Open Sans", + "tooltipPrimarySortDirection": "desc", + "tooltipPrimarySortField": "thresholdLevel", + "tooltipSecondarySortDirection": "desc", + "tooltipSecondarySortField": "value", + "tooltipTimestampEnabled": true + }, + "savedComposites": [ + { + "animateMode": "all", + "clickThrough": "", + "compositeName": "comp", + "enabled": true, + "hideMembers": true, + "members": [ + { + "seriesName": "A-series" + }, + { + "seriesName": "B-series" + } + ], + "sanitizeURLEnabled": true, + "sanitizedURL": "", + "showName": true, + "showValue": true, + "thresholdLevel": 0 + } + ], + "savedOverrides": [], + "shapes": [ + { + "text": "Hexagon Pointed Top", + "value": "hexagon_pointed_top" + }, + { + "text": "Hexagon Flat Top", + "value": "hexagon_flat_top" + }, + { + "text": "Circle", + "value": "circle" + }, + { + "text": "Cross", + "value": "cross" + }, + { + "text": "Diamond", + "value": "diamond" + }, + { + "text": "Square", + "value": "square" + }, + { + "text": "Star", + "value": "star" + }, + { + "text": "Triangle", + "value": "triangle" + }, + { + "text": "Wye", + "value": "wye" + } + ], + "sortDirections": [ + { + "text": "Ascending", + "value": "asc" + }, + { + "text": "Descending", + "value": "desc" + } + ], + "sortFields": [ + { + "text": "Name", + "value": "name" + }, + { + "text": "Threshold Level", + "value": "thresholdLevel" + }, + { + "text": "Value", + "value": "value" + } + ], + "svgContainer": {}, + "thresholdStates": [ + { + "text": "ok", + "value": 0 + }, + { + "text": "warning", + "value": 1 + }, + { + "text": "critical", + "value": 2 + }, + { + "text": "custom", + "value": 3 + } + ], + "unitFormats": [ + { + "submenu": [ + { + "text": "none", + "value": "none" + }, + { + "text": "short", + "value": "short" + }, + { + "text": "percent (0-100)", + "value": "percent" + }, + { + "text": "percent (0.0-1.0)", + "value": "percentunit" + }, + { + "text": "Humidity (%H)", + "value": "humidity" + }, + { + "text": "decibel", + "value": "dB" + }, + { + "text": "hexadecimal (0x)", + "value": "hex0x" + }, + { + "text": "hexadecimal", + "value": "hex" + }, + { + "text": "scientific notation", + "value": "sci" + }, + { + "text": "locale format", + "value": "locale" + } + ], + "text": "none" + }, + { + "submenu": [ + { + "text": "Dollars ($)", + "value": "currencyUSD" + }, + { + "text": "Pounds (£)", + "value": "currencyGBP" + }, + { + "text": "Euro (€)", + "value": "currencyEUR" + }, + { + "text": "Yen (¥)", + "value": "currencyJPY" + }, + { + "text": "Rubles (₽)", + "value": "currencyRUB" + }, + { + "text": "Hryvnias (₴)", + "value": "currencyUAH" + }, + { + "text": "Real (R$)", + "value": "currencyBRL" + }, + { + "text": "Danish Krone (kr)", + "value": "currencyDKK" + }, + { + "text": "Icelandic Króna (kr)", + "value": "currencyISK" + }, + { + "text": "Norwegian Krone (kr)", + "value": "currencyNOK" + }, + { + "text": "Swedish Krona (kr)", + "value": "currencySEK" + }, + { + "text": "Czech koruna (czk)", + "value": "currencyCZK" + }, + { + "text": "Swiss franc (CHF)", + "value": "currencyCHF" + }, + { + "text": "Polish Złoty (PLN)", + "value": "currencyPLN" + }, + { + "text": "Bitcoin (฿)", + "value": "currencyBTC" + }, + { + "text": "Vietnamese Dong (VND)", + "value": "currencyVND" + }, + { + "text": "Malaysian Ringgit (RM)", + "value": "currencyMYR" + }, + { + "text": "Bulgarian Lev (BGN)", + "value": "currencyBGN" + } + ], + "text": "currency" + }, + { + "submenu": [ + { + "text": "Hertz (1/s)", + "value": "hertz" + }, + { + "text": "nanoseconds (ns)", + "value": "ns" + }, + { + "text": "microseconds (µs)", + "value": "µs" + }, + { + "text": "milliseconds (ms)", + "value": "ms" + }, + { + "text": "seconds (s)", + "value": "s" + }, + { + "text": "minutes (m)", + "value": "m" + }, + { + "text": "hours (h)", + "value": "h" + }, + { + "text": "days (d)", + "value": "d" + }, + { + "text": "duration (ms)", + "value": "dtdurationms" + }, + { + "text": "duration (s)", + "value": "dtdurations" + }, + { + "text": "duration (hh:mm:ss)", + "value": "dthms" + }, + { + "text": "Timeticks (s/100)", + "value": "timeticks" + } + ], + "text": "time" + }, + { + "submenu": [ + { + "text": "YYYY-MM-DD HH:mm:ss", + "value": "dateTimeAsIso" + }, + { + "text": "DD/MM/YYYY h:mm:ss a", + "value": "dateTimeAsUS" + }, + { + "text": "From Now", + "value": "dateTimeFromNow" + } + ], + "text": "date \u0026 time" + }, + { + "submenu": [ + { + "text": "bits", + "value": "bits" + }, + { + "text": "bytes", + "value": "bytes" + }, + { + "text": "kibibytes", + "value": "kbytes" + }, + { + "text": "mebibytes", + "value": "mbytes" + }, + { + "text": "gibibytes", + "value": "gbytes" + } + ], + "text": "data (IEC)" + }, + { + "submenu": [ + { + "text": "bits", + "value": "decbits" + }, + { + "text": "bytes", + "value": "decbytes" + }, + { + "text": "kilobytes", + "value": "deckbytes" + }, + { + "text": "megabytes", + "value": "decmbytes" + }, + { + "text": "gigabytes", + "value": "decgbytes" + } + ], + "text": "data (Metric)" + }, + { + "submenu": [ + { + "text": "packets/sec", + "value": "pps" + }, + { + "text": "bits/sec", + "value": "bps" + }, + { + "text": "bytes/sec", + "value": "Bps" + }, + { + "text": "kilobits/sec", + "value": "Kbits" + }, + { + "text": "kilobytes/sec", + "value": "KBs" + }, + { + "text": "megabits/sec", + "value": "Mbits" + }, + { + "text": "megabytes/sec", + "value": "MBs" + }, + { + "text": "gigabytes/sec", + "value": "GBs" + }, + { + "text": "gigabits/sec", + "value": "Gbits" + } + ], + "text": "data rate" + }, + { + "submenu": [ + { + "text": "hashes/sec", + "value": "Hs" + }, + { + "text": "kilohashes/sec", + "value": "KHs" + }, + { + "text": "megahashes/sec", + "value": "MHs" + }, + { + "text": "gigahashes/sec", + "value": "GHs" + }, + { + "text": "terahashes/sec", + "value": "THs" + }, + { + "text": "petahashes/sec", + "value": "PHs" + }, + { + "text": "exahashes/sec", + "value": "EHs" + } + ], + "text": "hash rate" + }, + { + "submenu": [ + { + "text": "ops/sec (ops)", + "value": "ops" + }, + { + "text": "requests/sec (rps)", + "value": "reqps" + }, + { + "text": "reads/sec (rps)", + "value": "rps" + }, + { + "text": "writes/sec (wps)", + "value": "wps" + }, + { + "text": "I/O ops/sec (iops)", + "value": "iops" + }, + { + "text": "ops/min (opm)", + "value": "opm" + }, + { + "text": "reads/min (rpm)", + "value": "rpm" + }, + { + "text": "writes/min (wpm)", + "value": "wpm" + } + ], + "text": "throughput" + }, + { + "submenu": [ + { + "text": "millimetre (mm)", + "value": "lengthmm" + }, + { + "text": "meter (m)", + "value": "lengthm" + }, + { + "text": "inch (in)", + "value": "lengthin" + }, + { + "text": "feet (ft)", + "value": "lengthft" + }, + { + "text": "kilometer (km)", + "value": "lengthkm" + }, + { + "text": "mile (mi)", + "value": "lengthmi" + } + ], + "text": "length" + }, + { + "submenu": [ + { + "text": "Square Meters (m²)", + "value": "areaM2" + }, + { + "text": "Square Feet (ft²)", + "value": "areaF2" + }, + { + "text": "Square Miles (mi²)", + "value": "areaMI2" + } + ], + "text": "area" + }, + { + "submenu": [ + { + "text": "milligram (mg)", + "value": "massmg" + }, + { + "text": "gram (g)", + "value": "massg" + }, + { + "text": "pound (lb)", + "value": "masslb" + }, + { + "text": "kilogram (kg)", + "value": "masskg" + }, + { + "text": "metric ton (t)", + "value": "masst" + } + ], + "text": "mass" + }, + { + "submenu": [ + { + "text": "metres/second (m/s)", + "value": "velocityms" + }, + { + "text": "kilometers/hour (km/h)", + "value": "velocitykmh" + }, + { + "text": "miles/hour (mph)", + "value": "velocitymph" + }, + { + "text": "knot (kn)", + "value": "velocityknot" + } + ], + "text": "velocity" + }, + { + "submenu": [ + { + "text": "millilitre (mL)", + "value": "mlitre" + }, + { + "text": "litre (L)", + "value": "litre" + }, + { + "text": "cubic metre", + "value": "m3" + }, + { + "text": "Normal cubic metre", + "value": "Nm3" + }, + { + "text": "cubic decimetre", + "value": "dm3" + }, + { + "text": "gallons", + "value": "gallons" + } + ], + "text": "volume" + }, + { + "submenu": [ + { + "text": "Watt (W)", + "value": "watt" + }, + { + "text": "Kilowatt (kW)", + "value": "kwatt" + }, + { + "text": "Milliwatt (mW)", + "value": "mwatt" + }, + { + "text": "Watt per square metre (W/m²)", + "value": "Wm2" + }, + { + "text": "Volt-ampere (VA)", + "value": "voltamp" + }, + { + "text": "Kilovolt-ampere (kVA)", + "value": "kvoltamp" + }, + { + "text": "Volt-ampere reactive (var)", + "value": "voltampreact" + }, + { + "text": "Kilovolt-ampere reactive (kvar)", + "value": "kvoltampreact" + }, + { + "text": "Watt-hour (Wh)", + "value": "watth" + }, + { + "text": "Kilowatt-hour (kWh)", + "value": "kwatth" + }, + { + "text": "Kilowatt-min (kWm)", + "value": "kwattm" + }, + { + "text": "Joule (J)", + "value": "joule" + }, + { + "text": "Electron volt (eV)", + "value": "ev" + }, + { + "text": "Ampere (A)", + "value": "amp" + }, + { + "text": "Kiloampere (kA)", + "value": "kamp" + }, + { + "text": "Milliampere (mA)", + "value": "mamp" + }, + { + "text": "Volt (V)", + "value": "volt" + }, + { + "text": "Kilovolt (kV)", + "value": "kvolt" + }, + { + "text": "Millivolt (mV)", + "value": "mvolt" + }, + { + "text": "Decibel-milliwatt (dBm)", + "value": "dBm" + }, + { + "text": "Ohm (Ω)", + "value": "ohm" + }, + { + "text": "Lumens (Lm)", + "value": "lumens" + } + ], + "text": "energy" + }, + { + "submenu": [ + { + "text": "Celsius (°C)", + "value": "celsius" + }, + { + "text": "Fahrenheit (°F)", + "value": "fahrenheit" + }, + { + "text": "Kelvin (K)", + "value": "kelvin" + } + ], + "text": "temperature" + }, + { + "submenu": [ + { + "text": "Millibars", + "value": "pressurembar" + }, + { + "text": "Bars", + "value": "pressurebar" + }, + { + "text": "Kilobars", + "value": "pressurekbar" + }, + { + "text": "Hectopascals", + "value": "pressurehpa" + }, + { + "text": "Kilopascals", + "value": "pressurekpa" + }, + { + "text": "Inches of mercury", + "value": "pressurehg" + }, + { + "text": "PSI", + "value": "pressurepsi" + } + ], + "text": "pressure" + }, + { + "submenu": [ + { + "text": "Newton-meters (Nm)", + "value": "forceNm" + }, + { + "text": "Kilonewton-meters (kNm)", + "value": "forcekNm" + }, + { + "text": "Newtons (N)", + "value": "forceN" + }, + { + "text": "Kilonewtons (kN)", + "value": "forcekN" + } + ], + "text": "force" + }, + { + "submenu": [ + { + "text": "Gallons/min (gpm)", + "value": "flowgpm" + }, + { + "text": "Cubic meters/sec (cms)", + "value": "flowcms" + }, + { + "text": "Cubic feet/sec (cfs)", + "value": "flowcfs" + }, + { + "text": "Cubic feet/min (cfm)", + "value": "flowcfm" + }, + { + "text": "Litre/hour", + "value": "litreh" + }, + { + "text": "Litre/min (l/min)", + "value": "flowlpm" + }, + { + "text": "milliLitre/min (mL/min)", + "value": "flowmlpm" + } + ], + "text": "flow" + }, + { + "submenu": [ + { + "text": "Degrees (°)", + "value": "degree" + }, + { + "text": "Radians", + "value": "radian" + }, + { + "text": "Gradian", + "value": "grad" + } + ], + "text": "angle" + }, + { + "submenu": [ + { + "text": "Meters/sec²", + "value": "accMS2" + }, + { + "text": "Feet/sec²", + "value": "accFS2" + }, + { + "text": "G unit", + "value": "accG" + } + ], + "text": "acceleration" + }, + { + "submenu": [ + { + "text": "Becquerel (Bq)", + "value": "radbq" + }, + { + "text": "curie (Ci)", + "value": "radci" + }, + { + "text": "Gray (Gy)", + "value": "radgy" + }, + { + "text": "rad", + "value": "radrad" + }, + { + "text": "Sievert (Sv)", + "value": "radsv" + }, + { + "text": "rem", + "value": "radrem" + }, + { + "text": "Exposure (C/kg)", + "value": "radexpckg" + }, + { + "text": "roentgen (R)", + "value": "radr" + }, + { + "text": "Sievert/hour (Sv/h)", + "value": "radsvh" + } + ], + "text": "radiation" + }, + { + "submenu": [ + { + "text": "parts-per-million (ppm)", + "value": "ppm" + }, + { + "text": "parts-per-billion (ppb)", + "value": "conppb" + }, + { + "text": "nanogram per cubic metre (ng/m³)", + "value": "conngm3" + }, + { + "text": "nanogram per normal cubic metre (ng/Nm³)", + "value": "conngNm3" + }, + { + "text": "microgram per cubic metre (μg/m³)", + "value": "conμgm3" + }, + { + "text": "microgram per normal cubic metre (μg/Nm³)", + "value": "conμgNm3" + }, + { + "text": "milligram per cubic metre (mg/m³)", + "value": "conmgm3" + }, + { + "text": "milligram per normal cubic metre (mg/Nm³)", + "value": "conmgNm3" + }, + { + "text": "gram per cubic metre (g/m³)", + "value": "congm3" + }, + { + "text": "gram per normal cubic metre (g/Nm³)", + "value": "congNm3" + } + ], + "text": "concentration" + } + ] + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-polystat/v0alpha1.polystat_test.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-polystat/v0alpha1.polystat_test.v42.v2beta1.json index f72f1e3b485..5916250ffd5 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-polystat/v0alpha1.polystat_test.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-polystat/v0alpha1.polystat_test.v42.v2beta1.json @@ -130,7 +130,1073 @@ "group": "grafana-polystat-panel", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "grafana-polystat-panel", + "originalOptions": { + "animationModes": [ + { + "text": "Show All", + "value": "all" + }, + { + "text": "Show Triggered", + "value": "triggered" + } + ], + "colors": [ + "#299c46", + "rgba(237, 129, 40, 0.89)", + "#d44a3a" + ], + "d3DivId": "d3_svg_2", + "decimals": 2, + "displayModes": [ + { + "text": "Show All", + "value": "all" + }, + { + "text": "Show Triggered", + "value": "triggered" + } + ], + "fontSizes": [ + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 22, + 24, + 26, + 28, + 30, + 32, + 34, + 36, + 38, + 40, + 42, + 44, + 46, + 48, + 50, + 52, + 54, + 56, + 58, + 60, + 62, + 64, + 66, + 68, + 70 + ], + "fontTypes": [ + "Open Sans", + "Arial", + "Avant Garde", + "Bookman", + "Consolas", + "Courier", + "Courier New", + "Futura", + "Garamond", + "Helvetica", + "Palatino", + "Times", + "Times New Roman", + "Verdana" + ], + "format": "none", + "notcolors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "operatorName": "avg", + "operatorOptions": [ + { + "text": "Average", + "value": "avg" + }, + { + "text": "Count", + "value": "count" + }, + { + "text": "Current", + "value": "current" + }, + { + "text": "Delta", + "value": "delta" + }, + { + "text": "Difference", + "value": "diff" + }, + { + "text": "First", + "value": "first" + }, + { + "text": "Log Min", + "value": "logmin" + }, + { + "text": "Max", + "value": "max" + }, + { + "text": "Min", + "value": "min" + }, + { + "text": "Name", + "value": "name" + }, + { + "text": "Time of Last Point", + "value": "last_time" + }, + { + "text": "Time Step", + "value": "time_step" + }, + { + "text": "Total", + "value": "total" + } + ], + "polystat": { + "animationSpeed": 2500, + "columnAutoSize": true, + "columns": 1, + "defaultClickThrough": "", + "defaultClickThroughSanitize": true, + "displayLimit": 100, + "fontAutoScale": true, + "fontSize": 12, + "globalDisplayMode": "all", + "globalOperatorName": "avg", + "gradientEnabled": true, + "hexagonSortByDirection": "asc", + "hexagonSortByField": "name", + "maxMetrics": 0, + "polygonBorderColor": "black", + "polygonBorderSize": 2, + "radius": "", + "radiusAutoSize": true, + "rowAutoSize": true, + "rows": 1, + "shape": "hexagon_pointed_top", + "tooltipDisplayMode": "all", + "tooltipDisplayTextTriggeredEmpty": "OK", + "tooltipFontSize": 12, + "tooltipFontType": "Open Sans", + "tooltipPrimarySortDirection": "desc", + "tooltipPrimarySortField": "thresholdLevel", + "tooltipSecondarySortDirection": "desc", + "tooltipSecondarySortField": "value", + "tooltipTimestampEnabled": true + }, + "savedComposites": [], + "savedOverrides": [], + "shapes": [ + { + "text": "Hexagon Pointed Top", + "value": "hexagon_pointed_top" + }, + { + "text": "Hexagon Flat Top", + "value": "hexagon_flat_top" + }, + { + "text": "Circle", + "value": "circle" + }, + { + "text": "Cross", + "value": "cross" + }, + { + "text": "Diamond", + "value": "diamond" + }, + { + "text": "Square", + "value": "square" + }, + { + "text": "Star", + "value": "star" + }, + { + "text": "Triangle", + "value": "triangle" + }, + { + "text": "Wye", + "value": "wye" + } + ], + "sortDirections": [ + { + "text": "Ascending", + "value": "asc" + }, + { + "text": "Descending", + "value": "desc" + } + ], + "sortFields": [ + { + "text": "Name", + "value": "name" + }, + { + "text": "Threshold Level", + "value": "thresholdLevel" + }, + { + "text": "Value", + "value": "value" + } + ], + "svgContainer": {}, + "thresholdStates": [ + { + "text": "ok", + "value": 0 + }, + { + "text": "warning", + "value": 1 + }, + { + "text": "critical", + "value": 2 + }, + { + "text": "custom", + "value": 3 + } + ], + "unitFormats": [ + { + "submenu": [ + { + "text": "none", + "value": "none" + }, + { + "text": "short", + "value": "short" + }, + { + "text": "percent (0-100)", + "value": "percent" + }, + { + "text": "percent (0.0-1.0)", + "value": "percentunit" + }, + { + "text": "Humidity (%H)", + "value": "humidity" + }, + { + "text": "decibel", + "value": "dB" + }, + { + "text": "hexadecimal (0x)", + "value": "hex0x" + }, + { + "text": "hexadecimal", + "value": "hex" + }, + { + "text": "scientific notation", + "value": "sci" + }, + { + "text": "locale format", + "value": "locale" + } + ], + "text": "none" + }, + { + "submenu": [ + { + "text": "Dollars ($)", + "value": "currencyUSD" + }, + { + "text": "Pounds (£)", + "value": "currencyGBP" + }, + { + "text": "Euro (€)", + "value": "currencyEUR" + }, + { + "text": "Yen (¥)", + "value": "currencyJPY" + }, + { + "text": "Rubles (₽)", + "value": "currencyRUB" + }, + { + "text": "Hryvnias (₴)", + "value": "currencyUAH" + }, + { + "text": "Real (R$)", + "value": "currencyBRL" + }, + { + "text": "Danish Krone (kr)", + "value": "currencyDKK" + }, + { + "text": "Icelandic Króna (kr)", + "value": "currencyISK" + }, + { + "text": "Norwegian Krone (kr)", + "value": "currencyNOK" + }, + { + "text": "Swedish Krona (kr)", + "value": "currencySEK" + }, + { + "text": "Czech koruna (czk)", + "value": "currencyCZK" + }, + { + "text": "Swiss franc (CHF)", + "value": "currencyCHF" + }, + { + "text": "Polish Złoty (PLN)", + "value": "currencyPLN" + }, + { + "text": "Bitcoin (฿)", + "value": "currencyBTC" + }, + { + "text": "Vietnamese Dong (VND)", + "value": "currencyVND" + }, + { + "text": "Malaysian Ringgit (RM)", + "value": "currencyMYR" + }, + { + "text": "Bulgarian Lev (BGN)", + "value": "currencyBGN" + } + ], + "text": "currency" + }, + { + "submenu": [ + { + "text": "Hertz (1/s)", + "value": "hertz" + }, + { + "text": "nanoseconds (ns)", + "value": "ns" + }, + { + "text": "microseconds (µs)", + "value": "µs" + }, + { + "text": "milliseconds (ms)", + "value": "ms" + }, + { + "text": "seconds (s)", + "value": "s" + }, + { + "text": "minutes (m)", + "value": "m" + }, + { + "text": "hours (h)", + "value": "h" + }, + { + "text": "days (d)", + "value": "d" + }, + { + "text": "duration (ms)", + "value": "dtdurationms" + }, + { + "text": "duration (s)", + "value": "dtdurations" + }, + { + "text": "duration (hh:mm:ss)", + "value": "dthms" + }, + { + "text": "Timeticks (s/100)", + "value": "timeticks" + } + ], + "text": "time" + }, + { + "submenu": [ + { + "text": "YYYY-MM-DD HH:mm:ss", + "value": "dateTimeAsIso" + }, + { + "text": "DD/MM/YYYY h:mm:ss a", + "value": "dateTimeAsUS" + }, + { + "text": "From Now", + "value": "dateTimeFromNow" + } + ], + "text": "date \u0026 time" + }, + { + "submenu": [ + { + "text": "bits", + "value": "bits" + }, + { + "text": "bytes", + "value": "bytes" + }, + { + "text": "kibibytes", + "value": "kbytes" + }, + { + "text": "mebibytes", + "value": "mbytes" + }, + { + "text": "gibibytes", + "value": "gbytes" + } + ], + "text": "data (IEC)" + }, + { + "submenu": [ + { + "text": "bits", + "value": "decbits" + }, + { + "text": "bytes", + "value": "decbytes" + }, + { + "text": "kilobytes", + "value": "deckbytes" + }, + { + "text": "megabytes", + "value": "decmbytes" + }, + { + "text": "gigabytes", + "value": "decgbytes" + } + ], + "text": "data (Metric)" + }, + { + "submenu": [ + { + "text": "packets/sec", + "value": "pps" + }, + { + "text": "bits/sec", + "value": "bps" + }, + { + "text": "bytes/sec", + "value": "Bps" + }, + { + "text": "kilobits/sec", + "value": "Kbits" + }, + { + "text": "kilobytes/sec", + "value": "KBs" + }, + { + "text": "megabits/sec", + "value": "Mbits" + }, + { + "text": "megabytes/sec", + "value": "MBs" + }, + { + "text": "gigabytes/sec", + "value": "GBs" + }, + { + "text": "gigabits/sec", + "value": "Gbits" + } + ], + "text": "data rate" + }, + { + "submenu": [ + { + "text": "hashes/sec", + "value": "Hs" + }, + { + "text": "kilohashes/sec", + "value": "KHs" + }, + { + "text": "megahashes/sec", + "value": "MHs" + }, + { + "text": "gigahashes/sec", + "value": "GHs" + }, + { + "text": "terahashes/sec", + "value": "THs" + }, + { + "text": "petahashes/sec", + "value": "PHs" + }, + { + "text": "exahashes/sec", + "value": "EHs" + } + ], + "text": "hash rate" + }, + { + "submenu": [ + { + "text": "ops/sec (ops)", + "value": "ops" + }, + { + "text": "requests/sec (rps)", + "value": "reqps" + }, + { + "text": "reads/sec (rps)", + "value": "rps" + }, + { + "text": "writes/sec (wps)", + "value": "wps" + }, + { + "text": "I/O ops/sec (iops)", + "value": "iops" + }, + { + "text": "ops/min (opm)", + "value": "opm" + }, + { + "text": "requests/min (rpm)", + "value": "reqpm" + }, + { + "text": "reads/min (rpm)", + "value": "rpm" + }, + { + "text": "writes/min (wpm)", + "value": "wpm" + } + ], + "text": "throughput" + }, + { + "submenu": [ + { + "text": "millimetre (mm)", + "value": "lengthmm" + }, + { + "text": "meter (m)", + "value": "lengthm" + }, + { + "text": "inch (in)", + "value": "lengthin" + }, + { + "text": "feet (ft)", + "value": "lengthft" + }, + { + "text": "kilometer (km)", + "value": "lengthkm" + }, + { + "text": "mile (mi)", + "value": "lengthmi" + } + ], + "text": "length" + }, + { + "submenu": [ + { + "text": "Square Meters (m²)", + "value": "areaM2" + }, + { + "text": "Square Feet (ft²)", + "value": "areaF2" + }, + { + "text": "Square Miles (mi²)", + "value": "areaMI2" + } + ], + "text": "area" + }, + { + "submenu": [ + { + "text": "milligram (mg)", + "value": "massmg" + }, + { + "text": "gram (g)", + "value": "massg" + }, + { + "text": "pound (lb)", + "value": "masslb" + }, + { + "text": "kilogram (kg)", + "value": "masskg" + }, + { + "text": "metric ton (t)", + "value": "masst" + } + ], + "text": "mass" + }, + { + "submenu": [ + { + "text": "metres/second (m/s)", + "value": "velocityms" + }, + { + "text": "kilometers/hour (km/h)", + "value": "velocitykmh" + }, + { + "text": "miles/hour (mph)", + "value": "velocitymph" + }, + { + "text": "knot (kn)", + "value": "velocityknot" + } + ], + "text": "velocity" + }, + { + "submenu": [ + { + "text": "millilitre (mL)", + "value": "mlitre" + }, + { + "text": "litre (L)", + "value": "litre" + }, + { + "text": "cubic metre", + "value": "m3" + }, + { + "text": "Normal cubic metre", + "value": "Nm3" + }, + { + "text": "cubic decimetre", + "value": "dm3" + }, + { + "text": "gallons", + "value": "gallons" + } + ], + "text": "volume" + }, + { + "submenu": [ + { + "text": "Watt (W)", + "value": "watt" + }, + { + "text": "Kilowatt (kW)", + "value": "kwatt" + }, + { + "text": "Milliwatt (mW)", + "value": "mwatt" + }, + { + "text": "Watt per square metre (W/m²)", + "value": "Wm2" + }, + { + "text": "Volt-ampere (VA)", + "value": "voltamp" + }, + { + "text": "Kilovolt-ampere (kVA)", + "value": "kvoltamp" + }, + { + "text": "Volt-ampere reactive (var)", + "value": "voltampreact" + }, + { + "text": "Kilovolt-ampere reactive (kvar)", + "value": "kvoltampreact" + }, + { + "text": "Watt-hour (Wh)", + "value": "watth" + }, + { + "text": "Kilowatt-hour (kWh)", + "value": "kwatth" + }, + { + "text": "Kilowatt-min (kWm)", + "value": "kwattm" + }, + { + "text": "Joule (J)", + "value": "joule" + }, + { + "text": "Electron volt (eV)", + "value": "ev" + }, + { + "text": "Ampere (A)", + "value": "amp" + }, + { + "text": "Kiloampere (kA)", + "value": "kamp" + }, + { + "text": "Milliampere (mA)", + "value": "mamp" + }, + { + "text": "Volt (V)", + "value": "volt" + }, + { + "text": "Kilovolt (kV)", + "value": "kvolt" + }, + { + "text": "Millivolt (mV)", + "value": "mvolt" + }, + { + "text": "Decibel-milliwatt (dBm)", + "value": "dBm" + }, + { + "text": "Ohm (Ω)", + "value": "ohm" + }, + { + "text": "Lumens (Lm)", + "value": "lumens" + } + ], + "text": "energy" + }, + { + "submenu": [ + { + "text": "Celsius (°C)", + "value": "celsius" + }, + { + "text": "Fahrenheit (°F)", + "value": "fahrenheit" + }, + { + "text": "Kelvin (K)", + "value": "kelvin" + } + ], + "text": "temperature" + }, + { + "submenu": [ + { + "text": "Millibars", + "value": "pressurembar" + }, + { + "text": "Bars", + "value": "pressurebar" + }, + { + "text": "Kilobars", + "value": "pressurekbar" + }, + { + "text": "Hectopascals", + "value": "pressurehpa" + }, + { + "text": "Kilopascals", + "value": "pressurekpa" + }, + { + "text": "Inches of mercury", + "value": "pressurehg" + }, + { + "text": "PSI", + "value": "pressurepsi" + } + ], + "text": "pressure" + }, + { + "submenu": [ + { + "text": "Newton-meters (Nm)", + "value": "forceNm" + }, + { + "text": "Kilonewton-meters (kNm)", + "value": "forcekNm" + }, + { + "text": "Newtons (N)", + "value": "forceN" + }, + { + "text": "Kilonewtons (kN)", + "value": "forcekN" + } + ], + "text": "force" + }, + { + "submenu": [ + { + "text": "Gallons/min (gpm)", + "value": "flowgpm" + }, + { + "text": "Cubic meters/sec (cms)", + "value": "flowcms" + }, + { + "text": "Cubic feet/sec (cfs)", + "value": "flowcfs" + }, + { + "text": "Cubic feet/min (cfm)", + "value": "flowcfm" + }, + { + "text": "Litre/hour", + "value": "litreh" + }, + { + "text": "Litre/min (l/min)", + "value": "flowlpm" + }, + { + "text": "milliLitre/min (mL/min)", + "value": "flowmlpm" + } + ], + "text": "flow" + }, + { + "submenu": [ + { + "text": "Degrees (°)", + "value": "degree" + }, + { + "text": "Radians", + "value": "radian" + }, + { + "text": "Gradian", + "value": "grad" + } + ], + "text": "angle" + }, + { + "submenu": [ + { + "text": "Meters/sec²", + "value": "accMS2" + }, + { + "text": "Feet/sec²", + "value": "accFS2" + }, + { + "text": "G unit", + "value": "accG" + } + ], + "text": "acceleration" + }, + { + "submenu": [ + { + "text": "Becquerel (Bq)", + "value": "radbq" + }, + { + "text": "curie (Ci)", + "value": "radci" + }, + { + "text": "Gray (Gy)", + "value": "radgy" + }, + { + "text": "rad", + "value": "radrad" + }, + { + "text": "Sievert (Sv)", + "value": "radsv" + }, + { + "text": "rem", + "value": "radrem" + }, + { + "text": "Exposure (C/kg)", + "value": "radexpckg" + }, + { + "text": "roentgen (R)", + "value": "radr" + }, + { + "text": "Sievert/hour (Sv/h)", + "value": "radsvh" + } + ], + "text": "radiation" + }, + { + "submenu": [ + { + "text": "parts-per-million (ppm)", + "value": "ppm" + }, + { + "text": "parts-per-billion (ppb)", + "value": "conppb" + }, + { + "text": "nanogram per cubic metre (ng/m³)", + "value": "conngm3" + }, + { + "text": "nanogram per normal cubic metre (ng/Nm³)", + "value": "conngNm3" + }, + { + "text": "microgram per cubic metre (μg/m³)", + "value": "conμgm3" + }, + { + "text": "microgram per normal cubic metre (μg/Nm³)", + "value": "conμgNm3" + }, + { + "text": "milligram per cubic metre (mg/m³)", + "value": "conmgm3" + }, + { + "text": "milligram per normal cubic metre (mg/Nm³)", + "value": "conmgNm3" + }, + { + "text": "gram per cubic metre (g/m³)", + "value": "congm3" + }, + { + "text": "gram per normal cubic metre (g/Nm³)", + "value": "congNm3" + } + ], + "text": "concentration" + } + ] + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -250,7 +1316,1129 @@ "group": "grafana-polystat-panel", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "grafana-polystat-panel", + "originalOptions": { + "animationModes": [ + { + "text": "Show All", + "value": "all" + }, + { + "text": "Show Triggered", + "value": "triggered" + } + ], + "colors": [ + "#299c46", + "rgba(237, 129, 40, 0.89)", + "#d44a3a" + ], + "d3DivId": "d3_svg_4", + "decimals": 2, + "displayModes": [ + { + "text": "Show All", + "value": "all" + }, + { + "text": "Show Triggered", + "value": "triggered" + } + ], + "fontSizes": [ + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 22, + 24, + 26, + 28, + 30, + 32, + 34, + 36, + 38, + 40, + 42, + 44, + 46, + 48, + 50, + 52, + 54, + 56, + 58, + 60, + 62, + 64, + 66, + 68, + 70 + ], + "fontTypes": [ + "Open Sans", + "Arial", + "Avant Garde", + "Bookman", + "Consolas", + "Courier", + "Courier New", + "Futura", + "Garamond", + "Helvetica", + "Palatino", + "Times", + "Times New Roman", + "Verdana" + ], + "format": "none", + "notcolors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "operatorName": "avg", + "operatorOptions": [ + { + "text": "Average", + "value": "avg" + }, + { + "text": "Count", + "value": "count" + }, + { + "text": "Current", + "value": "current" + }, + { + "text": "Delta", + "value": "delta" + }, + { + "text": "Difference", + "value": "diff" + }, + { + "text": "First", + "value": "first" + }, + { + "text": "Log Min", + "value": "logmin" + }, + { + "text": "Max", + "value": "max" + }, + { + "text": "Min", + "value": "min" + }, + { + "text": "Name", + "value": "name" + }, + { + "text": "Time of Last Point", + "value": "last_time" + }, + { + "text": "Time Step", + "value": "time_step" + }, + { + "text": "Total", + "value": "total" + } + ], + "polystat": { + "animationSpeed": 2500, + "columnAutoSize": true, + "columns": "", + "defaultClickThrough": "", + "defaultClickThroughSanitize": true, + "displayLimit": 100, + "fontAutoScale": true, + "fontSize": 12, + "globalDisplayMode": "all", + "globalOperatorName": "avg", + "gradientEnabled": true, + "hexagonSortByDirection": "asc", + "hexagonSortByField": "name", + "maxMetrics": 0, + "polygonBorderColor": "black", + "polygonBorderSize": 2, + "radius": "", + "radiusAutoSize": true, + "rowAutoSize": true, + "rows": "", + "shape": "hexagon_pointed_top", + "tooltipDisplayMode": "all", + "tooltipDisplayTextTriggeredEmpty": "OK", + "tooltipFontSize": 12, + "tooltipFontType": "Open Sans", + "tooltipPrimarySortDirection": "desc", + "tooltipPrimarySortField": "thresholdLevel", + "tooltipSecondarySortDirection": "desc", + "tooltipSecondarySortField": "value", + "tooltipTimestampEnabled": true + }, + "savedComposites": [], + "savedOverrides": [], + "shapes": [ + { + "text": "Hexagon Pointed Top", + "value": "hexagon_pointed_top" + }, + { + "text": "Hexagon Flat Top", + "value": "hexagon_flat_top" + }, + { + "text": "Circle", + "value": "circle" + }, + { + "text": "Cross", + "value": "cross" + }, + { + "text": "Diamond", + "value": "diamond" + }, + { + "text": "Square", + "value": "square" + }, + { + "text": "Star", + "value": "star" + }, + { + "text": "Triangle", + "value": "triangle" + }, + { + "text": "Wye", + "value": "wye" + } + ], + "sortDirections": [ + { + "text": "Ascending", + "value": "asc" + }, + { + "text": "Descending", + "value": "desc" + } + ], + "sortFields": [ + { + "text": "Name", + "value": "name" + }, + { + "text": "Threshold Level", + "value": "thresholdLevel" + }, + { + "text": "Value", + "value": "value" + } + ], + "svgContainer": {}, + "thresholdStates": [ + { + "text": "ok", + "value": 0 + }, + { + "text": "warning", + "value": 1 + }, + { + "text": "critical", + "value": 2 + }, + { + "text": "custom", + "value": 3 + } + ], + "unitFormats": [ + { + "submenu": [ + { + "text": "none", + "value": "none" + }, + { + "text": "short", + "value": "short" + }, + { + "text": "percent (0-100)", + "value": "percent" + }, + { + "text": "percent (0.0-1.0)", + "value": "percentunit" + }, + { + "text": "Humidity (%H)", + "value": "humidity" + }, + { + "text": "decibel", + "value": "dB" + }, + { + "text": "hexadecimal (0x)", + "value": "hex0x" + }, + { + "text": "hexadecimal", + "value": "hex" + }, + { + "text": "scientific notation", + "value": "sci" + }, + { + "text": "locale format", + "value": "locale" + } + ], + "text": "none" + }, + { + "submenu": [ + { + "text": "Dollars ($)", + "value": "currencyUSD" + }, + { + "text": "Pounds (£)", + "value": "currencyGBP" + }, + { + "text": "Euro (€)", + "value": "currencyEUR" + }, + { + "text": "Yen (¥)", + "value": "currencyJPY" + }, + { + "text": "Rubles (₽)", + "value": "currencyRUB" + }, + { + "text": "Hryvnias (₴)", + "value": "currencyUAH" + }, + { + "text": "Real (R$)", + "value": "currencyBRL" + }, + { + "text": "Danish Krone (kr)", + "value": "currencyDKK" + }, + { + "text": "Icelandic Króna (kr)", + "value": "currencyISK" + }, + { + "text": "Norwegian Krone (kr)", + "value": "currencyNOK" + }, + { + "text": "Swedish Krona (kr)", + "value": "currencySEK" + }, + { + "text": "Czech koruna (czk)", + "value": "currencyCZK" + }, + { + "text": "Swiss franc (CHF)", + "value": "currencyCHF" + }, + { + "text": "Polish Złoty (PLN)", + "value": "currencyPLN" + }, + { + "text": "Bitcoin (฿)", + "value": "currencyBTC" + }, + { + "text": "Milli Bitcoin (mBTC)", + "value": "currencymBTC" + }, + { + "text": "Micro Bitcoin (μBTC)", + "value": "currencyμBTC" + }, + { + "text": "Vietnamese Dong (VND)", + "value": "currencyVND" + }, + { + "text": "Turkish Lira (₺)", + "value": "currencyTRY" + }, + { + "text": "Malaysian Ringgit (RM)", + "value": "currencyMYR" + }, + { + "text": "CFP franc (XPF)", + "value": "currencyXPF" + }, + { + "text": "Bulgarian Lev (BGN)", + "value": "currencyBGN" + } + ], + "text": "currency" + }, + { + "submenu": [ + { + "text": "Hertz (1/s)", + "value": "hertz" + }, + { + "text": "nanoseconds (ns)", + "value": "ns" + }, + { + "text": "microseconds (µs)", + "value": "µs" + }, + { + "text": "milliseconds (ms)", + "value": "ms" + }, + { + "text": "seconds (s)", + "value": "s" + }, + { + "text": "minutes (m)", + "value": "m" + }, + { + "text": "hours (h)", + "value": "h" + }, + { + "text": "days (d)", + "value": "d" + }, + { + "text": "duration (ms)", + "value": "dtdurationms" + }, + { + "text": "duration (s)", + "value": "dtdurations" + }, + { + "text": "duration (hh:mm:ss)", + "value": "dthms" + }, + { + "text": "Timeticks (s/100)", + "value": "timeticks" + } + ], + "text": "time" + }, + { + "submenu": [ + { + "text": "YYYY-MM-DD HH:mm:ss", + "value": "dateTimeAsIso" + }, + { + "text": "DD/MM/YYYY h:mm:ss a", + "value": "dateTimeAsUS" + }, + { + "text": "From Now", + "value": "dateTimeFromNow" + } + ], + "text": "date \u0026 time" + }, + { + "submenu": [ + { + "text": "bits", + "value": "bits" + }, + { + "text": "bytes", + "value": "bytes" + }, + { + "text": "kibibytes", + "value": "kbytes" + }, + { + "text": "mebibytes", + "value": "mbytes" + }, + { + "text": "gibibytes", + "value": "gbytes" + } + ], + "text": "data (IEC)" + }, + { + "submenu": [ + { + "text": "bits", + "value": "decbits" + }, + { + "text": "bytes", + "value": "decbytes" + }, + { + "text": "kilobytes", + "value": "deckbytes" + }, + { + "text": "megabytes", + "value": "decmbytes" + }, + { + "text": "gigabytes", + "value": "decgbytes" + } + ], + "text": "data (Metric)" + }, + { + "submenu": [ + { + "text": "packets/sec", + "value": "pps" + }, + { + "text": "bits/sec", + "value": "bps" + }, + { + "text": "bytes/sec", + "value": "Bps" + }, + { + "text": "kilobits/sec", + "value": "Kbits" + }, + { + "text": "kilobytes/sec", + "value": "KBs" + }, + { + "text": "megabits/sec", + "value": "Mbits" + }, + { + "text": "megabytes/sec", + "value": "MBs" + }, + { + "text": "gigabytes/sec", + "value": "GBs" + }, + { + "text": "gigabits/sec", + "value": "Gbits" + } + ], + "text": "data rate" + }, + { + "submenu": [ + { + "text": "hashes/sec", + "value": "Hs" + }, + { + "text": "kilohashes/sec", + "value": "KHs" + }, + { + "text": "megahashes/sec", + "value": "MHs" + }, + { + "text": "gigahashes/sec", + "value": "GHs" + }, + { + "text": "terahashes/sec", + "value": "THs" + }, + { + "text": "petahashes/sec", + "value": "PHs" + }, + { + "text": "exahashes/sec", + "value": "EHs" + } + ], + "text": "hash rate" + }, + { + "submenu": [ + { + "text": "counts/sec (cps)", + "value": "cps" + }, + { + "text": "ops/sec (ops)", + "value": "ops" + }, + { + "text": "requests/sec (rps)", + "value": "reqps" + }, + { + "text": "reads/sec (rps)", + "value": "rps" + }, + { + "text": "writes/sec (wps)", + "value": "wps" + }, + { + "text": "I/O ops/sec (iops)", + "value": "iops" + }, + { + "text": "events/sec (eps)", + "value": "eps" + }, + { + "text": "messages/sec (mps)", + "value": "mps" + }, + { + "text": "records/sec (rps)", + "value": "recps" + }, + { + "text": "rows/sec (rps)", + "value": "rowsps" + }, + { + "text": "counts/min (cpm)", + "value": "cpm" + }, + { + "text": "ops/min (opm)", + "value": "opm" + }, + { + "text": "requests/min (rpm)", + "value": "reqpm" + }, + { + "text": "reads/min (rpm)", + "value": "rpm" + }, + { + "text": "writes/min (wpm)", + "value": "wpm" + }, + { + "text": "events/min (epm)", + "value": "epm" + }, + { + "text": "messages/min (mpm)", + "value": "mpm" + }, + { + "text": "records/min (rpm)", + "value": "recpm" + }, + { + "text": "rows/min (rpm)", + "value": "rowspm" + } + ], + "text": "throughput" + }, + { + "submenu": [ + { + "text": "millimetre (mm)", + "value": "lengthmm" + }, + { + "text": "meter (m)", + "value": "lengthm" + }, + { + "text": "inch (in)", + "value": "lengthin" + }, + { + "text": "feet (ft)", + "value": "lengthft" + }, + { + "text": "kilometer (km)", + "value": "lengthkm" + }, + { + "text": "mile (mi)", + "value": "lengthmi" + } + ], + "text": "length" + }, + { + "submenu": [ + { + "text": "Square Meters (m²)", + "value": "areaM2" + }, + { + "text": "Square Feet (ft²)", + "value": "areaF2" + }, + { + "text": "Square Miles (mi²)", + "value": "areaMI2" + } + ], + "text": "area" + }, + { + "submenu": [ + { + "text": "milligram (mg)", + "value": "massmg" + }, + { + "text": "gram (g)", + "value": "massg" + }, + { + "text": "pound (lb)", + "value": "masslb" + }, + { + "text": "kilogram (kg)", + "value": "masskg" + }, + { + "text": "metric ton (t)", + "value": "masst" + } + ], + "text": "mass" + }, + { + "submenu": [ + { + "text": "metres/second (m/s)", + "value": "velocityms" + }, + { + "text": "kilometers/hour (km/h)", + "value": "velocitykmh" + }, + { + "text": "miles/hour (mph)", + "value": "velocitymph" + }, + { + "text": "knot (kn)", + "value": "velocityknot" + } + ], + "text": "velocity" + }, + { + "submenu": [ + { + "text": "millilitre (mL)", + "value": "mlitre" + }, + { + "text": "litre (L)", + "value": "litre" + }, + { + "text": "cubic metre", + "value": "m3" + }, + { + "text": "Normal cubic metre", + "value": "Nm3" + }, + { + "text": "cubic decimetre", + "value": "dm3" + }, + { + "text": "gallons", + "value": "gallons" + } + ], + "text": "volume" + }, + { + "submenu": [ + { + "text": "Watt (W)", + "value": "watt" + }, + { + "text": "Kilowatt (kW)", + "value": "kwatt" + }, + { + "text": "Milliwatt (mW)", + "value": "mwatt" + }, + { + "text": "Watt per square metre (W/m²)", + "value": "Wm2" + }, + { + "text": "Volt-Ampere (VA)", + "value": "voltamp" + }, + { + "text": "Kilovolt-Ampere (kVA)", + "value": "kvoltamp" + }, + { + "text": "Volt-Ampere reactive (VAr)", + "value": "voltampreact" + }, + { + "text": "Kilovolt-Ampere reactive (kVAr)", + "value": "kvoltampreact" + }, + { + "text": "Watt-hour (Wh)", + "value": "watth" + }, + { + "text": "Kilowatt-hour (kWh)", + "value": "kwatth" + }, + { + "text": "Kilowatt-min (kWm)", + "value": "kwattm" + }, + { + "text": "Joule (J)", + "value": "joule" + }, + { + "text": "Electron volt (eV)", + "value": "ev" + }, + { + "text": "Ampere (A)", + "value": "amp" + }, + { + "text": "Kiloampere (kA)", + "value": "kamp" + }, + { + "text": "Milliampere (mA)", + "value": "mamp" + }, + { + "text": "Volt (V)", + "value": "volt" + }, + { + "text": "Kilovolt (kV)", + "value": "kvolt" + }, + { + "text": "Millivolt (mV)", + "value": "mvolt" + }, + { + "text": "Decibel-milliwatt (dBm)", + "value": "dBm" + }, + { + "text": "Ohm (Ω)", + "value": "ohm" + }, + { + "text": "Lumens (Lm)", + "value": "lumens" + } + ], + "text": "energy" + }, + { + "submenu": [ + { + "text": "Celsius (°C)", + "value": "celsius" + }, + { + "text": "Fahrenheit (°F)", + "value": "fahrenheit" + }, + { + "text": "Kelvin (K)", + "value": "kelvin" + } + ], + "text": "temperature" + }, + { + "submenu": [ + { + "text": "Millibars", + "value": "pressurembar" + }, + { + "text": "Bars", + "value": "pressurebar" + }, + { + "text": "Kilobars", + "value": "pressurekbar" + }, + { + "text": "Hectopascals", + "value": "pressurehpa" + }, + { + "text": "Kilopascals", + "value": "pressurekpa" + }, + { + "text": "Inches of mercury", + "value": "pressurehg" + }, + { + "text": "PSI", + "value": "pressurepsi" + } + ], + "text": "pressure" + }, + { + "submenu": [ + { + "text": "Newton-meters (Nm)", + "value": "forceNm" + }, + { + "text": "Kilonewton-meters (kNm)", + "value": "forcekNm" + }, + { + "text": "Newtons (N)", + "value": "forceN" + }, + { + "text": "Kilonewtons (kN)", + "value": "forcekN" + } + ], + "text": "force" + }, + { + "submenu": [ + { + "text": "Gallons/min (gpm)", + "value": "flowgpm" + }, + { + "text": "Cubic meters/sec (cms)", + "value": "flowcms" + }, + { + "text": "Cubic feet/sec (cfs)", + "value": "flowcfs" + }, + { + "text": "Cubic feet/min (cfm)", + "value": "flowcfm" + }, + { + "text": "Litre/hour", + "value": "litreh" + }, + { + "text": "Litre/min (l/min)", + "value": "flowlpm" + }, + { + "text": "milliLitre/min (mL/min)", + "value": "flowmlpm" + } + ], + "text": "flow" + }, + { + "submenu": [ + { + "text": "Degrees (°)", + "value": "degree" + }, + { + "text": "Radians", + "value": "radian" + }, + { + "text": "Gradian", + "value": "grad" + } + ], + "text": "angle" + }, + { + "submenu": [ + { + "text": "Meters/sec²", + "value": "accMS2" + }, + { + "text": "Feet/sec²", + "value": "accFS2" + }, + { + "text": "G unit", + "value": "accG" + } + ], + "text": "acceleration" + }, + { + "submenu": [ + { + "text": "Becquerel (Bq)", + "value": "radbq" + }, + { + "text": "curie (Ci)", + "value": "radci" + }, + { + "text": "Gray (Gy)", + "value": "radgy" + }, + { + "text": "rad", + "value": "radrad" + }, + { + "text": "Sievert (Sv)", + "value": "radsv" + }, + { + "text": "rem", + "value": "radrem" + }, + { + "text": "Exposure (C/kg)", + "value": "radexpckg" + }, + { + "text": "roentgen (R)", + "value": "radr" + }, + { + "text": "Sievert/hour (Sv/h)", + "value": "radsvh" + } + ], + "text": "radiation" + }, + { + "submenu": [ + { + "text": "parts-per-million (ppm)", + "value": "ppm" + }, + { + "text": "parts-per-billion (ppb)", + "value": "conppb" + }, + { + "text": "nanogram per cubic metre (ng/m³)", + "value": "conngm3" + }, + { + "text": "nanogram per normal cubic metre (ng/Nm³)", + "value": "conngNm3" + }, + { + "text": "microgram per cubic metre (μg/m³)", + "value": "conμgm3" + }, + { + "text": "microgram per normal cubic metre (μg/Nm³)", + "value": "conμgNm3" + }, + { + "text": "milligram per cubic metre (mg/m³)", + "value": "conmgm3" + }, + { + "text": "milligram per normal cubic metre (mg/Nm³)", + "value": "conmgNm3" + }, + { + "text": "gram per cubic metre (g/m³)", + "value": "congm3" + }, + { + "text": "gram per normal cubic metre (g/Nm³)", + "value": "congNm3" + } + ], + "text": "concentration" + } + ] + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -370,7 +2558,1090 @@ "group": "grafana-polystat-panel", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "grafana-polystat-panel", + "originalOptions": { + "animationModes": [ + { + "text": "Show All", + "value": "all" + }, + { + "text": "Show Triggered", + "value": "triggered" + } + ], + "colors": [ + "#299c46", + "rgba(237, 129, 40, 0.89)", + "#d44a3a" + ], + "d3DivId": "d3_svg_5", + "decimals": 2, + "displayModes": [ + { + "text": "Show All", + "value": "all" + }, + { + "text": "Show Triggered", + "value": "triggered" + } + ], + "fontSizes": [ + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 22, + 24, + 26, + 28, + 30, + 32, + 34, + 36, + 38, + 40, + 42, + 44, + 46, + 48, + 50, + 52, + 54, + 56, + 58, + 60, + 62, + 64, + 66, + 68, + 70 + ], + "fontTypes": [ + "Open Sans", + "Arial", + "Avant Garde", + "Bookman", + "Consolas", + "Courier", + "Courier New", + "Futura", + "Garamond", + "Helvetica", + "Palatino", + "Times", + "Times New Roman", + "Verdana" + ], + "format": "none", + "notcolors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "operatorName": "avg", + "operatorOptions": [ + { + "text": "Average", + "value": "avg" + }, + { + "text": "Count", + "value": "count" + }, + { + "text": "Current", + "value": "current" + }, + { + "text": "Delta", + "value": "delta" + }, + { + "text": "Difference", + "value": "diff" + }, + { + "text": "First", + "value": "first" + }, + { + "text": "Log Min", + "value": "logmin" + }, + { + "text": "Max", + "value": "max" + }, + { + "text": "Min", + "value": "min" + }, + { + "text": "Name", + "value": "name" + }, + { + "text": "Time of Last Point", + "value": "last_time" + }, + { + "text": "Time Step", + "value": "time_step" + }, + { + "text": "Total", + "value": "total" + } + ], + "polystat": { + "animationSpeed": 2500, + "columnAutoSize": true, + "columns": "", + "defaultClickThrough": "", + "defaultClickThroughSanitize": true, + "displayLimit": 100, + "fontAutoScale": true, + "fontSize": 12, + "globalDisplayMode": "all", + "globalOperatorName": "avg", + "gradientEnabled": true, + "hexagonSortByDirection": "asc", + "hexagonSortByField": "name", + "maxMetrics": 0, + "polygonBorderColor": "black", + "polygonBorderSize": 2, + "radius": "", + "radiusAutoSize": true, + "rowAutoSize": true, + "rows": "", + "shape": "hexagon_pointed_top", + "tooltipDisplayMode": "all", + "tooltipDisplayTextTriggeredEmpty": "OK", + "tooltipFontSize": 12, + "tooltipFontType": "Open Sans", + "tooltipPrimarySortDirection": "desc", + "tooltipPrimarySortField": "thresholdLevel", + "tooltipSecondarySortDirection": "desc", + "tooltipSecondarySortField": "value", + "tooltipTimestampEnabled": true + }, + "savedComposites": [ + { + "animateMode": "all", + "clickThrough": "", + "compositeName": "comp", + "enabled": true, + "hideMembers": true, + "members": [ + { + "seriesName": "A-series" + }, + { + "seriesName": "B-series" + } + ], + "sanitizeURLEnabled": true, + "sanitizedURL": "", + "showName": true, + "showValue": true, + "thresholdLevel": 0 + } + ], + "savedOverrides": [], + "shapes": [ + { + "text": "Hexagon Pointed Top", + "value": "hexagon_pointed_top" + }, + { + "text": "Hexagon Flat Top", + "value": "hexagon_flat_top" + }, + { + "text": "Circle", + "value": "circle" + }, + { + "text": "Cross", + "value": "cross" + }, + { + "text": "Diamond", + "value": "diamond" + }, + { + "text": "Square", + "value": "square" + }, + { + "text": "Star", + "value": "star" + }, + { + "text": "Triangle", + "value": "triangle" + }, + { + "text": "Wye", + "value": "wye" + } + ], + "sortDirections": [ + { + "text": "Ascending", + "value": "asc" + }, + { + "text": "Descending", + "value": "desc" + } + ], + "sortFields": [ + { + "text": "Name", + "value": "name" + }, + { + "text": "Threshold Level", + "value": "thresholdLevel" + }, + { + "text": "Value", + "value": "value" + } + ], + "svgContainer": {}, + "thresholdStates": [ + { + "text": "ok", + "value": 0 + }, + { + "text": "warning", + "value": 1 + }, + { + "text": "critical", + "value": 2 + }, + { + "text": "custom", + "value": 3 + } + ], + "unitFormats": [ + { + "submenu": [ + { + "text": "none", + "value": "none" + }, + { + "text": "short", + "value": "short" + }, + { + "text": "percent (0-100)", + "value": "percent" + }, + { + "text": "percent (0.0-1.0)", + "value": "percentunit" + }, + { + "text": "Humidity (%H)", + "value": "humidity" + }, + { + "text": "decibel", + "value": "dB" + }, + { + "text": "hexadecimal (0x)", + "value": "hex0x" + }, + { + "text": "hexadecimal", + "value": "hex" + }, + { + "text": "scientific notation", + "value": "sci" + }, + { + "text": "locale format", + "value": "locale" + } + ], + "text": "none" + }, + { + "submenu": [ + { + "text": "Dollars ($)", + "value": "currencyUSD" + }, + { + "text": "Pounds (£)", + "value": "currencyGBP" + }, + { + "text": "Euro (€)", + "value": "currencyEUR" + }, + { + "text": "Yen (¥)", + "value": "currencyJPY" + }, + { + "text": "Rubles (₽)", + "value": "currencyRUB" + }, + { + "text": "Hryvnias (₴)", + "value": "currencyUAH" + }, + { + "text": "Real (R$)", + "value": "currencyBRL" + }, + { + "text": "Danish Krone (kr)", + "value": "currencyDKK" + }, + { + "text": "Icelandic Króna (kr)", + "value": "currencyISK" + }, + { + "text": "Norwegian Krone (kr)", + "value": "currencyNOK" + }, + { + "text": "Swedish Krona (kr)", + "value": "currencySEK" + }, + { + "text": "Czech koruna (czk)", + "value": "currencyCZK" + }, + { + "text": "Swiss franc (CHF)", + "value": "currencyCHF" + }, + { + "text": "Polish Złoty (PLN)", + "value": "currencyPLN" + }, + { + "text": "Bitcoin (฿)", + "value": "currencyBTC" + }, + { + "text": "Vietnamese Dong (VND)", + "value": "currencyVND" + }, + { + "text": "Malaysian Ringgit (RM)", + "value": "currencyMYR" + }, + { + "text": "Bulgarian Lev (BGN)", + "value": "currencyBGN" + } + ], + "text": "currency" + }, + { + "submenu": [ + { + "text": "Hertz (1/s)", + "value": "hertz" + }, + { + "text": "nanoseconds (ns)", + "value": "ns" + }, + { + "text": "microseconds (µs)", + "value": "µs" + }, + { + "text": "milliseconds (ms)", + "value": "ms" + }, + { + "text": "seconds (s)", + "value": "s" + }, + { + "text": "minutes (m)", + "value": "m" + }, + { + "text": "hours (h)", + "value": "h" + }, + { + "text": "days (d)", + "value": "d" + }, + { + "text": "duration (ms)", + "value": "dtdurationms" + }, + { + "text": "duration (s)", + "value": "dtdurations" + }, + { + "text": "duration (hh:mm:ss)", + "value": "dthms" + }, + { + "text": "Timeticks (s/100)", + "value": "timeticks" + } + ], + "text": "time" + }, + { + "submenu": [ + { + "text": "YYYY-MM-DD HH:mm:ss", + "value": "dateTimeAsIso" + }, + { + "text": "DD/MM/YYYY h:mm:ss a", + "value": "dateTimeAsUS" + }, + { + "text": "From Now", + "value": "dateTimeFromNow" + } + ], + "text": "date \u0026 time" + }, + { + "submenu": [ + { + "text": "bits", + "value": "bits" + }, + { + "text": "bytes", + "value": "bytes" + }, + { + "text": "kibibytes", + "value": "kbytes" + }, + { + "text": "mebibytes", + "value": "mbytes" + }, + { + "text": "gibibytes", + "value": "gbytes" + } + ], + "text": "data (IEC)" + }, + { + "submenu": [ + { + "text": "bits", + "value": "decbits" + }, + { + "text": "bytes", + "value": "decbytes" + }, + { + "text": "kilobytes", + "value": "deckbytes" + }, + { + "text": "megabytes", + "value": "decmbytes" + }, + { + "text": "gigabytes", + "value": "decgbytes" + } + ], + "text": "data (Metric)" + }, + { + "submenu": [ + { + "text": "packets/sec", + "value": "pps" + }, + { + "text": "bits/sec", + "value": "bps" + }, + { + "text": "bytes/sec", + "value": "Bps" + }, + { + "text": "kilobits/sec", + "value": "Kbits" + }, + { + "text": "kilobytes/sec", + "value": "KBs" + }, + { + "text": "megabits/sec", + "value": "Mbits" + }, + { + "text": "megabytes/sec", + "value": "MBs" + }, + { + "text": "gigabytes/sec", + "value": "GBs" + }, + { + "text": "gigabits/sec", + "value": "Gbits" + } + ], + "text": "data rate" + }, + { + "submenu": [ + { + "text": "hashes/sec", + "value": "Hs" + }, + { + "text": "kilohashes/sec", + "value": "KHs" + }, + { + "text": "megahashes/sec", + "value": "MHs" + }, + { + "text": "gigahashes/sec", + "value": "GHs" + }, + { + "text": "terahashes/sec", + "value": "THs" + }, + { + "text": "petahashes/sec", + "value": "PHs" + }, + { + "text": "exahashes/sec", + "value": "EHs" + } + ], + "text": "hash rate" + }, + { + "submenu": [ + { + "text": "ops/sec (ops)", + "value": "ops" + }, + { + "text": "requests/sec (rps)", + "value": "reqps" + }, + { + "text": "reads/sec (rps)", + "value": "rps" + }, + { + "text": "writes/sec (wps)", + "value": "wps" + }, + { + "text": "I/O ops/sec (iops)", + "value": "iops" + }, + { + "text": "ops/min (opm)", + "value": "opm" + }, + { + "text": "reads/min (rpm)", + "value": "rpm" + }, + { + "text": "writes/min (wpm)", + "value": "wpm" + } + ], + "text": "throughput" + }, + { + "submenu": [ + { + "text": "millimetre (mm)", + "value": "lengthmm" + }, + { + "text": "meter (m)", + "value": "lengthm" + }, + { + "text": "inch (in)", + "value": "lengthin" + }, + { + "text": "feet (ft)", + "value": "lengthft" + }, + { + "text": "kilometer (km)", + "value": "lengthkm" + }, + { + "text": "mile (mi)", + "value": "lengthmi" + } + ], + "text": "length" + }, + { + "submenu": [ + { + "text": "Square Meters (m²)", + "value": "areaM2" + }, + { + "text": "Square Feet (ft²)", + "value": "areaF2" + }, + { + "text": "Square Miles (mi²)", + "value": "areaMI2" + } + ], + "text": "area" + }, + { + "submenu": [ + { + "text": "milligram (mg)", + "value": "massmg" + }, + { + "text": "gram (g)", + "value": "massg" + }, + { + "text": "pound (lb)", + "value": "masslb" + }, + { + "text": "kilogram (kg)", + "value": "masskg" + }, + { + "text": "metric ton (t)", + "value": "masst" + } + ], + "text": "mass" + }, + { + "submenu": [ + { + "text": "metres/second (m/s)", + "value": "velocityms" + }, + { + "text": "kilometers/hour (km/h)", + "value": "velocitykmh" + }, + { + "text": "miles/hour (mph)", + "value": "velocitymph" + }, + { + "text": "knot (kn)", + "value": "velocityknot" + } + ], + "text": "velocity" + }, + { + "submenu": [ + { + "text": "millilitre (mL)", + "value": "mlitre" + }, + { + "text": "litre (L)", + "value": "litre" + }, + { + "text": "cubic metre", + "value": "m3" + }, + { + "text": "Normal cubic metre", + "value": "Nm3" + }, + { + "text": "cubic decimetre", + "value": "dm3" + }, + { + "text": "gallons", + "value": "gallons" + } + ], + "text": "volume" + }, + { + "submenu": [ + { + "text": "Watt (W)", + "value": "watt" + }, + { + "text": "Kilowatt (kW)", + "value": "kwatt" + }, + { + "text": "Milliwatt (mW)", + "value": "mwatt" + }, + { + "text": "Watt per square metre (W/m²)", + "value": "Wm2" + }, + { + "text": "Volt-ampere (VA)", + "value": "voltamp" + }, + { + "text": "Kilovolt-ampere (kVA)", + "value": "kvoltamp" + }, + { + "text": "Volt-ampere reactive (var)", + "value": "voltampreact" + }, + { + "text": "Kilovolt-ampere reactive (kvar)", + "value": "kvoltampreact" + }, + { + "text": "Watt-hour (Wh)", + "value": "watth" + }, + { + "text": "Kilowatt-hour (kWh)", + "value": "kwatth" + }, + { + "text": "Kilowatt-min (kWm)", + "value": "kwattm" + }, + { + "text": "Joule (J)", + "value": "joule" + }, + { + "text": "Electron volt (eV)", + "value": "ev" + }, + { + "text": "Ampere (A)", + "value": "amp" + }, + { + "text": "Kiloampere (kA)", + "value": "kamp" + }, + { + "text": "Milliampere (mA)", + "value": "mamp" + }, + { + "text": "Volt (V)", + "value": "volt" + }, + { + "text": "Kilovolt (kV)", + "value": "kvolt" + }, + { + "text": "Millivolt (mV)", + "value": "mvolt" + }, + { + "text": "Decibel-milliwatt (dBm)", + "value": "dBm" + }, + { + "text": "Ohm (Ω)", + "value": "ohm" + }, + { + "text": "Lumens (Lm)", + "value": "lumens" + } + ], + "text": "energy" + }, + { + "submenu": [ + { + "text": "Celsius (°C)", + "value": "celsius" + }, + { + "text": "Fahrenheit (°F)", + "value": "fahrenheit" + }, + { + "text": "Kelvin (K)", + "value": "kelvin" + } + ], + "text": "temperature" + }, + { + "submenu": [ + { + "text": "Millibars", + "value": "pressurembar" + }, + { + "text": "Bars", + "value": "pressurebar" + }, + { + "text": "Kilobars", + "value": "pressurekbar" + }, + { + "text": "Hectopascals", + "value": "pressurehpa" + }, + { + "text": "Kilopascals", + "value": "pressurekpa" + }, + { + "text": "Inches of mercury", + "value": "pressurehg" + }, + { + "text": "PSI", + "value": "pressurepsi" + } + ], + "text": "pressure" + }, + { + "submenu": [ + { + "text": "Newton-meters (Nm)", + "value": "forceNm" + }, + { + "text": "Kilonewton-meters (kNm)", + "value": "forcekNm" + }, + { + "text": "Newtons (N)", + "value": "forceN" + }, + { + "text": "Kilonewtons (kN)", + "value": "forcekN" + } + ], + "text": "force" + }, + { + "submenu": [ + { + "text": "Gallons/min (gpm)", + "value": "flowgpm" + }, + { + "text": "Cubic meters/sec (cms)", + "value": "flowcms" + }, + { + "text": "Cubic feet/sec (cfs)", + "value": "flowcfs" + }, + { + "text": "Cubic feet/min (cfm)", + "value": "flowcfm" + }, + { + "text": "Litre/hour", + "value": "litreh" + }, + { + "text": "Litre/min (l/min)", + "value": "flowlpm" + }, + { + "text": "milliLitre/min (mL/min)", + "value": "flowmlpm" + } + ], + "text": "flow" + }, + { + "submenu": [ + { + "text": "Degrees (°)", + "value": "degree" + }, + { + "text": "Radians", + "value": "radian" + }, + { + "text": "Gradian", + "value": "grad" + } + ], + "text": "angle" + }, + { + "submenu": [ + { + "text": "Meters/sec²", + "value": "accMS2" + }, + { + "text": "Feet/sec²", + "value": "accFS2" + }, + { + "text": "G unit", + "value": "accG" + } + ], + "text": "acceleration" + }, + { + "submenu": [ + { + "text": "Becquerel (Bq)", + "value": "radbq" + }, + { + "text": "curie (Ci)", + "value": "radci" + }, + { + "text": "Gray (Gy)", + "value": "radgy" + }, + { + "text": "rad", + "value": "radrad" + }, + { + "text": "Sievert (Sv)", + "value": "radsv" + }, + { + "text": "rem", + "value": "radrem" + }, + { + "text": "Exposure (C/kg)", + "value": "radexpckg" + }, + { + "text": "roentgen (R)", + "value": "radr" + }, + { + "text": "Sievert/hour (Sv/h)", + "value": "radsvh" + } + ], + "text": "radiation" + }, + { + "submenu": [ + { + "text": "parts-per-million (ppm)", + "value": "ppm" + }, + { + "text": "parts-per-billion (ppb)", + "value": "conppb" + }, + { + "text": "nanogram per cubic metre (ng/m³)", + "value": "conngm3" + }, + { + "text": "nanogram per normal cubic metre (ng/Nm³)", + "value": "conngNm3" + }, + { + "text": "microgram per cubic metre (μg/m³)", + "value": "conμgm3" + }, + { + "text": "microgram per normal cubic metre (μg/Nm³)", + "value": "conμgNm3" + }, + { + "text": "milligram per cubic metre (mg/m³)", + "value": "conmgm3" + }, + { + "text": "milligram per normal cubic metre (mg/Nm³)", + "value": "conmgNm3" + }, + { + "text": "gram per cubic metre (g/m³)", + "value": "congm3" + }, + { + "text": "gram per normal cubic metre (g/Nm³)", + "value": "congNm3" + } + ], + "text": "concentration" + } + ] + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/v0alpha1.v1beta1.v1beta1.all-panels.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/v0alpha1.v1beta1.v1beta1.all-panels.v42.v2alpha1.json index 315164a75a3..43ca604b064 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/v0alpha1.v1beta1.v1beta1.all-panels.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/v0alpha1.v1beta1.v1beta1.all-panels.v42.v2alpha1.json @@ -665,7 +665,42 @@ "kind": "heatmap", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "heatmap", + "originalOptions": { + "cards": {}, + "color": { + "cardColor": "#b4ff00", + "colorScale": "sqrt", + "colorScheme": "interpolateOranges", + "exponent": 0.5, + "mode": "spectrum" + }, + "dataFormat": "timeseries", + "heatmap": {}, + "hideZeroBuckets": false, + "highlightCards": true, + "legend": { + "show": false + }, + "reverseYBuckets": false, + "tooltip": { + "show": true, + "showHistogram": false + }, + "xAxis": { + "show": true + }, + "yAxis": { + "format": "short", + "logBase": 1, + "show": true + }, + "yBucketBound": "auto" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/v0alpha1.v1beta1.v1beta1.all-panels.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/v0alpha1.v1beta1.v1beta1.all-panels.v42.v2beta1.json index 94f898bfa16..ae693615b73 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/v0alpha1.v1beta1.v1beta1.all-panels.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/v0alpha1.v1beta1.v1beta1.all-panels.v42.v2beta1.json @@ -691,7 +691,42 @@ "group": "heatmap", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "heatmap", + "originalOptions": { + "cards": {}, + "color": { + "cardColor": "#b4ff00", + "colorScale": "sqrt", + "colorScheme": "interpolateOranges", + "exponent": 0.5, + "mode": "spectrum" + }, + "dataFormat": "timeseries", + "heatmap": {}, + "hideZeroBuckets": false, + "highlightCards": true, + "legend": { + "show": false + }, + "reverseYBuckets": false, + "tooltip": { + "show": true, + "showHistogram": false + }, + "xAxis": { + "show": true + }, + "yAxis": { + "format": "short", + "logBase": 1, + "show": true + }, + "yBucketBound": "auto" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/v0alpha1.v1beta1.v1beta1.home.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/v0alpha1.v1beta1.v1beta1.home.v42.v2alpha1.json index 2a6500f86db..7c77141f891 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/v0alpha1.v1beta1.v1beta1.home.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/v0alpha1.v1beta1.v1beta1.home.v42.v2alpha1.json @@ -56,6 +56,14 @@ "spec": { "pluginVersion": "9.0.0-pre", "options": { + "__angularMigration": { + "autoMigrateFrom": "dashlist", + "originalOptions": { + "tags": [ + "panel-tests" + ] + } + }, "maxItems": 1000, "query": "", "showHeadings": false, @@ -94,6 +102,15 @@ "spec": { "pluginVersion": "9.0.0-pre", "options": { + "__angularMigration": { + "autoMigrateFrom": "dashlist", + "originalOptions": { + "tags": [ + "gdev", + "demo" + ] + } + }, "maxItems": 1000, "query": "", "showHeadings": false, @@ -133,6 +150,15 @@ "spec": { "pluginVersion": "9.0.0-pre", "options": { + "__angularMigration": { + "autoMigrateFrom": "dashlist", + "originalOptions": { + "tags": [ + "templating", + "gdev" + ] + } + }, "maxItems": 1000, "query": "", "showHeadings": false, @@ -172,6 +198,15 @@ "spec": { "pluginVersion": "9.0.0-pre", "options": { + "__angularMigration": { + "autoMigrateFrom": "dashlist", + "originalOptions": { + "tags": [ + "gdev", + "datasource-test" + ] + } + }, "maxItems": 1000, "query": "", "showHeadings": false, @@ -211,6 +246,12 @@ "spec": { "pluginVersion": "9.0.0-pre", "options": { + "__angularMigration": { + "autoMigrateFrom": "dashlist", + "originalOptions": { + "tags": [] + } + }, "maxItems": 100, "query": "", "showHeadings": true, @@ -247,6 +288,15 @@ "spec": { "pluginVersion": "9.0.0-pre", "options": { + "__angularMigration": { + "autoMigrateFrom": "dashlist", + "originalOptions": { + "tags": [ + "gdev", + "demo" + ] + } + }, "maxItems": 1000, "query": "", "showHeadings": false, diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/v0alpha1.v1beta1.v1beta1.home.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/v0alpha1.v1beta1.v1beta1.home.v42.v2beta1.json index 0580a517cb2..5cec6a9741b 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/v0alpha1.v1beta1.v1beta1.home.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/v0alpha1.v1beta1.v1beta1.home.v42.v2beta1.json @@ -58,6 +58,14 @@ "version": "9.0.0-pre", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "dashlist", + "originalOptions": { + "tags": [ + "panel-tests" + ] + } + }, "maxItems": 1000, "query": "", "showHeadings": false, @@ -97,6 +105,15 @@ "version": "9.0.0-pre", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "dashlist", + "originalOptions": { + "tags": [ + "gdev", + "demo" + ] + } + }, "maxItems": 1000, "query": "", "showHeadings": false, @@ -137,6 +154,15 @@ "version": "9.0.0-pre", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "dashlist", + "originalOptions": { + "tags": [ + "templating", + "gdev" + ] + } + }, "maxItems": 1000, "query": "", "showHeadings": false, @@ -177,6 +203,15 @@ "version": "9.0.0-pre", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "dashlist", + "originalOptions": { + "tags": [ + "gdev", + "datasource-test" + ] + } + }, "maxItems": 1000, "query": "", "showHeadings": false, @@ -217,6 +252,12 @@ "version": "9.0.0-pre", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "dashlist", + "originalOptions": { + "tags": [] + } + }, "maxItems": 100, "query": "", "showHeadings": true, @@ -254,6 +295,15 @@ "version": "9.0.0-pre", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "dashlist", + "originalOptions": { + "tags": [ + "gdev", + "demo" + ] + } + }, "maxItems": 1000, "query": "", "showHeadings": false, diff --git a/apps/dashboard/pkg/migration/conversion/v1beta1_to_v2alpha1.go b/apps/dashboard/pkg/migration/conversion/v1beta1_to_v2alpha1.go index db86f8c87a9..47c12a6ac94 100644 --- a/apps/dashboard/pkg/migration/conversion/v1beta1_to_v2alpha1.go +++ b/apps/dashboard/pkg/migration/conversion/v1beta1_to_v2alpha1.go @@ -2296,20 +2296,24 @@ func buildVizConfig(panelMap map[string]interface{}) dashv2alpha1.DashboardVizCo // We check two cases: // 1. Panel already has autoMigrateFrom set (from v0→v1 migration) - panel type already converted // 2. Panel type is a known Angular panel - need to convert type AND set autoMigrateFrom + // 3. Panel has original options - need to set autoMigrateFrom and originalOptions autoMigrateFrom, hasAutoMigrateFrom := panelMap["autoMigrateFrom"].(string) + originalOptions := extractAngularOptions(panelMap) if !hasAutoMigrateFrom || autoMigrateFrom == "" { // Check if panel type is an Angular type that needs migration if newType := getAngularPanelMigration(panelType, panelMap); newType != "" { autoMigrateFrom = panelType // Original Angular type panelType = newType // New modern type + } else if len(originalOptions) > 0 { + autoMigrateFrom = panelType } } if autoMigrateFrom != "" { options["__angularMigration"] = map[string]interface{}{ "autoMigrateFrom": autoMigrateFrom, - "originalOptions": extractAngularOptions(panelMap), + "originalOptions": originalOptions, } } diff --git a/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/migrations/migrations.v42.json b/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/migrations/migrations.v42.json index 89ce80876d2..6ff361f144e 100644 --- a/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/migrations/migrations.v42.json +++ b/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/migrations/migrations.v42.json @@ -290,6 +290,7 @@ } }, { + "content": "# Graph panel \u003e\u003e Timeseries panel\n\nKnown issues:\n* hiding null/empty series\n* time regions", "datasource": { "type": "grafana-testdata-datasource" }, @@ -300,15 +301,7 @@ "y": 0 }, "id": 6, - "options": { - "code": { - "language": "plaintext", - "showLineNumbers": false, - "showMiniMap": false - }, - "content": "# Graph panel \u003e\u003e Timeseries panel\n\nKnown issues:\n* hiding null/empty series\n* time regions", - "mode": "markdown" - }, + "mode": "markdown", "pluginVersion": "11.0.0-pre", "targets": [ { diff --git a/devenv/dev-dashboards/migrations/migrations.json b/devenv/dev-dashboards/migrations/migrations.json index 4c2160e1f26..ad063b99cf4 100644 --- a/devenv/dev-dashboards/migrations/migrations.json +++ b/devenv/dev-dashboards/migrations/migrations.json @@ -299,15 +299,9 @@ "y": 0 }, "id": 6, - "options": { - "code": { - "language": "plaintext", - "showLineNumbers": false, - "showMiniMap": false - }, - "content": "# Graph panel >> Timeseries panel\n\nKnown issues:\n* hiding null/empty series\n* time regions", - "mode": "markdown" - }, + "options": {}, + "content": "# Graph panel >> Timeseries panel\n\nKnown issues:\n* hiding null/empty series\n* time regions", + "mode": "markdown", "pluginVersion": "11.0.0-pre", "targets": [ { diff --git a/eslint-suppressions.json b/eslint-suppressions.json index 08f37b316d6..80d18ce0cae 100644 --- a/eslint-suppressions.json +++ b/eslint-suppressions.json @@ -1888,11 +1888,6 @@ "count": 1 } }, - "public/app/features/dashboard-scene/serialization/angularMigration.test.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, "public/app/features/dashboard-scene/serialization/buildNewDashboardSaveModel.ts": { "@typescript-eslint/consistent-type-assertions": { "count": 1 diff --git a/public/app/features/dashboard-scene/serialization/angularMigration.test.ts b/public/app/features/dashboard-scene/serialization/angularMigration.test.ts index 46c67f6cd9f..2eea13b90d5 100644 --- a/public/app/features/dashboard-scene/serialization/angularMigration.test.ts +++ b/public/app/features/dashboard-scene/serialization/angularMigration.test.ts @@ -1,9 +1,22 @@ -import { PanelTypeChangedHandler } from '@grafana/data'; +import { FieldConfigSource, PanelTypeChangedHandler } from '@grafana/data'; import { getPanelPlugin } from '@grafana/data/test'; import { PanelModel } from 'app/features/dashboard/state/PanelModel'; import { getV2AngularMigrationHandler, getAngularPanelMigrationHandler } from './angularMigration'; +/** + * Test type for mutable panel model used in migration handler tests. + * Allows arbitrary properties to be added during migration. + */ +interface TestPanelModel { + id: number; + type: string; + options: Record; + fieldConfig: FieldConfigSource; + targets?: Array<{ refId: string; [key: string]: unknown }>; + [key: string]: unknown; +} + describe('getAngularPanelMigrationHandler', () => { describe('Given an old angular panel', () => { it('Should call migration handler', () => { @@ -43,9 +56,11 @@ describe('getAngularPanelMigrationHandler', () => { type: 'dashlist', }); - const mutatedModel: any = { + const mutatedModel: TestPanelModel = { + id: 1, type: 'dashlist', options: {}, + fieldConfig: { defaults: {}, overrides: [] }, }; getAngularPanelMigrationHandler(oldModel)(mutatedModel, reactPlugin); @@ -80,7 +95,7 @@ describe('getV2AngularMigrationHandler', () => { }, }; - const mutatedModel = { + const mutatedModel: TestPanelModel = { id: 1, type: 'stat', options: {}, @@ -89,6 +104,11 @@ describe('getV2AngularMigrationHandler', () => { getV2AngularMigrationHandler(migrationData)(mutatedModel, reactPlugin); + // Verify originalOptions were spread onto the panel (for plugins using setMigrationHandler) + expect(mutatedModel['format']).toBe('short'); + expect(mutatedModel['valueName']).toBe('avg'); + expect(mutatedModel['orientation']).toBe('horizontal'); + // Verify handler received correct arguments expect(receivedPrevPluginId).toBe('singlestat'); expect(receivedPrevOptions?.angular).toBeDefined(); @@ -123,7 +143,7 @@ describe('getV2AngularMigrationHandler', () => { }, }; - const mutatedModel = { + const mutatedModel: TestPanelModel = { id: 1, type: 'timeseries', options: {}, @@ -132,6 +152,10 @@ describe('getV2AngularMigrationHandler', () => { getV2AngularMigrationHandler(migrationData)(mutatedModel, reactPlugin); + // Verify originalOptions were spread onto the panel (for plugins using setMigrationHandler) + expect(mutatedModel['bars']).toBe(true); + expect(mutatedModel['lines']).toBe(false); + // Verify handler received correct arguments expect(receivedPrevPluginId).toBe('graph'); expect(receivedPrevOptions?.angular).toBeDefined(); @@ -163,7 +187,7 @@ describe('getV2AngularMigrationHandler', () => { }, }; - const mutatedModel = { + const mutatedModel: TestPanelModel = { id: 1, type: 'new-panel', options: {}, @@ -172,6 +196,9 @@ describe('getV2AngularMigrationHandler', () => { getV2AngularMigrationHandler(migrationData)(mutatedModel, reactPlugin); + // Verify originalOptions were spread onto the panel (for plugins using setMigrationHandler) + expect(mutatedModel['oldOption']).toBe('old'); + // Verify handler received correct arguments (options wrapper, not angular) expect(receivedPrevPluginId).toBe('some-react-panel'); expect(receivedPrevOptions?.options).toBeDefined(); @@ -222,4 +249,118 @@ describe('getV2AngularMigrationHandler', () => { warnSpy.mockRestore(); }); }); + + describe('Given v2 migration data for text panel with Angular-style properties', () => { + it('Should spread originalOptions onto panel for migration handlers using setMigrationHandler', () => { + // Text panel uses setMigrationHandler, not onPanelTypeChanged + // The migration handler expects content/mode to be directly on the panel object + const reactPlugin = getPanelPlugin({ id: 'text' }); + + // This simulates a text panel with Angular-style properties at root level + // The backend sets autoMigrateFrom="text" when it detects these properties + const migrationData = { + autoMigrateFrom: 'text', + originalOptions: { + content: 'Hello World', + mode: 'markdown', + }, + }; + + const mutatedModel: TestPanelModel = { + id: 1, + type: 'text', + options: {}, + fieldConfig: { defaults: {}, overrides: [] }, + }; + + getV2AngularMigrationHandler(migrationData)(mutatedModel, reactPlugin); + + // Verify originalOptions were spread onto the panel + // This allows textPanelMigrationHandler to see content/mode via panel.hasOwnProperty() + expect(mutatedModel['content']).toBe('Hello World'); + expect(mutatedModel['mode']).toBe('markdown'); + }); + + it('Should not overwrite existing panel properties when spreading originalOptions', () => { + const reactPlugin = getPanelPlugin({ id: 'text' }); + + const migrationData = { + autoMigrateFrom: 'text', + originalOptions: { + content: 'Old content', + mode: 'markdown', + id: 999, // Should not overwrite existing id + }, + }; + + const mutatedModel: TestPanelModel = { + id: 1, + type: 'text', + options: { existingOption: true }, + fieldConfig: { defaults: {}, overrides: [] }, + }; + + getV2AngularMigrationHandler(migrationData)(mutatedModel, reactPlugin); + + // defaults() only sets properties that don't already exist + expect(mutatedModel['content']).toBe('Old content'); + expect(mutatedModel['mode']).toBe('markdown'); + expect(mutatedModel.id).toBe(1); // Should keep original id + expect(mutatedModel.options).toEqual({ existingOption: true }); // Should keep existing options + }); + + it('Should work with empty originalOptions', () => { + const reactPlugin = getPanelPlugin({ id: 'text' }); + + const migrationData = { + autoMigrateFrom: 'text', + originalOptions: {}, + }; + + const mutatedModel: TestPanelModel = { + id: 1, + type: 'text', + options: {}, + fieldConfig: { defaults: {}, overrides: [] }, + }; + + // Should not throw + expect(() => { + getV2AngularMigrationHandler(migrationData)(mutatedModel, reactPlugin); + }).not.toThrow(); + }); + + it('Should spread originalOptions AND call onPanelTypeChanged if plugin has both', () => { + let handlerCalled = false; + const onPanelTypeChanged: PanelTypeChangedHandler = (panel, prevPluginId, prevOptions) => { + handlerCalled = true; + // Verify originalOptions were already spread onto panel before handler is called + expect((panel as TestPanelModel)['customProp']).toBe('custom value'); + return { migrated: true }; + }; + + const reactPlugin = getPanelPlugin({ id: 'custom-panel' }).setPanelChangeHandler(onPanelTypeChanged); + + const migrationData = { + autoMigrateFrom: 'custom-panel', + originalOptions: { + customProp: 'custom value', + }, + }; + + const mutatedModel: TestPanelModel = { + id: 1, + type: 'custom-panel', + options: {}, + fieldConfig: { defaults: {}, overrides: [] }, + }; + + getV2AngularMigrationHandler(migrationData)(mutatedModel, reactPlugin); + + // Verify both behaviors occurred + expect(mutatedModel['customProp']).toBe('custom value'); // originalOptions spread + expect(handlerCalled).toBe(true); // onPanelTypeChanged called + expect(mutatedModel.options).toEqual({ migrated: true }); // handler result applied + }); + }); }); diff --git a/public/app/features/dashboard-scene/serialization/angularMigration.ts b/public/app/features/dashboard-scene/serialization/angularMigration.ts index 54b47081080..c0751c802d9 100644 --- a/public/app/features/dashboard-scene/serialization/angularMigration.ts +++ b/public/app/features/dashboard-scene/serialization/angularMigration.ts @@ -77,6 +77,11 @@ export function getAngularPanelMigrationHandler(oldModel: PanelModel) { * 4. Handler calls stat plugin's onPanelTypeChanged with { angular: originalOptions } * 5. Plugin migrates format/valueName/etc to proper stat options * + * For panels where autoMigrateFrom equals the current type (e.g., "text" -> "text"): + * - These are panels with Angular-style properties at the root level (content, mode, etc.) + * - We spread originalOptions onto the panel so the plugin's migration handler can see them + * - This matches the v1 behavior where PanelModel.restoreModel() spreads all properties + * * @param migrationData The __angularMigration data extracted from panel options */ export function getV2AngularMigrationHandler(migrationData: AngularMigrationData) { @@ -90,19 +95,28 @@ export function getV2AngularMigrationHandler(migrationData: AngularMigrationData return; } - if (plugin.onPanelTypeChanged) { - // Some plugins rely on being able to access targets to set up the fieldConfig when migrating from angular. - // Proxy the targets property with a deprecation warning. - const targetClone = cloneDeep(panel.targets); - Object.defineProperty(panel, 'targets', { - get: function () { - console.warn( - 'Accessing the targets property when migrating a panel plugin is deprecated. Changes to this property will be ignored.' - ); - return targetClone; - }, - }); + // Spread originalOptions onto the panel object. + // This is critical for plugins that use setMigrationHandler (like text panel) which expect + // Angular properties (content, mode, etc.) to be directly on the panel object. + // This matches the v1 behavior where PanelModel.restoreModel() spreads all JSON properties. + if (originalOptions && Object.keys(originalOptions).length > 0) { + defaults(panel, originalOptions); + } + // Some plugins rely on being able to access targets to set up the fieldConfig when migrating from angular. + // Proxy the targets property with a deprecation warning. + const targetClone = cloneDeep(panel.targets); + Object.defineProperty(panel, 'targets', { + get: function () { + console.warn( + 'Accessing the targets property when migrating a panel plugin is deprecated. Changes to this property will be ignored.' + ); + return targetClone; + }, + }); + + // For panels with onPanelTypeChanged (e.g., singlestat -> stat), call the handler + if (plugin.onPanelTypeChanged) { // For Angular panels, wrap in { angular: ... } to match expected format // For React panels migrating from other React panels, pass options directly const prevOptions = wasAngular ? { angular: originalOptions } : { options: originalOptions }; diff --git a/public/app/features/dashboard/api/ResponseTransformers.ts b/public/app/features/dashboard/api/ResponseTransformers.ts index 5a536074efe..9d68b84e2e4 100644 --- a/public/app/features/dashboard/api/ResponseTransformers.ts +++ b/public/app/features/dashboard/api/ResponseTransformers.ts @@ -609,17 +609,25 @@ export function buildPanelKind(p: Panel): PanelKind { // Build options with Angular migration data if needed (matches backend behavior) // autoMigrateFrom is set during v0->v1 migration when Angular panels are converted - const { autoMigrateFrom } = p; + let { autoMigrateFrom } = p; let options = p.options ?? {}; + const originalOptions = extractAngularOptions(p); + + // When autoMigrateFrom is present OR when there are Angular-specific properties at root level, + // compose __angularMigration with only Angular-specific options. + // This filters out known Panel schema properties, passing only the Angular options to migration handlers. + // The second condition handles panels like "text" that have Angular-style properties (content, mode) + // but don't have autoMigrateFrom set because they don't need a type conversion. + if (!autoMigrateFrom && Object.keys(originalOptions).length > 0) { + autoMigrateFrom = p.type; + } - // When autoMigrateFrom is present, compose __angularMigration with only Angular-specific options - // This filters out known Panel schema properties, passing only the Angular options to migration handlers if (autoMigrateFrom) { options = { ...options, __angularMigration: { autoMigrateFrom, - originalOptions: extractAngularOptions(p), + originalOptions, }, }; } From 58a026b6a5bc0e869430c05829756e868ffe4d1b Mon Sep 17 00:00:00 2001 From: Yunwen Zheng Date: Thu, 18 Dec 2025 10:22:40 -0500 Subject: [PATCH 20/36] RecentlyViewedDashboards: Clear history button (#115519) * RecentlyViewedDashboards: Clear history button --- .../components/RecentlyViewedDashboards.tsx | 45 ++++++++++++++----- public/app/plugins/panel/dashlist/styles.ts | 2 +- public/locales/en-US/grafana.json | 1 + 3 files changed, 37 insertions(+), 11 deletions(-) diff --git a/public/app/features/browse-dashboards/components/RecentlyViewedDashboards.tsx b/public/app/features/browse-dashboards/components/RecentlyViewedDashboards.tsx index ebfbd1d0899..7b2d58fa423 100644 --- a/public/app/features/browse-dashboards/components/RecentlyViewedDashboards.tsx +++ b/public/app/features/browse-dashboards/components/RecentlyViewedDashboards.tsx @@ -1,10 +1,12 @@ import { css } from '@emotion/css'; -import { useAsync } from 'react-use'; +import { useState } from 'react'; +import { useAsyncRetry } from 'react-use'; -import { GrafanaTheme2 } from '@grafana/data'; +import { GrafanaTheme2, store } from '@grafana/data'; import { t, Trans } from '@grafana/i18n'; import { evaluateBooleanFlag } from '@grafana/runtime/internal'; -import { CollapsableSection, Grid, Spinner, Text, useStyles2 } from '@grafana/ui'; +import { Button, CollapsableSection, Spinner, Stack, Text, useStyles2, Grid } from '@grafana/ui'; +import { contextSrv } from 'app/core/services/context_srv'; import { useDashboardLocationInfo } from 'app/features/search/hooks/useDashboardLocationInfo'; import { DashListItem } from 'app/plugins/panel/dashlist/DashListItem'; @@ -12,10 +14,18 @@ import { getRecentlyViewedDashboards } from './utils'; const MAX_RECENT = 5; +const recentDashboardsKey = `dashboard_impressions-${contextSrv.user.orgId}`; + export function RecentlyViewedDashboards() { + const [isOpen, setIsOpen] = useState(true); + const styles = useStyles2(getStyles); - const { value: recentDashboards = [], loading } = useAsync(async () => { + const { + value: recentDashboards = [], + loading, + retry, + } = useAsyncRetry(async () => { if (!evaluateBooleanFlag('recentlyViewedDashboards', false)) { return []; } @@ -23,6 +33,11 @@ export function RecentlyViewedDashboards() { }, []); const { foldersByUid } = useDashboardLocationInfo(recentDashboards.length > 0); + const handleClearHistory = () => { + store.set(recentDashboardsKey, JSON.stringify([])); + retry(); + }; + if (!evaluateBooleanFlag('recentlyViewedDashboards', false)) { return null; } @@ -31,11 +46,19 @@ export function RecentlyViewedDashboards() { - Recently viewed - + + setIsOpen(!isOpen)}> + Recently viewed + + + } - isOpen={true} + isOpen={isOpen} + // passing empty function to disable controlled mode, we only want to control isOpen when click on title + // this avoid entire header section being clickable which can be confusing with the Clear history button + onToggle={() => {}} className={styles.title} contentClassName={styles.content} > @@ -71,14 +94,16 @@ export function RecentlyViewedDashboards() { const getStyles = (theme: GrafanaTheme2) => { return { title: css({ - '& button svg': { + cursor: 'default', + '& [id^="collapse-button-"] svg': { color: theme.colors.primary.text, }, h3: { - background: `linear-gradient(90deg, ${theme.colors.primary.text} 0%, ${theme.colors.secondary.text} 100%)`, + background: `linear-gradient(90deg, ${theme.colors.primary.shade} 0%, ${theme.colors.primary.text} 100%)`, WebkitTextFillColor: 'transparent', backgroundClip: 'text', color: 'transparent', + cursor: 'pointer', }, }), content: css({ diff --git a/public/app/plugins/panel/dashlist/styles.ts b/public/app/plugins/panel/dashlist/styles.ts index 5e725daaf2d..c6346480c22 100644 --- a/public/app/plugins/panel/dashlist/styles.ts +++ b/public/app/plugins/panel/dashlist/styles.ts @@ -6,7 +6,7 @@ export const getStyles = (theme: GrafanaTheme2) => { const gradient = `linear-gradient( 90deg, ${colorManipulator.alpha(theme.colors.primary.text, 0.1)} 0%, - ${colorManipulator.alpha(theme.colors.secondary.text, 0.1)} 100% + ${colorManipulator.alpha(theme.colors.secondary.main, 0.1)} 100% )`; return { dashlistLink: css({ diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index 25ad73abe1a..2e86a2a42d3 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -3717,6 +3717,7 @@ "text": "No results found for your query" }, "recently-viewed": { + "clear": "Clear history", "empty": "Nothing viewed yet", "title": "Recently viewed" }, From a65aa9d18fa5858359c09d2efba5b6e58935247a Mon Sep 17 00:00:00 2001 From: Vardan Torosyan Date: Thu, 18 Dec 2025 16:26:56 +0100 Subject: [PATCH 21/36] SCIM Docs: Replace warning with an information text for SAML identifier (#115353) * SCIM Docs: Replace warning with an information text for SAML identifier * Fix externalId warning --- .../configure-scim-provisioning/_index.md | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/docs/sources/setup-grafana/configure-access/configure-scim-provisioning/_index.md b/docs/sources/setup-grafana/configure-access/configure-scim-provisioning/_index.md index 9ef98bbc433..a07dde69e08 100644 --- a/docs/sources/setup-grafana/configure-access/configure-scim-provisioning/_index.md +++ b/docs/sources/setup-grafana/configure-access/configure-scim-provisioning/_index.md @@ -54,18 +54,6 @@ SCIM offers several advantages for managing users and teams in Grafana: ## Authentication and access requirements -{{< admonition type="warning" title="Critical: Aligning SAML Identifier with SCIM externalId" >}} -When using SAML for authentication alongside SCIM provisioning, a critical security measure is to ensure proper alignment between the the SCIM user's `externalId` and the SAML user identifier. The unique identifier used for SCIM provisioning (which becomes the `externalId` in Grafana, often sourced from a stable IdP attribute like Entra ID's `user.objectid`) **must also be sent as a claim in the SAML assertion from your Identity Provider.** -Furthermore, the Grafana SAML configuration must be correctly set up to identify and use this specific claim for linking the authenticated SAML user to their SCIM-provisioned user. This can be achieved by either ensuring the primary SAML login identifier by using the `assertion_attribute_external_uid` setting in Grafana to explicitly set the name of the SAML claim that contains the stable unique identifier attribute. - -**Why is this important?** -A mismatch or inconsistent mapping between this SAML login identifier and the SCIM `externalId` creates a critical security vulnerability. If these two identifiers are not reliably and uniquely aligned for each individual user, Grafana may fail to correctly link an authenticated SAML session to the intended SCIM-provisioned user profile and its associated permissions. This can enable a malicious actor to impersonate another user—for instance, by crafting a SAML assertion that, due to the identifier misalignment, incorrectly grants them the access rights of the targeted user. - -Grafana relies on this linkage to correctly associate the authenticated user from SAML with the provisioned user from SCIM. Failure to ensure a consistent and unique identifier across both systems can break this linkage, leading to incorrect user mapping and potential unauthorized access. - -Always verify that your SAML identity provider is configured to send a stable, unique user identifier that your SCIM configuration maps to `externalId`. Refer to your identity provider's documentation and the specific Grafana SCIM integration guides (e.g., for [Entra ID](configure-scim-with-azuread/) or [Okta](configure-scim-with-okta/)) for detailed instructions on configuring these attributes correctly. -{{< /admonition >}} - When you enable SCIM in Grafana, the following requirements and restrictions apply: 1. **Use the same identity provider for user provisioning and for authentication flow**: You must use the same identity provider for both authentication and user provisioning. @@ -74,6 +62,12 @@ When you enable SCIM in Grafana, the following requirements and restrictions app - Configure `userUID` SAML assertion in [Entra ID](/docs/grafana//setup-grafana/configure-access/configure-authentication/saml/configure-saml-with-azuread/#configure-saml-assertions-when-using-scim-provisioning) - Configure `userUID` SAML assertion in [Okta](/docs/grafana//setup-grafana/configure-access/configure-authentication/saml/configure-saml-with-okta/#configure-saml-assertions-when-using-scim-provisioning) +### Align SAML identifier with SCIM `externalId` + +When you use SAML with SCIM provisioning, align the SCIM `externalId` with the SAML user identifier. Use a stable IdP attribute (for example, Entra ID `user.objectid`) as the SCIM `externalId`, and send that same value as a SAML claim. Configure Grafana to read this claim with the `assertion_attribute_external_uid` setting so SAML authentication links to the SCIM-provisioned user and its permissions. + +If the SAML identifier and SCIM `externalId` differ, Grafana may not link the authenticated user to the intended SCIM profile, which can result in incorrect access. Verify your IdP sends a stable, unique identifier and that it matches the SCIM `externalId`. Refer to your IdP docs and the Grafana SCIM integration guides for [Entra ID](configure-scim-with-azuread/) and [Okta](configure-scim-with-okta/) for attribute configuration details. + ## Configure SCIM using the Grafana user interface You can configure SCIM in Grafana using the Grafana user interface. To do this, navigate to **Administration > Authentication > SCIM**. From 98aa6c50dc6ef8979c2f8e5e9f4fee4a944a40f1 Mon Sep 17 00:00:00 2001 From: Alexa Vargas <239999+axelavargas@users.noreply.github.com> Date: Thu, 18 Dec 2025 16:42:37 +0100 Subject: [PATCH 22/36] DashboardLibrary: Force v1 dashboard scene page manager when loading template dashboards (#115488) Force v1 manager for template dashboards feature --- .../DashboardScenePageStateManager.test.ts | 117 ++++++++++++++++++ .../pages/DashboardScenePageStateManager.ts | 6 + 2 files changed, 123 insertions(+) diff --git a/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.test.ts b/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.test.ts index 00de1c7ea13..b1857c9f01e 100644 --- a/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.test.ts +++ b/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.test.ts @@ -1673,6 +1673,123 @@ describe('UnifiedDashboardScenePageStateManager', () => { expect(manager2['activeManager']).toBeInstanceOf(DashboardScenePageStateManagerV2); }); }); + + describe('Template dashboards', () => { + let originalGetLocation: typeof locationService.getLocation; + + beforeEach(() => { + originalGetLocation = locationService.getLocation; + + // Mock window.location.search for loadDashboardLibrary + Object.defineProperty(window, 'location', { + value: { + search: '?gnetId=969&datasource=xpyRJd9Mz&mappings=%5B%5D', + }, + writable: true, + }); + }); + + afterEach(() => { + locationService.getLocation = originalGetLocation; + }); + + it('should always use v1 manager for template dashboards even when dashboardNewLayouts is enabled', async () => { + config.featureToggles.dashboardNewLayouts = true; + config.featureToggles.suggestedDashboards = true; + + // Mock location service with gnetId and mappings parameters + locationService.getLocation = jest.fn().mockReturnValue({ + pathname: '/dashboard/template', + search: '?gnetId=969&datasource=xpyRJd9Mz&mappings=%5B%5D', + }); + + // Mock the backend to return a community dashboard from grafana.com + setBackendSrv({ + get: jest.fn((url: string) => { + if (url.includes('/api/gnet/dashboards/')) { + return Promise.resolve({ + json: { + title: 'AWS ElastiCache Redis', + uid: '', + panels: [], + schemaVersion: 40, + }, + }); + } + return Promise.reject(new Error('Not found')); + }), + post: jest.fn((url: string) => { + if (url === '/api/dashboards/interpolate') { + return Promise.resolve({ + title: 'AWS ElastiCache Redis', + uid: '', + panels: [], + schemaVersion: 40, + }); + } + return Promise.reject(new Error('Not found')); + }), + } as unknown as BackendSrv); + + const manager = new UnifiedDashboardScenePageStateManager({}); + expect(manager['activeManager']).toBeInstanceOf(DashboardScenePageStateManagerV2); + + await manager.loadDashboard({ uid: '', route: DashboardRoutes.Template }); + + // Should switch to V1 manager for template dashboards + expect(manager['activeManager']).toBeInstanceOf(DashboardScenePageStateManager); + }); + + it('should reset to V2 manager when loading a new dashboard after template', async () => { + config.featureToggles.dashboardNewLayouts = true; + config.featureToggles.suggestedDashboards = true; + + // Mock locationService for this test too + locationService.getLocation = jest.fn().mockReturnValue({ + pathname: '/dashboard/template', + search: '?gnetId=969&datasource=xpyRJd9Mz&mappings=%5B%5D', + }); + + // Mock the backend for template load + setBackendSrv({ + get: jest.fn((url: string) => { + if (url.includes('/api/gnet/dashboards/')) { + return Promise.resolve({ + json: { + title: 'Template Dashboard', + uid: '', + panels: [], + schemaVersion: 40, + }, + }); + } + return Promise.reject(new Error('Not found')); + }), + post: jest.fn((url: string) => { + if (url === '/api/dashboards/interpolate') { + return Promise.resolve({ + title: 'Template Dashboard', + uid: '', + panels: [], + schemaVersion: 40, + }); + } + return Promise.reject(new Error('Not found')); + }), + } as unknown as BackendSrv); + + const manager = new UnifiedDashboardScenePageStateManager({}); + + // Load template - forces V1 + await manager.loadDashboard({ uid: '', route: DashboardRoutes.Template }); + expect(manager['activeManager']).toBeInstanceOf(DashboardScenePageStateManager); + + // Load new dashboard - should reset to V2 based on shouldForceV2API() + await manager.loadDashboard({ uid: '', route: DashboardRoutes.New }); + // Should be back to V2 manager + expect(manager['activeManager']).toBeInstanceOf(DashboardScenePageStateManagerV2); + }); + }); }); const customHomeDashboardV1Spec = { diff --git a/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts b/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts index 3a1841e6b32..3cc0df33e8a 100644 --- a/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts +++ b/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts @@ -1082,6 +1082,12 @@ export class UnifiedDashboardScenePageStateManager extends DashboardScenePageSta const newDashboardVersion = shouldForceV2API() ? 'v2' : 'v1'; this.setActiveManager(newDashboardVersion); } + + // Template dashboards are currently in v1 schema format. + if (options.route === DashboardRoutes.Template) { + this.setActiveManager('v1'); + } + return this.withVersionHandling((manager) => manager.loadDashboard.call(this, options)); } From d0792ebe9715ed91851c6eaf5a6494485cfe37ef Mon Sep 17 00:00:00 2001 From: Charandas <542168+charandas@users.noreply.github.com> Date: Thu, 18 Dec 2025 07:44:33 -0800 Subject: [PATCH 23/36] Secrets: Add gRPC client retry with exp. backoff" (#115526) Provisioning: secrets decrypt client should retry with exponential backoff --- pkg/registry/apis/secret/decrypt/grpc_client.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/pkg/registry/apis/secret/decrypt/grpc_client.go b/pkg/registry/apis/secret/decrypt/grpc_client.go index f892de5ebe5..0612455c3d8 100644 --- a/pkg/registry/apis/secret/decrypt/grpc_client.go +++ b/pkg/registry/apis/secret/decrypt/grpc_client.go @@ -9,10 +9,13 @@ import ( "maps" "os" "slices" + "time" "github.com/fullstorydev/grpchan" + grpc_retry "github.com/grpc-ecosystem/go-grpc-middleware/retry" "go.opentelemetry.io/otel/trace" "google.golang.org/grpc" + "google.golang.org/grpc/codes" "google.golang.org/grpc/credentials" "google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/metadata" @@ -72,6 +75,15 @@ func NewGRPCDecryptClientWithTLS( opts = append(opts, grpc.WithDisableServiceConfig()) } + // Add retry interceptor to retry on transient connection issues. + // Retries on ResourceExhausted (per-RPC limits reached) and Unavailable (system unavailable). + retryInterceptor := grpc_retry.UnaryClientInterceptor( + grpc_retry.WithMax(3), + grpc_retry.WithBackoff(grpc_retry.BackoffExponentialWithJitter(time.Second, 0.5)), + grpc_retry.WithCodes(codes.ResourceExhausted, codes.Unavailable), + ) + opts = append(opts, grpc.WithUnaryInterceptor(retryInterceptor)) + conn, err := grpc.NewClient(address, opts...) if err != nil { return nil, fmt.Errorf("failed to connect to grpc decrypt server at %s: %w", address, err) From facb25a09c260bc52c301849cce82999aa8edbf3 Mon Sep 17 00:00:00 2001 From: Igor Suleymanov Date: Thu, 18 Dec 2025 18:07:48 +0200 Subject: [PATCH 24/36] Fix Grafana App SDK logger log level (#115551) * Fix Grafana App SDK logger log level What This commit fixes the hardcoded value of the app SDK logger log level by properly setting it during the log manager initialization. Why To prevent app SDK logging from always logging at DEBUG. Signed-off-by: Igor Suleymanov * Add missing argument to the logging test Signed-off-by: Igor Suleymanov --------- Signed-off-by: Igor Suleymanov --- pkg/infra/log/log.go | 31 +++++++++++++++++++++++-------- pkg/infra/log/log_test.go | 2 +- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/pkg/infra/log/log.go b/pkg/infra/log/log.go index 9e7a51a8dc7..abae387729e 100644 --- a/pkg/infra/log/log.go +++ b/pkg/infra/log/log.go @@ -54,7 +54,8 @@ func init() { } logger := level.NewFilter(format(os.Stderr), level.AllowInfo()) root = newManager(logger) - initAppSDKLogger(logger) + // Use default Info level during package initialization before config is loaded + initAppSDKLogger(logger, slog.LevelInfo) RegisterContextualLogProvider(func(ctx context.Context) ([]any, bool) { pFromCtx := ctx.Value(logParamsContextKey{}) @@ -80,7 +81,7 @@ func newManager(logger gokitlog.Logger) *logManager { } } -func (lm *logManager) initialize(loggers []logWithFilters) { +func (lm *logManager) initialize(loggers []logWithFilters, levelStr string) { lm.mutex.Lock() defer lm.mutex.Unlock() @@ -113,7 +114,7 @@ func (lm *logManager) initialize(loggers []logWithFilters) { lm.loggersByName[name].Swap(&compositeLogger{loggers: ctxLoggers}) } - initAppSDKLogger(lm.ConcreteLogger) + initAppSDKLogger(lm.ConcreteLogger, stringToSlogLevel(levelStr)) } func (lm *logManager) New(ctx ...any) *ConcreteLogger { @@ -514,7 +515,7 @@ func ReadLoggingConfig(modes []string, logsPath string, cfg *ini.File) error { configLoggers = append(configLoggers, handler) } if len(configLoggers) > 0 { - root.initialize(configLoggers) + root.initialize(configLoggers, defaultLevelName) } return nil @@ -551,8 +552,22 @@ func SetupConsoleLogger(level string) error { return nil } -func initAppSDKLogger(gkl gokitlog.Logger) { - // We need to allow Debug logs here. go-kit/log does not support sharing the level we're using. - // TODO: Refactor such that we can pass in a level in a more appropriate manner. - logging.DefaultLogger = logging.NewSLogLogger(sloggokit.NewGoKitHandler(gkl, slog.LevelDebug)) +// stringToSlogLevel converts a log level string to slog.Level +func stringToSlogLevel(levelStr string) slog.Level { + switch strings.ToLower(levelStr) { + case "trace", "debug": + return slog.LevelDebug + case "info": + return slog.LevelInfo + case "warn", "warning": + return slog.LevelWarn + case "error", "critical": + return slog.LevelError + default: + return slog.LevelInfo + } +} + +func initAppSDKLogger(gkl gokitlog.Logger, level slog.Level) { + logging.DefaultLogger = logging.NewSLogLogger(sloggokit.NewGoKitHandler(gkl, level)) } diff --git a/pkg/infra/log/log_test.go b/pkg/infra/log/log_test.go index 98fee4be8c0..5302e565964 100644 --- a/pkg/infra/log/log_test.go +++ b/pkg/infra/log/log_test.go @@ -88,7 +88,7 @@ func TestNew(t *testing.T) { val: swapLogger, maxLevel: level.AllowAll(), }, - }) + }, "info") err := log1.Log("msg", "hello 1") require.NoError(t, err) From 19f6dbe1bb8f51e6cbfdedf74d905bb78900a308 Mon Sep 17 00:00:00 2001 From: Renato Costa <103441181+renatolabs@users.noreply.github.com> Date: Thu, 18 Dec 2025 11:21:36 -0500 Subject: [PATCH 25/36] unified-storage: add `BatchGet` support to the sqlkv implementation (#115517) * unified-storage: add `BatchGet` support to the sqlkv implementation * address comments * fix linting --- .../unified/resource/data/sqlkv_batch_get.sql | 12 +++ pkg/storage/unified/resource/sqlkv.go | 78 +++++++++++++++++-- pkg/storage/unified/testing/kv.go | 75 +++++++++++------- pkg/storage/unified/testing/kv_test.go | 1 - 4 files changed, 131 insertions(+), 35 deletions(-) create mode 100644 pkg/storage/unified/resource/data/sqlkv_batch_get.sql diff --git a/pkg/storage/unified/resource/data/sqlkv_batch_get.sql b/pkg/storage/unified/resource/data/sqlkv_batch_get.sql new file mode 100644 index 00000000000..0babcb36970 --- /dev/null +++ b/pkg/storage/unified/resource/data/sqlkv_batch_get.sql @@ -0,0 +1,12 @@ +SELECT r.{{ .Ident "key_path" }}, r.{{ .Ident "value" }} +FROM ( +{{ range $id, $key_path := .KeyPaths }} + {{ if eq $id 0 }} + SELECT {{ $.Arg $id }} AS idx, {{ $.Arg $key_path }} AS key_path + {{ else }} + UNION ALL SELECT {{ $.Arg $id }}, {{ $.Arg $key_path }} + {{ end }} +{{ end }} +) AS requested_keys +INNER JOIN {{ .TableName }} r ON r.{{ .Ident "key_path" }} = requested_keys.{{ .Ident "key_path" }} +ORDER BY requested_keys.{{ .Ident "idx" }}; diff --git a/pkg/storage/unified/resource/sqlkv.go b/pkg/storage/unified/resource/sqlkv.go index a56d00a3e12..22cd3085122 100644 --- a/pkg/storage/unified/resource/sqlkv.go +++ b/pkg/storage/unified/resource/sqlkv.go @@ -34,9 +34,10 @@ func mustTemplate(filename string) *template.Template { // Templates. var ( - sqlKVGet = mustTemplate("sqlkv_get.sql") - sqlKVDelete = mustTemplate("sqlkv_delete.sql") - sqlKVKeys = mustTemplate("sqlkv_keys.sql") + sqlKVKeys = mustTemplate("sqlkv_keys.sql") + sqlKVGet = mustTemplate("sqlkv_get.sql") + sqlKVBatchGet = mustTemplate("sqlkv_batch_get.sql") + sqlKVDelete = mustTemplate("sqlkv_delete.sql") ) // sqlKVSection can be embedded in structs used when rendering query templates @@ -107,13 +108,23 @@ func (req sqlKVGetRequest) Results() ([]byte, error) { return req.Value, nil } -type sqlKVDeleteRequest struct { +type sqlKVBatchGetRequest struct { sqltemplate.SQLTemplate - sqlKVSectionKey + sqlKVSection + Keys []string } -func (req sqlKVDeleteRequest) Validate() error { - return req.sqlKVSectionKey.Validate() +func (req sqlKVBatchGetRequest) Validate() error { + return req.sqlKVSection.Validate() +} + +func (req sqlKVBatchGetRequest) KeyPaths() []string { + result := make([]string, 0, len(req.Keys)) + for _, key := range req.Keys { + result = append(result, req.Section+"/"+key) + } + + return result } type sqlKVKeysRequest struct { @@ -142,6 +153,15 @@ func (req sqlKVKeysRequest) SortAscending() bool { return req.Options.Sort != SortOrderDesc } +type sqlKVDeleteRequest struct { + sqltemplate.SQLTemplate + sqlKVSectionKey +} + +func (req sqlKVDeleteRequest) Validate() error { + return req.sqlKVSectionKey.Validate() +} + var _ KV = &sqlKV{} type sqlKV struct { @@ -188,6 +208,7 @@ func (k *sqlKV) Keys(ctx context.Context, section string, opt ListOptions) iter. yield("", err) return } + defer closeRows(rows, yield) for rows.Next() { var key string @@ -225,7 +246,41 @@ func (k *sqlKV) Get(ctx context.Context, section string, key string) (io.ReadClo func (k *sqlKV) BatchGet(ctx context.Context, section string, keys []string) iter.Seq2[KeyValue, error] { return func(yield func(KeyValue, error) bool) { - panic("not implemented!") + if len(keys) == 0 { + return + } + + rows, err := dbutil.QueryRows(ctx, k.db, sqlKVBatchGet, sqlKVBatchGetRequest{ + SQLTemplate: sqltemplate.New(k.dialect), + sqlKVSection: sqlKVSection{section}, + Keys: keys, + }) + if err != nil { + yield(KeyValue{}, err) + return + } + defer closeRows(rows, yield) + + for rows.Next() { + var key string + var value []byte + if err := rows.Scan(&key, &value); err != nil { + yield(KeyValue{}, fmt.Errorf("error reading row: %w", err)) + return + } + + kv := KeyValue{ + Key: strings.TrimPrefix(key, section+"/"), + Value: io.NopCloser(bytes.NewReader(value)), + } + if !yield(kv, nil) { + return + } + } + + if err := rows.Err(); err != nil { + yield(KeyValue{}, fmt.Errorf("failed to read rows: %w", err)) + } } } @@ -273,3 +328,10 @@ func (k *sqlKV) BatchDelete(ctx context.Context, section string, keys []string) func (k *sqlKV) UnixTimestamp(ctx context.Context) (int64, error) { panic("not implemented!") } + +func closeRows[T any](rows db.Rows, yield func(T, error) bool) { + if err := rows.Close(); err != nil { + var zero T + yield(zero, fmt.Errorf("error closing rows: %w", err)) + } +} diff --git a/pkg/storage/unified/testing/kv.go b/pkg/storage/unified/testing/kv.go index b4c61f84c13..fbd831f6281 100644 --- a/pkg/storage/unified/testing/kv.go +++ b/pkg/storage/unified/testing/kv.go @@ -103,6 +103,7 @@ func namespacedKey(nsPrefix, key string) string { func runTestKVGet(t *testing.T, kv resource.KV, nsPrefix string) { ctx := testutil.NewTestContext(t, time.Now().Add(30*time.Second)) + nsPrefix += "-get" t.Run("get existing key", func(t *testing.T) { // First save a key @@ -221,6 +222,7 @@ func runTestKVSave(t *testing.T, kv resource.KV, nsPrefix string) { func runTestKVDelete(t *testing.T, kv resource.KV, nsPrefix string) { ctx := testutil.NewTestContext(t, time.Now().Add(30*time.Second)) + nsPrefix += "-delete" t.Run("delete existing key", func(t *testing.T) { // First create a key @@ -262,6 +264,7 @@ func runTestKVDelete(t *testing.T, kv resource.KV, nsPrefix string) { func runTestKVKeys(t *testing.T, kv resource.KV, nsPrefix string) { ctx := testutil.NewTestContext(t, time.Now().Add(30*time.Second)) + nsPrefix += "-keys" // Setup test data testKeys := namespacedKeys(nsPrefix, []string{"a1", "a2", "b1", "b2", "c1"}) @@ -360,6 +363,7 @@ func runTestKVKeys(t *testing.T, kv resource.KV, nsPrefix string) { func runTestKVKeysWithLimits(t *testing.T, kv resource.KV, nsPrefix string) { ctx := testutil.NewTestContext(t, time.Now().Add(30*time.Second)) + nsPrefix += "-keys-with-limits" // Setup test data testKeys := namespacedKeys(nsPrefix, []string{"a1", "a2", "b1", "b2", "c1", "c2", "d1", "d2"}) @@ -416,6 +420,7 @@ func runTestKVKeysWithLimits(t *testing.T, kv resource.KV, nsPrefix string) { func runTestKVKeysWithSort(t *testing.T, kv resource.KV, nsPrefix string) { ctx := testutil.NewTestContext(t, time.Now().Add(30*time.Second)) + nsPrefix += "-keys-with-sort" // Setup test data testKeys := namespacedKeys(nsPrefix, []string{"a1", "a2", "b1", "b2", "c1"}) @@ -619,29 +624,29 @@ func runTestKVUnixTimestamp(t *testing.T, kv resource.KV, nsPrefix string) { func runTestKVBatchGet(t *testing.T, kv resource.KV, nsPrefix string) { ctx := testutil.NewTestContext(t, time.Now().Add(30*time.Second)) - section := nsPrefix + "-batchget" + nsPrefix += "-batchget" t.Run("batch get existing keys", func(t *testing.T) { // Setup test data testData := map[string]string{ - "key1": "value1", - "key2": "value2", - "key3": "value3", + namespacedKey(nsPrefix, "key1"): "value1", + namespacedKey(nsPrefix, "key2"): "value2", + namespacedKey(nsPrefix, "key3"): "value3", } // Save test data for key, value := range testData { - saveKVHelper(t, kv, ctx, section, key, strings.NewReader(value)) + saveKVHelper(t, kv, ctx, testSection, key, strings.NewReader(value)) } // Batch get all keys - keys := []string{"key1", "key2", "key3"} + keys := namespacedKeys(nsPrefix, []string{"key1", "key2", "key3"}) type result struct { key string value string } var results []result - for kv, err := range kv.BatchGet(ctx, section, keys) { + for kv, err := range kv.BatchGet(ctx, testSection, keys) { require.NoError(t, err) value, err := io.ReadAll(kv.Value) require.NoError(t, err) @@ -651,10 +656,10 @@ func runTestKVBatchGet(t *testing.T, kv resource.KV, nsPrefix string) { } // Verify results - assert.Len(t, results, 3) + require.Len(t, results, 3) // Check that all keys are present and in order - expectedKeys := []string{"key1", "key2", "key3"} + expectedKeys := namespacedKeys(nsPrefix, []string{"key1", "key2", "key3"}) actualKeys := make([]string, len(results)) for i, r := range results { actualKeys[i] = r.key @@ -663,22 +668,40 @@ func runTestKVBatchGet(t *testing.T, kv resource.KV, nsPrefix string) { // Verify values for _, r := range results { - assert.Equal(t, testData[r.key], r.value) + assert.Equal(t, testData[r.key], r.value, "key = %s", r.key) } }) + t.Run("batch get with empty section", func(t *testing.T) { + var kvs []resource.KeyValue + var errs []error + keys := namespacedKeys(nsPrefix, []string{"key1", "key2", "key3"}) + for kv, err := range kv.BatchGet(ctx, "", keys) { + if err != nil { + errs = append(errs, err) + continue + } + + kvs = append(kvs, kv) + } + + require.Len(t, errs, 1) + assert.Contains(t, errs[0].Error(), "section is required") + assert.Empty(t, kvs) + }) + t.Run("batch get with non-existent keys", func(t *testing.T) { // Setup some test data - saveKVHelper(t, kv, ctx, section, "existing-key", strings.NewReader("existing-value")) + saveKVHelper(t, kv, ctx, testSection, namespacedKey(nsPrefix, "existing-key"), strings.NewReader("existing-value")) // Batch get with mix of existing and non-existent keys - keys := []string{"existing-key", "non-existent-1", "non-existent-2"} + keys := namespacedKeys(nsPrefix, []string{"existing-key", "non-existent-1", "non-existent-2"}) type result struct { key string value string } var results []result - for kv, err := range kv.BatchGet(ctx, section, keys) { + for kv, err := range kv.BatchGet(ctx, testSection, keys) { require.NoError(t, err) value, err := io.ReadAll(kv.Value) require.NoError(t, err) @@ -688,15 +711,15 @@ func runTestKVBatchGet(t *testing.T, kv resource.KV, nsPrefix string) { } // Should only return the existing key - assert.Len(t, results, 1) - assert.Equal(t, "existing-key", results[0].key) + require.Len(t, results, 1) + assert.Equal(t, namespacedKey(nsPrefix, "existing-key"), results[0].key) assert.Equal(t, "existing-value", results[0].value) }) t.Run("batch get with all non-existent keys", func(t *testing.T) { - keys := []string{"non-existent-1", "non-existent-2", "non-existent-3"} + keys := namespacedKeys(nsPrefix, []string{"non-existent-1", "non-existent-2", "non-existent-3"}) var results []resource.KeyValue - for kv, err := range kv.BatchGet(ctx, section, keys) { + for kv, err := range kv.BatchGet(ctx, testSection, keys) { require.NoError(t, err) results = append(results, kv) } @@ -708,7 +731,7 @@ func runTestKVBatchGet(t *testing.T, kv resource.KV, nsPrefix string) { t.Run("batch get with empty keys list", func(t *testing.T) { keys := []string{} var results []resource.KeyValue - for kv, err := range kv.BatchGet(ctx, section, keys) { + for kv, err := range kv.BatchGet(ctx, testSection, keys) { require.NoError(t, err) results = append(results, kv) } @@ -718,16 +741,16 @@ func runTestKVBatchGet(t *testing.T, kv resource.KV, nsPrefix string) { }) t.Run("batch get with empty section", func(t *testing.T) { - keys := []string{"some-key"} + keys := namespacedKeys(nsPrefix, []string{"some-key"}) var errors []error for kv, err := range kv.BatchGet(ctx, "", keys) { if err != nil { errors = append(errors, err) - break + continue } _ = kv // unused } - assert.Len(t, errors, 1) + require.Len(t, errors, 1) assert.Contains(t, errors[0].Error(), "section is required") }) @@ -741,13 +764,13 @@ func runTestKVBatchGet(t *testing.T, kv resource.KV, nsPrefix string) { // Save test data for key, value := range testData { - saveKVHelper(t, kv, ctx, section, key, strings.NewReader(value)) + saveKVHelper(t, kv, ctx, testSection, namespacedKey(nsPrefix, key), strings.NewReader(value)) } // Batch get in specific order - keys := []string{"z-key", "a-key", "m-key"} + keys := namespacedKeys(nsPrefix, []string{"z-key", "invalid-key1", "a-key", "invalid-key2", "m-key", "invalid-key3"}) var results []string - for kv, err := range kv.BatchGet(ctx, section, keys) { + for kv, err := range kv.BatchGet(ctx, testSection, keys) { require.NoError(t, err) err = kv.Value.Close() require.NoError(t, err) @@ -755,8 +778,8 @@ func runTestKVBatchGet(t *testing.T, kv resource.KV, nsPrefix string) { } // Verify order is preserved - assert.Len(t, results, 3) - expectedOrder := []string{"z-key", "a-key", "m-key"} + require.Len(t, results, 3) + expectedOrder := namespacedKeys(nsPrefix, []string{"z-key", "a-key", "m-key"}) assert.Equal(t, expectedOrder, results) }) } diff --git a/pkg/storage/unified/testing/kv_test.go b/pkg/storage/unified/testing/kv_test.go index 780f36fcc66..dafefc15ed2 100644 --- a/pkg/storage/unified/testing/kv_test.go +++ b/pkg/storage/unified/testing/kv_test.go @@ -50,7 +50,6 @@ func TestSQLKV(t *testing.T) { TestKVSave: true, TestKVConcurrent: true, TestKVUnixTimestamp: true, - TestKVBatchGet: true, TestKVBatchDelete: true, }, }) From 1862e5dac56c018afed1e55631cd48885214eafc Mon Sep 17 00:00:00 2001 From: Mihai Doarna Date: Thu, 18 Dec 2025 18:54:55 +0200 Subject: [PATCH 26/36] IAM: Fix team search for unistore (#115250) * fix team search for unistore * fix search in unistore * remove field prefix when generating the response * fix unit test * address feedback --- pkg/registry/apis/iam/team/legacy_search.go | 7 ++++-- pkg/registry/apis/iam/team_search.go | 23 +++++++++++++++---- pkg/registry/apis/iam/team_search_test.go | 2 +- .../unified/search/builders/team_search.go | 4 ++-- .../apis/iam/team_search_integration_test.go | 5 ++-- 5 files changed, 29 insertions(+), 12 deletions(-) diff --git a/pkg/registry/apis/iam/team/legacy_search.go b/pkg/registry/apis/iam/team/legacy_search.go index 938062bd9e6..8f9594b96ac 100644 --- a/pkg/registry/apis/iam/team/legacy_search.go +++ b/pkg/registry/apis/iam/team/legacy_search.go @@ -6,6 +6,7 @@ import ( "log/slog" "math" "strconv" + "strings" "google.golang.org/grpc" @@ -102,7 +103,8 @@ func getColumns(fields []string) []*resourcepb.ResourceTableColumnDefinition { columns := getDefaultColumns() for _, field := range fields { - if col, ok := builders.TeamSearchTableColumnDefinitions[field]; ok { + fieldName := strings.TrimPrefix(field, res.SEARCH_FIELD_PREFIX) + if col, ok := builders.TeamSearchTableColumnDefinitions[fieldName]; ok { columns = append(columns, col) } } @@ -121,7 +123,8 @@ func getDefaultColumns() []*resourcepb.ResourceTableColumnDefinition { func createCells(t *team.TeamDTO, fields []string) [][]byte { cells := createDefaultCells(t) for _, field := range fields { - switch field { + fieldName := strings.TrimPrefix(field, res.SEARCH_FIELD_PREFIX) + switch fieldName { case builders.TEAM_SEARCH_EMAIL: cells = append(cells, []byte(t.Email)) case builders.TEAM_SEARCH_PROVISIONED: diff --git a/pkg/registry/apis/iam/team_search.go b/pkg/registry/apis/iam/team_search.go index 786bf9be834..2252263a708 100644 --- a/pkg/registry/apis/iam/team_search.go +++ b/pkg/registry/apis/iam/team_search.go @@ -2,6 +2,7 @@ package iam import ( "encoding/json" + "fmt" "net/http" "net/url" "strconv" @@ -12,6 +13,7 @@ import ( "k8s.io/kube-openapi/pkg/validation/spec" iamv0alpha1 "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1" + "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/services/apiserver/builder" "github.com/grafana/grafana/pkg/services/featuremgmt" @@ -137,6 +139,12 @@ func (s *TeamSearchHandler) DoTeamSearch(w http.ResponseWriter, r *http.Request) return } + requester, err := identity.GetRequester(ctx) + if err != nil { + errhttp.Write(ctx, fmt.Errorf("no identity found for request: %w", err), w) + return + } + limit := 50 offset := 0 page := 1 @@ -154,16 +162,23 @@ func (s *TeamSearchHandler) DoTeamSearch(w http.ResponseWriter, r *http.Request) } searchRequest := &resourcepb.ResourceSearchRequest{ - Options: &resourcepb.ListOptions{}, + Options: &resourcepb.ListOptions{ + Key: &resourcepb.ResourceKey{ + Group: iamv0alpha1.TeamResourceInfo.GroupResource().Group, + Resource: iamv0alpha1.TeamResourceInfo.GroupResource().Resource, + Namespace: requester.GetNamespace(), + }, + }, Query: queryParams.Get("query"), Limit: int64(limit), Offset: int64(offset), Page: int64(page), Explain: queryParams.Has("explain") && queryParams.Get("explain") != "false", Fields: []string{ - builders.TEAM_SEARCH_EMAIL, - builders.TEAM_SEARCH_PROVISIONED, - builders.TEAM_SEARCH_EXTERNAL_UID, + resource.SEARCH_FIELD_TITLE, + resource.SEARCH_FIELD_PREFIX + builders.TEAM_SEARCH_EMAIL, + resource.SEARCH_FIELD_PREFIX + builders.TEAM_SEARCH_PROVISIONED, + resource.SEARCH_FIELD_PREFIX + builders.TEAM_SEARCH_EXTERNAL_UID, }, } diff --git a/pkg/registry/apis/iam/team_search_test.go b/pkg/registry/apis/iam/team_search_test.go index 76efed1c067..2142ba4e505 100644 --- a/pkg/registry/apis/iam/team_search_test.go +++ b/pkg/registry/apis/iam/team_search_test.go @@ -89,7 +89,7 @@ func TestTeamSearchHandler(t *testing.T) { if mockClient.LastSearchRequest == nil { t.Fatalf("expected Search to be called, but it was not") } - expectedFields := []string{"email", "provisioned", "externalUID"} + expectedFields := []string{"title", "fields.email", "fields.provisioned", "fields.externalUID"} if fmt.Sprintf("%v", mockClient.LastSearchRequest.Fields) != fmt.Sprintf("%v", expectedFields) { t.Errorf("expected fields %v, got %v", expectedFields, mockClient.LastSearchRequest.Fields) } diff --git a/pkg/storage/unified/search/builders/team_search.go b/pkg/storage/unified/search/builders/team_search.go index 4b09074b3dc..3dcf3cfdc95 100644 --- a/pkg/storage/unified/search/builders/team_search.go +++ b/pkg/storage/unified/search/builders/team_search.go @@ -46,8 +46,8 @@ func GetTeamSearchBuilder() (resource.DocumentBuilderInfo, error) { return resource.DocumentBuilderInfo{ GroupResource: schema.GroupResource{ - Group: "iam.grafana.app", - Resource: "searchTeams", + Group: v0alpha1.TeamResourceInfo.GroupResource().Group, + Resource: v0alpha1.TeamResourceInfo.GroupResource().Resource, }, Fields: fields, Builder: new(teamSearchBuilder), diff --git a/pkg/tests/apis/iam/team_search_integration_test.go b/pkg/tests/apis/iam/team_search_integration_test.go index c01ca9a641a..bf02f5718c9 100644 --- a/pkg/tests/apis/iam/team_search_integration_test.go +++ b/pkg/tests/apis/iam/team_search_integration_test.go @@ -21,8 +21,7 @@ import ( func TestIntegrationTeamSearch(t *testing.T) { testutil.SkipIntegrationTestInShortMode(t) - // TODO: Add rest.Mode3 and rest.Mode4 when they're supported - modes := []rest.DualWriterMode{rest.Mode0, rest.Mode1, rest.Mode2} + modes := []rest.DualWriterMode{rest.Mode0, rest.Mode1, rest.Mode2, rest.Mode3, rest.Mode4, rest.Mode5} for _, mode := range modes { t.Run(fmt.Sprintf("Team search with dual writer mode %d", mode), func(t *testing.T) { helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ @@ -38,6 +37,7 @@ func TestIntegrationTeamSearch(t *testing.T) { featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs, featuremgmt.FlagKubernetesAuthnMutation, }, + UnifiedStorageEnableSearch: true, }) doTeamSearchTests(t, helper) }) @@ -59,7 +59,6 @@ func doTeamSearchTests(t *testing.T, helper *apis.K8sTestHelper) { require.NoError(t, err) require.NotNil(t, team1) - // Create a second team with a different name team2YAML := helper.LoadYAMLOrJSONFile("testdata/team-test-create-v0.yaml") team2YAML.Object["metadata"].(map[string]interface{})["name"] = "testteam2" team2YAML.Object["spec"].(map[string]interface{})["title"] = "Another Team" From 051cdaad0d3d5c2f1e9f976fb2f87d6d5c6baf7e Mon Sep 17 00:00:00 2001 From: Galen Kistler <109082771+gtk-grafana@users.noreply.github.com> Date: Thu, 18 Dec 2025 11:11:33 -0600 Subject: [PATCH 27/36] Revert "Plugins: Add PluginInsights UI (#111603)" (#115574) This reverts commit 1f4f2b4d7c6af8fb32fbba448a95d85e8886d632. --- eslint-suppressions.json | 5 + .../src/types/featureToggles.gen.ts | 5 - pkg/services/featuremgmt/registry.go | 8 - pkg/services/featuremgmt/toggles_gen.csv | 1 - pkg/services/featuremgmt/toggles_gen.json | 14 -- public/app/features/plugins/admin/api.ts | 16 -- .../components/PluginDetailsPage.test.tsx | 2 - .../admin/components/PluginDetailsPage.tsx | 21 +-- .../components/PluginDetailsPanel.test.tsx | 71 +------- .../admin/components/PluginDetailsPanel.tsx | 9 +- .../admin/components/PluginInsights.test.tsx | 171 ------------------ .../admin/components/PluginInsights.tsx | 140 -------------- .../plugins/admin/mocks/catalogPlugin.mock.ts | 2 - .../plugins/admin/mocks/mockHelpers.ts | 8 - .../features/plugins/admin/state/actions.ts | 20 +- .../app/features/plugins/admin/state/hooks.ts | 29 +-- .../features/plugins/admin/state/reducer.ts | 5 - public/app/features/plugins/admin/types.ts | 49 ----- public/locales/en-US/grafana.json | 6 - 19 files changed, 16 insertions(+), 566 deletions(-) delete mode 100644 public/app/features/plugins/admin/components/PluginInsights.test.tsx delete mode 100644 public/app/features/plugins/admin/components/PluginInsights.tsx diff --git a/eslint-suppressions.json b/eslint-suppressions.json index 80d18ce0cae..1cdf9de4c4a 100644 --- a/eslint-suppressions.json +++ b/eslint-suppressions.json @@ -2868,6 +2868,11 @@ "count": 1 } }, + "public/app/features/plugins/admin/components/PluginDetailsPage.tsx": { + "@typescript-eslint/consistent-type-assertions": { + "count": 1 + } + }, "public/app/features/plugins/admin/helpers.ts": { "no-restricted-syntax": { "count": 2 diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index 6ce08b92452..b91161d966c 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -1189,11 +1189,6 @@ export interface FeatureToggles { */ onlyStoreActionSets?: boolean; /** - * Show insights for plugins in the plugin details page - * @default false - */ - pluginInsights?: boolean; - /** * Enables a new panel time settings drawer */ panelTimeSettings?: boolean; diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index af82c05e61d..d8a46601ad6 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -1960,14 +1960,6 @@ var ( Owner: identityAccessTeam, Expression: "true", }, - { - Name: "pluginInsights", - Description: "Show insights for plugins in the plugin details page", - Stage: FeatureStageExperimental, - FrontendOnly: true, - Owner: grafanaPluginsPlatformSquad, - Expression: "false", - }, { Name: "panelTimeSettings", Description: "Enables a new panel time settings drawer", diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index fedcd197a1f..3fbc019bff1 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -266,7 +266,6 @@ jaegerEnableGrpcEndpoint,experimental,@grafana/oss-big-tent,false,false,false pluginStoreServiceLoading,experimental,@grafana/plugins-platform-backend,false,false,false newPanelPadding,preview,@grafana/dashboards-squad,false,false,true onlyStoreActionSets,GA,@grafana/identity-access-team,false,false,false -pluginInsights,experimental,@grafana/plugins-platform-backend,false,false,true panelTimeSettings,experimental,@grafana/dashboards-squad,false,false,false elasticsearchRawDSLQuery,experimental,@grafana/partner-datasources,false,false,false kubernetesAnnotations,experimental,@grafana/grafana-backend-services-squad,false,false,false diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json index 3c33f362b29..866f8a78881 100644 --- a/pkg/services/featuremgmt/toggles_gen.json +++ b/pkg/services/featuremgmt/toggles_gen.json @@ -2693,20 +2693,6 @@ "expression": "false" } }, - { - "metadata": { - "name": "pluginInsights", - "resourceVersion": "1761300628147", - "creationTimestamp": "2025-10-24T10:10:28Z" - }, - "spec": { - "description": "Show insights for plugins in the plugin details page", - "stage": "experimental", - "codeowner": "@grafana/plugins-platform-backend", - "frontend": true, - "expression": "false" - } - }, { "metadata": { "name": "pluginInstallAPISync", diff --git a/public/app/features/plugins/admin/api.ts b/public/app/features/plugins/admin/api.ts index aa5bc32f183..74a072ba054 100644 --- a/public/app/features/plugins/admin/api.ts +++ b/public/app/features/plugins/admin/api.ts @@ -8,7 +8,6 @@ import { LocalPlugin, RemotePlugin, CatalogPluginDetails, - CatalogPluginInsights, Version, PluginVersion, InstancePlugin, @@ -48,21 +47,6 @@ export async function getPluginDetails(id: string): Promise { - if (!version) { - throw new Error('Version is required'); - } - try { - const insights = await getBackendSrv().get(`${GCOM_API_ROOT}/plugins/${id}/versions/${version}/insights`); - return insights; - } catch (error) { - if (isFetchError(error)) { - error.isHandled = true; - } - throw error; - } -} - export async function getRemotePlugins(): Promise { try { const { items: remotePlugins }: { items: RemotePlugin[] } = await getBackendSrv().get(`${GCOM_API_ROOT}/plugins`, { diff --git a/public/app/features/plugins/admin/components/PluginDetailsPage.test.tsx b/public/app/features/plugins/admin/components/PluginDetailsPage.test.tsx index 0ffc93f8f77..da4eef2f0d4 100644 --- a/public/app/features/plugins/admin/components/PluginDetailsPage.test.tsx +++ b/public/app/features/plugins/admin/components/PluginDetailsPage.test.tsx @@ -62,12 +62,10 @@ const plugin: CatalogPlugin = { angularDetected: false, isFullyInstalled: true, accessControl: {}, - insights: { id: 1, name: 'test-plugin', version: '1.0.0', insights: [] }, }; jest.mock('../state/hooks', () => ({ useGetSingle: jest.fn(), - useGetPluginInsights: jest.fn(), useFetchStatus: jest.fn().mockReturnValue({ isLoading: false }), useFetchDetailsStatus: () => ({ isLoading: false }), useIsRemotePluginsAvailable: () => false, diff --git a/public/app/features/plugins/admin/components/PluginDetailsPage.tsx b/public/app/features/plugins/admin/components/PluginDetailsPage.tsx index b135321e558..0e651a8e4bf 100644 --- a/public/app/features/plugins/admin/components/PluginDetailsPage.tsx +++ b/public/app/features/plugins/admin/components/PluginDetailsPage.tsx @@ -16,19 +16,11 @@ import { PluginDetailsPanel } from '../components/PluginDetailsPanel'; import { PluginDetailsSignature } from '../components/PluginDetailsSignature'; import { usePluginDetailsTabs } from '../hooks/usePluginDetailsTabs'; import { usePluginPageExtensions } from '../hooks/usePluginPageExtensions'; -import { useGetSingle, useFetchStatus, useFetchDetailsStatus, useGetPluginInsights } from '../state/hooks'; +import { useGetSingle, useFetchStatus, useFetchDetailsStatus } from '../state/hooks'; import { PluginTabIds } from '../types'; import { PluginDetailsDeprecatedWarning } from './PluginDetailsDeprecatedWarning'; -function isPluginTabId(value: string | null): value is PluginTabIds { - if (!value) { - return false; - } - const validIds: string[] = Object.values(PluginTabIds); - return validIds.includes(value); -} - export type Props = { // The ID of the plugin pluginId: string; @@ -57,13 +49,12 @@ export function PluginDetailsPage({ }; const queryParams = new URLSearchParams(location.search); const plugin = useGetSingle(pluginId); // fetches the plugin settings for this Grafana instance - useGetPluginInsights(pluginId, plugin?.isInstalled ? plugin?.installedVersion : plugin?.latestVersion); - const isNarrowScreen = useMedia('(max-width: 600px)'); - const pageParam = queryParams.get('page'); - const pageId = pageParam && isPluginTabId(pageParam) ? pageParam : undefined; - const { navModel, activePageId } = usePluginDetailsTabs(plugin, pageId, isNarrowScreen); - + const { navModel, activePageId } = usePluginDetailsTabs( + plugin, + queryParams.get('page') as PluginTabIds, + isNarrowScreen + ); const { actions, info, subtitle } = usePluginPageExtensions(plugin); const { isLoading: isFetchLoading } = useFetchStatus(); const { isLoading: isFetchDetailsLoading } = useFetchDetailsStatus(); diff --git a/public/app/features/plugins/admin/components/PluginDetailsPanel.test.tsx b/public/app/features/plugins/admin/components/PluginDetailsPanel.test.tsx index 20787099842..eade37f559c 100644 --- a/public/app/features/plugins/admin/components/PluginDetailsPanel.test.tsx +++ b/public/app/features/plugins/admin/components/PluginDetailsPanel.test.tsx @@ -1,23 +1,11 @@ -import userEvent from '@testing-library/user-event'; import { render, screen } from 'test/test-utils'; import { PluginSignatureStatus, PluginSignatureType, PluginType } from '@grafana/data'; -import { config } from '@grafana/runtime'; -import { CatalogPlugin, SCORE_LEVELS } from '../types'; +import { CatalogPlugin } from '../types'; import { PluginDetailsPanel } from './PluginDetailsPanel'; -jest.mock('@grafana/runtime', () => ({ - ...jest.requireActual('@grafana/runtime'), - config: { - ...jest.requireActual('@grafana/runtime').config, - featureToggles: { - pluginInsights: false, - }, - }, -})); - const mockPlugin: CatalogPlugin = { description: 'Test plugin description', downloads: 1000, @@ -197,61 +185,4 @@ describe('PluginDetailsPanel', () => { expect(regularLinks).toContainElement(raiseIssueLink); expect(regularLinks).not.toContainElement(websiteLink); }); - - it('should render plugin insights when plugin has insights', async () => { - config.featureToggles.pluginInsights = true; - const pluginWithInsights = { - ...mockPlugin, - insights: { - id: 1, - name: 'test-plugin', - version: '1.0.0', - insights: [ - { - name: 'security', - scoreValue: 90, - scoreLevel: SCORE_LEVELS.EXCELLENT, - items: [ - { - id: 'signature', - name: 'Signature verified', - level: 'ok' as const, - }, - ], - }, - ], - }, - }; - render(); - expect(screen.getByTestId('plugin-insights-container')).toBeInTheDocument(); - expect(screen.getByText('Plugin insights')).toBeInTheDocument(); - expect(screen.queryByText('Security')).toBeInTheDocument(); - await userEvent.click(screen.getByText('Security')); - expect(screen.getByTestId('plugin-insight-item-signature')).toBeInTheDocument(); - }); - - it('should not render plugin insights when plugin has no insights', () => { - const pluginWithoutInsights = { - ...mockPlugin, - insights: undefined, - }; - render(); - expect(screen.queryByTestId('plugin-insights-container')).not.toBeInTheDocument(); - expect(screen.queryByText('Plugin insights')).not.toBeInTheDocument(); - }); - - it('should not render plugin insights when insights array is empty', () => { - const pluginWithEmptyInsights = { - ...mockPlugin, - insights: { - id: 1, - name: 'test-plugin', - version: '1.0.0', - insights: [], - }, - }; - render(); - expect(screen.queryByTestId('plugin-insights-container')).not.toBeInTheDocument(); - expect(screen.queryByText('Plugin insights')).not.toBeInTheDocument(); - }); }); diff --git a/public/app/features/plugins/admin/components/PluginDetailsPanel.tsx b/public/app/features/plugins/admin/components/PluginDetailsPanel.tsx index aa8b6c792ef..00211b61c6e 100644 --- a/public/app/features/plugins/admin/components/PluginDetailsPanel.tsx +++ b/public/app/features/plugins/admin/components/PluginDetailsPanel.tsx @@ -3,7 +3,7 @@ import { useState } from 'react'; import { GrafanaTheme2 } from '@grafana/data'; import { t, Trans } from '@grafana/i18n'; -import { config, reportInteraction } from '@grafana/runtime'; +import { reportInteraction } from '@grafana/runtime'; import { PageInfoItem } from '@grafana/runtime/internal'; import { Stack, @@ -22,8 +22,6 @@ import { formatDate } from 'app/core/internationalization/dates'; import { CatalogPlugin } from '../types'; -import { PluginInsights } from './PluginInsights'; - type Props = { pluginExtentionsInfo: PageInfoItem[]; plugin: CatalogPlugin; width?: string }; export function PluginDetailsPanel(props: Props): React.ReactElement | null { @@ -71,11 +69,6 @@ export function PluginDetailsPanel(props: Props): React.ReactElement | null { return ( <> - {config.featureToggles.pluginInsights && plugin.insights && plugin.insights?.insights?.length > 0 && ( - - - - )} {pluginExtentionsInfo.map((infoItem, index) => { diff --git a/public/app/features/plugins/admin/components/PluginInsights.test.tsx b/public/app/features/plugins/admin/components/PluginInsights.test.tsx deleted file mode 100644 index efd064c7172..00000000000 --- a/public/app/features/plugins/admin/components/PluginInsights.test.tsx +++ /dev/null @@ -1,171 +0,0 @@ -import userEvent from '@testing-library/user-event'; -import { render, screen } from 'test/test-utils'; - -import { CatalogPluginInsights, InsightLevel, SCORE_LEVELS } from '../types'; - -import { PluginInsights } from './PluginInsights'; - -const mockPluginInsights: CatalogPluginInsights = { - id: 1, - name: 'test-plugin', - version: '1.0.0', - insights: [ - { - name: 'security', - scoreValue: 90, - scoreLevel: SCORE_LEVELS.EXCELLENT, - items: [ - { - id: 'signature', - name: 'Signature verified', - description: 'Plugin signature is valid', - level: 'ok' as InsightLevel, - }, - { - id: 'trackingscripts', - name: 'No unsafe JavaScript detected', - level: 'good' as InsightLevel, - }, - ], - }, - { - name: 'quality', - scoreValue: 60, - scoreLevel: SCORE_LEVELS.FAIR, - items: [ - { - id: 'metadatavalid', - name: 'Metadata is valid', - level: 'ok' as InsightLevel, - }, - { - id: 'code-rules', - name: 'Missing code rules', - description: 'Plugin lacks comprehensive code rules', - level: 'warning' as InsightLevel, - }, - ], - }, - ], -}; - -const mockPluginInsightsWithPoorLevel: CatalogPluginInsights = { - id: 3, - name: 'test-plugin-poor', - version: '0.8.0', - insights: [ - { - name: 'quality', - scoreValue: 35, - scoreLevel: SCORE_LEVELS.POOR, - items: [ - { - id: 'legacy-platform', - name: 'Quality issues detected', - level: 'warning' as InsightLevel, - }, - ], - }, - ], -}; - -describe('PluginInsights', () => { - it('should render plugin insights section', () => { - render(); - const insightsSection = screen.getByTestId('plugin-insights-container'); - expect(insightsSection).toBeInTheDocument(); - expect(screen.getByText('Plugin insights')).toBeInTheDocument(); - }); - - it('should render all insight categories with test ids', () => { - render(); - expect(screen.getByTestId('plugin-insight-security')).toBeInTheDocument(); - expect(screen.getByTestId('plugin-insight-quality')).toBeInTheDocument(); - }); - - it('should render category names with test ids', () => { - render(); - const securityCategory = screen.getByTestId('plugin-insight-security'); - const qualityCategory = screen.getByTestId('plugin-insight-quality'); - - expect(securityCategory).toBeInTheDocument(); - expect(securityCategory).toHaveTextContent('Security'); - expect(qualityCategory).toBeInTheDocument(); - expect(qualityCategory).toHaveTextContent('Quality'); - }); - - it('should render individual insight items with test ids', async () => { - render(); - await userEvent.click(screen.getByText('Security')); - expect(screen.getByTestId('plugin-insight-item-signature')).toBeInTheDocument(); - expect(screen.getByTestId('plugin-insight-item-trackingscripts')).toBeInTheDocument(); - await userEvent.click(screen.getByText('Quality')); - expect(screen.getByTestId('plugin-insight-item-metadatavalid')).toBeInTheDocument(); - expect(screen.getByTestId('plugin-insight-item-code-rules')).toBeInTheDocument(); - }); - - it('should display correct icons for Excellent score level', () => { - render(); - - const securityCategory = screen.getByTestId('plugin-insight-security'); - const securityIcon = securityCategory.querySelector('[data-testid="excellent-icon"]'); - expect(securityIcon).toBeInTheDocument(); - }); - - it('should display correct icons for Poor score levels', () => { - // Test Poor level - should show exclamation-triangle - render(); - const poorCategory = screen.getByTestId('plugin-insight-quality'); - const poorIcon = poorCategory.querySelector('[data-testid="poor-icon"]'); - expect(poorIcon).toBeInTheDocument(); - }); - - it('should handle multiple items with different insight levels', async () => { - const multiLevelInsights: CatalogPluginInsights = { - id: 5, - name: 'multi-level-plugin', - version: '2.0.0', - insights: [ - { - name: 'quality', - scoreValue: 75, - scoreLevel: SCORE_LEVELS.GOOD, - items: [ - { - id: 'code-rules', - name: 'Info level item', - level: 'info' as InsightLevel, - }, - { - id: 'sdk-usage', - name: 'OK level item', - level: 'ok' as InsightLevel, - }, - { - id: 'jsMap', - name: 'Good level item', - level: 'good' as InsightLevel, - }, - { - id: 'gosec', - name: 'Warning level item', - level: 'warning' as InsightLevel, - }, - { - id: 'legacy-builder', - name: 'Danger level item', - level: 'danger' as InsightLevel, - }, - ], - }, - ], - }; - render(); - await userEvent.click(screen.getByText('Quality')); - expect(screen.getByText('Info level item')).toBeInTheDocument(); - expect(screen.getByText('OK level item')).toBeInTheDocument(); - expect(screen.getByText('Good level item')).toBeInTheDocument(); - expect(screen.getByText('Warning level item')).toBeInTheDocument(); - expect(screen.getByText('Danger level item')).toBeInTheDocument(); - }); -}); diff --git a/public/app/features/plugins/admin/components/PluginInsights.tsx b/public/app/features/plugins/admin/components/PluginInsights.tsx deleted file mode 100644 index 805bcf926bf..00000000000 --- a/public/app/features/plugins/admin/components/PluginInsights.tsx +++ /dev/null @@ -1,140 +0,0 @@ -import { css } from '@emotion/css'; -import { capitalize } from 'lodash'; -import { useState } from 'react'; - -import { GrafanaTheme2 } from '@grafana/data'; -import { Trans } from '@grafana/i18n'; -import { reportInteraction } from '@grafana/runtime'; -import { Stack, Text, TextLink, CollapsableSection, Tooltip, Icon, useStyles2, useTheme2 } from '@grafana/ui'; - -import { CatalogPluginInsights } from '../types'; - -type Props = { pluginInsights: CatalogPluginInsights | undefined }; - -const PLUGINS_INSIGHTS_OPENED_EVENT_NAME = 'plugins_insights_opened'; - -export function PluginInsights(props: Props): React.ReactElement | null { - const { pluginInsights } = props; - const styles = useStyles2(getStyles); - const theme = useTheme2(); - const [openInsights, setOpenInsights] = useState>({}); - - const handleInsightToggle = (insightName: string, isOpen: boolean) => { - if (isOpen) { - reportInteraction(PLUGINS_INSIGHTS_OPENED_EVENT_NAME, { insight: insightName }); - } - setOpenInsights((prev) => ({ ...prev, [insightName]: isOpen })); - }; - - const tooltipInfo = ( - - - - - - All relevant signals are present and verified - - - - - - - - One or more signals are missing or need attention - - - -
- - - Do you find Plugin Insights usefull? Please share your feedback{' '} - - here - - . - - -
- ); - - return ( - <> - - - - Plugin insights - - - - - - {pluginInsights?.insights.map((insightItem, index) => { - return ( - - handleInsightToggle(insightItem.name, isOpen)} - label={ - - {insightItem.scoreLevel === 'Excellent' ? ( - - ) : ( - - )} - - {capitalize(insightItem.name)} - - - } - contentClassName={styles.pluginInsightsItems} - > - - {insightItem.items.map((item, idx) => ( - - - {item.level === 'good' ? ( - - ) : ( - - )} - - - {item.name} - - - ))} - - - - ); - })} - - - ); -} - -export const getStyles = (theme: GrafanaTheme2) => { - return { - pluginVersionDetails: css({ wordBreak: 'break-word' }), - pluginInsightsItems: css({ marginLeft: '26px', paddingTop: '0 !important' }), - pluginInsightsTooltipSeparator: css({ - border: 'none', - borderTop: `1px solid ${theme.colors.border.medium}`, - margin: `${theme.spacing(1)} 0`, - }), - }; -}; diff --git a/public/app/features/plugins/admin/mocks/catalogPlugin.mock.ts b/public/app/features/plugins/admin/mocks/catalogPlugin.mock.ts index 3625b687f7b..9ced4f20a84 100644 --- a/public/app/features/plugins/admin/mocks/catalogPlugin.mock.ts +++ b/public/app/features/plugins/admin/mocks/catalogPlugin.mock.ts @@ -34,7 +34,6 @@ export default { updatedAt: '2021-08-25T15:03:49.000Z', version: '4.2.2', error: undefined, - insights: { id: 1, name: 'alexanderzobnin-zabbix-app', version: '4.2.2', insights: [] }, details: { grafanaDependency: '>=8.0.0', pluginDependencies: [], @@ -382,7 +381,6 @@ export const datasourcePlugin = { angularDetected: false, isFullyInstalled: true, latestVersion: '1.20.0', - insights: { id: 2, name: 'grafana-redshift-datasource', version: '1.20.0', insights: [] }, details: { grafanaDependency: '>=8.0.0', pluginDependencies: [], diff --git a/public/app/features/plugins/admin/mocks/mockHelpers.ts b/public/app/features/plugins/admin/mocks/mockHelpers.ts index d6e04186f77..6034e8860e9 100644 --- a/public/app/features/plugins/admin/mocks/mockHelpers.ts +++ b/public/app/features/plugins/admin/mocks/mockHelpers.ts @@ -31,9 +31,6 @@ export const getPluginsStateMock = (plugins: CatalogPlugin[] = []): ReducerState 'plugins/fetchDetails': { status: RequestStatus.Fulfilled, }, - 'plugins/fetchPluginInsights': { - status: RequestStatus.Fulfilled, - }, }, // Backward compatibility plugins: [], @@ -78,11 +75,6 @@ export const mockPluginApis = ({ return Promise.resolve({ items: versions }); } - // Mock plugin insights - return empty insights to avoid API call errors - if (path.includes('/insights')) { - return Promise.resolve({ id: 1, name: '', version: '', insights: [] }); - } - // Mock local plugin settings (installed) if necessary if (local && path === `${API_ROOT}/${local.id}/settings`) { return Promise.resolve(local); diff --git a/public/app/features/plugins/admin/state/actions.ts b/public/app/features/plugins/admin/state/actions.ts index e9cf2d9d40d..6be68111dd5 100644 --- a/public/app/features/plugins/admin/state/actions.ts +++ b/public/app/features/plugins/admin/state/actions.ts @@ -13,7 +13,6 @@ import { getPluginErrors, getLocalPlugins, getPluginDetails, - getPluginInsights, installPlugin, uninstallPlugin, getInstancePlugins, @@ -166,22 +165,6 @@ export const fetchDetails = createAsyncThunk, stri } ); -export const fetchPluginInsights = createAsyncThunk, { id: string; version?: string }>( - `${STATE_PREFIX}/fetchPluginInsights`, - async ({ id, version }, thunkApi) => { - try { - const insights = await getPluginInsights(id, version); - - return { - id, - changes: { insights }, - }; - } catch (e) { - return thunkApi.rejectWithValue('Unknown error.'); - } - } -); - export const addPlugins = createAction(`${STATE_PREFIX}/addPlugins`); // 1. gets remote equivalents from the store (if there are any) @@ -282,8 +265,7 @@ export const panelPluginLoaded = createAction(`${STATE_PREFIX}/pane // TODO export const loadPanelPlugin = (id: string): ThunkResult> => { return async (dispatch, getStore) => { - const state = getStore(); - let plugin = state.plugins.panels[id]; + let plugin = getStore().plugins.panels[id]; if (!plugin) { plugin = await importPanelPlugin(id); diff --git a/public/app/features/plugins/admin/state/hooks.ts b/public/app/features/plugins/admin/state/hooks.ts index 6eb47d7e1aa..2185ec99465 100644 --- a/public/app/features/plugins/admin/state/hooks.ts +++ b/public/app/features/plugins/admin/state/hooks.ts @@ -6,16 +6,7 @@ import { useDispatch, useSelector } from 'app/types/store'; import { sortPlugins, Sorters, isPluginUpdatable } from '../helpers'; import { CatalogPlugin, PluginStatus } from '../types'; -import { - fetchAll, - fetchDetails, - fetchRemotePlugins, - install, - uninstall, - fetchAllLocal, - unsetInstall, - fetchPluginInsights, -} from './actions'; +import { fetchAll, fetchDetails, fetchRemotePlugins, install, uninstall, fetchAllLocal, unsetInstall } from './actions'; import { selectPlugins, selectById, @@ -53,18 +44,13 @@ export const useGetUpdatable = () => { }; }; -export const useGetSingle = (id: string, version?: string): CatalogPlugin | undefined => { +export const useGetSingle = (id: string): CatalogPlugin | undefined => { useFetchAll(); useFetchDetails(id); return useSelector((state) => selectById(state, id)); }; -export const useGetPluginInsights = (id: string, version: string | undefined): CatalogPlugin | undefined => { - useFetchPluginInsights(id, version); - return useSelector((state) => selectById(state, id)); -}; - export const useGetSingleLocalWithoutDetails = (id: string): CatalogPlugin | undefined => { useFetchAllLocal(); return useSelector((state) => selectById(state, id)); @@ -167,17 +153,6 @@ export const useFetchDetails = (id: string) => { }, [plugin]); // eslint-disable-line }; -export const useFetchPluginInsights = (id: string, version: string | undefined) => { - const dispatch = useDispatch(); - const plugin = useSelector((state) => selectById(state, id)); - const isNotFetching = !useSelector(selectIsRequestPending(fetchPluginInsights.typePrefix)); - const shouldFetch = isNotFetching && plugin && !plugin.insights && version; - - useEffect(() => { - shouldFetch && dispatch(fetchPluginInsights({ id, version })); - }, [plugin, version]); // eslint-disable-line -}; - export const useFetchDetailsLazy = () => { const dispatch = useDispatch(); diff --git a/public/app/features/plugins/admin/state/reducer.ts b/public/app/features/plugins/admin/state/reducer.ts index e3d5bec5427..f2414a31405 100644 --- a/public/app/features/plugins/admin/state/reducer.ts +++ b/public/app/features/plugins/admin/state/reducer.ts @@ -7,7 +7,6 @@ import { CatalogPlugin, ReducerState, RequestStatus } from '../types'; import { fetchDetails, - fetchPluginInsights, install, uninstall, loadPluginDashboards, @@ -64,10 +63,6 @@ const slice = createSlice({ .addCase(fetchDetails.fulfilled, (state, action) => { pluginsAdapter.updateOne(state.items, action.payload); }) - // Fetch Plugin Insights - .addCase(fetchPluginInsights.fulfilled, (state, action) => { - pluginsAdapter.updateOne(state.items, action.payload); - }) // Install .addCase(install.fulfilled, (state, action) => { pluginsAdapter.updateOne(state.items, action.payload); diff --git a/public/app/features/plugins/admin/types.ts b/public/app/features/plugins/admin/types.ts index df4114101b4..3cc66bba0b9 100644 --- a/public/app/features/plugins/admin/types.ts +++ b/public/app/features/plugins/admin/types.ts @@ -55,7 +55,6 @@ export interface CatalogPlugin extends WithAccessControlMetadata { updatedAt: string; installedVersion?: string; details?: CatalogPluginDetails; - insights?: CatalogPluginInsights; error?: PluginErrorCode; angularDetected?: boolean; // instance plugins may not be fully installed, which means a new instance @@ -91,54 +90,6 @@ export interface CatalogPluginDetails { screenshots?: Screenshots[] | null; } -export type InsightLevel = 'ok' | 'warning' | 'danger' | 'good' | 'info'; - -export const SCORE_LEVELS = { - EXCELLENT: 'Excellent', - GOOD: 'Good', - FAIR: 'Fair', - POOR: 'Poor', - CRITICAL: 'Critical', -} as const; - -export type ScoreLevel = (typeof SCORE_LEVELS)[keyof typeof SCORE_LEVELS]; - -export const INSIGHT_CATEGORIES = { - SECURITY: 'security', - QUALITY: 'quality', - PERFORMANCE: 'performance', -} as const; - -export const INSIGHT_LEVELS = { - GOOD: 'good', - OK: 'ok', - WARNING: 'warning', - DANGER: 'danger', - INFO: 'info', -} as const; - -export interface InsightItem { - id: string; - name: string; - description?: string; - level: InsightLevel; - link?: string; -} - -export interface InsightCategory { - name: string; - items: InsightItem[]; - scoreValue: number; - scoreLevel: ScoreLevel; -} - -export interface CatalogPluginInsights { - id: number; - name: string; - version: string; - insights: InsightCategory[]; -} - export interface CatalogPluginInfo { logos: { large: string; small: string }; keywords: string[]; diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index 2e86a2a42d3..6949fa04bb3 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -11390,12 +11390,6 @@ "latestReleaseDate": "Latest release date:", "latestVersion": "Latest Version", "license": "License", - "moreDetails": "Do you find Plugin Insights usefull? Please share your feedback <2>here.", - "pluginInsights": { - "header": "Plugin insights" - }, - "pluginInsightsSuccessTooltip": "All relevant signals are present and verified", - "pluginInsightsWarningTooltip": "One or more signals are missing or need attention", "raiseAnIssue": "Raise an issue", "reportAbuse": "Report a concern", "reportAbuseTooltip": "Report issues related to malicious or harmful plugins directly to Grafana Labs.", From 26ce2c09d78706e4235cc378d67da9f99c6ba99d Mon Sep 17 00:00:00 2001 From: Denis Vodopianov Date: Thu, 18 Dec 2025 19:10:30 +0100 Subject: [PATCH 28/36] chore: a drop-in replacement for FeatureToggles.IsEnabledGlobally in app settings (#113449) --- pkg/registry/apps/apps.go | 2 +- pkg/setting/setting.go | 5 +++++ pkg/setting/startup_setting.go | 14 ++++++++++++++ 3 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 pkg/setting/startup_setting.go diff --git a/pkg/registry/apps/apps.go b/pkg/registry/apps/apps.go index a1ec8aafd65..77c96fd437c 100644 --- a/pkg/registry/apps/apps.go +++ b/pkg/registry/apps/apps.go @@ -76,7 +76,7 @@ func ProvideAppInstallers( if features.IsEnabledGlobally(featuremgmt.FlagKubernetesLogsDrilldown) { installers = append(installers, logsdrilldownAppInstaller) } - //nolint:staticcheck + //nolint:staticcheck // not yet migrated to OpenFeature if features.IsEnabledGlobally(featuremgmt.FlagKubernetesAnnotations) { installers = append(installers, annotationAppInstaller) } diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index 1af02aa0d3a..3d202b20cec 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -510,6 +510,9 @@ type Cfg struct { // Query history QueryHistoryEnabled bool + // StartupSettings settings + StartupSettings StartupSettings + // Open feature settings OpenFeature OpenFeatureSettings @@ -1471,6 +1474,8 @@ func (cfg *Cfg) parseINIFile(iniFile *ini.File) error { // unified storage config cfg.setUnifiedStorageConfig() + // app platform config + cfg.readStartupSettingsSection() return nil } diff --git a/pkg/setting/startup_setting.go b/pkg/setting/startup_setting.go new file mode 100644 index 00000000000..8b2c2fa6362 --- /dev/null +++ b/pkg/setting/startup_setting.go @@ -0,0 +1,14 @@ +package setting + +type StartupSettings struct { + KubernetesAnnotationsAppEnabled bool +} + +func (cfg *Cfg) readStartupSettingsSection() { + settings := StartupSettings{} + + startupSettingsSection := cfg.Raw.Section("startup_settings") + settings.KubernetesAnnotationsAppEnabled = startupSettingsSection.Key("annotations_app_enabled").MustBool(false) + + cfg.StartupSettings = settings +} From 18501633467c50ec9afb4b224eb803fdf91d38b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laura=20Fern=C3=A1ndez?= Date: Thu, 18 Dec 2025 19:47:04 +0100 Subject: [PATCH 29/36] Rudderstack: Add new config option for rudderstack v3 url (#115374) --- conf/defaults.ini | 3 +++ conf/sample.ini | 3 +++ .../setup-grafana/configure-grafana/_index.md | 6 ++++++ packages/grafana-data/src/types/config.ts | 1 + packages/grafana-runtime/src/config.ts | 1 + pkg/api/dtos/frontend_settings.go | 1 + pkg/api/frontendsettings.go | 1 + pkg/services/frontend/frontend_settings.go | 1 + pkg/services/frontend/index.go | 1 + pkg/setting/setting.go | 2 ++ public/app/core/services/echo/init.ts | 16 +++++++--------- 11 files changed, 27 insertions(+), 9 deletions(-) diff --git a/conf/defaults.ini b/conf/defaults.ini index de83393e43d..8ee03e6a34c 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -335,6 +335,9 @@ rudderstack_data_plane_url = # Rudderstack SDK url, optional, only valid if rudderstack_write_key and rudderstack_data_plane_url is also set rudderstack_sdk_url = +# Rudderstack v3 SDK, optional, defaults to false. If set, Rudderstack v3 SDK will be used instead of v1 +rudderstack_v3_sdk_url = + # Rudderstack Config url, optional, used by Rudderstack SDK to fetch source config rudderstack_config_url = diff --git a/conf/sample.ini b/conf/sample.ini index d1d50f0a72a..0bb5b82fdc9 100644 --- a/conf/sample.ini +++ b/conf/sample.ini @@ -322,6 +322,9 @@ # Rudderstack SDK url, optional, only valid if rudderstack_write_key and rudderstack_data_plane_url is also set ;rudderstack_sdk_url = +# Rudderstack v3 SDK, optional, defaults to false. If set, Rudderstack v3 SDK will be used instead of v1 +;rudderstack_v3_sdk_url = + # Rudderstack Config url, optional, used by Rudderstack SDK to fetch source config ;rudderstack_config_url = diff --git a/docs/sources/setup-grafana/configure-grafana/_index.md b/docs/sources/setup-grafana/configure-grafana/_index.md index a82ca8f91dd..05d9cb66228 100644 --- a/docs/sources/setup-grafana/configure-grafana/_index.md +++ b/docs/sources/setup-grafana/configure-grafana/_index.md @@ -642,6 +642,12 @@ You must also provide the `rudderstack_write_key` to enable this feature. Optional. If tracking with RudderStack is enabled, you can provide a custom URL to load the RudderStack SDK. +#### `rudderstack_v3_sdk_url` + +Optional. +This is mirroring the old configuration option, which will be deprecated. +If `rudderstack_sdk_url` and `rudderstack_v3_sdk_url` are both set, the feature toggle `rudderstackUpgrade` will control which one is loaded. + #### `rudderstack_config_url` Optional. diff --git a/packages/grafana-data/src/types/config.ts b/packages/grafana-data/src/types/config.ts index f519b1d56b4..b2d3c16a3b1 100644 --- a/packages/grafana-data/src/types/config.ts +++ b/packages/grafana-data/src/types/config.ts @@ -289,6 +289,7 @@ export interface GrafanaConfig { rudderstackWriteKey: string; rudderstackDataPlaneUrl: string; rudderstackSdkUrl: string; + rudderstackV3SdkUrl: string; rudderstackConfigUrl: string; rudderstackIntegrationsUrl: string; applicationInsightsConnectionString: string; diff --git a/packages/grafana-runtime/src/config.ts b/packages/grafana-runtime/src/config.ts index 17fbbef447a..18cce14f236 100644 --- a/packages/grafana-runtime/src/config.ts +++ b/packages/grafana-runtime/src/config.ts @@ -224,6 +224,7 @@ export class GrafanaBootConfig { rudderstackWriteKey?: string; rudderstackDataPlaneUrl?: string; rudderstackSdkUrl?: string; + rudderstackV3SdkUrl?: string; rudderstackConfigUrl?: string; rudderstackIntegrationsUrl?: string; analyticsConsoleReporting = false; diff --git a/pkg/api/dtos/frontend_settings.go b/pkg/api/dtos/frontend_settings.go index 218817c6801..d140c328242 100644 --- a/pkg/api/dtos/frontend_settings.go +++ b/pkg/api/dtos/frontend_settings.go @@ -200,6 +200,7 @@ type FrontendSettingsDTO struct { RudderstackWriteKey string `json:"rudderstackWriteKey"` RudderstackDataPlaneUrl string `json:"rudderstackDataPlaneUrl"` RudderstackSdkUrl string `json:"rudderstackSdkUrl"` + RudderstackV3SdkUrl string `json:"rudderstackV3SdkUrl"` RudderstackConfigUrl string `json:"rudderstackConfigUrl"` RudderstackIntegrationsUrl string `json:"rudderstackIntegrationsUrl"` diff --git a/pkg/api/frontendsettings.go b/pkg/api/frontendsettings.go index c105b5ee829..b57262087e5 100644 --- a/pkg/api/frontendsettings.go +++ b/pkg/api/frontendsettings.go @@ -229,6 +229,7 @@ func (hs *HTTPServer) getFrontendSettings(c *contextmodel.ReqContext) (*dtos.Fro RudderstackWriteKey: hs.Cfg.RudderstackWriteKey, RudderstackDataPlaneUrl: hs.Cfg.RudderstackDataPlaneURL, RudderstackSdkUrl: hs.Cfg.RudderstackSDKURL, + RudderstackV3SdkUrl: hs.Cfg.RudderstackV3SDKURL, RudderstackConfigUrl: hs.Cfg.RudderstackConfigURL, RudderstackIntegrationsUrl: hs.Cfg.RudderstackIntegrationsURL, AnalyticsConsoleReporting: hs.Cfg.FrontendAnalyticsConsoleReporting, diff --git a/pkg/services/frontend/frontend_settings.go b/pkg/services/frontend/frontend_settings.go index f6cf9855b2d..d9476c8ff40 100644 --- a/pkg/services/frontend/frontend_settings.go +++ b/pkg/services/frontend/frontend_settings.go @@ -32,6 +32,7 @@ type FSFrontendSettings struct { RudderstackWriteKey string `json:"rudderstackWriteKey,omitempty"` RudderstackDataPlaneUrl string `json:"rudderstackDataPlaneUrl,omitempty"` RudderstackSdkUrl string `json:"rudderstackSdkUrl,omitempty"` + RudderstackV3SdkUrl string `json:"rudderstackV3SdkUrl,omitempty"` RudderstackConfigUrl string `json:"rudderstackConfigUrl,omitempty"` RudderstackIntegrationsUrl string `json:"rudderstackIntegrationsUrl,omitempty"` diff --git a/pkg/services/frontend/index.go b/pkg/services/frontend/index.go index e87ca894d20..25704f1ab6d 100644 --- a/pkg/services/frontend/index.go +++ b/pkg/services/frontend/index.go @@ -94,6 +94,7 @@ func NewIndexProvider(cfg *setting.Cfg, assetsManifest dtos.EntryPointAssets, li RudderstackDataPlaneUrl: cfg.RudderstackDataPlaneURL, RudderstackIntegrationsUrl: cfg.RudderstackIntegrationsURL, RudderstackSdkUrl: cfg.RudderstackSDKURL, + RudderstackV3SdkUrl: cfg.RudderstackV3SDKURL, RudderstackWriteKey: cfg.RudderstackWriteKey, TrustedTypesDefaultPolicyEnabled: (cfg.CSPEnabled && strings.Contains(cfg.CSPTemplate, "require-trusted-types-for")) || (cfg.CSPReportOnlyEnabled && strings.Contains(cfg.CSPReportOnlyTemplate, "require-trusted-types-for")), VerifyEmailEnabled: cfg.VerifyEmailEnabled, diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index 3d202b20cec..62b9332581f 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -414,6 +414,7 @@ type Cfg struct { RudderstackDataPlaneURL string RudderstackWriteKey string RudderstackSDKURL string + RudderstackV3SDKURL string RudderstackConfigURL string RudderstackIntegrationsURL string IntercomSecret string @@ -1284,6 +1285,7 @@ func (cfg *Cfg) parseINIFile(iniFile *ini.File) error { cfg.RudderstackWriteKey = analytics.Key("rudderstack_write_key").String() cfg.RudderstackDataPlaneURL = analytics.Key("rudderstack_data_plane_url").String() cfg.RudderstackSDKURL = analytics.Key("rudderstack_sdk_url").String() + cfg.RudderstackV3SDKURL = analytics.Key("rudderstack_v3_sdk_url").String() cfg.RudderstackConfigURL = analytics.Key("rudderstack_config_url").String() cfg.RudderstackIntegrationsURL = analytics.Key("rudderstack_integrations_url").String() cfg.IntercomSecret = analytics.Key("intercom_secret").String() diff --git a/public/app/core/services/echo/init.ts b/public/app/core/services/echo/init.ts index 5e49e72422a..9fce1a539ef 100644 --- a/public/app/core/services/echo/init.ts +++ b/public/app/core/services/echo/init.ts @@ -146,17 +146,15 @@ async function initRudderstackBackend() { return; } - // this will need to be updated when rudderstackSdkV3Url is added - // Desired logic: if only one of the sdk urls is provided, use respective code + // Logic: if only one of the sdk urls is provided, use respective code // otherwise defer to the feature toggle. - const fakeConfigRudderstackSdkV3Url: string | undefined = undefined; const hasOldSdkUrl = Boolean(config.rudderstackSdkUrl); - const hasNewSdkUrl = Boolean(fakeConfigRudderstackSdkV3Url); - const onlyOneConfigURLSet = hasOldSdkUrl !== hasNewSdkUrl; - const useNewRudderstack = onlyOneConfigURLSet ? hasNewSdkUrl : config.featureToggles.rudderstackUpgrade; + const hasNewSdkUrl = Boolean(config.rudderstackV3SdkUrl); + const onlyOneSdkUrlSet = hasOldSdkUrl !== hasNewSdkUrl; + const useNewRudderstack = onlyOneSdkUrlSet ? hasNewSdkUrl : config.featureToggles.rudderstackUpgrade; - const configUrl = useNewRudderstack ? fakeConfigRudderstackSdkV3Url : config.rudderstackSdkUrl; + const sdkUrl = useNewRudderstack ? config.rudderstackV3SdkUrl : config.rudderstackSdkUrl; const modulePromise = useNewRudderstack ? import('./backends/analytics/RudderstackV3Backend') @@ -168,8 +166,8 @@ async function initRudderstackBackend() { writeKey: config.rudderstackWriteKey, dataPlaneUrl: config.rudderstackDataPlaneUrl, user: contextSrv.user, - sdkUrl: config.rudderstackSdkUrl, - configUrl: configUrl, + sdkUrl, + configUrl: config.rudderstackConfigUrl, integrationsUrl: config.rudderstackIntegrationsUrl, buildInfo: config.buildInfo, }) From 05fd304dbd2be526ac96c29349f361d600144d29 Mon Sep 17 00:00:00 2001 From: Haris Rozajac <58232930+harisrozajac@users.noreply.github.com> Date: Thu, 18 Dec 2025 11:58:21 -0700 Subject: [PATCH 30/36] Dashboards: AdHoc and GroupBy wrapper (#115124) * wip; DrilldownControls * use wrapper so that drilldown controls wrap inline * keep labels on top when input expands vertically * add clear all button * add collapsible prop * i18n * Increase maxWidth for adhoc * bump scenes for testing * fix * remove clear all button * use new feature toggle; pass collapsible in v2 * update variable controls to use new feature flag * cleanup * wip (#115441) * wip * fix * update wrapping on smaller screens --------- Co-authored-by: Haris Rozajac * Filter out variables that are not in inControlsMenu * filter out inControlsMenu vars, not hidden ones * canary scenes * fix * cleanup * canary scenes * pass wideInput to groupby based on ff * update var name and bump scenes * bump scenes * yarn lock --------- Co-authored-by: Victor Marin --- package.json | 4 +- .../src/types/featureToggles.gen.ts | 4 + pkg/services/featuremgmt/registry.go | 7 ++ pkg/services/featuremgmt/toggles_gen.csv | 1 + pkg/services/featuremgmt/toggles_gen.go | 4 + pkg/services/featuremgmt/toggles_gen.json | 13 +++ .../scene/DashboardControls.tsx | 97 +++++++++++++++++++ .../scene/DrilldownControls.tsx | 77 +++++++++++++++ .../scene/VariableControls.tsx | 23 ++++- .../transformSaveModelSchemaV2ToScene.ts | 2 + .../dashboard-scene/utils/variables.ts | 2 + yarn.lock | 22 ++--- 12 files changed, 240 insertions(+), 16 deletions(-) create mode 100644 public/app/features/dashboard-scene/scene/DrilldownControls.tsx diff --git a/package.json b/package.json index 37dc6ff745c..73dec9dbc90 100644 --- a/package.json +++ b/package.json @@ -295,8 +295,8 @@ "@grafana/plugin-ui": "^0.11.1", "@grafana/prometheus": "workspace:*", "@grafana/runtime": "workspace:*", - "@grafana/scenes": "^6.51.0", - "@grafana/scenes-react": "^6.51.0", + "@grafana/scenes": "6.52.0", + "@grafana/scenes-react": "6.52.0", "@grafana/schema": "workspace:*", "@grafana/sql": "workspace:*", "@grafana/ui": "workspace:*", diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index b91161d966c..e32ce90bcc5 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -499,6 +499,10 @@ export interface FeatureToggles { */ newDashboardWithFiltersAndGroupBy?: boolean; /** + * Wraps the ad hoc and group by variables in a single wrapper, with all other variables below it + */ + dashboardAdHocAndGroupByWrapper?: boolean; + /** * Updates CloudWatch label parsing to be more accurate * @default true */ diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index d8a46601ad6..bd2707f3356 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -820,6 +820,13 @@ var ( Owner: grafanaDashboardsSquad, HideFromDocs: true, }, + { + Name: "dashboardAdHocAndGroupByWrapper", + Description: "Wraps the ad hoc and group by variables in a single wrapper, with all other variables below it", + Stage: FeatureStageExperimental, + Owner: grafanaDashboardsSquad, + HideFromDocs: true, + }, { Name: "cloudWatchNewLabelParsing", Description: "Updates CloudWatch label parsing to be more accurate", diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index 3fbc019bff1..095350eb0c8 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -113,6 +113,7 @@ scopeFilters,experimental,@grafana/dashboards-squad,false,false,false oauthRequireSubClaim,experimental,@grafana/identity-access-team,false,false,false refreshTokenRequired,experimental,@grafana/identity-access-team,false,false,false newDashboardWithFiltersAndGroupBy,experimental,@grafana/dashboards-squad,false,false,false +dashboardAdHocAndGroupByWrapper,experimental,@grafana/dashboards-squad,false,false,false cloudWatchNewLabelParsing,GA,@grafana/aws-datasources,false,false,false disableNumericMetricsSortingInExpressions,experimental,@grafana/oss-big-tent,false,true,false grafanaManagedRecordingRules,experimental,@grafana/alerting-squad,false,false,false diff --git a/pkg/services/featuremgmt/toggles_gen.go b/pkg/services/featuremgmt/toggles_gen.go index 020e0bce893..fd77326e8b0 100644 --- a/pkg/services/featuremgmt/toggles_gen.go +++ b/pkg/services/featuremgmt/toggles_gen.go @@ -339,6 +339,10 @@ const ( // Enables filters and group by variables on all new dashboards. Variables are added only if default data source supports filtering. FlagNewDashboardWithFiltersAndGroupBy = "newDashboardWithFiltersAndGroupBy" + // FlagDashboardAdHocAndGroupByWrapper + // Wraps the ad hoc and group by variables in a single wrapper, with all other variables below it + FlagDashboardAdHocAndGroupByWrapper = "dashboardAdHocAndGroupByWrapper" + // FlagCloudWatchNewLabelParsing // Updates CloudWatch label parsing to be more accurate FlagCloudWatchNewLabelParsing = "cloudWatchNewLabelParsing" diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json index 866f8a78881..0de187cce52 100644 --- a/pkg/services/featuremgmt/toggles_gen.json +++ b/pkg/services/featuremgmt/toggles_gen.json @@ -922,6 +922,19 @@ "frontend": true } }, + { + "metadata": { + "name": "dashboardAdHocAndGroupByWrapper", + "resourceVersion": "1765841806645", + "creationTimestamp": "2025-12-15T23:36:46Z" + }, + "spec": { + "description": "Wraps the ad hoc and group by variables in a single wrapper, with all other variables below it", + "stage": "experimental", + "codeowner": "@grafana/dashboards-squad", + "hideFromDocs": true + } + }, { "metadata": { "name": "dashboardDisableSchemaValidationV1", diff --git a/public/app/features/dashboard-scene/scene/DashboardControls.tsx b/public/app/features/dashboard-scene/scene/DashboardControls.tsx index 32f7d5289f8..a44fd918393 100644 --- a/public/app/features/dashboard-scene/scene/DashboardControls.tsx +++ b/public/app/features/dashboard-scene/scene/DashboardControls.tsx @@ -16,6 +16,7 @@ import { SceneObjectUrlSyncConfig, SceneObjectUrlValues, CancelActivationHandler, + sceneUtils, } from '@grafana/scenes'; import { Box, Button, useStyles2 } from '@grafana/ui'; import { playlistSrv } from 'app/features/playlist/PlaylistSrv'; @@ -27,6 +28,7 @@ import { getDashboardSceneFor } from '../utils/utils'; import { DashboardDataLayerControls } from './DashboardDataLayerControls'; import { DashboardLinksControls } from './DashboardLinksControls'; import { DashboardScene } from './DashboardScene'; +import { DrilldownControls } from './DrilldownControls'; import { VariableControls } from './VariableControls'; import { DashboardControlsButton } from './dashboard-controls-menu/DashboardControlsMenuButton'; import { hasDashboardControls, useHasDashboardControls } from './dashboard-controls-menu/utils'; @@ -151,11 +153,63 @@ function DashboardControlsRenderer({ model }: SceneComponentProps v.state.hide !== VariableHide.inControlsMenu); + const adHocVar = visibleVariables.find((v) => sceneUtils.isAdHocVariable(v)); + const groupByVar = visibleVariables.find((v) => sceneUtils.isGroupByVariable(v)); + const useUnifiedDrilldownUI = config.featureToggles.dashboardAdHocAndGroupByWrapper && adHocVar && groupByVar; + if (!model.hasControls()) { // To still have spacing when no controls are rendered return {renderHiddenVariables(dashboard)}; } + // When dashboardAdHocAndGroupByWrapper is enabled, use the new layout with topRow + if (useUnifiedDrilldownUI) { + return ( +
+
+ {config.featureToggles.scopeFilters && !editPanel && ( + + )} + {!hideVariableControls && ( +
+ +
+ )} +
+ {!hideTimeControls && ( +
+ + +
+ )} + {config.featureToggles.dashboardNewLayouts && ( +
+ +
+ )} +
+
+ {!hideVariableControls && ( + <> + + + + )} + {!hideLinksControls && !editPanel && } + {!hideDashboardControls && hasDashboardControls && } + {editPanel && } + {showDebugger && } +
+ ); + } + + // Original layout when feature toggle is off return (
+
+ +
+
+ +
+
+ ); +} + +const getStyles = (theme: GrafanaTheme2) => ({ + drilldownRow: css({ + display: 'flex', + flexWrap: 'nowrap', + [theme.breakpoints.down('xl')]: { + flexWrap: 'wrap', + }, + gap: theme.spacing(1), + width: '100%', + }), + adHocContainer: css({ + flex: '7 1 0%', // 70% of available space + minWidth: 0, + [theme.breakpoints.down('xl')]: { + // Force full width, causing groupBy to wrap + flex: '1 1 100%', + }, + display: 'flex', + // Make the wrapper and its children take full width + '& > div': { + alignItems: 'flex-start', + width: '100%', + flex: 1, + }, + }), + groupByContainer: css({ + flex: '3 1 0%', // 30% of available space + minWidth: 0, + display: 'flex', + // Make the wrapper and its children take full width + '& > div': { + alignItems: 'flex-start', + width: '100%', + flex: 1, + }, + [theme.breakpoints.down('sm')]: { + minWidth: '200px', + }, + }), + clearAllButton: css({ + alignSelf: 'flex-start', + fontSize: theme.typography.bodySmall.fontSize, + padding: theme.spacing(0.5, 0.5), + marginBottom: theme.spacing(1), + }), +}); diff --git a/public/app/features/dashboard-scene/scene/VariableControls.tsx b/public/app/features/dashboard-scene/scene/VariableControls.tsx index 03d23712c7b..73dd2614472 100644 --- a/public/app/features/dashboard-scene/scene/VariableControls.tsx +++ b/public/app/features/dashboard-scene/scene/VariableControls.tsx @@ -20,13 +20,30 @@ import { AddVariableButton } from './VariableControlsAddButton'; export function VariableControls({ dashboard }: { dashboard: DashboardScene }) { const { variables } = sceneGraph.getVariables(dashboard)!.useState(); + // Get visible variables for drilldown layout + const visibleVariables = variables.filter((v) => v.state.hide !== VariableHide.inControlsMenu); + + const adHocVar = visibleVariables.find((v) => sceneUtils.isAdHocVariable(v)); + const groupByVar = visibleVariables.find((v) => sceneUtils.isGroupByVariable(v)); + + const hasDrilldownControls = config.featureToggles.dashboardAdHocAndGroupByWrapper && adHocVar && groupByVar; + + const restVariables = visibleVariables.filter( + (v) => v.state.name !== adHocVar?.state.name && v.state.name !== groupByVar?.state.name + ); + + // Variables to render (exclude adhoc/groupby when drilldown controls are shown in top row) + const variablesToRender = hasDrilldownControls + ? restVariables.filter((v) => v.state.hide !== VariableHide.inControlsMenu) + : variables.filter((v) => v.state.hide !== VariableHide.inControlsMenu); + return ( <> - {variables - .filter((v) => v.state.hide !== VariableHide.inControlsMenu) - .map((variable) => ( + {variablesToRender.length > 0 && + variablesToRender.map((variable) => ( ))} + {config.featureToggles.dashboardNewLayouts ? : null} ); diff --git a/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.ts b/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.ts index ff3927d3814..f343cbce00d 100644 --- a/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.ts +++ b/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.ts @@ -336,6 +336,7 @@ function createSceneVariableFromVariableModel(variable: TypedVariableModelV2): S supportsMultiValueOperators: Boolean( getDataSourceSrv().getInstanceSettings({ type: ds?.type })?.meta.multiValueFilterOperators ), + collapsible: config.featureToggles.dashboardAdHocAndGroupByWrapper, }; if (variable.spec.allowCustomValue !== undefined) { adhocVariableState.allowCustomValue = variable.spec.allowCustomValue; @@ -460,6 +461,7 @@ function createSceneVariableFromVariableModel(variable: TypedVariableModelV2): S skipUrlSync: variable.spec.skipUrlSync, isMulti: variable.spec.multi, hide: transformVariableHideToEnumV1(variable.spec.hide), + wideInput: config.featureToggles.dashboardAdHocAndGroupByWrapper, drilldownRecommendationsEnabled: config.featureToggles.drilldownRecommendations, // @ts-expect-error defaultOptions: variable.options, diff --git a/public/app/features/dashboard-scene/utils/variables.ts b/public/app/features/dashboard-scene/utils/variables.ts index 2f262781b00..cd09e094845 100644 --- a/public/app/features/dashboard-scene/utils/variables.ts +++ b/public/app/features/dashboard-scene/utils/variables.ts @@ -164,6 +164,7 @@ export function createSceneVariableFromVariableModel(variable: TypedVariableMode useQueriesAsFilterForOptions: true, drilldownRecommendationsEnabled: config.featureToggles.drilldownRecommendations, layout: config.featureToggles.newFiltersUI ? 'combobox' : undefined, + collapsible: config.featureToggles.dashboardAdHocAndGroupByWrapper, supportsMultiValueOperators: Boolean( getDataSourceSrv().getInstanceSettings({ type: variable.datasource?.type })?.meta.multiValueFilterOperators ), @@ -288,6 +289,7 @@ export function createSceneVariableFromVariableModel(variable: TypedVariableMode text: variable.current?.text || [], skipUrlSync: variable.skipUrlSync, hide: variable.hide, + wideInput: config.featureToggles.dashboardAdHocAndGroupByWrapper, // @ts-expect-error defaultOptions: variable.options, defaultValue: variable.defaultValue, diff --git a/yarn.lock b/yarn.lock index a60a9c08912..64561ce4dca 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3604,11 +3604,11 @@ __metadata: languageName: unknown linkType: soft -"@grafana/scenes-react@npm:^6.51.0": - version: 6.51.0 - resolution: "@grafana/scenes-react@npm:6.51.0" +"@grafana/scenes-react@npm:6.52.0": + version: 6.52.0 + resolution: "@grafana/scenes-react@npm:6.52.0" dependencies: - "@grafana/scenes": "npm:6.51.0" + "@grafana/scenes": "npm:6.52.0" lru-cache: "npm:^10.2.2" react-use: "npm:^17.4.0" peerDependencies: @@ -3620,7 +3620,7 @@ __metadata: react: ^18.0.0 react-dom: ^18.0.0 react-router-dom: ^6.28.0 - checksum: 10/14acdfe5220e67e7450780320b779e2e4a255995d55f0c82eb0d25933e72598e54826df0a8beee05591efe01a91ddab840483fea3bb828bd5925c3f0b44b8d17 + checksum: 10/7f121bcc4fd50f525c7c3457666ad3a32b04783d322d6715aedb6119538f911d0ec265c9c5b49a80478c1deb99286d36d003399a8831f76b6c483f4458b4ce8b languageName: node linkType: hard @@ -3650,9 +3650,9 @@ __metadata: languageName: node linkType: hard -"@grafana/scenes@npm:6.51.0, @grafana/scenes@npm:^6.51.0": - version: 6.51.0 - resolution: "@grafana/scenes@npm:6.51.0" +"@grafana/scenes@npm:6.52.0": + version: 6.52.0 + resolution: "@grafana/scenes@npm:6.52.0" dependencies: "@floating-ui/react": "npm:^0.26.16" "@leeoniya/ufuzzy": "npm:^1.0.16" @@ -3672,7 +3672,7 @@ __metadata: react: ^18.0.0 react-dom: ^18.0.0 react-router-dom: ^6.28.0 - checksum: 10/4e4f43babe786ff729d58b7636182df57c58ce40c13b56036f725c070e0cf597cbe52aaa0f811184b8d42d8d1f9a32679695471d410f883051b09da44f8bf36a + checksum: 10/e52e0fb83396776c6cb79f8ac6a8aad0799eb2ccce9d0139f5734a49c3add7a1e3b97f14e0142c95b2bceee3ed8fa97b675b9b94c02382ecd683f470d06ef145 languageName: node linkType: hard @@ -19508,8 +19508,8 @@ __metadata: "@grafana/plugin-ui": "npm:^0.11.1" "@grafana/prometheus": "workspace:*" "@grafana/runtime": "workspace:*" - "@grafana/scenes": "npm:^6.51.0" - "@grafana/scenes-react": "npm:^6.51.0" + "@grafana/scenes": "npm:6.52.0" + "@grafana/scenes-react": "npm:6.52.0" "@grafana/schema": "workspace:*" "@grafana/sql": "workspace:*" "@grafana/test-utils": "workspace:*" From 39c562a911a0a7dc3663223adbcd92cc33fb18be Mon Sep 17 00:00:00 2001 From: Denis Vodopianov Date: Thu, 18 Dec 2025 22:46:32 +0100 Subject: [PATCH 31/36] Revert: chore: a drop-in replacement for FeatureToggles.IsEnabledGlobally in app settings (#115593) * Revert "chore: a drop-in replacement for FeatureToggles.IsEnabledGlobally in app settings (#113449)" This reverts commit 26ce2c09d78706e4235cc378d67da9f99c6ba99d. * Change FeatureToggles.IsEnabledGlobally deprecation message --- pkg/registry/apps/apps.go | 2 +- pkg/services/featuremgmt/models.go | 4 +--- pkg/setting/setting.go | 5 ----- pkg/setting/startup_setting.go | 14 -------------- 4 files changed, 2 insertions(+), 23 deletions(-) delete mode 100644 pkg/setting/startup_setting.go diff --git a/pkg/registry/apps/apps.go b/pkg/registry/apps/apps.go index 77c96fd437c..a1ec8aafd65 100644 --- a/pkg/registry/apps/apps.go +++ b/pkg/registry/apps/apps.go @@ -76,7 +76,7 @@ func ProvideAppInstallers( if features.IsEnabledGlobally(featuremgmt.FlagKubernetesLogsDrilldown) { installers = append(installers, logsdrilldownAppInstaller) } - //nolint:staticcheck // not yet migrated to OpenFeature + //nolint:staticcheck if features.IsEnabledGlobally(featuremgmt.FlagKubernetesAnnotations) { installers = append(installers, annotationAppInstaller) } diff --git a/pkg/services/featuremgmt/models.go b/pkg/services/featuremgmt/models.go index 304d17ee075..d59dff63c37 100644 --- a/pkg/services/featuremgmt/models.go +++ b/pkg/services/featuremgmt/models.go @@ -23,9 +23,7 @@ type FeatureToggles interface { // a full server restart for a change to take place. // // Deprecated: FeatureToggles.IsEnabledGlobally is deprecated and will be removed in a future release. - // Toggles that must be reliably evaluated at the service startup should be - // changed to settings (see setting.StartupSettings), and/or removed entirely. - // For app registration please use `grafana-apiserver.runtime_config` in settings.ini + // Toggles that must be reliably evaluated at the service startup should be changed to settings and/or removed entirely. IsEnabledGlobally(flag string) bool // Get the enabled flags -- this *may* also include disabled flags (with value false) diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index 62b9332581f..00bcdabd88d 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -511,9 +511,6 @@ type Cfg struct { // Query history QueryHistoryEnabled bool - // StartupSettings settings - StartupSettings StartupSettings - // Open feature settings OpenFeature OpenFeatureSettings @@ -1476,8 +1473,6 @@ func (cfg *Cfg) parseINIFile(iniFile *ini.File) error { // unified storage config cfg.setUnifiedStorageConfig() - // app platform config - cfg.readStartupSettingsSection() return nil } diff --git a/pkg/setting/startup_setting.go b/pkg/setting/startup_setting.go deleted file mode 100644 index 8b2c2fa6362..00000000000 --- a/pkg/setting/startup_setting.go +++ /dev/null @@ -1,14 +0,0 @@ -package setting - -type StartupSettings struct { - KubernetesAnnotationsAppEnabled bool -} - -func (cfg *Cfg) readStartupSettingsSection() { - settings := StartupSettings{} - - startupSettingsSection := cfg.Raw.Section("startup_settings") - settings.KubernetesAnnotationsAppEnabled = startupSettingsSection.Key("annotations_app_enabled").MustBool(false) - - cfg.StartupSettings = settings -} From 37c1e3fb02f46f315ea53b301fe089d86374419f Mon Sep 17 00:00:00 2001 From: Haris Rozajac <58232930+harisrozajac@users.noreply.github.com> Date: Thu, 18 Dec 2025 15:11:09 -0700 Subject: [PATCH 32/36] Dashboard Schema v1beta1 to v2alpha1: Preserve string template variable datasource references in query variables (#115516) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Dashboard migration: preserve legacy string datasource references Fix v1beta1 → v2alpha1 conversion to handle legacy string datasource references in QueryVariable, AdhocVariable, and GroupByVariable. Previously, string datasource references (both template variables like "$datasource" and direct names/UIDs like "prometheus") were being dropped during conversion, causing variable chaining to break. The frontend's DatasourceSrv.getInstanceSettings() already handles string references by trying uid → name → id lookup at runtime, so we preserve the string in the uid field and let the frontend resolve it. * trigger frontend ci tests when dashboard migration code changes * v1: if string convert to DS ref * Update migration testdata to fix template variable datasource references * update --- .github/actions/change-detection/action.yml | 1 + ....mimir_rollout_debugging.v42.v0alpha1.json | 8 ++- ....mimir_rollout_debugging.v42.v2alpha1.json | 12 ++++- ...5.mimir_rollout_debugging.v42.v2beta1.json | 10 +++- .../conversion/v0alpha1_to_v1beta1.go | 53 +++++++++++++++++++ .../conversion/v1beta1_to_v2alpha1.go | 12 +++++ .../input/v15.mimir_rollout_debugging.json | 8 ++- .../v15.mimir_rollout_debugging.v42.json | 8 ++- .../v15.mimir_rollout_debugging.v15.json | 8 ++- 9 files changed, 108 insertions(+), 12 deletions(-) diff --git a/.github/actions/change-detection/action.yml b/.github/actions/change-detection/action.yml index 2c6b46606f4..863cab646b9 100644 --- a/.github/actions/change-detection/action.yml +++ b/.github/actions/change-detection/action.yml @@ -95,6 +95,7 @@ runs: - 'nx.json' - 'tsconfig.json' - '.yarn/**' + - 'apps/dashboard/pkg/migration/**' - '${{ inputs.self }}' e2e: - 'e2e/**' diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v15.mimir_rollout_debugging.v42.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v15.mimir_rollout_debugging.v42.v0alpha1.json index 6c259253e55..35cc948844c 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v15.mimir_rollout_debugging.v42.v0alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v15.mimir_rollout_debugging.v42.v0alpha1.json @@ -743,7 +743,9 @@ "text": "prod", "value": "prod" }, - "datasource": "$datasource", + "datasource": { + "uid": "$datasource" + }, "hide": 0, "includeAll": true, "label": "cluster", @@ -764,7 +766,9 @@ "text": "prod", "value": "prod" }, - "datasource": "$datasource", + "datasource": { + "uid": "$datasource" + }, "hide": 0, "includeAll": false, "label": "namespace", diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v15.mimir_rollout_debugging.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v15.mimir_rollout_debugging.v42.v2alpha1.json index 8d340c0f65e..26945033c4c 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v15.mimir_rollout_debugging.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v15.mimir_rollout_debugging.v42.v2alpha1.json @@ -961,8 +961,12 @@ "hide": "dontHide", "refresh": "onDashboardLoad", "skipUrlSync": false, + "datasource": { + "type": "", + "uid": "$datasource" + }, "query": { - "kind": "prometheus", + "kind": "", "spec": { "__legacyStringValue": "label_values(up, job)" } @@ -988,8 +992,12 @@ "hide": "dontHide", "refresh": "onDashboardLoad", "skipUrlSync": false, + "datasource": { + "type": "", + "uid": "$datasource" + }, "query": { - "kind": "prometheus", + "kind": "", "spec": { "__legacyStringValue": "label_values(up{job=~\"$cluster\"}, instance)" } diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v15.mimir_rollout_debugging.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v15.mimir_rollout_debugging.v42.v2beta1.json index 8070895766a..344b0ef564a 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v15.mimir_rollout_debugging.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v15.mimir_rollout_debugging.v42.v2beta1.json @@ -978,8 +978,11 @@ "skipUrlSync": false, "query": { "kind": "DataQuery", - "group": "prometheus", + "group": "", "version": "v0", + "datasource": { + "name": "$datasource" + }, "spec": { "__legacyStringValue": "label_values(up, job)" } @@ -1007,8 +1010,11 @@ "skipUrlSync": false, "query": { "kind": "DataQuery", - "group": "prometheus", + "group": "", "version": "v0", + "datasource": { + "name": "$datasource" + }, "spec": { "__legacyStringValue": "label_values(up{job=~\"$cluster\"}, instance)" } diff --git a/apps/dashboard/pkg/migration/conversion/v0alpha1_to_v1beta1.go b/apps/dashboard/pkg/migration/conversion/v0alpha1_to_v1beta1.go index 2154a26e3a2..d0679c76e63 100644 --- a/apps/dashboard/pkg/migration/conversion/v0alpha1_to_v1beta1.go +++ b/apps/dashboard/pkg/migration/conversion/v0alpha1_to_v1beta1.go @@ -2,6 +2,7 @@ package conversion import ( "context" + "strings" "k8s.io/apimachinery/pkg/conversion" "k8s.io/apiserver/pkg/endpoints/request" @@ -79,5 +80,57 @@ func ConvertDashboard_V0_to_V1beta1(in *dashv0.Dashboard, out *dashv1.Dashboard, return schemaversion.NewMigrationError(err.Error(), schemaversion.GetSchemaVersion(in.Spec.Object), schemaversion.LATEST_VERSION, "Convert_V0_to_V1") } + // Normalize template variable datasources from string to object format + // This handles legacy dashboards where query variables have datasource: "$datasource" (string) + // instead of datasource: { uid: "$datasource" } (object) + // our migration pipeline in v36 doesn't address because this was not addressed historically + // in DashboardMigrator - see public/app/features/dashboard/state/DashboardMigrator.ts#L607 + // Which means that we have schemaVersion: 42 dashboards where datasource variable references are still strings + normalizeTemplateVariableDatasources(out.Spec.Object) + return nil } + +// normalizeTemplateVariableDatasources converts template variable string datasources to object format. +// Legacy dashboards may have query variables with datasource: "$datasource" (string). +// This normalizes them to datasource: { uid: "$datasource" } for consistent V1→V2 conversion. +func normalizeTemplateVariableDatasources(dashboard map[string]interface{}) { + templating, ok := dashboard["templating"].(map[string]interface{}) + if !ok { + return + } + + list, ok := templating["list"].([]interface{}) + if !ok { + return + } + + for _, variable := range list { + varMap, ok := variable.(map[string]interface{}) + if !ok { + continue + } + + varType, _ := varMap["type"].(string) + if varType != "query" { + continue + } + + ds := varMap["datasource"] + if dsStr, ok := ds.(string); ok && isTemplateVariableRef(dsStr) { + // Convert string template variable reference to object format + varMap["datasource"] = map[string]interface{}{ + "uid": dsStr, + } + } + } +} + +// isTemplateVariableRef checks if a string is a Grafana template variable reference. +// Template variables can be in the form: $varname or ${varname} +func isTemplateVariableRef(s string) bool { + if s == "" { + return false + } + return strings.HasPrefix(s, "$") || strings.HasPrefix(s, "${") +} diff --git a/apps/dashboard/pkg/migration/conversion/v1beta1_to_v2alpha1.go b/apps/dashboard/pkg/migration/conversion/v1beta1_to_v2alpha1.go index 47c12a6ac94..92c5d937fe7 100644 --- a/apps/dashboard/pkg/migration/conversion/v1beta1_to_v2alpha1.go +++ b/apps/dashboard/pkg/migration/conversion/v1beta1_to_v2alpha1.go @@ -1185,6 +1185,10 @@ func buildQueryVariable(ctx context.Context, varMap map[string]interface{}, comm // If no UID and no type, use default datasourceType = getDefaultDatasourceType(ctx, dsIndexProvider) } + } else if dsStr, ok := datasource.(string); ok && isTemplateVariable(dsStr) { + // Handle datasource variable reference (e.g., "$datasource") + // Only process template variables - other string values are not supported in V2 format + datasourceUID = dsStr } else { datasourceType = getDefaultDatasourceType(ctx, dsIndexProvider) } @@ -1532,6 +1536,10 @@ func buildAdhocVariable(ctx context.Context, varMap map[string]interface{}, comm // If no UID and no type, use default datasourceType = getDefaultDatasourceType(ctx, dsIndexProvider) } + } else if dsStr, ok := datasource.(string); ok && isTemplateVariable(dsStr) { + // Handle datasource variable reference (e.g., "$datasource") + // Only process template variables - other string values are not supported in V2 format + datasourceUID = dsStr } else { datasourceType = getDefaultDatasourceType(ctx, dsIndexProvider) } @@ -1709,6 +1717,10 @@ func buildGroupByVariable(ctx context.Context, varMap map[string]interface{}, co // Resolve Grafana datasource UID when type is "datasource" and UID is empty datasourceUID = resolveGrafanaDatasourceUID(datasourceType, datasourceUID) + } else if dsStr, ok := datasource.(string); ok && isTemplateVariable(dsStr) { + // Handle datasource variable reference (e.g., "$datasource") + // Only process template variables - other string values are not supported in V2 format + datasourceUID = dsStr } else { datasourceType = getDefaultDatasourceType(ctx, dsIndexProvider) } diff --git a/apps/dashboard/pkg/migration/testdata/input/v15.mimir_rollout_debugging.json b/apps/dashboard/pkg/migration/testdata/input/v15.mimir_rollout_debugging.json index dc0ca4e5845..1cc39838eab 100644 --- a/apps/dashboard/pkg/migration/testdata/input/v15.mimir_rollout_debugging.json +++ b/apps/dashboard/pkg/migration/testdata/input/v15.mimir_rollout_debugging.json @@ -654,7 +654,9 @@ "text": "prod", "value": "prod" }, - "datasource": "$datasource", + "datasource": { + "uid": "$datasource" + }, "hide": 0, "includeAll": true, "label": "cluster", @@ -677,7 +679,9 @@ "text": "prod", "value": "prod" }, - "datasource": "$datasource", + "datasource": { + "uid": "$datasource" + }, "hide": 0, "includeAll": false, "label": "namespace", diff --git a/apps/dashboard/pkg/migration/testdata/output/latest_version/v15.mimir_rollout_debugging.v42.json b/apps/dashboard/pkg/migration/testdata/output/latest_version/v15.mimir_rollout_debugging.v42.json index 6f507f5bc47..1195b2bec63 100644 --- a/apps/dashboard/pkg/migration/testdata/output/latest_version/v15.mimir_rollout_debugging.v42.json +++ b/apps/dashboard/pkg/migration/testdata/output/latest_version/v15.mimir_rollout_debugging.v42.json @@ -737,7 +737,9 @@ "text": "prod", "value": "prod" }, - "datasource": "$datasource", + "datasource": { + "uid": "$datasource" + }, "hide": 0, "includeAll": true, "label": "cluster", @@ -758,7 +760,9 @@ "text": "prod", "value": "prod" }, - "datasource": "$datasource", + "datasource": { + "uid": "$datasource" + }, "hide": 0, "includeAll": false, "label": "namespace", diff --git a/apps/dashboard/pkg/migration/testdata/output/single_version/v15.mimir_rollout_debugging.v15.json b/apps/dashboard/pkg/migration/testdata/output/single_version/v15.mimir_rollout_debugging.v15.json index dd9ec62ed85..30186560450 100644 --- a/apps/dashboard/pkg/migration/testdata/output/single_version/v15.mimir_rollout_debugging.v15.json +++ b/apps/dashboard/pkg/migration/testdata/output/single_version/v15.mimir_rollout_debugging.v15.json @@ -717,7 +717,9 @@ "text": "prod", "value": "prod" }, - "datasource": "$datasource", + "datasource": { + "uid": "$datasource" + }, "hide": 0, "includeAll": true, "label": "cluster", @@ -739,7 +741,9 @@ "text": "prod", "value": "prod" }, - "datasource": "$datasource", + "datasource": { + "uid": "$datasource" + }, "hide": 0, "includeAll": false, "label": "namespace", From 72e1f1e5461cdde23a888aea9d101702bfcb701a Mon Sep 17 00:00:00 2001 From: Leon Sorokin Date: Thu, 18 Dec 2025 16:45:00 -0600 Subject: [PATCH 33/36] Heatmap: Support for linear y axis (#113337) * wip * boop * Base factor on data * Add some basic option control * Remove old comments * Add feature flag * Apply feature flag to axis options * Turn factor calculation into exported function * Simplify bucket factor function * Clarify comments * Fix cell sizing of pre-bucketed heatmaps with log * Remove unnecessary category change * Consolidate editor for calculate from data no * Update bucket function sanity checks * Wire up scale config from yBucketScale * Hide bucket controls for heatmap cells * Fix splits * Add test coverage * Fix failing test * Add basic util test coverage * Fix tooltip for legacy in linear * Fix y bucket option width to be consistent * Hide tick alignment for explicit scale modes * Clarify comment * Make sure units are passed properly for linear * Remove null assertion operator * Clean up nested ternary * Add type protection to scaleLog * Remove repeated code for ySize calcs * Remove ternary for scaleDistribution * Add test coverage for YBucketScaleEditor * Add isHeatmapSparse function to tooltip utils * Create calculateYSizeDivisor util function * Fix y axis min and max options and extend to log * Add toLogBase test coverage * Create applyExplicitMinMax function * Add additional test coverage for scale editor * Run i18n-extract * Update eslint suppressions --------- Co-authored-by: Drew Slobodnjak <60050885+drew08t@users.noreply.github.com> --- eslint-suppressions.json | 2 +- .../src/types/featureToggles.gen.ts | 5 + .../panelcfg/x/HeatmapPanelCfg_types.gen.ts | 4 + pkg/services/featuremgmt/registry.go | 8 + pkg/services/featuremgmt/toggles_gen.csv | 1 + pkg/services/featuremgmt/toggles_gen.json | 14 + .../calculateHeatmap/heatmap.test.ts | 327 ++++++++++++++- .../transformers/calculateHeatmap/heatmap.ts | 158 ++++++-- .../plugins/panel/heatmap/HeatmapPanel.tsx | 16 +- .../plugins/panel/heatmap/HeatmapTooltip.tsx | 15 +- .../panel/heatmap/YBucketScaleEditor.test.tsx | 277 +++++++++++++ .../panel/heatmap/YBucketScaleEditor.tsx | 135 +++++++ public/app/plugins/panel/heatmap/module.tsx | 26 +- public/app/plugins/panel/heatmap/panelcfg.cue | 2 + .../app/plugins/panel/heatmap/panelcfg.gen.ts | 4 + .../panel/heatmap/tooltip/utils.test.ts | 47 +++ .../plugins/panel/heatmap/tooltip/utils.ts | 14 +- .../app/plugins/panel/heatmap/utils.test.ts | 375 +++++++++++++++++- public/app/plugins/panel/heatmap/utils.ts | 115 +++++- public/locales/en-US/grafana.json | 13 + 20 files changed, 1481 insertions(+), 77 deletions(-) create mode 100644 public/app/plugins/panel/heatmap/YBucketScaleEditor.test.tsx create mode 100644 public/app/plugins/panel/heatmap/YBucketScaleEditor.tsx create mode 100644 public/app/plugins/panel/heatmap/tooltip/utils.test.ts diff --git a/eslint-suppressions.json b/eslint-suppressions.json index 1cdf9de4c4a..1d0d5a2684c 100644 --- a/eslint-suppressions.json +++ b/eslint-suppressions.json @@ -4339,7 +4339,7 @@ }, "public/app/plugins/panel/heatmap/utils.ts": { "@typescript-eslint/consistent-type-assertions": { - "count": 16 + "count": 14 } }, "public/app/plugins/panel/histogram/Histogram.tsx": { diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index e32ce90bcc5..199be6d9c3f 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -1169,6 +1169,11 @@ export interface FeatureToggles { */ externalVizSuggestions?: boolean; /** + * Enable Y-axis scale configuration options for pre-bucketed heatmap data (heatmap-rows) + * @default false + */ + heatmapRowsAxisOptions?: boolean; + /** * Restrict PanelChrome contents with overflow: hidden; * @default true */ diff --git a/packages/grafana-schema/src/raw/composable/heatmap/panelcfg/x/HeatmapPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/heatmap/panelcfg/x/HeatmapPanelCfg_types.gen.ts index 1ff4370c9a2..b67a8cf5980 100644 --- a/packages/grafana-schema/src/raw/composable/heatmap/panelcfg/x/HeatmapPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/heatmap/panelcfg/x/HeatmapPanelCfg_types.gen.ts @@ -185,6 +185,10 @@ export interface RowsHeatmapOptions { * Sets the name of the cell when not calculating from data */ value?: string; + /** + * Controls the scale distribution of the y-axis buckets + */ + yBucketScale?: ui.ScaleDistributionConfig; } export interface Options { diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index bd2707f3356..e9a4a8c7caf 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -1928,6 +1928,14 @@ var ( Owner: grafanaDatavizSquad, Expression: "false", }, + { + Name: "heatmapRowsAxisOptions", + Description: "Enable Y-axis scale configuration options for pre-bucketed heatmap data (heatmap-rows)", + Stage: FeatureStageExperimental, + FrontendOnly: true, + Owner: grafanaDatavizSquad, + Expression: "false", + }, { Name: "preventPanelChromeOverflow", Description: "Restrict PanelChrome contents with overflow: hidden;", diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index 095350eb0c8..d56aff17bb7 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -262,6 +262,7 @@ pluginInstallAPISync,experimental,@grafana/plugins-platform-backend,false,false, newGauge,experimental,@grafana/dataviz-squad,false,false,true newVizSuggestions,preview,@grafana/dataviz-squad,false,false,true externalVizSuggestions,experimental,@grafana/dataviz-squad,false,false,true +heatmapRowsAxisOptions,experimental,@grafana/dataviz-squad,false,false,true preventPanelChromeOverflow,preview,@grafana/grafana-frontend-platform,false,false,true jaegerEnableGrpcEndpoint,experimental,@grafana/oss-big-tent,false,false,false pluginStoreServiceLoading,experimental,@grafana/plugins-platform-backend,false,false,false diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json index 0de187cce52..a0afcd87626 100644 --- a/pkg/services/featuremgmt/toggles_gen.json +++ b/pkg/services/featuremgmt/toggles_gen.json @@ -1647,6 +1647,20 @@ "codeowner": "@grafana/search-and-storage" } }, + { + "metadata": { + "name": "heatmapRowsAxisOptions", + "resourceVersion": "1765353244400", + "creationTimestamp": "2025-12-10T07:54:04Z" + }, + "spec": { + "description": "Enable Y-axis scale configuration options for pre-bucketed heatmap data (heatmap-rows)", + "stage": "experimental", + "codeowner": "@grafana/dataviz-squad", + "frontend": true, + "expression": "false" + } + }, { "metadata": { "name": "improvedExternalSessionHandling", diff --git a/public/app/features/transformers/calculateHeatmap/heatmap.test.ts b/public/app/features/transformers/calculateHeatmap/heatmap.test.ts index 4155a763d4d..d0a963ee9f7 100644 --- a/public/app/features/transformers/calculateHeatmap/heatmap.test.ts +++ b/public/app/features/transformers/calculateHeatmap/heatmap.test.ts @@ -1,7 +1,7 @@ import { FieldType, toDataFrame } from '@grafana/data'; -import { HeatmapCalculationOptions } from '@grafana/schema'; +import { HeatmapCalculationOptions, HeatmapCellLayout, ScaleDistribution } from '@grafana/schema'; -import { rowsToCellsHeatmap, calculateHeatmapFromData } from './heatmap'; +import { rowsToCellsHeatmap, calculateHeatmapFromData, calculateBucketFactor } from './heatmap'; describe('Heatmap transformer', () => { it('calculate heatmap from input data', async () => { @@ -121,4 +121,327 @@ describe('Heatmap transformer', () => { }) ).toThrowErrorMatchingInlineSnapshot(`"No numeric fields found for heatmap"`); }); + + describe('calculateBucketFactor', () => { + it('calculates ratio from last two buckets for log2 spacing', () => { + const buckets = [1, 2, 4, 8]; + expect(calculateBucketFactor(buckets)).toBe(2); + }); + + it('calculates ratio from last two buckets for log10 spacing', () => { + const buckets = [1, 10, 100, 1000]; + expect(calculateBucketFactor(buckets)).toBe(10); + }); + + it('calculates ratio for non-uniform spacing', () => { + const buckets = [1, 2.5, 6.25]; + expect(calculateBucketFactor(buckets)).toBe(2.5); + }); + + it('returns default factor for single value array', () => { + expect(calculateBucketFactor([5])).toBe(1.5); + }); + + it('returns default factor for empty array', () => { + expect(calculateBucketFactor([])).toBe(1.5); + }); + + it('returns default factor when ratio is not valid expansion (<=1)', () => { + const buckets = [10, 5]; // Descending + expect(calculateBucketFactor(buckets)).toBe(1.5); + }); + + it('returns default factor when ratio contains zero', () => { + const buckets = [0, 5]; + expect(calculateBucketFactor(buckets)).toBe(1.5); + }); + + it('returns default factor when ratio is infinite', () => { + const buckets = [5, Infinity]; + expect(calculateBucketFactor(buckets)).toBe(1.5); + }); + + it('accepts custom default factor', () => { + expect(calculateBucketFactor([5], 3)).toBe(3); + }); + }); + + describe('rowsToCellsHeatmap with linear scale', () => { + it('converts prometheus-style le labels to numeric buckets with linear scale', () => { + const frame = toDataFrame({ + fields: [ + { name: 'time', type: FieldType.time, values: [1000, 2000] }, + { + name: '1', + type: FieldType.number, + labels: { le: '1' }, + values: [10, 15], + }, + { + name: '10', + type: FieldType.number, + labels: { le: '10' }, + values: [20, 25], + }, + { + name: '100', + type: FieldType.number, + labels: { le: '100' }, + values: [30, 35], + }, + ], + }); + + const heatmap = rowsToCellsHeatmap({ + frame, + yBucketScale: { type: ScaleDistribution.Linear }, + }); + + expect(heatmap.fields[1].name).toBe('yMin'); + expect(heatmap.fields[1].values).toEqual([1, 10, 100, 1, 10, 100]); + }); + + it('converts ge labels to numeric buckets with linear scale', () => { + const frame = toDataFrame({ + fields: [ + { name: 'time', type: FieldType.time, values: [1000, 2000] }, + { + name: '1', + type: FieldType.number, + labels: { ge: '1' }, + values: [10, 15], + }, + { + name: '10', + type: FieldType.number, + labels: { ge: '10' }, + values: [20, 25], + }, + ], + }); + + const heatmap = rowsToCellsHeatmap({ + frame, + yBucketScale: { type: ScaleDistribution.Linear }, + layout: HeatmapCellLayout.ge, + }); + + expect(heatmap.fields[1].values).toEqual([1, 10, 1, 10]); + expect(heatmap.fields[1].name).toBe('yMin'); // ge layout + }); + + it('generates yMax field for linear scale', () => { + const frame = toDataFrame({ + fields: [ + { name: 'time', type: FieldType.time, values: [1000] }, + { name: '1', type: FieldType.number, values: [10] }, + { name: '2', type: FieldType.number, values: [20] }, + { name: '4', type: FieldType.number, values: [30] }, + ], + }); + + const heatmap = rowsToCellsHeatmap({ + frame, + yBucketScale: { type: ScaleDistribution.Linear }, + }); + + // Should have yMin, yMax, and count fields + expect(heatmap.fields.length).toBe(4); + expect(heatmap.fields[2].name).toBe('yMax'); + expect(heatmap.fields[2].type).toBe('number'); + + // yMax should be [2, 4, 8] (shifted buckets + calculated last bucket) + // Last bucket uses factor 2 (from 2→4) to estimate 4→8 + expect(heatmap.fields[2].values).toEqual([2, 4, 8]); + }); + + it('clears yOrdinalDisplay for linear scale', () => { + const frame = toDataFrame({ + fields: [ + { name: 'time', type: FieldType.time, values: [1000] }, + { name: '1', type: FieldType.number, values: [10] }, + { name: '10', type: FieldType.number, values: [20] }, + ], + }); + + const heatmap = rowsToCellsHeatmap({ + frame, + yBucketScale: { type: ScaleDistribution.Linear }, + }); + + expect(heatmap.meta?.custom?.yOrdinalDisplay).toBeUndefined(); + }); + + it('clears yOrdinalDisplay for log scale', () => { + const frame = toDataFrame({ + fields: [ + { name: 'time', type: FieldType.time, values: [1000] }, + { name: '1', type: FieldType.number, values: [10] }, + { name: '10', type: FieldType.number, values: [20] }, + ], + }); + + const heatmap = rowsToCellsHeatmap({ + frame, + yBucketScale: { type: ScaleDistribution.Log, log: 10 }, + }); + + expect(heatmap.meta?.custom?.yOrdinalDisplay).toBeUndefined(); + }); + + it('clears yOrdinalDisplay for symlog scale', () => { + const frame = toDataFrame({ + fields: [ + { name: 'time', type: FieldType.time, values: [1000] }, + { name: '1', type: FieldType.number, values: [10] }, + { name: '10', type: FieldType.number, values: [20] }, + ], + }); + + const heatmap = rowsToCellsHeatmap({ + frame, + yBucketScale: { type: ScaleDistribution.Symlog, log: 10, linearThreshold: 1 }, + }); + + expect(heatmap.meta?.custom?.yOrdinalDisplay).toBeUndefined(); + }); + + it('preserves yOrdinalDisplay for non-numeric scale (auto/ordinal)', () => { + const frame = toDataFrame({ + fields: [ + { name: 'time', type: FieldType.time, values: [1000] }, + { name: 'low', type: FieldType.number, values: [10] }, + { name: 'high', type: FieldType.number, values: [20] }, + ], + }); + + const heatmap = rowsToCellsHeatmap({ frame }); + + expect(heatmap.meta?.custom?.yOrdinalDisplay).toEqual(['low', 'high']); + }); + + it('sets unit to undefined for linear scale when no unit exists', () => { + const frame = toDataFrame({ + fields: [ + { name: 'time', type: FieldType.time, values: [1000] }, + { name: '1', type: FieldType.number, values: [10] }, + { name: '10', type: FieldType.number, values: [20] }, + ], + }); + + const heatmap = rowsToCellsHeatmap({ + frame, + yBucketScale: { type: ScaleDistribution.Linear }, + }); + + // No unit → expect undefined (not 'short') + expect(heatmap.fields[1].config.unit).toBeUndefined(); + }); + + it('passes through existing unit for linear scale', () => { + const frame = toDataFrame({ + fields: [ + { name: 'time', type: FieldType.time, values: [1000] }, + { name: '1', type: FieldType.number, values: [10], config: { unit: 'ms' } }, + { name: '10', type: FieldType.number, values: [20], config: { unit: 'ms' } }, + ], + }); + + const heatmap = rowsToCellsHeatmap({ + frame, + yBucketScale: { type: ScaleDistribution.Linear }, + }); + + // Existing unit → pass through unchanged + expect(heatmap.fields[1].config.unit).toBe('ms'); + }); + + it('sets unit to short for ordinal scale', () => { + const frame = toDataFrame({ + fields: [ + { name: 'time', type: FieldType.time, values: [1000] }, + { name: 'low', type: FieldType.number, values: [10] }, + { name: 'high', type: FieldType.number, values: [20] }, + ], + }); + + const heatmap = rowsToCellsHeatmap({ frame }); + + expect(heatmap.fields[1].config.unit).toBe('short'); + }); + + it('uses "count" as value field name for linear scale', () => { + const frame = toDataFrame({ + fields: [ + { name: 'time', type: FieldType.time, values: [1000] }, + { name: '1', type: FieldType.number, values: [10] }, + { name: '10', type: FieldType.number, values: [20] }, + ], + }); + + const heatmap = rowsToCellsHeatmap({ + frame, + yBucketScale: { type: ScaleDistribution.Linear }, + }); + + // Without yMax, should be 3 fields: xMax, y/yMin/yMax, yMax, count + const valueField = heatmap.fields.find((f) => f.name === 'count'); + expect(valueField).toBeDefined(); + }); + + it('uses "Value" as field name for ordinal scale', () => { + const frame = toDataFrame({ + fields: [ + { name: 'time', type: FieldType.time, values: [1000] }, + { name: 'low', type: FieldType.number, values: [10] }, + { name: 'high', type: FieldType.number, values: [20] }, + ], + }); + + const heatmap = rowsToCellsHeatmap({ frame }); + + const valueField = heatmap.fields.find((f) => f.name === 'Value'); + expect(valueField).toBeDefined(); + }); + + it('respects custom value field name for linear scale', () => { + const frame = toDataFrame({ + fields: [ + { name: 'time', type: FieldType.time, values: [1000] }, + { name: '1', type: FieldType.number, values: [10] }, + { name: '10', type: FieldType.number, values: [20] }, + ], + }); + + const heatmap = rowsToCellsHeatmap({ + frame, + yBucketScale: { type: ScaleDistribution.Linear }, + value: 'Temperature', + }); + + const valueField = heatmap.fields.find((f) => f.name === 'Temperature'); + expect(valueField).toBeDefined(); + }); + + it('calculates yMax upper bound using bucket factor', () => { + const frame = toDataFrame({ + fields: [ + { name: 'time', type: FieldType.time, values: [1000] }, + { name: '1', type: FieldType.number, values: [10] }, + { name: '10', type: FieldType.number, values: [20] }, + { name: '100', type: FieldType.number, values: [30] }, + ], + }); + + const heatmap = rowsToCellsHeatmap({ + frame, + yBucketScale: { type: ScaleDistribution.Linear }, + }); + + // buckets: [1, 10, 100] + // yMax: [10, 100, 1000] - last one calculated as 100 * 10 + const yMaxField = heatmap.fields.find((f) => f.name === 'yMax'); + expect(yMaxField?.values).toEqual([10, 100, 1000]); + }); + }); }); diff --git a/public/app/features/transformers/calculateHeatmap/heatmap.ts b/public/app/features/transformers/calculateHeatmap/heatmap.ts index 49a54bb43f1..43640708eae 100644 --- a/public/app/features/transformers/calculateHeatmap/heatmap.ts +++ b/public/app/features/transformers/calculateHeatmap/heatmap.ts @@ -19,6 +19,7 @@ import { isLikelyAscendingVector } from '@grafana/data/internal'; import { t } from '@grafana/i18n'; import { ScaleDistribution, + ScaleDistributionConfig, HeatmapCellLayout, HeatmapCalculationMode, HeatmapCalculationOptions, @@ -72,13 +73,36 @@ function parseNumeric(v?: string | null) { return v === '+Inf' ? Infinity : v === '-Inf' ? -Infinity : +(v ?? 0); } +/** + * Calculate the expansion factor from adjacent bucket values. + * This is used to estimate the size/bound of the next bucket based on the spacing of existing buckets. + * + * @param bucketValues - Array of bucket boundary values + * @param defaultFactor - Factor to use if ratio cannot be determined (default: 1.5 for 50% expansion) + * @returns The calculated or default expansion factor + */ +export function calculateBucketFactor(bucketValues: number[], defaultFactor = 1.5): number { + if (bucketValues.length >= 2) { + const last = bucketValues.at(-1)!; + const prev = bucketValues.at(-2)!; + const ratio = last / prev; + + // Only use ratio if it represents expansion (>1) and is valid + if (ratio > 1 && Number.isFinite(ratio)) { + return ratio; + } + } + + return defaultFactor; +} + export function sortAscStrInf(aName?: string | null, bName?: string | null) { return parseNumeric(aName) - parseNumeric(bName); } export interface HeatmapRowsCustomMeta { /** This provides the lookup values */ - yOrdinalDisplay: string[]; + yOrdinalDisplay?: string[]; yOrdinalLabel?: string[]; yMatchWithLabel?: string; yMinDisplay?: string; @@ -115,6 +139,7 @@ export interface RowsHeatmapOptions { unit?: string; decimals?: number; layout?: HeatmapCellLayout; + yBucketScale?: ScaleDistributionConfig; } /** Given existing buckets, create a values style frame */ @@ -129,10 +154,19 @@ export function rowsToCellsHeatmap(opts: RowsHeatmapOptions): DataFrame { throw new Error(t('heatmap.error.no-y-fields', 'No numeric fields found for heatmap')); } + // Determine if we should use numeric scaling based on yBucketScale option + // Default to 'auto' behavior (ordinal) if not specified + const scaleType = opts.yBucketScale?.type; + const useNumericScale = + scaleType === ScaleDistribution.Linear || + scaleType === ScaleDistribution.Log || + scaleType === ScaleDistribution.Symlog; + // similar to initBins() below const len = xValues.length * yFields.length; const xs = new Array(len); const ys = new Array(len); + const ys2 = useNumericScale ? new Array(len) : undefined; const counts2 = new Array(len); const counts = yFields.map((field) => field.values.slice()); @@ -144,21 +178,8 @@ export function rowsToCellsHeatmap(opts: RowsHeatmapOptions): DataFrame { } }); - const bucketBounds = Array.from({ length: yFields.length }, (v, i) => i); - - // fill flat/repeating array - for (let i = 0, yi = 0, xi = 0; i < len; yi = ++i % bucketBounds.length) { - ys[i] = bucketBounds[yi]; - - if (yi === 0 && i >= bucketBounds.length) { - xi++; - } - - xs[i] = xValues[xi]; - } - // this name determines whether cells are drawn above, below, or centered on the values - let ordinalFieldName = yFields[0].labels?.le != null ? 'yMax' : 'y'; + let ordinalFieldName = yFields[0].labels?.le != null ? 'yMax' : yFields[0].labels?.ge != null ? 'yMin' : 'y'; switch (opts.layout) { case HeatmapCellLayout.le: ordinalFieldName = 'yMax'; @@ -175,6 +196,45 @@ export function rowsToCellsHeatmap(opts: RowsHeatmapOptions): DataFrame { yOrdinalDisplay: yFields.map((f) => getFieldDisplayName(f, opts.frame)), yMatchWithLabel: Object.keys(yFields[0].labels ?? {})[0], }; + + let bucketBounds: number[]; + let bucketBoundsMax: number[] | undefined; + + if (useNumericScale) { + // Numeric mode: use numeric bucket values + bucketBounds = yFields.map((field) => { + const labelKey = custom.yMatchWithLabel; + const labelValue = labelKey ? field.labels?.[labelKey] : undefined; + const valueStr = labelValue ?? field.name; + return Number(valueStr); + }); + + // Generate upper bounds: shift values + calculate last bucket + bucketBoundsMax = bucketBounds.slice(); + bucketBoundsMax.shift(); + const factor = calculateBucketFactor(bucketBounds); + bucketBoundsMax.push(bucketBounds[bucketBounds.length - 1] * factor); + + custom.yMatchWithLabel = undefined; + } else { + // Auto mode: use ordinal indices like the original main branch behavior + bucketBounds = Array.from({ length: yFields.length }, (v, i) => i); + } + + // fill flat/repeating array + for (let i = 0, yi = 0, xi = 0; i < len; yi = ++i % bucketBounds.length) { + ys[i] = bucketBounds[yi]; + if (useNumericScale && ys2 && bucketBoundsMax) { + ys2[i] = bucketBoundsMax[yi]; + } + + if (yi === 0 && i >= bucketBounds.length) { + xi++; + } + + xs[i] = xValues[xi]; + } + if (custom.yMatchWithLabel) { custom.yOrdinalLabel = yFields.map((f) => f.labels?.[custom.yMatchWithLabel!] ?? ''); if (custom.yMatchWithLabel === 'le') { @@ -189,7 +249,7 @@ export function rowsToCellsHeatmap(opts: RowsHeatmapOptions): DataFrame { if (custom.yMinDisplay) { custom.yMinDisplay = formattedValueToString(fmt(0, opts.decimals)); } - custom.yOrdinalDisplay = custom.yOrdinalDisplay.map((name) => { + custom.yOrdinalDisplay = custom.yOrdinalDisplay?.map((name) => { let num = +name; if (!Number.isNaN(num)) { @@ -200,6 +260,11 @@ export function rowsToCellsHeatmap(opts: RowsHeatmapOptions): DataFrame { }); } + // Clear yOrdinalDisplay when using numeric scales (linear, log, symlog) + if (useNumericScale) { + custom.yOrdinalDisplay = undefined; + } + const valueCfg = { ...yFields[0].config, }; @@ -208,6 +273,43 @@ export function rowsToCellsHeatmap(opts: RowsHeatmapOptions): DataFrame { delete valueCfg.displayNameFromDS; } + // Build fields array - only include yMax in linear scale mode + const fields: Field[] = [ + { + name: xField.type === FieldType.time ? 'xMax' : 'x', + type: xField.type, + values: xs, + config: xField.config, + }, + { + name: useNumericScale ? 'yMin' : ordinalFieldName, + type: FieldType.number, + values: ys, + config: { + unit: useNumericScale ? yFields[0]?.config?.unit : 'short', // preserve original unit for numeric, use 'short' for ordinal + }, + }, + ]; + + // yMax provides explicit upper bounds for proper rendering, critical for ge layout + if (useNumericScale && ys2) { + fields.push({ + name: 'yMax', + type: FieldType.number, + values: ys2, + config: {}, + }); + } + + // Add value/count field + fields.push({ + name: opts.value?.length ? opts.value : useNumericScale ? 'count' : 'Value', + type: FieldType.number, + values: counts2, + config: valueCfg, + display: yFields[0].display, + }); + return { length: xs.length, refId: opts.frame.refId, @@ -215,29 +317,7 @@ export function rowsToCellsHeatmap(opts: RowsHeatmapOptions): DataFrame { type: DataFrameType.HeatmapCells, custom, }, - fields: [ - { - name: xField.type === FieldType.time ? 'xMax' : 'x', - type: xField.type, - values: xs, - config: xField.config, - }, - { - name: ordinalFieldName, - type: FieldType.number, - values: ys, - config: { - unit: 'short', // ordinal lookup - }, - }, - { - name: opts.value?.length ? opts.value : 'Value', - type: FieldType.number, - values: counts2, - config: valueCfg, - display: yFields[0].display, - }, - ], + fields, }; } diff --git a/public/app/plugins/panel/heatmap/HeatmapPanel.tsx b/public/app/plugins/panel/heatmap/HeatmapPanel.tsx index 4d2762a9be3..64efef6c5aa 100644 --- a/public/app/plugins/panel/heatmap/HeatmapPanel.tsx +++ b/public/app/plugins/panel/heatmap/HeatmapPanel.tsx @@ -5,7 +5,6 @@ import { DashboardCursorSync, PanelProps, TimeRange } from '@grafana/data'; import { PanelDataErrorView } from '@grafana/runtime'; import { ScaleDistributionConfig } from '@grafana/schema'; import { - ScaleDistribution, TooltipPlugin2, TooltipDisplayMode, UPlotChart, @@ -29,7 +28,7 @@ import { HeatmapTooltip } from './HeatmapTooltip'; import { HeatmapData, prepareHeatmapData } from './fields'; import { quantizeScheme } from './palettes'; import { Options } from './types'; -import { prepConfig } from './utils'; +import { calculateYSizeDivisor, prepConfig } from './utils'; interface HeatmapPanelProps extends PanelProps {} @@ -141,6 +140,16 @@ const HeatmapPanelViz = ({ const builder = useMemo(() => { const scaleConfig: ScaleDistributionConfig = dataRef.current?.heatmap?.fields[1].config?.custom?.scaleDistribution; + const activeScaleConfig = options.rowsFrame?.yBucketScale ?? scaleConfig; + + // For log/symlog scales: use 1 for pre-bucketed data with explicit scale, otherwise use split value + const hasExplicitScale = options.rowsFrame?.yBucketScale !== undefined; + const ySizeDivisor = calculateYSizeDivisor( + activeScaleConfig?.type, + hasExplicitScale, + options.calculation?.yBuckets?.value + ); + return prepConfig({ dataRef, theme, @@ -151,9 +160,10 @@ const HeatmapPanelViz = ({ hideGE: options.filterValues?.ge, exemplarColor: options.exemplars?.color ?? 'rgba(255,0,255,0.7)', yAxisConfig: options.yAxis, - ySizeDivisor: scaleConfig?.type === ScaleDistribution.Log ? +(options.calculation?.yBuckets?.value || 1) : 1, + ySizeDivisor, selectionMode: options.selectionMode, xAxisConfig: getXAxisConfig(annotationsLength), + rowsFrame: options.rowsFrame, }); // eslint-disable-next-line react-hooks/exhaustive-deps diff --git a/public/app/plugins/panel/heatmap/HeatmapTooltip.tsx b/public/app/plugins/panel/heatmap/HeatmapTooltip.tsx index 480d36a5a6a..269243f1ad8 100644 --- a/public/app/plugins/panel/heatmap/HeatmapTooltip.tsx +++ b/public/app/plugins/panel/heatmap/HeatmapTooltip.tsx @@ -4,7 +4,6 @@ import uPlot from 'uplot'; import { ActionModel, - DataFrameType, Field, FieldType, formattedValueToString, @@ -26,7 +25,7 @@ import { } from '@grafana/ui/internal'; import { ColorScale } from 'app/core/components/ColorScale/ColorScale'; import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv'; -import { isHeatmapCellsDense, readHeatmapRowsCustomMeta } from 'app/features/transformers/calculateHeatmap/heatmap'; +import { readHeatmapRowsCustomMeta } from 'app/features/transformers/calculateHeatmap/heatmap'; import { getDisplayValuesAndLinks } from 'app/features/visualization/data-hover/DataHoverView'; import { ExemplarTooltip } from 'app/features/visualization/data-hover/ExemplarTooltip'; @@ -35,7 +34,13 @@ import { isTooltipScrollable } from '../timeseries/utils'; import { HeatmapData } from './fields'; import { renderHistogram } from './renderHistogram'; -import { formatMilliseconds, getFieldFromData, getHoverCellColor, getSparseCellMinMax } from './tooltip/utils'; +import { + formatMilliseconds, + getFieldFromData, + getHoverCellColor, + getSparseCellMinMax, + isHeatmapSparse, +} from './tooltip/utils'; interface HeatmapTooltipProps { mode: TooltipDisplayMode; @@ -99,9 +104,7 @@ const HeatmapHoverCell = ({ const index = dataIdxs[1]!; const data = dataRef.current; - const [isSparse] = useState( - () => data.heatmap?.meta?.type === DataFrameType.HeatmapCells && !isHeatmapCellsDense(data.heatmap) - ); + const [isSparse] = useState(() => isHeatmapSparse(data.heatmap)); const xField = getFieldFromData(data.heatmap!, 'x', isSparse)!; const yField = getFieldFromData(data.heatmap!, 'y', isSparse)!; diff --git a/public/app/plugins/panel/heatmap/YBucketScaleEditor.test.tsx b/public/app/plugins/panel/heatmap/YBucketScaleEditor.test.tsx new file mode 100644 index 00000000000..6c0ff4b804c --- /dev/null +++ b/public/app/plugins/panel/heatmap/YBucketScaleEditor.test.tsx @@ -0,0 +1,277 @@ +import { render, screen, within } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import selectEvent from 'react-select-event'; + +import { StandardEditorContext, StandardEditorsRegistryItem } from '@grafana/data'; +import { ScaleDistribution, ScaleDistributionConfig } from '@grafana/schema'; + +import { YBucketScaleEditor } from './YBucketScaleEditor'; + +const mockContext: StandardEditorContext = { + data: [], +}; + +const mockItem: StandardEditorsRegistryItem = { + id: 'yBucketScale', + name: 'Y Bucket Scale', + editor: YBucketScaleEditor, +}; + +describe('YBucketScaleEditor', () => { + describe('Scale selection', () => { + it('should render with Auto selected when value is undefined', () => { + const onChange = jest.fn(); + render(); + + const autoButton = screen.getByRole('radio', { name: /auto/i }); + expect(autoButton).toBeChecked(); + }); + + it('should render with Linear selected when value is Linear', () => { + const onChange = jest.fn(); + render( + + ); + + const linearButton = screen.getByRole('radio', { name: /linear/i }); + expect(linearButton).toBeChecked(); + }); + + it('should call onChange with undefined when Auto is selected', async () => { + const onChange = jest.fn(); + render( + + ); + + const autoButton = screen.getByRole('radio', { name: /auto/i }); + await userEvent.click(autoButton); + + expect(onChange).toHaveBeenCalledWith(undefined); + }); + + it('should call onChange with Linear config when Linear is selected', async () => { + const onChange = jest.fn(); + render(); + + const linearButton = screen.getByRole('radio', { name: /linear/i }); + await userEvent.click(linearButton); + + expect(onChange).toHaveBeenCalledWith({ type: ScaleDistribution.Linear }); + }); + + it('should call onChange with Log config when Log is selected', async () => { + const onChange = jest.fn(); + render(); + + const logButton = screen.getByRole('radio', { name: /^log$/i }); + await userEvent.click(logButton); + + expect(onChange).toHaveBeenCalledWith({ type: ScaleDistribution.Log, log: 2 }); + }); + + it('should call onChange with Symlog config when Symlog is selected', async () => { + const onChange = jest.fn(); + render(); + + const symlogButton = screen.getByRole('radio', { name: /symlog/i }); + await userEvent.click(symlogButton); + + expect(onChange).toHaveBeenCalledWith({ type: ScaleDistribution.Symlog, log: 2, linearThreshold: 1 }); + }); + }); + + describe('Log base selection', () => { + it('should show log base selector for Log scale', () => { + const onChange = jest.fn(); + render( + + ); + + expect(screen.getByText('Log base')).toBeInTheDocument(); + }); + + it('should show log base selector for Symlog scale', () => { + const onChange = jest.fn(); + render( + + ); + + expect(screen.getByText('Log base')).toBeInTheDocument(); + }); + + it('should not show log base selector for Linear scale', () => { + const onChange = jest.fn(); + render( + + ); + + expect(screen.queryByText('Log base')).not.toBeInTheDocument(); + }); + + it('should not show log base selector for Auto', () => { + const onChange = jest.fn(); + render(); + + expect(screen.queryByText('Log base')).not.toBeInTheDocument(); + }); + + it('should preserve existing log base when switching to Log', async () => { + const onChange = jest.fn(); + render( + + ); + + const logButton = screen.getByRole('radio', { name: /^log$/i }); + await userEvent.click(logButton); + + expect(onChange).toHaveBeenCalledWith({ type: ScaleDistribution.Log, log: 10 }); + }); + + it('should update log base when changed for Log scale', async () => { + const onChange = jest.fn(); + render( + + ); + + // Find the log base field container and query the combobox within it + const logBaseLabel = screen.getByText('Log base'); + const fieldContainer = logBaseLabel.closest('div[style]') as HTMLElement; // The div with style="margin-top: 8px;" + const selectEl = within(fieldContainer).getByRole('combobox'); + + await selectEvent.select(selectEl, '10', { container: document.body }); + + expect(onChange).toHaveBeenCalledWith({ type: ScaleDistribution.Log, log: 10 }); + }); + + it('should update log base when changed for Symlog scale', async () => { + const onChange = jest.fn(); + render( + + ); + + // Find the log base field container and query the combobox within it + const logBaseLabel = screen.getByText('Log base'); + const fieldContainer = logBaseLabel.closest('div[style]') as HTMLElement; // The div with style="margin-top: 8px;" + const selectEl = within(fieldContainer).getByRole('combobox'); + + await selectEvent.select(selectEl, '10', { container: document.body }); + + expect(onChange).toHaveBeenCalledWith({ type: ScaleDistribution.Symlog, log: 10, linearThreshold: 1 }); + }); + }); + + describe('Linear threshold', () => { + it('should show linear threshold input for Symlog scale', () => { + const onChange = jest.fn(); + render( + + ); + + expect(screen.getByText('Linear threshold')).toBeInTheDocument(); + }); + + it('should not show linear threshold input for Log scale', () => { + const onChange = jest.fn(); + render( + + ); + + expect(screen.queryByText('Linear threshold')).not.toBeInTheDocument(); + }); + + it('should not update linear threshold for a 0 value', async () => { + const onChange = jest.fn(); + const origValue = { type: ScaleDistribution.Symlog, log: 10, linearThreshold: 1 }; + + render(); + + const input = screen.getByPlaceholderText('1'); + + await userEvent.clear(input); + await userEvent.type(input, '0'); + expect(onChange).not.toHaveBeenCalled(); + + await userEvent.type(input, '.'); + expect(onChange).not.toHaveBeenCalled(); + + await userEvent.type(input, '5'); + expect(onChange).toHaveBeenCalledWith({ ...origValue, linearThreshold: 0.5 }); + }); + + it('should update linear threshold for valid non-zero values', async () => { + const onChange = jest.fn(); + const origValue = { type: ScaleDistribution.Symlog, log: 2, linearThreshold: 1 }; + + render(); + + const input = screen.getByPlaceholderText('1'); + + await userEvent.clear(input); + await userEvent.type(input, '5'); + expect(onChange).toHaveBeenCalledWith({ ...origValue, linearThreshold: 5 }); + }); + + it('should not dispatch onChange for invalid input', async () => { + const onChange = jest.fn(); + const origValue = { type: ScaleDistribution.Symlog, log: 2, linearThreshold: 1 }; + + render(); + + const input = screen.getByPlaceholderText('1'); + + await userEvent.clear(input); + await userEvent.type(input, 'abc'); + expect(onChange).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/public/app/plugins/panel/heatmap/YBucketScaleEditor.tsx b/public/app/plugins/panel/heatmap/YBucketScaleEditor.tsx new file mode 100644 index 00000000000..fc96101f9bd --- /dev/null +++ b/public/app/plugins/panel/heatmap/YBucketScaleEditor.tsx @@ -0,0 +1,135 @@ +import { useState } from 'react'; + +import { SelectableValue, StandardEditorProps } from '@grafana/data'; +import { t } from '@grafana/i18n'; +import { ScaleDistribution, ScaleDistributionConfig } from '@grafana/schema'; +import { RadioButtonGroup, Field, Select, Input } from '@grafana/ui'; + +type ScaleOptionValue = 'auto' | ScaleDistribution; + +/** + * Simplified scale editor that shows all options in a single line. + * Includes "Auto" option which returns undefined to use default behavior. + */ +export const YBucketScaleEditor = (props: StandardEditorProps) => { + const { value, onChange } = props; + + const type = value?.type; + const log = value?.log ?? 2; + const isAuto = value === undefined; + + const [localLinearThreshold, setLocalLinearThreshold] = useState( + value?.linearThreshold != null ? String(value.linearThreshold) : '' + ); + + const currentOption: ScaleOptionValue = isAuto ? 'auto' : type!; + const showLogBase = type === ScaleDistribution.Log || type === ScaleDistribution.Symlog; + const showLinearThreshold = type === ScaleDistribution.Symlog; + + const SCALE_OPTIONS: Array> = [ + { + label: t('heatmap.y-bucket-scale-editor.scale-options.label-auto', 'Auto'), + value: 'auto', + }, + { + label: t('heatmap.y-bucket-scale-editor.scale-options.label-linear', 'Linear'), + value: ScaleDistribution.Linear, + }, + { + label: t('heatmap.y-bucket-scale-editor.scale-options.label-log', 'Log'), + value: ScaleDistribution.Log, + }, + { + label: t('heatmap.y-bucket-scale-editor.scale-options.label-symlog', 'Symlog'), + value: ScaleDistribution.Symlog, + }, + ]; + + const LOG_BASE_OPTIONS: Array> = [ + { + label: '2', + value: 2, + }, + { + label: '10', + value: 10, + }, + ]; + + const handleScaleChange = (v: ScaleOptionValue) => { + if (v === 'auto') { + onChange(undefined); + return; + } + + if (v === ScaleDistribution.Linear) { + onChange({ type: ScaleDistribution.Linear }); + return; + } + + if (v === ScaleDistribution.Log) { + onChange({ type: ScaleDistribution.Log, log }); + return; + } + + if (v === ScaleDistribution.Symlog) { + onChange({ + type: ScaleDistribution.Symlog, + log, + linearThreshold: value?.linearThreshold ?? 1, + }); + return; + } + }; + + const handleLogBaseChange = (newLog: number) => { + onChange({ + ...value!, + log: newLog, + }); + }; + + const handleLinearThresholdChange = (newValue: string) => { + setLocalLinearThreshold(newValue); + const numValue = parseFloat(newValue); + if (!isNaN(numValue) && numValue !== 0) { + onChange({ + ...value!, + linearThreshold: numValue, + }); + } + }; + + return ( + <> + + {showLogBase && ( + + handleLinearThresholdChange(e.currentTarget.value)} + placeholder={t('heatmap.y-bucket-scale-editor.linear-threshold-placeholder', '1')} + /> + + )} + + ); +}; diff --git a/public/app/plugins/panel/heatmap/module.tsx b/public/app/plugins/panel/heatmap/module.tsx index a7d44726886..99bb66e2c3c 100644 --- a/public/app/plugins/panel/heatmap/module.tsx +++ b/public/app/plugins/panel/heatmap/module.tsx @@ -1,4 +1,11 @@ -import { DataFrame, FieldConfigProperty, FieldType, identityOverrideProcessor, PanelPlugin } from '@grafana/data'; +import { + DataFrame, + DataFrameType, + FieldConfigProperty, + FieldType, + identityOverrideProcessor, + PanelPlugin, +} from '@grafana/data'; import { t } from '@grafana/i18n'; import { config } from '@grafana/runtime'; import { @@ -15,6 +22,7 @@ import { addHeatmapCalculationOptions } from 'app/features/transformers/calculat import { readHeatmapRowsCustomMeta } from 'app/features/transformers/calculateHeatmap/heatmap'; import { HeatmapPanel } from './HeatmapPanel'; +import { YBucketScaleEditor } from './YBucketScaleEditor'; import { prepareHeatmapData } from './fields'; import { heatmapChangedHandler, heatmapMigrationHandler } from './migrations'; import { colorSchemes, quantizeScheme } from './palettes'; @@ -59,6 +67,7 @@ export const plugin = new PanelPlugin(HeatmapPanel) const opts = context.options ?? defaultOptions; let isOrdinalY = false; + const isHeatmapCells = context.data.some((frame) => frame.meta?.type === DataFrameType.HeatmapCells); if (context.data.length > 0) { try { @@ -94,6 +103,17 @@ export const plugin = new PanelPlugin(HeatmapPanel) addHeatmapCalculationOptions('calculation.', builder, opts.calculation, category); } + if (!opts.calculate && !isHeatmapCells && config.featureToggles.heatmapRowsAxisOptions) { + builder.addCustomEditor({ + id: 'rowsFrame-yBucketScale', + path: 'rowsFrame.yBucketScale', + name: t('heatmap.name-y-bucket-scale', 'Y bucket scale'), + category, + editor: YBucketScaleEditor, + defaultValue: undefined, + }); + } + category = [t('heatmap.category-y-axis', 'Y Axis')]; builder @@ -170,7 +190,9 @@ export const plugin = new PanelPlugin(HeatmapPanel) category, }); - if (!opts.calculate) { + // Hide tick alignment for explicit scales - bucket boundaries are fixed by numeric labels + const hasExplicitScale = context.options?.rowsFrame?.yBucketScale !== undefined; + if (!opts.calculate && !hasExplicitScale) { builder.addRadio({ path: 'rowsFrame.layout', name: t('heatmap.name-tick-alignment', 'Tick alignment'), diff --git a/public/app/plugins/panel/heatmap/panelcfg.cue b/public/app/plugins/panel/heatmap/panelcfg.cue index e947aaf9bb6..e07717429d7 100644 --- a/public/app/plugins/panel/heatmap/panelcfg.cue +++ b/public/app/plugins/panel/heatmap/panelcfg.cue @@ -105,6 +105,8 @@ composableKinds: PanelCfg: lineage: { value?: string // Controls tick alignment when not calculating from data layout?: ui.HeatmapCellLayout + // Controls the scale distribution of the y-axis buckets + yBucketScale?: ui.ScaleDistributionConfig } @cuetsy(kind="interface") Options: { annotations?: ui.VizAnnotations diff --git a/public/app/plugins/panel/heatmap/panelcfg.gen.ts b/public/app/plugins/panel/heatmap/panelcfg.gen.ts index ef3ab0c9459..d5b822eb1ab 100644 --- a/public/app/plugins/panel/heatmap/panelcfg.gen.ts +++ b/public/app/plugins/panel/heatmap/panelcfg.gen.ts @@ -183,6 +183,10 @@ export interface RowsHeatmapOptions { * Sets the name of the cell when not calculating from data */ value?: string; + /** + * Controls the scale distribution of the y-axis buckets + */ + yBucketScale?: ui.ScaleDistributionConfig; } export interface Options { diff --git a/public/app/plugins/panel/heatmap/tooltip/utils.test.ts b/public/app/plugins/panel/heatmap/tooltip/utils.test.ts new file mode 100644 index 00000000000..a5da298d75c --- /dev/null +++ b/public/app/plugins/panel/heatmap/tooltip/utils.test.ts @@ -0,0 +1,47 @@ +import { DataFrameType, toDataFrame } from '@grafana/data'; + +import { isHeatmapSparse } from './utils'; + +describe('isHeatmapSparse', () => { + it('should return false when heatmap is undefined', () => { + expect(isHeatmapSparse(undefined)).toBe(false); + }); + + it('should return false for dense HeatmapCells (single Y field)', () => { + const heatmap = toDataFrame({ + fields: [{ name: 'y', values: [] }], + meta: { type: DataFrameType.HeatmapCells }, + }); + + expect(isHeatmapSparse(heatmap)).toBe(false); + }); + + it('should return true for sparse HeatmapCells (yMin and yMax fields)', () => { + const heatmap = toDataFrame({ + fields: [ + { name: 'yMin', values: [] }, + { name: 'yMax', values: [] }, + ], + meta: { type: DataFrameType.HeatmapCells }, + }); + + expect(isHeatmapSparse(heatmap)).toBe(true); + }); + + it('should return false for non-HeatmapCells data frames', () => { + const heatmap = toDataFrame({ + fields: [{ name: 'Value', values: [] }], + meta: { type: DataFrameType.HeatmapRows }, + }); + + expect(isHeatmapSparse(heatmap)).toBe(false); + }); + + it('should return false when meta is undefined', () => { + const heatmap = toDataFrame({ + fields: [{ name: 'value', values: [] }], + }); + + expect(isHeatmapSparse(heatmap)).toBe(false); + }); +}); diff --git a/public/app/plugins/panel/heatmap/tooltip/utils.ts b/public/app/plugins/panel/heatmap/tooltip/utils.ts index 58114245d4e..067bf7d2b71 100644 --- a/public/app/plugins/panel/heatmap/tooltip/utils.ts +++ b/public/app/plugins/panel/heatmap/tooltip/utils.ts @@ -1,4 +1,5 @@ -import { DataFrame, Field } from '@grafana/data'; +import { DataFrame, DataFrameType, Field } from '@grafana/data'; +import { isHeatmapCellsDense } from 'app/features/transformers/calculateHeatmap/heatmap'; import { HeatmapData } from '../fields'; @@ -91,3 +92,14 @@ export const getSparseCellMinMax = (data: HeatmapData, index: number): BucketsMi yBucketMax: yMax.values[index], }; }; + +/** + * Determines if a heatmap DataFrame is sparse (has explicit yMin/yMax bounds). + * Sparse heatmaps have HeatmapCells type and are not dense. + */ +export function isHeatmapSparse(heatmap: DataFrame | undefined): boolean { + if (!heatmap) { + return false; + } + return heatmap.meta?.type === DataFrameType.HeatmapCells && !isHeatmapCellsDense(heatmap); +} diff --git a/public/app/plugins/panel/heatmap/utils.test.ts b/public/app/plugins/panel/heatmap/utils.test.ts index 26fecaad53b..abb10812469 100644 --- a/public/app/plugins/panel/heatmap/utils.test.ts +++ b/public/app/plugins/panel/heatmap/utils.test.ts @@ -1,5 +1,374 @@ -describe('a test', () => { - it('has to have at least one test', () => { - expect(true).toBeTruthy(); +import { ScaleDistribution } from '@grafana/schema'; + +import { applyExplicitMinMax, boundedMinMax, calculateYSizeDivisor, toLogBase, valuesToFills } from './utils'; + +describe('toLogBase', () => { + it('returns 10 when value is 10', () => { + expect(toLogBase(10)).toBe(10); + }); + + it('returns 2 when value is 2', () => { + expect(toLogBase(2)).toBe(2); + }); + + it('returns 2 (default) when value is undefined', () => { + expect(toLogBase(undefined)).toBe(2); + }); + + it('returns 2 (default) for invalid values', () => { + expect(toLogBase(5)).toBe(2); + expect(toLogBase(0)).toBe(2); + expect(toLogBase(-1)).toBe(2); + expect(toLogBase(100)).toBe(2); + }); +}); + +describe('applyExplicitMinMax', () => { + it('returns original values when no explicit values provided', () => { + const [min, max] = applyExplicitMinMax(0, 100, undefined, undefined); + expect(min).toBe(0); + expect(max).toBe(100); + }); + + it('applies explicit min only', () => { + const [min, max] = applyExplicitMinMax(0, 100, 10, undefined); + expect(min).toBe(10); + expect(max).toBe(100); + }); + + it('applies explicit max only', () => { + const [min, max] = applyExplicitMinMax(0, 100, undefined, 90); + expect(min).toBe(0); + expect(max).toBe(90); + }); + + it('applies both explicit min and max', () => { + const [min, max] = applyExplicitMinMax(0, 100, 20, 80); + expect(min).toBe(20); + expect(max).toBe(80); + }); + + it('handles negative values', () => { + const [min, max] = applyExplicitMinMax(-50, 50, -10, 10); + expect(min).toBe(-10); + expect(max).toBe(10); + }); + + it('handles explicit min = 0', () => { + const [min, max] = applyExplicitMinMax(10, 100, 0, undefined); + expect(min).toBe(0); + expect(max).toBe(100); + }); + + it('handles explicit max = 0', () => { + const [min, max] = applyExplicitMinMax(-100, -10, undefined, 0); + expect(min).toBe(-100); + expect(max).toBe(0); + }); + + it('handles null scaleMin', () => { + const [min, max] = applyExplicitMinMax(null, 100, 10, undefined); + expect(min).toBe(10); + expect(max).toBe(100); + }); + + it('handles null scaleMax', () => { + const [min, max] = applyExplicitMinMax(0, null, undefined, 90); + expect(min).toBe(0); + expect(max).toBe(90); + }); + + it('preserves null when no explicit value provided', () => { + const [min, max] = applyExplicitMinMax(null, null, undefined, undefined); + expect(min).toBe(null); + expect(max).toBe(null); + }); +}); + +describe('calculateYSizeDivisor', () => { + it('returns 1 for linear scale', () => { + expect(calculateYSizeDivisor(ScaleDistribution.Linear, false, 2)).toBe(1); + }); + + it('returns 1 for log scale with explicit scale', () => { + expect(calculateYSizeDivisor(ScaleDistribution.Log, true, 2)).toBe(1); + }); + + it('returns 1 for symlog scale with explicit scale', () => { + expect(calculateYSizeDivisor(ScaleDistribution.Symlog, true, 2)).toBe(1); + }); + + it('returns split value for log scale without explicit scale', () => { + expect(calculateYSizeDivisor(ScaleDistribution.Log, false, 2)).toBe(2); + expect(calculateYSizeDivisor(ScaleDistribution.Log, false, 4)).toBe(4); + }); + + it('returns split value for symlog scale without explicit scale', () => { + expect(calculateYSizeDivisor(ScaleDistribution.Symlog, false, 2)).toBe(2); + expect(calculateYSizeDivisor(ScaleDistribution.Symlog, false, 3)).toBe(3); + }); + + it('handles string split values', () => { + expect(calculateYSizeDivisor(ScaleDistribution.Log, false, '2')).toBe(2); + expect(calculateYSizeDivisor(ScaleDistribution.Log, false, '4')).toBe(4); + }); + + it('returns 1 when split value is undefined', () => { + expect(calculateYSizeDivisor(ScaleDistribution.Log, false, undefined)).toBe(1); + }); + + it('returns 1 when scale type is undefined', () => { + expect(calculateYSizeDivisor(undefined, false, 2)).toBe(1); + }); + + it('returns 1 for ordinal scale', () => { + expect(calculateYSizeDivisor(ScaleDistribution.Ordinal, false, 2)).toBe(1); + }); +}); + +describe('boundedMinMax', () => { + describe('when min and max are not provided', () => { + it('calculates min and max from values', () => { + const values = [10, 20, 5, 30, 15]; + const [min, max] = boundedMinMax(values); + expect(min).toBe(5); + expect(max).toBe(30); + }); + + it('handles single value', () => { + const values = [42]; + const [min, max] = boundedMinMax(values); + expect(min).toBe(42); + expect(max).toBe(42); + }); + + it('handles negative values', () => { + const values = [-10, -20, -5, -30]; + const [min, max] = boundedMinMax(values); + expect(min).toBe(-30); + expect(max).toBe(-5); + }); + + it('handles mixed positive and negative values', () => { + const values = [-10, 20, -5, 30]; + const [min, max] = boundedMinMax(values); + expect(min).toBe(-10); + expect(max).toBe(30); + }); + + it('returns Infinity/-Infinity for empty array', () => { + const values: number[] = []; + const [min, max] = boundedMinMax(values); + expect(min).toBe(Infinity); + expect(max).toBe(-Infinity); + }); + }); + + describe('when min is provided', () => { + it('uses provided min value', () => { + const values = [10, 20, 5, 30]; + const [min, max] = boundedMinMax(values, 0); + expect(min).toBe(0); + expect(max).toBe(30); + }); + + it('uses provided min even if higher than data min', () => { + const values = [10, 20, 5, 30]; + const [min, max] = boundedMinMax(values, 15); + expect(min).toBe(15); + expect(max).toBe(30); + }); + }); + + describe('when max is provided', () => { + it('uses provided max value', () => { + const values = [10, 20, 5, 30]; + const [min, max] = boundedMinMax(values, undefined, 50); + expect(min).toBe(5); + expect(max).toBe(50); + }); + + it('uses provided max even if lower than data max', () => { + const values = [10, 20, 5, 30]; + const [min, max] = boundedMinMax(values, undefined, 25); + expect(min).toBe(5); + expect(max).toBe(25); + }); + }); + + describe('when both min and max are provided', () => { + it('uses both provided values', () => { + const values = [10, 20, 5, 30]; + const [min, max] = boundedMinMax(values, 0, 50); + expect(min).toBe(0); + expect(max).toBe(50); + }); + }); + + describe('with hideLE filter', () => { + it('excludes values less than or equal to hideLE', () => { + const values = [5, 10, 15, 20, 25]; + const [min, max] = boundedMinMax(values, undefined, undefined, 10); + expect(min).toBe(15); + expect(max).toBe(25); + }); + + it('excludes all values when hideLE is higher than all values', () => { + const values = [5, 10, 15]; + const [min, max] = boundedMinMax(values, undefined, undefined, 20); + expect(min).toBe(Infinity); + expect(max).toBe(-Infinity); + }); + }); + + describe('with hideGE filter', () => { + it('excludes values greater than or equal to hideGE', () => { + const values = [5, 10, 15, 20, 25]; + const [min, max] = boundedMinMax(values, undefined, undefined, -Infinity, 20); + expect(min).toBe(5); + expect(max).toBe(15); + }); + + it('excludes all values when hideGE is lower than all values', () => { + const values = [15, 20, 25]; + const [min, max] = boundedMinMax(values, undefined, undefined, -Infinity, 10); + expect(min).toBe(Infinity); + expect(max).toBe(-Infinity); + }); + }); + + describe('with both hideLE and hideGE filters', () => { + it('excludes values outside the range', () => { + const values = [5, 10, 15, 20, 25, 30]; + const [min, max] = boundedMinMax(values, undefined, undefined, 10, 25); + expect(min).toBe(15); + expect(max).toBe(20); + }); + + it('works with provided min/max bounds', () => { + const values = [5, 10, 15, 20, 25, 30]; + const [min, max] = boundedMinMax(values, 0, 50, 10, 25); + expect(min).toBe(0); + expect(max).toBe(50); + }); + }); +}); + +describe('valuesToFills', () => { + // Fake color palette for testing index mapping + const palette5 = ['c0', 'c1', 'c2', 'c3', 'c4']; + + describe('basic mapping', () => { + it('maps values to palette indices', () => { + const values = [0, 25, 50, 75, 100]; + const fills = valuesToFills(values, palette5, 0, 100); + + expect(fills).toEqual([0, 1, 2, 3, 4]); + }); + + it('maps min value to first palette index', () => { + const values = [10]; + const fills = valuesToFills(values, palette5, 10, 20); + + expect(fills[0]).toBe(0); + }); + + it('maps max value to last palette index', () => { + const values = [20]; + const fills = valuesToFills(values, palette5, 10, 20); + + expect(fills[0]).toBe(4); + }); + + it('maps mid-range values proportionally', () => { + const values = [15]; + const fills = valuesToFills(values, palette5, 10, 20); + + // 15 is middle of 10-20, should map to index 2 (middle color) + expect(fills[0]).toBe(2); + }); + }); + + describe('edge cases', () => { + it('clamps values below min to first index', () => { + const values = [5, 8, 10]; + const fills = valuesToFills(values, palette5, 10, 20); + + expect(fills[0]).toBe(0); // 5 < 10 + expect(fills[1]).toBe(0); // 8 < 10 + }); + + it('clamps values above max to last index', () => { + const values = [20, 25, 30]; + const fills = valuesToFills(values, palette5, 10, 20); + + expect(fills[0]).toBe(4); // 20 = max + expect(fills[1]).toBe(4); // 25 > max + expect(fills[2]).toBe(4); // 30 > max + }); + + it('handles zero range (min equals max)', () => { + const values = [10, 10, 10]; + const fills = valuesToFills(values, palette5, 10, 10); + + // When range is 0, defaults to 1, so all values map to 0 + expect(fills).toEqual([0, 0, 0]); + }); + + it('handles single color palette', () => { + const values = [0, 50, 100]; + const palette = ['c0']; + const fills = valuesToFills(values, palette, 0, 100); + + expect(fills).toEqual([0, 0, 0]); + }); + + it('handles large palette', () => { + const values = [50]; + const palette = Array.from({ length: 256 }, (_, i) => `c${i}`); + const fills = valuesToFills(values, palette, 0, 100); + + // 50 is 50% of 0-100, should map to 128 (middle of 256) + expect(fills[0]).toBe(128); + }); + }); + + describe('negative values', () => { + it('handles negative min and max', () => { + const values = [-10, -5, 0]; + const palette = ['c0', 'c1', 'c2']; + const fills = valuesToFills(values, palette, -10, 0); + + expect(fills[0]).toBe(0); // -10 is min + expect(fills[1]).toBe(1); // -5 is middle + expect(fills[2]).toBe(2); // 0 is max + }); + + it('handles range crossing zero', () => { + const values = [-10, 0, 10]; + const palette = ['c0', 'c1', 'c2']; + const fills = valuesToFills(values, palette, -10, 10); + + expect(fills[0]).toBe(0); // -10 is min + expect(fills[1]).toBe(1); // 0 is middle + expect(fills[2]).toBe(2); // 10 is max + }); + }); + + describe('preserves array length', () => { + it('returns array with same length as input', () => { + const values = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + const palette = ['c0', 'c1']; + const fills = valuesToFills(values, palette, 1, 10); + + expect(fills.length).toBe(values.length); + }); + + it('handles empty array', () => { + const values: number[] = []; + const fills = valuesToFills(values, palette5, 0, 100); + + expect(fills).toEqual([]); + }); }); }); diff --git a/public/app/plugins/panel/heatmap/utils.ts b/public/app/plugins/panel/heatmap/utils.ts index a51c86f0d1c..6ae058f6261 100644 --- a/public/app/plugins/panel/heatmap/utils.ts +++ b/public/app/plugins/panel/heatmap/utils.ts @@ -14,13 +14,22 @@ import { } from '@grafana/data'; import { AxisPlacement, ScaleDirection, ScaleDistribution, ScaleOrientation, HeatmapCellLayout } from '@grafana/schema'; import { UPlotConfigBuilder, UPlotConfigPrepFn } from '@grafana/ui'; -import { isHeatmapCellsDense, readHeatmapRowsCustomMeta } from 'app/features/transformers/calculateHeatmap/heatmap'; +import { + calculateBucketFactor, + isHeatmapCellsDense, + readHeatmapRowsCustomMeta, +} from 'app/features/transformers/calculateHeatmap/heatmap'; import { pointWithin, Quadtree, Rect } from '../barchart/quadtree'; import { HeatmapData } from './fields'; import { FieldConfig, HeatmapSelectionMode, YAxisConfig } from './types'; +/** Validates and returns a safe log base (2 or 10), defaults to 2 if invalid */ +export function toLogBase(value: number | undefined): 2 | 10 { + return value === 10 ? 10 : 2; +} + interface PathbuilderOpts { each: (u: uPlot, seriesIdx: number, dataIdx: number, lft: number, top: number, wid: number, hgt: number) => void; gap?: number | null; @@ -54,6 +63,7 @@ interface PrepConfigOpts { ySizeDivisor?: number; selectionMode?: HeatmapSelectionMode; xAxisConfig?: Parameters[0]['xAxisConfig']; + rowsFrame?: { yBucketScale?: { type: ScaleDistribution; log?: number; linearThreshold?: number } }; } export function prepConfig(opts: PrepConfigOpts) { @@ -69,8 +79,11 @@ export function prepConfig(opts: PrepConfigOpts) { ySizeDivisor, selectionMode = HeatmapSelectionMode.X, xAxisConfig, + rowsFrame, } = opts; + const yBucketScale = rowsFrame?.yBucketScale; + const xScaleKey = 'x'; let isTime = true; @@ -196,7 +209,20 @@ export function prepConfig(opts: PrepConfigOpts) { const yScale = yFieldConfig?.scaleDistribution ?? { type: ScaleDistribution.Linear }; const yAxisReverse = Boolean(yAxisConfig.reverse); const isSparseHeatmap = heatmapType === DataFrameType.HeatmapCells && !isHeatmapCellsDense(dataRef.current?.heatmap!); - const shouldUseLogScale = yScale.type !== ScaleDistribution.Linear || isSparseHeatmap; + + const scaleDistribution = (() => { + if (yBucketScale) { + return yBucketScale.type; + } + if (yScale.type !== ScaleDistribution.Linear || isSparseHeatmap) { + return ScaleDistribution.Log; + } + return ScaleDistribution.Linear; + })(); + + const scaleLog = toLogBase(yBucketScale?.log ?? yScale.log); + const scaleLinearThreshold = yBucketScale?.linearThreshold; + const isOrdinalY = readHeatmapRowsCustomMeta(dataRef.current?.heatmap).yOrdinalDisplay != null; // random to prevent syncing y in other heatmaps @@ -210,8 +236,9 @@ export function prepConfig(opts: PrepConfigOpts) { orientation: ScaleOrientation.Vertical, direction: yAxisReverse ? ScaleDirection.Down : ScaleDirection.Up, // should be tweakable manually - distribution: shouldUseLogScale ? ScaleDistribution.Log : ScaleDistribution.Linear, - log: yScale.log ?? 2, + distribution: scaleDistribution, + log: scaleLog, + linearThreshold: scaleLinearThreshold, range: // sparse already accounts for le/ge by explicit yMin & yMax cell bounds, so no need to expand y range isSparseHeatmap @@ -224,16 +251,16 @@ export function prepConfig(opts: PrepConfigOpts) { let scaleMin: number | null, scaleMax: number | null; - [scaleMin, scaleMax] = shouldUseLogScale - ? uPlot.rangeLog(dataMin, dataMax, (yScale.log ?? 2) as unknown as uPlot.Scale.LogBase, true) - : [dataMin, dataMax]; + const isLogScale = + scaleDistribution === ScaleDistribution.Log || scaleDistribution === ScaleDistribution.Symlog; + [scaleMin, scaleMax] = isLogScale ? uPlot.rangeLog(dataMin, dataMax, scaleLog, true) : [dataMin, dataMax]; - if (shouldUseLogScale && !isOrdinalY) { + let { min: explicitMin, max: explicitMax } = yAxisConfig; + + if (isLogScale && !isOrdinalY) { let yExp = u.scales[yScaleKey].log!; let log = yExp === 2 ? Math.log2 : Math.log10; - let { min: explicitMin, max: explicitMax } = yAxisConfig; - // guard against <= 0 if (explicitMin != null && explicitMin > 0) { // snap to magnitude @@ -245,6 +272,9 @@ export function prepConfig(opts: PrepConfigOpts) { let maxLog = log(explicitMax); scaleMax = yExp ** incrRoundUp(maxLog, 1); } + } else if (!isOrdinalY) { + // Apply explicit min/max for linear scale + [scaleMin, scaleMax] = applyExplicitMinMax(scaleMin, scaleMax, explicitMin, explicitMax); } return [scaleMin, scaleMax]; @@ -257,7 +287,7 @@ export function prepConfig(opts: PrepConfigOpts) { let { min: explicitMin, max: explicitMax } = yAxisConfig; // logarithmic expansion - if (shouldUseLogScale) { + if (scaleDistribution === ScaleDistribution.Log || scaleDistribution === ScaleDistribution.Symlog) { let yExp = u.scales[yScaleKey].log!; let minExpanded = false; @@ -280,17 +310,31 @@ export function prepConfig(opts: PrepConfigOpts) { } } + // For pre-bucketed data with explicit scale, calculate expansion factor from actual bucket spacing + // For calculated heatmaps, use the full log base + let expansionFactor: number = yExp; + + if (yBucketScale !== undefined) { + // Try to infer the bucket factor from the actual data spacing + const yValues = u.data[1]?.[1]; + if (Array.isArray(yValues) && yValues.length >= 2 && typeof yValues[0] === 'number') { + expansionFactor = calculateBucketFactor(yValues, yExp); + } + } + if (dataRef.current?.yLayout === HeatmapCellLayout.le) { if (!minExpanded) { - scaleMin /= yExp; + scaleMin /= expansionFactor; } } else if (dataRef.current?.yLayout === HeatmapCellLayout.ge) { if (!maxExpanded) { - scaleMax *= yExp; + scaleMax *= expansionFactor; } } else { - scaleMin /= yExp / 2; - scaleMax *= yExp / 2; + // Unknown layout - expand both directions + const factor = Math.sqrt(expansionFactor); // Use sqrt for balanced expansion + scaleMin /= factor; + scaleMax *= factor; } if (!isOrdinalY) { @@ -383,7 +427,7 @@ export function prepConfig(opts: PrepConfigOpts) { return splits.map((v) => v < 0 ? (meta.yMinDisplay ?? '') // Check prometheus style labels - : (meta.yOrdinalDisplay[v] ?? '') + : (meta.yOrdinalDisplay?.[v] ?? '') ); } return splits; @@ -585,15 +629,19 @@ export function heatmapPathsDense(opts: PathbuilderOpts) { let ySize: number; if (scaleX.distr === 3) { - xSize = Math.abs(valToPosX(xs[0] * scaleX.log!, scaleX, xDim, xOff) - valToPosX(xs[0], scaleX, xDim, xOff)); + // For log scales, calculate cell size from actual adjacent bucket positions + const nextXValue = xs[yBinQty] ?? xs[0] * scaleX.log!; + xSize = Math.abs(valToPosX(nextXValue, scaleX, xDim, xOff) - valToPosX(xs[0], scaleX, xDim, xOff)); } else { xSize = Math.abs(valToPosX(xBinIncr, scaleX, xDim, xOff) - valToPosX(0, scaleX, xDim, xOff)); } if (scaleY.distr === 3) { - ySize = - Math.abs(valToPosY(ys[0] * scaleY.log!, scaleY, yDim, yOff) - valToPosY(ys[0], scaleY, yDim, yOff)) / - ySizeDivisor; + // Use actual data spacing for pre-bucketed data, or full magnitude for calculated heatmaps with splits + const nextYValue = ySizeDivisor === 1 ? (ys[1] ?? ys[0] * scaleY.log!) : ys[0] * scaleY.log!; + + const baseYSize = Math.abs(valToPosY(nextYValue, scaleY, yDim, yOff) - valToPosY(ys[0], scaleY, yDim, yOff)); + ySize = baseYSize / ySizeDivisor; } else { ySize = Math.abs(valToPosY(yBinIncr, scaleY, yDim, yOff) - valToPosY(0, scaleY, yDim, yOff)) / ySizeDivisor; } @@ -882,3 +930,30 @@ export const valuesToFills = (values: number[], palette: string[], minValue: num return indexedFills; }; + +/** + * Calculates the Y-axis size divisor for heatmap cell rendering. + * For log/symlog scales with calculated data (no explicit scale), divides cells by the split value. + * Otherwise returns 1 (no division). + */ +export function calculateYSizeDivisor( + scaleType: ScaleDistribution | undefined, + hasExplicitScale: boolean, + splitValue: number | string | undefined +): number { + const isLogScale = scaleType === ScaleDistribution.Log || scaleType === ScaleDistribution.Symlog; + return isLogScale && !hasExplicitScale ? +(splitValue || 1) : 1; +} + +/** + * Applies explicit min/max values to scale range for linear scales. + * Returns the original values if explicitMin/explicitMax are undefined. + */ +export function applyExplicitMinMax( + scaleMin: number | null, + scaleMax: number | null, + explicitMin: number | undefined, + explicitMax: number | undefined +): [number | null, number | null] { + return [explicitMin ?? scaleMin, explicitMax ?? scaleMax]; +} diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index 6949fa04bb3..0de5ac009db 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -9361,6 +9361,7 @@ "name-unit": "Unit", "name-value-name": "Value name", "name-y-axis-scale": "Y axis scale", + "name-y-bucket-scale": "Y bucket scale", "placeholder-axis-label": "Auto", "placeholder-axis-width": "Auto", "placeholder-decimals": "Auto", @@ -9390,6 +9391,18 @@ "label-all": "All", "label-hidden": "Hidden", "label-single": "Single" + }, + "y-bucket-scale-editor": { + "linear-threshold-description": "Range within which the scale is linear", + "linear-threshold-label": "Linear threshold", + "linear-threshold-placeholder": "1", + "log-base-label": "Log base", + "scale-options": { + "label-auto": "Auto", + "label-linear": "Linear", + "label-log": "Log", + "label-symlog": "Symlog" + } } }, "help-modal": { From 0ec716a433c3f4e1304e2ec7e111eaf068936b33 Mon Sep 17 00:00:00 2001 From: Nathan Marrs Date: Thu, 18 Dec 2025 15:01:16 -0800 Subject: [PATCH 34/36] Embedded Dashboard Panels: Add Grafana Branding (#115198) * feat: add Grafana logo to embedded panels - Add Grafana logo watermark to solo panel view (embedded panels) - Logo appears in top-right corner with subtle background container - Logo hides on hover to avoid interfering with panel content - Uses React state to track hover for reliable behavior across nested elements * minor formatting * update changes to match public dashboards styling * match styles of public dashboards * feat: add responsive Grafana branding to embedded panels - Add 'Powered by Grafana' branding with text logo to solo panel view - Implement responsive scaling based on panel dimensions (0.6x to 1.0x) - Logo and text scale proportionally with panel size - Branding hides on hover to avoid interfering with panel content - Matches public dashboard branding pattern for consistency - Uses ResizeObserver for efficient responsive updates * feat: add Grafana branding to embedded solo panels - Add 'Powered by Grafana' branding with text logo to embedded panels - Create SoloPanelPageLogo component for reusable branding - Implement responsive scaling based on panel dimensions - Add hover-to-hide functionality to avoid content overlap - Logo scales between 0.6x and 1.0x based on panel size * refactor: move scale calculation into SoloPanelPageLogo component - Move responsive scale calculation logic from SoloPanelRenderer to SoloPanelPageLogo - Logo component now manages its own scaling based on container dimensions - Improves separation of concerns and component encapsulation * feat: add hideLogo query parameter to disable embedded panel branding - Add hideLogo query parameter support to SoloPanelPage - Logo can be hidden via ?hideLogo, ?hideLogo=true, or ?hideLogo=1 - Useful for customers who want to disable branding and for image rendering scenarios - Update Props interface to include hideLogo in queryParams type * feat: hide logo in panel image renderer URLs - Add hideLogo=true parameter to image renderer URLs in ShareLinkTab - Ensures logo is hidden when generating panel images through share feature - Update test to expect hideLogo=true in render URL * feat: hide logo in old dashboard sharing panel image URLs - Add hideLogo=true parameter to buildImageUrl in ShareModal utils - Ensures logo is hidden when generating panel images through old share modal - Update all ShareLink tests to expect hideLogo=true in render URLs * test: add comprehensive tests for SoloPanelPage and SoloPanelPageLogo - Add SoloPanelPageLogo tests covering rendering, hover behavior, theme selection, and scaling - Add SoloPanelPage tests covering logo visibility based on hideLogo prop - Test logo hiding functionality (most important behavior) - Test responsive scaling based on container dimensions - Test ResizeObserver integration - All 14 tests passing * refactor: centralize hideLogo handling in SoloPanelPageLogo Move hideLogo parsing and decision-making into SoloPanelPageLogo so SoloPanelPage/SoloPanelRenderer only pass through the raw query param value. * chore: clean up solo logo test and share link params Remove a duplicate SVG mock in SoloPanelPageLogo.test, and simplify ShareLinkTab image URL building without changing behavior. * chore: revert ShareLinkTab image query refactor Restore the previous image URL query-param mutation logic in ShareLinkTab to reduce risk. * chore: set hideLogo once for ShareLinkTab image URLs Avoid passing hideLogo twice when building the rendered image URL. * fix: handle boolean hideLogo query param in SoloPanelPageLogo Handle query params that are represented as booleans (e.g., ?hideLogo) and arrays, and avoid calling trim() on non-strings. * fix i18n * fix(dashboard-scene): address SoloPanelPageLogo review feedback Avoid double-scaling logo margin, clarify scaling comments, and extend tests for null/array values and ResizeObserver cleanup. * update margin left on logo to better match text spacing --- .../sharing/ShareLinkTab.test.tsx | 2 +- .../dashboard-scene/sharing/ShareLinkTab.tsx | 3 + .../solo/SoloPanelPage.test.tsx | 152 +++++++++ .../dashboard-scene/solo/SoloPanelPage.tsx | 67 ++-- .../solo/SoloPanelPageLogo.test.tsx | 291 ++++++++++++++++++ .../solo/SoloPanelPageLogo.tsx | 159 ++++++++++ .../components/ShareModal/ShareLink.test.tsx | 10 +- .../dashboard/components/ShareModal/utils.ts | 1 + public/locales/en-US/grafana.json | 3 + 9 files changed, 663 insertions(+), 25 deletions(-) create mode 100644 public/app/features/dashboard-scene/solo/SoloPanelPage.test.tsx create mode 100644 public/app/features/dashboard-scene/solo/SoloPanelPageLogo.test.tsx create mode 100644 public/app/features/dashboard-scene/solo/SoloPanelPageLogo.tsx diff --git a/public/app/features/dashboard-scene/sharing/ShareLinkTab.test.tsx b/public/app/features/dashboard-scene/sharing/ShareLinkTab.test.tsx index d50de744de1..ffdf1edd5dc 100644 --- a/public/app/features/dashboard-scene/sharing/ShareLinkTab.test.tsx +++ b/public/app/features/dashboard-scene/sharing/ShareLinkTab.test.tsx @@ -89,7 +89,7 @@ describe('ShareLinkTab', () => { await screen.findByRole('link', { name: selectors.pages.SharePanelModal.linkToRenderedImage }) ).toHaveAttribute( 'href', - 'http://dashboards.grafana.com/grafana/render/d-solo/dash-1?from=2019-02-11T13:00:00.000Z&to=2019-02-11T19:00:00.000Z&panelId=A$panel-12&__feature.dashboardSceneSolo=true&width=1000&height=500&tz=Pacific%2FEaster' + 'http://dashboards.grafana.com/grafana/render/d-solo/dash-1?from=2019-02-11T13:00:00.000Z&to=2019-02-11T19:00:00.000Z&panelId=A$panel-12&__feature.dashboardSceneSolo=true&hideLogo=true&width=1000&height=500&tz=Pacific%2FEaster' ); }); }); diff --git a/public/app/features/dashboard-scene/sharing/ShareLinkTab.tsx b/public/app/features/dashboard-scene/sharing/ShareLinkTab.tsx index 9d2daa5812b..cd70f0e2e83 100644 --- a/public/app/features/dashboard-scene/sharing/ShareLinkTab.tsx +++ b/public/app/features/dashboard-scene/sharing/ShareLinkTab.tsx @@ -81,6 +81,9 @@ export class ShareLinkTab extends SceneObjectBase implements imageQueryParams['__feature.dashboardSceneSolo'] = true; } + // hide Grafana logo in the rendered image + urlParamsUpdate.hideLogo = 'true'; + const imageUrl = getDashboardUrl({ uid: dashboard.state.uid, currentQueryParams: window.location.search, diff --git a/public/app/features/dashboard-scene/solo/SoloPanelPage.test.tsx b/public/app/features/dashboard-scene/solo/SoloPanelPage.test.tsx new file mode 100644 index 00000000000..a570131e6f9 --- /dev/null +++ b/public/app/features/dashboard-scene/solo/SoloPanelPage.test.tsx @@ -0,0 +1,152 @@ +import { render, screen } from '@testing-library/react'; +import { useParams } from 'react-router-dom-v5-compat'; + +import { SceneTimeRange, VizPanel } from '@grafana/scenes'; + +import { getDashboardScenePageStateManager } from '../pages/DashboardScenePageStateManager'; +import { DashboardScene } from '../scene/DashboardScene'; +import { DefaultGridLayoutManager } from '../scene/layout-default/DefaultGridLayoutManager'; + +import { SoloPanelRenderer } from './SoloPanelPage'; + +// Mock dependencies +jest.mock('react-router-dom-v5-compat', () => ({ + useParams: jest.fn(), +})); + +jest.mock('../pages/DashboardScenePageStateManager', () => ({ + getDashboardScenePageStateManager: jest.fn(), +})); + +jest.mock('../scene/SoloPanelContext', () => ({ + SoloPanelContextProvider: ({ children }: { children: React.ReactNode }) =>
{children}
, + useDefineSoloPanelContext: jest.fn(() => ({})), +})); + +jest.mock('./SoloPanelPageLogo', () => ({ + shouldHideSoloPanelLogo: (hideLogo?: unknown) => { + if (hideLogo === undefined) { + return false; + } + if (hideLogo === true) { + return true; + } + if (hideLogo === false) { + return false; + } + if (Array.isArray(hideLogo)) { + hideLogo = hideLogo[0] ?? ''; + } + const normalized = String(hideLogo).trim().toLowerCase(); + return normalized !== 'false' && normalized !== '0'; + }, + SoloPanelPageLogo: ({ isHovered, hideLogo }: { isHovered: boolean; hideLogo?: unknown }) => { + if (hideLogo === true) { + return null; + } + if (hideLogo === false) { + return ( +
+ Logo +
+ ); + } + if (Array.isArray(hideLogo)) { + hideLogo = hideLogo[0] ?? ''; + } + if (hideLogo !== undefined) { + const normalized = String(hideLogo).trim().toLowerCase(); + if (normalized !== 'false' && normalized !== '0') { + return null; + } + } + return ( +
+ Logo +
+ ); + }, +})); + +describe('SoloPanelPage', () => { + const mockStateManager = { + useState: jest.fn(() => ({ + dashboard: null, + loadError: null, + })), + loadDashboard: jest.fn(), + clearState: jest.fn(), + }; + + beforeEach(() => { + jest.clearAllMocks(); + (getDashboardScenePageStateManager as jest.Mock).mockReturnValue(mockStateManager); + (useParams as jest.Mock).mockReturnValue({ uid: 'test-uid', type: undefined, slug: undefined }); + }); + + describe('SoloPanelRenderer', () => { + const createMockDashboard = () => { + const panel = new VizPanel({ + title: 'Test Panel', + pluginId: 'table', + key: 'panel-1', + }); + + const dashboard = new DashboardScene({ + title: 'Test Dashboard', + uid: 'test-dash', + $timeRange: new SceneTimeRange({}), + body: DefaultGridLayoutManager.fromVizPanels([panel]), + }); + + // Mock the activate method + dashboard.activate = jest.fn(() => jest.fn()); + + // Mock useState to return the dashboard state object with required properties + dashboard.useState = jest.fn(() => ({ + controls: { + useState: jest.fn(() => ({ + refreshPicker: { + activate: jest.fn(() => jest.fn()), + }, + })), + }, + body: { + Component: () =>
Panel Content
, + }, + })) as unknown as typeof dashboard.useState; + + return dashboard; + }; + + it('should render the panel', () => { + const dashboard = createMockDashboard(); + render(); + + // The panel should be rendered (we can't easily test the actual panel content without more setup) + expect(screen.getByTestId('solo-panel-logo')).toBeInTheDocument(); + }); + + it('should render logo when hideLogo is false', () => { + const dashboard = createMockDashboard(); + render(); + + expect(screen.getByTestId('solo-panel-logo')).toBeInTheDocument(); + }); + + it('should not render logo when hideLogo is true', () => { + const dashboard = createMockDashboard(); + render(); + + expect(screen.queryByTestId('solo-panel-logo')).not.toBeInTheDocument(); + }); + + it('should initialize with isHovered as false', () => { + const dashboard = createMockDashboard(); + render(); + + const logo = screen.getByTestId('solo-panel-logo'); + expect(logo).toHaveAttribute('data-hovered', 'false'); + }); + }); +}); diff --git a/public/app/features/dashboard-scene/solo/SoloPanelPage.tsx b/public/app/features/dashboard-scene/solo/SoloPanelPage.tsx index 306776911de..65b7fbb443d 100644 --- a/public/app/features/dashboard-scene/solo/SoloPanelPage.tsx +++ b/public/app/features/dashboard-scene/solo/SoloPanelPage.tsx @@ -1,9 +1,9 @@ // Libraries import { css } from '@emotion/css'; -import { useEffect } from 'react'; +import { useEffect, useRef, useState } from 'react'; import { useParams } from 'react-router-dom-v5-compat'; -import { GrafanaTheme2 } from '@grafana/data'; +import { GrafanaTheme2, UrlQueryValue } from '@grafana/data'; import { t } from '@grafana/i18n'; import { UrlSyncContextProvider } from '@grafana/scenes'; import { Alert, Box, useStyles2 } from '@grafana/ui'; @@ -17,7 +17,10 @@ import { getDashboardScenePageStateManager } from '../pages/DashboardScenePageSt import { DashboardScene } from '../scene/DashboardScene'; import { SoloPanelContextProvider, useDefineSoloPanelContext } from '../scene/SoloPanelContext'; -export interface Props extends GrafanaRouteComponentProps {} +import { SoloPanelPageLogo } from './SoloPanelPageLogo'; + +export interface Props + extends GrafanaRouteComponentProps {} /** * Used for iframe embedding and image rendering of single panels @@ -52,18 +55,28 @@ export function SoloPanelPage({ queryParams }: Props) { return ( - + ); } export default SoloPanelPage; -export function SoloPanelRenderer({ dashboard, panelId }: { dashboard: DashboardScene; panelId: string }) { +export function SoloPanelRenderer({ + dashboard, + panelId, + hideLogo, +}: { + dashboard: DashboardScene; + panelId: string; + hideLogo?: UrlQueryValue; +}) { const { controls, body } = dashboard.useState(); const refreshPicker = controls?.useState()?.refreshPicker; const styles = useStyles2(getStyles); const soloPanelContext = useDefineSoloPanelContext(panelId)!; + const [isHovered, setIsHovered] = useState(false); + const containerRef = useRef(null); useEffect(() => { const dashDeactivate = dashboard.activate(); @@ -76,11 +89,19 @@ export function SoloPanelRenderer({ dashboard, panelId }: { dashboard: Dashboard }, [dashboard, refreshPicker]); return ( -
+
setIsHovered(true)} + onMouseLeave={() => setIsHovered(false)} + > + {renderHiddenVariables(dashboard)} - - - +
+ + + +
); } @@ -107,15 +128,23 @@ function renderHiddenVariables(dashboard: DashboardScene) { ); } -const getStyles = (theme: GrafanaTheme2) => ({ - container: css({ - position: 'fixed', - bottom: 0, - right: 0, - margin: 0, - left: 0, - top: 0, +const getStyles = (theme: GrafanaTheme2) => { + const panelWrapper = css({ width: '100%', height: '100%', - }), -}); + }); + + return { + container: css({ + position: 'fixed', + bottom: 0, + right: 0, + margin: 0, + left: 0, + top: 0, + width: '100%', + height: '100%', + }), + panelWrapper, + }; +}; diff --git a/public/app/features/dashboard-scene/solo/SoloPanelPageLogo.test.tsx b/public/app/features/dashboard-scene/solo/SoloPanelPageLogo.test.tsx new file mode 100644 index 00000000000..bbfe0c5bcdd --- /dev/null +++ b/public/app/features/dashboard-scene/solo/SoloPanelPageLogo.test.tsx @@ -0,0 +1,291 @@ +import { render, screen } from '@testing-library/react'; +import { createRef } from 'react'; + +import { GrafanaTheme2 } from '@grafana/data'; + +import { shouldHideSoloPanelLogo, SoloPanelPageLogo } from './SoloPanelPageLogo'; + +// Mock the theme hook +const mockUseTheme2 = jest.fn(); +const mockUseStyles2 = jest.fn((fn) => fn({} as GrafanaTheme2)); + +jest.mock('@grafana/ui', () => ({ + ...jest.requireActual('@grafana/ui'), + useTheme2: () => mockUseTheme2(), + useStyles2: (fn: (theme: GrafanaTheme2) => Record) => mockUseStyles2(fn), +})); + +// Mock the logo images for dark and light modes +jest.mock('img/grafana_text_logo_dark.svg', () => 'grafana-text-logo-dark.svg'); +jest.mock('img/grafana_text_logo_light.svg', () => 'grafana-text-logo-light.svg'); + +// Mock ResizeObserver +global.ResizeObserver = jest.fn().mockImplementation((callback) => { + return { + observe: jest.fn(), + unobserve: jest.fn(), + disconnect: jest.fn(), + // Helper to trigger resize + trigger: (width: number, height: number) => { + callback([{ contentRect: { width, height } }]); + }, + }; +}); + +// Helper function to assign a mock div to a ref +function assignMockDivToRef(ref: React.RefObject, mockDiv: HTMLDivElement) { + // Use type assertion to bypass readonly restriction in tests + (ref as { current: HTMLDivElement | null }).current = mockDiv; +} + +describe('SoloPanelPageLogo', () => { + describe('shouldHideSoloPanelLogo', () => { + it('treats null as false', () => { + expect(shouldHideSoloPanelLogo(null)).toBe(false); + }); + + it('treats presence (empty string) as true', () => { + expect(shouldHideSoloPanelLogo('')).toBe(true); + }); + + it('treats true/1 as true', () => { + expect(shouldHideSoloPanelLogo('true')).toBe(true); + expect(shouldHideSoloPanelLogo('1')).toBe(true); + expect(shouldHideSoloPanelLogo(' TRUE ')).toBe(true); + }); + + it('treats false/0 as false', () => { + expect(shouldHideSoloPanelLogo('false')).toBe(false); + expect(shouldHideSoloPanelLogo('0')).toBe(false); + expect(shouldHideSoloPanelLogo(' FALSE ')).toBe(false); + }); + + it('treats boolean true as true and boolean false as false', () => { + expect(shouldHideSoloPanelLogo(true)).toBe(true); + expect(shouldHideSoloPanelLogo(false)).toBe(false); + }); + + it('treats undefined as false', () => { + expect(shouldHideSoloPanelLogo(undefined)).toBe(false); + }); + + it('handles array values (uses the first value)', () => { + expect(shouldHideSoloPanelLogo([''])).toBe(true); + expect(shouldHideSoloPanelLogo(['true'])).toBe(true); + expect(shouldHideSoloPanelLogo(['1'])).toBe(true); + expect(shouldHideSoloPanelLogo(['false'])).toBe(false); + expect(shouldHideSoloPanelLogo(['0'])).toBe(false); + expect(shouldHideSoloPanelLogo(['false', 'true'])).toBe(false); + }); + }); + + const mockTheme = { + isDark: false, + colors: { + background: { primary: '#ffffff' }, + border: { weak: '#e0e0e0' }, + text: { secondary: '#666666' }, + }, + shape: { radius: { default: '4px' } }, + shadows: { z3: '0 2px 4px rgba(0,0,0,0.1)' }, + typography: { body: { fontSize: '14px' } }, + spacing: jest.fn((n: number) => `${n * 8}px`), + transitions: { + handleMotion: jest.fn(() => ({})), + }, + } as unknown as GrafanaTheme2; + + beforeEach(() => { + jest.clearAllMocks(); + mockUseTheme2.mockReturnValue({ + ...mockTheme, + isDark: false, + }); + mockUseStyles2.mockImplementation((fn) => fn(mockTheme)); + }); + + it('should render the logo component', () => { + const containerRef = createRef(); + const mockDiv = document.createElement('div'); + mockDiv.getBoundingClientRect = jest.fn(() => ({ + width: 800, + height: 600, + top: 0, + left: 0, + bottom: 600, + right: 800, + x: 0, + y: 0, + toJSON: jest.fn(), + })); + + assignMockDivToRef(containerRef, mockDiv); + + render(); + + expect(screen.getByText('Powered by')).toBeInTheDocument(); + expect(screen.getByAltText('Grafana')).toBeInTheDocument(); + }); + + it('should hide logo when isHovered is true', () => { + const containerRef = createRef(); + const mockDiv = document.createElement('div'); + mockDiv.getBoundingClientRect = jest.fn(() => ({ + width: 800, + height: 600, + top: 0, + left: 0, + bottom: 600, + right: 800, + x: 0, + y: 0, + toJSON: jest.fn(), + })); + assignMockDivToRef(containerRef, mockDiv); + + render(); + + // The logo should still be in the DOM but with reduced opacity + const poweredByText = screen.getByText('Powered by'); + expect(poweredByText).toBeInTheDocument(); + // The logoHidden class should be applied (we can't easily test the class name without more setup) + }); + + it('should show logo when isHovered is false', () => { + const containerRef = createRef(); + const mockDiv = document.createElement('div'); + mockDiv.getBoundingClientRect = jest.fn(() => ({ + width: 800, + height: 600, + top: 0, + left: 0, + bottom: 600, + right: 800, + x: 0, + y: 0, + toJSON: jest.fn(), + })); + assignMockDivToRef(containerRef, mockDiv); + + render(); + + // The logo should be visible + expect(screen.getByText('Powered by')).toBeInTheDocument(); + expect(screen.getByAltText('Grafana')).toBeInTheDocument(); + }); + + it('should use dark logo in dark theme', () => { + const containerRef = createRef(); + const mockDiv = document.createElement('div'); + mockDiv.getBoundingClientRect = jest.fn(() => ({ + width: 800, + height: 600, + top: 0, + left: 0, + bottom: 600, + right: 800, + x: 0, + y: 0, + toJSON: jest.fn(), + })); + assignMockDivToRef(containerRef, mockDiv); + + mockUseTheme2.mockReturnValue({ + ...mockTheme, + isDark: true, + }); + + render(); + + const logo = screen.getByAltText('Grafana'); + expect(logo).toHaveAttribute('src', 'grafana-text-logo-light.svg'); + }); + + it('should use correct logo based on theme', () => { + const containerRef = createRef(); + const mockDiv = document.createElement('div'); + mockDiv.getBoundingClientRect = jest.fn(() => ({ + width: 800, + height: 600, + top: 0, + left: 0, + bottom: 600, + right: 800, + x: 0, + y: 0, + toJSON: jest.fn(), + })); + assignMockDivToRef(containerRef, mockDiv); + + // The beforeEach sets isDark: false by default, so this should work + // But the previous test might have changed it, so let's ensure it's reset + mockUseTheme2.mockClear(); + mockUseTheme2.mockReturnValue({ + ...mockTheme, + isDark: false, + }); + + render(); + + const logo = screen.getByAltText('Grafana'); + // Verify logo is rendered (the exact src depends on theme, which is tested in other tests) + expect(logo).toBeInTheDocument(); + expect(logo).toHaveAttribute('src'); + }); + + it('should apply scaling styles based on container dimensions', () => { + const containerRef = createRef(); + const mockDiv = document.createElement('div'); + mockDiv.getBoundingClientRect = jest.fn(() => ({ + width: 400, + height: 300, + top: 0, + left: 0, + bottom: 300, + right: 400, + x: 0, + y: 0, + toJSON: jest.fn(), + })); + assignMockDivToRef(containerRef, mockDiv); + + render(); + + // Find the logo container by looking for the "Powered by" text's parent + const poweredByText = screen.getByText('Powered by'); + const logoContainer = poweredByText.parentElement as HTMLElement; + expect(logoContainer).toBeInTheDocument(); + // Check that inline styles are applied (scaling should be between 0.6 and 1.0) + expect(logoContainer.style.fontSize).toBeTruthy(); + expect(logoContainer.style.top).toBeTruthy(); + expect(logoContainer.style.right).toBeTruthy(); + }); + + it('should observe container resize', () => { + const containerRef = createRef(); + const mockDiv = document.createElement('div'); + mockDiv.getBoundingClientRect = jest.fn(() => ({ + width: 800, + height: 600, + top: 0, + left: 0, + bottom: 600, + right: 800, + x: 0, + y: 0, + toJSON: jest.fn(), + })); + assignMockDivToRef(containerRef, mockDiv); + + const { unmount } = render( + + ); + + expect(ResizeObserver).toHaveBeenCalled(); + const resizeObserverInstance = (ResizeObserver as jest.Mock).mock.results[0].value; + expect(resizeObserverInstance.observe).toHaveBeenCalledWith(mockDiv); + + unmount(); + expect(resizeObserverInstance.disconnect).toHaveBeenCalled(); + }); +}); diff --git a/public/app/features/dashboard-scene/solo/SoloPanelPageLogo.tsx b/public/app/features/dashboard-scene/solo/SoloPanelPageLogo.tsx new file mode 100644 index 00000000000..43889049807 --- /dev/null +++ b/public/app/features/dashboard-scene/solo/SoloPanelPageLogo.tsx @@ -0,0 +1,159 @@ +import { css, cx } from '@emotion/css'; +import { useEffect, useState } from 'react'; + +import { GrafanaTheme2, UrlQueryValue } from '@grafana/data'; +import { Trans } from '@grafana/i18n'; +import { useStyles2, useTheme2 } from '@grafana/ui'; +import grafanaTextLogoDarkSvg from 'img/grafana_text_logo_dark.svg'; +import grafanaTextLogoLightSvg from 'img/grafana_text_logo_light.svg'; + +interface SoloPanelPageLogoProps { + containerRef: React.RefObject; + isHovered: boolean; + hideLogo?: UrlQueryValue; +} + +export function shouldHideSoloPanelLogo(hideLogo?: UrlQueryValue): boolean { + if (hideLogo === undefined || hideLogo === null) { + return false; + } + + // React-router / locationSearchToObject can represent a "present but no value" query param as boolean true. + if (hideLogo === true) { + return true; + } + + if (hideLogo === false) { + return false; + } + + const value = Array.isArray(hideLogo) ? String(hideLogo[0] ?? '') : String(hideLogo); + + // Treat presence as "true", except explicit disable values. + // Examples: + // - ?hideLogo => hide + // - ?hideLogo=true => hide + // - ?hideLogo=1 => hide + // - ?hideLogo=false => show + // - ?hideLogo=0 => show + const normalized = value.trim().toLowerCase(); + return normalized !== 'false' && normalized !== '0'; +} + +export function SoloPanelPageLogo({ containerRef, isHovered, hideLogo }: SoloPanelPageLogoProps) { + const shouldHide = shouldHideSoloPanelLogo(hideLogo); + const [scale, setScale] = useState(1); + const styles = useStyles2(getStyles); + const theme = useTheme2(); + const grafanaLogo = theme.isDark ? grafanaTextLogoLightSvg : grafanaTextLogoDarkSvg; + + // Calculate responsive scale based on panel dimensions + useEffect(() => { + const updateScale = () => { + if (!containerRef.current) { + return; + } + + const { width, height } = containerRef.current.getBoundingClientRect(); + // Use the smaller dimension to ensure it scales appropriately for both wide and tall panels + const minDimension = Math.min(width, height); + + // Base scale calculation: scales from 0.6 (for small panels ~200px) up to 1.0 when the smaller dimension is ~800px + // Clamp to a maximum of 1.0 for larger panels + const baseScale = Math.max(0.6, Math.min(1.0, 0.6 + (minDimension - 200) / 600)); + + // Also consider width specifically for very wide but short panels; reaches 1.0 when width is ~1000px + const widthScale = Math.max(0.6, Math.min(1.0, 0.6 + (width - 200) / 800)); + + // Use the average of both for balanced scaling; panels around 1000x1000px (or larger in both dimensions) reach a scale of 1.0 + const finalScale = Math.min(1.0, (baseScale + widthScale) / 2); + setScale(finalScale); + }; + + updateScale(); + + const resizeObserver = new ResizeObserver(updateScale); + if (containerRef.current) { + resizeObserver.observe(containerRef.current); + } + + return () => { + resizeObserver.disconnect(); + }; + }, [containerRef]); + + if (shouldHide) { + return null; + } + + return ( +
+ + Powered by + + Grafana +
+ ); +} + +const getStyles = (theme: GrafanaTheme2) => { + const logoContainer = css({ + position: 'absolute', + // top, right, and padding will be set via inline styles for scaling + backgroundColor: theme.colors.background.primary, + borderRadius: theme.shape.radius.default, + opacity: 0.9, + pointerEvents: 'none', + zIndex: 1000, + display: 'flex', + alignItems: 'center', + boxShadow: theme.shadows.z3, + border: `1px solid ${theme.colors.border.weak}`, + // Base font size - will be scaled via inline style + fontSize: theme.typography.body.fontSize, + lineHeight: 1.2, + [theme.transitions.handleMotion('no-preference', 'reduce')]: { + transition: 'opacity 0.2s ease-in-out', + }, + }); + + const logoHidden = css({ + opacity: 0, + }); + + const text = css({ + color: theme.colors.text.secondary, + // fontSize will be inherited from parent container's scale + lineHeight: 1.2, + display: 'block', + }); + + const logo = css({ + // height will be set via inline style (16px * scale) to scale with panel size + display: 'block', + flexShrink: 0, + }); + + return { + logoContainer, + logoHidden, + text, + logo, + }; +}; diff --git a/public/app/features/dashboard/components/ShareModal/ShareLink.test.tsx b/public/app/features/dashboard/components/ShareModal/ShareLink.test.tsx index eef8b965a98..8bc902b6d0d 100644 --- a/public/app/features/dashboard/components/ShareModal/ShareLink.test.tsx +++ b/public/app/features/dashboard/components/ShareModal/ShareLink.test.tsx @@ -106,7 +106,7 @@ describe('ShareModal', () => { render(); const base = 'http://dashboards.grafana.com/render/d-solo/abcdefghi/my-dash'; - const params = '?from=1000&to=2000&orgId=1&panelId=22&width=1000&height=500&scale=1&tz=UTC'; + const params = '?from=1000&to=2000&orgId=1&panelId=22&hideLogo=true&width=1000&height=500&scale=1&tz=UTC'; expect( await screen.findByRole('link', { name: selectors.pages.SharePanelModal.linkToRenderedImage }) ).toHaveAttribute('href', base + params); @@ -117,7 +117,7 @@ describe('ShareModal', () => { render(); const base = 'http://dashboards.grafana.com/render/dashboard-solo/script/my-dash.js'; - const params = '?from=1000&to=2000&orgId=1&panelId=22&width=1000&height=500&scale=1&tz=UTC'; + const params = '?from=1000&to=2000&orgId=1&panelId=22&hideLogo=true&width=1000&height=500&scale=1&tz=UTC'; expect( await screen.findByRole('link', { name: selectors.pages.SharePanelModal.linkToRenderedImage }) ).toHaveAttribute('href', base + params); @@ -154,7 +154,7 @@ describe('ShareModal', () => { await screen.findByRole('link', { name: selectors.pages.SharePanelModal.linkToRenderedImage }) ).toHaveAttribute( 'href', - base + path + '?from=1000&to=2000&orgId=1&panelId=1&width=1000&height=500&scale=1&tz=UTC' + base + path + '?from=1000&to=2000&orgId=1&panelId=1&hideLogo=true&width=1000&height=500&scale=1&tz=UTC' ); }); @@ -172,7 +172,7 @@ describe('ShareModal', () => { render(); const base = 'http://dashboards.grafana.com/render/d-solo/abcdefghi/my-dash'; - const params = '?from=1000&to=2000&orgId=1&panelId=22&width=1000&height=500&scale=1&tz=UTC'; + const params = '?from=1000&to=2000&orgId=1&panelId=22&hideLogo=true&width=1000&height=500&scale=1&tz=UTC'; expect( await screen.findByRole('link', { name: selectors.pages.SharePanelModal.linkToRenderedImage }) ).toHaveAttribute('href', base + params); @@ -213,7 +213,7 @@ describe('when appUrl is set in the grafana config', () => { await screen.findByRole('link', { name: selectors.pages.SharePanelModal.linkToRenderedImage }) ).toHaveAttribute( 'href', - `http://dashboards.grafana.com/render/d-solo/${mockDashboard.uid}?orgId=1&from=1000&to=2000&panelId=${mockPanel.id}&width=1000&height=500&scale=1&tz=UTC` + `http://dashboards.grafana.com/render/d-solo/${mockDashboard.uid}?orgId=1&from=1000&to=2000&panelId=${mockPanel.id}&hideLogo=true&width=1000&height=500&scale=1&tz=UTC` ); }); }); diff --git a/public/app/features/dashboard/components/ShareModal/utils.ts b/public/app/features/dashboard/components/ShareModal/utils.ts index e60deafe3a5..c79203d5872 100644 --- a/public/app/features/dashboard/components/ShareModal/utils.ts +++ b/public/app/features/dashboard/components/ShareModal/utils.ts @@ -142,6 +142,7 @@ export function buildImageUrl( let imageUrl = soloUrl.replace(config.appSubUrl + '/dashboard-solo/', config.appSubUrl + '/render/dashboard-solo/'); imageUrl = imageUrl.replace(config.appSubUrl + '/d-solo/', config.appSubUrl + '/render/d-solo/'); imageUrl += + `&hideLogo=true` + `&width=${config.rendererDefaultImageWidth}` + `&height=${config.rendererDefaultImageHeight}` + `&scale=${config.rendererDefaultImageScale}` + diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index 0de5ac009db..febd4c0442e 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -7166,6 +7166,9 @@ "time-range-label": "Lock time range" } }, + "embedded-panel": { + "powered-by": "Powered by" + }, "empty-list-cta": { "pro-tip": "ProTip: {{proTip}}" }, From 606a59584a5fce12fd48f9e4153e1e4b01cb7963 Mon Sep 17 00:00:00 2001 From: Collin Fingar Date: Thu, 18 Dec 2025 18:18:24 -0500 Subject: [PATCH 35/36] Saved Queries: Pass editor ref for dynamic dropdown display (#114321) * Saved Queries: Pass editor ref for dynamic dropdown display * Updated docs per feedback * Update docs/sources/visualizations/dashboards/build-dashboards/annotate-visualizations/index.md Co-authored-by: Isabel Matwawana <76437239+imatwawana@users.noreply.github.com> * Update docs/sources/visualizations/dashboards/build-dashboards/annotate-visualizations/index.md Co-authored-by: Isabel Matwawana <76437239+imatwawana@users.noreply.github.com> * Update docs/sources/visualizations/dashboards/build-dashboards/create-dashboard/index.md Co-authored-by: Isabel Matwawana <76437239+imatwawana@users.noreply.github.com> * Update docs/sources/visualizations/dashboards/build-dashboards/create-dashboard/index.md Co-authored-by: Isabel Matwawana <76437239+imatwawana@users.noreply.github.com> * Update docs/sources/visualizations/explore/get-started-with-explore.md Co-authored-by: Isabel Matwawana <76437239+imatwawana@users.noreply.github.com> * Update docs/sources/visualizations/panels-visualizations/query-transform-data/_index.md Co-authored-by: Isabel Matwawana <76437239+imatwawana@users.noreply.github.com> * Update docs/sources/visualizations/panels-visualizations/query-transform-data/_index.md Co-authored-by: Isabel Matwawana <76437239+imatwawana@users.noreply.github.com> * Update docs/sources/visualizations/panels-visualizations/query-transform-data/_index.md Co-authored-by: Isabel Matwawana <76437239+imatwawana@users.noreply.github.com> * Update docs/sources/visualizations/panels-visualizations/query-transform-data/_index.md Co-authored-by: Isabel Matwawana <76437239+imatwawana@users.noreply.github.com> * Update docs/sources/visualizations/panels-visualizations/query-transform-data/_index.md Co-authored-by: Isabel Matwawana <76437239+imatwawana@users.noreply.github.com> --------- Co-authored-by: Nathan Marrs Co-authored-by: Isabel Matwawana <76437239+imatwawana@users.noreply.github.com> --- .../annotate-visualizations/index.md | 4 ++-- .../build-dashboards/create-dashboard/index.md | 4 ++-- .../explore/get-started-with-explore.md | 5 +++-- .../panel-editor-overview/index.md | 5 +++-- .../query-transform-data/_index.md | 10 +++++----- .../explore/QueryLibrary/QueryLibraryContext.tsx | 3 ++- .../query/components/QueryEditorRow.test.tsx | 3 +-- .../features/query/components/QueryEditorRow.tsx | 16 +++++++++++++--- 8 files changed, 31 insertions(+), 19 deletions(-) diff --git a/docs/sources/visualizations/dashboards/build-dashboards/annotate-visualizations/index.md b/docs/sources/visualizations/dashboards/build-dashboards/annotate-visualizations/index.md index f356d51c4a5..5458f90281e 100644 --- a/docs/sources/visualizations/dashboards/build-dashboards/annotate-visualizations/index.md +++ b/docs/sources/visualizations/dashboards/build-dashboards/annotate-visualizations/index.md @@ -163,9 +163,9 @@ To add a new annotation query to a dashboard, follow these steps: 1. To create a query, do one of the following: - Write or construct a query in the query language of your data source. The annotation query options are different for each data source. For information about annotations in a specific data source, refer to the specific [data source](ref:data-source) topic. - - Click **Replace with saved query** to reuse a [saved query](ref:saved-queries). + - Open the **Saved queries** drop-down menu and click **Replace query** to reuse a [saved query](ref:saved-queries). -1. (Optional) To [save the query](ref:save-query) for reuse, click the **Save query** button (or icon). +1. (Optional) To [save the query](ref:save-query) for reuse, open the **Saved queries** drop-down menu and click the **Save query** option. 1. (Optional) Click **Test annotation query** to ensure that the query is working properly. 1. (Optional) To add subsequent queries, click **+ Add query** or **+ Add from saved queries**, and test them as many times as needed. diff --git a/docs/sources/visualizations/dashboards/build-dashboards/create-dashboard/index.md b/docs/sources/visualizations/dashboards/build-dashboards/create-dashboard/index.md index feb6573c3e9..3d5e765e0cd 100644 --- a/docs/sources/visualizations/dashboards/build-dashboards/create-dashboard/index.md +++ b/docs/sources/visualizations/dashboards/build-dashboards/create-dashboard/index.md @@ -125,9 +125,9 @@ Dashboards and panels allow you to show your data in visual form. Each panel nee 1. To create a query, do one of the following: - Write or construct a query in the query language of your data source. - - Click **Replace with saved query** to reuse a [saved query](ref:saved-queries). + - Open the **Saved queries** drop-down menu and click **Replace query** to reuse a [saved query](ref:saved-queries). -1. (Optional) To [save the query](ref:save-query) for reuse, click the **Save query** button (or icon). +1. (Optional) To [save the query](ref:save-query) for reuse, open the **Saved queries** drop-down menu and click the **Save query** option. 1. Click **Refresh** to query the data source. 1. (Optional) To add subsequent queries, click **+ Add query** or **+ Add from saved queries**, and refresh the data source as many times as needed. diff --git a/docs/sources/visualizations/explore/get-started-with-explore.md b/docs/sources/visualizations/explore/get-started-with-explore.md index b59ac68c686..41f1f8c5c01 100644 --- a/docs/sources/visualizations/explore/get-started-with-explore.md +++ b/docs/sources/visualizations/explore/get-started-with-explore.md @@ -71,8 +71,9 @@ Explore consists of a toolbar, outline, query editor, the ability to add multipl - **Run query** - Click to run your query. - **Query editor** - Interface where you construct the query for a specific data source. Query editor elements differ based on data source. In order to run queries across multiple data sources you need to select **Mixed** from the data source picker. - - **Save query** - To [save the query](ref:save-query) for reuse, click the **Save query** button (or icon). - - **Replace with saved query** - Reuse a saved query. + - **Saved queries**: + - **Save query** - To [save the query](ref:save-query) for reuse, click the **Save query** button (or icon). + - **Replace query** - Reuse a saved query. - **+ Add query** - Add an additional query. - **+ Add from saved queries** - Add an additional query by reusing a saved query. diff --git a/docs/sources/visualizations/panels-visualizations/panel-editor-overview/index.md b/docs/sources/visualizations/panels-visualizations/panel-editor-overview/index.md index 4afbd8f71a6..f219f10a68a 100644 --- a/docs/sources/visualizations/panels-visualizations/panel-editor-overview/index.md +++ b/docs/sources/visualizations/panels-visualizations/panel-editor-overview/index.md @@ -88,8 +88,9 @@ The data section contains tabs where you enter queries, transform your data, and - **Queries** - Select your data source. You can also set or update the data source in existing dashboards using the drop-down menu in the **Queries** tab. - - **Save query** - To [save the query](ref:save-query) for reuse, click the **Save query** button (or icon). - - **Replace with saved query** - Reuse a saved query. + - **Saved queries**: + - **Save query** - To [save the query](ref:save-query) for reuse, click the **Save query** button (or icon). + - **Replace query** - Reuse a saved query. - **+ Add query** - Add an additional query. - **+ Add from saved queries** - Add an additional query by reusing a saved query. diff --git a/docs/sources/visualizations/panels-visualizations/query-transform-data/_index.md b/docs/sources/visualizations/panels-visualizations/query-transform-data/_index.md index 5b121028404..0aafe41f524 100644 --- a/docs/sources/visualizations/panels-visualizations/query-transform-data/_index.md +++ b/docs/sources/visualizations/panels-visualizations/query-transform-data/_index.md @@ -156,11 +156,11 @@ In the **Saved queries** drawer, you can: - Edit a query title, description, tags, or the availability of the query to other users in your organization. By default, saved queries are locked for editing. - When you access the **Saved queries** drawer from Explore, you can use the **Edit in Explore** option to edit the body of a query. -To access your saved queries, click **+ Add from saved queries** or **Replace with saved query** in the query editor: +To access your saved queries, click **+ Add from saved queries** or open the **Saved queries** drop-down menu and click **Replace query** in the query editor: {{< figure src="/media/docs/grafana/dashboards/screenshot-use-saved-queries-v12.3.png" max-width="750px" alt="Access saved queries" >}} -Clicking **+ Add from saved queries** adds an additional query, while clicking **Replace with saved query** updates your existing query. +Clicking **+ Add from saved queries** adds an additional query, while clicking **Replace query** in the **Saved queries** drop-down menu updates your existing query. {{< admonition type="note" >}} Users with Admin and Editor roles can create and save queries for reuse. @@ -172,7 +172,7 @@ Viewers can only reuse queries. To save a query you've created: -1. From the query editor, click the **Save query** icon: +1. From the query editor, open the **Saved queries** drop-down menu and click the **Save query** option: {{< figure src="/media/docs/grafana/panels-visualizations/screenshot-save-query-v12.2.png" max-width="750px" alt="Save a query" >}} @@ -227,7 +227,7 @@ To add a query, follow these steps: 1. To create a query, do one of the following: - Write or construct a query in the query language of your data source. - - Click **Replace with saved query** to reuse a saved query. + - Open the **Saved queries** drop-down menu and click **Replace query** to reuse a saved query. {{< admonition type="note" >}} [Saved queries](#saved-queries) is currently in [public preview](https://grafana.com/docs/release-life-cycle/). Grafana Labs offers limited support, and breaking changes might occur prior to the feature being made generally available. @@ -235,7 +235,7 @@ To add a query, follow these steps: This feature is only available on Grafana Enterprise and Grafana Cloud. {{< /admonition >}} -1. (Optional) To [save the query](#save-a-query) for reuse, click the **Save query** button (or icon). +1. (Optional) To [save the query](#save-a-query) for reuse, click the **Save query** option in the **Saved queries** drop-down menu. 1. (Optional) Click **+ Add query** or **Add from saved queries** to add more queries as needed. 1. Click **Run queries**. diff --git a/public/app/features/explore/QueryLibrary/QueryLibraryContext.tsx b/public/app/features/explore/QueryLibrary/QueryLibraryContext.tsx index 50ebe16e49c..6138829632a 100644 --- a/public/app/features/explore/QueryLibrary/QueryLibraryContext.tsx +++ b/public/app/features/explore/QueryLibrary/QueryLibraryContext.tsx @@ -43,7 +43,8 @@ export type QueryLibraryContextType = { app?: CoreApp, onUpdateSuccess?: () => void, onSelectQuery?: (query: DataQuery) => void, - datasourceFilters?: string[] + datasourceFilters?: string[], + parentRef?: React.RefObject ) => ReactNode; /** diff --git a/public/app/features/query/components/QueryEditorRow.test.tsx b/public/app/features/query/components/QueryEditorRow.test.tsx index f839e570819..13be75a8fcb 100644 --- a/public/app/features/query/components/QueryEditorRow.test.tsx +++ b/public/app/features/query/components/QueryEditorRow.test.tsx @@ -461,8 +461,7 @@ describe('QueryEditorRow', () => { render(); await waitFor(() => { - expect(screen.queryByText('Save query')).not.toBeInTheDocument(); - expect(screen.queryByText('Replace with saved query')).not.toBeInTheDocument(); + expect(screen.queryByText('Saved queries')).not.toBeInTheDocument(); }); }); diff --git a/public/app/features/query/components/QueryEditorRow.tsx b/public/app/features/query/components/QueryEditorRow.tsx index b474d0293f9..5cc7f3467d3 100644 --- a/public/app/features/query/components/QueryEditorRow.tsx +++ b/public/app/features/query/components/QueryEditorRow.tsx @@ -1,7 +1,7 @@ import classNames from 'classnames'; import { cloneDeep, filter, uniqBy, uniqueId } from 'lodash'; import pluralize from 'pluralize'; -import { PureComponent, ReactNode, type JSX } from 'react'; +import { PureComponent, ReactNode, type JSX, createRef } from 'react'; import { CoreApp, @@ -88,6 +88,7 @@ interface State { export class QueryEditorRow extends PureComponent, State> { dataSourceSrv = getDataSourceSrv(); id = ''; + editorRef = createRef(); state: State = { datasource: null, @@ -419,6 +420,7 @@ export class QueryEditorRow extends PureComponent )} @@ -542,7 +544,7 @@ export class QueryEditorRow extends PureComponent +
{queryLibraryRef && ( void; onSelectQuery: (query: DataQuery) => void; datasourceFilters: string[]; + parentRef: React.RefObject; }) { const { renderSavedQueryButtons } = useQueryLibraryContext(); - return renderSavedQueryButtons(props.query, props.app, props.onUpdateSuccess, props.onSelectQuery); + return renderSavedQueryButtons( + props.query, + props.app, + props.onUpdateSuccess, + props.onSelectQuery, + undefined, + props.parentRef + ); } // Will render editing header only if query library is enabled From 99f5f14de764bf2d09357fb81b0af82a39c7ddc0 Mon Sep 17 00:00:00 2001 From: Will Assis <35489495+gassiss@users.noreply.github.com> Date: Thu, 18 Dec 2025 18:35:32 -0500 Subject: [PATCH 36/36] unified-storage: move rvmanager into its own package (#115445) * unified-storage: move rvmanager into its own package so it can be reused with sqlkv later --- pkg/storage/unified/sql/backend.go | 9 +- pkg/storage/unified/sql/backend_test.go | 20 +++++ pkg/storage/unified/sql/bulk.go | 4 +- pkg/storage/unified/sql/list_iterator_test.go | 3 +- pkg/storage/unified/sql/queries.go | 72 +--------------- pkg/storage/unified/sql/queries_test.go | 23 ++--- .../data/resource_history_update_rv.sql | 0 .../data/resource_update_rv.sql | 0 .../data/resource_version_get.sql | 0 .../data/resource_version_insert.sql | 0 .../data/resource_version_update.sql | 0 pkg/storage/unified/sql/rvmanager/queries.go | 84 +++++++++++++++++++ .../unified/sql/{ => rvmanager}/rv_manager.go | 51 ++++++----- .../sql/{ => rvmanager}/rv_manager_test.go | 2 +- .../unified/sql/rvmanager/templates.go | 30 +++++++ 15 files changed, 186 insertions(+), 112 deletions(-) rename pkg/storage/unified/sql/{ => rvmanager}/data/resource_history_update_rv.sql (100%) rename pkg/storage/unified/sql/{ => rvmanager}/data/resource_update_rv.sql (100%) rename pkg/storage/unified/sql/{ => rvmanager}/data/resource_version_get.sql (100%) rename pkg/storage/unified/sql/{ => rvmanager}/data/resource_version_insert.sql (100%) rename pkg/storage/unified/sql/{ => rvmanager}/data/resource_version_update.sql (100%) create mode 100644 pkg/storage/unified/sql/rvmanager/queries.go rename pkg/storage/unified/sql/{ => rvmanager}/rv_manager.go (89%) rename pkg/storage/unified/sql/{ => rvmanager}/rv_manager_test.go (99%) create mode 100644 pkg/storage/unified/sql/rvmanager/templates.go diff --git a/pkg/storage/unified/sql/backend.go b/pkg/storage/unified/sql/backend.go index ebd04ce4a30..a129a01727a 100644 --- a/pkg/storage/unified/sql/backend.go +++ b/pkg/storage/unified/sql/backend.go @@ -31,6 +31,7 @@ import ( "github.com/grafana/grafana/pkg/storage/unified/resourcepb" "github.com/grafana/grafana/pkg/storage/unified/sql/db" "github.com/grafana/grafana/pkg/storage/unified/sql/dbutil" + "github.com/grafana/grafana/pkg/storage/unified/sql/rvmanager" "github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate" "github.com/grafana/grafana/pkg/util/debouncer" ) @@ -126,7 +127,7 @@ type backend struct { notifier eventNotifier // resource version manager - rvManager *resourceVersionManager + rvManager *rvmanager.ResourceVersionManager // testing simulatedNetworkLatency time.Duration @@ -163,7 +164,7 @@ func (b *backend) initLocked(ctx context.Context) error { } // Initialize ResourceVersionManager - rvManager, err := NewResourceVersionManager(ResourceManagerOptions{ + rvManager, err := rvmanager.NewResourceVersionManager(rvmanager.ResourceManagerOptions{ Dialect: b.dialect, DB: b.db, }) @@ -928,12 +929,12 @@ func (b *backend) listLatestRVs(ctx context.Context) (groupResourceRV, error) { func (b *backend) fetchLatestRV(ctx context.Context, x db.ContextExecer, d sqltemplate.Dialect, group, resource string) (int64, error) { ctx, span := tracer.Start(ctx, "sql.backend.fetchLatestRV") defer span.End() - res, err := dbutil.QueryRow(ctx, x, sqlResourceVersionGet, sqlResourceVersionGetRequest{ + res, err := dbutil.QueryRow(ctx, x, rvmanager.SqlResourceVersionGet, rvmanager.SqlResourceVersionGetRequest{ SQLTemplate: sqltemplate.New(d), Group: group, Resource: resource, ReadOnly: true, - Response: new(resourceVersionResponse), + Response: new(rvmanager.ResourceVersionResponse), }) if errors.Is(err, sql.ErrNoRows) { return 1, nil diff --git a/pkg/storage/unified/sql/backend_test.go b/pkg/storage/unified/sql/backend_test.go index f7ecd755f32..2f8f9fcb060 100644 --- a/pkg/storage/unified/sql/backend_test.go +++ b/pkg/storage/unified/sql/backend_test.go @@ -40,6 +40,26 @@ type testBackend struct { test.TestDBProvider } +func expectSuccessfulResourceVersionLock(t *testing.T, dbp test.TestDBProvider, rv int64, timestamp int64) { + dbp.SQLMock.ExpectQuery("select resource_version, unix_timestamp for update"). + WillReturnRows(sqlmock.NewRows([]string{"resource_version", "unix_timestamp"}). + AddRow(rv, timestamp)) +} + +func expectSuccessfulResourceVersionSaveRV(t *testing.T, dbp test.TestDBProvider) { + dbp.SQLMock.ExpectExec("update resource set resource_version").WillReturnResult(sqlmock.NewResult(1, 1)) + dbp.SQLMock.ExpectExec("update resource_history set resource_version").WillReturnResult(sqlmock.NewResult(1, 1)) + dbp.SQLMock.ExpectExec("update resource_version set resource_version").WillReturnResult(sqlmock.NewResult(1, 1)) +} + +func expectSuccessfulResourceVersionExec(t *testing.T, dbp test.TestDBProvider, cbs ...func()) { + for _, cb := range cbs { + cb() + } + expectSuccessfulResourceVersionLock(t, dbp, 100, 200) + expectSuccessfulResourceVersionSaveRV(t, dbp) +} + func (b testBackend) ExecWithResult(expectedSQL string, lastInsertID int64, rowsAffected int64) { b.SQLMock.ExpectExec(expectedSQL).WillReturnResult(sqlmock.NewResult(lastInsertID, rowsAffected)) } diff --git a/pkg/storage/unified/sql/bulk.go b/pkg/storage/unified/sql/bulk.go index 6580975a764..4037c74f0dd 100644 --- a/pkg/storage/unified/sql/bulk.go +++ b/pkg/storage/unified/sql/bulk.go @@ -281,13 +281,13 @@ func (b *backend) processBulkWithTx(ctx context.Context, tx db.Tx, setting resou } if b.dialect.DialectName() == "sqlite" { - nextRV, err := b.rvManager.lock(ctx, tx, key.Group, key.Resource) + nextRV, err := b.rvManager.Lock(ctx, tx, key.Group, key.Resource) if err != nil { b.log.Error("error locking RV", "error", err, "key", resource.NSGR(key)) } else { b.log.Info("successfully locked RV", "nextRV", nextRV, "key", resource.NSGR(key)) // Save the incremented RV - if err := b.rvManager.saveRV(ctx, tx, key.Group, key.Resource, nextRV); err != nil { + if err := b.rvManager.SaveRV(ctx, tx, key.Group, key.Resource, nextRV); err != nil { b.log.Error("error saving RV", "error", err, "key", resource.NSGR(key)) } else { b.log.Info("successfully saved RV", "rv", nextRV, "key", resource.NSGR(key)) diff --git a/pkg/storage/unified/sql/list_iterator_test.go b/pkg/storage/unified/sql/list_iterator_test.go index 3b567ca0f3e..e049e06525e 100644 --- a/pkg/storage/unified/sql/list_iterator_test.go +++ b/pkg/storage/unified/sql/list_iterator_test.go @@ -17,6 +17,7 @@ import ( dbsql "github.com/grafana/grafana/pkg/storage/unified/sql/db" "github.com/grafana/grafana/pkg/storage/unified/sql/db/dbimpl" "github.com/grafana/grafana/pkg/storage/unified/sql/dbutil" + "github.com/grafana/grafana/pkg/storage/unified/sql/rvmanager" "github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate" "github.com/grafana/grafana/pkg/tests/testsuite" "github.com/grafana/grafana/pkg/util/testutil" @@ -94,7 +95,7 @@ func TestIntegrationListIter(t *testing.T) { return fmt.Errorf("failed to insert test data: %w", err) } - if _, err = dbutil.Exec(ctx, tx, sqlResourceUpdateRV, sqlResourceUpdateRVRequest{ + if _, err = dbutil.Exec(ctx, tx, rvmanager.SqlResourceUpdateRV, rvmanager.SqlResourceUpdateRVRequest{ SQLTemplate: sqltemplate.New(dialect), GUIDToRV: map[string]int64{ item.guid: item.resourceVersion, diff --git a/pkg/storage/unified/sql/queries.go b/pkg/storage/unified/sql/queries.go index 5b9a177e17c..725c4617403 100644 --- a/pkg/storage/unified/sql/queries.go +++ b/pkg/storage/unified/sql/queries.go @@ -38,10 +38,8 @@ var ( sqlResourceList = mustTemplate("resource_list.sql") sqlResourceHistoryList = mustTemplate("resource_history_list.sql") sqlResourceHistoryListModifiedSince = mustTemplate("resource_history_list_since_modified.sql") - sqlResourceUpdateRV = mustTemplate("resource_update_rv.sql") sqlResourceHistoryRead = mustTemplate("resource_history_read.sql") sqlResourceHistoryReadLatestRV = mustTemplate("resource_history_read_latest_rv.sql") - sqlResourceHistoryUpdateRV = mustTemplate("resource_history_update_rv.sql") sqlResourceHistoryInsert = mustTemplate("resource_history_insert.sql") sqlResourceHistoryPoll = mustTemplate("resource_history_poll.sql") sqlResourceHistoryGet = mustTemplate("resource_history_get.sql") @@ -51,10 +49,7 @@ var ( sqlResourceInsertFromHistory = mustTemplate("resource_insert_from_history.sql") // sqlResourceLabelsInsert = mustTemplate("resource_labels_insert.sql") - sqlResourceVersionGet = mustTemplate("resource_version_get.sql") - sqlResourceVersionUpdate = mustTemplate("resource_version_update.sql") - sqlResourceVersionInsert = mustTemplate("resource_version_insert.sql") - sqlResourceVersionList = mustTemplate("resource_version_list.sql") + sqlResourceVersionList = mustTemplate("resource_version_list.sql") sqlResourceBlobInsert = mustTemplate("resource_blob_insert.sql") sqlResourceBlobQuery = mustTemplate("resource_blob_query.sql") @@ -365,76 +360,11 @@ func (r sqlResourceBlobQueryRequest) Validate() error { return nil } -// update RV - -type sqlResourceUpdateRVRequest struct { - sqltemplate.SQLTemplate - GUIDToRV map[string]int64 - GUIDToSnowflakeRV map[string]int64 -} - -func (r sqlResourceUpdateRVRequest) Validate() error { - return nil // TODO -} - -func (r sqlResourceUpdateRVRequest) SlashFunc() string { - if r.DialectName() == "postgres" { - return "CHR(47)" - } - - return "CHAR(47)" -} - -func (r sqlResourceUpdateRVRequest) TildeFunc() string { - if r.DialectName() == "postgres" { - return "CHR(126)" - } - - return "CHAR(126)" -} - -// resource_version table requests. -type resourceVersionResponse struct { - ResourceVersion int64 - CurrentEpoch int64 -} - -func (r *resourceVersionResponse) Results() (*resourceVersionResponse, error) { - return r, nil -} - type groupResourceVersion struct { Group, Resource string ResourceVersion int64 } -type sqlResourceVersionUpsertRequest struct { - sqltemplate.SQLTemplate - Group, Resource string - ResourceVersion int64 -} - -func (r sqlResourceVersionUpsertRequest) Validate() error { - return nil // TODO -} - -type sqlResourceVersionGetRequest struct { - sqltemplate.SQLTemplate - Group, Resource string - ReadOnly bool - Response *resourceVersionResponse -} - -func (r sqlResourceVersionGetRequest) Validate() error { - return nil // TODO -} -func (r sqlResourceVersionGetRequest) Results() (*resourceVersionResponse, error) { - return &resourceVersionResponse{ - ResourceVersion: r.Response.ResourceVersion, - CurrentEpoch: r.Response.CurrentEpoch, - }, nil -} - type sqlResourceVersionListRequest struct { sqltemplate.SQLTemplate *groupResourceVersion diff --git a/pkg/storage/unified/sql/queries_test.go b/pkg/storage/unified/sql/queries_test.go index 5673bfad1e8..93e9b1f5af4 100644 --- a/pkg/storage/unified/sql/queries_test.go +++ b/pkg/storage/unified/sql/queries_test.go @@ -8,6 +8,7 @@ import ( "github.com/grafana/grafana/pkg/apimachinery/utils" "github.com/grafana/grafana/pkg/storage/unified/resource" "github.com/grafana/grafana/pkg/storage/unified/resourcepb" + "github.com/grafana/grafana/pkg/storage/unified/sql/rvmanager" "github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate/mocks" ) @@ -162,10 +163,10 @@ func TestUnifiedStorageQueries(t *testing.T) { }, }, - sqlResourceUpdateRV: { + rvmanager.SqlResourceUpdateRV: { { Name: "single path", - Data: &sqlResourceUpdateRVRequest{ + Data: &rvmanager.SqlResourceUpdateRVRequest{ SQLTemplate: mocks.NewTestingSQLTemplate(), GUIDToRV: map[string]int64{ "guid1": 123, @@ -228,10 +229,10 @@ func TestUnifiedStorageQueries(t *testing.T) { }, }, - sqlResourceHistoryUpdateRV: { + rvmanager.SqlResourceHistoryUpdateRV: { { Name: "single path", - Data: &sqlResourceUpdateRVRequest{ + Data: &rvmanager.SqlResourceUpdateRVRequest{ SQLTemplate: mocks.NewTestingSQLTemplate(), GUIDToRV: map[string]int64{ "guid1": 123, @@ -334,23 +335,23 @@ func TestUnifiedStorageQueries(t *testing.T) { }, }, - sqlResourceVersionGet: { + rvmanager.SqlResourceVersionGet: { { Name: "single path", - Data: &sqlResourceVersionGetRequest{ + Data: &rvmanager.SqlResourceVersionGetRequest{ SQLTemplate: mocks.NewTestingSQLTemplate(), Resource: "resource", Group: "group", - Response: new(resourceVersionResponse), + Response: new(rvmanager.ResourceVersionResponse), ReadOnly: false, }, }, }, - sqlResourceVersionUpdate: { + rvmanager.SqlResourceVersionUpdate: { { Name: "increment resource version", - Data: &sqlResourceVersionUpsertRequest{ + Data: &rvmanager.SqlResourceVersionUpsertRequest{ SQLTemplate: mocks.NewTestingSQLTemplate(), Resource: "resource", Group: "group", @@ -359,10 +360,10 @@ func TestUnifiedStorageQueries(t *testing.T) { }, }, - sqlResourceVersionInsert: { + rvmanager.SqlResourceVersionInsert: { { Name: "single path", - Data: &sqlResourceVersionUpsertRequest{ + Data: &rvmanager.SqlResourceVersionUpsertRequest{ SQLTemplate: mocks.NewTestingSQLTemplate(), ResourceVersion: int64(12354), }, diff --git a/pkg/storage/unified/sql/data/resource_history_update_rv.sql b/pkg/storage/unified/sql/rvmanager/data/resource_history_update_rv.sql similarity index 100% rename from pkg/storage/unified/sql/data/resource_history_update_rv.sql rename to pkg/storage/unified/sql/rvmanager/data/resource_history_update_rv.sql diff --git a/pkg/storage/unified/sql/data/resource_update_rv.sql b/pkg/storage/unified/sql/rvmanager/data/resource_update_rv.sql similarity index 100% rename from pkg/storage/unified/sql/data/resource_update_rv.sql rename to pkg/storage/unified/sql/rvmanager/data/resource_update_rv.sql diff --git a/pkg/storage/unified/sql/data/resource_version_get.sql b/pkg/storage/unified/sql/rvmanager/data/resource_version_get.sql similarity index 100% rename from pkg/storage/unified/sql/data/resource_version_get.sql rename to pkg/storage/unified/sql/rvmanager/data/resource_version_get.sql diff --git a/pkg/storage/unified/sql/data/resource_version_insert.sql b/pkg/storage/unified/sql/rvmanager/data/resource_version_insert.sql similarity index 100% rename from pkg/storage/unified/sql/data/resource_version_insert.sql rename to pkg/storage/unified/sql/rvmanager/data/resource_version_insert.sql diff --git a/pkg/storage/unified/sql/data/resource_version_update.sql b/pkg/storage/unified/sql/rvmanager/data/resource_version_update.sql similarity index 100% rename from pkg/storage/unified/sql/data/resource_version_update.sql rename to pkg/storage/unified/sql/rvmanager/data/resource_version_update.sql diff --git a/pkg/storage/unified/sql/rvmanager/queries.go b/pkg/storage/unified/sql/rvmanager/queries.go new file mode 100644 index 00000000000..c0411cdf704 --- /dev/null +++ b/pkg/storage/unified/sql/rvmanager/queries.go @@ -0,0 +1,84 @@ +package rvmanager + +import ( + "github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate" +) + +type SqlResourceUpdateRVRequest struct { + sqltemplate.SQLTemplate + GUIDToRV map[string]int64 + GUIDToSnowflakeRV map[string]int64 +} + +func (r SqlResourceUpdateRVRequest) Validate() error { + return nil // TODO +} + +func (r SqlResourceUpdateRVRequest) SlashFunc() string { + if r.DialectName() == "postgres" { + return "CHR(47)" + } + + return "CHAR(47)" +} + +func (r SqlResourceUpdateRVRequest) TildeFunc() string { + if r.DialectName() == "postgres" { + return "CHR(126)" + } + + return "CHAR(126)" +} + +type ResourceVersionResponse struct { + ResourceVersion int64 + CurrentEpoch int64 +} + +func (r *ResourceVersionResponse) Results() (*ResourceVersionResponse, error) { + return r, nil +} + +type sqlResourceVersionGetRequest struct { + sqltemplate.SQLTemplate + Group, Resource string + ReadOnly bool + Response *ResourceVersionResponse +} + +func (r sqlResourceVersionGetRequest) Validate() error { + return nil // TODO +} +func (r sqlResourceVersionGetRequest) Results() (*ResourceVersionResponse, error) { + return &ResourceVersionResponse{ + ResourceVersion: r.Response.ResourceVersion, + CurrentEpoch: r.Response.CurrentEpoch, + }, nil +} + +type SqlResourceVersionUpsertRequest struct { + sqltemplate.SQLTemplate + Group, Resource string + ResourceVersion int64 +} + +func (r SqlResourceVersionUpsertRequest) Validate() error { + return nil // TODO +} + +type SqlResourceVersionGetRequest struct { + sqltemplate.SQLTemplate + Group, Resource string + ReadOnly bool + Response *ResourceVersionResponse +} + +func (r SqlResourceVersionGetRequest) Validate() error { + return nil // TODO +} +func (r SqlResourceVersionGetRequest) Results() (*ResourceVersionResponse, error) { + return &ResourceVersionResponse{ + ResourceVersion: r.Response.ResourceVersion, + CurrentEpoch: r.Response.CurrentEpoch, + }, nil +} diff --git a/pkg/storage/unified/sql/rv_manager.go b/pkg/storage/unified/sql/rvmanager/rv_manager.go similarity index 89% rename from pkg/storage/unified/sql/rv_manager.go rename to pkg/storage/unified/sql/rvmanager/rv_manager.go index 858345b1fc2..b4f3b5de596 100644 --- a/pkg/storage/unified/sql/rv_manager.go +++ b/pkg/storage/unified/sql/rvmanager/rv_manager.go @@ -1,4 +1,4 @@ -package sql +package rvmanager import ( "context" @@ -11,6 +11,7 @@ import ( "github.com/bwmarrin/snowflake" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" + "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" @@ -20,6 +21,8 @@ import ( "github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate" ) +var tracer = otel.Tracer("github.com/grafana/grafana/pkg/storage/unified/sql/rvmanager") + var ( rvmWriteDuration = promauto.NewHistogramVec(prometheus.HistogramOpts{ Name: "rvmanager_write_duration_seconds", @@ -62,8 +65,8 @@ const ( defaultBatchTimeout = 5 * time.Second ) -// resourceVersionManager handles resource version operations -type resourceVersionManager struct { +// ResourceVersionManager handles resource version operations +type ResourceVersionManager struct { dialect sqltemplate.Dialect db db.DB batchMu sync.RWMutex @@ -100,7 +103,7 @@ type ResourceManagerOptions struct { } // NewResourceVersionManager creates a new ResourceVersionManager -func NewResourceVersionManager(opts ResourceManagerOptions) (*resourceVersionManager, error) { +func NewResourceVersionManager(opts ResourceManagerOptions) (*ResourceVersionManager, error) { if opts.MaxBatchSize == 0 { opts.MaxBatchSize = defaultMaxBatchSize } @@ -113,7 +116,7 @@ func NewResourceVersionManager(opts ResourceManagerOptions) (*resourceVersionMan if opts.DB == nil { return nil, errors.New("db is required") } - return &resourceVersionManager{ + return &ResourceVersionManager{ dialect: opts.Dialect, db: opts.DB, batchChMap: make(map[string]chan *writeOp), @@ -123,7 +126,7 @@ func NewResourceVersionManager(opts ResourceManagerOptions) (*resourceVersionMan } // ExecWithRV executes the given function with an incremented resource version -func (m *resourceVersionManager) ExecWithRV(ctx context.Context, key *resourcepb.ResourceKey, fn WriteEventFunc) (rv int64, err error) { +func (m *ResourceVersionManager) ExecWithRV(ctx context.Context, key *resourcepb.ResourceKey, fn WriteEventFunc) (rv int64, err error) { rvmInflightWrites.WithLabelValues(key.Group, key.Resource).Inc() defer rvmInflightWrites.WithLabelValues(key.Group, key.Resource).Dec() @@ -179,7 +182,7 @@ func (m *resourceVersionManager) ExecWithRV(ctx context.Context, key *resourcepb } // startBatchProcessor is responsible for processing batches of write operations -func (m *resourceVersionManager) startBatchProcessor(group, resource string) { +func (m *ResourceVersionManager) startBatchProcessor(group, resource string) { ctx := context.TODO() batchKey := fmt.Sprintf("%s/%s", group, resource) @@ -216,7 +219,11 @@ func (m *resourceVersionManager) startBatchProcessor(group, resource string) { } } -func (m *resourceVersionManager) execBatch(ctx context.Context, group, resource string, batch []writeOp) { +var readCommitted = &sql.TxOptions{ + Isolation: sql.LevelReadCommitted, +} + +func (m *ResourceVersionManager) execBatch(ctx context.Context, group, resource string, batch []writeOp) { ctx, span := tracer.Start(ctx, "sql.resourceVersionManager.execBatch") defer span.End() @@ -245,7 +252,7 @@ func (m *resourceVersionManager) execBatch(ctx context.Context, group, resource guids := make([]string, len(batch)) // The GUIDs of the created resources in the same order as the batch rvs := make([]int64, len(batch)) // The RVs of the created resources in the same order as the batch - err = m.db.WithTx(ctx, ReadCommitted, func(ctx context.Context, tx db.Tx) error { + err = m.db.WithTx(ctx, readCommitted, func(ctx context.Context, tx db.Tx) error { span.AddEvent("starting_batch_transaction") writeTimer := prometheus.NewTimer(prometheus.ObserverFunc(func(v float64) { @@ -268,7 +275,7 @@ func (m *resourceVersionManager) execBatch(ctx context.Context, group, resource lockTimer := prometheus.NewTimer(prometheus.ObserverFunc(func(v float64) { rvmExecBatchPhaseDuration.WithLabelValues(group, resource, "waiting_for_lock").Observe(v) })) - rv, err := m.lock(ctx, tx, group, resource) + rv, err := m.Lock(ctx, tx, group, resource) lockTimer.ObserveDuration() if err != nil { span.AddEvent("resource_version_lock_failed", trace.WithAttributes( @@ -292,7 +299,7 @@ func (m *resourceVersionManager) execBatch(ctx context.Context, group, resource rv++ } // Update the resource version for the created resources in both the resource and the resource history - if _, err := dbutil.Exec(ctx, tx, sqlResourceUpdateRV, sqlResourceUpdateRVRequest{ + if _, err := dbutil.Exec(ctx, tx, SqlResourceUpdateRV, SqlResourceUpdateRVRequest{ SQLTemplate: sqltemplate.New(m.dialect), GUIDToRV: guidToRV, }); err != nil { @@ -303,7 +310,7 @@ func (m *resourceVersionManager) execBatch(ctx context.Context, group, resource } span.AddEvent("resource_versions_updated") - if _, err := dbutil.Exec(ctx, tx, sqlResourceHistoryUpdateRV, sqlResourceUpdateRVRequest{ + if _, err := dbutil.Exec(ctx, tx, SqlResourceHistoryUpdateRV, SqlResourceUpdateRVRequest{ SQLTemplate: sqltemplate.New(m.dialect), GUIDToRV: guidToRV, GUIDToSnowflakeRV: guidToSnowflakeRV, @@ -316,7 +323,7 @@ func (m *resourceVersionManager) execBatch(ctx context.Context, group, resource span.AddEvent("resource_history_versions_updated") // Record the latest RV in the resource version table - err = m.saveRV(ctx, tx, group, resource, rv) + err = m.SaveRV(ctx, tx, group, resource, rv) if err != nil { span.AddEvent("save_rv_failed", trace.WithAttributes( attribute.String("error", err.Error()), @@ -350,20 +357,20 @@ func snowflakeFromRv(rv int64) int64 { return (((rv / 1000) - snowflake.Epoch) << (snowflake.NodeBits + snowflake.StepBits)) + (rv % 1000) } -// lock locks the resource version for the given key -func (m *resourceVersionManager) lock(ctx context.Context, x db.ContextExecer, group, resource string) (nextRV int64, err error) { +// Lock locks the resource version for the given key +func (m *ResourceVersionManager) Lock(ctx context.Context, x db.ContextExecer, group, resource string) (nextRV int64, err error) { // 1. Lock the row and prevent concurrent updates until the transaction is committed - res, err := dbutil.QueryRow(ctx, x, sqlResourceVersionGet, sqlResourceVersionGetRequest{ + res, err := dbutil.QueryRow(ctx, x, SqlResourceVersionGet, sqlResourceVersionGetRequest{ SQLTemplate: sqltemplate.New(m.dialect), Group: group, Resource: resource, - Response: new(resourceVersionResponse), + Response: new(ResourceVersionResponse), ReadOnly: false, // Lock the row for update }) if errors.Is(err, sql.ErrNoRows) { // If there wasn't a row for this resource, create it - if _, err = dbutil.Exec(ctx, x, sqlResourceVersionInsert, sqlResourceVersionUpsertRequest{ + if _, err = dbutil.Exec(ctx, x, SqlResourceVersionInsert, SqlResourceVersionUpsertRequest{ SQLTemplate: sqltemplate.New(m.dialect), Group: group, Resource: resource, @@ -372,11 +379,11 @@ func (m *resourceVersionManager) lock(ctx context.Context, x db.ContextExecer, g } // Fetch the newly created resource version - res, err = dbutil.QueryRow(ctx, x, sqlResourceVersionGet, sqlResourceVersionGetRequest{ + res, err = dbutil.QueryRow(ctx, x, SqlResourceVersionGet, sqlResourceVersionGetRequest{ SQLTemplate: sqltemplate.New(m.dialect), Group: group, Resource: resource, - Response: new(resourceVersionResponse), + Response: new(ResourceVersionResponse), ReadOnly: true, }) if err != nil { @@ -390,8 +397,8 @@ func (m *resourceVersionManager) lock(ctx context.Context, x db.ContextExecer, g return max(res.CurrentEpoch, res.ResourceVersion+1), nil } -func (m *resourceVersionManager) saveRV(ctx context.Context, x db.ContextExecer, group, resource string, rv int64) error { - _, err := dbutil.Exec(ctx, x, sqlResourceVersionUpdate, sqlResourceVersionUpsertRequest{ +func (m *ResourceVersionManager) SaveRV(ctx context.Context, x db.ContextExecer, group, resource string, rv int64) error { + _, err := dbutil.Exec(ctx, x, SqlResourceVersionUpdate, SqlResourceVersionUpsertRequest{ SQLTemplate: sqltemplate.New(m.dialect), Group: group, Resource: resource, diff --git a/pkg/storage/unified/sql/rv_manager_test.go b/pkg/storage/unified/sql/rvmanager/rv_manager_test.go similarity index 99% rename from pkg/storage/unified/sql/rv_manager_test.go rename to pkg/storage/unified/sql/rvmanager/rv_manager_test.go index 46d23d26a59..9a2e105aa2f 100644 --- a/pkg/storage/unified/sql/rv_manager_test.go +++ b/pkg/storage/unified/sql/rvmanager/rv_manager_test.go @@ -1,4 +1,4 @@ -package sql +package rvmanager import ( "testing" diff --git a/pkg/storage/unified/sql/rvmanager/templates.go b/pkg/storage/unified/sql/rvmanager/templates.go new file mode 100644 index 00000000000..65c91a273af --- /dev/null +++ b/pkg/storage/unified/sql/rvmanager/templates.go @@ -0,0 +1,30 @@ +package rvmanager + +import ( + "embed" + "fmt" + "text/template" +) + +// Templates setup. +var ( + //go:embed data/*.sql + sqlTemplatesFS embed.FS + + sqlTemplates = template.Must(template.New("sql").ParseFS(sqlTemplatesFS, `data/*.sql`)) +) + +func mustTemplate(filename string) *template.Template { + if t := sqlTemplates.Lookup(filename); t != nil { + return t + } + panic(fmt.Sprintf("template file not found: %s", filename)) +} + +var ( + SqlResourceUpdateRV = mustTemplate("resource_update_rv.sql") + SqlResourceHistoryUpdateRV = mustTemplate("resource_history_update_rv.sql") + SqlResourceVersionGet = mustTemplate("resource_version_get.sql") + SqlResourceVersionUpdate = mustTemplate("resource_version_update.sql") + SqlResourceVersionInsert = mustTemplate("resource_version_insert.sql") +)