Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d666c4e2a7 | |||
| 5012b4079e |
@@ -95,7 +95,6 @@ runs:
|
||||
- 'nx.json'
|
||||
- 'tsconfig.json'
|
||||
- '.yarn/**'
|
||||
- 'apps/dashboard/pkg/migration/**'
|
||||
- '${{ inputs.self }}'
|
||||
e2e:
|
||||
- 'e2e/**'
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ ARG JS_SRC=js-builder
|
||||
|
||||
# Dependabot cannot update dependencies listed in ARGs
|
||||
# By using FROM instructions we can delegate dependency updates to dependabot
|
||||
FROM alpine:3.23.2 AS alpine-base
|
||||
FROM alpine:3.23.0 AS alpine-base
|
||||
FROM ubuntu:22.04 AS ubuntu-base
|
||||
FROM golang:1.25.5-alpine AS go-builder-base
|
||||
FROM --platform=${JS_PLATFORM} node:24-alpine AS js-builder-base
|
||||
|
||||
+9
-3
@@ -300,9 +300,15 @@
|
||||
"y": 0
|
||||
},
|
||||
"id": 6,
|
||||
"options": {},
|
||||
"content": "# Graph panel \u003e\u003e Timeseries panel\n\nKnown issues:\n* hiding null/empty series\n* time regions",
|
||||
"mode": "markdown",
|
||||
"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"
|
||||
},
|
||||
"pluginVersion": "11.0.0-pre",
|
||||
"targets": [
|
||||
{
|
||||
|
||||
+2
-6
@@ -743,9 +743,7 @@
|
||||
"text": "prod",
|
||||
"value": "prod"
|
||||
},
|
||||
"datasource": {
|
||||
"uid": "$datasource"
|
||||
},
|
||||
"datasource": "$datasource",
|
||||
"hide": 0,
|
||||
"includeAll": true,
|
||||
"label": "cluster",
|
||||
@@ -766,9 +764,7 @@
|
||||
"text": "prod",
|
||||
"value": "prod"
|
||||
},
|
||||
"datasource": {
|
||||
"uid": "$datasource"
|
||||
},
|
||||
"datasource": "$datasource",
|
||||
"hide": 0,
|
||||
"includeAll": false,
|
||||
"label": "namespace",
|
||||
|
||||
+2
-10
@@ -961,12 +961,8 @@
|
||||
"hide": "dontHide",
|
||||
"refresh": "onDashboardLoad",
|
||||
"skipUrlSync": false,
|
||||
"datasource": {
|
||||
"type": "",
|
||||
"uid": "$datasource"
|
||||
},
|
||||
"query": {
|
||||
"kind": "",
|
||||
"kind": "prometheus",
|
||||
"spec": {
|
||||
"__legacyStringValue": "label_values(up, job)"
|
||||
}
|
||||
@@ -992,12 +988,8 @@
|
||||
"hide": "dontHide",
|
||||
"refresh": "onDashboardLoad",
|
||||
"skipUrlSync": false,
|
||||
"datasource": {
|
||||
"type": "",
|
||||
"uid": "$datasource"
|
||||
},
|
||||
"query": {
|
||||
"kind": "",
|
||||
"kind": "prometheus",
|
||||
"spec": {
|
||||
"__legacyStringValue": "label_values(up{job=~\"$cluster\"}, instance)"
|
||||
}
|
||||
|
||||
+2
-8
@@ -978,11 +978,8 @@
|
||||
"skipUrlSync": false,
|
||||
"query": {
|
||||
"kind": "DataQuery",
|
||||
"group": "",
|
||||
"group": "prometheus",
|
||||
"version": "v0",
|
||||
"datasource": {
|
||||
"name": "$datasource"
|
||||
},
|
||||
"spec": {
|
||||
"__legacyStringValue": "label_values(up, job)"
|
||||
}
|
||||
@@ -1010,11 +1007,8 @@
|
||||
"skipUrlSync": false,
|
||||
"query": {
|
||||
"kind": "DataQuery",
|
||||
"group": "",
|
||||
"group": "prometheus",
|
||||
"version": "v0",
|
||||
"datasource": {
|
||||
"name": "$datasource"
|
||||
},
|
||||
"spec": {
|
||||
"__legacyStringValue": "label_values(up{job=~\"$cluster\"}, instance)"
|
||||
}
|
||||
|
||||
+1
-8
@@ -115,14 +115,7 @@
|
||||
"kind": "logs",
|
||||
"spec": {
|
||||
"pluginVersion": "",
|
||||
"options": {
|
||||
"__angularMigration": {
|
||||
"autoMigrateFrom": "logs",
|
||||
"originalOptions": {
|
||||
"height": 100
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {},
|
||||
"fieldConfig": {
|
||||
"defaults": {},
|
||||
"overrides": []
|
||||
|
||||
+1
-8
@@ -120,14 +120,7 @@
|
||||
"group": "logs",
|
||||
"version": "",
|
||||
"spec": {
|
||||
"options": {
|
||||
"__angularMigration": {
|
||||
"autoMigrateFrom": "logs",
|
||||
"originalOptions": {
|
||||
"height": 100
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {},
|
||||
"fieldConfig": {
|
||||
"defaults": {},
|
||||
"overrides": []
|
||||
|
||||
+1
-14
@@ -182,20 +182,7 @@
|
||||
"kind": "table",
|
||||
"spec": {
|
||||
"pluginVersion": "",
|
||||
"options": {
|
||||
"__angularMigration": {
|
||||
"autoMigrateFrom": "table",
|
||||
"originalOptions": {
|
||||
"grid": {
|
||||
"max": 100,
|
||||
"min": 0
|
||||
},
|
||||
"legend": true,
|
||||
"y2_format": "bytes",
|
||||
"y_format": "short"
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {},
|
||||
"fieldConfig": {
|
||||
"defaults": {},
|
||||
"overrides": []
|
||||
|
||||
+1
-14
@@ -189,20 +189,7 @@
|
||||
"group": "table",
|
||||
"version": "",
|
||||
"spec": {
|
||||
"options": {
|
||||
"__angularMigration": {
|
||||
"autoMigrateFrom": "table",
|
||||
"originalOptions": {
|
||||
"grid": {
|
||||
"max": 100,
|
||||
"min": 0
|
||||
},
|
||||
"legend": true,
|
||||
"y2_format": "bytes",
|
||||
"y_format": "short"
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {},
|
||||
"fieldConfig": {
|
||||
"defaults": {},
|
||||
"overrides": []
|
||||
|
||||
+1
-23
@@ -435,29 +435,7 @@
|
||||
"kind": "table",
|
||||
"spec": {
|
||||
"pluginVersion": "",
|
||||
"options": {
|
||||
"__angularMigration": {
|
||||
"autoMigrateFrom": "table",
|
||||
"originalOptions": {
|
||||
"styles": [
|
||||
{
|
||||
"colors": [
|
||||
"red",
|
||||
"yellow",
|
||||
"green"
|
||||
],
|
||||
"pattern": "/.*/",
|
||||
"thresholds": [
|
||||
"10",
|
||||
"20"
|
||||
],
|
||||
"unit": "short"
|
||||
}
|
||||
],
|
||||
"table": "table2"
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {},
|
||||
"fieldConfig": {
|
||||
"defaults": {},
|
||||
"overrides": []
|
||||
|
||||
+1
-23
@@ -449,29 +449,7 @@
|
||||
"group": "table",
|
||||
"version": "",
|
||||
"spec": {
|
||||
"options": {
|
||||
"__angularMigration": {
|
||||
"autoMigrateFrom": "table",
|
||||
"originalOptions": {
|
||||
"styles": [
|
||||
{
|
||||
"colors": [
|
||||
"red",
|
||||
"yellow",
|
||||
"green"
|
||||
],
|
||||
"pattern": "/.*/",
|
||||
"thresholds": [
|
||||
"10",
|
||||
"20"
|
||||
],
|
||||
"unit": "short"
|
||||
}
|
||||
],
|
||||
"table": "table2"
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {},
|
||||
"fieldConfig": {
|
||||
"defaults": {},
|
||||
"overrides": []
|
||||
|
||||
+1
-9
@@ -110,15 +110,7 @@
|
||||
"kind": "text",
|
||||
"spec": {
|
||||
"pluginVersion": "",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {},
|
||||
"fieldConfig": {
|
||||
"defaults": {},
|
||||
"overrides": []
|
||||
|
||||
+1
-9
@@ -115,15 +115,7 @@
|
||||
"group": "text",
|
||||
"version": "",
|
||||
"spec": {
|
||||
"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"
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {},
|
||||
"fieldConfig": {
|
||||
"defaults": {},
|
||||
"overrides": []
|
||||
|
||||
+1
-9
@@ -361,15 +361,7 @@
|
||||
"kind": "text",
|
||||
"spec": {
|
||||
"pluginVersion": "",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {},
|
||||
"fieldConfig": {
|
||||
"defaults": {},
|
||||
"overrides": []
|
||||
|
||||
+1
-9
@@ -372,15 +372,7 @@
|
||||
"group": "text",
|
||||
"version": "",
|
||||
"spec": {
|
||||
"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"
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {},
|
||||
"fieldConfig": {
|
||||
"defaults": {},
|
||||
"overrides": []
|
||||
|
||||
+1
-9
@@ -167,15 +167,7 @@
|
||||
"kind": "text",
|
||||
"spec": {
|
||||
"pluginVersion": "",
|
||||
"options": {
|
||||
"__angularMigration": {
|
||||
"autoMigrateFrom": "text",
|
||||
"originalOptions": {
|
||||
"content": "## Data center = $datacenter\n\n### server = $server\n\n#### pod = $pod",
|
||||
"mode": "markdown"
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {},
|
||||
"fieldConfig": {
|
||||
"defaults": {},
|
||||
"overrides": []
|
||||
|
||||
+1
-9
@@ -174,15 +174,7 @@
|
||||
"group": "text",
|
||||
"version": "",
|
||||
"spec": {
|
||||
"options": {
|
||||
"__angularMigration": {
|
||||
"autoMigrateFrom": "text",
|
||||
"originalOptions": {
|
||||
"content": "## Data center = $datacenter\n\n### server = $server\n\n#### pod = $pod",
|
||||
"mode": "markdown"
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {},
|
||||
"fieldConfig": {
|
||||
"defaults": {},
|
||||
"overrides": []
|
||||
|
||||
+1
-9
@@ -273,15 +273,7 @@
|
||||
"kind": "text",
|
||||
"spec": {
|
||||
"pluginVersion": "",
|
||||
"options": {
|
||||
"__angularMigration": {
|
||||
"autoMigrateFrom": "text",
|
||||
"originalOptions": {
|
||||
"content": "## Data center = $datacenter\n\n### server = $server\n\n#### pod = $pod",
|
||||
"mode": "markdown"
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {},
|
||||
"fieldConfig": {
|
||||
"defaults": {},
|
||||
"overrides": []
|
||||
|
||||
+1
-9
@@ -282,15 +282,7 @@
|
||||
"group": "text",
|
||||
"version": "",
|
||||
"spec": {
|
||||
"options": {
|
||||
"__angularMigration": {
|
||||
"autoMigrateFrom": "text",
|
||||
"originalOptions": {
|
||||
"content": "## Data center = $datacenter\n\n### server = $server\n\n#### pod = $pod",
|
||||
"mode": "markdown"
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {},
|
||||
"fieldConfig": {
|
||||
"defaults": {},
|
||||
"overrides": []
|
||||
|
||||
+9
-2
@@ -296,7 +296,6 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"content": "# Graph panel \u003e\u003e Timeseries panel\n\nKnown issues:\n* hiding null/empty series\n* time regions",
|
||||
"datasource": {
|
||||
"type": "grafana-testdata-datasource"
|
||||
},
|
||||
@@ -307,7 +306,15 @@
|
||||
"y": 0
|
||||
},
|
||||
"id": 6,
|
||||
"mode": "markdown",
|
||||
"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"
|
||||
},
|
||||
"pluginVersion": "11.0.0-pre",
|
||||
"targets": [
|
||||
{
|
||||
|
||||
+7
-7
@@ -1256,13 +1256,13 @@
|
||||
"spec": {
|
||||
"pluginVersion": "11.0.0-pre",
|
||||
"options": {
|
||||
"__angularMigration": {
|
||||
"autoMigrateFrom": "text",
|
||||
"originalOptions": {
|
||||
"content": "# Graph panel \u003e\u003e Timeseries panel\n\nKnown issues:\n* hiding null/empty series\n* time regions",
|
||||
"mode": "markdown"
|
||||
}
|
||||
}
|
||||
"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"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {},
|
||||
|
||||
+7
-7
@@ -1301,13 +1301,13 @@
|
||||
"version": "11.0.0-pre",
|
||||
"spec": {
|
||||
"options": {
|
||||
"__angularMigration": {
|
||||
"autoMigrateFrom": "text",
|
||||
"originalOptions": {
|
||||
"content": "# Graph panel \u003e\u003e Timeseries panel\n\nKnown issues:\n* hiding null/empty series\n* time regions",
|
||||
"mode": "markdown"
|
||||
}
|
||||
}
|
||||
"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"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {},
|
||||
|
||||
-72
@@ -62,12 +62,6 @@
|
||||
"spec": {
|
||||
"pluginVersion": "7.4.0-pre",
|
||||
"options": {
|
||||
"__angularMigration": {
|
||||
"autoMigrateFrom": "gauge",
|
||||
"originalOptions": {
|
||||
"nullPointMode": "null"
|
||||
}
|
||||
},
|
||||
"baseColor": "#299c46",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
@@ -157,12 +151,6 @@
|
||||
"spec": {
|
||||
"pluginVersion": "7.4.0-pre",
|
||||
"options": {
|
||||
"__angularMigration": {
|
||||
"autoMigrateFrom": "gauge",
|
||||
"originalOptions": {
|
||||
"nullPointMode": "null"
|
||||
}
|
||||
},
|
||||
"baseColor": "#299c46",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
@@ -253,12 +241,6 @@
|
||||
"spec": {
|
||||
"pluginVersion": "7.4.0-pre",
|
||||
"options": {
|
||||
"__angularMigration": {
|
||||
"autoMigrateFrom": "gauge",
|
||||
"originalOptions": {
|
||||
"nullPointMode": "null"
|
||||
}
|
||||
},
|
||||
"baseColor": "#299c46",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
@@ -338,12 +320,6 @@
|
||||
"spec": {
|
||||
"pluginVersion": "7.4.0-pre",
|
||||
"options": {
|
||||
"__angularMigration": {
|
||||
"autoMigrateFrom": "gauge",
|
||||
"originalOptions": {
|
||||
"nullPointMode": "null"
|
||||
}
|
||||
},
|
||||
"baseColor": "#299c46",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
@@ -425,12 +401,6 @@
|
||||
"spec": {
|
||||
"pluginVersion": "7.4.0-pre",
|
||||
"options": {
|
||||
"__angularMigration": {
|
||||
"autoMigrateFrom": "gauge",
|
||||
"originalOptions": {
|
||||
"nullPointMode": "null"
|
||||
}
|
||||
},
|
||||
"baseColor": "#299c46",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
@@ -510,12 +480,6 @@
|
||||
"spec": {
|
||||
"pluginVersion": "7.4.0-pre",
|
||||
"options": {
|
||||
"__angularMigration": {
|
||||
"autoMigrateFrom": "gauge",
|
||||
"originalOptions": {
|
||||
"nullPointMode": "null"
|
||||
}
|
||||
},
|
||||
"baseColor": "#299c46",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
@@ -595,12 +559,6 @@
|
||||
"spec": {
|
||||
"pluginVersion": "7.4.0-pre",
|
||||
"options": {
|
||||
"__angularMigration": {
|
||||
"autoMigrateFrom": "gauge",
|
||||
"originalOptions": {
|
||||
"nullPointMode": "null"
|
||||
}
|
||||
},
|
||||
"baseColor": "#299c46",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
@@ -681,12 +639,6 @@
|
||||
"spec": {
|
||||
"pluginVersion": "7.4.0-pre",
|
||||
"options": {
|
||||
"__angularMigration": {
|
||||
"autoMigrateFrom": "gauge",
|
||||
"originalOptions": {
|
||||
"nullPointMode": "null"
|
||||
}
|
||||
},
|
||||
"baseColor": "#299c46",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
@@ -778,12 +730,6 @@
|
||||
"spec": {
|
||||
"pluginVersion": "7.4.0-pre",
|
||||
"options": {
|
||||
"__angularMigration": {
|
||||
"autoMigrateFrom": "gauge",
|
||||
"originalOptions": {
|
||||
"nullPointMode": "null"
|
||||
}
|
||||
},
|
||||
"baseColor": "#299c46",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
@@ -981,12 +927,6 @@
|
||||
"spec": {
|
||||
"pluginVersion": "7.4.0-pre",
|
||||
"options": {
|
||||
"__angularMigration": {
|
||||
"autoMigrateFrom": "gauge",
|
||||
"originalOptions": {
|
||||
"nullPointMode": "null"
|
||||
}
|
||||
},
|
||||
"baseColor": "#299c46",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
@@ -1066,12 +1006,6 @@
|
||||
"spec": {
|
||||
"pluginVersion": "7.4.0-pre",
|
||||
"options": {
|
||||
"__angularMigration": {
|
||||
"autoMigrateFrom": "gauge",
|
||||
"originalOptions": {
|
||||
"nullPointMode": "null"
|
||||
}
|
||||
},
|
||||
"baseColor": "#299c46",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
@@ -1151,12 +1085,6 @@
|
||||
"spec": {
|
||||
"pluginVersion": "7.4.0-pre",
|
||||
"options": {
|
||||
"__angularMigration": {
|
||||
"autoMigrateFrom": "gauge",
|
||||
"originalOptions": {
|
||||
"nullPointMode": "null"
|
||||
}
|
||||
},
|
||||
"baseColor": "#299c46",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
|
||||
-72
@@ -67,12 +67,6 @@
|
||||
"version": "7.4.0-pre",
|
||||
"spec": {
|
||||
"options": {
|
||||
"__angularMigration": {
|
||||
"autoMigrateFrom": "gauge",
|
||||
"originalOptions": {
|
||||
"nullPointMode": "null"
|
||||
}
|
||||
},
|
||||
"baseColor": "#299c46",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
@@ -165,12 +159,6 @@
|
||||
"version": "7.4.0-pre",
|
||||
"spec": {
|
||||
"options": {
|
||||
"__angularMigration": {
|
||||
"autoMigrateFrom": "gauge",
|
||||
"originalOptions": {
|
||||
"nullPointMode": "null"
|
||||
}
|
||||
},
|
||||
"baseColor": "#299c46",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
@@ -264,12 +252,6 @@
|
||||
"version": "7.4.0-pre",
|
||||
"spec": {
|
||||
"options": {
|
||||
"__angularMigration": {
|
||||
"autoMigrateFrom": "gauge",
|
||||
"originalOptions": {
|
||||
"nullPointMode": "null"
|
||||
}
|
||||
},
|
||||
"baseColor": "#299c46",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
@@ -352,12 +334,6 @@
|
||||
"version": "7.4.0-pre",
|
||||
"spec": {
|
||||
"options": {
|
||||
"__angularMigration": {
|
||||
"autoMigrateFrom": "gauge",
|
||||
"originalOptions": {
|
||||
"nullPointMode": "null"
|
||||
}
|
||||
},
|
||||
"baseColor": "#299c46",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
@@ -442,12 +418,6 @@
|
||||
"version": "7.4.0-pre",
|
||||
"spec": {
|
||||
"options": {
|
||||
"__angularMigration": {
|
||||
"autoMigrateFrom": "gauge",
|
||||
"originalOptions": {
|
||||
"nullPointMode": "null"
|
||||
}
|
||||
},
|
||||
"baseColor": "#299c46",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
@@ -530,12 +500,6 @@
|
||||
"version": "7.4.0-pre",
|
||||
"spec": {
|
||||
"options": {
|
||||
"__angularMigration": {
|
||||
"autoMigrateFrom": "gauge",
|
||||
"originalOptions": {
|
||||
"nullPointMode": "null"
|
||||
}
|
||||
},
|
||||
"baseColor": "#299c46",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
@@ -618,12 +582,6 @@
|
||||
"version": "7.4.0-pre",
|
||||
"spec": {
|
||||
"options": {
|
||||
"__angularMigration": {
|
||||
"autoMigrateFrom": "gauge",
|
||||
"originalOptions": {
|
||||
"nullPointMode": "null"
|
||||
}
|
||||
},
|
||||
"baseColor": "#299c46",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
@@ -707,12 +665,6 @@
|
||||
"version": "7.4.0-pre",
|
||||
"spec": {
|
||||
"options": {
|
||||
"__angularMigration": {
|
||||
"autoMigrateFrom": "gauge",
|
||||
"originalOptions": {
|
||||
"nullPointMode": "null"
|
||||
}
|
||||
},
|
||||
"baseColor": "#299c46",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
@@ -807,12 +759,6 @@
|
||||
"version": "7.4.0-pre",
|
||||
"spec": {
|
||||
"options": {
|
||||
"__angularMigration": {
|
||||
"autoMigrateFrom": "gauge",
|
||||
"originalOptions": {
|
||||
"nullPointMode": "null"
|
||||
}
|
||||
},
|
||||
"baseColor": "#299c46",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
@@ -1015,12 +961,6 @@
|
||||
"version": "7.4.0-pre",
|
||||
"spec": {
|
||||
"options": {
|
||||
"__angularMigration": {
|
||||
"autoMigrateFrom": "gauge",
|
||||
"originalOptions": {
|
||||
"nullPointMode": "null"
|
||||
}
|
||||
},
|
||||
"baseColor": "#299c46",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
@@ -1103,12 +1043,6 @@
|
||||
"version": "7.4.0-pre",
|
||||
"spec": {
|
||||
"options": {
|
||||
"__angularMigration": {
|
||||
"autoMigrateFrom": "gauge",
|
||||
"originalOptions": {
|
||||
"nullPointMode": "null"
|
||||
}
|
||||
},
|
||||
"baseColor": "#299c46",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
@@ -1191,12 +1125,6 @@
|
||||
"version": "7.4.0-pre",
|
||||
"spec": {
|
||||
"options": {
|
||||
"__angularMigration": {
|
||||
"autoMigrateFrom": "gauge",
|
||||
"originalOptions": {
|
||||
"nullPointMode": "null"
|
||||
}
|
||||
},
|
||||
"baseColor": "#299c46",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
|
||||
+6
-66
@@ -412,17 +412,7 @@
|
||||
"kind": "text",
|
||||
"spec": {
|
||||
"pluginVersion": "",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {},
|
||||
"fieldConfig": {
|
||||
"defaults": {},
|
||||
"overrides": []
|
||||
@@ -466,17 +456,7 @@
|
||||
"kind": "text",
|
||||
"spec": {
|
||||
"pluginVersion": "",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {},
|
||||
"fieldConfig": {
|
||||
"defaults": {},
|
||||
"overrides": []
|
||||
@@ -520,17 +500,7 @@
|
||||
"kind": "text",
|
||||
"spec": {
|
||||
"pluginVersion": "",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {},
|
||||
"fieldConfig": {
|
||||
"defaults": {},
|
||||
"overrides": []
|
||||
@@ -1711,17 +1681,7 @@
|
||||
"kind": "text",
|
||||
"spec": {
|
||||
"pluginVersion": "",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {},
|
||||
"fieldConfig": {
|
||||
"defaults": {},
|
||||
"overrides": []
|
||||
@@ -2101,17 +2061,7 @@
|
||||
"kind": "text",
|
||||
"spec": {
|
||||
"pluginVersion": "",
|
||||
"options": {
|
||||
"__angularMigration": {
|
||||
"autoMigrateFrom": "text",
|
||||
"originalOptions": {
|
||||
"content": "Just verify that the tooltip time has millisecond resolution ",
|
||||
"editable": true,
|
||||
"error": false,
|
||||
"mode": "markdown"
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {},
|
||||
"fieldConfig": {
|
||||
"defaults": {},
|
||||
"overrides": []
|
||||
@@ -2155,17 +2105,7 @@
|
||||
"kind": "text",
|
||||
"spec": {
|
||||
"pluginVersion": "",
|
||||
"options": {
|
||||
"__angularMigration": {
|
||||
"autoMigrateFrom": "text",
|
||||
"originalOptions": {
|
||||
"content": "Verify that axis labels look ok",
|
||||
"editable": true,
|
||||
"error": false,
|
||||
"mode": "markdown"
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {},
|
||||
"fieldConfig": {
|
||||
"defaults": {},
|
||||
"overrides": []
|
||||
|
||||
+6
-66
@@ -429,17 +429,7 @@
|
||||
"group": "text",
|
||||
"version": "",
|
||||
"spec": {
|
||||
"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"
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {},
|
||||
"fieldConfig": {
|
||||
"defaults": {},
|
||||
"overrides": []
|
||||
@@ -485,17 +475,7 @@
|
||||
"group": "text",
|
||||
"version": "",
|
||||
"spec": {
|
||||
"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"
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {},
|
||||
"fieldConfig": {
|
||||
"defaults": {},
|
||||
"overrides": []
|
||||
@@ -541,17 +521,7 @@
|
||||
"group": "text",
|
||||
"version": "",
|
||||
"spec": {
|
||||
"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"
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {},
|
||||
"fieldConfig": {
|
||||
"defaults": {},
|
||||
"overrides": []
|
||||
@@ -1809,17 +1779,7 @@
|
||||
"group": "text",
|
||||
"version": "",
|
||||
"spec": {
|
||||
"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"
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {},
|
||||
"fieldConfig": {
|
||||
"defaults": {},
|
||||
"overrides": []
|
||||
@@ -2212,17 +2172,7 @@
|
||||
"group": "text",
|
||||
"version": "",
|
||||
"spec": {
|
||||
"options": {
|
||||
"__angularMigration": {
|
||||
"autoMigrateFrom": "text",
|
||||
"originalOptions": {
|
||||
"content": "Just verify that the tooltip time has millisecond resolution ",
|
||||
"editable": true,
|
||||
"error": false,
|
||||
"mode": "markdown"
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {},
|
||||
"fieldConfig": {
|
||||
"defaults": {},
|
||||
"overrides": []
|
||||
@@ -2268,17 +2218,7 @@
|
||||
"group": "text",
|
||||
"version": "",
|
||||
"spec": {
|
||||
"options": {
|
||||
"__angularMigration": {
|
||||
"autoMigrateFrom": "text",
|
||||
"originalOptions": {
|
||||
"content": "Verify that axis labels look ok",
|
||||
"editable": true,
|
||||
"error": false,
|
||||
"mode": "markdown"
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {},
|
||||
"fieldConfig": {
|
||||
"defaults": {},
|
||||
"overrides": []
|
||||
|
||||
+6
-232
@@ -74,44 +74,7 @@
|
||||
"kind": "heatmap",
|
||||
"spec": {
|
||||
"pluginVersion": "",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {},
|
||||
"fieldConfig": {
|
||||
"defaults": {},
|
||||
"overrides": []
|
||||
@@ -153,46 +116,7 @@
|
||||
"kind": "heatmap",
|
||||
"spec": {
|
||||
"pluginVersion": "",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {},
|
||||
"fieldConfig": {
|
||||
"defaults": {},
|
||||
"overrides": []
|
||||
@@ -234,44 +158,7 @@
|
||||
"kind": "heatmap",
|
||||
"spec": {
|
||||
"pluginVersion": "",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {},
|
||||
"fieldConfig": {
|
||||
"defaults": {},
|
||||
"overrides": []
|
||||
@@ -317,46 +204,7 @@
|
||||
"kind": "heatmap",
|
||||
"spec": {
|
||||
"pluginVersion": "",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {},
|
||||
"fieldConfig": {
|
||||
"defaults": {},
|
||||
"overrides": []
|
||||
@@ -398,44 +246,7 @@
|
||||
"kind": "heatmap",
|
||||
"spec": {
|
||||
"pluginVersion": "",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {},
|
||||
"fieldConfig": {
|
||||
"defaults": {},
|
||||
"overrides": []
|
||||
@@ -477,44 +288,7 @@
|
||||
"kind": "heatmap",
|
||||
"spec": {
|
||||
"pluginVersion": "",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {},
|
||||
"fieldConfig": {
|
||||
"defaults": {},
|
||||
"overrides": []
|
||||
|
||||
+6
-232
@@ -78,44 +78,7 @@
|
||||
"group": "heatmap",
|
||||
"version": "",
|
||||
"spec": {
|
||||
"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"
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {},
|
||||
"fieldConfig": {
|
||||
"defaults": {},
|
||||
"overrides": []
|
||||
@@ -160,46 +123,7 @@
|
||||
"group": "heatmap",
|
||||
"version": "",
|
||||
"spec": {
|
||||
"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"
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {},
|
||||
"fieldConfig": {
|
||||
"defaults": {},
|
||||
"overrides": []
|
||||
@@ -244,44 +168,7 @@
|
||||
"group": "heatmap",
|
||||
"version": "",
|
||||
"spec": {
|
||||
"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"
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {},
|
||||
"fieldConfig": {
|
||||
"defaults": {},
|
||||
"overrides": []
|
||||
@@ -329,46 +216,7 @@
|
||||
"group": "heatmap",
|
||||
"version": "",
|
||||
"spec": {
|
||||
"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"
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {},
|
||||
"fieldConfig": {
|
||||
"defaults": {},
|
||||
"overrides": []
|
||||
@@ -413,44 +261,7 @@
|
||||
"group": "heatmap",
|
||||
"version": "",
|
||||
"spec": {
|
||||
"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"
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {},
|
||||
"fieldConfig": {
|
||||
"defaults": {},
|
||||
"overrides": []
|
||||
@@ -495,44 +306,7 @@
|
||||
"group": "heatmap",
|
||||
"version": "",
|
||||
"spec": {
|
||||
"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"
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {},
|
||||
"fieldConfig": {
|
||||
"defaults": {},
|
||||
"overrides": []
|
||||
|
||||
+3
-3274
File diff suppressed because it is too large
Load Diff
+3
-3274
File diff suppressed because it is too large
Load Diff
+1
-36
@@ -665,42 +665,7 @@
|
||||
"kind": "heatmap",
|
||||
"spec": {
|
||||
"pluginVersion": "",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {},
|
||||
"fieldConfig": {
|
||||
"defaults": {},
|
||||
"overrides": []
|
||||
|
||||
+1
-36
@@ -691,42 +691,7 @@
|
||||
"group": "heatmap",
|
||||
"version": "",
|
||||
"spec": {
|
||||
"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"
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {},
|
||||
"fieldConfig": {
|
||||
"defaults": {},
|
||||
"overrides": []
|
||||
|
||||
-50
@@ -56,14 +56,6 @@
|
||||
"spec": {
|
||||
"pluginVersion": "9.0.0-pre",
|
||||
"options": {
|
||||
"__angularMigration": {
|
||||
"autoMigrateFrom": "dashlist",
|
||||
"originalOptions": {
|
||||
"tags": [
|
||||
"panel-tests"
|
||||
]
|
||||
}
|
||||
},
|
||||
"maxItems": 1000,
|
||||
"query": "",
|
||||
"showHeadings": false,
|
||||
@@ -102,15 +94,6 @@
|
||||
"spec": {
|
||||
"pluginVersion": "9.0.0-pre",
|
||||
"options": {
|
||||
"__angularMigration": {
|
||||
"autoMigrateFrom": "dashlist",
|
||||
"originalOptions": {
|
||||
"tags": [
|
||||
"gdev",
|
||||
"demo"
|
||||
]
|
||||
}
|
||||
},
|
||||
"maxItems": 1000,
|
||||
"query": "",
|
||||
"showHeadings": false,
|
||||
@@ -150,15 +133,6 @@
|
||||
"spec": {
|
||||
"pluginVersion": "9.0.0-pre",
|
||||
"options": {
|
||||
"__angularMigration": {
|
||||
"autoMigrateFrom": "dashlist",
|
||||
"originalOptions": {
|
||||
"tags": [
|
||||
"templating",
|
||||
"gdev"
|
||||
]
|
||||
}
|
||||
},
|
||||
"maxItems": 1000,
|
||||
"query": "",
|
||||
"showHeadings": false,
|
||||
@@ -198,15 +172,6 @@
|
||||
"spec": {
|
||||
"pluginVersion": "9.0.0-pre",
|
||||
"options": {
|
||||
"__angularMigration": {
|
||||
"autoMigrateFrom": "dashlist",
|
||||
"originalOptions": {
|
||||
"tags": [
|
||||
"gdev",
|
||||
"datasource-test"
|
||||
]
|
||||
}
|
||||
},
|
||||
"maxItems": 1000,
|
||||
"query": "",
|
||||
"showHeadings": false,
|
||||
@@ -246,12 +211,6 @@
|
||||
"spec": {
|
||||
"pluginVersion": "9.0.0-pre",
|
||||
"options": {
|
||||
"__angularMigration": {
|
||||
"autoMigrateFrom": "dashlist",
|
||||
"originalOptions": {
|
||||
"tags": []
|
||||
}
|
||||
},
|
||||
"maxItems": 100,
|
||||
"query": "",
|
||||
"showHeadings": true,
|
||||
@@ -288,15 +247,6 @@
|
||||
"spec": {
|
||||
"pluginVersion": "9.0.0-pre",
|
||||
"options": {
|
||||
"__angularMigration": {
|
||||
"autoMigrateFrom": "dashlist",
|
||||
"originalOptions": {
|
||||
"tags": [
|
||||
"gdev",
|
||||
"demo"
|
||||
]
|
||||
}
|
||||
},
|
||||
"maxItems": 1000,
|
||||
"query": "",
|
||||
"showHeadings": false,
|
||||
|
||||
-50
@@ -58,14 +58,6 @@
|
||||
"version": "9.0.0-pre",
|
||||
"spec": {
|
||||
"options": {
|
||||
"__angularMigration": {
|
||||
"autoMigrateFrom": "dashlist",
|
||||
"originalOptions": {
|
||||
"tags": [
|
||||
"panel-tests"
|
||||
]
|
||||
}
|
||||
},
|
||||
"maxItems": 1000,
|
||||
"query": "",
|
||||
"showHeadings": false,
|
||||
@@ -105,15 +97,6 @@
|
||||
"version": "9.0.0-pre",
|
||||
"spec": {
|
||||
"options": {
|
||||
"__angularMigration": {
|
||||
"autoMigrateFrom": "dashlist",
|
||||
"originalOptions": {
|
||||
"tags": [
|
||||
"gdev",
|
||||
"demo"
|
||||
]
|
||||
}
|
||||
},
|
||||
"maxItems": 1000,
|
||||
"query": "",
|
||||
"showHeadings": false,
|
||||
@@ -154,15 +137,6 @@
|
||||
"version": "9.0.0-pre",
|
||||
"spec": {
|
||||
"options": {
|
||||
"__angularMigration": {
|
||||
"autoMigrateFrom": "dashlist",
|
||||
"originalOptions": {
|
||||
"tags": [
|
||||
"templating",
|
||||
"gdev"
|
||||
]
|
||||
}
|
||||
},
|
||||
"maxItems": 1000,
|
||||
"query": "",
|
||||
"showHeadings": false,
|
||||
@@ -203,15 +177,6 @@
|
||||
"version": "9.0.0-pre",
|
||||
"spec": {
|
||||
"options": {
|
||||
"__angularMigration": {
|
||||
"autoMigrateFrom": "dashlist",
|
||||
"originalOptions": {
|
||||
"tags": [
|
||||
"gdev",
|
||||
"datasource-test"
|
||||
]
|
||||
}
|
||||
},
|
||||
"maxItems": 1000,
|
||||
"query": "",
|
||||
"showHeadings": false,
|
||||
@@ -252,12 +217,6 @@
|
||||
"version": "9.0.0-pre",
|
||||
"spec": {
|
||||
"options": {
|
||||
"__angularMigration": {
|
||||
"autoMigrateFrom": "dashlist",
|
||||
"originalOptions": {
|
||||
"tags": []
|
||||
}
|
||||
},
|
||||
"maxItems": 100,
|
||||
"query": "",
|
||||
"showHeadings": true,
|
||||
@@ -295,15 +254,6 @@
|
||||
"version": "9.0.0-pre",
|
||||
"spec": {
|
||||
"options": {
|
||||
"__angularMigration": {
|
||||
"autoMigrateFrom": "dashlist",
|
||||
"originalOptions": {
|
||||
"tags": [
|
||||
"gdev",
|
||||
"demo"
|
||||
]
|
||||
}
|
||||
},
|
||||
"maxItems": 1000,
|
||||
"query": "",
|
||||
"showHeadings": false,
|
||||
|
||||
@@ -2,7 +2,6 @@ package conversion
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"k8s.io/apimachinery/pkg/conversion"
|
||||
"k8s.io/apiserver/pkg/endpoints/request"
|
||||
@@ -80,57 +79,5 @@ 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, "${")
|
||||
}
|
||||
|
||||
@@ -1185,10 +1185,6 @@ 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)
|
||||
}
|
||||
@@ -1536,10 +1532,6 @@ 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)
|
||||
}
|
||||
@@ -1717,10 +1709,6 @@ 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)
|
||||
}
|
||||
@@ -2308,24 +2296,20 @@ 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": originalOptions,
|
||||
"originalOptions": extractAngularOptions(panelMap),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+9
-2
@@ -290,7 +290,6 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"content": "# Graph panel \u003e\u003e Timeseries panel\n\nKnown issues:\n* hiding null/empty series\n* time regions",
|
||||
"datasource": {
|
||||
"type": "grafana-testdata-datasource"
|
||||
},
|
||||
@@ -301,7 +300,15 @@
|
||||
"y": 0
|
||||
},
|
||||
"id": 6,
|
||||
"mode": "markdown",
|
||||
"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"
|
||||
},
|
||||
"pluginVersion": "11.0.0-pre",
|
||||
"targets": [
|
||||
{
|
||||
|
||||
+2
-6
@@ -654,9 +654,7 @@
|
||||
"text": "prod",
|
||||
"value": "prod"
|
||||
},
|
||||
"datasource": {
|
||||
"uid": "$datasource"
|
||||
},
|
||||
"datasource": "$datasource",
|
||||
"hide": 0,
|
||||
"includeAll": true,
|
||||
"label": "cluster",
|
||||
@@ -679,9 +677,7 @@
|
||||
"text": "prod",
|
||||
"value": "prod"
|
||||
},
|
||||
"datasource": {
|
||||
"uid": "$datasource"
|
||||
},
|
||||
"datasource": "$datasource",
|
||||
"hide": 0,
|
||||
"includeAll": false,
|
||||
"label": "namespace",
|
||||
|
||||
Vendored
+2
-6
@@ -737,9 +737,7 @@
|
||||
"text": "prod",
|
||||
"value": "prod"
|
||||
},
|
||||
"datasource": {
|
||||
"uid": "$datasource"
|
||||
},
|
||||
"datasource": "$datasource",
|
||||
"hide": 0,
|
||||
"includeAll": true,
|
||||
"label": "cluster",
|
||||
@@ -760,9 +758,7 @@
|
||||
"text": "prod",
|
||||
"value": "prod"
|
||||
},
|
||||
"datasource": {
|
||||
"uid": "$datasource"
|
||||
},
|
||||
"datasource": "$datasource",
|
||||
"hide": 0,
|
||||
"includeAll": false,
|
||||
"label": "namespace",
|
||||
|
||||
Vendored
+2
-6
@@ -717,9 +717,7 @@
|
||||
"text": "prod",
|
||||
"value": "prod"
|
||||
},
|
||||
"datasource": {
|
||||
"uid": "$datasource"
|
||||
},
|
||||
"datasource": "$datasource",
|
||||
"hide": 0,
|
||||
"includeAll": true,
|
||||
"label": "cluster",
|
||||
@@ -741,9 +739,7 @@
|
||||
"text": "prod",
|
||||
"value": "prod"
|
||||
},
|
||||
"datasource": {
|
||||
"uid": "$datasource"
|
||||
},
|
||||
"datasource": "$datasource",
|
||||
"hide": 0,
|
||||
"includeAll": false,
|
||||
"label": "namespace",
|
||||
|
||||
@@ -335,9 +335,6 @@ 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 =
|
||||
|
||||
|
||||
@@ -322,9 +322,6 @@
|
||||
# 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 =
|
||||
|
||||
|
||||
@@ -299,9 +299,15 @@
|
||||
"y": 0
|
||||
},
|
||||
"id": 6,
|
||||
"options": {},
|
||||
"content": "# Graph panel >> Timeseries panel\n\nKnown issues:\n* hiding null/empty series\n* time regions",
|
||||
"mode": "markdown",
|
||||
"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"
|
||||
},
|
||||
"pluginVersion": "11.0.0-pre",
|
||||
"targets": [
|
||||
{
|
||||
|
||||
@@ -54,6 +54,18 @@ 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.
|
||||
@@ -62,12 +74,6 @@ When you enable SCIM in Grafana, the following requirements and restrictions app
|
||||
- Configure `userUID` SAML assertion in [Entra ID](/docs/grafana/<GRAFANA_VERSION>/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/<GRAFANA_VERSION>/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**.
|
||||
|
||||
@@ -642,12 +642,6 @@ 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.
|
||||
|
||||
+2
-2
@@ -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.
|
||||
- Open the **Saved queries** drop-down menu and click **Replace query** to reuse a [saved query](ref:saved-queries).
|
||||
- Click **Replace with saved query** to reuse a [saved query](ref:saved-queries).
|
||||
|
||||
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) To [save the query](ref:save-query) for reuse, click the **Save query** button (or icon).
|
||||
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.
|
||||
|
||||
|
||||
@@ -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.
|
||||
- Open the **Saved queries** drop-down menu and click **Replace query** to reuse a [saved query](ref:saved-queries).
|
||||
- Click **Replace with saved query** to reuse a [saved query](ref:saved-queries).
|
||||
|
||||
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) To [save the query](ref:save-query) for reuse, click the **Save query** button (or icon).
|
||||
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.
|
||||
|
||||
|
||||
@@ -71,9 +71,8 @@ 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.
|
||||
- **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.
|
||||
- **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.
|
||||
- **+ Add query** - Add an additional query.
|
||||
- **+ Add from saved queries** - Add an additional query by reusing a saved query.
|
||||
|
||||
|
||||
@@ -88,9 +88,8 @@ 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.
|
||||
- **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.
|
||||
- **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.
|
||||
- **+ Add query** - Add an additional query.
|
||||
- **+ Add from saved queries** - Add an additional query by reusing a saved query.
|
||||
|
||||
|
||||
@@ -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 open the **Saved queries** drop-down menu and click **Replace query** in the query editor:
|
||||
To access your saved queries, click **+ Add from saved queries** or **Replace with saved 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 query** in the **Saved queries** drop-down menu updates your existing query.
|
||||
Clicking **+ Add from saved queries** adds an additional query, while clicking **Replace with saved query** 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, open the **Saved queries** drop-down menu and click the **Save query** option:
|
||||
1. From the query editor, click the **Save query** icon:
|
||||
|
||||
{{< 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.
|
||||
- Open the **Saved queries** drop-down menu and click **Replace query** to reuse a saved query.
|
||||
- Click **Replace with saved 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** option in the **Saved queries** drop-down menu.
|
||||
1. (Optional) To [save the query](#save-a-query) for reuse, click the **Save query** button (or icon).
|
||||
1. (Optional) Click **+ Add query** or **Add from saved queries** to add more queries as needed.
|
||||
|
||||
1. Click **Run queries**.
|
||||
|
||||
@@ -1888,6 +1888,11 @@
|
||||
"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
|
||||
@@ -2868,11 +2873,6 @@
|
||||
"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
|
||||
@@ -4339,7 +4339,7 @@
|
||||
},
|
||||
"public/app/plugins/panel/heatmap/utils.ts": {
|
||||
"@typescript-eslint/consistent-type-assertions": {
|
||||
"count": 14
|
||||
"count": 16
|
||||
}
|
||||
},
|
||||
"public/app/plugins/panel/histogram/Histogram.tsx": {
|
||||
|
||||
+2
-2
@@ -295,8 +295,8 @@
|
||||
"@grafana/plugin-ui": "^0.11.1",
|
||||
"@grafana/prometheus": "workspace:*",
|
||||
"@grafana/runtime": "workspace:*",
|
||||
"@grafana/scenes": "6.52.0",
|
||||
"@grafana/scenes-react": "6.52.0",
|
||||
"@grafana/scenes": "^6.51.0",
|
||||
"@grafana/scenes-react": "^6.51.0",
|
||||
"@grafana/schema": "workspace:*",
|
||||
"@grafana/sql": "workspace:*",
|
||||
"@grafana/ui": "workspace:*",
|
||||
|
||||
@@ -289,7 +289,6 @@ export interface GrafanaConfig {
|
||||
rudderstackWriteKey: string;
|
||||
rudderstackDataPlaneUrl: string;
|
||||
rudderstackSdkUrl: string;
|
||||
rudderstackV3SdkUrl: string;
|
||||
rudderstackConfigUrl: string;
|
||||
rudderstackIntegrationsUrl: string;
|
||||
applicationInsightsConnectionString: string;
|
||||
|
||||
+5
-9
@@ -499,10 +499,6 @@ 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
|
||||
*/
|
||||
@@ -1169,11 +1165,6 @@ 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
|
||||
*/
|
||||
@@ -1198,6 +1189,11 @@ 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;
|
||||
|
||||
@@ -224,7 +224,6 @@ export class GrafanaBootConfig {
|
||||
rudderstackWriteKey?: string;
|
||||
rudderstackDataPlaneUrl?: string;
|
||||
rudderstackSdkUrl?: string;
|
||||
rudderstackV3SdkUrl?: string;
|
||||
rudderstackConfigUrl?: string;
|
||||
rudderstackIntegrationsUrl?: string;
|
||||
analyticsConsoleReporting = false;
|
||||
|
||||
Generated
-4
@@ -185,10 +185,6 @@ 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 {
|
||||
|
||||
@@ -200,7 +200,6 @@ 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"`
|
||||
|
||||
|
||||
@@ -229,7 +229,6 @@ 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,
|
||||
|
||||
+8
-23
@@ -54,8 +54,7 @@ func init() {
|
||||
}
|
||||
logger := level.NewFilter(format(os.Stderr), level.AllowInfo())
|
||||
root = newManager(logger)
|
||||
// Use default Info level during package initialization before config is loaded
|
||||
initAppSDKLogger(logger, slog.LevelInfo)
|
||||
initAppSDKLogger(logger)
|
||||
|
||||
RegisterContextualLogProvider(func(ctx context.Context) ([]any, bool) {
|
||||
pFromCtx := ctx.Value(logParamsContextKey{})
|
||||
@@ -81,7 +80,7 @@ func newManager(logger gokitlog.Logger) *logManager {
|
||||
}
|
||||
}
|
||||
|
||||
func (lm *logManager) initialize(loggers []logWithFilters, levelStr string) {
|
||||
func (lm *logManager) initialize(loggers []logWithFilters) {
|
||||
lm.mutex.Lock()
|
||||
defer lm.mutex.Unlock()
|
||||
|
||||
@@ -114,7 +113,7 @@ func (lm *logManager) initialize(loggers []logWithFilters, levelStr string) {
|
||||
lm.loggersByName[name].Swap(&compositeLogger{loggers: ctxLoggers})
|
||||
}
|
||||
|
||||
initAppSDKLogger(lm.ConcreteLogger, stringToSlogLevel(levelStr))
|
||||
initAppSDKLogger(lm.ConcreteLogger)
|
||||
}
|
||||
|
||||
func (lm *logManager) New(ctx ...any) *ConcreteLogger {
|
||||
@@ -515,7 +514,7 @@ func ReadLoggingConfig(modes []string, logsPath string, cfg *ini.File) error {
|
||||
configLoggers = append(configLoggers, handler)
|
||||
}
|
||||
if len(configLoggers) > 0 {
|
||||
root.initialize(configLoggers, defaultLevelName)
|
||||
root.initialize(configLoggers)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -552,22 +551,8 @@ func SetupConsoleLogger(level string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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))
|
||||
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))
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"log/slog"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
|
||||
@@ -103,8 +102,7 @@ func getColumns(fields []string) []*resourcepb.ResourceTableColumnDefinition {
|
||||
columns := getDefaultColumns()
|
||||
|
||||
for _, field := range fields {
|
||||
fieldName := strings.TrimPrefix(field, res.SEARCH_FIELD_PREFIX)
|
||||
if col, ok := builders.TeamSearchTableColumnDefinitions[fieldName]; ok {
|
||||
if col, ok := builders.TeamSearchTableColumnDefinitions[field]; ok {
|
||||
columns = append(columns, col)
|
||||
}
|
||||
}
|
||||
@@ -123,8 +121,7 @@ func getDefaultColumns() []*resourcepb.ResourceTableColumnDefinition {
|
||||
func createCells(t *team.TeamDTO, fields []string) [][]byte {
|
||||
cells := createDefaultCells(t)
|
||||
for _, field := range fields {
|
||||
fieldName := strings.TrimPrefix(field, res.SEARCH_FIELD_PREFIX)
|
||||
switch fieldName {
|
||||
switch field {
|
||||
case builders.TEAM_SEARCH_EMAIL:
|
||||
cells = append(cells, []byte(t.Email))
|
||||
case builders.TEAM_SEARCH_PROVISIONED:
|
||||
|
||||
@@ -2,7 +2,6 @@ package iam
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
@@ -13,7 +12,6 @@ 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"
|
||||
@@ -139,12 +137,6 @@ 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
|
||||
@@ -162,23 +154,16 @@ func (s *TeamSearchHandler) DoTeamSearch(w http.ResponseWriter, r *http.Request)
|
||||
}
|
||||
|
||||
searchRequest := &resourcepb.ResourceSearchRequest{
|
||||
Options: &resourcepb.ListOptions{
|
||||
Key: &resourcepb.ResourceKey{
|
||||
Group: iamv0alpha1.TeamResourceInfo.GroupResource().Group,
|
||||
Resource: iamv0alpha1.TeamResourceInfo.GroupResource().Resource,
|
||||
Namespace: requester.GetNamespace(),
|
||||
},
|
||||
},
|
||||
Options: &resourcepb.ListOptions{},
|
||||
Query: queryParams.Get("query"),
|
||||
Limit: int64(limit),
|
||||
Offset: int64(offset),
|
||||
Page: int64(page),
|
||||
Explain: queryParams.Has("explain") && queryParams.Get("explain") != "false",
|
||||
Fields: []string{
|
||||
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,
|
||||
builders.TEAM_SEARCH_EMAIL,
|
||||
builders.TEAM_SEARCH_PROVISIONED,
|
||||
builders.TEAM_SEARCH_EXTERNAL_UID,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -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{"title", "fields.email", "fields.provisioned", "fields.externalUID"}
|
||||
expectedFields := []string{"email", "provisioned", "externalUID"}
|
||||
if fmt.Sprintf("%v", mockClient.LastSearchRequest.Fields) != fmt.Sprintf("%v", expectedFields) {
|
||||
t.Errorf("expected fields %v, got %v", expectedFields, mockClient.LastSearchRequest.Fields)
|
||||
}
|
||||
|
||||
@@ -9,13 +9,10 @@ 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"
|
||||
@@ -75,15 +72,6 @@ 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)
|
||||
|
||||
@@ -23,7 +23,9 @@ 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 and/or removed entirely.
|
||||
// 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
|
||||
IsEnabledGlobally(flag string) bool
|
||||
|
||||
// Get the enabled flags -- this *may* also include disabled flags (with value false)
|
||||
|
||||
@@ -820,13 +820,6 @@ 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",
|
||||
@@ -1928,14 +1921,6 @@ 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;",
|
||||
@@ -1975,6 +1960,14 @@ 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",
|
||||
|
||||
Generated
+1
-2
@@ -113,7 +113,6 @@ 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
|
||||
@@ -262,12 +261,12 @@ 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
|
||||
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
|
||||
|
||||
|
Generated
-4
@@ -339,10 +339,6 @@ 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"
|
||||
|
||||
+14
-27
@@ -922,19 +922,6 @@
|
||||
"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",
|
||||
@@ -1647,20 +1634,6 @@
|
||||
"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",
|
||||
@@ -2720,6 +2693,20 @@
|
||||
"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",
|
||||
|
||||
@@ -32,7 +32,6 @@ 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"`
|
||||
|
||||
|
||||
@@ -94,7 +94,6 @@ 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,
|
||||
|
||||
@@ -414,7 +414,6 @@ type Cfg struct {
|
||||
RudderstackDataPlaneURL string
|
||||
RudderstackWriteKey string
|
||||
RudderstackSDKURL string
|
||||
RudderstackV3SDKURL string
|
||||
RudderstackConfigURL string
|
||||
RudderstackIntegrationsURL string
|
||||
IntercomSecret string
|
||||
@@ -1282,7 +1281,6 @@ 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()
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
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" }};
|
||||
@@ -34,10 +34,9 @@ func mustTemplate(filename string) *template.Template {
|
||||
|
||||
// Templates.
|
||||
var (
|
||||
sqlKVKeys = mustTemplate("sqlkv_keys.sql")
|
||||
sqlKVGet = mustTemplate("sqlkv_get.sql")
|
||||
sqlKVBatchGet = mustTemplate("sqlkv_batch_get.sql")
|
||||
sqlKVDelete = mustTemplate("sqlkv_delete.sql")
|
||||
sqlKVGet = mustTemplate("sqlkv_get.sql")
|
||||
sqlKVDelete = mustTemplate("sqlkv_delete.sql")
|
||||
sqlKVKeys = mustTemplate("sqlkv_keys.sql")
|
||||
)
|
||||
|
||||
// sqlKVSection can be embedded in structs used when rendering query templates
|
||||
@@ -108,23 +107,13 @@ func (req sqlKVGetRequest) Results() ([]byte, error) {
|
||||
return req.Value, nil
|
||||
}
|
||||
|
||||
type sqlKVBatchGetRequest struct {
|
||||
type sqlKVDeleteRequest struct {
|
||||
sqltemplate.SQLTemplate
|
||||
sqlKVSection
|
||||
Keys []string
|
||||
sqlKVSectionKey
|
||||
}
|
||||
|
||||
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
|
||||
func (req sqlKVDeleteRequest) Validate() error {
|
||||
return req.sqlKVSectionKey.Validate()
|
||||
}
|
||||
|
||||
type sqlKVKeysRequest struct {
|
||||
@@ -153,15 +142,6 @@ 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 {
|
||||
@@ -208,7 +188,6 @@ 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
|
||||
@@ -246,41 +225,7 @@ 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) {
|
||||
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))
|
||||
}
|
||||
panic("not implemented!")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -328,10 +273,3 @@ 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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,8 +46,8 @@ func GetTeamSearchBuilder() (resource.DocumentBuilderInfo, error) {
|
||||
|
||||
return resource.DocumentBuilderInfo{
|
||||
GroupResource: schema.GroupResource{
|
||||
Group: v0alpha1.TeamResourceInfo.GroupResource().Group,
|
||||
Resource: v0alpha1.TeamResourceInfo.GroupResource().Resource,
|
||||
Group: "iam.grafana.app",
|
||||
Resource: "searchTeams",
|
||||
},
|
||||
Fields: fields,
|
||||
Builder: new(teamSearchBuilder),
|
||||
|
||||
@@ -31,7 +31,6 @@ 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"
|
||||
)
|
||||
@@ -127,7 +126,7 @@ type backend struct {
|
||||
notifier eventNotifier
|
||||
|
||||
// resource version manager
|
||||
rvManager *rvmanager.ResourceVersionManager
|
||||
rvManager *resourceVersionManager
|
||||
|
||||
// testing
|
||||
simulatedNetworkLatency time.Duration
|
||||
@@ -164,7 +163,7 @@ func (b *backend) initLocked(ctx context.Context) error {
|
||||
}
|
||||
|
||||
// Initialize ResourceVersionManager
|
||||
rvManager, err := rvmanager.NewResourceVersionManager(rvmanager.ResourceManagerOptions{
|
||||
rvManager, err := NewResourceVersionManager(ResourceManagerOptions{
|
||||
Dialect: b.dialect,
|
||||
DB: b.db,
|
||||
})
|
||||
@@ -929,12 +928,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, rvmanager.SqlResourceVersionGet, rvmanager.SqlResourceVersionGetRequest{
|
||||
res, err := dbutil.QueryRow(ctx, x, sqlResourceVersionGet, sqlResourceVersionGetRequest{
|
||||
SQLTemplate: sqltemplate.New(d),
|
||||
Group: group,
|
||||
Resource: resource,
|
||||
ReadOnly: true,
|
||||
Response: new(rvmanager.ResourceVersionResponse),
|
||||
Response: new(resourceVersionResponse),
|
||||
})
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return 1, nil
|
||||
|
||||
@@ -40,26 +40,6 @@ 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))
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -17,7 +17,6 @@ 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"
|
||||
@@ -95,7 +94,7 @@ func TestIntegrationListIter(t *testing.T) {
|
||||
return fmt.Errorf("failed to insert test data: %w", err)
|
||||
}
|
||||
|
||||
if _, err = dbutil.Exec(ctx, tx, rvmanager.SqlResourceUpdateRV, rvmanager.SqlResourceUpdateRVRequest{
|
||||
if _, err = dbutil.Exec(ctx, tx, sqlResourceUpdateRV, sqlResourceUpdateRVRequest{
|
||||
SQLTemplate: sqltemplate.New(dialect),
|
||||
GUIDToRV: map[string]int64{
|
||||
item.guid: item.resourceVersion,
|
||||
|
||||
@@ -38,8 +38,10 @@ 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")
|
||||
@@ -49,7 +51,10 @@ var (
|
||||
sqlResourceInsertFromHistory = mustTemplate("resource_insert_from_history.sql")
|
||||
|
||||
// sqlResourceLabelsInsert = mustTemplate("resource_labels_insert.sql")
|
||||
sqlResourceVersionList = mustTemplate("resource_version_list.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")
|
||||
|
||||
sqlResourceBlobInsert = mustTemplate("resource_blob_insert.sql")
|
||||
sqlResourceBlobQuery = mustTemplate("resource_blob_query.sql")
|
||||
@@ -360,11 +365,76 @@ 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
|
||||
|
||||
@@ -8,7 +8,6 @@ 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"
|
||||
)
|
||||
|
||||
@@ -163,10 +162,10 @@ func TestUnifiedStorageQueries(t *testing.T) {
|
||||
},
|
||||
},
|
||||
|
||||
rvmanager.SqlResourceUpdateRV: {
|
||||
sqlResourceUpdateRV: {
|
||||
{
|
||||
Name: "single path",
|
||||
Data: &rvmanager.SqlResourceUpdateRVRequest{
|
||||
Data: &sqlResourceUpdateRVRequest{
|
||||
SQLTemplate: mocks.NewTestingSQLTemplate(),
|
||||
GUIDToRV: map[string]int64{
|
||||
"guid1": 123,
|
||||
@@ -229,10 +228,10 @@ func TestUnifiedStorageQueries(t *testing.T) {
|
||||
},
|
||||
},
|
||||
|
||||
rvmanager.SqlResourceHistoryUpdateRV: {
|
||||
sqlResourceHistoryUpdateRV: {
|
||||
{
|
||||
Name: "single path",
|
||||
Data: &rvmanager.SqlResourceUpdateRVRequest{
|
||||
Data: &sqlResourceUpdateRVRequest{
|
||||
SQLTemplate: mocks.NewTestingSQLTemplate(),
|
||||
GUIDToRV: map[string]int64{
|
||||
"guid1": 123,
|
||||
@@ -335,23 +334,23 @@ func TestUnifiedStorageQueries(t *testing.T) {
|
||||
},
|
||||
},
|
||||
|
||||
rvmanager.SqlResourceVersionGet: {
|
||||
sqlResourceVersionGet: {
|
||||
{
|
||||
Name: "single path",
|
||||
Data: &rvmanager.SqlResourceVersionGetRequest{
|
||||
Data: &sqlResourceVersionGetRequest{
|
||||
SQLTemplate: mocks.NewTestingSQLTemplate(),
|
||||
Resource: "resource",
|
||||
Group: "group",
|
||||
Response: new(rvmanager.ResourceVersionResponse),
|
||||
Response: new(resourceVersionResponse),
|
||||
ReadOnly: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
rvmanager.SqlResourceVersionUpdate: {
|
||||
sqlResourceVersionUpdate: {
|
||||
{
|
||||
Name: "increment resource version",
|
||||
Data: &rvmanager.SqlResourceVersionUpsertRequest{
|
||||
Data: &sqlResourceVersionUpsertRequest{
|
||||
SQLTemplate: mocks.NewTestingSQLTemplate(),
|
||||
Resource: "resource",
|
||||
Group: "group",
|
||||
@@ -360,10 +359,10 @@ func TestUnifiedStorageQueries(t *testing.T) {
|
||||
},
|
||||
},
|
||||
|
||||
rvmanager.SqlResourceVersionInsert: {
|
||||
sqlResourceVersionInsert: {
|
||||
{
|
||||
Name: "single path",
|
||||
Data: &rvmanager.SqlResourceVersionUpsertRequest{
|
||||
Data: &sqlResourceVersionUpsertRequest{
|
||||
SQLTemplate: mocks.NewTestingSQLTemplate(),
|
||||
ResourceVersion: int64(12354),
|
||||
},
|
||||
|
||||
+22
-29
@@ -1,4 +1,4 @@
|
||||
package rvmanager
|
||||
package sql
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -11,7 +11,6 @@ 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"
|
||||
|
||||
@@ -21,8 +20,6 @@ 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",
|
||||
@@ -65,8 +62,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
|
||||
@@ -103,7 +100,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
|
||||
}
|
||||
@@ -116,7 +113,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),
|
||||
@@ -126,7 +123,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()
|
||||
|
||||
@@ -182,7 +179,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)
|
||||
|
||||
@@ -219,11 +216,7 @@ func (m *ResourceVersionManager) startBatchProcessor(group, resource string) {
|
||||
}
|
||||
}
|
||||
|
||||
var readCommitted = &sql.TxOptions{
|
||||
Isolation: sql.LevelReadCommitted,
|
||||
}
|
||||
|
||||
func (m *ResourceVersionManager) execBatch(ctx context.Context, group, resource string, batch []writeOp) {
|
||||
func (m *resourceVersionManager) execBatch(ctx context.Context, group, resource string, batch []writeOp) {
|
||||
ctx, span := tracer.Start(ctx, "sql.resourceVersionManager.execBatch")
|
||||
defer span.End()
|
||||
|
||||
@@ -252,7 +245,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) {
|
||||
@@ -275,7 +268,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(
|
||||
@@ -299,7 +292,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 {
|
||||
@@ -310,7 +303,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,
|
||||
@@ -323,7 +316,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()),
|
||||
@@ -357,20 +350,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,
|
||||
@@ -379,11 +372,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 {
|
||||
@@ -397,8 +390,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,
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package rvmanager
|
||||
package sql
|
||||
|
||||
import (
|
||||
"testing"
|
||||
@@ -1,84 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
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")
|
||||
)
|
||||
@@ -103,7 +103,6 @@ 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
|
||||
@@ -222,7 +221,6 @@ 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
|
||||
@@ -264,7 +262,6 @@ 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"})
|
||||
@@ -363,7 +360,6 @@ 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"})
|
||||
@@ -420,7 +416,6 @@ 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"})
|
||||
@@ -624,29 +619,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))
|
||||
nsPrefix += "-batchget"
|
||||
section := nsPrefix + "-batchget"
|
||||
|
||||
t.Run("batch get existing keys", func(t *testing.T) {
|
||||
// Setup test data
|
||||
testData := map[string]string{
|
||||
namespacedKey(nsPrefix, "key1"): "value1",
|
||||
namespacedKey(nsPrefix, "key2"): "value2",
|
||||
namespacedKey(nsPrefix, "key3"): "value3",
|
||||
"key1": "value1",
|
||||
"key2": "value2",
|
||||
"key3": "value3",
|
||||
}
|
||||
|
||||
// Save test data
|
||||
for key, value := range testData {
|
||||
saveKVHelper(t, kv, ctx, testSection, key, strings.NewReader(value))
|
||||
saveKVHelper(t, kv, ctx, section, key, strings.NewReader(value))
|
||||
}
|
||||
|
||||
// Batch get all keys
|
||||
keys := namespacedKeys(nsPrefix, []string{"key1", "key2", "key3"})
|
||||
keys := []string{"key1", "key2", "key3"}
|
||||
type result struct {
|
||||
key string
|
||||
value string
|
||||
}
|
||||
var results []result
|
||||
for kv, err := range kv.BatchGet(ctx, testSection, keys) {
|
||||
for kv, err := range kv.BatchGet(ctx, section, keys) {
|
||||
require.NoError(t, err)
|
||||
value, err := io.ReadAll(kv.Value)
|
||||
require.NoError(t, err)
|
||||
@@ -656,10 +651,10 @@ func runTestKVBatchGet(t *testing.T, kv resource.KV, nsPrefix string) {
|
||||
}
|
||||
|
||||
// Verify results
|
||||
require.Len(t, results, 3)
|
||||
assert.Len(t, results, 3)
|
||||
|
||||
// Check that all keys are present and in order
|
||||
expectedKeys := namespacedKeys(nsPrefix, []string{"key1", "key2", "key3"})
|
||||
expectedKeys := []string{"key1", "key2", "key3"}
|
||||
actualKeys := make([]string, len(results))
|
||||
for i, r := range results {
|
||||
actualKeys[i] = r.key
|
||||
@@ -668,40 +663,22 @@ func runTestKVBatchGet(t *testing.T, kv resource.KV, nsPrefix string) {
|
||||
|
||||
// Verify values
|
||||
for _, r := range results {
|
||||
assert.Equal(t, testData[r.key], r.value, "key = %s", r.key)
|
||||
assert.Equal(t, testData[r.key], r.value)
|
||||
}
|
||||
})
|
||||
|
||||
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, testSection, namespacedKey(nsPrefix, "existing-key"), strings.NewReader("existing-value"))
|
||||
saveKVHelper(t, kv, ctx, section, "existing-key", strings.NewReader("existing-value"))
|
||||
|
||||
// Batch get with mix of existing and non-existent keys
|
||||
keys := namespacedKeys(nsPrefix, []string{"existing-key", "non-existent-1", "non-existent-2"})
|
||||
keys := []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, testSection, keys) {
|
||||
for kv, err := range kv.BatchGet(ctx, section, keys) {
|
||||
require.NoError(t, err)
|
||||
value, err := io.ReadAll(kv.Value)
|
||||
require.NoError(t, err)
|
||||
@@ -711,15 +688,15 @@ func runTestKVBatchGet(t *testing.T, kv resource.KV, nsPrefix string) {
|
||||
}
|
||||
|
||||
// Should only return the existing key
|
||||
require.Len(t, results, 1)
|
||||
assert.Equal(t, namespacedKey(nsPrefix, "existing-key"), results[0].key)
|
||||
assert.Len(t, results, 1)
|
||||
assert.Equal(t, "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 := namespacedKeys(nsPrefix, []string{"non-existent-1", "non-existent-2", "non-existent-3"})
|
||||
keys := []string{"non-existent-1", "non-existent-2", "non-existent-3"}
|
||||
var results []resource.KeyValue
|
||||
for kv, err := range kv.BatchGet(ctx, testSection, keys) {
|
||||
for kv, err := range kv.BatchGet(ctx, section, keys) {
|
||||
require.NoError(t, err)
|
||||
results = append(results, kv)
|
||||
}
|
||||
@@ -731,7 +708,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, testSection, keys) {
|
||||
for kv, err := range kv.BatchGet(ctx, section, keys) {
|
||||
require.NoError(t, err)
|
||||
results = append(results, kv)
|
||||
}
|
||||
@@ -741,16 +718,16 @@ func runTestKVBatchGet(t *testing.T, kv resource.KV, nsPrefix string) {
|
||||
})
|
||||
|
||||
t.Run("batch get with empty section", func(t *testing.T) {
|
||||
keys := namespacedKeys(nsPrefix, []string{"some-key"})
|
||||
keys := []string{"some-key"}
|
||||
var errors []error
|
||||
for kv, err := range kv.BatchGet(ctx, "", keys) {
|
||||
if err != nil {
|
||||
errors = append(errors, err)
|
||||
continue
|
||||
break
|
||||
}
|
||||
_ = kv // unused
|
||||
}
|
||||
require.Len(t, errors, 1)
|
||||
assert.Len(t, errors, 1)
|
||||
assert.Contains(t, errors[0].Error(), "section is required")
|
||||
})
|
||||
|
||||
@@ -764,13 +741,13 @@ func runTestKVBatchGet(t *testing.T, kv resource.KV, nsPrefix string) {
|
||||
|
||||
// Save test data
|
||||
for key, value := range testData {
|
||||
saveKVHelper(t, kv, ctx, testSection, namespacedKey(nsPrefix, key), strings.NewReader(value))
|
||||
saveKVHelper(t, kv, ctx, section, key, strings.NewReader(value))
|
||||
}
|
||||
|
||||
// Batch get in specific order
|
||||
keys := namespacedKeys(nsPrefix, []string{"z-key", "invalid-key1", "a-key", "invalid-key2", "m-key", "invalid-key3"})
|
||||
keys := []string{"z-key", "a-key", "m-key"}
|
||||
var results []string
|
||||
for kv, err := range kv.BatchGet(ctx, testSection, keys) {
|
||||
for kv, err := range kv.BatchGet(ctx, section, keys) {
|
||||
require.NoError(t, err)
|
||||
err = kv.Value.Close()
|
||||
require.NoError(t, err)
|
||||
@@ -778,8 +755,8 @@ func runTestKVBatchGet(t *testing.T, kv resource.KV, nsPrefix string) {
|
||||
}
|
||||
|
||||
// Verify order is preserved
|
||||
require.Len(t, results, 3)
|
||||
expectedOrder := namespacedKeys(nsPrefix, []string{"z-key", "a-key", "m-key"})
|
||||
assert.Len(t, results, 3)
|
||||
expectedOrder := []string{"z-key", "a-key", "m-key"}
|
||||
assert.Equal(t, expectedOrder, results)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@ func TestSQLKV(t *testing.T) {
|
||||
TestKVSave: true,
|
||||
TestKVConcurrent: true,
|
||||
TestKVUnixTimestamp: true,
|
||||
TestKVBatchGet: true,
|
||||
TestKVBatchDelete: true,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -21,7 +21,8 @@ import (
|
||||
func TestIntegrationTeamSearch(t *testing.T) {
|
||||
testutil.SkipIntegrationTestInShortMode(t)
|
||||
|
||||
modes := []rest.DualWriterMode{rest.Mode0, rest.Mode1, rest.Mode2, rest.Mode3, rest.Mode4, rest.Mode5}
|
||||
// TODO: Add rest.Mode3 and rest.Mode4 when they're supported
|
||||
modes := []rest.DualWriterMode{rest.Mode0, rest.Mode1, rest.Mode2}
|
||||
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{
|
||||
@@ -37,7 +38,6 @@ func TestIntegrationTeamSearch(t *testing.T) {
|
||||
featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs,
|
||||
featuremgmt.FlagKubernetesAuthnMutation,
|
||||
},
|
||||
UnifiedStorageEnableSearch: true,
|
||||
})
|
||||
doTeamSearchTests(t, helper)
|
||||
})
|
||||
@@ -59,6 +59,7 @@ 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"
|
||||
|
||||
@@ -146,15 +146,17 @@ async function initRudderstackBackend() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Logic: if only one of the sdk urls is provided, use respective code
|
||||
// this will need to be updated when rudderstackSdkV3Url is added
|
||||
// Desired 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(config.rudderstackV3SdkUrl);
|
||||
const onlyOneSdkUrlSet = hasOldSdkUrl !== hasNewSdkUrl;
|
||||
const useNewRudderstack = onlyOneSdkUrlSet ? hasNewSdkUrl : config.featureToggles.rudderstackUpgrade;
|
||||
const hasNewSdkUrl = Boolean(fakeConfigRudderstackSdkV3Url);
|
||||
const onlyOneConfigURLSet = hasOldSdkUrl !== hasNewSdkUrl;
|
||||
const useNewRudderstack = onlyOneConfigURLSet ? hasNewSdkUrl : config.featureToggles.rudderstackUpgrade;
|
||||
|
||||
const sdkUrl = useNewRudderstack ? config.rudderstackV3SdkUrl : config.rudderstackSdkUrl;
|
||||
const configUrl = useNewRudderstack ? fakeConfigRudderstackSdkV3Url : config.rudderstackSdkUrl;
|
||||
|
||||
const modulePromise = useNewRudderstack
|
||||
? import('./backends/analytics/RudderstackV3Backend')
|
||||
@@ -166,8 +168,8 @@ async function initRudderstackBackend() {
|
||||
writeKey: config.rudderstackWriteKey,
|
||||
dataPlaneUrl: config.rudderstackDataPlaneUrl,
|
||||
user: contextSrv.user,
|
||||
sdkUrl,
|
||||
configUrl: config.rudderstackConfigUrl,
|
||||
sdkUrl: config.rudderstackSdkUrl,
|
||||
configUrl: configUrl,
|
||||
integrationsUrl: config.rudderstackIntegrationsUrl,
|
||||
buildInfo: config.buildInfo,
|
||||
})
|
||||
|
||||
@@ -22,7 +22,7 @@ export function initAlerting() {
|
||||
component: ({ dashboard }) =>
|
||||
alertingEnabled ? (
|
||||
<Suspense fallback={null} key="alert-rules-button">
|
||||
{dashboard && <AlertRulesToolbarButton dashboardUid={dashboard.uid} />}
|
||||
{dashboard && dashboard.uid && <AlertRulesToolbarButton dashboardUid={dashboard.uid} />}
|
||||
</Suspense>
|
||||
) : null,
|
||||
index: -2,
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { useState } from 'react';
|
||||
import { useAsyncRetry } from 'react-use';
|
||||
import { useAsync } from 'react-use';
|
||||
|
||||
import { GrafanaTheme2, store } from '@grafana/data';
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { t, Trans } from '@grafana/i18n';
|
||||
import { evaluateBooleanFlag } from '@grafana/runtime/internal';
|
||||
import { Button, CollapsableSection, Spinner, Stack, Text, useStyles2, Grid } from '@grafana/ui';
|
||||
import { contextSrv } from 'app/core/services/context_srv';
|
||||
import { CollapsableSection, Grid, Spinner, Text, useStyles2 } from '@grafana/ui';
|
||||
import { useDashboardLocationInfo } from 'app/features/search/hooks/useDashboardLocationInfo';
|
||||
import { DashListItem } from 'app/plugins/panel/dashlist/DashListItem';
|
||||
|
||||
@@ -14,18 +12,10 @@ 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,
|
||||
retry,
|
||||
} = useAsyncRetry(async () => {
|
||||
const { value: recentDashboards = [], loading } = useAsync(async () => {
|
||||
if (!evaluateBooleanFlag('recentlyViewedDashboards', false)) {
|
||||
return [];
|
||||
}
|
||||
@@ -33,11 +23,6 @@ export function RecentlyViewedDashboards() {
|
||||
}, []);
|
||||
const { foldersByUid } = useDashboardLocationInfo(recentDashboards.length > 0);
|
||||
|
||||
const handleClearHistory = () => {
|
||||
store.set(recentDashboardsKey, JSON.stringify([]));
|
||||
retry();
|
||||
};
|
||||
|
||||
if (!evaluateBooleanFlag('recentlyViewedDashboards', false)) {
|
||||
return null;
|
||||
}
|
||||
@@ -46,19 +31,11 @@ export function RecentlyViewedDashboards() {
|
||||
<CollapsableSection
|
||||
headerDataTestId="browseDashboardsRecentlyViewedTitle"
|
||||
label={
|
||||
<Stack direction="row" justifyContent="space-between" alignItems="baseline" width="100%">
|
||||
<Text variant="h5" element="h3" onClick={() => setIsOpen(!isOpen)}>
|
||||
<Trans i18nKey="browse-dashboards.recently-viewed.title">Recently viewed</Trans>
|
||||
</Text>
|
||||
<Button icon="times" size="xs" variant="secondary" fill="text" onClick={handleClearHistory}>
|
||||
{t('browse-dashboards.recently-viewed.clear', 'Clear history')}
|
||||
</Button>
|
||||
</Stack>
|
||||
<Text variant="h5" element="h3">
|
||||
<Trans i18nKey="browse-dashboards.recently-viewed.title">Recently viewed</Trans>
|
||||
</Text>
|
||||
}
|
||||
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={() => {}}
|
||||
isOpen={true}
|
||||
className={styles.title}
|
||||
contentClassName={styles.content}
|
||||
>
|
||||
@@ -94,16 +71,14 @@ export function RecentlyViewedDashboards() {
|
||||
const getStyles = (theme: GrafanaTheme2) => {
|
||||
return {
|
||||
title: css({
|
||||
cursor: 'default',
|
||||
'& [id^="collapse-button-"] svg': {
|
||||
'& button svg': {
|
||||
color: theme.colors.primary.text,
|
||||
},
|
||||
h3: {
|
||||
background: `linear-gradient(90deg, ${theme.colors.primary.shade} 0%, ${theme.colors.primary.text} 100%)`,
|
||||
background: `linear-gradient(90deg, ${theme.colors.primary.text} 0%, ${theme.colors.secondary.text} 100%)`,
|
||||
WebkitTextFillColor: 'transparent',
|
||||
backgroundClip: 'text',
|
||||
color: 'transparent',
|
||||
cursor: 'pointer',
|
||||
},
|
||||
}),
|
||||
content: css({
|
||||
|
||||
@@ -76,12 +76,6 @@ export function DashboardEditPaneRenderer({ editPane, dashboard, isDocked }: Pro
|
||||
data-testid={selectors.pages.Dashboard.Sidebar.optionsButton}
|
||||
active={selectedObject === dashboard ? true : false}
|
||||
/>
|
||||
{/* <Sidebar.Button
|
||||
tooltip={t('dashboard.sidebar.edit-schema.tooltip', 'Edit as code')}
|
||||
title={t('dashboard.sidebar.edit-schema.title', 'Code')}
|
||||
icon="brackets-curly"
|
||||
onClick={() => dashboard.openV2SchemaEditor()}
|
||||
/> */}
|
||||
<Sidebar.Divider />
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -18,7 +18,7 @@ const newExportButtonSelector = selectors.pages.Dashboard.DashNav.NewExportButto
|
||||
|
||||
export function ShareExportDashboardButton({ dashboard }: Props) {
|
||||
return (
|
||||
<Dropdown overlay={<ExportMenu dashboard={dashboard} />} placement="left-start">
|
||||
<Dropdown overlay={<ExportMenu dashboard={dashboard} />} placement="left-end">
|
||||
<Sidebar.Button
|
||||
icon="download-alt"
|
||||
data-testid={newExportButtonSelector.Menu.container}
|
||||
|
||||
@@ -1673,123 +1673,6 @@ 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 = {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user