Merge remote-tracking branch 'origin/main' into ds-apiserver-with-configs
This commit is contained in:
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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'
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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"
|
||||
@@ -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.
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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,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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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¶m2=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×tamp=__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"
|
||||
}
|
||||
+85
@@ -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" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
+129
@@ -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
|
||||
}
|
||||
+166
@@ -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"
|
||||
}
|
||||
+188
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
)
|
||||
|
||||
+10
-814
File diff suppressed because it is too large
Load Diff
@@ -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,3 +1,3 @@
|
||||
module github.com/grafana/grafana/apps/preferences
|
||||
|
||||
go 1.24.5
|
||||
go 1.24.6
|
||||
|
||||
@@ -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"},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
@@ -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
|
||||
|
||||
+211
@@ -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
|
||||
}
|
||||
|
||||
+52
@@ -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"):
|
||||
|
||||
+39
@@ -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,
|
||||
}
|
||||
}
|
||||
+4
@@ -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)
|
||||
}
|
||||
|
||||
+2
@@ -4,6 +4,8 @@
|
||||
|
||||
package v0alpha1
|
||||
|
||||
type HistoricJobExpansion interface{}
|
||||
|
||||
type JobExpansion interface{}
|
||||
|
||||
type RepositoryExpansion interface{}
|
||||
|
||||
+60
@@ -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{} },
|
||||
),
|
||||
}
|
||||
}
|
||||
+5
@@ -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
Reference in New Issue
Block a user