Merge remote-tracking branch 'origin/main' into ds-apiserver-with-configs

This commit is contained in:
Ryan McKinley
2025-08-19 11:24:22 +03:00
1084 changed files with 74398 additions and 47532 deletions
+19
View File
@@ -157,4 +157,23 @@ module.exports = [
'no-barrel-files/no-barrel-files': 'error',
},
},
{
// custom rule for Table to avoid performance regressions
files: ['packages/grafana-ui/src/components/Table/TableNG/Cells/**/*.{ts,tsx}'],
rules: {
'no-restricted-imports': [
'error',
{
patterns: [
{
group: ['**/themes/ThemeContext'],
importNames: ['useStyles2', 'useTheme2'],
message:
'Do not use "useStyles2" or "useTheme2" in a cell directly. Instead, provide styles to cells via `getDefaultCellStyles` or `getCellSpecificStyles`.',
},
],
},
],
},
},
];
+12 -67
View File
@@ -502,8 +502,8 @@ exports[`better eslint`] = {
[0, 0, 0, "Do not use any type assertions.", "1"],
[0, 0, 0, "Do not use any type assertions.", "2"],
[0, 0, 0, "Do not use any type assertions.", "3"],
[0, 0, 0, "Unexpected any. Specify a different type.", "4"],
[0, 0, 0, "Unexpected any. Specify a different type.", "5"]
[0, 0, 0, "Do not use any type assertions.", "4"],
[0, 0, 0, "Do not use any type assertions.", "5"]
],
"packages/grafana-runtime/src/services/EchoSrv.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
@@ -1063,8 +1063,7 @@ exports[`better eslint`] = {
[0, 0, 0, "Do not use any type assertions.", "1"],
[0, 0, 0, "Do not use any type assertions.", "2"],
[0, 0, 0, "Do not use any type assertions.", "3"],
[0, 0, 0, "Do not use any type assertions.", "4"],
[0, 0, 0, "Unexpected any. Specify a different type.", "5"]
[0, 0, 0, "Do not use any type assertions.", "4"]
],
"public/app/core/utils/object.ts:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"],
@@ -2076,10 +2075,6 @@ exports[`better eslint`] = {
[0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"]
],
"public/app/features/dashboard/components/TransformationsEditor/TransformationPicker.tsx:5381": [
[0, 0, 0, "\'VerticalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"],
[0, 0, 0, "Add noMargin prop to Card components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "1"]
],
"public/app/features/dashboard/components/TransformationsEditor/TransformationPickerNg.tsx:5381": [
[0, 0, 0, "Add noMargin prop to Card components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"]
],
"public/app/features/dashboard/components/TransformationsEditor/TransformationsEditor.tsx:5381": [
@@ -2153,6 +2148,9 @@ exports[`better eslint`] = {
[0, 0, 0, "Unexpected any. Specify a different type.", "26"],
[0, 0, 0, "Unexpected any. Specify a different type.", "27"]
],
"public/app/features/dashboard/state/DashboardMigratorToBackend.test.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
],
"public/app/features/dashboard/state/DashboardModel.repeat.test.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
[0, 0, 0, "Unexpected any. Specify a different type.", "1"],
@@ -2299,9 +2297,6 @@ exports[`better eslint`] = {
"public/app/features/dimensions/editors/ValueMappingsEditor/ValueMappingEditRow.tsx:5381": [
[0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"]
],
"public/app/features/dimensions/editors/ValueMappingsEditor/ValueMappingsEditor.tsx:5381": [
[0, 0, 0, "\'VerticalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"]
],
"public/app/features/dimensions/scale.ts:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"]
],
@@ -2404,9 +2399,6 @@ exports[`better eslint`] = {
"public/app/features/expressions/guards.ts:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"]
],
"public/app/features/geo/editor/locationModeEditor.tsx:5381": [
[0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"]
],
"public/app/features/geo/gazetteer/gazetteer.ts:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"],
[0, 0, 0, "Unexpected any. Specify a different type.", "1"]
@@ -2496,8 +2488,7 @@ exports[`better eslint`] = {
[0, 0, 0, "Unexpected any. Specify a different type.", "3"],
[0, 0, 0, "Unexpected any. Specify a different type.", "4"],
[0, 0, 0, "Unexpected any. Specify a different type.", "5"],
[0, 0, 0, "Unexpected any. Specify a different type.", "6"],
[0, 0, 0, "Unexpected any. Specify a different type.", "7"]
[0, 0, 0, "Unexpected any. Specify a different type.", "6"]
],
"public/app/features/manage-dashboards/state/reducers.ts:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"],
@@ -2805,9 +2796,6 @@ exports[`better eslint`] = {
"public/app/features/transformers/FilterByValueTransformer/ValueMatchers/valueMatchersUI.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
],
"public/app/features/transformers/calculateHeatmap/editor/AxisEditor.tsx:5381": [
[0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"]
],
"public/app/features/transformers/calculateHeatmap/editor/helper.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
],
@@ -2827,10 +2815,6 @@ exports[`better eslint`] = {
"public/app/features/transformers/editors/ConvertFieldTypeTransformerEditor.tsx:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"]
],
"public/app/features/transformers/editors/EnumMappingEditor.tsx:5381": [
[0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"],
[0, 0, 0, "\'VerticalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "1"]
],
"public/app/features/transformers/editors/EnumMappingRow.tsx:5381": [
[0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"]
],
@@ -2866,15 +2850,9 @@ exports[`better eslint`] = {
[0, 0, 0, "Unexpected any. Specify a different type.", "3"],
[0, 0, 0, "Unexpected any. Specify a different type.", "4"]
],
"public/app/features/transformers/joinByLabels/JoinByLabelsTransformerEditor.tsx:5381": [
[0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"]
],
"public/app/features/transformers/lookupGazetteer/FieldLookupTransformerEditor.tsx:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"]
],
"public/app/features/transformers/partitionByValues/PartitionByValuesEditor.tsx:5381": [
[0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"]
],
"public/app/features/transformers/spatial/optionsHelper.tsx:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"],
[0, 0, 0, "Do not use any type assertions.", "1"],
@@ -3683,9 +3661,6 @@ exports[`better eslint`] = {
"public/app/plugins/panel/annolist/AnnotationListItem.tsx:5381": [
[0, 0, 0, "Add noMargin prop to Card components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"]
],
"public/app/plugins/panel/barchart/TickSpacingEditor.tsx:5381": [
[0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"]
],
"public/app/plugins/panel/barchart/bars.ts:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"]
],
@@ -3747,11 +3722,8 @@ exports[`better eslint`] = {
"public/app/plugins/panel/geomap/editor/GeomapStyleRulesEditor.tsx:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"]
],
"public/app/plugins/panel/geomap/editor/MapViewEditor.tsx:5381": [
[0, 0, 0, "\'VerticalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"]
],
"public/app/plugins/panel/geomap/editor/StyleEditor.tsx:5381": [
[0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"],
[0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"],
[0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "1"],
[0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "2"],
[0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "3"],
@@ -3765,7 +3737,7 @@ exports[`better eslint`] = {
[0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "11"],
[0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "12"],
[0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "13"],
[0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "14"],
[0, 0, 0, "Do not use any type assertions.", "14"],
[0, 0, 0, "Do not use any type assertions.", "15"],
[0, 0, 0, "Do not use any type assertions.", "16"],
[0, 0, 0, "Do not use any type assertions.", "17"],
@@ -3776,8 +3748,7 @@ exports[`better eslint`] = {
[0, 0, 0, "Do not use any type assertions.", "22"],
[0, 0, 0, "Do not use any type assertions.", "23"],
[0, 0, 0, "Do not use any type assertions.", "24"],
[0, 0, 0, "Do not use any type assertions.", "25"],
[0, 0, 0, "Do not use any type assertions.", "26"]
[0, 0, 0, "Do not use any type assertions.", "25"]
],
"public/app/plugins/panel/geomap/editor/StyleRuleEditor.tsx:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"]
@@ -3903,8 +3874,7 @@ exports[`better eslint`] = {
[0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "1"]
],
"public/app/plugins/panel/table/cells/SparklineCellOptionsEditor.tsx:5381": [
[0, 0, 0, "\'VerticalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"],
[0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "1"]
[0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"]
],
"public/app/plugins/panel/table/migrations.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
@@ -3947,15 +3917,6 @@ exports[`better eslint`] = {
"public/app/plugins/panel/text/textPanelMigrationHandler.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
],
"public/app/plugins/panel/timeseries/InsertNullsEditor.tsx:5381": [
[0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"]
],
"public/app/plugins/panel/timeseries/LineStyleEditor.tsx:5381": [
[0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"]
],
"public/app/plugins/panel/timeseries/SpanNullsEditor.tsx:5381": [
[0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"]
],
"public/app/plugins/panel/timeseries/migrations.ts:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"],
[0, 0, 0, "Do not use any type assertions.", "1"],
@@ -3980,8 +3941,7 @@ exports[`better eslint`] = {
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
],
"public/app/plugins/panel/timeseries/plugins/annotations2/AnnotationTooltip2.tsx:5381": [
[0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"],
[0, 0, 0, "Unexpected any. Specify a different type.", "1"]
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
],
"public/app/plugins/panel/xychart/SeriesEditor.tsx:5381": [
[0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"],
@@ -4221,36 +4181,24 @@ exports[`no skipping a11y tests in stories`] = {
"packages/grafana-alerting/src/grafana/contactPoints/components/ContactPointSelector/ContactPointSelector.story.tsx:5381": [
[0, 0, 0, "No skipping of a11y tests in stories. Please fix the component or story instead.", "5381"]
],
"packages/grafana-ui/src/components/ColorPicker/ColorPicker.story.tsx:5381": [
[0, 0, 0, "No skipping of a11y tests in stories. Please fix the component or story instead.", "5381"]
],
"packages/grafana-ui/src/components/Combobox/MultiCombobox.story.tsx:5381": [
[0, 0, 0, "No skipping of a11y tests in stories. Please fix the component or story instead.", "5381"]
],
"packages/grafana-ui/src/components/FileDropzone/FileDropzone.story.tsx:5381": [
[0, 0, 0, "No skipping of a11y tests in stories. Please fix the component or story instead.", "5381"]
],
"packages/grafana-ui/src/components/FormField/FormField.story.tsx:5381": [
[0, 0, 0, "No skipping of a11y tests in stories. Please fix the component or story instead.", "5381"]
],
"packages/grafana-ui/src/components/Forms/Checkbox.story.tsx:5381": [
[0, 0, 0, "No skipping of a11y tests in stories. Please fix the component or story instead.", "5381"]
],
"packages/grafana-ui/src/components/Forms/FieldArray.story.tsx:5381": [
[0, 0, 0, "No skipping of a11y tests in stories. Please fix the component or story instead.", "5381"]
],
"packages/grafana-ui/src/components/Forms/RadioButtonGroup/RadioButtonGroup.story.tsx:5381": [
[0, 0, 0, "No skipping of a11y tests in stories. Please fix the component or story instead.", "5381"]
],
"packages/grafana-ui/src/components/InteractiveTable/InteractiveTable.story.tsx:5381": [
[0, 0, 0, "No skipping of a11y tests in stories. Please fix the component or story instead.", "5381"]
],
"packages/grafana-ui/src/components/Layout/Stack/Stack.story.tsx:5381": [
[0, 0, 0, "No skipping of a11y tests in stories. Please fix the component or story instead.", "5381"]
],
"packages/grafana-ui/src/components/Link/TextLink.story.tsx:5381": [
[0, 0, 0, "No skipping of a11y tests in stories. Please fix the component or story instead.", "5381"]
],
"packages/grafana-ui/src/components/Menu/Menu.story.tsx:5381": [
[0, 0, 0, "No skipping of a11y tests in stories. Please fix the component or story instead.", "5381"]
],
@@ -4281,9 +4229,6 @@ exports[`no skipping a11y tests in stories`] = {
"packages/grafana-ui/src/components/Table/Table.story.tsx:5381": [
[0, 0, 0, "No skipping of a11y tests in stories. Please fix the component or story instead.", "5381"]
],
"packages/grafana-ui/src/components/VizLayout/VizLayout.story.tsx:5381": [
[0, 0, 0, "No skipping of a11y tests in stories. Please fix the component or story instead.", "5381"]
],
"packages/grafana-ui/src/components/VizLegend/VizLegend.story.tsx:5381": [
[0, 0, 0, "No skipping of a11y tests in stories. Please fix the component or story instead.", "5381"]
]
+20 -15
View File
@@ -50,7 +50,7 @@ steps:
- apk add --update make
- CODEGEN_VERIFY=1 make gen-cue
depends_on: []
image: golang:1.24.5-alpine
image: golang:1.24.6-alpine
name: verify-gen-cue
trigger:
branch: main
@@ -117,7 +117,7 @@ steps:
depends_on: []
environment:
CGO_ENABLED: 0
image: golang:1.24.5-alpine
image: golang:1.24.6-alpine
name: compile-build-cmd
- commands:
- '# It is required that code generated from Thema/CUE be committed and in sync
@@ -127,7 +127,7 @@ steps:
- apk add --update make
- CODEGEN_VERIFY=1 make gen-cue
depends_on: []
image: golang:1.24.5-alpine
image: golang:1.24.6-alpine
name: verify-gen-cue
- commands:
- '# It is required that generated jsonnet is committed and in sync with its inputs.'
@@ -136,7 +136,7 @@ steps:
- apk add --update make
- CODEGEN_VERIFY=1 make gen-jsonnet
depends_on: []
image: golang:1.24.5-alpine
image: golang:1.24.6-alpine
name: verify-gen-jsonnet
- commands:
- yarn install --immutable || yarn install --immutable
@@ -186,7 +186,7 @@ steps:
environment:
_EXPERIMENTAL_DAGGER_CLOUD_TOKEN:
from_secret: dagger_token
image: golang:1.24.5-alpine
image: golang:1.24.6-alpine
name: rgm-package
pull: always
volumes:
@@ -664,7 +664,7 @@ steps:
depends_on: []
environment:
CGO_ENABLED: 0
image: golang:1.24.5-alpine
image: golang:1.24.6-alpine
name: compile-build-cmd
- commands:
- ./bin/build artifacts docker fetch --edition oss
@@ -794,7 +794,7 @@ steps:
depends_on: []
environment:
CGO_ENABLED: 0
image: golang:1.24.5-alpine
image: golang:1.24.6-alpine
name: compile-build-cmd
- commands:
- ./bin/build artifacts docker fetch --edition oss
@@ -935,7 +935,7 @@ steps:
depends_on: []
environment:
CGO_ENABLED: 0
image: golang:1.24.5-alpine
image: golang:1.24.6-alpine
name: compile-build-cmd
- commands:
- ./bin/build artifacts packages --artifacts-editions=oss --tag $${DRONE_TAG} --src-bucket
@@ -1027,7 +1027,7 @@ steps:
depends_on: []
environment:
CGO_ENABLED: 0
image: golang:1.24.5-alpine
image: golang:1.24.6-alpine
name: compile-build-cmd
- commands:
- yarn install --immutable || yarn install --immutable
@@ -1127,7 +1127,7 @@ steps:
depends_on: []
environment:
CGO_ENABLED: 0
image: golang:1.24.5-alpine
image: golang:1.24.6-alpine
name: compile-build-cmd
- depends_on:
- compile-build-cmd
@@ -1224,7 +1224,7 @@ steps:
depends_on: []
environment:
CGO_ENABLED: 0
image: golang:1.24.5-alpine
image: golang:1.24.6-alpine
name: compile-build-cmd
- commands:
- ./bin/build publish grafana-com --edition oss ${DRONE_TAG}
@@ -1298,7 +1298,7 @@ steps:
STORYBOOK_DESTINATION:
from_secret: rgm_storybook_destination
UBUNTU_BASE: ubuntu:22.04
image: golang:1.24.5-alpine
image: golang:1.24.6-alpine
name: rgm-build
pull: always
volumes:
@@ -1375,7 +1375,7 @@ steps:
STORYBOOK_DESTINATION:
from_secret: rgm_storybook_destination
UBUNTU_BASE: ubuntu:22.04
image: golang:1.24.5-alpine
image: golang:1.24.6-alpine
name: rgm-build
pull: always
volumes:
@@ -1494,7 +1494,7 @@ steps:
STORYBOOK_DESTINATION:
from_secret: rgm_storybook_destination
UBUNTU_BASE: ubuntu:22.04
image: golang:1.24.5-alpine
image: golang:1.24.6-alpine
name: rgm-build
pull: always
volumes:
@@ -1619,7 +1619,7 @@ steps:
STORYBOOK_DESTINATION:
from_secret: rgm_storybook_destination
UBUNTU_BASE: ubuntu:22.04
image: golang:1.24.5-alpine
image: golang:1.24.6-alpine
name: rgm-build
pull: always
volumes:
@@ -1873,3 +1873,8 @@ get:
path: secret/data/common/gcr
kind: secret
name: gcr_credentials
---
kind: signature
hmac: 70171a24c7e9ab7af105bd39d02f9aa6d28a4505963dfc9140f53cdfe4965ef8
...
+13 -11
View File
@@ -43,8 +43,8 @@
/docs/sources/datasources/ @lwandz13
/docs/sources/panels-visualizations/ @imatwawana
/docs/sources/release-notes/ @irenerl24 @GrafanaWriter
/docs/sources/upgrade-guide/ @imatwawana
/docs/sources/whatsnew/ @imatwawana
/docs/sources/upgrade-guide/ @jtvdez
/docs/sources/whatsnew/ @jtvdez
/docs/sources/developers/plugins/ @grafana/plugins-platform-frontend @grafana/plugins-platform-backend
@@ -115,6 +115,7 @@
/pkg/components/loki/ @grafana/grafana-backend-group
/pkg/components/null/ @grafana/grafana-backend-group
/pkg/components/simplejson/ @grafana/grafana-backend-group
/pkg/configprovider/ @grafana/grafana-backend-services-squad
/pkg/events/ @grafana/grafana-backend-group
/pkg/extensions/ @grafana/grafana-backend-group
/pkg/ifaces/ @grafana/grafana-backend-group
@@ -302,7 +303,7 @@
/devenv/docker/blocks/sensugo/ @grafana/grafana-backend-group
/devenv/docker/blocks/slow_proxy/ @bergquist
/devenv/docker/blocks/smtp/ @bergquist
/devenv/docker/blocks/tempo/ @grafana/observability-traces-and-profiling
/devenv/docker/blocks/tempo/ @grafana/oss-big-tent @grafana/observability-traces-and-profiling
/devenv/docker/blocks/traefik/ @mckn
/devenv/docker/blocks/zipkin/ @grafana/oss-big-tent
/devenv/docker/blocks/webdav/ @grafana/alerting-backend
@@ -349,7 +350,7 @@
/pkg/tsdb/prometheus/ @grafana/oss-big-tent
/pkg/tsdb/elasticsearch/ @grafana/partner-datasources
/pkg/tsdb/loki/ @grafana/oss-big-tent
/pkg/tsdb/tempo/ @grafana/observability-traces-and-profiling
/pkg/tsdb/tempo/ @grafana/oss-big-tent @grafana/observability-traces-and-profiling
/pkg/tsdb/grafana-pyroscope-datasource/ @grafana/observability-traces-and-profiling
/pkg/tsdb/parca/ @grafana/oss-big-tent
@@ -554,10 +555,10 @@
/packages/grafana-o11y-ds-frontend/src/IntervalInput/ @grafana/observability-traces-and-profiling
/packages/grafana-o11y-ds-frontend/src/NodeGraph/ @grafana/observability-traces-and-profiling
/packages/grafana-o11y-ds-frontend/src/pyroscope/ @grafana/observability-traces-and-profiling
/packages/grafana-o11y-ds-frontend/src/SpanBar/ @grafana/observability-traces-and-profiling
/packages/grafana-o11y-ds-frontend/src/TraceToLogs/ @grafana/observability-traces-and-profiling
/packages/grafana-o11y-ds-frontend/src/TraceToMetrics/ @grafana/observability-traces-and-profiling
/packages/grafana-o11y-ds-frontend/src/TraceToProfiles/ @grafana/observability-traces-and-profiling
/packages/grafana-o11y-ds-frontend/src/SpanBar/ @grafana/oss-big-tent @grafana/observability-traces-and-profiling
/packages/grafana-o11y-ds-frontend/src/TraceToLogs/ @grafana/oss-big-tent @grafana/observability-traces-and-profiling
/packages/grafana-o11y-ds-frontend/src/TraceToMetrics/ @grafana/oss-big-tent @grafana/observability-traces-and-profiling
/packages/grafana-o11y-ds-frontend/src/TraceToProfiles/ @grafana/oss-big-tent @grafana/observability-traces-and-profiling
# @grafana/plugin-configs
/packages/grafana-plugin-configs/ @grafana/plugins-platform-frontend
@@ -607,6 +608,7 @@
/packages/grafana-runtime/src/utils/returnToPrevious.ts @grafana/grafana-search-navigate-organise
/packages/grafana-runtime/src/utils/toDataQueryError.ts @grafana/grafana-datasources-core-services
/packages/grafana-runtime/src/utils/userStorage* @grafana/plugins-platform-frontend @grafana/grafana-frontend-platform
/packages/grafana-runtime/src/utils/useFavoriteDatasources* @grafana/plugins-platform-frontend
# @grafana/schema
/packages/grafana-schema/ @grafana/grafana-app-platform-squad
@@ -642,7 +644,7 @@
/packages/grafana-schema/src/**/statetimeline @grafana/dataviz-squad
/packages/grafana-schema/src/**/statushistory @grafana/dataviz-squad
/packages/grafana-schema/src/**/table @grafana/dataviz-squad
/packages/grafana-schema/src/**/tempo @grafana/observability-traces-and-profiling
/packages/grafana-schema/src/**/tempo @grafana/oss-big-tent @grafana/observability-traces-and-profiling
/packages/grafana-schema/src/**/text @grafana/dataviz-squad
/packages/grafana-schema/src/**/timeseries @grafana/dataviz-squad
/packages/grafana-schema/src/**/trend @grafana/dataviz-squad
@@ -737,7 +739,6 @@ playwright.storybook.config.ts @grafana/grafana-frontend-platform
/public/app/core/components/GraphNG/ @grafana/dataviz-squad
/public/app/core/components/help/ @grafana/grafana-search-navigate-organise
/public/app/core/components/Indent/ @grafana/grafana-search-navigate-organise
/public/app/core/components/InviteUserButton/ @grafana/sharing-squad
/public/app/core/components/Layers/ @grafana/dataviz-squad
/public/app/core/components/Login/ @grafana/grafana-search-navigate-organise
/public/app/core/components/NativeScrollbar.tsx @grafana/grafana-search-navigate-organise
@@ -1029,7 +1030,7 @@ playwright.storybook.config.ts @grafana/grafana-frontend-platform
/public/app/plugins/datasource/prometheus/ @grafana/oss-big-tent
/public/app/plugins/datasource/cloud-monitoring/ @grafana/partner-datasources
/public/app/plugins/datasource/zipkin/ @grafana/oss-big-tent
/public/app/plugins/datasource/tempo/ @grafana/observability-traces-and-profiling
/public/app/plugins/datasource/tempo/ @grafana/oss-big-tent @grafana/observability-traces-and-profiling
/public/app/plugins/datasource/grafana-pyroscope-datasource/ @grafana/observability-traces-and-profiling
/public/app/plugins/datasource/parca/ @grafana/oss-big-tent
/public/app/plugins/datasource/alertmanager/ @grafana/alerting-squad
@@ -1134,6 +1135,7 @@ embed.go @grafana/grafana-as-code
/.github/actions/setup-grafana-bench/ @Proximyst
/.github/actions/build-package @grafana/grafana-developer-enablement-squad
/.github/actions/change-detection @grafana/grafana-developer-enablement-squad
/.github/actions/setup-node @grafana/grafana-frontend-platform
/.github/workflows/actionlint-format.txt @grafana/grafana-developer-enablement-squad
/.github/workflows/actionlint.yml @grafana/grafana-developer-enablement-squad
/.github/workflows/add-to-whats-new.yml @grafana/docs-tooling
+20
View File
@@ -6,3 +6,23 @@ self-hosted-runner:
- github-hosted-ubuntu-x64-small
- github-hosted-ubuntu-x64-large
- github-hosted-windows-x64-large
- ubuntu-x64
- ubuntu-x64-io
- ubuntu-x64-small
- ubuntu-x64-small-io
- ubuntu-x64-large
- ubuntu-x64-large-io
- ubuntu-x64-xlarge
- ubuntu-x64-xlarge-io
- ubuntu-x64-2xlarge
- ubuntu-x64-2xlarge-io
- ubuntu-arm64
- ubuntu-arm64-io
- ubuntu-arm64-small
- ubuntu-arm64-small-io
- ubuntu-arm64-large
- ubuntu-arm64-large-io
- ubuntu-arm64-xlarge
- ubuntu-arm64-xlarge-io
- ubuntu-arm64-2xlarge
- ubuntu-arm64-2xlarge-io
+11
View File
@@ -0,0 +1,11 @@
name: Setup Node.js
description: Sets up a node.js environment with presets for the Grafana repository.
runs:
using: "composite"
steps:
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'yarn'
cache-dependency-path: 'yarn.lock'
+8
View File
@@ -947,6 +947,14 @@
"url": "https://github.com/orgs/grafana/projects/202"
}
},
{
"type": "label",
"name": "area/gitsync",
"action": "addToProject",
"addToProject": {
"url": "https://github.com/orgs/grafana/projects/792"
}
},
{
"type": "label",
"name": "area/backend/db/sqlite",
+16 -2
View File
@@ -51,7 +51,7 @@ jobs:
run: |
TO_COMMIT="$(gh api repos/grafana/alerting/commits/"$BRANCH" --jq '.sha')"
if [ -z "$TO_COMMIT" ]; then
echo "Branch $BRANCH not found in alerting repo, falling back to main branch"
echo "Branch $BRANCH not found in alerting repo"
exit 1
fi
echo "to_commit=$TO_COMMIT" >> "$GITHUB_OUTPUT"
@@ -85,7 +85,7 @@ jobs:
printf "%s\n" "$ALERTING_COMMITS"
# make the list for markdown and replace PR numbers with links
ALERTING_COMMITS_FORMATTED="$(echo "$ALERTING_COMMITS" | while read -r line; do echo "- $line" | sed -E 's/\(#([0-9]+)\)/[#\1](https:\/\/github.com\/grafana\/grafana\/pull\/\1)/g'; done)"
ALERTING_COMMITS_FORMATTED="$(echo "$ALERTING_COMMITS" | while read -r line; do echo "- $line" | sed -E 's/\(#([0-9]+)\)/[#\1](https:\/\/github.com\/grafana\/alerting\/pull\/\1)/g'; done)"
{
echo "alerting_commits<<EOF"
@@ -123,6 +123,9 @@ jobs:
title: 'Alerting: Update alerting module to ${{ steps.latest-commit.outputs.to_commit }}'
branch: alerting/update-alerting-module
delete-branch: true
labels: |
no-changelog
no-backport
body: |
Updates Grafana Alerting module to latest version.
@@ -144,3 +147,14 @@ jobs:
echo "## Pull Request Created"
echo "🔗 [View Pull Request]($PR_URL)"
} >> "$GITHUB_STEP_SUMMARY"
- name: Send Slack Message
uses: grafana/shared-workflows/actions/send-slack-message@send-slack-message/v2.0.3
with:
method: 'chat.postMessage'
# send to alerting-reviews channel
payload-templated: true
payload: |
{
"channel": "C076RNRRZ2N",
"text": "Update alerting module in Grafana ${{ steps.create-pr.outputs.pull-request-url }}"
}
@@ -14,6 +14,7 @@ area/backend/db/mysql
area/backend/db/postgres
area/backend/db/sql
area/backend/db/sqlite
area/gitsync
area/configuration
area/dashboard/annotations
area/dashboard/data-links
+27 -8
View File
@@ -14,12 +14,31 @@ on:
- 'docs/**'
- 'latest.json'
permissions:
contents: read
id-token: write
jobs:
detect-changes:
name: Detect whether code changed
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
changed: ${{ steps.detect-changes.outputs.backend }}
steps:
- uses: actions/checkout@v4
with:
persist-credentials: true # required to get more history in the changed-files action
fetch-depth: 2
- name: Detect changes
id: detect-changes
uses: ./.github/actions/change-detection
with:
self: .github/workflows/backend-code-checks.yml
validate-configs:
permissions:
contents: read
id-token: write
needs: detect-changes
if: needs.detect-changes.outputs.changed == 'true'
name: Validate Backend Configs
runs-on: ubuntu-latest
steps:
@@ -39,7 +58,7 @@ jobs:
run: |
CODEGEN_VERIFY=1 make gen-cue
CODEGEN_VERIFY=1 make gen-jsonnet
- name: Validate go.mod
run: go run scripts/modowners/modowners.go check go.mod
@@ -48,7 +67,7 @@ jobs:
- name: Setup Grafana Enterprise
if: github.event.pull_request.head.repo.fork == false
uses: ./.github/actions/setup-enterprise
- name: Generate and Validate OpenAPI Specs
run: |
# For PRs from forks, we'll just run the basic swagger-gen without validation
@@ -57,10 +76,10 @@ jobs:
make swagger-gen
exit 0
fi
# Clean and regenerate OpenAPI specs
make swagger-clean && make openapi3-gen
# Check if the generated specs differ from what's in the repository
for f in public/api-merged.json public/openapi3.json; do git add $f; done
if [ -z "$(git diff --name-only --cached)" ]; then
+1 -13
View File
@@ -32,19 +32,7 @@ jobs:
persist-credentials: false
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'yarn'
- name: Cache node_modules
uses: actions/cache@v4
with:
path: |
node_modules
key: node_modules-${{ hashFiles('yarn.lock') }}
restore-keys: |
node_modules-
uses: ./.github/actions/setup-node
- name: Install dependencies
env:
+20
View File
@@ -13,7 +13,27 @@ permissions:
contents: read
jobs:
detect-changes:
name: Detect whether code changed
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
changed: ${{ steps.detect-changes.outputs.backend }}
steps:
- uses: actions/checkout@v4
with:
persist-credentials: true # required to get more history in the changed-files action
fetch-depth: 2
- name: Detect changes
id: detect-changes
uses: ./.github/actions/change-detection
with:
self: .github/workflows/go-lint.yml
lint-go:
needs: detect-changes
if: needs.detect-changes.outputs.changed == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
@@ -17,7 +17,7 @@ permissions:
jobs:
update:
runs-on: "ubuntu-latest"
if: ${{ github.actor == 'dependabot[bot]' && github.event.pull_request.head.repo.full_name == github.repository }}
if: ${{ github.event.pull_request.user.login == 'dependabot[bot]' && github.event.pull_request.head.repo.full_name == github.repository }}
continue-on-error: true
steps:
- name: Retrieve GitHub App secrets
+1 -1
View File
@@ -55,7 +55,7 @@ jobs:
- uses: actions/cache@v4
id: cache
with:
key: "build-grafana-${{ runner.os }}-${{ hashFiles('yarn.lock', 'public/*', 'packages/*', 'pkg/**/*.go', '**/go.mod', '**/go.sum', '!**_test.go', '!**.test.ts', '!**.test.tsx', 'Dockerfile') }}"
key: "build-grafana-${{ runner.os }}-${{ hashFiles('yarn.lock', 'public/*', 'packages/*', 'conf/*', 'pkg/**/*.go', '**/go.mod', '**/go.sum', '!**_test.go', '!**.test.ts', '!**.test.tsx', 'Dockerfile') }}"
path: |
build-dir
+68 -54
View File
@@ -11,7 +11,7 @@ permissions: {}
jobs:
detect-changes:
name: Detect whether code changed
runs-on: ubuntu-latest
runs-on: ubuntu-x64-small
permissions:
contents: read
outputs:
@@ -31,100 +31,115 @@ jobs:
permissions:
contents: read
id-token: write
needs:
- detect-changes
# Run this workflow only for PRs from forks; if it gets merged into `main` or `release-*`,
# the `frontend-unit-tests-enterprise` workflow will run instead
needs: detect-changes
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true && needs.detect-changes.outputs.changed == 'true'
runs-on: ubuntu-latest-8-cores
name: "Unit tests (${{ matrix.chunk }} / 8)"
runs-on: ubuntu-x64-large
name: "Unit tests (${{ matrix.shard }} / ${{ matrix.total }})"
strategy:
fail-fast: false
matrix:
chunk: [1, 2, 3, 4, 5, 6, 7, 8]
shard: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]
total: [16]
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'yarn'
cache-dependency-path: 'yarn.lock'
- run: yarn install --immutable --check-cache
- name: Setup Node.js
uses: ./.github/actions/setup-node
- name: Yarn install
run: yarn install --immutable
env:
PUPPETEER_SKIP_DOWNLOAD: true
CYPRESS_INSTALL_BINARY: 0
- run: yarn run test:ci
env:
TEST_MAX_WORKERS: 2
TEST_SHARD: ${{ matrix.chunk }}
TEST_SHARD_TOTAL: 8
TEST_MAX_WORKERS: 4
TEST_SHARD: ${{ matrix.shard }}
TEST_SHARD_TOTAL: ${{ matrix.total }}
frontend-unit-tests-enterprise:
permissions:
contents: read
id-token: write
needs:
- detect-changes
# Run this workflow for non-PR events (like pushes to `main` or `release-*`) OR for internal PRs (PRs not from forks)
needs: detect-changes
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false && needs.detect-changes.outputs.changed == 'true'
runs-on: ubuntu-latest-8-cores
name: "Unit tests (${{ matrix.chunk }} / 8)"
runs-on: ubuntu-x64-large
name: "Unit tests (${{ matrix.shard }} / ${{ matrix.total }})"
strategy:
fail-fast: false
matrix:
chunk: [1, 2, 3, 4, 5, 6, 7, 8]
shard: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]
total: [16]
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'yarn'
cache-dependency-path: 'yarn.lock'
- name: Setup Enterprise
uses: ./.github/actions/setup-enterprise
with:
github-app-name: 'grafana-ci-bot'
- run: yarn install --immutable --check-cache
- name: Setup Node.js
uses: ./.github/actions/setup-node
- name: Yarn install
run: yarn install --immutable
env:
# Switch from default hardened mode to faster mode for internal PRs
YARN_ENABLE_HARDENED_MODE: ${{ github.event.pull_request.head.repo.fork == false && '1' || '0' }}
PUPPETEER_SKIP_DOWNLOAD: true
CYPRESS_INSTALL_BINARY: 0
- run: yarn run test:ci
env:
TEST_MAX_WORKERS: 2
TEST_SHARD: ${{ matrix.chunk }}
TEST_SHARD_TOTAL: 8
TEST_MAX_WORKERS: 4
TEST_SHARD: ${{ matrix.shard }}
TEST_SHARD_TOTAL: ${{ matrix.total }}
frontend-decoupled-plugin-tests:
needs: detect-changes
needs:
- detect-changes
if: needs.detect-changes.outputs.changed == 'true'
runs-on: ubuntu-latest-8-cores
runs-on: ubuntu-x64-large
name: "Decoupled plugin tests"
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'yarn'
cache-dependency-path: 'yarn.lock'
- run: yarn install --immutable --check-cache
- name: Setup Node.js
uses: ./.github/actions/setup-node
- name: Yarn install
run: yarn install --immutable
env:
# Switch from default hardened mode to faster mode for internal PRs
YARN_ENABLE_HARDENED_MODE: ${{ github.event.pull_request.head.repo.fork == false && '1' || '0' }}
PUPPETEER_SKIP_DOWNLOAD: true
CYPRESS_INSTALL_BINARY: 0
- run: yarn run plugin:test:ci
frontend-packages-unit-tests:
needs: detect-changes
needs:
- detect-changes
if: needs.detect-changes.outputs.changed == 'true'
runs-on: ubuntu-latest-8-cores
runs-on: ubuntu-x64-large
name: "Packages unit tests"
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'yarn'
cache-dependency-path: 'yarn.lock'
- run: yarn install --immutable --check-cache
- name: Setup Node.js
uses: ./.github/actions/setup-node
- name: Yarn install
run: yarn install --immutable
env:
# Switch from default hardened mode to faster mode for internal PRs
YARN_ENABLE_HARDENED_MODE: ${{ github.event.pull_request.head.repo.fork == false && '1' || '0' }}
PUPPETEER_SKIP_DOWNLOAD: true
CYPRESS_INSTALL_BINARY: 0
- run: yarn run packages:test:ci
# This is the job that is actually required by rulesets.
# We need to require EITHER the OSS or the Enterprise job to pass.
# However, if one is skipped, GitHub won't flat-map the shards,
@@ -141,15 +156,14 @@ jobs:
if: always()
name: All frontend unit tests complete
runs-on: ubuntu-latest
runs-on: ubuntu-x64-small
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: Check test suites
env:
NEEDS: ${{ toJson(needs) }}
run: |
FAILURES="$(echo "$NEEDS" | jq 'with_entries(select(.value.result == "failure")) | map_values(.result)')"
echo "$FAILURES"
if [ "$(echo "$FAILURES" | jq '. | length')" != "0" ]; then
exit 1
fi
echo "All OK!"
uses: ./.github/actions/check-jobs
with:
needs: ${{ toJson(needs) }}
failure-message: "One or more unit test jobs have failed"
success-message: "All unit tests completed successfully"
+27 -3
View File
@@ -18,7 +18,27 @@ concurrency:
permissions: {}
jobs:
detect-changes:
name: Detect whether code changed
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
changed: ${{ steps.detect-changes.outputs.backend }}
steps:
- uses: actions/checkout@v4
with:
persist-credentials: true # required to get more history in the changed-files action
fetch-depth: 2
- name: Detect changes
id: detect-changes
uses: ./.github/actions/change-detection
with:
self: .github/workflows/pr-test-integration.yml
sqlite:
needs: detect-changes
if: needs.detect-changes.outputs.changed == 'true'
strategy:
matrix:
shard: [
@@ -47,8 +67,10 @@ jobs:
run: |
set -euo pipefail
readarray -t PACKAGES <<< "$(./scripts/ci/backend-tests/pkgs-with-tests-named.sh -b TestIntegration | ./scripts/ci/backend-tests/shard.sh -N"$SHARD" -d-)"
go test -tags=sqlite -timeout=5m -run '^TestIntegration' "${PACKAGES[@]}"
go test -tags=sqlite -timeout=8m -run '^TestIntegration' "${PACKAGES[@]}"
mysql:
needs: detect-changes
if: needs.detect-changes.outputs.changed == 'true'
strategy:
matrix:
shard: [
@@ -93,8 +115,10 @@ jobs:
run: |
set -euo pipefail
readarray -t PACKAGES <<< "$(./scripts/ci/backend-tests/pkgs-with-tests-named.sh -b TestIntegration | ./scripts/ci/backend-tests/shard.sh -N"$SHARD" -d-)"
go test -p=1 -tags=mysql -timeout=5m -run '^TestIntegration' "${PACKAGES[@]}"
CGO_ENABLED=0 go test -p=1 -tags=mysql -timeout=8m -run '^TestIntegration' "${PACKAGES[@]}"
postgres:
needs: detect-changes
if: needs.detect-changes.outputs.changed == 'true'
strategy:
matrix:
shard: [
@@ -138,7 +162,7 @@ jobs:
run: |
set -euo pipefail
readarray -t PACKAGES <<< "$(./scripts/ci/backend-tests/pkgs-with-tests-named.sh -b TestIntegration | ./scripts/ci/backend-tests/shard.sh -N"$SHARD" -d-)"
go test -p=1 -tags=postgres -timeout=5m -run '^TestIntegration' "${PACKAGES[@]}"
CGO_ENABLED=0 go test -p=1 -tags=postgres -timeout=8m -run '^TestIntegration' "${PACKAGES[@]}"
# This is the job that is actually required by rulesets.
# We want to only require one job instead of all the individual tests and shards.
+2 -2
View File
@@ -80,7 +80,7 @@ jobs:
create_security_branch_grafana:
name: Create security branch (Grafana Security Mirror)
needs: setup
uses: ./.github/workflows/create-security-branch.yml
uses: grafana/grafana/.github/workflows/create-security-branch.yml@main
with:
release_branch: ${{ needs.setup.outputs.release_branch }}
security_branch_number: "01"
@@ -88,7 +88,7 @@ jobs:
create_security_branch_enterprise:
name: Create security branch (Enterprise)
needs: setup
uses: ./.github/workflows/create-security-branch.yml
uses: grafana/grafana/.github/workflows/create-security-branch.yml@main
with:
release_branch: ${{ needs.setup.outputs.release_branch }}
security_branch_number: "01"
+20 -1
View File
@@ -14,10 +14,29 @@ concurrency:
cancel-in-progress: true
jobs:
detect-changes:
name: Detect whether code changed
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
changed: ${{ steps.detect-changes.outputs.backend }}
steps:
- uses: actions/checkout@v4
with:
persist-credentials: true # required to get more history in the changed-files action
fetch-depth: 2
- name: Detect changes
id: detect-changes
uses: ./.github/actions/change-detection
with:
self: .github/workflows/swagger-gen.yml
verify:
name: Verify committed API specs match
runs-on: ubuntu-latest
if: ${{ github.repository == 'grafana/grafana' }}
needs: detect-changes
if: github.repository == 'grafana/grafana' && needs.detect-changes.outputs.changed == 'true'
permissions:
contents: read # clone the repository
id-token: write # required for Vault access
-1
View File
@@ -30,7 +30,6 @@ jobs:
with:
persist-credentials: false
fetch-depth: ${{ steps.fetch_depth.outputs.fetch_depth }}
ref: ${{ github.event.pull_request.head.ref }}
- name: Trufflehog
uses: trufflesecurity/trufflehog@eafb8c5f6a06175141c27f17bcc17941853d0047 # v3.90.0
with:
+3
View File
@@ -117,6 +117,9 @@ profile.cov
/pkg/server/wireexts_enterprise.go
/pkg/server/enterprise_wire_gen.go
/pkg/build/cmd/enterprise.go
/pkg/build/cmd/artifactspage.go
/pkg/build/cmd/artifactspage.tmpl.html
/pkg/build/cmd/exportversion.go
/pkg/extensions/*
!/pkg/extensions/.keep
!/pkg/extensions/main.go
+65
View File
@@ -1,3 +1,68 @@
<!-- 12.1.1 START -->
# 12.1.1 (2025-08-13)
### Features and enhancements
- **Alerting:** Add rule group name validation to the Prometheus conversion API [#108767](https://github.com/grafana/grafana/pull/108767), [@alexander-akhmetov](https://github.com/alexander-akhmetov)
- **CloudWatch:** Update grafana/aws-sdk-go with STS endpo… [#109357](https://github.com/grafana/grafana/pull/109357), [@iwysiu](https://github.com/iwysiu)
- **Go:** Update to 1.24.6 [#109318](https://github.com/grafana/grafana/pull/109318), [@Proximyst](https://github.com/Proximyst)
### Bug fixes
- **Alerting:** Fix active time intervals when time interval is renamed [#108547](https://github.com/grafana/grafana/pull/108547), [@yuri-tceretian](https://github.com/yuri-tceretian)
- **Alerting:** Fix subpath handling in the alerting package [#109505](https://github.com/grafana/grafana/pull/109505), [@konrad147](https://github.com/konrad147)
- **Config:** Fix date_formats options being moved to a different section [#109366](https://github.com/grafana/grafana/pull/109366), [@joshhunt](https://github.com/joshhunt)
<!-- 12.1.1 END -->
<!-- 12.0.4 START -->
# 12.0.4 (2025-08-13)
### Features and enhancements
- **Go:** Update to 1.24.6 [#109317](https://github.com/grafana/grafana/pull/109317), [@Proximyst](https://github.com/Proximyst)
### Bug fixes
- **Azure:** Fix time management field [#108481](https://github.com/grafana/grafana/pull/108481), [@aangelisc](https://github.com/aangelisc)
- **Azure:** Fix time management field [#108481](https://github.com/grafana/grafana/pull/108481), [@aangelisc](https://github.com/aangelisc)
- **Azure:** Fix time management field [#108481](https://github.com/grafana/grafana/pull/108481), [@aangelisc](https://github.com/aangelisc)
- **Azure:** Fix time management field [#108481](https://github.com/grafana/grafana/pull/108481), [@aangelisc](https://github.com/aangelisc)
<!-- 12.0.4 END -->
<!-- 11.6.5 START -->
# 11.6.5 (2025-08-13)
### Features and enhancements
- **Alerting:** Bump alerting package to include change to NewTLSClient [#108817](https://github.com/grafana/grafana/pull/108817), [@rwwiv](https://github.com/rwwiv)
- **Go:** Update to 1.24.6 [#109314](https://github.com/grafana/grafana/pull/109314), [@Proximyst](https://github.com/Proximyst)
<!-- 11.6.5 END -->
<!-- 11.5.8 START -->
# 11.5.8 (2025-08-12)
### Features and enhancements
- **Go:** Update to 1.24.6 [#109315](https://github.com/grafana/grafana/pull/109315), [@Proximyst](https://github.com/Proximyst)
<!-- 11.5.8 END -->
<!-- 11.4.8 START -->
# 11.4.8 (2025-08-12)
### Features and enhancements
- **Go:** Update to 1.24.6 [#109316](https://github.com/grafana/grafana/pull/109316), [@Proximyst](https://github.com/Proximyst)
### Bug fixes
- **Dashboards:** Panel react for `timeFrom` and `timeShift` changes using variables [#98659](https://github.com/grafana/grafana/pull/98659), [@Sergej-Vlasov](https://github.com/Sergej-Vlasov)
<!-- 11.4.8 END -->
<!-- 12.1.0 START -->
# 12.1.0 (2025-07-23)
+1 -1
View File
@@ -16,7 +16,7 @@ ARG JS_SRC=js-builder
# By using FROM instructions we can delegate dependency updates to dependabot
FROM alpine:3.21.3 AS alpine-base
FROM ubuntu:22.04 AS ubuntu-base
FROM golang:1.24.5-alpine AS go-builder-base
FROM golang:1.24.6-alpine AS go-builder-base
FROM --platform=${JS_PLATFORM} node:22-alpine AS js-builder-base
# Javascript build stage
+1 -1
View File
@@ -9,7 +9,7 @@ include .bingo/Variables.mk
include .citools/Variables.mk
GO = go
GO_VERSION = 1.24.5
GO_VERSION = 1.24.6
GO_LINT_FILES ?= $(shell ./scripts/go-workspace/golangci-lint-includes.sh)
GO_TEST_FILES ?= $(shell ./scripts/go-workspace/test-includes.sh)
SH_FILES ?= $(shell find ./scripts -name *.sh)
+1 -1
View File
@@ -1,6 +1,6 @@
module github.com/grafana/grafana/apps/advisor
go 1.24.5
go 1.24.6
require (
github.com/Masterminds/semver/v3 v3.4.0
+1 -1
View File
@@ -1,6 +1,6 @@
module github.com/grafana/grafana/apps/alerting/notifications
go 1.24.5
go 1.24.6
require (
github.com/grafana/grafana-app-sdk v0.40.3
+2 -2
View File
@@ -1,12 +1,13 @@
module github.com/grafana/grafana/apps/dashboard
go 1.24.5
go 1.24.6
require (
cuelang.org/go v0.11.1
github.com/grafana/grafana-app-sdk v0.40.3
github.com/grafana/grafana-plugin-sdk-go v0.278.0
github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250514132646-acbc7b54ed9e
github.com/prometheus/client_golang v1.23.0
github.com/stretchr/testify v1.10.0
k8s.io/apimachinery v0.33.3
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff
@@ -80,7 +81,6 @@ require (
github.com/perimeterx/marshmallow v1.1.5 // indirect
github.com/pierrec/lz4/v4 v4.1.22 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_golang v1.23.0 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.65.0 // indirect
github.com/prometheus/procfs v0.16.1 // indirect
@@ -941,7 +941,7 @@ ConditionalRenderingVariableKind: {
ConditionalRenderingVariableSpec: {
variable: string
operator: "equals" | "notEquals"
operator: "equals" | "notEquals" | "matches" | "notMatches"
value: string
}
@@ -942,7 +942,7 @@ ConditionalRenderingVariableKind: {
ConditionalRenderingVariableSpec: {
variable: string
operator: "equals" | "notEquals"
operator: "equals" | "notEquals" | "matches" | "notMatches"
value: string
}
@@ -945,7 +945,7 @@ ConditionalRenderingVariableKind: {
ConditionalRenderingVariableSpec: {
variable: string
operator: "equals" | "notEquals"
operator: "equals" | "notEquals" | "matches" | "notMatches"
value: string
}
@@ -1859,8 +1859,10 @@ const (
type DashboardConditionalRenderingVariableSpecOperator string
const (
DashboardConditionalRenderingVariableSpecOperatorEquals DashboardConditionalRenderingVariableSpecOperator = "equals"
DashboardConditionalRenderingVariableSpecOperatorNotEquals DashboardConditionalRenderingVariableSpecOperator = "notEquals"
DashboardConditionalRenderingVariableSpecOperatorEquals DashboardConditionalRenderingVariableSpecOperator = "equals"
DashboardConditionalRenderingVariableSpecOperatorNotEquals DashboardConditionalRenderingVariableSpecOperator = "notEquals"
DashboardConditionalRenderingVariableSpecOperatorMatches DashboardConditionalRenderingVariableSpecOperator = "matches"
DashboardConditionalRenderingVariableSpecOperatorNotMatches DashboardConditionalRenderingVariableSpecOperator = "notMatches"
)
// +k8s:openapi-gen=true
@@ -946,7 +946,7 @@ ConditionalRenderingVariableKind: {
ConditionalRenderingVariableSpec: {
variable: string
operator: "equals" | "notEquals"
operator: "equals" | "notEquals" | "matches" | "notMatches"
value: string
}
@@ -1884,8 +1884,10 @@ const (
type DashboardConditionalRenderingVariableSpecOperator string
const (
DashboardConditionalRenderingVariableSpecOperatorEquals DashboardConditionalRenderingVariableSpecOperator = "equals"
DashboardConditionalRenderingVariableSpecOperatorNotEquals DashboardConditionalRenderingVariableSpecOperator = "notEquals"
DashboardConditionalRenderingVariableSpecOperatorEquals DashboardConditionalRenderingVariableSpecOperator = "equals"
DashboardConditionalRenderingVariableSpecOperatorNotEquals DashboardConditionalRenderingVariableSpecOperator = "notEquals"
DashboardConditionalRenderingVariableSpecOperatorMatches DashboardConditionalRenderingVariableSpecOperator = "matches"
DashboardConditionalRenderingVariableSpecOperatorNotMatches DashboardConditionalRenderingVariableSpecOperator = "notMatches"
)
// +k8s:openapi-gen=true
@@ -8,8 +8,11 @@ import (
dashv1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1"
dashv2alpha1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1"
dashv2beta1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1"
"github.com/grafana/grafana/pkg/infra/log"
)
var logger = log.New("dashboard.conversion")
func RegisterConversions(s *runtime.Scheme) error {
// v0 conversions
if err := s.AddConversionFunc((*dashv0.Dashboard)(nil), (*dashv1.Dashboard)(nil), func(a, b interface{}, scope conversion.Scope) error {
@@ -1,6 +1,9 @@
package conversion
import (
"errors"
"fmt"
"k8s.io/apimachinery/pkg/conversion"
dashv0 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v0alpha1"
@@ -25,8 +28,45 @@ func Convert_V0_to_V1(in *dashv0.Dashboard, out *dashv1.Dashboard, scope convers
if err := migration.Migrate(out.Spec.Object, schemaversion.LATEST_VERSION); err != nil {
out.Status.Conversion.Failed = true
out.Status.Conversion.Error = err.Error()
// Classify error type for metrics
errorType := "conversion_error"
var migrationErr *schemaversion.MigrationError
var minVersionErr *schemaversion.MinimumVersionError
if errors.As(err, &migrationErr) {
errorType = "schema_version_migration_error"
} else if errors.As(err, &minVersionErr) {
errorType = "schema_minimum_version_error"
}
// Record failure metrics
migration.MDashboardConversionFailureTotal.WithLabelValues(
dashv0.APIVERSION,
dashv1.APIVERSION,
fmt.Sprintf("%v", in.Spec.Object["schemaVersion"]),
fmt.Sprintf("%d", schemaversion.LATEST_VERSION),
errorType,
).Inc()
logger.Error("Dashboard conversion failed",
"sourceVersionAPI", dashv0.APIVERSION,
"targetVersionAPI", dashv1.APIVERSION,
"dashboardUID", in.UID,
"sourceSchemaVersion", in.Spec.Object["schemaVersion"],
"targetSchemaVersion", schemaversion.LATEST_VERSION,
"errorType", errorType,
"error", err)
return nil
}
migration.MDashboardConversionSuccessTotal.WithLabelValues(
dashv0.APIVERSION,
dashv1.APIVERSION,
fmt.Sprintf("%v", in.Spec.Object["schemaVersion"]),
fmt.Sprintf("%d", schemaversion.LATEST_VERSION),
).Inc()
return nil
}
+44
View File
@@ -0,0 +1,44 @@
package migration
import (
"github.com/prometheus/client_golang/prometheus"
)
const (
metricsNamespace = "grafana"
metricsSubSystem = "dashboard_migration"
)
var (
// MDashboardConversionSuccessTotal is a metric counter for successful dashboard conversions
MDashboardConversionSuccessTotal *prometheus.CounterVec
// MDashboardConversionFailureTotal is a metric counter for failed dashboard conversions
MDashboardConversionFailureTotal *prometheus.CounterVec
)
func init() {
MDashboardConversionSuccessTotal = prometheus.NewCounterVec(prometheus.CounterOpts{
Namespace: metricsNamespace,
Subsystem: metricsSubSystem,
Name: "conversion_success_total",
Help: "Total number of successful dashboard conversions",
}, []string{"source_version_api", "target_version_api", "source_schema_version", "target_schema_version"})
MDashboardConversionFailureTotal = prometheus.NewCounterVec(prometheus.CounterOpts{
Namespace: metricsNamespace,
Subsystem: metricsSubSystem,
Name: "conversion_failure_total",
Help: "Total number of failed dashboard conversions",
}, []string{"source_version_api", "target_version_api", "source_schema_version", "target_schema_version", "error_type"})
}
// RegisterMetrics registers all migration metrics with the provided Prometheus registerer
func RegisterMetrics(reg prometheus.Registerer) {
if reg != nil {
reg.MustRegister(
MDashboardConversionSuccessTotal,
MDashboardConversionFailureTotal,
)
}
}
@@ -0,0 +1,89 @@
package schemaversion
var notPersistedProperties = []string{
"events",
"isViewing",
"isEditing",
"isInView",
"hasRefreshed",
"cachedPluginOptions",
"plugin",
"queryRunner",
"replaceVariables",
"configRev",
"hasSavedPanelEditChange",
"getDisplayTitle",
"dataSupport",
"key",
"isNew",
"refreshWhenInView",
}
var mustKeepProperties = []string{
"id",
"gridPos",
"type",
"title",
"scopedVars",
"repeat",
"repeatPanelId",
"repeatDirection",
"repeatedByRow",
"minSpan",
"collapsed",
"panels",
"targets",
"datasource",
"timeFrom",
"timeShift",
"hideTimeOverride",
"description",
"links",
"fullscreen",
"isEditing",
"isViewing",
"hasRefreshed",
"events",
"cacheTimeout",
"queryCachingTTL",
"cachedPluginOptions",
"transparent",
"pluginVersion",
"queryRunner",
"transformations",
"fieldConfig",
"maxDataPoints",
"interval",
"replaceVariables",
"libraryPanel",
"getDisplayTitle",
"configRev",
"key",
}
// getOptionsToRemember returns a map of panel properties that should be remembered
// during panel type changes, excluding notPersistedProperties and mustKeepProperties
func getOptionsToRemember(panel map[string]interface{}) map[string]interface{} {
// Create sets for faster lookup
notPersistedSet := make(map[string]bool)
for _, prop := range notPersistedProperties {
notPersistedSet[prop] = true
}
mustKeepSet := make(map[string]bool)
for _, prop := range mustKeepProperties {
mustKeepSet[prop] = true
}
// Filter the panel properties
result := make(map[string]interface{})
for key, value := range panel {
// Skip properties that are in notPersistedProperties or mustKeepProperties
if notPersistedSet[key] || mustKeepSet[key] {
continue
}
result[key] = value
}
return result
}
@@ -5,7 +5,7 @@ import (
)
const (
MIN_VERSION = 25
MIN_VERSION = 16
LATEST_VERSION = 41
)
@@ -38,6 +38,15 @@ type PanelPluginInfoProvider interface {
func GetMigrations(dsInfoProvider DataSourceInfoProvider, panelProvider PanelPluginInfoProvider) map[int]SchemaVersionMigrationFunc {
return map[int]SchemaVersionMigrationFunc{
17: V17,
18: V18,
19: V19,
20: V20,
21: V21,
22: V22,
23: V23,
24: V24(panelProvider),
25: V25,
26: V26,
27: V27,
28: V28(panelProvider),
@@ -0,0 +1,132 @@
package schemaversion
import (
"math"
"sort"
)
// V17 migrates panel minSpan property to maxPerRow property.
// This migration converts the deprecated minSpan property to the newer maxPerRow property
// which controls how many panels can be displayed in a single row.
//
// The conversion algorithm:
// 1. Calculate max = GRID_COLUMN_COUNT / panel.minSpan
// 2. Get all factors of GRID_COLUMN_COUNT (24): [1, 2, 3, 4, 6, 8, 12, 24]
// 3. Find the first factor greater than max
// 4. Use the previous factor as maxPerRow
//
// Example before migration:
//
// "panels": [
// {
// "id": 1,
// "type": "graph",
// "minSpan": 8
// }
// ]
//
// Example after migration:
//
// "panels": [
// {
// "id": 1,
// "type": "graph",
// "maxPerRow": 3
// }
// ]
//
// The minSpan property is removed after conversion.
func V17(dashboard map[string]interface{}) error {
dashboard["schemaVersion"] = 17
panels, ok := dashboard["panels"].([]interface{})
if !ok {
return nil
}
for _, p := range panels {
panel, ok := p.(map[string]interface{})
if !ok {
continue
}
migrateMinSpanToMaxPerRow(panel)
}
return nil
}
// migrateMinSpanToMaxPerRow converts minSpan to maxPerRow using the same algorithm as the frontend.
func migrateMinSpanToMaxPerRow(panel map[string]interface{}) {
minSpanValue, ok := panel["minSpan"]
if !ok {
return
}
// Convert minSpan to a number (could be int, float64, etc.)
var minSpan float64
switch v := minSpanValue.(type) {
case int:
minSpan = float64(v)
case float64:
minSpan = v
case int64:
minSpan = float64(v)
default:
// If we can't convert minSpan to a number, just delete it and return
delete(panel, "minSpan")
return
}
// Ensure minSpan is positive to avoid division by zero
if minSpan <= 0 {
delete(panel, "minSpan")
return
}
const gridColumnCount = 24
max := gridColumnCount / minSpan
factors := getFactors(gridColumnCount)
// Find the first factor greater than max
factorIndex := -1
for i, factor := range factors {
if float64(factor) > max {
factorIndex = i
break
}
}
// Use the previous factor as maxPerRow
if factorIndex > 0 {
panel["maxPerRow"] = factors[factorIndex-1]
} else if factorIndex == 0 {
// If the first factor is already greater than max, use 1
panel["maxPerRow"] = 1
} else {
// If no factor is greater than max, use the largest factor
panel["maxPerRow"] = factors[len(factors)-1]
}
// Remove the minSpan property
delete(panel, "minSpan")
}
// getFactors returns all factors of a number
// Example: getFactors(24) returns [1, 2, 3, 4, 6, 8, 12, 24]
func getFactors(num int) []int {
factors := []int{}
for i := 1; i <= int(math.Sqrt(float64(num))); i++ {
if num%i == 0 {
factors = append(factors, i)
if i != num/i {
factors = append(factors, num/i)
}
}
}
// Sort factors in ascending order
sort.Ints(factors)
return factors
}
@@ -0,0 +1,297 @@
package schemaversion_test
import (
"testing"
"github.com/grafana/grafana/apps/dashboard/pkg/migration/schemaversion"
)
func TestV17(t *testing.T) {
tests := []migrationTestCase{
{
name: "panel with minSpan 8 gets converted to maxPerRow 3",
input: map[string]interface{}{
"title": "V17 MinSpan to MaxPerRow Migration Test Dashboard",
"schemaVersion": 16,
"panels": []interface{}{
map[string]interface{}{
"id": 1,
"type": "graph",
"title": "Test Panel",
"minSpan": 8,
},
},
},
expected: map[string]interface{}{
"title": "V17 MinSpan to MaxPerRow Migration Test Dashboard",
"schemaVersion": 17,
"panels": []interface{}{
map[string]interface{}{
"id": 1,
"type": "graph",
"title": "Test Panel",
"maxPerRow": 3,
},
},
},
},
{
name: "panel with minSpan 4 gets converted to maxPerRow 6",
input: map[string]interface{}{
"title": "V17 MinSpan Migration Test",
"schemaVersion": 16,
"panels": []interface{}{
map[string]interface{}{
"id": 2,
"type": "singlestat",
"title": "Single Stat Panel",
"minSpan": 4,
},
},
},
expected: map[string]interface{}{
"title": "V17 MinSpan Migration Test",
"schemaVersion": 17,
"panels": []interface{}{
map[string]interface{}{
"id": 2,
"type": "singlestat",
"title": "Single Stat Panel",
"maxPerRow": 6,
},
},
},
},
{
name: "panel with minSpan 2 gets converted to maxPerRow 12",
input: map[string]interface{}{
"title": "V17 MinSpan Migration Test",
"schemaVersion": 16,
"panels": []interface{}{
map[string]interface{}{
"id": 3,
"type": "graph",
"title": "Wide Panel",
"minSpan": 2,
},
},
},
expected: map[string]interface{}{
"title": "V17 MinSpan Migration Test",
"schemaVersion": 17,
"panels": []interface{}{
map[string]interface{}{
"id": 3,
"type": "graph",
"title": "Wide Panel",
"maxPerRow": 12,
},
},
},
},
{
name: "panel with minSpan 12 gets converted to maxPerRow 2",
input: map[string]interface{}{
"title": "V17 MinSpan Migration Test",
"schemaVersion": 16,
"panels": []interface{}{
map[string]interface{}{
"id": 4,
"type": "graph",
"title": "Narrow Panel",
"minSpan": 12,
},
},
},
expected: map[string]interface{}{
"title": "V17 MinSpan Migration Test",
"schemaVersion": 17,
"panels": []interface{}{
map[string]interface{}{
"id": 4,
"type": "graph",
"title": "Narrow Panel",
"maxPerRow": 2,
},
},
},
},
{
name: "panel with minSpan 24 gets converted to maxPerRow 1",
input: map[string]interface{}{
"title": "V17 MinSpan Migration Test",
"schemaVersion": 16,
"panels": []interface{}{
map[string]interface{}{
"id": 5,
"type": "graph",
"title": "Full Width Panel",
"minSpan": 24,
},
},
},
expected: map[string]interface{}{
"title": "V17 MinSpan Migration Test",
"schemaVersion": 17,
"panels": []interface{}{
map[string]interface{}{
"id": 5,
"type": "graph",
"title": "Full Width Panel",
"maxPerRow": 1,
},
},
},
},
{
name: "panel with minSpan 1 gets converted to maxPerRow 24",
input: map[string]interface{}{
"title": "V17 MinSpan Migration Test",
"schemaVersion": 16,
"panels": []interface{}{
map[string]interface{}{
"id": 6,
"type": "graph",
"title": "Tiny Panel",
"minSpan": 1,
},
},
},
expected: map[string]interface{}{
"title": "V17 MinSpan Migration Test",
"schemaVersion": 17,
"panels": []interface{}{
map[string]interface{}{
"id": 6,
"type": "graph",
"title": "Tiny Panel",
"maxPerRow": 24,
},
},
},
},
{
name: "multiple panels with different minSpan values",
input: map[string]interface{}{
"title": "V17 Multiple Panels Migration Test",
"schemaVersion": 16,
"panels": []interface{}{
map[string]interface{}{
"id": 1,
"type": "graph",
"title": "Panel 1",
"minSpan": 8,
},
map[string]interface{}{
"id": 2,
"type": "singlestat",
"title": "Panel 2",
"minSpan": 4,
},
map[string]interface{}{
"id": 3,
"type": "table",
"title": "Panel 3 - No minSpan",
},
},
},
expected: map[string]interface{}{
"title": "V17 Multiple Panels Migration Test",
"schemaVersion": 17,
"panels": []interface{}{
map[string]interface{}{
"id": 1,
"type": "graph",
"title": "Panel 1",
"maxPerRow": 3,
},
map[string]interface{}{
"id": 2,
"type": "singlestat",
"title": "Panel 2",
"maxPerRow": 6,
},
map[string]interface{}{
"id": 3,
"type": "table",
"title": "Panel 3 - No minSpan",
},
},
},
},
{
name: "panel with invalid minSpan gets cleaned up",
input: map[string]interface{}{
"title": "V17 Invalid MinSpan Test",
"schemaVersion": 16,
"panels": []interface{}{
map[string]interface{}{
"id": 1,
"type": "graph",
"title": "Invalid MinSpan Panel",
"minSpan": "invalid",
},
map[string]interface{}{
"id": 2,
"type": "graph",
"title": "Zero MinSpan Panel",
"minSpan": 0,
},
map[string]interface{}{
"id": 3,
"type": "graph",
"title": "Negative MinSpan Panel",
"minSpan": -5,
},
},
},
expected: map[string]interface{}{
"title": "V17 Invalid MinSpan Test",
"schemaVersion": 17,
"panels": []interface{}{
map[string]interface{}{
"id": 1,
"type": "graph",
"title": "Invalid MinSpan Panel",
},
map[string]interface{}{
"id": 2,
"type": "graph",
"title": "Zero MinSpan Panel",
},
map[string]interface{}{
"id": 3,
"type": "graph",
"title": "Negative MinSpan Panel",
},
},
},
},
{
name: "dashboard with no panels",
input: map[string]interface{}{
"title": "V17 No Panels Test",
"schemaVersion": 16,
},
expected: map[string]interface{}{
"title": "V17 No Panels Test",
"schemaVersion": 17,
},
},
{
name: "dashboard with empty panels array",
input: map[string]interface{}{
"title": "V17 Empty Panels Test",
"schemaVersion": 16,
"panels": []interface{}{},
},
expected: map[string]interface{}{
"title": "V17 Empty Panels Test",
"schemaVersion": 17,
"panels": []interface{}{},
},
},
}
runMigrationTests(t, tests, schemaversion.V17)
}
@@ -0,0 +1,117 @@
package schemaversion
// V18 migrates gauge panel options from the legacy `options-gauge` format to the new `options` format.
// This migration restructures gauge panel configuration to use the modern options structure with valueOptions.
//
// Example before migration:
//
// "panels": [
// {
// "type": "gauge",
// "options-gauge": {
// "unit": "ms",
// "stat": "last",
// "decimals": 2,
// "prefix": "Value: ",
// "suffix": " ms",
// "thresholds": [
// { "color": "green", "value": 0 },
// { "color": "red", "value": 100 }
// ]
// }
// }
// ]
//
// Example after migration:
//
// "panels": [
// {
// "type": "gauge",
// "options": {
// "valueOptions": {
// "unit": "ms",
// "stat": "last",
// "decimals": 2,
// "prefix": "Value: ",
// "suffix": " ms"
// },
// "thresholds": [
// { "color": "red", "value": 100 },
// { "color": "green", "value": 0 }
// ]
// }
// }
// ]
func V18(dashboard map[string]interface{}) error {
dashboard["schemaVersion"] = 18
panels, ok := dashboard["panels"].([]interface{})
if !ok {
return nil
}
for _, p := range panels {
panel, ok := p.(map[string]interface{})
if !ok {
continue
}
migrateGaugePanelOptions(panel)
}
return nil
}
func migrateGaugePanelOptions(panel map[string]interface{}) {
optionsGauge, hasOptionsGauge := panel["options-gauge"].(map[string]interface{})
if !hasOptionsGauge {
return
}
options := map[string]interface{}{}
valueOptions := map[string]interface{}{}
if unit, ok := optionsGauge["unit"]; ok {
valueOptions["unit"] = unit
}
if stat, ok := optionsGauge["stat"]; ok {
valueOptions["stat"] = stat
}
if decimals, ok := optionsGauge["decimals"]; ok {
valueOptions["decimals"] = decimals
}
if prefix, ok := optionsGauge["prefix"]; ok {
valueOptions["prefix"] = prefix
}
if suffix, ok := optionsGauge["suffix"]; ok {
valueOptions["suffix"] = suffix
}
options["valueOptions"] = valueOptions
if thresholds, ok := optionsGauge["thresholds"].([]interface{}); ok && len(thresholds) > 0 {
reversedThresholds := make([]interface{}, len(thresholds))
for i, threshold := range thresholds {
reversedThresholds[len(thresholds)-1-i] = threshold
}
options["thresholds"] = reversedThresholds
}
// Copy any other properties from options-gauge to options
for key, value := range optionsGauge {
// Skip properties that were moved to valueOptions or are being deleted
if key == "options" || key == "unit" || key == "stat" || key == "decimals" || key == "prefix" || key == "suffix" || key == "thresholds" {
continue
}
options[key] = value
}
panel["options"] = options
delete(panel, "options-gauge")
// Clean up options.options property if it exists
// This options prop was due to a bug
if panelOptions, ok := panel["options"].(map[string]interface{}); ok {
delete(panelOptions, "options")
}
}
@@ -0,0 +1,219 @@
package schemaversion_test
import (
"testing"
"github.com/grafana/grafana/apps/dashboard/pkg/migration/schemaversion"
)
func TestV18(t *testing.T) {
tests := []migrationTestCase{
{
name: "gauge panel with legacy options-gauge gets migrated to new options format",
input: map[string]interface{}{
"title": "V18 Gauge Options Migration Test Dashboard",
"schemaVersion": 17,
"panels": []interface{}{
map[string]interface{}{
"id": 1,
"type": "gauge",
"title": "Gauge Panel",
"options-gauge": map[string]interface{}{
"unit": "ms",
"stat": "last",
"decimals": 2,
"prefix": "Value: ",
"suffix": " ms",
"thresholds": []interface{}{
map[string]interface{}{"color": "green", "value": 0},
map[string]interface{}{"color": "red", "value": 100},
},
},
},
},
},
expected: map[string]interface{}{
"title": "V18 Gauge Options Migration Test Dashboard",
"schemaVersion": 18,
"panels": []interface{}{
map[string]interface{}{
"id": 1,
"type": "gauge",
"title": "Gauge Panel",
"options": map[string]interface{}{
"valueOptions": map[string]interface{}{
"unit": "ms",
"stat": "last",
"decimals": 2,
"prefix": "Value: ",
"suffix": " ms",
},
"thresholds": []interface{}{
map[string]interface{}{"color": "red", "value": 100},
map[string]interface{}{"color": "green", "value": 0},
},
},
},
},
},
},
{
name: "gauge panel with only some gauge options gets migrated correctly",
input: map[string]interface{}{
"title": "V18 Partial Gauge Options Migration Test Dashboard",
"schemaVersion": 17,
"panels": []interface{}{
map[string]interface{}{
"id": 1,
"type": "gauge",
"title": "Partial Gauge Panel",
"options-gauge": map[string]interface{}{
"unit": "percent",
"decimals": 1,
},
},
},
},
expected: map[string]interface{}{
"title": "V18 Partial Gauge Options Migration Test Dashboard",
"schemaVersion": 18,
"panels": []interface{}{
map[string]interface{}{
"id": 1,
"type": "gauge",
"title": "Partial Gauge Panel",
"options": map[string]interface{}{
"valueOptions": map[string]interface{}{
"unit": "percent",
"decimals": 1,
},
},
},
},
},
},
{
name: "gauge panel with buggy options property gets cleaned up",
input: map[string]interface{}{
"title": "V18 Buggy Options Cleanup Test Dashboard",
"schemaVersion": 17,
"panels": []interface{}{
map[string]interface{}{
"id": 1,
"type": "gauge",
"title": "Buggy Gauge Panel",
"options-gauge": map[string]interface{}{
"unit": "bytes",
"options": "this should be deleted",
"stat": "avg",
"decimals": 0,
},
},
},
},
expected: map[string]interface{}{
"title": "V18 Buggy Options Cleanup Test Dashboard",
"schemaVersion": 18,
"panels": []interface{}{
map[string]interface{}{
"id": 1,
"type": "gauge",
"title": "Buggy Gauge Panel",
"options": map[string]interface{}{
"valueOptions": map[string]interface{}{
"unit": "bytes",
"stat": "avg",
"decimals": 0,
},
},
},
},
},
},
{
name: "gauge panel with additional custom properties gets migrated correctly",
input: map[string]interface{}{
"title": "V18 Custom Properties Migration Test Dashboard",
"schemaVersion": 17,
"panels": []interface{}{
map[string]interface{}{
"id": 1,
"type": "gauge",
"title": "Custom Gauge Panel",
"options-gauge": map[string]interface{}{
"unit": "short",
"customProperty": "customValue",
"anotherProp": 42,
},
},
},
},
expected: map[string]interface{}{
"title": "V18 Custom Properties Migration Test Dashboard",
"schemaVersion": 18,
"panels": []interface{}{
map[string]interface{}{
"id": 1,
"type": "gauge",
"title": "Custom Gauge Panel",
"options": map[string]interface{}{
"valueOptions": map[string]interface{}{
"unit": "short",
},
"customProperty": "customValue",
"anotherProp": 42,
},
},
},
},
},
{
name: "non-gauge panel remains unchanged",
input: map[string]interface{}{
"title": "V18 Non-Gauge Panel Test Dashboard",
"schemaVersion": 17,
"panels": []interface{}{
map[string]interface{}{
"id": 1,
"type": "graph",
"title": "Graph Panel",
"options": map[string]interface{}{
"legend": map[string]interface{}{
"show": true,
},
},
},
},
},
expected: map[string]interface{}{
"title": "V18 Non-Gauge Panel Test Dashboard",
"schemaVersion": 18,
"panels": []interface{}{
map[string]interface{}{
"id": 1,
"type": "graph",
"title": "Graph Panel",
"options": map[string]interface{}{
"legend": map[string]interface{}{
"show": true,
},
},
},
},
},
},
{
name: "dashboard with no panels remains unchanged",
input: map[string]interface{}{
"title": "V18 No Panels Test Dashboard",
"schemaVersion": 17,
},
expected: map[string]interface{}{
"title": "V18 No Panels Test Dashboard",
"schemaVersion": 18,
},
},
}
runMigrationTests(t, tests, schemaversion.V18)
}
@@ -0,0 +1,166 @@
package schemaversion
import (
"regexp"
"strings"
)
// V19 migrates panel links to ensure they have proper URL structure and handle legacy properties.
// This migration converts legacy panel link properties to the new URL-based format.
//
// Example before migration:
//
// "panels": [
// {
// "links": [
// {
// "dashboard": "my dashboard",
// "keepTime": true,
// "includeVars": true,
// "params": "customParam"
// }
// ]
// }
// ]
//
// Example after migration:
//
// "panels": [
// {
// "links": [
// {
// "url": "dashboard/db/my-dashboard?$__keepTime&$__includeVars&customParam",
// "title": "",
// "targetBlank": false
// }
// ]
// }
// ]
func V19(dashboard map[string]interface{}) error {
dashboard["schemaVersion"] = 19
panels, ok := dashboard["panels"].([]interface{})
if !ok {
return nil
}
for _, p := range panels {
panel, ok := p.(map[string]interface{})
if !ok {
continue
}
links, ok := panel["links"].([]interface{})
if !ok {
continue
}
panel["links"] = upgradePanelLinks(links)
}
return nil
}
func upgradePanelLinks(links []interface{}) []interface{} {
if len(links) == 0 {
return links
}
result := []interface{}{}
for _, link := range links {
linkMap, ok := link.(map[string]interface{})
if !ok {
continue
}
result = append(result, upgradePanelLink(linkMap))
}
return result
}
func upgradePanelLink(link map[string]interface{}) map[string]interface{} {
url := buildPanelLinkURL(link)
result := map[string]interface{}{
"url": url,
"title": getStringValue(link, "title"),
"targetBlank": getBoolValue(link, "targetBlank"),
}
return result
}
// buildPanelLinkURL builds the URL for a panel link based on legacy properties
func buildPanelLinkURL(link map[string]interface{}) string {
var url string
// Check for existing URL first
if existingURL, ok := link["url"].(string); ok && existingURL != "" {
url = existingURL
} else if dashboard, ok := link["dashboard"].(string); ok && dashboard != "" {
// Convert dashboard name to slugified URL
url = "dashboard/db/" + slugifyForURL(dashboard)
} else if dashUri, ok := link["dashUri"].(string); ok && dashUri != "" {
url = "dashboard/" + dashUri
} else {
// Default fallback
url = "/"
}
// Add query parameters
params := []string{}
if getBoolValue(link, "keepTime") {
params = append(params, "$__url_time_range")
}
if getBoolValue(link, "includeVars") {
params = append(params, "$__all_variables")
}
if customParams, ok := link["params"].(string); ok && customParams != "" {
params = append(params, customParams)
}
// Append parameters to URL
paramUsed := false
for _, param := range params {
if param != "" {
if paramUsed {
url += "&"
} else {
url += "?"
paramUsed = true
}
url += param
}
}
return url
}
var reNonWordOrSpace = regexp.MustCompile(`[^a-z0-9_ ]+`)
var reSpaces = regexp.MustCompile(` +`)
// slugifyForURL converts a dashboard name to a URL-friendly slug
func slugifyForURL(name string) string {
name = strings.ToLower(name)
name = reNonWordOrSpace.ReplaceAllString(name, "")
name = reSpaces.ReplaceAllString(name, "-")
return name
}
func getStringValue(m map[string]interface{}, key string) string {
if v, ok := m[key].(string); ok {
return v
}
return ""
}
func getBoolValue(m map[string]interface{}, key string) bool {
if v, ok := m[key].(bool); ok {
return v
}
return false
}
@@ -0,0 +1,292 @@
package schemaversion_test
import (
"testing"
"github.com/grafana/grafana/apps/dashboard/pkg/migration/schemaversion"
)
func TestV19(t *testing.T) {
tests := []migrationTestCase{
{
name: "panel with legacy dashboard link gets upgraded to URL format",
input: map[string]interface{}{
"title": "V19 Panel Links Migration Test Dashboard",
"schemaVersion": 18,
"panels": []interface{}{
map[string]interface{}{
"id": 1,
"links": []interface{}{
map[string]interface{}{
"dashboard": "my dashboard",
"title": "Dashboard Link",
},
},
},
},
},
expected: map[string]interface{}{
"title": "V19 Panel Links Migration Test Dashboard",
"schemaVersion": 19,
"panels": []interface{}{
map[string]interface{}{
"id": 1,
"links": []interface{}{
map[string]interface{}{
"url": "dashboard/db/my-dashboard",
"title": "Dashboard Link",
"targetBlank": false,
},
},
},
},
},
},
{
name: "panel with legacy dashUri link gets upgraded to URL format",
input: map[string]interface{}{
"title": "V19 DashUri Links Migration Test Dashboard",
"schemaVersion": 18,
"panels": []interface{}{
map[string]interface{}{
"id": 1,
"links": []interface{}{
map[string]interface{}{
"dashUri": "my-dashboard-uid",
"title": "DashUri Link",
},
},
},
},
},
expected: map[string]interface{}{
"title": "V19 DashUri Links Migration Test Dashboard",
"schemaVersion": 19,
"panels": []interface{}{
map[string]interface{}{
"id": 1,
"links": []interface{}{
map[string]interface{}{
"url": "dashboard/my-dashboard-uid",
"title": "DashUri Link",
"targetBlank": false,
},
},
},
},
},
},
{
name: "panel with keepTime flag gets upgraded with keepTime parameter",
input: map[string]interface{}{
"title": "V19 KeepTime Links Migration Test Dashboard",
"schemaVersion": 18,
"panels": []interface{}{
map[string]interface{}{
"id": 1,
"links": []interface{}{
map[string]interface{}{
"url": "http://example.com",
"keepTime": true,
"title": "KeepTime Link",
},
},
},
},
},
expected: map[string]interface{}{
"title": "V19 KeepTime Links Migration Test Dashboard",
"schemaVersion": 19,
"panels": []interface{}{
map[string]interface{}{
"id": 1,
"links": []interface{}{
map[string]interface{}{
"url": "http://example.com?$__url_time_range",
"title": "KeepTime Link",
"targetBlank": false,
},
},
},
},
},
},
{
name: "panel with includeVars flag gets upgraded with includeVars parameter",
input: map[string]interface{}{
"title": "V19 IncludeVars Links Migration Test Dashboard",
"schemaVersion": 18,
"panels": []interface{}{
map[string]interface{}{
"id": 1,
"links": []interface{}{
map[string]interface{}{
"url": "http://example.com",
"includeVars": true,
"title": "IncludeVars Link",
},
},
},
},
},
expected: map[string]interface{}{
"title": "V19 IncludeVars Links Migration Test Dashboard",
"schemaVersion": 19,
"panels": []interface{}{
map[string]interface{}{
"id": 1,
"links": []interface{}{
map[string]interface{}{
"url": "http://example.com?$__all_variables",
"title": "IncludeVars Link",
"targetBlank": false,
},
},
},
},
},
},
{
name: "panel with custom params gets upgraded with params in URL",
input: map[string]interface{}{
"title": "V19 Custom Params Links Migration Test Dashboard",
"schemaVersion": 18,
"panels": []interface{}{
map[string]interface{}{
"id": 1,
"links": []interface{}{
map[string]interface{}{
"url": "http://example.com",
"params": "customParam=value",
"title": "Custom Params Link",
},
},
},
},
},
expected: map[string]interface{}{
"title": "V19 Custom Params Links Migration Test Dashboard",
"schemaVersion": 19,
"panels": []interface{}{
map[string]interface{}{
"id": 1,
"links": []interface{}{
map[string]interface{}{
"url": "http://example.com?customParam=value",
"title": "Custom Params Link",
"targetBlank": false,
},
},
},
},
},
},
{
name: "panel with multiple flags and params gets upgraded correctly",
input: map[string]interface{}{
"title": "V19 Complex Links Migration Test Dashboard",
"schemaVersion": 18,
"panels": []interface{}{
map[string]interface{}{
"id": 1,
"links": []interface{}{
map[string]interface{}{
"dashboard": "my dashboard",
"keepTime": true,
"includeVars": true,
"params": "customParam=value",
"title": "Complex Link",
"targetBlank": true,
},
},
},
},
},
expected: map[string]interface{}{
"title": "V19 Complex Links Migration Test Dashboard",
"schemaVersion": 19,
"panels": []interface{}{
map[string]interface{}{
"id": 1,
"links": []interface{}{
map[string]interface{}{
"url": "dashboard/db/my-dashboard?$__url_time_range&$__all_variables&customParam=value",
"title": "Complex Link",
"targetBlank": true,
},
},
},
},
},
},
{
name: "panel with existing URL and no legacy properties remains unchanged",
input: map[string]interface{}{
"title": "V19 Existing URL Links Migration Test Dashboard",
"schemaVersion": 18,
"panels": []interface{}{
map[string]interface{}{
"id": 1,
"links": []interface{}{
map[string]interface{}{
"url": "http://example.com",
"title": "Existing URL Link",
"targetBlank": false,
},
},
},
},
},
expected: map[string]interface{}{
"title": "V19 Existing URL Links Migration Test Dashboard",
"schemaVersion": 19,
"panels": []interface{}{
map[string]interface{}{
"id": 1,
"links": []interface{}{
map[string]interface{}{
"url": "http://example.com",
"title": "Existing URL Link",
"targetBlank": false,
},
},
},
},
},
},
{
name: "panel with no links remains unchanged",
input: map[string]interface{}{
"title": "V19 No Links Migration Test Dashboard",
"schemaVersion": 18,
"panels": []interface{}{
map[string]interface{}{
"id": 1,
},
},
},
expected: map[string]interface{}{
"title": "V19 No Links Migration Test Dashboard",
"schemaVersion": 19,
"panels": []interface{}{
map[string]interface{}{
"id": 1,
},
},
},
},
{
name: "dashboard with no panels remains unchanged",
input: map[string]interface{}{
"title": "V19 No Panels Migration Test Dashboard",
"schemaVersion": 18,
},
expected: map[string]interface{}{
"title": "V19 No Panels Migration Test Dashboard",
"schemaVersion": 19,
},
},
}
runMigrationTests(t, tests, schemaversion.V19)
}
@@ -0,0 +1,163 @@
package schemaversion
import (
"regexp"
"github.com/grafana/grafana/apps/dashboard/pkg/migration/utils"
)
// V20 migrates legacy variable syntax in data links and field options.
// This migration updates variable names from old syntax to new dotted syntax
// used in data links URLs and field option titles.
//
// Variable syntax changes:
// - __series_name → __series.name
// - $__series_name → ${__series.name}
// - __value_time → __value.time
// - __field_name → __field.name
// - $__field_name → ${__field.name}
//
// Example before migration:
//
// "panels": [
// {
// "options": {
// "dataLinks": [
// {
// "url": "http://example.com?series=$__series_name&time=__value_time"
// }
// ],
// "fieldOptions": {
// "defaults": {
// "title": "Field: __field_name",
// "links": [
// {
// "url": "http://example.com?field=$__field_name"
// }
// ]
// }
// }
// }
// }
// ]
//
// Example after migration:
//
// "panels": [
// {
// "options": {
// "dataLinks": [
// {
// "url": "http://example.com?series=${__series.name}&time=__value.time"
// }
// ],
// "fieldOptions": {
// "defaults": {
// "title": "Field: __field.name",
// "links": [
// {
// "url": "http://example.com?field=${__field.name}"
// }
// ]
// }
// }
// }
// }
// ]
func V20(dashboard map[string]interface{}) error {
dashboard["schemaVersion"] = 20
panels, ok := dashboard["panels"].([]interface{})
if !ok {
return nil
}
for _, p := range panels {
panel, ok := p.(map[string]interface{})
if !ok {
continue
}
// Update data links and field options in panel options
if options, ok := panel["options"].(map[string]interface{}); ok {
updateDataLinksVariableSyntax(options)
updateFieldOptionsVariableSyntax(options)
}
}
return nil
}
// updateDataLinksVariableSyntax updates variable syntax in panel data links
func updateDataLinksVariableSyntax(options map[string]interface{}) {
dataLinks, ok := options["dataLinks"].([]interface{})
if !ok || !utils.IsArray(dataLinks) {
return
}
for _, link := range dataLinks {
if linkMap, ok := link.(map[string]interface{}); ok {
if url, ok := linkMap["url"].(string); ok {
linkMap["url"] = updateVariablesSyntax(url)
}
}
}
}
// updateFieldOptionsVariableSyntax updates variable syntax in field options
func updateFieldOptionsVariableSyntax(options map[string]interface{}) {
fieldOptions, ok := options["fieldOptions"].(map[string]interface{})
if !ok {
return
}
defaults, ok := fieldOptions["defaults"].(map[string]interface{})
if !ok {
return
}
// Update field option title
if title, ok := defaults["title"].(string); ok {
defaults["title"] = updateVariablesSyntax(title)
}
// Update field option links
links, ok := defaults["links"].([]interface{})
if !ok || !utils.IsArray(links) {
return
}
for _, link := range links {
if linkMap, ok := link.(map[string]interface{}); ok {
if url, ok := linkMap["url"].(string); ok {
linkMap["url"] = updateVariablesSyntax(url)
}
}
}
}
// Define the regex pattern to match legacy variable names
// Pattern matches: __series_name, $__series_name, __value_time, __field_name, $__field_name
// Defined here to avoid compilation for every function call
var legacyVariableNamesRegex = regexp.MustCompile(`(__series_name)|(\$__series_name)|(__value_time)|(__field_name)|(\$__field_name)`)
// updateVariablesSyntax updates legacy variable names to new dotted syntax
// This function replicates the frontend updateVariablesSyntax behavior
func updateVariablesSyntax(text string) string {
return legacyVariableNamesRegex.ReplaceAllStringFunc(text, func(match string) string {
switch match {
case "__series_name":
return "__series.name"
case "$__series_name":
return "${__series.name}"
case "__value_time":
return "__value.time"
case "__field_name":
return "__field.name"
case "$__field_name":
return "${__field.name}"
default:
return match
}
})
}
@@ -0,0 +1,322 @@
package schemaversion_test
import (
"testing"
"github.com/grafana/grafana/apps/dashboard/pkg/migration/schemaversion"
)
func TestV20(t *testing.T) {
tests := []migrationTestCase{
{
name: "panel with data links gets variable syntax migrated",
input: map[string]interface{}{
"title": "V20 Data Links Variable Syntax Migration Test Dashboard",
"schemaVersion": 19,
"panels": []interface{}{
map[string]interface{}{
"type": "timeseries",
"title": "Panel with data links",
"id": 1,
"options": map[string]interface{}{
"dataLinks": []interface{}{
map[string]interface{}{
"url": "http://mylink.com?series=$__series_name&time=__value_time&field=__field_name",
},
map[string]interface{}{
"url": "http://another.com?series=${__series_name}&field=${__field_name}",
},
},
},
},
},
},
expected: map[string]interface{}{
"title": "V20 Data Links Variable Syntax Migration Test Dashboard",
"schemaVersion": 20,
"panels": []interface{}{
map[string]interface{}{
"type": "timeseries",
"title": "Panel with data links",
"id": 1,
"options": map[string]interface{}{
"dataLinks": []interface{}{
map[string]interface{}{
"url": "http://mylink.com?series=${__series.name}&time=__value.time&field=__field.name",
},
map[string]interface{}{
"url": "http://another.com?series=${__series.name}&field=${__field.name}",
},
},
},
},
},
},
},
{
name: "panel with field options title gets variable syntax migrated",
input: map[string]interface{}{
"title": "V20 Field Options Title Migration Test Dashboard",
"schemaVersion": 19,
"panels": []interface{}{
map[string]interface{}{
"type": "stat",
"title": "Panel with field options title",
"id": 2,
"options": map[string]interface{}{
"fieldOptions": map[string]interface{}{
"defaults": map[string]interface{}{
"title": "Series: __series_name, Field: $__field_name, Time: __value_time",
},
},
},
},
},
},
expected: map[string]interface{}{
"title": "V20 Field Options Title Migration Test Dashboard",
"schemaVersion": 20,
"panels": []interface{}{
map[string]interface{}{
"type": "stat",
"title": "Panel with field options title",
"id": 2,
"options": map[string]interface{}{
"fieldOptions": map[string]interface{}{
"defaults": map[string]interface{}{
"title": "Series: __series.name, Field: ${__field.name}, Time: __value.time",
},
},
},
},
},
},
},
{
name: "panel with field options links gets variable syntax migrated",
input: map[string]interface{}{
"title": "V20 Field Options Links Migration Test Dashboard",
"schemaVersion": 19,
"panels": []interface{}{
map[string]interface{}{
"type": "gauge",
"title": "Panel with field options links",
"id": 3,
"options": map[string]interface{}{
"fieldOptions": map[string]interface{}{
"defaults": map[string]interface{}{
"links": []interface{}{
map[string]interface{}{
"url": "http://example.com?series=__series_name&field=$__field_name",
},
map[string]interface{}{
"url": "http://test.com?time=__value_time",
},
},
},
},
},
},
},
},
expected: map[string]interface{}{
"title": "V20 Field Options Links Migration Test Dashboard",
"schemaVersion": 20,
"panels": []interface{}{
map[string]interface{}{
"type": "gauge",
"title": "Panel with field options links",
"id": 3,
"options": map[string]interface{}{
"fieldOptions": map[string]interface{}{
"defaults": map[string]interface{}{
"links": []interface{}{
map[string]interface{}{
"url": "http://example.com?series=__series.name&field=${__field.name}",
},
map[string]interface{}{
"url": "http://test.com?time=__value.time",
},
},
},
},
},
},
},
},
},
{
name: "panel with both data links and field options gets variable syntax migrated",
input: map[string]interface{}{
"title": "V20 Combined Migration Test Dashboard",
"schemaVersion": 19,
"panels": []interface{}{
map[string]interface{}{
"type": "table",
"title": "Panel with both data links and field options",
"id": 4,
"options": map[string]interface{}{
"dataLinks": []interface{}{
map[string]interface{}{
"url": "http://datalink.com?series=$__series_name",
},
},
"fieldOptions": map[string]interface{}{
"defaults": map[string]interface{}{
"title": "Field Name: __field_name",
"links": []interface{}{
map[string]interface{}{
"url": "http://fieldlink.com?field=$__field_name&time=__value_time",
},
},
},
},
},
},
},
},
expected: map[string]interface{}{
"title": "V20 Combined Migration Test Dashboard",
"schemaVersion": 20,
"panels": []interface{}{
map[string]interface{}{
"type": "table",
"title": "Panel with both data links and field options",
"id": 4,
"options": map[string]interface{}{
"dataLinks": []interface{}{
map[string]interface{}{
"url": "http://datalink.com?series=${__series.name}",
},
},
"fieldOptions": map[string]interface{}{
"defaults": map[string]interface{}{
"title": "Field Name: __field.name",
"links": []interface{}{
map[string]interface{}{
"url": "http://fieldlink.com?field=${__field.name}&time=__value.time",
},
},
},
},
},
},
},
},
},
{
name: "panel without data links or field options remains unchanged",
input: map[string]interface{}{
"title": "V20 No Links Migration Test Dashboard",
"schemaVersion": 19,
"panels": []interface{}{
map[string]interface{}{
"type": "singlestat",
"title": "Panel without links",
"id": 5,
"options": map[string]interface{}{
"someOtherOption": "value",
},
},
},
},
expected: map[string]interface{}{
"title": "V20 No Links Migration Test Dashboard",
"schemaVersion": 20,
"panels": []interface{}{
map[string]interface{}{
"type": "singlestat",
"title": "Panel without links",
"id": 5,
"options": map[string]interface{}{
"someOtherOption": "value",
},
},
},
},
},
{
name: "dashboard without panels remains unchanged",
input: map[string]interface{}{
"title": "V20 No Panels Migration Test Dashboard",
"schemaVersion": 19,
},
expected: map[string]interface{}{
"title": "V20 No Panels Migration Test Dashboard",
"schemaVersion": 20,
},
},
{
name: "panel with empty data links array remains unchanged",
input: map[string]interface{}{
"title": "V20 Empty Data Links Migration Test Dashboard",
"schemaVersion": 19,
"panels": []interface{}{
map[string]interface{}{
"type": "graph",
"title": "Panel with empty data links",
"id": 6,
"options": map[string]interface{}{
"dataLinks": []interface{}{},
},
},
},
},
expected: map[string]interface{}{
"title": "V20 Empty Data Links Migration Test Dashboard",
"schemaVersion": 20,
"panels": []interface{}{
map[string]interface{}{
"type": "graph",
"title": "Panel with empty data links",
"id": 6,
"options": map[string]interface{}{
"dataLinks": []interface{}{},
},
},
},
},
},
{
name: "panel with legacy variables that don't need migration",
input: map[string]interface{}{
"title": "V20 No Legacy Variables Migration Test Dashboard",
"schemaVersion": 19,
"panels": []interface{}{
map[string]interface{}{
"type": "text",
"title": "Panel with modern variables",
"id": 7,
"options": map[string]interface{}{
"dataLinks": []interface{}{
map[string]interface{}{
"url": "http://modern.com?series=${__series.name}&field=${__field.name}",
},
},
},
},
},
},
expected: map[string]interface{}{
"title": "V20 No Legacy Variables Migration Test Dashboard",
"schemaVersion": 20,
"panels": []interface{}{
map[string]interface{}{
"type": "text",
"title": "Panel with modern variables",
"id": 7,
"options": map[string]interface{}{
"dataLinks": []interface{}{
map[string]interface{}{
"url": "http://modern.com?series=${__series.name}&field=${__field.name}",
},
},
},
},
},
},
},
}
runMigrationTests(t, tests, schemaversion.V20)
}
@@ -0,0 +1,120 @@
package schemaversion
import (
"strings"
"github.com/grafana/grafana/apps/dashboard/pkg/migration/utils"
)
// V21 migrates data links to replace __series.labels with __field.labels.
// This migration updates the variable syntax used in data links from the old series-based
// syntax to the new field-based syntax.
//
// Example before migration:
//
// "panels": [
// {
// "options": {
// "dataLinks": [
// {
// "url": "http://example.com?series=${__series.labels}&${__series.labels.a}"
// }
// ],
// "fieldOptions": {
// "defaults": {
// "links": [
// {
// "url": "http://example.com?series=${__series.labels}&${__series.labels.x}"
// }
// ]
// }
// }
// }
// }
// ]
//
// Example after migration:
//
// "panels": [
// {
// "options": {
// "dataLinks": [
// {
// "url": "http://example.com?series=${__field.labels}&${__field.labels.a}"
// }
// ],
// "fieldOptions": {
// "defaults": {
// "links": [
// {
// "url": "http://example.com?series=${__field.labels}&${__field.labels.x}"
// }
// ]
// }
// }
// }
// }
// ]
func V21(dashboard map[string]interface{}) error {
dashboard["schemaVersion"] = 21
panels, ok := dashboard["panels"].([]interface{})
if !ok {
return nil
}
for _, p := range panels {
panel, ok := p.(map[string]interface{})
if !ok {
continue
}
// Update data links in panel options
if options, ok := panel["options"].(map[string]interface{}); ok {
updateDataLinks(options)
updateFieldOptionsLinks(options)
}
}
return nil
}
func updateDataLinks(options map[string]interface{}) {
dataLinks, ok := options["dataLinks"].([]interface{})
if !ok || !utils.IsArray(dataLinks) {
return
}
for _, link := range dataLinks {
if linkMap, ok := link.(map[string]interface{}); ok {
if url, ok := linkMap["url"].(string); ok {
linkMap["url"] = strings.ReplaceAll(url, "__series.labels", "__field.labels")
}
}
}
}
func updateFieldOptionsLinks(options map[string]interface{}) {
fieldOptions, ok := options["fieldOptions"].(map[string]interface{})
if !ok {
return
}
defaults, ok := fieldOptions["defaults"].(map[string]interface{})
if !ok {
return
}
links, ok := defaults["links"].([]interface{})
if !ok {
return
}
for _, link := range links {
if linkMap, ok := link.(map[string]interface{}); ok {
if url, ok := linkMap["url"].(string); ok {
linkMap["url"] = strings.ReplaceAll(url, "__series.labels", "__field.labels")
}
}
}
}
@@ -0,0 +1,232 @@
package schemaversion_test
import (
"testing"
"github.com/grafana/grafana/apps/dashboard/pkg/migration/schemaversion"
)
func TestV21(t *testing.T) {
tests := []migrationTestCase{
{
name: "panel with data links gets migrated",
input: map[string]interface{}{
"title": "V21 Data Links Migration Test Dashboard",
"schemaVersion": 20,
"panels": []interface{}{
map[string]interface{}{
"type": "timeseries",
"title": "Panel with data links",
"id": 1,
"options": map[string]interface{}{
"dataLinks": []interface{}{
map[string]interface{}{
"url": "http://mylink.com?series=${__series.labels}&${__series.labels.a}",
},
},
},
},
},
},
expected: map[string]interface{}{
"title": "V21 Data Links Migration Test Dashboard",
"schemaVersion": 21,
"panels": []interface{}{
map[string]interface{}{
"type": "timeseries",
"title": "Panel with data links",
"id": 1,
"options": map[string]interface{}{
"dataLinks": []interface{}{
map[string]interface{}{
"url": "http://mylink.com?series=${__field.labels}&${__field.labels.a}",
},
},
},
},
},
},
},
{
name: "panel with field options links gets migrated",
input: map[string]interface{}{
"title": "V21 Field Options Links Migration Test Dashboard",
"schemaVersion": 20,
"panels": []interface{}{
map[string]interface{}{
"type": "stat",
"title": "Panel with field options links",
"id": 2,
"options": map[string]interface{}{
"fieldOptions": map[string]interface{}{
"defaults": map[string]interface{}{
"links": []interface{}{
map[string]interface{}{
"url": "http://mylink.com?series=${__series.labels}&${__series.labels.x}",
},
},
},
},
},
},
},
},
expected: map[string]interface{}{
"title": "V21 Field Options Links Migration Test Dashboard",
"schemaVersion": 21,
"panels": []interface{}{
map[string]interface{}{
"type": "stat",
"title": "Panel with field options links",
"id": 2,
"options": map[string]interface{}{
"fieldOptions": map[string]interface{}{
"defaults": map[string]interface{}{
"links": []interface{}{
map[string]interface{}{
"url": "http://mylink.com?series=${__field.labels}&${__field.labels.x}",
},
},
},
},
},
},
},
},
},
{
name: "panel with both data links and field options links gets migrated",
input: map[string]interface{}{
"title": "V21 Both Links Migration Test Dashboard",
"schemaVersion": 20,
"panels": []interface{}{
map[string]interface{}{
"type": "graph",
"title": "Panel with both link types",
"id": 3,
"options": map[string]interface{}{
"dataLinks": []interface{}{
map[string]interface{}{
"url": "http://mylink.com?series=${__series.labels}",
},
},
"fieldOptions": map[string]interface{}{
"defaults": map[string]interface{}{
"links": []interface{}{
map[string]interface{}{
"url": "http://mylink.com?field=${__series.labels}",
},
},
},
},
},
},
},
},
expected: map[string]interface{}{
"title": "V21 Both Links Migration Test Dashboard",
"schemaVersion": 21,
"panels": []interface{}{
map[string]interface{}{
"type": "graph",
"title": "Panel with both link types",
"id": 3,
"options": map[string]interface{}{
"dataLinks": []interface{}{
map[string]interface{}{
"url": "http://mylink.com?series=${__field.labels}",
},
},
"fieldOptions": map[string]interface{}{
"defaults": map[string]interface{}{
"links": []interface{}{
map[string]interface{}{
"url": "http://mylink.com?field=${__field.labels}",
},
},
},
},
},
},
},
},
},
{
name: "panel without __series.labels is unchanged",
input: map[string]interface{}{
"title": "V21 No Series Labels Test Dashboard",
"schemaVersion": 20,
"panels": []interface{}{
map[string]interface{}{
"type": "timeseries",
"title": "Panel without series labels",
"id": 4,
"options": map[string]interface{}{
"dataLinks": []interface{}{
map[string]interface{}{
"url": "http://mylink.com?other=${__field.labels}",
},
},
},
},
},
},
expected: map[string]interface{}{
"title": "V21 No Series Labels Test Dashboard",
"schemaVersion": 21,
"panels": []interface{}{
map[string]interface{}{
"type": "timeseries",
"title": "Panel without series labels",
"id": 4,
"options": map[string]interface{}{
"dataLinks": []interface{}{
map[string]interface{}{
"url": "http://mylink.com?other=${__field.labels}",
},
},
},
},
},
},
},
{
name: "panel without options is unchanged",
input: map[string]interface{}{
"title": "V21 No Options Test Dashboard",
"schemaVersion": 20,
"panels": []interface{}{
map[string]interface{}{
"type": "timeseries",
"title": "Panel without options",
"id": 5,
},
},
},
expected: map[string]interface{}{
"title": "V21 No Options Test Dashboard",
"schemaVersion": 21,
"panels": []interface{}{
map[string]interface{}{
"type": "timeseries",
"title": "Panel without options",
"id": 5,
},
},
},
},
{
name: "dashboard without panels is unchanged",
input: map[string]interface{}{
"title": "V21 No Panels Test Dashboard",
"schemaVersion": 20,
},
expected: map[string]interface{}{
"title": "V21 No Panels Test Dashboard",
"schemaVersion": 21,
},
},
}
runMigrationTests(t, tests, schemaversion.V21)
}
@@ -0,0 +1,66 @@
package schemaversion
// V22 migrates table panel styles to set align property to 'auto'.
// This migration ensures that all table panel styles have their align property
// set to 'auto' for consistent alignment behavior.
//
// Example before migration:
//
// "panels": [
// {
// "type": "table",
// "styles": [
// { "type": "number", "pattern": "Time", "align": "left" },
// { "type": "string", "pattern": "Value", "align": "right" }
// ]
// }
// ]
//
// Example after migration:
//
// "panels": [
// {
// "type": "table",
// "styles": [
// { "type": "number", "pattern": "Time", "align": "auto" },
// { "type": "string", "pattern": "Value", "align": "auto" }
// ]
// }
// ]
func V22(dashboard map[string]interface{}) error {
dashboard["schemaVersion"] = 22
panels, ok := dashboard["panels"].([]interface{})
if !ok {
return nil
}
for _, p := range panels {
panel, ok := p.(map[string]interface{})
if !ok {
continue
}
// Only process table panels
panelType, ok := panel["type"].(string)
if !ok || panelType != "table" {
continue
}
styles, ok := panel["styles"].([]interface{})
if !ok {
continue
}
// Update each style to set align to 'auto'
for _, s := range styles {
style, ok := s.(map[string]interface{})
if !ok {
continue
}
style["align"] = "auto"
}
}
return nil
}
@@ -0,0 +1,123 @@
package schemaversion_test
import (
"testing"
"github.com/grafana/grafana/apps/dashboard/pkg/migration/schemaversion"
)
func TestV22(t *testing.T) {
tests := []migrationTestCase{
{
name: "table panel styles align is set to auto",
input: map[string]interface{}{
"title": "V22 Table Panel Styles Test",
"schemaVersion": 21,
"panels": []interface{}{
map[string]interface{}{
"id": 1,
"type": "table",
"styles": []interface{}{
map[string]interface{}{
"type": "number",
"pattern": "Time",
"align": "left",
},
map[string]interface{}{
"type": "string",
"pattern": "Value",
"align": "right",
},
},
},
},
},
expected: map[string]interface{}{
"title": "V22 Table Panel Styles Test",
"schemaVersion": 22,
"panels": []interface{}{
map[string]interface{}{
"id": 1,
"type": "table",
"styles": []interface{}{
map[string]interface{}{
"type": "number",
"pattern": "Time",
"align": "auto",
},
map[string]interface{}{
"type": "string",
"pattern": "Value",
"align": "auto",
},
},
},
},
},
},
{
name: "non-table panel is unchanged except schemaVersion",
input: map[string]interface{}{
"title": "V22 Non-Table Panel Test",
"schemaVersion": 21,
"panels": []interface{}{
map[string]interface{}{
"id": 2,
"type": "graph",
"styles": []interface{}{
map[string]interface{}{
"type": "number",
"pattern": "Time",
"align": "left",
},
},
},
},
},
expected: map[string]interface{}{
"title": "V22 Non-Table Panel Test",
"schemaVersion": 22,
"panels": []interface{}{
map[string]interface{}{
"id": 2,
"type": "graph",
"styles": []interface{}{
map[string]interface{}{
"type": "number",
"pattern": "Time",
"align": "left",
},
},
},
},
},
},
{
name: "table panel with no styles is unchanged except schemaVersion",
input: map[string]interface{}{
"title": "V22 Table Panel No Styles Test",
"schemaVersion": 21,
"panels": []interface{}{
map[string]interface{}{
"id": 3,
"type": "table",
"styles": []interface{}{},
},
},
},
expected: map[string]interface{}{
"title": "V22 Table Panel No Styles Test",
"schemaVersion": 22,
"panels": []interface{}{
map[string]interface{}{
"id": 3,
"type": "table",
"styles": []interface{}{},
},
},
},
},
}
runMigrationTests(t, tests, schemaversion.V22)
}
@@ -0,0 +1,133 @@
package schemaversion
import "github.com/grafana/grafana/apps/dashboard/pkg/migration/utils"
// V23 migrates multi variables to ensure their current property is aligned with their multi property.
// This migration ensures that variables with multi=true have current.value and current.text as arrays,
// and variables with multi=false have current.value and current.text as single values.
//
// Example before migration:
//
// "templating": {
// "list": [
// { "type": "query", "multi": true, "current": { "value": "A", "text": "A" } },
// { "type": "query", "multi": false, "current": { "value": ["B"], "text": ["B"] } }
// ]
// }
//
// Example after migration:
//
// "templating": {
// "list": [
// { "type": "query", "multi": true, "current": { "value": ["A"], "text": ["A"] } },
// { "type": "query", "multi": false, "current": { "value": "B", "text": "B" } }
// ]
// }
func V23(dashboard map[string]interface{}) error {
dashboard["schemaVersion"] = 23
templating, ok := dashboard["templating"].(map[string]interface{})
if !ok {
return nil
}
list, ok := templating["list"].([]interface{})
if !ok {
return nil
}
for _, v := range list {
variable, ok := v.(map[string]interface{})
if !ok {
continue
}
if !isMulti(variable) {
continue
}
current, ok := variable["current"].(map[string]interface{})
if !ok {
continue
}
if isEmptyObject(current) {
continue
}
multi, ok := variable["multi"].(bool)
if !ok {
continue
}
variable["current"] = alignCurrentWithMulti(current, multi)
}
return nil
}
// isMulti checks if a variable supports multi-selection
func isMulti(variable map[string]interface{}) bool {
_, hasMulti := variable["multi"]
return hasMulti
}
func isEmptyObject(value interface{}) bool {
if value == nil {
return true
}
obj, ok := value.(map[string]interface{})
if !ok {
return false
}
return len(obj) == 0
}
// alignCurrentWithMulti aligns the current property with the multi property
func alignCurrentWithMulti(current map[string]interface{}, multi bool) map[string]interface{} {
if current == nil {
return current
}
result := make(map[string]interface{})
for k, v := range current {
result[k] = v
}
if multi {
// Convert single values to arrays
if value, ok := result["value"]; ok {
if !utils.IsArray(value) {
result["value"] = []interface{}{value}
}
}
if text, ok := result["text"]; ok {
if !utils.IsArray(text) {
result["text"] = []interface{}{text}
}
}
} else {
// Convert arrays to single values
if value, ok := result["value"]; ok {
if utils.IsArray(value) {
if arr, ok := value.([]interface{}); ok && len(arr) > 0 {
result["value"] = arr[0]
} else {
result["value"] = ""
}
}
}
if text, ok := result["text"]; ok {
if utils.IsArray(text) {
if arr, ok := text.([]interface{}); ok && len(arr) > 0 {
result["text"] = arr[0]
} else {
result["text"] = ""
}
}
}
}
return result
}
@@ -0,0 +1,229 @@
package schemaversion_test
import (
"testing"
"github.com/grafana/grafana/apps/dashboard/pkg/migration/schemaversion"
)
func TestV23(t *testing.T) {
tests := []migrationTestCase{
{
name: "multi variable with single value gets converted to array",
input: map[string]interface{}{
"title": "V23 Multi Variable Single Value Test",
"schemaVersion": 22,
"templating": map[string]interface{}{
"list": []interface{}{
map[string]interface{}{
"type": "query",
"name": "multi_single_value",
"multi": true,
"current": map[string]interface{}{"value": "A", "text": "A", "selected": true},
},
},
},
},
expected: map[string]interface{}{
"title": "V23 Multi Variable Single Value Test",
"schemaVersion": 23,
"templating": map[string]interface{}{
"list": []interface{}{
map[string]interface{}{
"type": "query",
"name": "multi_single_value",
"multi": true,
"current": map[string]interface{}{"value": []interface{}{"A"}, "text": []interface{}{"A"}, "selected": true},
},
},
},
},
},
{
name: "multi variable with array value stays as array",
input: map[string]interface{}{
"title": "V23 Multi Variable Array Value Test",
"schemaVersion": 22,
"templating": map[string]interface{}{
"list": []interface{}{
map[string]interface{}{
"type": "query",
"name": "multi_array_value",
"multi": true,
"current": map[string]interface{}{"value": []interface{}{"B", "C"}, "text": []interface{}{"B", "C"}, "selected": true},
},
},
},
},
expected: map[string]interface{}{
"title": "V23 Multi Variable Array Value Test",
"schemaVersion": 23,
"templating": map[string]interface{}{
"list": []interface{}{
map[string]interface{}{
"type": "query",
"name": "multi_array_value",
"multi": true,
"current": map[string]interface{}{"value": []interface{}{"B", "C"}, "text": []interface{}{"B", "C"}, "selected": true},
},
},
},
},
},
{
name: "non-multi variable with array value gets converted to single value",
input: map[string]interface{}{
"title": "V23 Non-Multi Variable Array Value Test",
"schemaVersion": 22,
"templating": map[string]interface{}{
"list": []interface{}{
map[string]interface{}{
"type": "query",
"name": "non_multi_array_value",
"multi": false,
"current": map[string]interface{}{"value": []interface{}{"D"}, "text": []interface{}{"D"}, "selected": true},
},
},
},
},
expected: map[string]interface{}{
"title": "V23 Non-Multi Variable Array Value Test",
"schemaVersion": 23,
"templating": map[string]interface{}{
"list": []interface{}{
map[string]interface{}{
"type": "query",
"name": "non_multi_array_value",
"multi": false,
"current": map[string]interface{}{"value": "D", "text": "D", "selected": true},
},
},
},
},
},
{
name: "non-multi variable with single value stays as single value",
input: map[string]interface{}{
"title": "V23 Non-Multi Variable Single Value Test",
"schemaVersion": 22,
"templating": map[string]interface{}{
"list": []interface{}{
map[string]interface{}{
"type": "query",
"name": "non_multi_single_value",
"multi": false,
"current": map[string]interface{}{"value": "E", "text": "E", "selected": true},
},
},
},
},
expected: map[string]interface{}{
"title": "V23 Non-Multi Variable Single Value Test",
"schemaVersion": 23,
"templating": map[string]interface{}{
"list": []interface{}{
map[string]interface{}{
"type": "query",
"name": "non_multi_single_value",
"multi": false,
"current": map[string]interface{}{"value": "E", "text": "E", "selected": true},
},
},
},
},
},
{
name: "variable without multi property is unchanged",
input: map[string]interface{}{
"title": "V23 No Multi Property Test",
"schemaVersion": 22,
"templating": map[string]interface{}{
"list": []interface{}{
map[string]interface{}{
"type": "query",
"name": "no_multi_property",
"current": map[string]interface{}{"value": "F", "text": "F", "selected": true},
},
},
},
},
expected: map[string]interface{}{
"title": "V23 No Multi Property Test",
"schemaVersion": 23,
"templating": map[string]interface{}{
"list": []interface{}{
map[string]interface{}{
"type": "query",
"name": "no_multi_property",
"current": map[string]interface{}{"value": "F", "text": "F", "selected": true},
},
},
},
},
},
{
name: "variable with empty current is unchanged",
input: map[string]interface{}{
"title": "V23 Empty Current Test",
"schemaVersion": 22,
"templating": map[string]interface{}{
"list": []interface{}{
map[string]interface{}{
"type": "query",
"name": "empty_current",
"multi": true,
"current": map[string]interface{}{},
},
},
},
},
expected: map[string]interface{}{
"title": "V23 Empty Current Test",
"schemaVersion": 23,
"templating": map[string]interface{}{
"list": []interface{}{
map[string]interface{}{
"type": "query",
"name": "empty_current",
"multi": true,
"current": map[string]interface{}{},
},
},
},
},
},
{
name: "variable with nil current is unchanged",
input: map[string]interface{}{
"title": "V23 Nil Current Test",
"schemaVersion": 22,
"templating": map[string]interface{}{
"list": []interface{}{
map[string]interface{}{
"type": "query",
"name": "nil_current",
"multi": true,
"current": nil,
},
},
},
},
expected: map[string]interface{}{
"title": "V23 Nil Current Test",
"schemaVersion": 23,
"templating": map[string]interface{}{
"list": []interface{}{
map[string]interface{}{
"type": "query",
"name": "nil_current",
"multi": true,
"current": nil,
},
},
},
},
},
}
runMigrationTests(t, tests, schemaversion.V23)
}
@@ -0,0 +1,631 @@
package schemaversion
import (
"strconv"
)
// V24 migration migrates the angular table panel to the standard table panel
// In the frontend, this is an auto-migration meaning that this angular panel is always migrated to table panel.
// The backend replicates the complete frontend auto-migration logic since it cannot rely on frontend auto-migration.
//
// This migration performs:
// 1. Converts 'styles' array to 'fieldConfig' with 'defaults' and 'overrides'
// 2. Migrates thresholds and colors to new threshold format
// 3. Converts column-specific styles to field overrides
// 4. Migrates transformations from old format to new transformation system
// 5. Handles various style properties: unit, decimals, alignment, color modes, links, date formatting, hidden columns
// 6. Removes deprecated properties: styles, transform, columns
// Example 1: Basic table with defaults
// Before migration:
// {
// "panels": [
// {
// "id": 1,
// "type": "table",
// "title": "Basic Table",
// "styles": [
// {
// "pattern": "/.*/",
// "thresholds": ["10", "20", "30"],
// "colors": ["green", "yellow", "red"],
// "unit": "bytes",
// "decimals": 2
// }
// ],
// "targets": [{ "refId": "A" }]
// }
// ]
// }
//
// After migration:
// {
// "panels": [
// {
// "id": 1,
// "type": "table",
// "title": "Basic Table",
// "fieldConfig": {
// "defaults": {
// "unit": "bytes",
// "decimals": 2,
// "custom": {},
// "thresholds": {
// "mode": "absolute",
// "steps": [
// { "color": "green", "value": null },
// { "color": "green", "value": 10 },
// { "color": "yellow", "value": 20 },
// { "color": "red", "value": 30 }
// ]
// }
// },
// "overrides": []
// },
// "transformations": [],
// "targets": [{ "refId": "A" }],
// "pluginVersion": "1.0.0"
// }
// ]
// }
// Example 2: Complex table with overrides and transformations
// Before migration:
// {
// "panels": [
// {
// "id": 2,
// "type": "table",
// "title": "Complex Table",
// "styles": [
// {
// "pattern": "/.*/",
// "unit": "percent",
// "align": "center",
// "colorMode": "cell"
// },
// {
// "pattern": "Status",
// "alias": "Current Status",
// "colorMode": "value",
// "align": "left"
// },
// {
// "pattern": "/Error.*/",
// "link": true,
// "linkUrl": "http://example.com/errors",
// "linkTooltip": "View errors",
// "linkTargetBlank": true
// },
// {
// "pattern": "Time",
// "type": "date",
// "dateFormat": "YYYY-MM-DD HH:mm:ss",
// "alias": "Timestamp"
// },
// {
// "pattern": "Hidden",
// "type": "hidden"
// }
// ],
// "transform": "timeseries_aggregations",
// "columns": [
// { "value": "avg", "text": "Average" },
// { "value": "max", "text": "Maximum" }
// ],
// "targets": [{ "refId": "A" }]
// }
// ]
// }
//
// After migration:
// {
// "panels": [
// {
// "id": 2,
// "type": "table",
// "title": "Complex Table",
// "fieldConfig": {
// "defaults": {
// "unit": "percent",
// "custom": {
// "align": "center",
// "cellOptions": { "type": "color-background" }
// }
// },
// "overrides": [
// {
// "matcher": { "id": "byName", "options": "Status" },
// "properties": [
// { "id": "displayName", "value": "Current Status" },
// { "id": "custom.cellOptions", "value": { "type": "color-text" } },
// { "id": "custom.align", "value": "left" }
// ]
// },
// {
// "matcher": { "id": "byRegexp", "options": "/Error.*/" },
// "properties": [
// {
// "id": "links",
// "value": [{
// "title": "View errors",
// "url": "http://example.com/errors",
// "targetBlank": true
// }]
// }
// ]
// },
// {
// "matcher": { "id": "byName", "options": "Time" },
// "properties": [
// { "id": "displayName", "value": "Timestamp" },
// { "id": "unit", "value": "time: YYYY-MM-DD HH:mm:ss" }
// ]
// },
// {
// "matcher": { "id": "byName", "options": "Hidden" },
// "properties": [
// { "id": "custom.hidden", "value": true }
// ]
// }
// ]
// },
// "transformations": [
// {
// "id": "reduce",
// "options": {
// "reducers": ["mean", "max"],
// "includeTimeField": false
// }
// }
// ],
// "targets": [{ "refId": "A" }],
// "pluginVersion": "1.0.0"
// }
// ]
// }
type v24Migrator struct {
panelProvider PanelPluginInfoProvider
panelPlugins []PanelPluginInfo
}
func V24(panelProvider PanelPluginInfoProvider) SchemaVersionMigrationFunc {
migrator := &v24Migrator{
panelProvider: panelProvider,
panelPlugins: panelProvider.GetPanels(),
}
return migrator.migrate
}
func (m *v24Migrator) migrate(dashboard map[string]interface{}) error {
dashboard["schemaVersion"] = 24
panels, ok := dashboard["panels"].([]interface{})
if !ok {
return nil
}
for _, panel := range panels {
panelMap, ok := panel.(map[string]interface{})
if !ok {
continue
}
wasAngularTable := panelMap["type"] == "table"
wasReactTable := panelMap["table"] == "table2"
if wasAngularTable && panelMap["styles"] == nil {
continue
}
if !wasAngularTable || wasReactTable {
continue
}
// Find if the panel plugin exists
tablePanelPlugin := m.panelProvider.GetPanelPlugin("table")
if tablePanelPlugin.ID == "" {
return NewMigrationError("table panel plugin not found when migrating dashboard to schema version 24", 24, LATEST_VERSION)
}
panelMap["pluginVersion"] = tablePanelPlugin.Version
err := tablePanelChangedHandler(panelMap)
if err != nil {
return err
}
}
return nil
}
func tablePanelChangedHandler(panel map[string]interface{}) error {
prevOptions := getOptionsToRemember(panel)
transformations := migrateTransformations(panel, prevOptions)
prevDefaults := findDefaultStyle(prevOptions)
defaults := migrateDefaults(prevDefaults)
overrides := findNonDefaultStyles(prevOptions)
if len(overrides) == 0 {
overrides = []interface{}{}
}
panel["transformations"] = transformations
panel["fieldConfig"] = map[string]interface{}{
"defaults": defaults,
"overrides": overrides,
}
// Add default table panel options to match frontend behavior
panel["options"] = map[string]interface{}{
"cellHeight": "sm",
"footer": map[string]interface{}{
"countRows": false,
"fields": "",
"reducer": []interface{}{"sum"},
"show": false,
},
"showHeader": true,
}
// Remove deprecated properties
delete(panel, "styles")
delete(panel, "transform")
delete(panel, "columns")
return nil
}
// findDefaultStyle finds the style with pattern '/.*/' (default style)
func findDefaultStyle(prevOptions map[string]interface{}) map[string]interface{} {
if styles, ok := prevOptions["styles"].([]interface{}); ok {
for _, style := range styles {
if styleMap, ok := style.(map[string]interface{}); ok {
if pattern, ok := styleMap["pattern"].(string); ok && pattern == "/.*/" {
return styleMap
}
}
}
}
return nil
}
// findNonDefaultStyles finds all styles that don't have pattern '/.*/'
func findNonDefaultStyles(prevOptions map[string]interface{}) []interface{} {
var overrides []interface{}
if styles, ok := prevOptions["styles"].([]interface{}); ok {
for _, style := range styles {
if styleMap, ok := style.(map[string]interface{}); ok {
if pattern, ok := styleMap["pattern"].(string); ok && pattern != "/.*/" {
override := migrateTableStyleToOverride(styleMap)
overrides = append(overrides, override)
}
}
}
}
return overrides
}
// migrateTransformations converts old table transformations to new format
func migrateTransformations(panel map[string]interface{}, oldOpts map[string]interface{}) []interface{} {
transformations := []interface{}{}
if existing, ok := panel["transformations"].([]interface{}); ok {
transformations = existing
}
// Check if oldOpts has a transform that we can map
if transform, ok := oldOpts["transform"].(string); ok {
if newTransformID, exists := transformsMap[transform]; exists {
opts := map[string]interface{}{
"reducers": []interface{}{},
}
// Handle timeseries_aggregations specifically
if transform == "timeseries_aggregations" {
opts["includeTimeField"] = false
// Map columns to reducers
if columns, ok := oldOpts["columns"].([]interface{}); ok {
var reducers []interface{}
for _, column := range columns {
if columnMap, ok := column.(map[string]interface{}); ok {
if value, ok := columnMap["value"].(string); ok {
if reducer, exists := columnsMap[value]; exists {
reducers = append(reducers, reducer)
}
}
}
}
opts["reducers"] = reducers
}
}
// Add the transformation
transformation := map[string]interface{}{
"id": newTransformID,
"options": opts,
}
transformations = append(transformations, transformation)
}
}
return transformations
}
// transformsMap maps old transform names to new transformation IDs
var transformsMap = map[string]string{
"timeseries_to_rows": "seriesToRows",
"timeseries_to_columns": "seriesToColumns",
"timeseries_aggregations": "reduce",
"table": "merge",
}
// columnsMap maps old column values to new reducer names
var columnsMap = map[string]string{
"avg": "mean",
"min": "min",
"max": "max",
"total": "sum",
"current": "lastNotNull",
"count": "count",
}
// migrateTableStyleToOverride converts a table style to a field config override
func migrateTableStyleToOverride(style map[string]interface{}) map[string]interface{} {
pattern, _ := style["pattern"].(string)
// Determine field matcher ID based on pattern
fieldMatcherID := "byName"
if pattern != "" && len(pattern) >= 2 && pattern[0] == '/' && pattern[len(pattern)-1] == '/' {
fieldMatcherID = "byRegexp"
}
override := map[string]interface{}{
"matcher": map[string]interface{}{
"id": fieldMatcherID,
"options": pattern,
},
"properties": []interface{}{},
}
properties := override["properties"].([]interface{})
// Add display name
if alias, ok := style["alias"].(string); ok && alias != "" {
properties = append(properties, map[string]interface{}{
"id": "displayName",
"value": alias,
})
}
// Add unit
if unit, ok := style["unit"].(string); ok && unit != "" {
properties = append(properties, map[string]interface{}{
"id": "unit",
"value": unit,
})
}
// Add decimals
if decimals, ok := style["decimals"].(float64); ok {
properties = append(properties, map[string]interface{}{
"id": "decimals",
"value": int(decimals),
})
} else if decimals, ok := style["decimals"].(int); ok {
properties = append(properties, map[string]interface{}{
"id": "decimals",
"value": decimals,
})
}
// Handle date type
if styleType, ok := style["type"].(string); ok && styleType == "date" {
if dateFormat, ok := style["dateFormat"].(string); ok {
properties = append(properties, map[string]interface{}{
"id": "unit",
"value": "time: " + dateFormat,
})
}
}
// Handle hidden type
if styleType, ok := style["type"].(string); ok && styleType == "hidden" {
properties = append(properties, map[string]interface{}{
"id": "custom.hidden",
"value": true,
})
}
// Handle links
if link, ok := style["link"].(bool); ok && link {
linkTooltip, _ := style["linkTooltip"].(string)
linkUrl, _ := style["linkUrl"].(string)
linkTargetBlank, _ := style["linkTargetBlank"].(bool)
properties = append(properties, map[string]interface{}{
"id": "links",
"value": []interface{}{
map[string]interface{}{
"title": linkTooltip,
"url": linkUrl,
"targetBlank": linkTargetBlank,
},
},
})
}
// Handle color mode
if colorMode, ok := style["colorMode"].(string); ok && colorMode != "" {
if newColorMode, exists := colorModeMap[colorMode]; exists {
properties = append(properties, map[string]interface{}{
"id": "custom.cellOptions",
"value": map[string]interface{}{
"type": newColorMode,
},
})
}
}
// Handle alignment
if align, ok := style["align"].(string); ok && align != "" {
alignValue := align
if align == "auto" {
alignValue = ""
}
properties = append(properties, map[string]interface{}{
"id": "custom.align",
"value": alignValue,
})
}
// Handle thresholds
if thresholds, ok := style["thresholds"].([]interface{}); ok && len(thresholds) > 0 {
if colors, ok := style["colors"].([]interface{}); ok && len(colors) > 0 {
steps := generateThresholds(thresholds, colors)
properties = append(properties, map[string]interface{}{
"id": "thresholds",
"value": map[string]interface{}{
"mode": "absolute",
"steps": steps,
},
})
}
}
override["properties"] = properties
return override
}
// migrateDefaults converts default table styles to field config defaults
func migrateDefaults(prevDefaults map[string]interface{}) map[string]interface{} {
defaults := map[string]interface{}{
"custom": map[string]interface{}{
"align": "auto",
"cellOptions": map[string]interface{}{
"type": "auto",
},
"inspect": false,
},
"mappings": []interface{}{},
}
// Add default thresholds for all table panels to match frontend behavior
// The frontend applies the table panel's default field config which includes thresholds
hasThresholds := false
if prevDefaults != nil {
if thresholds, ok := prevDefaults["thresholds"].([]interface{}); ok && len(thresholds) > 0 {
hasThresholds = true
}
}
// Add default thresholds for all table panels (when prevDefaults exists) without existing thresholds
if !hasThresholds {
defaults["thresholds"] = map[string]interface{}{
"mode": "absolute",
"steps": []interface{}{
map[string]interface{}{"color": "green"},
map[string]interface{}{"color": "red", "value": 80},
},
}
}
if prevDefaults == nil {
return defaults
}
if unit, ok := prevDefaults["unit"].(string); ok && unit != "" {
defaults["unit"] = unit
}
if decimals, ok := prevDefaults["decimals"].(float64); ok {
defaults["decimals"] = int(decimals)
}
if alias, ok := prevDefaults["alias"].(string); ok && alias != "" {
defaults["displayName"] = alias
}
if align, ok := prevDefaults["align"].(string); ok && align != "" {
alignValue := align
if align == "auto" {
alignValue = ""
}
defaults["custom"].(map[string]interface{})["align"] = alignValue
}
if thresholds, ok := prevDefaults["thresholds"].([]interface{}); ok && len(thresholds) > 0 {
if colors, ok := prevDefaults["colors"].([]interface{}); ok && len(colors) > 0 {
steps := generateThresholds(thresholds, colors)
defaults["thresholds"] = map[string]interface{}{
"mode": "absolute",
"steps": steps,
}
}
}
if colorMode, ok := prevDefaults["colorMode"].(string); ok && colorMode != "" {
if newColorMode, exists := colorModeMap[colorMode]; exists {
defaults["custom"].(map[string]interface{})["cellOptions"] = map[string]interface{}{
"type": newColorMode,
}
}
}
return defaults
}
func generateThresholds(thresholds []interface{}, colors []interface{}) []interface{} {
steps := []interface{}{}
// Add the base step (equivalent to -Infinity)
var baseColor interface{} = "red" // default fallback
if len(colors) > 0 && colors[0] != nil {
baseColor = colors[0]
}
steps = append(steps, map[string]interface{}{
"color": baseColor,
"value": nil,
})
// Add threshold steps
for i, threshold := range thresholds {
var color interface{}
// Use colors[i+1] for the i-th threshold (colors[0] was used for base step)
if i+1 < len(colors) && colors[i+1] != nil {
color = colors[i+1]
} else {
color = "red"
}
var value float64
switch v := threshold.(type) {
case string:
if parsed, err := strconv.ParseFloat(v, 64); err == nil {
value = parsed
}
case float64:
value = v
case int:
value = float64(v)
}
steps = append(steps, map[string]interface{}{
"color": color,
"value": value,
})
}
return steps
}
var colorModeMap = map[string]string{
"cell": "color-background",
"row": "color-background",
"value": "color-text",
}
@@ -0,0 +1,823 @@
package schemaversion_test
import (
"testing"
"github.com/grafana/grafana/apps/dashboard/pkg/migration/schemaversion"
"github.com/grafana/grafana/apps/dashboard/pkg/migration/testutil"
)
func TestV24(t *testing.T) {
tests := []migrationTestCase{
{
name: "should migrate basic Angular table with defaults",
input: map[string]interface{}{
"schemaVersion": 23,
"panels": []interface{}{
map[string]interface{}{
"id": 1,
"type": "table",
"title": "Basic Table",
"legend": true,
"styles": []interface{}{
map[string]interface{}{
"thresholds": []interface{}{"10", "20", "30"},
"colors": []interface{}{"red", "yellow", "green"},
"pattern": "/.*/",
},
},
"targets": []interface{}{
map[string]interface{}{"refId": "A"},
},
},
},
},
expected: map[string]interface{}{
"schemaVersion": 24,
"panels": []interface{}{
map[string]interface{}{
"id": 1,
"type": "table",
"title": "Basic Table",
"legend": true,
"fieldConfig": map[string]interface{}{
"defaults": map[string]interface{}{
"custom": map[string]interface{}{
"align": "auto",
"cellOptions": map[string]interface{}{
"type": "auto",
},
"inspect": false,
},
"mappings": []interface{}{},
"thresholds": map[string]interface{}{
"mode": "absolute",
"steps": []interface{}{
map[string]interface{}{"value": nil, "color": "red"},
map[string]interface{}{"value": float64(10), "color": "yellow"},
map[string]interface{}{"value": float64(20), "color": "green"},
map[string]interface{}{"value": float64(30), "color": "red"},
},
},
},
"overrides": []interface{}{},
},
"options": map[string]interface{}{
"cellHeight": "sm",
"footer": map[string]interface{}{
"countRows": false,
"fields": "",
"reducer": []interface{}{"sum"},
"show": false,
},
"showHeader": true,
},
"transformations": []interface{}{},
"targets": []interface{}{
map[string]interface{}{"refId": "A"},
},
"pluginVersion": "1.0.0",
},
},
},
},
{
name: "should migrate table with complex defaults and overrides",
input: map[string]interface{}{
"schemaVersion": 23,
"panels": []interface{}{
map[string]interface{}{
"id": 2,
"type": "table",
"title": "Complex Table",
"styles": []interface{}{
// Default style
map[string]interface{}{
"pattern": "/.*/",
"unit": "bytes",
"decimals": float64(2),
"align": "center",
"colorMode": "cell",
"thresholds": []interface{}{"100", "500"},
"colors": []interface{}{"green", "yellow", "red"},
},
// Column-specific override with exact name
map[string]interface{}{
"pattern": "Status",
"alias": "Current Status",
"unit": "short",
"decimals": float64(0),
"colorMode": "value",
"align": "left",
},
// Column-specific override with regex pattern
map[string]interface{}{
"pattern": "/Error.*/",
"link": true,
"linkUrl": "http://example.com/errors",
"linkTooltip": "View error details",
"linkTargetBlank": true,
"colorMode": "row",
"colors": []interface{}{"red", "orange"},
},
// Date column
map[string]interface{}{
"pattern": "Time",
"type": "date",
"dateFormat": "YYYY-MM-DD HH:mm:ss",
"alias": "Timestamp",
},
// Hidden column
map[string]interface{}{
"pattern": "Hidden",
"type": "hidden",
},
},
"targets": []interface{}{
map[string]interface{}{"refId": "A"},
},
},
},
},
expected: map[string]interface{}{
"schemaVersion": 24,
"panels": []interface{}{
map[string]interface{}{
"id": 2,
"type": "table",
"title": "Complex Table",
"fieldConfig": map[string]interface{}{
"defaults": map[string]interface{}{
"unit": "bytes",
"decimals": 2,
"custom": map[string]interface{}{
"align": "center",
"cellOptions": map[string]interface{}{
"type": "color-background",
},
"inspect": false,
},
"mappings": []interface{}{},
"thresholds": map[string]interface{}{
"mode": "absolute",
"steps": []interface{}{
map[string]interface{}{"value": nil, "color": "green"},
map[string]interface{}{"value": float64(100), "color": "yellow"},
map[string]interface{}{"value": float64(500), "color": "red"},
},
},
},
"overrides": []interface{}{
map[string]interface{}{
"matcher": map[string]interface{}{
"id": "byName",
"options": "Status",
},
"properties": []interface{}{
map[string]interface{}{"id": "displayName", "value": "Current Status"},
map[string]interface{}{"id": "unit", "value": "short"},
map[string]interface{}{"id": "decimals", "value": 0},
map[string]interface{}{"id": "custom.cellOptions", "value": map[string]interface{}{"type": "color-text"}},
map[string]interface{}{"id": "custom.align", "value": "left"},
},
},
map[string]interface{}{
"matcher": map[string]interface{}{
"id": "byRegexp",
"options": "/Error.*/",
},
"properties": []interface{}{
map[string]interface{}{
"id": "links",
"value": []interface{}{
map[string]interface{}{
"title": "View error details",
"url": "http://example.com/errors",
"targetBlank": true,
},
},
},
map[string]interface{}{"id": "custom.cellOptions", "value": map[string]interface{}{"type": "color-background"}},
},
},
map[string]interface{}{
"matcher": map[string]interface{}{
"id": "byName",
"options": "Time",
},
"properties": []interface{}{
map[string]interface{}{"id": "displayName", "value": "Timestamp"},
map[string]interface{}{"id": "unit", "value": "time: YYYY-MM-DD HH:mm:ss"},
},
},
map[string]interface{}{
"matcher": map[string]interface{}{
"id": "byName",
"options": "Hidden",
},
"properties": []interface{}{
map[string]interface{}{"id": "custom.hidden", "value": true},
},
},
},
},
"options": map[string]interface{}{
"cellHeight": "sm",
"footer": map[string]interface{}{
"countRows": false,
"fields": "",
"reducer": []interface{}{"sum"},
"show": false,
},
"showHeader": true,
},
"transformations": []interface{}{},
"targets": []interface{}{
map[string]interface{}{"refId": "A"},
},
"pluginVersion": "1.0.0",
},
},
},
},
{
name: "should migrate table with timeseries_aggregations transform",
input: map[string]interface{}{
"schemaVersion": 23,
"panels": []interface{}{
map[string]interface{}{
"id": 3,
"type": "table",
"title": "Table with Aggregations",
"styles": []interface{}{
map[string]interface{}{
"pattern": "/.*/",
"unit": "percent",
"decimals": float64(1),
},
},
"transform": "timeseries_aggregations",
"columns": []interface{}{
map[string]interface{}{"value": "avg", "text": "Average"},
map[string]interface{}{"value": "max", "text": "Maximum"},
map[string]interface{}{"value": "min", "text": "Minimum"},
map[string]interface{}{"value": "total", "text": "Total"},
map[string]interface{}{"value": "current", "text": "Current"},
map[string]interface{}{"value": "count", "text": "Count"},
},
"targets": []interface{}{
map[string]interface{}{"refId": "A"},
},
},
},
},
expected: map[string]interface{}{
"schemaVersion": 24,
"panels": []interface{}{
map[string]interface{}{
"id": 3,
"type": "table",
"title": "Table with Aggregations",
"fieldConfig": map[string]interface{}{
"defaults": map[string]interface{}{
"unit": "percent",
"decimals": 1,
"custom": map[string]interface{}{
"align": "auto",
"cellOptions": map[string]interface{}{
"type": "auto",
},
"inspect": false,
},
"mappings": []interface{}{},
"thresholds": map[string]interface{}{
"mode": "absolute",
"steps": []interface{}{
map[string]interface{}{"color": "green"},
map[string]interface{}{"color": "red", "value": 80},
},
},
},
"overrides": []interface{}{},
},
"options": map[string]interface{}{
"cellHeight": "sm",
"footer": map[string]interface{}{
"countRows": false,
"fields": "",
"reducer": []interface{}{"sum"},
"show": false,
},
"showHeader": true,
},
"transformations": []interface{}{
map[string]interface{}{
"id": "reduce",
"options": map[string]interface{}{
"reducers": []interface{}{"mean", "max", "min", "sum", "lastNotNull", "count"},
"includeTimeField": false,
},
},
},
"targets": []interface{}{
map[string]interface{}{"refId": "A"},
},
"pluginVersion": "1.0.0",
},
},
},
},
{
name: "should migrate table with timeseries_to_rows transform",
input: map[string]interface{}{
"schemaVersion": 23,
"panels": []interface{}{
map[string]interface{}{
"id": 4,
"type": "table",
"title": "Table with Rows Transform",
"styles": []interface{}{
map[string]interface{}{
"pattern": "/.*/",
"unit": "short",
},
},
"transform": "timeseries_to_rows",
"targets": []interface{}{
map[string]interface{}{"refId": "A"},
},
},
},
},
expected: map[string]interface{}{
"schemaVersion": 24,
"panels": []interface{}{
map[string]interface{}{
"id": 4,
"type": "table",
"title": "Table with Rows Transform",
"fieldConfig": map[string]interface{}{
"defaults": map[string]interface{}{
"unit": "short",
"custom": map[string]interface{}{
"align": "auto",
"cellOptions": map[string]interface{}{
"type": "auto",
},
"inspect": false,
},
"mappings": []interface{}{},
"thresholds": map[string]interface{}{
"mode": "absolute",
"steps": []interface{}{
map[string]interface{}{"color": "green"},
map[string]interface{}{"color": "red", "value": 80},
},
},
},
"overrides": []interface{}{},
},
"options": map[string]interface{}{
"cellHeight": "sm",
"footer": map[string]interface{}{
"countRows": false,
"fields": "",
"reducer": []interface{}{"sum"},
"show": false,
},
"showHeader": true,
},
"transformations": []interface{}{
map[string]interface{}{
"id": "seriesToRows",
"options": map[string]interface{}{
"reducers": []interface{}{},
},
},
},
"targets": []interface{}{
map[string]interface{}{"refId": "A"},
},
"pluginVersion": "1.0.0",
},
},
},
},
{
name: "should migrate table with timeseries_to_columns transform",
input: map[string]interface{}{
"schemaVersion": 23,
"panels": []interface{}{
map[string]interface{}{
"id": 5,
"type": "table",
"title": "Table with Columns Transform",
"styles": []interface{}{
map[string]interface{}{
"pattern": "/.*/",
"unit": "bytes",
},
},
"transform": "timeseries_to_columns",
"targets": []interface{}{
map[string]interface{}{"refId": "A"},
},
},
},
},
expected: map[string]interface{}{
"schemaVersion": 24,
"panels": []interface{}{
map[string]interface{}{
"id": 5,
"type": "table",
"title": "Table with Columns Transform",
"fieldConfig": map[string]interface{}{
"defaults": map[string]interface{}{
"unit": "bytes",
"custom": map[string]interface{}{
"align": "auto",
"cellOptions": map[string]interface{}{
"type": "auto",
},
"inspect": false,
},
"mappings": []interface{}{},
"thresholds": map[string]interface{}{
"mode": "absolute",
"steps": []interface{}{
map[string]interface{}{"color": "green"},
map[string]interface{}{"color": "red", "value": 80},
},
},
},
"overrides": []interface{}{},
},
"options": map[string]interface{}{
"cellHeight": "sm",
"footer": map[string]interface{}{
"countRows": false,
"fields": "",
"reducer": []interface{}{"sum"},
"show": false,
},
"showHeader": true,
},
"transformations": []interface{}{
map[string]interface{}{
"id": "seriesToColumns",
"options": map[string]interface{}{
"reducers": []interface{}{},
},
},
},
"targets": []interface{}{
map[string]interface{}{"refId": "A"},
},
"pluginVersion": "1.0.0",
},
},
},
},
{
name: "should migrate table with table merge transform",
input: map[string]interface{}{
"schemaVersion": 23,
"panels": []interface{}{
map[string]interface{}{
"id": 6,
"type": "table",
"title": "Table with Merge Transform",
"styles": []interface{}{
map[string]interface{}{
"pattern": "/.*/",
"align": "auto",
},
},
"transform": "table",
"targets": []interface{}{
map[string]interface{}{"refId": "A"},
},
},
},
},
expected: map[string]interface{}{
"schemaVersion": 24,
"panels": []interface{}{
map[string]interface{}{
"id": 6,
"type": "table",
"title": "Table with Merge Transform",
"fieldConfig": map[string]interface{}{
"defaults": map[string]interface{}{
"custom": map[string]interface{}{
"align": "",
"cellOptions": map[string]interface{}{
"type": "auto",
},
"inspect": false,
},
"mappings": []interface{}{},
"thresholds": map[string]interface{}{
"mode": "absolute",
"steps": []interface{}{
map[string]interface{}{"color": "green"},
map[string]interface{}{"color": "red", "value": 80},
},
},
},
"overrides": []interface{}{},
},
"options": map[string]interface{}{
"cellHeight": "sm",
"footer": map[string]interface{}{
"countRows": false,
"fields": "",
"reducer": []interface{}{"sum"},
"show": false,
},
"showHeader": true,
},
"transformations": []interface{}{
map[string]interface{}{
"id": "merge",
"options": map[string]interface{}{
"reducers": []interface{}{},
},
},
},
"targets": []interface{}{
map[string]interface{}{"refId": "A"},
},
"pluginVersion": "1.0.0",
},
},
},
},
{
name: "should migrate table with existing transformations",
input: map[string]interface{}{
"schemaVersion": 23,
"panels": []interface{}{
map[string]interface{}{
"id": 7,
"type": "table",
"title": "Table with Existing Transformations",
"styles": []interface{}{
map[string]interface{}{
"pattern": "/.*/",
"unit": "short",
},
},
"transformations": []interface{}{
map[string]interface{}{
"id": "filterFieldsByName",
"options": map[string]interface{}{
"include": map[string]interface{}{
"names": []interface{}{"field1", "field2"},
},
},
},
},
"transform": "timeseries_to_rows",
"targets": []interface{}{
map[string]interface{}{"refId": "A"},
},
},
},
},
expected: map[string]interface{}{
"schemaVersion": 24,
"panels": []interface{}{
map[string]interface{}{
"id": 7,
"type": "table",
"title": "Table with Existing Transformations",
"fieldConfig": map[string]interface{}{
"defaults": map[string]interface{}{
"unit": "short",
"custom": map[string]interface{}{
"align": "auto",
"cellOptions": map[string]interface{}{
"type": "auto",
},
"inspect": false,
},
"mappings": []interface{}{},
"thresholds": map[string]interface{}{
"mode": "absolute",
"steps": []interface{}{
map[string]interface{}{"color": "green"},
map[string]interface{}{"color": "red", "value": 80},
},
},
},
"overrides": []interface{}{},
},
"options": map[string]interface{}{
"cellHeight": "sm",
"footer": map[string]interface{}{
"countRows": false,
"fields": "",
"reducer": []interface{}{"sum"},
"show": false,
},
"showHeader": true,
},
"transformations": []interface{}{
map[string]interface{}{
"id": "filterFieldsByName",
"options": map[string]interface{}{
"include": map[string]interface{}{
"names": []interface{}{"field1", "field2"},
},
},
},
map[string]interface{}{
"id": "seriesToRows",
"options": map[string]interface{}{
"reducers": []interface{}{},
},
},
},
"targets": []interface{}{
map[string]interface{}{"refId": "A"},
},
"pluginVersion": "1.0.0",
},
},
},
},
{
name: "should not migrate angular table without styles",
input: map[string]interface{}{
"schemaVersion": 23,
"panels": []interface{}{
map[string]interface{}{
"id": 8,
"type": "table",
"title": "Table without styles",
},
},
},
expected: map[string]interface{}{
"schemaVersion": 24,
"panels": []interface{}{
map[string]interface{}{
"id": 8,
"type": "table",
"title": "Table without styles",
},
},
},
},
{
name: "should not migrate react table (table2)",
input: map[string]interface{}{
"schemaVersion": 23,
"panels": []interface{}{
map[string]interface{}{
"id": 9,
"type": "table",
"table": "table2",
"title": "React table",
"styles": []interface{}{
map[string]interface{}{
"pattern": "/.*/",
"unit": "short",
},
},
},
},
},
expected: map[string]interface{}{
"schemaVersion": 24,
"panels": []interface{}{
map[string]interface{}{
"id": 9,
"type": "table",
"table": "table2",
"title": "React table",
"styles": []interface{}{
map[string]interface{}{
"pattern": "/.*/",
"unit": "short",
},
},
},
},
},
},
{
name: "should not migrate non-table panels",
input: map[string]interface{}{
"schemaVersion": 23,
"panels": []interface{}{
map[string]interface{}{
"id": 10,
"type": "graph",
"title": "Graph panel",
"targets": []interface{}{
map[string]interface{}{"refId": "A"},
},
},
map[string]interface{}{
"id": 11,
"type": "singlestat",
"title": "Singlestat panel",
},
},
},
expected: map[string]interface{}{
"schemaVersion": 24,
"panels": []interface{}{
map[string]interface{}{
"id": 10,
"type": "graph",
"title": "Graph panel",
"targets": []interface{}{
map[string]interface{}{"refId": "A"},
},
},
map[string]interface{}{
"id": 11,
"type": "singlestat",
"title": "Singlestat panel",
},
},
},
},
{
name: "should handle mixed numeric and string thresholds",
input: map[string]interface{}{
"schemaVersion": 23,
"panels": []interface{}{
map[string]interface{}{
"id": 12,
"type": "table",
"title": "Mixed threshold types",
"styles": []interface{}{
map[string]interface{}{
"pattern": "/.*/",
"thresholds": []interface{}{10, "20", 30.5},
"colors": []interface{}{"green", "yellow", "orange", "red"},
},
},
"targets": []interface{}{
map[string]interface{}{"refId": "A"},
},
},
},
},
expected: map[string]interface{}{
"schemaVersion": 24,
"panels": []interface{}{
map[string]interface{}{
"id": 12,
"type": "table",
"title": "Mixed threshold types",
"fieldConfig": map[string]interface{}{
"defaults": map[string]interface{}{
"custom": map[string]interface{}{
"align": "auto",
"cellOptions": map[string]interface{}{
"type": "auto",
},
"inspect": false,
},
"mappings": []interface{}{},
"thresholds": map[string]interface{}{
"mode": "absolute",
"steps": []interface{}{
map[string]interface{}{"value": nil, "color": "green"},
map[string]interface{}{"value": float64(10), "color": "yellow"},
map[string]interface{}{"value": float64(20), "color": "orange"},
map[string]interface{}{"value": float64(30.5), "color": "red"},
},
},
},
"overrides": []interface{}{},
},
"options": map[string]interface{}{
"cellHeight": "sm",
"footer": map[string]interface{}{
"countRows": false,
"fields": "",
"reducer": []interface{}{"sum"},
"show": false,
},
"showHeader": true,
},
"transformations": []interface{}{},
"targets": []interface{}{
map[string]interface{}{"refId": "A"},
},
"pluginVersion": "1.0.0",
},
},
},
},
}
runMigrationTests(t, tests, schemaversion.V24(testutil.GetTestPanelProvider()))
}
@@ -0,0 +1,49 @@
package schemaversion
// V25 migration is a no-op migration
// It only updates the schema version to 25
// It's created to keep the migration history consistent with frontend migrator
// Variable tag removal is handled in v28 migration
// Example before migration:
// {
// "templating": {
// "list": [
// {
// "name": "tags should not be removed",
// "type": "query",
// "datasource": "prometheus",
// "tags": ["tags should not be removed"],
// "tagsQuery": "tag should not be removed",
// "tagValuesQuery": "tag should not be removed",
// "useTags": true,
// "options": []
// }
// ]
// }
// }
// Example after migration:
// {
// "templating": {
// "list": [
// {
// "name": "tags should not be removed",
// "type": "query",
// "datasource": "prometheus",
// "tags": ["tags should not be removed"],
// "tagsQuery": "tag should not be removed",
// "tagValuesQuery": "tag should not be removed",
// "useTags": true,
// "options": []
// }
// ]
// }
// }
func V25(dashboard map[string]interface{}) error {
dashboard["schemaVersion"] = int(25)
return nil
}
@@ -0,0 +1,38 @@
package schemaversion_test
import (
"testing"
"github.com/grafana/grafana/apps/dashboard/pkg/migration/schemaversion"
)
func TestV25(t *testing.T) {
tests := []migrationTestCase{
{
name: "v25 no-op migration, updates schema version only",
input: map[string]interface{}{
"title": "V25 No-Op Migration Test Dashboard",
"schemaVersion": 24,
"panels": []interface{}{
map[string]interface{}{
"type": "timeseries",
"title": "Panel remains unchanged",
"id": 1,
},
},
},
expected: map[string]interface{}{
"title": "V25 No-Op Migration Test Dashboard",
"schemaVersion": 25,
"panels": []interface{}{
map[string]interface{}{
"type": "timeseries",
"title": "Panel remains unchanged",
"id": 1,
},
},
},
},
}
runMigrationTests(t, tests, schemaversion.V25)
}
@@ -258,6 +258,24 @@ func (m *v28Migrator) migratetSinglestat(panel map[string]interface{}, defaults
// Migrate thresholds FIRST (consolidated: both panel types create DEFAULT_THRESHOLDS for empty strings)
m.migrateThresholds(angularOpts, defaults)
// If no thresholds were set from angular migration, add default stat panel thresholds
// This matches the behavior of frontend pluginLoaded which adds default thresholds
if _, hasThresholds := defaults["thresholds"]; !hasThresholds {
defaults["thresholds"] = map[string]interface{}{
"mode": "absolute",
"steps": []interface{}{
map[string]interface{}{
"color": "green",
"value": nil,
},
map[string]interface{}{
"color": "red",
"value": 80,
},
},
}
}
// Apply common angular option migrations (value mappings can now use threshold colors)
m.applyCommonAngularMigration(panel, defaults, options, angularOpts)
@@ -298,6 +316,24 @@ func (m *v28Migrator) migrateGrafanaSinglestatPanel(panel map[string]interface{}
// Migrate thresholds FIRST (consolidated: both panel types create DEFAULT_THRESHOLDS for empty strings)
m.migrateThresholds(angularOpts, defaults)
// If no thresholds were set from angular migration, add default stat panel thresholds
// This matches the behavior of frontend pluginLoaded which adds default thresholds
if _, hasThresholds := defaults["thresholds"]; !hasThresholds {
defaults["thresholds"] = map[string]interface{}{
"mode": "absolute",
"steps": []interface{}{
map[string]interface{}{
"color": "green",
"value": nil,
},
map[string]interface{}{
"color": "red",
"value": 80,
},
},
}
}
// Apply common angular option migrations (value mappings can now use threshold colors)
m.applyCommonAngularMigration(panel, defaults, options, angularOpts)
@@ -160,6 +160,19 @@ func TestV28(t *testing.T) {
"fixedColor": "#ff0000",
},
"mappings": []interface{}{},
"thresholds": map[string]interface{}{
"mode": "absolute",
"steps": []interface{}{
map[string]interface{}{
"color": "green",
"value": nil,
},
map[string]interface{}{
"color": "red",
"value": 80,
},
},
},
},
"overrides": []interface{}{},
},
@@ -228,6 +241,19 @@ func TestV28(t *testing.T) {
"fixedColor": "#ff0000",
},
"mappings": []interface{}{},
"thresholds": map[string]interface{}{
"mode": "absolute",
"steps": []interface{}{
map[string]interface{}{
"color": "green",
"value": nil,
},
map[string]interface{}{
"color": "red",
"value": 80,
},
},
},
},
"overrides": []interface{}{},
},
@@ -512,7 +538,20 @@ func TestV28(t *testing.T) {
},
"fieldConfig": map[string]interface{}{
"defaults": map[string]interface{}{
"unit": "short",
"unit": "short",
"thresholds": map[string]interface{}{
"mode": "absolute",
"steps": []interface{}{
map[string]interface{}{
"color": "green",
"value": nil,
},
map[string]interface{}{
"color": "red",
"value": 80,
},
},
},
"mappings": []interface{}{},
},
"overrides": []interface{}{},
@@ -564,7 +603,20 @@ func TestV28(t *testing.T) {
},
"fieldConfig": map[string]interface{}{
"defaults": map[string]interface{}{
"unit": "short",
"unit": "short",
"thresholds": map[string]interface{}{
"mode": "absolute",
"steps": []interface{}{
map[string]interface{}{
"color": "green",
"value": nil,
},
map[string]interface{}{
"color": "red",
"value": 80,
},
},
},
"mappings": []interface{}{},
},
"overrides": []interface{}{},
@@ -626,7 +678,20 @@ func TestV28(t *testing.T) {
},
"fieldConfig": map[string]interface{}{
"defaults": map[string]interface{}{
"unit": "bytes",
"unit": "bytes",
"thresholds": map[string]interface{}{
"mode": "absolute",
"steps": []interface{}{
map[string]interface{}{
"color": "green",
"value": nil,
},
map[string]interface{}{
"color": "red",
"value": 80,
},
},
},
"mappings": []interface{}{},
},
"overrides": []interface{}{},
@@ -0,0 +1,125 @@
{
"title": "V17 MinSpan to MaxPerRow Migration Test Dashboard",
"schemaVersion": 16,
"panels": [
{
"id": 1,
"type": "graph",
"title": "Panel with minSpan 8",
"minSpan": 8,
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 0
}
},
{
"id": 2,
"type": "singlestat",
"title": "Panel with minSpan 4",
"minSpan": 4,
"gridPos": {
"h": 4,
"w": 6,
"x": 0,
"y": 8
}
},
{
"id": 3,
"type": "table",
"title": "Panel with minSpan 2",
"minSpan": 2,
"gridPos": {
"h": 6,
"w": 24,
"x": 0,
"y": 12
}
},
{
"id": 4,
"type": "graph",
"title": "Panel with minSpan 12",
"minSpan": 12,
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 0
}
},
{
"id": 5,
"type": "gauge",
"title": "Panel with minSpan 24",
"minSpan": 24,
"gridPos": {
"h": 4,
"w": 24,
"x": 0,
"y": 18
}
},
{
"id": 6,
"type": "stat",
"title": "Panel with minSpan 1",
"minSpan": 1,
"gridPos": {
"h": 3,
"w": 4,
"x": 6,
"y": 8
}
},
{
"id": 7,
"type": "graph",
"title": "Panel without minSpan",
"gridPos": {
"h": 6,
"w": 8,
"x": 12,
"y": 8
}
},
{
"id": 8,
"type": "text",
"title": "Panel with invalid minSpan",
"minSpan": "invalid",
"gridPos": {
"h": 4,
"w": 6,
"x": 18,
"y": 8
}
},
{
"id": 9,
"type": "graph",
"title": "Panel with zero minSpan",
"minSpan": 0,
"gridPos": {
"h": 4,
"w": 6,
"x": 0,
"y": 22
}
},
{
"id": 10,
"type": "graph",
"title": "Panel with negative minSpan",
"minSpan": -5,
"gridPos": {
"h": 4,
"w": 6,
"x": 6,
"y": 22
}
}
]
}
@@ -0,0 +1,66 @@
{
"title": "V18 Gauge Options Migration Test Dashboard",
"schemaVersion": 17,
"panels": [
{
"id": 1,
"type": "gauge",
"title": "Complete Gauge Panel",
"options-gauge": {
"unit": "ms",
"stat": "last",
"decimals": 2,
"prefix": "Value: ",
"suffix": " ms",
"thresholds": [
{"color": "green", "value": 0},
{"color": "yellow", "value": 50},
{"color": "red", "value": 100}
]
}
},
{
"id": 2,
"type": "gauge",
"title": "Partial Gauge Panel",
"options-gauge": {
"unit": "percent",
"decimals": 1
}
},
{
"id": 3,
"type": "gauge",
"title": "Buggy Gauge Panel",
"options-gauge": {
"unit": "bytes",
"options": "this should be deleted",
"stat": "avg",
"decimals": 0
}
},
{
"id": 4,
"type": "gauge",
"title": "Custom Properties Gauge Panel",
"options-gauge": {
"unit": "short",
"customProperty": "customValue",
"anotherProp": 42,
"thresholds": [
{"color": "blue", "value": 10}
]
}
},
{
"id": 5,
"type": "graph",
"title": "Non-Gauge Panel",
"options": {
"legend": {
"show": true
}
}
}
]
}
@@ -0,0 +1,73 @@
{
"title": "V19 Panel Links Migration Test Dashboard",
"schemaVersion": 18,
"panels": [
{
"id": 1,
"type": "graph",
"title": "Panel with legacy dashboard link",
"links": [
{
"dashboard": "my dashboard",
"title": "Dashboard Link",
"keepTime": true
}
]
},
{
"id": 2,
"type": "stat",
"title": "Panel with dashUri link",
"links": [
{
"dashUri": "my-dashboard-uid",
"title": "DashUri Link",
"includeVars": true
}
]
},
{
"id": 3,
"type": "table",
"title": "Panel with custom params",
"links": [
{
"url": "http://example.com",
"params": "customParam=value",
"title": "Custom Params Link"
}
]
},
{
"id": 4,
"type": "timeseries",
"title": "Panel with complex link",
"links": [
{
"dashboard": "complex dashboard",
"keepTime": true,
"includeVars": true,
"params": "param1=value1&param2=value2",
"title": "Complex Link",
"targetBlank": true
}
]
},
{
"id": 5,
"type": "gauge",
"title": "Panel with existing URL",
"links": [
{
"url": "http://existing-url.com",
"title": "Existing URL Link"
}
]
},
{
"id": 6,
"type": "singlestat",
"title": "Panel with no links"
}
]
}
@@ -0,0 +1,152 @@
{
"title": "V20 Variable Syntax Migration Test Dashboard",
"schemaVersion": 19,
"panels": [
{
"type": "timeseries",
"title": "Panel with data links using legacy variable syntax",
"id": 1,
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 0
},
"options": {
"dataLinks": [
{
"title": "Link with series name",
"url": "http://example.com?series=$__series_name&timestamp=__value_time",
"targetBlank": true
},
{
"title": "Link with field name",
"url": "http://grafana.com/dashboard?field=__field_name&series=$__series_name",
"targetBlank": false
}
]
}
},
{
"type": "stat",
"title": "Panel with field options using legacy variable syntax",
"id": 2,
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 0
},
"options": {
"fieldOptions": {
"defaults": {
"title": "Series: __series_name, Field: $__field_name, Time: __value_time",
"links": [
{
"title": "Field link",
"url": "http://monitoring.com?field=$__field_name&series=__series_name",
"targetBlank": true
},
{
"title": "Time-based link",
"url": "http://logs.com?time=__value_time&field=__field_name",
"targetBlank": false
}
]
}
}
}
},
{
"type": "gauge",
"title": "Panel with both data links and field options",
"id": 3,
"gridPos": {
"h": 8,
"w": 24,
"x": 0,
"y": 8
},
"options": {
"dataLinks": [
{
"title": "Combined link",
"url": "http://combined.com?series=$__series_name&field=$__field_name&time=__value_time",
"targetBlank": true
}
],
"fieldOptions": {
"defaults": {
"title": "Complete: __series_name / __field_name / __value_time",
"links": [
{
"title": "Comprehensive link",
"url": "http://comprehensive.com?s=$__series_name&f=__field_name&t=__value_time",
"targetBlank": false
}
]
}
}
}
},
{
"type": "table",
"title": "Panel with no legacy variables (should remain unchanged)",
"id": 4,
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 16
},
"options": {
"dataLinks": [
{
"title": "Modern link",
"url": "http://modern.com?series=${__series.name}&field=${__field.name}&time=${__value.time}",
"targetBlank": true
}
],
"fieldOptions": {
"defaults": {
"title": "Modern: ${__series.name} / ${__field.name} / ${__value.time}",
"links": [
{
"title": "Modern field link",
"url": "http://modern-field.com?s=${__series.name}&f=${__field.name}",
"targetBlank": false
}
]
}
}
}
},
{
"type": "text",
"title": "Panel with no data links or field options",
"id": 5,
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 16
},
"options": {
"content": "This panel has no data links or field options to migrate."
}
}
],
"time": {
"from": "now-6h",
"to": "now"
},
"timepicker": {},
"timezone": "",
"tags": [
"migration-test"
],
"style": "dark",
"refresh": "5s",
"version": 0,
"uid": "v20-migration-test"
}
@@ -0,0 +1,85 @@
{
"title": "V21 Data Links Series to Field Migration Test Dashboard",
"schemaVersion": 20,
"panels": [
{
"type": "timeseries",
"title": "Panel with data links",
"id": 1,
"options": {
"dataLinks": [
{
"url": "http://mylink.com?series=${__series.labels}&${__series.labels.a}",
"title": "Data Link 1"
},
{
"url": "http://anotherlink.com?param=${__series.labels}",
"title": "Data Link 2"
}
]
}
},
{
"type": "stat",
"title": "Panel with field options links",
"id": 2,
"options": {
"fieldOptions": {
"defaults": {
"links": [
{
"url": "http://mylink.com?series=${__series.labels}&${__series.labels.x}",
"title": "Field Link 1"
},
{
"url": "http://fieldlink.com?field=${__series.labels}",
"title": "Field Link 2"
}
]
}
}
}
},
{
"type": "graph",
"title": "Panel with both link types",
"id": 3,
"options": {
"dataLinks": [
{
"url": "http://mylink.com?series=${__series.labels}",
"title": "Graph Data Link"
}
],
"fieldOptions": {
"defaults": {
"links": [
{
"url": "http://mylink.com?field=${__series.labels}",
"title": "Graph Field Link"
}
]
}
}
}
},
{
"type": "timeseries",
"title": "Panel without series labels",
"id": 4,
"options": {
"dataLinks": [
{
"url": "http://mylink.com?other=${__field.labels}",
"title": "No Series Labels Link"
}
]
}
},
{
"type": "timeseries",
"title": "Panel without options",
"id": 5
}
]
}
@@ -0,0 +1,14 @@
{
"title": "V22 Table Panel Styles Test",
"schemaVersion": 21,
"panels": [
{
"id": 1,
"type": "table",
"styles": [
{ "type": "number", "pattern": "Time", "align": "left" },
{ "type": "string", "pattern": "Value", "align": "right" }
]
}
]
}
@@ -0,0 +1,129 @@
{
"title": "V23 Multi Variables Migration Test Dashboard",
"schemaVersion": 22,
"templating": {
"list": [
{
"name": "multi_single_value",
"type": "query",
"datasource": "prometheus",
"multi": true,
"current": {
"value": "A",
"text": "A",
"selected": true
}
},
{
"name": "multi_array_value",
"type": "query",
"datasource": "prometheus",
"multi": true,
"current": {
"value": ["B", "C"],
"text": ["B", "C"],
"selected": true
}
},
{
"name": "non_multi_array_value",
"type": "query",
"datasource": "prometheus",
"multi": false,
"current": {
"value": ["D"],
"text": ["D"],
"selected": true
}
},
{
"name": "non_multi_single_value",
"type": "query",
"datasource": "prometheus",
"multi": false,
"current": {
"value": "E",
"text": "E",
"selected": true
}
},
{
"name": "no_multi_property",
"type": "query",
"datasource": "prometheus",
"current": {
"value": "F",
"text": "F",
"selected": true
}
},
{
"name": "empty_current",
"type": "query",
"datasource": "prometheus",
"multi": true,
"current": {}
},
{
"name": "nil_current",
"type": "query",
"datasource": "prometheus",
"multi": true,
"current": null
},
{
"name": "custom_variable",
"type": "custom",
"multi": true,
"current": {
"value": "G",
"text": "G",
"selected": true
}
},
{
"name": "textbox_variable",
"type": "textbox",
"multi": false,
"current": {
"value": "H",
"text": "H",
"selected": true
}
},
{
"name": "datasource_variable",
"type": "datasource",
"multi": true,
"current": {
"value": ["prometheus", "influxdb"],
"text": ["Prometheus", "InfluxDB"],
"selected": true
}
},
{
"name": "interval_variable",
"type": "interval",
"multi": false,
"current": {
"value": ["1m"],
"text": ["1m"],
"selected": true
}
}
]
},
"panels": [
{
"id": 1,
"title": "Test Panel",
"type": "stat",
"targets": [
{
"refId": "A",
"expr": "up"
}
]
}
]
}
@@ -0,0 +1,345 @@
{
"schemaVersion": 23,
"panels": [
{
"id": 1,
"type": "table",
"title": "Basic Angular Table with Defaults",
"description": "Tests basic migration with default style pattern (/.*/) containing thresholds and colors. Should convert styles to fieldConfig.defaults with threshold steps.",
"legend": true,
"styles": [
{
"thresholds": ["10", "20", "30"],
"colors": ["red", "yellow", "green"],
"pattern": "/.*/"
}
],
"targets": [{ "refId": "A" }, {}]
},
{
"id": 2,
"type": "table",
"title": "Complex Table with All Style Features",
"description": "Tests comprehensive migration including: default style with thresholds/colors/unit/decimals/align/colorMode, column overrides with exact name and regex patterns, date formatting, hidden columns, and links with tooltips.",
"styles": [
{
"pattern": "/.*/",
"thresholds": ["100", "500"],
"colors": ["green", "yellow", "red"],
"unit": "bytes",
"decimals": 2,
"align": "center",
"colorMode": "cell"
},
{
"pattern": "Status",
"alias": "Current Status",
"unit": "short",
"decimals": 0,
"colorMode": "value",
"align": "left"
},
{
"pattern": "/Error.*/",
"link": true,
"linkUrl": "http://example.com/errors",
"linkTooltip": "View error details",
"linkTargetBlank": true,
"colorMode": "row"
},
{
"pattern": "Time",
"type": "date",
"dateFormat": "YYYY-MM-DD HH:mm:ss",
"alias": "Timestamp"
},
{
"pattern": "Hidden",
"type": "hidden"
}
],
"targets": [{ "refId": "A" }]
},
{
"id": 3,
"type": "table",
"title": "Table with Timeseries Aggregations Transform",
"description": "Tests migration of timeseries_aggregations transform to reduce transformation with column mappings (avg->mean, max->max, min->min, total->sum, current->lastNotNull, count->count).",
"styles": [
{
"pattern": "/.*/",
"unit": "percent",
"decimals": 1
}
],
"transform": "timeseries_aggregations",
"columns": [
{ "value": "avg", "text": "Average" },
{ "value": "max", "text": "Maximum" },
{ "value": "min", "text": "Minimum" },
{ "value": "total", "text": "Total" },
{ "value": "current", "text": "Current" },
{ "value": "count", "text": "Count" }
],
"targets": [{ "refId": "A" }]
},
{
"id": 4,
"type": "table",
"title": "Table with Timeseries to Rows Transform",
"description": "Tests migration of timeseries_to_rows transform to seriesToRows transformation.",
"styles": [
{
"pattern": "/.*/",
"unit": "short"
}
],
"transform": "timeseries_to_rows",
"targets": [{ "refId": "A" }]
},
{
"id": 5,
"type": "table",
"title": "Table with Timeseries to Columns Transform",
"description": "Tests migration of timeseries_to_columns transform to seriesToColumns transformation.",
"styles": [
{
"pattern": "/.*/",
"unit": "bytes"
}
],
"transform": "timeseries_to_columns",
"targets": [{ "refId": "A" }]
},
{
"id": 6,
"type": "table",
"title": "Table with Merge Transform",
"description": "Tests migration of table transform to merge transformation. Also tests auto alignment conversion to empty string.",
"styles": [
{
"pattern": "/.*/",
"align": "auto"
}
],
"transform": "table",
"targets": [{ "refId": "A" }]
},
{
"id": 7,
"type": "table",
"title": "Table with Existing Transformations",
"description": "Tests that existing transformations are preserved and new transformation from old format is appended to the list.",
"styles": [
{
"pattern": "/.*/",
"unit": "short"
}
],
"transformations": [
{
"id": "filterFieldsByName",
"options": {
"include": {
"names": ["field1", "field2"]
}
}
}
],
"transform": "timeseries_to_rows",
"targets": [{ "refId": "A" }]
},
{
"id": 8,
"type": "table",
"title": "Mixed Threshold Types",
"description": "Tests handling of mixed numeric and string threshold values (int, string, float) with proper type conversion.",
"styles": [
{
"pattern": "/.*/",
"thresholds": [10, "20", 30.5],
"colors": ["green", "yellow", "orange", "red"]
}
],
"targets": [{ "refId": "A" }]
},
{
"id": 9,
"type": "table",
"title": "All Color Modes Test",
"description": "Tests all color mode mappings: cell->color-background, row->color-background, value->color-text.",
"styles": [
{
"pattern": "/.*/",
"colorMode": "cell"
},
{
"pattern": "CellColumn",
"colorMode": "cell"
},
{
"pattern": "RowColumn",
"colorMode": "row"
},
{
"pattern": "ValueColumn",
"colorMode": "value"
}
],
"targets": [{ "refId": "A" }]
},
{
"id": 10,
"type": "table",
"title": "All Alignment Options",
"description": "Tests all alignment options: left, center, right, and auto (should convert to empty string).",
"styles": [
{
"pattern": "/.*/",
"align": "center"
},
{
"pattern": "LeftColumn",
"align": "left"
},
{
"pattern": "RightColumn",
"align": "right"
},
{
"pattern": "AutoColumn",
"align": "auto"
}
],
"targets": [{ "refId": "A" }]
},
{
"id": 11,
"type": "table",
"title": "Field Matcher Types Test",
"description": "Tests both field matcher types: byName for exact matches and byRegexp for regex patterns.",
"styles": [
{
"pattern": "/.*/",
"unit": "short"
},
{
"pattern": "ExactColumnName",
"alias": "Exact Match"
},
{
"pattern": "/Regex.*Pattern/",
"alias": "Regex Match"
},
{
"pattern": "/^Start/",
"alias": "Start Pattern"
},
{
"pattern": "/End$/",
"alias": "End Pattern"
}
],
"targets": [{ "refId": "A" }]
},
{
"id": 12,
"type": "table",
"title": "Link Configuration Test",
"description": "Tests various link configurations: with and without tooltip, with and without target blank.",
"styles": [
{
"pattern": "/.*/",
"unit": "short"
},
{
"pattern": "LinkWithTooltip",
"link": true,
"linkUrl": "http://example.com/with-tooltip",
"linkTooltip": "Click to view details",
"linkTargetBlank": true
},
{
"pattern": "LinkWithoutTooltip",
"link": true,
"linkUrl": "http://example.com/no-tooltip",
"linkTargetBlank": false
},
{
"pattern": "LinkMinimal",
"link": true,
"linkUrl": "http://example.com/minimal"
}
],
"targets": [{ "refId": "A" }]
},
{
"id": 13,
"type": "table",
"title": "Date Format Variations",
"description": "Tests various date format patterns and aliases.",
"styles": [
{
"pattern": "/.*/",
"unit": "short"
},
{
"pattern": "DateISO",
"type": "date",
"dateFormat": "YYYY-MM-DD",
"alias": "ISO Date"
},
{
"pattern": "DateTime",
"type": "date",
"dateFormat": "YYYY-MM-DD HH:mm:ss",
"alias": "Full DateTime"
},
{
"pattern": "TimeOnly",
"type": "date",
"dateFormat": "HH:mm:ss",
"alias": "Time Only"
}
],
"targets": [{ "refId": "A" }]
},
{
"id": 14,
"type": "table",
"title": "React Table - Should NOT Migrate",
"description": "React table (table2) should not be migrated. Properties should remain unchanged.",
"table": "table2",
"styles": [
{
"pattern": "/.*/",
"unit": "short",
"thresholds": ["10", "20"],
"colors": ["red", "yellow", "green"]
}
],
"targets": [{ "refId": "A" }]
},
{
"id": 15,
"type": "table",
"title": "Angular Table without Styles - Should NOT Migrate",
"description": "Angular table without styles property should not be migrated.",
"targets": [{ "refId": "A" }]
},
{
"id": 16,
"type": "graph",
"title": "Non-Table Panel - Should NOT Migrate",
"description": "Non-table panels should remain completely unchanged.",
"targets": [{ "refId": "A" }]
},
{
"id": 17,
"type": "singlestat",
"title": "Singlestat Panel - Should NOT Migrate",
"description": "Other panel types should not be affected by table migration.",
"targets": [{ "refId": "A" }]
}
]
}
@@ -0,0 +1,56 @@
{
"title": "V25 No-Op Migration Test Dashboard",
"schemaVersion": 24,
"panels": [
{
"type": "timeseries",
"title": "Panel with transformations remains unchanged",
"id": 1,
"transformations": [
]
},
{
"type": "graph",
"title": "Graph",
"id": 2,
"yAxes": [
{
"show": true,
"min": null,
"max": null
}
]
}
],
"templating": {
"list": [
{
"name": "tags should not be removed",
"type": "query",
"datasource": "prometheus",
"tags": ["tags should not be removed"],
"tagsQuery": "tag should not be removed",
"tagValuesQuery": "tag should not be removed",
"useTags": true,
"options": []
}
]
},
"annotations": {
"list": [
{
"name": "Deployments",
"datasource": "grafana",
"enable": true
}
]
},
"time": {
"from": "now-6h",
"to": "now"
},
"timepicker": {
"refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"]
}
}
@@ -0,0 +1,317 @@
{
"panels": [
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 0
},
"id": 1,
"maxPerRow": 3,
"targets": [
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"refId": "A"
}
],
"title": "Panel with minSpan 8",
"type": "graph"
},
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"fieldConfig": {
"defaults": {
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 4,
"w": 6,
"x": 0,
"y": 8
},
"id": 2,
"maxPerRow": 6,
"options": {
"colorMode": "none",
"graphMode": "none",
"justifyMode": "auto",
"orientation": "horizontal",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"mean"
],
"fields": "",
"values": false
},
"showPercentChange": false,
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "1.0.0",
"targets": [
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"refId": "A"
}
],
"title": "Panel with minSpan 4",
"type": "stat"
},
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"gridPos": {
"h": 6,
"w": 24,
"x": 0,
"y": 12
},
"id": 3,
"maxPerRow": 12,
"targets": [
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"refId": "A"
}
],
"title": "Panel with minSpan 2",
"type": "table"
},
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 0
},
"id": 4,
"maxPerRow": 2,
"targets": [
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"refId": "A"
}
],
"title": "Panel with minSpan 12",
"type": "graph"
},
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"gridPos": {
"h": 4,
"w": 24,
"x": 0,
"y": 18
},
"id": 5,
"maxPerRow": 1,
"targets": [
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"refId": "A"
}
],
"title": "Panel with minSpan 24",
"type": "gauge"
},
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"gridPos": {
"h": 3,
"w": 4,
"x": 6,
"y": 8
},
"id": 6,
"maxPerRow": 24,
"options": {
"justifyMode": "auto",
"percentChangeColorMode": "standard",
"showPercentChange": false,
"textMode": "auto",
"wideLayout": true
},
"targets": [
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"refId": "A"
}
],
"title": "Panel with minSpan 1",
"type": "stat"
},
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"gridPos": {
"h": 6,
"w": 8,
"x": 12,
"y": 8
},
"id": 7,
"targets": [
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"refId": "A"
}
],
"title": "Panel without minSpan",
"type": "graph"
},
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"gridPos": {
"h": 4,
"w": 6,
"x": 18,
"y": 8
},
"id": 8,
"targets": [
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"refId": "A"
}
],
"title": "Panel with invalid minSpan",
"type": "text"
},
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"gridPos": {
"h": 4,
"w": 6,
"x": 0,
"y": 22
},
"id": 9,
"targets": [
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"refId": "A"
}
],
"title": "Panel with zero minSpan",
"type": "graph"
},
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"gridPos": {
"h": 4,
"w": 6,
"x": 6,
"y": 22
},
"id": 10,
"targets": [
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"refId": "A"
}
],
"title": "Panel with negative minSpan",
"type": "graph"
}
],
"refresh": "",
"schemaVersion": 41,
"title": "V17 MinSpan to MaxPerRow Migration Test Dashboard"
}
@@ -0,0 +1,162 @@
{
"panels": [
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"id": 1,
"options": {
"thresholds": [
{
"color": "red",
"value": 100
},
{
"color": "yellow",
"value": 50
},
{
"color": "green",
"value": 0
}
],
"valueOptions": {
"decimals": 2,
"prefix": "Value: ",
"stat": "last",
"suffix": " ms",
"unit": "ms"
}
},
"targets": [
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"refId": "A"
}
],
"title": "Complete Gauge Panel",
"type": "gauge"
},
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"id": 2,
"options": {
"valueOptions": {
"decimals": 1,
"unit": "percent"
}
},
"targets": [
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"refId": "A"
}
],
"title": "Partial Gauge Panel",
"type": "gauge"
},
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"id": 3,
"options": {
"valueOptions": {
"decimals": 0,
"stat": "avg",
"unit": "bytes"
}
},
"targets": [
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"refId": "A"
}
],
"title": "Buggy Gauge Panel",
"type": "gauge"
},
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"id": 4,
"options": {
"anotherProp": 42,
"customProperty": "customValue",
"thresholds": [
{
"color": "blue",
"value": 10
}
],
"valueOptions": {
"unit": "short"
}
},
"targets": [
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"refId": "A"
}
],
"title": "Custom Properties Gauge Panel",
"type": "gauge"
},
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"id": 5,
"options": {
"legend": {
"show": true,
"showLegend": true
}
},
"targets": [
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"refId": "A"
}
],
"title": "Non-Gauge Panel",
"type": "graph"
}
],
"refresh": "",
"schemaVersion": 41,
"title": "V18 Gauge Options Migration Test Dashboard"
}
@@ -0,0 +1,206 @@
{
"panels": [
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"id": 1,
"links": [
{
"targetBlank": false,
"title": "Dashboard Link",
"url": "dashboard/db/my-dashboard?$__url_time_range"
}
],
"targets": [
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"refId": "A"
}
],
"title": "Panel with legacy dashboard link",
"type": "graph"
},
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"id": 2,
"links": [
{
"targetBlank": false,
"title": "DashUri Link",
"url": "dashboard/my-dashboard-uid?$__all_variables"
}
],
"options": {
"justifyMode": "auto",
"percentChangeColorMode": "standard",
"showPercentChange": false,
"textMode": "auto",
"wideLayout": true
},
"targets": [
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"refId": "A"
}
],
"title": "Panel with dashUri link",
"type": "stat"
},
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"id": 3,
"links": [
{
"targetBlank": false,
"title": "Custom Params Link",
"url": "http://example.com?customParam=value"
}
],
"targets": [
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"refId": "A"
}
],
"title": "Panel with custom params",
"type": "table"
},
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"id": 4,
"links": [
{
"targetBlank": true,
"title": "Complex Link",
"url": "dashboard/db/complex-dashboard?$__url_time_range\u0026$__all_variables\u0026param1=value1\u0026param2=value2"
}
],
"targets": [
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"refId": "A"
}
],
"title": "Panel with complex link",
"type": "timeseries"
},
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"id": 5,
"links": [
{
"targetBlank": false,
"title": "Existing URL Link",
"url": "http://existing-url.com"
}
],
"targets": [
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"refId": "A"
}
],
"title": "Panel with existing URL",
"type": "gauge"
},
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"fieldConfig": {
"defaults": {
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": []
},
"id": 6,
"options": {
"colorMode": "none",
"graphMode": "none",
"justifyMode": "auto",
"orientation": "horizontal",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"mean"
],
"fields": "",
"values": false
},
"showPercentChange": false,
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "1.0.0",
"targets": [
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"refId": "A"
}
],
"title": "Panel with no links",
"type": "stat"
}
],
"refresh": "",
"schemaVersion": 41,
"title": "V19 Panel Links Migration Test Dashboard"
}
@@ -0,0 +1,232 @@
{
"panels": [
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 0
},
"id": 1,
"options": {
"dataLinks": [
{
"targetBlank": true,
"title": "Link with series name",
"url": "http://example.com?series=${__series.name}\u0026timestamp=__value.time"
},
{
"targetBlank": false,
"title": "Link with field name",
"url": "http://grafana.com/dashboard?field=__field.name\u0026series=${__series.name}"
}
]
},
"targets": [
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"refId": "A"
}
],
"title": "Panel with data links using legacy variable syntax",
"type": "timeseries"
},
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 0
},
"id": 2,
"options": {
"fieldOptions": {
"defaults": {
"links": [
{
"targetBlank": true,
"title": "Field link",
"url": "http://monitoring.com?field=${__field.name}\u0026series=__series.name"
},
{
"targetBlank": false,
"title": "Time-based link",
"url": "http://logs.com?time=__value.time\u0026field=__field.name"
}
],
"title": "Series: __series.name, Field: ${__field.name}, Time: __value.time"
}
},
"justifyMode": "auto",
"percentChangeColorMode": "standard",
"showPercentChange": false,
"textMode": "auto",
"wideLayout": true
},
"targets": [
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"refId": "A"
}
],
"title": "Panel with field options using legacy variable syntax",
"type": "stat"
},
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"gridPos": {
"h": 8,
"w": 24,
"x": 0,
"y": 8
},
"id": 3,
"options": {
"dataLinks": [
{
"targetBlank": true,
"title": "Combined link",
"url": "http://combined.com?series=${__series.name}\u0026field=${__field.name}\u0026time=__value.time"
}
],
"fieldOptions": {
"defaults": {
"links": [
{
"targetBlank": false,
"title": "Comprehensive link",
"url": "http://comprehensive.com?s=${__series.name}\u0026f=__field.name\u0026t=__value.time"
}
],
"title": "Complete: __series.name / __field.name / __value.time"
}
}
},
"targets": [
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"refId": "A"
}
],
"title": "Panel with both data links and field options",
"type": "gauge"
},
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 16
},
"id": 4,
"options": {
"dataLinks": [
{
"targetBlank": true,
"title": "Modern link",
"url": "http://modern.com?series=${__series.name}\u0026field=${__field.name}\u0026time=${__value.time}"
}
],
"fieldOptions": {
"defaults": {
"links": [
{
"targetBlank": false,
"title": "Modern field link",
"url": "http://modern-field.com?s=${__series.name}\u0026f=${__field.name}"
}
],
"title": "Modern: ${__series.name} / ${__field.name} / ${__value.time}"
}
}
},
"targets": [
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"refId": "A"
}
],
"title": "Panel with no legacy variables (should remain unchanged)",
"type": "table"
},
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 16
},
"id": 5,
"options": {
"content": "This panel has no data links or field options to migrate."
},
"targets": [
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"refId": "A"
}
],
"title": "Panel with no data links or field options",
"type": "text"
}
],
"refresh": "5s",
"schemaVersion": 41,
"style": "dark",
"tags": [
"migration-test"
],
"time": {
"from": "now-6h",
"to": "now"
},
"timepicker": {},
"timezone": "",
"title": "V20 Variable Syntax Migration Test Dashboard",
"uid": "v20-migration-test",
"version": 0
}
@@ -0,0 +1,166 @@
{
"panels": [
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"id": 1,
"options": {
"dataLinks": [
{
"title": "Data Link 1",
"url": "http://mylink.com?series=${__field.labels}\u0026${__field.labels.a}"
},
{
"title": "Data Link 2",
"url": "http://anotherlink.com?param=${__field.labels}"
}
]
},
"targets": [
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"refId": "A"
}
],
"title": "Panel with data links",
"type": "timeseries"
},
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"id": 2,
"options": {
"fieldOptions": {
"defaults": {
"links": [
{
"title": "Field Link 1",
"url": "http://mylink.com?series=${__field.labels}\u0026${__field.labels.x}"
},
{
"title": "Field Link 2",
"url": "http://fieldlink.com?field=${__field.labels}"
}
]
}
},
"justifyMode": "auto",
"percentChangeColorMode": "standard",
"showPercentChange": false,
"textMode": "auto",
"wideLayout": true
},
"targets": [
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"refId": "A"
}
],
"title": "Panel with field options links",
"type": "stat"
},
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"id": 3,
"options": {
"dataLinks": [
{
"title": "Graph Data Link",
"url": "http://mylink.com?series=${__field.labels}"
}
],
"fieldOptions": {
"defaults": {
"links": [
{
"title": "Graph Field Link",
"url": "http://mylink.com?field=${__field.labels}"
}
]
}
}
},
"targets": [
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"refId": "A"
}
],
"title": "Panel with both link types",
"type": "graph"
},
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"id": 4,
"options": {
"dataLinks": [
{
"title": "No Series Labels Link",
"url": "http://mylink.com?other=${__field.labels}"
}
]
},
"targets": [
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"refId": "A"
}
],
"title": "Panel without series labels",
"type": "timeseries"
},
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"id": 5,
"targets": [
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"refId": "A"
}
],
"title": "Panel without options",
"type": "timeseries"
}
],
"refresh": "",
"schemaVersion": 41,
"title": "V21 Data Links Series to Field Migration Test Dashboard"
}
@@ -0,0 +1,90 @@
{
"panels": [
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"fieldConfig": {
"defaults": {
"custom": {
"align": "auto",
"cellOptions": {
"type": "auto"
},
"inspect": false
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green"
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "Time"
},
"properties": [
{
"id": "custom.align",
"value": ""
}
]
},
{
"matcher": {
"id": "byName",
"options": "Value"
},
"properties": [
{
"id": "custom.align",
"value": ""
}
]
}
]
},
"id": 1,
"options": {
"cellHeight": "sm",
"footer": {
"countRows": false,
"fields": "",
"reducer": [
"sum"
],
"show": false
},
"showHeader": true
},
"pluginVersion": "1.0.0",
"targets": [
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"refId": "A"
}
],
"transformations": [],
"type": "table"
}
],
"refresh": "",
"schemaVersion": 41,
"title": "V22 Table Panel Styles Test"
}
@@ -0,0 +1,188 @@
{
"panels": [
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"id": 1,
"options": {
"justifyMode": "auto",
"percentChangeColorMode": "standard",
"showPercentChange": false,
"textMode": "auto",
"wideLayout": true
},
"targets": [
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"expr": "up",
"refId": "A"
}
],
"title": "Test Panel",
"type": "stat"
}
],
"refresh": "",
"schemaVersion": 41,
"templating": {
"list": [
{
"current": {
"selected": true,
"text": [
"A"
],
"value": [
"A"
]
},
"datasource": {
"uid": "prometheus"
},
"multi": true,
"name": "multi_single_value",
"refresh": 1,
"type": "query"
},
{
"current": {
"selected": true,
"text": [
"B",
"C"
],
"value": [
"B",
"C"
]
},
"datasource": {
"uid": "prometheus"
},
"multi": true,
"name": "multi_array_value",
"refresh": 1,
"type": "query"
},
{
"current": {
"selected": true,
"text": "D",
"value": "D"
},
"datasource": {
"uid": "prometheus"
},
"multi": false,
"name": "non_multi_array_value",
"refresh": 1,
"type": "query"
},
{
"current": {
"selected": true,
"text": "E",
"value": "E"
},
"datasource": {
"uid": "prometheus"
},
"multi": false,
"name": "non_multi_single_value",
"refresh": 1,
"type": "query"
},
{
"current": {
"selected": true,
"text": "F",
"value": "F"
},
"datasource": {
"uid": "prometheus"
},
"name": "no_multi_property",
"refresh": 1,
"type": "query"
},
{
"current": {},
"datasource": {
"uid": "prometheus"
},
"multi": true,
"name": "empty_current",
"refresh": 1,
"type": "query"
},
{
"current": null,
"datasource": {
"uid": "prometheus"
},
"multi": true,
"name": "nil_current",
"refresh": 1,
"type": "query"
},
{
"current": {
"selected": true,
"text": [
"G"
],
"value": [
"G"
]
},
"multi": true,
"name": "custom_variable",
"type": "custom"
},
{
"current": {
"selected": true,
"text": "H",
"value": "H"
},
"multi": false,
"name": "textbox_variable",
"type": "textbox"
},
{
"current": {
"selected": true,
"text": [
"Prometheus",
"InfluxDB"
],
"value": [
"prometheus",
"influxdb"
]
},
"multi": true,
"name": "datasource_variable",
"type": "datasource"
},
{
"current": {
"selected": true,
"text": "1m",
"value": "1m"
},
"multi": false,
"name": "interval_variable",
"type": "interval"
}
]
},
"title": "V23 Multi Variables Migration Test Dashboard"
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,97 @@
{
"annotations": {
"list": [
{
"datasource": {
"uid": "grafana"
},
"enable": true,
"name": "Deployments"
}
]
},
"panels": [
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"id": 1,
"targets": [
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"refId": "A"
}
],
"title": "Panel with transformations remains unchanged",
"transformations": [],
"type": "timeseries"
},
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"id": 2,
"targets": [
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"refId": "A"
}
],
"title": "Graph",
"type": "graph",
"yAxes": [
{
"max": null,
"min": null,
"show": true
}
]
}
],
"refresh": "",
"schemaVersion": 41,
"templating": {
"list": [
{
"datasource": {
"uid": "prometheus"
},
"name": "tags should not be removed",
"options": [],
"refresh": 1,
"type": "query"
}
]
},
"time": {
"from": "now-6h",
"to": "now"
},
"timepicker": {
"refresh_intervals": [
"5s",
"10s",
"30s",
"1m",
"5m",
"15m",
"30m",
"1h",
"2h",
"1d"
]
},
"title": "V25 No-Op Migration Test Dashboard"
}
@@ -78,6 +78,10 @@ func (m *TestPanelProvider) GetPanels() []schemaversion.PanelPluginInfo {
ID: "stat",
Version: "1.0.0",
},
{
ID: "table",
Version: "1.0.0",
},
// Note: grafana-singlestat-panel is not included to match frontend test environment
// This ensures both frontend and backend migrations produce the same result
}
@@ -0,0 +1,9 @@
package utils
func IsArray(value interface{}) bool {
if value == nil {
return false
}
_, ok := value.([]interface{})
return ok
}
+1 -1
View File
@@ -1,6 +1,6 @@
module github.com/grafana/grafana/apps/folder
go 1.24.5
go 1.24.6
require (
github.com/grafana/grafana-app-sdk v0.40.3
+1 -1
View File
@@ -1,6 +1,6 @@
module github.com/grafana/grafana/apps/iam
go 1.24.5
go 1.24.6
require (
github.com/grafana/grafana-app-sdk v0.40.3
+11 -149
View File
@@ -1,197 +1,70 @@
module github.com/grafana/grafana/apps/investigations
go 1.24.5
go 1.24.6
require (
github.com/grafana/grafana v0.0.0-00010101000000-000000000000
github.com/grafana/grafana-app-sdk v0.40.2
github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250804150913-990f1c69ecc2
github.com/stretchr/testify v1.10.0
github.com/grafana/grafana-app-sdk v0.40.3
k8s.io/apimachinery v0.33.3
k8s.io/apiserver v0.33.3
k8s.io/klog/v2 v2.130.1
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff
)
// transitive dependencies that need replaced
// TODO: stop depending on grafana core
replace github.com/grafana/grafana => ../..
replace github.com/prometheus/alertmanager => github.com/grafana/prometheus-alertmanager v0.25.1-0.20250620093340-be61a673dee6
require (
filippo.io/edwards25519 v1.1.0 // indirect
github.com/BurntSushi/toml v1.5.0 // indirect
github.com/VividCortex/mysqlerr v0.0.0-20170204212430-6c6b55f8796f // indirect
github.com/antlr4-go/antlr/v4 v4.13.1 // indirect
github.com/apache/arrow-go/v18 v18.3.0 // indirect
github.com/armon/go-metrics v0.4.1 // indirect
github.com/aws/aws-sdk-go-v2 v1.36.5 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.17.70 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.36 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.36 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.4 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.17 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.34.0 // indirect
github.com/aws/smithy-go v1.22.4 // indirect
github.com/barkimedes/go-deepcopy v0.0.0-20220514131651-17c30cfc62df // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/blang/semver v3.5.1+incompatible // indirect
github.com/blang/semver/v4 v4.0.0 // indirect
github.com/bluele/gcache v0.0.2 // indirect
github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 // indirect
github.com/bwmarrin/snowflake v0.3.0 // indirect
github.com/cenkalti/backoff/v5 v5.0.2 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cheekybits/genny v1.0.0 // indirect
github.com/chromedp/cdproto v0.0.0-20250429231605-6ed5b53462d4 // indirect
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/diegoholiveira/jsonlogic/v3 v3.7.4 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/elazarl/goproxy v1.7.2 // indirect
github.com/emicklei/go-restful/v3 v3.12.1 // indirect
github.com/evanphx/json-patch v5.6.0+incompatible // indirect
github.com/fatih/color v1.18.0 // indirect
github.com/evanphx/json-patch v5.9.11+incompatible // indirect
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
github.com/getkin/kin-openapi v0.132.0 // indirect
github.com/go-jose/go-jose/v3 v3.0.4 // indirect
github.com/go-kit/log v0.2.1 // indirect
github.com/go-logfmt/logfmt v0.6.0 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.21.0 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
github.com/go-sql-driver/mysql v1.9.3 // indirect
github.com/go-stack/stack v1.8.1 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/goccy/go-json v0.10.5 // indirect
github.com/gogo/googleapis v1.4.1 // indirect
github.com/go-test/deep v1.1.1 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-migrate/migrate/v4 v4.7.0 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/btree v1.1.3 // indirect
github.com/google/flatbuffers v25.2.10+incompatible // indirect
github.com/google/gnostic-models v0.6.9 // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/mux v1.8.1 // indirect
github.com/grafana/alerting v0.0.0-20250729175202-b4b881b7b263 // indirect
github.com/grafana/authlib v0.0.0-20250710201142-9542f2f28d43 // indirect
github.com/grafana/authlib/types v0.0.0-20250710201142-9542f2f28d43 // indirect
github.com/grafana/dataplane/sdata v0.0.9 // indirect
github.com/grafana/dskit v0.0.0-20250611075409-46f51e1ce914 // indirect
github.com/grafana/grafana-app-sdk/logging v0.40.1 // indirect
github.com/grafana/grafana-aws-sdk v1.1.0 // indirect
github.com/grafana/grafana-azure-sdk-go/v2 v2.2.0 // indirect
github.com/grafana/grafana-plugin-sdk-go v0.278.0 // indirect
github.com/grafana/grafana/pkg/apiserver v0.0.0-20250804150913-990f1c69ecc2 // indirect
github.com/grafana/otel-profiling-go v0.5.1 // indirect
github.com/grafana/pyroscope-go/godeltaprof v0.1.8 // indirect
github.com/grafana/sqlds/v4 v4.2.4 // indirect
github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0 // indirect
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2 // indirect
github.com/grafana/grafana-app-sdk/logging v0.40.2 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-hclog v1.6.3 // indirect
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
github.com/hashicorp/go-metrics v0.5.4 // indirect
github.com/hashicorp/go-msgpack/v2 v2.1.2 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-plugin v1.6.3 // indirect
github.com/hashicorp/go-sockaddr v1.0.7 // indirect
github.com/hashicorp/golang-lru v1.0.2 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/hashicorp/memberlist v0.5.2 // indirect
github.com/hashicorp/yamux v0.1.1 // indirect
github.com/jaegertracing/jaeger-idl v0.5.0 // indirect
github.com/jmespath-community/go-jmespath v1.1.1 // indirect
github.com/jmoiron/sqlx v1.3.5 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/jpillora/backoff v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/jszwedko/go-datemath v0.1.1-0.20230526204004-640a500621d6 // indirect
github.com/klauspost/compress v1.18.0 // indirect
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
github.com/lib/pq v1.10.9 // indirect
github.com/magefile/mage v1.15.0 // indirect
github.com/mailru/easyjson v0.9.0 // indirect
github.com/mattetti/filebuffer v1.0.1 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/mattn/go-sqlite3 v1.14.22 // indirect
github.com/mdlayher/socket v0.4.1 // indirect
github.com/mdlayher/vsock v1.2.1 // indirect
github.com/miekg/dns v1.1.63 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mithrandie/csvq v1.18.1 // indirect
github.com/mithrandie/csvq-driver v1.7.0 // indirect
github.com/mithrandie/go-file/v2 v2.1.0 // indirect
github.com/mithrandie/go-text v1.6.0 // indirect
github.com/mithrandie/ternary v1.1.1 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/nikunjy/rules v1.5.0 // indirect
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect
github.com/oklog/run v1.1.0 // indirect
github.com/oklog/ulid v1.3.1 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/open-feature/go-sdk v1.14.1 // indirect
github.com/open-feature/go-sdk-contrib/providers/go-feature-flag v0.2.3 // indirect
github.com/open-feature/go-sdk-contrib/providers/ofrep v0.1.5 // indirect
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
github.com/onsi/ginkgo/v2 v2.22.2 // indirect
github.com/onsi/gomega v1.36.2 // indirect
github.com/perimeterx/marshmallow v1.1.5 // indirect
github.com/pierrec/lz4/v4 v4.1.22 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/alertmanager v0.28.0 // indirect
github.com/prometheus/client_golang v1.22.0 // indirect
github.com/prometheus/client_golang v1.23.0 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.65.0 // indirect
github.com/prometheus/exporter-toolkit v0.14.0 // indirect
github.com/prometheus/procfs v0.16.1 // indirect
github.com/puzpuzpuz/xsync/v2 v2.5.1 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/spf13/pflag v1.0.7 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/thomaspoignant/go-feature-flag v1.42.0 // indirect
github.com/tjhop/slog-gokit v0.1.3 // indirect
github.com/unknwon/bra v0.0.0-20200517080246-1e3013ecaff8 // indirect
github.com/unknwon/com v1.0.1 // indirect
github.com/unknwon/log v0.0.0-20200308114134-929b1006e34a // indirect
github.com/urfave/cli v1.22.16 // indirect
github.com/x448/float16 v0.8.4 // indirect
github.com/zeebo/xxh3 v1.0.2 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.61.0 // indirect
go.opentelemetry.io/contrib/propagators/jaeger v1.36.0 // indirect
go.opentelemetry.io/contrib/samplers/jaegerremote v0.30.0 // indirect
go.opentelemetry.io/otel v1.37.0 // indirect
go.opentelemetry.io/otel/exporters/jaeger v1.17.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0 // indirect
go.opentelemetry.io/otel/metric v1.37.0 // indirect
go.opentelemetry.io/otel/sdk v1.37.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.37.0 // indirect
go.opentelemetry.io/otel/trace v1.37.0 // indirect
go.opentelemetry.io/proto/otlp v1.7.0 // indirect
go.uber.org/atomic v1.11.0 // indirect
go.yaml.in/yaml/v2 v2.4.2 // indirect
golang.org/x/crypto v0.40.0 // indirect
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 // indirect
golang.org/x/mod v0.26.0 // indirect
golang.org/x/net v0.42.0 // indirect
golang.org/x/oauth2 v0.30.0 // indirect
golang.org/x/sync v0.16.0 // indirect
@@ -199,30 +72,19 @@ require (
golang.org/x/term v0.33.0 // indirect
golang.org/x/text v0.27.0 // indirect
golang.org/x/time v0.11.0 // indirect
golang.org/x/tools v0.35.0 // indirect
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect
gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect
google.golang.org/grpc v1.74.2 // indirect
google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/fsnotify/fsnotify.v1 v1.4.7 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/api v0.33.3 // indirect
k8s.io/apiextensions-apiserver v0.33.3 // indirect
k8s.io/client-go v0.33.3 // indirect
k8s.io/component-base v0.33.3 // indirect
k8s.io/utils v0.0.0-20241210054802-24370beab758 // indirect
modernc.org/libc v1.65.0 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.10.0 // indirect
modernc.org/sqlite v1.37.0 // indirect
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.7.0 // indirect
sigs.k8s.io/yaml v1.5.0 // indirect
xorm.io/builder v0.3.6 // indirect
)
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1,6 +1,6 @@
module github.com/grafana/grafana/apps/playlist
go 1.24.5
go 1.24.6
require (
github.com/grafana/grafana-app-sdk v0.40.3
+1 -1
View File
@@ -1,3 +1,3 @@
module github.com/grafana/grafana/apps/preferences
go 1.24.5
go 1.24.6
+1 -1
View File
@@ -1,6 +1,6 @@
module github.com/grafana/grafana/apps/provisioning
go 1.24.5
go 1.24.6
require (
github.com/grafana/grafana-app-sdk v0.40.3
@@ -204,6 +204,9 @@ type JobStatus struct {
// Summary of processed actions
Summary []*JobResourceSummary `json:"summary,omitempty"`
// URLs contains URLs for the reference branch or commit if applicable.
URLs *RepositoryURLs `json:"url,omitempty"`
}
// Convert a JOB to a
@@ -235,3 +238,28 @@ type JobResourceSummary struct {
// This may not be an exhaustive list and recommend looking at the logs for more info
Errors []string `json:"errors,omitempty"`
}
// HistoricJob is an append only log, saving all jobs that have been processed.
//
// NOTE: This should not be used directly by any external consumer.
// When there is a more stable integration with loki (an appropriate append only store)
// this may be removed without notice.
//
// The repository name and type are stored as labels.
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +genclient
type HistoricJob struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec JobSpec `json:"spec,omitempty"`
Status JobStatus `json:"status,omitempty"`
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type HistoricJobList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []HistoricJob `json:"items,omitempty"`
}
@@ -87,6 +87,34 @@ var JobResourceInfo = utils.NewResourceInfo(GROUP, VERSION,
},
})
var HistoricJobResourceInfo = utils.NewResourceInfo(GROUP, VERSION,
"historicjobs", "historicjob", "HistoricJob",
func() runtime.Object { return &HistoricJob{} }, // newObj
func() runtime.Object { return &HistoricJobList{} }, // newList
utils.TableColumns{ // Returned by `kubectl get`. Doesn't affect disk storage.
Definition: []metav1.TableColumnDefinition{
{Name: "Name", Type: "string", Format: "name"},
{Name: "Created At", Type: "date"},
{Name: "Action", Type: "string"},
{Name: "State", Type: "string"},
{Name: "Message", Type: "string"},
},
Reader: func(obj any) ([]interface{}, error) {
m, ok := obj.(*HistoricJob)
if !ok {
return nil, errors.New("expected HistoricJob")
}
return []interface{}{
m.Name, // may our may not be nice to read
m.CreationTimestamp.UTC().Format(time.RFC3339),
m.Spec.Action,
m.Status.State,
m.Status.Message,
}, nil
},
})
var (
// SchemeGroupVersion is group version used to register these objects
SchemeGroupVersion = schema.GroupVersion{Group: GROUP, Version: VERSION}
@@ -124,6 +152,8 @@ func AddKnownTypes(gv schema.GroupVersion, scheme *runtime.Scheme) error {
&Job{},
&JobList{},
&RefList{},
&HistoricJob{},
&HistoricJobList{},
)
return nil
}
@@ -316,7 +316,7 @@ type ResourceWrapper struct {
Repository ResourceRepositoryInfo `json:"repository"`
// Typed links for this file (only supported by external systems, github etc)
URLs *ResourceURLs `json:"urls,omitempty"`
URLs *RepositoryURLs `json:"urls,omitempty"`
// The modified time in the remote file system
Timestamp *metav1.Time `json:"timestamp,omitempty"`
@@ -374,14 +374,14 @@ type ResourceRepositoryInfo struct {
Name string `json:"name"`
}
type ResourceURLs struct {
// A URL pointing to the this file in the repository
SourceURL string `json:"sourceURL,omitempty"`
type RepositoryURLs struct {
// A URL pointing to the repository this lives in
RepositoryURL string `json:"repositoryURL,omitempty"`
// A URL that will create a new pull requeset for this branch
// A URL pointing to the file or ref in the repository
SourceURL string `json:"sourceURL,omitempty"`
// A URL that will create a new pull request for this branch
NewPullRequestURL string `json:"newPullRequestURL,omitempty"`
// Compare this version to the target branch
@@ -439,6 +439,11 @@ type ResourceStats struct {
// +listType=atomic
Instance []ResourceCount `json:"instance,omitempty"`
// Stats across all unified storage
// When legacy storage is still used, this will offer a shim
// +listType=atomic
Unmanaged []ResourceCount `json:"unmanaged,omitempty"`
// Stats for each manager
// +listType=atomic
Managed []ManagerStats `json:"managed,omitempty"`
@@ -237,6 +237,67 @@ func (in *HealthStatus) DeepCopy() *HealthStatus {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HistoricJob) DeepCopyInto(out *HistoricJob) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
in.Status.DeepCopyInto(&out.Status)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HistoricJob.
func (in *HistoricJob) DeepCopy() *HistoricJob {
if in == nil {
return nil
}
out := new(HistoricJob)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *HistoricJob) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HistoricJobList) DeepCopyInto(out *HistoricJobList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]HistoricJob, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HistoricJobList.
func (in *HistoricJobList) DeepCopy() *HistoricJobList {
if in == nil {
return nil
}
out := new(HistoricJobList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *HistoricJobList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HistoryItem) DeepCopyInto(out *HistoryItem) {
*out = *in
@@ -438,6 +499,11 @@ func (in *JobStatus) DeepCopyInto(out *JobStatus) {
}
}
}
if in.URLs != nil {
in, out := &in.URLs, &out.URLs
*out = new(RepositoryURLs)
**out = **in
}
return
}
@@ -729,6 +795,22 @@ func (in *RepositoryStatus) DeepCopy() *RepositoryStatus {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *RepositoryURLs) DeepCopyInto(out *RepositoryURLs) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RepositoryURLs.
func (in *RepositoryURLs) DeepCopy() *RepositoryURLs {
if in == nil {
return nil
}
out := new(RepositoryURLs)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *RepositoryView) DeepCopyInto(out *RepositoryView) {
*out = *in
@@ -913,6 +995,11 @@ func (in *ResourceStats) DeepCopyInto(out *ResourceStats) {
*out = make([]ResourceCount, len(*in))
copy(*out, *in)
}
if in.Unmanaged != nil {
in, out := &in.Unmanaged, &out.Unmanaged
*out = make([]ResourceCount, len(*in))
copy(*out, *in)
}
if in.Managed != nil {
in, out := &in.Managed, &out.Managed
*out = make([]ManagerStats, len(*in))
@@ -957,22 +1044,6 @@ func (in *ResourceType) DeepCopy() *ResourceType {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ResourceURLs) DeepCopyInto(out *ResourceURLs) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceURLs.
func (in *ResourceURLs) DeepCopy() *ResourceURLs {
if in == nil {
return nil
}
out := new(ResourceURLs)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ResourceWrapper) DeepCopyInto(out *ResourceWrapper) {
*out = *in
@@ -980,7 +1051,7 @@ func (in *ResourceWrapper) DeepCopyInto(out *ResourceWrapper) {
out.Repository = in.Repository
if in.URLs != nil {
in, out := &in.URLs, &out.URLs
*out = new(ResourceURLs)
*out = new(RepositoryURLs)
**out = **in
}
if in.Timestamp != nil {
@@ -25,6 +25,8 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA
"github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.GitLabRepositoryConfig": schema_pkg_apis_provisioning_v0alpha1_GitLabRepositoryConfig(ref),
"github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.GitRepositoryConfig": schema_pkg_apis_provisioning_v0alpha1_GitRepositoryConfig(ref),
"github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.HealthStatus": schema_pkg_apis_provisioning_v0alpha1_HealthStatus(ref),
"github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.HistoricJob": schema_pkg_apis_provisioning_v0alpha1_HistoricJob(ref),
"github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.HistoricJobList": schema_pkg_apis_provisioning_v0alpha1_HistoricJobList(ref),
"github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.HistoryItem": schema_pkg_apis_provisioning_v0alpha1_HistoryItem(ref),
"github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.HistoryList": schema_pkg_apis_provisioning_v0alpha1_HistoryList(ref),
"github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.Job": schema_pkg_apis_provisioning_v0alpha1_Job(ref),
@@ -43,6 +45,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA
"github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.RepositoryList": schema_pkg_apis_provisioning_v0alpha1_RepositoryList(ref),
"github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.RepositorySpec": schema_pkg_apis_provisioning_v0alpha1_RepositorySpec(ref),
"github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.RepositoryStatus": schema_pkg_apis_provisioning_v0alpha1_RepositoryStatus(ref),
"github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.RepositoryURLs": schema_pkg_apis_provisioning_v0alpha1_RepositoryURLs(ref),
"github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.RepositoryView": schema_pkg_apis_provisioning_v0alpha1_RepositoryView(ref),
"github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.RepositoryViewList": schema_pkg_apis_provisioning_v0alpha1_RepositoryViewList(ref),
"github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ResourceCount": schema_pkg_apis_provisioning_v0alpha1_ResourceCount(ref),
@@ -53,7 +56,6 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA
"github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ResourceRepositoryInfo": schema_pkg_apis_provisioning_v0alpha1_ResourceRepositoryInfo(ref),
"github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ResourceStats": schema_pkg_apis_provisioning_v0alpha1_ResourceStats(ref),
"github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ResourceType": schema_pkg_apis_provisioning_v0alpha1_ResourceType(ref),
"github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ResourceURLs": schema_pkg_apis_provisioning_v0alpha1_ResourceURLs(ref),
"github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ResourceWrapper": schema_pkg_apis_provisioning_v0alpha1_ResourceWrapper(ref),
"github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.SyncJobOptions": schema_pkg_apis_provisioning_v0alpha1_SyncJobOptions(ref),
"github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.SyncOptions": schema_pkg_apis_provisioning_v0alpha1_SyncOptions(ref),
@@ -602,6 +604,100 @@ func schema_pkg_apis_provisioning_v0alpha1_HealthStatus(ref common.ReferenceCall
}
}
func schema_pkg_apis_provisioning_v0alpha1_HistoricJob(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Description: "HistoricJob is an append only log, saving all jobs that have been processed.\n\nNOTE: This should not be used directly by any external consumer. When there is a more stable integration with loki (an appropriate append only store) this may be removed without notice.\n\nThe repository name and type are stored as labels.",
Type: []string{"object"},
Properties: map[string]spec.Schema{
"kind": {
SchemaProps: spec.SchemaProps{
Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds",
Type: []string{"string"},
Format: "",
},
},
"apiVersion": {
SchemaProps: spec.SchemaProps{
Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources",
Type: []string{"string"},
Format: "",
},
},
"metadata": {
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"),
},
},
"spec": {
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.JobSpec"),
},
},
"status": {
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.JobStatus"),
},
},
},
},
},
Dependencies: []string{
"github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.JobSpec", "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.JobStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"},
}
}
func schema_pkg_apis_provisioning_v0alpha1_HistoricJobList(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"kind": {
SchemaProps: spec.SchemaProps{
Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds",
Type: []string{"string"},
Format: "",
},
},
"apiVersion": {
SchemaProps: spec.SchemaProps{
Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources",
Type: []string{"string"},
Format: "",
},
},
"metadata": {
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"),
},
},
"items": {
SchemaProps: spec.SchemaProps{
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.HistoricJob"),
},
},
},
},
},
},
},
},
Dependencies: []string{
"github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.HistoricJob", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"},
}
}
func schema_pkg_apis_provisioning_v0alpha1_HistoryItem(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
@@ -1020,11 +1116,17 @@ func schema_pkg_apis_provisioning_v0alpha1_JobStatus(ref common.ReferenceCallbac
},
},
},
"url": {
SchemaProps: spec.SchemaProps{
Description: "URLs contains URLs for the reference branch or commit if applicable.",
Ref: ref("github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.RepositoryURLs"),
},
},
},
},
},
Dependencies: []string{
"github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.JobResourceSummary"},
"github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.JobResourceSummary", "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.RepositoryURLs"},
}
}
@@ -1555,6 +1657,46 @@ func schema_pkg_apis_provisioning_v0alpha1_RepositoryStatus(ref common.Reference
}
}
func schema_pkg_apis_provisioning_v0alpha1_RepositoryURLs(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"repositoryURL": {
SchemaProps: spec.SchemaProps{
Description: "A URL pointing to the repository this lives in",
Type: []string{"string"},
Format: "",
},
},
"sourceURL": {
SchemaProps: spec.SchemaProps{
Description: "A URL pointing to the file or ref in the repository",
Type: []string{"string"},
Format: "",
},
},
"newPullRequestURL": {
SchemaProps: spec.SchemaProps{
Description: "A URL that will create a new pull request for this branch",
Type: []string{"string"},
Format: "",
},
},
"compareURL": {
SchemaProps: spec.SchemaProps{
Description: "Compare this version to the target branch",
Type: []string{"string"},
Format: "",
},
},
},
},
},
}
}
func schema_pkg_apis_provisioning_v0alpha1_RepositoryView(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
@@ -2030,6 +2172,25 @@ func schema_pkg_apis_provisioning_v0alpha1_ResourceStats(ref common.ReferenceCal
},
},
},
"unmanaged": {
VendorExtensible: spec.VendorExtensible{
Extensions: spec.Extensions{
"x-kubernetes-list-type": "atomic",
},
},
SchemaProps: spec.SchemaProps{
Description: "Stats across all unified storage When legacy storage is still used, this will offer a shim",
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ResourceCount"),
},
},
},
},
},
"managed": {
VendorExtensible: spec.VendorExtensible{
Extensions: spec.Extensions{
@@ -2101,46 +2262,6 @@ func schema_pkg_apis_provisioning_v0alpha1_ResourceType(ref common.ReferenceCall
}
}
func schema_pkg_apis_provisioning_v0alpha1_ResourceURLs(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"sourceURL": {
SchemaProps: spec.SchemaProps{
Description: "A URL pointing to the this file in the repository",
Type: []string{"string"},
Format: "",
},
},
"repositoryURL": {
SchemaProps: spec.SchemaProps{
Description: "A URL pointing to the repository this lives in",
Type: []string{"string"},
Format: "",
},
},
"newPullRequestURL": {
SchemaProps: spec.SchemaProps{
Description: "A URL that will create a new pull requeset for this branch",
Type: []string{"string"},
Format: "",
},
},
"compareURL": {
SchemaProps: spec.SchemaProps{
Description: "Compare this version to the target branch",
Type: []string{"string"},
Format: "",
},
},
},
},
},
}
}
func schema_pkg_apis_provisioning_v0alpha1_ResourceWrapper(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
@@ -2193,7 +2314,7 @@ func schema_pkg_apis_provisioning_v0alpha1_ResourceWrapper(ref common.ReferenceC
"urls": {
SchemaProps: spec.SchemaProps{
Description: "Typed links for this file (only supported by external systems, github etc)",
Ref: ref("github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ResourceURLs"),
Ref: ref("github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.RepositoryURLs"),
},
},
"timestamp": {
@@ -2234,7 +2355,7 @@ func schema_pkg_apis_provisioning_v0alpha1_ResourceWrapper(ref common.ReferenceC
},
},
Dependencies: []string{
"github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ResourceObjects", "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ResourceRepositoryInfo", "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ResourceURLs", "k8s.io/apimachinery/pkg/apis/meta/v1.Time"},
"github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.RepositoryURLs", "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ResourceObjects", "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ResourceRepositoryInfo", "k8s.io/apimachinery/pkg/apis/meta/v1.Time"},
}
}
@@ -18,9 +18,11 @@ API rule violation: list_type_missing,github.com/grafana/grafana/apps/provisioni
API rule violation: list_type_missing,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,TestResults,Errors
API rule violation: list_type_missing,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,WebhookStatus,SubscribedEvents
API rule violation: names_match,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,JobSpec,PullRequest
API rule violation: names_match,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,JobStatus,URLs
API rule violation: names_match,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,ManagerStats,Identity
API rule violation: names_match,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,RepositorySpec,GitHub
API rule violation: names_match,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,RepositorySpec,GitLab
API rule violation: names_match,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,ResourceWrapper,URLs
API rule violation: names_match,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,SyncStatus,JobID
API rule violation: names_match,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,WebhookResponse,Message
API rule violation: streaming_list_type_json_tags,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,HistoricJobList,Items
@@ -0,0 +1,211 @@
// SPDX-License-Identifier: AGPL-3.0-only
// Code generated by applyconfiguration-gen. DO NOT EDIT.
package v0alpha1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
types "k8s.io/apimachinery/pkg/types"
v1 "k8s.io/client-go/applyconfigurations/meta/v1"
)
// HistoricJobApplyConfiguration represents a declarative configuration of the HistoricJob type for use
// with apply.
type HistoricJobApplyConfiguration struct {
v1.TypeMetaApplyConfiguration `json:",inline"`
*v1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"`
Spec *JobSpecApplyConfiguration `json:"spec,omitempty"`
Status *JobStatusApplyConfiguration `json:"status,omitempty"`
}
// HistoricJob constructs a declarative configuration of the HistoricJob type for use with
// apply.
func HistoricJob(name, namespace string) *HistoricJobApplyConfiguration {
b := &HistoricJobApplyConfiguration{}
b.WithName(name)
b.WithNamespace(namespace)
b.WithKind("HistoricJob")
b.WithAPIVersion("provisioning.grafana.app/v0alpha1")
return b
}
// WithKind sets the Kind field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the Kind field is set to the value of the last call.
func (b *HistoricJobApplyConfiguration) WithKind(value string) *HistoricJobApplyConfiguration {
b.TypeMetaApplyConfiguration.Kind = &value
return b
}
// WithAPIVersion sets the APIVersion field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the APIVersion field is set to the value of the last call.
func (b *HistoricJobApplyConfiguration) WithAPIVersion(value string) *HistoricJobApplyConfiguration {
b.TypeMetaApplyConfiguration.APIVersion = &value
return b
}
// WithName sets the Name field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the Name field is set to the value of the last call.
func (b *HistoricJobApplyConfiguration) WithName(value string) *HistoricJobApplyConfiguration {
b.ensureObjectMetaApplyConfigurationExists()
b.ObjectMetaApplyConfiguration.Name = &value
return b
}
// WithGenerateName sets the GenerateName field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the GenerateName field is set to the value of the last call.
func (b *HistoricJobApplyConfiguration) WithGenerateName(value string) *HistoricJobApplyConfiguration {
b.ensureObjectMetaApplyConfigurationExists()
b.ObjectMetaApplyConfiguration.GenerateName = &value
return b
}
// WithNamespace sets the Namespace field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the Namespace field is set to the value of the last call.
func (b *HistoricJobApplyConfiguration) WithNamespace(value string) *HistoricJobApplyConfiguration {
b.ensureObjectMetaApplyConfigurationExists()
b.ObjectMetaApplyConfiguration.Namespace = &value
return b
}
// WithUID sets the UID field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the UID field is set to the value of the last call.
func (b *HistoricJobApplyConfiguration) WithUID(value types.UID) *HistoricJobApplyConfiguration {
b.ensureObjectMetaApplyConfigurationExists()
b.ObjectMetaApplyConfiguration.UID = &value
return b
}
// WithResourceVersion sets the ResourceVersion field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the ResourceVersion field is set to the value of the last call.
func (b *HistoricJobApplyConfiguration) WithResourceVersion(value string) *HistoricJobApplyConfiguration {
b.ensureObjectMetaApplyConfigurationExists()
b.ObjectMetaApplyConfiguration.ResourceVersion = &value
return b
}
// WithGeneration sets the Generation field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the Generation field is set to the value of the last call.
func (b *HistoricJobApplyConfiguration) WithGeneration(value int64) *HistoricJobApplyConfiguration {
b.ensureObjectMetaApplyConfigurationExists()
b.ObjectMetaApplyConfiguration.Generation = &value
return b
}
// WithCreationTimestamp sets the CreationTimestamp field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the CreationTimestamp field is set to the value of the last call.
func (b *HistoricJobApplyConfiguration) WithCreationTimestamp(value metav1.Time) *HistoricJobApplyConfiguration {
b.ensureObjectMetaApplyConfigurationExists()
b.ObjectMetaApplyConfiguration.CreationTimestamp = &value
return b
}
// WithDeletionTimestamp sets the DeletionTimestamp field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the DeletionTimestamp field is set to the value of the last call.
func (b *HistoricJobApplyConfiguration) WithDeletionTimestamp(value metav1.Time) *HistoricJobApplyConfiguration {
b.ensureObjectMetaApplyConfigurationExists()
b.ObjectMetaApplyConfiguration.DeletionTimestamp = &value
return b
}
// WithDeletionGracePeriodSeconds sets the DeletionGracePeriodSeconds field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the DeletionGracePeriodSeconds field is set to the value of the last call.
func (b *HistoricJobApplyConfiguration) WithDeletionGracePeriodSeconds(value int64) *HistoricJobApplyConfiguration {
b.ensureObjectMetaApplyConfigurationExists()
b.ObjectMetaApplyConfiguration.DeletionGracePeriodSeconds = &value
return b
}
// WithLabels puts the entries into the Labels field in the declarative configuration
// and returns the receiver, so that objects can be build by chaining "With" function invocations.
// If called multiple times, the entries provided by each call will be put on the Labels field,
// overwriting an existing map entries in Labels field with the same key.
func (b *HistoricJobApplyConfiguration) WithLabels(entries map[string]string) *HistoricJobApplyConfiguration {
b.ensureObjectMetaApplyConfigurationExists()
if b.ObjectMetaApplyConfiguration.Labels == nil && len(entries) > 0 {
b.ObjectMetaApplyConfiguration.Labels = make(map[string]string, len(entries))
}
for k, v := range entries {
b.ObjectMetaApplyConfiguration.Labels[k] = v
}
return b
}
// WithAnnotations puts the entries into the Annotations field in the declarative configuration
// and returns the receiver, so that objects can be build by chaining "With" function invocations.
// If called multiple times, the entries provided by each call will be put on the Annotations field,
// overwriting an existing map entries in Annotations field with the same key.
func (b *HistoricJobApplyConfiguration) WithAnnotations(entries map[string]string) *HistoricJobApplyConfiguration {
b.ensureObjectMetaApplyConfigurationExists()
if b.ObjectMetaApplyConfiguration.Annotations == nil && len(entries) > 0 {
b.ObjectMetaApplyConfiguration.Annotations = make(map[string]string, len(entries))
}
for k, v := range entries {
b.ObjectMetaApplyConfiguration.Annotations[k] = v
}
return b
}
// WithOwnerReferences adds the given value to the OwnerReferences field in the declarative configuration
// and returns the receiver, so that objects can be build by chaining "With" function invocations.
// If called multiple times, values provided by each call will be appended to the OwnerReferences field.
func (b *HistoricJobApplyConfiguration) WithOwnerReferences(values ...*v1.OwnerReferenceApplyConfiguration) *HistoricJobApplyConfiguration {
b.ensureObjectMetaApplyConfigurationExists()
for i := range values {
if values[i] == nil {
panic("nil value passed to WithOwnerReferences")
}
b.ObjectMetaApplyConfiguration.OwnerReferences = append(b.ObjectMetaApplyConfiguration.OwnerReferences, *values[i])
}
return b
}
// WithFinalizers adds the given value to the Finalizers field in the declarative configuration
// and returns the receiver, so that objects can be build by chaining "With" function invocations.
// If called multiple times, values provided by each call will be appended to the Finalizers field.
func (b *HistoricJobApplyConfiguration) WithFinalizers(values ...string) *HistoricJobApplyConfiguration {
b.ensureObjectMetaApplyConfigurationExists()
for i := range values {
b.ObjectMetaApplyConfiguration.Finalizers = append(b.ObjectMetaApplyConfiguration.Finalizers, values[i])
}
return b
}
func (b *HistoricJobApplyConfiguration) ensureObjectMetaApplyConfigurationExists() {
if b.ObjectMetaApplyConfiguration == nil {
b.ObjectMetaApplyConfiguration = &v1.ObjectMetaApplyConfiguration{}
}
}
// WithSpec sets the Spec field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the Spec field is set to the value of the last call.
func (b *HistoricJobApplyConfiguration) WithSpec(value *JobSpecApplyConfiguration) *HistoricJobApplyConfiguration {
b.Spec = value
return b
}
// WithStatus sets the Status field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the Status field is set to the value of the last call.
func (b *HistoricJobApplyConfiguration) WithStatus(value *JobStatusApplyConfiguration) *HistoricJobApplyConfiguration {
b.Status = value
return b
}
// GetName retrieves the value of the Name field in the declarative configuration.
func (b *HistoricJobApplyConfiguration) GetName() *string {
b.ensureObjectMetaApplyConfigurationExists()
return b.ObjectMetaApplyConfiguration.Name
}
@@ -18,6 +18,7 @@ type JobStatusApplyConfiguration struct {
Errors []string `json:"errors,omitempty"`
Progress *float64 `json:"progress,omitempty"`
Summary []*provisioningv0alpha1.JobResourceSummary `json:"summary,omitempty"`
URLs *RepositoryURLsApplyConfiguration `json:"url,omitempty"`
}
// JobStatusApplyConfiguration constructs a declarative configuration of the JobStatus type for use with
@@ -88,3 +89,11 @@ func (b *JobStatusApplyConfiguration) WithSummary(values ...**provisioningv0alph
}
return b
}
// WithURLs sets the URLs field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the URLs field is set to the value of the last call.
func (b *JobStatusApplyConfiguration) WithURLs(value *RepositoryURLsApplyConfiguration) *JobStatusApplyConfiguration {
b.URLs = value
return b
}
@@ -0,0 +1,52 @@
// SPDX-License-Identifier: AGPL-3.0-only
// Code generated by applyconfiguration-gen. DO NOT EDIT.
package v0alpha1
// RepositoryURLsApplyConfiguration represents a declarative configuration of the RepositoryURLs type for use
// with apply.
type RepositoryURLsApplyConfiguration struct {
RepositoryURL *string `json:"repositoryURL,omitempty"`
SourceURL *string `json:"sourceURL,omitempty"`
NewPullRequestURL *string `json:"newPullRequestURL,omitempty"`
CompareURL *string `json:"compareURL,omitempty"`
}
// RepositoryURLsApplyConfiguration constructs a declarative configuration of the RepositoryURLs type for use with
// apply.
func RepositoryURLs() *RepositoryURLsApplyConfiguration {
return &RepositoryURLsApplyConfiguration{}
}
// WithRepositoryURL sets the RepositoryURL field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the RepositoryURL field is set to the value of the last call.
func (b *RepositoryURLsApplyConfiguration) WithRepositoryURL(value string) *RepositoryURLsApplyConfiguration {
b.RepositoryURL = &value
return b
}
// WithSourceURL sets the SourceURL field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the SourceURL field is set to the value of the last call.
func (b *RepositoryURLsApplyConfiguration) WithSourceURL(value string) *RepositoryURLsApplyConfiguration {
b.SourceURL = &value
return b
}
// WithNewPullRequestURL sets the NewPullRequestURL field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the NewPullRequestURL field is set to the value of the last call.
func (b *RepositoryURLsApplyConfiguration) WithNewPullRequestURL(value string) *RepositoryURLsApplyConfiguration {
b.NewPullRequestURL = &value
return b
}
// WithCompareURL sets the CompareURL field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the CompareURL field is set to the value of the last call.
func (b *RepositoryURLsApplyConfiguration) WithCompareURL(value string) *RepositoryURLsApplyConfiguration {
b.CompareURL = &value
return b
}
@@ -32,6 +32,8 @@ func ForKind(kind schema.GroupVersionKind) interface{} {
return &provisioningv0alpha1.GitRepositoryConfigApplyConfiguration{}
case v0alpha1.SchemeGroupVersion.WithKind("HealthStatus"):
return &provisioningv0alpha1.HealthStatusApplyConfiguration{}
case v0alpha1.SchemeGroupVersion.WithKind("HistoricJob"):
return &provisioningv0alpha1.HistoricJobApplyConfiguration{}
case v0alpha1.SchemeGroupVersion.WithKind("Job"):
return &provisioningv0alpha1.JobApplyConfiguration{}
case v0alpha1.SchemeGroupVersion.WithKind("JobResourceSummary"):
@@ -54,6 +56,8 @@ func ForKind(kind schema.GroupVersionKind) interface{} {
return &provisioningv0alpha1.RepositorySpecApplyConfiguration{}
case v0alpha1.SchemeGroupVersion.WithKind("RepositoryStatus"):
return &provisioningv0alpha1.RepositoryStatusApplyConfiguration{}
case v0alpha1.SchemeGroupVersion.WithKind("RepositoryURLs"):
return &provisioningv0alpha1.RepositoryURLsApplyConfiguration{}
case v0alpha1.SchemeGroupVersion.WithKind("ResourceCount"):
return &provisioningv0alpha1.ResourceCountApplyConfiguration{}
case v0alpha1.SchemeGroupVersion.WithKind("ResourceRef"):
@@ -0,0 +1,39 @@
// SPDX-License-Identifier: AGPL-3.0-only
// Code generated by client-gen. DO NOT EDIT.
package fake
import (
v0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
provisioningv0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1"
typedprovisioningv0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1"
gentype "k8s.io/client-go/gentype"
)
// fakeHistoricJobs implements HistoricJobInterface
type fakeHistoricJobs struct {
*gentype.FakeClientWithListAndApply[*v0alpha1.HistoricJob, *v0alpha1.HistoricJobList, *provisioningv0alpha1.HistoricJobApplyConfiguration]
Fake *FakeProvisioningV0alpha1
}
func newFakeHistoricJobs(fake *FakeProvisioningV0alpha1, namespace string) typedprovisioningv0alpha1.HistoricJobInterface {
return &fakeHistoricJobs{
gentype.NewFakeClientWithListAndApply[*v0alpha1.HistoricJob, *v0alpha1.HistoricJobList, *provisioningv0alpha1.HistoricJobApplyConfiguration](
fake.Fake,
namespace,
v0alpha1.SchemeGroupVersion.WithResource("historicjobs"),
v0alpha1.SchemeGroupVersion.WithKind("HistoricJob"),
func() *v0alpha1.HistoricJob { return &v0alpha1.HistoricJob{} },
func() *v0alpha1.HistoricJobList { return &v0alpha1.HistoricJobList{} },
func(dst, src *v0alpha1.HistoricJobList) { dst.ListMeta = src.ListMeta },
func(list *v0alpha1.HistoricJobList) []*v0alpha1.HistoricJob {
return gentype.ToPointerSlice(list.Items)
},
func(list *v0alpha1.HistoricJobList, items []*v0alpha1.HistoricJob) {
list.Items = gentype.FromPointerSlice(items)
},
),
fake,
}
}
@@ -14,6 +14,10 @@ type FakeProvisioningV0alpha1 struct {
*testing.Fake
}
func (c *FakeProvisioningV0alpha1) HistoricJobs(namespace string) v0alpha1.HistoricJobInterface {
return newFakeHistoricJobs(c, namespace)
}
func (c *FakeProvisioningV0alpha1) Jobs(namespace string) v0alpha1.JobInterface {
return newFakeJobs(c, namespace)
}
@@ -4,6 +4,8 @@
package v0alpha1
type HistoricJobExpansion interface{}
type JobExpansion interface{}
type RepositoryExpansion interface{}
@@ -0,0 +1,60 @@
// SPDX-License-Identifier: AGPL-3.0-only
// Code generated by client-gen. DO NOT EDIT.
package v0alpha1
import (
context "context"
provisioningv0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
applyconfigurationprovisioningv0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1"
scheme "github.com/grafana/grafana/apps/provisioning/pkg/generated/clientset/versioned/scheme"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
types "k8s.io/apimachinery/pkg/types"
watch "k8s.io/apimachinery/pkg/watch"
gentype "k8s.io/client-go/gentype"
)
// HistoricJobsGetter has a method to return a HistoricJobInterface.
// A group's client should implement this interface.
type HistoricJobsGetter interface {
HistoricJobs(namespace string) HistoricJobInterface
}
// HistoricJobInterface has methods to work with HistoricJob resources.
type HistoricJobInterface interface {
Create(ctx context.Context, historicJob *provisioningv0alpha1.HistoricJob, opts v1.CreateOptions) (*provisioningv0alpha1.HistoricJob, error)
Update(ctx context.Context, historicJob *provisioningv0alpha1.HistoricJob, opts v1.UpdateOptions) (*provisioningv0alpha1.HistoricJob, error)
// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
UpdateStatus(ctx context.Context, historicJob *provisioningv0alpha1.HistoricJob, opts v1.UpdateOptions) (*provisioningv0alpha1.HistoricJob, error)
Delete(ctx context.Context, name string, opts v1.DeleteOptions) error
DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error
Get(ctx context.Context, name string, opts v1.GetOptions) (*provisioningv0alpha1.HistoricJob, error)
List(ctx context.Context, opts v1.ListOptions) (*provisioningv0alpha1.HistoricJobList, error)
Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error)
Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *provisioningv0alpha1.HistoricJob, err error)
Apply(ctx context.Context, historicJob *applyconfigurationprovisioningv0alpha1.HistoricJobApplyConfiguration, opts v1.ApplyOptions) (result *provisioningv0alpha1.HistoricJob, err error)
// Add a +genclient:noStatus comment above the type to avoid generating ApplyStatus().
ApplyStatus(ctx context.Context, historicJob *applyconfigurationprovisioningv0alpha1.HistoricJobApplyConfiguration, opts v1.ApplyOptions) (result *provisioningv0alpha1.HistoricJob, err error)
HistoricJobExpansion
}
// historicJobs implements HistoricJobInterface
type historicJobs struct {
*gentype.ClientWithListAndApply[*provisioningv0alpha1.HistoricJob, *provisioningv0alpha1.HistoricJobList, *applyconfigurationprovisioningv0alpha1.HistoricJobApplyConfiguration]
}
// newHistoricJobs returns a HistoricJobs
func newHistoricJobs(c *ProvisioningV0alpha1Client, namespace string) *historicJobs {
return &historicJobs{
gentype.NewClientWithListAndApply[*provisioningv0alpha1.HistoricJob, *provisioningv0alpha1.HistoricJobList, *applyconfigurationprovisioningv0alpha1.HistoricJobApplyConfiguration](
"historicjobs",
c.RESTClient(),
scheme.ParameterCodec,
namespace,
func() *provisioningv0alpha1.HistoricJob { return &provisioningv0alpha1.HistoricJob{} },
func() *provisioningv0alpha1.HistoricJobList { return &provisioningv0alpha1.HistoricJobList{} },
),
}
}
@@ -14,6 +14,7 @@ import (
type ProvisioningV0alpha1Interface interface {
RESTClient() rest.Interface
HistoricJobsGetter
JobsGetter
RepositoriesGetter
}
@@ -23,6 +24,10 @@ type ProvisioningV0alpha1Client struct {
restClient rest.Interface
}
func (c *ProvisioningV0alpha1Client) HistoricJobs(namespace string) HistoricJobInterface {
return newHistoricJobs(c, namespace)
}
func (c *ProvisioningV0alpha1Client) Jobs(namespace string) JobInterface {
return newJobs(c, namespace)
}
@@ -39,6 +39,8 @@ func (f *genericInformer) Lister() cache.GenericLister {
func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource) (GenericInformer, error) {
switch resource {
// Group=provisioning.grafana.app, Version=v0alpha1
case v0alpha1.SchemeGroupVersion.WithResource("historicjobs"):
return &genericInformer{resource: resource.GroupResource(), informer: f.Provisioning().V0alpha1().HistoricJobs().Informer()}, nil
case v0alpha1.SchemeGroupVersion.WithResource("jobs"):
return &genericInformer{resource: resource.GroupResource(), informer: f.Provisioning().V0alpha1().Jobs().Informer()}, nil
case v0alpha1.SchemeGroupVersion.WithResource("repositories"):

Some files were not shown because too many files have changed in this diff Show More